diff --git a/op-chain-ops/crossdomain/legacy_withdrawal.go b/op-chain-ops/crossdomain/legacy_withdrawal.go index f0264090cc1c6..3f227a03f641e 100644 --- a/op-chain-ops/crossdomain/legacy_withdrawal.go +++ b/op-chain-ops/crossdomain/legacy_withdrawal.go @@ -14,10 +14,10 @@ import ( // LegacyWithdrawal represents a pre bedrock upgrade withdrawal. type LegacyWithdrawal struct { - Target *common.Address - Sender *common.Address - Data []byte - Nonce *big.Int + Target *common.Address `json:"target"` + Sender *common.Address `json:"sender"` + Data []byte `json:"data"` + Nonce *big.Int `json:"nonce"` } var _ WithdrawalMessage = (*LegacyWithdrawal)(nil) diff --git a/op-chain-ops/crossdomain/migrate.go b/op-chain-ops/crossdomain/migrate.go new file mode 100644 index 0000000000000..1284276e7e3eb --- /dev/null +++ b/op-chain-ops/crossdomain/migrate.go @@ -0,0 +1,116 @@ +package crossdomain + +import ( + "errors" + "fmt" + "math/big" + + "github.com/ethereum-optimism/optimism/op-bindings/bindings" + "github.com/ethereum-optimism/optimism/op-bindings/predeploys" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" +) + +var ( + abiTrue = common.Hash{31: 0x01} + errLegacyStorageSlotNotFound = errors.New("cannot find storage slot") +) + +// MigrateWithdrawals will migrate a list of pending withdrawals given a StateDB. +func MigrateWithdrawals(withdrawals []*PendingWithdrawal, db vm.StateDB, l1CrossDomainMessenger, l1StandardBridge *common.Address) error { + for _, legacy := range withdrawals { + legacySlot, err := legacy.StorageSlot() + if err != nil { + return err + } + + legacyValue := db.GetState(predeploys.LegacyMessagePasserAddr, legacySlot) + if legacyValue != abiTrue { + return fmt.Errorf("%w: %s", errLegacyStorageSlotNotFound, legacyValue) + } + + withdrawal, err := MigrateWithdrawal(&legacy.LegacyWithdrawal, l1CrossDomainMessenger, l1StandardBridge) + if err != nil { + return err + } + + slot, err := withdrawal.StorageSlot() + if err != nil { + return err + } + + db.SetState(predeploys.L2ToL1MessagePasserAddr, slot, abiTrue) + } + return nil +} + +// MigrateWithdrawal will turn a LegacyWithdrawal into a bedrock +// style Withdrawal. +func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger, l1StandardBridge *common.Address) (*Withdrawal, error) { + value := new(big.Int) + + isFromL2StandardBridge := *withdrawal.Sender == predeploys.L2StandardBridgeAddr + + if withdrawal.Target == nil { + return nil, errors.New("withdrawal target cannot be nil") + } + + isToL1StandardBridge := *withdrawal.Target == *l1StandardBridge + + if isFromL2StandardBridge && isToL1StandardBridge { + abi, err := bindings.L1StandardBridgeMetaData.GetAbi() + if err != nil { + return nil, err + } + + method, err := abi.MethodById(withdrawal.Data) + if err != nil { + return nil, err + } + if method.Name == "finalizeETHWithdrawal" { + data, err := method.Inputs.Unpack(withdrawal.Data[4:]) + if err != nil { + return nil, err + } + // bounds check + if len(data) < 3 { + return nil, errors.New("not enough data") + } + var ok bool + value, ok = data[2].(*big.Int) + if !ok { + return nil, errors.New("not big.Int") + } + } + } + + abi, err := bindings.L1CrossDomainMessengerMetaData.GetAbi() + if err != nil { + return nil, err + } + + versionedNonce := EncodeVersionedNonce(withdrawal.Nonce, common.Big1) + data, err := abi.Pack( + "relayMessage", + versionedNonce, + withdrawal.Sender, + withdrawal.Target, + value, + new(big.Int), + withdrawal.Data, + ) + if err != nil { + return nil, fmt.Errorf("cannot abi encode relayMessage: %w", err) + } + + w := NewWithdrawal( + withdrawal.Nonce, + &predeploys.L2CrossDomainMessengerAddr, + l1CrossDomainMessenger, + value, + new(big.Int), + data, + ) + return w, nil +} diff --git a/op-chain-ops/crossdomain/migrate_test.go b/op-chain-ops/crossdomain/migrate_test.go new file mode 100644 index 0000000000000..9e854028c8324 --- /dev/null +++ b/op-chain-ops/crossdomain/migrate_test.go @@ -0,0 +1,41 @@ +package crossdomain_test + +import ( + "fmt" + "testing" + + "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/stretchr/testify/require" +) + +func TestMigrateWithdrawal(t *testing.T) { + withdrawals := make([]*crossdomain.LegacyWithdrawal, 0) + + for _, receipt := range receipts { + msg, err := findCrossDomainMessage(receipt) + require.Nil(t, err) + withdrawal, err := msg.ToWithdrawal() + require.Nil(t, err) + legacyWithdrawal, ok := withdrawal.(*crossdomain.LegacyWithdrawal) + require.True(t, ok) + withdrawals = append(withdrawals, legacyWithdrawal) + } + + l1CrossDomainMessenger := common.HexToAddress("0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1") + l1StandardBridge := common.HexToAddress("0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1") + + for i, legacy := range withdrawals { + t.Run(fmt.Sprintf("test%d", i), func(t *testing.T) { + withdrawal, err := crossdomain.MigrateWithdrawal(legacy, &l1CrossDomainMessenger, &l1StandardBridge) + require.Nil(t, err) + require.NotNil(t, withdrawal) + + require.Equal(t, legacy.Nonce.Uint64(), withdrawal.Nonce.Uint64()) + require.Equal(t, *withdrawal.Sender, predeploys.L2CrossDomainMessengerAddr) + require.Equal(t, *withdrawal.Target, l1CrossDomainMessenger) + }) + } +} diff --git a/op-chain-ops/crossdomain/withdrawal.go b/op-chain-ops/crossdomain/withdrawal.go index a3d20ec725cd3..521ed561e5994 100644 --- a/op-chain-ops/crossdomain/withdrawal.go +++ b/op-chain-ops/crossdomain/withdrawal.go @@ -13,12 +13,12 @@ var _ WithdrawalMessage = (*Withdrawal)(nil) // Withdrawal represents a withdrawal transaction on L2 type Withdrawal struct { - Nonce *big.Int - Sender *common.Address - Target *common.Address - Value *big.Int - GasLimit *big.Int - Data []byte + Nonce *big.Int `json:"nonce"` + Sender *common.Address `json:"sender"` + Target *common.Address `json:"target"` + Value *big.Int `json:"value"` + GasLimit *big.Int `json:"gasLimit"` + Data []byte `json:"data"` } // NewWithdrawal will create a Withdrawal diff --git a/op-chain-ops/crossdomain/withdrawals.go b/op-chain-ops/crossdomain/withdrawals.go index fbbd059907ef1..49eddfeba9880 100644 --- a/op-chain-ops/crossdomain/withdrawals.go +++ b/op-chain-ops/crossdomain/withdrawals.go @@ -15,12 +15,8 @@ import ( // A PendingWithdrawal represents a withdrawal that has // not been finalized on L1 type PendingWithdrawal struct { - Target common.Address `json:"target"` - Sender common.Address `json:"sender"` - Message []byte `json:"message"` - MessageNonce *big.Int `json:"nonce"` - GasLimit *big.Int `json:"gasLimit"` - TransactionHash common.Hash `json:"transactionHash"` + LegacyWithdrawal `json:"withdrawal"` + TransactionHash common.Hash `json:"transactionHash"` } // Backends represents a set of backends for L1 and L2. @@ -119,11 +115,12 @@ func GetPendingWithdrawals(messengers *Messengers, version *big.Int, start, end log.Info("%s not yet relayed", event.Raw.TxHash) withdrawal := PendingWithdrawal{ - Target: event.Target, - Sender: event.Sender, - Message: event.Message, - MessageNonce: event.MessageNonce, - GasLimit: event.GasLimit, + LegacyWithdrawal: LegacyWithdrawal{ + Target: &event.Target, + Sender: &event.Sender, + Data: event.Message, + Nonce: event.MessageNonce, + }, TransactionHash: event.Raw.TxHash, } diff --git a/op-chain-ops/crossdomain/withdrawals_test.go b/op-chain-ops/crossdomain/withdrawals_test.go index 30cc34335d9e9..287ce52145a31 100644 --- a/op-chain-ops/crossdomain/withdrawals_test.go +++ b/op-chain-ops/crossdomain/withdrawals_test.go @@ -272,8 +272,7 @@ func TestGetPendingWithdrawals(t *testing.T) { for i, msg := range msgs[3:] { withdrawal := withdrawals[i] - require.Equal(t, msg.Target, withdrawal.Target) - require.Equal(t, msg.Message, withdrawal.Message) - require.Equal(t, uint64(msg.MinGasLimit), withdrawal.GasLimit.Uint64()) + require.Equal(t, msg.Target, *withdrawal.Target) + require.Equal(t, msg.Message, withdrawal.Data) } }