diff --git a/.semgrep/rules/sol-rules.yaml b/.semgrep/rules/sol-rules.yaml index e227674283a51..359d4ecbe4581 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/op-chain-ops/cmd/deposit-hash/main.go b/op-chain-ops/cmd/deposit-hash/main.go index 9166a0667c090..cc52a96676528 100644 --- a/op-chain-ops/cmd/deposit-hash/main.go +++ b/op-chain-ops/cmd/deposit-hash/main.go @@ -32,8 +32,7 @@ func main() { for _, ethLog := range l1Receipt.Logs { if ethLog.Topics[0].String() == depositLogTopic.String() { - - reconstructedDep, err := derive.UnmarshalDepositLogEvent(ethLog) + reconstructedDep, err := derive.UnmarshalDepositLogEventIgnoreNonce(ethLog) if err != nil { log.Crit("Failed to parse deposit event ", "err", err) } diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index 5df3f4510feda..30093bcec2a74 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,31 @@ 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 { + depositNonce := uint64(0) + configUpdateNonce := uint64(0) + // if Isthmus is active at genesis, we must ensure the nonces are set correctly + if d.L2GenesisIsthmusTimeOffset != nil && *d.L2GenesisIsthmusTimeOffset == 0 { + // SystemConfig emits 3 ConfigUpdate events which increments the nonce by 3 + configUpdateNonce += 3 + // If a custom gas token is set, a TransactionDeposited event is emitted from + // the OptimismPortal, which increments the deposit nonce by 1 + if d.UseCustomGasToken { + depositNonce += 1 + } + } + return eth.SystemConfig{ + BatcherAddr: d.BatchSenderAddress, + Overhead: eth.Bytes32(common.BigToHash(new(big.Int).SetUint64(d.GasPriceOracleOverhead))), + Scalar: d.FeeScalar(), + GasLimit: uint64(d.L2GenesisBlockGasLimit), + DepositNonce: depositNonce, + ConfigUpdateNonce: configUpdateNonce, + } +} + // 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 e70c69e9f481a..61c702f036338 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 d7215b80650f7..7ab973ce6a839 100644 --- a/op-e2e/actions/helpers/user.go +++ b/op-e2e/actions/helpers/user.go @@ -393,7 +393,7 @@ func (s *CrossLayerUser) CheckDepositTx(t Testing, l1TxHash common.Hash, index i require.False(t, l2Success) } else { require.Less(t, index, len(depositReceipt.Logs), "must have enough logs in receipt") - reconstructedDep, err := derive.UnmarshalDepositLogEvent(depositReceipt.Logs[index]) + reconstructedDep, err := derive.UnmarshalDepositLogEventIgnoreNonce(depositReceipt.Logs[index]) require.NoError(t, err, "Could not reconstruct L2 Deposit") l2Tx := types.NewTx(reconstructedDep) s.L2.CheckReceipt(t, l2Success, l2Tx.Hash()) diff --git a/op-e2e/e2eutils/setup.go b/op-e2e/e2eutils/setup.go index dbc9ecea25b9c..e75f4a69eaa5f 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 655cf612c9ad4..8ce73cf26f4cb 100644 --- a/op-e2e/system/bridge/bridge_test.go +++ b/op-e2e/system/bridge/bridge_test.go @@ -102,7 +102,7 @@ func TestERC20BridgeDeposits(t *testing.T) { depositEvent, err := receipts.FindLog(depositReceipt.Logs, portal.ParseTransactionDeposited) require.NoError(t, err, "Should emit deposit event") - depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw) + depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw) require.NoError(t, err) _, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash()) require.NoError(t, err) diff --git a/op-e2e/system/e2esys/setup.go b/op-e2e/system/e2esys/setup.go index a54a46d1e5db2..9934abe38c215 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 bde29ddfa52c9..0f9f1c2c0df2b 100644 --- a/op-e2e/system/gastoken/gastoken_test.go +++ b/op-e2e/system/gastoken/gastoken_test.go @@ -255,7 +255,7 @@ func setCustomGasToken(t *testing.T, cfg e2esys.SystemConfig, sys *e2esys.System depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited) require.NoError(t, err, "Should emit deposit event") - depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw) + depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw) require.NoError(t, err) l2Client := sys.NodeClient("sequencer") @@ -326,7 +326,7 @@ func checkDeposit(t *testing.T, gto gasTokenTestOpts, enabled bool) { // compute the deposit transaction hash + poll for it depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited) require.NoError(t, err, "Should emit deposit event") - depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw) + depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw) require.NoError(t, err) _, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash()) require.NoError(t, err) @@ -496,7 +496,7 @@ func checkFeeWithdrawal(t *testing.T, gto gasTokenTestOpts, enabled bool) { // Compute the deposit transaction hash + poll for it depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited) require.NoError(t, err, "Should emit deposit event") - depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw) + depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw) require.NoError(t, err) _, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash()) require.NoError(t, err) diff --git a/op-e2e/system/helpers/tx_helper.go b/op-e2e/system/helpers/tx_helper.go index 10c16c0e74656..69d603a1e213d 100644 --- a/op-e2e/system/helpers/tx_helper.go +++ b/op-e2e/system/helpers/tx_helper.go @@ -53,7 +53,7 @@ func SendDepositTx(t *testing.T, cfg e2esys.SystemConfig, l1Client *ethclient.Cl t.Logf("SendDepositTx: included on L1") // Wait for transaction to be included on L2 - reconstructedDep, err := derive.UnmarshalDepositLogEvent(l1Receipt.Logs[0]) + reconstructedDep, err := derive.UnmarshalDepositLogEventIgnoreNonce(l1Receipt.Logs[0]) require.NoError(t, err, "Could not reconstruct L2 Deposit") tx = types.NewTx(reconstructedDep) l2Receipt, err := wait.ForReceipt(ctx, l2Client, tx.Hash(), l2Opts.ExpectedStatus) diff --git a/op-node/rollup/derive/attributes.go b/op-node/rollup/derive/attributes.go index 5d859a9fcd596..cb9584dcdc1be 100644 --- a/op-node/rollup/derive/attributes.go +++ b/op-node/rollup/derive/attributes.go @@ -65,38 +65,35 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex // case we need to fetch all transaction receipts from the L1 origin block so we can scan for // user deposits. if l2Parent.L1Origin.Number != epoch.Number { - info, receipts, err := ba.l1.FetchReceipts(ctx, epoch.Hash) - if err != nil { + var receipts types.Receipts + if l1Info, receipts, err = ba.l1.FetchReceipts(ctx, epoch.Hash); err != nil { return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info and receipts: %w", err)) } - if l2Parent.L1Origin.Hash != info.ParentHash() { + if l2Parent.L1Origin.Hash != l1Info.ParentHash() { return nil, NewResetError( fmt.Errorf("cannot create new block with L1 origin %s (parent %s) on top of L1 origin %s", - epoch, info.ParentHash(), l2Parent.L1Origin)) + epoch, l1Info.ParentHash(), l2Parent.L1Origin)) } - deposits, err := DeriveDeposits(receipts, ba.rollupCfg.DepositContractAddress) - if err != nil { + if depositTxs, sysConfig.DepositNonce, err = DeriveDeposits(receipts, ba.rollupCfg.DepositContractAddress, sysConfig.DepositNonce); err != nil { // deposits may never be ignored. Failing to process them is a critical error. return nil, NewCriticalError(fmt.Errorf("failed to derive some deposits: %w", err)) } + // apply sysCfg changes - if err := UpdateSystemConfigWithL1Receipts(&sysConfig, receipts, ba.rollupCfg, info.Time()); err != nil { + if err = UpdateSystemConfigWithL1Receipts(&sysConfig, receipts, ba.rollupCfg, l1Info.Time()); err != nil { return nil, NewCriticalError(fmt.Errorf("failed to apply derived L1 sysCfg updates: %w", err)) } - l1Info = info - depositTxs = deposits seqNumber = 0 } else { if l2Parent.L1Origin.Hash != epoch.Hash { return nil, NewResetError(fmt.Errorf("cannot create new block with L1 origin %s in conflict with L1 origin %s", epoch, l2Parent.L1Origin)) } - info, err := ba.l1.InfoByHash(ctx, epoch.Hash) + l1Info, err = ba.l1.InfoByHash(ctx, epoch.Hash) if err != nil { return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info: %w", err)) } - l1Info = info depositTxs = nil seqNumber = l2Parent.SequenceNumber + 1 } diff --git a/op-node/rollup/derive/deposit_log.go b/op-node/rollup/derive/deposit_log.go index 74fde28c59882..7a3ae59a42159 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 4cb335b1bef48..03a47f8a4dc5b 100644 --- a/op-node/rollup/derive/deposit_log_test.go +++ b/op-node/rollup/derive/deposit_log_test.go @@ -31,7 +31,7 @@ func TestUnmarshalLogEvent(t *testing.T) { log.TxIndex = uint(rng.Intn(10000)) log.Index = uint(source.LogIndex) log.BlockHash = source.L1BlockHash - depOutput, err := UnmarshalDepositLogEvent(log) + depOutput, _, err := UnmarshalDepositLogEvent(log, 0) if err != nil { t.Fatal(err) } @@ -121,7 +121,7 @@ func TestDeriveUserDeposits(t *testing.T) { if err != nil { t.Fatal(err) } - got, err := UserDeposits(receipts, MockDepositContractAddr) + got, _, err := UserDeposits(receipts, MockDepositContractAddr, 0) require.NoError(t, err) require.Equal(t, len(got), len(expectedDeposits)) for d, depTx := range got { diff --git a/op-node/rollup/derive/deposit_log_tob_test.go b/op-node/rollup/derive/deposit_log_tob_test.go index 49721104b0867..ee172644bbf1d 100644 --- a/op-node/rollup/derive/deposit_log_tob_test.go +++ b/op-node/rollup/derive/deposit_log_tob_test.go @@ -144,7 +144,7 @@ func FuzzDeriveDepositsRoundTrip(f *testing.F) { receipts, expectedDeposits := fuzzReceipts(typeProvider, blockHash, MockDepositContractAddr) // Derive our user deposits from the transaction receipts - derivedDeposits, err := UserDeposits(receipts, MockDepositContractAddr) + derivedDeposits, _, err := UserDeposits(receipts, MockDepositContractAddr, 0) require.NoError(t, err) // Ensure all deposits we derived matched what we expected to receive. @@ -188,13 +188,13 @@ func FuzzDeriveDepositsBadVersion(f *testing.F) { // Generate any topic but the deposit event versions we support. // TODO: As opposed to keeping this hardcoded, a method such as IsValidVersion(v) should be // used here. - badTopic := DepositEventVersion0 - for badTopic == DepositEventVersion0 { - typeProvider.Fuzz(&badTopic) + badVersion := DepositEventVersion0 + for badVersion == DepositEventVersion0 { + typeProvider.Fuzz(&badVersion) } // Set our bad topic and update our state - log.Topics[3] = badTopic + log.Topics[3] = PackNonceAndVersion(0, badVersion) hasBadDepositVersion = true } } @@ -202,7 +202,7 @@ func FuzzDeriveDepositsBadVersion(f *testing.F) { } // Derive our user deposits from the transaction receipts - _, err := UserDeposits(receipts, MockDepositContractAddr) + _, _, err := UserDeposits(receipts, MockDepositContractAddr, 0) // If we patched a bad deposit version this iteration, we should expect an error and not be able to proceed // further diff --git a/op-node/rollup/derive/deposits.go b/op-node/rollup/derive/deposits.go index b71cd619678c0..10fbb5f3f328a 100644 --- a/op-node/rollup/derive/deposits.go +++ b/op-node/rollup/derive/deposits.go @@ -11,7 +11,7 @@ import ( ) // UserDeposits transforms the L2 block-height and L1 receipts into the transaction inputs for a full L2 block -func UserDeposits(receipts []*types.Receipt, depositContractAddr common.Address) ([]*types.DepositTx, error) { +func UserDeposits(receipts []*types.Receipt, depositContractAddr common.Address, currentNonce uint64) ([]*types.DepositTx, uint64, error) { var out []*types.DepositTx var result error for i, rec := range receipts { @@ -20,7 +20,8 @@ func UserDeposits(receipts []*types.Receipt, depositContractAddr common.Address) } for j, log := range rec.Logs { if log.Address == depositContractAddr && len(log.Topics) > 0 && log.Topics[0] == DepositEventABIHash { - dep, err := UnmarshalDepositLogEvent(log) + dep, newNonce, err := UnmarshalDepositLogEvent(log, currentNonce) + currentNonce = newNonce if err != nil { result = multierror.Append(result, fmt.Errorf("malformatted L1 deposit log in receipt %d, log %d: %w", i, j, err)) } else { @@ -29,12 +30,13 @@ func UserDeposits(receipts []*types.Receipt, depositContractAddr common.Address) } } } - return out, result + return out, currentNonce, result } -func DeriveDeposits(receipts []*types.Receipt, depositContractAddr common.Address) ([]hexutil.Bytes, error) { +func DeriveDeposits(receipts []*types.Receipt, depositContractAddr common.Address, currentNonce uint64) ([]hexutil.Bytes, uint64, error) { var result error - userDeposits, err := UserDeposits(receipts, depositContractAddr) + userDeposits, newNonce, err := UserDeposits(receipts, depositContractAddr, currentNonce) + currentNonce = newNonce if err != nil { result = multierror.Append(result, err) } @@ -47,5 +49,5 @@ func DeriveDeposits(receipts []*types.Receipt, depositContractAddr common.Addres encodedTxs = append(encodedTxs, opaqueTx) } } - return encodedTxs, result + return encodedTxs, currentNonce, result } diff --git a/op-node/rollup/derive/fuzz_parsers_test.go b/op-node/rollup/derive/fuzz_parsers_test.go index afb7e0850e262..abc5c70f5f3ff 100644 --- a/op-node/rollup/derive/fuzz_parsers_test.go +++ b/op-node/rollup/derive/fuzz_parsers_test.go @@ -299,7 +299,7 @@ func FuzzUnmarshallLogEvent(f *testing.F) { depositEvent.Raw = types.Log{} // Clear out the log // Verify that is passes our custom unmarshalling logic - dep, err := UnmarshalDepositLogEvent(logs[0]) + dep, _, err := UnmarshalDepositLogEvent(logs[0], 0) if err != nil { t.Fatalf("Could not unmarshal log that was emitted by the deposit contract: %v", err) } diff --git a/op-node/rollup/derive/l1_block_info.go b/op-node/rollup/derive/l1_block_info.go index a01fe5bca6b91..d7f55d7e91166 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 3f7dd0647e6d8..bc8195f80d832 100644 --- a/op-node/rollup/derive/l1_block_info_test.go +++ b/op-node/rollup/derive/l1_block_info_test.go @@ -165,7 +165,7 @@ func TestParseL1InfoDepositTxData(t *testing.T) { 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") }) t.Run("activation-block interop", func(t *testing.T) { @@ -192,7 +192,7 @@ func TestParseL1InfoDepositTxData(t *testing.T) { 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)) }) } 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 d7c9f2f8931dc..1454603c91647 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 6da1a952b5fe1..2d304d9ae27b9 100644 --- a/op-node/rollup/derive/payload_util.go +++ b/op-node/rollup/derive/payload_util.go @@ -85,10 +85,12 @@ func PayloadToSystemConfig(rollupCfg *rollup.Config, payload *eth.ExecutionPaylo binary.BigEndian.PutUint32(info.L1FeeScalar[28:32], info.BaseFeeScalar) } r := eth.SystemConfig{ - BatcherAddr: info.BatcherAddr, - Overhead: info.L1FeeOverhead, - Scalar: info.L1FeeScalar, - GasLimit: uint64(payload.GasLimit), + BatcherAddr: info.BatcherAddr, + Overhead: info.L1FeeOverhead, + Scalar: info.L1FeeScalar, + GasLimit: uint64(payload.GasLimit), + DepositNonce: info.DepositNonce, + ConfigUpdateNonce: info.ConfigUpdateNonce, } if rollupCfg.IsHolocene(uint64(payload.Timestamp)) { if err := eip1559.ValidateHoloceneExtraData(payload.ExtraData); err != nil { diff --git a/op-node/rollup/derive/system_config.go b/op-node/rollup/derive/system_config.go index 72c4e713c30e9..1c38c4e577225 100644 --- a/op-node/rollup/derive/system_config.go +++ b/op-node/rollup/derive/system_config.go @@ -27,7 +27,8 @@ var ( var ( ConfigUpdateEventABI = "ConfigUpdate(uint256,uint8,bytes)" ConfigUpdateEventABIHash = crypto.Keccak256Hash([]byte(ConfigUpdateEventABI)) - ConfigUpdateEventVersion0 = common.Hash{} + ConfigUpdateEventVersion0 = uint64(0) + ConfigUpdateEventVersion1 = uint64(1) ) // UpdateSystemConfigWithL1Receipts filters all L1 receipts to find config updates and applies the config updates to the given sysCfg @@ -39,7 +40,8 @@ func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*type } for j, log := range rec.Logs { if log.Address == cfg.L1SystemConfigAddress && len(log.Topics) > 0 && log.Topics[0] == ConfigUpdateEventABIHash { - if err := ProcessSystemConfigUpdateLogEvent(sysCfg, log, cfg, l1Time); err != nil { + var err error + if err = ProcessSystemConfigUpdateLogEvent(sysCfg, log, cfg, l1Time); err != nil { result = multierror.Append(result, fmt.Errorf("malformatted L1 system sysCfg log in receipt %d, log %d: %w", i, j, err)) } } @@ -53,7 +55,7 @@ func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*type // parse log data for: // // event ConfigUpdate( -// uint256 indexed version, +// uint256 indexed nonceAndVersion, // UpdateType indexed updateType, // bytes data // ); @@ -66,10 +68,18 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L } // indexed 0 - version := ev.Topics[1] - if version != ConfigUpdateEventVersion0 { - return fmt.Errorf("unrecognized SystemConfig update event version: %s", version) + newNonce, version := UnpackNonceAndVersion(ev.Topics[1]) + switch version { + case ConfigUpdateEventVersion0: + case ConfigUpdateEventVersion1: + if newNonce != destSysCfg.ConfigUpdateNonce+1 { + return fmt.Errorf("invalid config update nonce, expected %d got %d", destSysCfg.ConfigUpdateNonce+1, newNonce) + } + destSysCfg.ConfigUpdateNonce = newNonce + default: + return fmt.Errorf("unrecognized SystemConfig update event version: %d", version) } + // indexed 1 updateType := ev.Topics[2] diff --git a/op-node/rollup/derive/system_config_test.go b/op-node/rollup/derive/system_config_test.go index add1790938796..e9530af628287 100644 --- a/op-node/rollup/derive/system_config_test.go +++ b/op-node/rollup/derive/system_config_test.go @@ -57,7 +57,7 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { log: &types.Log{ Topics: []common.Hash{ ConfigUpdateEventABIHash, - ConfigUpdateEventVersion0, + PackNonceAndVersion(0, ConfigUpdateEventVersion0), SystemConfigUpdateUnsafeBlockSigner, }, }, @@ -77,7 +77,7 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { log: &types.Log{ Topics: []common.Hash{ ConfigUpdateEventABIHash, - ConfigUpdateEventVersion0, + PackNonceAndVersion(0, ConfigUpdateEventVersion0), SystemConfigUpdateBatcher, }, }, @@ -101,7 +101,7 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { log: &types.Log{ Topics: []common.Hash{ ConfigUpdateEventABIHash, - ConfigUpdateEventVersion0, + PackNonceAndVersion(0, ConfigUpdateEventVersion0), SystemConfigUpdateFeeScalars, }, }, @@ -127,7 +127,7 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { log: &types.Log{ Topics: []common.Hash{ ConfigUpdateEventABIHash, - ConfigUpdateEventVersion0, + PackNonceAndVersion(0, ConfigUpdateEventVersion0), SystemConfigUpdateGasLimit, }, }, @@ -151,7 +151,7 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { log: &types.Log{ Topics: []common.Hash{ ConfigUpdateEventABIHash, - ConfigUpdateEventVersion0, + PackNonceAndVersion(0, ConfigUpdateEventVersion0), SystemConfigUpdateFeeScalars, }, }, @@ -191,7 +191,7 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { log: &types.Log{ Topics: []common.Hash{ ConfigUpdateEventABIHash, - ConfigUpdateEventVersion0, + PackNonceAndVersion(0, ConfigUpdateEventVersion0), SystemConfigUpdateEIP1559Params, }, }, diff --git a/op-service/eth/types.go b/op-service/eth/types.go index 122e9a4df7833..0de9e97d54c26 100644 --- a/op-service/eth/types.go +++ b/op-service/eth/types.go @@ -455,6 +455,10 @@ type SystemConfig struct { // value will be 0 if Holocene is not active, or if derivation has yet to // process any EIP_1559_PARAMS system config update events. EIP1559Params Bytes8 `json:"eip1559Params"` + // DepositNonce identifies the nonce of the last TransactionDeposited event processed by this chain. + DepositNonce uint64 `json:"depositNonce"` + // ConfigUpdateNonce identifies the nonce of the last ConfigUpdate event processed by this chain. + ConfigUpdateNonce uint64 `json:"configUpdateNonce"` // More fields can be added for future SystemConfig versions. // MarshalPreHolocene indicates whether or not this struct should be diff --git a/op-service/eth/types_test.go b/op-service/eth/types_test.go index 03409e16008d5..40d69343e3eb1 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-wheel/commands.go b/op-wheel/commands.go index 521578a34cb6b..7500399360c11 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 74ba7b4790e95..e5d0b927ef909 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 58fe5eff5dcab..2d46cb141792e 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 0000000000000..963a5c2c6dfe6 --- /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 904375167f48d..a1504263445c5 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 4cf4a06f943fe..67744c535b1ca 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 0000000000000..1f48d25175a76 --- /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 a43b3c7c39639..599f9e037008d 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 dd72e3fa6f894..ba9e9a08b1eaf 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/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index 69f9bf5bf29b6..5f9967a51bbaf 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(); @@ -279,9 +280,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(); @@ -301,8 +300,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); @@ -347,7 +348,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 0eed85d9511c7..7082352a8d8cb 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; @@ -110,6 +111,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"); diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index 63357584a8809..eb09e0971be27 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 f22930eef9226..0e5ab6474727b 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: 175722) +GasBenchMark_L1BlockInterop_DepositsComplete:test_depositsComplete_benchmark() (gas: 7589) +GasBenchMark_L1BlockInterop_DepositsComplete_Warm:test_depositsComplete_benchmark() (gas: 5589) +GasBenchMark_L1BlockInterop_SetValuesInterop:test_setL1BlockValuesInterop_benchmark() (gas: 197772) GasBenchMark_L1BlockInterop_SetValuesInterop_Warm:test_setL1BlockValuesInterop_benchmark() (gas: 5144) -GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158531) +GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158463) GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597) -GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369235) -GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967442) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564429) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076577) -GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467041) -GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512790) -GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72667) \ No newline at end of file +GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369177) +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: 72624) diff --git a/packages/contracts-bedrock/snapshots/abi/L1Block.json b/packages/contracts-bedrock/snapshots/abi/L1Block.json index 020c9e942c757..6401e50c10db2 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 ab089f0cec555..922285d876432 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 2f52ed573d37c..82f05a8815452 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 5b9f72b9446c8..b3d605debfd3f 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 0000000000000..3231b6dd0c999 --- /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 b7e18556fa2cb..0aad67497e96f 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 a459af15801b5..0ecc5036a2686 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 0000000000000..ccc94ffbc8ad2 --- /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 42019379ded7d..f00f43250629c 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": "0x8dacac7564b2fff7574fe509334cf1206ca46dfe6e8f2a13bbf25e96aef38d73", + "sourceCodeHash": "0x6b55db1f8683b0891153b64300c8615729829e775cd62c9049f14af1600abb06" + }, + "src/L1/SystemConfigIsthmus.sol": { + "initCodeHash": "0xee5664bebc847846a72c66adafd27a524ed0360e588485708edf0fd91060f71d", + "sourceCodeHash": "0x026e65d1c17f7d64cbfc1bf798bcc0b31dec785423f5d741d6cbf7668a341300" }, "src/L2/BaseFeeVault.sol": { "initCodeHash": "0xc403d4c555d8e69a2699e01d192ae7327136701fa02da10a6d75a584b3c364c9", @@ -60,12 +68,12 @@ "sourceCodeHash": "0x305c72d7be9149fce7095bd4641a1a19acada3126fbc43599f674cadbf6e7d6c" }, "src/L2/L1Block.sol": { - "initCodeHash": "0x22f9b9277e33dc27df8366c2dd6e8340d294947b57116db35c6d14c41225633f", - "sourceCodeHash": "0xffb6cf768097b2d6cb6ecb2d6463c176af9acd70415aa0d2e4f017758f737eee" + "initCodeHash": "0x6cec4b2ad760ed96b2a453e5d4f29bd8ba08b4d4993fce1e026bc90e9fbcff0f", + "sourceCodeHash": "0x09837ac08602eaddb1e65b14b413bb14f73cb53c2700fdfe886a83b92e253c9e" }, "src/L2/L1BlockInterop.sol": { - "initCodeHash": "0x67e99306d9a09cac587f65cfa2c0de55da9eca184fd1fc3f4b885d2c47114483", - "sourceCodeHash": "0x9493f90136917fc95d2ac942f061c1b9cffeff6d327afb46fe4e69784e7f2100" + "initCodeHash": "0xb32dd7fb8087ddc7ec998bdf2b828def76c7357c0c88a01a5f7b3a813edd5727", + "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 2928d2147b5c8..82eac2b0f225b 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 14ee2ff9609a0..8af802ff27129 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 0000000000000..0fdd65b3e88fb --- /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 0000000000000..a6184a1f10dd5 --- /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 10448f0b7fb65..15e073b037c9f 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 fecd4dc0d6b21..cbfd58daa8aa0 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 0000000000000..76ac1826a28a7 --- /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 0b4562543d925..3595000e838f6 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 af7656876bcbf..2cdea7a395b13 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 0000000000000..70d5269280f3c --- /dev/null +++ b/packages/contracts-bedrock/src/L1/SystemConfigIsthmus.sol @@ -0,0 +1,47 @@ +// 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_) { + bytes32 slot = CONFIG_UPDATE_NONCE_SLOT; + assembly { + nonce_ := sload(slot) + } + } + + function _configUpdateNonceAndVersion() internal virtual override returns (uint256) { + uint64 nonce = configUpdateNonce(); + nonce++; + _setConfigUpdateNonce(nonce); + return uint256(nonce) << 128 | VERSION_1; + } + + function _setConfigUpdateNonce(uint64 _nonce) internal { + bytes32 slot = CONFIG_UPDATE_NONCE_SLOT; + assembly { + sstore(slot, _nonce) + } + } +} diff --git a/packages/contracts-bedrock/src/L2/L1Block.sol b/packages/contracts-bedrock/src/L2/L1Block.sol index 3767b80988da7..009a889613cd5 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.5.1-beta.6 function version() public pure virtual returns (string memory) { - return "1.5.1-beta.5"; + return "1.5.1-beta.6"; } /// @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 7b92b202a052d..89f4d6227006c 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 5aa4ee7d3d8a4..ca358d2124d39 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 19ff94552fc17..7361371dce89b 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol @@ -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 1e395b3279c4f..f451b035a7f9a 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol @@ -24,7 +24,7 @@ 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, @@ -47,7 +47,7 @@ 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, @@ -70,7 +70,7 @@ 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, diff --git a/packages/contracts-bedrock/test/L1/SystemConfig.t.sol b/packages/contracts-bedrock/test/L1/SystemConfig.t.sol index af96c0cce9017..d02a3d3ca5292 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/safe/DeputyGuardianModule.t.sol b/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol index b112547cd5624..4e8e14b7e0b40 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 d04b3f1ac0220..746a5a16daac7 100644 --- a/packages/contracts-bedrock/test/setup/CommonTest.sol +++ b/packages/contracts-bedrock/test/setup/CommonTest.sol @@ -171,6 +171,24 @@ 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 `1 << 128 | 1` is the nonce | version. + function emitTransactionDepositedIsthmus( + address _from, + address _to, + uint256 _mint, + uint256 _value, + uint64 _gasLimit, + bool _isCreation, + bytes memory _data + ) + internal + { + emit TransactionDeposited( + _from, _to, 1 << 128 | 1, abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data) + ); + } + function enableLegacyContracts() 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 7056f0cbdd6b0..d79e3cdba1363 100644 --- a/packages/contracts-bedrock/test/setup/Events.sol +++ b/packages/contracts-bedrock/test/setup/Events.sol @@ -13,7 +13,9 @@ import { Types } from "src/libraries/Types.sol"; contract Events { /// @dev OpenZeppelin Ownable.sol transferOwnership event event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData); + event TransactionDeposited( + address indexed from, address indexed to, uint256 indexed nonceAndVersion, bytes opaqueData + ); event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to); diff --git a/packages/contracts-bedrock/test/universal/BenchmarkTest.t.sol b/packages/contracts-bedrock/test/universal/BenchmarkTest.t.sol index ae283ef8dba1e..390d2791f8a44 100644 --- a/packages/contracts-bedrock/test/universal/BenchmarkTest.t.sol +++ b/packages/contracts-bedrock/test/universal/BenchmarkTest.t.sol @@ -178,7 +178,9 @@ contract GasBenchMark_L1BlockInterop is GasBenchMark_L1Block { type(uint256).max, type(uint256).max, keccak256(abi.encode(1)), - bytes32(type(uint256).max) + bytes32(type(uint256).max), + type(uint64).max, + type(uint64).max ); } } diff --git a/packages/contracts-bedrock/test/universal/Specs.t.sol b/packages/contracts-bedrock/test/universal/Specs.t.sol index 14dcbc9bc884d..4806b43c4c823 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 bbe1f38fbf720..148a3ddb7880c 100644 --- a/packages/contracts-bedrock/test/vendor/Initializable.t.sol +++ b/packages/contracts-bedrock/test/vendor/Initializable.t.sol @@ -372,27 +372,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);