diff --git a/op-node/rollup/derive/fuzz_parsers_test.go b/op-node/rollup/derive/fuzz_parsers_test.go index 9fd047feca50c..5dddea53138fa 100644 --- a/op-node/rollup/derive/fuzz_parsers_test.go +++ b/op-node/rollup/derive/fuzz_parsers_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ethereum-optimism/optimism/op-bindings/bindings" + "github.com/ethereum-optimism/optimism/op-node/testutils" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -40,14 +41,6 @@ func BytesToBigInt(b []byte) *big.Int { return new(big.Int).SetBytes(cap_byte_slice(b, 32)) } -func BigEqual(a, b *big.Int) bool { - if a == nil || b == nil { - return a == b - } else { - return a.Cmp(b) == 0 - } -} - // FuzzL1InfoRoundTrip checks that our encoder round trips properly func FuzzL1InfoRoundTrip(f *testing.F) { f.Fuzz(func(t *testing.T, number, time uint64, baseFee, hash []byte, seqNumber uint64) { @@ -67,7 +60,7 @@ func FuzzL1InfoRoundTrip(f *testing.F) { if err != nil { t.Fatalf("Failed to unmarshal binary: %v", err) } - if !cmp.Equal(in, out, cmp.Comparer(BigEqual)) { + if !cmp.Equal(in, out, cmp.Comparer(testutils.BigEqual)) { t.Fatalf("The data did not round trip correctly. in: %v. out: %v", in, out) } @@ -120,7 +113,7 @@ func FuzzL1InfoAgainstContract(f *testing.F) { t.Fatalf("Failed to unmarshal binary: %v", err) } - if !cmp.Equal(expected, actual, cmp.Comparer(BigEqual)) { + if !cmp.Equal(expected, actual, cmp.Comparer(testutils.BigEqual)) { t.Fatalf("The data did not round trip correctly. expected: %v. actual: %v", expected, actual) } @@ -266,7 +259,7 @@ func FuzzUnmarshallLogEvent(f *testing.F) { reconstructed.To = *dep.To } - if !cmp.Equal(depositEvent, reconstructed, cmp.Comparer(BigEqual)) { + if !cmp.Equal(depositEvent, reconstructed, cmp.Comparer(testutils.BigEqual)) { t.Fatalf("The deposit tx did not match. tx: %v. actual: %v", reconstructed, depositEvent) } @@ -279,7 +272,7 @@ func FuzzUnmarshallLogEvent(f *testing.F) { OpaqueData: opaqueData, Raw: types.Log{}, } - if !cmp.Equal(depositEvent, inputArgs, cmp.Comparer(BigEqual)) { + if !cmp.Equal(depositEvent, inputArgs, cmp.Comparer(testutils.BigEqual)) { t.Fatalf("The input args did not match. input: %v. actual: %v", inputArgs, depositEvent) } }) diff --git a/op-node/testutils/assertions.go b/op-node/testutils/assertions.go new file mode 100644 index 0000000000000..47d44e7dbd997 --- /dev/null +++ b/op-node/testutils/assertions.go @@ -0,0 +1,20 @@ +package testutils + +import ( + "math/big" + "testing" +) + +func BigEqual(a, b *big.Int) bool { + if a == nil || b == nil { + return a == b + } else { + return a.Cmp(b) == 0 + } +} + +func RequireBigEqual(t *testing.T, exp, actual *big.Int) { + if !BigEqual(exp, actual) { + t.Fatalf("expected %s to be equal to %s", exp.String(), actual.String()) + } +} diff --git a/op-node/withdrawals/testdata/bridge-withdrawal.json b/op-node/withdrawals/testdata/bridge-withdrawal.json new file mode 100644 index 0000000000000..34f42053e7406 --- /dev/null +++ b/op-node/withdrawals/testdata/bridge-withdrawal.json @@ -0,0 +1,134 @@ +{ + "type": "0x2", + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x28786", + "logsBloom": "0x0000000000004000001000000000000000000000000000100010000000100000000000000000808000000000020400800000000000000004000010000000000000002000000004004000000800000010040000000000000000000000000000000000000012000000000000000000080088080000000200000000001c000000000200000000000000000040000100000000800000000000000001000000201000000000000002004201008000000001000000200000400000000000000000100000000052000200000100000408020010000002100000000000040000200020000000000000000200000100400000000000000000000000000001100000000000", + "logs": [ + { + "address": "0x7c6b91d9be155a6db01f749217d76ff02a7227f2", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000c20c5ec92fda6e611a08485123cdc0d5b84bd3a2", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000001f4", + "blockNumber": "0x36", + "transactionHash": "0x9346381068b59d2098495baa72ed2f773c1e09458610a7a208984859dff73add", + "transactionIndex": "0x1", + "blockHash": "0xfdd4ad8a984b45687aca0463db491cbd0e85273d970019a3f8bf618b614938df", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x7c6b91d9be155a6db01f749217d76ff02a7227f2", + "topics": [ + "0xcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5", + "0x000000000000000000000000c20c5ec92fda6e611a08485123cdc0d5b84bd3a2" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000001f4", + "blockNumber": "0x36", + "transactionHash": "0x9346381068b59d2098495baa72ed2f773c1e09458610a7a208984859dff73add", + "transactionIndex": "0x1", + "blockHash": "0xfdd4ad8a984b45687aca0463db491cbd0e85273d970019a3f8bf618b614938df", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x4200000000000000000000000000000000000016", + "topics": [ + "0x87bf7b546c8de873abb0db5b579ec131f8d0cf5b14f39933551cf9ced23a6136", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000004200000000000000000000000000000000000007", + "0x0000000000000000000000006900000000000000000000000000000000000002" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000031b80000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e4d764ad0b0001000000000000000000000000000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000001000000000000000000000000069000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e40166a07a00000000000000000000000089d51be807d98fc974a0f41b2e67a8228d7846ef0000000000000000000000007c6b91d9be155a6db01f749217d76ff02a7227f2000000000000000000000000c20c5ec92fda6e611a08485123cdc0d5b84bd3a2000000000000000000000000c20c5ec92fda6e611a08485123cdc0d5b84bd3a200000000000000000000000000000000000000000000000000000000000001f400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x36", + "transactionHash": "0x9346381068b59d2098495baa72ed2f773c1e09458610a7a208984859dff73add", + "transactionIndex": "0x1", + "blockHash": "0xfdd4ad8a984b45687aca0463db491cbd0e85273d970019a3f8bf618b614938df", + "logIndex": "0x2", + "removed": false + }, + { + "address": "0x4200000000000000000000000000000000000016", + "topics": [ + "0x2ef6ceb1668fdd882b1f89ddd53a666b0c1113d14cf90c0fbf97c7b1ad880fbb", + "0x0d827f8148288e3a2466018f71b968ece4ea9f9e2a81c30da9bd46cce2868285" + ], + "data": "0x", + "blockNumber": "0x36", + "transactionHash": "0x9346381068b59d2098495baa72ed2f773c1e09458610a7a208984859dff73add", + "transactionIndex": "0x1", + "blockHash": "0xfdd4ad8a984b45687aca0463db491cbd0e85273d970019a3f8bf618b614938df", + "logIndex": "0x3", + "removed": false + }, + { + "address": "0x4200000000000000000000000000000000000007", + "topics": [ + "0xcb0f7ffd78f9aee47a248fae8db181db6eee833039123e026dcbff529522e52a", + "0x0000000000000000000000006900000000000000000000000000000000000003" + ], + "data": "0x000000000000000000000000420000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000800001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e40166a07a00000000000000000000000089d51be807d98fc974a0f41b2e67a8228d7846ef0000000000000000000000007c6b91d9be155a6db01f749217d76ff02a7227f2000000000000000000000000c20c5ec92fda6e611a08485123cdc0d5b84bd3a2000000000000000000000000c20c5ec92fda6e611a08485123cdc0d5b84bd3a200000000000000000000000000000000000000000000000000000000000001f400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x36", + "transactionHash": "0x9346381068b59d2098495baa72ed2f773c1e09458610a7a208984859dff73add", + "transactionIndex": "0x1", + "blockHash": "0xfdd4ad8a984b45687aca0463db491cbd0e85273d970019a3f8bf618b614938df", + "logIndex": "0x4", + "removed": false + }, + { + "address": "0x4200000000000000000000000000000000000007", + "topics": [ + "0x8ebb2ec2465bdb2a06a66fc37a0963af8a2a6a1479d81d56fdb8cbb98096d546", + "0x0000000000000000000000004200000000000000000000000000000000000010" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x36", + "transactionHash": "0x9346381068b59d2098495baa72ed2f773c1e09458610a7a208984859dff73add", + "transactionIndex": "0x1", + "blockHash": "0xfdd4ad8a984b45687aca0463db491cbd0e85273d970019a3f8bf618b614938df", + "logIndex": "0x5", + "removed": false + }, + { + "address": "0x4200000000000000000000000000000000000010", + "topics": [ + "0x7ff126db8024424bbfd9826e8ab82ff59136289ea440b04b39a0df1b03b9cabf", + "0x0000000000000000000000007c6b91d9be155a6db01f749217d76ff02a7227f2", + "0x00000000000000000000000089d51be807d98fc974a0f41b2e67a8228d7846ef", + "0x000000000000000000000000c20c5ec92fda6e611a08485123cdc0d5b84bd3a2" + ], + "data": "0x000000000000000000000000c20c5ec92fda6e611a08485123cdc0d5b84bd3a200000000000000000000000000000000000000000000000000000000000001f400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x36", + "transactionHash": "0x9346381068b59d2098495baa72ed2f773c1e09458610a7a208984859dff73add", + "transactionIndex": "0x1", + "blockHash": "0xfdd4ad8a984b45687aca0463db491cbd0e85273d970019a3f8bf618b614938df", + "logIndex": "0x6", + "removed": false + }, + { + "address": "0x4200000000000000000000000000000000000010", + "topics": [ + "0x73d170910aba9e6d50b102db522b1dbcd796216f5128b445aa2135272886497e", + "0x00000000000000000000000089d51be807d98fc974a0f41b2e67a8228d7846ef", + "0x0000000000000000000000007c6b91d9be155a6db01f749217d76ff02a7227f2", + "0x000000000000000000000000c20c5ec92fda6e611a08485123cdc0d5b84bd3a2" + ], + "data": "0x000000000000000000000000c20c5ec92fda6e611a08485123cdc0d5b84bd3a200000000000000000000000000000000000000000000000000000000000001f400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x36", + "transactionHash": "0x9346381068b59d2098495baa72ed2f773c1e09458610a7a208984859dff73add", + "transactionIndex": "0x1", + "blockHash": "0xfdd4ad8a984b45687aca0463db491cbd0e85273d970019a3f8bf618b614938df", + "logIndex": "0x7", + "removed": false + } + ], + "transactionHash": "0x9346381068b59d2098495baa72ed2f773c1e09458610a7a208984859dff73add", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x28786", + "blockHash": "0xfdd4ad8a984b45687aca0463db491cbd0e85273d970019a3f8bf618b614938df", + "blockNumber": "0x36", + "transactionIndex": "0x1" +} \ No newline at end of file diff --git a/op-node/withdrawals/utils.go b/op-node/withdrawals/utils.go index 6b5ec3f1a9a04..0b7738022ddfb 100644 --- a/op-node/withdrawals/utils.go +++ b/op-node/withdrawals/utils.go @@ -21,6 +21,9 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) +var WithdrawalInitiatedTopic = crypto.Keccak256Hash([]byte("WithdrawalInitiated(uint256,address,address,uint256,uint256,bytes)")) +var WithdrawalInitiatedExtension1Topic = crypto.Keccak256Hash([]byte("WithdrawalInitiatedExtension1(bytes32)")) + // WaitForFinalizationPeriod waits until there is OutputProof for an L2 block number larger than the supplied l2BlockNumber // and that the output is finalized. // This functions polls and can block for a very long time if used on mainnet. @@ -258,56 +261,48 @@ func WithdrawalHash(ev *bindings.L2ToL1MessagePasserWithdrawalInitiated) (common return crypto.Keccak256Hash(enc), nil } -// ParseWithdrawalInitiated parses +// ParseWithdrawalInitiated parses WithdrawalInitiated events from +// a transaction receipt. It does not support multiple withdrawals +// per receipt. func ParseWithdrawalInitiated(receipt *types.Receipt) (*bindings.L2ToL1MessagePasserWithdrawalInitiated, error) { contract, err := bindings.NewL2ToL1MessagePasser(common.Address{}, nil) if err != nil { return nil, err } - abi, err := bindings.L2ToL1MessagePasserMetaData.GetAbi() - if err != nil { - return nil, err - } for _, log := range receipt.Logs { - event, err := abi.EventByID(log.Topics[0]) - if err != nil { - return nil, err + if len(log.Topics) == 0 || log.Topics[0] != WithdrawalInitiatedTopic { + continue } - if event.Name == "WithdrawalInitiated" { - ev, err := contract.ParseWithdrawalInitiated(*log) - if err != nil { - return nil, fmt.Errorf("failed to parse log: %w", err) - } - return ev, nil + + ev, err := contract.ParseWithdrawalInitiated(*log) + if err != nil { + return nil, fmt.Errorf("failed to parse log: %w", err) } + return ev, nil } return nil, errors.New("Unable to find WithdrawalInitiated event") } -// ParseWithdrawalInitiatedExtension1 parses +// ParseWithdrawalInitiatedExtension1 parses WithdrawalInitiatedExtension1 events +// from a transaction receipt. It does not support multiple withdrawals per +// receipt. func ParseWithdrawalInitiatedExtension1(receipt *types.Receipt) (*bindings.L2ToL1MessagePasserWithdrawalInitiatedExtension1, error) { contract, err := bindings.NewL2ToL1MessagePasser(common.Address{}, nil) if err != nil { return nil, err } - abi, err := bindings.L2ToL1MessagePasserMetaData.GetAbi() - if err != nil { - return nil, err - } for _, log := range receipt.Logs { - event, err := abi.EventByID(log.Topics[0]) - if err != nil { - return nil, err + if len(log.Topics) == 0 || log.Topics[0] != WithdrawalInitiatedExtension1Topic { + continue } - if event.Name == "WithdrawalInitiatedExtension1" { - ev, err := contract.ParseWithdrawalInitiatedExtension1(*log) - if err != nil { - return nil, fmt.Errorf("failed to parse log: %w", err) - } - return ev, nil + + ev, err := contract.ParseWithdrawalInitiatedExtension1(*log) + if err != nil { + return nil, fmt.Errorf("failed to parse log: %w", err) } + return ev, nil } return nil, errors.New("Unable to find WithdrawalInitiatedExtension1 event") } diff --git a/op-node/withdrawals/utils_test.go b/op-node/withdrawals/utils_test.go new file mode 100644 index 0000000000000..6802d6930c9f2 --- /dev/null +++ b/op-node/withdrawals/utils_test.go @@ -0,0 +1,160 @@ +package withdrawals + +import ( + "encoding/json" + "math/big" + "os" + "path" + "testing" + + "github.com/ethereum-optimism/optimism/op-bindings/bindings" + "github.com/ethereum-optimism/optimism/op-node/testutils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" +) + +func TestParseWithdrawalInitiated(t *testing.T) { + tests := []struct { + name string + file string + expected *bindings.L2ToL1MessagePasserWithdrawalInitiated + }{ + { + "withdrawal through bridge", + "bridge-withdrawal.json", + &bindings.L2ToL1MessagePasserWithdrawalInitiated{ + Nonce: new(big.Int), + Sender: common.HexToAddress("0x4200000000000000000000000000000000000007"), + Target: common.HexToAddress("0x6900000000000000000000000000000000000002"), + Value: new(big.Int), + GasLimit: big.NewInt(203648), + Data: hexutil.MustDecode( + "0xd764ad0b00010000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000042000000000000000000000000000000" + + "0000001000000000000000000000000069000000000000000000000000000000" + + "0000000300000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "000000c000000000000000000000000000000000000000000000000000000000" + + "000000e40166a07a00000000000000000000000089d51be807d98fc974a0f41b" + + "2e67a8228d7846ef0000000000000000000000007c6b91d9be155a6db01f7492" + + "17d76ff02a7227f2000000000000000000000000c20c5ec92fda6e611a084851" + + "23cdc0d5b84bd3a2000000000000000000000000c20c5ec92fda6e611a084851" + + "23cdc0d5b84bd3a2000000000000000000000000000000000000000000000000" + + "00000000000001f4000000000000000000000000000000000000000000000000" + + "00000000000000c0000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "00000000", + ), + Raw: types.Log{ + Address: common.HexToAddress("0x4200000000000000000000000000000000000016"), + Topics: []common.Hash{ + common.HexToHash("0x87bf7b546c8de873abb0db5b579ec131f8d0cf5b14f39933551cf9ced23a6136"), + common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), + common.HexToHash("0x0000000000000000000000004200000000000000000000000000000000000007"), + common.HexToHash("0x0000000000000000000000006900000000000000000000000000000000000002"), + }, + Data: hexutil.MustDecode( + "0x00000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000031b80" + + "000000000000000000000000000000000000000000000000000000000000006000" + + "000000000000000000000000000000000000000000000000000000000001e4d764" + + "ad0b00010000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000004200000000000000000000000000000000000010" + + "000000000000000000000000690000000000000000000000000000000000000300" + + "000000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000000" + + "00000000000000000000000000000000000000000000000000000000c000000000" + + "000000000000000000000000000000000000000000000000000000e40166a07a00" + + "000000000000000000000089d51be807d98fc974a0f41b2e67a8228d7846ef0000" + + "000000000000000000007c6b91d9be155a6db01f749217d76ff02a7227f2000000" + + "000000000000000000c20c5ec92fda6e611a08485123cdc0d5b84bd3a200000000" + + "0000000000000000c20c5ec92fda6e611a08485123cdc0d5b84bd3a20000000000" + + "0000000000000000000000000000000000000000000000000001f4000000000000" + + "00000000000000000000000000000000000000000000000000c000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000", + ), + BlockNumber: 0x36, + TxHash: common.HexToHash("0x9346381068b59d2098495baa72ed2f773c1e09458610a7a208984859dff73add"), + TxIndex: 1, + BlockHash: common.HexToHash("0xfdd4ad8a984b45687aca0463db491cbd0e85273d970019a3f8bf618b614938df"), + Index: 2, + Removed: false, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + f, err := os.Open(path.Join("testdata", test.file)) + require.NoError(t, err) + dec := json.NewDecoder(f) + receipt := new(types.Receipt) + require.NoError(t, dec.Decode(receipt)) + parsed, err := ParseWithdrawalInitiated(receipt) + require.NoError(t, err) + + // Have to do this weird thing to compare zero bigints. + // When they're deserialized from JSON, the internal byte + // array is an empty array whereas it is nil in the expectation. + parsedNonce := parsed.Nonce + parsedValue := parsed.Value + expNonce := test.expected.Nonce + expValue := test.expected.Value + testutils.RequireBigEqual(t, expNonce, parsedNonce) + testutils.RequireBigEqual(t, expValue, parsedValue) + parsed.Nonce = nil + parsed.Value = nil + test.expected.Nonce = nil + test.expected.Value = nil + + require.EqualValues(t, test.expected, parsed) + }) + } +} + +func TestParseWithdrawalInitiatedExtension1(t *testing.T) { + tests := []struct { + name string + file string + expected *bindings.L2ToL1MessagePasserWithdrawalInitiatedExtension1 + }{ + { + "withdrawal through bridge", + "bridge-withdrawal.json", + &bindings.L2ToL1MessagePasserWithdrawalInitiatedExtension1{ + Hash: common.HexToHash("0x0d827f8148288e3a2466018f71b968ece4ea9f9e2a81c30da9bd46cce2868285"), + Raw: types.Log{ + Address: common.HexToAddress("0x4200000000000000000000000000000000000016"), + Topics: []common.Hash{ + common.HexToHash("0x2ef6ceb1668fdd882b1f89ddd53a666b0c1113d14cf90c0fbf97c7b1ad880fbb"), + common.HexToHash("0x0d827f8148288e3a2466018f71b968ece4ea9f9e2a81c30da9bd46cce2868285"), + }, + Data: []byte{}, + BlockNumber: 0x36, + TxHash: common.HexToHash("0x9346381068b59d2098495baa72ed2f773c1e09458610a7a208984859dff73add"), + TxIndex: 0x1, + BlockHash: common.HexToHash("0xfdd4ad8a984b45687aca0463db491cbd0e85273d970019a3f8bf618b614938df"), + Index: 0x3, + Removed: false, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + f, err := os.Open(path.Join("testdata", test.file)) + require.NoError(t, err) + dec := json.NewDecoder(f) + receipt := new(types.Receipt) + require.NoError(t, dec.Decode(receipt)) + parsed, err := ParseWithdrawalInitiatedExtension1(receipt) + require.NoError(t, err) + require.EqualValues(t, test.expected, parsed) + }) + } +}