diff --git a/op-chain-ops/cmd/deposit-hash/main.go b/op-chain-ops/cmd/deposit-hash/main.go index 9166a0667c090..cc52a96676528 100644 --- a/op-chain-ops/cmd/deposit-hash/main.go +++ b/op-chain-ops/cmd/deposit-hash/main.go @@ -32,8 +32,7 @@ func main() { for _, ethLog := range l1Receipt.Logs { if ethLog.Topics[0].String() == depositLogTopic.String() { - - reconstructedDep, err := derive.UnmarshalDepositLogEvent(ethLog) + reconstructedDep, err := derive.UnmarshalDepositLogEventIgnoreNonce(ethLog) if err != nil { log.Crit("Failed to parse deposit event ", "err", err) } diff --git a/op-e2e/actions/helpers/user.go b/op-e2e/actions/helpers/user.go index d7215b80650f7..7ab973ce6a839 100644 --- a/op-e2e/actions/helpers/user.go +++ b/op-e2e/actions/helpers/user.go @@ -393,7 +393,7 @@ func (s *CrossLayerUser) CheckDepositTx(t Testing, l1TxHash common.Hash, index i require.False(t, l2Success) } else { require.Less(t, index, len(depositReceipt.Logs), "must have enough logs in receipt") - reconstructedDep, err := derive.UnmarshalDepositLogEvent(depositReceipt.Logs[index]) + reconstructedDep, err := derive.UnmarshalDepositLogEventIgnoreNonce(depositReceipt.Logs[index]) require.NoError(t, err, "Could not reconstruct L2 Deposit") l2Tx := types.NewTx(reconstructedDep) s.L2.CheckReceipt(t, l2Success, l2Tx.Hash()) diff --git a/op-e2e/system/bridge/bridge_test.go b/op-e2e/system/bridge/bridge_test.go index 655cf612c9ad4..8ce73cf26f4cb 100644 --- a/op-e2e/system/bridge/bridge_test.go +++ b/op-e2e/system/bridge/bridge_test.go @@ -102,7 +102,7 @@ func TestERC20BridgeDeposits(t *testing.T) { depositEvent, err := receipts.FindLog(depositReceipt.Logs, portal.ParseTransactionDeposited) require.NoError(t, err, "Should emit deposit event") - depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw) + depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw) require.NoError(t, err) _, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash()) require.NoError(t, err) diff --git a/op-e2e/system/gastoken/gastoken_test.go b/op-e2e/system/gastoken/gastoken_test.go index bde29ddfa52c9..0f9f1c2c0df2b 100644 --- a/op-e2e/system/gastoken/gastoken_test.go +++ b/op-e2e/system/gastoken/gastoken_test.go @@ -255,7 +255,7 @@ func setCustomGasToken(t *testing.T, cfg e2esys.SystemConfig, sys *e2esys.System depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited) require.NoError(t, err, "Should emit deposit event") - depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw) + depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw) require.NoError(t, err) l2Client := sys.NodeClient("sequencer") @@ -326,7 +326,7 @@ func checkDeposit(t *testing.T, gto gasTokenTestOpts, enabled bool) { // compute the deposit transaction hash + poll for it depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited) require.NoError(t, err, "Should emit deposit event") - depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw) + depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw) require.NoError(t, err) _, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash()) require.NoError(t, err) @@ -496,7 +496,7 @@ func checkFeeWithdrawal(t *testing.T, gto gasTokenTestOpts, enabled bool) { // Compute the deposit transaction hash + poll for it depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited) require.NoError(t, err, "Should emit deposit event") - depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw) + depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw) require.NoError(t, err) _, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash()) require.NoError(t, err) diff --git a/op-e2e/system/helpers/tx_helper.go b/op-e2e/system/helpers/tx_helper.go index 10c16c0e74656..69d603a1e213d 100644 --- a/op-e2e/system/helpers/tx_helper.go +++ b/op-e2e/system/helpers/tx_helper.go @@ -53,7 +53,7 @@ func SendDepositTx(t *testing.T, cfg e2esys.SystemConfig, l1Client *ethclient.Cl t.Logf("SendDepositTx: included on L1") // Wait for transaction to be included on L2 - reconstructedDep, err := derive.UnmarshalDepositLogEvent(l1Receipt.Logs[0]) + reconstructedDep, err := derive.UnmarshalDepositLogEventIgnoreNonce(l1Receipt.Logs[0]) require.NoError(t, err, "Could not reconstruct L2 Deposit") tx = types.NewTx(reconstructedDep) l2Receipt, err := wait.ForReceipt(ctx, l2Client, tx.Hash(), l2Opts.ExpectedStatus) diff --git a/op-node/rollup/derive/attributes.go b/op-node/rollup/derive/attributes.go index 5d859a9fcd596..cb9584dcdc1be 100644 --- a/op-node/rollup/derive/attributes.go +++ b/op-node/rollup/derive/attributes.go @@ -65,38 +65,35 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex // case we need to fetch all transaction receipts from the L1 origin block so we can scan for // user deposits. if l2Parent.L1Origin.Number != epoch.Number { - info, receipts, err := ba.l1.FetchReceipts(ctx, epoch.Hash) - if err != nil { + var receipts types.Receipts + if l1Info, receipts, err = ba.l1.FetchReceipts(ctx, epoch.Hash); err != nil { return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info and receipts: %w", err)) } - if l2Parent.L1Origin.Hash != info.ParentHash() { + if l2Parent.L1Origin.Hash != l1Info.ParentHash() { return nil, NewResetError( fmt.Errorf("cannot create new block with L1 origin %s (parent %s) on top of L1 origin %s", - epoch, info.ParentHash(), l2Parent.L1Origin)) + epoch, l1Info.ParentHash(), l2Parent.L1Origin)) } - deposits, err := DeriveDeposits(receipts, ba.rollupCfg.DepositContractAddress) - if err != nil { + if depositTxs, sysConfig.DepositNonce, err = DeriveDeposits(receipts, ba.rollupCfg.DepositContractAddress, sysConfig.DepositNonce); err != nil { // deposits may never be ignored. Failing to process them is a critical error. return nil, NewCriticalError(fmt.Errorf("failed to derive some deposits: %w", err)) } + // apply sysCfg changes - if err := UpdateSystemConfigWithL1Receipts(&sysConfig, receipts, ba.rollupCfg, info.Time()); err != nil { + if err = UpdateSystemConfigWithL1Receipts(&sysConfig, receipts, ba.rollupCfg, l1Info.Time()); err != nil { return nil, NewCriticalError(fmt.Errorf("failed to apply derived L1 sysCfg updates: %w", err)) } - l1Info = info - depositTxs = deposits seqNumber = 0 } else { if l2Parent.L1Origin.Hash != epoch.Hash { return nil, NewResetError(fmt.Errorf("cannot create new block with L1 origin %s in conflict with L1 origin %s", epoch, l2Parent.L1Origin)) } - info, err := ba.l1.InfoByHash(ctx, epoch.Hash) + l1Info, err = ba.l1.InfoByHash(ctx, epoch.Hash) if err != nil { return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info: %w", err)) } - l1Info = info depositTxs = nil seqNumber = l2Parent.SequenceNumber + 1 } diff --git a/op-node/rollup/derive/deposit_log.go b/op-node/rollup/derive/deposit_log.go index 74fde28c59882..b20721ad6a428 100644 --- a/op-node/rollup/derive/deposit_log.go +++ b/op-node/rollup/derive/deposit_log.go @@ -17,9 +17,26 @@ import ( var ( DepositEventABI = "TransactionDeposited(address,address,uint256,bytes)" DepositEventABIHash = crypto.Keccak256Hash([]byte(DepositEventABI)) - DepositEventVersion0 = common.Hash{} + DepositEventVersion0 = uint64(0) + DepositEventVersion1 = uint64(1) ) +// UnmarshalDepositLogEventIgnoreNonce decodes an EVM log entry emitted by the deposit contract into typed deposit data. +// Same as UnmarshalDepositLogEvent, but it force-ensures that the deposit nonce is correct. +// This is useful for testing or non-consensus applications. +func UnmarshalDepositLogEventIgnoreNonce(ev *types.Log) (*types.DepositTx, error) { + previousNonce := uint64(0) + if len(ev.Topics) == 4 { + nonce, version := UnpackNonceAndVersion(ev.Topics[3]) + switch version { + case DepositEventVersion1: + previousNonce = nonce - 1 + } + } + dep, _, err := UnmarshalDepositLogEvent(ev, previousNonce) + return dep, err +} + // UnmarshalDepositLogEvent decodes an EVM log entry emitted by the deposit contract into typed deposit data. // // parse log data for: @@ -27,23 +44,23 @@ var ( // event TransactionDeposited( // address indexed from, // address indexed to, -// uint256 indexed version, +// uint256 indexed nonceAndVersion, // bytes opaqueData // ); // // Additionally, the event log-index and -func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) { +func UnmarshalDepositLogEvent(ev *types.Log, currentNonce uint64) (*types.DepositTx, uint64, error) { if len(ev.Topics) != 4 { - return nil, fmt.Errorf("expected 4 event topics (event identity, indexed from, indexed to, indexed version), got %d", len(ev.Topics)) + return nil, currentNonce, fmt.Errorf("expected 4 event topics (event identity, indexed from, indexed to, indexed version), got %d", len(ev.Topics)) } if ev.Topics[0] != DepositEventABIHash { - return nil, fmt.Errorf("invalid deposit event selector: %s, expected %s", ev.Topics[0], DepositEventABIHash) + return nil, currentNonce, fmt.Errorf("invalid deposit event selector: %s, expected %s", ev.Topics[0], DepositEventABIHash) } if len(ev.Data) < 64 { - return nil, fmt.Errorf("incomplate opaqueData slice header (%d bytes): %x", len(ev.Data), ev.Data) + return nil, currentNonce, fmt.Errorf("incomplate opaqueData slice header (%d bytes): %x", len(ev.Data), ev.Data) } if len(ev.Data)%32 != 0 { - return nil, fmt.Errorf("expected log data to be multiple of 32 bytes: got %d bytes", len(ev.Data)) + return nil, currentNonce, fmt.Errorf("expected log data to be multiple of 32 bytes: got %d bytes", len(ev.Data)) } // indexed 0 @@ -51,7 +68,7 @@ func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) { // indexed 1 to := common.BytesToAddress(ev.Topics[2][12:]) // indexed 2 - version := ev.Topics[3] + nonceAndVersion := ev.Topics[3] // unindexed data // Solidity serializes the event's Data field as follows: // abi.encode(abi.encodPacked(uint256 mint, uint256 value, uint64 gasLimit, uint8 isCreation, bytes data)) @@ -60,14 +77,14 @@ func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) { var opaqueContentOffset uint256.Int opaqueContentOffset.SetBytes(ev.Data[0:32]) if !opaqueContentOffset.IsUint64() || opaqueContentOffset.Uint64() != 32 { - return nil, fmt.Errorf("invalid opaqueData slice header offset: %d", opaqueContentOffset.Uint64()) + return nil, currentNonce, fmt.Errorf("invalid opaqueData slice header offset: %d", opaqueContentOffset.Uint64()) } // The next 32 bytes indicate the length of the opaqueData content. var opaqueContentLength uint256.Int opaqueContentLength.SetBytes(ev.Data[32:64]) // Make sure the length is an uint64, it's not larger than the remaining data, and the log is using minimal padding (i.e. can't add 32 bytes without exceeding data) if !opaqueContentLength.IsUint64() || opaqueContentLength.Uint64() > uint64(len(ev.Data)-64) || opaqueContentLength.Uint64()+32 <= uint64(len(ev.Data)-64) { - return nil, fmt.Errorf("invalid opaqueData slice header length: %d", opaqueContentLength.Uint64()) + return nil, currentNonce, fmt.Errorf("invalid opaqueData slice header length: %d", opaqueContentLength.Uint64()) } // The remaining data is the opaqueData which is tightly packed // and then padded to 32 bytes by the EVM. @@ -83,17 +100,39 @@ func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) { dep.From = from dep.IsSystemTransaction = false + newNonce, version := UnpackNonceAndVersion(nonceAndVersion) + var err error switch version { case DepositEventVersion0: err = unmarshalDepositVersion0(&dep, to, opaqueData) + case DepositEventVersion1: + if newNonce != currentNonce+1 { + return nil, currentNonce, fmt.Errorf("invalid deposit nonce, expected %d got %d", currentNonce+1, newNonce) + } + err = unmarshalDepositVersion1(&dep, to, opaqueData) default: - return nil, fmt.Errorf("invalid deposit version, got %s", version) + return nil, currentNonce, fmt.Errorf("invalid deposit version, got %d", version) } if err != nil { - return nil, fmt.Errorf("failed to decode deposit (version %s): %w", version, err) + return nil, currentNonce, fmt.Errorf("failed to decode deposit (version %d): %w", version, err) } - return &dep, nil + return &dep, newNonce, nil +} + +func UnpackNonceAndVersion(nonceAndVersion common.Hash) (nonce uint64, version uint64) { + i := new(big.Int).SetBytes(nonceAndVersion[:]) + mask64 := new(big.Int).SetBytes(common.Hex2Bytes("ffffffffffffffff")) + version = mask64.And(mask64, i).Uint64() + nonce = i.Lsh(i, 128).Uint64() + return +} + +func PackNonceAndVersion(nonce uint64, version uint64) common.Hash { + i := new(big.Int).SetUint64(nonce) + i.Rsh(i, 128) + i.Or(i, new(big.Int).SetUint64(version)) + return common.BytesToHash(i.Bytes()) } func unmarshalDepositVersion0(dep *types.DepositTx, to common.Address, opaqueData []byte) error { @@ -140,6 +179,11 @@ func unmarshalDepositVersion0(dep *types.DepositTx, to common.Address, opaqueDat return nil } +func unmarshalDepositVersion1(dep *types.DepositTx, to common.Address, opaqueData []byte) error { + // version 1 simply adds a nonce; the rest is the same + return unmarshalDepositVersion0(dep, to, opaqueData) +} + // MarshalDepositLogEvent returns an EVM log entry that encodes a TransactionDeposited event from the deposit contract. // This is the reverse of the deposit transaction derivation. func MarshalDepositLogEvent(depositContractAddr common.Address, deposit *types.DepositTx) (*types.Log, error) { @@ -151,7 +195,7 @@ func MarshalDepositLogEvent(depositContractAddr common.Address, deposit *types.D DepositEventABIHash, eth.AddressAsLeftPaddedHash(deposit.From), toBytes, - DepositEventVersion0, + PackNonceAndVersion(0, DepositEventVersion0), } data := make([]byte, 64, 64+3*32) diff --git a/op-node/rollup/derive/deposit_log_test.go b/op-node/rollup/derive/deposit_log_test.go index 4cb335b1bef48..03a47f8a4dc5b 100644 --- a/op-node/rollup/derive/deposit_log_test.go +++ b/op-node/rollup/derive/deposit_log_test.go @@ -31,7 +31,7 @@ func TestUnmarshalLogEvent(t *testing.T) { log.TxIndex = uint(rng.Intn(10000)) log.Index = uint(source.LogIndex) log.BlockHash = source.L1BlockHash - depOutput, err := UnmarshalDepositLogEvent(log) + depOutput, _, err := UnmarshalDepositLogEvent(log, 0) if err != nil { t.Fatal(err) } @@ -121,7 +121,7 @@ func TestDeriveUserDeposits(t *testing.T) { if err != nil { t.Fatal(err) } - got, err := UserDeposits(receipts, MockDepositContractAddr) + got, _, err := UserDeposits(receipts, MockDepositContractAddr, 0) require.NoError(t, err) require.Equal(t, len(got), len(expectedDeposits)) for d, depTx := range got { diff --git a/op-node/rollup/derive/deposit_log_tob_test.go b/op-node/rollup/derive/deposit_log_tob_test.go index 49721104b0867..ee172644bbf1d 100644 --- a/op-node/rollup/derive/deposit_log_tob_test.go +++ b/op-node/rollup/derive/deposit_log_tob_test.go @@ -144,7 +144,7 @@ func FuzzDeriveDepositsRoundTrip(f *testing.F) { receipts, expectedDeposits := fuzzReceipts(typeProvider, blockHash, MockDepositContractAddr) // Derive our user deposits from the transaction receipts - derivedDeposits, err := UserDeposits(receipts, MockDepositContractAddr) + derivedDeposits, _, err := UserDeposits(receipts, MockDepositContractAddr, 0) require.NoError(t, err) // Ensure all deposits we derived matched what we expected to receive. @@ -188,13 +188,13 @@ func FuzzDeriveDepositsBadVersion(f *testing.F) { // Generate any topic but the deposit event versions we support. // TODO: As opposed to keeping this hardcoded, a method such as IsValidVersion(v) should be // used here. - badTopic := DepositEventVersion0 - for badTopic == DepositEventVersion0 { - typeProvider.Fuzz(&badTopic) + badVersion := DepositEventVersion0 + for badVersion == DepositEventVersion0 { + typeProvider.Fuzz(&badVersion) } // Set our bad topic and update our state - log.Topics[3] = badTopic + log.Topics[3] = PackNonceAndVersion(0, badVersion) hasBadDepositVersion = true } } @@ -202,7 +202,7 @@ func FuzzDeriveDepositsBadVersion(f *testing.F) { } // Derive our user deposits from the transaction receipts - _, err := UserDeposits(receipts, MockDepositContractAddr) + _, _, err := UserDeposits(receipts, MockDepositContractAddr, 0) // If we patched a bad deposit version this iteration, we should expect an error and not be able to proceed // further diff --git a/op-node/rollup/derive/deposits.go b/op-node/rollup/derive/deposits.go index b71cd619678c0..10fbb5f3f328a 100644 --- a/op-node/rollup/derive/deposits.go +++ b/op-node/rollup/derive/deposits.go @@ -11,7 +11,7 @@ import ( ) // UserDeposits transforms the L2 block-height and L1 receipts into the transaction inputs for a full L2 block -func UserDeposits(receipts []*types.Receipt, depositContractAddr common.Address) ([]*types.DepositTx, error) { +func UserDeposits(receipts []*types.Receipt, depositContractAddr common.Address, currentNonce uint64) ([]*types.DepositTx, uint64, error) { var out []*types.DepositTx var result error for i, rec := range receipts { @@ -20,7 +20,8 @@ func UserDeposits(receipts []*types.Receipt, depositContractAddr common.Address) } for j, log := range rec.Logs { if log.Address == depositContractAddr && len(log.Topics) > 0 && log.Topics[0] == DepositEventABIHash { - dep, err := UnmarshalDepositLogEvent(log) + dep, newNonce, err := UnmarshalDepositLogEvent(log, currentNonce) + currentNonce = newNonce if err != nil { result = multierror.Append(result, fmt.Errorf("malformatted L1 deposit log in receipt %d, log %d: %w", i, j, err)) } else { @@ -29,12 +30,13 @@ func UserDeposits(receipts []*types.Receipt, depositContractAddr common.Address) } } } - return out, result + return out, currentNonce, result } -func DeriveDeposits(receipts []*types.Receipt, depositContractAddr common.Address) ([]hexutil.Bytes, error) { +func DeriveDeposits(receipts []*types.Receipt, depositContractAddr common.Address, currentNonce uint64) ([]hexutil.Bytes, uint64, error) { var result error - userDeposits, err := UserDeposits(receipts, depositContractAddr) + userDeposits, newNonce, err := UserDeposits(receipts, depositContractAddr, currentNonce) + currentNonce = newNonce if err != nil { result = multierror.Append(result, err) } @@ -47,5 +49,5 @@ func DeriveDeposits(receipts []*types.Receipt, depositContractAddr common.Addres encodedTxs = append(encodedTxs, opaqueTx) } } - return encodedTxs, result + return encodedTxs, currentNonce, result } diff --git a/op-node/rollup/derive/fuzz_parsers_test.go b/op-node/rollup/derive/fuzz_parsers_test.go index afb7e0850e262..abc5c70f5f3ff 100644 --- a/op-node/rollup/derive/fuzz_parsers_test.go +++ b/op-node/rollup/derive/fuzz_parsers_test.go @@ -299,7 +299,7 @@ func FuzzUnmarshallLogEvent(f *testing.F) { depositEvent.Raw = types.Log{} // Clear out the log // Verify that is passes our custom unmarshalling logic - dep, err := UnmarshalDepositLogEvent(logs[0]) + dep, _, err := UnmarshalDepositLogEvent(logs[0], 0) if err != nil { t.Fatalf("Could not unmarshal log that was emitted by the deposit contract: %v", err) } diff --git a/op-node/rollup/derive/l1_block_info.go b/op-node/rollup/derive/l1_block_info.go index a01fe5bca6b91..7fdab56dd34b0 100644 --- a/op-node/rollup/derive/l1_block_info.go +++ b/op-node/rollup/derive/l1_block_info.go @@ -20,12 +20,14 @@ import ( const ( L1InfoFuncBedrockSignature = "setL1BlockValues(uint64,uint64,uint256,bytes32,uint64,bytes32,uint256,uint256)" L1InfoFuncEcotoneSignature = "setL1BlockValuesEcotone()" + L1InfoFuncIsthmusSignature = "setL1BlockValuesIsthmus()" L1InfoFuncInteropSignature = "setL1BlockValuesInterop()" DepositsCompleteSignature = "depositsComplete()" L1InfoArguments = 8 L1InfoBedrockLen = 4 + 32*L1InfoArguments - L1InfoEcotoneLen = 4 + 32*5 // after Ecotone upgrade, args are packed into 5 32-byte slots - DepositsCompleteLen = 4 // only the selector + L1InfoEcotoneLen = 4 + 32*5 // after Ecotone upgrade, args are packed into 5 32-byte slots + L1InfoIsthmusLen = 4 + 32*5 + 16 // after Isthmus upgrade, args are packed into 5 32-byte slots and 1 16-byte slot + DepositsCompleteLen = 4 // only the selector // DepositsCompleteGas allocates 21k gas for intrinsic tx costs, and // an additional 15k to ensure that the DepositsComplete call does not run out of gas. // GasBenchMark_L1BlockInterop_DepositsComplete:test_depositsComplete_benchmark() (gas: 7768) @@ -37,6 +39,7 @@ const ( var ( L1InfoFuncBedrockBytes4 = crypto.Keccak256([]byte(L1InfoFuncBedrockSignature))[:4] L1InfoFuncEcotoneBytes4 = crypto.Keccak256([]byte(L1InfoFuncEcotoneSignature))[:4] + L1InfoFuncIsthmusBytes4 = crypto.Keccak256([]byte(L1InfoFuncIsthmusSignature))[:4] L1InfoFuncInteropBytes4 = crypto.Keccak256([]byte(L1InfoFuncInteropSignature))[:4] DepositsCompleteBytes4 = crypto.Keccak256([]byte(DepositsCompleteSignature))[:4] L1InfoDepositerAddress = common.HexToAddress("0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001") @@ -66,6 +69,9 @@ type L1BlockInfo struct { BlobBaseFee *big.Int // added by Ecotone upgrade BaseFeeScalar uint32 // added by Ecotone upgrade BlobBaseFeeScalar uint32 // added by Ecotone upgrade + + DepositNonce uint64 // added by the Isthmus upgrade + ConfigUpdateNonce uint64 // added by the Isthmus upgrade } // Bedrock Binary Format @@ -155,7 +161,23 @@ func (info *L1BlockInfo) unmarshalBinaryBedrock(data []byte) error { return nil } -// Interop & Ecotone Binary Format +// Ecotone Binary Format +// +---------+--------------------------+ +// | Bytes | Field | +// +---------+--------------------------+ +// | 4 | Function signature | +// | 4 | BaseFeeScalar | +// | 4 | BlobBaseFeeScalar | +// | 8 | SequenceNumber | +// | 8 | Timestamp | +// | 8 | L1BlockNumber | +// | 32 | BaseFee | +// | 32 | BlobBaseFee | +// | 32 | BlockHash | +// | 32 | BatcherHash | +// +---------+--------------------------+ + +// Isthmus & Interop Binary Format // +---------+--------------------------+ // | Bytes | Field | // +---------+--------------------------+ @@ -169,26 +191,41 @@ func (info *L1BlockInfo) unmarshalBinaryBedrock(data []byte) error { // | 32 | BlobBaseFee | // | 32 | BlockHash | // | 32 | BatcherHash | +// | 8 | DepositNonce | +// | 8 | ConfigUpdateNonce | // +---------+--------------------------+ func (info *L1BlockInfo) marshalBinaryEcotone() ([]byte, error) { - out, err := marshalBinaryWithSignature(info, L1InfoFuncEcotoneBytes4) + out, err := marshalBinaryWithSignature(info, L1InfoFuncEcotoneBytes4, false) if err != nil { return nil, fmt.Errorf("failed to marshal Ecotone l1 block info: %w", err) } return out, nil } +func (info *L1BlockInfo) marshalBinaryIsthmus() ([]byte, error) { + out, err := marshalBinaryWithSignature(info, L1InfoFuncIsthmusBytes4, true) + if err != nil { + return nil, fmt.Errorf("failed to marshal Isthmus l1 block info: %w", err) + } + return out, nil +} + func (info *L1BlockInfo) marshalBinaryInterop() ([]byte, error) { - out, err := marshalBinaryWithSignature(info, L1InfoFuncInteropBytes4) + out, err := marshalBinaryWithSignature(info, L1InfoFuncInteropBytes4, true) if err != nil { return nil, fmt.Errorf("failed to marshal Interop l1 block info: %w", err) } return out, nil } -func marshalBinaryWithSignature(info *L1BlockInfo, signature []byte) ([]byte, error) { - w := bytes.NewBuffer(make([]byte, 0, L1InfoEcotoneLen)) // Ecotone and Interop have the same length +func marshalBinaryWithSignature(info *L1BlockInfo, signature []byte, includeNonces bool) ([]byte, error) { + var w *bytes.Buffer + if includeNonces { + w = bytes.NewBuffer(make([]byte, 0, L1InfoIsthmusLen)) // Isthmus and Interop have the same length + } else { + w = bytes.NewBuffer(make([]byte, 0, L1InfoEcotoneLen)) + } if err := solabi.WriteSignature(w, signature); err != nil { return nil, err } @@ -224,19 +261,35 @@ func marshalBinaryWithSignature(info *L1BlockInfo, signature []byte) ([]byte, er if err := solabi.WriteAddress(w, info.BatcherAddr); err != nil { return nil, err } + if includeNonces { + if err := binary.Write(w, binary.BigEndian, info.DepositNonce); err != nil { + return nil, err + } + if err := binary.Write(w, binary.BigEndian, info.ConfigUpdateNonce); err != nil { + return nil, err + } + } return w.Bytes(), nil } func (info *L1BlockInfo) unmarshalBinaryEcotone(data []byte) error { - return unmarshalBinaryWithSignatureAndData(info, L1InfoFuncEcotoneBytes4, data) + return unmarshalBinaryWithSignatureAndData(info, L1InfoFuncEcotoneBytes4, data, false) +} + +func (info *L1BlockInfo) unmarshalBinaryIsthmus(data []byte) error { + return unmarshalBinaryWithSignatureAndData(info, L1InfoFuncIsthmusBytes4, data, true) } func (info *L1BlockInfo) unmarshalBinaryInterop(data []byte) error { - return unmarshalBinaryWithSignatureAndData(info, L1InfoFuncInteropBytes4, data) + return unmarshalBinaryWithSignatureAndData(info, L1InfoFuncInteropBytes4, data, true) } -func unmarshalBinaryWithSignatureAndData(info *L1BlockInfo, signature []byte, data []byte) error { - if len(data) != L1InfoEcotoneLen { +func unmarshalBinaryWithSignatureAndData(info *L1BlockInfo, signature []byte, data []byte, includeNonces bool) error { + expectedLength := L1InfoEcotoneLen + if includeNonces { + expectedLength = L1InfoIsthmusLen + } + if len(data) != expectedLength { return fmt.Errorf("data is unexpected length: %d", len(data)) } r := bytes.NewReader(data) @@ -273,6 +326,14 @@ func unmarshalBinaryWithSignatureAndData(info *L1BlockInfo, signature []byte, da if info.BatcherAddr, err = solabi.ReadAddress(r); err != nil { return err } + if includeNonces { + if err := binary.Read(r, binary.BigEndian, &info.DepositNonce); err != nil { + return ErrInvalidFormat + } + if err := binary.Read(r, binary.BigEndian, &info.ConfigUpdateNonce); err != nil { + return ErrInvalidFormat + } + } if !solabi.EmptyReader(r) { return errors.New("too many bytes") } @@ -285,6 +346,12 @@ func isEcotoneButNotFirstBlock(rollupCfg *rollup.Config, l2Timestamp uint64) boo return rollupCfg.IsEcotone(l2Timestamp) && !rollupCfg.IsEcotoneActivationBlock(l2Timestamp) } +// isIsthmusButNotFirstBlock returns whether the specified block is subject to the Isthmus upgrade, +// but is not the activation block itself. +func isIsthmusButNotFirstBlock(rollupCfg *rollup.Config, l2Timestamp uint64) bool { + return rollupCfg.IsIsthmus(l2Timestamp) && !rollupCfg.IsIsthmusActivationBlock(l2Timestamp) +} + // isInteropButNotFirstBlock returns whether the specified block is subject to the Interop upgrade, // but is not the activation block itself. func isInteropButNotFirstBlock(rollupCfg *rollup.Config, l2Timestamp uint64) bool { @@ -302,6 +369,9 @@ func L1BlockInfoFromBytes(rollupCfg *rollup.Config, l2BlockTime uint64, data []b if isInteropButNotFirstBlock(rollupCfg, l2BlockTime) { return &info, info.unmarshalBinaryInterop(data) } + if isIsthmusButNotFirstBlock(rollupCfg, l2BlockTime) { + return &info, info.unmarshalBinaryIsthmus(data) + } if isEcotoneButNotFirstBlock(rollupCfg, l2BlockTime) { return &info, info.unmarshalBinaryEcotone(data) } @@ -332,27 +402,34 @@ func L1InfoDeposit(rollupCfg *rollup.Config, sysCfg eth.SystemConfig, seqNumber } l1BlockInfo.BlobBaseFeeScalar = scalars.BlobBaseFeeScalar l1BlockInfo.BaseFeeScalar = scalars.BaseFeeScalar - if isInteropButNotFirstBlock(rollupCfg, l2Timestamp) { - out, err := l1BlockInfo.marshalBinaryInterop() - if err != nil { - return nil, fmt.Errorf("failed to marshal Interop l1 block info: %w", err) + if isIsthmusButNotFirstBlock(rollupCfg, l2Timestamp) { + l1BlockInfo.DepositNonce = sysCfg.DepositNonce + l1BlockInfo.ConfigUpdateNonce = sysCfg.ConfigUpdateNonce + if isInteropButNotFirstBlock(rollupCfg, l2Timestamp) { + data, err = l1BlockInfo.marshalBinaryInterop() + if err != nil { + return nil, fmt.Errorf("failed to marshal Interop l1 block info: %w", err) + } + } else { + data, err = l1BlockInfo.marshalBinaryIsthmus() + if err != nil { + return nil, fmt.Errorf("failed to marshal Isthmus l1 block info: %w", err) + } } - data = out } else { - out, err := l1BlockInfo.marshalBinaryEcotone() + data, err = l1BlockInfo.marshalBinaryEcotone() if err != nil { return nil, fmt.Errorf("failed to marshal Ecotone l1 block info: %w", err) } - data = out } } else { l1BlockInfo.L1FeeOverhead = sysCfg.Overhead l1BlockInfo.L1FeeScalar = sysCfg.Scalar - out, err := l1BlockInfo.marshalBinaryBedrock() + var err error + data, err = l1BlockInfo.marshalBinaryBedrock() if err != nil { return nil, fmt.Errorf("failed to marshal Bedrock l1 block info: %w", err) } - data = out } source := L1InfoDepositSource{ diff --git a/op-node/rollup/derive/payload_util.go b/op-node/rollup/derive/payload_util.go index 6da1a952b5fe1..2d304d9ae27b9 100644 --- a/op-node/rollup/derive/payload_util.go +++ b/op-node/rollup/derive/payload_util.go @@ -85,10 +85,12 @@ func PayloadToSystemConfig(rollupCfg *rollup.Config, payload *eth.ExecutionPaylo binary.BigEndian.PutUint32(info.L1FeeScalar[28:32], info.BaseFeeScalar) } r := eth.SystemConfig{ - BatcherAddr: info.BatcherAddr, - Overhead: info.L1FeeOverhead, - Scalar: info.L1FeeScalar, - GasLimit: uint64(payload.GasLimit), + BatcherAddr: info.BatcherAddr, + Overhead: info.L1FeeOverhead, + Scalar: info.L1FeeScalar, + GasLimit: uint64(payload.GasLimit), + DepositNonce: info.DepositNonce, + ConfigUpdateNonce: info.ConfigUpdateNonce, } if rollupCfg.IsHolocene(uint64(payload.Timestamp)) { if err := eip1559.ValidateHoloceneExtraData(payload.ExtraData); err != nil { diff --git a/op-node/rollup/derive/system_config.go b/op-node/rollup/derive/system_config.go index 72c4e713c30e9..1c38c4e577225 100644 --- a/op-node/rollup/derive/system_config.go +++ b/op-node/rollup/derive/system_config.go @@ -27,7 +27,8 @@ var ( var ( ConfigUpdateEventABI = "ConfigUpdate(uint256,uint8,bytes)" ConfigUpdateEventABIHash = crypto.Keccak256Hash([]byte(ConfigUpdateEventABI)) - ConfigUpdateEventVersion0 = common.Hash{} + ConfigUpdateEventVersion0 = uint64(0) + ConfigUpdateEventVersion1 = uint64(1) ) // UpdateSystemConfigWithL1Receipts filters all L1 receipts to find config updates and applies the config updates to the given sysCfg @@ -39,7 +40,8 @@ func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*type } for j, log := range rec.Logs { if log.Address == cfg.L1SystemConfigAddress && len(log.Topics) > 0 && log.Topics[0] == ConfigUpdateEventABIHash { - if err := ProcessSystemConfigUpdateLogEvent(sysCfg, log, cfg, l1Time); err != nil { + var err error + if err = ProcessSystemConfigUpdateLogEvent(sysCfg, log, cfg, l1Time); err != nil { result = multierror.Append(result, fmt.Errorf("malformatted L1 system sysCfg log in receipt %d, log %d: %w", i, j, err)) } } @@ -53,7 +55,7 @@ func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*type // parse log data for: // // event ConfigUpdate( -// uint256 indexed version, +// uint256 indexed nonceAndVersion, // UpdateType indexed updateType, // bytes data // ); @@ -66,10 +68,18 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L } // indexed 0 - version := ev.Topics[1] - if version != ConfigUpdateEventVersion0 { - return fmt.Errorf("unrecognized SystemConfig update event version: %s", version) + newNonce, version := UnpackNonceAndVersion(ev.Topics[1]) + switch version { + case ConfigUpdateEventVersion0: + case ConfigUpdateEventVersion1: + if newNonce != destSysCfg.ConfigUpdateNonce+1 { + return fmt.Errorf("invalid config update nonce, expected %d got %d", destSysCfg.ConfigUpdateNonce+1, newNonce) + } + destSysCfg.ConfigUpdateNonce = newNonce + default: + return fmt.Errorf("unrecognized SystemConfig update event version: %d", version) } + // indexed 1 updateType := ev.Topics[2] diff --git a/op-node/rollup/derive/system_config_test.go b/op-node/rollup/derive/system_config_test.go index add1790938796..e9530af628287 100644 --- a/op-node/rollup/derive/system_config_test.go +++ b/op-node/rollup/derive/system_config_test.go @@ -57,7 +57,7 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { log: &types.Log{ Topics: []common.Hash{ ConfigUpdateEventABIHash, - ConfigUpdateEventVersion0, + PackNonceAndVersion(0, ConfigUpdateEventVersion0), SystemConfigUpdateUnsafeBlockSigner, }, }, @@ -77,7 +77,7 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { log: &types.Log{ Topics: []common.Hash{ ConfigUpdateEventABIHash, - ConfigUpdateEventVersion0, + PackNonceAndVersion(0, ConfigUpdateEventVersion0), SystemConfigUpdateBatcher, }, }, @@ -101,7 +101,7 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { log: &types.Log{ Topics: []common.Hash{ ConfigUpdateEventABIHash, - ConfigUpdateEventVersion0, + PackNonceAndVersion(0, ConfigUpdateEventVersion0), SystemConfigUpdateFeeScalars, }, }, @@ -127,7 +127,7 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { log: &types.Log{ Topics: []common.Hash{ ConfigUpdateEventABIHash, - ConfigUpdateEventVersion0, + PackNonceAndVersion(0, ConfigUpdateEventVersion0), SystemConfigUpdateGasLimit, }, }, @@ -151,7 +151,7 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { log: &types.Log{ Topics: []common.Hash{ ConfigUpdateEventABIHash, - ConfigUpdateEventVersion0, + PackNonceAndVersion(0, ConfigUpdateEventVersion0), SystemConfigUpdateFeeScalars, }, }, @@ -191,7 +191,7 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { log: &types.Log{ Topics: []common.Hash{ ConfigUpdateEventABIHash, - ConfigUpdateEventVersion0, + PackNonceAndVersion(0, ConfigUpdateEventVersion0), SystemConfigUpdateEIP1559Params, }, }, diff --git a/op-service/eth/types.go b/op-service/eth/types.go index 122e9a4df7833..0de9e97d54c26 100644 --- a/op-service/eth/types.go +++ b/op-service/eth/types.go @@ -455,6 +455,10 @@ type SystemConfig struct { // value will be 0 if Holocene is not active, or if derivation has yet to // process any EIP_1559_PARAMS system config update events. EIP1559Params Bytes8 `json:"eip1559Params"` + // DepositNonce identifies the nonce of the last TransactionDeposited event processed by this chain. + DepositNonce uint64 `json:"depositNonce"` + // ConfigUpdateNonce identifies the nonce of the last ConfigUpdate event processed by this chain. + ConfigUpdateNonce uint64 `json:"configUpdateNonce"` // More fields can be added for future SystemConfig versions. // MarshalPreHolocene indicates whether or not this struct should be diff --git a/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol b/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol index 74ba7b4790e95..7c4c5f7ec3d33 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol @@ -38,7 +38,7 @@ interface IOptimismPortal2 { event DisputeGameBlacklisted(IDisputeGame indexed disputeGame); event Initialized(uint8 version); event RespectedGameTypeSet(GameType indexed newGameType, Timestamp indexed updatedAt); - event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData); + event TransactionDeposited(address indexed from, address indexed to, uint256 indexed nonceAndVersion, bytes opaqueData); event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to); event WithdrawalProvenExtension1(bytes32 indexed withdrawalHash, address indexed proofSubmitter); @@ -92,6 +92,7 @@ interface IOptimismPortal2 { function paused() external view returns (bool); function proofMaturityDelaySeconds() external view returns (uint256); function proofSubmitters(bytes32, uint256) external view returns (address); + function depositNonce() external view returns (uint64); function proveWithdrawalTransaction( Types.WithdrawalTransaction memory _tx, uint256 _disputeGameIndex, diff --git a/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol b/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol index 58fe5eff5dcab..895b2b729d751 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol @@ -39,7 +39,7 @@ interface IOptimismPortalInterop { event DisputeGameBlacklisted(IDisputeGame indexed disputeGame); event Initialized(uint8 version); event RespectedGameTypeSet(GameType indexed newGameType, Timestamp indexed updatedAt); - event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData); + event TransactionDeposited(address indexed from, address indexed to, uint256 indexed nonceAndVersion, bytes opaqueData); event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to); event WithdrawalProvenExtension1(bytes32 indexed withdrawalHash, address indexed proofSubmitter); @@ -93,6 +93,7 @@ interface IOptimismPortalInterop { function paused() external view returns (bool); function proofMaturityDelaySeconds() external view returns (uint256); function proofSubmitters(bytes32, uint256) external view returns (address); + function depositNonce() external view returns (uint64); function proveWithdrawalTransaction( Types.WithdrawalTransaction memory _tx, uint256 _disputeGameIndex, diff --git a/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol b/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol index 904375167f48d..5dd8df0515ede 100644 --- a/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol +++ b/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol @@ -23,7 +23,7 @@ interface ISystemConfig { address gasPayingToken; } - event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); + event ConfigUpdate(uint256 indexed nonceAndVersion, UpdateType indexed updateType, bytes data); event Initialized(uint8 version); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); @@ -45,6 +45,7 @@ interface ISystemConfig { function gasLimit() external view returns (uint64); function eip1559Denominator() external view returns (uint32); function eip1559Elasticity() external view returns (uint32); + function configUpdateNonce() external view returns (uint64); function gasPayingToken() external view returns (address addr_, uint8 decimals_); function gasPayingTokenName() external view returns (string memory name_); function gasPayingTokenSymbol() external view returns (string memory symbol_); diff --git a/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol b/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol index 4cf4a06f943fe..f6f612f972980 100644 --- a/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol +++ b/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol @@ -5,7 +5,7 @@ import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; interface ISystemConfigInterop { - event ConfigUpdate(uint256 indexed version, ISystemConfig.UpdateType indexed updateType, bytes data); + event ConfigUpdate(uint256 indexed nonceAndVersion, ISystemConfig.UpdateType indexed updateType, bytes data); event Initialized(uint8 version); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); @@ -27,6 +27,7 @@ interface ISystemConfigInterop { function gasLimit() external view returns (uint64); function eip1559Denominator() external view returns (uint32); function eip1559Elasticity() external view returns (uint32); + function configUpdateNonce() external view returns (uint64); function gasPayingToken() external view returns (address addr_, uint8 decimals_); function gasPayingTokenName() external view returns (string memory name_); function gasPayingTokenSymbol() external view returns (string memory symbol_); diff --git a/packages/contracts-bedrock/interfaces/L2/IL1Block.sol b/packages/contracts-bedrock/interfaces/L2/IL1Block.sol index a43b3c7c39639..e2dad614e955a 100644 --- a/packages/contracts-bedrock/interfaces/L2/IL1Block.sol +++ b/packages/contracts-bedrock/interfaces/L2/IL1Block.sol @@ -34,6 +34,7 @@ interface IL1Block { ) external; function setL1BlockValuesEcotone() external; + function setL1BlockValuesIsthmus() external; function timestamp() external view returns (uint64); function version() external pure returns (string memory); diff --git a/packages/contracts-bedrock/interfaces/L2/IL1BlockInterop.sol b/packages/contracts-bedrock/interfaces/L2/IL1BlockInterop.sol index dd72e3fa6f894..bbc0b532e8337 100644 --- a/packages/contracts-bedrock/interfaces/L2/IL1BlockInterop.sol +++ b/packages/contracts-bedrock/interfaces/L2/IL1BlockInterop.sol @@ -52,6 +52,7 @@ interface IL1BlockInterop { ) external; function setL1BlockValuesEcotone() external; + function setL1BlockValuesIsthmus() external; function setL1BlockValuesInterop() external; function timestamp() external view returns (uint64); function version() external pure returns (string memory); diff --git a/packages/contracts-bedrock/snapshots/.gas-snapshot b/packages/contracts-bedrock/snapshots/.gas-snapshot index f22930eef9226..2a739da168b10 100644 --- a/packages/contracts-bedrock/snapshots/.gas-snapshot +++ b/packages/contracts-bedrock/snapshots/.gas-snapshot @@ -4,10 +4,10 @@ GasBenchMark_L1BlockInterop_SetValuesInterop:test_setL1BlockValuesInterop_benchm GasBenchMark_L1BlockInterop_SetValuesInterop_Warm:test_setL1BlockValuesInterop_benchmark() (gas: 5144) GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158531) GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597) -GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369235) -GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967442) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564429) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076577) -GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467041) -GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512790) -GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72667) \ No newline at end of file +GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369279) +GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967486) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564473) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076621) +GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467085) +GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512834) +GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72667) diff --git a/packages/contracts-bedrock/snapshots/abi/L1Block.json b/packages/contracts-bedrock/snapshots/abi/L1Block.json index 020c9e942c757..f32fcf0eaa54c 100644 --- a/packages/contracts-bedrock/snapshots/abi/L1Block.json +++ b/packages/contracts-bedrock/snapshots/abi/L1Block.json @@ -77,6 +77,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "configUpdateNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "gasPayingToken", @@ -282,6 +295,13 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "setL1BlockValuesIsthmus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "timestamp", @@ -295,6 +315,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "transactionDepositedNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", diff --git a/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json b/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json index ab089f0cec555..f9764b9fe0ec4 100644 --- a/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json @@ -77,6 +77,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "configUpdateNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "dependencySetSize", @@ -359,6 +372,13 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "setL1BlockValuesIsthmus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "timestamp", @@ -372,6 +392,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "transactionDepositedNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json b/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json index 2f52ed573d37c..876f624c1a16f 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json @@ -669,6 +669,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "transactionDepositedNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", @@ -745,7 +758,7 @@ { "indexed": true, "internalType": "uint256", - "name": "version", + "name": "nonceAndVersion", "type": "uint256" }, { diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json b/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json index 5b9f72b9446c8..0689361a26989 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json @@ -687,6 +687,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "transactionDepositedNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", @@ -763,7 +776,7 @@ { "indexed": true, "internalType": "uint256", - "name": "version", + "name": "nonceAndVersion", "type": "uint256" }, { diff --git a/packages/contracts-bedrock/snapshots/abi/SystemConfig.json b/packages/contracts-bedrock/snapshots/abi/SystemConfig.json index b7e18556fa2cb..d8f0c27ff8cb9 100644 --- a/packages/contracts-bedrock/snapshots/abi/SystemConfig.json +++ b/packages/contracts-bedrock/snapshots/abi/SystemConfig.json @@ -186,6 +186,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "configUpdateNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "disputeGameFactory", @@ -750,7 +763,7 @@ { "indexed": true, "internalType": "uint256", - "name": "version", + "name": "nonceAndVersion", "type": "uint256" }, { diff --git a/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json b/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json index a459af15801b5..94c8bcb23719c 100644 --- a/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json @@ -194,6 +194,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "configUpdateNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "dependencyManager", @@ -911,7 +924,7 @@ { "indexed": true, "internalType": "uint256", - "name": "version", + "name": "nonceAndVersion", "type": "uint256" }, { diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 42019379ded7d..b40f69a39ada7 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -20,12 +20,12 @@ "sourceCodeHash": "0x05ed7ad68e4e9bca7334314e794a1f66e5899532bb01cfa3a7716cb2688df9d5" }, "src/L1/OptimismPortal2.sol": { - "initCodeHash": "0xfd14fd690752519064d6de6c3e15d69ec9146bc8714e56ac286305773dbb1533", - "sourceCodeHash": "0x3dbd4601c67a43c42f403f6b28e6e2d8bf4f3d2cf2f2d8f7460026e0c6c66def" + "initCodeHash": "0xdb6412a96646bf1afef037aa304c47f74925759ed5541c212721d0f861dd9d23", + "sourceCodeHash": "0x2d139bfb3f650e9b034369f0f184e1feb4a640e875713f5a32be54212f51d500" }, "src/L1/OptimismPortalInterop.sol": { - "initCodeHash": "0xc7a9282ef32425b65a647039908ea2b8d6ef231ba1b87c345c7b9f3f73acc240", - "sourceCodeHash": "0x85e9f10ba1884b1a45737fd35ae4c2f9a9054f81a6aba08941ab7a95e74543da" + "initCodeHash": "0x23fade64dc997b2f097cc39d038de1bbda08eb1d524db5fe1065356e839122a6", + "sourceCodeHash": "0x338a865e588ea511074e6b4870f0958580ce80cf04b3e4e5a6779ee6e6a92ea7" }, "src/L1/ProtocolVersions.sol": { "initCodeHash": "0x0000ec89712d8b4609873f1ba76afffd4205bf9110818995c90134dbec12e91e", @@ -36,12 +36,12 @@ "sourceCodeHash": "0xafa784ea78818a382ff3a61e2d84be58c7978110c06b9273db68c0213ead02d3" }, "src/L1/SystemConfig.sol": { - "initCodeHash": "0xe1baf5d3415baf65a45031c37e4bd794ecb7f779b227f6cbed06d945aa2097fc", - "sourceCodeHash": "0x52b7d8825b4962e720513906ac993d36964cf03c45364c9f03d88507a0caac57" + "initCodeHash": "0x77f86e6648153d12c8e0a623fd0fbe5840e1f91d87d2459bc085a6fad1001f3e", + "sourceCodeHash": "0xe955b52a337cb6c22202f9726c19e01c848c886f39d0be11faa82e7e8f1bdfb2" }, "src/L1/SystemConfigInterop.sol": { - "initCodeHash": "0x91ed371ee2f6d4a9ed1483971df8a6888cc8b5aca5212b180e395476f21cb268", - "sourceCodeHash": "0x6a51dc1b2bfadd9781c729f8db70972926f364b0e605536fb075bfddd0d4c433" + "initCodeHash": "0xacc717abf3a12c893099db529829fcc5c8e4b355a7ec87cf281842c0e99733f6", + "sourceCodeHash": "0xee1a9e73f9e5675268b2f92b59acde090d7ad258281a430444b5a12d32a0b23e" }, "src/L2/BaseFeeVault.sol": { "initCodeHash": "0xc403d4c555d8e69a2699e01d192ae7327136701fa02da10a6d75a584b3c364c9", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json b/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json index 2928d2147b5c8..d3ea09febce1d 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json @@ -75,5 +75,19 @@ "offset": 0, "slot": "7", "type": "uint256" + }, + { + "bytes": "8", + "label": "transactionDepositedNonce", + "offset": 0, + "slot": "8", + "type": "uint64" + }, + { + "bytes": "8", + "label": "configUpdateNonce", + "offset": 8, + "slot": "8", + "type": "uint64" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/L1BlockInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/L1BlockInterop.json index 14ee2ff9609a0..f19b9ba1e0568 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/L1BlockInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/L1BlockInterop.json @@ -76,11 +76,25 @@ "slot": "7", "type": "uint256" }, + { + "bytes": "8", + "label": "transactionDepositedNonce", + "offset": 0, + "slot": "8", + "type": "uint64" + }, + { + "bytes": "8", + "label": "configUpdateNonce", + "offset": 8, + "slot": "8", + "type": "uint64" + }, { "bytes": "64", "label": "dependencySet", "offset": 0, - "slot": "8", + "slot": "9", "type": "struct EnumerableSet.UintSet" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json index 0fdd65b3e88fb..161ea5e25907e 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json @@ -124,5 +124,12 @@ "offset": 0, "slot": "61", "type": "uint256" + }, + { + "bytes": "8", + "label": "transactionDepositedNonce", + "offset": 0, + "slot": "62", + "type": "uint64" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json index 0fdd65b3e88fb..161ea5e25907e 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json @@ -124,5 +124,12 @@ "offset": 0, "slot": "61", "type": "uint256" + }, + { + "bytes": "8", + "label": "transactionDepositedNonce", + "offset": 0, + "slot": "62", + "type": "uint64" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json index a6184a1f10dd5..797c9947c5e7b 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json @@ -96,5 +96,12 @@ "offset": 4, "slot": "106", "type": "uint32" + }, + { + "bytes": "8", + "label": "configUpdateNonce", + "offset": 8, + "slot": "106", + "type": "uint64" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json index a6184a1f10dd5..797c9947c5e7b 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json @@ -96,5 +96,12 @@ "offset": 4, "slot": "106", "type": "uint32" + }, + { + "bytes": "8", + "label": "configUpdateNonce", + "offset": 8, + "slot": "106", + "type": "uint64" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol index 10448f0b7fb65..baacd72d550cd 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol @@ -71,7 +71,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { uint256 internal immutable DISPUTE_GAME_FINALITY_DELAY_SECONDS; /// @notice Version of the deposit event. - uint256 internal constant DEPOSIT_VERSION = 0; + uint256 internal constant DEPOSIT_VERSION = 1; /// @notice The L2 gas limit set when eth is deposited using the receive() function. uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000; @@ -141,14 +141,19 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// It is not safe to trust `ERC20.balanceOf` as it may lie. uint256 internal _balance; + /// @notice Nonce incremented for each TransactionDeposited event + uint64 public depositNonce; + /// @notice Emitted when a transaction is deposited from L1 to L2. /// The parameters of this event are read by the rollup node and used to derive deposit /// transactions on L2. - /// @param from Address that triggered the deposit transaction. - /// @param to Address that the deposit transaction is directed to. - /// @param version Version of this deposit transaction event. - /// @param opaqueData ABI encoded deposit data to be parsed off-chain. - event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData); + /// @param from Address that triggered the deposit transaction. + /// @param to Address that the deposit transaction is directed to. + /// @param nonceAndVersion Nonce (first 128-bits) and version (second 128-bits). + /// @param opaqueData ABI encoded deposit data to be parsed off-chain. + event TransactionDeposited( + address indexed from, address indexed to, uint256 indexed nonceAndVersion, bytes opaqueData + ); /// @notice Emitted when a withdrawal transaction is proven. /// @param withdrawalHash Hash of the withdrawal transaction. @@ -183,9 +188,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { } /// @notice Semantic version. - /// @custom:semver 3.11.0-beta.9 + /// @custom:semver 3.11.0-beta.10 function version() public pure virtual returns (string memory) { - return "3.11.0-beta.9"; + return "3.11.0-beta.10"; } /// @notice Constructs the OptimismPortal contract. @@ -597,7 +602,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { // Emit a TransactionDeposited event so that the rollup node can derive a deposit // transaction for this deposit. - emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData); + _emitTransactionDeposited(from, _to, opaqueData); } /// @notice Sets the gas paying token for the L2 system. This token is used as the @@ -611,10 +616,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { // Emit the special deposit transaction directly that sets the gas paying // token in the L1Block predeploy contract. - emit TransactionDeposited( + _emitTransactionDeposited( Constants.DEPOSITOR_ACCOUNT, Predeploys.L1_BLOCK_ATTRIBUTES, - DEPOSIT_VERSION, abi.encodePacked( uint256(0), // mint uint256(0), // value @@ -625,6 +629,13 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { ); } + /// @notice Emit a TransactionDeposited event and increment the nonce. + function _emitTransactionDeposited(address _from, address _to, bytes memory _opaqueData) internal { + depositNonce++; + uint256 _version = uint256(depositNonce) << 128 | DEPOSIT_VERSION; + emit TransactionDeposited(_from, _to, _version, _opaqueData); + } + /// @notice Blacklists a dispute game. Should only be used in the event that a dispute game resolves incorrectly. /// @param _disputeGame Dispute game to blacklist. function blacklistDisputeGame(IDisputeGame _disputeGame) external { diff --git a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol index fecd4dc0d6b21..758eace0ef116 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol @@ -25,9 +25,9 @@ contract OptimismPortalInterop is OptimismPortal2 { OptimismPortal2(_proofMaturityDelaySeconds, _disputeGameFinalityDelaySeconds) { } - /// @custom:semver +interop-beta.6 + /// @custom:semver +interop-beta.7 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop-beta.6"); + return string.concat(super.version(), "+interop-beta.7"); } /// @notice Sets static configuration options for the L2 system. @@ -41,10 +41,9 @@ contract OptimismPortalInterop is OptimismPortal2 { useGas(SYSTEM_DEPOSIT_GAS_LIMIT); // Emit the special deposit transaction directly that sets the config in the L1Block predeploy contract. - emit TransactionDeposited( + _emitTransactionDeposited( Constants.DEPOSITOR_ACCOUNT, Predeploys.L1_BLOCK_ATTRIBUTES, - DEPOSIT_VERSION, abi.encodePacked( uint256(0), // mint uint256(0), // value diff --git a/packages/contracts-bedrock/src/L1/SystemConfig.sol b/packages/contracts-bedrock/src/L1/SystemConfig.sol index 0b4562543d925..a45110eb588d6 100644 --- a/packages/contracts-bedrock/src/L1/SystemConfig.sol +++ b/packages/contracts-bedrock/src/L1/SystemConfig.sol @@ -49,7 +49,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { } /// @notice Version identifier, used for upgrades. - uint256 public constant VERSION = 0; + uint256 public constant VERSION = 1; /// @notice Storage slot that the unsafe block signer is stored at. /// Storing it at this deterministic storage slot allows for decoupling the storage @@ -130,16 +130,19 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { /// @notice The EIP-1559 elasticity multiplier. uint32 public eip1559Elasticity; + /// @notice Nonce incremented for each ConfigUpdate event + uint64 public configUpdateNonce; + /// @notice Emitted when configuration is updated. - /// @param version SystemConfig version. - /// @param updateType Type of update. - /// @param data Encoded update data. - event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); + /// @param nonceAndVersion Nonce (first 128-bits) and version (second 128-bits). + /// @param updateType Type of update. + /// @param data Encoded update data. + event ConfigUpdate(uint256 indexed nonceAndVersion, UpdateType indexed updateType, bytes data); /// @notice Semantic version. - /// @custom:semver 2.3.0-beta.9 + /// @custom:semver 2.3.0-beta.10 function version() public pure virtual returns (string memory) { - return "2.3.0-beta.9"; + return "2.3.0-beta.10"; } /// @notice Constructs the SystemConfig contract. @@ -323,7 +326,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { Storage.setAddress(UNSAFE_BLOCK_SIGNER_SLOT, _unsafeBlockSigner); bytes memory data = abi.encode(_unsafeBlockSigner); - emit ConfigUpdate(VERSION, UpdateType.UNSAFE_BLOCK_SIGNER, data); + _emitConfigUpdate(UpdateType.UNSAFE_BLOCK_SIGNER, data); } /// @notice Updates the batcher hash. Can only be called by the owner. @@ -338,7 +341,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { batcherHash = _batcherHash; bytes memory data = abi.encode(_batcherHash); - emit ConfigUpdate(VERSION, UpdateType.BATCHER, data); + _emitConfigUpdate(UpdateType.BATCHER, data); } /// @notice Updates gas config. Can only be called by the owner. @@ -359,7 +362,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { scalar = _scalar; bytes memory data = abi.encode(_overhead, _scalar); - emit ConfigUpdate(VERSION, UpdateType.FEE_SCALARS, data); + _emitConfigUpdate(UpdateType.FEE_SCALARS, data); } /// @notice Updates gas config as of the Ecotone upgrade. Can only be called by the owner. @@ -379,7 +382,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { scalar = (uint256(0x01) << 248) | (uint256(_blobbasefeeScalar) << 32) | _basefeeScalar; bytes memory data = abi.encode(overhead, scalar); - emit ConfigUpdate(VERSION, UpdateType.FEE_SCALARS, data); + _emitConfigUpdate(UpdateType.FEE_SCALARS, data); } /// @notice Updates the L2 gas limit. Can only be called by the owner. @@ -396,7 +399,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { gasLimit = _gasLimit; bytes memory data = abi.encode(_gasLimit); - emit ConfigUpdate(VERSION, UpdateType.GAS_LIMIT, data); + _emitConfigUpdate(UpdateType.GAS_LIMIT, data); } /// @notice Updates the EIP-1559 parameters of the chain. Can only be called by the owner. @@ -415,7 +418,14 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { eip1559Elasticity = _elasticity; bytes memory data = abi.encode(uint256(_denominator) << 32 | uint64(_elasticity)); - emit ConfigUpdate(VERSION, UpdateType.EIP_1559_PARAMS, data); + _emitConfigUpdate(UpdateType.EIP_1559_PARAMS, data); + } + + /// @notice Emit a ConfigUpdate event and increment the nonce. + function _emitConfigUpdate(UpdateType _updateType, bytes memory _data) internal { + configUpdateNonce++; + uint256 _version = uint256(configUpdateNonce) << 128 | VERSION; + emit ConfigUpdate(_version, _updateType, _data); } /// @notice Sets the start block in a backwards compatible way. Proxies diff --git a/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol b/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol index af7656876bcbf..aa587beb6d8df 100644 --- a/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol +++ b/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol @@ -68,9 +68,9 @@ contract SystemConfigInterop is SystemConfig { Storage.setAddress(DEPENDENCY_MANAGER_SLOT, _dependencyManager); } - /// @custom:semver +interop-beta.8 + /// @custom:semver +interop-beta.9 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop-beta.8"); + return string.concat(super.version(), "+interop-beta.9"); } /// @notice Internal setter for the gas paying token address, includes validation. diff --git a/packages/contracts-bedrock/src/L2/L1Block.sol b/packages/contracts-bedrock/src/L2/L1Block.sol index 3767b80988da7..1ed2cb0b89db6 100644 --- a/packages/contracts-bedrock/src/L2/L1Block.sol +++ b/packages/contracts-bedrock/src/L2/L1Block.sol @@ -60,6 +60,12 @@ contract L1Block is ISemver, IGasToken { /// @notice The latest L1 blob base fee. uint256 public blobBaseFee; + /// @notice Nonce incremented for each TransactionDeposited event + uint64 public depositNonce; + + /// @notice Nonce incremented for each ConfigUpdate event + uint64 public configUpdateNonce; + /// @custom:semver 1.5.1-beta.5 function version() public pure virtual returns (string memory) { return "1.5.1-beta.5"; @@ -136,11 +142,26 @@ contract L1Block is ISemver, IGasToken { /// 7. _blobBaseFee L1 blob base fee. /// 8. _hash L1 blockhash. /// 9. _batcherHash Versioned hash to authenticate batcher by. - function setL1BlockValuesEcotone() public { - _setL1BlockValuesEcotone(); + function setL1BlockValuesEcotone() external { + address depositor = DEPOSITOR_ACCOUNT(); + assembly { + // Revert if the caller is not the depositor account. + if xor(caller(), depositor) { + mstore(0x00, 0x3cc50b45) // 0x3cc50b45 is the 4-byte selector of "NotDepositor()" + revert(0x1C, 0x04) // returns the stored 4-byte selector from above + } + // sequencenum (uint64), blobBaseFeeScalar (uint32), baseFeeScalar (uint32) + sstore(sequenceNumber.slot, shr(128, calldataload(4))) + // number (uint64) and timestamp (uint64) + sstore(number.slot, shr(128, calldataload(20))) + sstore(basefee.slot, calldataload(36)) // uint256 + sstore(blobBaseFee.slot, calldataload(68)) // uint256 + sstore(hash.slot, calldataload(100)) // bytes32 + sstore(batcherHash.slot, calldataload(132)) // bytes32 + } } - /// @notice Updates the L1 block values for an Ecotone upgraded chain. + /// @notice Updates the L1 block values for an Isthmus upgraded chain. /// Params are packed and passed in as raw msg.data instead of ABI to reduce calldata size. /// Params are expected to be in the following order: /// 1. _baseFeeScalar L1 base fee scalar @@ -152,7 +173,13 @@ contract L1Block is ISemver, IGasToken { /// 7. _blobBaseFee L1 blob base fee. /// 8. _hash L1 blockhash. /// 9. _batcherHash Versioned hash to authenticate batcher by. - function _setL1BlockValuesEcotone() internal { + /// 10. _depositNonce Nonce of the latest TransactionDeposited event processed up to this block. + /// 11. _configUpdateNonce Nonce of the latest ConfigUpdate event processed up to this block. + function setL1BlockValuesIsthmus() external { + _setL1BlockValuesIsthmus(); + } + + function _setL1BlockValuesIsthmus() internal { address depositor = DEPOSITOR_ACCOUNT(); assembly { // Revert if the caller is not the depositor account. @@ -168,6 +195,8 @@ contract L1Block is ISemver, IGasToken { sstore(blobBaseFee.slot, calldataload(68)) // uint256 sstore(hash.slot, calldataload(100)) // bytes32 sstore(batcherHash.slot, calldataload(132)) // bytes32 + // depositNonce (uint64) and configUpdateNonce (uint64) + sstore(depositNonce.slot, shr(128, calldataload(164))) } } diff --git a/packages/contracts-bedrock/src/L2/L1BlockInterop.sol b/packages/contracts-bedrock/src/L2/L1BlockInterop.sol index 7b92b202a052d..31c22fea78cc4 100644 --- a/packages/contracts-bedrock/src/L2/L1BlockInterop.sol +++ b/packages/contracts-bedrock/src/L2/L1BlockInterop.sol @@ -78,15 +78,15 @@ contract L1BlockInterop is L1Block { } /// @notice Updates the `isDeposit` flag and sets the L1 block values for an Interop upgraded chain. - /// It updates the L1 block values through the `setL1BlockValuesEcotone` function. - /// It forwards the calldata to the internally-used `setL1BlockValuesEcotone` function. + /// It updates the L1 block values through the `_setL1BlockValuesIsthmus` function. + /// It forwards the calldata to the internally-used `_setL1BlockValuesIsthmus` function. function setL1BlockValuesInterop() external { // Set the isDeposit flag to true. assembly { sstore(IS_DEPOSIT_SLOT, 1) } - _setL1BlockValuesEcotone(); + _setL1BlockValuesIsthmus(); } /// @notice Resets the isDeposit flag. diff --git a/packages/contracts-bedrock/src/libraries/Encoding.sol b/packages/contracts-bedrock/src/libraries/Encoding.sol index 5aa4ee7d3d8a4..8350de5bf4354 100644 --- a/packages/contracts-bedrock/src/libraries/Encoding.sol +++ b/packages/contracts-bedrock/src/libraries/Encoding.sol @@ -177,6 +177,52 @@ library Encoding { ); } + /// @notice Returns an appropriately encoded call to L1Block.setL1BlockValuesIsthmus + /// @param _baseFeeScalar L1 base fee Scalar + /// @param _blobBaseFeeScalar L1 blob base fee Scalar + /// @param _sequenceNumber Number of L2 blocks since epoch start. + /// @param _timestamp L1 timestamp. + /// @param _number L1 blocknumber. + /// @param _baseFee L1 base fee. + /// @param _blobBaseFee L1 blob base fee. + /// @param _hash L1 blockhash. + /// @param _batcherHash Versioned hash to authenticate batcher by. + /// @param _depositNonce Nonce of the latest TransactionDeposited event processed up to this block. + /// @param _configUpdateNonce Nonce of the latest ConfigUpdate event processed up to this block. + function encodeSetL1BlockValuesIsthmus( + uint32 _baseFeeScalar, + uint32 _blobBaseFeeScalar, + uint64 _sequenceNumber, + uint64 _timestamp, + uint64 _number, + uint256 _baseFee, + uint256 _blobBaseFee, + bytes32 _hash, + bytes32 _batcherHash, + uint64 _depositNonce, + uint64 _configUpdateNonce + ) + internal + pure + returns (bytes memory) + { + bytes4 functionSignature = bytes4(keccak256("setL1BlockValuesIsthmus()")); + return abi.encodePacked( + functionSignature, + _baseFeeScalar, + _blobBaseFeeScalar, + _sequenceNumber, + _timestamp, + _number, + _baseFee, + _blobBaseFee, + _hash, + _batcherHash, + _depositNonce, + _configUpdateNonce + ); + } + /// @notice Returns an appropriately encoded call to L1Block.setL1BlockValuesInterop /// @param _baseFeeScalar L1 base fee Scalar /// @param _blobBaseFeeScalar L1 blob base fee Scalar @@ -187,6 +233,8 @@ library Encoding { /// @param _blobBaseFee L1 blob base fee. /// @param _hash L1 blockhash. /// @param _batcherHash Versioned hash to authenticate batcher by. + /// @param _depositNonce Nonce of the latest TransactionDeposited event processed up to this block. + /// @param _configUpdateNonce Nonce of the latest ConfigUpdate event processed up to this block. function encodeSetL1BlockValuesInterop( uint32 _baseFeeScalar, uint32 _blobBaseFeeScalar, @@ -196,7 +244,9 @@ library Encoding { uint256 _baseFee, uint256 _blobBaseFee, bytes32 _hash, - bytes32 _batcherHash + bytes32 _batcherHash, + uint64 _depositNonce, + uint64 _configUpdateNonce ) internal pure @@ -213,7 +263,9 @@ library Encoding { _baseFee, _blobBaseFee, _hash, - _batcherHash + _batcherHash, + _depositNonce, + _configUpdateNonce ); } } diff --git a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol index c3e80183c6eb6..e9470d72cd7fe 100644 --- a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol +++ b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol @@ -225,7 +225,7 @@ contract PreBridgeETH is CommonTest { assertEq(address(optimismPortal2).balance, 0); } uint256 nonce = l1CrossDomainMessenger.messageNonce(); - uint256 version = 0; // Internal constant in the OptimismPortal: DEPOSIT_VERSION + uint256 nonceAndVersion = 1 << 128 | 1; // Internal constant in the OptimismPortal: DEPOSIT_VERSION address l1MessengerAliased = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); bytes memory message = abi.encodeCall(StandardBridge.finalizeBridgeETH, (alice, alice, value, hex"dead")); @@ -270,7 +270,7 @@ contract PreBridgeETH is CommonTest { // OptimismPortal emits a TransactionDeposited event on `depositTransaction` call vm.expectEmit(address(optimismPortal2)); - emit TransactionDeposited(l1MessengerAliased, address(l2CrossDomainMessenger), version, opaqueData); + emit TransactionDeposited(l1MessengerAliased, address(l2CrossDomainMessenger), nonceAndVersion, opaqueData); // SentMessage event emitted by the CrossDomainMessenger vm.expectEmit(address(l1CrossDomainMessenger)); @@ -356,7 +356,7 @@ contract PreBridgeETHTo is CommonTest { /// address depending on whether the bridge call is legacy or not. function _preBridgeETHTo(bool isLegacy, uint256 value) internal { uint256 nonce = l1CrossDomainMessenger.messageNonce(); - uint256 version = 0; // Internal constant in the OptimismPortal: DEPOSIT_VERSION + uint256 nonceAndVersion = 1 << 128 | 1; // Internal constant in the OptimismPortal: DEPOSIT_VERSION address l1MessengerAliased = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); if (isLegacy) { @@ -506,7 +506,7 @@ contract L1StandardBridge_DepositERC20_Test is CommonTest { /// Only EOA can call depositERC20. function test_depositERC20_succeeds() external { uint256 nonce = l1CrossDomainMessenger.messageNonce(); - uint256 version = 0; // Internal constant in the OptimismPortal: DEPOSIT_VERSION + uint256 nonceAndVersion = 1 << 128 | 1; // Internal constant in the OptimismPortal: DEPOSIT_VERSION address l1MessengerAliased = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); // Deal Alice's ERC20 State @@ -589,7 +589,7 @@ contract L1StandardBridge_DepositERC20To_Test is CommonTest { /// Contracts can call depositERC20. function test_depositERC20To_succeeds() external { uint256 nonce = l1CrossDomainMessenger.messageNonce(); - uint256 version = 0; // Internal constant in the OptimismPortal: DEPOSIT_VERSION + uint256 nonceAndVersion = 1 << 128 | 1; // Internal constant in the OptimismPortal: DEPOSIT_VERSION address l1MessengerAliased = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); bytes memory message = abi.encodeCall( diff --git a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol index 19ff94552fc17..ce9e1b282e315 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol @@ -325,7 +325,7 @@ contract OptimismPortal2_Test is CommonTest { emit TransactionDeposited( 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001, Predeploys.L1_BLOCK_ATTRIBUTES, - 0, + 1, abi.encodePacked( uint256(0), // mint uint256(0), // value @@ -383,12 +383,15 @@ contract OptimismPortal2_Test is CommonTest { VmSafe.Log memory systemPath = logs[0]; VmSafe.Log memory userPath = logs[1]; + // nonce is incremented, copy it + bytes32 systemPathVersion = bytes32(uint256(systemPath.topics[3]) | 1 << 128); + assertEq(systemPath.topics.length, 4); assertEq(systemPath.topics.length, userPath.topics.length); - assertEq(systemPath.topics[0], userPath.topics[0]); - assertEq(systemPath.topics[1], userPath.topics[1]); - assertEq(systemPath.topics[2], userPath.topics[2]); - assertEq(systemPath.topics[3], userPath.topics[3]); + assertEq(systemPath.topics[0], userPath.topics[0]); // address + assertEq(systemPath.topics[1], userPath.topics[1]); // from + assertEq(systemPath.topics[2], userPath.topics[2]); // to + assertEq(systemPathVersion, userPath.topics[3]); // nonceAndVersion assertEq(systemPath.data, userPath.data); } @@ -1821,7 +1824,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal emit TransactionDeposited( _from, // from _to, - uint256(0), // DEPOSIT_VERSION + uint256(1), // DEPOSIT_VERSION opaqueData ); @@ -2056,7 +2059,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal emit TransactionDeposited( _from, // from _to, - uint256(0), // DEPOSIT_VERSION + uint256(1), // DEPOSIT_VERSION opaqueData ); diff --git a/packages/contracts-bedrock/test/L1/SystemConfig.t.sol b/packages/contracts-bedrock/test/L1/SystemConfig.t.sol index af96c0cce9017..3711c09d1340b 100644 --- a/packages/contracts-bedrock/test/L1/SystemConfig.t.sol +++ b/packages/contracts-bedrock/test/L1/SystemConfig.t.sol @@ -18,7 +18,7 @@ import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IL1Block } from "interfaces/L2/IL1Block.sol"; contract SystemConfig_Init is CommonTest { - event ConfigUpdate(uint256 indexed version, ISystemConfig.UpdateType indexed updateType, bytes data); + event ConfigUpdate(uint256 indexed nonceAndVersion, ISystemConfig.UpdateType indexed updateType, bytes data); } contract SystemConfig_Initialize_Test is SystemConfig_Init { @@ -485,7 +485,7 @@ contract SystemConfig_Init_CustomGasToken is SystemConfig_Init { emit TransactionDeposited( 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001, Predeploys.L1_BLOCK_ATTRIBUTES, - 0, // deposit version + 1 << 128 | 1, // deposit version abi.encodePacked( uint256(0), // mint uint256(0), // value @@ -588,7 +588,7 @@ contract SystemConfig_Setters_Test is SystemConfig_Init { /// @dev Tests that `setBatcherHash` updates the batcher hash successfully. function testFuzz_setBatcherHash_succeeds(bytes32 newBatcherHash) external { vm.expectEmit(address(systemConfig)); - emit ConfigUpdate(0, ISystemConfig.UpdateType.BATCHER, abi.encode(newBatcherHash)); + emit ConfigUpdate(4 << 128 | 1, ISystemConfig.UpdateType.BATCHER, abi.encode(newBatcherHash)); vm.prank(systemConfig.owner()); systemConfig.setBatcherHash(newBatcherHash); @@ -600,7 +600,7 @@ contract SystemConfig_Setters_Test is SystemConfig_Init { // always zero out most significant byte newScalar = (newScalar << 16) >> 16; vm.expectEmit(address(systemConfig)); - emit ConfigUpdate(0, ISystemConfig.UpdateType.FEE_SCALARS, abi.encode(newOverhead, newScalar)); + emit ConfigUpdate(4 << 128 | 1, ISystemConfig.UpdateType.FEE_SCALARS, abi.encode(newOverhead, newScalar)); vm.prank(systemConfig.owner()); systemConfig.setGasConfig(newOverhead, newScalar); @@ -615,7 +615,9 @@ contract SystemConfig_Setters_Test is SystemConfig_Init { ffi.encodeScalarEcotone({ _basefeeScalar: _basefeeScalar, _blobbasefeeScalar: _blobbasefeeScalar }); vm.expectEmit(address(systemConfig)); - emit ConfigUpdate(0, ISystemConfig.UpdateType.FEE_SCALARS, abi.encode(systemConfig.overhead(), encoded)); + emit ConfigUpdate( + 4 << 128 | 1, ISystemConfig.UpdateType.FEE_SCALARS, abi.encode(systemConfig.overhead(), encoded) + ); vm.prank(systemConfig.owner()); systemConfig.setGasConfigEcotone({ _basefeeScalar: _basefeeScalar, _blobbasefeeScalar: _blobbasefeeScalar }); @@ -635,7 +637,7 @@ contract SystemConfig_Setters_Test is SystemConfig_Init { newGasLimit = uint64(bound(uint256(newGasLimit), uint256(minimumGasLimit), uint256(maximumGasLimit))); vm.expectEmit(address(systemConfig)); - emit ConfigUpdate(0, ISystemConfig.UpdateType.GAS_LIMIT, abi.encode(newGasLimit)); + emit ConfigUpdate(4 << 128 | 1, ISystemConfig.UpdateType.GAS_LIMIT, abi.encode(newGasLimit)); vm.prank(systemConfig.owner()); systemConfig.setGasLimit(newGasLimit); @@ -645,7 +647,7 @@ contract SystemConfig_Setters_Test is SystemConfig_Init { /// @dev Tests that `setUnsafeBlockSigner` updates the block signer successfully. function testFuzz_setUnsafeBlockSigner_succeeds(address newUnsafeSigner) external { vm.expectEmit(address(systemConfig)); - emit ConfigUpdate(0, ISystemConfig.UpdateType.UNSAFE_BLOCK_SIGNER, abi.encode(newUnsafeSigner)); + emit ConfigUpdate(4 << 128 | 1, ISystemConfig.UpdateType.UNSAFE_BLOCK_SIGNER, abi.encode(newUnsafeSigner)); vm.prank(systemConfig.owner()); systemConfig.setUnsafeBlockSigner(newUnsafeSigner); @@ -661,7 +663,9 @@ contract SystemConfig_Setters_Test is SystemConfig_Init { vm.expectEmit(address(systemConfig)); emit ConfigUpdate( - 0, ISystemConfig.UpdateType.EIP_1559_PARAMS, abi.encode(uint256(_denominator) << 32 | uint64(_elasticity)) + 4 << 128 | 1, + ISystemConfig.UpdateType.EIP_1559_PARAMS, + abi.encode(uint256(_denominator) << 32 | uint64(_elasticity)) ); vm.prank(systemConfig.owner()); diff --git a/packages/contracts-bedrock/test/setup/CommonTest.sol b/packages/contracts-bedrock/test/setup/CommonTest.sol index d04b3f1ac0220..5d3d56427d1eb 100644 --- a/packages/contracts-bedrock/test/setup/CommonTest.sol +++ b/packages/contracts-bedrock/test/setup/CommonTest.sol @@ -168,7 +168,7 @@ contract CommonTest is Test, Setup, Events { ) internal { - emit TransactionDeposited(_from, _to, 0, abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data)); + emit TransactionDeposited(_from, _to, 1, abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data)); } function enableLegacyContracts() public { diff --git a/packages/contracts-bedrock/test/setup/Events.sol b/packages/contracts-bedrock/test/setup/Events.sol index 7056f0cbdd6b0..d79e3cdba1363 100644 --- a/packages/contracts-bedrock/test/setup/Events.sol +++ b/packages/contracts-bedrock/test/setup/Events.sol @@ -13,7 +13,9 @@ import { Types } from "src/libraries/Types.sol"; contract Events { /// @dev OpenZeppelin Ownable.sol transferOwnership event event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData); + event TransactionDeposited( + address indexed from, address indexed to, uint256 indexed nonceAndVersion, bytes opaqueData + ); event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to); diff --git a/packages/contracts-bedrock/test/universal/BenchmarkTest.t.sol b/packages/contracts-bedrock/test/universal/BenchmarkTest.t.sol index ae283ef8dba1e..390d2791f8a44 100644 --- a/packages/contracts-bedrock/test/universal/BenchmarkTest.t.sol +++ b/packages/contracts-bedrock/test/universal/BenchmarkTest.t.sol @@ -178,7 +178,9 @@ contract GasBenchMark_L1BlockInterop is GasBenchMark_L1Block { type(uint256).max, type(uint256).max, keccak256(abi.encode(1)), - bytes32(type(uint256).max) + bytes32(type(uint256).max), + type(uint64).max, + type(uint64).max ); } } diff --git a/packages/contracts-bedrock/test/universal/Specs.t.sol b/packages/contracts-bedrock/test/universal/Specs.t.sol index 14dcbc9bc884d..ebf14df8ddde9 100644 --- a/packages/contracts-bedrock/test/universal/Specs.t.sol +++ b/packages/contracts-bedrock/test/universal/Specs.t.sol @@ -261,6 +261,7 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("respectedGameTypeUpdatedAt()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("proofSubmitters(bytes32,uint256)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("numProofSubmitters(bytes32)") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("depositNonce()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("balance()") }); _addSpec({ _name: "OptimismPortalInterop", @@ -313,6 +314,7 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "OptimismPortal2", _sel: _getSel("respectedGameTypeUpdatedAt()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("proofSubmitters(bytes32,uint256)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("numProofSubmitters(bytes32)") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("depositNonce()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("balance()") }); _addSpec({ _name: "OptimismPortal2", @@ -363,6 +365,7 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfig", _sel: _getSel("gasLimit()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("eip1559Denominator()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("eip1559Elasticity()") }); + _addSpec({ _name: "SystemConfig", _sel: _getSel("configUpdateNonce()") }); _addSpec({ _name: "SystemConfig", _sel: ISystemConfig.initialize.selector }); _addSpec({ _name: "SystemConfig", _sel: ISystemConfig.minimumGasLimit.selector }); _addSpec({ _name: "SystemConfig", _sel: _getSel("overhead()") }); @@ -418,6 +421,7 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("gasLimit()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("eip1559Denominator()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("eip1559Elasticity()") }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("configUpdateNonce()") }); _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfigInterop.initialize.selector }); _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfig.initialize.selector }); _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfigInterop.minimumGasLimit.selector });