From fcdf93ead7ca9855e667d6fa5aed376309cc76c7 Mon Sep 17 00:00:00 2001 From: Michael de Hoog Date: Fri, 27 Dec 2024 09:49:33 -1000 Subject: [PATCH] Introduce nonce for consensus events on L1 --- .semgrep/rules/sol-rules.yaml | 2 + go.mod | 4 +- go.sum | 4 +- op-chain-ops/cmd/deposit-hash/main.go | 3 +- op-chain-ops/genesis/config.go | 22 +- op-chain-ops/interopgen/recipe.go | 1 + op-e2e/actions/helpers/user.go | 2 +- op-e2e/e2eutils/setup.go | 15 +- op-e2e/system/bridge/bridge_test.go | 2 +- op-e2e/system/e2esys/setup.go | 1 + op-e2e/system/gastoken/gastoken_test.go | 6 +- op-e2e/system/helpers/tx_helper.go | 2 +- .../rollup/attributes/engine_consolidate.go | 4 +- op-node/rollup/derive/attributes.go | 27 +- op-node/rollup/derive/deposit_log.go | 72 +- op-node/rollup/derive/deposit_log_test.go | 4 +- op-node/rollup/derive/deposit_log_tob_test.go | 12 +- op-node/rollup/derive/deposits.go | 14 +- op-node/rollup/derive/fuzz_parsers_test.go | 2 +- .../derive/isthmus_upgrade_transactions.go | 39 + .../isthmus_upgrade_transactions_test.go | 49 + op-node/rollup/derive/l1_block_info.go | 126 ++- op-node/rollup/derive/l1_block_info_test.go | 113 +- .../rollup/derive/l1_block_info_tob_test.go | 2 + op-node/rollup/derive/payload_util.go | 10 +- op-node/rollup/derive/system_config.go | 22 +- op-node/rollup/derive/system_config_test.go | 12 +- op-node/rollup/sequencing/sequencer.go | 8 +- op-node/rollup/superchain.go | 2 + .../client/l2/engineapi/l2_engine_api.go | 35 +- op-service/eth/types.go | 52 +- op-service/eth/types_test.go | 2 +- op-service/sources/types.go | 35 +- op-wheel/commands.go | 2 + .../interfaces/L1/IOptimismPortal2.sol | 2 +- .../interfaces/L1/IOptimismPortalInterop.sol | 4 +- .../interfaces/L1/IOptimismPortalIsthmus.sol | 120 +++ .../interfaces/L1/ISystemConfig.sol | 3 +- .../interfaces/L1/ISystemConfigInterop.sol | 6 +- .../interfaces/L1/ISystemConfigIsthmus.sol | 75 ++ .../interfaces/L2/IL1Block.sol | 3 + .../interfaces/L2/IL1BlockInterop.sol | 3 + packages/contracts-bedrock/justfile | 3 +- .../scripts/deploy/Deploy.s.sol | 13 +- .../scripts/deploy/DeployConfig.s.sol | 7 + .../deploy/DeployImplementations.s.sol | 86 ++ .../contracts-bedrock/snapshots/.gas-snapshot | 24 +- .../snapshots/abi/L1Block.json | 33 + .../snapshots/abi/L1BlockInterop.json | 33 + .../snapshots/abi/OptimismPortal2.json | 2 +- .../snapshots/abi/OptimismPortalInterop.json | 28 +- .../snapshots/abi/OptimismPortalIsthmus.json | 975 ++++++++++++++++++ .../snapshots/abi/SystemConfig.json | 15 +- .../snapshots/abi/SystemConfigInterop.json | 54 +- .../snapshots/abi/SystemConfigIsthmus.json | 851 +++++++++++++++ .../snapshots/semver-lock.json | 32 +- .../snapshots/storageLayout/L1Block.json | 14 + .../storageLayout/L1BlockInterop.json | 16 +- .../storageLayout/OptimismPortalIsthmus.json | 128 +++ .../storageLayout/SystemConfigIsthmus.json | 100 ++ .../src/L1/OptimismPortal2.sol | 53 +- .../src/L1/OptimismPortalInterop.sol | 13 +- .../src/L1/OptimismPortalIsthmus.sol | 46 + .../contracts-bedrock/src/L1/SystemConfig.sol | 35 +- .../src/L1/SystemConfigInterop.sol | 7 +- .../src/L1/SystemConfigIsthmus.sol | 40 + packages/contracts-bedrock/src/L2/L1Block.sol | 41 +- .../src/L2/L1BlockInterop.sol | 10 +- .../src/libraries/Encoding.sol | 56 +- .../test/L1/OptimismPortal2.t.sol | 32 +- .../test/L1/OptimismPortalInterop.t.sol | 15 +- .../test/L1/OptimismPortalIsthmus.t.sol | 65 ++ .../test/L1/SystemConfig.t.sol | 2 +- .../test/L1/SystemConfigIsthmus.t.sol | 145 +++ .../test/safe/DeputyGuardianModule.t.sol | 10 +- .../test/setup/CommonTest.sol | 33 + .../contracts-bedrock/test/setup/Events.sol | 4 +- .../test/universal/BenchmarkTest.t.sol | 4 +- .../test/universal/Specs.t.sol | 149 +++ .../test/vendor/Initializable.t.sol | 22 +- 80 files changed, 3805 insertions(+), 315 deletions(-) create mode 100644 op-node/rollup/derive/isthmus_upgrade_transactions.go create mode 100644 op-node/rollup/derive/isthmus_upgrade_transactions_test.go create mode 100644 packages/contracts-bedrock/interfaces/L1/IOptimismPortalIsthmus.sol create mode 100644 packages/contracts-bedrock/interfaces/L1/ISystemConfigIsthmus.sol create mode 100644 packages/contracts-bedrock/snapshots/abi/OptimismPortalIsthmus.json create mode 100644 packages/contracts-bedrock/snapshots/abi/SystemConfigIsthmus.json create mode 100644 packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalIsthmus.json create mode 100644 packages/contracts-bedrock/snapshots/storageLayout/SystemConfigIsthmus.json create mode 100644 packages/contracts-bedrock/src/L1/OptimismPortalIsthmus.sol create mode 100644 packages/contracts-bedrock/src/L1/SystemConfigIsthmus.sol create mode 100644 packages/contracts-bedrock/test/L1/OptimismPortalIsthmus.t.sol create mode 100644 packages/contracts-bedrock/test/L1/SystemConfigIsthmus.t.sol diff --git a/.semgrep/rules/sol-rules.yaml b/.semgrep/rules/sol-rules.yaml index 27d6d1e7fd4f..c8a615cb38ad 100644 --- a/.semgrep/rules/sol-rules.yaml +++ b/.semgrep/rules/sol-rules.yaml @@ -209,7 +209,9 @@ rules: } paths: exclude: + - packages/contracts-bedrock/src/L1/SystemConfigIsthmus.sol - packages/contracts-bedrock/src/L1/SystemConfigInterop.sol + - packages/contracts-bedrock/src/L1/OptimismPortalIsthmus.sol - packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol - id: sol-safety-proper-initializer diff --git a/go.mod b/go.mod index 54a4f4d995b7..6b17c00ce182 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/ethereum/go-ethereum v1.14.11 github.com/fatih/color v1.16.0 github.com/fsnotify/fsnotify v1.8.0 + github.com/golang-jwt/jwt/v4 v4.5.1 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/google/go-cmp v0.6.0 github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8 @@ -113,7 +114,6 @@ require ( github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20241009165004-a3522334989c // indirect @@ -252,7 +252,7 @@ require ( rsc.io/tmplfunc v0.0.3 // indirect ) -replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101411.5-rc.1 +replace github.com/ethereum/go-ethereum => github.com/mdehoog/op-geth v0.0.0-20250102015754-dc3e6fe74538 //replace github.com/ethereum/go-ethereum => ../go-ethereum diff --git a/go.sum b/go.sum index b2925d2a0194..f05ea05dd0eb 100644 --- a/go.sum +++ b/go.sum @@ -189,8 +189,6 @@ github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/u github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= -github.com/ethereum-optimism/op-geth v1.101411.5-rc.1 h1:8fhtAycm/+xugWev5jInUxgF0Wdc29PxSODZXca6Qi8= -github.com/ethereum-optimism/op-geth v1.101411.5-rc.1/go.mod h1:n6VeI9cKFxmXCauD7Ji9lgTAg+2TYGLZu5AXgVJB4tk= github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241213092551-33a63fce8214 h1:94dIMFDCafAQ3FCC1pryuhgfZc1jPoDwK4xSMOPshN8= github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241213092551-33a63fce8214/go.mod h1:9feO8jcL5OZ1tvRjEfNAHz4Aggvd6373l+ZxmZZAyZs= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= @@ -514,6 +512,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mdehoog/op-geth v0.0.0-20250102015754-dc3e6fe74538 h1:Qze+Ib3oTftmUNFlnP7w4/yEYSMKA9ZKFGE/7TfM4TQ= +github.com/mdehoog/op-geth v0.0.0-20250102015754-dc3e6fe74538/go.mod h1:n6VeI9cKFxmXCauD7Ji9lgTAg+2TYGLZu5AXgVJB4tk= github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU= github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= diff --git a/op-chain-ops/cmd/deposit-hash/main.go b/op-chain-ops/cmd/deposit-hash/main.go index 9166a0667c09..cc52a9667652 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-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index 5df3f4510fed..704328fe50ba 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -996,13 +996,8 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Header, l2GenesisBlockHa Hash: l2GenesisBlockHash, Number: l2GenesisBlockNumber, }, - L2Time: l1StartBlock.Time, - SystemConfig: eth.SystemConfig{ - BatcherAddr: d.BatchSenderAddress, - Overhead: eth.Bytes32(common.BigToHash(new(big.Int).SetUint64(d.GasPriceOracleOverhead))), - Scalar: eth.Bytes32(d.FeeScalar()), - GasLimit: uint64(d.L2GenesisBlockGasLimit), - }, + L2Time: l1StartBlock.Time, + SystemConfig: d.GenesisSystemConfig(), }, BlockTime: d.L2BlockTime, MaxSequencerDrift: d.MaxSequencerDrift, @@ -1027,6 +1022,19 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Header, l2GenesisBlockHa }, nil } +// GenesisSystemConfig converts a DeployConfig to a eth.SystemConfig. If Ecotone is active at genesis, the +// Overhead value is considered a noop. +func (d *DeployConfig) GenesisSystemConfig() eth.SystemConfig { + cfg := eth.SystemConfig{ + BatcherAddr: d.BatchSenderAddress, + Overhead: eth.Bytes32(common.BigToHash(new(big.Int).SetUint64(d.GasPriceOracleOverhead))), + Scalar: d.FeeScalar(), + GasLimit: uint64(d.L2GenesisBlockGasLimit), + } + cfg.IncrementGenesisNonces((*uint64)(d.L2GenesisIsthmusTimeOffset), d.UseCustomGasToken) + return cfg +} + // NewDeployConfig reads a config file given a path on the filesystem. func NewDeployConfig(path string) (*DeployConfig, error) { file, err := os.ReadFile(path) diff --git a/op-chain-ops/interopgen/recipe.go b/op-chain-ops/interopgen/recipe.go index e70c69e9f481..61c702f03633 100644 --- a/op-chain-ops/interopgen/recipe.go +++ b/op-chain-ops/interopgen/recipe.go @@ -234,6 +234,7 @@ func InteropL2DevConfig(l1ChainID, l2ChainID uint64, addrs devkeys.Addresses) (* L2GenesisFjordTimeOffset: new(hexutil.Uint64), L2GenesisGraniteTimeOffset: new(hexutil.Uint64), L2GenesisHoloceneTimeOffset: new(hexutil.Uint64), + L2GenesisIsthmusTimeOffset: new(hexutil.Uint64), L2GenesisInteropTimeOffset: new(hexutil.Uint64), L1CancunTimeOffset: new(hexutil.Uint64), UseInterop: true, diff --git a/op-e2e/actions/helpers/user.go b/op-e2e/actions/helpers/user.go index d7215b80650f..7ab973ce6a83 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/e2eutils/setup.go b/op-e2e/e2eutils/setup.go index dbc9ecea25b9..e75f4a69eaa5 100644 --- a/op-e2e/e2eutils/setup.go +++ b/op-e2e/e2eutils/setup.go @@ -6,7 +6,6 @@ import ( "path" "time" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -205,6 +204,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) * FjordTime: deployConf.FjordTime(uint64(deployConf.L1GenesisBlockTimestamp)), GraniteTime: deployConf.GraniteTime(uint64(deployConf.L1GenesisBlockTimestamp)), HoloceneTime: deployConf.HoloceneTime(uint64(deployConf.L1GenesisBlockTimestamp)), + IsthmusTime: deployConf.IsthmusTime(uint64(deployConf.L1GenesisBlockTimestamp)), InteropTime: deployConf.InteropTime(uint64(deployConf.L1GenesisBlockTimestamp)), AltDAConfig: pcfg, } @@ -226,16 +226,12 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) * } func SystemConfigFromDeployConfig(deployConfig *genesis.DeployConfig) eth.SystemConfig { - return eth.SystemConfig{ - BatcherAddr: deployConfig.BatchSenderAddress, - Overhead: eth.Bytes32(common.BigToHash(new(big.Int).SetUint64(deployConfig.GasPriceOracleOverhead))), - Scalar: eth.Bytes32(deployConfig.FeeScalar()), - GasLimit: uint64(deployConfig.L2GenesisBlockGasLimit), - } + return deployConfig.GenesisSystemConfig() } func ApplyDeployConfigForks(deployConfig *genesis.DeployConfig) { - isHolocene := os.Getenv("OP_E2E_USE_HOLOCENE") == "true" + isIsthmus := os.Getenv("OP_E2E_USE_ISTHMUS") == "true" + isHolocene := isIsthmus || os.Getenv("OP_E2E_USE_HOLOCENE") == "true" isGranite := isHolocene || os.Getenv("OP_E2E_USE_GRANITE") == "true" isFjord := isGranite || os.Getenv("OP_E2E_USE_FJORD") == "true" isEcotone := isFjord || os.Getenv("OP_E2E_USE_ECOTONE") == "true" @@ -255,6 +251,9 @@ func ApplyDeployConfigForks(deployConfig *genesis.DeployConfig) { if isHolocene { deployConfig.L2GenesisHoloceneTimeOffset = new(hexutil.Uint64) } + if isIsthmus { + deployConfig.L2GenesisIsthmusTimeOffset = new(hexutil.Uint64) + } // Canyon and lower is activated by default deployConfig.L2GenesisCanyonTimeOffset = new(hexutil.Uint64) deployConfig.L2GenesisRegolithTimeOffset = new(hexutil.Uint64) diff --git a/op-e2e/system/bridge/bridge_test.go b/op-e2e/system/bridge/bridge_test.go index 655cf612c9ad..8ce73cf26f4c 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/e2esys/setup.go b/op-e2e/system/e2esys/setup.go index a54a46d1e5db..9934abe38c21 100644 --- a/op-e2e/system/e2esys/setup.go +++ b/op-e2e/system/e2esys/setup.go @@ -638,6 +638,7 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, FjordTime: cfg.DeployConfig.FjordTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), GraniteTime: cfg.DeployConfig.GraniteTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), HoloceneTime: cfg.DeployConfig.HoloceneTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + IsthmusTime: cfg.DeployConfig.IsthmusTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), InteropTime: cfg.DeployConfig.InteropTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), ProtocolVersionsAddress: cfg.L1Deployments.ProtocolVersionsProxy, AltDAConfig: rollupAltDAConfig, diff --git a/op-e2e/system/gastoken/gastoken_test.go b/op-e2e/system/gastoken/gastoken_test.go index bde29ddfa52c..0f9f1c2c0df2 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 10c16c0e7465..69d603a1e213 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/attributes/engine_consolidate.go b/op-node/rollup/attributes/engine_consolidate.go index 3cd9e8931210..e8a55b7d7569 100644 --- a/op-node/rollup/attributes/engine_consolidate.go +++ b/op-node/rollup/attributes/engine_consolidate.go @@ -138,7 +138,9 @@ func logL1InfoTxns(rollupCfg *rollup.Config, l log.Logger, l2Number, l2Timestamp "unsafe_l1_time", unsafeInfo.Time, "unsafe_seq_num", unsafeInfo.SequenceNumber, "unsafe_l1_basefee", unsafeInfo.BaseFee, "unsafe_batcher_addr", unsafeInfo.BatcherAddr, ) - if bytes.HasPrefix(safeTxValue.Data(), types.EcotoneL1AttributesSelector) { + if bytes.HasPrefix(safeTxValue.Data(), types.EcotoneL1AttributesSelector[:]) || + bytes.HasPrefix(safeTxValue.Data(), types.IsthmusL1AttributesSelector[:]) || + bytes.HasPrefix(safeTxValue.Data(), types.InteropL1AttributesSelector[:]) { l.Error("L1 Info transaction differs", "safe_l1_blob_basefee", safeInfo.BlobBaseFee, "safe_l1_basefee_scalar", safeInfo.BaseFeeScalar, diff --git a/op-node/rollup/derive/attributes.go b/op-node/rollup/derive/attributes.go index 5d859a9fcd59..7abd120c12ce 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 } @@ -124,6 +121,14 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex upgradeTxs = append(upgradeTxs, fjord...) } + if ba.rollupCfg.IsIsthmusActivationBlock(nextL2Time) { + isthmus, err := IsthmusNetworkUpgradeTransactions() + if err != nil { + return nil, NewCriticalError(fmt.Errorf("failed to build isthmus network upgrade txs: %w", err)) + } + upgradeTxs = append(upgradeTxs, isthmus...) + } + l1InfoTx, err := L1InfoDepositBytes(ba.rollupCfg, sysConfig, seqNumber, l1Info, nextL2Time) if err != nil { return nil, NewCriticalError(fmt.Errorf("failed to create l1InfoTx: %w", err)) diff --git a/op-node/rollup/derive/deposit_log.go b/op-node/rollup/derive/deposit_log.go index 74fde28c5988..7a3ae59a4215 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.Rsh(i, 128).Uint64() + return +} + +func PackNonceAndVersion(nonce uint64, version uint64) common.Hash { + i := new(big.Int).SetUint64(nonce) + i.Lsh(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 4cb335b1bef4..03a47f8a4dc5 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 49721104b086..ee172644bbf1 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 b71cd619678c..10fbb5f3f328 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 afb7e0850e26..abc5c70f5f3f 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/isthmus_upgrade_transactions.go b/op-node/rollup/derive/isthmus_upgrade_transactions.go new file mode 100644 index 000000000000..f6c71c0e9f51 --- /dev/null +++ b/op-node/rollup/derive/isthmus_upgrade_transactions.go @@ -0,0 +1,39 @@ +package derive + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +var ( + IsthmusL1BlockDeployerAddress = common.HexToAddress("0x4210000000000000000000000000000000000005") + deployIsthmusL1BlockSource = UpgradeDepositSource{Intent: "Isthmus: L1 Block Deployment"} + isthmusL1BlockDeploymentBytecode = common.FromHex("0x608060405234801561001057600080fd5b50610b8c806100206000396000f3fe608060405234801561001057600080fd5b506004361061018d5760003560e01c806371cfaa3f116100e3578063c59859181161008c578063e591b28211610066578063e591b28214610387578063e81b2c6d146103a9578063f8206140146103b257600080fd5b8063c59859181461034b578063d84447151461036b578063de35f5cb1461037357600080fd5b80638b239f73116100bd5780638b239f73146103195780639e8c496614610322578063b80777ea1461032b57600080fd5b806371cfaa3f146102d257806374dd5eab146102e55780638381f58a1461030557600080fd5b8063440a5e20116101455780635cf249691161011f5780635cf249691461026b57806364ca23ef1461027457806368d5dca6146102a157600080fd5b8063440a5e201461021957806354fd4d5014610221578063550fcdc91461026357600080fd5b806309bd5a601161017657806309bd5a60146101af57806321326849146101cb5780634397dfef146101e357600080fd5b8063015d8eb914610192578063098999be146101a7575b600080fd5b6101a56101a03660046109f7565b6103bb565b005b6101a56104fa565b6101b860025481565b6040519081526020015b60405180910390f35b6101d3610504565b60405190151581526020016101c2565b6101eb610543565b6040805173ffffffffffffffffffffffffffffffffffffffff909316835260ff9091166020830152016101c2565b6101a5610557565b60408051808201909152600581527f312e362e3000000000000000000000000000000000000000000000000000000060208201525b6040516101c29190610a69565b6102566105ae565b6101b860015481565b6003546102889067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101c2565b6003546102bd9068010000000000000000900463ffffffff1681565b60405163ffffffff90911681526020016101c2565b6101a56102e0366004610adc565b6105bd565b6008546102889068010000000000000000900467ffffffffffffffff1681565b6000546102889067ffffffffffffffff1681565b6101b860055481565b6101b860065481565b6000546102889068010000000000000000900467ffffffffffffffff1681565b6003546102bd906c01000000000000000000000000900463ffffffff1681565b610256610672565b6008546102889067ffffffffffffffff1681565b60405173deaddeaddeaddeaddeaddeaddeaddeaddead000181526020016101c2565b6101b860045481565b6101b860075481565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610462576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4c31426c6f636b3a206f6e6c7920746865206465706f7369746f72206163636f60448201527f756e742063616e20736574204c3120626c6f636b2076616c7565730000000000606482015260840160405180910390fd5b6000805467ffffffffffffffff98891668010000000000000000027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116998916999099179890981790975560019490945560029290925560038054919094167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009190911617909255600491909155600555600655565b61050261067c565b565b60008061050f610543565b5073ffffffffffffffffffffffffffffffffffffffff1673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee141592915050565b60008061054e6106dc565b90939092509050565b73deaddeaddeaddeaddeaddeaddeaddeaddead000133811461058157633cc50b456000526004601cfd5b60043560801c60035560143560801c60005560243560015560443560075560643560025560843560045550565b60606105b861075d565b905090565b3373deaddeaddeaddeaddeaddeaddeaddeaddead00011461060a576040517f3cc50b4500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6106168484848461081e565b604080518381526020810183905260ff85169173ffffffffffffffffffffffffffffffffffffffff8716917f10e43c4d58f3ef4edae7c1ca2e7f02d46b2cadbcc046737038527ed8486ffeb0910160405180910390a350505050565b60606105b86108f0565b73deaddeaddeaddeaddeaddeaddeaddeaddead00013381146106a657633cc50b456000526004601cfd5b60043560801c60035560143560801c60005560243560015560443560075560643560025560843560045560a43560801c60085550565b6000808061071261070e60017f04adb1412b2ddc16fcc0d4538d5c8f07cf9c83abecc6b41f6f69037b708fbcec610b41565b5490565b73ffffffffffffffffffffffffffffffffffffffff81169350905082610751575073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee92601292509050565b60a081901c9150509091565b606060006107696106dc565b5090507fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff8216016107e257505060408051808201909152600381527f4554480000000000000000000000000000000000000000000000000000000000602082015290565b61081861081361070e60017fa48b38a4b44951360fbdcbfaaeae5ed6ae92585412e9841b70ec72ed8cd05764610b41565b6109a6565b91505090565b61088461084c60017f04adb1412b2ddc16fcc0d4538d5c8f07cf9c83abecc6b41f6f69037b708fbcec610b41565b74ff000000000000000000000000000000000000000060a086901b1673ffffffffffffffffffffffffffffffffffffffff8716179055565b6108b76108b260017f657c3582c29b3176614e3a33ddd1ec48352696a04e92b3c0566d72010fa8863d610b41565b839055565b6108ea6108e560017fa48b38a4b44951360fbdcbfaaeae5ed6ae92585412e9841b70ec72ed8cd05764610b41565b829055565b50505050565b606060006108fc6106dc565b5090507fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff82160161097557505060408051808201909152600581527f4574686572000000000000000000000000000000000000000000000000000000602082015290565b61081861081361070e60017f657c3582c29b3176614e3a33ddd1ec48352696a04e92b3c0566d72010fa8863d610b41565b60405160005b82811a156109bc576001016109ac565b80825260208201838152600082820152505060408101604052919050565b803567ffffffffffffffff811681146109f257600080fd5b919050565b600080600080600080600080610100898b031215610a1457600080fd5b610a1d896109da565b9750610a2b60208a016109da565b96506040890135955060608901359450610a4760808a016109da565b979a969950949793969560a0850135955060c08501359460e001359350915050565b600060208083528351808285015260005b81811015610a9657858101830151858201604001528201610a7a565b81811115610aa8576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60008060008060808587031215610af257600080fd5b843573ffffffffffffffffffffffffffffffffffffffff81168114610b1657600080fd5b9350602085013560ff81168114610b2c57600080fd5b93969395505050506040820135916060013590565b600082821015610b7a577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b50039056fea164736f6c634300080f000a") +) + +// IsthmusNetworkUpgradeTransactions returns the transactions required to upgrade the Isthmus network. +func IsthmusNetworkUpgradeTransactions() ([]hexutil.Bytes, error) { + upgradeTxns := make([]hexutil.Bytes, 0, 1) + + deployL1BlockTransaction, err := types.NewTx(&types.DepositTx{ + SourceHash: deployIsthmusL1BlockSource.SourceHash(), + From: IsthmusL1BlockDeployerAddress, + To: nil, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 375_000, + IsSystemTransaction: false, + Data: isthmusL1BlockDeploymentBytecode, + }).MarshalBinary() + + if err != nil { + return nil, err + } + + upgradeTxns = append(upgradeTxns, deployL1BlockTransaction) + + return upgradeTxns, nil +} diff --git a/op-node/rollup/derive/isthmus_upgrade_transactions_test.go b/op-node/rollup/derive/isthmus_upgrade_transactions_test.go new file mode 100644 index 000000000000..c37eb0a87f67 --- /dev/null +++ b/op-node/rollup/derive/isthmus_upgrade_transactions_test.go @@ -0,0 +1,49 @@ +package derive + +import ( + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-chain-ops/interopgen" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" +) + +func TestIsthmusSourcesMatchSpec(t *testing.T) { + for _, test := range []struct { + source UpgradeDepositSource + expectedHash string + }{ + { + source: deployIsthmusL1BlockSource, + expectedHash: "0x3b2d0821ca2411ad5cd3595804d1213d15737188ae4cbd58aa19c821a6c211bf", + }, + } { + require.Equal(t, common.HexToHash(test.expectedHash), test.source.SourceHash()) + } +} + +func TestIsthmusNetworkTransactions(t *testing.T) { + upgradeTxns, err := IsthmusNetworkUpgradeTransactions() + require.NoError(t, err) + require.Len(t, upgradeTxns, 1) + + deployL1BlockSender, deployL1Block := toDepositTxn(t, upgradeTxns[0]) + require.Equal(t, deployL1BlockSender, common.HexToAddress("0x4210000000000000000000000000000000000005")) + require.Equal(t, deployIsthmusL1BlockSource.SourceHash(), deployL1Block.SourceHash()) + require.Nil(t, deployL1Block.To()) + require.Equal(t, uint64(375_000), deployL1Block.Gas()) + require.Equal(t, isthmusL1BlockDeploymentBytecode, deployL1Block.Data()) + + l1 := interopgen.CreateL1(log.Root(), nil, nil, &interopgen.L1Config{ + ChainID: big.NewInt(1337), + }) + address, err := l1.Create(deployL1BlockSender, isthmusL1BlockDeploymentBytecode) + require.NoError(t, err) + require.Equal(t, address, common.HexToAddress("0x4fa2Be8cd41504037F1838BcE3bCC93bC68Ff537")) + codeHash := crypto.Keccak256Hash(l1.GetCode(address)) + require.Equal(t, codeHash, common.HexToHash("0x68d031cd7a8147e7799609e42996a2b798d7c9e3dffad8960012432c146af8ad")) +} diff --git a/op-node/rollup/derive/l1_block_info.go b/op-node/rollup/derive/l1_block_info.go index a01fe5bca6b9..d7f55d7e9116 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,7 @@ func (info *L1BlockInfo) unmarshalBinaryBedrock(data []byte) error { return nil } -// Interop & Ecotone Binary Format +// Ecotone Binary Format // +---------+--------------------------+ // | Bytes | Field | // +---------+--------------------------+ @@ -171,24 +177,55 @@ func (info *L1BlockInfo) unmarshalBinaryBedrock(data []byte) error { // | 32 | BatcherHash | // +---------+--------------------------+ +// Isthmus & Interop 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 | +// | 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) } @@ -319,6 +389,15 @@ func L1InfoDeposit(rollupCfg *rollup.Config, sysCfg eth.SystemConfig, seqNumber SequenceNumber: seqNumber, BatcherAddr: sysCfg.BatcherAddr, } + isthmus := isIsthmusButNotFirstBlock(rollupCfg, l2Timestamp) + if !isthmus { + if sysCfg.DepositNonce != 0 { + return nil, fmt.Errorf("found non-zero deposit nonce for non-Isthmus block") + } + if sysCfg.ConfigUpdateNonce != 0 { + return nil, fmt.Errorf("found non-zero config update nonce for non-Isthmus block") + } + } var data []byte if isEcotoneButNotFirstBlock(rollupCfg, l2Timestamp) { l1BlockInfo.BlobBaseFee = block.BlobBaseFee() @@ -332,27 +411,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 isthmus { + 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/l1_block_info_test.go b/op-node/rollup/derive/l1_block_info_test.go index 3f7dd0647e6d..d37da2e932c7 100644 --- a/op-node/rollup/derive/l1_block_info_test.go +++ b/op-node/rollup/derive/l1_block_info_test.go @@ -63,6 +63,19 @@ func TestParseL1InfoDepositTxData(t *testing.T) { return 0 }}, } + assertInfo := func(rollupCfg *rollup.Config, info *testutils.MockBlockInfo, depTx *types.DepositTx, l2BlockTime, seqNr uint64, l1Cfg eth.SystemConfig) { + res, err := L1BlockInfoFromBytes(rollupCfg, l2BlockTime, depTx.Data) + require.NoError(t, err, "expected valid deposit info") + assert.Equal(t, res.Number, info.NumberU64()) + assert.Equal(t, res.Time, info.Time()) + assert.True(t, res.BaseFee.Sign() >= 0) + assert.Equal(t, res.BaseFee.Bytes(), info.BaseFee().Bytes()) + assert.Equal(t, res.BlockHash, info.Hash()) + assert.Equal(t, res.SequenceNumber, seqNr) + assert.Equal(t, res.BatcherAddr, l1Cfg.BatcherAddr) + assert.Equal(t, res.L1FeeOverhead, l1Cfg.Overhead) + assert.Equal(t, res.L1FeeScalar, l1Cfg.Scalar) + } var rollupCfg rollup.Config for i, testCase := range cases { t.Run(testCase.name, func(t *testing.T) { @@ -72,17 +85,7 @@ func TestParseL1InfoDepositTxData(t *testing.T) { seqNr := testCase.seqNr(rng) depTx, err := L1InfoDeposit(&rollupCfg, l1Cfg, seqNr, info, 0) require.NoError(t, err) - res, err := L1BlockInfoFromBytes(&rollupCfg, info.Time(), depTx.Data) - require.NoError(t, err, "expected valid deposit info") - assert.Equal(t, res.Number, info.NumberU64()) - assert.Equal(t, res.Time, info.Time()) - assert.True(t, res.BaseFee.Sign() >= 0) - assert.Equal(t, res.BaseFee.Bytes(), info.BaseFee().Bytes()) - assert.Equal(t, res.BlockHash, info.Hash()) - assert.Equal(t, res.SequenceNumber, seqNr) - assert.Equal(t, res.BatcherAddr, l1Cfg.BatcherAddr) - assert.Equal(t, res.L1FeeOverhead, l1Cfg.Overhead) - assert.Equal(t, res.L1FeeScalar, l1Cfg.Scalar) + assertInfo(&rollupCfg, info, depTx, 0, seqNr, l1Cfg) }) } t.Run("no data", func(t *testing.T) { @@ -112,10 +115,13 @@ func TestParseL1InfoDepositTxData(t *testing.T) { info := testutils.MakeBlockInfo(nil)(rng) rollupCfg := rollup.Config{} rollupCfg.ActivateAtGenesis(rollup.Regolith) - depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, 0) + l1Cfg := randomL1Cfg(rng, info) + seqNr := randomSeqNr(rng) + depTx, err := L1InfoDeposit(&rollupCfg, l1Cfg, seqNr, info, 0) require.NoError(t, err) require.False(t, depTx.IsSystemTransaction) require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) + assertInfo(&rollupCfg, info, depTx, 0, seqNr, l1Cfg) }) t.Run("ecotone", func(t *testing.T) { rng := rand.New(rand.NewSource(1234)) @@ -124,11 +130,14 @@ func TestParseL1InfoDepositTxData(t *testing.T) { rollupCfg.ActivateAtGenesis(rollup.Ecotone) // run 1 block after ecotone transition timestamp := rollupCfg.Genesis.L2Time + rollupCfg.BlockTime - depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, timestamp) + l1Cfg := randomL1Cfg(rng, info) + seqNr := randomSeqNr(rng) + depTx, err := L1InfoDeposit(&rollupCfg, l1Cfg, seqNr, info, timestamp) require.NoError(t, err) require.False(t, depTx.IsSystemTransaction) require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) require.Equal(t, L1InfoEcotoneLen, len(depTx.Data)) + assertInfo(&rollupCfg, info, depTx, timestamp, seqNr, l1Cfg) }) t.Run("activation-block ecotone", func(t *testing.T) { rng := rand.New(rand.NewSource(1234)) @@ -137,22 +146,77 @@ func TestParseL1InfoDepositTxData(t *testing.T) { rollupCfg.ActivateAtGenesis(rollup.Delta) ecotoneTime := rollupCfg.Genesis.L2Time + rollupCfg.BlockTime // activate ecotone just after genesis rollupCfg.EcotoneTime = &ecotoneTime - depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, ecotoneTime) + l1Cfg := randomL1Cfg(rng, info) + seqNr := randomSeqNr(rng) + depTx, err := L1InfoDeposit(&rollupCfg, l1Cfg, seqNr, info, ecotoneTime) require.NoError(t, err) require.False(t, depTx.IsSystemTransaction) require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) require.Equal(t, L1InfoBedrockLen, len(depTx.Data)) + assertInfo(&rollupCfg, info, depTx, ecotoneTime, seqNr, l1Cfg) }) t.Run("genesis-block ecotone", func(t *testing.T) { rng := rand.New(rand.NewSource(1234)) info := testutils.MakeBlockInfo(nil)(rng) rollupCfg := rollup.Config{BlockTime: 2, Genesis: rollup.Genesis{L2Time: 1000}} rollupCfg.ActivateAtGenesis(rollup.Ecotone) - depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, rollupCfg.Genesis.L2Time) + l1Cfg := randomL1Cfg(rng, info) + seqNr := randomSeqNr(rng) + depTx, err := L1InfoDeposit(&rollupCfg, l1Cfg, seqNr, info, rollupCfg.Genesis.L2Time) require.NoError(t, err) require.False(t, depTx.IsSystemTransaction) require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) require.Equal(t, L1InfoEcotoneLen, len(depTx.Data)) + assertInfo(&rollupCfg, info, depTx, rollupCfg.Genesis.L2Time, seqNr, l1Cfg) + }) + t.Run("isthmus", func(t *testing.T) { + rng := rand.New(rand.NewSource(1234)) + info := testutils.MakeBlockInfo(nil)(rng) + rollupCfg := rollup.Config{BlockTime: 2, Genesis: rollup.Genesis{L2Time: 1000}} + rollupCfg.ActivateAtGenesis(rollup.Isthmus) + // run 1 block after isthmus transition + timestamp := rollupCfg.Genesis.L2Time + rollupCfg.BlockTime + l1Cfg := randomL1Cfg(rng, info) + seqNr := randomSeqNr(rng) + depTx, err := L1InfoDeposit(&rollupCfg, l1Cfg, seqNr, info, timestamp) + require.NoError(t, err) + require.False(t, depTx.IsSystemTransaction) + require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) + require.Equal(t, L1InfoIsthmusLen, len(depTx.Data), "the length is same in isthmus") + require.Equal(t, L1InfoFuncIsthmusBytes4, depTx.Data[:4], "upgrade is active, need isthmus signature") + assertInfo(&rollupCfg, info, depTx, timestamp, seqNr, l1Cfg) + }) + t.Run("activation-block isthmus", func(t *testing.T) { + rng := rand.New(rand.NewSource(1234)) + info := testutils.MakeBlockInfo(nil)(rng) + rollupCfg := rollup.Config{BlockTime: 2, Genesis: rollup.Genesis{L2Time: 1000}} + rollupCfg.ActivateAtGenesis(rollup.Holocene) + isthmusTime := rollupCfg.Genesis.L2Time + rollupCfg.BlockTime // activate isthmus just after genesis + rollupCfg.IsthmusTime = &isthmusTime + l1Cfg := randomL1Cfg(rng, info) + seqNr := randomSeqNr(rng) + depTx, err := L1InfoDeposit(&rollupCfg, l1Cfg, seqNr, info, isthmusTime) + require.NoError(t, err) + require.False(t, depTx.IsSystemTransaction) + require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) + // Isthmus activates, but ecotone L1 info is still used at this upgrade block + require.Equal(t, L1InfoEcotoneLen, len(depTx.Data)) + require.Equal(t, L1InfoFuncEcotoneBytes4, depTx.Data[:4]) + assertInfo(&rollupCfg, info, depTx, isthmusTime, seqNr, l1Cfg) + }) + t.Run("genesis-block isthmus", func(t *testing.T) { + rng := rand.New(rand.NewSource(1234)) + info := testutils.MakeBlockInfo(nil)(rng) + rollupCfg := rollup.Config{BlockTime: 2, Genesis: rollup.Genesis{L2Time: 1000}} + rollupCfg.ActivateAtGenesis(rollup.Isthmus) + l1Cfg := randomL1Cfg(rng, info) + seqNr := randomSeqNr(rng) + depTx, err := L1InfoDeposit(&rollupCfg, l1Cfg, seqNr, info, rollupCfg.Genesis.L2Time) + require.NoError(t, err) + require.False(t, depTx.IsSystemTransaction) + require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) + require.Equal(t, L1InfoIsthmusLen, len(depTx.Data)) + assertInfo(&rollupCfg, info, depTx, rollupCfg.Genesis.L2Time, seqNr, l1Cfg) }) t.Run("interop", func(t *testing.T) { rng := rand.New(rand.NewSource(1234)) @@ -161,12 +225,15 @@ func TestParseL1InfoDepositTxData(t *testing.T) { rollupCfg.ActivateAtGenesis(rollup.Interop) // run 1 block after interop transition timestamp := rollupCfg.Genesis.L2Time + rollupCfg.BlockTime - depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, timestamp) + l1Cfg := randomL1Cfg(rng, info) + seqNr := randomSeqNr(rng) + depTx, err := L1InfoDeposit(&rollupCfg, l1Cfg, seqNr, info, timestamp) require.NoError(t, err) require.False(t, depTx.IsSystemTransaction) require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) - require.Equal(t, L1InfoEcotoneLen, len(depTx.Data), "the length is same in interop") + require.Equal(t, L1InfoIsthmusLen, len(depTx.Data), "the length is same in interop") require.Equal(t, L1InfoFuncInteropBytes4, depTx.Data[:4], "upgrade is active, need interop signature") + assertInfo(&rollupCfg, info, depTx, timestamp, seqNr, l1Cfg) }) t.Run("activation-block interop", func(t *testing.T) { rng := rand.New(rand.NewSource(1234)) @@ -175,24 +242,30 @@ func TestParseL1InfoDepositTxData(t *testing.T) { rollupCfg.ActivateAtGenesis(rollup.Fjord) interopTime := rollupCfg.Genesis.L2Time + rollupCfg.BlockTime // activate interop just after genesis rollupCfg.InteropTime = &interopTime - depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, interopTime) + l1Cfg := randomL1Cfg(rng, info) + seqNr := randomSeqNr(rng) + depTx, err := L1InfoDeposit(&rollupCfg, l1Cfg, seqNr, info, interopTime) require.NoError(t, err) require.False(t, depTx.IsSystemTransaction) require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) // Interop activates, but ecotone L1 info is still used at this upgrade block require.Equal(t, L1InfoEcotoneLen, len(depTx.Data)) require.Equal(t, L1InfoFuncEcotoneBytes4, depTx.Data[:4]) + assertInfo(&rollupCfg, info, depTx, interopTime, seqNr, l1Cfg) }) t.Run("genesis-block interop", func(t *testing.T) { rng := rand.New(rand.NewSource(1234)) info := testutils.MakeBlockInfo(nil)(rng) rollupCfg := rollup.Config{BlockTime: 2, Genesis: rollup.Genesis{L2Time: 1000}} rollupCfg.ActivateAtGenesis(rollup.Interop) - depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, rollupCfg.Genesis.L2Time) + l1Cfg := randomL1Cfg(rng, info) + seqNr := randomSeqNr(rng) + depTx, err := L1InfoDeposit(&rollupCfg, l1Cfg, seqNr, info, rollupCfg.Genesis.L2Time) require.NoError(t, err) require.False(t, depTx.IsSystemTransaction) require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) - require.Equal(t, L1InfoEcotoneLen, len(depTx.Data)) + require.Equal(t, L1InfoIsthmusLen, len(depTx.Data)) + assertInfo(&rollupCfg, info, depTx, rollupCfg.Genesis.L2Time, seqNr, l1Cfg) }) } diff --git a/op-node/rollup/derive/l1_block_info_tob_test.go b/op-node/rollup/derive/l1_block_info_tob_test.go index d7c9f2f8931d..1454603c9164 100644 --- a/op-node/rollup/derive/l1_block_info_tob_test.go +++ b/op-node/rollup/derive/l1_block_info_tob_test.go @@ -26,6 +26,8 @@ func FuzzParseL1InfoDepositTxDataValid(f *testing.F) { typeProvider.Fuzz(&seqNr) var sysCfg eth.SystemConfig typeProvider.Fuzz(&sysCfg) + sysCfg.DepositNonce = 0 + sysCfg.ConfigUpdateNonce = 0 var rollupCfg rollup.Config // Create our deposit tx from our info diff --git a/op-node/rollup/derive/payload_util.go b/op-node/rollup/derive/payload_util.go index 6da1a952b5fe..2d304d9ae27b 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 72c4e713c30e..1c38c4e57722 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 add179093879..e9530af62828 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-node/rollup/sequencing/sequencer.go b/op-node/rollup/sequencing/sequencer.go index 2476b9b639f9..1cc6e1de60f0 100644 --- a/op-node/rollup/sequencing/sequencer.go +++ b/op-node/rollup/sequencing/sequencer.go @@ -543,11 +543,17 @@ func (d *Sequencer) startBuildingBlock() { d.log.Info("Sequencing Fjord upgrade block") } - // For the Granite activation block we shouldn't include any sequencer transactions. + // For the Granite activation block we allow sequencer transactions. if d.rollupCfg.IsGraniteActivationBlock(uint64(attrs.Timestamp)) { d.log.Info("Sequencing Granite upgrade block") } + // For the Isthmus activation block we shouldn't include any sequencer transactions. + if d.rollupCfg.IsIsthmusActivationBlock(uint64(attrs.Timestamp)) { + attrs.NoTxPool = true + d.log.Info("Sequencing Isthmus upgrade block") + } + d.log.Debug("prepared attributes for new block", "num", l2Head.Number+1, "time", uint64(attrs.Timestamp), "origin", l1Origin, "origin_time", l1Origin.Time, "noTxPool", attrs.NoTxPool) diff --git a/op-node/rollup/superchain.go b/op-node/rollup/superchain.go index ef6ed1f51676..bf5b97d212ca 100644 --- a/op-node/rollup/superchain.go +++ b/op-node/rollup/superchain.go @@ -35,6 +35,7 @@ func LoadOPStackRollupConfig(chainID uint64) (*Config, error) { Scalar: eth.Bytes32(sysCfg.Scalar), GasLimit: sysCfg.GasLimit, } + genesisSysConfig.IncrementGenesisNonces(chConfig.IsthmusTime, chConfig.GasPayingToken != nil) } else { return nil, fmt.Errorf("unable to retrieve genesis SystemConfig of chain %d", chainID) } @@ -92,6 +93,7 @@ func LoadOPStackRollupConfig(chainID uint64) (*Config, error) { FjordTime: chConfig.FjordTime, GraniteTime: chConfig.GraniteTime, HoloceneTime: chConfig.HoloceneTime, + IsthmusTime: chConfig.IsthmusTime, BatchInboxAddress: common.Address(chConfig.BatchInboxAddr), DepositContractAddress: common.Address(addrs.OptimismPortalProxy), L1SystemConfigAddress: common.Address(addrs.SystemConfigProxy), diff --git a/op-program/client/l2/engineapi/l2_engine_api.go b/op-program/client/l2/engineapi/l2_engine_api.go index 45343d8c7fd8..33156bf9d175 100644 --- a/op-program/client/l2/engineapi/l2_engine_api.go +++ b/op-program/client/l2/engineapi/l2_engine_api.go @@ -486,23 +486,24 @@ func (ea *L2EngineAPI) newPayload(_ context.Context, payload *eth.ExecutionPaylo txs[i] = tx } block, err := engine.ExecutableDataToBlock(engine.ExecutableData{ - ParentHash: payload.ParentHash, - FeeRecipient: payload.FeeRecipient, - StateRoot: common.Hash(payload.StateRoot), - ReceiptsRoot: common.Hash(payload.ReceiptsRoot), - LogsBloom: payload.LogsBloom[:], - Random: common.Hash(payload.PrevRandao), - Number: uint64(payload.BlockNumber), - GasLimit: uint64(payload.GasLimit), - GasUsed: uint64(payload.GasUsed), - Timestamp: uint64(payload.Timestamp), - ExtraData: payload.ExtraData, - BaseFeePerGas: (*uint256.Int)(&payload.BaseFeePerGas).ToBig(), - BlockHash: payload.BlockHash, - Transactions: txs, - Withdrawals: toGethWithdrawals(payload), - ExcessBlobGas: (*uint64)(payload.ExcessBlobGas), - BlobGasUsed: (*uint64)(payload.BlobGasUsed), + ParentHash: payload.ParentHash, + FeeRecipient: payload.FeeRecipient, + StateRoot: common.Hash(payload.StateRoot), + ReceiptsRoot: common.Hash(payload.ReceiptsRoot), + LogsBloom: payload.LogsBloom[:], + Random: common.Hash(payload.PrevRandao), + Number: uint64(payload.BlockNumber), + GasLimit: uint64(payload.GasLimit), + GasUsed: uint64(payload.GasUsed), + Timestamp: uint64(payload.Timestamp), + ExtraData: payload.ExtraData, + BaseFeePerGas: (*uint256.Int)(&payload.BaseFeePerGas).ToBig(), + BlockHash: payload.BlockHash, + Transactions: txs, + Withdrawals: toGethWithdrawals(payload), + ExcessBlobGas: (*uint64)(payload.ExcessBlobGas), + BlobGasUsed: (*uint64)(payload.BlobGasUsed), + WithdrawalsRoot: payload.WithdrawalsRoot, }, hashes, root, ea.backend.Config()) if err != nil { log.Debug("Invalid NewPayload params", "params", payload, "error", err) diff --git a/op-service/eth/types.go b/op-service/eth/types.go index 122e9a4df783..8151ea38a1d2 100644 --- a/op-service/eth/types.go +++ b/op-service/eth/types.go @@ -231,6 +231,8 @@ type ExecutionPayload struct { BlobGasUsed *Uint64Quantity `json:"blobGasUsed,omitempty"` // Nil if not present (Bedrock, Canyon, Delta) ExcessBlobGas *Uint64Quantity `json:"excessBlobGas,omitempty"` + // Nil if not present (Bedrock, Canyon, Delta, Ecotone, Fjord, Granite, Holocene) + WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"` } func (payload *ExecutionPayload) ID() BlockID { @@ -307,22 +309,23 @@ func BlockAsPayload(bl *types.Block, shanghaiTime *uint64) (*ExecutionPayload, e } payload := &ExecutionPayload{ - ParentHash: bl.ParentHash(), - FeeRecipient: bl.Coinbase(), - StateRoot: Bytes32(bl.Root()), - ReceiptsRoot: Bytes32(bl.ReceiptHash()), - LogsBloom: Bytes256(bl.Bloom()), - PrevRandao: Bytes32(bl.MixDigest()), - BlockNumber: Uint64Quantity(bl.NumberU64()), - GasLimit: Uint64Quantity(bl.GasLimit()), - GasUsed: Uint64Quantity(bl.GasUsed()), - Timestamp: Uint64Quantity(bl.Time()), - ExtraData: bl.Extra(), - BaseFeePerGas: Uint256Quantity(*baseFee), - BlockHash: bl.Hash(), - Transactions: opaqueTxs, - ExcessBlobGas: (*Uint64Quantity)(bl.ExcessBlobGas()), - BlobGasUsed: (*Uint64Quantity)(bl.BlobGasUsed()), + ParentHash: bl.ParentHash(), + FeeRecipient: bl.Coinbase(), + StateRoot: Bytes32(bl.Root()), + ReceiptsRoot: Bytes32(bl.ReceiptHash()), + LogsBloom: Bytes256(bl.Bloom()), + PrevRandao: Bytes32(bl.MixDigest()), + BlockNumber: Uint64Quantity(bl.NumberU64()), + GasLimit: Uint64Quantity(bl.GasLimit()), + GasUsed: Uint64Quantity(bl.GasUsed()), + Timestamp: Uint64Quantity(bl.Time()), + ExtraData: bl.Extra(), + BaseFeePerGas: Uint256Quantity(*baseFee), + BlockHash: bl.Hash(), + Transactions: opaqueTxs, + ExcessBlobGas: (*Uint64Quantity)(bl.ExcessBlobGas()), + BlobGasUsed: (*Uint64Quantity)(bl.BlobGasUsed()), + WithdrawalsRoot: bl.WithdrawalsRoot(), } if shanghaiTime != nil && uint64(payload.Timestamp) >= *shanghaiTime { @@ -455,6 +458,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 @@ -518,6 +525,19 @@ func (sysCfg *SystemConfig) EcotoneScalars() (EcotoneScalars, error) { return DecodeScalar(sysCfg.Scalar) } +func (sysCfg *SystemConfig) IncrementGenesisNonces(isthmusTime *uint64, customGasToken bool) { + // if Isthmus is active at genesis, we must ensure the nonces are set correctly + if isthmusTime != nil && *isthmusTime == 0 { + // SystemConfig emits 3 ConfigUpdate events which increments the nonce by 3 + sysCfg.ConfigUpdateNonce += 3 + // If a custom gas token is in use, a TransactionDeposited event is emitted from + // the OptimismPortal, which increments the deposit nonce by 1 + if customGasToken { + sysCfg.DepositNonce += 1 + } + } +} + // DecodeScalar decodes the blobBaseFeeScalar and baseFeeScalar from a 32-byte scalar value. // It uses the first byte to determine the scalar format. func DecodeScalar(scalar [32]byte) (EcotoneScalars, error) { diff --git a/op-service/eth/types_test.go b/op-service/eth/types_test.go index 03409e16008d..40d69343e3eb 100644 --- a/op-service/eth/types_test.go +++ b/op-service/eth/types_test.go @@ -86,7 +86,7 @@ func TestSystemConfigMarshaling(t *testing.T) { } j, err := json.Marshal(sysConfig) require.NoError(t, err) - require.Equal(t, `{"batcherAddr":"0x4100000000000000000000000000000000000000","overhead":"0x0405060000000000000000000000000000000000000000000000000000000000","scalar":"0x0708090000000000000000000000000000000000000000000000000000000000","gasLimit":1234,"eip1559Params":"0x0000000000000000"}`, string(j)) + require.Equal(t, `{"batcherAddr":"0x4100000000000000000000000000000000000000","overhead":"0x0405060000000000000000000000000000000000000000000000000000000000","scalar":"0x0708090000000000000000000000000000000000000000000000000000000000","gasLimit":1234,"eip1559Params":"0x0000000000000000","depositNonce":0,"configUpdateNonce":0}`, string(j)) sysConfig.MarshalPreHolocene = true j, err = json.Marshal(sysConfig) require.NoError(t, err) diff --git a/op-service/sources/types.go b/op-service/sources/types.go index f337beb02058..27a234890537 100644 --- a/op-service/sources/types.go +++ b/op-service/sources/types.go @@ -293,23 +293,24 @@ func (block *RPCBlock) ExecutionPayloadEnvelope(trustCache bool) (*eth.Execution } payload := ð.ExecutionPayload{ - ParentHash: block.ParentHash, - FeeRecipient: block.Coinbase, - StateRoot: eth.Bytes32(block.Root), - ReceiptsRoot: eth.Bytes32(block.ReceiptHash), - LogsBloom: block.Bloom, - PrevRandao: eth.Bytes32(block.MixDigest), // mix-digest field is used for prevRandao post-merge - BlockNumber: block.Number, - GasLimit: block.GasLimit, - GasUsed: block.GasUsed, - Timestamp: block.Time, - ExtraData: eth.BytesMax32(block.Extra), - BaseFeePerGas: eth.Uint256Quantity(baseFee), - BlockHash: block.Hash, - Transactions: opaqueTxs, - Withdrawals: block.Withdrawals, - BlobGasUsed: block.BlobGasUsed, - ExcessBlobGas: block.ExcessBlobGas, + ParentHash: block.ParentHash, + FeeRecipient: block.Coinbase, + StateRoot: eth.Bytes32(block.Root), + ReceiptsRoot: eth.Bytes32(block.ReceiptHash), + LogsBloom: block.Bloom, + PrevRandao: eth.Bytes32(block.MixDigest), // mix-digest field is used for prevRandao post-merge + BlockNumber: block.Number, + GasLimit: block.GasLimit, + GasUsed: block.GasUsed, + Timestamp: block.Time, + ExtraData: eth.BytesMax32(block.Extra), + BaseFeePerGas: eth.Uint256Quantity(baseFee), + BlockHash: block.Hash, + Transactions: opaqueTxs, + Withdrawals: block.Withdrawals, + BlobGasUsed: block.BlobGasUsed, + ExcessBlobGas: block.ExcessBlobGas, + WithdrawalsRoot: block.WithdrawalsRoot, } return ð.ExecutionPayloadEnvelope{ diff --git a/op-wheel/commands.go b/op-wheel/commands.go index 521578a34cb6..7500399360c1 100644 --- a/op-wheel/commands.go +++ b/op-wheel/commands.go @@ -256,6 +256,8 @@ func rollupFromGethConfig(cfg *params.ChainConfig) *rollup.Config { CanyonTime: cfg.CanyonTime, EcotoneTime: cfg.EcotoneTime, GraniteTime: cfg.GraniteTime, + HoloceneTime: cfg.HoloceneTime, + IsthmusTime: cfg.IsthmusTime, InteropTime: cfg.InteropTime, } } diff --git a/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol b/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol index 74ba7b4790e9..e5d0b927ef90 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); diff --git a/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol b/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol index 58fe5eff5dca..2d46cb141792 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol @@ -39,13 +39,14 @@ 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); receive() external payable; + function DEPOSIT_NONCE_SLOT() external view returns (bytes32); function balance() external view returns (uint256); function blacklistDisputeGame(IDisputeGame _disputeGame) external; function checkWithdrawal(bytes32 _withdrawalHash, address _proofSubmitter) external view; @@ -93,6 +94,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 nonce_); function proveWithdrawalTransaction( Types.WithdrawalTransaction memory _tx, uint256 _disputeGameIndex, diff --git a/packages/contracts-bedrock/interfaces/L1/IOptimismPortalIsthmus.sol b/packages/contracts-bedrock/interfaces/L1/IOptimismPortalIsthmus.sol new file mode 100644 index 000000000000..963a5c2c6dfe --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L1/IOptimismPortalIsthmus.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { Types } from "src/libraries/Types.sol"; +import { GameType, Timestamp } from "src/dispute/lib/LibUDT.sol"; +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; + +interface IOptimismPortalIsthmus { + error AlreadyFinalized(); + error BadTarget(); + error Blacklisted(); + error CallPaused(); + error ContentLengthMismatch(); + error EmptyItem(); + error GasEstimation(); + error InvalidDataRemainder(); + error InvalidDisputeGame(); + error InvalidGameType(); + error InvalidHeader(); + error InvalidMerkleProof(); + error InvalidProof(); + error LargeCalldata(); + error NoValue(); + error NonReentrant(); + error OnlyCustomGasToken(); + error OutOfGas(); + error ProposalNotValidated(); + error SmallGasLimit(); + error TransferFailed(); + error Unauthorized(); + error UnexpectedList(); + error UnexpectedString(); + error Unproven(); + + 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 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); + + receive() external payable; + + function DEPOSIT_NONCE_SLOT() external view returns (bytes32); + function balance() external view returns (uint256); + function blacklistDisputeGame(IDisputeGame _disputeGame) external; + function checkWithdrawal(bytes32 _withdrawalHash, address _proofSubmitter) external view; + function depositERC20Transaction( + address _to, + uint256 _mint, + uint256 _value, + uint64 _gasLimit, + bool _isCreation, + bytes memory _data + ) + external; + function depositTransaction( + address _to, + uint256 _value, + uint64 _gasLimit, + bool _isCreation, + bytes memory _data + ) + external + payable; + function disputeGameBlacklist(IDisputeGame) external view returns (bool); + function disputeGameFactory() external view returns (IDisputeGameFactory); + function disputeGameFinalityDelaySeconds() external view returns (uint256); + function donateETH() external payable; + function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external; + function finalizeWithdrawalTransactionExternalProof( + Types.WithdrawalTransaction memory _tx, + address _proofSubmitter + ) + external; + function finalizedWithdrawals(bytes32) external view returns (bool); + function guardian() external view returns (address); + function initialize( + IDisputeGameFactory _disputeGameFactory, + ISystemConfig _systemConfig, + ISuperchainConfig _superchainConfig, + GameType _initialRespectedGameType + ) + external; + function l2Sender() external view returns (address); + function minimumGasLimit(uint64 _byteCount) external pure returns (uint64); + function numProofSubmitters(bytes32 _withdrawalHash) external view returns (uint256); + function params() external view returns (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum); // nosemgrep + 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 nonce_); + function proveWithdrawalTransaction( + Types.WithdrawalTransaction memory _tx, + uint256 _disputeGameIndex, + Types.OutputRootProof memory _outputRootProof, + bytes[] memory _withdrawalProof + ) + external; + function provenWithdrawals( + bytes32, + address + ) + external + view + returns (IDisputeGame disputeGameProxy, uint64 timestamp); // nosemgrep + function respectedGameType() external view returns (GameType); + function respectedGameTypeUpdatedAt() external view returns (uint64); + function setGasPayingToken(address _token, uint8 _decimals, bytes32 _name, bytes32 _symbol) external; + function setRespectedGameType(GameType _gameType) external; + function superchainConfig() external view returns (ISuperchainConfig); + function systemConfig() external view returns (ISystemConfig); + function version() external pure returns (string memory); + + function __constructor__(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds) external; +} diff --git a/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol b/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol index 904375167f48..a1504263445c 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); @@ -46,6 +46,7 @@ interface ISystemConfig { function eip1559Denominator() external view returns (uint32); function eip1559Elasticity() external view returns (uint32); function gasPayingToken() external view returns (address addr_, uint8 decimals_); + function gasPayingTokenAddress() external view returns (address addr_); function gasPayingTokenName() external view returns (string memory name_); function gasPayingTokenSymbol() external view returns (string memory symbol_); function initialize( diff --git a/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol b/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol index 4cf4a06f943f..67744c535b1c 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); @@ -18,7 +18,9 @@ interface ISystemConfigInterop { function OPTIMISM_PORTAL_SLOT() external view returns (bytes32); function START_BLOCK_SLOT() external view returns (bytes32); function UNSAFE_BLOCK_SIGNER_SLOT() external view returns (bytes32); + function CONFIG_UPDATE_NONCE_SLOT() external view returns (bytes32); function VERSION() external view returns (uint256); + function VERSION_1() external view returns (uint256); function basefeeScalar() external view returns (uint32); function batchInbox() external view returns (address addr_); function batcherHash() external view returns (bytes32); @@ -27,7 +29,9 @@ 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 nonce_); function gasPayingToken() external view returns (address addr_, uint8 decimals_); + function gasPayingTokenAddress() external view returns (address addr_); function gasPayingTokenName() external view returns (string memory name_); function gasPayingTokenSymbol() external view returns (string memory symbol_); function isCustomGasToken() external view returns (bool); diff --git a/packages/contracts-bedrock/interfaces/L1/ISystemConfigIsthmus.sol b/packages/contracts-bedrock/interfaces/L1/ISystemConfigIsthmus.sol new file mode 100644 index 000000000000..1f48d25175a7 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L1/ISystemConfigIsthmus.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; + +/// @notice This interface corresponds to the Custom Gas Token version of the SystemConfig contract. +interface ISystemConfigIsthmus { + event ConfigUpdate(uint256 indexed nonceAndVersion, ISystemConfig.UpdateType indexed updateType, bytes data); + event Initialized(uint8 version); + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + function BATCH_INBOX_SLOT() external view returns (bytes32); + function DISPUTE_GAME_FACTORY_SLOT() external view returns (bytes32); + function L1_CROSS_DOMAIN_MESSENGER_SLOT() external view returns (bytes32); + function L1_ERC_721_BRIDGE_SLOT() external view returns (bytes32); + function L1_STANDARD_BRIDGE_SLOT() external view returns (bytes32); + function OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT() external view returns (bytes32); + function OPTIMISM_PORTAL_SLOT() external view returns (bytes32); + function START_BLOCK_SLOT() external view returns (bytes32); + function UNSAFE_BLOCK_SIGNER_SLOT() external view returns (bytes32); + function CONFIG_UPDATE_NONCE_SLOT() external view returns (bytes32); + function VERSION() external view returns (uint256); + function VERSION_1() external view returns (uint256); + function basefeeScalar() external view returns (uint32); + function batchInbox() external view returns (address addr_); + function batcherHash() external view returns (bytes32); + function blobbasefeeScalar() external view returns (uint32); + function disputeGameFactory() external view returns (address addr_); + function gasLimit() external view returns (uint64); + function eip1559Denominator() external view returns (uint32); + function eip1559Elasticity() external view returns (uint32); + function configUpdateNonce() external view returns (uint64 nonce_); + function gasPayingToken() external view returns (address addr_, uint8 decimals_); + function gasPayingTokenAddress() external view returns (address addr_); + function gasPayingTokenName() external view returns (string memory name_); + function gasPayingTokenSymbol() external view returns (string memory symbol_); + function initialize( + address _owner, + uint32 _basefeeScalar, + uint32 _blobbasefeeScalar, + bytes32 _batcherHash, + uint64 _gasLimit, + address _unsafeBlockSigner, + IResourceMetering.ResourceConfig memory _config, + address _batchInbox, + ISystemConfig.Addresses memory _addresses + ) + external; + function isCustomGasToken() external view returns (bool); + function l1CrossDomainMessenger() external view returns (address addr_); + function l1ERC721Bridge() external view returns (address addr_); + function l1StandardBridge() external view returns (address addr_); + function maximumGasLimit() external pure returns (uint64); + function minimumGasLimit() external view returns (uint64); + function optimismMintableERC20Factory() external view returns (address addr_); + function optimismPortal() external view returns (address addr_); + function overhead() external view returns (uint256); + function owner() external view returns (address); + function renounceOwnership() external; + function resourceConfig() external view returns (IResourceMetering.ResourceConfig memory); + function scalar() external view returns (uint256); + function setBatcherHash(bytes32 _batcherHash) external; + function setGasConfig(uint256 _overhead, uint256 _scalar) external; + function setGasConfigEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar) external; + function setGasLimit(uint64 _gasLimit) external; + function setUnsafeBlockSigner(address _unsafeBlockSigner) external; + function setEIP1559Params(uint32 _denominator, uint32 _elasticity) external; + function startBlock() external view returns (uint256 startBlock_); + function transferOwnership(address newOwner) external; // nosemgrep + function unsafeBlockSigner() external view returns (address addr_); + function version() external pure returns (string memory); + + function __constructor__() external; +} diff --git a/packages/contracts-bedrock/interfaces/L2/IL1Block.sol b/packages/contracts-bedrock/interfaces/L2/IL1Block.sol index a43b3c7c3963..599f9e037008 100644 --- a/packages/contracts-bedrock/interfaces/L2/IL1Block.sol +++ b/packages/contracts-bedrock/interfaces/L2/IL1Block.sol @@ -12,6 +12,8 @@ interface IL1Block { function batcherHash() external view returns (bytes32); function blobBaseFee() external view returns (uint256); function blobBaseFeeScalar() external view returns (uint32); + function depositNonce() external view returns (uint64); + 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_); @@ -34,6 +36,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 dd72e3fa6f89..ba9e9a08b1ea 100644 --- a/packages/contracts-bedrock/interfaces/L2/IL1BlockInterop.sol +++ b/packages/contracts-bedrock/interfaces/L2/IL1BlockInterop.sol @@ -25,6 +25,8 @@ interface IL1BlockInterop { function batcherHash() external view returns (bytes32); function blobBaseFee() external view returns (uint256); function blobBaseFeeScalar() external view returns (uint32); + function depositNonce() external view returns (uint64); + function configUpdateNonce() external view returns (uint64); function dependencySetSize() external view returns (uint8); function depositsComplete() external; function gasPayingToken() external view returns (address addr_, uint8 decimals_); @@ -52,6 +54,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/justfile b/packages/contracts-bedrock/justfile index 59b275bc474d..221c1f92c76a 100644 --- a/packages/contracts-bedrock/justfile +++ b/packages/contracts-bedrock/justfile @@ -69,11 +69,12 @@ print-pinned-block-number: # rpc call responses in ~/.foundry/cache/rpc. The default block will need to be updated # when the L1 chain is upgraded. # TODO(opcm upgrades): unskip the "NMC" tests which fail on the forked upgrade path with "UnexpectedRootClaim" errors. +# TODO(isthmus upgrades): unskip the "*Isthmus*" tests once mainnet L1 is upgraded, post Isthmus. test-upgrade *ARGS: build-go-ffi #!/bin/bash echo "Running upgrade tests at block $pinnedBlockNumber" export FORK_BLOCK_NUMBER=$pinnedBlockNumber - export NO_MATCH_CONTRACTS="OptimismPortal2WithMockERC20_Test|OptimismPortal2_FinalizeWithdrawal_Test|AnchorStateRegistry_Initialize_Test|AnchorStateRegistry_TryUpdateAnchorState_Test|FaultDisputeGame_Test|FaultDispute_1v1_Actors_Test" + export NO_MATCH_CONTRACTS="OptimismPortal2WithMockERC20_Test|OptimismPortal2_FinalizeWithdrawal_Test|AnchorStateRegistry_Initialize_Test|AnchorStateRegistry_TryUpdateAnchorState_Test|FaultDisputeGame_Test|FaultDispute_1v1_Actors_Test|OptimismPortalIsthmus_Test|SystemConfigIsthmus_Test|SystemConfigIsthmus_Setters_Test" FORK_RPC_URL=$ETH_RPC_URL \ FORK_TEST=true \ forge test --match-path "test/{L1,dispute}/**" \ diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index 6a7519263985..7a153e4583d8 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -20,6 +20,7 @@ import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from import { DeployImplementationsInput, DeployImplementations, + DeployImplementationsIsthmus, DeployImplementationsInterop, DeployImplementationsOutput } from "scripts/deploy/DeployImplementations.s.sol"; @@ -202,7 +203,7 @@ contract Deploy is Deployer { deploySuperchain(); } - deployImplementations({ _isInterop: cfg.useInterop() }); + deployImplementations(); // Deploy Current OPChain Contracts deployOpChain(); @@ -277,9 +278,7 @@ contract Deploy is Deployer { } /// @notice Deploy all of the implementations - function deployImplementations(bool _isInterop) public { - require(_isInterop == cfg.useInterop(), "Deploy: Interop setting mismatch."); - + function deployImplementations() public { console.log("Deploying implementations"); DeployImplementations di = new DeployImplementations(); (DeployImplementationsInput dii, DeployImplementationsOutput dio) = di.etchIOContracts(); @@ -299,8 +298,10 @@ contract Deploy is Deployer { dii.set(dii.protocolVersionsProxy.selector, mustGetAddress("ProtocolVersionsProxy")); dii.set(dii.salt.selector, _implSalt()); - if (_isInterop) { + if (cfg.useInterop()) { di = DeployImplementations(new DeployImplementationsInterop()); + } else if (cfg.l2GenesisIsthmusTimeOffset() == 0) { + di = DeployImplementations(new DeployImplementationsIsthmus()); } di.run(dii, dio); @@ -345,7 +346,7 @@ contract Deploy is Deployer { _opcm: OPContractsManager(mustGetAddress("OPContractsManager")), _mips: IMIPS(mustGetAddress("Mips")) }); - if (_isInterop) { + if (cfg.useInterop()) { ChainAssertions.checkSystemConfigInterop({ _contracts: contracts, _cfg: cfg, _isProxy: false }); } else { ChainAssertions.checkSystemConfig({ _contracts: contracts, _cfg: cfg, _isProxy: false }); diff --git a/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol index cf8e4d38739b..8a0cfa1e794a 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol @@ -30,6 +30,7 @@ contract DeployConfig is Script { uint256 public l2GenesisFjordTimeOffset; uint256 public l2GenesisGraniteTimeOffset; uint256 public l2GenesisHoloceneTimeOffset; + uint256 public l2GenesisIsthmusTimeOffset; uint256 public maxSequencerDrift; uint256 public sequencerWindowSize; uint256 public channelTimeout; @@ -109,6 +110,7 @@ contract DeployConfig is Script { l2GenesisFjordTimeOffset = _readOr(_json, "$.l2GenesisFjordTimeOffset", NULL_OFFSET); l2GenesisGraniteTimeOffset = _readOr(_json, "$.l2GenesisGraniteTimeOffset", NULL_OFFSET); l2GenesisHoloceneTimeOffset = _readOr(_json, "$.l2GenesisHoloceneTimeOffset", NULL_OFFSET); + l2GenesisIsthmusTimeOffset = _readOr(_json, "$.l2GenesisIsthmusTimeOffset", NULL_OFFSET); maxSequencerDrift = stdJson.readUint(_json, "$.maxSequencerDrift"); sequencerWindowSize = stdJson.readUint(_json, "$.sequencerWindowSize"); @@ -225,6 +227,11 @@ contract DeployConfig is Script { useInterop = _useInterop; } + /// @notice Allow the `l2GenesisIsthmusTimeOffset` to be overridden in testing environments + function setL2GenesisIsthmusTimeOffset(uint256 _l2GenesisIsthmusTimeOffset) public { + l2GenesisIsthmusTimeOffset = _l2GenesisIsthmusTimeOffset; + } + /// @notice Allow the `fundDevAccounts` config to be overridden. function setFundDevAccounts(bool _fundDevAccounts) public { fundDevAccounts = _fundDevAccounts; diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index 63357584a880..eb09e0971be2 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -24,6 +24,9 @@ import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; +import { IOptimismPortalIsthmus } from "interfaces/L1/IOptimismPortalIsthmus.sol"; +import { ISystemConfigIsthmus } from "interfaces/L1/ISystemConfigIsthmus.sol"; + import { OPContractsManagerInterop } from "src/L1/OPContractsManagerInterop.sol"; import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol"; import { ISystemConfigInterop } from "interfaces/L1/ISystemConfigInterop.sol"; @@ -904,6 +907,89 @@ contract DeployImplementations is Script { } } +// Using the base scripts and contracts (DeploySuperchain, DeployImplementations, DeployOPChain, and +// the corresponding OPContractsManager) deploys a standard chain. For nonstandard and in-development +// features we need to modify some or all of those contracts, and we do that via inheritance. Using +// Isthmus as an example, we've made the following changes to L1 contracts: +// - `OptimismPortalIsthmus is OptimismPortal`: A different portal implementation is used, and +// it has an additional method for retrieving the TransactionDeposited nonce. +// - `SystemConfigIsthmus is SystemConfig`: A different system config implementation is used, and +// it has an additional method for retrieving the ConfigUpdate nonce. +// +// Similar to how inheritance was used to develop the new portal and system config contracts, we use +// inheritance to modify up to all of the deployer contracts. For this isthmus example, what this +// means is we need: +// - A `DeployImplementationsIsthmus is DeployImplementations` that: +// - Deploys OptimismPortalIsthmus instead of OptimismPortal. +// - Deploys SystemConfigIsthmus instead of SystemConfig. +contract DeployImplementationsIsthmus is DeployImplementations { + function deployOptimismPortalImpl( + DeployImplementationsInput _dii, + DeployImplementationsOutput _dio + ) + public + override + { + string memory release = _dii.l1ContractsRelease(); + string memory stdVerToml = _dii.standardVersionsToml(); + string memory contractName = "optimism_portal"; + IOptimismPortalIsthmus impl; + + address existingImplementation = getReleaseAddress(release, contractName, stdVerToml); + if (existingImplementation != address(0)) { + impl = IOptimismPortalIsthmus(payable(existingImplementation)); + } else { + uint256 proofMaturityDelaySeconds = _dii.proofMaturityDelaySeconds(); + uint256 disputeGameFinalityDelaySeconds = _dii.disputeGameFinalityDelaySeconds(); + vm.broadcast(msg.sender); + impl = IOptimismPortalIsthmus( + DeployUtils.create1({ + _name: "OptimismPortalIsthmus", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IOptimismPortalIsthmus.__constructor__, + (proofMaturityDelaySeconds, disputeGameFinalityDelaySeconds) + ) + ) + }) + ); + } + + vm.label(address(impl), "OptimismPortalImpl"); + _dio.set(_dio.optimismPortalImpl.selector, address(impl)); + } + + function deploySystemConfigImpl( + DeployImplementationsInput _dii, + DeployImplementationsOutput _dio + ) + public + override + { + string memory release = _dii.l1ContractsRelease(); + string memory stdVerToml = _dii.standardVersionsToml(); + + string memory contractName = "system_config"; + ISystemConfigIsthmus impl; + + address existingImplementation = getReleaseAddress(release, contractName, stdVerToml); + if (existingImplementation != address(0)) { + impl = ISystemConfigIsthmus(existingImplementation); + } else { + vm.broadcast(msg.sender); + impl = ISystemConfigIsthmus( + DeployUtils.create1({ + _name: "SystemConfigIsthmus", + _args: DeployUtils.encodeConstructor(abi.encodeCall(ISystemConfigIsthmus.__constructor__, ())) + }) + ); + } + + vm.label(address(impl), "SystemConfigImpl"); + _dio.set(_dio.systemConfigImpl.selector, address(impl)); + } +} + // Similar to how DeploySuperchain.s.sol contains a lot of comments to thoroughly document the script // architecture, this comment block documents how to update the deploy scripts to support new features. // diff --git a/packages/contracts-bedrock/snapshots/.gas-snapshot b/packages/contracts-bedrock/snapshots/.gas-snapshot index 08785045bb50..68dfe9e76d25 100644 --- a/packages/contracts-bedrock/snapshots/.gas-snapshot +++ b/packages/contracts-bedrock/snapshots/.gas-snapshot @@ -1,13 +1,13 @@ -GasBenchMark_L1BlockInterop_DepositsComplete:test_depositsComplete_benchmark() (gas: 7567) -GasBenchMark_L1BlockInterop_DepositsComplete_Warm:test_depositsComplete_benchmark() (gas: 5567) -GasBenchMark_L1BlockInterop_SetValuesInterop:test_setL1BlockValuesInterop_benchmark() (gas: 175677) -GasBenchMark_L1BlockInterop_SetValuesInterop_Warm:test_setL1BlockValuesInterop_benchmark() (gas: 5099) -GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158531) +GasBenchMark_L1BlockInterop_DepositsComplete:test_depositsComplete_benchmark() (gas: 7634) +GasBenchMark_L1BlockInterop_DepositsComplete_Warm:test_depositsComplete_benchmark() (gas: 5634) +GasBenchMark_L1BlockInterop_SetValuesInterop:test_setL1BlockValuesInterop_benchmark() (gas: 197772) +GasBenchMark_L1BlockInterop_SetValuesInterop_Warm:test_setL1BlockValuesInterop_benchmark() (gas: 5144) +GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158463) GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597) -GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369280) -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: 369222) +GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967384) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564543) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076519) +GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 466939) +GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512688) +GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72602) diff --git a/packages/contracts-bedrock/snapshots/abi/L1Block.json b/packages/contracts-bedrock/snapshots/abi/L1Block.json index 020c9e942c75..6401e50c10db 100644 --- a/packages/contracts-bedrock/snapshots/abi/L1Block.json +++ b/packages/contracts-bedrock/snapshots/abi/L1Block.json @@ -77,6 +77,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "configUpdateNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "depositNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "gasPayingToken", @@ -282,6 +308,13 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "setL1BlockValuesIsthmus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "timestamp", diff --git a/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json b/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json index ab089f0cec55..922285d87643 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", @@ -90,6 +103,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "depositNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "depositsComplete", @@ -359,6 +385,13 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "setL1BlockValuesIsthmus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "timestamp", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json b/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json index 2f52ed573d37..82f05a881545 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json @@ -745,7 +745,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 5b9f72b9446c..b3d605debfd3 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json @@ -19,6 +19,19 @@ "stateMutability": "payable", "type": "receive" }, + { + "inputs": [], + "name": "DEPOSIT_NONCE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "balance", @@ -101,6 +114,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "depositNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "nonce_", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -763,7 +789,7 @@ { "indexed": true, "internalType": "uint256", - "name": "version", + "name": "nonceAndVersion", "type": "uint256" }, { diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismPortalIsthmus.json b/packages/contracts-bedrock/snapshots/abi/OptimismPortalIsthmus.json new file mode 100644 index 000000000000..3231b6dd0c99 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortalIsthmus.json @@ -0,0 +1,975 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_proofMaturityDelaySeconds", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_disputeGameFinalityDelaySeconds", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [], + "name": "DEPOSIT_NONCE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "balance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IDisputeGame", + "name": "_disputeGame", + "type": "address" + } + ], + "name": "blacklistDisputeGame", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_withdrawalHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_proofSubmitter", + "type": "address" + } + ], + "name": "checkWithdrawal", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_mint", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "_gasLimit", + "type": "uint64" + }, + { + "internalType": "bool", + "name": "_isCreation", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "depositERC20Transaction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "depositNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "nonce_", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "_gasLimit", + "type": "uint64" + }, + { + "internalType": "bool", + "name": "_isCreation", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "depositTransaction", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IDisputeGame", + "name": "", + "type": "address" + } + ], + "name": "disputeGameBlacklist", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "disputeGameFactory", + "outputs": [ + { + "internalType": "contract IDisputeGameFactory", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "disputeGameFinalityDelaySeconds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "donateETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Types.WithdrawalTransaction", + "name": "_tx", + "type": "tuple" + } + ], + "name": "finalizeWithdrawalTransaction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Types.WithdrawalTransaction", + "name": "_tx", + "type": "tuple" + }, + { + "internalType": "address", + "name": "_proofSubmitter", + "type": "address" + } + ], + "name": "finalizeWithdrawalTransactionExternalProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "finalizedWithdrawals", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "guardian", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IDisputeGameFactory", + "name": "_disputeGameFactory", + "type": "address" + }, + { + "internalType": "contract ISystemConfig", + "name": "_systemConfig", + "type": "address" + }, + { + "internalType": "contract ISuperchainConfig", + "name": "_superchainConfig", + "type": "address" + }, + { + "internalType": "GameType", + "name": "_initialRespectedGameType", + "type": "uint32" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "l2Sender", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "_byteCount", + "type": "uint64" + } + ], + "name": "minimumGasLimit", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_withdrawalHash", + "type": "bytes32" + } + ], + "name": "numProofSubmitters", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "params", + "outputs": [ + { + "internalType": "uint128", + "name": "prevBaseFee", + "type": "uint128" + }, + { + "internalType": "uint64", + "name": "prevBoughtGas", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "prevBlockNum", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proofMaturityDelaySeconds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "proofSubmitters", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Types.WithdrawalTransaction", + "name": "_tx", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "_disputeGameIndex", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "version", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "messagePasserStorageRoot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "latestBlockhash", + "type": "bytes32" + } + ], + "internalType": "struct Types.OutputRootProof", + "name": "_outputRootProof", + "type": "tuple" + }, + { + "internalType": "bytes[]", + "name": "_withdrawalProof", + "type": "bytes[]" + } + ], + "name": "proveWithdrawalTransaction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "provenWithdrawals", + "outputs": [ + { + "internalType": "contract IDisputeGame", + "name": "disputeGameProxy", + "type": "address" + }, + { + "internalType": "uint64", + "name": "timestamp", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "respectedGameType", + "outputs": [ + { + "internalType": "GameType", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "respectedGameTypeUpdatedAt", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint8", + "name": "_decimals", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "_name", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_symbol", + "type": "bytes32" + } + ], + "name": "setGasPayingToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "GameType", + "name": "_gameType", + "type": "uint32" + } + ], + "name": "setRespectedGameType", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "superchainConfig", + "outputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "systemConfig", + "outputs": [ + { + "internalType": "contract ISystemConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IDisputeGame", + "name": "disputeGame", + "type": "address" + } + ], + "name": "DisputeGameBlacklisted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "GameType", + "name": "newGameType", + "type": "uint32" + }, + { + "indexed": true, + "internalType": "Timestamp", + "name": "updatedAt", + "type": "uint64" + } + ], + "name": "RespectedGameTypeSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "nonceAndVersion", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "opaqueData", + "type": "bytes" + } + ], + "name": "TransactionDeposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "withdrawalHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "name": "WithdrawalFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "withdrawalHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "WithdrawalProven", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "withdrawalHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "proofSubmitter", + "type": "address" + } + ], + "name": "WithdrawalProvenExtension1", + "type": "event" + }, + { + "inputs": [], + "name": "AlreadyFinalized", + "type": "error" + }, + { + "inputs": [], + "name": "BadTarget", + "type": "error" + }, + { + "inputs": [], + "name": "Blacklisted", + "type": "error" + }, + { + "inputs": [], + "name": "CallPaused", + "type": "error" + }, + { + "inputs": [], + "name": "ContentLengthMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyItem", + "type": "error" + }, + { + "inputs": [], + "name": "GasEstimation", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidDataRemainder", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidDisputeGame", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidGameType", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidHeader", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidMerkleProof", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidProof", + "type": "error" + }, + { + "inputs": [], + "name": "LargeCalldata", + "type": "error" + }, + { + "inputs": [], + "name": "NoValue", + "type": "error" + }, + { + "inputs": [], + "name": "NonReentrant", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyCustomGasToken", + "type": "error" + }, + { + "inputs": [], + "name": "OutOfGas", + "type": "error" + }, + { + "inputs": [], + "name": "ProposalNotValidated", + "type": "error" + }, + { + "inputs": [], + "name": "SmallGasLimit", + "type": "error" + }, + { + "inputs": [], + "name": "TransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "UnexpectedList", + "type": "error" + }, + { + "inputs": [], + "name": "UnexpectedString", + "type": "error" + }, + { + "inputs": [], + "name": "Unproven", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/SystemConfig.json b/packages/contracts-bedrock/snapshots/abi/SystemConfig.json index b7e18556fa2c..0aad67497e96 100644 --- a/packages/contracts-bedrock/snapshots/abi/SystemConfig.json +++ b/packages/contracts-bedrock/snapshots/abi/SystemConfig.json @@ -256,6 +256,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "gasPayingTokenAddress", + "outputs": [ + { + "internalType": "address", + "name": "addr_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "gasPayingTokenName", @@ -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 a459af15801b..0ecc5036a268 100644 --- a/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json @@ -12,6 +12,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "CONFIG_UPDATE_NONCE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "DISPUTE_GAME_FACTORY_SLOT", @@ -129,6 +142,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "VERSION_1", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -194,6 +220,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "configUpdateNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "nonce_", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "dependencyManager", @@ -277,6 +316,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "gasPayingTokenAddress", + "outputs": [ + { + "internalType": "address", + "name": "addr_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "gasPayingTokenName", @@ -911,7 +963,7 @@ { "indexed": true, "internalType": "uint256", - "name": "version", + "name": "nonceAndVersion", "type": "uint256" }, { diff --git a/packages/contracts-bedrock/snapshots/abi/SystemConfigIsthmus.json b/packages/contracts-bedrock/snapshots/abi/SystemConfigIsthmus.json new file mode 100644 index 000000000000..ccc94ffbc8ad --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/SystemConfigIsthmus.json @@ -0,0 +1,851 @@ +[ + { + "inputs": [], + "name": "BATCH_INBOX_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CONFIG_UPDATE_NONCE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DISPUTE_GAME_FACTORY_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "L1_CROSS_DOMAIN_MESSENGER_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "L1_ERC_721_BRIDGE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "L1_STANDARD_BRIDGE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OPTIMISM_PORTAL_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "START_BLOCK_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UNSAFE_BLOCK_SIGNER_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VERSION_1", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "basefeeScalar", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "batchInbox", + "outputs": [ + { + "internalType": "address", + "name": "addr_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "batcherHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "blobbasefeeScalar", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "configUpdateNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "nonce_", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "disputeGameFactory", + "outputs": [ + { + "internalType": "address", + "name": "addr_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eip1559Denominator", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eip1559Elasticity", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gasLimit", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gasPayingToken", + "outputs": [ + { + "internalType": "address", + "name": "addr_", + "type": "address" + }, + { + "internalType": "uint8", + "name": "decimals_", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gasPayingTokenAddress", + "outputs": [ + { + "internalType": "address", + "name": "addr_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gasPayingTokenName", + "outputs": [ + { + "internalType": "string", + "name": "name_", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gasPayingTokenSymbol", + "outputs": [ + { + "internalType": "string", + "name": "symbol_", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "uint32", + "name": "_basefeeScalar", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "_blobbasefeeScalar", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "_batcherHash", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "_gasLimit", + "type": "uint64" + }, + { + "internalType": "address", + "name": "_unsafeBlockSigner", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint32", + "name": "maxResourceLimit", + "type": "uint32" + }, + { + "internalType": "uint8", + "name": "elasticityMultiplier", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "baseFeeMaxChangeDenominator", + "type": "uint8" + }, + { + "internalType": "uint32", + "name": "minimumBaseFee", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "systemTxMaxGas", + "type": "uint32" + }, + { + "internalType": "uint128", + "name": "maximumBaseFee", + "type": "uint128" + } + ], + "internalType": "struct IResourceMetering.ResourceConfig", + "name": "_config", + "type": "tuple" + }, + { + "internalType": "address", + "name": "_batchInbox", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "l1CrossDomainMessenger", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ERC721Bridge", + "type": "address" + }, + { + "internalType": "address", + "name": "l1StandardBridge", + "type": "address" + }, + { + "internalType": "address", + "name": "disputeGameFactory", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortal", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismMintableERC20Factory", + "type": "address" + }, + { + "internalType": "address", + "name": "gasPayingToken", + "type": "address" + } + ], + "internalType": "struct SystemConfig.Addresses", + "name": "_addresses", + "type": "tuple" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isCustomGasToken", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1CrossDomainMessenger", + "outputs": [ + { + "internalType": "address", + "name": "addr_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1ERC721Bridge", + "outputs": [ + { + "internalType": "address", + "name": "addr_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1StandardBridge", + "outputs": [ + { + "internalType": "address", + "name": "addr_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maximumGasLimit", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "minimumGasLimit", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "optimismMintableERC20Factory", + "outputs": [ + { + "internalType": "address", + "name": "addr_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "optimismPortal", + "outputs": [ + { + "internalType": "address", + "name": "addr_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "overhead", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "resourceConfig", + "outputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "maxResourceLimit", + "type": "uint32" + }, + { + "internalType": "uint8", + "name": "elasticityMultiplier", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "baseFeeMaxChangeDenominator", + "type": "uint8" + }, + { + "internalType": "uint32", + "name": "minimumBaseFee", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "systemTxMaxGas", + "type": "uint32" + }, + { + "internalType": "uint128", + "name": "maximumBaseFee", + "type": "uint128" + } + ], + "internalType": "struct IResourceMetering.ResourceConfig", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "scalar", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_batcherHash", + "type": "bytes32" + } + ], + "name": "setBatcherHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_denominator", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "_elasticity", + "type": "uint32" + } + ], + "name": "setEIP1559Params", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_overhead", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_scalar", + "type": "uint256" + } + ], + "name": "setGasConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_basefeeScalar", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "_blobbasefeeScalar", + "type": "uint32" + } + ], + "name": "setGasConfigEcotone", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "_gasLimit", + "type": "uint64" + } + ], + "name": "setGasLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_unsafeBlockSigner", + "type": "address" + } + ], + "name": "setUnsafeBlockSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "startBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "startBlock_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unsafeBlockSigner", + "outputs": [ + { + "internalType": "address", + "name": "addr_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "nonceAndVersion", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "enum SystemConfig.UpdateType", + "name": "updateType", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "ConfigUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 42019379ded7..e4968e62f0f7 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -20,12 +20,16 @@ "sourceCodeHash": "0x05ed7ad68e4e9bca7334314e794a1f66e5899532bb01cfa3a7716cb2688df9d5" }, "src/L1/OptimismPortal2.sol": { - "initCodeHash": "0xfd14fd690752519064d6de6c3e15d69ec9146bc8714e56ac286305773dbb1533", - "sourceCodeHash": "0x3dbd4601c67a43c42f403f6b28e6e2d8bf4f3d2cf2f2d8f7460026e0c6c66def" + "initCodeHash": "0x3ef7fc0fdd07456a89e0447df9557fd23ef1aa70324a8a3153c06faf1112c4a3", + "sourceCodeHash": "0x05327fd71b535b321f3a60305eb7815f0453d0441639e68e10fbcf6440e1a83b" }, "src/L1/OptimismPortalInterop.sol": { - "initCodeHash": "0xc7a9282ef32425b65a647039908ea2b8d6ef231ba1b87c345c7b9f3f73acc240", - "sourceCodeHash": "0x85e9f10ba1884b1a45737fd35ae4c2f9a9054f81a6aba08941ab7a95e74543da" + "initCodeHash": "0xacda1476b110d0dc7de8ed6c6a6f4e99e3f4e1dc443807509bb0a0943667beb5", + "sourceCodeHash": "0xe6ffad3284f9691af3e104d011b81b8931c062fddd418a0910fa4dc5d8ade1bd" + }, + "src/L1/OptimismPortalIsthmus.sol": { + "initCodeHash": "0x425868130d2530c0156f6b5ce247489ad8bbe374f0755846d78154877dc3b76d", + "sourceCodeHash": "0xd08baa00513cf60272fb8faf9a9b4ad94992c56ce08703542f90692738830649" }, "src/L1/ProtocolVersions.sol": { "initCodeHash": "0x0000ec89712d8b4609873f1ba76afffd4205bf9110818995c90134dbec12e91e", @@ -36,12 +40,16 @@ "sourceCodeHash": "0xafa784ea78818a382ff3a61e2d84be58c7978110c06b9273db68c0213ead02d3" }, "src/L1/SystemConfig.sol": { - "initCodeHash": "0xe1baf5d3415baf65a45031c37e4bd794ecb7f779b227f6cbed06d945aa2097fc", - "sourceCodeHash": "0x52b7d8825b4962e720513906ac993d36964cf03c45364c9f03d88507a0caac57" + "initCodeHash": "0xc64aa8d395a080b33f5bb06535aa16d3db6d396e53324d184e0364615f6c0ae2", + "sourceCodeHash": "0x357339025ed0e94973a8713258787f4f4f6ce2cfa5543e4ea3919cd245137aa0" }, "src/L1/SystemConfigInterop.sol": { - "initCodeHash": "0x91ed371ee2f6d4a9ed1483971df8a6888cc8b5aca5212b180e395476f21cb268", - "sourceCodeHash": "0x6a51dc1b2bfadd9781c729f8db70972926f364b0e605536fb075bfddd0d4c433" + "initCodeHash": "0xa8e43c129585275c7f18276b7c388c09bf86c47e1bc0db53f5cdc5abeef9d07a", + "sourceCodeHash": "0x6b55db1f8683b0891153b64300c8615729829e775cd62c9049f14af1600abb06" + }, + "src/L1/SystemConfigIsthmus.sol": { + "initCodeHash": "0x91267413fed69ea223edab5ac0b8565c24375c1099c4bd31d6a6271223b6d16b", + "sourceCodeHash": "0x96edab7b0b31663cf6b5ccc97a3bdee6080069761e5878a3f50924ca2c6cc9f1" }, "src/L2/BaseFeeVault.sol": { "initCodeHash": "0xc403d4c555d8e69a2699e01d192ae7327136701fa02da10a6d75a584b3c364c9", @@ -60,12 +68,12 @@ "sourceCodeHash": "0x305c72d7be9149fce7095bd4641a1a19acada3126fbc43599f674cadbf6e7d6c" }, "src/L2/L1Block.sol": { - "initCodeHash": "0x22f9b9277e33dc27df8366c2dd6e8340d294947b57116db35c6d14c41225633f", - "sourceCodeHash": "0xffb6cf768097b2d6cb6ecb2d6463c176af9acd70415aa0d2e4f017758f737eee" + "initCodeHash": "0x42f931521a350b33934d28ecba12fc400fe910e8e77e931412a08ccd2f65593d", + "sourceCodeHash": "0x2b1181e8b05f36a9e4b715cc45cd231ba2c2b7cf31192449f9388fbb71df9681" }, "src/L2/L1BlockInterop.sol": { - "initCodeHash": "0x67e99306d9a09cac587f65cfa2c0de55da9eca184fd1fc3f4b885d2c47114483", - "sourceCodeHash": "0x9493f90136917fc95d2ac942f061c1b9cffeff6d327afb46fe4e69784e7f2100" + "initCodeHash": "0x4767383344df74dc1e09001c3fa2ab29917174a179fe88aab309ca09a18be647", + "sourceCodeHash": "0xbf0036e9db41fa25df7fc5818cf898e509173790d6759e77337ec3fb9c5b49c8" }, "src/L2/L1FeeVault.sol": { "initCodeHash": "0x6745b7be3895a5e8d373df0066d931bae29c47672ac46c2f5829bd0052cc6d9e", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json b/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json index 2928d2147b5c..82eac2b0f225 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": "depositNonce", + "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 14ee2ff9609a..8af802ff2712 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": "depositNonce", + "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/OptimismPortalIsthmus.json b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalIsthmus.json new file mode 100644 index 000000000000..0fdd65b3e88f --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalIsthmus.json @@ -0,0 +1,128 @@ +[ + { + "bytes": "1", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "uint8" + }, + { + "bytes": "1", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "bool" + }, + { + "bytes": "32", + "label": "params", + "offset": 0, + "slot": "1", + "type": "struct ResourceMetering.ResourceParams" + }, + { + "bytes": "1536", + "label": "__gap", + "offset": 0, + "slot": "2", + "type": "uint256[48]" + }, + { + "bytes": "20", + "label": "l2Sender", + "offset": 0, + "slot": "50", + "type": "address" + }, + { + "bytes": "32", + "label": "finalizedWithdrawals", + "offset": 0, + "slot": "51", + "type": "mapping(bytes32 => bool)" + }, + { + "bytes": "32", + "label": "spacer_52_0_32", + "offset": 0, + "slot": "52", + "type": "bytes32" + }, + { + "bytes": "1", + "label": "spacer_53_0_1", + "offset": 0, + "slot": "53", + "type": "bool" + }, + { + "bytes": "20", + "label": "superchainConfig", + "offset": 1, + "slot": "53", + "type": "contract ISuperchainConfig" + }, + { + "bytes": "20", + "label": "spacer_54_0_20", + "offset": 0, + "slot": "54", + "type": "address" + }, + { + "bytes": "20", + "label": "systemConfig", + "offset": 0, + "slot": "55", + "type": "contract ISystemConfig" + }, + { + "bytes": "20", + "label": "disputeGameFactory", + "offset": 0, + "slot": "56", + "type": "contract IDisputeGameFactory" + }, + { + "bytes": "32", + "label": "provenWithdrawals", + "offset": 0, + "slot": "57", + "type": "mapping(bytes32 => mapping(address => struct OptimismPortal2.ProvenWithdrawal))" + }, + { + "bytes": "32", + "label": "disputeGameBlacklist", + "offset": 0, + "slot": "58", + "type": "mapping(contract IDisputeGame => bool)" + }, + { + "bytes": "4", + "label": "respectedGameType", + "offset": 0, + "slot": "59", + "type": "GameType" + }, + { + "bytes": "8", + "label": "respectedGameTypeUpdatedAt", + "offset": 4, + "slot": "59", + "type": "uint64" + }, + { + "bytes": "32", + "label": "proofSubmitters", + "offset": 0, + "slot": "60", + "type": "mapping(bytes32 => address[])" + }, + { + "bytes": "32", + "label": "_balance", + "offset": 0, + "slot": "61", + "type": "uint256" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigIsthmus.json b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigIsthmus.json new file mode 100644 index 000000000000..a6184a1f10dd --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigIsthmus.json @@ -0,0 +1,100 @@ +[ + { + "bytes": "1", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "uint8" + }, + { + "bytes": "1", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "bool" + }, + { + "bytes": "1600", + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "uint256[50]" + }, + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "address" + }, + { + "bytes": "1568", + "label": "__gap", + "offset": 0, + "slot": "52", + "type": "uint256[49]" + }, + { + "bytes": "32", + "label": "overhead", + "offset": 0, + "slot": "101", + "type": "uint256" + }, + { + "bytes": "32", + "label": "scalar", + "offset": 0, + "slot": "102", + "type": "uint256" + }, + { + "bytes": "32", + "label": "batcherHash", + "offset": 0, + "slot": "103", + "type": "bytes32" + }, + { + "bytes": "8", + "label": "gasLimit", + "offset": 0, + "slot": "104", + "type": "uint64" + }, + { + "bytes": "4", + "label": "basefeeScalar", + "offset": 8, + "slot": "104", + "type": "uint32" + }, + { + "bytes": "4", + "label": "blobbasefeeScalar", + "offset": 12, + "slot": "104", + "type": "uint32" + }, + { + "bytes": "32", + "label": "_resourceConfig", + "offset": 0, + "slot": "105", + "type": "struct IResourceMetering.ResourceConfig" + }, + { + "bytes": "4", + "label": "eip1559Denominator", + "offset": 0, + "slot": "106", + "type": "uint32" + }, + { + "bytes": "4", + "label": "eip1559Elasticity", + "offset": 4, + "slot": "106", + "type": "uint32" + } +] \ 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 10448f0b7fb6..15e073b037c9 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol @@ -144,11 +144,13 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// @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 +185,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. @@ -231,8 +233,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// @notice Getter for the balance of the contract. function balance() public view returns (uint256) { - (address token,) = gasPayingToken(); - if (token == Constants.ETHER) { + if (gasPayingToken() == Constants.ETHER) { return address(this).balance; } else { return _balance; @@ -289,8 +290,8 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { } /// @notice Returns the gas paying token and its decimals. - function gasPayingToken() internal view returns (address addr_, uint8 decimals_) { - (addr_, decimals_) = systemConfig.gasPayingToken(); + function gasPayingToken() internal view returns (address addr_) { + addr_ = systemConfig.gasPayingTokenAddress(); } /// @notice Getter for the resource config. @@ -355,12 +356,12 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { // bugs, then we know that this withdrawal was actually triggered on L2 and can therefore // be relayed on L1. if ( - SecureMerkleTrie.verifyInclusionProof({ + !SecureMerkleTrie.verifyInclusionProof({ _key: abi.encode(storageKey), _value: hex"01", _proof: _withdrawalProof, _root: _outputRootProof.messagePasserStorageRoot - }) == false + }) ) revert InvalidMerkleProof(); // Designate the withdrawalHash as proven by storing the `disputeGameProxy` & `timestamp` in the @@ -412,7 +413,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { l2Sender = _tx.sender; bool success; - (address token,) = gasPayingToken(); + address token = gasPayingToken(); if (token == Constants.ETHER) { // Trigger the call to the target contract. We use a custom low level method // SafeCall.callWithMinGas to ensure two key properties @@ -493,7 +494,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { metered(_gasLimit) { // Can only be called if an ERC20 token is used for gas paying on L2 - (address token,) = gasPayingToken(); + address token = gasPayingToken(); if (token == Constants.ETHER) revert OnlyCustomGasToken(); // Gives overflow protection for L2 account balances. @@ -540,8 +541,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { payable metered(_gasLimit) { - (address token,) = gasPayingToken(); - if (token != Constants.ETHER && msg.value != 0) revert NoValue(); + if (gasPayingToken() != Constants.ETHER && msg.value != 0) revert NoValue(); _depositTransaction({ _to: _to, @@ -586,18 +586,16 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { // Transform the from-address to its alias if the caller is a contract. address from = msg.sender; - if (msg.sender != tx.origin) { - from = AddressAliasHelper.applyL1ToL2Alias(msg.sender); + if (from != tx.origin) { + from = AddressAliasHelper.applyL1ToL2Alias(from); } // Compute the opaque data that will be emitted as part of the TransactionDeposited event. // We use opaque data so that we can update the TransactionDeposited event in the future // without breaking the current interface. - bytes memory opaqueData = abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data); - // 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, abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data)); } /// @notice Sets the gas paying token for the L2 system. This token is used as the @@ -611,10 +609,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 +622,14 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { ); } + function _emitTransactionDeposited(address _from, address _to, bytes memory _opaqueData) internal { + emit TransactionDeposited(_from, _to, _transactionDepositedNonceAndVersion(), _opaqueData); + } + + function _transactionDepositedNonceAndVersion() internal virtual returns (uint256) { + return DEPOSIT_VERSION; + } + /// @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 fecd4dc0d6b2..cbfd58daa8aa 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.15; // Contracts -import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; +import { OptimismPortalIsthmus } from "src/L1/OptimismPortalIsthmus.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; @@ -17,17 +17,17 @@ import { IL1BlockInterop, ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; /// @notice The OptimismPortal is a low-level contract responsible for passing messages between L1 /// and L2. Messages sent directly to the OptimismPortal have no form of replayability. /// Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface. -contract OptimismPortalInterop is OptimismPortal2 { +contract OptimismPortalInterop is OptimismPortalIsthmus { constructor( uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds ) - OptimismPortal2(_proofMaturityDelaySeconds, _disputeGameFinalityDelaySeconds) + OptimismPortalIsthmus(_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/OptimismPortalIsthmus.sol b/packages/contracts-bedrock/src/L1/OptimismPortalIsthmus.sol new file mode 100644 index 000000000000..76ac1826a28a --- /dev/null +++ b/packages/contracts-bedrock/src/L1/OptimismPortalIsthmus.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Contracts +import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; + +/// @custom:proxied true +/// @title OptimismPortalIsthmus +/// @notice The OptimismPortal is a low-level contract responsible for passing messages between L1 +/// and L2. Messages sent directly to the OptimismPortal have no form of replayability. +/// Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface. +contract OptimismPortalIsthmus is OptimismPortal2 { + /// @notice Version of the deposit event. + uint256 internal constant DEPOSIT_VERSION_1 = 1; + + /// @notice The storage slot that holds the deposit nonce. + /// @dev `bytes32(uint256(keccak256('optimismportal.depositnonce')) - 1)` + bytes32 public constant DEPOSIT_NONCE_SLOT = 0xfbdb6804978a124792ffaccc985bc1ad9ad7a5b3ff3fc4eb6936a7c373b67089; + + constructor( + uint256 _proofMaturityDelaySeconds, + uint256 _disputeGameFinalityDelaySeconds + ) + OptimismPortal2(_proofMaturityDelaySeconds, _disputeGameFinalityDelaySeconds) + { } + + /// @custom:semver +isthmus-beta.1 + function version() public pure virtual override returns (string memory) { + return string.concat(super.version(), "+isthmus-beta.1"); + } + + /// @notice Nonce incremented for each TransactionDeposited event + function depositNonce() public view returns (uint64 nonce_) { + assembly { + nonce_ := sload(DEPOSIT_NONCE_SLOT) + } + } + + function _transactionDepositedNonceAndVersion() internal virtual override returns (uint256) { + uint64 nonce = depositNonce() + 1; + assembly { + sstore(DEPOSIT_NONCE_SLOT, nonce) + } + return uint256(nonce) << 128 | DEPOSIT_VERSION_1; + } +} diff --git a/packages/contracts-bedrock/src/L1/SystemConfig.sol b/packages/contracts-bedrock/src/L1/SystemConfig.sol index 0b4562543d92..3595000e838f 100644 --- a/packages/contracts-bedrock/src/L1/SystemConfig.sol +++ b/packages/contracts-bedrock/src/L1/SystemConfig.sol @@ -131,15 +131,15 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { uint32 public eip1559Elasticity; /// @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. @@ -265,11 +265,16 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { startBlock_ = Storage.getUint(START_BLOCK_SLOT); } - /// @notice Getter for the gas paying asset address. + /// @notice Getter for the gas paying asset address + decimals. function gasPayingToken() public view returns (address addr_, uint8 decimals_) { (addr_, decimals_) = GasPayingToken.getToken(); } + /// @notice Getter for the gas paying asset address. + function gasPayingTokenAddress() public view returns (address addr_) { + (addr_,) = GasPayingToken.getToken(); + } + /// @notice Getter for custom gas token paying networks. Returns true if the /// network uses a custom gas token. function isCustomGasToken() public view returns (bool) { @@ -323,7 +328,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); + emit ConfigUpdate(_configUpdateNonceAndVersion(), UpdateType.UNSAFE_BLOCK_SIGNER, data); } /// @notice Updates the batcher hash. Can only be called by the owner. @@ -338,7 +343,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { batcherHash = _batcherHash; bytes memory data = abi.encode(_batcherHash); - emit ConfigUpdate(VERSION, UpdateType.BATCHER, data); + emit ConfigUpdate(_configUpdateNonceAndVersion(), UpdateType.BATCHER, data); } /// @notice Updates gas config. Can only be called by the owner. @@ -359,7 +364,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { scalar = _scalar; bytes memory data = abi.encode(_overhead, _scalar); - emit ConfigUpdate(VERSION, UpdateType.FEE_SCALARS, data); + emit ConfigUpdate(_configUpdateNonceAndVersion(), UpdateType.FEE_SCALARS, data); } /// @notice Updates gas config as of the Ecotone upgrade. Can only be called by the owner. @@ -379,7 +384,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); + emit ConfigUpdate(_configUpdateNonceAndVersion(), UpdateType.FEE_SCALARS, data); } /// @notice Updates the L2 gas limit. Can only be called by the owner. @@ -396,7 +401,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken { gasLimit = _gasLimit; bytes memory data = abi.encode(_gasLimit); - emit ConfigUpdate(VERSION, UpdateType.GAS_LIMIT, data); + emit ConfigUpdate(_configUpdateNonceAndVersion(), UpdateType.GAS_LIMIT, data); } /// @notice Updates the EIP-1559 parameters of the chain. Can only be called by the owner. @@ -415,7 +420,11 @@ 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); + emit ConfigUpdate(_configUpdateNonceAndVersion(), UpdateType.EIP_1559_PARAMS, data); + } + + function _configUpdateNonceAndVersion() internal virtual returns (uint256) { + return VERSION; } /// @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 af7656876bcb..2cdea7a395b1 100644 --- a/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol +++ b/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.15; // Contracts import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { SystemConfig } from "src/L1/SystemConfig.sol"; +import { SystemConfigIsthmus } from "src/L1/SystemConfigIsthmus.sol"; // Libraries import { Constants } from "src/libraries/Constants.sol"; @@ -21,7 +22,7 @@ import { ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; /// @notice The SystemConfig contract is used to manage configuration of an Optimism network. /// All configuration is stored on L1 and picked up by L2 as part of the derviation of /// the L2 chain. -contract SystemConfigInterop is SystemConfig { +contract SystemConfigInterop is SystemConfigIsthmus { /// @notice Storage slot where the dependency manager address is stored /// @dev Equal to bytes32(uint256(keccak256("systemconfig.dependencymanager")) - 1) bytes32 internal constant DEPENDENCY_MANAGER_SLOT = @@ -68,9 +69,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/L1/SystemConfigIsthmus.sol b/packages/contracts-bedrock/src/L1/SystemConfigIsthmus.sol new file mode 100644 index 000000000000..c0475c4521e2 --- /dev/null +++ b/packages/contracts-bedrock/src/L1/SystemConfigIsthmus.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Contracts +import { SystemConfig } from "src/L1/SystemConfig.sol"; + +/// @custom:proxied true +/// @title SystemConfigIsthmus +/// @notice The SystemConfig contract is used to manage configuration of an Optimism network. +/// All configuration is stored on L1 and picked up by L2 as part of the derviation of +/// the L2 chain. +contract SystemConfigIsthmus is SystemConfig { + /// @notice Version identifier, used for upgrades. + uint256 public constant VERSION_1 = 1; + + /// @notice The storage slot that holds the deposit nonce. + /// @dev `bytes32(uint256(keccak256('systemconfig.configupdatenonce')) - 1)` + bytes32 public constant CONFIG_UPDATE_NONCE_SLOT = + 0x93fcce48e210616d14f0f2849f0028a91b366cdf4152de896d874c59cb47c5ee; + + /// @custom:semver +isthmus-beta.1 + function version() public pure virtual override returns (string memory) { + return string.concat(super.version(), "+isthmus-beta.1"); + } + + /// @notice Nonce incremented for each ConfigUpdate event + function configUpdateNonce() public view returns (uint64 nonce_) { + assembly { + nonce_ := sload(CONFIG_UPDATE_NONCE_SLOT) + } + } + + function _configUpdateNonceAndVersion() internal virtual override returns (uint256) { + uint64 nonce = configUpdateNonce() + 1; + assembly { + sstore(CONFIG_UPDATE_NONCE_SLOT, nonce) + } + return uint256(nonce) << 128 | VERSION_1; + } +} diff --git a/packages/contracts-bedrock/src/L2/L1Block.sol b/packages/contracts-bedrock/src/L2/L1Block.sol index 3767b80988da..c512132fd309 100644 --- a/packages/contracts-bedrock/src/L2/L1Block.sol +++ b/packages/contracts-bedrock/src/L2/L1Block.sol @@ -60,9 +60,15 @@ contract L1Block is ISemver, IGasToken { /// @notice The latest L1 blob base fee. uint256 public blobBaseFee; - /// @custom:semver 1.5.1-beta.5 + /// @notice Nonce incremented for each TransactionDeposited event + uint64 public depositNonce; + + /// @notice Nonce incremented for each ConfigUpdate event + uint64 public configUpdateNonce; + + /// @custom:semver 1.6.0 function version() public pure virtual returns (string memory) { - return "1.5.1-beta.5"; + return "1.6.0"; } /// @notice Returns the gas paying token, its decimals, name and symbol. @@ -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 7b92b202a052..89f4d6227006 100644 --- a/packages/contracts-bedrock/src/L2/L1BlockInterop.sol +++ b/packages/contracts-bedrock/src/L2/L1BlockInterop.sol @@ -49,9 +49,9 @@ contract L1BlockInterop is L1Block { /// keccak256(abi.encode(uint256(keccak256("l1Block.identifier.isDeposit")) - 1)) & ~bytes32(uint256(0xff)) uint256 internal constant IS_DEPOSIT_SLOT = 0x921bd3a089295c6e5540e8fba8195448d253efd6f2e3e495b499b627dc36a300; - /// @custom:semver +interop-beta.3 + /// @custom:semver +interop-beta.4 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop-beta.3"); + return string.concat(super.version(), "+interop-beta.4"); } /// @notice Returns whether the call was triggered from a a deposit or not. @@ -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 5aa4ee7d3d8a..ca358d2124d3 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/OptimismPortal2.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol index 19ff94552fc1..7c6c2cbd6cdd 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol @@ -135,7 +135,7 @@ contract OptimismPortal2_Test is CommonTest { assertEq(optimismPortal2.paused(), true); } - /// @dev Tests that `receive` successdully deposits ETH. + /// @dev Tests that `receive` successfully deposits ETH. function testFuzz_receive_succeeds(uint256 _value) external { uint256 balanceBefore = address(optimismPortal2).balance; _value = bound(_value, 0, type(uint256).max - balanceBefore); @@ -419,7 +419,9 @@ contract OptimismPortal2_Test is CommonTest { function test_depositERC20Transaction_balanceOverflow_reverts() external { // TODO(opcm upgrades): remove skip once upgrade path is implemented skipIfForkTest("OptimismPortal2_Test: gas paying token functionality DNE on op mainnet"); - vm.mockCall(address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(42), 18)); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.gasPayingTokenAddress, ()), abi.encode(address(42)) + ); // The balance slot vm.store(address(optimismPortal2), bytes32(uint256(61)), bytes32(type(uint256).max)); @@ -1012,7 +1014,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // modify the gas token to be non ether vm.mockCall( - address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(L1Token), 18) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingTokenAddress, ()), abi.encode(address(L1Token)) ); uint256 bobBalanceBefore = L1Token.balanceOf(bob); @@ -1132,8 +1134,8 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { function test_finalizeWithdrawalTransaction_provenWithdrawalHashNonEtherTargetToken_reverts() external { vm.mockCall( address(systemConfig), - abi.encodeCall(systemConfig.gasPayingToken, ()), - abi.encode(address(_defaultTx.target), 18) + abi.encodeCall(systemConfig.gasPayingTokenAddress, ()), + abi.encode(address(_defaultTx.target)) ); optimismPortal2.proveWithdrawalTransaction({ @@ -1812,7 +1814,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal // Mock the gas paying token to be the ERC20 token vm.mockCall( - address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingTokenAddress, ()), abi.encode(address(token)) ); bytes memory opaqueData = abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data); @@ -1887,7 +1889,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal function test_depositERC20Transaction_notEnoughAmount_reverts() external { // Mock the gas paying token to be the ERC20 token vm.mockCall( - address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingTokenAddress, ()), abi.encode(address(token)) ); vm.expectRevert(stdError.arithmeticError); // Deposit the token into the portal @@ -1902,7 +1904,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal // Mock the gas paying token to be the ERC20 token vm.mockCall( - address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingTokenAddress, ()), abi.encode(address(token)) ); // Mock the token balance @@ -1921,7 +1923,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal function test_depositERC20Transaction_isCreationNotZeroTarget_reverts() external { // Mock the gas paying token to be the ERC20 token vm.mockCall( - address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingTokenAddress, ()), abi.encode(address(token)) ); // Call minimumGasLimit(0) before vm.expectRevert to ensure vm.expectRevert is for depositERC20Transaction @@ -1936,7 +1938,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal function test_depositERC20Transaction_gasLimitTooLow_reverts() external { // Mock the gas paying token to be the ERC20 token vm.mockCall( - address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingTokenAddress, ()), abi.encode(address(token)) ); vm.expectRevert(SmallGasLimit.selector); @@ -1951,7 +1953,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal // Mock the gas paying token to be the ERC20 token vm.mockCall( - address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingTokenAddress, ()), abi.encode(address(token)) ); uint64 gasLimit = optimismPortal2.minimumGasLimit(120_001); @@ -1968,7 +1970,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal // Mock the gas paying token to be the ERC20 token vm.mockCall( - address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingTokenAddress, ()), abi.encode(address(token)) ); // Deposit the token into the portal @@ -1986,7 +1988,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal // Mock the gas paying token to be the ERC20 token vm.mockCall( - address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingTokenAddress, ()), abi.encode(address(token)) ); // Deposit the token into the portal @@ -2047,7 +2049,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal // Mock the gas paying token to be the ERC20 token vm.mockCall( - address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingTokenAddress, ()), abi.encode(address(token)) ); bytes memory opaqueData = abi.encodePacked(uint256(0), _value, _gasLimit, _isCreation, _data); @@ -2118,7 +2120,7 @@ contract OptimismPortal2WithMockERC20_Test is OptimismPortal2_FinalizeWithdrawal function test_depositTransaction_customGasTokenWithValue_reverts() external { // Mock the gas paying token to be the ERC20 token vm.mockCall( - address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18) + address(systemConfig), abi.encodeCall(systemConfig.gasPayingTokenAddress, ()), abi.encode(address(token)) ); vm.expectRevert(NoValue.selector); diff --git a/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol index 1e395b3279c4..86e01c7ad258 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol @@ -24,14 +24,15 @@ contract OptimismPortalInterop_Test is CommonTest { /// @dev Tests that the config for the gas paying token can be set. function testFuzz_setConfig_gasPayingToken_succeeds(bytes calldata _value) public { vm.expectEmit(address(optimismPortal2)); - emitTransactionDeposited({ + emitTransactionDepositedIsthmus({ _from: Constants.DEPOSITOR_ACCOUNT, _to: Predeploys.L1_BLOCK_ATTRIBUTES, _value: 0, _mint: 0, _gasLimit: 200_000, _isCreation: false, - _data: abi.encodeCall(IL1BlockInterop.setConfig, (ConfigType.SET_GAS_PAYING_TOKEN, _value)) + _data: abi.encodeCall(IL1BlockInterop.setConfig, (ConfigType.SET_GAS_PAYING_TOKEN, _value)), + _nonce: 1 }); vm.prank(address(_optimismPortalInterop().systemConfig())); @@ -47,14 +48,15 @@ contract OptimismPortalInterop_Test is CommonTest { /// @dev Tests that the config for adding a dependency can be set. function testFuzz_setConfig_addDependency_succeeds(bytes calldata _value) public { vm.expectEmit(address(optimismPortal2)); - emitTransactionDeposited({ + emitTransactionDepositedIsthmus({ _from: Constants.DEPOSITOR_ACCOUNT, _to: Predeploys.L1_BLOCK_ATTRIBUTES, _value: 0, _mint: 0, _gasLimit: 200_000, _isCreation: false, - _data: abi.encodeCall(IL1BlockInterop.setConfig, (ConfigType.ADD_DEPENDENCY, _value)) + _data: abi.encodeCall(IL1BlockInterop.setConfig, (ConfigType.ADD_DEPENDENCY, _value)), + _nonce: 1 }); vm.prank(address(_optimismPortalInterop().systemConfig())); @@ -70,14 +72,15 @@ contract OptimismPortalInterop_Test is CommonTest { /// @dev Tests that the config for removing a dependency can be set. function testFuzz_setConfig_removeDependency_succeeds(bytes calldata _value) public { vm.expectEmit(address(optimismPortal2)); - emitTransactionDeposited({ + emitTransactionDepositedIsthmus({ _from: Constants.DEPOSITOR_ACCOUNT, _to: Predeploys.L1_BLOCK_ATTRIBUTES, _value: 0, _mint: 0, _gasLimit: 200_000, _isCreation: false, - _data: abi.encodeCall(IL1BlockInterop.setConfig, (ConfigType.REMOVE_DEPENDENCY, _value)) + _data: abi.encodeCall(IL1BlockInterop.setConfig, (ConfigType.REMOVE_DEPENDENCY, _value)), + _nonce: 1 }); vm.prank(address(_optimismPortalInterop().systemConfig())); diff --git a/packages/contracts-bedrock/test/L1/OptimismPortalIsthmus.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortalIsthmus.t.sol new file mode 100644 index 000000000000..50d972f3963e --- /dev/null +++ b/packages/contracts-bedrock/test/L1/OptimismPortalIsthmus.t.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing +import { CommonTest } from "test/setup/CommonTest.sol"; + +// Interfaces +import { IOptimismPortalIsthmus } from "interfaces/L1/IOptimismPortalIsthmus.sol"; + +contract OptimismPortalIsthmus_Test is CommonTest { + /// @notice Marked virtual to be overridden in + /// test/kontrol/deployment/DeploymentSummary.t.sol + function setUp() public virtual override { + super.enableIsthmus(); + super.setUp(); + optimismPortal2.version(); + } + + /// @dev Tests that `receive` successfully deposits ETH. + function testFuzz_receive_succeeds(uint256 _value) external { + uint256 balanceBefore = address(optimismPortal2).balance; + _value = bound(_value, 0, type(uint256).max - balanceBefore); + + vm.expectEmit(address(optimismPortal2)); + emitTransactionDepositedIsthmus(alice, alice, _value, _value, 100_000, false, hex"", 1); + + // give alice money and send as an eoa + vm.deal(alice, _value); + vm.prank(alice, alice); + (bool s,) = address(optimismPortal2).call{ value: _value }(hex""); + + assertTrue(s); + assertEq(address(optimismPortal2).balance, balanceBefore + _value); + } + + function test_nonce_increment_works() external { + vm.deal(alice, 100_000_000); + + uint256 value = 2; + for (uint64 i = 1; i <= 10; i++) { + vm.expectEmit(address(optimismPortal2)); + emitTransactionDepositedIsthmus(alice, alice, value, value, 100_000, false, hex"", i); + vm.prank(alice, alice); + (bool s,) = address(optimismPortal2).call{ value: value }(hex""); + assertTrue(s); + } + } + + function test_depositNonce_works() external { + vm.deal(alice, 100_000_000); + + uint64 nonce = IOptimismPortalIsthmus(payable(address(optimismPortal2))).depositNonce(); + assertEq(0, nonce); + + uint256 value = 2; + for (uint64 i = 1; i <= 2; i++) { + vm.prank(alice, alice); + (bool s,) = address(optimismPortal2).call{ value: value }(hex""); + assertTrue(s); + } + + nonce = IOptimismPortalIsthmus(payable(address(optimismPortal2))).depositNonce(); + assertEq(2, nonce); + } +} diff --git a/packages/contracts-bedrock/test/L1/SystemConfig.t.sol b/packages/contracts-bedrock/test/L1/SystemConfig.t.sol index af96c0cce901..d02a3d3ca529 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 { diff --git a/packages/contracts-bedrock/test/L1/SystemConfigIsthmus.t.sol b/packages/contracts-bedrock/test/L1/SystemConfigIsthmus.t.sol new file mode 100644 index 000000000000..d111d64717cd --- /dev/null +++ b/packages/contracts-bedrock/test/L1/SystemConfigIsthmus.t.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing +import { CommonTest } from "test/setup/CommonTest.sol"; + +// Interfaces +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISystemConfigIsthmus } from "interfaces/L1/ISystemConfigIsthmus.sol"; + +contract SystemConfig_Init is CommonTest { + event ConfigUpdate(uint256 indexed nonceAndVersion, ISystemConfig.UpdateType indexed updateType, bytes data); +} + +contract SystemConfigIsthmus_Test is SystemConfig_Init { + /// @notice Marked virtual to be overridden in + /// test/kontrol/deployment/DeploymentSummary.t.sol + function setUp() public virtual override { + super.enableIsthmus(); + super.setUp(); + systemConfig.version(); + } + + function test_nonce_increment_works() external { + bytes32 newBatcherHash = bytes32(uint256(1234)); + for (uint256 i = 4; i <= 10; i++) { + vm.expectEmit(address(systemConfig)); + emit ConfigUpdate(i << 128 | 1, ISystemConfig.UpdateType.BATCHER, abi.encode(newBatcherHash)); + vm.prank(systemConfig.owner()); + systemConfig.setBatcherHash(newBatcherHash); + } + } + + function test_configUpdateNonce_works() external { + // genesis emits 3 logs, so nonce starts at 3 + uint64 nonce = ISystemConfigIsthmus(payable(address(systemConfig))).configUpdateNonce(); + assertEq(3, nonce); + + for (uint64 i = 1; i <= 2; i++) { + vm.prank(systemConfig.owner()); + systemConfig.setBatcherHash(bytes32(0)); + } + + nonce = ISystemConfigIsthmus(payable(address(systemConfig))).configUpdateNonce(); + assertEq(5, nonce); + } +} + +contract SystemConfigIsthmus_Setters_Test is SystemConfig_Init { + /// @notice Marked virtual to be overridden in + /// test/kontrol/deployment/DeploymentSummary.t.sol + function setUp() public virtual override { + super.enableIsthmus(); + super.setUp(); + } + + /// @dev Tests that `setBatcherHash` updates the batcher hash successfully. + function testFuzz_setBatcherHash_succeeds(bytes32 newBatcherHash) external { + vm.expectEmit(address(systemConfig)); + emit ConfigUpdate(4 << 128 | 1, ISystemConfig.UpdateType.BATCHER, abi.encode(newBatcherHash)); + + vm.prank(systemConfig.owner()); + systemConfig.setBatcherHash(newBatcherHash); + assertEq(systemConfig.batcherHash(), newBatcherHash); + } + + /// @dev Tests that `setGasConfig` updates the overhead and scalar successfully. + function testFuzz_setGasConfig_succeeds(uint256 newOverhead, uint256 newScalar) external { + // always zero out most significant byte + newScalar = (newScalar << 16) >> 16; + vm.expectEmit(address(systemConfig)); + emit ConfigUpdate(4 << 128 | 1, ISystemConfig.UpdateType.FEE_SCALARS, abi.encode(newOverhead, newScalar)); + + vm.prank(systemConfig.owner()); + systemConfig.setGasConfig(newOverhead, newScalar); + assertEq(systemConfig.overhead(), newOverhead); + assertEq(systemConfig.scalar(), newScalar); + } + + function testFuzz_setGasConfigEcotone_succeeds(uint32 _basefeeScalar, uint32 _blobbasefeeScalar) external { + // TODO(opcm upgrades): remove skip once upgrade is implemented + skipIfForkTest("SystemConfig_Setters_TestFail: 'setGasConfigEcotone' method DNE on op mainnet"); + bytes32 encoded = + ffi.encodeScalarEcotone({ _basefeeScalar: _basefeeScalar, _blobbasefeeScalar: _blobbasefeeScalar }); + + vm.expectEmit(address(systemConfig)); + emit ConfigUpdate( + 4 << 128 | 1, ISystemConfig.UpdateType.FEE_SCALARS, abi.encode(systemConfig.overhead(), encoded) + ); + + vm.prank(systemConfig.owner()); + systemConfig.setGasConfigEcotone({ _basefeeScalar: _basefeeScalar, _blobbasefeeScalar: _blobbasefeeScalar }); + assertEq(systemConfig.basefeeScalar(), _basefeeScalar); + assertEq(systemConfig.blobbasefeeScalar(), _blobbasefeeScalar); + assertEq(systemConfig.scalar(), uint256(encoded)); + + (uint32 basefeeScalar, uint32 blobbbasefeeScalar) = ffi.decodeScalarEcotone(encoded); + assertEq(uint256(basefeeScalar), uint256(_basefeeScalar)); + assertEq(uint256(blobbbasefeeScalar), uint256(_blobbasefeeScalar)); + } + + /// @dev Tests that `setGasLimit` updates the gas limit successfully. + function testFuzz_setGasLimit_succeeds(uint64 newGasLimit) external { + uint64 minimumGasLimit = systemConfig.minimumGasLimit(); + uint64 maximumGasLimit = systemConfig.maximumGasLimit(); + newGasLimit = uint64(bound(uint256(newGasLimit), uint256(minimumGasLimit), uint256(maximumGasLimit))); + + vm.expectEmit(address(systemConfig)); + emit ConfigUpdate(4 << 128 | 1, ISystemConfig.UpdateType.GAS_LIMIT, abi.encode(newGasLimit)); + + vm.prank(systemConfig.owner()); + systemConfig.setGasLimit(newGasLimit); + assertEq(systemConfig.gasLimit(), newGasLimit); + } + + /// @dev Tests that `setUnsafeBlockSigner` updates the block signer successfully. + function testFuzz_setUnsafeBlockSigner_succeeds(address newUnsafeSigner) external { + vm.expectEmit(address(systemConfig)); + emit ConfigUpdate(4 << 128 | 1, ISystemConfig.UpdateType.UNSAFE_BLOCK_SIGNER, abi.encode(newUnsafeSigner)); + + vm.prank(systemConfig.owner()); + systemConfig.setUnsafeBlockSigner(newUnsafeSigner); + assertEq(systemConfig.unsafeBlockSigner(), newUnsafeSigner); + } + + /// @dev Tests that `setEIP1559Params` updates the EIP1559 parameters successfully. + function testFuzz_setEIP1559Params_succeeds(uint32 _denominator, uint32 _elasticity) external { + // TODO(opcm upgrades): remove skip once upgrade is implemented + skipIfForkTest("SystemConfig_Setters_TestFail: 'setEIP1559Params' method DNE on op mainnet"); + _denominator = uint32(bound(_denominator, 2, type(uint32).max)); + _elasticity = uint32(bound(_elasticity, 2, type(uint32).max)); + + vm.expectEmit(address(systemConfig)); + emit ConfigUpdate( + 4 << 128 | 1, + ISystemConfig.UpdateType.EIP_1559_PARAMS, + abi.encode(uint256(_denominator) << 32 | uint64(_elasticity)) + ); + + vm.prank(systemConfig.owner()); + systemConfig.setEIP1559Params(_denominator, _elasticity); + assertEq(systemConfig.eip1559Denominator(), _denominator); + assertEq(systemConfig.eip1559Elasticity(), _elasticity); + } +} diff --git a/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol b/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol index b112547cd562..4e8e14b7e0b4 100644 --- a/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol +++ b/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol @@ -289,12 +289,14 @@ contract DeputyGuardianModule_NoPortalCollisions_Test is DeputyGuardianModule_Te /// @dev tests that no function selectors in the L1 contracts collide with the OptimismPortal2 functions called by /// the DeputyGuardianModule. function test_noPortalCollisions_succeeds() external { - string[] memory excludes = new string[](5); + string[] memory excludes = new string[](7); excludes[0] = "src/dispute/lib/*"; excludes[1] = "src/L1/OptimismPortal2.sol"; - excludes[2] = "src/L1/OptimismPortalInterop.sol"; - excludes[3] = "interfaces/L1/IOptimismPortal2.sol"; - excludes[4] = "interfaces/L1/IOptimismPortalInterop.sol"; + excludes[2] = "src/L1/OptimismPortalIsthmus.sol"; + excludes[3] = "src/L1/OptimismPortalInterop.sol"; + excludes[4] = "interfaces/L1/IOptimismPortal2.sol"; + excludes[5] = "interfaces/L1/IOptimismPortalIsthmus.sol"; + excludes[6] = "interfaces/L1/IOptimismPortalInterop.sol"; Abi[] memory abis = ForgeArtifacts.getContractFunctionAbis("src/{L1,dispute,universal}", excludes); for (uint256 i; i < abis.length; i++) { for (uint256 j; j < abis[i].entries.length; j++) { diff --git a/packages/contracts-bedrock/test/setup/CommonTest.sol b/packages/contracts-bedrock/test/setup/CommonTest.sol index aea7c629b695..244676b306d4 100644 --- a/packages/contracts-bedrock/test/setup/CommonTest.sol +++ b/packages/contracts-bedrock/test/setup/CommonTest.sol @@ -35,6 +35,7 @@ contract CommonTest is Test, Setup, Events { bool useAltDAOverride; address customGasToken; + bool useIsthmus; bool useInteropOverride; ERC20 L1Token; @@ -62,6 +63,9 @@ contract CommonTest is Test, Setup, Events { if (customGasToken != address(0)) { deploy.cfg().setUseCustomGasToken(customGasToken); } + if (useIsthmus) { + deploy.cfg().setL2GenesisIsthmusTimeOffset(0); + } if (useInteropOverride) { deploy.cfg().setUseInterop(true); } @@ -166,6 +170,25 @@ contract CommonTest is Test, Setup, Events { emit TransactionDeposited(_from, _to, 0, abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data)); } + /// @dev Helper function that wraps `TransactionDeposited` event. + /// The magic `nonce << 128 | 1` is the nonce | version. + function emitTransactionDepositedIsthmus( + address _from, + address _to, + uint256 _mint, + uint256 _value, + uint64 _gasLimit, + bool _isCreation, + bytes memory _data, + uint64 _nonce + ) + internal + { + emit TransactionDeposited( + _from, _to, uint256(_nonce) << 128 | 1, abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data) + ); + } + function enableAltDA() public { // Check if the system has already been deployed, based off of the heuristic that alice and bob have not been // set by the `setUp` function yet. @@ -187,6 +210,16 @@ contract CommonTest is Test, Setup, Events { customGasToken = _token; } + function enableIsthmus() public { + // Check if the system has already been deployed, based off of the heuristic that alice and bob have not been + // set by the `setUp` function yet. + if (!(alice == address(0) && bob == address(0))) { + revert("CommonTest: Cannot enable interop after deployment. Consider overriding `setUp`."); + } + + useIsthmus = true; + } + function enableInterop() public { // Check if the system has already been deployed, based off of the heuristic that alice and bob have not been // set by the `setUp` function yet. diff --git a/packages/contracts-bedrock/test/setup/Events.sol b/packages/contracts-bedrock/test/setup/Events.sol index 7056f0cbdd6b..d79e3cdba136 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 ae283ef8dba1..390d2791f8a4 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 14dcbc9bc884..4806b43c4c82 100644 --- a/packages/contracts-bedrock/test/universal/Specs.t.sol +++ b/packages/contracts-bedrock/test/universal/Specs.t.sol @@ -15,9 +15,11 @@ import { OPContractsManager } from "src/L1/OPContractsManager.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISystemConfigIsthmus } from "interfaces/L1/ISystemConfigIsthmus.sol"; import { ISystemConfigInterop } from "interfaces/L1/ISystemConfigInterop.sol"; import { IDataAvailabilityChallenge } from "interfaces/L1/IDataAvailabilityChallenge.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; +import { SystemConfigIsthmus } from "../../src/L1/SystemConfigIsthmus.sol"; /// @title Specification_Test /// @dev Specifies common security properties of entrypoints to L1 contracts, including authorization and @@ -210,6 +212,7 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "L1StandardBridge", _sel: _getSel("systemConfig()") }); // OptimismPortalInterop + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("DEPOSIT_NONCE_SLOT()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("depositTransaction(address,uint256,uint64,bool,bytes)") @@ -261,6 +264,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", @@ -273,6 +277,67 @@ contract Specification_Test is CommonTest { _auth: Role.SYSTEMCONFIGOWNER }); + // OptimismPortalIsthmus + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("DEPOSIT_NONCE_SLOT()") }); + _addSpec({ + _name: "OptimismPortalIsthmus", + _sel: _getSel("depositTransaction(address,uint256,uint64,bool,bytes)") + }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("donateETH()") }); + _addSpec({ + _name: "OptimismPortalIsthmus", + _sel: IOptimismPortal2.finalizeWithdrawalTransaction.selector, + _pausable: true + }); + _addSpec({ + _name: "OptimismPortalIsthmus", + _sel: IOptimismPortal2.finalizeWithdrawalTransactionExternalProof.selector, + _pausable: true + }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("finalizedWithdrawals(bytes32)") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("guardian()") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("initialize(address,address,address,uint32)") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("l2Sender()") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("minimumGasLimit(uint64)") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("params()") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("paused()") }); + _addSpec({ + _name: "OptimismPortalIsthmus", + _sel: IOptimismPortal2.proveWithdrawalTransaction.selector, + _pausable: true + }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("provenWithdrawals(bytes32,address)") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("superchainConfig()") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("systemConfig()") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("version()") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("disputeGameFactory()") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("disputeGameBlacklist(address)") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("respectedGameType()") }); + // Comment out the auth to not disturb the testDeputyGuardianAuth test. This code is not meant to run in + // production, + // and will be merged into the OptimismPortal2 contract itself in the future. + _addSpec({ + _name: "OptimismPortalIsthmus", + _sel: _getSel("blacklistDisputeGame(address)") /*, _auth: Role.GUARDIAN*/ + }); + _addSpec({ + _name: "OptimismPortalIsthmus", + _sel: _getSel("setRespectedGameType(uint32)") /*, _auth: Role.GUARDIAN*/ + }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("checkWithdrawal(bytes32,address)") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("proofMaturityDelaySeconds()") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("disputeGameFinalityDelaySeconds()") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("respectedGameTypeUpdatedAt()") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("proofSubmitters(bytes32,uint256)") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("numProofSubmitters(bytes32)") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("depositNonce()") }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("balance()") }); + _addSpec({ + _name: "OptimismPortalIsthmus", + _sel: _getSel("depositERC20Transaction(address,uint256,uint256,uint64,bool,bytes)") + }); + _addSpec({ _name: "OptimismPortalIsthmus", _sel: _getSel("setGasPayingToken(address,uint8,bytes32,bytes32)") }); + // OptimismPortal2 _addSpec({ _name: "OptimismPortal2", _sel: _getSel("depositTransaction(address,uint256,uint64,bool,bytes)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("donateETH()") }); @@ -396,6 +461,7 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfig", _sel: _getSel("OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("BATCH_INBOX_SLOT()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("gasPayingToken()") }); + _addSpec({ _name: "SystemConfig", _sel: _getSel("gasPayingTokenAddress()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("gasPayingTokenName()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("gasPayingTokenSymbol()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("isCustomGasToken()") }); @@ -410,6 +476,85 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfig", _sel: _getSel("blobbasefeeScalar()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("maximumGasLimit()") }); + // SystemConfigIsthmus + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("UNSAFE_BLOCK_SIGNER_SLOT()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("START_BLOCK_SLOT()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("VERSION()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("batcherHash()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("gasLimit()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("eip1559Denominator()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("eip1559Elasticity()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: ISystemConfig.initialize.selector }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: ISystemConfigIsthmus.minimumGasLimit.selector }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("overhead()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("owner()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("renounceOwnership()"), _auth: Role.SYSTEMCONFIGOWNER }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: ISystemConfigIsthmus.resourceConfig.selector }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("scalar()") }); + _addSpec({ + _name: "SystemConfigIsthmus", + _sel: ISystemConfigIsthmus.setBatcherHash.selector, + _auth: Role.SYSTEMCONFIGOWNER + }); + _addSpec({ + _name: "SystemConfigIsthmus", + _sel: ISystemConfigIsthmus.setGasConfig.selector, + _auth: Role.SYSTEMCONFIGOWNER + }); + _addSpec({ + _name: "SystemConfigIsthmus", + _sel: ISystemConfigIsthmus.setGasLimit.selector, + _auth: Role.SYSTEMCONFIGOWNER + }); + _addSpec({ + _name: "SystemConfigIsthmus", + _sel: ISystemConfigIsthmus.setEIP1559Params.selector, + _auth: Role.SYSTEMCONFIGOWNER + }); + _addSpec({ + _name: "SystemConfigIsthmus", + _sel: ISystemConfigIsthmus.setUnsafeBlockSigner.selector, + _auth: Role.SYSTEMCONFIGOWNER + }); + _addSpec({ + _name: "SystemConfigIsthmus", + _sel: _getSel("transferOwnership(address)"), + _auth: Role.SYSTEMCONFIGOWNER + }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: ISystemConfigIsthmus.unsafeBlockSigner.selector }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("version()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("l1CrossDomainMessenger()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("l1ERC721Bridge()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("l1StandardBridge()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("optimismPortal()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("optimismMintableERC20Factory()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("batchInbox()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("startBlock()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("L1_CROSS_DOMAIN_MESSENGER_SLOT()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("L1_ERC_721_BRIDGE_SLOT()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("L1_STANDARD_BRIDGE_SLOT()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("OPTIMISM_PORTAL_SLOT()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("BATCH_INBOX_SLOT()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("gasPayingToken()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("gasPayingTokenAddress()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("gasPayingTokenName()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("gasPayingTokenSymbol()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("isCustomGasToken()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("DISPUTE_GAME_FACTORY_SLOT()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("disputeGameFactory()") }); + _addSpec({ + _name: "SystemConfigIsthmus", + _sel: _getSel("setGasConfigEcotone(uint32,uint32)"), + _auth: Role.SYSTEMCONFIGOWNER + }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("basefeeScalar()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("blobbasefeeScalar()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("maximumGasLimit()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("CONFIG_UPDATE_NONCE_SLOT()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("VERSION_1()") }); + _addSpec({ _name: "SystemConfigIsthmus", _sel: _getSel("configUpdateNonce()") }); + // SystemConfigInterop _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("UNSAFE_BLOCK_SIGNER_SLOT()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("START_BLOCK_SLOT()") }); @@ -472,6 +617,7 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("BATCH_INBOX_SLOT()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("gasPayingToken()") }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("gasPayingTokenAddress()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("gasPayingTokenName()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("gasPayingTokenSymbol()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("isCustomGasToken()") }); @@ -485,6 +631,9 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("basefeeScalar()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("blobbasefeeScalar()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("maximumGasLimit()") }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("CONFIG_UPDATE_NONCE_SLOT()") }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("VERSION_1()") }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("configUpdateNonce()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("addDependency(uint256)"), _auth: Role.DEPENDENCYMANAGER }); _addSpec({ _name: "SystemConfigInterop", diff --git a/packages/contracts-bedrock/test/vendor/Initializable.t.sol b/packages/contracts-bedrock/test/vendor/Initializable.t.sol index d8e06d29d602..66b8e1f8e80e 100644 --- a/packages/contracts-bedrock/test/vendor/Initializable.t.sol +++ b/packages/contracts-bedrock/test/vendor/Initializable.t.sol @@ -371,27 +371,27 @@ contract Initializer_Test is CommonTest { /// 3. The `initialize()` function of each contract cannot be called again. function test_cannotReinitialize_succeeds() public { // Collect exclusions. - string[] memory excludes = new string[](9); + string[] memory excludes = new string[](10); // TODO: Neither of these contracts are labeled properly in the deployment script. Both are // currently being labeled as their non-interop versions. Remove these exclusions once // the deployment script is fixed. - excludes[0] = "src/L1/SystemConfigInterop.sol"; - excludes[1] = "src/L1/OptimismPortalInterop.sol"; + excludes[0] = "src/L1/SystemConfigIsthmus.sol"; + excludes[1] = "src/L1/OptimismPortalIsthmus.sol"; + excludes[2] = "src/L1/SystemConfigInterop.sol"; + excludes[3] = "src/L1/OptimismPortalInterop.sol"; // Contract is currently not being deployed as part of the standard deployment script. - excludes[2] = "src/L2/OptimismSuperchainERC20.sol"; + excludes[4] = "src/L2/OptimismSuperchainERC20.sol"; // Periphery contracts don't get deployed as part of the standard deployment script. - excludes[3] = "src/periphery/*"; + excludes[5] = "src/periphery/*"; // TODO: Deployment script is currently "broken" in the sense that it doesn't properly // label the FaultDisputeGame and PermissionedDisputeGame contracts and instead // simply deploys them anonymously. Means that functions like "getInitializedSlot" // don't work properly. Remove these exclusions once the deployment script is fixed. - excludes[4] = "src/dispute/FaultDisputeGame.sol"; - excludes[5] = "src/dispute/PermissionedDisputeGame.sol"; + excludes[6] = "src/dispute/FaultDisputeGame.sol"; + excludes[7] = "src/dispute/PermissionedDisputeGame.sol"; // TODO: Eventually remove this exclusion. Same reason as above dispute contracts. - excludes[6] = "src/L1/OPContractsManager.sol"; - excludes[7] = "src/L1/OPContractsManagerInterop.sol"; - // The L2OutputOracle is not always deployed (and is no longer being modified) - excludes[8] = "src/L1/L2OutputOracle.sol"; + excludes[8] = "src/L1/OPContractsManager.sol"; + excludes[9] = "src/L1/OPContractsManagerInterop.sol"; // Get all contract names in the src directory, minus the excluded contracts. string[] memory contractNames = ForgeArtifacts.getContractNames("src/*", excludes);