diff --git a/op-chain-ops/cmd/withdrawals/main.go b/op-chain-ops/cmd/withdrawals/main.go index 3e5b55b3bce71..5163ca758b803 100644 --- a/op-chain-ops/cmd/withdrawals/main.go +++ b/op-chain-ops/cmd/withdrawals/main.go @@ -22,11 +22,14 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" ) // abiTrue represents the storage representation of the boolean @@ -116,6 +119,10 @@ func main() { Value: "bad-withdrawals.json", Usage: "Path to write JSON file of bad withdrawals to manually inspect", }, + &cli.StringFlag{ + Name: "storage-out", + Usage: "Path to write text file of L2ToL1MessagePasser storage", + }, }, Action: func(ctx *cli.Context) error { clients, err := util.NewClients(ctx) @@ -163,10 +170,11 @@ func main() { } outfile := ctx.String("bad-withdrawals-out") - f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o755) + f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) if err != nil { return err } + defer f.Close() // create a transactor opts, err := newTransactor(ctx) @@ -177,6 +185,28 @@ func main() { // Need this to compare in event parsing l1StandardBridgeAddress := common.HexToAddress(ctx.String("l1-standard-bridge-address")) + if storageOutfile := ctx.String("storage-out"); storageOutfile != "" { + ff, err := os.OpenFile(storageOutfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) + if err != nil { + return err + } + defer ff.Close() + + log.Info("Fetching storage for L2ToL1MessagePasser") + if storageRange, err := callStorageRange(clients, predeploys.L2ToL1MessagePasserAddr); err != nil { + log.Info("error getting storage range", "err", err) + } else { + str := "" + for key, value := range storageRange { + str += fmt.Sprintf("%s: %s\n", key.Hex(), value.Hex()) + } + _, err = ff.WriteString(str) + if err != nil { + return err + } + } + } + // iterate over all of the withdrawals and submit them for i, wd := range wds { log.Info("Processing withdrawal", "index", i) @@ -234,7 +264,7 @@ func main() { // successful messages can be skipped, received messages failed // their execution and should be replayed if isSuccessNew { - log.Info("Message already relayed", "index", i, "hash", hash, "slot", slot) + log.Info("Message already relayed", "index", i, "hash", hash.Hex(), "slot", slot.Hex()) continue } @@ -248,7 +278,7 @@ func main() { // the value should be set to a boolean in storage if !bytes.Equal(storageValue, abiTrue.Bytes()) { - return fmt.Errorf("storage slot %x not found in state", slot) + return fmt.Errorf("storage slot %x not found in state", slot.Hex()) } legacySlot, err := wd.StorageSlot() @@ -443,10 +473,48 @@ func callTrace(c *util.Clients, receipt *types.Receipt) (callFrame, error) { Tracer: &tracer, } err := c.L1RpcClient.Call(&finalizationTrace, "debug_traceTransaction", receipt.TxHash, traceConfig) + return finalizationTrace, err +} + +func callStorageRangeAt( + client *rpc.Client, + blockHash common.Hash, + txIndex int, + addr common.Address, + keyStart hexutil.Bytes, + maxResult int, +) (*eth.StorageRangeResult, error) { + var storageRange *eth.StorageRangeResult + err := client.Call(&storageRange, "debug_storageRangeAt", blockHash, txIndex, addr, keyStart, maxResult) + return storageRange, err +} + +func callStorageRange(c *util.Clients, addr common.Address) (state.Storage, error) { + header, err := c.L2Client.HeaderByNumber(context.Background(), nil) if err != nil { - return finalizationTrace, err + return nil, err } - return finalizationTrace, err + hash := header.Hash() + keyStart := hexutil.Bytes(common.Hash{}.Bytes()) + maxResult := 1000 + + ret := make(state.Storage) + + for { + result, err := callStorageRangeAt(c.L2RpcClient, hash, 0, addr, keyStart, maxResult) + if err != nil { + return nil, err + } + for key, value := range result.Storage { + ret[key] = value.Value + } + if result.NextKey == nil { + break + } else { + keyStart = hexutil.Bytes(result.NextKey.Bytes()) + } + } + return ret, nil } // handleFinalizeETHWithdrawal will ensure that the calldata is correct @@ -709,9 +777,13 @@ func newWithdrawals(ctx *cli.Context, l1ChainID *big.Int) ([]*crossdomain.Legacy witnessFile := ctx.String("witness-file") log.Debug("Migration data", "ovm-path", ovmMsgs, "evm-messages", evmMsgs, "witness-file", witnessFile) - ovmMessages, err := crossdomain.NewSentMessageFromJSON(ovmMsgs) - if err != nil { - return nil, err + var ovmMessages []*crossdomain.SentMessage + var err error + if ovmMsgs != "" { + ovmMessages, err = crossdomain.NewSentMessageFromJSON(ovmMsgs) + if err != nil { + return nil, err + } } // use empty ovmMessages if its not mainnet. The mainnet messages are diff --git a/op-chain-ops/crossdomain/migrate.go b/op-chain-ops/crossdomain/migrate.go index 741f353fb9e00..f0ebfae1681dc 100644 --- a/op-chain-ops/crossdomain/migrate.go +++ b/op-chain-ops/crossdomain/migrate.go @@ -96,17 +96,12 @@ func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger *com return w, nil } +// MigrateWithdrawalGasLimit computes the gas limit for the migrated withdrawal. func MigrateWithdrawalGasLimit(data []byte) uint64 { - // Compute the cost of the calldata - dataCost := uint64(0) - for _, b := range data { - if b == 0 { - dataCost += params.TxDataZeroGas - } else { - dataCost += params.TxDataNonZeroGasEIP2028 - } - } - + // Compute the upper bound on the gas limit. This could be more + // accurate if individual 0 bytes and non zero bytes were accounted + // for. + dataCost := uint64(len(data)) * params.TxDataNonZeroGasEIP2028 // Set the outer gas limit. This cannot be zero gasLimit := dataCost + 200_000 // Cap the gas limit to be 25 million to prevent creating withdrawals diff --git a/op-chain-ops/crossdomain/migrate_test.go b/op-chain-ops/crossdomain/migrate_test.go index 45a604eaeaae1..c14fadc8cdbae 100644 --- a/op-chain-ops/crossdomain/migrate_test.go +++ b/op-chain-ops/crossdomain/migrate_test.go @@ -71,15 +71,15 @@ func TestMigrateWithdrawalGasLimit(t *testing.T) { }, { input: []byte{0xff, 0x00}, - output: 200_000 + 16 + 4, + output: 200_000 + 16 + 16, }, { input: []byte{0x00}, - output: 200_000 + 4, + output: 200_000 + 16, }, { input: []byte{0x00, 0x00, 0x00}, - output: 200_000 + 4 + 4 + 4, + output: 200_000 + 16 + 16 + 16, }, } diff --git a/packages/sdk/src/utils/message-utils.ts b/packages/sdk/src/utils/message-utils.ts index 323734b1fdf0d..63f17635303e7 100644 --- a/packages/sdk/src/utils/message-utils.ts +++ b/packages/sdk/src/utils/message-utils.ts @@ -1,8 +1,10 @@ -import { hashWithdrawal, calldataCost } from '@eth-optimism/core-utils' -import { BigNumber } from 'ethers' +import { hashWithdrawal } from '@eth-optimism/core-utils' +import { BigNumber, utils } from 'ethers' import { LowLevelMessage } from '../interfaces' +const { hexDataLength } = utils + /** * Utility for hashing a LowLevelMessage object. * @@ -25,7 +27,7 @@ export const hashLowLevelMessage = (message: LowLevelMessage): string => { */ export const migratedWithdrawalGasLimit = (data: string): BigNumber => { // Compute the gas limit and cap at 25 million - const dataCost = calldataCost(data) + const dataCost = BigNumber.from(hexDataLength(data)).mul(16) let minGasLimit = dataCost.add(200_000) if (minGasLimit.gt(25_000_000)) { minGasLimit = BigNumber.from(25_000_000) diff --git a/packages/sdk/test/utils/message-utils.spec.ts b/packages/sdk/test/utils/message-utils.spec.ts index 718b7f6d4a59f..b1c2179499797 100644 --- a/packages/sdk/test/utils/message-utils.spec.ts +++ b/packages/sdk/test/utils/message-utils.spec.ts @@ -15,9 +15,9 @@ describe('Message Utils', () => { const tests = [ { input: '0x', result: BigNumber.from(200_000) }, { input: '0xff', result: BigNumber.from(200_000 + 16) }, - { input: '0xff00', result: BigNumber.from(200_000 + 16 + 4) }, - { input: '0x00', result: BigNumber.from(200_000 + 4) }, - { input: '0x000000', result: BigNumber.from(200_000 + 4 + 4 + 4) }, + { input: '0xff00', result: BigNumber.from(200_000 + 16 + 16) }, + { input: '0x00', result: BigNumber.from(200_000 + 16) }, + { input: '0x000000', result: BigNumber.from(200_000 + 16 + 16 + 16) }, ] for (const test of tests) {