Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate warp precompile #391

Merged
merged 30 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c30bee7
Migrate warp backend from Subnet-EVM
aaronbuchwald Nov 13, 2023
1a36626
warp/handlers: update duration greater than assertion to pass on windows
aaronbuchwald Nov 13, 2023
c38a9ef
Update precompile setup for warp
aaronbuchwald Nov 13, 2023
2ddaa54
Pass predicate context through block build / verify
aaronbuchwald Nov 14, 2023
9a6aa1d
Fix up dynamic fee extra data window handling post D-Upgrade
aaronbuchwald Nov 14, 2023
c04403a
Migrate x/warp package and start migrating tests
aaronbuchwald Nov 14, 2023
90013aa
Fix most of the tests
aaronbuchwald Nov 14, 2023
5f0218f
Copy chain configs in vm initialize to fix test
aaronbuchwald Nov 14, 2023
17909e8
Remove duration stats test from warp network handler
aaronbuchwald Nov 14, 2023
ad5959e
Merge branch 'warp-backend' into warp-precompile-setup
aaronbuchwald Nov 14, 2023
73bd5f4
Fix trailing newline
aaronbuchwald Nov 14, 2023
69864a9
Merge branch 'warp-precompile-setup' into migrate-warp-precompile
aaronbuchwald Nov 14, 2023
c557b7f
Move aggregateSignatures log to add more detail
aaronbuchwald Nov 16, 2023
1636af4
Add subnetIDStr option to warp p2p API server/client
aaronbuchwald Nov 16, 2023
2daa47b
Merge branch 'master' into warp-backend
aaronbuchwald Nov 17, 2023
1f73db7
Update signature aggregator constructor comment
aaronbuchwald Nov 17, 2023
71ccb87
Add back CanTransferMC and TransferMultiCoin
aaronbuchwald Nov 17, 2023
e6f8ca2
Remove duplicate from reserved address space
aaronbuchwald Nov 17, 2023
65d3368
remove duplicate check in different package
aaronbuchwald Nov 17, 2023
533a0af
Remove js related code from contracts/
aaronbuchwald Nov 17, 2023
dea508e
Update README to apply to both Coreth and Subnet-EVM
aaronbuchwald Nov 17, 2023
9711885
Update warp/aggregator/aggregator.go
aaronbuchwald Nov 27, 2023
7c0a0c2
Merge branch 'master' into warp-backend
aaronbuchwald Nov 28, 2023
3853d48
core/vm: move native asset contracts to separate file
aaronbuchwald Nov 28, 2023
676bcac
Remove unused istanbul field from txpool
aaronbuchwald Nov 28, 2023
d1bb80b
Merge branch 'warp-backend' into warp-precompile-setup
aaronbuchwald Nov 28, 2023
8b12e57
Merge branch 'warp-precompile-setup' into migrate-warp-precompile
aaronbuchwald Nov 28, 2023
5ce4692
force load precompile registry from vm.go
aaronbuchwald Nov 28, 2023
8719eea
use packTopic in MakeTopics
aaronbuchwald Nov 28, 2023
52acc1b
Merge branch 'master' into migrate-warp-precompile
aaronbuchwald Nov 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions accounts/abi/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,55 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
return append(method.ID, arguments...), nil
}

// PackEvent packs the given event name and arguments to conform the ABI.
// Returns the topics for the event including the event signature (if non-anonymous event) and
// hashes derived from indexed arguments and the packed data of non-indexed args according to
// the event ABI specification.
// The order of arguments must match the order of the event definition.
// https://docs.soliditylang.org/en/v0.8.17/abi-spec.html#indexed-event-encoding.
// Note: PackEvent does not support array (fixed or dynamic-size) or struct types.
func (abi ABI) PackEvent(name string, args ...interface{}) ([]common.Hash, []byte, error) {
event, exist := abi.Events[name]
if !exist {
return nil, nil, fmt.Errorf("event '%s' not found", name)
}
if len(args) != len(event.Inputs) {
return nil, nil, fmt.Errorf("event '%s' unexpected number of inputs %d", name, len(args))
}

var (
nonIndexedInputs = make([]interface{}, 0)
indexedInputs = make([]interface{}, 0)
nonIndexedArgs Arguments
indexedArgs Arguments
)

for i, arg := range event.Inputs {
if arg.Indexed {
indexedArgs = append(indexedArgs, arg)
indexedInputs = append(indexedInputs, args[i])
} else {
nonIndexedArgs = append(nonIndexedArgs, arg)
nonIndexedInputs = append(nonIndexedInputs, args[i])
}
}

packedArguments, err := nonIndexedArgs.Pack(nonIndexedInputs...)
if err != nil {
return nil, nil, err
}
topics := make([]common.Hash, 0, len(indexedArgs)+1)
if !event.Anonymous {
topics = append(topics, event.ID)
}
indexedTopics, err := PackTopics(indexedInputs)
if err != nil {
return nil, nil, err
}

return append(topics, indexedTopics...), packedArguments, nil
}

