From 65bdd7a807fafbd7bcc3cb488e3829c71dbe471f Mon Sep 17 00:00:00 2001 From: Nate Beauregard Date: Fri, 1 Nov 2024 14:06:36 -0400 Subject: [PATCH] Update builder to listen for withdrawal events Updates the builder to use the withdrawal tx event attributes for constructing the withdrawal hash instead of the withdrawal message. --- bindings/L2ToL1MessagePasserExecuter.go | 15 ++-- builder/builder.go | 110 ++++++++++++++++-------- builder/builder_test.go | 25 +++--- e2e/stack_test.go | 4 +- evm/executer_test.go | 7 +- 5 files changed, 99 insertions(+), 62 deletions(-) diff --git a/bindings/L2ToL1MessagePasserExecuter.go b/bindings/L2ToL1MessagePasserExecuter.go index 0e2d7585..f296fbfb 100644 --- a/bindings/L2ToL1MessagePasserExecuter.go +++ b/bindings/L2ToL1MessagePasserExecuter.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/ethereum-optimism/optimism/op-bindings/predeploys" + "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" bindings "github.com/polymerdao/monomer/bindings/generated" @@ -33,21 +34,15 @@ func NewL2ToL1MessagePasserExecuter(evm *vm.EVM) (*L2ToL1MessagePasserExecuter, return &L2ToL1MessagePasserExecuter{executer}, nil } -func (e *L2ToL1MessagePasserExecuter) InitiateWithdrawal( - sender common.Address, - amount *big.Int, - l1Address common.Address, - gasLimit *big.Int, - data []byte, -) error { - data, err := e.ABI.Pack(initiateWithdrawalMethodName, l1Address, gasLimit, data) +func (e *L2ToL1MessagePasserExecuter) InitiateWithdrawal(params *crossdomain.Withdrawal) error { + data, err := e.ABI.Pack(initiateWithdrawalMethodName, params.Target, params.GasLimit, []byte(params.Data)) if err != nil { return fmt.Errorf("create initiateWithdrawal data: %v", err) } _, err = e.Call(&monomerevm.CallParams{ - Sender: &sender, - Value: amount, + Sender: params.Sender, + Value: params.Value, Data: data, }) if err != nil { diff --git a/builder/builder.go b/builder/builder.go index 0e79847f..e5b4ed81 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -11,6 +11,7 @@ import ( cdctypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" sdktx "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/state" @@ -290,71 +291,106 @@ func (b *Builder) parseWithdrawalMessages( return execTxResult, nil } for _, msg := range cosmosTx.GetBody().GetMessages() { - withdrawalMsg := new(rolluptypes.MsgInitiateWithdrawal) - if msg.TypeUrl == cdctypes.MsgTypeURL(withdrawalMsg) { - if err := withdrawalMsg.Unmarshal(msg.GetValue()); err != nil { - return nil, fmt.Errorf("unmarshal MsgInitiateWithdrawal: %v", err) - } - - // Store the withdrawal message hash in the monomer EVM state db. - nonce, err := b.storeWithdrawalMsgInEVM(withdrawalMsg, ethState, header) + userWithdrawalMsgType := cdctypes.MsgTypeURL(new(rolluptypes.MsgInitiateWithdrawal)) + feeWithdrawalMsgType := cdctypes.MsgTypeURL(new(rolluptypes.MsgInitiateFeeWithdrawal)) + if msg.TypeUrl == userWithdrawalMsgType || msg.TypeUrl == feeWithdrawalMsgType { + // Store the withdrawal message hash in the monomer EVM state db and populate the nonce in the event data. + err := b.storeWithdrawalMsgInEVM(execTxResult, ethState, header) if err != nil { return nil, fmt.Errorf("store withdrawal msg in EVM: %v", err) } - - // Populate the nonce in the tx event attributes. - for i := range execTxResult.Events { - event := &execTxResult.Events[i] // Get a pointer to the event, so we can modify it. - if event.Type == rolluptypes.EventTypeWithdrawalInitiated { - event.Attributes = append(event.Attributes, abcitypes.EventAttribute{ - Key: rolluptypes.AttributeKeyNonce, - Value: hexutil.Encode(nonce.Bytes()), - }) - } - } } } } return execTxResult, nil } -// storeWithdrawalMsgInEVM stores the withdrawal message hash in the monomer evm state db and returns the L2ToL1MessagePasser -// message nonce used for the withdrawal. This is used for proving withdrawals. +// storeWithdrawalMsgInEVM stores the withdrawal message hash in the monomer evm state db and appends the L2ToL1MessagePasser +// message nonce used for the withdrawal to the tx events. This is used for proving withdrawals. func (b *Builder) storeWithdrawalMsgInEVM( - withdrawalMsg *rolluptypes.MsgInitiateWithdrawal, + execTxResult *abcitypes.ExecTxResult, ethState *state.StateDB, header *monomer.Header, -) (*big.Int, error) { +) error { monomerEVM, err := evm.NewEVM(ethState, header) if err != nil { - return nil, fmt.Errorf("new EVM: %v", err) + return fmt.Errorf("new EVM: %v", err) } executer, err := bindings.NewL2ToL1MessagePasserExecuter(monomerEVM) if err != nil { - return nil, fmt.Errorf("new L2ToL1MessagePasserExecuter: %v", err) + return fmt.Errorf("new L2ToL1MessagePasserExecuter: %v", err) } // Get the current message nonce before initiating the withdrawal. messageNonce, err := executer.GetMessageNonce() if err != nil { - return nil, fmt.Errorf("get message nonce: %v", err) + return fmt.Errorf("get message nonce: %v", err) } - senderCosmosAddress, err := sdk.AccAddressFromBech32(withdrawalMsg.GetSender()) + var event *abcitypes.Event + for i := range execTxResult.Events { + if execTxResult.Events[i].Type == rolluptypes.EventTypeWithdrawalInitiated { + event = &execTxResult.Events[i] + break + } + } + if event == nil { + return fmt.Errorf("withdrawal event not found") + } + + params, err := parseWithdrawalEventAttributes(event) if err != nil { - return nil, fmt.Errorf("convert sender to cosmos address: %v", err) + return fmt.Errorf("parse withdrawal attributes: %v", err) } // Initiate the withdrawal in the Monomer ethereum state. - if err = executer.InitiateWithdrawal( - common.BytesToAddress(senderCosmosAddress.Bytes()), - withdrawalMsg.Value.BigInt(), - common.HexToAddress(withdrawalMsg.GetTarget()), - new(big.Int).SetBytes(withdrawalMsg.GasLimit), - withdrawalMsg.GetData(), - ); err != nil { - return nil, fmt.Errorf("initiate withdrawal: %v", err) + if err = executer.InitiateWithdrawal(params); err != nil { + return fmt.Errorf("initiate withdrawal: %v", err) } - return messageNonce, nil + // Populate the nonce in the tx event attributes. + event.Attributes = append(event.Attributes, abcitypes.EventAttribute{ + Key: rolluptypes.AttributeKeyNonce, + Value: hexutil.EncodeBig(messageNonce), + }) + + return nil +} + +// parseWithdrawalEventAttributes parses the withdrawal event attributes and returns the converted withdrawal parameters. +func parseWithdrawalEventAttributes(withdrawalEvent *abcitypes.Event) (*crossdomain.Withdrawal, error) { + var params crossdomain.Withdrawal + for _, attr := range withdrawalEvent.Attributes { + switch attr.Key { + case rolluptypes.AttributeKeySender: + senderCosmosAddress, err := sdk.AccAddressFromBech32(attr.Value) + if err != nil { + return nil, fmt.Errorf("convert sender to cosmos address: %v", err) + } + sender := common.BytesToAddress(senderCosmosAddress.Bytes()) + params.Sender = &sender + case rolluptypes.AttributeKeyL1Target: + target := common.HexToAddress(attr.Value) + params.Target = &target + case rolluptypes.AttributeKeyValue: + value, err := hexutil.DecodeBig(attr.Value) + if err != nil { + return nil, fmt.Errorf("decode value: %v", err) + } + params.Value = value + case rolluptypes.AttributeKeyGasLimit: + gasLimitBz, err := hexutil.Decode(attr.Value) + if err != nil { + return nil, fmt.Errorf("decode gas limit: %v", err) + } + params.GasLimit = new(big.Int).SetBytes(gasLimitBz) + case rolluptypes.AttributeKeyData: + data, err := hexutil.Decode(attr.Value) + if err != nil { + return nil, fmt.Errorf("decode data: %v", err) + } + params.Data = data + } + } + return ¶ms, nil } diff --git a/builder/builder_test.go b/builder/builder_test.go index c048a030..bde580c2 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -313,7 +313,7 @@ func TestBuildRollupTxs(t *testing.T) { depositTxETH := ethTxs[1] require.NotNil(t, depositTxETH.Mint()) require.NotNil(t, depositTxETH.To(), "Deposit transaction must have a 'to' address") - recipientAddr, err := monomer.CosmosETHAddress(*depositTxETH.To()).Encode("cosmos") + userCosmosAddr, err := monomer.CosmosETHAddress(*depositTxETH.To()).Encode("cosmos") require.NoError(t, err) from, err := gethtypes.NewCancunSigner(depositTxETH.ChainId()).Sender(depositTxETH) @@ -322,13 +322,17 @@ func TestBuildRollupTxs(t *testing.T) { require.NoError(t, err) withdrawalTx := testapp.ToTx(t, &types.MsgInitiateWithdrawal{ - Sender: recipientAddr, + Sender: userCosmosAddr, Target: common.HexToAddress("0x12345abcde").String(), Value: math.NewIntFromBigInt(depositTxETH.Value()), GasLimit: big.NewInt(100_000).Bytes(), Data: []byte{}, }) + feeWithdrawalTx := testapp.ToTx(t, &types.MsgInitiateFeeWithdrawal{ + Sender: userCosmosAddr, + }) + b := builder.New( env.pool, env.app, @@ -340,7 +344,7 @@ func TestBuildRollupTxs(t *testing.T) { ) // Prepare the payload - txs := bfttypes.Txs{depositTxs, withdrawalTx} + txs := bfttypes.Txs{depositTxs, withdrawalTx, feeWithdrawalTx} payload := &builder.Payload{ InjectedTransactions: txs, GasLimit: 1000000000000, @@ -352,12 +356,14 @@ func TestBuildRollupTxs(t *testing.T) { builtBlock, preBuildInfo, postBuildInfo := buildBlock(t, b, env.app, payload) // Test deposit was received - checkDepositTxResult(t, env.txStore, depositTxs, depositTxETH.Mint(), depositTxETH.Value(), mintAddr, recipientAddr) + checkDepositTxResult(t, env.txStore, depositTxs, depositTxETH.Mint(), depositTxETH.Value(), mintAddr, userCosmosAddr) withdrawalTxResult, err := env.txStore.Get(bfttypes.Tx(withdrawalTx).Hash()) require.NoError(t, err) require.NotNil(t, withdrawalTxResult) - require.Truef(t, withdrawalTxResult.Result.IsOK(), "Expected the withdrawal transaction to be successful, but it failed") + require.Truef(t, withdrawalTxResult.Result.IsOK(), "User withdrawal transaction not successful") + + // TODO: Check fee withdrawal transaction result once a genesis state for the minimum fee withdrawal amount is added // Verify block creation genesisBlock, err := env.blockStore.BlockByHeight(uint64(preBuildInfo.GetLastBlockHeight())) @@ -378,10 +384,9 @@ func TestBuildRollupTxs(t *testing.T) { require.NoError(t, err) verifyBlockContent(t, wantBlock, gotBlock, builtBlock) - // We expect two transactions: one combined transaction (l1InfoTx + depositTxs) and one withdrawal transaction (withdrawalTx). - require.Equal(t, 2, builtBlock.Txs.Len(), "Expected the built block to contain 2 transactions: depositTxs and withdrawalTx") + // We expect three transactions: one combined transaction (l1InfoTx + depositTxs) and two withdrawal transactions (withdrawalTx, feeWithdrawalTx). + require.Equal(t, 3, builtBlock.Txs.Len(), "Expected the built block to contain 3 transactions: depositTxs, withdrawalTx, feeWithdrawalTx") - // Test parseWithdrawalMessages checkWithdrawalTxResult(t, withdrawalTxResult) expectedStateRoot := wantBlock.Header.StateRoot @@ -515,9 +520,9 @@ func checkDepositTxResult(t *testing.T, txStore txstore.TxStore, depositTx bftty remainingMintAmount := new(big.Int).Sub(mintAmount, transferAmount) require.Equal(t, mintAddr, MintEthEvent.Attributes[1].Value, "Mint address mismatch") - require.Equal(t, hexutil.Encode(remainingMintAmount.Bytes()), MintEthEvent.Attributes[2].Value, "Mint amount mismatch") + require.Equal(t, hexutil.EncodeBig(remainingMintAmount), MintEthEvent.Attributes[2].Value, "Mint amount mismatch") require.Equal(t, recipientAddr, MintEthEvent.Attributes[3].Value, "Recipient address mismatch") - require.Equal(t, hexutil.Encode(transferAmount.Bytes()), MintEthEvent.Attributes[4].Value, "Transfer amount mismatch") + require.Equal(t, hexutil.EncodeBig(transferAmount), MintEthEvent.Attributes[4].Value, "Transfer amount mismatch") } // getEventData retrieves event data from the subscription channel. The event data is retrieved in the order diff --git a/e2e/stack_test.go b/e2e/stack_test.go index 0eb57e83..c3b112cd 100644 --- a/e2e/stack_test.go +++ b/e2e/stack_test.go @@ -319,7 +319,7 @@ func ethRollupFlow(t *testing.T, stack *e2e.StackConfig) { userCosmosAddr, err := userCosmosETHAddress.Encode("cosmos") require.NoError(t, err) - depositValueHex := hexutil.Encode(depositAmount.Bytes()) + depositValueHex := hexutil.EncodeBig(depositAmount) requireEthIsMinted(t, stack, userCosmosAddr, depositValueHex) t.Log("Monomer can ingest user deposit txs from L1 and mint ETH on L2") @@ -536,7 +536,7 @@ func erc20RollupFlow(t *testing.T, stack *e2e.StackConfig) { // assert the user's bridged WETH is on L2 userAddr, err := monomer.CosmosETHAddress(userAddress).Encode("cosmos") require.NoError(t, err) - requireERC20IsMinted(t, stack, userAddr, weth9Address.String(), hexutil.Encode(wethL2Amount.Bytes())) + requireERC20IsMinted(t, stack, userAddr, weth9Address.String(), hexutil.EncodeBig(wethL2Amount)) t.Log("Monomer can ingest ERC-20 deposit txs from L1 and mint ERC-20 tokens on L2") } diff --git a/evm/executer_test.go b/evm/executer_test.go index 941600a1..143a5b83 100644 --- a/evm/executer_test.go +++ b/evm/executer_test.go @@ -48,14 +48,15 @@ func TestL2ToL1MessagePasserExecuter(t *testing.T) { data := []byte("data") nonce := encodeVersionedNonce(big.NewInt(0)) - withdrawalHash, err := crossdomain.NewWithdrawal( + withdrawalParams := crossdomain.NewWithdrawal( nonce, &sender, &l1TargetAddress, amount, gasLimit, data, - ).Hash() + ) + withdrawalHash, err := withdrawalParams.Hash() require.NoError(t, err) // Check that the withdrawal hash is not in the sentMessages mapping @@ -69,7 +70,7 @@ func TestL2ToL1MessagePasserExecuter(t *testing.T) { require.Equal(t, nonce, initialMessageNonce) // Initiate a withdrawal - err = executer.InitiateWithdrawal(sender, amount, l1TargetAddress, gasLimit, data) + err = executer.InitiateWithdrawal(withdrawalParams) require.NoError(t, err) // Check that the withdrawal hash is in the sentMessages mapping