Skip to content

Commit

Permalink
Update builder to listen for withdrawal events
Browse files Browse the repository at this point in the history
Updates the builder to use the withdrawal tx event attributes for
constructing the withdrawal hash instead of the withdrawal message.
  • Loading branch information
natebeauregard committed Nov 1, 2024
1 parent 87ab1af commit 65bdd7a
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 62 deletions.
15 changes: 5 additions & 10 deletions bindings/L2ToL1MessagePasserExecuter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down
110 changes: 73 additions & 37 deletions builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 &params, nil
}
25 changes: 15 additions & 10 deletions builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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()))
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions e2e/stack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
}
Expand Down
7 changes: 4 additions & 3 deletions evm/executer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 65bdd7a

Please sign in to comment.