// PackOutput packs the given [args] as the output of given method [name] to conform the ABI.
// This does not include method ID.
func (abi ABI) PackOutput(name string, args ...interface{}) ([]byte, error) {
Expand Down
96 changes: 96 additions & 0 deletions accounts/abi/abi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/assert"
)

const jsondata = `
Expand Down Expand Up @@ -1202,3 +1203,98 @@ func TestUnpackRevert(t *testing.T) {
})
}
}

func TestABI_PackEvent(t *testing.T) {
tests := []struct {
name string
json string
event string
args []interface{}
expectedTopics []common.Hash
expectedData []byte
}{
{
name: "received",
json: `[
{"type":"event","name":"received","anonymous":false,"inputs":[
{"indexed":false,"name":"sender","type":"address"},
{"indexed":false,"name":"amount","type":"uint256"},
{"indexed":false,"name":"memo","type":"bytes"}
]
}]`,
event: "received(address,uint256,bytes)",
args: []interface{}{
common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
big.NewInt(1),
[]byte{0x88},
},
expectedTopics: []common.Hash{
common.HexToHash("0x75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed"),
},
expectedData: common.Hex2Bytes("000000000000000000000000376c47978271565f56deb45495afa69e59c16ab20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000018800000000000000000000000000000000000000000000000000000000000000"),
},
{
name: "received",
json: `[
{"type":"event","name":"received","anonymous":true,"inputs":[
{"indexed":false,"name":"sender","type":"address"},
{"indexed":false,"name":"amount","type":"uint256"},
{"indexed":false,"name":"memo","type":"bytes"}
]
}]`,
event: "received(address,uint256,bytes)",
args: []interface{}{
common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
big.NewInt(1),
[]byte{0x88},
},
expectedTopics: []common.Hash{},
expectedData: common.Hex2Bytes("000000000000000000000000376c47978271565f56deb45495afa69e59c16ab20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000018800000000000000000000000000000000000000000000000000000000000000"),
}, {
name: "Transfer",
json: `[
{ "constant": true, "inputs": [], "name": "name", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" },
{ "constant": false, "inputs": [ { "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "approve", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" },
{ "constant": true, "inputs": [], "name": "totalSupply", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" },
{ "constant": false, "inputs": [ { "name": "_from", "type": "address" }, { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" },
{ "constant": true, "inputs": [], "name": "decimals", "outputs": [ { "name": "", "type": "uint8" } ], "payable": false, "stateMutability": "view", "type": "function" },
{ "constant": true, "inputs": [ { "name": "_owner", "type": "address" } ], "name": "balanceOf", "outputs": [ { "name": "balance", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" },
{ "constant": true, "inputs": [], "name": "symbol", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" },
{ "constant": false, "inputs": [ { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transfer", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" },
{ "constant": true, "inputs": [ { "name": "_owner", "type": "address" }, { "name": "_spender", "type": "address" } ], "name": "allowance", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" },
{ "payable": true, "stateMutability": "payable", "type": "fallback" },
{ "anonymous": false, "inputs": [ { "indexed": true, "name": "owner", "type": "address" }, { "indexed": true, "name": "spender", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Approval", "type": "event" },
{ "anonymous": false, "inputs": [ { "indexed": true, "name": "from", "type": "address" }, { "indexed": true, "name": "to", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" }
]`,
event: "Transfer(address,address,uint256)",
args: []interface{}{
common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
big.NewInt(100),
},
expectedTopics: []common.Hash{
common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
common.HexToHash("0x0000000000000000000000008db97c7cece249c2b98bdc0226cc4c2a57bf52fc"),
common.HexToHash("0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab2"),
},
expectedData: common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000064"),
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
abi, err := JSON(strings.NewReader(test.json))
if err != nil {
t.Error(err)
}

topics, data, err := abi.PackEvent(test.name, test.args...)
if err != nil {
t.Fatal(err)
}

assert.EqualValues(t, test.expectedTopics, topics)
assert.EqualValues(t, test.expectedData, data)
})
}
}
145 changes: 86 additions & 59 deletions accounts/abi/topics.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,70 +37,97 @@ import (
"github.com/ethereum/go-ethereum/crypto"
)

// packTopic packs rule into the corresponding hash value for a log's topic
// according to the Solidity documentation:
// https://docs.soliditylang.org/en/v0.8.17/abi-spec.html#indexed-event-encoding.
func packTopic(rule interface{}) (common.Hash, error) {
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
var topic common.Hash

// Try to generate the topic based on simple types
switch rule := rule.(type) {
case common.Hash:
copy(topic[:], rule[:])
case common.Address:
copy(topic[common.HashLength-common.AddressLength:], rule[:])
case *big.Int:
blob := rule.Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case bool:
if rule {
topic[common.HashLength-1] = 1
}
case int8:
copy(topic[:], genIntType(int64(rule), 1))
case int16:
copy(topic[:], genIntType(int64(rule), 2))
case int32:
copy(topic[:], genIntType(int64(rule), 4))
case int64:
copy(topic[:], genIntType(rule, 8))
case uint8:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint16:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint32:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint64:
blob := new(big.Int).SetUint64(rule).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case string:
hash := crypto.Keccak256Hash([]byte(rule))
copy(topic[:], hash[:])
case []byte:
hash := crypto.Keccak256Hash(rule)
copy(topic[:], hash[:])

default:
// todo(rjl493456442) according solidity documentation, indexed event
// parameters that are not value types i.e. arrays and structs are not
// stored directly but instead a keccak256-hash of an encoding is stored.
//
// We only convert strings and bytes to hash, still need to deal with
// array(both fixed-size and dynamic-size) and struct.

// Attempt to generate the topic from funky types
val := reflect.ValueOf(rule)
switch {
// static byte array
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val)
default:
return common.Hash{}, fmt.Errorf("unsupported indexed type: %T", rule)
}
}
return topic, nil
}

// PackTopics packs the array of filters into an array of corresponding topics
// according to the Solidity documentation.
// Note: PackTopics does not support array (fixed or dynamic-size) or struct types.
func PackTopics(filter []interface{}) ([]common.Hash, error) {
topics := make([]common.Hash, len(filter))
for i, rule := range filter {
topic, err := packTopic(rule)
if err != nil {
return nil, err
}
topics[i] = topic
}

return topics, nil
}

// MakeTopics converts a filter query argument list into a filter topic set.
func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) {
topics := make([][]common.Hash, len(query))
for i, filter := range query {
for _, rule := range filter {
var topic common.Hash

// Try to generate the topic based on simple types
switch rule := rule.(type) {
case common.Hash:
copy(topic[:], rule[:])
case common.Address:
copy(topic[common.HashLength-common.AddressLength:], rule[:])
case *big.Int:
blob := rule.Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case bool:
if rule {
topic[common.HashLength-1] = 1
}
case int8:
copy(topic[:], genIntType(int64(rule), 1))
case int16:
copy(topic[:], genIntType(int64(rule), 2))
case int32:
copy(topic[:], genIntType(int64(rule), 4))
case int64:
copy(topic[:], genIntType(rule, 8))
case uint8:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint16:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint32:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint64:
blob := new(big.Int).SetUint64(rule).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case string:
hash := crypto.Keccak256Hash([]byte(rule))
copy(topic[:], hash[:])
case []byte:
hash := crypto.Keccak256Hash(rule)
copy(topic[:], hash[:])

default:
// todo(rjl493456442) according solidity documentation, indexed event
// parameters that are not value types i.e. arrays and structs are not
// stored directly but instead a keccak256-hash of an encoding is stored.
//
// We only convert stringS and bytes to hash, still need to deal with
// array(both fixed-size and dynamic-size) and struct.

// Attempt to generate the topic from funky types
val := reflect.ValueOf(rule)
switch {
// static byte array
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val)
default:
return nil, fmt.Errorf("unsupported indexed type: %T", rule)
}
topic, err := packTopic(rule)
if err != nil {
return nil, err
}
topics[i] = append(topics[i], topic)
}
Expand Down
19 changes: 12 additions & 7 deletions consensus/dummy/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func (self *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, heade
if err != nil {
return fmt.Errorf("failed to calculate base fee: %w", err)
}
if !bytes.Equal(expectedRollupWindowBytes, header.Extra) {
if len(header.Extra) < len(expectedRollupWindowBytes) || !bytes.Equal(expectedRollupWindowBytes, header.Extra[:len(expectedRollupWindowBytes)]) {
return fmt.Errorf("expected rollup window bytes: %x, found %x", expectedRollupWindowBytes, header.Extra)
}
if header.BaseFee == nil {
Expand Down Expand Up @@ -194,16 +194,21 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header
if uncle {
return errUnclesUnsupported
}
// Ensure that the header's extra-data section is of a reasonable size
if !config.IsApricotPhase3(header.Time) {
switch {
case config.IsDUpgrade(header.Time):
if len(header.Extra) < params.DynamicFeeExtraDataSize {
return fmt.Errorf("expected extra-data field length >= %d, found %d", params.DynamicFeeExtraDataSize, len(header.Extra))
}
case config.IsApricotPhase3(header.Time):
if len(header.Extra) != params.DynamicFeeExtraDataSize {
return fmt.Errorf("expected extra-data field to be: %d, but found %d", params.DynamicFeeExtraDataSize, len(header.Extra))
}
default:
if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
}
} else {
if uint64(len(header.Extra)) != params.ApricotPhase3ExtraDataSize {
return fmt.Errorf("expected extra-data field to be: %d, but found %d", params.ApricotPhase3ExtraDataSize, len(header.Extra))
}
}

// Ensure gas-related header fields are correct
if err := self.verifyHeaderGasFields(config, header, parent); err != nil {
return err
Expand Down
10 changes: 6 additions & 4 deletions consensus/dummy/dynamic_fees.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, timestamp uin
isApricotPhase5 = config.IsApricotPhase5(parent.Time)
)
if !isApricotPhase3 || parent.Number.Cmp(common.Big0) == 0 {
initialSlice := make([]byte, params.ApricotPhase3ExtraDataSize)
initialSlice := make([]byte, params.DynamicFeeExtraDataSize)
initialBaseFee := big.NewInt(params.ApricotPhase3InitialBaseFee)
return initialSlice, initialBaseFee, nil
}
if uint64(len(parent.Extra)) != params.ApricotPhase3ExtraDataSize {
return nil, nil, fmt.Errorf("expected length of parent extra data to be %d, but found %d", params.ApricotPhase3ExtraDataSize, len(parent.Extra))

if uint64(len(parent.Extra)) < params.DynamicFeeExtraDataSize {
return nil, nil, fmt.Errorf("expected length of parent extra data to be %d, but found %d", params.DynamicFeeExtraDataSize, len(parent.Extra))
}
dynamicFeeWindow := parent.Extra[:params.DynamicFeeExtraDataSize]

if timestamp < parent.Time {
return nil, nil, fmt.Errorf("cannot calculate base fee for timestamp (%d) prior to parent timestamp (%d)", timestamp, parent.Time)
Expand All @@ -61,7 +63,7 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, timestamp uin

// roll the window over by the difference between the timestamps to generate
// the new rollup window.
newRollupWindow, err := rollLongWindow(parent.Extra, int(roll))
newRollupWindow, err := rollLongWindow(dynamicFeeWindow, int(roll))
if err != nil {
return nil, nil, err
}
Expand Down
Loading