diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index 9aef9f6b7c3d0..a249365efa6dd 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -1111,6 +1111,8 @@ type L1Deployments struct { OptimismMintableERC20FactoryProxy common.Address `json:"OptimismMintableERC20FactoryProxy"` OptimismPortal common.Address `json:"OptimismPortal"` OptimismPortalProxy common.Address `json:"OptimismPortalProxy"` + ETHLockbox common.Address `json:"ETHLockbox"` + ETHLockboxProxy common.Address `json:"ETHLockboxProxy"` ProxyAdmin common.Address `json:"ProxyAdmin"` SystemConfig common.Address `json:"SystemConfig"` SystemConfigProxy common.Address `json:"SystemConfigProxy"` @@ -1156,6 +1158,7 @@ func (d *L1Deployments) Check(deployConfig *DeployConfig) error { name == "DataAvailabilityChallengeProxy") { continue } + if val.Field(i).Interface().(common.Address) == (common.Address{}) { return fmt.Errorf("%s is not set", name) } diff --git a/op-chain-ops/interopgen/configs.go b/op-chain-ops/interopgen/configs.go index c3113d240e10c..a365fd6cb78ee 100644 --- a/op-chain-ops/interopgen/configs.go +++ b/op-chain-ops/interopgen/configs.go @@ -74,15 +74,16 @@ type L2Config struct { Challenger common.Address SystemConfigOwner common.Address genesis.L2InitializationConfig - Prefund map[common.Address]*big.Int - SaltMixer string - GasLimit uint64 - DisputeGameType uint32 - DisputeAbsolutePrestate common.Hash - DisputeMaxGameDepth uint64 - DisputeSplitDepth uint64 - DisputeClockExtension uint64 - DisputeMaxClockDuration uint64 + Prefund map[common.Address]*big.Int + SaltMixer string + GasLimit uint64 + DisputeGameUsesSuperRoots bool + DisputeGameType uint32 + DisputeAbsolutePrestate common.Hash + DisputeMaxGameDepth uint64 + DisputeSplitDepth uint64 + DisputeClockExtension uint64 + DisputeMaxClockDuration uint64 } func (c *L2Config) Check(log log.Logger) error { diff --git a/op-chain-ops/interopgen/deploy.go b/op-chain-ops/interopgen/deploy.go index 4ef1d32b658df..84a06245132ae 100644 --- a/op-chain-ops/interopgen/deploy.go +++ b/op-chain-ops/interopgen/deploy.go @@ -198,24 +198,25 @@ func DeployL2ToL1(l1Host *script.Host, superCfg *SuperchainConfig, superDeployme l1Host.SetTxOrigin(cfg.Deployer) output, err := opcm.DeployOPChain(l1Host, opcm.DeployOPChainInput{ - OpChainProxyAdminOwner: cfg.ProxyAdminOwner, - SystemConfigOwner: cfg.SystemConfigOwner, - Batcher: cfg.BatchSenderAddress, - UnsafeBlockSigner: cfg.P2PSequencerAddress, - Proposer: cfg.Proposer, - Challenger: cfg.Challenger, - BasefeeScalar: cfg.GasPriceOracleBaseFeeScalar, - BlobBaseFeeScalar: cfg.GasPriceOracleBlobBaseFeeScalar, - L2ChainId: new(big.Int).SetUint64(cfg.L2ChainID), - Opcm: superDeployment.Opcm, - SaltMixer: cfg.SaltMixer, - GasLimit: cfg.GasLimit, - DisputeGameType: cfg.DisputeGameType, - DisputeAbsolutePrestate: cfg.DisputeAbsolutePrestate, - DisputeMaxGameDepth: cfg.DisputeMaxGameDepth, - DisputeSplitDepth: cfg.DisputeSplitDepth, - DisputeClockExtension: cfg.DisputeClockExtension, - DisputeMaxClockDuration: cfg.DisputeMaxClockDuration, + OpChainProxyAdminOwner: cfg.ProxyAdminOwner, + SystemConfigOwner: cfg.SystemConfigOwner, + Batcher: cfg.BatchSenderAddress, + UnsafeBlockSigner: cfg.P2PSequencerAddress, + Proposer: cfg.Proposer, + Challenger: cfg.Challenger, + BasefeeScalar: cfg.GasPriceOracleBaseFeeScalar, + BlobBaseFeeScalar: cfg.GasPriceOracleBlobBaseFeeScalar, + L2ChainId: new(big.Int).SetUint64(cfg.L2ChainID), + Opcm: superDeployment.Opcm, + SaltMixer: cfg.SaltMixer, + GasLimit: cfg.GasLimit, + DisputeGameUsesSuperRoots: cfg.DisputeGameUsesSuperRoots, + DisputeGameType: cfg.DisputeGameType, + DisputeAbsolutePrestate: cfg.DisputeAbsolutePrestate, + DisputeMaxGameDepth: cfg.DisputeMaxGameDepth, + DisputeSplitDepth: cfg.DisputeSplitDepth, + DisputeClockExtension: cfg.DisputeClockExtension, + DisputeMaxClockDuration: cfg.DisputeMaxClockDuration, }) if err != nil { return nil, fmt.Errorf("failed to deploy L2 OP chain: %w", err) diff --git a/op-chain-ops/interopgen/deployments.go b/op-chain-ops/interopgen/deployments.go index 6b9e34b7a871e..48a2b93b2245b 100644 --- a/op-chain-ops/interopgen/deployments.go +++ b/op-chain-ops/interopgen/deployments.go @@ -16,6 +16,7 @@ type Implementations struct { OpcmUpgrader common.Address `json:"OPCMUpgrader"` DelayedWETHImpl common.Address `json:"DelayedWETHImpl"` OptimismPortalImpl common.Address `json:"OptimismPortalImpl"` + ETHLockboxImpl common.Address `json:"ETHLockboxImpl"` PreimageOracleSingleton common.Address `json:"PreimageOracleSingleton"` MipsSingleton common.Address `json:"MipsSingleton"` SystemConfigImpl common.Address `json:"SystemConfigImpl"` @@ -51,6 +52,7 @@ type L2OpchainDeployment struct { L1CrossDomainMessengerProxy common.Address `json:"L1CrossDomainMessengerProxy"` // Fault proof contracts below. OptimismPortalProxy common.Address `json:"OptimismPortalProxy"` + ETHLockboxProxy common.Address `json:"ETHLockboxProxy"` DisputeGameFactoryProxy common.Address `json:"DisputeGameFactoryProxy"` AnchorStateRegistryProxy common.Address `json:"AnchorStateRegistryProxy"` FaultDisputeGame common.Address `json:"FaultDisputeGame"` diff --git a/op-chain-ops/interopgen/recipe.go b/op-chain-ops/interopgen/recipe.go index 6cf3265925c4d..b835716ea588b 100644 --- a/op-chain-ops/interopgen/recipe.go +++ b/op-chain-ops/interopgen/recipe.go @@ -275,15 +275,16 @@ func (r *InteropDevL2Recipe) build(l1ChainID uint64, addrs devkeys.Addresses) (* UseAltDA: false, }, }, - Prefund: make(map[common.Address]*big.Int), - SaltMixer: "", - GasLimit: 60_000_000, - DisputeGameType: 1, // PERMISSIONED_CANNON Game Type - DisputeAbsolutePrestate: common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c"), - DisputeMaxGameDepth: 73, - DisputeSplitDepth: 30, - DisputeClockExtension: 10800, // 3 hours (input in seconds) - DisputeMaxClockDuration: 302400, // 3.5 days (input in seconds) + Prefund: make(map[common.Address]*big.Int), + SaltMixer: "", + GasLimit: 60_000_000, + DisputeGameUsesSuperRoots: true, + DisputeGameType: 1, // PERMISSIONED_CANNON Game Type + DisputeAbsolutePrestate: common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c"), + DisputeMaxGameDepth: 73, + DisputeSplitDepth: 30, + DisputeClockExtension: 10800, // 3 hours (input in seconds) + DisputeMaxClockDuration: 302400, // 3.5 days (input in seconds) } l2Users := devkeys.ChainUserKeys(new(big.Int).SetUint64(r.ChainID)) diff --git a/op-chain-ops/solc/types.go b/op-chain-ops/solc/types.go index ab6568183acf9..a7d24d208daa6 100644 --- a/op-chain-ops/solc/types.go +++ b/op-chain-ops/solc/types.go @@ -263,6 +263,8 @@ type Expression struct { ReferencedDeclaration int `json:"referencedDeclaration,omitempty"` ArgumentTypes []AstTypeDescriptions `json:"argumentTypes,omitempty"` Value interface{} `json:"value,omitempty"` + Kind string `json:"kind,omitempty"` + Expression *Expression `json:"expression,omitempty"` } type ForgeArtifact struct { diff --git a/op-deployer/pkg/deployer/bootstrap/validator.go b/op-deployer/pkg/deployer/bootstrap/validator.go index 5daaf890d56b5..b37283fe97520 100644 --- a/op-deployer/pkg/deployer/bootstrap/validator.go +++ b/op-deployer/pkg/deployer/bootstrap/validator.go @@ -47,6 +47,7 @@ type ValidatorInput struct { ProtocolVersionsImpl common.Address `json:"protocolVersionsImpl"` L1ERC721BridgeImpl common.Address `json:"l1ERC721BridgeImpl"` OptimismPortalImpl common.Address `json:"optimismPortalImpl"` + ETHLockboxImpl common.Address `json:"ethLockboxImpl" evm:"ethLockboxImpl"` SystemConfigImpl common.Address `json:"systemConfigImpl"` OptimismMintableERC20FactoryImpl common.Address `json:"optimismMintableERC20FactoryImpl"` L1CrossDomainMessengerImpl common.Address `json:"l1CrossDomainMessengerImpl"` diff --git a/op-deployer/pkg/deployer/bootstrap/validator_test.go b/op-deployer/pkg/deployer/bootstrap/validator_test.go index ff58e714867a9..e2450b599bef4 100644 --- a/op-deployer/pkg/deployer/bootstrap/validator_test.go +++ b/op-deployer/pkg/deployer/bootstrap/validator_test.go @@ -62,6 +62,7 @@ func testValidator(t *testing.T, forkRPCURL string, loc *artifacts.Locator, rele ProtocolVersionsImpl: common.Address{'2'}, L1ERC721BridgeImpl: common.Address{'3'}, OptimismPortalImpl: common.Address{'4'}, + ETHLockboxImpl: common.Address{'5'}, SystemConfigImpl: common.Address{'5'}, OptimismMintableERC20FactoryImpl: common.Address{'6'}, L1CrossDomainMessengerImpl: common.Address{'7'}, diff --git a/op-deployer/pkg/deployer/inspect/l1.go b/op-deployer/pkg/deployer/inspect/l1.go index e8bccb0238ffc..b5205c42c965e 100644 --- a/op-deployer/pkg/deployer/inspect/l1.go +++ b/op-deployer/pkg/deployer/inspect/l1.go @@ -71,6 +71,8 @@ func (l L1Contracts) AsL1Deployments() *genesis.L1Deployments { OptimismMintableERC20FactoryProxy: l.OpChainDeployment.OptimismMintableERC20FactoryProxyAddress, OptimismPortal: l.ImplementationsDeployment.OptimismPortalImplAddress, OptimismPortalProxy: l.OpChainDeployment.OptimismPortalProxyAddress, + ETHLockbox: l.ImplementationsDeployment.ETHLockboxImplAddress, + ETHLockboxProxy: l.OpChainDeployment.ETHLockboxProxyAddress, ProxyAdmin: l.OpChainDeployment.ProxyAdminAddress, SystemConfig: l.ImplementationsDeployment.SystemConfigImplAddress, SystemConfigProxy: l.OpChainDeployment.SystemConfigProxyAddress, @@ -98,6 +100,7 @@ type OpChainDeployment struct { L1StandardBridgeProxyAddress common.Address `json:"l1StandardBridgeProxyAddress"` L1CrossDomainMessengerProxyAddress common.Address `json:"l1CrossDomainMessengerProxyAddress"` OptimismPortalProxyAddress common.Address `json:"optimismPortalProxyAddress"` + ETHLockboxProxyAddress common.Address `json:"ethLockboxProxyAddress"` DisputeGameFactoryProxyAddress common.Address `json:"disputeGameFactoryProxyAddress"` AnchorStateRegistryProxyAddress common.Address `json:"anchorStateRegistryProxyAddress"` AnchorStateRegistryImplAddress common.Address `json:"anchorStateRegistryImplAddress"` @@ -113,6 +116,7 @@ type ImplementationsDeployment struct { OpcmAddress common.Address `json:"opcmAddress"` DelayedWETHImplAddress common.Address `json:"delayedWETHImplAddress"` OptimismPortalImplAddress common.Address `json:"optimismPortalImplAddress"` + ETHLockboxImplAddress common.Address `json:"ethLockboxImplAddress"` PreimageOracleSingletonAddress common.Address `json:"preimageOracleSingletonAddress"` MipsSingletonAddress common.Address `json:"mipsSingletonAddress"` SystemConfigImplAddress common.Address `json:"systemConfigImplAddress"` @@ -169,6 +173,7 @@ func L1(globalState *state.State, chainID common.Hash) (*L1Contracts, error) { L1StandardBridgeProxyAddress: chainState.L1StandardBridgeProxyAddress, L1CrossDomainMessengerProxyAddress: chainState.L1CrossDomainMessengerProxyAddress, OptimismPortalProxyAddress: chainState.OptimismPortalProxyAddress, + ETHLockboxProxyAddress: chainState.ETHLockboxProxyAddress, DisputeGameFactoryProxyAddress: chainState.DisputeGameFactoryProxyAddress, AnchorStateRegistryProxyAddress: chainState.AnchorStateRegistryProxyAddress, FaultDisputeGameAddress: chainState.FaultDisputeGameAddress, @@ -182,6 +187,7 @@ func L1(globalState *state.State, chainID common.Hash) (*L1Contracts, error) { OpcmAddress: globalState.ImplementationsDeployment.OpcmAddress, DelayedWETHImplAddress: globalState.ImplementationsDeployment.DelayedWETHImplAddress, OptimismPortalImplAddress: globalState.ImplementationsDeployment.OptimismPortalImplAddress, + ETHLockboxImplAddress: globalState.ImplementationsDeployment.ETHLockboxImplAddress, PreimageOracleSingletonAddress: globalState.ImplementationsDeployment.PreimageOracleSingletonAddress, MipsSingletonAddress: globalState.ImplementationsDeployment.MipsSingletonAddress, SystemConfigImplAddress: globalState.ImplementationsDeployment.SystemConfigImplAddress, diff --git a/op-deployer/pkg/deployer/integration_test/apply_test.go b/op-deployer/pkg/deployer/integration_test/apply_test.go index 83884e07bac73..af9ae8e57c61d 100644 --- a/op-deployer/pkg/deployer/integration_test/apply_test.go +++ b/op-deployer/pkg/deployer/integration_test/apply_test.go @@ -297,7 +297,7 @@ func TestProofParamOverrides(t *testing.T) { { "disputeGameFinalityDelaySeconds", uint64Caster, - st.ImplementationsDeployment.OptimismPortalImplAddress, + st.ImplementationsDeployment.AnchorStateRegistryImplAddress, }, { "faultGameAbsolutePrestate", @@ -462,6 +462,7 @@ func TestAdditionalDisputeGames(t *testing.T) { intent.Chains[0].AdditionalDisputeGames = []state.AdditionalDisputeGame{ { ChainProofParams: state.ChainProofParams{ + DisputeGameUsesSuperRoots: false, DisputeGameType: 255, DisputeAbsolutePrestate: standard.DisputeAbsolutePrestate, DisputeMaxGameDepth: 50, @@ -675,10 +676,11 @@ func validateSuperchainDeployment(t *testing.T, st *state.State, cg codeGetter) func validateOPChainDeployment(t *testing.T, cg codeGetter, st *state.State, intent *state.Intent, govEnabled bool) { // Validate that the implementation addresses are always set, even in subsequent deployments // that pull from an existing OPCM deployment. - implAddrs := []struct { + type addrTuple struct { name string addr common.Address - }{ + } + implAddrs := []addrTuple{ {"DelayedWETHImplAddress", st.ImplementationsDeployment.DelayedWETHImplAddress}, {"OptimismPortalImplAddress", st.ImplementationsDeployment.OptimismPortalImplAddress}, {"SystemConfigImplAddress", st.ImplementationsDeployment.SystemConfigImplAddress}, @@ -690,6 +692,11 @@ func validateOPChainDeployment(t *testing.T, cg codeGetter, st *state.State, int {"MipsSingletonAddress", st.ImplementationsDeployment.MipsSingletonAddress}, {"PreimageOracleSingletonAddress", st.ImplementationsDeployment.PreimageOracleSingletonAddress}, } + + if !intent.L1ContractsLocator.IsTag() { + implAddrs = append(implAddrs, addrTuple{"ETHLockboxImplAddress", st.ImplementationsDeployment.ETHLockboxImplAddress}) + } + for _, addr := range implAddrs { require.NotEmpty(t, addr.addr, "%s should be set", addr.name) code := cg(t, addr.addr) diff --git a/op-deployer/pkg/deployer/opcm/dispute_game_factory.go b/op-deployer/pkg/deployer/opcm/dispute_game_factory.go index 6e79f0683a945..61480523510c4 100644 --- a/op-deployer/pkg/deployer/opcm/dispute_game_factory.go +++ b/op-deployer/pkg/deployer/opcm/dispute_game_factory.go @@ -8,7 +8,6 @@ import ( type SetDisputeGameImplInput struct { Factory common.Address Impl common.Address - Portal common.Address AnchorStateRegistry common.Address GameType uint32 } diff --git a/op-deployer/pkg/deployer/opcm/implementations.go b/op-deployer/pkg/deployer/opcm/implementations.go index 36efedde9a065..917754cbd5620 100644 --- a/op-deployer/pkg/deployer/opcm/implementations.go +++ b/op-deployer/pkg/deployer/opcm/implementations.go @@ -37,6 +37,7 @@ type DeployImplementationsOutput struct { OpcmUpgrader common.Address `json:"opcmUpgraderAddress"` DelayedWETHImpl common.Address `json:"delayedWETHImplAddress"` OptimismPortalImpl common.Address `json:"optimismPortalImplAddress"` + ETHLockboxImpl common.Address `json:"ethLockboxImplAddress" evm:"ethLockboxImpl"` PreimageOracleSingleton common.Address `json:"preimageOracleSingletonAddress"` MipsSingleton common.Address `json:"mipsSingletonAddress"` SystemConfigImpl common.Address `json:"systemConfigImplAddress"` diff --git a/op-deployer/pkg/deployer/opcm/opchain.go b/op-deployer/pkg/deployer/opcm/opchain.go index a9015044b9e7b..813b7435170ad 100644 --- a/op-deployer/pkg/deployer/opcm/opchain.go +++ b/op-deployer/pkg/deployer/opcm/opchain.go @@ -33,6 +33,7 @@ type DeployOPChainInput struct { SaltMixer string GasLimit uint64 + DisputeGameUsesSuperRoots bool DisputeGameType uint32 DisputeAbsolutePrestate common.Hash DisputeMaxGameDepth uint64 @@ -63,6 +64,7 @@ type DeployOPChainOutput struct { L1CrossDomainMessengerProxy common.Address // Fault proof contracts below. OptimismPortalProxy common.Address + ETHLockboxProxy common.Address `evm:"ethLockboxProxy"` DisputeGameFactoryProxy common.Address AnchorStateRegistryProxy common.Address FaultDisputeGame common.Address @@ -92,6 +94,7 @@ type ReadImplementationAddressesInput struct { type ReadImplementationAddressesOutput struct { DelayedWETH common.Address OptimismPortal common.Address + ETHLockbox common.Address `evm:"ethLockbox"` SystemConfig common.Address L1CrossDomainMessenger common.Address L1ERC721Bridge common.Address diff --git a/op-deployer/pkg/deployer/pipeline/dispute_games.go b/op-deployer/pkg/deployer/pipeline/dispute_games.go index dd95398556cdc..5363ba7c49796 100644 --- a/op-deployer/pkg/deployer/pipeline/dispute_games.go +++ b/op-deployer/pkg/deployer/pipeline/dispute_games.go @@ -127,13 +127,12 @@ func deployDisputeGame( lgr.Info("setting dispute game impl on factory", "respected", game.MakeRespected) sdgiInput := opcm.SetDisputeGameImplInput{ - Factory: thisState.DisputeGameFactoryProxyAddress, - Impl: out.DisputeGameImpl, - GameType: game.DisputeGameType, - AnchorStateRegistry: thisState.AnchorStateRegistryProxyAddress, + Factory: thisState.DisputeGameFactoryProxyAddress, + Impl: out.DisputeGameImpl, + GameType: game.DisputeGameType, } if game.MakeRespected { - sdgiInput.Portal = thisState.OptimismPortalProxyAddress + sdgiInput.AnchorStateRegistry = thisState.AnchorStateRegistryProxyAddress } if err := opcm.SetDisputeGameImpl( env.L1ScriptHost, diff --git a/op-deployer/pkg/deployer/pipeline/implementations.go b/op-deployer/pkg/deployer/pipeline/implementations.go index ef591210b6de7..a88bec43543d8 100644 --- a/op-deployer/pkg/deployer/pipeline/implementations.go +++ b/op-deployer/pkg/deployer/pipeline/implementations.go @@ -72,6 +72,7 @@ func DeployImplementations(env *Env, intent *state.Intent, st *state.State) erro OpcmUpgraderAddress: dio.OpcmUpgrader, DelayedWETHImplAddress: dio.DelayedWETHImpl, OptimismPortalImplAddress: dio.OptimismPortalImpl, + ETHLockboxImplAddress: dio.ETHLockboxImpl, PreimageOracleSingletonAddress: dio.PreimageOracleSingleton, MipsSingletonAddress: dio.MipsSingleton, SystemConfigImplAddress: dio.SystemConfigImpl, diff --git a/op-deployer/pkg/deployer/pipeline/opchain.go b/op-deployer/pkg/deployer/pipeline/opchain.go index 03d5b375671d3..3007aaa1f47fe 100644 --- a/op-deployer/pkg/deployer/pipeline/opchain.go +++ b/op-deployer/pkg/deployer/pipeline/opchain.go @@ -58,6 +58,7 @@ func DeployOPChain(env *Env, intent *state.Intent, st *state.State, chainID comm st.ImplementationsDeployment.DelayedWETHImplAddress = impls.DelayedWETH st.ImplementationsDeployment.OptimismPortalImplAddress = impls.OptimismPortal + st.ImplementationsDeployment.ETHLockboxImplAddress = impls.ETHLockbox st.ImplementationsDeployment.SystemConfigImplAddress = impls.SystemConfig st.ImplementationsDeployment.L1CrossDomainMessengerImplAddress = impls.L1CrossDomainMessenger st.ImplementationsDeployment.L1ERC721BridgeImplAddress = impls.L1ERC721Bridge @@ -73,12 +74,13 @@ func DeployOPChain(env *Env, intent *state.Intent, st *state.State, chainID comm func makeDCI(intent *state.Intent, thisIntent *state.ChainIntent, chainID common.Hash, st *state.State) (opcm.DeployOPChainInput, error) { proofParams, err := jsonutil.MergeJSON( state.ChainProofParams{ - DisputeGameType: standard.DisputeGameType, - DisputeAbsolutePrestate: standard.DisputeAbsolutePrestate, - DisputeMaxGameDepth: standard.DisputeMaxGameDepth, - DisputeSplitDepth: standard.DisputeSplitDepth, - DisputeClockExtension: standard.DisputeClockExtension, - DisputeMaxClockDuration: standard.DisputeMaxClockDuration, + DisputeGameUsesSuperRoots: standard.DisputeGameUsesSuperRoots, + DisputeGameType: standard.DisputeGameType, + DisputeAbsolutePrestate: standard.DisputeAbsolutePrestate, + DisputeMaxGameDepth: standard.DisputeMaxGameDepth, + DisputeSplitDepth: standard.DisputeSplitDepth, + DisputeClockExtension: standard.DisputeClockExtension, + DisputeMaxClockDuration: standard.DisputeMaxClockDuration, }, intent.GlobalDeployOverrides, thisIntent.DeployOverrides, @@ -100,6 +102,7 @@ func makeDCI(intent *state.Intent, thisIntent *state.ChainIntent, chainID common Opcm: st.ImplementationsDeployment.OpcmAddress, SaltMixer: st.Create2Salt.String(), // passing through salt generated at state initialization GasLimit: standard.GasLimit, + DisputeGameUsesSuperRoots: proofParams.DisputeGameUsesSuperRoots, DisputeGameType: proofParams.DisputeGameType, DisputeAbsolutePrestate: proofParams.DisputeAbsolutePrestate, DisputeMaxGameDepth: proofParams.DisputeMaxGameDepth, @@ -123,6 +126,7 @@ func makeChainState(chainID common.Hash, dco opcm.DeployOPChainOutput) *state.Ch L1StandardBridgeProxyAddress: dco.L1StandardBridgeProxy, L1CrossDomainMessengerProxyAddress: dco.L1CrossDomainMessengerProxy, OptimismPortalProxyAddress: dco.OptimismPortalProxy, + ETHLockboxProxyAddress: dco.ETHLockboxProxy, DisputeGameFactoryProxyAddress: dco.DisputeGameFactoryProxy, AnchorStateRegistryProxyAddress: dco.AnchorStateRegistryProxy, FaultDisputeGameAddress: dco.FaultDisputeGame, diff --git a/op-deployer/pkg/deployer/standard/standard.go b/op-deployer/pkg/deployer/standard/standard.go index 1f43b33fe1137..9478f328d3971 100644 --- a/op-deployer/pkg/deployer/standard/standard.go +++ b/op-deployer/pkg/deployer/standard/standard.go @@ -26,6 +26,7 @@ const ( ProofMaturityDelaySeconds uint64 = 604800 DisputeGameFinalityDelaySeconds uint64 = 302400 MIPSVersion uint64 = 1 + DisputeGameUsesSuperRoots bool = false DisputeGameType uint32 = 1 // PERMISSIONED game type DisputeMaxGameDepth uint64 = 73 DisputeSplitDepth uint64 = 30 diff --git a/op-deployer/pkg/deployer/state/chain_intent.go b/op-deployer/pkg/deployer/state/chain_intent.go index f374466ea41ca..b53a8d0a8bc1e 100644 --- a/op-deployer/pkg/deployer/state/chain_intent.go +++ b/op-deployer/pkg/deployer/state/chain_intent.go @@ -17,6 +17,7 @@ const ( ) type ChainProofParams struct { + DisputeGameUsesSuperRoots bool `json:"disputeGameUsesSuperRoots" toml:"disputeGameUsesSuperRoots"` DisputeGameType uint32 `json:"respectedGameType" toml:"respectedGameType"` DisputeAbsolutePrestate common.Hash `json:"faultGameAbsolutePrestate" toml:"faultGameAbsolutePrestate"` DisputeMaxGameDepth uint64 `json:"faultGameMaxDepth" toml:"faultGameMaxDepth"` diff --git a/op-deployer/pkg/deployer/state/state.go b/op-deployer/pkg/deployer/state/state.go index 0ec6c923f30bc..25f99234d9e80 100644 --- a/op-deployer/pkg/deployer/state/state.go +++ b/op-deployer/pkg/deployer/state/state.go @@ -76,6 +76,7 @@ type ImplementationsDeployment struct { OpcmUpgraderAddress common.Address `json:"opcmUpgraderAddress"` DelayedWETHImplAddress common.Address `json:"delayedWETHImplAddress"` OptimismPortalImplAddress common.Address `json:"optimismPortalImplAddress"` + ETHLockboxImplAddress common.Address `json:"ethLockboxImplAddress"` PreimageOracleSingletonAddress common.Address `json:"preimageOracleSingletonAddress"` MipsSingletonAddress common.Address `json:"mipsSingletonAddress"` SystemConfigImplAddress common.Address `json:"systemConfigImplAddress"` @@ -106,6 +107,7 @@ type ChainState struct { L1StandardBridgeProxyAddress common.Address `json:"l1StandardBridgeProxyAddress"` L1CrossDomainMessengerProxyAddress common.Address `json:"l1CrossDomainMessengerProxyAddress"` OptimismPortalProxyAddress common.Address `json:"optimismPortalProxyAddress"` + ETHLockboxProxyAddress common.Address `json:"ethLockboxProxyAddress"` DisputeGameFactoryProxyAddress common.Address `json:"disputeGameFactoryProxyAddress"` AnchorStateRegistryProxyAddress common.Address `json:"anchorStateRegistryProxyAddress"` FaultDisputeGameAddress common.Address `json:"faultDisputeGameAddress"` diff --git a/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade_test.go b/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade_test.go index 95585401b1182..b025855751b11 100644 --- a/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade_test.go +++ b/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade_test.go @@ -21,6 +21,7 @@ import ( ) func TestUpgradeOPChainInput_OpChainConfigs(t *testing.T) { + t.Skip("skipping for upgrade 15") input := &UpgradeOPChainInput{ Prank: common.Address{0xaa}, Opcm: common.Address{0xbb}, @@ -54,6 +55,7 @@ func TestUpgradeOPChainInput_OpChainConfigs(t *testing.T) { } func TestUpgrader_Upgrade(t *testing.T) { + t.Skip("skipping for upgrade 15") lgr := testlog.Logger(t, slog.LevelDebug) forkedL1, stopL1, err := devnet.NewForkedSepolia(lgr) diff --git a/op-e2e/actions/helpers/user_test.go b/op-e2e/actions/helpers/user_test.go index 00b27dea1fe47..ae3a30feabf6f 100644 --- a/op-e2e/actions/helpers/user_test.go +++ b/op-e2e/actions/helpers/user_test.go @@ -67,10 +67,6 @@ func TestCrossLayerUser_Standard(t *testing.T) { testCrossLayerUser(t, config.AllocTypeStandard) } -func TestCrossLayerUser_L2OO(t *testing.T) { - testCrossLayerUser(t, config.AllocTypeL2OO) -} - // TestCrossLayerUser tests that common actions of the CrossLayerUser actor work in various hardfork configurations: // - transact on L1 // - transact on L2 @@ -308,6 +304,12 @@ func runCrossLayerUserTest(gt *testing.T, test hardforkScheduledTest) { require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "proposal failed") } + // Mine an empty block so that the timestamp is updated. Otherwise ActProveWithdrawal will fail + // because it tries to estimate gas based on the current timestamp, which is the same timestamp + // as the dispute game creation timestamp, which causes proveWithdrawalTransaction to revert. + miner.ActL1StartBlock(12)(t) + miner.ActL1EndBlock(t) + // prove our withdrawal on L1 alice.ActProveWithdrawal(t) // include proved withdrawal in new L1 block diff --git a/op-e2e/actions/proposer/l2_proposer_test.go b/op-e2e/actions/proposer/l2_proposer_test.go index 917fff2bafd5c..da5000d855004 100644 --- a/op-e2e/actions/proposer/l2_proposer_test.go +++ b/op-e2e/actions/proposer/l2_proposer_test.go @@ -28,17 +28,10 @@ func TestProposerBatchType(t *testing.T) { t.Run("SingularBatch/Standard", func(t *testing.T) { runProposerTest(t, nil, config.AllocTypeStandard) }) - t.Run("SingularBatch/L2OO", func(t *testing.T) { - runProposerTest(t, nil, config.AllocTypeL2OO) - }) t.Run("SpanBatch/Standard", func(t *testing.T) { deltaTimeOffset := hexutil.Uint64(0) runProposerTest(t, &deltaTimeOffset, config.AllocTypeStandard) }) - t.Run("SpanBatch/L2OO", func(t *testing.T) { - deltaTimeOffset := hexutil.Uint64(0) - runProposerTest(t, &deltaTimeOffset, config.AllocTypeL2OO) - }) } func runProposerTest(gt *testing.T, deltaTimeOffset *hexutil.Uint64, allocType config.AllocType) { diff --git a/op-e2e/config/init.go b/op-e2e/config/init.go index b13a60023426d..74c54093a2e0d 100644 --- a/op-e2e/config/init.go +++ b/op-e2e/config/init.go @@ -436,12 +436,13 @@ func defaultIntent(root string, loc *artifacts.Locator, deployer common.Address, { ChainProofParams: state.ChainProofParams{ // Fast game - DisputeGameType: 254, - DisputeAbsolutePrestate: defaultPrestate, - DisputeMaxGameDepth: 14 + 3 + 1, - DisputeSplitDepth: 14, - DisputeClockExtension: 0, - DisputeMaxClockDuration: 0, + DisputeGameUsesSuperRoots: false, + DisputeGameType: 254, + DisputeAbsolutePrestate: defaultPrestate, + DisputeMaxGameDepth: 14 + 3 + 1, + DisputeSplitDepth: 14, + DisputeClockExtension: 0, + DisputeMaxClockDuration: 0, }, VMType: state.VMTypeAlphabet, UseCustomOracle: true, @@ -452,23 +453,25 @@ func defaultIntent(root string, loc *artifacts.Locator, deployer common.Address, { ChainProofParams: state.ChainProofParams{ // Alphabet game - DisputeGameType: 255, - DisputeAbsolutePrestate: defaultPrestate, - DisputeMaxGameDepth: 14 + 3 + 1, - DisputeSplitDepth: 14, - DisputeClockExtension: 0, - DisputeMaxClockDuration: 1200, + DisputeGameUsesSuperRoots: false, + DisputeGameType: 255, + DisputeAbsolutePrestate: defaultPrestate, + DisputeMaxGameDepth: 14 + 3 + 1, + DisputeSplitDepth: 14, + DisputeClockExtension: 0, + DisputeMaxClockDuration: 1200, }, VMType: state.VMTypeAlphabet, }, { ChainProofParams: state.ChainProofParams{ - DisputeGameType: 0, - DisputeAbsolutePrestate: cannonPrestate(root, allocType), - DisputeMaxGameDepth: 50, - DisputeSplitDepth: 14, - DisputeClockExtension: 0, - DisputeMaxClockDuration: 1200, + DisputeGameUsesSuperRoots: false, + DisputeGameType: 0, + DisputeAbsolutePrestate: cannonPrestate(root, allocType), + DisputeMaxGameDepth: 50, + DisputeSplitDepth: 14, + DisputeClockExtension: 0, + DisputeMaxClockDuration: 1200, }, VMType: cannonVMType(allocType), }, diff --git a/op-e2e/system/bridge/validity_test.go b/op-e2e/system/bridge/validity_test.go index 4c4ceff07805f..efd5900889d18 100644 --- a/op-e2e/system/bridge/validity_test.go +++ b/op-e2e/system/bridge/validity_test.go @@ -266,10 +266,6 @@ func TestMixedDepositValidity(t *testing.T) { } } -func TestMixedWithdrawalValidity_L2OO(t *testing.T) { - testMixedWithdrawalValidity(t, config.AllocTypeL2OO) -} - func TestMixedWithdrawalValidity_Standard(t *testing.T) { testMixedWithdrawalValidity(t, config.AllocTypeStandard) } @@ -449,6 +445,12 @@ func testMixedWithdrawalValidity(t *testing.T, allocType config.AllocType) { receiptCl := ethclient.NewClient(rpcClient) blockCl := ethclient.NewClient(rpcClient) + // Mine an empty block so that the timestamp is updated. Otherwise, + // proveWithdrawalTransaction gas estimation may fail because the current timestamp is + // the same as the dispute game creation timestamp. + err = wait.ForNextBlock(ctx, l1Client) + require.NoError(t, err) + // Now create the withdrawal params, err := helpers.ProveWithdrawalParameters(context.Background(), proofCl, receiptCl, blockCl, tx.Hash(), header, l2OutputOracle, disputeGameFactory, optimismPortal2, cfg.AllocType) require.Nil(t, err) @@ -575,7 +577,20 @@ func testMixedWithdrawalValidity(t *testing.T, allocType config.AllocType) { require.NoError(t, err) } + // Do a large deposit into the OptimismPortal so there's a balance to withdraw + depositAmount := big.NewInt(1_000_000_000_000) + transactor.Account.L1Opts.Value = depositAmount + tx, err := depositContract.DepositTransaction(transactor.Account.L1Opts, fromAddr, depositAmount, 120_000, false, nil) + require.NoError(t, err) + receipt, err := wait.ForReceiptOK(ctx, l1Client, tx.Hash()) + require.NoError(t, err) + require.Equal(t, receipt.Status, types.ReceiptStatusSuccessful) + + // Increase the expected nonce + transactor.ExpectedL1Nonce++ + // Finalize withdrawal + transactor.Account.L1Opts.Value = big.NewInt(0) _, err = depositContract.FinalizeWithdrawalTransaction( transactor.Account.L1Opts, withdrawalTransaction, diff --git a/op-e2e/system/bridge/withdrawal_test.go b/op-e2e/system/bridge/withdrawal_test.go index 1f56fe4c4adec..12b5fe12e2249 100644 --- a/op-e2e/system/bridge/withdrawal_test.go +++ b/op-e2e/system/bridge/withdrawal_test.go @@ -9,10 +9,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestWithdrawals_L2OO(t *testing.T) { - testWithdrawals(t, config.AllocTypeL2OO) -} - func TestWithdrawals_Standard(t *testing.T) { testWithdrawals(t, config.AllocTypeStandard) } diff --git a/op-e2e/system/helpers/tx_helper.go b/op-e2e/system/helpers/tx_helper.go index 10c16c0e74656..fefddf8f98b6d 100644 --- a/op-e2e/system/helpers/tx_helper.go +++ b/op-e2e/system/helpers/tx_helper.go @@ -53,7 +53,9 @@ 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]) + // The last log is the deposit log + idx := len(l1Receipt.Logs) - 1 + reconstructedDep, err := derive.UnmarshalDepositLogEvent(l1Receipt.Logs[idx]) 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-e2e/system/helpers/withdrawal_helper.go b/op-e2e/system/helpers/withdrawal_helper.go index a2a57c9e3bb66..787495e4aa579 100644 --- a/op-e2e/system/helpers/withdrawal_helper.go +++ b/op-e2e/system/helpers/withdrawal_helper.go @@ -127,12 +127,19 @@ func ProveWithdrawal(t *testing.T, cfg e2esys.SystemConfig, clients ClientProvid require.NoError(t, err) } + // Wait for another block to be mined so that the timestamp increases. Otherwise, + // proveWithdrawalTransaction gas estimation may fail because the current timestamp is the same + // as the dispute game creation timestamp. + err = wait.ForNextBlock(ctx, l1Client) + require.NoError(t, err) + receiptCl := clients.NodeClient(l2NodeName) headerCl := clients.NodeClient(l2NodeName) proofCl := gethclient.New(receiptCl.Client()) ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second) defer cancel() + // Get the latest header header, err := receiptCl.HeaderByNumber(ctx, new(big.Int).SetUint64(blockNumber)) require.NoError(t, err) diff --git a/op-e2e/system/proofs/proposer_l2oo_test.go b/op-e2e/system/proofs/proposer_l2oo_test.go deleted file mode 100644 index bf718c616538b..0000000000000 --- a/op-e2e/system/proofs/proposer_l2oo_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package proofs - -import ( - "context" - "math/big" - "testing" - "time" - - op_e2e "github.com/ethereum-optimism/optimism/op-e2e" - "github.com/ethereum-optimism/optimism/op-e2e/config" - - "github.com/ethereum-optimism/optimism/op-e2e/bindings" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" - "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/stretchr/testify/require" -) - -func TestL2OutputSubmitter(t *testing.T) { - op_e2e.InitParallel(t) - - cfg := e2esys.DefaultSystemConfig(t, e2esys.WithAllocType(config.AllocTypeL2OO)) - cfg.NonFinalizedProposals = true // speed up the time till we see output proposals - - sys, err := cfg.Start(t) - require.Nil(t, err, "Error starting up system") - - l1Client := sys.NodeClient("l1") - - rollupClient := sys.RollupClient("sequencer") - - // OutputOracle is already deployed - l2OutputOracle, err := bindings.NewL2OutputOracleCaller(cfg.L1Deployments.L2OutputOracleProxy, l1Client) - require.Nil(t, err) - - initialOutputBlockNumber, err := l2OutputOracle.LatestBlockNumber(&bind.CallOpts{}) - require.Nil(t, err) - - // Wait until the second output submission from L2. The output submitter submits outputs from the - // unsafe portion of the chain which gets reorged on startup. The sequencer has an out of date view - // when it creates it's first block and uses and old L1 Origin. It then does not submit a batch - // for that block and subsequently reorgs to match what the verifier derives when running the - // reconcillation process. - l2Verif := sys.NodeClient("verifier") - _, err = geth.WaitForBlock(big.NewInt(6), l2Verif) - require.Nil(t, err) - - // Wait for batch submitter to update L2 output oracle. - timeoutCh := time.After(15 * time.Second) - ticker := time.NewTicker(1 * time.Second) - defer ticker.Stop() - for { - l2ooBlockNumber, err := l2OutputOracle.LatestBlockNumber(&bind.CallOpts{}) - require.Nil(t, err) - - // Wait for the L2 output oracle to have been changed from the initial - // timestamp set in the contract constructor. - if l2ooBlockNumber.Cmp(initialOutputBlockNumber) > 0 { - // Retrieve the l2 output committed at this updated timestamp. - committedL2Output, err := l2OutputOracle.GetL2OutputAfter(&bind.CallOpts{}, l2ooBlockNumber) - require.NotEqual(t, [32]byte{}, committedL2Output.OutputRoot, "Empty L2 Output") - require.Nil(t, err) - - // Fetch the corresponding L2 block and assert the committed L2 - // output matches the block's state root. - // - // NOTE: This assertion will change once the L2 output format is - // finalized. - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - l2Output, err := rollupClient.OutputAtBlock(ctx, l2ooBlockNumber.Uint64()) - require.Nil(t, err) - require.Equal(t, l2Output.OutputRoot[:], committedL2Output.OutputRoot[:]) - break - } - - select { - case <-timeoutCh: - t.Fatalf("State root oracle not updated") - case <-ticker.C: - } - } -} diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index 7f94d2bc88559..e256ab97e995d 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -28,7 +28,10 @@ compilation_restrictions = [ { paths = "src/dispute/SuperFaultDisputeGame.sol", optimizer_runs = 5000 }, { paths = "src/dispute/SuperPermissionedDisputeGame.sol", optimizer_runs = 5000 }, { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 5000 }, + { paths = "src/L1/OPContractsManagerInterop.sol", optimizer_runs = 5000 }, { paths = "src/L1/StandardValidator.sol", optimizer_runs = 5000 }, + { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 5000 }, + { paths = "src/L1/OptimismPortalInterop.sol", optimizer_runs = 5000 }, ] extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout'] @@ -139,8 +142,13 @@ additional_compiler_profiles = [ compilation_restrictions = [ { paths = "src/dispute/FaultDisputeGame.sol", optimizer_runs = 0 }, { paths = "src/dispute/PermissionedDisputeGame.sol", optimizer_runs = 0 }, + { paths = "src/dispute/SuperFaultDisputeGame.sol", optimizer_runs = 0 }, + { paths = "src/dispute/SuperPermissionedDisputeGame.sol", optimizer_runs = 0 }, { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 0 }, + { paths = "src/L1/OPContractsManagerInterop.sol", optimizer_runs = 0 }, { paths = "src/L1/StandardValidator.sol", optimizer_runs = 0 }, + { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 0 }, + { paths = "src/L1/OptimismPortalInterop.sol", optimizer_runs = 0 }, ] ################################################################ diff --git a/packages/contracts-bedrock/interfaces/L1/IETHLockbox.sol b/packages/contracts-bedrock/interfaces/L1/IETHLockbox.sol new file mode 100644 index 0000000000000..5d4abd72c79f5 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L1/IETHLockbox.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; + +interface IETHLockbox is IProxyAdminOwnedBase, ISemver { + error ETHLockbox_Unauthorized(); + error ETHLockbox_Paused(); + error ETHLockbox_InsufficientBalance(); + error ETHLockbox_NoWithdrawalTransactions(); + error ETHLockbox_DifferentProxyAdminOwner(); + + event Initialized(uint8 version); + event ETHLocked(address indexed portal, uint256 amount); + event ETHUnlocked(address indexed portal, uint256 amount); + event PortalAuthorized(address indexed portal); + event LockboxAuthorized(address indexed lockbox); + event LiquidityMigrated(address indexed lockbox, uint256 amount); + event LiquidityReceived(address indexed lockbox, uint256 amount); + + function initialize(ISuperchainConfig _superchainConfig, IOptimismPortal2[] calldata _portals) external; + function superchainConfig() external view returns (ISuperchainConfig); + function paused() external view returns (bool); + function authorizedPortals(address) external view returns (bool); + function authorizedLockboxes(address) external view returns (bool); + function receiveLiquidity() external payable; + function lockETH() external payable; + function unlockETH(uint256 _value) external; + function authorizePortal(IOptimismPortal2 _portal) external; + function authorizeLockbox(IETHLockbox _lockbox) external; + function migrateLiquidity(IETHLockbox _lockbox) external; + + function __constructor__() external; +} diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol index 37701010a88b1..aecb6d4604642 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol @@ -21,6 +21,7 @@ import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.s import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; interface IOPContractsManagerContractsContainer { function __constructor__( @@ -38,16 +39,20 @@ interface IOPContractsManagerGameTypeAdder { uint256 indexed l2ChainId, GameType indexed gameType, address newDisputeGame, address oldDisputeGame ); - function __constructor__( - IOPContractsManagerContractsContainer _contractsContainer - ) - external; + function __constructor__(IOPContractsManagerContractsContainer _contractsContainer) external; - function addGameType(IOPContractsManager.AddGameInput[] memory _gameConfigs, address _superchainConfig) + function addGameType( + IOPContractsManager.AddGameInput[] memory _gameConfigs, + address _superchainConfig + ) external returns (IOPContractsManager.AddGameOutput[] memory); - function updatePrestate(IOPContractsManager.OpChainConfig[] memory _prestateUpdateInputs, address _superchainConfig) external; + function updatePrestate( + IOPContractsManager.OpChainConfig[] memory _prestateUpdateInputs, + address _superchainConfig + ) + external; function contractsContainer() external view returns (IOPContractsManagerContractsContainer); } @@ -55,12 +60,13 @@ interface IOPContractsManagerGameTypeAdder { interface IOPContractsManagerDeployer { event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); - function __constructor__( - IOPContractsManagerContractsContainer _contractsContainer - ) - external; + function __constructor__(IOPContractsManagerContractsContainer _contractsContainer) external; - function deploy(IOPContractsManager.DeployInput memory _input, address _superchainConfig, address _deployer) + function deploy( + IOPContractsManager.DeployInput memory _input, + address _superchainConfig, + address _deployer + ) external returns (IOPContractsManager.DeployOutput memory); @@ -70,17 +76,13 @@ interface IOPContractsManagerDeployer { interface IOPContractsManagerUpgrader { event Upgraded(uint256 indexed l2ChainId, address indexed systemConfig, address indexed upgrader); - function __constructor__( - IOPContractsManagerContractsContainer _contractsContainer - ) - external; + function __constructor__(IOPContractsManagerContractsContainer _contractsContainer) external; function upgrade(IOPContractsManager.OpChainConfig[] memory _opChainConfigs) external; function contractsContainer() external view returns (IOPContractsManagerContractsContainer); } - interface IOPContractsManager { // -------- Structs -------- @@ -106,6 +108,7 @@ interface IOPContractsManager { string saltMixer; uint64 gasLimit; // Configurable dispute game parameters. + bool disputeGameUsesSuperRoots; GameType disputeGameType; Claim disputeAbsolutePrestate; uint256 disputeMaxGameDepth; @@ -123,6 +126,7 @@ interface IOPContractsManager { IOptimismMintableERC20Factory optimismMintableERC20FactoryProxy; IL1StandardBridge l1StandardBridgeProxy; IL1CrossDomainMessenger l1CrossDomainMessengerProxy; + IETHLockbox ethLockboxProxy; // Fault proof contracts below. IOptimismPortal2 optimismPortalProxy; IDisputeGameFactory disputeGameFactoryProxy; @@ -156,6 +160,7 @@ interface IOPContractsManager { address protocolVersionsImpl; address l1ERC721BridgeImpl; address optimismPortalImpl; + address ethLockboxImpl; address systemConfigImpl; address optimismMintableERC20FactoryImpl; address l1CrossDomainMessengerImpl; @@ -171,6 +176,7 @@ interface IOPContractsManager { ISystemConfig systemConfigProxy; IProxyAdmin proxyAdmin; Claim absolutePrestate; + bool disputeGameUsesSuperRoots; } struct AddGameInput { @@ -247,8 +253,6 @@ interface IOPContractsManager { error SuperchainProxyAdminMismatch(); - error PrestateNotSet(); - error PrestateRequired(); // -------- Methods -------- @@ -303,3 +307,27 @@ interface IOPContractsManager { function setRC(bool _isRC) external; } + +/// @notice Minimal interface only used for calling `implementations()` method but without retrieving the ETHLockbox +/// on it, since the OPCM contracts already deployed on mainnet don't have it. +/// @dev Only used for testing. +interface IOPCMImplementationsWithoutLockbox { + /// @notice The implementation contracts for the OP Stack, without the newly added ETHLockbox. + struct Implementations { + address superchainConfigImpl; + address protocolVersionsImpl; + address l1ERC721BridgeImpl; + address optimismPortalImpl; + address systemConfigImpl; + address optimismMintableERC20FactoryImpl; + address l1CrossDomainMessengerImpl; + address l1StandardBridgeImpl; + address disputeGameFactoryImpl; + address anchorStateRegistryImpl; + address delayedWETHImpl; + address mipsImpl; + } + + /// @notice Returns the implementation contracts without the ETHLockbox. + function implementations() external view returns (Implementations memory); +} diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerLegacyUpgrade.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerLegacyUpgrade.sol new file mode 100644 index 0000000000000..057139005a96a --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerLegacyUpgrade.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { Claim } from "src/dispute/lib/Types.sol"; + +/// @title IOPContractsManagerLegacyUpgrade +/// @notice Interface for the legacy OPContractsManager upgrade function. +/// This interface is used to test Upgrade 13 and 14 paths and can be safely removed +/// after those upgrades are completed. Only difference in the new struct is the added +/// disputeGameUsesSuperRoots boolean. +interface IOPContractsManagerLegacyUpgrade { + struct OpChainConfig { + ISystemConfig systemConfigProxy; + IProxyAdmin proxyAdmin; + Claim absolutePrestate; + } + + function upgrade(OpChainConfig[] memory _opChainConfigs) external; +} diff --git a/packages/contracts-bedrock/interfaces/L1/IOPPrestateUpdater.sol b/packages/contracts-bedrock/interfaces/L1/IOPPrestateUpdater.sol deleted file mode 100644 index 7456fed5231d5..0000000000000 --- a/packages/contracts-bedrock/interfaces/L1/IOPPrestateUpdater.sol +++ /dev/null @@ -1,142 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Libraries -import { GameType } from "src/dispute/lib/Types.sol"; - -// Interfaces -import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; -import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; -import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; -import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; - -interface IOPPrestateUpdater { - // -------- Constants and Variables -------- - - function version() external pure returns (string memory); - - /// @notice Address of the SuperchainConfig contract shared by all chains. - function superchainConfig() external view returns (ISuperchainConfig); - - /// @notice Address of the ProtocolVersions contract shared by all chains. - function protocolVersions() external view returns (IProtocolVersions); - - /// @notice Address of the ProxyAdmin contract shared by all chains. - function superchainProxyAdmin() external view returns (IProxyAdmin); - - /// @notice L1 smart contracts release deployed by this version of OPCM. This is used in opcm to signal which - /// version of the L1 smart contracts is deployed. It takes the format of `op-contracts/vX.Y.Z`. - function l1ContractsRelease() external view returns (string memory); - - // -------- Events -------- - - /// @notice Emitted when a new OP Stack chain is deployed. - /// @param l2ChainId Chain ID of the new chain. - /// @param deployer Address that deployed the chain. - /// @param deployOutput ABI-encoded output of the deployment. - event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); - - /// @notice Emitted when a chain is upgraded - /// @param systemConfig Address of the chain's SystemConfig contract - /// @param upgrader Address that initiated the upgrade - event Upgraded(uint256 indexed l2ChainId, ISystemConfig indexed systemConfig, address indexed upgrader); - - /// @notice Emitted when a new game type is added to a chain - /// @param l2ChainId Chain ID of the chain - /// @param gameType Type of the game being added - /// @param newDisputeGame Address of the deployed dispute game - /// @param oldDisputeGame Address of the old dispute game - event GameTypeAdded(uint256 indexed l2ChainId, GameType indexed gameType, IDisputeGame newDisputeGame, IDisputeGame oldDisputeGame); - - // -------- Errors -------- - - error BytesArrayTooLong(); - error DeploymentFailed(); - error EmptyInitcode(); - error IdentityPrecompileCallFailed(); - error NotABlueprint(); - error ReservedBitsSet(); - error UnexpectedPreambleData(bytes data); - error UnsupportedERCVersion(uint8 version); - error OnlyUpgradeController(); - error PrestateNotSet(); - - /// @notice Thrown when an address is the zero address. - error AddressNotFound(address who); - - /// @notice Throw when a contract address has no code. - error AddressHasNoCode(address who); - - /// @notice Thrown when a release version is already set. - error AlreadyReleased(); - - /// @notice Thrown when an invalid `l2ChainId` is provided to `deploy`. - error InvalidChainId(); - - /// @notice Thrown when a role's address is not valid. - error InvalidRoleAddress(string role); - - /// @notice Thrown when the latest release is not set upon initialization. - error LatestReleaseNotSet(); - - /// @notice Thrown when the starting anchor root is not provided. - error InvalidStartingAnchorRoot(); - - /// @notice Thrown when certain methods are called outside of a DELEGATECALL. - error OnlyDelegatecall(); - - /// @notice Thrown when game configs passed to addGameType are invalid. - error InvalidGameConfigs(); - - /// @notice Thrown when the SuperchainConfig of the chain does not match the SuperchainConfig of this OPCM. - error SuperchainConfigMismatch(ISystemConfig systemConfig); - - error SuperchainProxyAdminMismatch(); - - /// @notice Thrown when a function from the parent (OPCM) is not implemented. - error NotImplemented(); - - /// @notice Thrown when the prestate of a permissioned disputed game is 0. - error PrestateRequired(); - - // -------- Methods -------- - - function __constructor__( - ISuperchainConfig _superchainConfig, - IProtocolVersions _protocolVersions, - IOPContractsManager.Blueprints memory _blueprints - ) - external; - - function deploy(IOPContractsManager.DeployInput calldata _input) external returns (IOPContractsManager.DeployOutput memory); - - /// @notice Upgrades the implementation of all proxies in the specified chains - /// @param _opChainConfigs The chains to upgrade - function upgrade(IOPContractsManager.OpChainConfig[] memory _opChainConfigs) external; - - /// @notice addGameType deploys a new dispute game and links it to the DisputeGameFactory. The inputted _gameConfigs - /// must be added in ascending GameType order. - function addGameType(IOPContractsManager.AddGameInput[] memory _gameConfigs) external returns (IOPContractsManager.AddGameOutput[] memory); - - /// @notice Maps an L2 chain ID to an L1 batch inbox address as defined by the standard - /// configuration's convention. This convention is `versionByte || keccak256(bytes32(chainId))[:19]`, - /// where || denotes concatenation`, versionByte is 0x00, and chainId is a uint256. - /// https://specs.optimism.io/protocol/configurability.html#consensus-parameters - function chainIdToBatchInboxAddress(uint256 _l2ChainId) external pure returns (address); - - /// @notice Returns the blueprint contract addresses. - function blueprints() external view returns (IOPContractsManager.Blueprints memory); - - /// @notice Returns the implementation contract addresses. - function implementations() external view returns (IOPContractsManager.Implementations memory); - - function upgradeController() external view returns (address); - - function isRC() external view returns (bool); - - function setRC(bool _isRC) external; - - function updatePrestate(IOPContractsManager.OpChainConfig[] memory _prestateUpdateInputs) external; -} diff --git a/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol b/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol index bbf12c3fce4dc..f9cfc5325436c 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol @@ -2,48 +2,59 @@ pragma solidity ^0.8.0; import { Types } from "src/libraries/Types.sol"; -import { GameType, Timestamp } from "src/dispute/lib/LibUDT.sol"; +import { GameType } 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"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; -interface IOptimismPortal2 { - error AlreadyFinalized(); - error BadTarget(); - error Blacklisted(); - error CallPaused(); +interface IOptimismPortal2 is IProxyAdminOwnedBase { + error OptimismPortal_Unauthorized(); error ContentLengthMismatch(); error EmptyItem(); - error GasEstimation(); error InvalidDataRemainder(); - error InvalidDisputeGame(); - error InvalidGameType(); error InvalidHeader(); - error InvalidMerkleProof(); - error InvalidProof(); - error LargeCalldata(); - error NonReentrant(); + error ReinitializableBase_ZeroInitVersion(); + error OptimismPortal_AlreadyFinalized(); + error OptimismPortal_BadTarget(); + error OptimismPortal_CallPaused(); + error OptimismPortal_CalldataTooLarge(); + error OptimismPortal_GasEstimation(); + error OptimismPortal_GasLimitTooLow(); + error OptimismPortal_ImproperDisputeGame(); + error OptimismPortal_InvalidDisputeGame(); + error OptimismPortal_InvalidMerkleProof(); + error OptimismPortal_InvalidOutputRootProof(); + error OptimismPortal_InvalidProofTimestamp(); + error OptimismPortal_InvalidRootClaim(); + error OptimismPortal_NoReentrancy(); + error OptimismPortal_ProofNotOldEnough(); + error OptimismPortal_Unproven(); + error OptimismPortal_InvalidOutputRootIndex(); + error OptimismPortal_InvalidSuperRootProof(); + error OptimismPortal_InvalidOutputRootChainId(); + error OptimismPortal_WrongProofMethod(); + error Encoding_EmptySuperRoot(); + error Encoding_InvalidSuperRootVersion(); error OutOfGas(); - error ProposalNotValidated(); - error SmallGasLimit(); - error Unauthorized(); error UnexpectedList(); error UnexpectedString(); - error Unproven(); - error LegacyGame(); - 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 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); + event ETHMigrated(address indexed lockbox, uint256 ethBalance); + event LockboxUpdated(address oldLockbox, address newLockbox); receive() external payable; - function blacklistDisputeGame(IDisputeGame _disputeGame) external; + function anchorStateRegistry() external view returns (IAnchorStateRegistry); + function ethLockbox() external view returns (IETHLockbox); function checkWithdrawal(bytes32 _withdrawalHash, address _proofSubmitter) external view; function depositTransaction( address _to, @@ -54,10 +65,10 @@ interface IOptimismPortal2 { ) 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 updateLockbox(IETHLockbox _newLockbox) external; function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external; function finalizeWithdrawalTransactionExternalProof( Types.WithdrawalTransaction memory _tx, @@ -67,12 +78,14 @@ interface IOptimismPortal2 { function finalizedWithdrawals(bytes32) external view returns (bool); function guardian() external view returns (address); function initialize( - IDisputeGameFactory _disputeGameFactory, ISystemConfig _systemConfig, ISuperchainConfig _superchainConfig, - GameType _initialRespectedGameType + IAnchorStateRegistry _anchorStateRegistry, + IETHLockbox _ethLockbox, + bool _superRootsActive ) external; + function initVersion() external view returns (uint8); function l2Sender() external view returns (address); function minimumGasLimit(uint64 _byteCount) external pure returns (uint64); function numProofSubmitters(bytes32 _withdrawalHash) external view returns (uint256); @@ -87,19 +100,35 @@ interface IOptimismPortal2 { bytes[] memory _withdrawalProof ) external; + function proveWithdrawalTransaction( + Types.WithdrawalTransaction memory _tx, + IDisputeGame _disputeGameProxy, + uint256 _outputRootIndex, + Types.SuperRootProof memory _superRootProof, + Types.OutputRootProof memory _outputRootProof, + bytes[] memory _withdrawalProof + ) + external; function provenWithdrawals( bytes32, address ) external view - returns (IDisputeGame disputeGameProxy, uint64 timestamp); // nosemgrep + returns (IDisputeGame disputeGameProxy, uint64 timestamp); function respectedGameType() external view returns (GameType); function respectedGameTypeUpdatedAt() external view returns (uint64); - function setRespectedGameType(GameType _gameType) external; function superchainConfig() external view returns (ISuperchainConfig); + function superRootsActive() external view returns (bool); function systemConfig() external view returns (ISystemConfig); + function upgrade( + IAnchorStateRegistry _anchorStateRegistry, + IETHLockbox _ethLockbox, + bool _superRootsActive + ) + external; function version() external pure returns (string memory); + function migrateLiquidity() external; - function __constructor__(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds) external; + function __constructor__(uint256 _proofMaturityDelaySeconds) external; } diff --git a/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol b/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol index 6dec13a8335c9..7cd674a0c1835 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol @@ -2,49 +2,60 @@ pragma solidity ^0.8.0; import { Types } from "src/libraries/Types.sol"; -import { GameType, Timestamp } from "src/dispute/lib/LibUDT.sol"; +import { GameType } 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"; import { ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; +import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; -interface IOptimismPortalInterop { - error AlreadyFinalized(); - error BadTarget(); - error Blacklisted(); - error CallPaused(); +interface IOptimismPortalInterop is IProxyAdminOwnedBase { error ContentLengthMismatch(); error EmptyItem(); - error GasEstimation(); error InvalidDataRemainder(); - error InvalidDisputeGame(); - error InvalidGameType(); error InvalidHeader(); - error InvalidMerkleProof(); - error InvalidProof(); - error LargeCalldata(); - error NonReentrant(); + error ReinitializableBase_ZeroInitVersion(); + error OptimismPortal_AlreadyFinalized(); + error OptimismPortal_BadTarget(); + error OptimismPortal_CallPaused(); + error OptimismPortal_CalldataTooLarge(); + error OptimismPortal_GasEstimation(); + error OptimismPortal_GasLimitTooLow(); + error OptimismPortal_ImproperDisputeGame(); + error OptimismPortal_InvalidDisputeGame(); + error OptimismPortal_InvalidMerkleProof(); + error OptimismPortal_InvalidOutputRootProof(); + error OptimismPortal_InvalidProofTimestamp(); + error OptimismPortal_InvalidRootClaim(); + error OptimismPortal_NoReentrancy(); + error OptimismPortal_ProofNotOldEnough(); + error OptimismPortal_Unauthorized(); + error OptimismPortal_Unproven(); + error OptimismPortal_InvalidOutputRootIndex(); + error OptimismPortal_InvalidSuperRootProof(); + error OptimismPortal_InvalidOutputRootChainId(); + error OptimismPortal_WrongProofMethod(); + error Encoding_EmptySuperRoot(); + error Encoding_InvalidSuperRootVersion(); error OutOfGas(); - error ProposalNotValidated(); - error SmallGasLimit(); - error Unauthorized(); error UnexpectedList(); error UnexpectedString(); - error Unproven(); - error LegacyGame(); - 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 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); + event ETHMigrated(address indexed lockbox, uint256 ethBalance); + event LockboxUpdated(address oldLockbox, address newLockbox); receive() external payable; - function blacklistDisputeGame(IDisputeGame _disputeGame) external; + function anchorStateRegistry() external view returns (IAnchorStateRegistry); + function ethLockbox() external view returns (IETHLockbox); function checkWithdrawal(bytes32 _withdrawalHash, address _proofSubmitter) external view; function depositTransaction( address _to, @@ -55,25 +66,28 @@ interface IOptimismPortalInterop { ) 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 updateLockbox(IETHLockbox _newLockbox) external; function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external; function finalizeWithdrawalTransactionExternalProof( Types.WithdrawalTransaction memory _tx, address _proofSubmitter ) external; + function migrateLiquidity() external; function finalizedWithdrawals(bytes32) external view returns (bool); function guardian() external view returns (address); function initialize( - IDisputeGameFactory _disputeGameFactory, ISystemConfig _systemConfig, ISuperchainConfig _superchainConfig, - GameType _initialRespectedGameType + IAnchorStateRegistry _anchorStateRegistry, + IETHLockbox _ethLockbox, + bool _superRootsActive ) external; + function initVersion() external view returns (uint8); function l2Sender() external view returns (address); function minimumGasLimit(uint64 _byteCount) external pure returns (uint64); function numProofSubmitters(bytes32 _withdrawalHash) external view returns (uint256); @@ -88,6 +102,15 @@ interface IOptimismPortalInterop { bytes[] memory _withdrawalProof ) external; + function proveWithdrawalTransaction( + Types.WithdrawalTransaction memory _tx, + IDisputeGame _disputeGameProxy, + uint256 _outputRootIndex, + Types.SuperRootProof memory _superRootProof, + Types.OutputRootProof memory _outputRootProof, + bytes[] memory _withdrawalProof + ) + external; function provenWithdrawals( bytes32, address @@ -98,10 +121,16 @@ interface IOptimismPortalInterop { function respectedGameType() external view returns (GameType); function respectedGameTypeUpdatedAt() external view returns (uint64); function setConfig(ConfigType _type, bytes memory _value) external; - function setRespectedGameType(GameType _gameType) external; function superchainConfig() external view returns (ISuperchainConfig); + function superRootsActive() external view returns (bool); function systemConfig() external view returns (ISystemConfig); + function upgrade( + IAnchorStateRegistry _anchorStateRegistry, + IETHLockbox _ethLockbox, + bool _superRootsActive + ) + external; function version() external pure returns (string memory); - function __constructor__(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds) external; + function __constructor__(uint256 _proofMaturityDelaySeconds) external; } diff --git a/packages/contracts-bedrock/interfaces/L1/IProxyAdminOwnedBase.sol b/packages/contracts-bedrock/interfaces/L1/IProxyAdminOwnedBase.sol new file mode 100644 index 0000000000000..73346bff11294 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L1/IProxyAdminOwnedBase.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IProxyAdminOwnedBase { + function proxyAdminOwner() external view returns (address); +} diff --git a/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol b/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol index e5677516fbf54..151c47808015e 100644 --- a/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol +++ b/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol @@ -22,6 +22,8 @@ interface ISystemConfig { address optimismMintableERC20Factory; } + error ReinitializableBase_ZeroInitVersion(); + event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); event Initialized(uint8 version); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); @@ -54,12 +56,15 @@ interface ISystemConfig { address _unsafeBlockSigner, IResourceMetering.ResourceConfig memory _config, address _batchInbox, - Addresses memory _addresses + Addresses memory _addresses, + uint256 _l2ChainId ) external; + function initVersion() external view returns (uint8); function l1CrossDomainMessenger() external view returns (address addr_); function l1ERC721Bridge() external view returns (address addr_); function l1StandardBridge() external view returns (address addr_); + function l2ChainId() external view returns (uint256); function maximumGasLimit() external pure returns (uint64); function minimumGasLimit() external view returns (uint64); function operatorFeeConstant() external view returns (uint64); @@ -81,6 +86,7 @@ interface ISystemConfig { function startBlock() external view returns (uint256 startBlock_); function transferOwnership(address newOwner) external; // nosemgrep function unsafeBlockSigner() external view returns (address addr_); + function upgrade(uint256 _l2ChainId) external; function version() external pure returns (string memory); function __constructor__() external; diff --git a/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol b/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol index 19cac2b41543a..6842e0f701392 100644 --- a/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol +++ b/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol @@ -5,6 +5,8 @@ import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; interface ISystemConfigInterop { + error ReinitializableBase_ZeroInitVersion(); + event ConfigUpdate(uint256 indexed version, ISystemConfig.UpdateType indexed updateType, bytes data); event Initialized(uint8 version); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); @@ -30,6 +32,7 @@ interface ISystemConfigInterop { function l1CrossDomainMessenger() external view returns (address addr_); function l1ERC721Bridge() external view returns (address addr_); function l1StandardBridge() external view returns (address addr_); + function l2ChainId() external view returns (uint256); function maximumGasLimit() external pure returns (uint64); function minimumGasLimit() external view returns (uint64); function operatorFeeConstant() external view returns (uint64); @@ -65,9 +68,11 @@ interface ISystemConfigInterop { IResourceMetering.ResourceConfig memory _config, address _batchInbox, ISystemConfig.Addresses memory _addresses, - address _dependencyManager + address _dependencyManager, + uint256 _l2ChainId ) external; + function initVersion() external view returns (uint8); function version() external pure returns (string memory); function __constructor__() external; diff --git a/packages/contracts-bedrock/interfaces/dispute/IAnchorStateRegistry.sol b/packages/contracts-bedrock/interfaces/dispute/IAnchorStateRegistry.sol index a6f7d3a45c430..d6c96648e63d1 100644 --- a/packages/contracts-bedrock/interfaces/dispute/IAnchorStateRegistry.sol +++ b/packages/contracts-bedrock/interfaces/dispute/IAnchorStateRegistry.sol @@ -5,30 +5,33 @@ import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { GameType, Hash, OutputRoot } from "src/dispute/lib/Types.sol"; interface IAnchorStateRegistry { - error AnchorStateRegistry_Unauthorized(); - error AnchorStateRegistry_InvalidAnchorGame(); error AnchorStateRegistry_AnchorGameBlacklisted(); + error AnchorStateRegistry_InvalidAnchorGame(); + error AnchorStateRegistry_Unauthorized(); - event AnchorNotUpdated(IFaultDisputeGame indexed game); event AnchorUpdated(IFaultDisputeGame indexed game); + event DisputeGameBlacklisted(IDisputeGame indexed disputeGame); event Initialized(uint8 version); + event RespectedGameTypeSet(GameType gameType); + event RetirementTimestampSet(uint256 timestamp); function anchorGame() external view returns (IFaultDisputeGame); function anchors(GameType) external view returns (Hash, uint256); + function blacklistDisputeGame(IDisputeGame _disputeGame) external; + function disputeGameBlacklist(IDisputeGame) external view returns (bool); function getAnchorRoot() external view returns (Hash, uint256); + function disputeGameFinalityDelaySeconds() external view returns (uint256); function disputeGameFactory() external view returns (IDisputeGameFactory); function initialize( ISuperchainConfig _superchainConfig, IDisputeGameFactory _disputeGameFactory, - IOptimismPortal2 _portal, - OutputRoot memory _startingAnchorRoot + OutputRoot memory _startingAnchorRoot, + GameType _startingRespectedGameType ) external; - function isGameBlacklisted(IDisputeGame _game) external view returns (bool); function isGameProper(IDisputeGame _game) external view returns (bool); function isGameRegistered(IDisputeGame _game) external view returns (bool); @@ -37,11 +40,16 @@ interface IAnchorStateRegistry { function isGameRetired(IDisputeGame _game) external view returns (bool); function isGameFinalized(IDisputeGame _game) external view returns (bool); function isGameClaimValid(IDisputeGame _game) external view returns (bool); - function portal() external view returns (IOptimismPortal2); + function paused() external view returns (bool); function respectedGameType() external view returns (GameType); + function retirementTimestamp() external view returns (uint64); function setAnchorState(IDisputeGame _game) external; + function setRespectedGameType(GameType _gameType) external; function superchainConfig() external view returns (ISuperchainConfig); + function updateRetirementTimestamp() external; function version() external view returns (string memory); - function __constructor__() external; + function __constructor__( + uint256 _disputeGameFinalityDelaySeconds + ) external; } diff --git a/packages/contracts-bedrock/interfaces/safe/IDeputyGuardianModule.sol b/packages/contracts-bedrock/interfaces/safe/IDeputyGuardianModule.sol index a5c0e33130279..d4d76649a50c7 100644 --- a/packages/contracts-bedrock/interfaces/safe/IDeputyGuardianModule.sol +++ b/packages/contracts-bedrock/interfaces/safe/IDeputyGuardianModule.sol @@ -2,22 +2,21 @@ pragma solidity ^0.8.0; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; -import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; import { GameType, Timestamp } from "src/dispute/lib/Types.sol"; import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; interface IDeputyGuardianModule is ISemver { - error ExecutionFailed(string); - error Unauthorized(); + error DeputyGuardianModule_ExecutionFailed(string); + error DeputyGuardianModule_Unauthorized(); event Paused(string identifier); event Unpaused(); event DisputeGameBlacklisted(IDisputeGame indexed game); event RespectedGameTypeSet(GameType indexed gameType, Timestamp indexed updatedAt); + event RetirementTimestampUpdated(Timestamp indexed updatedAt); function version() external view returns (string memory); function __constructor__(Safe _safe, ISuperchainConfig _superchainConfig, address _deputyGuardian) external; @@ -26,7 +25,7 @@ interface IDeputyGuardianModule is ISemver { function deputyGuardian() external view returns (address deputyGuardian_); function pause() external; function unpause() external; - function setAnchorState(IAnchorStateRegistry _registry, IFaultDisputeGame _game) external; - function blacklistDisputeGame(IOptimismPortal2 _portal, IDisputeGame _game) external; - function setRespectedGameType(IOptimismPortal2 _portal, GameType _gameType) external; + function blacklistDisputeGame(IAnchorStateRegistry _anchorStateRegistry, IDisputeGame _game) external; + function setRespectedGameType(IAnchorStateRegistry _anchorStateRegistry, GameType _gameType) external; + function updateRetirementTimestamp(IAnchorStateRegistry _anchorStateRegistry) external; } diff --git a/packages/contracts-bedrock/interfaces/universal/IReinitializableBase.sol b/packages/contracts-bedrock/interfaces/universal/IReinitializableBase.sol new file mode 100644 index 0000000000000..d6d096a743bbe --- /dev/null +++ b/packages/contracts-bedrock/interfaces/universal/IReinitializableBase.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IReinitializableBase { + error ReinitializableBase_ZeroInitVersion(); + + function initVersion() external view returns (uint8); + + // ReinitializerBase is abstract, so it has no constructor in its interface. + function __constructor__() external; +} diff --git a/packages/contracts-bedrock/justfile b/packages/contracts-bedrock/justfile index 09c0f1f8a23b5..0cde991fcc278 100644 --- a/packages/contracts-bedrock/justfile +++ b/packages/contracts-bedrock/justfile @@ -27,6 +27,10 @@ forge-build-dev *ARGS: build-source: forge build --skip "/**/test/**" --skip "/**/scripts/**" +# Builds source contracts and scripts, skipping tests. +build-no-tests: + forge build --skip "/**/test/**" + # Builds the contracts. build *ARGS: lint-fix-no-fail just forge-build {{ARGS}} @@ -65,7 +69,7 @@ test-dev *ARGS: build-go-ffi # Default block number for the forked upgrade path. export sepoliaBlockNumber := "7701807" -export mainnetBlockNumber := "21971446" +export mainnetBlockNumber := "21983965" export pinnedBlockNumber := if env_var_or_default("FORK_BASE_CHAIN", "") == "mainnet" { mainnetBlockNumber @@ -303,6 +307,7 @@ check: semver-diff-check-no-build \ validate-deploy-configs \ validate-spacers-no-build \ + reinitializer-check-no-build \ interfaces-check-no-build \ lint-forge-tests-check-no-build diff --git a/packages/contracts-bedrock/lib/automate b/packages/contracts-bedrock/lib/automate new file mode 160000 index 0000000000000..0117585fea20f --- /dev/null +++ b/packages/contracts-bedrock/lib/automate @@ -0,0 +1 @@ +Subproject commit 0117585fea20ff0cd24fd17bf74a6debaa4d57d2 diff --git a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh index c2a9e6688afbd..435cd301a35ce 100755 --- a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh +++ b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh @@ -49,6 +49,7 @@ changed_contracts=$(jq -r ' # All files in semver-lock.json should be in this list. ALLOWED_FILES=( "src/L1/DataAvailabilityChallenge.sol:DataAvailabilityChallenge" + "src/L1/ETHLockbox.sol:ETHLockbox" "src/L1/L1CrossDomainMessenger.sol:L1CrossDomainMessenger" "src/L1/L1ERC721Bridge.sol:L1ERC721Bridge" "src/L1/L1StandardBridge.sol:L1StandardBridge" diff --git a/packages/contracts-bedrock/scripts/checks/interfaces/main.go b/packages/contracts-bedrock/scripts/checks/interfaces/main.go index 211730d3adc1f..0ecc45e19034b 100644 --- a/packages/contracts-bedrock/scripts/checks/interfaces/main.go +++ b/packages/contracts-bedrock/scripts/checks/interfaces/main.go @@ -26,6 +26,9 @@ var excludeContracts = []string{ // EAS "IEAS", "ISchemaResolver", "ISchemaRegistry", + // Misc stuff that can be ignored + "IOPContractsManagerLegacyUpgrade", + // TODO: Interfaces that need to be fixed "IInitializable", "IOptimismMintableERC20", "ILegacyMintableERC20", "KontrolCheatsBase", "ISystemConfigInterop", "IResolvedDelegateProxy", diff --git a/packages/contracts-bedrock/scripts/checks/reinitializer/main.go b/packages/contracts-bedrock/scripts/checks/reinitializer/main.go index c41e403b98562..e44c955d038bd 100644 --- a/packages/contracts-bedrock/scripts/checks/reinitializer/main.go +++ b/packages/contracts-bedrock/scripts/checks/reinitializer/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "math" "os" "strconv" "strings" @@ -103,15 +104,23 @@ func getReinitializerValue(node *solc.AstNode) (uint64, error) { for _, modifier := range node.Modifiers { if modifier.ModifierName.Name == "reinitializer" { - valStr, ok := modifier.Arguments[0].Value.(string) - if !ok { - return 0, fmt.Errorf("reinitializer value is not a string") + if modifier.Arguments[0].Kind == "functionCall" { + if modifier.Arguments[0].Expression.Name == "initVersion" { + return math.MaxUint64, nil // uint64 max representing initVersion call + } else { + return 0, fmt.Errorf("reinitializer value is not a call to initVersion") + } + } else { + valStr, ok := modifier.Arguments[0].Value.(string) + if !ok { + return 0, fmt.Errorf("reinitializer value is not a string") + } + val, err := strconv.Atoi(valStr) + if err != nil { + return 0, fmt.Errorf("reinitializer value is not an integer") + } + return uint64(val), nil } - val, err := strconv.Atoi(valStr) - if err != nil { - return 0, fmt.Errorf("reinitializer value is not an integer") - } - return uint64(val), nil } } diff --git a/packages/contracts-bedrock/scripts/checks/reinitializer/main_test.go b/packages/contracts-bedrock/scripts/checks/reinitializer/main_test.go index 93ca35173b63c..0f9fc382e2def 100644 --- a/packages/contracts-bedrock/scripts/checks/reinitializer/main_test.go +++ b/packages/contracts-bedrock/scripts/checks/reinitializer/main_test.go @@ -1,6 +1,7 @@ package main import ( + "math" "testing" "github.com/ethereum-optimism/optimism/op-chain-ops/solc" @@ -155,6 +156,38 @@ func TestGetReinitializerValue(t *testing.T) { want: 0, wantErr: true, }, + { + name: "Valid reinitializer with initVersion call", + node: &solc.AstNode{ + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{ + Kind: "functionCall", + Expression: &solc.Expression{Name: "initVersion"}, + }}, + }, + }, + }, + want: math.MaxUint64, + wantErr: false, + }, + { + name: "Invalid function call - not initVersion", + node: &solc.AstNode{ + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{ + Kind: "functionCall", + Expression: &solc.Expression{Name: "someOtherFunction"}, + }}, + }, + }, + }, + want: 0, + wantErr: true, + }, } for _, tt := range tests { @@ -484,6 +517,85 @@ func TestCheckArtifact(t *testing.T) { }, wantErr: false, // Should return nil without error }, + { + name: "Matching reinitializer values with initVersion", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "ContractDefinition", + Nodes: []solc.AstNode{ + { + NodeType: "FunctionDefinition", + Name: "initialize", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{ + Kind: "functionCall", + Expression: &solc.Expression{Name: "initVersion"}, + }}, + }, + }, + }, + { + NodeType: "FunctionDefinition", + Name: "upgrade", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{ + Kind: "functionCall", + Expression: &solc.Expression{Name: "initVersion"}, + }}, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "Mismatched reinitializer values - one with initVersion, one with constant", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "ContractDefinition", + Nodes: []solc.AstNode{ + { + NodeType: "FunctionDefinition", + Name: "initialize", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{ + Kind: "functionCall", + Expression: &solc.Expression{Name: "initVersion"}, + }}, + }, + }, + }, + { + NodeType: "FunctionDefinition", + Name: "upgrade", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "2"}}, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: true, + }, } for _, tt := range tests { diff --git a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol index 026cdb178f96f..a8457150ec48b 100644 --- a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol +++ b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol @@ -22,7 +22,7 @@ import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { ProtocolVersion, IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; @@ -32,6 +32,7 @@ import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMin import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; import { IMIPS } from "interfaces/cannon/IMIPS.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; library ChainAssertions { Vm internal constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); @@ -367,7 +368,7 @@ library ChainAssertions { internal view { - IOptimismPortal2 portal = IOptimismPortal2(payable(_contracts.OptimismPortal)); + IOptimismPortal portal = IOptimismPortal(payable(_contracts.OptimismPortal)); console.log( "Running chain assertions on the OptimismPortal2 %s at %s", _isProxy ? "proxy" : "implementation", @@ -385,20 +386,49 @@ library ChainAssertions { if (_isProxy) { require(address(portal.disputeGameFactory()) == _contracts.DisputeGameFactory, "CHECK-OP2-20"); + require(address(portal.anchorStateRegistry()) == _contracts.AnchorStateRegistry, "CHECK-OP2-25"); require(address(portal.systemConfig()) == _contracts.SystemConfig, "CHECK-OP2-30"); require(portal.guardian() == guardian, "CHECK-OP2-40"); require(address(portal.superchainConfig()) == address(_contracts.SuperchainConfig), "CHECK-OP2-50"); require(portal.paused() == ISuperchainConfig(_contracts.SuperchainConfig).paused(), "CHECK-OP2-60"); require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER, "CHECK-OP2-70"); + require(address(portal.ethLockbox()) == _contracts.ETHLockbox, "CHECK-OP2-80"); } else { - require(address(portal.disputeGameFactory()) == address(0), "CHECK-OP2-80"); + require(address(portal.anchorStateRegistry()) == address(0), "CHECK-OP2-80"); require(address(portal.systemConfig()) == address(0), "CHECK-OP2-90"); require(address(portal.superchainConfig()) == address(0), "CHECK-OP2-100"); require(portal.l2Sender() == address(0), "CHECK-OP2-110"); + require(address(portal.ethLockbox()) == address(0), "CHECK-OP2-120"); } // This slot is the custom gas token _balance and this check ensures // that it stays unset for forwards compatibility with custom gas token. - require(vm.load(address(portal), bytes32(uint256(61))) == bytes32(0), "CHECK-OP2-120"); + require(vm.load(address(portal), bytes32(uint256(61))) == bytes32(0), "CHECK-OP2-130"); + } + + /// @notice Asserts that the ETHLockbox is setup correctly + function checkETHLockbox(Types.ContractSet memory _contracts, DeployConfig _cfg, bool _isProxy) internal view { + IETHLockbox ethLockbox = IETHLockbox(_contracts.ETHLockbox); + ISuperchainConfig superchainConfig = ISuperchainConfig(_contracts.SuperchainConfig); + + console.log( + "Running chain assertions on the ETHLockbox %s at %s", + _isProxy ? "proxy" : "implementation", + address(ethLockbox) + ); + + require(address(ethLockbox) != address(0), "CHECK-ELB-10"); + + // Check that the contract is initialized + DeployUtils.assertInitialized({ _contractAddress: address(ethLockbox), _isProxy: _isProxy, _slot: 0, _offset: 0 }); + + if (_isProxy) { + require(ethLockbox.superchainConfig() == superchainConfig, "CHECK-ELB-20"); + require(ethLockbox.authorizedPortals(_contracts.OptimismPortal), "CHECK-ELB-30"); + require(ethLockbox.proxyAdminOwner() == _cfg.finalSystemOwner(), "CHECK-ELB-40"); + } else { + require(address(ethLockbox.superchainConfig()) == address(0), "CHECK-ELB-50"); + require(ethLockbox.authorizedPortals(_contracts.OptimismPortal) == false, "CHECK-ELB-60"); + } } /// @notice Asserts that the ProtocolVersions is setup correctly diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index f4638d208011d..1a353f2f063f4 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -35,7 +35,6 @@ import { GameType, Claim, GameTypes, OutputRoot, Hash } from "src/dispute/lib/Ty import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; import { IProxy } from "interfaces/universal/IProxy.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IDataAvailabilityChallenge } from "interfaces/L1/IDataAvailabilityChallenge.sol"; @@ -119,6 +118,7 @@ contract Deploy is Deployer { AnchorStateRegistry: artifacts.getAddress("AnchorStateRegistryProxy"), OptimismMintableERC20Factory: artifacts.getAddress("OptimismMintableERC20FactoryProxy"), OptimismPortal: artifacts.getAddress("OptimismPortalProxy"), + ETHLockbox: artifacts.getAddress("ETHLockboxProxy"), SystemConfig: artifacts.getAddress("SystemConfigProxy"), L1ERC721Bridge: artifacts.getAddress("L1ERC721BridgeProxy"), ProtocolVersions: artifacts.getAddress("ProtocolVersionsProxy"), @@ -138,6 +138,7 @@ contract Deploy is Deployer { AnchorStateRegistry: artifacts.getAddress("AnchorStateRegistryImpl"), OptimismMintableERC20Factory: artifacts.getAddress("OptimismMintableERC20FactoryImpl"), OptimismPortal: artifacts.getAddress("OptimismPortal2Impl"), + ETHLockbox: artifacts.getAddress("ETHLockboxImpl"), SystemConfig: artifacts.getAddress("SystemConfigImpl"), L1ERC721Bridge: artifacts.getAddress("L1ERC721BridgeImpl"), ProtocolVersions: artifacts.getAddress("ProtocolVersionsImpl"), @@ -206,7 +207,7 @@ contract Deploy is Deployer { // Set the respected game type according to the deploy config vm.startPrank(ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")).guardian()); - IOptimismPortal2(artifacts.mustGetAddress("OptimismPortalProxy")).setRespectedGameType( + IAnchorStateRegistry(artifacts.mustGetAddress("AnchorStateRegistryProxy")).setRespectedGameType( GameType.wrap(uint32(cfg.respectedGameType())) ); vm.stopPrank(); @@ -318,6 +319,7 @@ contract Deploy is Deployer { AnchorStateRegistry: address(0), OptimismMintableERC20Factory: address(dio.optimismMintableERC20FactoryImpl()), OptimismPortal: address(dio.optimismPortalImpl()), + ETHLockbox: address(dio.ethLockboxImpl()), SystemConfig: address(dio.systemConfigImpl()), L1ERC721Bridge: address(dio.l1ERC721BridgeImpl()), ProtocolVersions: address(dio.protocolVersionsImpl()), @@ -328,6 +330,7 @@ contract Deploy is Deployer { ChainAssertions.checkL1StandardBridge({ _contracts: impls, _isProxy: false }); ChainAssertions.checkL1ERC721Bridge({ _contracts: impls, _isProxy: false }); ChainAssertions.checkOptimismPortal2({ _contracts: impls, _cfg: cfg, _isProxy: false }); + ChainAssertions.checkETHLockbox({ _contracts: impls, _cfg: cfg, _isProxy: false }); ChainAssertions.checkOptimismMintableERC20Factory({ _contracts: impls, _isProxy: false }); ChainAssertions.checkDisputeGameFactory({ _contracts: impls, _expectedOwner: address(0), _isProxy: false }); ChainAssertions.checkDelayedWETH({ _contracts: impls, _cfg: cfg, _isProxy: false, _expectedOwner: address(0) }); @@ -372,6 +375,7 @@ contract Deploy is Deployer { artifacts.save("OptimismMintableERC20FactoryProxy", address(deployOutput.optimismMintableERC20FactoryProxy)); artifacts.save("L1StandardBridgeProxy", address(deployOutput.l1StandardBridgeProxy)); artifacts.save("L1CrossDomainMessengerProxy", address(deployOutput.l1CrossDomainMessengerProxy)); + artifacts.save("ETHLockboxProxy", address(deployOutput.ethLockboxProxy)); // Fault Proof contracts artifacts.save("DisputeGameFactoryProxy", address(deployOutput.disputeGameFactoryProxy)); @@ -380,7 +384,6 @@ contract Deploy is Deployer { artifacts.save("PermissionedDisputeGame", address(deployOutput.permissionedDisputeGame)); artifacts.save("OptimismPortalProxy", address(deployOutput.optimismPortalProxy)); artifacts.save("OptimismPortal2Proxy", address(deployOutput.optimismPortalProxy)); - // Check if the permissionless game implementation is already set IDisputeGameFactory factory = IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")); address permissionlessGameImpl = address(factory.gameImpls(GameTypes.CANNON)); @@ -524,7 +527,8 @@ contract Deploy is Deployer { disputeGameFactory: artifacts.mustGetAddress("DisputeGameFactoryProxy"), optimismPortal: artifacts.mustGetAddress("OptimismPortalProxy"), optimismMintableERC20Factory: artifacts.mustGetAddress("OptimismMintableERC20FactoryProxy") - }) + }), + cfg.l2ChainID() ) ) }); @@ -965,6 +969,7 @@ contract Deploy is Deployer { ), saltMixer: saltMixer, gasLimit: uint64(cfg.l2GenesisBlockGasLimit()), + disputeGameUsesSuperRoots: false, disputeGameType: GameTypes.PERMISSIONED_CANNON, disputeAbsolutePrestate: Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate())), disputeMaxGameDepth: cfg.faultGameMaxDepth(), diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index 5cd0414734547..776803cb6df1f 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -24,7 +24,8 @@ import { IOPContractsManagerUpgrader, IOPContractsManagerContractsContainer } from "interfaces/L1/IOPContractsManager.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; @@ -158,7 +159,8 @@ contract DeployImplementationsOutput is BaseDeployIO { IOPContractsManagerDeployer internal _opcmDeployer; IOPContractsManagerUpgrader internal _opcmUpgrader; IDelayedWETH internal _delayedWETHImpl; - IOptimismPortal2 internal _optimismPortalImpl; + IOptimismPortal internal _optimismPortalImpl; + IETHLockbox internal _ethLockboxImpl; IPreimageOracle internal _preimageOracleSingleton; IMIPS internal _mipsSingleton; ISystemConfig internal _systemConfigImpl; @@ -182,7 +184,8 @@ contract DeployImplementationsOutput is BaseDeployIO { else if (_sel == this.opcmUpgrader.selector) _opcmUpgrader = IOPContractsManagerUpgrader(_addr); else if (_sel == this.superchainConfigImpl.selector) _superchainConfigImpl = ISuperchainConfig(_addr); else if (_sel == this.protocolVersionsImpl.selector) _protocolVersionsImpl = IProtocolVersions(_addr); - else if (_sel == this.optimismPortalImpl.selector) _optimismPortalImpl = IOptimismPortal2(payable(_addr)); + else if (_sel == this.optimismPortalImpl.selector) _optimismPortalImpl = IOptimismPortal(payable(_addr)); + else if (_sel == this.ethLockboxImpl.selector) _ethLockboxImpl = IETHLockbox(payable(_addr)); else if (_sel == this.delayedWETHImpl.selector) _delayedWETHImpl = IDelayedWETH(payable(_addr)); else if (_sel == this.preimageOracleSingleton.selector) _preimageOracleSingleton = IPreimageOracle(_addr); else if (_sel == this.mipsSingleton.selector) _mipsSingleton = IMIPS(_addr); @@ -217,7 +220,8 @@ contract DeployImplementationsOutput is BaseDeployIO { address(this.l1StandardBridgeImpl()), address(this.optimismMintableERC20FactoryImpl()), address(this.disputeGameFactoryImpl()), - address(this.anchorStateRegistryImpl()) + address(this.anchorStateRegistryImpl()), + address(this.ethLockboxImpl()) ); DeployUtils.assertValidContractAddresses(Solarray.extend(addrs1, addrs2)); @@ -260,11 +264,16 @@ contract DeployImplementationsOutput is BaseDeployIO { return _protocolVersionsImpl; } - function optimismPortalImpl() public view returns (IOptimismPortal2) { + function optimismPortalImpl() public view returns (IOptimismPortal) { DeployUtils.assertValidContractAddress(address(_optimismPortalImpl)); return _optimismPortalImpl; } + function ethLockboxImpl() public view returns (IETHLockbox) { + DeployUtils.assertValidContractAddress(address(_ethLockboxImpl)); + return _ethLockboxImpl; + } + function delayedWETHImpl() public view returns (IDelayedWETH) { DeployUtils.assertValidContractAddress(address(_delayedWETHImpl)); return _delayedWETHImpl; @@ -327,6 +336,7 @@ contract DeployImplementationsOutput is BaseDeployIO { assertValidOpcm(_dii); assertValidOptimismMintableERC20FactoryImpl(_dii); assertValidOptimismPortalImpl(_dii); + assertValidETHLockboxImpl(_dii); assertValidPreimageOracleSingleton(_dii); assertValidSystemConfigImpl(_dii); } @@ -339,11 +349,11 @@ contract DeployImplementationsOutput is BaseDeployIO { } function assertValidOptimismPortalImpl(DeployImplementationsInput) internal view { - IOptimismPortal2 portal = optimismPortalImpl(); + IOptimismPortal portal = optimismPortalImpl(); DeployUtils.assertInitialized({ _contractAddress: address(portal), _isProxy: false, _slot: 0, _offset: 0 }); - require(address(portal.disputeGameFactory()) == address(0), "PORTAL-10"); + require(address(portal.anchorStateRegistry()) == address(0), "PORTAL-10"); require(address(portal.systemConfig()) == address(0), "PORTAL-20"); require(address(portal.superchainConfig()) == address(0), "PORTAL-30"); require(portal.l2Sender() == address(0), "PORTAL-40"); @@ -351,6 +361,17 @@ contract DeployImplementationsOutput is BaseDeployIO { // This slot is the custom gas token _balance and this check ensures // that it stays unset for forwards compatibility with custom gas token. require(vm.load(address(portal), bytes32(uint256(61))) == bytes32(0), "PORTAL-50"); + + require(address(portal.ethLockbox()) == address(0), "PORTAL-60"); + } + + function assertValidETHLockboxImpl(DeployImplementationsInput) internal view { + IETHLockbox lockbox = ethLockboxImpl(); + + DeployUtils.assertInitialized({ _contractAddress: address(lockbox), _isProxy: false, _slot: 0, _offset: 0 }); + + require(address(lockbox.superchainConfig()) == address(0), "ELB-10"); + require(lockbox.authorizedPortals(address(optimismPortalImpl())) == false, "ELB-20"); } function assertValidDelayedWETHImpl(DeployImplementationsInput _dii) internal view { @@ -485,11 +506,12 @@ contract DeployImplementations is Script { deployL1StandardBridgeImpl(_dio); deployOptimismMintableERC20FactoryImpl(_dio); deployOptimismPortalImpl(_dii, _dio); + deployETHLockboxImpl(_dio); deployDelayedWETHImpl(_dii, _dio); deployPreimageOracleSingleton(_dii, _dio); deployMipsSingleton(_dii, _dio); deployDisputeGameFactoryImpl(_dio); - deployAnchorStateRegistryImpl(_dio); + deployAnchorStateRegistryImpl(_dii, _dio); // Deploy the OP Contracts Manager with the new implementations set. deployOPContractsManager(_dii, _dio); @@ -516,6 +538,7 @@ contract DeployImplementations is Script { protocolVersionsImpl: address(_dio.protocolVersionsImpl()), l1ERC721BridgeImpl: address(_dio.l1ERC721BridgeImpl()), optimismPortalImpl: address(_dio.optimismPortalImpl()), + ethLockboxImpl: address(_dio.ethLockboxImpl()), systemConfigImpl: address(_dio.systemConfigImpl()), optimismMintableERC20FactoryImpl: address(_dio.optimismMintableERC20FactoryImpl()), l1CrossDomainMessengerImpl: address(_dio.l1CrossDomainMessengerImpl()), @@ -680,6 +703,18 @@ contract DeployImplementations is Script { _dio.set(_dio.optimismMintableERC20FactoryImpl.selector, address(impl)); } + function deployETHLockboxImpl(DeployImplementationsOutput _dio) public virtual { + IETHLockbox impl = IETHLockbox( + DeployUtils.createDeterministic({ + _name: "ETHLockbox", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IETHLockbox.__constructor__, ())), + _salt: _salt + }) + ); + vm.label(address(impl), "ETHLockboxImpl"); + _dio.set(_dio.ethLockboxImpl.selector, address(impl)); + } + // --- Fault Proofs Contracts --- // The fault proofs contracts are configured as follows: @@ -725,14 +760,11 @@ contract DeployImplementations is Script { virtual { uint256 proofMaturityDelaySeconds = _dii.proofMaturityDelaySeconds(); - uint256 disputeGameFinalityDelaySeconds = _dii.disputeGameFinalityDelaySeconds(); - IOptimismPortal2 impl = IOptimismPortal2( + IOptimismPortal impl = IOptimismPortal( DeployUtils.createDeterministic({ _name: "OptimismPortal2", _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IOptimismPortal2.__constructor__, (proofMaturityDelaySeconds, disputeGameFinalityDelaySeconds) - ) + abi.encodeCall(IOptimismPortal.__constructor__, (proofMaturityDelaySeconds)) ), _salt: _salt }) @@ -810,11 +842,20 @@ contract DeployImplementations is Script { _dio.set(_dio.disputeGameFactoryImpl.selector, address(impl)); } - function deployAnchorStateRegistryImpl(DeployImplementationsOutput _dio) public virtual { + function deployAnchorStateRegistryImpl( + DeployImplementationsInput _dii, + DeployImplementationsOutput _dio + ) + public + virtual + { + uint256 disputeGameFinalityDelaySeconds = _dii.disputeGameFinalityDelaySeconds(); IAnchorStateRegistry impl = IAnchorStateRegistry( DeployUtils.createDeterministic({ _name: "AnchorStateRegistry", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IAnchorStateRegistry.__constructor__, ())), + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IAnchorStateRegistry.__constructor__, (disputeGameFinalityDelaySeconds)) + ), _salt: _salt }) ); @@ -963,19 +1004,15 @@ contract DeployImplementationsInterop is DeployImplementations { override { uint256 proofMaturityDelaySeconds = _dii.proofMaturityDelaySeconds(); - uint256 disputeGameFinalityDelaySeconds = _dii.disputeGameFinalityDelaySeconds(); IOptimismPortalInterop impl = IOptimismPortalInterop( DeployUtils.createDeterministic({ _name: "OptimismPortalInterop", _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IOptimismPortalInterop.__constructor__, (proofMaturityDelaySeconds, disputeGameFinalityDelaySeconds) - ) + abi.encodeCall(IOptimismPortalInterop.__constructor__, (proofMaturityDelaySeconds)) ), _salt: _salt }) ); - vm.label(address(impl), "OptimismPortalImpl"); _dio.set(_dio.optimismPortalImpl.selector, address(impl)); } diff --git a/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol index 9541db0bbab5a..2978582420a2d 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol @@ -27,12 +27,13 @@ import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; import { Claim, Duration, GameType, GameTypes, Hash } from "src/dispute/lib/Types.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; contract DeployOPChainInput is BaseDeployIO { address internal _opChainProxyAdminOwner; @@ -51,6 +52,7 @@ contract DeployOPChainInput is BaseDeployIO { uint64 internal _gasLimit; // Configurable dispute game inputs + bool internal _disputeGameUsesSuperRoots; GameType internal _disputeGameType; Claim internal _disputeAbsolutePrestate; uint256 internal _disputeMaxGameDepth; @@ -98,6 +100,9 @@ contract DeployOPChainInput is BaseDeployIO { _operatorFeeScalar = SafeCast.toUint32(_value); } else if (_sel == this.operatorFeeConstant.selector) { _operatorFeeConstant = SafeCast.toUint64(_value); + } else if (_sel == this.disputeGameUsesSuperRoots.selector) { + require(_value == 0 || _value == 1, "DeployOPChainInput: invalid disputeGameUsesSuperRoots"); + _disputeGameUsesSuperRoots = _value == 1; } else { revert("DeployOPChainInput: unknown selector"); } @@ -194,6 +199,10 @@ contract DeployOPChainInput is BaseDeployIO { return _gasLimit; } + function disputeGameUsesSuperRoots() public view returns (bool) { + return _disputeGameUsesSuperRoots; + } + function disputeGameType() public view returns (GameType) { return _disputeGameType; } @@ -239,7 +248,8 @@ contract DeployOPChainOutput is BaseDeployIO { IOptimismMintableERC20Factory internal _optimismMintableERC20FactoryProxy; IL1StandardBridge internal _l1StandardBridgeProxy; IL1CrossDomainMessenger internal _l1CrossDomainMessengerProxy; - IOptimismPortal2 internal _optimismPortalProxy; + IOptimismPortal internal _optimismPortalProxy; + IETHLockbox internal _ethLockboxProxy; IDisputeGameFactory internal _disputeGameFactoryProxy; IAnchorStateRegistry internal _anchorStateRegistryProxy; IFaultDisputeGame internal _faultDisputeGame; @@ -257,7 +267,8 @@ contract DeployOPChainOutput is BaseDeployIO { else if (_sel == this.optimismMintableERC20FactoryProxy.selector) _optimismMintableERC20FactoryProxy = IOptimismMintableERC20Factory(_addr) ; else if (_sel == this.l1StandardBridgeProxy.selector) _l1StandardBridgeProxy = IL1StandardBridge(payable(_addr)) ; else if (_sel == this.l1CrossDomainMessengerProxy.selector) _l1CrossDomainMessengerProxy = IL1CrossDomainMessenger(_addr) ; - else if (_sel == this.optimismPortalProxy.selector) _optimismPortalProxy = IOptimismPortal2(payable(_addr)) ; + else if (_sel == this.optimismPortalProxy.selector) _optimismPortalProxy = IOptimismPortal(payable(_addr)) ; + else if (_sel == this.ethLockboxProxy.selector) _ethLockboxProxy = IETHLockbox(payable(_addr)) ; else if (_sel == this.disputeGameFactoryProxy.selector) _disputeGameFactoryProxy = IDisputeGameFactory(_addr) ; else if (_sel == this.anchorStateRegistryProxy.selector) _anchorStateRegistryProxy = IAnchorStateRegistry(_addr) ; else if (_sel == this.faultDisputeGame.selector) _faultDisputeGame = IFaultDisputeGame(_addr) ; @@ -308,12 +319,18 @@ contract DeployOPChainOutput is BaseDeployIO { return _l1CrossDomainMessengerProxy; } - function optimismPortalProxy() public returns (IOptimismPortal2) { + function optimismPortalProxy() public returns (IOptimismPortal) { DeployUtils.assertValidContractAddress(address(_optimismPortalProxy)); DeployUtils.assertERC1967ImplementationSet(address(_optimismPortalProxy)); return _optimismPortalProxy; } + function ethLockboxProxy() public returns (IETHLockbox) { + DeployUtils.assertValidContractAddress(address(_ethLockboxProxy)); + DeployUtils.assertERC1967ImplementationSet(address(_ethLockboxProxy)); + return _ethLockboxProxy; + } + function disputeGameFactoryProxy() public returns (IDisputeGameFactory) { DeployUtils.assertValidContractAddress(address(_disputeGameFactoryProxy)); DeployUtils.assertERC1967ImplementationSet(address(_disputeGameFactoryProxy)); @@ -371,6 +388,7 @@ contract DeployOPChain is Script { startingAnchorRoot: _doi.startingAnchorRoot(), saltMixer: _doi.saltMixer(), gasLimit: _doi.gasLimit(), + disputeGameUsesSuperRoots: _doi.disputeGameUsesSuperRoots(), disputeGameType: _doi.disputeGameType(), disputeAbsolutePrestate: _doi.disputeAbsolutePrestate(), disputeMaxGameDepth: _doi.disputeMaxGameDepth(), @@ -390,6 +408,7 @@ contract DeployOPChain is Script { vm.label(address(deployOutput.l1StandardBridgeProxy), "l1StandardBridgeProxy"); vm.label(address(deployOutput.l1CrossDomainMessengerProxy), "l1CrossDomainMessengerProxy"); vm.label(address(deployOutput.optimismPortalProxy), "optimismPortalProxy"); + vm.label(address(deployOutput.ethLockboxProxy), "ethLockboxProxy"); vm.label(address(deployOutput.disputeGameFactoryProxy), "disputeGameFactoryProxy"); vm.label(address(deployOutput.anchorStateRegistryProxy), "anchorStateRegistryProxy"); // vm.label(address(deployOutput.faultDisputeGame), "faultDisputeGame"); @@ -408,6 +427,7 @@ contract DeployOPChain is Script { _doo.set(_doo.l1StandardBridgeProxy.selector, address(deployOutput.l1StandardBridgeProxy)); _doo.set(_doo.l1CrossDomainMessengerProxy.selector, address(deployOutput.l1CrossDomainMessengerProxy)); _doo.set(_doo.optimismPortalProxy.selector, address(deployOutput.optimismPortalProxy)); + _doo.set(_doo.ethLockboxProxy.selector, address(deployOutput.ethLockboxProxy)); _doo.set(_doo.disputeGameFactoryProxy.selector, address(deployOutput.disputeGameFactoryProxy)); _doo.set(_doo.anchorStateRegistryProxy.selector, address(deployOutput.anchorStateRegistryProxy)); // _doo.set(_doo.faultDisputeGame.selector, address(deployOutput.faultDisputeGame)); @@ -440,7 +460,8 @@ contract DeployOPChain is Script { address(_doo.anchorStateRegistryProxy()), address(_doo.permissionedDisputeGame()), // address(_doo.faultDisputeGame()), - address(_doo.delayedWETHPermissionedGameProxy()) + address(_doo.delayedWETHPermissionedGameProxy()), + address(_doo.ethLockboxProxy()) ); // TODO: Eventually switch from Permissioned to Permissionless. Add this address back in. // address(_delayedWETHPermissionlessGameProxy) @@ -459,6 +480,7 @@ contract DeployOPChain is Script { assertValidL1StandardBridge(_doi, _doo); assertValidOptimismMintableERC20Factory(_doi, _doo); assertValidOptimismPortal(_doi, _doo); + assertValidETHLockbox(_doi, _doo); assertValidPermissionedDisputeGame(_doi, _doo); assertValidSystemConfig(_doi, _doo); assertValidAddressManager(_doi, _doo); @@ -609,19 +631,32 @@ contract DeployOPChain is Script { } function assertValidOptimismPortal(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { - IOptimismPortal2 portal = _doo.optimismPortalProxy(); + IOptimismPortal portal = _doo.optimismPortalProxy(); ISuperchainConfig superchainConfig = ISuperchainConfig(address(_doi.opcm().superchainConfig())); - require(address(portal.disputeGameFactory()) == address(_doo.disputeGameFactoryProxy()), "PORTAL-10"); - require(address(portal.systemConfig()) == address(_doo.systemConfigProxy()), "PORTAL-20"); - require(address(portal.superchainConfig()) == address(superchainConfig), "PORTAL-30"); - require(portal.guardian() == superchainConfig.guardian(), "PORTAL-40"); - require(portal.paused() == superchainConfig.paused(), "PORTAL-50"); - require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER, "PORTAL-60"); + require(address(portal.anchorStateRegistry()) == address(_doo.anchorStateRegistryProxy()), "PORTAL-10"); + require(address(portal.disputeGameFactory()) == address(_doo.disputeGameFactoryProxy()), "PORTAL-20"); + require(address(portal.systemConfig()) == address(_doo.systemConfigProxy()), "PORTAL-30"); + require(address(portal.superchainConfig()) == address(superchainConfig), "PORTAL-40"); + require(portal.guardian() == superchainConfig.guardian(), "PORTAL-50"); + require(portal.paused() == superchainConfig.paused(), "PORTAL-60"); + require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER, "PORTAL-70"); // This slot is the custom gas token _balance and this check ensures // that it stays unset for forwards compatibility with custom gas token. - require(vm.load(address(portal), bytes32(uint256(61))) == bytes32(0), "PORTAL-70"); + require(vm.load(address(portal), bytes32(uint256(61))) == bytes32(0), "PORTAL-80"); + + // Check once the portal is updated to use the new lockbox. + require(address(portal.ethLockbox()) == address(_doo.ethLockboxProxy()), "PORTAL-90"); + require(portal.proxyAdminOwner() == _doi.opChainProxyAdminOwner(), "PORTAL-100"); + } + + function assertValidETHLockbox(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { + IETHLockbox lockbox = _doo.ethLockboxProxy(); + + require(address(lockbox.superchainConfig()) == address(_doi.opcm().superchainConfig()), "ETHLOCKBOX-10"); + require(lockbox.authorizedPortals(address(_doo.optimismPortalProxy())), "ETHLOCKBOX-20"); + require(lockbox.proxyAdminOwner() == _doi.opChainProxyAdminOwner(), "ETHLOCKBOX-30"); } function assertValidDisputeGameFactory(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { @@ -702,6 +737,11 @@ contract DeployOPChain is Script { == DeployUtils.assertERC1967ImplementationSet(address(_doo.anchorStateRegistryProxy())), "OPCPA-110" ); + require( + admin.getProxyImplementation(address(_doo.ethLockboxProxy())) + == DeployUtils.assertERC1967ImplementationSet(address(_doo.ethLockboxProxy())), + "OPCPA-120" + ); } // -------- Utilities -------- diff --git a/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol b/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol index 211281b506d37..e7b7f76edfd7c 100644 --- a/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol @@ -30,6 +30,7 @@ contract ReadImplementationAddressesInput is DeployOPChainOutput { contract ReadImplementationAddressesOutput is BaseDeployIO { address internal _delayedWETH; address internal _optimismPortal; + address internal _ethLockbox; address internal _systemConfig; address internal _l1CrossDomainMessenger; address internal _l1ERC721Bridge; @@ -43,6 +44,7 @@ contract ReadImplementationAddressesOutput is BaseDeployIO { require(_addr != address(0), "ReadImplementationAddressesOutput: cannot set zero address"); if (_sel == this.delayedWETH.selector) _delayedWETH = _addr; else if (_sel == this.optimismPortal.selector) _optimismPortal = _addr; + else if (_sel == this.ethLockbox.selector) _ethLockbox = _addr; else if (_sel == this.systemConfig.selector) _systemConfig = _addr; else if (_sel == this.l1CrossDomainMessenger.selector) _l1CrossDomainMessenger = _addr; else if (_sel == this.l1ERC721Bridge.selector) _l1ERC721Bridge = _addr; @@ -64,6 +66,11 @@ contract ReadImplementationAddressesOutput is BaseDeployIO { return _optimismPortal; } + function ethLockbox() public view returns (address) { + require(_ethLockbox != address(0), "ReadImplementationAddressesOutput: ethLockbox not set"); + return _ethLockbox; + } + function systemConfig() public view returns (address) { require(_systemConfig != address(0), "ReadImplementationAddressesOutput: systemConfig not set"); return _systemConfig; @@ -154,5 +161,8 @@ contract ReadImplementationAddresses is Script { address preimageOracle = address(IMIPS(mipsLogic).oracle()); _rio.set(_rio.preimageOracleSingleton.selector, preimageOracle); + + address ethLockbox = _rii.opcm().implementations().ethLockboxImpl; + _rio.set(_rio.ethLockbox.selector, ethLockbox); } } diff --git a/packages/contracts-bedrock/scripts/deploy/SetDisputeGameImpl.s.sol b/packages/contracts-bedrock/scripts/deploy/SetDisputeGameImpl.s.sol index 17e3d5a99dce2..77d4880f02aa5 100644 --- a/packages/contracts-bedrock/scripts/deploy/SetDisputeGameImpl.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/SetDisputeGameImpl.s.sol @@ -7,11 +7,11 @@ import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; import { GameType } from "src/dispute/lib/Types.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; contract SetDisputeGameImplInput is BaseDeployIO { IDisputeGameFactory internal _factory; - IOptimismPortal2 internal _portal; + IAnchorStateRegistry internal _anchorStateRegistry; IFaultDisputeGame internal _impl; uint32 internal _gameType; @@ -20,7 +20,7 @@ contract SetDisputeGameImplInput is BaseDeployIO { require(_addr != address(0), "SetDisputeGameImplInput: cannot set zero address"); if (_sel == this.factory.selector) _factory = IDisputeGameFactory(_addr); - else if (_sel == this.portal.selector) _portal = IOptimismPortal2(payable(_addr)); + else if (_sel == this.anchorStateRegistry.selector) _anchorStateRegistry = IAnchorStateRegistry(_addr); else if (_sel == this.impl.selector) _impl = IFaultDisputeGame(_addr); else revert("SetDisputeGameImplInput: unknown selector"); } @@ -36,8 +36,8 @@ contract SetDisputeGameImplInput is BaseDeployIO { return _factory; } - function portal() public view returns (IOptimismPortal2) { - return _portal; + function anchorStateRegistry() public view returns (IAnchorStateRegistry) { + return _anchorStateRegistry; } function impl() public view returns (IFaultDisputeGame) { @@ -57,15 +57,15 @@ contract SetDisputeGameImpl is Script { require(address(factory.gameImpls(gameType)) == address(0), "SDGI-10"); IFaultDisputeGame impl = _input.impl(); - IOptimismPortal2 portal = _input.portal(); + IAnchorStateRegistry anchorStateRegistry = _input.anchorStateRegistry(); vm.broadcast(msg.sender); factory.setImplementation(gameType, impl); - if (address(portal) != address(0)) { - require(address(portal.disputeGameFactory()) == address(factory), "SDGI-20"); + if (address(anchorStateRegistry) != address(0)) { + require(address(anchorStateRegistry.disputeGameFactory()) == address(factory), "SDGI-20"); vm.broadcast(msg.sender); - portal.setRespectedGameType(gameType); + anchorStateRegistry.setRespectedGameType(gameType); } assertValid(_input); @@ -75,9 +75,12 @@ contract SetDisputeGameImpl is Script { GameType gameType = GameType.wrap(_input.gameType()); require(address(_input.factory().gameImpls(gameType)) == address(_input.impl()), "SDGI-30"); - if (address(_input.portal()) != address(0)) { - require(address(_input.portal().disputeGameFactory()) == address(_input.factory()), "SDGI-40"); - require(GameType.unwrap(_input.portal().respectedGameType()) == GameType.unwrap(gameType), "SDGI-50"); + if (address(_input.anchorStateRegistry()) != address(0)) { + require(address(_input.anchorStateRegistry().disputeGameFactory()) == address(_input.factory()), "SDGI-40"); + require( + GameType.unwrap(_input.anchorStateRegistry().respectedGameType()) == GameType.unwrap(gameType), + "SDGI-50" + ); } } } diff --git a/packages/contracts-bedrock/scripts/go-ffi/differential-testing.go b/packages/contracts-bedrock/scripts/go-ffi/differential-testing.go index e35d2a82c3036..7c328d23f2686 100644 --- a/packages/contracts-bedrock/scripts/go-ffi/differential-testing.go +++ b/packages/contracts-bedrock/scripts/go-ffi/differential-testing.go @@ -38,14 +38,15 @@ var ( {Type: fixedBytes}, } - uint32Type, _ = abi.NewType("uint32", "", nil) - // Plain address type addressType, _ = abi.NewType("address", "", nil) // Plain uint8 type uint8Type, _ = abi.NewType("uint8", "", nil) + // Plain uint32 type + uint32Type, _ = abi.NewType("uint32", "", nil) + // Plain uint256 type uint256Type, _ = abi.NewType("uint256", "", nil) @@ -102,6 +103,19 @@ var ( {Name: "symbol", Type: fixedBytes}, } + // Super root proof tuple (uint8, uint64, OutputRootWithChainId[]) + superRootProof, _ = abi.NewType("tuple", "SuperRootProof", []abi.ArgumentMarshaling{ + {Name: "version", Type: "bytes1"}, + {Name: "timestamp", Type: "uint64"}, + {Name: "outputRoots", Type: "tuple[]", Components: []abi.ArgumentMarshaling{ + {Name: "chainId", Type: "uint256"}, + {Name: "root", Type: "bytes32"}, + }}, + }) + superRootProofArgs = abi.Arguments{ + {Type: superRootProof}, + } + // Dependency tuple (uint256) dependencyArgs = abi.Arguments{{Name: "chainId", Type: uint256Type}} ) @@ -520,6 +534,49 @@ func DiffTestUtils() { packed, err := bytesArgs.Pack(&encoded) checkErr(err, "Error encoding output") + fmt.Print(hexutil.Encode(packed)) + case "encodeSuperRootProof": + // Parse input argument as abi encoded super root proof + if len(args) < 2 { + panic("Error: encodeSuperRoot requires at least 1 argument") + } + + // Parse the input as hex data + superRootProofData := common.FromHex(args[1]) + proof, err := parseSuperRootProof(superRootProofData) + checkErr(err, "Error parsing super root proof") + + // Encode super root proof + encoded, err := encodeSuperRootProof(proof) + checkErr(err, "Error encoding super root") + + // Pack encoded super root + packed, err := bytesArgs.Pack(&encoded) + checkErr(err, "Error encoding output") + + fmt.Print(hexutil.Encode(packed)) + case "hashSuperRootProof": + // Parse input argument as abi encoded super root proof + if len(args) < 2 { + panic("Error: hashSuperRootProof requires at least 1 argument") + } + + // Parse the input as hex data + superRootProofData := common.FromHex(args[1]) + proof, err := parseSuperRootProof(superRootProofData) + checkErr(err, "Error parsing super root proof") + + // Encode super root proof + encoded, err := encodeSuperRootProof(proof) + checkErr(err, "Error encoding super root proof") + + // Hash super root proof + hash := crypto.Keccak256Hash(encoded) + + // Pack hash + packed, err := fixedBytesArgs.Pack(&hash) + checkErr(err, "Error encoding output") + fmt.Print(hexutil.Encode(packed)) default: panic(fmt.Errorf("Unknown command: %s", args[0])) diff --git a/packages/contracts-bedrock/scripts/go-ffi/utils.go b/packages/contracts-bedrock/scripts/go-ffi/utils.go index 0c535c03feef1..157dd4d85c547 100644 --- a/packages/contracts-bedrock/scripts/go-ffi/utils.go +++ b/packages/contracts-bedrock/scripts/go-ffi/utils.go @@ -1,6 +1,7 @@ package main import ( + "encoding/binary" "errors" "fmt" "math/big" @@ -13,6 +14,18 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) +type OutputRootWithChainId struct { + ChainId *big.Int + Root common.Hash +} + +// Define a proper type for SuperRootProof +type SuperRootProof struct { + Version uint8 + Timestamp uint64 + OutputRoots []OutputRootWithChainId +} + var UnknownNonceVersion = errors.New("Unknown nonce version") // checkOk checks if ok is false, and panics if so. @@ -50,6 +63,78 @@ func encodeCrossDomainMessage(nonce *big.Int, sender common.Address, target comm return encoded, err } +// parseSuperRootProof parses an abi encoded super root proof into a SuperRootProof struct. +func parseSuperRootProof(abiEncodedProof []byte) (*SuperRootProof, error) { + // Parse the input as hex data + unpacked, err := superRootProofArgs.Unpack(abiEncodedProof) + if err != nil { + return nil, err + } + + // The Unpack method returns a slice of interface{}, so we need to get the first element + if len(unpacked) != 1 { + return nil, errors.New("unexpected number of values after unpacking super root proof") + } + + // Use an anonymous struct matching the tuple’s layout. + tmp := unpacked[0].(struct { + Version [1]uint8 `json:"version"` + Timestamp uint64 `json:"timestamp"` + OutputRoots []struct { + ChainId *big.Int `json:"chainId"` + Root [32]byte `json:"root"` + } `json:"outputRoots"` + }) + + // Convert into our desired SuperRootProof type. + proof := SuperRootProof{ + Version: tmp.Version[0], + Timestamp: tmp.Timestamp, + } + for _, o := range tmp.OutputRoots { + proof.OutputRoots = append(proof.OutputRoots, OutputRootWithChainId{ + ChainId: o.ChainId, + Root: common.BytesToHash(o.Root[:]), + }) + } + + return &proof, nil +} + +// encodeSuperRootProof encodes a super root proof into a byte array. +func encodeSuperRootProof(superRootProof *SuperRootProof) ([]byte, error) { + // Version must match the expected version (0x01) + if superRootProof.Version != 0x01 { + return nil, errors.New("invalid super root version") + } + + // Output roots must not be empty + if len(superRootProof.OutputRoots) == 0 { + return nil, errors.New("empty super root") + } + + // Start with version byte and timestamp + encoded := []byte{superRootProof.Version} + + // Add timestamp as bytes8 (uint64) + timestampBytes := make([]byte, 8) + binary.BigEndian.PutUint64(timestampBytes, superRootProof.Timestamp) + encoded = append(encoded, timestampBytes...) + + // Add each output root (chainId + root) + for _, outputRoot := range superRootProof.OutputRoots { + // Append chainId bytes (padded to 32 bytes) + chainIdBytes := make([]byte, 32) + outputRoot.ChainId.FillBytes(chainIdBytes) + encoded = append(encoded, chainIdBytes...) + + // Append root hash (already 32 bytes) + encoded = append(encoded, outputRoot.Root.Bytes()...) + } + + return encoded, nil +} + // hashWithdrawal hashes a withdrawal transaction. func hashWithdrawal(nonce *big.Int, sender common.Address, target common.Address, value *big.Int, gasLimit *big.Int, data []byte) (common.Hash, error) { wd := crossdomain.Withdrawal{ diff --git a/packages/contracts-bedrock/scripts/libraries/DeployUtils.sol b/packages/contracts-bedrock/scripts/libraries/DeployUtils.sol index 1cec8d04778b1..b4c5bd7b141ed 100644 --- a/packages/contracts-bedrock/scripts/libraries/DeployUtils.sol +++ b/packages/contracts-bedrock/scripts/libraries/DeployUtils.sol @@ -17,6 +17,7 @@ import { IProxy } from "interfaces/universal/IProxy.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; import { IL1ChugSplashProxy, IStaticL1ChugSplashProxy } from "interfaces/legacy/IL1ChugSplashProxy.sol"; import { IResolvedDelegateProxy } from "interfaces/legacy/IResolvedDelegateProxy.sol"; +import { IReinitializableBase } from "interfaces/universal/IReinitializableBase.sol"; library DeployUtils { Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); @@ -360,7 +361,19 @@ library DeployUtils { bytes32 slotVal = vm.load(_contractAddress, bytes32(_slot)); uint8 val = uint8((uint256(slotVal) >> (_offset * 8)) & 0xFF); if (_isProxy) { - require(val == 1, "DeployUtils: storage value is not 1 at the given slot and offset"); + // Using a try/catch here to check if the contract has an initVersion() defined. + // EIP-150 safe because we require that we have at least 200k gas before the call which + // is more than enough to avoid running out of gas when 63/64 of the gas is provided to + // the initVersion() call (which simply reads an immutable variable). Since this is + // only ever triggered as part of a script, we can safely assume we'll have the gas. + require(gasleft() > 200_000, "DeployUtils: insufficient gas for initVersion() call"); + + // eip150-safe + try IReinitializableBase(_contractAddress).initVersion() returns (uint8 initVersion_) { + require(val == initVersion_, "DeployUtils: storage value is incorrect at the given slot and offset"); + } catch { + require(val == 1, "DeployUtils: storage value is not set at the given slot and offset"); + } } else { require(val == type(uint8).max, "DeployUtils: storage value is not 0xff at the given slot and offset"); } diff --git a/packages/contracts-bedrock/scripts/libraries/Types.sol b/packages/contracts-bedrock/scripts/libraries/Types.sol index 01d0995aec61c..f8c6eb9026463 100644 --- a/packages/contracts-bedrock/scripts/libraries/Types.sol +++ b/packages/contracts-bedrock/scripts/libraries/Types.sol @@ -14,6 +14,7 @@ library Types { address AnchorStateRegistry; address OptimismMintableERC20Factory; address OptimismPortal; + address ETHLockbox; address SystemConfig; address L1ERC721Bridge; address ProtocolVersions; diff --git a/packages/contracts-bedrock/snapshots/abi/AnchorStateRegistry.json b/packages/contracts-bedrock/snapshots/abi/AnchorStateRegistry.json index 25919d1d4bfb1..3aa3c26096c60 100644 --- a/packages/contracts-bedrock/snapshots/abi/AnchorStateRegistry.json +++ b/packages/contracts-bedrock/snapshots/abi/AnchorStateRegistry.json @@ -1,6 +1,12 @@ [ { - "inputs": [], + "inputs": [ + { + "internalType": "uint256", + "name": "_disputeGameFinalityDelaySeconds", + "type": "uint256" + } + ], "stateMutability": "nonpayable", "type": "constructor" }, @@ -41,6 +47,38 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract IDisputeGame", + "name": "_disputeGame", + "type": "address" + } + ], + "name": "blacklistDisputeGame", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IDisputeGame", + "name": "", + "type": "address" + } + ], + "name": "disputeGameBlacklist", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "disputeGameFactory", @@ -54,6 +92,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "disputeGameFinalityDelaySeconds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "getAnchorRoot", @@ -84,11 +135,6 @@ "name": "_disputeGameFactory", "type": "address" }, - { - "internalType": "contract IOptimismPortal2", - "name": "_portal", - "type": "address" - }, { "components": [ { @@ -105,6 +151,11 @@ "internalType": "struct OutputRoot", "name": "_startingAnchorRoot", "type": "tuple" + }, + { + "internalType": "GameType", + "name": "_startingRespectedGameType", + "type": "uint32" } ], "name": "initialize", @@ -266,12 +317,12 @@ }, { "inputs": [], - "name": "portal", + "name": "paused", "outputs": [ { - "internalType": "contract IOptimismPortal2", + "internalType": "bool", "name": "", - "type": "address" + "type": "bool" } ], "stateMutability": "view", @@ -290,6 +341,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "retirementTimestamp", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -303,6 +367,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "GameType", + "name": "_gameType", + "type": "uint32" + } + ], + "name": "setRespectedGameType", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "superchainConfig", @@ -316,6 +393,13 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "updateRetirementTimestamp", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "version", @@ -339,7 +423,7 @@ "type": "address" } ], - "name": "AnchorNotUpdated", + "name": "AnchorUpdated", "type": "event" }, { @@ -347,12 +431,12 @@ "inputs": [ { "indexed": true, - "internalType": "contract IFaultDisputeGame", - "name": "game", + "internalType": "contract IDisputeGame", + "name": "disputeGame", "type": "address" } ], - "name": "AnchorUpdated", + "name": "DisputeGameBlacklisted", "type": "event" }, { @@ -368,6 +452,32 @@ "name": "Initialized", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "GameType", + "name": "gameType", + "type": "uint32" + } + ], + "name": "RespectedGameTypeSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "RetirementTimestampSet", + "type": "event" + }, { "inputs": [], "name": "AnchorStateRegistry_AnchorGameBlacklisted", diff --git a/packages/contracts-bedrock/snapshots/abi/DeputyGuardianModule.json b/packages/contracts-bedrock/snapshots/abi/DeputyGuardianModule.json index f1006749351ea..ab662e4ee49fc 100644 --- a/packages/contracts-bedrock/snapshots/abi/DeputyGuardianModule.json +++ b/packages/contracts-bedrock/snapshots/abi/DeputyGuardianModule.json @@ -23,8 +23,8 @@ { "inputs": [ { - "internalType": "contract IOptimismPortal2", - "name": "_portal", + "internalType": "contract IAnchorStateRegistry", + "name": "_anchorStateRegistry", "type": "address" }, { @@ -75,25 +75,7 @@ "inputs": [ { "internalType": "contract IAnchorStateRegistry", - "name": "_registry", - "type": "address" - }, - { - "internalType": "contract IFaultDisputeGame", - "name": "_game", - "type": "address" - } - ], - "name": "setAnchorState", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract IOptimismPortal2", - "name": "_portal", + "name": "_anchorStateRegistry", "type": "address" }, { @@ -127,6 +109,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract IAnchorStateRegistry", + "name": "_anchorStateRegistry", + "type": "address" + } + ], + "name": "updateRetirementTimestamp", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "version", @@ -185,6 +180,19 @@ "name": "RespectedGameTypeSet", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Timestamp", + "name": "updatedAt", + "type": "uint64" + } + ], + "name": "RetirementTimestampUpdated", + "type": "event" + }, { "anonymous": false, "inputs": [], @@ -199,12 +207,12 @@ "type": "string" } ], - "name": "ExecutionFailed", + "name": "DeputyGuardianModule_ExecutionFailed", "type": "error" }, { "inputs": [], - "name": "Unauthorized", + "name": "DeputyGuardianModule_Unauthorized", "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/ETHLockbox.json b/packages/contracts-bedrock/snapshots/abi/ETHLockbox.json new file mode 100644 index 0000000000000..d0e9c8fb9a626 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/ETHLockbox.json @@ -0,0 +1,321 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "contract IETHLockbox", + "name": "_lockbox", + "type": "address" + } + ], + "name": "authorizeLockbox", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IOptimismPortal2", + "name": "_portal", + "type": "address" + } + ], + "name": "authorizePortal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "authorizedLockboxes", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "authorizedPortals", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "_superchainConfig", + "type": "address" + }, + { + "internalType": "contract IOptimismPortal2[]", + "name": "_portals", + "type": "address[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "lockETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IETHLockbox", + "name": "_lockbox", + "type": "address" + } + ], + "name": "migrateLiquidity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proxyAdminOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "receiveLiquidity", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "superchainConfig", + "outputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + } + ], + "name": "unlockETH", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "portal", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ETHLocked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "portal", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ETHUnlocked", + "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": "lockbox", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "LiquidityMigrated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "lockbox", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "LiquidityReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "lockbox", + "type": "address" + } + ], + "name": "LockboxAuthorized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "portal", + "type": "address" + } + ], + "name": "PortalAuthorized", + "type": "event" + }, + { + "inputs": [], + "name": "ETHLockbox_DifferentProxyAdminOwner", + "type": "error" + }, + { + "inputs": [], + "name": "ETHLockbox_InsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "ETHLockbox_NoWithdrawalTransactions", + "type": "error" + }, + { + "inputs": [], + "name": "ETHLockbox_Paused", + "type": "error" + }, + { + "inputs": [], + "name": "ETHLockbox_Unauthorized", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json index a12ea8e94117b..a59cc1337d061 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json @@ -293,6 +293,11 @@ "name": "gasLimit", "type": "uint64" }, + { + "internalType": "bool", + "name": "disputeGameUsesSuperRoots", + "type": "bool" + }, { "internalType": "GameType", "name": "disputeGameType", @@ -368,6 +373,11 @@ "name": "l1CrossDomainMessengerProxy", "type": "address" }, + { + "internalType": "contract IETHLockbox", + "name": "ethLockboxProxy", + "type": "address" + }, { "internalType": "contract IOptimismPortal2", "name": "optimismPortalProxy", @@ -438,6 +448,11 @@ "name": "optimismPortalImpl", "type": "address" }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, { "internalType": "address", "name": "systemConfigImpl", @@ -622,6 +637,11 @@ "internalType": "Claim", "name": "absolutePrestate", "type": "bytes32" + }, + { + "internalType": "bool", + "name": "disputeGameUsesSuperRoots", + "type": "bool" } ], "internalType": "struct OPContractsManager.OpChainConfig[]", @@ -652,6 +672,11 @@ "internalType": "Claim", "name": "absolutePrestate", "type": "bytes32" + }, + { + "internalType": "bool", + "name": "disputeGameUsesSuperRoots", + "type": "bool" } ], "internalType": "struct OPContractsManager.OpChainConfig[]", @@ -758,11 +783,6 @@ "name": "OnlyUpgradeController", "type": "error" }, - { - "inputs": [], - "name": "PrestateNotSet", - "type": "error" - }, { "inputs": [], "name": "PrestateRequired", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json index 45af4462a5637..9e09b81cb68b2 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json @@ -75,6 +75,11 @@ "name": "optimismPortalImpl", "type": "address" }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, { "internalType": "address", "name": "systemConfigImpl", @@ -210,6 +215,11 @@ "name": "optimismPortalImpl", "type": "address" }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, { "internalType": "address", "name": "systemConfigImpl", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json index d543175c82822..a3e0783fd0d34 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json @@ -186,6 +186,11 @@ "name": "gasLimit", "type": "uint64" }, + { + "internalType": "bool", + "name": "disputeGameUsesSuperRoots", + "type": "bool" + }, { "internalType": "GameType", "name": "disputeGameType", @@ -271,6 +276,11 @@ "name": "l1CrossDomainMessengerProxy", "type": "address" }, + { + "internalType": "contract IETHLockbox", + "name": "ethLockboxProxy", + "type": "address" + }, { "internalType": "contract IOptimismPortal2", "name": "optimismPortalProxy", @@ -341,6 +351,11 @@ "name": "optimismPortalImpl", "type": "address" }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, { "internalType": "address", "name": "systemConfigImpl", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployerInterop.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployerInterop.json index d543175c82822..a3e0783fd0d34 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployerInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployerInterop.json @@ -186,6 +186,11 @@ "name": "gasLimit", "type": "uint64" }, + { + "internalType": "bool", + "name": "disputeGameUsesSuperRoots", + "type": "bool" + }, { "internalType": "GameType", "name": "disputeGameType", @@ -271,6 +276,11 @@ "name": "l1CrossDomainMessengerProxy", "type": "address" }, + { + "internalType": "contract IETHLockbox", + "name": "ethLockboxProxy", + "type": "address" + }, { "internalType": "contract IOptimismPortal2", "name": "optimismPortalProxy", @@ -341,6 +351,11 @@ "name": "optimismPortalImpl", "type": "address" }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, { "internalType": "address", "name": "systemConfigImpl", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json index a2088238caf93..75a4caf40467b 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json @@ -244,6 +244,11 @@ "name": "optimismPortalImpl", "type": "address" }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, { "internalType": "address", "name": "systemConfigImpl", @@ -311,6 +316,11 @@ "internalType": "Claim", "name": "absolutePrestate", "type": "bytes32" + }, + { + "internalType": "bool", + "name": "disputeGameUsesSuperRoots", + "type": "bool" } ], "internalType": "struct OPContractsManager.OpChainConfig[]", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json new file mode 100644 index 0000000000000..22c1d40b77cd2 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json @@ -0,0 +1,950 @@ +[ + { + "inputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "_superchainConfig", + "type": "address" + }, + { + "internalType": "contract IProtocolVersions", + "name": "_protocolVersions", + "type": "address" + }, + { + "internalType": "contract IProxyAdmin", + "name": "_superchainProxyAdmin", + "type": "address" + }, + { + "internalType": "string", + "name": "_l1ContractsRelease", + "type": "string" + }, + { + "components": [ + { + "internalType": "address", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ChugSplashProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "resolvedDelegateProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame2", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame2", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Blueprints", + "name": "_blueprints", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "superchainConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "protocolVersionsImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ERC721BridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismMintableERC20FactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1CrossDomainMessengerImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1StandardBridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "disputeGameFactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "anchorStateRegistryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "delayedWETHImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "mipsImpl", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Implementations", + "name": "_implementations", + "type": "tuple" + }, + { + "internalType": "address", + "name": "_upgradeController", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "string", + "name": "saltMixer", + "type": "string" + }, + { + "internalType": "contract ISystemConfig", + "name": "systemConfig", + "type": "address" + }, + { + "internalType": "contract IProxyAdmin", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "contract IDelayedWETH", + "name": "delayedWETH", + "type": "address" + }, + { + "internalType": "GameType", + "name": "disputeGameType", + "type": "uint32" + }, + { + "internalType": "Claim", + "name": "disputeAbsolutePrestate", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "disputeMaxGameDepth", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "disputeSplitDepth", + "type": "uint256" + }, + { + "internalType": "Duration", + "name": "disputeClockExtension", + "type": "uint64" + }, + { + "internalType": "Duration", + "name": "disputeMaxClockDuration", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "initialBond", + "type": "uint256" + }, + { + "internalType": "contract IBigStepper", + "name": "vm", + "type": "address" + }, + { + "internalType": "bool", + "name": "permissioned", + "type": "bool" + } + ], + "internalType": "struct OPContractsManager.AddGameInput[]", + "name": "_gameConfigs", + "type": "tuple[]" + } + ], + "name": "addGameType", + "outputs": [ + { + "components": [ + { + "internalType": "contract IDelayedWETH", + "name": "delayedWETH", + "type": "address" + }, + { + "internalType": "contract IFaultDisputeGame", + "name": "faultDisputeGame", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.AddGameOutput[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "blueprints", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ChugSplashProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "resolvedDelegateProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame2", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame2", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Blueprints", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" + } + ], + "name": "chainIdToBatchInboxAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "opChainProxyAdminOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "batcher", + "type": "address" + }, + { + "internalType": "address", + "name": "unsafeBlockSigner", + "type": "address" + }, + { + "internalType": "address", + "name": "proposer", + "type": "address" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Roles", + "name": "roles", + "type": "tuple" + }, + { + "internalType": "uint32", + "name": "basefeeScalar", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blobBasefeeScalar", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "startingAnchorRoot", + "type": "bytes" + }, + { + "internalType": "string", + "name": "saltMixer", + "type": "string" + }, + { + "internalType": "uint64", + "name": "gasLimit", + "type": "uint64" + }, + { + "internalType": "GameType", + "name": "disputeGameType", + "type": "uint32" + }, + { + "internalType": "Claim", + "name": "disputeAbsolutePrestate", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "disputeMaxGameDepth", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "disputeSplitDepth", + "type": "uint256" + }, + { + "internalType": "Duration", + "name": "disputeClockExtension", + "type": "uint64" + }, + { + "internalType": "Duration", + "name": "disputeMaxClockDuration", + "type": "uint64" + } + ], + "internalType": "struct OPContractsManager.DeployInput", + "name": "_input", + "type": "tuple" + } + ], + "name": "deploy", + "outputs": [ + { + "components": [ + { + "internalType": "contract IProxyAdmin", + "name": "opChainProxyAdmin", + "type": "address" + }, + { + "internalType": "contract IAddressManager", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "contract IL1ERC721Bridge", + "name": "l1ERC721BridgeProxy", + "type": "address" + }, + { + "internalType": "contract ISystemConfig", + "name": "systemConfigProxy", + "type": "address" + }, + { + "internalType": "contract IOptimismMintableERC20Factory", + "name": "optimismMintableERC20FactoryProxy", + "type": "address" + }, + { + "internalType": "contract IL1StandardBridge", + "name": "l1StandardBridgeProxy", + "type": "address" + }, + { + "internalType": "contract IL1CrossDomainMessenger", + "name": "l1CrossDomainMessengerProxy", + "type": "address" + }, + { + "internalType": "contract IOptimismPortal2", + "name": "optimismPortalProxy", + "type": "address" + }, + { + "internalType": "contract IDisputeGameFactory", + "name": "disputeGameFactoryProxy", + "type": "address" + }, + { + "internalType": "contract IAnchorStateRegistry", + "name": "anchorStateRegistryProxy", + "type": "address" + }, + { + "internalType": "contract IFaultDisputeGame", + "name": "faultDisputeGame", + "type": "address" + }, + { + "internalType": "contract IPermissionedDisputeGame", + "name": "permissionedDisputeGame", + "type": "address" + }, + { + "internalType": "contract IDelayedWETH", + "name": "delayedWETHPermissionedGameProxy", + "type": "address" + }, + { + "internalType": "contract IDelayedWETH", + "name": "delayedWETHPermissionlessGameProxy", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.DeployOutput", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementations", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "superchainConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "protocolVersionsImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ERC721BridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismMintableERC20FactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1CrossDomainMessengerImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1StandardBridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "disputeGameFactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "anchorStateRegistryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "delayedWETHImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "mipsImpl", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Implementations", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isRC", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1ContractsRelease", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolVersions", + "outputs": [ + { + "internalType": "contract IProtocolVersions", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_isRC", + "type": "bool" + } + ], + "name": "setRC", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "superchainConfig", + "outputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "superchainProxyAdmin", + "outputs": [ + { + "internalType": "contract IProxyAdmin", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "contract ISystemConfig", + "name": "systemConfigProxy", + "type": "address" + }, + { + "internalType": "contract IProxyAdmin", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "Claim", + "name": "absolutePrestate", + "type": "bytes32" + } + ], + "internalType": "struct OPContractsManager.OpChainConfig[]", + "name": "_opChainConfigs", + "type": "tuple[]" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "upgradeController", + "outputs": [ + { + "internalType": "address", + "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": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "deployer", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "deployOutput", + "type": "bytes" + } + ], + "name": "Deployed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "GameType", + "name": "gameType", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "contract IDisputeGame", + "name": "newDisputeGame", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract IDisputeGame", + "name": "oldDisputeGame", + "type": "address" + } + ], + "name": "GameTypeAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "contract ISystemConfig", + "name": "systemConfig", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "upgrader", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "who", + "type": "address" + } + ], + "name": "AddressHasNoCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "who", + "type": "address" + } + ], + "name": "AddressNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "AlreadyReleased", + "type": "error" + }, + { + "inputs": [], + "name": "BytesArrayTooLong", + "type": "error" + }, + { + "inputs": [], + "name": "DeploymentFailed", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyInitcode", + "type": "error" + }, + { + "inputs": [], + "name": "IdentityPrecompileCallFailed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidChainId", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidGameConfigs", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "role", + "type": "string" + } + ], + "name": "InvalidRoleAddress", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidStartingAnchorRoot", + "type": "error" + }, + { + "inputs": [], + "name": "LatestReleaseNotSet", + "type": "error" + }, + { + "inputs": [], + "name": "NotABlueprint", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyDelegatecall", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyUpgradeController", + "type": "error" + }, + { + "inputs": [], + "name": "PrestateNotSet", + "type": "error" + }, + { + "inputs": [], + "name": "ReservedBitsSet", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract ISystemConfig", + "name": "systemConfig", + "type": "address" + } + ], + "name": "SuperchainConfigMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "SuperchainProxyAdminMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "UnexpectedPreambleData", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "UnsupportedERCVersion", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json index 8efb6ad77e6d9..bcb065640164d 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json @@ -141,6 +141,11 @@ "name": "optimismPortalImpl", "type": "address" }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, { "internalType": "address", "name": "systemConfigImpl", @@ -208,6 +213,11 @@ "internalType": "Claim", "name": "absolutePrestate", "type": "bytes32" + }, + { + "internalType": "bool", + "name": "disputeGameUsesSuperRoots", + "type": "bool" } ], "internalType": "struct OPContractsManager.OpChainConfig[]", @@ -281,11 +291,6 @@ "name": "NotABlueprint", "type": "error" }, - { - "inputs": [], - "name": "PrestateNotSet", - "type": "error" - }, { "inputs": [], "name": "ReservedBitsSet", diff --git a/packages/contracts-bedrock/snapshots/abi/OPPrestateUpdater.json b/packages/contracts-bedrock/snapshots/abi/OPPrestateUpdater.json new file mode 100644 index 0000000000000..d44b099737a6f --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/OPPrestateUpdater.json @@ -0,0 +1,908 @@ +[ + { + "inputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "_superchainConfig", + "type": "address" + }, + { + "internalType": "contract IProtocolVersions", + "name": "_protocolVersions", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ChugSplashProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "resolvedDelegateProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame2", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame2", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Blueprints", + "name": "_blueprints", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "string", + "name": "saltMixer", + "type": "string" + }, + { + "internalType": "contract ISystemConfig", + "name": "systemConfig", + "type": "address" + }, + { + "internalType": "contract IProxyAdmin", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "contract IDelayedWETH", + "name": "delayedWETH", + "type": "address" + }, + { + "internalType": "GameType", + "name": "disputeGameType", + "type": "uint32" + }, + { + "internalType": "Claim", + "name": "disputeAbsolutePrestate", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "disputeMaxGameDepth", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "disputeSplitDepth", + "type": "uint256" + }, + { + "internalType": "Duration", + "name": "disputeClockExtension", + "type": "uint64" + }, + { + "internalType": "Duration", + "name": "disputeMaxClockDuration", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "initialBond", + "type": "uint256" + }, + { + "internalType": "contract IBigStepper", + "name": "vm", + "type": "address" + }, + { + "internalType": "bool", + "name": "permissioned", + "type": "bool" + } + ], + "internalType": "struct OPContractsManager.AddGameInput[]", + "name": "_gameConfigs", + "type": "tuple[]" + } + ], + "name": "addGameType", + "outputs": [ + { + "components": [ + { + "internalType": "contract IDelayedWETH", + "name": "delayedWETH", + "type": "address" + }, + { + "internalType": "contract IFaultDisputeGame", + "name": "faultDisputeGame", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.AddGameOutput[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "blueprints", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ChugSplashProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "resolvedDelegateProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame2", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame2", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Blueprints", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" + } + ], + "name": "chainIdToBatchInboxAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "opChainProxyAdminOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "batcher", + "type": "address" + }, + { + "internalType": "address", + "name": "unsafeBlockSigner", + "type": "address" + }, + { + "internalType": "address", + "name": "proposer", + "type": "address" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Roles", + "name": "roles", + "type": "tuple" + }, + { + "internalType": "uint32", + "name": "basefeeScalar", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blobBasefeeScalar", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "startingAnchorRoot", + "type": "bytes" + }, + { + "internalType": "string", + "name": "saltMixer", + "type": "string" + }, + { + "internalType": "uint64", + "name": "gasLimit", + "type": "uint64" + }, + { + "internalType": "GameType", + "name": "disputeGameType", + "type": "uint32" + }, + { + "internalType": "Claim", + "name": "disputeAbsolutePrestate", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "disputeMaxGameDepth", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "disputeSplitDepth", + "type": "uint256" + }, + { + "internalType": "Duration", + "name": "disputeClockExtension", + "type": "uint64" + }, + { + "internalType": "Duration", + "name": "disputeMaxClockDuration", + "type": "uint64" + } + ], + "internalType": "struct OPContractsManager.DeployInput", + "name": "_input", + "type": "tuple" + } + ], + "name": "deploy", + "outputs": [ + { + "components": [ + { + "internalType": "contract IProxyAdmin", + "name": "opChainProxyAdmin", + "type": "address" + }, + { + "internalType": "contract IAddressManager", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "contract IL1ERC721Bridge", + "name": "l1ERC721BridgeProxy", + "type": "address" + }, + { + "internalType": "contract ISystemConfig", + "name": "systemConfigProxy", + "type": "address" + }, + { + "internalType": "contract IOptimismMintableERC20Factory", + "name": "optimismMintableERC20FactoryProxy", + "type": "address" + }, + { + "internalType": "contract IL1StandardBridge", + "name": "l1StandardBridgeProxy", + "type": "address" + }, + { + "internalType": "contract IL1CrossDomainMessenger", + "name": "l1CrossDomainMessengerProxy", + "type": "address" + }, + { + "internalType": "contract IOptimismPortal2", + "name": "optimismPortalProxy", + "type": "address" + }, + { + "internalType": "contract IDisputeGameFactory", + "name": "disputeGameFactoryProxy", + "type": "address" + }, + { + "internalType": "contract IAnchorStateRegistry", + "name": "anchorStateRegistryProxy", + "type": "address" + }, + { + "internalType": "contract IFaultDisputeGame", + "name": "faultDisputeGame", + "type": "address" + }, + { + "internalType": "contract IPermissionedDisputeGame", + "name": "permissionedDisputeGame", + "type": "address" + }, + { + "internalType": "contract IDelayedWETH", + "name": "delayedWETHPermissionedGameProxy", + "type": "address" + }, + { + "internalType": "contract IDelayedWETH", + "name": "delayedWETHPermissionlessGameProxy", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.DeployOutput", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "implementations", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "superchainConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "protocolVersionsImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ERC721BridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismMintableERC20FactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1CrossDomainMessengerImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1StandardBridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "disputeGameFactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "anchorStateRegistryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "delayedWETHImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "mipsImpl", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Implementations", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isRC", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1ContractsRelease", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "protocolVersions", + "outputs": [ + { + "internalType": "contract IProtocolVersions", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_isRC", + "type": "bool" + } + ], + "name": "setRC", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "superchainConfig", + "outputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "superchainProxyAdmin", + "outputs": [ + { + "internalType": "contract IProxyAdmin", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "contract ISystemConfig", + "name": "systemConfigProxy", + "type": "address" + }, + { + "internalType": "contract IProxyAdmin", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "Claim", + "name": "absolutePrestate", + "type": "bytes32" + } + ], + "internalType": "struct OPContractsManager.OpChainConfig[]", + "name": "_prestateUpdateInputs", + "type": "tuple[]" + } + ], + "name": "updatePrestate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "contract ISystemConfig", + "name": "systemConfigProxy", + "type": "address" + }, + { + "internalType": "contract IProxyAdmin", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "Claim", + "name": "absolutePrestate", + "type": "bytes32" + } + ], + "internalType": "struct OPContractsManager.OpChainConfig[]", + "name": "_opChainConfigs", + "type": "tuple[]" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "upgradeController", + "outputs": [ + { + "internalType": "address", + "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": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "deployer", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "deployOutput", + "type": "bytes" + } + ], + "name": "Deployed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "GameType", + "name": "gameType", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "contract IDisputeGame", + "name": "newDisputeGame", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract IDisputeGame", + "name": "oldDisputeGame", + "type": "address" + } + ], + "name": "GameTypeAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "contract ISystemConfig", + "name": "systemConfig", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "upgrader", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "who", + "type": "address" + } + ], + "name": "AddressHasNoCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "who", + "type": "address" + } + ], + "name": "AddressNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "AlreadyReleased", + "type": "error" + }, + { + "inputs": [], + "name": "BytesArrayTooLong", + "type": "error" + }, + { + "inputs": [], + "name": "DeploymentFailed", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyInitcode", + "type": "error" + }, + { + "inputs": [], + "name": "IdentityPrecompileCallFailed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidChainId", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidGameConfigs", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "role", + "type": "string" + } + ], + "name": "InvalidRoleAddress", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidStartingAnchorRoot", + "type": "error" + }, + { + "inputs": [], + "name": "LatestReleaseNotSet", + "type": "error" + }, + { + "inputs": [], + "name": "NotABlueprint", + "type": "error" + }, + { + "inputs": [], + "name": "NotImplemented", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyDelegatecall", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyUpgradeController", + "type": "error" + }, + { + "inputs": [], + "name": "PrestateNotSet", + "type": "error" + }, + { + "inputs": [], + "name": "PrestateRequired", + "type": "error" + }, + { + "inputs": [], + "name": "ReservedBitsSet", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract ISystemConfig", + "name": "systemConfig", + "type": "address" + } + ], + "name": "SuperchainConfigMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "SuperchainProxyAdminMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "UnexpectedPreambleData", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "UnsupportedERCVersion", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json b/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json index 71b8677f7dd85..5a60bf69cd620 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json @@ -5,11 +5,6 @@ "internalType": "uint256", "name": "_proofMaturityDelaySeconds", "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_disputeGameFinalityDelaySeconds", - "type": "uint256" } ], "stateMutability": "nonpayable", @@ -20,16 +15,16 @@ "type": "receive" }, { - "inputs": [ + "inputs": [], + "name": "anchorStateRegistry", + "outputs": [ { - "internalType": "contract IDisputeGame", - "name": "_disputeGame", + "internalType": "contract IAnchorStateRegistry", + "name": "", "type": "address" } ], - "name": "blacklistDisputeGame", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { @@ -83,25 +78,6 @@ "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", @@ -135,6 +111,19 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [], + "name": "ethLockbox", + "outputs": [ + { + "internalType": "contract IETHLockbox", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -263,12 +252,20 @@ "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "initVersion", + "outputs": [ { - "internalType": "contract IDisputeGameFactory", - "name": "_disputeGameFactory", - "type": "address" - }, + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "contract ISystemConfig", "name": "_systemConfig", @@ -280,9 +277,19 @@ "type": "address" }, { - "internalType": "GameType", - "name": "_initialRespectedGameType", - "type": "uint32" + "internalType": "contract IAnchorStateRegistry", + "name": "_anchorStateRegistry", + "type": "address" + }, + { + "internalType": "contract IETHLockbox", + "name": "_ethLockbox", + "type": "address" + }, + { + "internalType": "bool", + "name": "_superRootsActive", + "type": "bool" } ], "name": "initialize", @@ -303,6 +310,13 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "migrateLiquidity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -496,6 +510,127 @@ "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": "contract IDisputeGame", + "name": "_disputeGameProxy", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_outputRootIndex", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes1", + "name": "version", + "type": "bytes1" + }, + { + "internalType": "uint64", + "name": "timestamp", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "root", + "type": "bytes32" + } + ], + "internalType": "struct Types.OutputRootWithChainId[]", + "name": "outputRoots", + "type": "tuple[]" + } + ], + "internalType": "struct Types.SuperRootProof", + "name": "_superRootProof", + "type": "tuple" + }, + { + "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": [ { @@ -525,6 +660,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "proxyAdminOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "respectedGameType", @@ -552,16 +700,16 @@ "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "superRootsActive", + "outputs": [ { - "internalType": "GameType", - "name": "_gameType", - "type": "uint32" + "internalType": "bool", + "name": "", + "type": "bool" } ], - "name": "setRespectedGameType", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { @@ -590,6 +738,42 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract IETHLockbox", + "name": "_newLockbox", + "type": "address" + } + ], + "name": "updateLockbox", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IAnchorStateRegistry", + "name": "_anchorStateRegistry", + "type": "address" + }, + { + "internalType": "contract IETHLockbox", + "name": "_ethLockbox", + "type": "address" + }, + { + "internalType": "bool", + "name": "_superRootsActive", + "type": "bool" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "version", @@ -608,12 +792,18 @@ "inputs": [ { "indexed": true, - "internalType": "contract IDisputeGame", - "name": "disputeGame", + "internalType": "address", + "name": "lockbox", "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "ethBalance", + "type": "uint256" } ], - "name": "DisputeGameBlacklisted", + "name": "ETHMigrated", "type": "event" }, { @@ -633,19 +823,19 @@ "anonymous": false, "inputs": [ { - "indexed": true, - "internalType": "GameType", - "name": "newGameType", - "type": "uint32" + "indexed": false, + "internalType": "address", + "name": "oldLockbox", + "type": "address" }, { - "indexed": true, - "internalType": "Timestamp", - "name": "updatedAt", - "type": "uint64" + "indexed": false, + "internalType": "address", + "name": "newLockbox", + "type": "address" } ], - "name": "RespectedGameTypeSet", + "name": "LockboxUpdated", "type": "event" }, { @@ -744,117 +934,152 @@ }, { "inputs": [], - "name": "AlreadyFinalized", + "name": "ContentLengthMismatch", "type": "error" }, { "inputs": [], - "name": "BadTarget", + "name": "EmptyItem", "type": "error" }, { "inputs": [], - "name": "Blacklisted", + "name": "Encoding_EmptySuperRoot", "type": "error" }, { "inputs": [], - "name": "CallPaused", + "name": "Encoding_InvalidSuperRootVersion", "type": "error" }, { "inputs": [], - "name": "ContentLengthMismatch", + "name": "InvalidDataRemainder", "type": "error" }, { "inputs": [], - "name": "EmptyItem", + "name": "InvalidHeader", "type": "error" }, { "inputs": [], - "name": "GasEstimation", + "name": "OptimismPortal_AlreadyFinalized", "type": "error" }, { "inputs": [], - "name": "InvalidDataRemainder", + "name": "OptimismPortal_BadTarget", "type": "error" }, { "inputs": [], - "name": "InvalidDisputeGame", + "name": "OptimismPortal_CallPaused", "type": "error" }, { "inputs": [], - "name": "InvalidGameType", + "name": "OptimismPortal_CalldataTooLarge", "type": "error" }, { "inputs": [], - "name": "InvalidHeader", + "name": "OptimismPortal_GasEstimation", "type": "error" }, { "inputs": [], - "name": "InvalidMerkleProof", + "name": "OptimismPortal_GasLimitTooLow", "type": "error" }, { "inputs": [], - "name": "InvalidProof", + "name": "OptimismPortal_ImproperDisputeGame", "type": "error" }, { "inputs": [], - "name": "LargeCalldata", + "name": "OptimismPortal_InvalidDisputeGame", "type": "error" }, { "inputs": [], - "name": "LegacyGame", + "name": "OptimismPortal_InvalidMerkleProof", "type": "error" }, { "inputs": [], - "name": "NonReentrant", + "name": "OptimismPortal_InvalidOutputRootChainId", "type": "error" }, { "inputs": [], - "name": "OutOfGas", + "name": "OptimismPortal_InvalidOutputRootIndex", "type": "error" }, { "inputs": [], - "name": "ProposalNotValidated", + "name": "OptimismPortal_InvalidOutputRootProof", "type": "error" }, { "inputs": [], - "name": "SmallGasLimit", + "name": "OptimismPortal_InvalidProofTimestamp", "type": "error" }, { "inputs": [], - "name": "Unauthorized", + "name": "OptimismPortal_InvalidRootClaim", "type": "error" }, { "inputs": [], - "name": "UnexpectedList", + "name": "OptimismPortal_InvalidSuperRootProof", "type": "error" }, { "inputs": [], - "name": "UnexpectedString", + "name": "OptimismPortal_NoReentrancy", + "type": "error" + }, + { + "inputs": [], + "name": "OptimismPortal_ProofNotOldEnough", + "type": "error" + }, + { + "inputs": [], + "name": "OptimismPortal_Unauthorized", "type": "error" }, { "inputs": [], - "name": "Unproven", + "name": "OptimismPortal_Unproven", + "type": "error" + }, + { + "inputs": [], + "name": "OptimismPortal_WrongProofMethod", + "type": "error" + }, + { + "inputs": [], + "name": "OutOfGas", + "type": "error" + }, + { + "inputs": [], + "name": "ReinitializableBase_ZeroInitVersion", + "type": "error" + }, + { + "inputs": [], + "name": "UnexpectedList", + "type": "error" + }, + { + "inputs": [], + "name": "UnexpectedString", "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json b/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json index 2dd27e68eaff9..4835f00aa2ca9 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json @@ -5,11 +5,6 @@ "internalType": "uint256", "name": "_proofMaturityDelaySeconds", "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_disputeGameFinalityDelaySeconds", - "type": "uint256" } ], "stateMutability": "nonpayable", @@ -20,16 +15,16 @@ "type": "receive" }, { - "inputs": [ + "inputs": [], + "name": "anchorStateRegistry", + "outputs": [ { - "internalType": "contract IDisputeGame", - "name": "_disputeGame", + "internalType": "contract IAnchorStateRegistry", + "name": "", "type": "address" } ], - "name": "blacklistDisputeGame", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { @@ -83,25 +78,6 @@ "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", @@ -135,6 +111,19 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [], + "name": "ethLockbox", + "outputs": [ + { + "internalType": "contract IETHLockbox", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -263,12 +252,20 @@ "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "initVersion", + "outputs": [ { - "internalType": "contract IDisputeGameFactory", - "name": "_disputeGameFactory", - "type": "address" - }, + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "contract ISystemConfig", "name": "_systemConfig", @@ -280,9 +277,19 @@ "type": "address" }, { - "internalType": "GameType", - "name": "_initialRespectedGameType", - "type": "uint32" + "internalType": "contract IAnchorStateRegistry", + "name": "_anchorStateRegistry", + "type": "address" + }, + { + "internalType": "contract IETHLockbox", + "name": "_ethLockbox", + "type": "address" + }, + { + "internalType": "bool", + "name": "_superRootsActive", + "type": "bool" } ], "name": "initialize", @@ -303,6 +310,13 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "migrateLiquidity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -496,6 +510,127 @@ "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": "contract IDisputeGame", + "name": "_disputeGameProxy", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_outputRootIndex", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes1", + "name": "version", + "type": "bytes1" + }, + { + "internalType": "uint64", + "name": "timestamp", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "root", + "type": "bytes32" + } + ], + "internalType": "struct Types.OutputRootWithChainId[]", + "name": "outputRoots", + "type": "tuple[]" + } + ], + "internalType": "struct Types.SuperRootProof", + "name": "_superRootProof", + "type": "tuple" + }, + { + "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": [ { @@ -525,6 +660,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "proxyAdminOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "respectedGameType", @@ -570,16 +718,16 @@ "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "superRootsActive", + "outputs": [ { - "internalType": "GameType", - "name": "_gameType", - "type": "uint32" + "internalType": "bool", + "name": "", + "type": "bool" } ], - "name": "setRespectedGameType", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { @@ -608,6 +756,42 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract IETHLockbox", + "name": "_newLockbox", + "type": "address" + } + ], + "name": "updateLockbox", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IAnchorStateRegistry", + "name": "_anchorStateRegistry", + "type": "address" + }, + { + "internalType": "contract IETHLockbox", + "name": "_ethLockbox", + "type": "address" + }, + { + "internalType": "bool", + "name": "_superRootsActive", + "type": "bool" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "version", @@ -626,12 +810,18 @@ "inputs": [ { "indexed": true, - "internalType": "contract IDisputeGame", - "name": "disputeGame", + "internalType": "address", + "name": "lockbox", "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "ethBalance", + "type": "uint256" } ], - "name": "DisputeGameBlacklisted", + "name": "ETHMigrated", "type": "event" }, { @@ -651,19 +841,19 @@ "anonymous": false, "inputs": [ { - "indexed": true, - "internalType": "GameType", - "name": "newGameType", - "type": "uint32" + "indexed": false, + "internalType": "address", + "name": "oldLockbox", + "type": "address" }, { - "indexed": true, - "internalType": "Timestamp", - "name": "updatedAt", - "type": "uint64" + "indexed": false, + "internalType": "address", + "name": "newLockbox", + "type": "address" } ], - "name": "RespectedGameTypeSet", + "name": "LockboxUpdated", "type": "event" }, { @@ -762,117 +952,152 @@ }, { "inputs": [], - "name": "AlreadyFinalized", + "name": "ContentLengthMismatch", "type": "error" }, { "inputs": [], - "name": "BadTarget", + "name": "EmptyItem", "type": "error" }, { "inputs": [], - "name": "Blacklisted", + "name": "Encoding_EmptySuperRoot", "type": "error" }, { "inputs": [], - "name": "CallPaused", + "name": "Encoding_InvalidSuperRootVersion", "type": "error" }, { "inputs": [], - "name": "ContentLengthMismatch", + "name": "InvalidDataRemainder", "type": "error" }, { "inputs": [], - "name": "EmptyItem", + "name": "InvalidHeader", "type": "error" }, { "inputs": [], - "name": "GasEstimation", + "name": "OptimismPortal_AlreadyFinalized", "type": "error" }, { "inputs": [], - "name": "InvalidDataRemainder", + "name": "OptimismPortal_BadTarget", "type": "error" }, { "inputs": [], - "name": "InvalidDisputeGame", + "name": "OptimismPortal_CallPaused", "type": "error" }, { "inputs": [], - "name": "InvalidGameType", + "name": "OptimismPortal_CalldataTooLarge", "type": "error" }, { "inputs": [], - "name": "InvalidHeader", + "name": "OptimismPortal_GasEstimation", "type": "error" }, { "inputs": [], - "name": "InvalidMerkleProof", + "name": "OptimismPortal_GasLimitTooLow", "type": "error" }, { "inputs": [], - "name": "InvalidProof", + "name": "OptimismPortal_ImproperDisputeGame", "type": "error" }, { "inputs": [], - "name": "LargeCalldata", + "name": "OptimismPortal_InvalidDisputeGame", "type": "error" }, { "inputs": [], - "name": "LegacyGame", + "name": "OptimismPortal_InvalidMerkleProof", "type": "error" }, { "inputs": [], - "name": "NonReentrant", + "name": "OptimismPortal_InvalidOutputRootChainId", "type": "error" }, { "inputs": [], - "name": "OutOfGas", + "name": "OptimismPortal_InvalidOutputRootIndex", "type": "error" }, { "inputs": [], - "name": "ProposalNotValidated", + "name": "OptimismPortal_InvalidOutputRootProof", "type": "error" }, { "inputs": [], - "name": "SmallGasLimit", + "name": "OptimismPortal_InvalidProofTimestamp", "type": "error" }, { "inputs": [], - "name": "Unauthorized", + "name": "OptimismPortal_InvalidRootClaim", "type": "error" }, { "inputs": [], - "name": "UnexpectedList", + "name": "OptimismPortal_InvalidSuperRootProof", "type": "error" }, { "inputs": [], - "name": "UnexpectedString", + "name": "OptimismPortal_NoReentrancy", + "type": "error" + }, + { + "inputs": [], + "name": "OptimismPortal_ProofNotOldEnough", + "type": "error" + }, + { + "inputs": [], + "name": "OptimismPortal_Unauthorized", "type": "error" }, { "inputs": [], - "name": "Unproven", + "name": "OptimismPortal_Unproven", + "type": "error" + }, + { + "inputs": [], + "name": "OptimismPortal_WrongProofMethod", + "type": "error" + }, + { + "inputs": [], + "name": "OutOfGas", + "type": "error" + }, + { + "inputs": [], + "name": "ReinitializableBase_ZeroInitVersion", + "type": "error" + }, + { + "inputs": [], + "name": "UnexpectedList", + "type": "error" + }, + { + "inputs": [], + "name": "UnexpectedString", "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 5009ab31ca872..8f11882ac1b33 100644 --- a/packages/contracts-bedrock/snapshots/abi/SystemConfig.json +++ b/packages/contracts-bedrock/snapshots/abi/SystemConfig.json @@ -283,6 +283,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "initVersion", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -393,6 +406,11 @@ "internalType": "struct SystemConfig.Addresses", "name": "_addresses", "type": "tuple" + }, + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" } ], "name": "initialize", @@ -439,6 +457,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "l2ChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "maximumGasLimit", @@ -758,6 +789,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "version", @@ -827,5 +871,10 @@ ], "name": "OwnershipTransferred", "type": "event" + }, + { + "inputs": [], + "name": "ReinitializableBase_ZeroInitVersion", + "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json b/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json index 8bcac1f13ccc9..c11d382aa528f 100644 --- a/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json @@ -304,6 +304,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "initVersion", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -414,6 +427,11 @@ "internalType": "struct SystemConfig.Addresses", "name": "_addresses", "type": "tuple" + }, + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" } ], "name": "initialize", @@ -536,6 +554,11 @@ "internalType": "address", "name": "_dependencyManager", "type": "address" + }, + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" } ], "name": "initialize", @@ -582,6 +605,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "l2ChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "maximumGasLimit", @@ -914,6 +950,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "version", @@ -983,5 +1032,10 @@ ], "name": "OwnershipTransferred", "type": "event" + }, + { + "inputs": [], + "name": "ReinitializableBase_ZeroInitVersion", + "type": "error" } ] \ 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 5667bfa15807f..ea49df2bac3c4 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -3,6 +3,10 @@ "initCodeHash": "0xacbae98cc7c0f7ecbf36dc44bbf7cb0a011e6e6b781e28b9dbf947e31482b30d", "sourceCodeHash": "0xe772f7db8033e4a738850cb28ac4849d3a454c93732135a8a10d4f7cb498088e" }, + "src/L1/ETHLockbox.sol:ETHLockbox": { + "initCodeHash": "0x599593bf96170081d2fd191bb2f61eaa2d094faafb6a583a34b3eea2dc49e5c6", + "sourceCodeHash": "0x34da4a8a0e32cabce97056f10cc1c8231f14a1cd706ae63ee0bef3c1f65f3e13" + }, "src/L1/L1CrossDomainMessenger.sol:L1CrossDomainMessenger": { "initCodeHash": "0x5a6272f6bd4346da460b7ff2ebc426d158d7ffc65dc0f2c0651a9e6d545bfd03", "sourceCodeHash": "0xf11fa72481dbe43dad4e7a48645fcf92d0feeefa0b98b282042bdda568508372" @@ -16,16 +20,16 @@ "sourceCodeHash": "0x44797707aea8c63dec049a02d69ea056662a06e5cf320028ab8b388634bf1c67" }, "src/L1/OPContractsManager.sol:OPContractsManager": { - "initCodeHash": "0x216fe3ceef5d3e839d18a00383793bd326f20944fc3b55ef099a26a22141de18", - "sourceCodeHash": "0xd1de3414a3db731447cb6172df23a00cb1664783de56a93fb29f87d6bc8b8bb0" + "initCodeHash": "0x9e7496b9720bae99e5db18dc18c99b5c06cd755c6d4079e0d38c56d625fe7b4e", + "sourceCodeHash": "0x8302de0ccd551512f2430d545ea55c938b002701d2292fd7aad2b4d2b975727b" }, "src/L1/OptimismPortal2.sol:OptimismPortal2": { - "initCodeHash": "0xd1651b8a6f4d25611a0105d5cc7c1da3921417bd44da870ec63bf5ccd1bc7c63", - "sourceCodeHash": "0xce7373d8c7df47caa8b090f3afb3d2539677f12cb3eff7fc0ab1fd85638f05c1" + "initCodeHash": "0x774dd0c79fb11ea7ecbbffb28d1c005b72790577adbeeeb881c2a180e2feaadf", + "sourceCodeHash": "0x2d36fbd2326ceafe89ca1a3b80c223ad7f934e89e996d20b847498f015316bdf" }, "src/L1/OptimismPortalInterop.sol:OptimismPortalInterop": { - "initCodeHash": "0xd59854648bf205dfbea96b483b2937441c32e9ef66b002468c2c14c0d6661728", - "sourceCodeHash": "0xd00b267dcf125e77c10b28c088be4378ec779927e3bcfeb6aa9a7f3d51370490" + "initCodeHash": "0x9c35223461ee313b77eafb727c51300596975b5134ff51847d8fab424b4dcd0b", + "sourceCodeHash": "0xa1ecf8a19638dd24d80f62e2faef132afa15c6945c14133fe58709015fcb14a6" }, "src/L1/ProtocolVersions.sol:ProtocolVersions": { "initCodeHash": "0x5a76c8530cb24cf23d3baacc6eefaac226382af13f1e2a35535d2ec2b0573b29", @@ -36,12 +40,12 @@ "sourceCodeHash": "0xfd56e63e76b1f203cceeb9bbb14396ae803cbbbf7e80ca0ee11fb586321812af" }, "src/L1/SystemConfig.sol:SystemConfig": { - "initCodeHash": "0xd5b8b8eb47763556d9953019d1f81b1d790f15433aa9696b159a3fc45ecee148", - "sourceCodeHash": "0x6bfbc78b0fef2f65beff11a81f924728a7bd439a56986997621099551805aff9" + "initCodeHash": "0x471ac69544fdee81d0734b87151297ad4cf91ca1d43a2c95e9e644c0424eed65", + "sourceCodeHash": "0x8c3ccb8f3718c00c3f9bf7c866a22d87ea18a87a6e9454c31d1c97638107434c" }, "src/L1/SystemConfigInterop.sol:SystemConfigInterop": { - "initCodeHash": "0xbef4696b2dcb6d43c3b3c438338bfb2224a1ea5002ed0612ec36a7821d7e3da2", - "sourceCodeHash": "0x1653aaa4d2b44d34ca1f9f2b4971eeb7594c8c2d27771b1f68b8d38cb79f2368" + "initCodeHash": "0xcdca84b074ddfd6b4e4df1a57d3500c1d48ecf88852af47f6351e9ae24b6fc2a", + "sourceCodeHash": "0x71914fa1408befaef3a06a67404e0ab580d9d945e068ba23063ea6588f0f68a6" }, "src/L2/BaseFeeVault.sol:BaseFeeVault": { "initCodeHash": "0xc403d4c555d8e69a2699e01d192ae7327136701fa02da10a6d75a584b3c364c9", @@ -156,8 +160,8 @@ "sourceCodeHash": "0x03c160168986ffc8d26a90c37366e7ad6da03f49d83449e1f8b3de0f4b590f6f" }, "src/dispute/AnchorStateRegistry.sol:AnchorStateRegistry": { - "initCodeHash": "0x08cc5a5e41eadb6c411fa6387ddc0cf12be360855599dd622cce84c0ba081e77", - "sourceCodeHash": "0xe0aaa79f7184724ff0fba2e92e85f652f936fecd099288edb0a0f6b0e0240f34" + "initCodeHash": "0xb905a31a816dc7354e9153a6cbf08d968c6d631e5383bd64c5ff1825bf284825", + "sourceCodeHash": "0xf785b369133782f614ed792766f5ec05d79b639db29abc2af2a4074f8600fb36" }, "src/dispute/DelayedWETH.sol:DelayedWETH": { "initCodeHash": "0xdd0b5e523f3b53563fe0b6e6165fb73605b14910ffa32a7cbed855cdebab47c6", @@ -196,8 +200,8 @@ "sourceCodeHash": "0x62c9a6182d82692fb9c173ddb0d7978bcff2d1d4dc8cd2f10625e1e65bda6888" }, "src/safe/DeputyGuardianModule.sol:DeputyGuardianModule": { - "initCodeHash": "0x5eaf823d81995ce1f703f26e31049c54c1d4902dd9873a0b4645d470f2f459a2", - "sourceCodeHash": "0x17236a91c4171ae9525eae0e59fa65bb2dc320d62677cfc7d7eb942f182619fb" + "initCodeHash": "0xf0ccc9997a120b2642bf4b15b5c12b041219c5e748c0c5e24af36f9cde62f175", + "sourceCodeHash": "0xa7dc83c6278118b9a7b4633633eeaa7e5b466bf24fdd184925ab620555caf0aa" }, "src/safe/DeputyPauseModule.sol:DeputyPauseModule": { "initCodeHash": "0xa3b7bf0c93b41f39ebc18a81322b90127a633d684ae9f86c2f2a1c48fe7f1372", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/AnchorStateRegistry.json b/packages/contracts-bedrock/snapshots/storageLayout/AnchorStateRegistry.json index fac376a90d126..4d981001f8fba 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/AnchorStateRegistry.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/AnchorStateRegistry.json @@ -27,25 +27,39 @@ "slot": "1", "type": "contract IDisputeGameFactory" }, - { - "bytes": "20", - "label": "portal", - "offset": 0, - "slot": "2", - "type": "contract IOptimismPortal2" - }, { "bytes": "20", "label": "anchorGame", "offset": 0, - "slot": "3", + "slot": "2", "type": "contract IFaultDisputeGame" }, { "bytes": "64", "label": "startingAnchorRoot", "offset": 0, - "slot": "4", + "slot": "3", "type": "struct OutputRoot" + }, + { + "bytes": "32", + "label": "disputeGameBlacklist", + "offset": 0, + "slot": "5", + "type": "mapping(contract IDisputeGame => bool)" + }, + { + "bytes": "4", + "label": "respectedGameType", + "offset": 0, + "slot": "6", + "type": "GameType" + }, + { + "bytes": "8", + "label": "retirementTimestamp", + "offset": 4, + "slot": "6", + "type": "uint64" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/ETHLockbox.json b/packages/contracts-bedrock/snapshots/storageLayout/ETHLockbox.json new file mode 100644 index 0000000000000..4ba870d921ec9 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/ETHLockbox.json @@ -0,0 +1,37 @@ +[ + { + "bytes": "1", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "uint8" + }, + { + "bytes": "1", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "superchainConfig", + "offset": 2, + "slot": "0", + "type": "contract ISuperchainConfig" + }, + { + "bytes": "32", + "label": "authorizedPortals", + "offset": 0, + "slot": "1", + "type": "mapping(address => bool)" + }, + { + "bytes": "32", + "label": "authorizedLockboxes", + "offset": 0, + "slot": "2", + "type": "mapping(address => bool)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json index 98248abd1d8c6..a8ce2f3c54c12 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json @@ -7,7 +7,7 @@ "type": "struct OPContractsManager.Blueprints" }, { - "bytes": "384", + "bytes": "416", "label": "implementation", "offset": 0, "slot": "9", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json new file mode 100644 index 0000000000000..3c1445a982928 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json @@ -0,0 +1,30 @@ +[ + { + "bytes": "32", + "label": "L1_CONTRACTS_RELEASE", + "offset": 0, + "slot": "0", + "type": "string" + }, + { + "bytes": "288", + "label": "blueprint", + "offset": 0, + "slot": "1", + "type": "struct OPContractsManager.Blueprints" + }, + { + "bytes": "384", + "label": "implementation", + "offset": 0, + "slot": "10", + "type": "struct OPContractsManager.Implementations" + }, + { + "bytes": "1", + "label": "isRC", + "offset": 0, + "slot": "22", + "type": "bool" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPPrestateUpdater.json b/packages/contracts-bedrock/snapshots/storageLayout/OPPrestateUpdater.json new file mode 100644 index 0000000000000..3c1445a982928 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPPrestateUpdater.json @@ -0,0 +1,30 @@ +[ + { + "bytes": "32", + "label": "L1_CONTRACTS_RELEASE", + "offset": 0, + "slot": "0", + "type": "string" + }, + { + "bytes": "288", + "label": "blueprint", + "offset": 0, + "slot": "1", + "type": "struct OPContractsManager.Blueprints" + }, + { + "bytes": "384", + "label": "implementation", + "offset": 0, + "slot": "10", + "type": "struct OPContractsManager.Implementations" + }, + { + "bytes": "1", + "label": "isRC", + "offset": 0, + "slot": "22", + "type": "bool" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json index 654695522e049..a66c4b71b4d16 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json @@ -78,10 +78,10 @@ }, { "bytes": "20", - "label": "disputeGameFactory", + "label": "spacer_56_0_20", "offset": 0, "slot": "56", - "type": "contract IDisputeGameFactory" + "type": "address" }, { "bytes": "32", @@ -92,21 +92,21 @@ }, { "bytes": "32", - "label": "disputeGameBlacklist", + "label": "spacer_58_0_32", "offset": 0, "slot": "58", - "type": "mapping(contract IDisputeGame => bool)" + "type": "bytes32" }, { "bytes": "4", - "label": "respectedGameType", + "label": "spacer_59_0_4", "offset": 0, "slot": "59", "type": "GameType" }, { "bytes": "8", - "label": "respectedGameTypeUpdatedAt", + "label": "spacer_59_4_8", "offset": 4, "slot": "59", "type": "uint64" @@ -124,5 +124,26 @@ "offset": 0, "slot": "61", "type": "uint256" + }, + { + "bytes": "20", + "label": "anchorStateRegistry", + "offset": 0, + "slot": "62", + "type": "contract IAnchorStateRegistry" + }, + { + "bytes": "20", + "label": "ethLockbox", + "offset": 0, + "slot": "63", + "type": "contract IETHLockbox" + }, + { + "bytes": "1", + "label": "superRootsActive", + "offset": 20, + "slot": "63", + "type": "bool" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json index 654695522e049..a66c4b71b4d16 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json @@ -78,10 +78,10 @@ }, { "bytes": "20", - "label": "disputeGameFactory", + "label": "spacer_56_0_20", "offset": 0, "slot": "56", - "type": "contract IDisputeGameFactory" + "type": "address" }, { "bytes": "32", @@ -92,21 +92,21 @@ }, { "bytes": "32", - "label": "disputeGameBlacklist", + "label": "spacer_58_0_32", "offset": 0, "slot": "58", - "type": "mapping(contract IDisputeGame => bool)" + "type": "bytes32" }, { "bytes": "4", - "label": "respectedGameType", + "label": "spacer_59_0_4", "offset": 0, "slot": "59", "type": "GameType" }, { "bytes": "8", - "label": "respectedGameTypeUpdatedAt", + "label": "spacer_59_4_8", "offset": 4, "slot": "59", "type": "uint64" @@ -124,5 +124,26 @@ "offset": 0, "slot": "61", "type": "uint256" + }, + { + "bytes": "20", + "label": "anchorStateRegistry", + "offset": 0, + "slot": "62", + "type": "contract IAnchorStateRegistry" + }, + { + "bytes": "20", + "label": "ethLockbox", + "offset": 0, + "slot": "63", + "type": "contract IETHLockbox" + }, + { + "bytes": "1", + "label": "superRootsActive", + "offset": 20, + "slot": "63", + "type": "bool" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json index ea0d05feb90d5..3d1796e5e4063 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json @@ -110,5 +110,12 @@ "offset": 12, "slot": "106", "type": "uint64" + }, + { + "bytes": "32", + "label": "l2ChainId", + "offset": 0, + "slot": "107", + "type": "uint256" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json index ea0d05feb90d5..3d1796e5e4063 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json @@ -110,5 +110,12 @@ "offset": 12, "slot": "106", "type": "uint64" + }, + { + "bytes": "32", + "label": "l2ChainId", + "offset": 0, + "slot": "107", + "type": "uint256" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L1/ETHLockbox.sol b/packages/contracts-bedrock/src/L1/ETHLockbox.sol new file mode 100644 index 0000000000000..3931c90fb3e0e --- /dev/null +++ b/packages/contracts-bedrock/src/L1/ETHLockbox.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +// Contracts +import { ProxyAdminOwnedBase } from "src/L1/ProxyAdminOwnedBase.sol"; +import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +// Libraries +import { Constants } from "src/libraries/Constants.sol"; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; + +/// @custom:proxied true +/// @title ETHLockbox +/// @notice Manages ETH liquidity locking and unlocking for authorized OptimismPortals, enabling unified ETH liquidity +/// management across chains in the superchain cluster. +contract ETHLockbox is ProxyAdminOwnedBase, Initializable, ISemver { + /// @notice Thrown when the lockbox is paused. + error ETHLockbox_Paused(); + + /// @notice Thrown when the caller is not authorized. + error ETHLockbox_Unauthorized(); + + /// @notice Thrown when the value to unlock is greater than the balance of the lockbox. + error ETHLockbox_InsufficientBalance(); + + /// @notice Thrown when attempting to unlock ETH from the lockbox through a withdrawal transaction. + error ETHLockbox_NoWithdrawalTransactions(); + + /// @notice Thrown when the admin owner of the lockbox is different from the admin owner of the proxy admin. + error ETHLockbox_DifferentProxyAdminOwner(); + + /// @notice Emitted when ETH is locked in the lockbox by an authorized portal. + /// @param portal The address of the portal that locked the ETH. + /// @param amount The amount of ETH locked. + event ETHLocked(address indexed portal, uint256 amount); + + /// @notice Emitted when ETH is unlocked from the lockbox by an authorized portal. + /// @param portal The address of the portal that unlocked the ETH. + /// @param amount The amount of ETH unlocked. + event ETHUnlocked(address indexed portal, uint256 amount); + + /// @notice Emitted when a portal is authorized to lock and unlock ETH. + /// @param portal The address of the portal that was authorized. + event PortalAuthorized(address indexed portal); + + /// @notice Emitted when an ETH lockbox is authorized to migrate its liquidity to the current ETH lockbox. + /// @param lockbox The address of the ETH lockbox that was authorized. + event LockboxAuthorized(address indexed lockbox); + + /// @notice Emitted when ETH liquidity is migrated from the current ETH lockbox to another. + /// @param lockbox The address of the ETH lockbox that was migrated. + event LiquidityMigrated(address indexed lockbox, uint256 amount); + + /// @notice Emitted when ETH liquidity is received during an authorized lockbox migration. + /// @param lockbox The address of the ETH lockbox that received the liquidity. + /// @param amount The amount of ETH received. + event LiquidityReceived(address indexed lockbox, uint256 amount); + + /// @notice The address of the SuperchainConfig contract. + ISuperchainConfig public superchainConfig; + + /// @notice Mapping of authorized portals. + mapping(address => bool) public authorizedPortals; + + /// @notice Mapping of authorized lockboxes. + mapping(address => bool) public authorizedLockboxes; + + /// @notice Semantic version. + /// @custom:semver 0.0.1 + function version() public view virtual returns (string memory) { + return "0.0.1"; + } + + /// @notice Constructs the ETHLockbox contract. + constructor() { + _disableInitializers(); + } + + /// @notice Initializer. + /// @param _superchainConfig The address of the SuperchainConfig contract. + /// @param _portals The addresses of the portals to authorize. + function initialize( + ISuperchainConfig _superchainConfig, + IOptimismPortal[] calldata _portals + ) + external + initializer + { + superchainConfig = ISuperchainConfig(_superchainConfig); + for (uint256 i; i < _portals.length; i++) { + _authorizePortal(address(_portals[i])); + } + } + + /// @notice Authorizes a portal to lock and unlock ETH. + /// @param _portal The address of the portal to authorize. + function authorizePortal(IOptimismPortal _portal) external { + if (msg.sender != proxyAdminOwner()) revert ETHLockbox_Unauthorized(); + _authorizePortal(address(_portal)); + } + + /// @notice Getter for the current paused status. + function paused() public view returns (bool) { + return superchainConfig.paused(); + } + + /// @notice Receives the ETH liquidity migrated from an authorized lockbox. + function receiveLiquidity() external payable { + if (!authorizedLockboxes[msg.sender]) revert ETHLockbox_Unauthorized(); + emit LiquidityReceived(msg.sender, msg.value); + } + + /// @notice Locks ETH in the lockbox. + /// Called by an authorized portal on a deposit to lock the ETH value. + function lockETH() external payable { + if (!authorizedPortals[msg.sender]) revert ETHLockbox_Unauthorized(); + emit ETHLocked(msg.sender, msg.value); + } + + /// @notice Unlocks ETH from the lockbox. + /// Called by an authorized portal when finalizing a withdrawal that requires ETH. + /// Cannot be called if the lockbox is paused. + /// @param _value The amount of ETH to unlock. + function unlockETH(uint256 _value) external { + if (paused()) revert ETHLockbox_Paused(); + if (!authorizedPortals[msg.sender]) revert ETHLockbox_Unauthorized(); + if (_value > address(this).balance) revert ETHLockbox_InsufficientBalance(); + /// NOTE: Check l2Sender is not set to avoid this function to be called as a target on a withdrawal transaction + if (IOptimismPortal(payable(msg.sender)).l2Sender() != Constants.DEFAULT_L2_SENDER) { + revert ETHLockbox_NoWithdrawalTransactions(); + } + + // Using `donateETH` to avoid triggering a deposit + IOptimismPortal(payable(msg.sender)).donateETH{ value: _value }(); + emit ETHUnlocked(msg.sender, _value); + } + + /// @notice Authorizes an ETH lockbox to migrate its liquidity to the current ETH lockbox. + /// @param _lockbox The address of the ETH lockbox to authorize. + function authorizeLockbox(IETHLockbox _lockbox) external { + if (msg.sender != proxyAdminOwner()) revert ETHLockbox_Unauthorized(); + if (!_sameProxyAdminOwner(address(_lockbox))) revert ETHLockbox_DifferentProxyAdminOwner(); + + authorizedLockboxes[address(_lockbox)] = true; + emit LockboxAuthorized(address(_lockbox)); + } + + /// @notice Migrates liquidity from the current ETH lockbox to another. + /// @dev Must be called atomically with `OptimismPortal.updateLockbox()` in the same + /// transaction batch, or otherwise the OptimismPortal may not be able to unlock ETH + /// from the ETHLockbox on finalized withdrawals. + /// @param _lockbox The address of the ETH lockbox to migrate liquidity to. + function migrateLiquidity(IETHLockbox _lockbox) external { + if (msg.sender != proxyAdminOwner()) revert ETHLockbox_Unauthorized(); + if (!_sameProxyAdminOwner(address(_lockbox))) revert ETHLockbox_DifferentProxyAdminOwner(); + + IETHLockbox(_lockbox).receiveLiquidity{ value: address(this).balance }(); + emit LiquidityMigrated(address(_lockbox), address(this).balance); + } + + /// @notice Authorizes a portal to lock and unlock ETH. + /// @param _portal The address of the portal to authorize. + function _authorizePortal(address _portal) internal { + if (!_sameProxyAdminOwner(_portal)) revert ETHLockbox_DifferentProxyAdminOwner(); + authorizedPortals[_portal] = true; + emit PortalAuthorized(_portal); + } +} diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 8aaabee8ef251..50f80f588c727 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.15; import { Blueprint } from "src/libraries/Blueprint.sol"; import { Constants } from "src/libraries/Constants.sol"; import { Bytes } from "src/libraries/Bytes.sol"; -import { Claim, Duration, GameType, GameTypes, OutputRoot } from "src/dispute/lib/Types.sol"; +import { Claim, Duration, GameType, GameTypes, OutputRoot, Hash } from "src/dispute/lib/Types.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; // Interfaces @@ -17,13 +17,12 @@ import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.so import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; -import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; @@ -31,6 +30,7 @@ import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; import { IHasSuperchainConfig } from "interfaces/L1/IHasSuperchainConfig.sol"; import { ISystemConfigInterop } from "interfaces/L1/ISystemConfigInterop.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; contract OPContractsManagerContractsContainer { /// @notice Addresses of the Blueprint contracts. @@ -298,6 +298,7 @@ contract OPContractsManagerGameTypeAdder is OPContractsManagerBase { // This conversion is safe because the GameType is a uint32, which will always fit in an int256. int256 gameTypeInt = int256(uint256(gameConfig.disputeGameType.raw())); + // Ensure that the game configs are added in ascending order, and not duplicated. if (lastGameConfig >= gameTypeInt) revert OPContractsManager.InvalidGameConfigs(); lastGameConfig = gameTypeInt; @@ -309,6 +310,7 @@ contract OPContractsManagerGameTypeAdder is OPContractsManagerBase { getGameImplementation(getDisputeGameFactory(gameConfig.systemConfig), GameTypes.PERMISSIONED_CANNON) ) ); + // Pull out the chain ID. uint256 l2ChainId = getL2ChainId(pdg); @@ -526,34 +528,12 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @dev This function is intended to be called via DELEGATECALL from the Upgrade Controller Safe function upgrade(OPContractsManager.OpChainConfig[] memory _opChainConfigs) external virtual { OPContractsManager.Implementations memory impls = getImplementations(); - OPContractsManager.Blueprints memory bps = getBlueprints(); for (uint256 i = 0; i < _opChainConfigs.length; i++) { assertValidOpChainConfig(_opChainConfigs[i]); ISystemConfig.Addresses memory opChainAddrs = _opChainConfigs[i].systemConfigProxy.getAddresses(); - // -------- Upgrade SystemConfig to Isthmus implementation -------- - upgradeTo( - _opChainConfigs[i].proxyAdmin, address(_opChainConfigs[i].systemConfigProxy), impls.systemConfigImpl - ); - - // -------- Upgrade Contracts Stored in SystemConfig -------- - - // OptimismPortal and L1CrossDomainMessenger are being upgraded to include the fixes - // for EIP-7623 (minimum gas limits for L1 -> L2 messages). - upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.optimismPortal, impls.optimismPortalImpl); - upgradeTo( - _opChainConfigs[i].proxyAdmin, opChainAddrs.l1CrossDomainMessenger, impls.l1CrossDomainMessengerImpl - ); - - // L1ERC721Bridge and L1StandardBridge are being upgraded to include the tweaks to the - // EOA checking code for EIP-7702 (code length == 23). - upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.l1ERC721Bridge, impls.l1ERC721BridgeImpl); - upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.l1StandardBridge, impls.l1StandardBridgeImpl); - - // -------- Discover and Upgrade Proofs Contracts -------- - - // All chains have the Permissioned Dispute Game. + // All chains have the PermissionedDisputeGame, grab that. IPermissionedDisputeGame permissionedDisputeGame = IPermissionedDisputeGame( address( getGameImplementation( @@ -562,37 +542,136 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { ) ); - // We're also going to need the l2ChainId below, so we cache it in the outer scope. + // Grab the L2 chain ID from the PermissionedDisputeGame. uint256 l2ChainId = getL2ChainId(IFaultDisputeGame(address(permissionedDisputeGame))); - deployAndSetNewGameImpl({ - _l2ChainId: l2ChainId, - _disputeGame: IDisputeGame(address(permissionedDisputeGame)), - _gameType: GameTypes.PERMISSIONED_CANNON, - _opChainConfig: _opChainConfigs[i], - _implementations: impls, - _blueprints: bps, - _opChainAddrs: opChainAddrs - }); + // Grab the current respectedGameType from the OptimismPortal contract before the upgrade. + GameType respectedGameType = IOptimismPortal(payable(opChainAddrs.optimismPortal)).respectedGameType(); + + // Grab the current SuperchainConfig from the OptimismPortal contract before the upgrade. + ISuperchainConfig superchainConfig = + IOptimismPortal(payable(opChainAddrs.optimismPortal)).superchainConfig(); - // Now retrieve the permissionless game. If it exists, replace its implementation. - IFaultDisputeGame permissionlessDisputeGame = IFaultDisputeGame( - address(getGameImplementation(IDisputeGameFactory(opChainAddrs.disputeGameFactory), GameTypes.CANNON)) + // Start by upgrading the SystemConfig contract to have the l2ChainId. + upgradeToAndCall( + _opChainConfigs[i].proxyAdmin, + address(_opChainConfigs[i].systemConfigProxy), + impls.systemConfigImpl, + abi.encodeCall(ISystemConfig.upgrade, (l2ChainId)) ); - if (address(permissionlessDisputeGame) != address(0)) { - // Deploy and set a new permissionless game to update its prestate + // Separate context to avoid stack too deep. + IAnchorStateRegistry newAnchorStateRegistryProxy; + { + // Deploy a new AnchorStateRegistry contract. + // We use the SOT suffix to avoid CREATE2 conflicts with the existing ASR. + newAnchorStateRegistryProxy = IAnchorStateRegistry( + deployProxy({ + _l2ChainId: l2ChainId, + _proxyAdmin: _opChainConfigs[i].proxyAdmin, + _saltMixer: reusableSaltMixer(_opChainConfigs[i]), + _contractName: "AnchorStateRegistry-SOT" + }) + ); + + // Separate context to avoid stack too deep. + { + // Get the existing anchor root from the old AnchorStateRegistry contract. + // Get the AnchorStateRegistry from the PermissionedDisputeGame. + (Hash root, uint256 l2BlockNumber) = getAnchorStateRegistry( + IFaultDisputeGame(address(permissionedDisputeGame)) + ).anchors(respectedGameType); + + // Upgrade and initialize the AnchorStateRegistry contract. + // Since this is a net-new contract, we need to initialize it. + upgradeToAndCall( + _opChainConfigs[i].proxyAdmin, + address(newAnchorStateRegistryProxy), + impls.anchorStateRegistryImpl, + abi.encodeCall( + IAnchorStateRegistry.initialize, + ( + superchainConfig, + IDisputeGameFactory(opChainAddrs.disputeGameFactory), + OutputRoot({ root: root, l2BlockNumber: l2BlockNumber }), + respectedGameType + ) + ) + ); + } + } + + // Upgrade the OptimismPortal contract implementation. + upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.optimismPortal, impls.optimismPortalImpl); + + // Separate context to avoid stack too deep. + { + // Deploy the ETHLockbox proxy. + IETHLockbox ethLockbox; + { + ethLockbox = IETHLockbox( + deployProxy({ + _l2ChainId: l2ChainId, + _proxyAdmin: _opChainConfigs[i].proxyAdmin, + _saltMixer: reusableSaltMixer(_opChainConfigs[i]), + _contractName: "ETHLockbox" + }) + ); + + // Initialize the ETHLockbox setting the OptimismPortal as an authorized portal. + IOptimismPortal[] memory portals = new IOptimismPortal[](1); + portals[0] = IOptimismPortal(payable(opChainAddrs.optimismPortal)); + upgradeToAndCall( + _opChainConfigs[i].proxyAdmin, + address(ethLockbox), + impls.ethLockboxImpl, + abi.encodeCall(IETHLockbox.initialize, (superchainConfig, portals)) + ); + } + + // Call `upgrade` on the OptimismPortal contract. + IOptimismPortal(payable(opChainAddrs.optimismPortal)).upgrade( + newAnchorStateRegistryProxy, ethLockbox, _opChainConfigs[i].disputeGameUsesSuperRoots + ); + } + + // We also need to redeploy the dispute games because the AnchorStateRegistry is new. + // Separate context to avoid stack too deep. + { + // Deploy and set a new permissioned game to update its prestate. deployAndSetNewGameImpl({ _l2ChainId: l2ChainId, - _disputeGame: IDisputeGame(address(permissionlessDisputeGame)), - _gameType: GameTypes.CANNON, + _disputeGame: IDisputeGame(address(permissionedDisputeGame)), + _newAnchorStateRegistryProxy: newAnchorStateRegistryProxy, + _gameType: GameTypes.PERMISSIONED_CANNON, _opChainConfig: _opChainConfigs[i], - _implementations: impls, - _blueprints: bps, _opChainAddrs: opChainAddrs }); } + // Separate context to avoid stack too deep. + { + // Now retrieve the permissionless game. + IFaultDisputeGame permissionlessDisputeGame = IFaultDisputeGame( + address( + getGameImplementation(IDisputeGameFactory(opChainAddrs.disputeGameFactory), GameTypes.CANNON) + ) + ); + + // If it exists, replace its implementation. + if (address(permissionlessDisputeGame) != address(0)) { + // Deploy and set a new permissionless game to update its prestate + deployAndSetNewGameImpl({ + _l2ChainId: l2ChainId, + _disputeGame: IDisputeGame(address(permissionlessDisputeGame)), + _newAnchorStateRegistryProxy: newAnchorStateRegistryProxy, + _gameType: GameTypes.CANNON, + _opChainConfig: _opChainConfigs[i], + _opChainAddrs: opChainAddrs + }); + } + } + // Emit the upgraded event with the address of the caller. Since this will be a delegatecall, // the caller will be the value of the ADDRESS opcode. emit Upgraded(l2ChainId, _opChainConfigs[i].systemConfigProxy, address(this)); @@ -621,32 +700,33 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @notice Deploys and sets a new dispute game implementation /// @param _l2ChainId The L2 chain ID /// @param _disputeGame The current dispute game implementation + /// @param _newAnchorStateRegistryProxy The new anchor state registry proxy /// @param _gameType The type of game to deploy /// @param _opChainConfig The OP chain configuration - /// @param _blueprints The blueprint addresses - /// @param _implementations The implementation addresses /// @param _opChainAddrs The OP chain addresses function deployAndSetNewGameImpl( uint256 _l2ChainId, IDisputeGame _disputeGame, + IAnchorStateRegistry _newAnchorStateRegistryProxy, GameType _gameType, OPContractsManager.OpChainConfig memory _opChainConfig, - OPContractsManager.Blueprints memory _blueprints, - OPContractsManager.Implementations memory _implementations, ISystemConfig.Addresses memory _opChainAddrs ) internal { + OPContractsManager.Blueprints memory bps = getBlueprints(); + OPContractsManager.Implementations memory impls = getImplementations(); + // Get the constructor params for the game IFaultDisputeGame.GameConstructorParams memory params = getGameConstructorParams(IFaultDisputeGame(address(_disputeGame))); // Modify the params with the new vm values. - params.vm = IBigStepper(_implementations.mipsImpl); - if (Claim.unwrap(_opChainConfig.absolutePrestate) == bytes32(0)) { - revert OPContractsManager.PrestateNotSet(); + params.anchorStateRegistry = IAnchorStateRegistry(address(_newAnchorStateRegistryProxy)); + params.vm = IBigStepper(impls.mipsImpl); + if (Claim.unwrap(_opChainConfig.absolutePrestate) != bytes32(0)) { + params.absolutePrestate = _opChainConfig.absolutePrestate; } - params.absolutePrestate = _opChainConfig.absolutePrestate; IDisputeGame newGame; if (GameType.unwrap(_gameType) == GameType.unwrap(GameTypes.PERMISSIONED_CANNON)) { @@ -654,8 +734,8 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { address challenger = getChallenger(IPermissionedDisputeGame(address(_disputeGame))); newGame = IDisputeGame( Blueprint.deployFrom( - _blueprints.permissionedDisputeGame1, - _blueprints.permissionedDisputeGame2, + bps.permissionedDisputeGame1, + bps.permissionedDisputeGame2, computeSalt(_l2ChainId, reusableSaltMixer(_opChainConfig), "PermissionedDisputeGame"), encodePermissionedFDGConstructor(params, proposer, challenger) ) @@ -663,8 +743,8 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { } else { newGame = IDisputeGame( Blueprint.deployFrom( - _blueprints.permissionlessDisputeGame1, - _blueprints.permissionlessDisputeGame2, + bps.permissionlessDisputeGame1, + bps.permissionlessDisputeGame2, computeSalt(_l2ChainId, reusableSaltMixer(_opChainConfig), "PermissionlessDisputeGame"), encodePermissionlessFDGConstructor(params) ) @@ -727,9 +807,11 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { // Deploy ERC-1967 proxied contracts. output.l1ERC721BridgeProxy = IL1ERC721Bridge(deployProxy(_input.l2ChainId, output.opChainProxyAdmin, _input.saltMixer, "L1ERC721Bridge")); - output.optimismPortalProxy = IOptimismPortal2( + output.optimismPortalProxy = IOptimismPortal( payable(deployProxy(_input.l2ChainId, output.opChainProxyAdmin, _input.saltMixer, "OptimismPortal")) ); + output.ethLockboxProxy = + IETHLockbox(deployProxy(_input.l2ChainId, output.opChainProxyAdmin, _input.saltMixer, "ETHLockbox")); output.systemConfigProxy = ISystemConfig(deployProxy(_input.l2ChainId, output.opChainProxyAdmin, _input.saltMixer, "SystemConfig")); output.optimismMintableERC20FactoryProxy = IOptimismMintableERC20Factory( @@ -806,11 +888,17 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { output.opChainProxyAdmin, address(output.l1ERC721BridgeProxy), implementation.l1ERC721BridgeImpl, data ); - data = encodeOptimismPortalInitializer(output, _superchainConfig); + data = encodeOptimismPortalInitializer(_input, output, _superchainConfig); upgradeToAndCall( output.opChainProxyAdmin, address(output.optimismPortalProxy), implementation.optimismPortalImpl, data ); + // Initialize the ETHLockbox. + IOptimismPortal[] memory portals = new IOptimismPortal[](1); + portals[0] = output.optimismPortalProxy; + data = encodeETHLockboxInitializer(_superchainConfig, portals); + upgradeToAndCall(output.opChainProxyAdmin, address(output.ethLockboxProxy), implementation.ethLockboxImpl, data); + data = encodeSystemConfigInitializer(_input, output); upgradeToAndCall( output.opChainProxyAdmin, address(output.systemConfigProxy), implementation.systemConfigImpl, data @@ -955,6 +1043,7 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { /// @notice Helper method for encoding the OptimismPortal initializer data. function encodeOptimismPortalInitializer( + OPContractsManager.DeployInput memory _input, OPContractsManager.DeployOutput memory _output, ISuperchainConfig _superchainConfig ) @@ -964,16 +1053,30 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { returns (bytes memory) { return abi.encodeCall( - IOptimismPortal2.initialize, + IOptimismPortal.initialize, ( - _output.disputeGameFactoryProxy, _output.systemConfigProxy, _superchainConfig, - GameTypes.PERMISSIONED_CANNON + _output.anchorStateRegistryProxy, + _output.ethLockboxProxy, + _input.disputeGameUsesSuperRoots ) ); } + /// @notice Helper method for encoding the ETHLockbox initializer data. + function encodeETHLockboxInitializer( + ISuperchainConfig _superchainConfig, + IOptimismPortal[] memory _portals + ) + internal + view + virtual + returns (bytes memory) + { + return abi.encodeCall(IETHLockbox.initialize, (_superchainConfig, _portals)); + } + /// @notice Helper method for encoding the SystemConfig initializer data. function encodeSystemConfigInitializer( OPContractsManager.DeployInput memory _input, @@ -998,7 +1101,8 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { _input.roles.unsafeBlockSigner, referenceResourceConfig, chainIdToBatchInboxAddress(_input.l2ChainId), - opChainAddrs + opChainAddrs, + _input.l2ChainId ) ); } @@ -1058,7 +1162,7 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { OutputRoot memory startingAnchorRoot = abi.decode(_input.startingAnchorRoot, (OutputRoot)); return abi.encodeCall( IAnchorStateRegistry.initialize, - (_superchainConfig, _output.disputeGameFactoryProxy, _output.optimismPortalProxy, startingAnchorRoot) + (_superchainConfig, _output.disputeGameFactoryProxy, startingAnchorRoot, GameTypes.PERMISSIONED_CANNON) ); } @@ -1114,7 +1218,8 @@ contract OPContractsManagerDeployerInterop is OPContractsManagerDeployer { referenceResourceConfig, chainIdToBatchInboxAddress(_input.l2ChainId), opChainAddrs, - dependencyManager + dependencyManager, + _input.l2ChainId ) ); } @@ -1145,6 +1250,7 @@ contract OPContractsManager is ISemver { string saltMixer; uint64 gasLimit; // Configurable dispute game parameters. + bool disputeGameUsesSuperRoots; GameType disputeGameType; Claim disputeAbsolutePrestate; uint256 disputeMaxGameDepth; @@ -1162,8 +1268,9 @@ contract OPContractsManager is ISemver { IOptimismMintableERC20Factory optimismMintableERC20FactoryProxy; IL1StandardBridge l1StandardBridgeProxy; IL1CrossDomainMessenger l1CrossDomainMessengerProxy; + IETHLockbox ethLockboxProxy; // Fault proof contracts below. - IOptimismPortal2 optimismPortalProxy; + IOptimismPortal optimismPortalProxy; IDisputeGameFactory disputeGameFactoryProxy; IAnchorStateRegistry anchorStateRegistryProxy; IFaultDisputeGame faultDisputeGame; @@ -1195,6 +1302,7 @@ contract OPContractsManager is ISemver { address protocolVersionsImpl; address l1ERC721BridgeImpl; address optimismPortalImpl; + address ethLockboxImpl; address systemConfigImpl; address optimismMintableERC20FactoryImpl; address l1CrossDomainMessengerImpl; @@ -1210,6 +1318,7 @@ contract OPContractsManager is ISemver { ISystemConfig systemConfigProxy; IProxyAdmin proxyAdmin; Claim absolutePrestate; + bool disputeGameUsesSuperRoots; } struct AddGameInput { @@ -1235,9 +1344,9 @@ contract OPContractsManager is ISemver { // -------- Constants and Variables -------- - /// @custom:semver 1.9.0 + /// @custom:semver 1.10.0 function version() public pure virtual returns (string memory) { - return "1.9.0"; + return "1.10.0"; } OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder; @@ -1312,9 +1421,6 @@ contract OPContractsManager is ISemver { /// @notice Thrown when the SuperchainProxyAdmin does not match the SuperchainConfig's admin. error SuperchainProxyAdminMismatch(); - /// @notice Thrown when a prestate is not set for a game. - error PrestateNotSet(); - /// @notice Thrown when the prestate of a permissioned disputed game is 0. error PrestateRequired(); diff --git a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol index 0b43241ea4f9f..5b8cef6cc83dd 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol @@ -2,11 +2,12 @@ pragma solidity 0.8.15; // Contracts +import { ProxyAdminOwnedBase } from "src/L1/ProxyAdminOwnedBase.sol"; import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import { ResourceMetering } from "src/L1/ResourceMetering.sol"; +import { ReinitializableBase } from "src/universal/ReinitializableBase.sol"; // Libraries -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { EOA } from "src/libraries/EOA.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; import { Constants } from "src/libraries/Constants.sol"; @@ -14,59 +15,35 @@ import { Types } from "src/libraries/Types.sol"; import { Hashing } from "src/libraries/Hashing.sol"; import { SecureMerkleTrie } from "src/libraries/trie/SecureMerkleTrie.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; -import { - BadTarget, - LargeCalldata, - SmallGasLimit, - Unauthorized, - CallPaused, - GasEstimation, - NonReentrant, - InvalidProof, - InvalidGameType, - InvalidDisputeGame, - InvalidMerkleProof, - Blacklisted, - Unproven, - ProposalNotValidated, - AlreadyFinalized, - LegacyGame -} from "src/libraries/PortalErrors.sol"; -import { GameStatus, GameType, Claim, Timestamp } from "src/dispute/lib/Types.sol"; +import { GameStatus, GameType } from "src/dispute/lib/Types.sol"; // Interfaces -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; /// @custom:proxied true /// @title OptimismPortal2 /// @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 OptimismPortal2 is Initializable, ResourceMetering, ISemver { - /// @notice Allows for interactions with non standard ERC20 tokens. - using SafeERC20 for IERC20; - +contract OptimismPortal2 is Initializable, ResourceMetering, ReinitializableBase, ProxyAdminOwnedBase, ISemver { /// @notice Represents a proven withdrawal. - /// @custom:field disputeGameProxy The address of the dispute game proxy that the withdrawal was proven against. + /// @custom:field disputeGameProxy Game that the withdrawal was proven against. /// @custom:field timestamp Timestamp at which the withdrawal was proven. struct ProvenWithdrawal { IDisputeGame disputeGameProxy; uint64 timestamp; } - /// @notice The delay between when a withdrawal transaction is proven and when it may be finalized. + /// @notice The delay between when a withdrawal is proven and when it may be finalized. uint256 internal immutable PROOF_MATURITY_DELAY_SECONDS; - /// @notice The delay between when a dispute game is resolved and when a withdrawal proven against it may be - /// finalized. - uint256 internal immutable DISPUTE_GAME_FINALITY_DELAY_SECONDS; - /// @notice Version of the deposit event. uint256 internal constant DEPOSIT_VERSION = 0; @@ -94,7 +71,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// @notice Spacer for backwards compatibility. bool private spacer_53_0_1; - /// @notice Contract of the Superchain Config. + /// @notice Address of the SuperchainConfig contract. ISuperchainConfig public superchainConfig; /// @custom:legacy @@ -102,25 +79,30 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// @notice Spacer taking up the legacy `l2Oracle` address slot. address private spacer_54_0_20; - /// @notice Contract of the SystemConfig. + /// @notice Address of the SystemConfig contract. /// @custom:network-specific ISystemConfig public systemConfig; - /// @notice Address of the DisputeGameFactory. /// @custom:network-specific - IDisputeGameFactory public disputeGameFactory; + /// @custom:legacy + /// @custom:spacer disputeGameFactory + /// @notice Spacer taking up the legacy `disputeGameFactory` address slot. + address private spacer_56_0_20; - /// @notice A mapping of withdrawal hashes to proof submitters to `ProvenWithdrawal` data. + /// @notice A mapping of withdrawal hashes to proof submitters to ProvenWithdrawal data. mapping(bytes32 => mapping(address => ProvenWithdrawal)) public provenWithdrawals; - /// @notice A mapping of dispute game addresses to whether or not they are blacklisted. - mapping(IDisputeGame => bool) public disputeGameBlacklist; + /// @custom:legacy + /// @custom:spacer disputeGameBlacklist + bytes32 private spacer_58_0_32; - /// @notice The game type that the OptimismPortal consults for output proposals. - GameType public respectedGameType; + /// @custom:legacy + /// @custom:spacer respectedGameType + GameType private spacer_59_0_4; - /// @notice The timestamp at which the respected game type was last updated. - uint64 public respectedGameTypeUpdatedAt; + /// @custom:legacy + /// @custom:spacer respectedGameTypeUpdatedAt + uint64 private spacer_59_4_8; /// @notice Mapping of withdrawal hashes to addresses that have submitted a proof for the /// withdrawal. Original OptimismPortal contract only allowed one proof to be submitted @@ -133,12 +115,19 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// @custom:legacy /// @custom:spacer _balance - /// @notice Spacer taking up the legacy `_balance` slot. uint256 private spacer_61_0_32; - /// @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. + /// @notice Address of the AnchorStateRegistry contract. + IAnchorStateRegistry public anchorStateRegistry; + + /// @notice Address of the ETHLockbox contract. + IETHLockbox public ethLockbox; + + /// @notice Whether the OptimismPortal is using Super Roots or Output Roots. + bool public superRootsActive; + + /// @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. @@ -151,8 +140,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// @param to Address that the withdrawal transaction is directed to. event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to); - /// @notice Emitted when a withdrawal transaction is proven. Exists as a separate event to allow for backwards - /// compatibility for tooling that observes the `WithdrawalProven` event. + /// @notice Emitted when a withdrawal transaction is proven. Exists as a separate event to + /// allow for backwards compatibility for tooling that observes the WithdrawalProven + /// event. /// @param withdrawalHash Hash of the withdrawal transaction. /// @param proofSubmitter Address of the proof submitter. event WithdrawalProvenExtension1(bytes32 indexed withdrawalHash, address indexed proofSubmitter); @@ -162,74 +152,143 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// @param success Whether the withdrawal transaction was successful. event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); - /// @notice Emitted when a dispute game is blacklisted by the Guardian. - /// @param disputeGame Address of the dispute game that was blacklisted. - event DisputeGameBlacklisted(IDisputeGame indexed disputeGame); + /// @notice Emitted when the total ETH balance is migrated to the ETHLockbox. + /// @param lockbox The address of the ETHLockbox contract. + /// @param ethBalance Amount of ETH migrated. + event ETHMigrated(address indexed lockbox, uint256 ethBalance); + + /// @notice Emitted when the ETHLockbox contract is updated. + /// @param oldLockbox The address of the old ETHLockbox contract. + /// @param newLockbox The address of the new ETHLockbox contract. + event LockboxUpdated(address oldLockbox, address newLockbox); + + /// @notice Thrown when a withdrawal has already been finalized. + error OptimismPortal_AlreadyFinalized(); + + /// @notice Thrown when the target of a withdrawal is unsafe. + error OptimismPortal_BadTarget(); + + /// @notice Thrown when the calldata for a deposit is too large. + error OptimismPortal_CalldataTooLarge(); + + /// @notice Thrown when the portal is paused. + error OptimismPortal_CallPaused(); + + /// @notice Thrown when a gas estimation transaction is being executed. + error OptimismPortal_GasEstimation(); + + /// @notice Thrown when the gas limit for a deposit is too low. + error OptimismPortal_GasLimitTooLow(); + + /// @notice Thrown when the target of a withdrawal is not a proper dispute game. + error OptimismPortal_ImproperDisputeGame(); + + /// @notice Thrown when a withdrawal has not been proven against a valid dispute game. + error OptimismPortal_InvalidDisputeGame(); + + /// @notice Thrown when a withdrawal has not been proven against a valid merkle proof. + error OptimismPortal_InvalidMerkleProof(); + + /// @notice Thrown when a withdrawal has not been proven against a valid output root proof. + error OptimismPortal_InvalidOutputRootProof(); + + /// @notice Thrown when a withdrawal's timestamp is not greater than the dispute game's creation timestamp. + error OptimismPortal_InvalidProofTimestamp(); + + /// @notice Thrown when the root claim of a dispute game is invalid. + error OptimismPortal_InvalidRootClaim(); + + /// @notice Thrown when a withdrawal is being finalized by a reentrant call. + error OptimismPortal_NoReentrancy(); - /// @notice Emitted when the Guardian changes the respected game type in the portal. - /// @param newGameType The new respected game type. - /// @param updatedAt The timestamp at which the respected game type was updated. - event RespectedGameTypeSet(GameType indexed newGameType, Timestamp indexed updatedAt); + /// @notice Thrown when a withdrawal has not been proven for long enough. + error OptimismPortal_ProofNotOldEnough(); + + /// @notice Thrown when a withdrawal has not been proven. + error OptimismPortal_Unproven(); + + /// @notice Thrown when the caller is not authorized to call the function. + error OptimismPortal_Unauthorized(); + + /// @notice Thrown when the wrong proof method is used. + error OptimismPortal_WrongProofMethod(); + + /// @notice Thrown when a super root proof is invalid. + error OptimismPortal_InvalidSuperRootProof(); + + /// @notice Thrown when an output root index is invalid. + error OptimismPortal_InvalidOutputRootIndex(); + + /// @notice Thrown when an output root chain id is invalid. + error OptimismPortal_InvalidOutputRootChainId(); /// @notice Reverts when paused. modifier whenNotPaused() { - if (paused()) revert CallPaused(); + if (paused()) revert OptimismPortal_CallPaused(); _; } /// @notice Semantic version. - /// @custom:semver 3.14.0 + /// @custom:semver 4.0.0 function version() public pure virtual returns (string memory) { - return "3.14.0"; + return "4.0.0"; } - /// @notice Constructs the OptimismPortal contract. - constructor(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds) { + /// @param _proofMaturityDelaySeconds The proof maturity delay in seconds. + constructor(uint256 _proofMaturityDelaySeconds) ReinitializableBase(2) { PROOF_MATURITY_DELAY_SECONDS = _proofMaturityDelaySeconds; - DISPUTE_GAME_FINALITY_DELAY_SECONDS = _disputeGameFinalityDelaySeconds; - _disableInitializers(); } /// @notice Initializer. - /// @param _disputeGameFactory Contract of the DisputeGameFactory. - /// @param _systemConfig Contract of the SystemConfig. - /// @param _superchainConfig Contract of the SuperchainConfig. + /// @param _systemConfig Address of the SystemConfig. + /// @param _superchainConfig Address of the SuperchainConfig. + /// @param _anchorStateRegistry Address of the AnchorStateRegistry. + /// @param _ethLockbox Contract of the ETHLockbox. + /// @param _superRootsActive Whether the OptimismPortal is using Super Roots or Output Roots. function initialize( - IDisputeGameFactory _disputeGameFactory, ISystemConfig _systemConfig, ISuperchainConfig _superchainConfig, - GameType _initialRespectedGameType + IAnchorStateRegistry _anchorStateRegistry, + IETHLockbox _ethLockbox, + bool _superRootsActive ) external - initializer + reinitializer(initVersion()) { - disputeGameFactory = _disputeGameFactory; systemConfig = _systemConfig; superchainConfig = _superchainConfig; + anchorStateRegistry = _anchorStateRegistry; + ethLockbox = _ethLockbox; + superRootsActive = _superRootsActive; - // Set the `l2Sender` slot, only if it is currently empty. This signals the first initialization of the - // contract. + // Set the l2Sender slot, only if it is currently empty. This signals the first + // initialization of the contract. if (l2Sender == address(0)) { l2Sender = Constants.DEFAULT_L2_SENDER; - - // Set the `respectedGameTypeUpdatedAt` timestamp, to ignore all games of the respected type prior - // to this operation. - respectedGameTypeUpdatedAt = uint64(block.timestamp); - - // Set the initial respected game type - respectedGameType = _initialRespectedGameType; } __ResourceMetering_init(); } - /// @notice Getter function for the address of the guardian. - /// Public getter is legacy and will be removed in the future. Use `SuperchainConfig.guardian()` instead. - /// @return Address of the guardian. - /// @custom:legacy - function guardian() public view returns (address) { - return superchainConfig.guardian(); + /// @notice Upgrades the OptimismPortal contract to have a reference to the AnchorStateRegistry. + /// @param _anchorStateRegistry AnchorStateRegistry contract. + /// @param _ethLockbox ETHLockbox contract. + /// @param _superRootsActive Whether the OptimismPortal is using Super Roots or Output Roots. + function upgrade( + IAnchorStateRegistry _anchorStateRegistry, + IETHLockbox _ethLockbox, + bool _superRootsActive + ) + external + reinitializer(initVersion()) + { + anchorStateRegistry = _anchorStateRegistry; + ethLockbox = _ethLockbox; + superRootsActive = _superRootsActive; + + // Migrate the whole ETH balance to the ETHLockbox. + _migrateLiquidity(); } /// @notice Getter for the current paused status. @@ -242,9 +301,33 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { return PROOF_MATURITY_DELAY_SECONDS; } + /// @notice Getter for the address of the DisputeGameFactory contract. + function disputeGameFactory() public view returns (IDisputeGameFactory) { + return anchorStateRegistry.disputeGameFactory(); + } + + /// @custom:legacy + /// @notice Getter function for the address of the guardian. + function guardian() public view returns (address) { + return superchainConfig.guardian(); + } + + /// @custom:legacy /// @notice Getter for the dispute game finality delay. - function disputeGameFinalityDelaySeconds() public view returns (uint256) { - return DISPUTE_GAME_FINALITY_DELAY_SECONDS; + function disputeGameFinalityDelaySeconds() external view returns (uint256) { + return anchorStateRegistry.disputeGameFinalityDelaySeconds(); + } + + /// @custom:legacy + /// @notice Getter for the respected game type. + function respectedGameType() external view returns (GameType) { + return anchorStateRegistry.respectedGameType(); + } + + /// @custom:legacy + /// @notice Getter for the timestamp at which the respected game type was updated. + function respectedGameTypeUpdatedAt() external view returns (uint64) { + return anchorStateRegistry.retirementTimestamp(); } /// @notice Computes the minimum gas limit for a deposit. @@ -267,28 +350,60 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { } /// @notice Accepts ETH value without triggering a deposit to L2. - /// This function mainly exists for the sake of the migration between the legacy - /// Optimism system and Bedrock. function donateETH() external payable { // Intentionally empty. } - /// @notice Getter for the resource config. - /// Used internally by the ResourceMetering contract. - /// The SystemConfig is the source of truth for the resource config. - /// @return config_ ResourceMetering ResourceConfig - function _resourceConfig() internal view override returns (ResourceMetering.ResourceConfig memory config_) { - IResourceMetering.ResourceConfig memory config = systemConfig.resourceConfig(); - assembly ("memory-safe") { - config_ := config + /// @notice Updates the ETHLockbox contract. + /// @dev This function MUST be called atomically with `ETHLockbox.migrateLiquidity()` + /// in the same transaction batch, or otherwise the OptimismPortal may not be able to + /// unlock ETH from the ETHLockbox on finalized withdrawals. + /// @param _newLockbox The address of the new ETHLockbox contract. + function updateLockbox(IETHLockbox _newLockbox) external { + if (msg.sender != proxyAdminOwner()) revert OptimismPortal_Unauthorized(); + + address oldLockbox = address(ethLockbox); + ethLockbox = _newLockbox; + + emit LockboxUpdated(oldLockbox, address(_newLockbox)); + } + + /// @notice Proves a withdrawal transaction using a Super Root proof. Only callable when the + /// OptimismPortal is using Super Roots (superRootsActive flag is true). + /// @param _tx Withdrawal transaction to finalize. + /// @param _disputeGameProxy Address of the dispute game to prove the withdrawal against. + /// @param _outputRootIndex Index of the target Output Root within the Super Root. + /// @param _superRootProof Inclusion proof of the Output Root within the Super Root. + /// @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser storage root. + /// @param _withdrawalProof Inclusion proof of the withdrawal within the L2ToL1MessagePasser. + function proveWithdrawalTransaction( + Types.WithdrawalTransaction memory _tx, + IDisputeGame _disputeGameProxy, + uint256 _outputRootIndex, + Types.SuperRootProof calldata _superRootProof, + Types.OutputRootProof calldata _outputRootProof, + bytes[] calldata _withdrawalProof + ) + external + whenNotPaused + { + // Make sure that the OptimismPortal is using Super Roots. + if (!superRootsActive) { + revert OptimismPortal_WrongProofMethod(); } + + // Prove the transaction. + _proveWithdrawalTransaction( + _tx, _disputeGameProxy, _outputRootIndex, _superRootProof, _outputRootProof, _withdrawalProof + ); } - /// @notice Proves a withdrawal transaction. + /// @notice Proves a withdrawal transaction using an Output Root proof. Only callable when the + /// OptimismPortal is using Output Roots (superRootsActive flag is false). /// @param _tx Withdrawal transaction to finalize. /// @param _disputeGameIndex Index of the dispute game to prove the withdrawal against. - /// @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser contract's storage root. - /// @param _withdrawalProof Inclusion proof of the withdrawal in L2ToL1MessagePasser contract. + /// @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser storage root. + /// @param _withdrawalProof Inclusion proof of the withdrawal within the L2ToL1MessagePasser. function proveWithdrawalTransaction( Types.WithdrawalTransaction memory _tx, uint256 _disputeGameIndex, @@ -298,47 +413,102 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { external whenNotPaused { - // Prevent users from creating a deposit transaction where this address is the message - // sender on L2. Because this is checked here, we do not need to check again in - // `finalizeWithdrawalTransaction`. - if (_tx.target == address(this)) revert BadTarget(); + // Make sure that the OptimismPortal is using Output Roots. + if (superRootsActive) { + revert OptimismPortal_WrongProofMethod(); + } // Fetch the dispute game proxy from the `DisputeGameFactory` contract. - (GameType gameType,, IDisputeGame gameProxy) = disputeGameFactory.gameAtIndex(_disputeGameIndex); - Claim outputRoot = gameProxy.rootClaim(); - - // The game type of the dispute game must be the respected game type. - if (gameType.raw() != respectedGameType.raw()) revert InvalidGameType(); - - // The game type of the DisputeGame must have been the respected game type at creation. - // eip150-safe - try gameProxy.wasRespectedGameTypeWhenCreated() returns (bool wasRespected_) { - if (!wasRespected_) revert InvalidGameType(); - } catch { - revert LegacyGame(); + (,, IDisputeGame disputeGameProxy) = disputeGameFactory().gameAtIndex(_disputeGameIndex); + + // Create a dummy super root proof to pass into the internal function. Note that this is + // not a valid Super Root proof but it isn't used anywhere in the internal function when + // using Output Roots. + Types.SuperRootProof memory superRootProof; + + // Prove the transaction. + _proveWithdrawalTransaction(_tx, disputeGameProxy, 0, superRootProof, _outputRootProof, _withdrawalProof); + } + + /// @notice Internal function for proving a withdrawal transaction, used by both the Super Root + /// and Output Root proof functions. Will eventually be replaced with a single function + /// when the Output Root proof method is deprecated. + /// @param _tx Withdrawal transaction to prove. + /// @param _disputeGameProxy Address of the dispute game to prove the withdrawal against. + /// @param _outputRootIndex Index of the target Output Root within the Super Root. + /// @param _superRootProof Inclusion proof of the Output Root within the Super Root. + /// @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser storage root. + /// @param _withdrawalProof Inclusion proof of the withdrawal within the L2ToL1MessagePasser. + function _proveWithdrawalTransaction( + Types.WithdrawalTransaction memory _tx, + IDisputeGame _disputeGameProxy, + uint256 _outputRootIndex, + Types.SuperRootProof memory _superRootProof, + Types.OutputRootProof memory _outputRootProof, + bytes[] memory _withdrawalProof + ) + internal + { + // Make sure that the target address is safe. + if (_isUnsafeTarget(_tx.target)) { + revert OptimismPortal_BadTarget(); } - // Game must have been created after the respected game type was updated. This check is a - // strict inequality because we want to prevent users from being able to prove or finalize - // withdrawals against games that were created in the same block that the retirement - // timestamp was set. If the retirement timestamp and game type are changed in the same - // block, such games could still be considered valid even if they used the old game type - // that we intended to invalidate. - require( - gameProxy.createdAt().raw() > respectedGameTypeUpdatedAt, - "OptimismPortal: dispute game created before respected game type was updated" - ); + // Game must be a Proper Game. + if (!anchorStateRegistry.isGameProper(_disputeGameProxy)) { + revert OptimismPortal_ImproperDisputeGame(); + } + + // Game must have been respected game type when created. + if (!anchorStateRegistry.isGameRespected(_disputeGameProxy)) { + revert OptimismPortal_InvalidDisputeGame(); + } + + // Game must not have resolved in favor of the Challenger (invalid root claim). + if (_disputeGameProxy.status() == GameStatus.CHALLENGER_WINS) { + revert OptimismPortal_InvalidDisputeGame(); + } + + // As a sanity check, we make sure that the current timestamp is not less than or equal to + // the dispute game's creation timestamp. Not strictly necessary but extra layer of + // safety against weird bugs. Note that this blocks withdrawals from being proven in the + // same block that a dispute game is created. + if (block.timestamp <= _disputeGameProxy.createdAt().raw()) { + revert OptimismPortal_InvalidProofTimestamp(); + } - // Verify that the output root can be generated with the elements in the proof. - if (outputRoot.raw() != Hashing.hashOutputRootProof(_outputRootProof)) revert InvalidProof(); + // Validate the provided Output Root and/or Super Root proof depending on proof method. + if (superRootsActive) { + // Verify that the super root can be generated with the elements in the proof. + if (_disputeGameProxy.rootClaim().raw() != Hashing.hashSuperRootProof(_superRootProof)) { + revert OptimismPortal_InvalidSuperRootProof(); + } + + // Check that the index exists in the super root proof. + if (_outputRootIndex >= _superRootProof.outputRoots.length) { + revert OptimismPortal_InvalidOutputRootIndex(); + } + + // Check that the output root has the correct chain id. + Types.OutputRootWithChainId memory outputRoot = _superRootProof.outputRoots[_outputRootIndex]; + if (outputRoot.chainId != systemConfig.l2ChainId()) { + revert OptimismPortal_InvalidOutputRootChainId(); + } + + // Verify that the output root can be generated with the elements in the proof. + if (outputRoot.root != Hashing.hashOutputRootProof(_outputRootProof)) { + revert OptimismPortal_InvalidOutputRootProof(); + } + } else { + // Verify that the output root can be generated with the elements in the proof. + if (_disputeGameProxy.rootClaim().raw() != Hashing.hashOutputRootProof(_outputRootProof)) { + revert OptimismPortal_InvalidOutputRootProof(); + } + } // Load the ProvenWithdrawal into memory, using the withdrawal hash as a unique identifier. bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx); - // We do not allow for proving withdrawals against dispute games that have resolved against the favor - // of the root claim. - if (gameProxy.status() == GameStatus.CHALLENGER_WINS) revert InvalidDisputeGame(); - // Compute the storage slot of the withdrawal hash in the L2ToL1MessagePasser contract. // Refer to the Solidity documentation for more information on how storage layouts are // computed for mappings. @@ -360,21 +530,22 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { _proof: _withdrawalProof, _root: _outputRootProof.messagePasserStorageRoot }) == false - ) revert InvalidMerkleProof(); + ) { + revert OptimismPortal_InvalidMerkleProof(); + } - // Designate the withdrawalHash as proven by storing the `disputeGameProxy` & `timestamp` in the - // `provenWithdrawals` mapping. A `withdrawalHash` can only be proven once unless the dispute game it proved - // against resolves against the favor of the root claim. + // Designate the withdrawalHash as proven by storing the disputeGameProxy and timestamp in + // the provenWithdrawals mapping. A given user may re-prove a withdrawalHash multiple + // times, but each proof will reset the proof timer. provenWithdrawals[withdrawalHash][msg.sender] = - ProvenWithdrawal({ disputeGameProxy: gameProxy, timestamp: uint64(block.timestamp) }); - - // Emit a `WithdrawalProven` event. - emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target); - // Emit a `WithdrawalProvenExtension1` event. - emit WithdrawalProvenExtension1(withdrawalHash, msg.sender); + ProvenWithdrawal({ disputeGameProxy: _disputeGameProxy, timestamp: uint64(block.timestamp) }); // Add the proof submitter to the list of proof submitters for this withdrawal hash. proofSubmitters[withdrawalHash].push(msg.sender); + + // Emit a WithdrawalProven events. + emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target); + emit WithdrawalProvenExtension1(withdrawalHash, msg.sender); } /// @notice Finalizes a withdrawal transaction. @@ -396,9 +567,16 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { // Make sure that the l2Sender has not yet been set. The l2Sender is set to a value other // than the default value when a withdrawal transaction is being finalized. This check is // a defacto reentrancy guard. - if (l2Sender != Constants.DEFAULT_L2_SENDER) revert NonReentrant(); + if (l2Sender != Constants.DEFAULT_L2_SENDER) { + revert OptimismPortal_NoReentrancy(); + } + + // Make sure that the target address is safe. + if (_isUnsafeTarget(_tx.target)) { + revert OptimismPortal_BadTarget(); + } - // Compute the withdrawal hash. + // Grab the withdrawal. bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx); // Check that the withdrawal can be finalized. @@ -407,6 +585,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { // Mark the withdrawal as finalized so it can't be replayed. finalizedWithdrawals[withdrawalHash] = true; + // Unlock the ETH from the ETHLockbox. + if (_tx.value > 0) ethLockbox.unlockETH(_tx.value); + // Set the l2Sender so contracts know who triggered this withdrawal on L2. l2Sender = _tx.sender; @@ -430,14 +611,61 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { // sub call to the target contract if the minimum gas limit specified by the user would not // be sufficient to execute the sub call. if (!success && tx.origin == Constants.ESTIMATION_ADDRESS) { - revert GasEstimation(); + revert OptimismPortal_GasEstimation(); } } + /// @notice Checks that a withdrawal has been proven and is ready to be finalized. + /// @param _withdrawalHash Hash of the withdrawal. + /// @param _proofSubmitter Address of the proof submitter. + function checkWithdrawal(bytes32 _withdrawalHash, address _proofSubmitter) public view { + // Grab the withdrawal and dispute game proxy. + ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[_withdrawalHash][_proofSubmitter]; + IDisputeGame disputeGameProxy = provenWithdrawal.disputeGameProxy; + + // Check that this withdrawal has not already been finalized, this is replay protection. + if (finalizedWithdrawals[_withdrawalHash]) { + revert OptimismPortal_AlreadyFinalized(); + } + + // A withdrawal can only be finalized if it has been proven. We know that a withdrawal has + // been proven at least once when its timestamp is non-zero. Unproven withdrawals will have + // a timestamp of zero. + if (provenWithdrawal.timestamp == 0) { + revert OptimismPortal_Unproven(); + } + + // As a sanity check, we make sure that the proven withdrawal's timestamp is greater than + // starting timestamp inside the Dispute Game. Not strictly necessary but extra layer of + // safety against weird bugs in the proving step. Note that this blocks withdrawals that + // are proven in the same block that a dispute game is created. + if (provenWithdrawal.timestamp <= disputeGameProxy.createdAt().raw()) { + revert OptimismPortal_InvalidProofTimestamp(); + } + + // A proven withdrawal must wait at least `PROOF_MATURITY_DELAY_SECONDS` before finalizing. + if (block.timestamp - provenWithdrawal.timestamp <= PROOF_MATURITY_DELAY_SECONDS) { + revert OptimismPortal_ProofNotOldEnough(); + } + + // Check that the root claim is valid. + if (!anchorStateRegistry.isGameClaimValid(disputeGameProxy)) { + revert OptimismPortal_InvalidRootClaim(); + } + } + + /// @notice Migrates the total ETH balance to the ETHLockbox. + function migrateLiquidity() public { + if (msg.sender != proxyAdminOwner()) revert OptimismPortal_Unauthorized(); + _migrateLiquidity(); + } + /// @notice Accepts deposits of ETH and data, and emits a TransactionDeposited event for use in /// deriving deposit transactions. Note that if a deposit is made by a contract, its /// address will be aliased when retrieved using `tx.origin` or `msg.sender`. Consider /// using the CrossDomainMessenger contracts for a simpler developer experience. + /// @dev The `msg.value` is locked on the ETHLockbox and minted as ETH when the deposit + /// arrives on L2, while `_value` specifies how much ETH to send to the target. /// @param _to Target address on L2. /// @param _value ETH value to send to the recipient. /// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1. @@ -454,19 +682,28 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { payable metered(_gasLimit) { + // Lock the ETH in the ETHLockbox. + if (msg.value > 0) ethLockbox.lockETH{ value: msg.value }(); + // Just to be safe, make sure that people specify address(0) as the target when doing // contract creations. - if (_isCreation && _to != address(0)) revert BadTarget(); + if (_isCreation && _to != address(0)) { + revert OptimismPortal_BadTarget(); + } // Prevent depositing transactions that have too small of a gas limit. Users should pay // more for more resource usage. - if (_gasLimit < minimumGasLimit(uint64(_data.length))) revert SmallGasLimit(); + if (_gasLimit < minimumGasLimit(uint64(_data.length))) { + revert OptimismPortal_GasLimitTooLow(); + } // Prevent the creation of deposit transactions that have too much calldata. This gives an // upper limit on the size of unsafe blocks over the p2p network. 120kb is chosen to ensure // that the transaction can fit into the p2p network policy of 128kb even though deposit // transactions are not gossipped over the p2p network. - if (_data.length > 120_000) revert LargeCalldata(); + if (_data.length > 120_000) { + revert OptimismPortal_CalldataTooLarge(); + } // Transform the from-address to its alias if the caller is a contract. address from = msg.sender; @@ -484,108 +721,34 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData); } - /// @notice Blacklists a dispute game. Should only be used in the event that a dispute game resolves incorrectly. - /// @param _disputeGame Dispute game to blacklist. - function blacklistDisputeGame(IDisputeGame _disputeGame) external { - if (msg.sender != guardian()) revert Unauthorized(); - disputeGameBlacklist[_disputeGame] = true; - emit DisputeGameBlacklisted(_disputeGame); + /// @notice External getter for the number of proof submitters for a withdrawal hash. + /// @param _withdrawalHash Hash of the withdrawal. + /// @return The number of proof submitters for the withdrawal hash. + function numProofSubmitters(bytes32 _withdrawalHash) external view returns (uint256) { + return proofSubmitters[_withdrawalHash].length; } - /// @notice Sets the respected game type. Changing this value can alter the security properties of the system, - /// depending on the new game's behavior. - /// @param _gameType The game type to consult for output proposals. - function setRespectedGameType(GameType _gameType) external { - if (msg.sender != guardian()) revert Unauthorized(); - // respectedGameTypeUpdatedAt is now no longer set by default. We want to avoid modifying - // this function's signature as that would result in changes to the DeputyGuardianModule. - // We use type(uint32).max as a temporary solution to allow us to update the - // respectedGameTypeUpdatedAt timestamp without modifying this function's signature. - if (_gameType.raw() == type(uint32).max) { - respectedGameTypeUpdatedAt = uint64(block.timestamp); - } else { - respectedGameType = _gameType; - } - emit RespectedGameTypeSet(respectedGameType, Timestamp.wrap(respectedGameTypeUpdatedAt)); + /// @notice Checks if a target address is unsafe. + function _isUnsafeTarget(address _target) internal view virtual returns (bool) { + // Prevent users from targetting an unsafe target address on a withdrawal transaction. + return _target == address(this) || _target == address(ethLockbox); } - /// @notice Checks if a withdrawal can be finalized. This function will revert if the withdrawal cannot be - /// finalized, and otherwise has no side-effects. - /// @param _withdrawalHash Hash of the withdrawal to check. - /// @param _proofSubmitter The submitter of the proof for the withdrawal hash - function checkWithdrawal(bytes32 _withdrawalHash, address _proofSubmitter) public view { - ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[_withdrawalHash][_proofSubmitter]; - IDisputeGame disputeGameProxy = provenWithdrawal.disputeGameProxy; - - // The dispute game must not be blacklisted. - if (disputeGameBlacklist[disputeGameProxy]) revert Blacklisted(); - - // A withdrawal can only be finalized if it has been proven. We know that a withdrawal has - // been proven at least once when its timestamp is non-zero. Unproven withdrawals will have - // a timestamp of zero. - if (provenWithdrawal.timestamp == 0) revert Unproven(); - - // Grab the createdAt timestamp once. - uint64 createdAt = disputeGameProxy.createdAt().raw(); - - // As a sanity check, we make sure that the proven withdrawal's timestamp is greater than - // starting timestamp inside the Dispute Game. Not strictly necessary but extra layer of - // safety against weird bugs in the proving step. - require( - provenWithdrawal.timestamp > createdAt, - "OptimismPortal: withdrawal timestamp less than dispute game creation timestamp" - ); - - // A proven withdrawal must wait at least `PROOF_MATURITY_DELAY_SECONDS` before finalizing. - require( - block.timestamp - provenWithdrawal.timestamp > PROOF_MATURITY_DELAY_SECONDS, - "OptimismPortal: proven withdrawal has not matured yet" - ); - - // A proven withdrawal must wait until the dispute game it was proven against has been - // resolved in favor of the root claim (the output proposal). This is to prevent users - // from finalizing withdrawals proven against non-finalized output roots. - if (disputeGameProxy.status() != GameStatus.DEFENDER_WINS) revert ProposalNotValidated(); - - // The game type of the dispute game must have been the respected game type at creation - // time. We check that the game type is the respected game type at proving time, but it's - // possible that the respected game type has since changed. Users can still use this game - // to finalize a withdrawal as long as it has not been otherwise invalidated. - // The game type of the DisputeGame must have been the respected game type at creation. - // eip150-safe - try disputeGameProxy.wasRespectedGameTypeWhenCreated() returns (bool wasRespected_) { - if (!wasRespected_) revert InvalidGameType(); - } catch { - revert LegacyGame(); - } + /// @notice Migrates the total ETH balance to the ETHLockbox. + function _migrateLiquidity() internal { + uint256 ethBalance = address(this).balance; + ethLockbox.lockETH{ value: ethBalance }(); - // Game must have been created after the respected game type was updated. This check is a - // strict inequality because we want to prevent users from being able to prove or finalize - // withdrawals against games that were created in the same block that the retirement - // timestamp was set. If the retirement timestamp and game type are changed in the same - // block, such games could still be considered valid even if they used the old game type - // that we intended to invalidate. - require( - createdAt > respectedGameTypeUpdatedAt, - "OptimismPortal: dispute game created before respected game type was updated" - ); - - // Before a withdrawal can be finalized, the dispute game it was proven against must have been - // resolved for at least `DISPUTE_GAME_FINALITY_DELAY_SECONDS`. This is to allow for manual - // intervention in the event that a dispute game is resolved incorrectly. - require( - block.timestamp - disputeGameProxy.resolvedAt().raw() > DISPUTE_GAME_FINALITY_DELAY_SECONDS, - "OptimismPortal: output proposal in air-gap" - ); - - // Check that this withdrawal has not already been finalized, this is replay protection. - if (finalizedWithdrawals[_withdrawalHash]) revert AlreadyFinalized(); + emit ETHMigrated(address(ethLockbox), ethBalance); } - /// @notice External getter for the number of proof submitters for a withdrawal hash. - /// @param _withdrawalHash Hash of the withdrawal. - /// @return The number of proof submitters for the withdrawal hash. - function numProofSubmitters(bytes32 _withdrawalHash) external view returns (uint256) { - return proofSubmitters[_withdrawalHash].length; + /// @notice Getter for the resource config. Used internally by the ResourceMetering contract. + /// The SystemConfig is the source of truth for the resource config. + /// @return config_ ResourceMetering ResourceConfig + function _resourceConfig() internal view override returns (ResourceMetering.ResourceConfig memory config_) { + IResourceMetering.ResourceConfig memory config = systemConfig.resourceConfig(); + assembly ("memory-safe") { + config_ := config + } } } diff --git a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol index 7920a4931b165..da2e9559e645b 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol @@ -7,7 +7,6 @@ import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { Constants } from "src/libraries/Constants.sol"; -import { Unauthorized } from "src/libraries/PortalErrors.sol"; // Interfaces import { IL1BlockInterop, ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; @@ -18,23 +17,19 @@ import { IL1BlockInterop, ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; /// 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 { - constructor( - uint256 _proofMaturityDelaySeconds, - uint256 _disputeGameFinalityDelaySeconds - ) - OptimismPortal2(_proofMaturityDelaySeconds, _disputeGameFinalityDelaySeconds) - { } - - /// @custom:semver +interop.3 + /// @param _proofMaturityDelaySeconds The proof maturity delay in seconds. + constructor(uint256 _proofMaturityDelaySeconds) OptimismPortal2(_proofMaturityDelaySeconds) { } + + /// @custom:semver +interop.4 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop.3"); + return string.concat(super.version(), "+interop.4"); } /// @notice Sets static configuration options for the L2 system. /// @param _type Type of configuration to set. /// @param _value Encoded value of the configuration. function setConfig(ConfigType _type, bytes memory _value) external { - if (msg.sender != address(systemConfig)) revert Unauthorized(); + if (msg.sender != address(systemConfig)) revert OptimismPortal_Unauthorized(); // Set L2 deposit gas as used without paying burning gas. Ensures that deposits cannot use too much L2 gas. // This value must be large enough to cover the cost of calling `L1Block.setConfig`. diff --git a/packages/contracts-bedrock/src/L1/ProxyAdminOwnedBase.sol b/packages/contracts-bedrock/src/L1/ProxyAdminOwnedBase.sol new file mode 100644 index 0000000000000..350b91cf10c87 --- /dev/null +++ b/packages/contracts-bedrock/src/L1/ProxyAdminOwnedBase.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { Storage } from "src/libraries/Storage.sol"; +import { Constants } from "src/libraries/Constants.sol"; + +/// @notice Base contract for ProxyAdmin-owned contracts. It's main goal is to expose the ProxyAdmin owner address on +/// a function and also to check if the current contract and a given proxy have the same ProxyAdmin owner. +abstract contract ProxyAdminOwnedBase { + /// @notice Getter for the owner of the ProxyAdmin. + /// The ProxyAdmin is the owner of the current proxy contract. + function proxyAdminOwner() public view returns (address) { + // Get the proxy admin address reading for the reserved slot it has on the Proxy contract. + IProxyAdmin proxyAdmin = IProxyAdmin(Storage.getAddress(Constants.PROXY_OWNER_ADDRESS)); + // Return the owner of the proxy admin. + return proxyAdmin.owner(); + } + + /// @notice Checks if the ProxyAdmin owner of the current contract is the same as the ProxyAdmin owner of the given + /// proxy. + /// @param _proxy The address of the proxy to check. + function _sameProxyAdminOwner(address _proxy) internal view returns (bool) { + return proxyAdminOwner() == ProxyAdminOwnedBase(_proxy).proxyAdminOwner(); + } +} diff --git a/packages/contracts-bedrock/src/L1/SystemConfig.sol b/packages/contracts-bedrock/src/L1/SystemConfig.sol index e767bc64a50ba..092767efb54bd 100644 --- a/packages/contracts-bedrock/src/L1/SystemConfig.sol +++ b/packages/contracts-bedrock/src/L1/SystemConfig.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.15; // Contracts import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { ReinitializableBase } from "src/universal/ReinitializableBase.sol"; // Libraries import { Storage } from "src/libraries/Storage.sol"; @@ -16,7 +17,7 @@ import { IResourceMetering } from "interfaces/L1/IResourceMetering.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 SystemConfig is OwnableUpgradeable, ISemver { +contract SystemConfig is OwnableUpgradeable, ReinitializableBase, ISemver { /// @notice Enum representing different types of updates. /// @custom:value BATCHER Represents an update to the batcher hash. /// @custom:value FEE_SCALARS Represents an update to l1 data fee scalars. @@ -129,6 +130,9 @@ contract SystemConfig is OwnableUpgradeable, ISemver { /// @notice The operator fee constant. uint64 public operatorFeeConstant; + /// @notice The L2 chain ID that this SystemConfig configures. + uint256 public l2ChainId; + /// @notice Emitted when configuration is updated. /// @param version SystemConfig version. /// @param updateType Type of update. @@ -136,15 +140,15 @@ contract SystemConfig is OwnableUpgradeable, ISemver { event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); /// @notice Semantic version. - /// @custom:semver 2.5.0 + /// @custom:semver 2.6.0 function version() public pure virtual returns (string memory) { - return "2.5.0"; + return "2.6.0"; } /// @notice Constructs the SystemConfig contract. /// @dev START_BLOCK_SLOT is set to type(uint256).max here so that it will be a dead value /// in the singleton. - constructor() { + constructor() ReinitializableBase(2) { Storage.setUint(START_BLOCK_SLOT, type(uint256).max); _disableInitializers(); } @@ -161,6 +165,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver { /// @param _batchInbox Batch inbox address. An identifier for the op-node to find /// canonical data. /// @param _addresses Set of L1 contract addresses. These should be the proxies. + /// @param _l2ChainId The L2 chain ID that this SystemConfig configures. function initialize( address _owner, uint32 _basefeeScalar, @@ -170,10 +175,11 @@ contract SystemConfig is OwnableUpgradeable, ISemver { address _unsafeBlockSigner, IResourceMetering.ResourceConfig memory _config, address _batchInbox, - SystemConfig.Addresses memory _addresses + SystemConfig.Addresses memory _addresses, + uint256 _l2ChainId ) public - initializer + reinitializer(initVersion()) { __Ownable_init(); transferOwnership(_owner); @@ -195,6 +201,14 @@ contract SystemConfig is OwnableUpgradeable, ISemver { _setStartBlock(); _setResourceConfig(_config); + + l2ChainId = _l2ChainId; + } + + /// @notice Upgrades the SystemConfig by setting the L2 chain ID variable. + /// @param _l2ChainId The L2 chain ID that this SystemConfig configures. + function upgrade(uint256 _l2ChainId) external reinitializer(initVersion()) { + l2ChainId = _l2ChainId; } /// @notice Returns the minimum L2 gas limit that can be safely set for the system to diff --git a/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol b/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol index 1d7bd8794517f..fc4135c088326 100644 --- a/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol +++ b/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol @@ -46,7 +46,8 @@ contract SystemConfigInterop is SystemConfig { IResourceMetering.ResourceConfig memory _config, address _batchInbox, SystemConfig.Addresses memory _addresses, - address _dependencyManager + address _dependencyManager, + uint256 _l2ChainId ) external { @@ -60,14 +61,15 @@ contract SystemConfigInterop is SystemConfig { _unsafeBlockSigner: _unsafeBlockSigner, _config: _config, _batchInbox: _batchInbox, - _addresses: _addresses + _addresses: _addresses, + _l2ChainId: _l2ChainId }); Storage.setAddress(DEPENDENCY_MANAGER_SLOT, _dependencyManager); } - /// @custom:semver +interop.1 + /// @custom:semver +interop.2 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop.1"); + return string.concat(super.version(), "+interop.2"); } /// @notice Adds a chain to the interop dependency set. Can only be called by the dependency manager. diff --git a/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol b/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol index 60cd9ba31e12a..35b9d2736fbc7 100644 --- a/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol +++ b/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol @@ -13,7 +13,6 @@ import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; /// @custom:proxied true /// @title AnchorStateRegistry @@ -23,8 +22,11 @@ import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; /// be initialized with a more recent starting state which reduces the amount of required offchain computation. contract AnchorStateRegistry is Initializable, ISemver { /// @notice Semantic version. - /// @custom:semver 2.2.2 - string public constant version = "2.2.2"; + /// @custom:semver 3.0.0 + string public constant version = "3.0.0"; + + /// @notice The dispute game finality delay in seconds. + uint256 internal immutable DISPUTE_GAME_FINALITY_DELAY_SECONDS; /// @notice Address of the SuperchainConfig contract. ISuperchainConfig public superchainConfig; @@ -32,61 +34,105 @@ contract AnchorStateRegistry is Initializable, ISemver { /// @notice Address of the DisputeGameFactory contract. IDisputeGameFactory public disputeGameFactory; - /// @notice Address of the OptimismPortal contract. - IOptimismPortal2 public portal; - /// @notice The game whose claim is currently being used as the anchor state. IFaultDisputeGame public anchorGame; /// @notice The starting anchor root. OutputRoot internal startingAnchorRoot; - /// @notice Emitted when an anchor state is not updated. - /// @param game Game that was not used as the new anchor game. - event AnchorNotUpdated(IFaultDisputeGame indexed game); + /// @notice Mapping of blacklisted dispute games. + mapping(IDisputeGame => bool) public disputeGameBlacklist; + + /// @notice The respected game type. + GameType public respectedGameType; + + /// @notice The retirement timestamp. All games created before or at this timestamp are + /// considered retired and are therefore not valid games. Retirement is used as a + /// blanket invalidation mechanism if games resolve incorrectly. + uint64 public retirementTimestamp; /// @notice Emitted when an anchor state is updated. /// @param game Game that was used as the new anchor game. event AnchorUpdated(IFaultDisputeGame indexed game); - /// @notice Thrown when an unauthorized caller attempts to set the anchor state. - error AnchorStateRegistry_Unauthorized(); + /// @notice Emitted when the respected game type is set. + /// @param gameType The new respected game type. + event RespectedGameTypeSet(GameType gameType); - /// @notice Thrown when an invalid anchor game is provided. - error AnchorStateRegistry_InvalidAnchorGame(); + /// @notice Emitted when the retirement timestamp is set. + /// @param timestamp The new retirement timestamp. + event RetirementTimestampSet(uint256 timestamp); + + /// @notice Emitted when a dispute game is blacklisted. + /// @param disputeGame The dispute game that was blacklisted. + event DisputeGameBlacklisted(IDisputeGame indexed disputeGame); /// @notice Thrown when the anchor root is requested, but the anchor game is blacklisted. error AnchorStateRegistry_AnchorGameBlacklisted(); - /// @notice Constructor to disable initializers. - constructor() { + /// @notice Thrown when an invalid anchor game is provided. + error AnchorStateRegistry_InvalidAnchorGame(); + + /// @notice Thrown when an unauthorized caller attempts to set the anchor state. + error AnchorStateRegistry_Unauthorized(); + + /// @param _disputeGameFinalityDelaySeconds The dispute game finality delay in seconds. + constructor(uint256 _disputeGameFinalityDelaySeconds) { + DISPUTE_GAME_FINALITY_DELAY_SECONDS = _disputeGameFinalityDelaySeconds; _disableInitializers(); } /// @notice Initializes the contract. /// @param _superchainConfig The address of the SuperchainConfig contract. /// @param _disputeGameFactory The address of the DisputeGameFactory contract. - /// @param _portal The address of the OptimismPortal contract. /// @param _startingAnchorRoot The starting anchor root. function initialize( ISuperchainConfig _superchainConfig, IDisputeGameFactory _disputeGameFactory, - IOptimismPortal2 _portal, - OutputRoot memory _startingAnchorRoot + OutputRoot memory _startingAnchorRoot, + GameType _startingRespectedGameType ) external initializer { superchainConfig = _superchainConfig; disputeGameFactory = _disputeGameFactory; - portal = _portal; startingAnchorRoot = _startingAnchorRoot; + respectedGameType = _startingRespectedGameType; + retirementTimestamp = uint64(block.timestamp); + } + + /// @notice Returns whether the contract is paused. + function paused() public view returns (bool) { + return superchainConfig.paused(); } - /// @notice Returns the respected game type. - /// @return The respected game type. - function respectedGameType() public view returns (GameType) { - return portal.respectedGameType(); + /// @notice Returns the dispute game finality delay in seconds. + function disputeGameFinalityDelaySeconds() external view returns (uint256) { + return DISPUTE_GAME_FINALITY_DELAY_SECONDS; + } + + /// @notice Allows the Guardian to set the respected game type. + /// @param _gameType The new respected game type. + function setRespectedGameType(GameType _gameType) external { + if (msg.sender != superchainConfig.guardian()) revert AnchorStateRegistry_Unauthorized(); + respectedGameType = _gameType; + emit RespectedGameTypeSet(_gameType); + } + + /// @notice Allows the Guardian to update the retirement timestamp. + function updateRetirementTimestamp() external { + if (msg.sender != superchainConfig.guardian()) revert AnchorStateRegistry_Unauthorized(); + retirementTimestamp = uint64(block.timestamp); + emit RetirementTimestampSet(block.timestamp); + } + + /// @notice Allows the Guardian to blacklist a dispute game. + /// @param _disputeGame Dispute game to blacklist. + function blacklistDisputeGame(IDisputeGame _disputeGame) external { + if (msg.sender != superchainConfig.guardian()) revert AnchorStateRegistry_Unauthorized(); + disputeGameBlacklist[_disputeGame] = true; + emit DisputeGameBlacklisted(_disputeGame); } /// @custom:legacy @@ -129,6 +175,9 @@ contract AnchorStateRegistry is Initializable, ISemver { /// @param _game The game to check. /// @return Whether the game is of a respected game type. function isGameRespected(IDisputeGame _game) public view returns (bool) { + // We don't do a try/catch here for legacy games because by the time this code is live on + // mainnet, users won't be using legacy games anymore. Avoiding the try/catch simplifies + // the logic. return _game.wasRespectedGameTypeWhenCreated(); } @@ -136,7 +185,7 @@ contract AnchorStateRegistry is Initializable, ISemver { /// @param _game The game to check. /// @return Whether the game is blacklisted. function isGameBlacklisted(IDisputeGame _game) public view returns (bool) { - return portal.disputeGameBlacklist(_game); + return disputeGameBlacklist[_game]; } /// @notice Determines whether a game is retired. @@ -146,7 +195,7 @@ contract AnchorStateRegistry is Initializable, ISemver { // Must be created after the respectedGameTypeUpdatedAt timestamp. Note that this means all // games created in the same block as the respectedGameTypeUpdatedAt timestamp are // considered retired. - return _game.createdAt().raw() <= portal.respectedGameTypeUpdatedAt(); + return _game.createdAt().raw() <= retirementTimestamp; } /// @notice Returns whether a game is resolved. @@ -186,6 +235,11 @@ contract AnchorStateRegistry is Initializable, ISemver { return false; } + // Must not be paused, temporarily causes game to be considered improper. + if (paused()) { + return false; + } + return true; } @@ -200,7 +254,7 @@ contract AnchorStateRegistry is Initializable, ISemver { // Game must be beyond the "airgap period" - time since resolution must be at least // "dispute game finality delay" seconds in the past. - if (block.timestamp - _game.resolvedAt().raw() <= portal.disputeGameFinalityDelaySeconds()) { + if (block.timestamp - _game.resolvedAt().raw() <= DISPUTE_GAME_FINALITY_DELAY_SECONDS) { return false; } diff --git a/packages/contracts-bedrock/src/libraries/Encoding.sol b/packages/contracts-bedrock/src/libraries/Encoding.sol index 00c20ea459bb4..6140b62c9e655 100644 --- a/packages/contracts-bedrock/src/libraries/Encoding.sol +++ b/packages/contracts-bedrock/src/libraries/Encoding.sol @@ -9,6 +9,12 @@ import { RLPWriter } from "src/libraries/rlp/RLPWriter.sol"; /// @title Encoding /// @notice Encoding handles Optimism's various different encoding schemes. library Encoding { + /// @notice Thrown when a provided Super Root proof has an invalid version. + error Encoding_InvalidSuperRootVersion(); + + /// @notice Thrown when a provided Super Root proof has no Output Roots. + error Encoding_EmptySuperRoot(); + /// @notice RLP encodes the L2 transaction that would be generated when a given deposit is sent /// to the L2 system. Useful for searching for a deposit in the L2 system. The /// transaction is prefixed with 0x7e to identify its EIP-2718 type. @@ -262,4 +268,30 @@ library Encoding { _batcherHash ); } + + /// @notice Encodes a super root proof into the preimage of a Super Root. + /// @param _superRootProof Super root proof to encode. + /// @return Encoded super root proof. + function encodeSuperRootProof(Types.SuperRootProof memory _superRootProof) internal pure returns (bytes memory) { + // Version must match the expected version. + if (_superRootProof.version != 0x01) { + revert Encoding_InvalidSuperRootVersion(); + } + + // Output roots must not be empty. + if (_superRootProof.outputRoots.length == 0) { + revert Encoding_EmptySuperRoot(); + } + + // Start with version byte and timestamp. + bytes memory encoded = bytes.concat(bytes1(0x01), bytes8(_superRootProof.timestamp)); + + // Add each output root (chainId + root) + for (uint256 i = 0; i < _superRootProof.outputRoots.length; i++) { + Types.OutputRootWithChainId memory outputRoot = _superRootProof.outputRoots[i]; + encoded = bytes.concat(encoded, bytes32(outputRoot.chainId), outputRoot.root); + } + + return encoded; + } } diff --git a/packages/contracts-bedrock/src/libraries/Hashing.sol b/packages/contracts-bedrock/src/libraries/Hashing.sol index b736ad9e4b7e7..782bbbe4f9960 100644 --- a/packages/contracts-bedrock/src/libraries/Hashing.sol +++ b/packages/contracts-bedrock/src/libraries/Hashing.sol @@ -146,4 +146,11 @@ library Hashing { { return keccak256(abi.encode(_destination, _source, _nonce, _sender, _target, _message)); } + + /// @notice Hashes a Super Root proof into a Super Root. + /// @param _superRootProof Super Root proof to hash. + /// @return Hashed super root proof. + function hashSuperRootProof(Types.SuperRootProof memory _superRootProof) internal pure returns (bytes32) { + return keccak256(Encoding.encodeSuperRootProof(_superRootProof)); + } } diff --git a/packages/contracts-bedrock/src/libraries/PortalErrors.sol b/packages/contracts-bedrock/src/libraries/PortalErrors.sol deleted file mode 100644 index 9096a2938fdc1..0000000000000 --- a/packages/contracts-bedrock/src/libraries/PortalErrors.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/// @notice Error for when a deposit or withdrawal is to a bad target. -error BadTarget(); -/// @notice Error for when a deposit has too much calldata. -error LargeCalldata(); -/// @notice Error for when a deposit has too small of a gas limit. -error SmallGasLimit(); -/// @notice Error for when a withdrawal transfer fails. -error TransferFailed(); -/// @notice Error for when a method cannot be called with non zero CALLVALUE. -error NoValue(); -/// @notice Error for an unauthorized CALLER. -error Unauthorized(); -/// @notice Error for when a method cannot be called when paused. This could be renamed -/// to `Paused` in the future, but it collides with the `Paused` event. -error CallPaused(); -/// @notice Error for special gas estimation. -error GasEstimation(); -/// @notice Error for when a method is being reentered. -error NonReentrant(); -/// @notice Error for invalid proof. -error InvalidProof(); -/// @notice Error for invalid game type. -error InvalidGameType(); -/// @notice Error for an invalid dispute game. -error InvalidDisputeGame(); -/// @notice Error for an invalid merkle proof. -error InvalidMerkleProof(); -/// @notice Error for when a dispute game has been blacklisted. -error Blacklisted(); -/// @notice Error for when trying to withdrawal without first proven. -error Unproven(); -/// @notice Error for when a proposal is not validated. -error ProposalNotValidated(); -/// @notice Error for when a withdrawal has already been finalized. -error AlreadyFinalized(); -/// @notice Error for when a game is a legacy game. -error LegacyGame(); diff --git a/packages/contracts-bedrock/src/libraries/Types.sol b/packages/contracts-bedrock/src/libraries/Types.sol index 7e9a65654bc19..3c18c4e1bc151 100644 --- a/packages/contracts-bedrock/src/libraries/Types.sol +++ b/packages/contracts-bedrock/src/libraries/Types.sol @@ -29,6 +29,24 @@ library Types { bytes32 latestBlockhash; } + /// @notice Struct representing an output root with a chain id. + /// @custom:field chainId The chain ID of the L2 chain that the output root commits to. + /// @custom:field root The output root. + struct OutputRootWithChainId { + uint256 chainId; + bytes32 root; + } + + /// @notice Struct representing a super root proof. + /// @custom:field version The version of the super root proof. + /// @custom:field timestamp The timestamp of the super root proof. + /// @custom:field outputRoots The output roots that are included in the super root proof. + struct SuperRootProof { + bytes1 version; + uint64 timestamp; + OutputRootWithChainId[] outputRoots; + } + /// @notice Struct representing a deposit transaction (L1 => L2 transaction) created by an end /// user (as opposed to a system deposit transaction generated by the system). /// @custom:field from Address of the sender of the transaction. diff --git a/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol b/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol index a742c452ef0ba..2a0be015c0f8b 100644 --- a/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol +++ b/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol @@ -6,14 +6,11 @@ import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; import { Enum } from "safe-contracts/common/Enum.sol"; // Libraries -import { Unauthorized } from "src/libraries/PortalErrors.sol"; import { GameType, Timestamp } from "src/dispute/lib/Types.sol"; // Interfaces import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; -import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; @@ -24,7 +21,10 @@ import { ISemver } from "interfaces/universal/ISemver.sol"; /// authorization at any time by disabling this module. contract DeputyGuardianModule is ISemver { /// @notice Error message for failed transaction execution - error ExecutionFailed(string); + error DeputyGuardianModule_ExecutionFailed(string); + + /// @notice Thrown when the caller is not the deputy guardian. + error DeputyGuardianModule_Unauthorized(); /// @notice Emitted when the SuperchainConfig is paused event Paused(string identifier); @@ -38,6 +38,9 @@ contract DeputyGuardianModule is ISemver { /// @notice Emitted when the respected game type is set event RespectedGameTypeSet(GameType indexed gameType, Timestamp indexed updatedAt); + /// @notice Emitted when the retirement timestamp is updated + event RetirementTimestampUpdated(Timestamp indexed updatedAt); + /// @notice The Safe contract instance Safe internal immutable SAFE; @@ -48,8 +51,8 @@ contract DeputyGuardianModule is ISemver { address internal immutable DEPUTY_GUARDIAN; /// @notice Semantic version. - /// @custom:semver 2.0.1-beta.5 - string public constant version = "2.0.1-beta.5"; + /// @custom:semver 3.0.0 + string public constant version = "3.0.0"; // Constructor to initialize the Safe and baseModule instances constructor(Safe _safe, ISuperchainConfig _superchainConfig, address _deputyGuardian) { @@ -79,7 +82,7 @@ contract DeputyGuardianModule is ISemver { /// @notice Internal function to ensure that only the deputy guardian can call certain functions. function _onlyDeputyGuardian() internal view { if (msg.sender != DEPUTY_GUARDIAN) { - revert Unauthorized(); + revert DeputyGuardianModule_Unauthorized(); } } @@ -93,7 +96,7 @@ contract DeputyGuardianModule is ISemver { (bool success, bytes memory returnData) = SAFE.execTransactionFromModuleReturnData(address(SUPERCHAIN_CONFIG), 0, data, Enum.Operation.Call); if (!success) { - revert ExecutionFailed(string(returnData)); + revert DeputyGuardianModule_ExecutionFailed(string(returnData)); } emit Paused("Deputy Guardian"); } @@ -108,58 +111,58 @@ contract DeputyGuardianModule is ISemver { (bool success, bytes memory returnData) = SAFE.execTransactionFromModuleReturnData(address(SUPERCHAIN_CONFIG), 0, data, Enum.Operation.Call); if (!success) { - revert ExecutionFailed(string(returnData)); + revert DeputyGuardianModule_ExecutionFailed(string(returnData)); } emit Unpaused(); } /// @notice Calls the Security Council Safe's `execTransactionFromModuleReturnData()`, with the arguments - /// necessary to call `setAnchorState()` on the `AnchorStateRegistry` contract. + /// necessary to call `blacklistDisputeGame()` on the `AnchorStateRegistry` contract. /// Only the deputy guardian can call this function. - /// @param _registry The `IAnchorStateRegistry` contract instance. - /// @param _game The `IFaultDisputeGame` contract instance. - function setAnchorState(IAnchorStateRegistry _registry, IFaultDisputeGame _game) external { + /// @param _anchorStateRegistry The `AnchorStateRegistry` contract instance. + /// @param _game The `IDisputeGame` contract instance. + function blacklistDisputeGame(IAnchorStateRegistry _anchorStateRegistry, IDisputeGame _game) external { _onlyDeputyGuardian(); - bytes memory data = abi.encodeCall(IAnchorStateRegistry.setAnchorState, (_game)); + bytes memory data = abi.encodeCall(IAnchorStateRegistry.blacklistDisputeGame, (_game)); (bool success, bytes memory returnData) = - SAFE.execTransactionFromModuleReturnData(address(_registry), 0, data, Enum.Operation.Call); + SAFE.execTransactionFromModuleReturnData(address(_anchorStateRegistry), 0, data, Enum.Operation.Call); if (!success) { - revert ExecutionFailed(string(returnData)); + revert DeputyGuardianModule_ExecutionFailed(string(returnData)); } + emit DisputeGameBlacklisted(_game); } /// @notice Calls the Security Council Safe's `execTransactionFromModuleReturnData()`, with the arguments - /// necessary to call `blacklistDisputeGame()` on the `OptimismPortal2` contract. + /// necessary to call `setRespectedGameType()` on the `AnchorStateRegistry` contract. /// Only the deputy guardian can call this function. - /// @param _portal The `OptimismPortal2` contract instance. - /// @param _game The `IDisputeGame` contract instance. - function blacklistDisputeGame(IOptimismPortal2 _portal, IDisputeGame _game) external { + /// @param _anchorStateRegistry The `AnchorStateRegistry` contract instance. + /// @param _gameType The `GameType` to set as the respected game type. + function setRespectedGameType(IAnchorStateRegistry _anchorStateRegistry, GameType _gameType) external { _onlyDeputyGuardian(); - bytes memory data = abi.encodeCall(IOptimismPortal2.blacklistDisputeGame, (_game)); + bytes memory data = abi.encodeCall(IAnchorStateRegistry.setRespectedGameType, (_gameType)); (bool success, bytes memory returnData) = - SAFE.execTransactionFromModuleReturnData(address(_portal), 0, data, Enum.Operation.Call); + SAFE.execTransactionFromModuleReturnData(address(_anchorStateRegistry), 0, data, Enum.Operation.Call); if (!success) { - revert ExecutionFailed(string(returnData)); + revert DeputyGuardianModule_ExecutionFailed(string(returnData)); } - emit DisputeGameBlacklisted(_game); + emit RespectedGameTypeSet(_gameType, Timestamp.wrap(uint64(block.timestamp))); } /// @notice Calls the Security Council Safe's `execTransactionFromModuleReturnData()`, with the arguments - /// necessary to call `setRespectedGameType()` on the `OptimismPortal2` contract. + /// necessary to call `updateRetirementTimestamp()` on the `AnchorStateRegistry` contract. /// Only the deputy guardian can call this function. - /// @param _portal The `OptimismPortal2` contract instance. - /// @param _gameType The `GameType` to set as the respected game type. - function setRespectedGameType(IOptimismPortal2 _portal, GameType _gameType) external { + /// @param _anchorStateRegistry The `AnchorStateRegistry` contract instance. + function updateRetirementTimestamp(IAnchorStateRegistry _anchorStateRegistry) external { _onlyDeputyGuardian(); - bytes memory data = abi.encodeCall(IOptimismPortal2.setRespectedGameType, (_gameType)); + bytes memory data = abi.encodeCall(IAnchorStateRegistry.updateRetirementTimestamp, ()); (bool success, bytes memory returnData) = - SAFE.execTransactionFromModuleReturnData(address(_portal), 0, data, Enum.Operation.Call); + SAFE.execTransactionFromModuleReturnData(address(_anchorStateRegistry), 0, data, Enum.Operation.Call); if (!success) { - revert ExecutionFailed(string(returnData)); + revert DeputyGuardianModule_ExecutionFailed(string(returnData)); } - emit RespectedGameTypeSet(_gameType, Timestamp.wrap(uint64(block.timestamp))); + emit RetirementTimestampUpdated(Timestamp.wrap(uint64(block.timestamp))); } } diff --git a/packages/contracts-bedrock/src/universal/ReinitializableBase.sol b/packages/contracts-bedrock/src/universal/ReinitializableBase.sol new file mode 100644 index 0000000000000..056a15986e02a --- /dev/null +++ b/packages/contracts-bedrock/src/universal/ReinitializableBase.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +/// @title ReinitializableBase +/// @notice A base contract for reinitializable contracts that exposes a version number. +abstract contract ReinitializableBase { + /// @notice Thrown when the initialization version is zero. + error ReinitializableBase_ZeroInitVersion(); + + /// @notice Current initialization version. + uint8 internal immutable INIT_VERSION; + + /// @param _initVersion Current initialization version. + constructor(uint8 _initVersion) { + // Sanity check, we should never have a zero init version. + if (_initVersion == 0) revert ReinitializableBase_ZeroInitVersion(); + INIT_VERSION = _initVersion; + } + + /// @notice Getter for the current initialization version. + /// @return The current initialization version. + function initVersion() public view returns (uint8) { + return INIT_VERSION; + } +} diff --git a/packages/contracts-bedrock/test/L1/ETHLockbox.t.sol b/packages/contracts-bedrock/test/L1/ETHLockbox.t.sol new file mode 100644 index 0000000000000..17ade24455429 --- /dev/null +++ b/packages/contracts-bedrock/test/L1/ETHLockbox.t.sol @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing utilities +import { Constants } from "src/libraries/Constants.sol"; + +// Interfaces +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; + +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; + +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; + +// Test +import { CommonTest } from "test/setup/CommonTest.sol"; + +import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; + +contract ETHLockboxTest is CommonTest { + error InvalidInitialization(); + + event ETHLocked(address indexed portal, uint256 amount); + event ETHUnlocked(address indexed portal, uint256 amount); + event PortalAuthorized(address indexed portal); + event LockboxAuthorized(address indexed lockbox); + event LiquidityMigrated(address indexed lockbox, uint256 amount); + event LiquidityReceived(address indexed lockbox, uint256 amount); + + ProxyAdmin public proxyAdmin; + address public proxyAdminOwner; + + function setUp() public virtual override { + super.setUp(); + + // If not on the last upgrade network, we skip the test since the `ETHLockbox` won't be yet deployed + // TODO(#14691): Remove this check once Upgrade 15 is deployed on Mainnet. + if (isForkTest() && !deploy.cfg().useUpgradedFork()) vm.skip(true); + + proxyAdmin = ProxyAdmin(artifacts.mustGetAddress("ProxyAdmin")); + proxyAdminOwner = proxyAdmin.owner(); + } + + /// @notice Tests the superchain config was correctly set during initialization. + function test_initialization_succeeds() public view { + assertEq(address(ethLockbox.superchainConfig()), address(superchainConfig)); + assertEq(ethLockbox.authorizedPortals(address(optimismPortal2)), true); + } + + /// @notice Tests it reverts when the contract is already initialized. + function test_initialize_alreadyInitialized_reverts() public { + vm.expectRevert("Initializable: contract is already initialized"); + IOptimismPortal2[] memory _portals = new IOptimismPortal2[](1); + ethLockbox.initialize(superchainConfig, _portals); + } + + /// @notice Tests the proxy admin owner is correctly returned. + function test_proxyProxyAdminOwner_succeeds() public view { + assertEq(ethLockbox.proxyAdminOwner(), proxyAdminOwner); + } + + /// @notice Tests the paused status is correctly returned. + function test_paused_succeeds() public { + // Assert the paused status is false + assertEq(ethLockbox.paused(), false); + + // Mock the superchain config to return true for the paused status + vm.mockCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.paused, ()), abi.encode(true)); + + // Assert the paused status is true + assertEq(ethLockbox.paused(), true); + } + + /// @notice Tests the liquidity is correctly received. + function testFuzz_receiveLiquidity_succeeds(address _lockbox, uint256 _value) public { + // Since on the fork the `_lockbox` fuzzed address doesn't exist, we skip the test + if (isForkTest()) vm.skip(true); + assumeNotForgeAddress(_lockbox); + vm.assume(address(_lockbox) != address(ethLockbox)); + + // Deal the value to the lockbox + deal(address(_lockbox), _value); + + // Mock the admin owner of the lockbox to be the same as the current lockbox proxy admin owner + vm.mockCall( + address(_lockbox), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) + ); + + // Authorize the lockbox if needed + if (!ethLockbox.authorizedLockboxes(_lockbox)) { + vm.prank(proxyAdminOwner); + ethLockbox.authorizeLockbox(IETHLockbox(_lockbox)); + } + + // Get the balance of the lockbox before the receive + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; + + // Expect the `LiquidityReceived` event to be emitted + vm.expectEmit(address(ethLockbox)); + emit LiquidityReceived(_lockbox, _value); + + // Call the `receiveLiquidity` function + vm.prank(address(_lockbox)); + ethLockbox.receiveLiquidity{ value: _value }(); + + // Assert the lockbox's balance increased by the amount received + assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + _value); + } + + /// @notice Tests it reverts when the caller is not an authorized portal. + function testFuzz_lockETH_unauthorizedPortal_reverts(address _caller) public { + vm.assume(!ethLockbox.authorizedPortals(_caller)); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(IETHLockbox.ETHLockbox_Unauthorized.selector); + + // Call the `lockETH` function with an unauthorized caller + vm.prank(_caller); + ethLockbox.lockETH(); + } + + /// @notice Tests the ETH is correctly locked when the caller is an authorized portal. + function testFuzz_lockETH_succeeds(uint256 _amount) public { + // Prevent overflow on an upgrade context + _amount = bound(_amount, 0, type(uint256).max - address(ethLockbox).balance); + + // Deal the ETH amount to the portal + vm.deal(address(optimismPortal2), _amount); + + // Get the balance of the portal and lockbox before the lock to compare later on the assertions + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; + + // Look for the emit of the `ETHLocked` event + vm.expectEmit(address(ethLockbox)); + emit ETHLocked(address(optimismPortal2), _amount); + + // Call the `lockETH` function with the portal + vm.prank(address(optimismPortal2)); + ethLockbox.lockETH{ value: _amount }(); + + // Assert the portal's balance decreased and the lockbox's balance increased by the amount locked + assertEq(address(optimismPortal2).balance, portalBalanceBefore - _amount); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _amount); + } + + /// @notice Tests the ETH is correctly locked when the caller is an authorized portal with different portals. + function testFuzz_lockETH_multiplePortals_succeeds(IOptimismPortal2 _portal, uint256 _amount) public { + // Since on the fork the `_portal` fuzzed address doesn't exist, we skip the test + if (isForkTest()) vm.skip(true); + assumeNotForgeAddress(address(_portal)); + vm.assume(address(_portal) != address(ethLockbox)); + + // Mock the admin owner of the portal to be the same as the current lockbox proxy admin owner + vm.mockCall( + address(_portal), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) + ); + + // Set the portal as an authorized portal if needed + if (!ethLockbox.authorizedPortals(address(_portal))) { + vm.prank(proxyAdminOwner); + ethLockbox.authorizePortal(_portal); + } + + // Deal the ETH amount to the portal + vm.deal(address(_portal), _amount); + + // Get the balance of the lockbox before the lock to compare later on the assertions + uint256 lockboxBalanceBefore = address(ethLockbox).balance; + + // Look for the emit of the `ETHLocked` event + vm.expectEmit(address(ethLockbox)); + emit ETHLocked(address(_portal), _amount); + + // Call the `lockETH` function with the portal + vm.prank(address(_portal)); + ethLockbox.lockETH{ value: _amount }(); + + // Assert the portal's balance decreased and the lockbox's balance increased by the amount locked + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _amount); + } + + /// @notice Tests `unlockETH` reverts when the contract is paused. + function testFuzz_unlockETH_paused_reverts(address _caller, uint256 _value) public { + // Mock the superchain config to return true for the paused status + vm.mockCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.paused, ()), abi.encode(true)); + + // Expect the revert with `Paused` selector + vm.expectRevert(IETHLockbox.ETHLockbox_Paused.selector); + + // Call the `unlockETH` function with the caller + vm.prank(_caller); + ethLockbox.unlockETH(_value); + } + + /// @notice Tests it reverts when the caller is not an authorized portal. + function testFuzz_unlockETH_unauthorizedPortal_reverts(address _caller, uint256 _value) public { + vm.assume(!ethLockbox.authorizedPortals(_caller)); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(IETHLockbox.ETHLockbox_Unauthorized.selector); + + // Call the `unlockETH` function with an unauthorized caller + vm.prank(_caller); + ethLockbox.unlockETH(_value); + } + + /// @notice Tests `unlockETH` reverts when the `_value` input is greater than the balance of the lockbox. + function testFuzz_unlockETH_insufficientBalance_reverts(uint256 _value) public { + _value = bound(_value, address(ethLockbox).balance + 1, type(uint256).max); + + // Expect the revert with `InsufficientBalance` selector + vm.expectRevert(IETHLockbox.ETHLockbox_InsufficientBalance.selector); + + // Call the `unlockETH` function with the portal + vm.prank(address(optimismPortal2)); + ethLockbox.unlockETH(_value); + } + + /// @notice Tests `unlockETH` reverts when the portal is not the L2 sender to prevent unlocking ETH from the lockbox + /// through a withdrawal transaction. + function testFuzz_unlockETH_withdrawalTransaction_reverts(uint256 _value, address _l2Sender) public { + _value = bound(_value, 0, address(ethLockbox).balance); + vm.assume(_l2Sender != Constants.DEFAULT_L2_SENDER); + + // Mock the L2 sender + vm.mockCall(address(optimismPortal2), abi.encodeCall(IOptimismPortal.l2Sender, ()), abi.encode(_l2Sender)); + + // Expect the revert with `NoWithdrawalTransactions` selector + vm.expectRevert(IETHLockbox.ETHLockbox_NoWithdrawalTransactions.selector); + + // Call the `unlockETH` function with the portal + vm.prank(address(optimismPortal2)); + ethLockbox.unlockETH(_value); + } + + /// @notice Tests the ETH is correctly unlocked when the caller is an authorized portal. + function testFuzz_unlockETH_succeeds(uint256 _value) public { + // Deal the ETH amount to the lockbox + vm.deal(address(ethLockbox), _value); + + // Get the balance of the portal and lockbox before the unlock to compare later on the assertions + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; + + // Expect `donateETH` function to be called on Portal + vm.expectCall(address(optimismPortal2), abi.encodeCall(IOptimismPortal.donateETH, ())); + + // Look for the emit of the `ETHUnlocked` event + vm.expectEmit(address(ethLockbox)); + emit ETHUnlocked(address(optimismPortal2), _value); + + // Call the `unlockETH` function with the portal + vm.prank(address(optimismPortal2)); + ethLockbox.unlockETH(_value); + + // Assert the portal's balance increased and the lockbox's balance decreased by the amount unlocked + assertEq(address(optimismPortal2).balance, portalBalanceBefore + _value); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore - _value); + } + + /// @notice Tests the ETH is correctly unlocked when the caller is an authorized portal. + function testFuzz_unlockETH_multiplePortals_succeeds(IOptimismPortal2 _portal, uint256 _value) public { + assumeNotForgeAddress(address(_portal)); + + // Mock the admin owner of the portal to be the same as the current lockbox proxy admin owner + vm.mockCall( + address(_portal), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) + ); + + // Set the portal as an authorized portal if needed + if (!ethLockbox.authorizedPortals(address(_portal))) { + vm.prank(proxyAdminOwner); + ethLockbox.authorizePortal(_portal); + } + + // Deal the ETH amount to the lockbox + vm.deal(address(ethLockbox), _value); + + // Get the balance of the portal and lockbox before the unlock to compare later on the assertions + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; + + // Expect `donateETH` function to be called on Portal + vm.expectCall(address(optimismPortal2), abi.encodeCall(IOptimismPortal.donateETH, ())); + + // Look for the emit of the `ETHUnlocked` event + vm.expectEmit(address(ethLockbox)); + emit ETHUnlocked(address(optimismPortal2), _value); + + // Call the `unlockETH` function with the portal + vm.prank(address(optimismPortal2)); + ethLockbox.unlockETH(_value); + + // Assert the portal's balance increased and the lockbox's balance decreased by the amount unlocked + assertEq(address(optimismPortal2).balance, portalBalanceBefore + _value); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore - _value); + } + + /// @notice Tests the `authorizePortal` function reverts when the caller is not the proxy admin. + function testFuzz_authorizePortal_unauthorized_reverts(address _caller) public { + vm.assume(_caller != proxyAdminOwner); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(IETHLockbox.ETHLockbox_Unauthorized.selector); + + // Call the `authorizePortal` function with an unauthorized caller + vm.prank(_caller); + ethLockbox.authorizePortal(optimismPortal2); + } + + /// @notice Tests the `authorizePortal` function reverts when the proxy admin owner of the portal is not the same as + /// the one of the lockbox. + function testFuzz_authorizePortal_differentProxyAdminOwner_reverts(IOptimismPortal2 _portal) public { + assumeNotForgeAddress(address(_portal)); + vm.mockCall(address(_portal), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(address(0))); + + // Expect the revert with `DifferentOwner` selector + vm.expectRevert(IETHLockbox.ETHLockbox_DifferentProxyAdminOwner.selector); + + // Call the `authorizePortal` function + vm.prank(proxyAdminOwner); + ethLockbox.authorizePortal(_portal); + } + + /// @notice Tests the `authorizeLockbox` function succeeds using the `optimismPortal2` address as the portal. + function test_authorizePortal_succeeds() public { + // Calculate the correct storage slot for the mapping value + bytes32 mappingSlot = bytes32(uint256(1)); // position on the layout + address key = address(optimismPortal2); + bytes32 slot = keccak256(abi.encode(key, mappingSlot)); + + // Reset the authorization status to false + vm.store(address(ethLockbox), slot, bytes32(0)); + + // Expect the `PortalAuthorized` event to be emitted + vm.expectEmit(address(ethLockbox)); + emit PortalAuthorized(address(optimismPortal2)); + + // Call the `authorizePortal` function with the portal + vm.prank(proxyAdminOwner); + ethLockbox.authorizePortal(optimismPortal2); + + // Assert the portal is authorized + assertTrue(ethLockbox.authorizedPortals(address(optimismPortal2))); + } + + /// @notice Tests the `authorizeLockbox` function succeeds + function testFuzz_authorizePortal_succeeds(IOptimismPortal2 _portal) public { + assumeNotForgeAddress(address(_portal)); + + // Mock the admin owner of the portal to be the same as the current lockbox proxy admin owner + vm.mockCall( + address(_portal), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) + ); + + // Expect the `PortalAuthorized` event to be emitted + vm.expectEmit(address(ethLockbox)); + emit PortalAuthorized(address(_portal)); + + // Call the `authorizePortal` function with the portal + vm.prank(proxyAdminOwner); + ethLockbox.authorizePortal(_portal); + + // Assert the portal is authorized + assertTrue(ethLockbox.authorizedPortals(address(_portal))); + } + + /// @notice Tests the `authorizeLockbox` function reverts when the caller is not the proxy admin. + function testFuzz_authorizeLockbox_unauthorized_reverts(address _caller) public { + vm.assume(_caller != proxyAdminOwner); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(IETHLockbox.ETHLockbox_Unauthorized.selector); + + // Call the `authorizeLockbox` function with an unauthorized caller + vm.prank(_caller); + ethLockbox.authorizeLockbox(ethLockbox); + } + + /// @notice Tests the `authorizeLockbox` function reverts when the proxy admin owner of the lockbox is not the same + /// as the proxy admin owner of + /// the proxy admin. + function testFuzz_authorizeLockbox_differentProxyAdminOwner_reverts(address _lockbox) public { + assumeNotForgeAddress(_lockbox); + + vm.mockCall(address(_lockbox), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(address(0))); + + // Expect the revert with `ETHLockbox_DifferentProxyAdminOwner` selector + vm.expectRevert(IETHLockbox.ETHLockbox_DifferentProxyAdminOwner.selector); + + // Call the `authorizeLockbox` function with the lockbox + vm.prank(proxyAdminOwner); + ethLockbox.authorizeLockbox(IETHLockbox(_lockbox)); + } + + /// @notice Tests the `authorizeLockbox` function succeeds + function testFuzz_authorizeLockbox_succeeds(address _lockbox) public { + assumeNotForgeAddress(_lockbox); + + // Mock the admin owner of the lockbox to be the same as the current lockbox proxy admin owner + vm.mockCall( + address(_lockbox), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) + ); + + // Expect the `LockboxAuthorized` event to be emitted + vm.expectEmit(address(ethLockbox)); + emit LockboxAuthorized(_lockbox); + + // Authorize the lockbox + vm.prank(proxyAdminOwner); + ethLockbox.authorizeLockbox(IETHLockbox(_lockbox)); + + // Assert the lockbox is authorized + assertTrue(ethLockbox.authorizedLockboxes(_lockbox)); + } + + /// @notice Tests the `migrateLiquidity` function reverts when the caller is not the proxy admin. + function testFuzz_migrateLiquidity_unauthorized_reverts(address _caller) public { + vm.assume(_caller != proxyAdminOwner); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(IETHLockbox.ETHLockbox_Unauthorized.selector); + + // Call the `migrateLiquidity` function with an unauthorized caller + vm.prank(_caller); + ethLockbox.migrateLiquidity(ethLockbox); + } + + /// @notice Tests the `migrateLiquidity` function reverts when the proxy admin owner of the lockbox is not the same + /// as the proxy admin owner of + /// the proxy admin. + function testFuzz_migrateLiquidity_differentProxyAdminOwner_reverts(address _lockbox) public { + assumeNotForgeAddress(_lockbox); + + vm.mockCall(address(_lockbox), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(address(0))); + + // Expect the revert with `ETHLockbox_DifferentProxyAdminOwner` selector + vm.expectRevert(IETHLockbox.ETHLockbox_DifferentProxyAdminOwner.selector); + + // Call the `migrateLiquidity` function with the lockbox + vm.prank(proxyAdminOwner); + ethLockbox.migrateLiquidity(IETHLockbox(_lockbox)); + } + + /// @notice Tests the `migrateLiquidity` function succeeds + function testFuzz_migrateLiquidity_succeeds(uint256 _balance, address _lockbox) public { + _balance = bound(_balance, 0, type(uint256).max - address(ethLockbox).balance); + + // Since on the fork the `_lockbox` fuzzed address doesn't exist, we skip the test + if (isForkTest()) vm.skip(true); + assumeNotForgeAddress(_lockbox); + vm.assume(address(_lockbox) != address(ethLockbox)); + + // Mock on the lockbox that will receive the migration for it to succeed + vm.mockCall( + address(_lockbox), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) + ); + vm.mockCall( + address(_lockbox), abi.encodeCall(IETHLockbox.authorizedLockboxes, (address(ethLockbox))), abi.encode(true) + ); + vm.mockCall(address(_lockbox), abi.encodeCall(IETHLockbox.receiveLiquidity, ()), abi.encode(true)); + + // Deal the balance to the lockbox + deal(address(_lockbox), _balance); + + // Get balances before the migration + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; + uint256 newLockboxBalanceBefore = address(_lockbox).balance; + + // Expect the `LiquidityMigrated` event to be emitted + vm.expectEmit(address(ethLockbox)); + emit LiquidityMigrated(_lockbox, ethLockboxBalanceBefore); + + // Call the `migrateLiquidity` function with the lockbox + vm.prank(proxyAdminOwner); + ethLockbox.migrateLiquidity(IETHLockbox(_lockbox)); + + // Assert the liquidity was migrated + assertEq(address(ethLockbox).balance, 0); + assertEq(address(_lockbox).balance, newLockboxBalanceBefore + ethLockboxBalanceBefore); + } +} diff --git a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol index 093c87c5e19fc..8776c0f7a8397 100644 --- a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol +++ b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol @@ -163,7 +163,8 @@ contract L1StandardBridge_Initialize_TestFail is CommonTest { } contract L1StandardBridge_Receive_Test is CommonTest { /// @dev Tests receive bridges ETH successfully. function test_receive_succeeds() external { - uint256 balanceBefore = address(optimismPortal2).balance; + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; // The legacy event must be emitted for backwards compatibility vm.expectEmit(address(l1StandardBridge)); @@ -187,7 +188,8 @@ contract L1StandardBridge_Receive_Test is CommonTest { vm.prank(alice, alice); (bool success,) = address(l1StandardBridge).call{ value: 100 }(hex""); assertEq(success, true); - assertEq(address(optimismPortal2).balance, balanceBefore + 100); + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 100); } } @@ -196,7 +198,7 @@ contract PreBridgeETH is CommonTest { /// on whether the bridge call is legacy or not. function _preBridgeETH(bool isLegacy, uint256 value) internal { if (!isForkTest()) { - assertEq(address(optimismPortal2).balance, 0); + assertEq(address(optimismPortal2).balance, 0, "OptimismPortal2 balance should be 0"); } uint256 nonce = l1CrossDomainMessenger.messageNonce(); uint256 version = 0; // Internal constant in the OptimismPortal: DEPOSIT_VERSION @@ -266,9 +268,11 @@ contract L1StandardBridge_DepositETH_Test is PreBridgeETH { /// ETH ends up in the optimismPortal. function test_depositETH_fromEOA_succeeds() external { _preBridgeETH({ isLegacy: true, value: 500 }); - uint256 balanceBefore = address(optimismPortal2).balance; + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; l1StandardBridge.depositETH{ value: 500 }(50000, hex"dead"); - assertEq(address(optimismPortal2).balance, balanceBefore + 500); + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 500); } /// @dev Tests that depositing ETH succeeds for an EOA using 7702 delegation. @@ -277,9 +281,11 @@ contract L1StandardBridge_DepositETH_Test is PreBridgeETH { vm.etch(alice, abi.encodePacked(hex"EF0100", address(0))); _preBridgeETH({ isLegacy: true, value: 500 }); - uint256 balanceBefore = address(optimismPortal2).balance; + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; l1StandardBridge.depositETH{ value: 500 }(50000, hex"dead"); - assertEq(address(optimismPortal2).balance, balanceBefore + 500); + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 500); } } @@ -301,9 +307,11 @@ contract L1StandardBridge_BridgeETH_Test is PreBridgeETH { /// ETH ends up in the optimismPortal. function test_bridgeETH_succeeds() external { _preBridgeETH({ isLegacy: false, value: 500 }); - uint256 balanceBefore = address(optimismPortal2).balance; + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; l1StandardBridge.bridgeETH{ value: 500 }(50000, hex"dead"); - assertEq(address(optimismPortal2).balance, balanceBefore + 500); + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 500); } } @@ -381,9 +389,11 @@ contract L1StandardBridge_DepositETHTo_Test is PreBridgeETHTo { /// ETH ends up in the optimismPortal. function test_depositETHTo_succeeds() external { _preBridgeETHTo({ isLegacy: true, value: 600 }); - uint256 balanceBefore = address(optimismPortal2).balance; + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; l1StandardBridge.depositETHTo{ value: 600 }(bob, 60000, hex"dead"); - assertEq(address(optimismPortal2).balance, balanceBefore + 600); + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 600); } } @@ -395,9 +405,11 @@ contract L1StandardBridge_BridgeETHTo_Test is PreBridgeETHTo { /// ETH ends up in the optimismPortal. function test_bridgeETHTo_succeeds() external { _preBridgeETHTo({ isLegacy: false, value: 600 }); - uint256 balanceBefore = address(optimismPortal2).balance; + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; l1StandardBridge.bridgeETHTo{ value: 600 }(bob, 60000, hex"dead"); - assertEq(address(optimismPortal2).balance, balanceBefore + 600); + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 600); } } diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 6f742180c67a8..185ab8f79f31c 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -27,6 +27,7 @@ import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMin import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { IMIPS } from "interfaces/cannon/IMIPS.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; @@ -39,12 +40,15 @@ import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IOPContractsManager, + IOPCMImplementationsWithoutLockbox, IOPContractsManagerGameTypeAdder, IOPContractsManagerDeployer, IOPContractsManagerUpgrader, IOPContractsManagerContractsContainer } from "interfaces/L1/IOPContractsManager.sol"; +import { IOPContractsManagerLegacyUpgrade } from "interfaces/L1/IOPContractsManagerLegacyUpgrade.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; // Contracts import { @@ -144,6 +148,7 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { startingAnchorRoot: _doi.startingAnchorRoot(), saltMixer: _doi.saltMixer(), gasLimit: _doi.gasLimit(), + disputeGameUsesSuperRoots: _doi.disputeGameUsesSuperRoots(), disputeGameType: _doi.disputeGameType(), disputeAbsolutePrestate: _doi.disputeAbsolutePrestate(), disputeMaxGameDepth: _doi.disputeMaxGameDepth(), @@ -268,7 +273,8 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { IOPContractsManager.OpChainConfig({ systemConfigProxy: systemConfig, proxyAdmin: proxyAdmin, - absolutePrestate: absolutePrestate + absolutePrestate: absolutePrestate, + disputeGameUsesSuperRoots: false }) ); @@ -283,6 +289,29 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { faultDisputeGame = IFaultDisputeGame(address(artifacts.mustGetAddress("FaultDisputeGame"))); } + /// @notice Converts the new OpChainConfig struct to the legacy OpChainConfig struct format. + /// This function is used to test Upgrade 13 and 14 paths and can be safely removed + /// after those upgrades are completed. Only difference in the new struct is the added + /// disputeGameUsesSuperRoots boolean. + /// @param _opChainConfigs The new OpChainConfig structs to convert. + /// @return The legacy OpChainConfig structs. + function convertToLegacyConfigs(IOPContractsManager.OpChainConfig[] memory _opChainConfigs) + public + pure + returns (IOPContractsManagerLegacyUpgrade.OpChainConfig[] memory) + { + IOPContractsManagerLegacyUpgrade.OpChainConfig[] memory legacyConfigs = + new IOPContractsManagerLegacyUpgrade.OpChainConfig[](_opChainConfigs.length); + for (uint256 i = 0; i < _opChainConfigs.length; i++) { + legacyConfigs[i] = IOPContractsManagerLegacyUpgrade.OpChainConfig({ + systemConfigProxy: _opChainConfigs[i].systemConfigProxy, + proxyAdmin: _opChainConfigs[i].proxyAdmin, + absolutePrestate: _opChainConfigs[i].absolutePrestate + }); + } + return legacyConfigs; + } + function expectEmitUpgraded(address impl, address proxy) public { vm.expectEmit(proxy); emit Upgraded(impl); @@ -291,7 +320,8 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { function runV200UpgradeAndChecks(address _delegateCaller) public { // The address below corresponds with the address of the v2.0.0-rc.1 OPCM on mainnet. IOPContractsManager deployedOPCM = IOPContractsManager(address(0x026b2F158255Beac46c1E7c6b8BbF29A4b6A7B76)); - IOPContractsManager.Implementations memory impls = deployedOPCM.implementations(); + IOPCMImplementationsWithoutLockbox.Implementations memory impls = + IOPCMImplementationsWithoutLockbox(address(deployedOPCM)).implementations(); // Cache the old L1xDM address so we can look for it in the AddressManager's event address oldL1CrossDomainMessenger = addressManager.getAddress("OVM_L1CrossDomainMessenger"); @@ -349,7 +379,8 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { vm.etch(_delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); DelegateCaller(_delegateCaller).dcForward( - address(deployedOPCM), abi.encodeCall(IOPContractsManager.upgrade, (opChainConfigs)) + address(deployedOPCM), + abi.encodeCall(IOPContractsManagerLegacyUpgrade.upgrade, (convertToLegacyConfigs(opChainConfigs))) ); VmSafe.Gas memory gas = vm.lastCallGas(); @@ -396,7 +427,10 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { } function runUpgrade14UpgradeAndChecks(address _delegateCaller) public { - IOPContractsManager.Implementations memory impls = opcm.implementations(); + // TODO(#14665): Replace this address with once the final OPCM is deployed for Upgrade 14. + IOPContractsManager deployedOPCM = IOPContractsManager(address(0x3A1f523a4bc09cd344A2745a108Bb0398288094F)); + IOPCMImplementationsWithoutLockbox.Implementations memory impls = + IOPCMImplementationsWithoutLockbox(address(deployedOPCM)).implementations(); // sanity check IPermissionedDisputeGame oldPDG = @@ -427,7 +461,8 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { vm.etch(_delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); DelegateCaller(_delegateCaller).dcForward( - address(opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChainConfigs)) + address(deployedOPCM), + abi.encodeCall(IOPContractsManagerLegacyUpgrade.upgrade, (convertToLegacyConfigs(opChainConfigs))) ); VmSafe.Gas memory gas = vm.lastCallGas(); @@ -458,9 +493,126 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { } } + function runUpgrade15UpgradeAndChecks(address _delegateCaller) public { + IOPContractsManager.Implementations memory impls = opcm.implementations(); + + // Predict the address of the new AnchorStateRegistry proxy. + // Subcontext to avoid stack too deep. + address newAsrProxy; + { + // Compute the salt using the system config address. + bytes32 salt = keccak256( + abi.encode( + l2ChainId, + string.concat(string(bytes.concat(bytes32(uint256(uint160(address(systemConfig))))))), + "AnchorStateRegistry-SOT" + ) + ); + + // Use the actual proxy instead of the local code so we can reuse this test. + address proxyBp = opcm.blueprints().proxy; + Blueprint.Preamble memory preamble = Blueprint.parseBlueprintPreamble(proxyBp.code); + bytes memory initCode = bytes.concat(preamble.initcode, abi.encode(proxyAdmin)); + newAsrProxy = vm.computeCreate2Address(salt, keccak256(initCode), _delegateCaller); + vm.label(newAsrProxy, "NewAnchorStateRegistryProxy"); + } + + // Grab the PermissionedDisputeGame and FaultDisputeGame implementations before upgrade. + address oldPDGImpl = address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)); + address oldFDGImpl = address(disputeGameFactory.gameImpls(GameTypes.CANNON)); + IPermissionedDisputeGame oldPDG = IPermissionedDisputeGame(oldPDGImpl); + IFaultDisputeGame oldFDG = IFaultDisputeGame(oldFDGImpl); + + // Expect the SystemConfig and OptimismPortal to be upgraded. + expectEmitUpgraded(impls.systemConfigImpl, address(systemConfig)); + expectEmitUpgraded(impls.optimismPortalImpl, address(optimismPortal2)); + + // We always expect the PermissionedDisputeGame to be deployed. We don't yet know the + // address of the new permissionedGame which will be deployed by the + // OPContractsManager.upgrade() call, so ignore the first topic. + vm.expectEmit(false, true, true, true, address(disputeGameFactory)); + emit ImplementationSet(address(0), GameTypes.PERMISSIONED_CANNON); + + // If the old FaultDisputeGame exists, we expect it to be upgraded. + if (address(oldFDG) != address(0)) { + // Ignore the first topic for the same reason as the previous comment. + vm.expectEmit(false, true, true, true, address(disputeGameFactory)); + emit ImplementationSet(address(0), GameTypes.CANNON); + } + + vm.expectEmit(address(_delegateCaller)); + emit Upgraded(l2ChainId, systemConfig, address(_delegateCaller)); + + // Temporarily replace the upgrader with a DelegateCaller so we can test the upgrade, + // then reset its code to the original code. + bytes memory delegateCallerCode = address(_delegateCaller).code; + vm.etch(_delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + + // Execute the upgrade. + // We use the new format here, not the legacy one. + DelegateCaller(_delegateCaller).dcForward( + address(opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChainConfigs)) + ); + + // Less than 90% of the gas target of 20M to account for the gas used by using Safe. + VmSafe.Gas memory gas = vm.lastCallGas(); + assertLt(gas.gasTotalUsed, 0.9 * 20_000_000, "Upgrade exceeds gas target of 15M"); + + // Reset the upgrader's code to the original code. + vm.etch(_delegateCaller, delegateCallerCode); + + // Grab the new implementations. + address newPDGImpl = address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)); + IPermissionedDisputeGame pdg = IPermissionedDisputeGame(newPDGImpl); + address newFDGImpl = address(disputeGameFactory.gameImpls(GameTypes.CANNON)); + IFaultDisputeGame fdg = IFaultDisputeGame(newFDGImpl); + + // Check that the PermissionedDisputeGame is upgraded to the expected version, references + // the correct anchor state and has the mipsImpl. Although Upgrade 15 doesn't actually + // change any of this, we might as well check it again. + assertEq(ISemver(address(pdg)).version(), "1.4.1"); + assertEq(address(pdg.vm()), impls.mipsImpl); + assertEq(pdg.l2ChainId(), oldPDG.l2ChainId()); + + // If the old FaultDisputeGame exists, we expect it to be upgraded. Check same as above. + if (address(oldFDG) != address(0)) { + assertEq(ISemver(address(fdg)).version(), "1.4.1"); + assertEq(address(fdg.vm()), impls.mipsImpl); + assertEq(fdg.l2ChainId(), oldFDG.l2ChainId()); + } + + // Make sure that the SystemConfig is upgraded to the right version. It must also have the + // right l2ChainId and must be properly initialized. + assertEq(ISemver(address(systemConfig)).version(), "2.6.0"); + assertEq(impls.systemConfigImpl, EIP1967Helper.getImplementation(address(systemConfig))); + assertEq(systemConfig.l2ChainId(), l2ChainId); + DeployUtils.assertInitialized({ _contractAddress: address(systemConfig), _isProxy: true, _slot: 0, _offset: 0 }); + + // Make sure that the OptimismPortal is upgraded to the right version. It must also have a + // reference to the new AnchorStateRegistry. + assertEq(ISemver(address(optimismPortal2)).version(), "4.0.0"); + assertEq(impls.optimismPortalImpl, EIP1967Helper.getImplementation(address(optimismPortal2))); + assertEq(address(optimismPortal2.anchorStateRegistry()), address(newAsrProxy)); + DeployUtils.assertInitialized({ + _contractAddress: address(optimismPortal2), + _isProxy: true, + _slot: 0, + _offset: 0 + }); + + // Make sure the new AnchorStateRegistry has the right version and is initialized. + assertEq(ISemver(address(newAsrProxy)).version(), "3.0.0"); + vm.prank(address(proxyAdmin)); + assertEq(IProxy(payable(newAsrProxy)).admin(), address(proxyAdmin)); + DeployUtils.assertInitialized({ _contractAddress: address(newAsrProxy), _isProxy: true, _slot: 0, _offset: 0 }); + } + function runUpgradeTestAndChecks(address _delegateCaller) public { + // TODO(#14691): Remove this function once Upgrade 15 is deployed on Mainnet. runV200UpgradeAndChecks(_delegateCaller); + // TODO(#14691): Remove this function once Upgrade 15 is deployed on Mainnet. runUpgrade14UpgradeAndChecks(_delegateCaller); + runUpgrade15UpgradeAndChecks(_delegateCaller); } } @@ -525,6 +677,34 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { // Try to upgrade the current OPChain runUpgradeTestAndChecks(upgrader); } + + function test_upgrade_absolutePrestateNotSet_succeeds() public { + // Get the pdg and fdg before the upgrade + Claim pdgPrestateBefore = IPermissionedDisputeGame( + address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) + ).absolutePrestate(); + Claim fdgPrestateBefore = + IFaultDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.CANNON))).absolutePrestate(); + + runV200UpgradeAndChecks(upgrader); + runUpgrade14UpgradeAndChecks(upgrader); + + // Set the absolute prestate input to 0 + opChainConfigs[0].absolutePrestate = Claim.wrap(bytes32(0)); + + runUpgrade15UpgradeAndChecks(upgrader); + + // Get the absolute prestate after the upgrade + Claim pdgPrestateAfter = IPermissionedDisputeGame( + address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)) + ).absolutePrestate(); + Claim fdgPrestateAfter = + IFaultDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.CANNON))).absolutePrestate(); + + // Assert that the absolute prestate is 0 + assertEq(Claim.unwrap(pdgPrestateAfter), Claim.unwrap(pdgPrestateBefore)); + assertEq(Claim.unwrap(fdgPrestateAfter), Claim.unwrap(fdgPrestateBefore)); + } } contract OPContractsManager_Upgrade_TestFails is OPContractsManager_Upgrade_Harness { @@ -552,12 +732,6 @@ contract OPContractsManager_Upgrade_TestFails is OPContractsManager_Upgrade_Harn address(opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChainConfigs)) ); } - - function test_upgrade_absolutePrestateNotSet_reverts() public { - opChainConfigs[0].absolutePrestate = Claim.wrap(bytes32(0)); - vm.expectRevert(IOPContractsManager.PrestateNotSet.selector); - DelegateCaller(upgrader).dcForward(address(opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChainConfigs))); - } } contract OPContractsManager_SetRC_Test is OPContractsManager_Upgrade_Harness { @@ -646,7 +820,11 @@ contract OPContractsManager_AddGameType_Test is Test { }), optimismPortalImpl: DeployUtils.create1({ _name: "OptimismPortal2", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismPortal2.__constructor__, (1, 1))) + _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismPortal2.__constructor__, (1))) + }), + ethLockboxImpl: DeployUtils.create1({ + _name: "ETHLockbox", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IETHLockbox.__constructor__, ())) }), systemConfigImpl: DeployUtils.create1({ _name: "SystemConfig", @@ -670,7 +848,7 @@ contract OPContractsManager_AddGameType_Test is Test { }), anchorStateRegistryImpl: DeployUtils.create1({ _name: "AnchorStateRegistry", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IAnchorStateRegistry.__constructor__, ())) + _args: DeployUtils.encodeConstructor(abi.encodeCall(IAnchorStateRegistry.__constructor__, (1))) }), delayedWETHImpl: DeployUtils.create1({ _name: "DelayedWETH", @@ -773,6 +951,7 @@ contract OPContractsManager_AddGameType_Test is Test { l2ChainId: 100, saltMixer: "hello", gasLimit: 30_000_000, + disputeGameUsesSuperRoots: false, disputeGameType: GameType.wrap(1), disputeAbsolutePrestate: Claim.wrap( bytes32(hex"038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c") @@ -1009,7 +1188,11 @@ contract OPContractsManager_UpdatePrestate_Test is Test { }), optimismPortalImpl: DeployUtils.create1({ _name: "OptimismPortal2", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismPortal2.__constructor__, (1, 1))) + _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismPortal2.__constructor__, (1))) + }), + ethLockboxImpl: DeployUtils.create1({ + _name: "ETHLockbox", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IETHLockbox.__constructor__, ())) }), systemConfigImpl: DeployUtils.create1({ _name: "SystemConfig", @@ -1033,7 +1216,7 @@ contract OPContractsManager_UpdatePrestate_Test is Test { }), anchorStateRegistryImpl: DeployUtils.create1({ _name: "AnchorStateRegistry", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IAnchorStateRegistry.__constructor__, ())) + _args: DeployUtils.encodeConstructor(abi.encodeCall(IAnchorStateRegistry.__constructor__, (1))) }), delayedWETHImpl: DeployUtils.create1({ _name: "DelayedWETH", @@ -1125,6 +1308,7 @@ contract OPContractsManager_UpdatePrestate_Test is Test { l2ChainId: 100, saltMixer: "hello", gasLimit: 30_000_000, + disputeGameUsesSuperRoots: false, disputeGameType: GameType.wrap(1), disputeAbsolutePrestate: Claim.wrap( bytes32(hex"038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c") @@ -1146,7 +1330,10 @@ contract OPContractsManager_UpdatePrestate_Test is Test { function test_updatePrestate_pdgOnlyWithValidInput_succeeds() public { IOPContractsManager.OpChainConfig[] memory inputs = new IOPContractsManager.OpChainConfig[](1); inputs[0] = IOPContractsManager.OpChainConfig( - chainDeployOutput.systemConfigProxy, chainDeployOutput.opChainProxyAdmin, Claim.wrap(bytes32(hex"ABBA")) + chainDeployOutput.systemConfigProxy, + chainDeployOutput.opChainProxyAdmin, + Claim.wrap(bytes32(hex"ABBA")), + false ); address proxyAdminOwner = chainDeployOutput.opChainProxyAdmin.owner(); @@ -1177,7 +1364,10 @@ contract OPContractsManager_UpdatePrestate_Test is Test { IOPContractsManager.OpChainConfig[] memory inputs = new IOPContractsManager.OpChainConfig[](1); inputs[0] = IOPContractsManager.OpChainConfig( - chainDeployOutput.systemConfigProxy, chainDeployOutput.opChainProxyAdmin, Claim.wrap(bytes32(hex"ABBA")) + chainDeployOutput.systemConfigProxy, + chainDeployOutput.opChainProxyAdmin, + Claim.wrap(bytes32(hex"ABBA")), + false ); address proxyAdminOwner = chainDeployOutput.opChainProxyAdmin.owner(); @@ -1214,7 +1404,8 @@ contract OPContractsManager_UpdatePrestate_Test is Test { inputs[0] = IOPContractsManager.OpChainConfig({ systemConfigProxy: chainDeployOutput.systemConfigProxy, proxyAdmin: chainDeployOutput.opChainProxyAdmin, - absolutePrestate: Claim.wrap(bytes32(0)) + absolutePrestate: Claim.wrap(bytes32(0)), + disputeGameUsesSuperRoots: false }); address proxyAdminOwner = chainDeployOutput.opChainProxyAdmin.owner(); diff --git a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol index 6b6f60bf778b6..c0c05e6f327e4 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol @@ -1,14 +1,21 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -// Testing +// Forge import { VmSafe } from "forge-std/Vm.sol"; +import { console2 as console } from "forge-std/console2.sol"; + +// Testing import { CommonTest } from "test/setup/CommonTest.sol"; import { NextImpl } from "test/mocks/NextImpl.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; +// Scripts +import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; + // Contracts import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; +import { Predeploys } from "src/libraries/Predeploys.sol"; // Libraries import { Types } from "src/libraries/Types.sol"; @@ -17,14 +24,15 @@ import { Constants } from "src/libraries/Constants.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import "src/dispute/lib/Types.sol"; -import "src/libraries/PortalErrors.sol"; // Interfaces import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IProxy } from "interfaces/universal/IProxy.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; contract OptimismPortal2_Test is CommonTest { address depositor; @@ -44,23 +52,26 @@ contract OptimismPortal2_Test is CommonTest { /// @notice Marked virtual to be overridden in /// test/kontrol/deployment/DeploymentSummary.t.sol function test_constructor_succeeds() external virtual { - IOptimismPortal2 opImpl = IOptimismPortal2(payable(EIP1967Helper.getImplementation(address(optimismPortal2)))); - assertEq(address(opImpl.disputeGameFactory()), address(0)); + IOptimismPortal opImpl = IOptimismPortal(payable(EIP1967Helper.getImplementation(address(optimismPortal2)))); + assertEq(address(opImpl.anchorStateRegistry()), address(0)); assertEq(address(opImpl.systemConfig()), address(0)); assertEq(address(opImpl.superchainConfig()), address(0)); - assertEq(opImpl.respectedGameType().raw(), deploy.cfg().respectedGameType()); assertEq(opImpl.l2Sender(), address(0)); + assertEq(address(opImpl.anchorStateRegistry()), address(0)); + assertEq(address(opImpl.ethLockbox()), address(0)); } /// @dev Tests that the initializer sets the correct values. /// @notice Marked virtual to be overridden in /// test/kontrol/deployment/DeploymentSummary.t.sol function test_initialize_succeeds() external virtual { + assertEq(address(optimismPortal2.anchorStateRegistry()), address(anchorStateRegistry)); assertEq(address(optimismPortal2.disputeGameFactory()), address(disputeGameFactory)); assertEq(address(optimismPortal2.superchainConfig()), address(superchainConfig)); assertEq(optimismPortal2.l2Sender(), Constants.DEFAULT_L2_SENDER); assertEq(optimismPortal2.paused(), false); assertEq(address(optimismPortal2.systemConfig()), address(systemConfig)); + assertEq(address(optimismPortal2.ethLockbox()), address(ethLockbox)); returnIfForkTest( "OptimismPortal2_Initialize_Test: Do not check guardian and respectedGameType on forked networks" @@ -73,6 +84,32 @@ contract OptimismPortal2_Test is CommonTest { assertEq(optimismPortal2.respectedGameType().raw(), deploy.cfg().respectedGameType()); } + /// @dev Tests that the upgrade function succeeds. + function testFuzz_upgrade_succeeds(address _newAnchorStateRegistry, uint256 _balance) external { + // Prevent overflow on an upgrade context + _balance = bound(_balance, 0, type(uint256).max - address(ethLockbox).balance); + + // Set the initialize state of the portal to false. + vm.store(address(optimismPortal2), bytes32(uint256(0)), bytes32(uint256(0))); + + // Set the balance of the portal and get the lockbox balance before the upgrade. + deal(address(optimismPortal2), _balance); + uint256 lockboxBalanceBefore = address(ethLockbox).balance; + + // Expect the ETH to be migrated to the lockbox. + vm.expectCall(address(ethLockbox), _balance, abi.encodeCall(ethLockbox.lockETH, ())); + + // Call the upgrade function. + vm.prank(Predeploys.PROXY_ADMIN); + optimismPortal2.upgrade(IAnchorStateRegistry(_newAnchorStateRegistry), IETHLockbox(ethLockbox), true); + + // Assert the portal is properly upgraded. + assertEq(address(optimismPortal2.ethLockbox()), address(ethLockbox)); + assertEq(address(optimismPortal2.anchorStateRegistry()), _newAnchorStateRegistry); + assertEq(address(optimismPortal2).balance, 0); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _balance); + } + /// @dev Tests that `pause` successfully pauses /// when called by the GUARDIAN. function test_pause_succeeds() external { @@ -136,7 +173,10 @@ contract OptimismPortal2_Test is CommonTest { /// @dev Tests that `receive` successdully deposits ETH. function testFuzz_receive_succeeds(uint256 _value) external { + // Prevent overflow on an upgrade context + _value = bound(_value, 0, type(uint256).max - address(ethLockbox).balance); uint256 balanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; _value = bound(_value, 0, type(uint256).max - balanceBefore); vm.expectEmit(address(optimismPortal2)); @@ -150,20 +190,24 @@ contract OptimismPortal2_Test is CommonTest { _data: hex"" }); + // Expect call to the ETHLockbox to lock the funds only if the value is greater than 0. + vm.expectCall(address(ethLockbox), _value, abi.encodeCall(ethLockbox.lockETH, ()), _value > 0 ? 1 : 0); + // give alice money and send as an eoa vm.deal(alice, _value); vm.prank(alice, alice); (bool s,) = address(optimismPortal2).call{ value: _value }(hex""); assertTrue(s); - assertEq(address(optimismPortal2).balance, balanceBefore + _value); + assertEq(address(optimismPortal2).balance, balanceBefore); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _value); } /// @dev Tests that `depositTransaction` reverts when the destination address is non-zero /// for a contract creation deposit. function test_depositTransaction_contractCreation_reverts() external { // contract creation must have a target of address(0) - vm.expectRevert(BadTarget.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_BadTarget.selector); optimismPortal2.depositTransaction(address(1), 1, 0, true, hex""); } @@ -172,7 +216,7 @@ contract OptimismPortal2_Test is CommonTest { function test_depositTransaction_largeData_reverts() external { uint256 size = 120_001; uint64 gasLimit = optimismPortal2.minimumGasLimit(uint64(size)); - vm.expectRevert(LargeCalldata.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_CalldataTooLarge.selector); optimismPortal2.depositTransaction({ _to: address(0), _value: 0, @@ -184,7 +228,7 @@ contract OptimismPortal2_Test is CommonTest { /// @dev Tests that `depositTransaction` reverts when the gas limit is too small. function test_depositTransaction_smallGasLimit_reverts() external { - vm.expectRevert(SmallGasLimit.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_GasLimitTooLow.selector); optimismPortal2.depositTransaction({ _to: address(1), _value: 0, _gasLimit: 0, _isCreation: false, _data: hex"" }); } @@ -194,7 +238,7 @@ contract OptimismPortal2_Test is CommonTest { uint64 gasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length)); if (_shouldFail) { gasLimit = uint64(bound(gasLimit, 0, gasLimit - 1)); - vm.expectRevert(SmallGasLimit.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_GasLimitTooLow.selector); } optimismPortal2.depositTransaction({ @@ -226,6 +270,8 @@ contract OptimismPortal2_Test is CommonTest { ) external { + // Prevent overflow on an upgrade context + _mint = bound(_mint, 0, type(uint256).max - address(ethLockbox).balance); _gasLimit = uint64( bound( _gasLimit, @@ -236,6 +282,7 @@ contract OptimismPortal2_Test is CommonTest { if (_isCreation) _to = address(0); uint256 balanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; _mint = bound(_mint, 0, type(uint256).max - balanceBefore); // EOA emulation @@ -250,6 +297,9 @@ contract OptimismPortal2_Test is CommonTest { _data: _data }); + // Expect call to the ETHLockbox to lock the funds only if the value is greater than 0. + vm.expectCall(address(ethLockbox), _mint, abi.encodeCall(ethLockbox.lockETH, ()), _mint > 0 ? 1 : 0); + vm.deal(depositor, _mint); vm.prank(depositor, depositor); optimismPortal2.depositTransaction{ value: _mint }({ @@ -259,7 +309,9 @@ contract OptimismPortal2_Test is CommonTest { _isCreation: _isCreation, _data: _data }); - assertEq(address(optimismPortal2).balance, balanceBefore + _mint); + + assertEq(address(optimismPortal2).balance, balanceBefore); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _mint); } /// @dev Tests that `depositTransaction` succeeds for an EOA using 7702 delegation. @@ -274,6 +326,10 @@ contract OptimismPortal2_Test is CommonTest { ) external { + assumeNotForgeAddress(_7702Target); + // Prevent overflow on an upgrade context + _mint = bound(_mint, 0, type(uint256).max - address(ethLockbox).balance); + _gasLimit = uint64( bound( _gasLimit, @@ -283,8 +339,9 @@ contract OptimismPortal2_Test is CommonTest { ); if (_isCreation) _to = address(0); - uint256 balanceBefore = address(optimismPortal2).balance; - _mint = bound(_mint, 0, type(uint256).max - balanceBefore); + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; + _mint = bound(_mint, 0, type(uint256).max - portalBalanceBefore); // EOA emulation vm.expectEmit(address(optimismPortal2)); @@ -310,7 +367,8 @@ contract OptimismPortal2_Test is CommonTest { _isCreation: _isCreation, _data: _data }); - assertEq(address(optimismPortal2).balance, balanceBefore + _mint); + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _mint); } /// @dev Tests that `depositTransaction` succeeds for a contract. @@ -324,6 +382,8 @@ contract OptimismPortal2_Test is CommonTest { ) external { + // Prevent overflow on an upgrade context + _mint = bound(_mint, 0, type(uint256).max - address(ethLockbox).balance); _gasLimit = uint64( bound( _gasLimit, @@ -334,6 +394,7 @@ contract OptimismPortal2_Test is CommonTest { if (_isCreation) _to = address(0); uint256 balanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; _mint = bound(_mint, 0, type(uint256).max - balanceBefore); vm.expectEmit(address(optimismPortal2)); @@ -347,6 +408,9 @@ contract OptimismPortal2_Test is CommonTest { _data: _data }); + // Expect call to the ETHLockbox to lock the funds only if the value is greater than 0. + vm.expectCall(address(ethLockbox), _mint, abi.encodeCall(ethLockbox.lockETH, ()), _mint > 0 ? 1 : 0); + vm.deal(address(this), _mint); vm.prank(address(this)); optimismPortal2.depositTransaction{ value: _mint }({ @@ -356,7 +420,8 @@ contract OptimismPortal2_Test is CommonTest { _isCreation: _isCreation, _data: _data }); - assertEq(address(optimismPortal2).balance, balanceBefore + _mint); + assertEq(address(optimismPortal2).balance, balanceBefore); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _mint); } /// @dev Tests that the donateETH function donates ETH and does no state read/write @@ -365,6 +430,7 @@ contract OptimismPortal2_Test is CommonTest { vm.deal(alice, _amount); uint256 preBalance = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; _amount = bound(_amount, 0, type(uint256).max - preBalance); vm.startStateDiffRecording(); @@ -373,6 +439,8 @@ contract OptimismPortal2_Test is CommonTest { // not necessary since it's checked below assertEq(address(optimismPortal2).balance, preBalance + _amount); + // check that the ETHLockbox balance is unchanged + assertEq(address(ethLockbox).balance, lockboxBalanceBefore); // 0 for extcodesize of proxy before being called by this test, // 1 for the call to the proxy by the pranked address @@ -400,12 +468,34 @@ contract OptimismPortal2_Test is CommonTest { // storage accesses of delegate call of proxy to impl is empty (No storage read or write!) assertEq(accountAccesses[2].storageAccesses.length, 0); } + + /// @dev Tests that `updateLockbox` reverts if the caller is not the proxy admin owner. + function testFuzz_updateLockbox_notProxyAdminOwner_reverts(address _caller) external { + vm.assume(_caller != optimismPortal2.proxyAdminOwner()); + vm.expectRevert(IOptimismPortal.OptimismPortal_Unauthorized.selector); + + vm.prank(_caller); + optimismPortal2.updateLockbox(IETHLockbox(address(1))); + } + + /// @dev Tests that `updateLockbox` updates the ETHLockbox contract. + function testFuzz_updateLockbox_succeeds(address _newLockbox) external { + address oldLockbox = address(optimismPortal2.ethLockbox()); + vm.assume(_newLockbox != oldLockbox); + + vm.expectEmit(address(optimismPortal2)); + emit LockboxUpdated(oldLockbox, _newLockbox); + + vm.prank(optimismPortal2.proxyAdminOwner()); + optimismPortal2.updateLockbox(IETHLockbox(_newLockbox)); + + assertEq(address(optimismPortal2.ethLockbox()), _newLockbox); + } } contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Reusable default values for a test withdrawal Types.WithdrawalTransaction _defaultTx; - IFaultDisputeGame game; uint256 _proposedGameIndex; uint256 _proposedBlockNumber; @@ -428,6 +518,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { gasLimit: 100_000, data: hex"aa" // includes calldata for ERC20 withdrawal test }); + // Get withdrawal proof data we can use for testing. (_stateRoot, _storageRoot, _outputRoot, _withdrawalHash, _withdrawalProof) = ffi.getProveWithdrawalTransactionInputs(_defaultTx); @@ -455,12 +546,13 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { address(disputeGameFactory), keccak256(abi.encode(GameType.wrap(0), uint256(102))), bytes32(uint256(0)) ); } else { - // Warp forward in time to ensure that the game is created after the retirement timestamp. - vm.warp(optimismPortal2.respectedGameTypeUpdatedAt() + 1 seconds); - // Set up the dummy game. _proposedBlockNumber = 0xFF; } + + // Warp forward in time to ensure that the game is created after the retirement timestamp. + vm.warp(anchorStateRegistry.retirementTimestamp() + 1); + GameType respectedGameType = optimismPortal2.respectedGameType(); game = IFaultDisputeGame( payable( @@ -479,12 +571,12 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.warp(block.timestamp + game.maxClockDuration().raw() + 1 seconds); // Fund the portal so that we can withdraw ETH. - vm.deal(address(optimismPortal2), 0xFFFFFFFF); + vm.deal(address(ethLockbox), 0xFFFFFFFF); } /// @dev Asserts that the reentrant call will revert. function callPortalAndExpectRevert() external payable { - vm.expectRevert(NonReentrant.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_NoReentrancy.selector); // Arguments here don't matter, as the require check is the first thing that happens. // We assume that this has already been proven. optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); @@ -492,62 +584,33 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { assertFalse(optimismPortal2.finalizedWithdrawals(Hashing.hashWithdrawal(_defaultTx))); } - /// @dev Tests that `blacklistDisputeGame` reverts when called by a non-guardian. - function testFuzz_blacklist_onlyGuardian_reverts(address _act) external { - vm.assume(_act != address(optimismPortal2.guardian())); - - vm.expectRevert(Unauthorized.selector); - optimismPortal2.blacklistDisputeGame(IDisputeGame(address(0xdead))); - } - - /// @dev Tests that the guardian role can blacklist any dispute game. - function testFuzz_blacklist_guardian_succeeds(IDisputeGame _addr) external { - vm.expectEmit(address(optimismPortal2)); - emit DisputeGameBlacklisted(_addr); - - vm.prank(optimismPortal2.guardian()); - optimismPortal2.blacklistDisputeGame(_addr); + /// @dev Tests that `finalizeWithdrawalTransaction` reverts when the target is the portal contract or the lockbox. + function test_finalizeWithdrawalTransaction_badTarget_reverts() external { + _defaultTx.target = address(optimismPortal2); + vm.expectRevert(IOptimismPortal.OptimismPortal_BadTarget.selector); + optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); - assertTrue(optimismPortal2.disputeGameBlacklist(_addr)); + _defaultTx.target = address(ethLockbox); + vm.expectRevert(IOptimismPortal.OptimismPortal_BadTarget.selector); + optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } - /// @dev Tests that `setRespectedGameType` reverts when called by a non-guardian. - function testFuzz_setRespectedGameType_onlyGuardian_reverts(address _act, GameType _ty) external { - vm.assume(_act != address(optimismPortal2.guardian())); + /// @notice Sets the supeRootsActive variable to the provided value. + /// @param _superRootsActive The value to set the superRootsActive variable to. + function setSuperRootsActive(bool _superRootsActive) public { + // Get the slot for superRootsActive. + StorageSlot memory slot = ForgeArtifacts.getSlot("OptimismPortal2", "superRootsActive"); - vm.prank(_act); - vm.expectRevert(Unauthorized.selector); - optimismPortal2.setRespectedGameType(_ty); - } + // Load the existing storage slot value. + bytes32 existingValue = vm.load(address(optimismPortal2), bytes32(slot.slot)); - /// @dev Tests that the guardian role can set the respected game type to anything they want. - function testFuzz_setRespectedGameType_guardianCanSetRespectedGameType_succeeds(GameType _ty) external { - vm.assume(_ty.raw() != type(uint32).max); - uint64 respectedGameTypeUpdatedAt = optimismPortal2.respectedGameTypeUpdatedAt(); - vm.expectEmit(address(optimismPortal2)); - emit RespectedGameTypeSet(_ty, Timestamp.wrap(respectedGameTypeUpdatedAt)); - vm.prank(optimismPortal2.guardian()); - optimismPortal2.setRespectedGameType(_ty); - // GameType changes, but the timestamp doesn't. - assertEq(optimismPortal2.respectedGameType().raw(), _ty.raw()); - assertEq(optimismPortal2.respectedGameTypeUpdatedAt(), respectedGameTypeUpdatedAt); - } + // Inject the bool into the existing storage slot value with a bitwise OR. + // Shift the bool left by the offset of the storage slot and OR with existing value. + bytes32 newValue = + bytes32(uint256(uint8(_superRootsActive ? 1 : 0)) << slot.offset * 8 | uint256(existingValue)); - /// @dev Tests that the guardian can set the `respectedGameTypeUpdatedAt` timestamp to current timestamp. - function testFuzz_setRespectedGameType_guardianCanSetRespectedGameTypeUpdatedAt_succeeds(uint64 _elapsed) - external - { - _elapsed = uint64(bound(_elapsed, 0, type(uint64).max - uint64(block.timestamp))); - GameType _ty = GameType.wrap(type(uint32).max); - uint64 _newRespectedGameTypeUpdatedAt = uint64(block.timestamp) + _elapsed; - GameType _existingGameType = optimismPortal2.respectedGameType(); - vm.warp(_newRespectedGameTypeUpdatedAt); - emit RespectedGameTypeSet(_existingGameType, Timestamp.wrap(_newRespectedGameTypeUpdatedAt)); - vm.prank(optimismPortal2.guardian()); - optimismPortal2.setRespectedGameType(_ty); - // GameType doesn't change, but the timestamp does. - assertEq(optimismPortal2.respectedGameType().raw(), _existingGameType.raw()); - assertEq(optimismPortal2.respectedGameTypeUpdatedAt(), _newRespectedGameTypeUpdatedAt); + // Store the new value at the correct slot/offset. + vm.store(address(optimismPortal2), bytes32(slot.slot), newValue); } /// @dev Tests that `proveWithdrawalTransaction` reverts when paused. @@ -555,7 +618,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.prank(optimismPortal2.guardian()); superchainConfig.pause("identifier"); - vm.expectRevert(CallPaused.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_CallPaused.selector); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex, @@ -567,7 +630,35 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { /// @dev Tests that `proveWithdrawalTransaction` reverts when the target is the portal contract. function test_proveWithdrawalTransaction_onSelfCall_reverts() external { _defaultTx.target = address(optimismPortal2); - vm.expectRevert(BadTarget.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_BadTarget.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + + _defaultTx.target = address(ethLockbox); + vm.expectRevert(IOptimismPortal.OptimismPortal_BadTarget.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + + /// @dev Tests that `proveWithdrawalTransaction` reverts when the current timestamp is less + /// than or equal to the creation timestamp of the dispute game. + function testFuzz_proveWithdrawalTransaction_timestampLessThanOrEqualToCreation_reverts(uint64 _timestamp) + external + { + // Set the timestamp to be less than or equal to the creation timestamp of the dispute game. + _timestamp = uint64(bound(_timestamp, 0, game.createdAt().raw())); + vm.warp(_timestamp); + + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidProofTimestamp.selector); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex, @@ -580,7 +671,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { function test_proveWithdrawalTransaction_onInvalidOutputRootProof_reverts() external { // Modify the version to invalidate the withdrawal proof. _outputRootProof.version = bytes32(uint256(1)); - vm.expectRevert(InvalidProof.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidOutputRootProof.selector); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex, @@ -624,25 +715,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); vm.mockCall(address(game2), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); - vm.expectRevert(InvalidDisputeGame.selector); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); - } - - /// @dev Tests that `proveWithdrawalTransaction` reverts if the dispute game being proven against is not of the - /// respected game type. - function test_proveWithdrawalTransaction_badGameType_reverts() external { - vm.mockCall( - address(disputeGameFactory), - abi.encodeCall(disputeGameFactory.gameAtIndex, (_proposedGameIndex)), - abi.encode(GameType.wrap(0xFF), Timestamp.wrap(uint64(block.timestamp)), IDisputeGame(address(game))) - ); - - vm.expectRevert(InvalidGameType.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidDisputeGame.selector); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex, @@ -654,7 +727,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { /// @dev Tests that `proveWithdrawalTransaction` reverts if the game was not the respected game type when created. function test_proveWithdrawalTransaction_wasNotRespectedGameTypeWhenCreated_reverts() external { vm.mockCall(address(game), abi.encodeCall(game.wasRespectedGameTypeWhenCreated, ()), abi.encode(false)); - vm.expectRevert(InvalidGameType.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidDisputeGame.selector); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex, @@ -667,7 +740,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { /// `wasRespectedGameTypeWhenCreated`. function test_proveWithdrawalTransaction_legacyGame_reverts() external { vm.mockCallRevert(address(game), abi.encodeCall(game.wasRespectedGameTypeWhenCreated, ()), ""); - vm.expectRevert(LegacyGame.selector); + vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex, @@ -679,7 +752,8 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { /// @dev Tests that `proveWithdrawalTransaction` succeeds if the game was created after the /// game retirement timestamp. function testFuzz_proveWithdrawalTransaction_createdAfterRetirementTimestamp_succeeds(uint64 _createdAt) external { - _createdAt = uint64(bound(_createdAt, optimismPortal2.respectedGameTypeUpdatedAt() + 1, type(uint64).max)); + _createdAt = uint64(bound(_createdAt, optimismPortal2.respectedGameTypeUpdatedAt() + 1, type(uint64).max - 1)); + vm.warp(_createdAt + 1); vm.mockCall(address(game), abi.encodeCall(game.createdAt, ()), abi.encode(uint64(_createdAt))); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, @@ -696,45 +770,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { { _createdAt = uint64(bound(_createdAt, 0, optimismPortal2.respectedGameTypeUpdatedAt())); vm.mockCall(address(game), abi.encodeCall(game.createdAt, ()), abi.encode(uint64(_createdAt))); - vm.expectRevert("OptimismPortal: dispute game created before respected game type was updated"); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); - } - - /// @dev Tests that `proveWithdrawalTransaction` can be re-executed if the dispute game proven against has been - /// blacklisted. - function test_proveWithdrawalTransaction_replayProveBlacklisted_succeeds() external { - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); - - // Blacklist the dispute dispute game. - vm.prank(optimismPortal2.guardian()); - optimismPortal2.blacklistDisputeGame(IDisputeGame(address(game))); - - // Mock the status of the dispute game we just proved against to be CHALLENGER_WINS. - vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); - // Create a new game to re-prove against - disputeGameFactory.create( - optimismPortal2.respectedGameType(), Claim.wrap(_outputRoot), abi.encode(_proposedBlockNumber + 1) - ); - _proposedGameIndex = disputeGameFactory.gameCount() - 1; - - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); + vm.expectRevert(IOptimismPortal.OptimismPortal_ImproperDisputeGame.selector); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex, @@ -759,12 +795,16 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Mock the status of the dispute game we just proved against to be CHALLENGER_WINS. vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); + // Create a new game to re-prove against disputeGameFactory.create( optimismPortal2.respectedGameType(), Claim.wrap(_outputRoot), abi.encode(_proposedBlockNumber + 1) ); _proposedGameIndex = disputeGameFactory.gameCount() - 1; + // Warp 1 second into the future so we're not in the same block as the dispute game. + vm.warp(block.timestamp + 1 seconds); + vm.expectEmit(true, true, true, true); emit WithdrawalProven(_withdrawalHash, alice, bob); vm.expectEmit(true, true, true, true); @@ -798,7 +838,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Update the respected game type to 0xbeef. vm.prank(optimismPortal2.guardian()); - optimismPortal2.setRespectedGameType(GameType.wrap(0xbeef)); + anchorStateRegistry.setRespectedGameType(GameType.wrap(0xbeef)); // Create a new game and mock the game type as 0xbeef in the factory. vm.mockCall( @@ -807,6 +847,9 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { abi.encode(GameType.wrap(0xbeef), Timestamp.wrap(uint64(block.timestamp)), IDisputeGame(address(newGame))) ); + // Warp 1 second into the future so we're not in the same block as the dispute game. + vm.warp(block.timestamp + 1 seconds); + // Re-proving should be successful against the new game. vm.expectEmit(true, true, true, true); emit WithdrawalProven(_withdrawalHash, alice, bob); @@ -820,6 +863,216 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { }); } + /// @dev Tests that `proveWithdrawalTransaction` reverts when using the Output Roots version of + /// `proveWithdrawalTransaction` when `superRootsActive` is true. + function test_proveWithdrawalTransaction_outputRootVersionWhenSuperRootsActive_reverts() external { + // Set superRootsActive to true. + setSuperRootsActive(true); + + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_WrongProofMethod.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + + /// @dev Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version of + /// `proveWithdrawalTransaction` when `superRootsActive` is false. + function test_proveWithdrawalTransaction_superRootsVersionWhenSuperRootsInactive_reverts() external { + // Set up a dummy super root proof. + Types.OutputRootWithChainId[] memory outputRootWithChainIdArr = new Types.OutputRootWithChainId[](1); + outputRootWithChainIdArr[0] = + Types.OutputRootWithChainId({ root: _outputRoot, chainId: systemConfig.l2ChainId() }); + Types.SuperRootProof memory superRootProof = Types.SuperRootProof({ + version: 0x01, + timestamp: uint64(block.timestamp), + outputRoots: outputRootWithChainIdArr + }); + + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_WrongProofMethod.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + + /// @dev Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version of + /// `proveWithdrawalTransaction` when the provided proof is invalid. + function test_proveWithdrawalTransaction_superRootsVersionBadProof_reverts() external { + // Enable super roots. + setSuperRootsActive(true); + + // Set up a dummy super root proof. + Types.OutputRootWithChainId[] memory outputRootWithChainIdArr = new Types.OutputRootWithChainId[](1); + outputRootWithChainIdArr[0] = + Types.OutputRootWithChainId({ root: _outputRoot, chainId: systemConfig.l2ChainId() }); + Types.SuperRootProof memory superRootProof = Types.SuperRootProof({ + version: 0x01, + timestamp: uint64(block.timestamp), + outputRoots: outputRootWithChainIdArr + }); + + // Should revert because the proof is wrong. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidSuperRootProof.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + + /// @dev Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version of + /// `proveWithdrawalTransaction` when the provided proof is valid but the index is out of + /// bounds. + function test_proveWithdrawalTransaction_superRootsVersionBadIndex_reverts() external { + // Enable super roots. + setSuperRootsActive(true); + + // Set up a dummy super root proof. + Types.OutputRootWithChainId[] memory outputRootWithChainIdArr = new Types.OutputRootWithChainId[](1); + outputRootWithChainIdArr[0] = + Types.OutputRootWithChainId({ root: _outputRoot, chainId: systemConfig.l2ChainId() }); + Types.SuperRootProof memory superRootProof = Types.SuperRootProof({ + version: 0x01, + timestamp: uint64(block.timestamp), + outputRoots: outputRootWithChainIdArr + }); + + // Figure out what the right hash would be. + bytes32 expectedSuperRoot = Hashing.hashSuperRootProof(superRootProof); + + // Mock the game to return the expected super root. + vm.mockCall(address(game), abi.encodeCall(game.rootClaim, ()), abi.encode(expectedSuperRoot)); + + // Should revert because the proof is wrong. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidOutputRootIndex.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: outputRootWithChainIdArr.length, // out of bounds + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + + /// @dev Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version of + /// `proveWithdrawalTransaction` when the provided proof is valid, index is correct, but + /// the output root has the wrong chain id. + function test_proveWithdrawalTransaction_superRootsVersionBadChainId_reverts() external { + // Enable super roots. + setSuperRootsActive(true); + + // Set up a dummy super root proof. + Types.OutputRootWithChainId[] memory outputRootWithChainIdArr = new Types.OutputRootWithChainId[](1); + outputRootWithChainIdArr[0] = Types.OutputRootWithChainId({ + root: _outputRoot, + chainId: systemConfig.l2ChainId() + 1 // wrong chain id + }); + Types.SuperRootProof memory superRootProof = Types.SuperRootProof({ + version: 0x01, + timestamp: uint64(block.timestamp), + outputRoots: outputRootWithChainIdArr + }); + + // Figure out what the right hash would be. + bytes32 expectedSuperRoot = Hashing.hashSuperRootProof(superRootProof); + + // Mock the game to return the expected super root. + vm.mockCall(address(game), abi.encodeCall(game.rootClaim, ()), abi.encode(expectedSuperRoot)); + + // Should revert because the proof is wrong. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidOutputRootChainId.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + + /// @dev Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version of + /// `proveWithdrawalTransaction` when the provided proof is valid, index is correct, chain + /// id is correct, but the output root proof is invalid. + function test_proveWithdrawalTransaction_superRootsVersionBadOutputRootProof_reverts() external { + // Enable super roots. + setSuperRootsActive(true); + + // Set up a dummy super root proof. + Types.OutputRootWithChainId[] memory outputRootWithChainIdArr = new Types.OutputRootWithChainId[](1); + outputRootWithChainIdArr[0] = Types.OutputRootWithChainId({ + root: keccak256(abi.encode(_outputRoot)), // random root so the proof is wrong + chainId: systemConfig.l2ChainId() + }); + Types.SuperRootProof memory superRootProof = Types.SuperRootProof({ + version: 0x01, + timestamp: uint64(block.timestamp), + outputRoots: outputRootWithChainIdArr + }); + + // Figure out what the right hash would be. + bytes32 expectedSuperRoot = Hashing.hashSuperRootProof(superRootProof); + + // Mock the game to return the expected super root. + vm.mockCall(address(game), abi.encodeCall(game.rootClaim, ()), abi.encode(expectedSuperRoot)); + + // Should revert because the proof is wrong. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidOutputRootProof.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + + /// @dev Tests that `proveWithdrawalTransaction` succeeds when all parameters are valid. + function test_proveWithdrawalTransaction_superRootsVersion_succeeds() external { + // Enable super roots. + setSuperRootsActive(true); + + // Set up a dummy super root proof. + Types.OutputRootWithChainId[] memory outputRootWithChainIdArr = new Types.OutputRootWithChainId[](1); + outputRootWithChainIdArr[0] = + Types.OutputRootWithChainId({ root: _outputRoot, chainId: systemConfig.l2ChainId() }); + Types.SuperRootProof memory superRootProof = Types.SuperRootProof({ + version: 0x01, + timestamp: uint64(block.timestamp), + outputRoots: outputRootWithChainIdArr + }); + + // Figure out what the right hash would be. + bytes32 expectedSuperRoot = Hashing.hashSuperRootProof(superRootProof); + + // Mock the game to return the expected super root. + vm.mockCall(address(game), abi.encodeCall(game.rootClaim, ()), abi.encode(expectedSuperRoot)); + + // Should succeed. + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + /// @dev Tests that `proveWithdrawalTransaction` succeeds. function test_proveWithdrawalTransaction_validWithdrawalProof_succeeds() external { vm.expectEmit(true, true, true, true); @@ -873,7 +1126,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { emit WithdrawalFinalized(_withdrawalHash, true); optimismPortal2.finalizeWithdrawalTransactionExternalProof(_defaultTx, address(0xb0b)); - vm.expectRevert(AlreadyFinalized.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_AlreadyFinalized.selector); optimismPortal2.finalizeWithdrawalTransactionExternalProof(_defaultTx, address(this)); assert(address(bob).balance == bobBalanceBefore + 100); @@ -895,7 +1148,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); vm.startPrank(alice, Constants.ESTIMATION_ADDRESS); - vm.expectRevert(GasEstimation.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_GasEstimation.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } @@ -940,7 +1193,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.warp(block.timestamp + game_noData.maxClockDuration().raw() + 1 seconds); // Fund the portal so that we can withdraw ETH. vm.store(address(optimismPortal2), bytes32(uint256(61)), bytes32(uint256(0xFFFFFFFF))); - vm.deal(address(optimismPortal2), 0xFFFFFFFF); + vm.deal(address(ethLockbox), 0xFFFFFFFF); uint256 bobBalanceBefore = bob.balance; @@ -1005,7 +1258,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { ); // Warp 1 second into the future so that the proof is submitted after the timestamp of game creation. - vm.warp(block.timestamp + 1 seconds); + vm.warp(block.timestamp + 1); // Prove the withdrawal transaction against the invalid dispute game, as 0xb0b. vm.expectEmit(true, true, true, true); @@ -1044,7 +1297,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Ensure both proofs are registered successfully. assertEq(optimismPortal2.numProofSubmitters(_withdrawalHash), 2); - vm.expectRevert(ProposalNotValidated.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); vm.prank(address(0xb0b)); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); @@ -1060,7 +1313,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.prank(optimismPortal2.guardian()); superchainConfig.pause("identifier"); - vm.expectRevert(CallPaused.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_CallPaused.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } @@ -1068,7 +1321,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { function test_finalizeWithdrawalTransaction_ifWithdrawalNotProven_reverts() external { uint256 bobBalanceBefore = address(bob).balance; - vm.expectRevert(Unproven.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_Unproven.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); assert(address(bob).balance == bobBalanceBefore); @@ -1090,7 +1343,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { _withdrawalProof: _withdrawalProof }); - vm.expectRevert("OptimismPortal: proven withdrawal has not matured yet"); + vm.expectRevert(IOptimismPortal.OptimismPortal_ProofNotOldEnough.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); assert(address(bob).balance == bobBalanceBefore); @@ -1120,7 +1373,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.mockCall(address(game), abi.encodeCall(game.createdAt, ()), abi.encode(block.timestamp + 1)); // Attempt to finalize the withdrawal - vm.expectRevert("OptimismPortal: withdrawal timestamp less than dispute game creation timestamp"); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidProofTimestamp.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); // Ensure that bob's balance has remained the same @@ -1148,7 +1401,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); // Attempt to finalize the withdrawal - vm.expectRevert(ProposalNotValidated.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); // Ensure that bob's balance has remained the same @@ -1206,7 +1459,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { emit WithdrawalFinalized(_withdrawalHash, true); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); - vm.expectRevert(AlreadyFinalized.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_AlreadyFinalized.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } @@ -1324,7 +1577,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Total ETH supply is currently about 120M ETH. uint256 value = bound(_value, 0, 200_000_000 ether); - vm.deal(address(optimismPortal2), value); + vm.deal(address(ethLockbox), value); uint256 gasLimit = bound(_gasLimit, 0, 50_000_000); uint256 nonce = l2ToL1MessagePasser.messageNonce(); @@ -1404,7 +1657,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Total ETH supply is currently about 120M ETH. uint256 value = bound(_value, 0, 200_000_000 ether); - vm.deal(address(optimismPortal2), value); + vm.deal(address(ethLockbox), value); uint256 gasLimit = bound(_gasLimit, 0, 50_000_000); uint256 nonce = l2ToL1MessagePasser.messageNonce(); @@ -1455,7 +1708,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Change the respectedGameType vm.prank(optimismPortal2.guardian()); - optimismPortal2.setRespectedGameType(_newGameType); + anchorStateRegistry.setRespectedGameType(_newGameType); // Withdrawal transaction still finalizable vm.expectCallMinGas(_tx.target, _tx.value, uint64(_tx.gasLimit), _tx.data); @@ -1481,11 +1734,11 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { game.resolve(); vm.prank(optimismPortal2.guardian()); - optimismPortal2.blacklistDisputeGame(IDisputeGame(address(game))); + anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(game))); vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); - vm.expectRevert(Blacklisted.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } @@ -1511,7 +1764,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { game.resolve(); // Attempt to finalize the withdrawal directly after the game resolves. This should fail. - vm.expectRevert("OptimismPortal: output proposal in air-gap"); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); // Finalize the withdrawal transaction. This should succeed. @@ -1546,9 +1799,10 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Set respectedGameTypeUpdatedAt. vm.prank(optimismPortal2.guardian()); - optimismPortal2.setRespectedGameType(GameType.wrap(type(uint32).max)); + anchorStateRegistry.updateRetirementTimestamp(); - vm.expectRevert("OptimismPortal: dispute game created before respected game type was updated"); + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } @@ -1578,7 +1832,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.mockCall(address(game), abi.encodeCall(game.wasRespectedGameTypeWhenCreated, ()), abi.encode(false)); - vm.expectRevert(InvalidGameType.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } @@ -1607,9 +1861,11 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Warp past the dispute game finality delay. vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); + // Mock the wasRespectedGameTypeWhenCreated call to revert. vm.mockCallRevert(address(game), abi.encodeCall(game.wasRespectedGameTypeWhenCreated, ()), ""); - vm.expectRevert(LegacyGame.selector); + // Should revert. + vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } @@ -1629,13 +1885,13 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Attempt to finalize the withdrawal transaction 1 second before the proof has matured. This should fail. vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds()); - vm.expectRevert("OptimismPortal: proven withdrawal has not matured yet"); + vm.expectRevert(IOptimismPortal.OptimismPortal_ProofNotOldEnough.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); // Warp 1 second in the future, past the proof maturity delay, and attempt to finalize the withdrawal. // This should also fail, since the dispute game has not resolved yet. vm.warp(block.timestamp + 1 seconds); - vm.expectRevert(ProposalNotValidated.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); // Finalize the dispute game and attempt to finalize the withdrawal again. This should also fail, since the @@ -1643,7 +1899,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { game.resolveClaim(0, 0); game.resolve(); vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds()); - vm.expectRevert("OptimismPortal: output proposal in air-gap"); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); // Warp 1 second in the future, past the air gap dispute game delay, and attempt to finalize the withdrawal. @@ -1652,6 +1908,140 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); assertTrue(optimismPortal2.finalizedWithdrawals(_withdrawalHash)); } + + /// @notice Tests that checkWithdrawal succeeds if the withdrawal has been proven, the dispute + /// game has been finalized, and the root claim is valid. + function test_checkWithdrawal_succeeds() external { + // Prove the withdrawal transaction. + vm.expectEmit(true, true, true, true); + emit WithdrawalProven(_withdrawalHash, alice, bob); + vm.expectEmit(true, true, true, true); + emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + + // Warp past the finalization period. + vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + + // Mark the dispute game as CHALLENGER_WINS. + vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); + + // Mock isGameClaimValid to return true. + vm.mockCall( + address(anchorStateRegistry), abi.encodeCall(anchorStateRegistry.isGameClaimValid, (game)), abi.encode(true) + ); + + // Should succeed. + optimismPortal2.checkWithdrawal(_withdrawalHash, address(this)); + } + + /// @notice Tests that checkWithdrawal reverts if the withdrawal has already been finalized. + function test_checkWithdrawal_ifAlreadyFinalized_reverts() external { + // Prove the withdrawal transaction. + vm.expectEmit(true, true, true, true); + emit WithdrawalProven(_withdrawalHash, alice, bob); + vm.expectEmit(true, true, true, true); + emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + + // Warp and resolve the dispute game. + game.resolveClaim(0, 0); + game.resolve(); + vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + + // Finalize the withdrawal. + optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); + + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_AlreadyFinalized.selector); + optimismPortal2.checkWithdrawal(_withdrawalHash, address(this)); + } + + /// @notice Tests that checkWithdrawal reverts if the withdrawal has not been proven. + function test_checkWithdrawal_ifUnproven_reverts() external { + // Don't prove the withdrawal transaction. + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_Unproven.selector); + optimismPortal2.checkWithdrawal(_withdrawalHash, address(this)); + } + + /// @notice Tests that checkWithdrawal reverts if the proof timestamp is greater than the game + /// creation timestamp. + function testFuzz_checkWithdrawal_ifInvalidProofTimestamp_reverts(uint64 _createdAt) external { + // Prove the withdrawal transaction. + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + + // Mock the game creation timestamp to be greater than the proof timestamp. + _createdAt = uint64(bound(_createdAt, block.timestamp, type(uint64).max)); + vm.mockCall(address(game), abi.encodeCall(game.createdAt, ()), abi.encode(_createdAt)); + + // Warp beyond the proof maturity delay. + vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + + // Mark the dispute game as CHALLENGER_WINS. + vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); + + // Mock isGameClaimValid to return true. + vm.mockCall( + address(anchorStateRegistry), abi.encodeCall(anchorStateRegistry.isGameClaimValid, (game)), abi.encode(true) + ); + + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidProofTimestamp.selector); + optimismPortal2.checkWithdrawal(_withdrawalHash, address(this)); + } + + /// @notice Tests that checkWithdrawal reverts if the proof timestamp is less than the proof + /// maturity delay. + function test_checkWithdrawal_ifProofNotOldEnough_reverts() external { + // Prove but don't warp ahead past the proof maturity delay. + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + + // Should revert. + vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() - 1); + vm.expectRevert(IOptimismPortal.OptimismPortal_ProofNotOldEnough.selector); + optimismPortal2.checkWithdrawal(_withdrawalHash, address(this)); + } + + /// @notice Tests that checkWithdrawal reverts if the root claim is invalid. + function test_checkWithdrawal_ifInvalidRootClaim_reverts() external { + // Prove the withdrawal. + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + + // Warp past the proof maturity delay. + vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + + // Mock the game to have CHALLENGER_WINS status + vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); + + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); + optimismPortal2.checkWithdrawal(_withdrawalHash, address(this)); + } } contract OptimismPortal2_Upgradeable_Test is CommonTest { @@ -1682,7 +2072,7 @@ contract OptimismPortal2_Upgradeable_Test is CommonTest { // The value passed to the initialize must be larger than the last value // that initialize was called with. IProxy(payable(address(optimismPortal2))).upgradeToAndCall( - address(nextImpl), abi.encodeCall(NextImpl.initialize, (2)) + address(nextImpl), abi.encodeCall(NextImpl.initialize, (3)) ); assertEq(IProxy(payable(address(optimismPortal2))).implementation(), address(nextImpl)); @@ -1693,6 +2083,107 @@ contract OptimismPortal2_Upgradeable_Test is CommonTest { } } +contract OptimismPortal2_LiquidityMigration_Test is CommonTest { + function setUp() public override { + super.setUp(); + } + + /// @notice Tests the liquidity migration from the portal to the lockbox reverts if not called by the admin owner. + function testFuzz_migrateLiquidity_notProxyAdminOwner_reverts(address _caller) external { + vm.assume(_caller != optimismPortal2.proxyAdminOwner()); + vm.expectRevert(IOptimismPortal.OptimismPortal_Unauthorized.selector); + vm.prank(_caller); + optimismPortal2.migrateLiquidity(); + } + + /// @notice Tests that the liquidity migration from the portal to the lockbox succeeds. + function test_migrateLiquidity_succeeds(uint256 _portalBalance) external { + _portalBalance = uint256(bound(_portalBalance, 0, type(uint256).max - address(ethLockbox).balance)); + vm.deal(address(optimismPortal2), _portalBalance); + + uint256 lockboxBalanceBefore = address(ethLockbox).balance; + address proxyAdminOwner = optimismPortal2.proxyAdminOwner(); + + vm.expectCall(address(ethLockbox), _portalBalance, abi.encodeCall(ethLockbox.lockETH, ())); + + vm.expectEmit(address(optimismPortal2)); + emit ETHMigrated(address(ethLockbox), _portalBalance); + + vm.prank(proxyAdminOwner); + optimismPortal2.migrateLiquidity(); + + assertEq(address(optimismPortal2).balance, 0); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _portalBalance); + } +} + +/// @title OptimismPortal2_upgrade_Test +/// @notice Reusable test for the current upgrade() function in the OptimismPortal2 contract. If +/// the upgrade() function is changed, tests inside of this contract should be updated to +/// reflect the new function. If the upgrade() function is removed, remove the +/// corresponding tests but leave this contract in place so it's easy to add tests back +/// in the future. +contract OptimismPortal2_upgrade_Test is CommonTest { + function setUp() public override { + super.setUp(); + } + + /// @notice Tests that the upgrade() function succeeds. + function test_upgrade_succeeds() external { + // Get the slot for _initialized. + StorageSlot memory slot = ForgeArtifacts.getSlot("OptimismPortal2", "_initialized"); + + // Set the initialized slot to 0. + vm.store(address(optimismPortal2), bytes32(slot.slot), bytes32(0)); + + // Trigger upgrade(). + optimismPortal2.upgrade(IAnchorStateRegistry(address(0xdeadbeef)), IETHLockbox(ethLockbox), true); + + // Verify that the initialized slot was updated. + bytes32 initializedSlotAfter = vm.load(address(optimismPortal2), bytes32(slot.slot)); + assertEq(initializedSlotAfter, bytes32(uint256(2))); + + // Verify that superRootsActive was set to true. + assertEq(optimismPortal2.superRootsActive(), true); + + // Verify that the AnchorStateRegistry was set. + assertEq(address(optimismPortal2.anchorStateRegistry()), address(0xdeadbeef)); + } + + /// @notice Tests that the upgrade() function reverts if called a second time. + function test_upgrade_upgradeTwice_reverts() external { + // Get the slot for _initialized. + StorageSlot memory slot = ForgeArtifacts.getSlot("OptimismPortal2", "_initialized"); + + // Set the initialized slot to 0. + vm.store(address(optimismPortal2), bytes32(slot.slot), bytes32(0)); + + // Trigger first upgrade. + optimismPortal2.upgrade(IAnchorStateRegistry(address(0xdeadbeef)), IETHLockbox(ethLockbox), true); + + // Try to trigger second upgrade. + vm.expectRevert("Initializable: contract is already initialized"); + optimismPortal2.upgrade(IAnchorStateRegistry(address(0xdeadbeef)), IETHLockbox(ethLockbox), true); + } + + /// @notice Tests that the upgrade() function reverts if called after initialization. + function test_upgrade_afterInitialization_reverts() external { + // Get the slot for _initialized. + StorageSlot memory slot = ForgeArtifacts.getSlot("OptimismPortal2", "_initialized"); + + // Slot value should be set to 2 (already initialized). + bytes32 initializedSlotBefore = vm.load(address(optimismPortal2), bytes32(slot.slot)); + assertEq(initializedSlotBefore, bytes32(uint256(2))); + + // AnchorStateRegistry address should be non-zero. + assertNotEq(address(optimismPortal2.anchorStateRegistry()), address(0)); + + // Try to trigger upgrade(). + vm.expectRevert("Initializable: contract is already initialized"); + optimismPortal2.upgrade(IAnchorStateRegistry(address(0xdeadbeef)), IETHLockbox(ethLockbox), true); + } +} + /// @title OptimismPortal2_ResourceFuzz_Test /// @dev Test various values of the resource metering config to ensure that deposits cannot be /// broken by changing the config. diff --git a/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol index df9100ce8817f..3a8c9973e62dc 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol @@ -7,7 +7,6 @@ import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { Constants } from "src/libraries/Constants.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; -import "src/libraries/PortalErrors.sol"; // Interfaces import { IL1BlockInterop, ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; @@ -46,7 +45,7 @@ contract OptimismPortalInterop_Test is CommonTest { /// @dev Tests that setting the add dependency config as not the system config reverts. function testFuzz_setConfig_addDependencyButNotSystemConfig_reverts(bytes calldata _value) public { - vm.expectRevert(Unauthorized.selector); + vm.expectRevert(IOptimismPortalInterop.OptimismPortal_Unauthorized.selector); _optimismPortalInterop().setConfig(ConfigType.ADD_DEPENDENCY, _value); } @@ -69,7 +68,7 @@ contract OptimismPortalInterop_Test is CommonTest { /// @dev Tests that setting the remove dependency config as not the system config reverts. function testFuzz_setConfig_removeDependencyButNotSystemConfig_reverts(bytes calldata _value) public { - vm.expectRevert(Unauthorized.selector); + vm.expectRevert(IOptimismPortalInterop.OptimismPortal_Unauthorized.selector); _optimismPortalInterop().setConfig(ConfigType.REMOVE_DEPENDENCY, _value); } diff --git a/packages/contracts-bedrock/test/L1/SystemConfig.t.sol b/packages/contracts-bedrock/test/L1/SystemConfig.t.sol index fbae6b41524b3..9fc8dfc974414 100644 --- a/packages/contracts-bedrock/test/L1/SystemConfig.t.sol +++ b/packages/contracts-bedrock/test/L1/SystemConfig.t.sol @@ -4,6 +4,9 @@ pragma solidity 0.8.15; // Testing import { CommonTest } from "test/setup/CommonTest.sol"; +// Scripts +import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; + // Libraries import { Constants } from "src/libraries/Constants.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; @@ -114,6 +117,7 @@ contract SystemConfig_Initialize_Test is SystemConfig_Init { assertEq(addrs.optimismPortal, address(optimismPortal2)); assertEq(address(systemConfig.optimismMintableERC20Factory()), address(optimismMintableERC20Factory)); assertEq(addrs.optimismMintableERC20Factory, address(optimismMintableERC20Factory)); + assertNotEq(systemConfig.l2ChainId(), 0); } } @@ -145,7 +149,8 @@ contract SystemConfig_Initialize_TestFail is SystemConfig_Initialize_Test { disputeGameFactory: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0) - }) + }), + _l2ChainId: 1234 }); } @@ -174,7 +179,8 @@ contract SystemConfig_Initialize_TestFail is SystemConfig_Initialize_Test { disputeGameFactory: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0) - }) + }), + _l2ChainId: 1234 }); assertEq(systemConfig.startBlock(), block.number); } @@ -204,7 +210,8 @@ contract SystemConfig_Initialize_TestFail is SystemConfig_Initialize_Test { disputeGameFactory: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0) - }) + }), + _l2ChainId: 1234 }); assertEq(systemConfig.startBlock(), 1); } @@ -318,7 +325,8 @@ contract SystemConfig_Init_ResourceConfig is SystemConfig_Init { disputeGameFactory: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0) - }) + }), + _l2ChainId: 1234 }); } } @@ -480,3 +488,63 @@ contract SystemConfig_Setters_Test is SystemConfig_Init { assertEq(systemConfig.eip1559Elasticity(), _elasticity); } } + +/// @title SystemConfig_upgrade_Test +/// @notice Reusable test for the current upgrade() function in the SystemConfig contract. If +/// the upgrade() function is changed, tests inside of this contract should be updated to +/// reflect the new function. If the upgrade() function is removed, remove the +/// corresponding tests but leave this contract in place so it's easy to add tests back +/// in the future. +contract SystemConfig_upgrade_Test is SystemConfig_Init { + /// @notice Tests that the upgrade() function succeeds. + function test_upgrade_succeeds() external { + // Get the slot for _initialized. + StorageSlot memory slot = ForgeArtifacts.getSlot("SystemConfig", "_initialized"); + + // Set the initialized slot to 0. + vm.store(address(systemConfig), bytes32(slot.slot), bytes32(0)); + + // Trigger upgrade(). + systemConfig.upgrade(1234); + + // Verify that the initialized slot was updated. + bytes32 initializedSlotAfter = vm.load(address(systemConfig), bytes32(slot.slot)); + assertEq(initializedSlotAfter, bytes32(uint256(2))); + + // Verify that the l2ChainId was updated. + assertEq(systemConfig.l2ChainId(), 1234); + } + + /// @notice Tests that the upgrade() function reverts if called a second time. + function test_upgrade_upgradeTwice_reverts() external { + // Get the slot for _initialized. + StorageSlot memory slot = ForgeArtifacts.getSlot("SystemConfig", "_initialized"); + + // Set the initialized slot to 0. + vm.store(address(systemConfig), bytes32(slot.slot), bytes32(0)); + + // Trigger first upgrade. + systemConfig.upgrade(1234); + + // Try to trigger second upgrade. + vm.expectRevert("Initializable: contract is already initialized"); + systemConfig.upgrade(1234); + } + + /// @notice Tests that the upgrade() function reverts if called after initialization. + function test_upgrade_afterInitialization_reverts() external { + // Get the slot for _initialized. + StorageSlot memory slot = ForgeArtifacts.getSlot("SystemConfig", "_initialized"); + + // Slot value should be set to 2 (already initialized). + bytes32 initializedSlotBefore = vm.load(address(systemConfig), bytes32(slot.slot)); + assertEq(initializedSlotBefore, bytes32(uint256(2))); + + // l2ChainId should be non-zero. + assertNotEq(systemConfig.l2ChainId(), 0); + + // Try to trigger upgrade(). + vm.expectRevert("Initializable: contract is already initialized"); + systemConfig.upgrade(1234); + } +} diff --git a/packages/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol b/packages/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol index b3c7bf06400c3..c171d97737eb8 100644 --- a/packages/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol +++ b/packages/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol @@ -8,6 +8,7 @@ import { FaultDisputeGame_Init, _changeClaimStatus } from "test/dispute/FaultDis import { GameType, GameStatus, Hash, Claim, VMStatuses, OutputRoot } from "src/dispute/lib/Types.sol"; // Interfaces +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; @@ -15,8 +16,9 @@ contract AnchorStateRegistry_Init is FaultDisputeGame_Init { /// @dev A valid l2BlockNumber that comes after the current anchor root block. uint256 validL2BlockNumber; - event AnchorNotUpdated(IFaultDisputeGame indexed game); event AnchorUpdated(IFaultDisputeGame indexed game); + event RespectedGameTypeSet(GameType gameType); + event RetirementTimestampSet(uint256 timestamp); function setUp() public virtual override { // Duplicating the initialization/setup logic of FaultDisputeGame_Test. @@ -46,7 +48,6 @@ contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_Init { // Verify contract addresses. assert(anchorStateRegistry.superchainConfig() == superchainConfig); assert(anchorStateRegistry.disputeGameFactory() == disputeGameFactory); - assert(anchorStateRegistry.portal() == optimismPortal2); } } @@ -57,11 +58,11 @@ contract AnchorStateRegistry_Initialize_TestFail is AnchorStateRegistry_Init { anchorStateRegistry.initialize( superchainConfig, disputeGameFactory, - optimismPortal2, OutputRoot({ root: Hash.wrap(0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF), l2BlockNumber: 0 - }) + }), + GameType.wrap(0) ); } } @@ -73,6 +74,25 @@ contract AnchorStateRegistry_Version_Test is AnchorStateRegistry_Init { } } +contract AnchorStateRegistry_Paused_Test is AnchorStateRegistry_Init { + /// @notice Tests that paused() will return the correct value. + function test_paused_succeeds() public { + // Pause the superchain. + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("testing"); + + // Paused should return true. + assertTrue(anchorStateRegistry.paused()); + + // Unpause the superchain. + vm.prank(superchainConfig.guardian()); + superchainConfig.unpause(); + + // Paused should return false. + assertFalse(anchorStateRegistry.paused()); + } +} + contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_Init { /// @notice Tests that getAnchorRoot will return the value of the starting anchor root when no /// anchor game exists yet. @@ -106,6 +126,29 @@ contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_Init { assertEq(l2BlockNumber, gameProxy.l2BlockNumber()); } + /// @notice Tests that getAnchorRoot will return the latest anchor root even if the superchain + /// is paused. + function test_getAnchorRoot_superchainPaused_succeeds() public { + // Mock the game to be resolved. + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); + vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); + + // Mock the game to be the defender wins. + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); + + // Set the anchor game to the game proxy. + anchorStateRegistry.setAnchorState(gameProxy); + + // Pause the superchain. + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("testing"); + + // We should get the anchor root back. + (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); + assertEq(root.raw(), gameProxy.rootClaim().raw()); + assertEq(l2BlockNumber, gameProxy.l2BlockNumber()); + } + /// @notice Tests that getAnchorRoot returns even if the anchor game is blacklisted. function test_getAnchorRoot_blacklistedGame_succeeds() public { // Mock the game to be resolved. @@ -118,12 +161,9 @@ contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_Init { // Set the anchor game to the game proxy. anchorStateRegistry.setAnchorState(gameProxy); - // Mock the disputeGameBlacklist call to return true. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)), - abi.encode(true) - ); + // Blacklist the game. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.blacklistDisputeGame(gameProxy); // Get the anchor root. (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); @@ -172,12 +212,11 @@ contract AnchorStateRegistry_IsGameRegistered_Test is AnchorStateRegistry_Init { contract AnchorStateRegistry_IsGameBlacklisted_Test is AnchorStateRegistry_Init { /// @notice Tests that isGameBlacklisted will return true if the game is blacklisted. function test_isGameBlacklisted_isActuallyBlacklisted_succeeds() public { - // Mock the disputeGameBlacklist call to return true. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)), - abi.encode(true) - ); + // Blacklist the game. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.blacklistDisputeGame(gameProxy); + + // Should return true. assertTrue(anchorStateRegistry.isGameBlacklisted(gameProxy)); } @@ -185,8 +224,8 @@ contract AnchorStateRegistry_IsGameBlacklisted_Test is AnchorStateRegistry_Init function test_isGameBlacklisted_isNotBlacklisted_succeeds() public { // Mock the disputeGameBlacklist call to return false. vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)), + address(anchorStateRegistry), + abi.encodeCall(anchorStateRegistry.disputeGameBlacklist, (gameProxy)), abi.encode(false) ); assertFalse(anchorStateRegistry.isGameBlacklisted(gameProxy)); @@ -214,34 +253,35 @@ contract AnchorStateRegistry_IsGameRespected_Test is AnchorStateRegistry_Init { contract AnchorStateRegistry_IsGameRetired_Test is AnchorStateRegistry_Init { /// @notice Tests that isGameRetired will return true if the game is retired. - /// @param _retirementTimestamp The retirement timestamp to use for the test. - function testFuzz_isGameRetired_isRetired_succeeds(uint64 _retirementTimestamp) public { - // Make sure retirement timestamp is greater than or equal to the game's creation time. - _retirementTimestamp = uint64(bound(_retirementTimestamp, gameProxy.createdAt().raw(), type(uint64).max)); + /// @param _createdAtTimestamp The createdAt timestamp to use for the test. + function testFuzz_isGameRetired_isRetired_succeeds(uint64 _createdAtTimestamp) public { + // Set the retirement timestamp to now. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); + + // Make sure createdAt timestamp is less than or equal to the retirementTimestamp. + _createdAtTimestamp = uint64(bound(_createdAtTimestamp, 0, anchorStateRegistry.retirementTimestamp())); // Mock the respectedGameTypeUpdatedAt call. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()), - abi.encode(_retirementTimestamp) - ); + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.createdAt, ()), abi.encode(_createdAtTimestamp)); // Game should be retired. assertTrue(anchorStateRegistry.isGameRetired(gameProxy)); } /// @notice Tests that isGameRetired will return false if the game is not retired. - /// @param _retirementTimestamp The retirement timestamp to use for the test. - function testFuzz_isGameRetired_isNotRetired_succeeds(uint64 _retirementTimestamp) public { - // Make sure retirement timestamp is earlier than the game's creation time. - _retirementTimestamp = uint64(bound(_retirementTimestamp, 0, gameProxy.createdAt().raw() - 1)); + /// @param _createdAtTimestamp The createdAt timestamp to use for the test. + function testFuzz_isGameRetired_isNotRetired_succeeds(uint64 _createdAtTimestamp) public { + // Set the retirement timestamp to now. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); - // Mock the respectedGameTypeUpdatedAt call to be earlier than the game's creation time. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()), - abi.encode(_retirementTimestamp) - ); + // Make sure createdAt timestamp is greater than the retirementTimestamp. + _createdAtTimestamp = + uint64(bound(_createdAtTimestamp, anchorStateRegistry.retirementTimestamp() + 1, type(uint64).max)); + + // Mock the call to createdAt. + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.createdAt, ()), abi.encode(_createdAtTimestamp)); // Game should not be retired. assertFalse(anchorStateRegistry.isGameRetired(gameProxy)); @@ -287,29 +327,38 @@ contract AnchorStateRegistry_IsGameProper_Test is AnchorStateRegistry_Init { /// @notice Tests that isGameProper will return false if the game is blacklisted. function test_isGameProper_isBlacklisted_succeeds() public { - // Mock the disputeGameBlacklist call to return true. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)), - abi.encode(true) - ); + // Blacklist the game. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.blacklistDisputeGame(gameProxy); + + // Should return false. + assertFalse(anchorStateRegistry.isGameProper(gameProxy)); + } + + /// @notice Tests that isGameProper will return false if the superchain is paused. + function test_isGameProper_superchainPaused_succeeds() public { + // Pause the superchain. + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("testing"); + // Game should not be proper. assertFalse(anchorStateRegistry.isGameProper(gameProxy)); } /// @notice Tests that isGameProper will return false if the game is retired. - /// @param _retirementTimestamp The retirement timestamp to use for the test. - function testFuzz_isGameProper_isRetired_succeeds(uint64 _retirementTimestamp) public { - // Make sure retirement timestamp is later than the game's creation time. - _retirementTimestamp = uint64(bound(_retirementTimestamp, gameProxy.createdAt().raw() + 1, type(uint64).max)); + /// @param _createdAtTimestamp The createdAt timestamp to use for the test. + function testFuzz_isGameProper_isRetired_succeeds(uint64 _createdAtTimestamp) public { + // Set the retirement timestamp to now. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); - // Mock the respectedGameTypeUpdatedAt call to be later than the game's creation time. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()), - abi.encode(_retirementTimestamp) - ); + // Make sure createdAt timestamp is less than or equal to the retirementTimestamp. + _createdAtTimestamp = uint64(bound(_createdAtTimestamp, 0, anchorStateRegistry.retirementTimestamp())); + // Mock the call to createdAt. + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.createdAt, ()), abi.encode(_createdAtTimestamp)); + + // Game should not be proper. assertFalse(anchorStateRegistry.isGameProper(gameProxy)); } } @@ -476,8 +525,8 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_Init { function testFuzz_isGameClaimValid_isBlacklisted_succeeds() public { // Mock the disputeGameBlacklist call to return true. vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)), + address(anchorStateRegistry), + abi.encodeCall(anchorStateRegistry.disputeGameBlacklist, (gameProxy)), abi.encode(true) ); @@ -486,17 +535,17 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_Init { } /// @notice Tests that isGameClaimValid will return false if the game is retired. - /// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test. - function testFuzz_isGameClaimValid_isRetired_succeeds(uint256 _resolvedAtTimestamp) public { - // Make sure retirement timestamp is later than the game's creation time. - _resolvedAtTimestamp = uint64(bound(_resolvedAtTimestamp, gameProxy.createdAt().raw() + 1, type(uint64).max)); + /// @param _createdAtTimestamp The createdAt timestamp to use for the test. + function testFuzz_isGameClaimValid_isRetired_succeeds(uint256 _createdAtTimestamp) public { + // Set the retirement timestamp to now. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); - // Mock the respectedGameTypeUpdatedAt call to be later than the game's creation time. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()), - abi.encode(_resolvedAtTimestamp) - ); + // Make sure createdAt timestamp is less than or equal to the retirementTimestamp. + _createdAtTimestamp = uint64(bound(_createdAtTimestamp, 0, anchorStateRegistry.retirementTimestamp())); + + // Mock the call to createdAt. + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.createdAt, ()), abi.encode(_createdAtTimestamp)); // Claim should not be valid. assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); @@ -528,6 +577,16 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_Init { // Claim should not be valid. assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); } + + /// @notice Tests that isGameClaimValid will return false if the superchain is paused. + function test_isGameClaimValid_superchainPaused_succeeds() public { + // Pause the superchain. + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("testing"); + + // Game should not be valid. + assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); + } } contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_Init { @@ -768,12 +827,9 @@ contract AnchorStateRegistry_SetAnchorState_TestFail is AnchorStateRegistry_Init vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); - // Mock the disputeGameBlacklist call to return true. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)), - abi.encode(true) - ); + // Blacklist the game. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.blacklistDisputeGame(gameProxy); // Update the anchor state. vm.prank(address(gameProxy)); @@ -786,8 +842,7 @@ contract AnchorStateRegistry_SetAnchorState_TestFail is AnchorStateRegistry_Init assertEq(updatedRoot.raw(), root.raw()); } - /// @notice Tests that setAnchorState will revert if the game is valid and the game is - /// retired. + /// @notice Tests that setAnchorState will revert if the game is retired. /// @param _l2BlockNumber The L2 block number to use for the game. function testFuzz_setAnchorState_retiredGame_fails(uint256 _l2BlockNumber) public { // Grab block number of the existing anchor root. @@ -802,11 +857,15 @@ contract AnchorStateRegistry_SetAnchorState_TestFail is AnchorStateRegistry_Init // Mock that the game was respected. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(true)); - // Mock the respectedGameTypeUpdatedAt call to be later than the game's creation time. + // Set the retirement timestamp. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); + + // Mock the call to createdAt. vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()), - abi.encode(gameProxy.createdAt().raw() + 1) + address(gameProxy), + abi.encodeCall(gameProxy.createdAt, ()), + abi.encode(anchorStateRegistry.retirementTimestamp() - 1) ); // Update the anchor state. @@ -819,4 +878,153 @@ contract AnchorStateRegistry_SetAnchorState_TestFail is AnchorStateRegistry_Init assertEq(updatedL2BlockNumber, l2BlockNumber); assertEq(updatedRoot.raw(), root.raw()); } + + /// @notice Tests that setAnchorState will revert if the superchain is paused. + function test_setAnchorState_superchainPaused_fails() public { + // Pause the superchain. + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("testing"); + + // Update the anchor state. + vm.prank(address(gameProxy)); + vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); + anchorStateRegistry.setAnchorState(gameProxy); + } +} + +contract AnchorStateRegistry_setRespectedGameType_Test is AnchorStateRegistry_Init { + /// @notice Tests that setRespectedGameType succeeds when called by the guardian + /// @param _gameType The game type to set as respected + function testFuzz_setRespectedGameType_succeeds(GameType _gameType) public { + // Call as guardian + vm.prank(superchainConfig.guardian()); + vm.expectEmit(address(anchorStateRegistry)); + emit RespectedGameTypeSet(_gameType); + anchorStateRegistry.setRespectedGameType(_gameType); + + // Verify the game type was set + assertEq(anchorStateRegistry.respectedGameType().raw(), _gameType.raw()); + } +} + +contract AnchorStateRegistry_setRespectedGameType_TestFail is AnchorStateRegistry_Init { + /// @notice Tests that setRespectedGameType reverts when not called by the guardian + /// @param _gameType The game type to attempt to set + /// @param _caller The address attempting to call the function + function testFuzz_setRespectedGameType_notGuardian_reverts(GameType _gameType, address _caller) public { + // Ensure caller is not the guardian + vm.assume(_caller != superchainConfig.guardian()); + + // Attempt to call as non-guardian + vm.prank(_caller); + vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_Unauthorized.selector); + anchorStateRegistry.setRespectedGameType(_gameType); + } +} + +contract AnchorStateRegistry_updateRetirementTimestamp_Test is AnchorStateRegistry_Init { + /// @notice Tests that updateRetirementTimestamp succeeds when called by the guardian + function test_updateRetirementTimestamp_succeeds() public { + // Call as guardian + vm.prank(superchainConfig.guardian()); + vm.expectEmit(address(anchorStateRegistry)); + emit RetirementTimestampSet(block.timestamp); + anchorStateRegistry.updateRetirementTimestamp(); + + // Verify the timestamp was set + assertEq(anchorStateRegistry.retirementTimestamp(), block.timestamp); + } + + /// @notice Tests that updateRetirementTimestamp can be called multiple times by the guardian + function test_updateRetirementTimestamp_multipleUpdates_succeeds() public { + // First update + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); + uint64 firstTimestamp = anchorStateRegistry.retirementTimestamp(); + + // Warp forward and update again + vm.warp(block.timestamp + 1000); + vm.prank(superchainConfig.guardian()); + vm.expectEmit(address(anchorStateRegistry)); + emit RetirementTimestampSet(block.timestamp); + anchorStateRegistry.updateRetirementTimestamp(); + + // Verify the timestamp was updated + assertEq(anchorStateRegistry.retirementTimestamp(), block.timestamp); + assertGt(anchorStateRegistry.retirementTimestamp(), firstTimestamp); + } +} + +contract AnchorStateRegistry_updateRetirementTimestamp_TestFail is AnchorStateRegistry_Init { + /// @notice Tests that updateRetirementTimestamp reverts when not called by the guardian + /// @param _caller The address attempting to call the function + function testFuzz_updateRetirementTimestamp_notGuardian_reverts(address _caller) public { + // Ensure caller is not the guardian + vm.assume(_caller != superchainConfig.guardian()); + + // Attempt to call as non-guardian + vm.prank(_caller); + vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_Unauthorized.selector); + anchorStateRegistry.updateRetirementTimestamp(); + } +} + +contract AnchorStateRegistry_blacklistDisputeGame_Test is AnchorStateRegistry_Init { + /// @notice Tests that blacklistDisputeGame succeeds when called by the guardian + function test_blacklistDisputeGame_succeeds() public { + // Call as guardian + vm.prank(superchainConfig.guardian()); + vm.expectEmit(address(anchorStateRegistry)); + emit DisputeGameBlacklisted(gameProxy); + anchorStateRegistry.blacklistDisputeGame(gameProxy); + + // Verify the game was blacklisted + assertTrue(anchorStateRegistry.disputeGameBlacklist(gameProxy)); + } + + /// @notice Tests that multiple games can be blacklisted + function test_blacklistDisputeGame_multipleGames_succeeds() public { + // Create a second game proxy + IDisputeGame secondGame = IDisputeGame(address(0x123)); + + // Blacklist both games + vm.startPrank(superchainConfig.guardian()); + anchorStateRegistry.blacklistDisputeGame(gameProxy); + anchorStateRegistry.blacklistDisputeGame(secondGame); + vm.stopPrank(); + + // Verify both games are blacklisted + assertTrue(anchorStateRegistry.disputeGameBlacklist(gameProxy)); + assertTrue(anchorStateRegistry.disputeGameBlacklist(secondGame)); + } +} + +contract AnchorStateRegistry_blacklistDisputeGame_TestFail is AnchorStateRegistry_Init { + /// @notice Tests that blacklistDisputeGame reverts when not called by the guardian + /// @param _caller The address attempting to call the function + function testFuzz_blacklistDisputeGame_notGuardian_reverts(address _caller) public { + // Ensure caller is not the guardian + vm.assume(_caller != superchainConfig.guardian()); + + // Attempt to call as non-guardian + vm.prank(_caller); + vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_Unauthorized.selector); + anchorStateRegistry.blacklistDisputeGame(gameProxy); + } + + /// @notice Tests that blacklisting a game twice succeeds but doesn't change state + function test_blacklistDisputeGame_twice_succeeds() public { + // Blacklist the game + vm.startPrank(superchainConfig.guardian()); + anchorStateRegistry.blacklistDisputeGame(gameProxy); + + // Blacklist again - should emit event but not change state + vm.expectEmit(address(anchorStateRegistry)); + emit DisputeGameBlacklisted(gameProxy); + anchorStateRegistry.blacklistDisputeGame(gameProxy); + vm.stopPrank(); + + // Verify the game is still blacklisted + assertTrue(anchorStateRegistry.disputeGameBlacklist(gameProxy)); + } } diff --git a/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol index a90f62ea68f8a..a655fcf12f3b8 100644 --- a/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol @@ -96,6 +96,12 @@ contract FaultDisputeGame_Init is DisputeGameFactory_Init { // Register the game implementation with the factory. disputeGameFactory.setImplementation(GAME_TYPE, gameImpl); uint256 bondAmount = disputeGameFactory.initBonds(GAME_TYPE); + + // Warp ahead of the game retirement timestamp if needed. + if (block.timestamp <= anchorStateRegistry.retirementTimestamp()) { + vm.warp(anchorStateRegistry.retirementTimestamp() + 1); + } + // Create a new game. gameProxy = IFaultDisputeGame( payable(address(disputeGameFactory.create{ value: bondAmount }(GAME_TYPE, rootClaim, extraData))) diff --git a/packages/contracts-bedrock/test/dispute/SuperFaultDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/SuperFaultDisputeGame.t.sol index 099a85e56c010..919deaa2c960b 100644 --- a/packages/contracts-bedrock/test/dispute/SuperFaultDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/dispute/SuperFaultDisputeGame.t.sol @@ -98,7 +98,12 @@ contract SuperFaultDisputeGame_Init is DisputeGameFactory_Init { uint256 bondAmount = disputeGameFactory.initBonds(GAME_TYPE); vm.prank(superchainConfig.guardian()); - optimismPortal2.setRespectedGameType(GAME_TYPE); + anchorStateRegistry.setRespectedGameType(GAME_TYPE); + + // Warp ahead of the game retirement timestamp if needed. + if (block.timestamp <= anchorStateRegistry.retirementTimestamp()) { + vm.warp(anchorStateRegistry.retirementTimestamp() + 1); + } // Create a new game. gameProxy = IFaultDisputeGame( diff --git a/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol b/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol index 15ce33c252b18..8ad36653893fe 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol @@ -14,7 +14,6 @@ import { ResourceMetering } from "src/L1/ResourceMetering.sol"; import { Constants } from "src/libraries/Constants.sol"; import { Types } from "src/libraries/Types.sol"; import "src/dispute/lib/Types.sol"; -import "src/libraries/PortalErrors.sol"; // Interfaces import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; @@ -119,7 +118,7 @@ contract OptimismPortal2_Invariant_Harness is CommonTest { }); // Warp forward in time to ensure that the game is created after the retirement timestamp. - vm.warp(optimismPortal2.respectedGameTypeUpdatedAt() + 1 seconds); + vm.warp(anchorStateRegistry.retirementTimestamp() + 1); // Create a dispute game with the output root we've proposed. _proposedBlockNumber = 0xFF; @@ -140,7 +139,7 @@ contract OptimismPortal2_Invariant_Harness is CommonTest { game.resolve(); // Fund the portal so that we can withdraw ETH. - vm.deal(address(optimismPortal2), 0xFFFFFFFF); + vm.deal(address(ethLockbox), 0xFFFFFFFF); } } @@ -188,7 +187,7 @@ contract OptimismPortal2_CannotTimeTravel is OptimismPortal2_Invariant_Harness { /// A withdrawal that has been proven should not be able to be finalized /// until after the proof maturity period has elapsed. function invariant_cannotFinalizeBeforePeriodHasPassed() external { - vm.expectRevert("OptimismPortal: proven withdrawal has not matured yet"); + vm.expectRevert(IOptimismPortal2.OptimismPortal_ProofNotOldEnough.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } } @@ -217,7 +216,7 @@ contract OptimismPortal2_CannotFinalizeTwice is OptimismPortal2_Invariant_Harnes /// Ensures that there is no chain of calls that can be made that allows a withdrawal to be /// finalized twice. function invariant_cannotFinalizeTwice() external { - vm.expectRevert(AlreadyFinalized.selector); + vm.expectRevert(IOptimismPortal2.OptimismPortal_AlreadyFinalized.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } } diff --git a/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol b/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol index 4ca84d96b0209..2107e15dc5021 100644 --- a/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol +++ b/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol @@ -45,7 +45,8 @@ contract SystemConfig_GasLimitBoundaries_Invariant is Test { disputeGameFactory: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0) - }) + }), + 1234 // _l2ChainId ) ) ); diff --git a/packages/contracts-bedrock/test/kontrol/proofs/OptimismPortal2.k.sol b/packages/contracts-bedrock/test/kontrol/proofs/OptimismPortal2.k.sol index 7d7b49e3a0c4a..756b225751e90 100644 --- a/packages/contracts-bedrock/test/kontrol/proofs/OptimismPortal2.k.sol +++ b/packages/contracts-bedrock/test/kontrol/proofs/OptimismPortal2.k.sol @@ -6,7 +6,6 @@ import { KontrolUtils } from "./utils/KontrolUtils.sol"; import { Types } from "src/libraries/Types.sol"; import { IOptimismPortal2 as OptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { ISuperchainConfig as SuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import "src/libraries/PortalErrors.sol"; contract OptimismPortal2Kontrol is DeploymentSummaryFaultProofs, KontrolUtils { OptimismPortal optimismPortal; @@ -26,7 +25,7 @@ contract OptimismPortal2Kontrol is DeploymentSummaryFaultProofs, KontrolUtils { vm.prank(optimismPortal.guardian()); superchainConfig.pause("identifier"); - vm.expectRevert(CallPaused.selector); + vm.expectRevert(OptimismPortal.OptimismPortal_CallPaused.selector); optimismPortal.finalizeWithdrawalTransaction(_tx); } @@ -47,7 +46,7 @@ contract OptimismPortal2Kontrol is DeploymentSummaryFaultProofs, KontrolUtils { vm.prank(optimismPortal.guardian()); superchainConfig.pause("identifier"); - vm.expectRevert(CallPaused.selector); + vm.expectRevert(OptimismPortal.OptimismPortal_CallPaused.selector); optimismPortal.proveWithdrawalTransaction(_tx, _l2OutputIndex, _outputRootProof, _withdrawalProof); } diff --git a/packages/contracts-bedrock/test/libraries/Encoding.t.sol b/packages/contracts-bedrock/test/libraries/Encoding.t.sol index 277cce328dcdd..76ab3343d74c6 100644 --- a/packages/contracts-bedrock/test/libraries/Encoding.t.sol +++ b/packages/contracts-bedrock/test/libraries/Encoding.t.sol @@ -1,17 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -// Testing utilities +// Testing import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries +import { Encoding } from "src/libraries/Encoding.sol"; import { Types } from "src/libraries/Types.sol"; import { LegacyCrossDomainUtils } from "src/libraries/LegacyCrossDomainUtils.sol"; -// Target contract -import { Encoding } from "src/libraries/Encoding.sol"; +contract Encoding_TestInit is CommonTest { + Encoding_Harness encoding; -contract Encoding_Test is CommonTest { + function setUp() public override { + super.setUp(); + encoding = new Encoding_Harness(); + } +} + +contract Encoding_Test is Encoding_TestInit { /// @dev Tests encoding and decoding a nonce and version. function testFuzz_nonceVersioning_succeeds(uint240 _nonce, uint16 _version) external pure { (uint240 nonce, uint16 version) = Encoding.decodeVersionedNonce(Encoding.encodeVersionedNonce(_nonce, _version)); @@ -77,8 +84,6 @@ contract Encoding_Test is CommonTest { uint256 minInvalidNonce = (uint256(type(uint240).max) + 1) * 2; nonce = bound(nonce, minInvalidNonce, type(uint256).max); - EncodingContract encoding = new EncodingContract(); - vm.expectRevert(bytes("Encoding: unknown cross domain message version")); encoding.encodeCrossDomainMessage(nonce, address(this), address(this), 1, 100, hex""); } @@ -107,7 +112,182 @@ contract Encoding_Test is CommonTest { } } -contract EncodingContract { +contract Encoding_encodeSuperRootProof_Test is Encoding_TestInit { + /// @notice Tests successful encoding of a valid super root proof + /// @param _timestamp The timestamp of the super root proof + /// @param _length The number of output roots in the super root proof + /// @param _seed The seed used to generate the output roots + function testFuzz_encodeSuperRootProof_succeeds(uint64 _timestamp, uint256 _length, uint256 _seed) external pure { + // Ensure at least 1 element and cap at a reasonable maximum to avoid gas issues + _length = uint256(bound(_length, 1, 50)); + + // Create output roots array + Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](_length); + + // Generate deterministic chain IDs and roots based on the seed + for (uint256 i = 0; i < _length; i++) { + // Use different derivations of the seed for each value + uint256 chainId = uint256(keccak256(abi.encode(_seed, "chainId", i))); + bytes32 root = keccak256(abi.encode(_seed, "root", i)); + + outputRoots[i] = Types.OutputRootWithChainId({ chainId: chainId, root: root }); + } + + // Create the super root proof + Types.SuperRootProof memory proof = + Types.SuperRootProof({ version: 0x01, timestamp: _timestamp, outputRoots: outputRoots }); + + // Encode the proof + bytes memory encoded = Encoding.encodeSuperRootProof(proof); + + // Verify encoding structure + assertEq(encoded[0], bytes1(0x01), "Version byte should be 0x01"); + + // Verify timestamp (bytes 1-8) + bytes8 encodedTimestamp; + for (uint256 i = 0; i < 8; i++) { + encodedTimestamp |= bytes8(encoded[i + 1]) >> (i * 8); + } + assertEq(uint64(encodedTimestamp), _timestamp, "Timestamp should match"); + + // Verify each chain ID and root is encoded correctly + uint256 offset = 9; // 1 byte version + 8 bytes timestamp + for (uint256 i = 0; i < _length; i++) { + // Extract chain ID (32 bytes) + uint256 encodedChainId; + assembly { + // Load 32 bytes from encoded at position offset + encodedChainId := mload(add(add(encoded, 32), offset)) + } + assertEq(encodedChainId, outputRoots[i].chainId, "Chain ID should match"); + offset += 32; + + // Extract root (32 bytes) + bytes32 encodedRoot; + assembly { + // Load 32 bytes from encoded at position offset + encodedRoot := mload(add(add(encoded, 32), offset)) + } + assertEq(encodedRoot, outputRoots[i].root, "Root should match"); + offset += 32; + } + + // Verify total length + assertEq(encoded.length, 9 + (_length * 64), "Encoded length should match expected"); + } + + /// @notice Tests encoding with a single output root + function test_encodeSuperRootProof_singleOutputRoot_succeeds() external pure { + // Create a single output root + Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](1); + outputRoots[0] = Types.OutputRootWithChainId({ chainId: 10, root: bytes32(uint256(0xdeadbeef)) }); + + // Create the super root proof + Types.SuperRootProof memory proof = + Types.SuperRootProof({ version: 0x01, timestamp: 1234567890, outputRoots: outputRoots }); + + // Encode the proof + bytes memory encoded = Encoding.encodeSuperRootProof(proof); + + // Expected: 1 byte version + 8 bytes timestamp + (32 bytes chainId + 32 bytes root) + assertEq(encoded.length, 1 + 8 + 64, "Encoded length should be 73 bytes"); + assertEq(encoded[0], bytes1(0x01), "First byte should be version 0x01"); + } + + /// @notice Tests encoding with multiple output roots + function test_encodeSuperRootProof_multipleOutputRoots_succeeds() external pure { + // Create multiple output roots + Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](3); + outputRoots[0] = Types.OutputRootWithChainId({ chainId: 10, root: bytes32(uint256(0xdeadbeef)) }); + outputRoots[1] = Types.OutputRootWithChainId({ chainId: 20, root: bytes32(uint256(0xbeefcafe)) }); + outputRoots[2] = Types.OutputRootWithChainId({ chainId: 30, root: bytes32(uint256(0xcafebabe)) }); + + // Create the super root proof + Types.SuperRootProof memory proof = + Types.SuperRootProof({ version: 0x01, timestamp: 1234567890, outputRoots: outputRoots }); + + // Encode the proof + bytes memory encoded = Encoding.encodeSuperRootProof(proof); + + // Expected: 1 byte version + 8 bytes timestamp + 3 * (32 bytes chainId + 32 bytes root) + assertEq(encoded.length, 1 + 8 + (3 * 64), "Encoded length should be 201 bytes"); + } + + /// @notice Tests that the Solidity impl of encodeSuperRootProof matches the FFI impl + /// @param _timestamp The timestamp of the super root proof + /// @param _length The number of output roots in the super root proof + /// @param _seed The seed used to generate the output roots + function testDiff_encodeSuperRootProof_succeeds(uint64 _timestamp, uint256 _length, uint256 _seed) external { + // Ensure at least 1 element and cap at a reasonable maximum to avoid gas issues + _length = uint256(bound(_length, 1, 50)); + + // Create output roots array + Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](_length); + + // Generate deterministic chain IDs and roots based on the seed + for (uint256 i = 0; i < _length; i++) { + // Use different derivations of the seed for each value + uint256 chainId = uint256(keccak256(abi.encode(_seed, "chainId", i))); + bytes32 root = keccak256(abi.encode(_seed, "root", i)); + + outputRoots[i] = Types.OutputRootWithChainId({ chainId: chainId, root: root }); + } + + // Create the super root proof + Types.SuperRootProof memory proof = + Types.SuperRootProof({ version: 0x01, timestamp: _timestamp, outputRoots: outputRoots }); + + // Encode using the Solidity implementation + bytes memory encoding1 = Encoding.encodeSuperRootProof(proof); + + // Encode using the FFI implementation + bytes memory encoding2 = ffi.encodeSuperRootProof(proof); + + // Compare the results + assertEq(encoding1, encoding2, "Solidity and FFI implementations should match"); + } +} + +contract Encoding_encodeSuperRootProof_TestFail is Encoding_TestInit { + /// @notice Tests that encoding fails when version is not 0x01 + /// @param _version The version to use for the super root proof + /// @param _timestamp The timestamp of the super root proof + function testFuzz_encodeSuperRootProof_invalidVersion_reverts(bytes1 _version, uint64 _timestamp) external { + // Ensure version is not 0x01 + if (_version == 0x01) { + _version = 0x02; + } + + // Create a minimal valid output roots array + Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](1); + outputRoots[0] = Types.OutputRootWithChainId({ chainId: 1, root: bytes32(uint256(1)) }); + + // Create the super root proof with invalid version + Types.SuperRootProof memory proof = + Types.SuperRootProof({ version: _version, timestamp: _timestamp, outputRoots: outputRoots }); + + // Expect revert when encoding + vm.expectRevert(Encoding.Encoding_InvalidSuperRootVersion.selector); + encoding.encodeSuperRootProof(proof); + } + + /// @notice Tests that encoding fails when output roots array is empty + /// @param _timestamp The timestamp of the super root proof + function testFuzz_encodeSuperRootProof_emptyOutputRoots_reverts(uint64 _timestamp) external { + // Create an empty output roots array + Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](0); + + // Create the super root proof with empty output roots + Types.SuperRootProof memory proof = + Types.SuperRootProof({ version: 0x01, timestamp: _timestamp, outputRoots: outputRoots }); + + // Expect revert when encoding + vm.expectRevert(Encoding.Encoding_EmptySuperRoot.selector); + encoding.encodeSuperRootProof(proof); + } +} + +contract Encoding_Harness { function encodeCrossDomainMessage( uint256 nonce, address sender, @@ -122,4 +302,8 @@ contract EncodingContract { { return Encoding.encodeCrossDomainMessage(nonce, sender, target, value, gasLimit, data); } + + function encodeSuperRootProof(Types.SuperRootProof memory proof) external pure returns (bytes memory) { + return Encoding.encodeSuperRootProof(proof); + } } diff --git a/packages/contracts-bedrock/test/libraries/Hashing.t.sol b/packages/contracts-bedrock/test/libraries/Hashing.t.sol index 45242acb088c6..73aef7cb22023 100644 --- a/packages/contracts-bedrock/test/libraries/Hashing.t.sol +++ b/packages/contracts-bedrock/test/libraries/Hashing.t.sol @@ -12,6 +12,12 @@ import { LegacyCrossDomainUtils } from "src/libraries/LegacyCrossDomainUtils.sol // Target contract import { Hashing } from "src/libraries/Hashing.sol"; +contract Hashing_Harness { + function hashSuperRootProof(Types.SuperRootProof memory _proof) external pure returns (bytes32) { + return Hashing.hashSuperRootProof(_proof); + } +} + contract Hashing_hashDepositSource_Test is CommonTest { /// @notice Tests that hashDepositSource returns the correct hash in a simple case. function test_hashDepositSource_succeeds() external pure { @@ -136,3 +142,51 @@ contract Hashing_hashDepositTransaction_Test is CommonTest { ); } } + +contract Hashing_hashSuperRootProof_Test is CommonTest { + Hashing_Harness internal harness; + + /// @notice Sets up the test. + function setUp() public override { + super.setUp(); + harness = new Hashing_Harness(); + } + + /// @notice Tests that the Solidity impl of hashSuperRootProof matches the FFI impl + /// @param _proof The super root proof to test. + function testDiff_hashSuperRootProof_succeeds(Types.SuperRootProof memory _proof) external { + // Make sure the proof has the right version. + _proof.version = 0x01; + + // Make sure the proof has at least one output root. + if (_proof.outputRoots.length == 0) { + _proof.outputRoots = new Types.OutputRootWithChainId[](1); + _proof.outputRoots[0] = Types.OutputRootWithChainId({ + chainId: vm.randomUint(0, type(uint64).max), + root: bytes32(vm.randomUint()) + }); + } + + // Encode using the Solidity implementation + bytes32 hash1 = harness.hashSuperRootProof(_proof); + + // Encode using the FFI implementation + bytes32 hash2 = ffi.hashSuperRootProof(_proof); + + // Compare the results + assertEq(hash1, hash2, "Solidity and FFI implementations should match"); + } + + /// @notice Tests that hashSuperRootProof reverts when the version is incorrect. + /// @param _proof The super root proof to test. + function testFuzz_hashSuperRootProof_wrongVersion_reverts(Types.SuperRootProof memory _proof) external { + // 0x01 is the correct version, so we need any other version. + if (_proof.version == 0x01) { + _proof.version = 0x00; + } + + // Should always revert when the version is incorrect. + vm.expectRevert(Encoding.Encoding_InvalidSuperRootVersion.selector); + harness.hashSuperRootProof(_proof); + } +} diff --git a/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol b/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol index 85680343ccc95..07d577e642239 100644 --- a/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol @@ -17,7 +17,7 @@ import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.so import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; @@ -25,6 +25,7 @@ import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { IProxy } from "interfaces/universal/IProxy.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { DeployImplementationsInput, @@ -88,7 +89,8 @@ contract DeployImplementationsOutput_Test is Test { function test_set_succeeds() public { IOPContractsManager opcm = IOPContractsManager(address(makeAddr("opcm"))); - IOptimismPortal2 optimismPortalImpl = IOptimismPortal2(payable(makeAddr("optimismPortalImpl"))); + IOptimismPortal optimismPortalImpl = IOptimismPortal(payable(makeAddr("optimismPortalImpl"))); + IETHLockbox ethLockboxImpl = IETHLockbox(payable(makeAddr("ethLockboxImpl"))); IDelayedWETH delayedWETHImpl = IDelayedWETH(payable(makeAddr("delayedWETHImpl"))); IPreimageOracle preimageOracleSingleton = IPreimageOracle(makeAddr("preimageOracleSingleton")); IMIPS mipsSingleton = IMIPS(makeAddr("mipsSingleton")); @@ -104,6 +106,7 @@ contract DeployImplementationsOutput_Test is Test { vm.etch(address(opcm), hex"01"); vm.etch(address(optimismPortalImpl), hex"01"); + vm.etch(address(ethLockboxImpl), hex"01"); vm.etch(address(delayedWETHImpl), hex"01"); vm.etch(address(preimageOracleSingleton), hex"01"); vm.etch(address(mipsSingleton), hex"01"); @@ -116,6 +119,7 @@ contract DeployImplementationsOutput_Test is Test { vm.etch(address(anchorStateRegistryImpl), hex"01"); dio.set(dio.opcm.selector, address(opcm)); dio.set(dio.optimismPortalImpl.selector, address(optimismPortalImpl)); + dio.set(dio.ethLockboxImpl.selector, address(ethLockboxImpl)); dio.set(dio.delayedWETHImpl.selector, address(delayedWETHImpl)); dio.set(dio.preimageOracleSingleton.selector, address(preimageOracleSingleton)); dio.set(dio.mipsSingleton.selector, address(mipsSingleton)); @@ -139,6 +143,7 @@ contract DeployImplementationsOutput_Test is Test { assertEq(address(optimismMintableERC20FactoryImpl), address(dio.optimismMintableERC20FactoryImpl()), "900"); assertEq(address(disputeGameFactoryImpl), address(dio.disputeGameFactoryImpl()), "950"); assertEq(address(anchorStateRegistryImpl), address(dio.anchorStateRegistryImpl()), "960"); + assertEq(address(ethLockboxImpl), address(dio.ethLockboxImpl()), "1000"); } function test_getters_whenNotSet_reverts() public { @@ -147,6 +152,9 @@ contract DeployImplementationsOutput_Test is Test { vm.expectRevert(expectedErr); dio.optimismPortalImpl(); + vm.expectRevert(expectedErr); + dio.ethLockboxImpl(); + vm.expectRevert(expectedErr); dio.delayedWETHImpl(); @@ -186,6 +194,10 @@ contract DeployImplementationsOutput_Test is Test { vm.expectRevert(expectedErr); dio.optimismPortalImpl(); + dio.set(dio.ethLockboxImpl.selector, emptyAddr); + vm.expectRevert(expectedErr); + dio.ethLockboxImpl(); + dio.set(dio.delayedWETHImpl.selector, emptyAddr); vm.expectRevert(expectedErr); dio.delayedWETHImpl(); @@ -284,11 +296,12 @@ contract DeployImplementations_Test is Test { deployImplementations.deployL1StandardBridgeImpl(dio); deployImplementations.deployOptimismMintableERC20FactoryImpl(dio); deployImplementations.deployOptimismPortalImpl(dii, dio); + deployImplementations.deployETHLockboxImpl(dio); deployImplementations.deployDelayedWETHImpl(dii, dio); deployImplementations.deployPreimageOracleSingleton(dii, dio); deployImplementations.deployMipsSingleton(dii, dio); deployImplementations.deployDisputeGameFactoryImpl(dio); - deployImplementations.deployAnchorStateRegistryImpl(dio); + deployImplementations.deployAnchorStateRegistryImpl(dii, dio); deployImplementations.deployOPContractsManager(dii, dio); // Store the original addresses. @@ -298,6 +311,7 @@ contract DeployImplementations_Test is Test { address l1StandardBridgeImpl = address(dio.l1StandardBridgeImpl()); address optimismMintableERC20FactoryImpl = address(dio.optimismMintableERC20FactoryImpl()); address optimismPortalImpl = address(dio.optimismPortalImpl()); + address ethLockboxImpl = address(dio.ethLockboxImpl()); address delayedWETHImpl = address(dio.delayedWETHImpl()); address preimageOracleSingleton = address(dio.preimageOracleSingleton()); address mipsSingleton = address(dio.mipsSingleton()); @@ -312,11 +326,12 @@ contract DeployImplementations_Test is Test { deployImplementations.deployL1StandardBridgeImpl(dio); deployImplementations.deployOptimismMintableERC20FactoryImpl(dio); deployImplementations.deployOptimismPortalImpl(dii, dio); + deployImplementations.deployETHLockboxImpl(dio); deployImplementations.deployDelayedWETHImpl(dii, dio); deployImplementations.deployPreimageOracleSingleton(dii, dio); deployImplementations.deployMipsSingleton(dii, dio); deployImplementations.deployDisputeGameFactoryImpl(dio); - deployImplementations.deployAnchorStateRegistryImpl(dio); + deployImplementations.deployAnchorStateRegistryImpl(dii, dio); deployImplementations.deployOPContractsManager(dii, dio); // Assert that the addresses did not change. @@ -332,6 +347,7 @@ contract DeployImplementations_Test is Test { assertEq(disputeGameFactoryImpl, address(dio.disputeGameFactoryImpl()), "1000"); assertEq(anchorStateRegistryImpl, address(dio.anchorStateRegistryImpl()), "1100"); assertEq(opcm, address(dio.opcm()), "1200"); + assertEq(ethLockboxImpl, address(dio.ethLockboxImpl()), "1300"); } function testFuzz_run_memory_succeeds(bytes32 _seed) public { diff --git a/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol b/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol index 2dca24b486bdc..1ece4e242bb9e 100644 --- a/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol +++ b/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol @@ -4,15 +4,14 @@ pragma solidity ^0.8.0; import { Test } from "forge-std/Test.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; -import { GameType } from "src/dispute/lib/Types.sol"; +import { GameType, OutputRoot, Hash } from "src/dispute/lib/Types.sol"; import { SetDisputeGameImpl, SetDisputeGameImplInput } from "scripts/deploy/SetDisputeGameImpl.s.sol"; import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol"; import { Proxy } from "src/universal/Proxy.sol"; -import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; -import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; +import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; contract SetDisputeGameImplInput_Test is Test { SetDisputeGameImplInput input; @@ -70,7 +69,7 @@ contract SetDisputeGameImpl_Test is Test { SetDisputeGameImpl script; SetDisputeGameImplInput input; IDisputeGameFactory factory; - IOptimismPortal2 portal; + IAnchorStateRegistry anchorStateRegistry; address mockImpl; uint32 gameType; @@ -78,8 +77,8 @@ contract SetDisputeGameImpl_Test is Test { script = new SetDisputeGameImpl(); input = new SetDisputeGameImplInput(); DisputeGameFactory dgfImpl = new DisputeGameFactory(); - OptimismPortal2 portalImpl = new OptimismPortal2(0, 0); SuperchainConfig supConfigImpl = new SuperchainConfig(); + AnchorStateRegistry anchorStateRegistryImpl = new AnchorStateRegistry(0); Proxy supConfigProxy = new Proxy(address(1)); vm.prank(address(1)); @@ -92,21 +91,21 @@ contract SetDisputeGameImpl_Test is Test { factoryProxy.upgradeToAndCall(address(dgfImpl), abi.encodeCall(dgfImpl.initialize, (address(this)))); factory = IDisputeGameFactory(address(factoryProxy)); - Proxy portalProxy = new Proxy(address(1)); + Proxy anchorStateRegistryProxy = new Proxy(address(1)); vm.prank(address(1)); - portalProxy.upgradeToAndCall( - address(portalImpl), + anchorStateRegistryProxy.upgradeToAndCall( + address(anchorStateRegistryImpl), abi.encodeCall( - portalImpl.initialize, + anchorStateRegistryImpl.initialize, ( - factory, - ISystemConfig(makeAddr("sysConfig")), ISuperchainConfig(address(supConfigProxy)), + factory, + OutputRoot({ root: Hash.wrap(0), l2BlockNumber: 0 }), GameType.wrap(100) ) ) ); - portal = IOptimismPortal2(payable(address(portalProxy))); + anchorStateRegistry = IAnchorStateRegistry(address(anchorStateRegistryProxy)); mockImpl = makeAddr("impl"); gameType = 999; @@ -115,7 +114,7 @@ contract SetDisputeGameImpl_Test is Test { function test_run_succeeds() public { input.set(input.factory.selector, address(factory)); input.set(input.impl.selector, mockImpl); - input.set(input.portal.selector, address(portal)); + input.set(input.anchorStateRegistry.selector, address(anchorStateRegistry)); input.set(input.gameType.selector, gameType); script.run(input); @@ -124,7 +123,7 @@ contract SetDisputeGameImpl_Test is Test { function test_run_whenImplAlreadySet_reverts() public { input.set(input.factory.selector, address(factory)); input.set(input.impl.selector, mockImpl); - input.set(input.portal.selector, address(portal)); + input.set(input.anchorStateRegistry.selector, address(anchorStateRegistry)); input.set(input.gameType.selector, gameType); // First run should succeed diff --git a/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol b/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol index 06e20c078c122..8b4944be7077a 100644 --- a/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol +++ b/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol @@ -55,7 +55,8 @@ contract UpgradeOPChainInput_Test is Test { configs[0] = OPContractsManager.OpChainConfig({ systemConfigProxy: ISystemConfig(systemConfig1), proxyAdmin: IProxyAdmin(proxyAdmin1), - absolutePrestate: Claim.wrap(bytes32(uint256(1))) + absolutePrestate: Claim.wrap(bytes32(uint256(1))), + disputeGameUsesSuperRoots: false }); // Setup mock addresses and contracts for second config @@ -67,7 +68,8 @@ contract UpgradeOPChainInput_Test is Test { configs[1] = OPContractsManager.OpChainConfig({ systemConfigProxy: ISystemConfig(systemConfig2), proxyAdmin: IProxyAdmin(proxyAdmin2), - absolutePrestate: Claim.wrap(bytes32(uint256(2))) + absolutePrestate: Claim.wrap(bytes32(uint256(2))), + disputeGameUsesSuperRoots: false }); input.set(input.opChainConfigs.selector, configs); @@ -111,7 +113,8 @@ contract UpgradeOPChainInput_Test is Test { configs[0] = OPContractsManager.OpChainConfig({ systemConfigProxy: ISystemConfig(mockSystemConfig), proxyAdmin: IProxyAdmin(mockProxyAdmin), - absolutePrestate: Claim.wrap(bytes32(uint256(1))) + absolutePrestate: Claim.wrap(bytes32(uint256(1))), + disputeGameUsesSuperRoots: false }); vm.expectRevert("UpgradeOPCMInput: unknown selector"); @@ -147,7 +150,8 @@ contract UpgradeOPChain_Test is Test { config = OPContractsManager.OpChainConfig({ systemConfigProxy: ISystemConfig(makeAddr("systemConfigProxy")), proxyAdmin: IProxyAdmin(makeAddr("proxyAdmin")), - absolutePrestate: Claim.wrap(keccak256("absolutePrestate")) + absolutePrestate: Claim.wrap(keccak256("absolutePrestate")), + disputeGameUsesSuperRoots: false }); OPContractsManager.OpChainConfig[] memory configs = new OPContractsManager.OpChainConfig[](1); configs[0] = config; diff --git a/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol b/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol index f17ec2204f716..cdecf1110fa74 100644 --- a/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol +++ b/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol @@ -14,17 +14,14 @@ import "src/dispute/lib/Types.sol"; // Interfaces import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; -import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; contract DeputyGuardianModule_TestInit is CommonTest, SafeTestTools { using SafeTestLib for SafeInstance; - error Unauthorized(); - error ExecutionFailed(string); - event ExecutionFromModuleSuccess(address indexed); + event RetirementTimestampUpdated(Timestamp indexed); IDeputyGuardianModule deputyGuardianModule; SafeInstance safeInstance; @@ -91,7 +88,7 @@ contract DeputyGuardianModule_Pause_Test is DeputyGuardianModule_TestInit { contract DeputyGuardianModule_Pause_TestFail is DeputyGuardianModule_TestInit { /// @dev Tests that `pause` reverts when called by a non deputy guardian. function test_pause_notDeputyGuardian_reverts() external { - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector)); + vm.expectRevert(abi.encodeWithSelector(IDeputyGuardianModule.DeputyGuardianModule_Unauthorized.selector)); deputyGuardianModule.pause(); } @@ -104,7 +101,12 @@ contract DeputyGuardianModule_Pause_TestFail is DeputyGuardianModule_TestInit { ); vm.prank(address(deputyGuardian)); - vm.expectRevert(abi.encodeWithSelector(ExecutionFailed.selector, "SuperchainConfig: pause() reverted")); + vm.expectRevert( + abi.encodeWithSelector( + IDeputyGuardianModule.DeputyGuardianModule_ExecutionFailed.selector, + "SuperchainConfig: pause() reverted" + ) + ); deputyGuardianModule.pause(); } } @@ -140,7 +142,7 @@ contract DeputyGuardianModule_Unpause_Test is DeputyGuardianModule_TestInit { contract DeputyGuardianModule_Unpause_TestFail is DeputyGuardianModule_Unpause_Test { /// @dev Tests that `unpause` reverts when called by a non deputy guardian. function test_unpause_notDeputyGuardian_reverts() external { - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector)); + vm.expectRevert(abi.encodeWithSelector(IDeputyGuardianModule.DeputyGuardianModule_Unauthorized.selector)); deputyGuardianModule.unpause(); assertTrue(superchainConfig.paused()); } @@ -153,42 +155,14 @@ contract DeputyGuardianModule_Unpause_TestFail is DeputyGuardianModule_Unpause_T "SuperchainConfig: unpause reverted" ); - vm.prank(address(deputyGuardian)); - vm.expectRevert(abi.encodeWithSelector(ExecutionFailed.selector, "SuperchainConfig: unpause reverted")); - deputyGuardianModule.unpause(); - } -} - -contract DeputyGuardianModule_SetAnchorState_TestFail is DeputyGuardianModule_TestInit { - function test_setAnchorState_notDeputyGuardian_reverts() external { - IAnchorStateRegistry asr = IAnchorStateRegistry(makeAddr("asr")); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector)); - deputyGuardianModule.setAnchorState(asr, IFaultDisputeGame(address(0))); - } - - function test_setAnchorState_targetReverts_reverts() external { - IAnchorStateRegistry asr = IAnchorStateRegistry(makeAddr("asr")); - vm.mockCallRevert( - address(asr), abi.encodePacked(asr.setAnchorState.selector), "AnchorStateRegistry: setAnchorState reverted" - ); vm.prank(address(deputyGuardian)); vm.expectRevert( - abi.encodeWithSelector(ExecutionFailed.selector, "AnchorStateRegistry: setAnchorState reverted") - ); - deputyGuardianModule.setAnchorState(asr, IFaultDisputeGame(address(0))); - } -} - -contract DeputyGuardianModule_SetAnchorState_Test is DeputyGuardianModule_TestInit { - function test_setAnchorState_succeeds() external { - IAnchorStateRegistry asr = IAnchorStateRegistry(makeAddr("asr")); - vm.mockCall( - address(asr), abi.encodeCall(IAnchorStateRegistry.setAnchorState, (IFaultDisputeGame(address(0)))), "" + abi.encodeWithSelector( + IDeputyGuardianModule.DeputyGuardianModule_ExecutionFailed.selector, + "SuperchainConfig: unpause reverted" + ) ); - vm.expectEmit(address(safeInstance.safe)); - emit ExecutionFromModuleSuccess(address(deputyGuardianModule)); - vm.prank(address(deputyGuardian)); - deputyGuardianModule.setAnchorState(asr, IFaultDisputeGame(address(0))); + deputyGuardianModule.unpause(); } } @@ -205,8 +179,8 @@ contract DeputyGuardianModule_BlacklistDisputeGame_Test is DeputyGuardianModule_ emit DisputeGameBlacklisted(game); vm.prank(address(deputyGuardian)); - deputyGuardianModule.blacklistDisputeGame(optimismPortal2, game); - assertTrue(optimismPortal2.disputeGameBlacklist(game)); + deputyGuardianModule.blacklistDisputeGame(anchorStateRegistry, game); + assertTrue(anchorStateRegistry.disputeGameBlacklist(game)); } } @@ -214,25 +188,28 @@ contract DeputyGuardianModule_BlacklistDisputeGame_TestFail is DeputyGuardianMod /// @dev Tests that `blacklistDisputeGame` reverts when called by a non deputy guardian. function test_blacklistDisputeGame_notDeputyGuardian_reverts() external { IDisputeGame game = IDisputeGame(makeAddr("game")); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector)); - deputyGuardianModule.blacklistDisputeGame(optimismPortal2, game); - assertFalse(optimismPortal2.disputeGameBlacklist(game)); + vm.expectRevert(abi.encodeWithSelector(IDeputyGuardianModule.DeputyGuardianModule_Unauthorized.selector)); + deputyGuardianModule.blacklistDisputeGame(anchorStateRegistry, game); + assertFalse(anchorStateRegistry.disputeGameBlacklist(game)); } /// @dev Tests that when the call from the Safe reverts, the error message is returned. function test_blacklistDisputeGame_targetReverts_reverts() external { vm.mockCallRevert( - address(optimismPortal2), - abi.encodePacked(optimismPortal2.blacklistDisputeGame.selector), - "OptimismPortal2: blacklistDisputeGame reverted" + address(anchorStateRegistry), + abi.encodePacked(anchorStateRegistry.blacklistDisputeGame.selector), + "AnchorStateRegistry: blacklistDisputeGame reverted" ); IDisputeGame game = IDisputeGame(makeAddr("game")); vm.prank(address(deputyGuardian)); vm.expectRevert( - abi.encodeWithSelector(ExecutionFailed.selector, "OptimismPortal2: blacklistDisputeGame reverted") + abi.encodeWithSelector( + IDeputyGuardianModule.DeputyGuardianModule_ExecutionFailed.selector, + "AnchorStateRegistry: blacklistDisputeGame reverted" + ) ); - deputyGuardianModule.blacklistDisputeGame(optimismPortal2, game); + deputyGuardianModule.blacklistDisputeGame(anchorStateRegistry, game); } } @@ -240,11 +217,6 @@ contract DeputyGuardianModule_setRespectedGameType_Test is DeputyGuardianModule_ /// @dev Tests that `setRespectedGameType` successfully updates the respected game type when called by the deputy /// guardian. function testFuzz_setRespectedGameType_succeeds(GameType _gameType) external { - // Game type(uint32).max is reserved for setting the respectedGameTypeUpdatedAt timestamp. - // TODO(#14638): Remove this once we've removed the hack. - uint32 boundedGameType = uint32(bound(_gameType.raw(), 0, type(uint32).max - 1)); - _gameType = GameType.wrap(boundedGameType); - vm.expectEmit(address(safeInstance.safe)); emit ExecutionFromModuleSuccess(address(deputyGuardianModule)); @@ -252,9 +224,8 @@ contract DeputyGuardianModule_setRespectedGameType_Test is DeputyGuardianModule_ emit RespectedGameTypeSet(_gameType, Timestamp.wrap(uint64(block.timestamp))); vm.prank(address(deputyGuardian)); - deputyGuardianModule.setRespectedGameType(optimismPortal2, _gameType); - assertEq(GameType.unwrap(optimismPortal2.respectedGameType()), GameType.unwrap(_gameType)); - assertEq(optimismPortal2.respectedGameTypeUpdatedAt(), uint64(block.timestamp)); + deputyGuardianModule.setRespectedGameType(anchorStateRegistry, _gameType); + assertEq(GameType.unwrap(anchorStateRegistry.respectedGameType()), GameType.unwrap(_gameType)); } } @@ -262,31 +233,81 @@ contract DeputyGuardianModule_setRespectedGameType_TestFail is DeputyGuardianMod /// @dev Tests that `setRespectedGameType` when called by a non deputy guardian. function testFuzz_setRespectedGameType_notDeputyGuardian_reverts(GameType _gameType) external { // Change the game type if it's the same to avoid test rejections. - if (GameType.unwrap(optimismPortal2.respectedGameType()) == GameType.unwrap(_gameType)) { + if (GameType.unwrap(anchorStateRegistry.respectedGameType()) == GameType.unwrap(_gameType)) { unchecked { _gameType = GameType.wrap(GameType.unwrap(_gameType) + 1); } } - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector)); - deputyGuardianModule.setRespectedGameType(optimismPortal2, _gameType); - assertNotEq(GameType.unwrap(optimismPortal2.respectedGameType()), GameType.unwrap(_gameType)); + vm.expectRevert(abi.encodeWithSelector(IDeputyGuardianModule.DeputyGuardianModule_Unauthorized.selector)); + deputyGuardianModule.setRespectedGameType(anchorStateRegistry, _gameType); + assertNotEq(GameType.unwrap(anchorStateRegistry.respectedGameType()), GameType.unwrap(_gameType)); } /// @dev Tests that when the call from the Safe reverts, the error message is returned. function test_setRespectedGameType_targetReverts_reverts() external { vm.mockCallRevert( - address(optimismPortal2), - abi.encodePacked(optimismPortal2.setRespectedGameType.selector), - "OptimismPortal2: setRespectedGameType reverted" + address(anchorStateRegistry), + abi.encodePacked(anchorStateRegistry.setRespectedGameType.selector), + "AnchorStateRegistry: setRespectedGameType reverted" ); GameType gameType = GameType.wrap(1); vm.prank(address(deputyGuardian)); vm.expectRevert( - abi.encodeWithSelector(ExecutionFailed.selector, "OptimismPortal2: setRespectedGameType reverted") + abi.encodeWithSelector( + IDeputyGuardianModule.DeputyGuardianModule_ExecutionFailed.selector, + "AnchorStateRegistry: setRespectedGameType reverted" + ) + ); + deputyGuardianModule.setRespectedGameType(anchorStateRegistry, gameType); + } +} + +contract DeputyGuardianModule_updateRetirementTimestamp_Test is DeputyGuardianModule_TestInit { + /// @notice Tests that updateRetirementTimestamp() successfully updates the retirement timestamp + /// when called by the deputy guardian. + function test_updateRetirementTimestamp_succeeds() external { + vm.expectEmit(address(safeInstance.safe)); + emit ExecutionFromModuleSuccess(address(deputyGuardianModule)); + + vm.expectEmit(address(deputyGuardianModule)); + emit RetirementTimestampUpdated(Timestamp.wrap(uint64(block.timestamp))); + + vm.prank(address(deputyGuardian)); + deputyGuardianModule.updateRetirementTimestamp(anchorStateRegistry); + assertEq(anchorStateRegistry.retirementTimestamp(), block.timestamp); + } +} + +contract DeputyGuardianModule_updateRetirementTimestamp_TestFail is DeputyGuardianModule_TestInit { + /// @notice Tests that updateRetirementTimestamp() reverts when called by an address other than + /// the deputy guardian. + function testFuzz_updateRetirementTimestamp_notDeputyGuardian_reverts(address _caller) external { + vm.assume(_caller != address(deputyGuardian)); + vm.prank(_caller); + vm.expectRevert(abi.encodeWithSelector(IDeputyGuardianModule.DeputyGuardianModule_Unauthorized.selector)); + deputyGuardianModule.updateRetirementTimestamp(anchorStateRegistry); + } + + /// @notice Tests that when the call from the Safe reverts, the error message is returned. + function test_updateRetirementTimestamp_targetReverts_reverts() external { + // Mock a revert from the ASR. + vm.mockCallRevert( + address(anchorStateRegistry), + abi.encodePacked(anchorStateRegistry.updateRetirementTimestamp.selector), + "AnchorStateRegistry: updateRetirementTimestamp reverted" + ); + + // Call the function and expect a revert. + vm.prank(address(deputyGuardian)); + vm.expectRevert( + abi.encodeWithSelector( + IDeputyGuardianModule.DeputyGuardianModule_ExecutionFailed.selector, + "AnchorStateRegistry: updateRetirementTimestamp reverted" + ) ); - deputyGuardianModule.setRespectedGameType(optimismPortal2, gameType); + deputyGuardianModule.updateRetirementTimestamp(anchorStateRegistry); } } @@ -296,16 +317,14 @@ contract DeputyGuardianModule_NoPortalCollisions_Test is DeputyGuardianModule_Te function test_noPortalCollisions_succeeds() external { string[] memory excludes = new string[](5); 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[1] = "src/dispute/AnchorStateRegistry.sol"; + excludes[2] = "interfaces/dispute/IAnchorStateRegistry.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++) { bytes4 sel = abis[i].entries[j].sel; - assertNotEq(sel, optimismPortal2.blacklistDisputeGame.selector); - assertNotEq(sel, optimismPortal2.setRespectedGameType.selector); + assertNotEq(sel, anchorStateRegistry.blacklistDisputeGame.selector); + assertNotEq(sel, anchorStateRegistry.setRespectedGameType.selector); } } } diff --git a/packages/contracts-bedrock/test/safe/DeputyPauseModule.t.sol b/packages/contracts-bedrock/test/safe/DeputyPauseModule.t.sol index 25c54a7d3d3a5..ca87a54662a53 100644 --- a/packages/contracts-bedrock/test/safe/DeputyPauseModule.t.sol +++ b/packages/contracts-bedrock/test/safe/DeputyPauseModule.t.sol @@ -568,7 +568,8 @@ contract DeputyPauseModule_Pause_TestFail is DeputyPauseModule_TestInit { IDeputyPauseModule.DeputyPauseModule_ExecutionFailed.selector, string( abi.encodeWithSelector( - IDeputyGuardianModule.ExecutionFailed.selector, "SuperchainConfig: pause() reverted" + IDeputyGuardianModule.DeputyGuardianModule_ExecutionFailed.selector, + "SuperchainConfig: pause() reverted" ) ) ) diff --git a/packages/contracts-bedrock/test/setup/Events.sol b/packages/contracts-bedrock/test/setup/Events.sol index 7056f0cbdd6b0..022258e02baf9 100644 --- a/packages/contracts-bedrock/test/setup/Events.sol +++ b/packages/contracts-bedrock/test/setup/Events.sol @@ -106,4 +106,8 @@ contract Events { event Unpaused(); event BalanceChanged(address account, uint256 balance); + + event ETHMigrated(address indexed lockbox, uint256 ethBalance); + + event LockboxUpdated(address oldLockbox, address newLockbox); } diff --git a/packages/contracts-bedrock/test/setup/FFIInterface.sol b/packages/contracts-bedrock/test/setup/FFIInterface.sol index c1e1612da8e6b..97ed21cbc7fa6 100644 --- a/packages/contracts-bedrock/test/setup/FFIInterface.sol +++ b/packages/contracts-bedrock/test/setup/FFIInterface.sol @@ -188,6 +188,28 @@ contract FFIInterface { return abi.decode(result, (bytes)); } + function encodeSuperRootProof(Types.SuperRootProof calldata proof) external returns (bytes memory) { + string[] memory cmds = new string[](4); + cmds[0] = "scripts/go-ffi/go-ffi"; + cmds[1] = "diff"; + cmds[2] = "encodeSuperRootProof"; + cmds[3] = vm.toString(abi.encode(proof)); + + bytes memory result = Process.run(cmds); + return abi.decode(result, (bytes)); + } + + function hashSuperRootProof(Types.SuperRootProof calldata proof) external returns (bytes32) { + string[] memory cmds = new string[](4); + cmds[0] = "scripts/go-ffi/go-ffi"; + cmds[1] = "diff"; + cmds[2] = "hashSuperRootProof"; + cmds[3] = vm.toString(abi.encode(proof)); + + bytes memory result = Process.run(cmds); + return abi.decode(result, (bytes32)); + } + function decodeVersionedNonce(uint256 nonce) external returns (uint256, uint256) { string[] memory cmds = new string[](4); cmds[0] = "scripts/go-ffi/go-ffi"; diff --git a/packages/contracts-bedrock/test/setup/ForkLive.s.sol b/packages/contracts-bedrock/test/setup/ForkLive.s.sol index 3de0214c8ea2c..6f3d663000ee6 100644 --- a/packages/contracts-bedrock/test/setup/ForkLive.s.sol +++ b/packages/contracts-bedrock/test/setup/ForkLive.s.sol @@ -24,6 +24,9 @@ import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOPContractsManagerLegacyUpgrade } from "interfaces/L1/IOPContractsManagerLegacyUpgrade.sol"; /// @title ForkLive /// @notice This script is called by Setup.sol as a preparation step for the foundry test suite, and is run as an @@ -34,6 +37,7 @@ import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.so /// Therefore this script can only be run against a fork of a production network which is listed in the /// superchain-registry. /// This contract must not have constructor logic because it is set into state using `etch`. + contract ForkLive is Deployer { using stdToml for string; @@ -116,6 +120,15 @@ contract ForkLive is Deployer { artifacts.save("OptimismPortalProxy", optimismPortal); artifacts.save("OptimismPortal2Impl", EIP1967Helper.getImplementation(optimismPortal)); + // Get the lockbox address from the portal, and save it + /// NOTE: Using try catch because this function could be called before or after the upgrade. + try IOptimismPortal2(payable(optimismPortal)).ethLockbox() returns (IETHLockbox ethLockbox_) { + console.log("ForkLive: ETHLockboxProxy found: %s", address(ethLockbox_)); + artifacts.save("ETHLockboxProxy", address(ethLockbox_)); + } catch { + console.log("ForkLive: ETHLockboxProxy not found"); + } + address addressManager = vm.parseTomlAddress(opToml, ".addresses.AddressManager"); artifacts.save("AddressManager", addressManager); artifacts.save( @@ -177,17 +190,42 @@ contract ForkLive is Deployer { opChains[0] = IOPContractsManager.OpChainConfig({ systemConfigProxy: systemConfig, proxyAdmin: proxyAdmin, - absolutePrestate: Claim.wrap(bytes32(keccak256("absolutePrestate"))) + absolutePrestate: Claim.wrap(bytes32(keccak256("absolutePrestate"))), + disputeGameUsesSuperRoots: false }); // Temporarily replace the upgrader with a DelegateCaller so we can test the upgrade, // then reset its code to the original code. bytes memory upgraderCode = address(upgrader).code; vm.etch(upgrader, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + + // Some upgrades require the legacy format. + IOPContractsManagerLegacyUpgrade.OpChainConfig[] memory legacyConfigs = + new IOPContractsManagerLegacyUpgrade.OpChainConfig[](opChains.length); + for (uint256 i = 0; i < opChains.length; i++) { + legacyConfigs[i] = IOPContractsManagerLegacyUpgrade.OpChainConfig({ + systemConfigProxy: opChains[i].systemConfigProxy, + proxyAdmin: opChains[i].proxyAdmin, + absolutePrestate: opChains[i].absolutePrestate + }); + } + + // Start by doing Upgrade 13. DelegateCaller(upgrader).dcForward( - address(0x026b2F158255Beac46c1E7c6b8BbF29A4b6A7B76), abi.encodeCall(IOPContractsManager.upgrade, (opChains)) + address(0x026b2F158255Beac46c1E7c6b8BbF29A4b6A7B76), + abi.encodeCall(IOPContractsManagerLegacyUpgrade.upgrade, (legacyConfigs)) ); + + // Then do Upgrade 14. + DelegateCaller(upgrader).dcForward( + address(0x3A1f523a4bc09cd344A2745a108Bb0398288094F), + abi.encodeCall(IOPContractsManagerLegacyUpgrade.upgrade, (legacyConfigs)) + ); + + // Then do the final upgrade. DelegateCaller(upgrader).dcForward(address(opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChains))); + + // Reset the upgrader to the original code. vm.etch(upgrader, upgraderCode); console.log("ForkLive: Saving newly deployed contracts"); @@ -205,6 +243,11 @@ contract ForkLive is Deployer { IAnchorStateRegistry newAnchorStateRegistry = IPermissionedDisputeGame(permissionedDisputeGame).anchorStateRegistry(); artifacts.save("AnchorStateRegistryProxy", address(newAnchorStateRegistry)); + + // Get the lockbox address from the portal, and save it + IOptimismPortal2 portal = IOptimismPortal2(artifacts.mustGetAddress("OptimismPortalProxy")); + address lockboxAddress = address(portal.ethLockbox()); + artifacts.save("ETHLockboxProxy", lockboxAddress); } /// @notice Saves the proxy and implementation addresses for a contract name diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index 62c7118ac7359..a650d23ee3405 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -22,7 +22,8 @@ import { Chains } from "scripts/libraries/Chains.sol"; // Interfaces import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; @@ -96,7 +97,8 @@ contract Setup { IDelayedWETH delayedWETHPermissionedGameProxy; // L1 contracts - core - IOptimismPortal2 optimismPortal2; + IOptimismPortal optimismPortal2; + IETHLockbox ethLockbox; ISystemConfig systemConfig; IL1StandardBridge l1StandardBridge; IL1CrossDomainMessenger l1CrossDomainMessenger; @@ -229,7 +231,14 @@ contract Setup { console.log("Setup: completed L1 deployment, registering addresses now"); - optimismPortal2 = IOptimismPortal2(artifacts.mustGetAddress("OptimismPortalProxy")); + optimismPortal2 = IOptimismPortal(artifacts.mustGetAddress("OptimismPortalProxy")); + + // Only skip ETHLockbox assignment if we're in a fork test with non-upgraded fork + // TODO(#14691): Remove this check once Upgrade 15 is deployed on Mainnet. + if (!isForkTest() || deploy.cfg().useUpgradedFork()) { + ethLockbox = IETHLockbox(artifacts.mustGetAddress("ETHLockboxProxy")); + } + systemConfig = ISystemConfig(artifacts.mustGetAddress("SystemConfigProxy")); l1StandardBridge = IL1StandardBridge(artifacts.mustGetAddress("L1StandardBridgeProxy")); l1CrossDomainMessenger = IL1CrossDomainMessenger(artifacts.mustGetAddress("L1CrossDomainMessengerProxy")); diff --git a/packages/contracts-bedrock/test/universal/ReinitializableBase.t.sol b/packages/contracts-bedrock/test/universal/ReinitializableBase.t.sol new file mode 100644 index 0000000000000..1c3f7d93eb052 --- /dev/null +++ b/packages/contracts-bedrock/test/universal/ReinitializableBase.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing +import { Test } from "forge-std/Test.sol"; + +// Contracts +import { ReinitializableBase } from "src/universal/ReinitializableBase.sol"; + +contract ReinitializableBase_Test is Test { + /// @notice Tests that the contract is created correctly and initVersion returns the right + /// value when the provided init version is non-zero. + /// @param _initVersion The init version to use when creating the contract. + function testFuzz_initVersion_validVersion_succeeds(uint8 _initVersion) public { + // Zero version not allowed. + _initVersion = uint8(bound(_initVersion, 1, type(uint8).max)); + + // Deploy the reinitializable contract. + ReinitializableBase_Harness harness = new ReinitializableBase_Harness(_initVersion); + + // Check the init version. + assertEq(harness.initVersion(), _initVersion); + } +} + +contract ReinitializableBase_TestFail is Test { + /// @notice Tests that the contract creation reverts when the init version is zero. + function test_initVersion_zeroVersion_reverts() public { + vm.expectRevert(ReinitializableBase.ReinitializableBase_ZeroInitVersion.selector); + new ReinitializableBase_Harness(0); + } +} + +contract ReinitializableBase_Harness is ReinitializableBase { + constructor(uint8 _initVersion) ReinitializableBase(_initVersion) { } +} diff --git a/packages/contracts-bedrock/test/universal/Specs.t.sol b/packages/contracts-bedrock/test/universal/Specs.t.sol index abd2ebfc8d203..236294d0b0fc9 100644 --- a/packages/contracts-bedrock/test/universal/Specs.t.sol +++ b/packages/contracts-bedrock/test/universal/Specs.t.sol @@ -209,31 +209,45 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "L1StandardBridge", _sel: _getSel("version()") }); // OptimismPortalInterop + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("anchorStateRegistry()") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("checkWithdrawal(bytes32,address)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("depositTransaction(address,uint256,uint64,bool,bytes)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("donateETH()") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("updateLockbox(address)") }); _addSpec({ _name: "OptimismPortalInterop", - _sel: IOptimismPortal2.finalizeWithdrawalTransaction.selector, + _sel: IOptimismPortalInterop.finalizeWithdrawalTransaction.selector, _pausable: true }); _addSpec({ _name: "OptimismPortalInterop", - _sel: IOptimismPortal2.finalizeWithdrawalTransactionExternalProof.selector, + _sel: IOptimismPortalInterop.finalizeWithdrawalTransactionExternalProof.selector, _pausable: true }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("finalizedWithdrawals(bytes32)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("guardian()") }); - _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("initialize(address,address,address,uint32)") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("initialize(address,address,address,address,bool)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("l2Sender()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("minimumGasLimit(uint64)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("params()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("paused()") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("initVersion()") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("superRootsActive()") }); + _addSpec({ + _name: "OptimismPortalInterop", + _sel: _getSel( + "proveWithdrawalTransaction((uint256,address,address,uint256,uint256,bytes),uint256,(bytes32,bytes32,bytes32,bytes32),bytes[])" + ), + _pausable: true + }); _addSpec({ _name: "OptimismPortalInterop", - _sel: IOptimismPortal2.proveWithdrawalTransaction.selector, + _sel: _getSel( + "proveWithdrawalTransaction((uint256,address,address,uint256,uint256,bytes),address,uint256,(bytes1,uint64,(uint256,bytes32)[]),(bytes32,bytes32,bytes32,bytes32),bytes[])" + ), _pausable: true }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("provenWithdrawals(bytes32,address)") }); @@ -241,25 +255,16 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("systemConfig()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("version()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("disputeGameFactory()") }); - _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("disputeGameBlacklist(address)") }); _addSpec({ _name: "OptimismPortalInterop", _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: "OptimismPortalInterop", - _sel: _getSel("blacklistDisputeGame(address)") /*, _auth: Role.GUARDIAN*/ - }); - _addSpec({ - _name: "OptimismPortalInterop", - _sel: _getSel("setRespectedGameType(uint32)") /*, _auth: Role.GUARDIAN*/ - }); - _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("checkWithdrawal(bytes32,address)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("proofMaturityDelaySeconds()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("disputeGameFinalityDelaySeconds()") }); _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("ethLockbox()") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("migrateLiquidity()") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("proxyAdminOwner()") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("upgrade(address,address,bool)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: IOptimismPortalInterop.setConfig.selector, @@ -267,8 +272,11 @@ contract Specification_Test is CommonTest { }); // OptimismPortal2 + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("anchorStateRegistry()") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("checkWithdrawal(bytes32,address)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("depositTransaction(address,uint256,uint64,bool,bytes)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("donateETH()") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("updateLockbox(address)") }); _addSpec({ _name: "OptimismPortal2", _sel: IOptimismPortal2.finalizeWithdrawalTransaction.selector, @@ -281,14 +289,25 @@ contract Specification_Test is CommonTest { }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("finalizedWithdrawals(bytes32)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("guardian()") }); - _addSpec({ _name: "OptimismPortal2", _sel: _getSel("initialize(address,address,address,uint32)") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("initialize(address,address,address,address,bool)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("l2Sender()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("minimumGasLimit(uint64)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("params()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("paused()") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("initVersion()") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("superRootsActive()") }); _addSpec({ _name: "OptimismPortal2", - _sel: IOptimismPortal2.proveWithdrawalTransaction.selector, + _sel: _getSel( + "proveWithdrawalTransaction((uint256,address,address,uint256,uint256,bytes),uint256,(bytes32,bytes32,bytes32,bytes32),bytes[])" + ), + _pausable: true + }); + _addSpec({ + _name: "OptimismPortal2", + _sel: _getSel( + "proveWithdrawalTransaction((uint256,address,address,uint256,uint256,bytes),address,uint256,(bytes1,uint64,(uint256,bytes32)[]),(bytes32,bytes32,bytes32,bytes32),bytes[])" + ), _pausable: true }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("provenWithdrawals(bytes32,address)") }); @@ -296,16 +315,19 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "OptimismPortal2", _sel: _getSel("systemConfig()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("version()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("disputeGameFactory()") }); - _addSpec({ _name: "OptimismPortal2", _sel: _getSel("disputeGameBlacklist(address)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("respectedGameType()") }); - _addSpec({ _name: "OptimismPortal2", _sel: _getSel("blacklistDisputeGame(address)"), _auth: Role.GUARDIAN }); - _addSpec({ _name: "OptimismPortal2", _sel: _getSel("setRespectedGameType(uint32)"), _auth: Role.GUARDIAN }); - _addSpec({ _name: "OptimismPortal2", _sel: _getSel("checkWithdrawal(bytes32,address)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("proofMaturityDelaySeconds()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("disputeGameFinalityDelaySeconds()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("respectedGameTypeUpdatedAt()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("proofSubmitters(bytes32,uint256)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("numProofSubmitters(bytes32)") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("upgrade(address,address,bool)") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("ethLockbox()") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("migrateLiquidity()") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("proxyAdminOwner()") }); + + // ProxyAdminOwnedBase + _addSpec({ _name: "ProxyAdminOwnedBase", _sel: _getSel("proxyAdminOwner()") }); // ProtocolVersions _addSpec({ _name: "ProtocolVersions", _sel: _getSel("RECOMMENDED_SLOT()") }); @@ -329,6 +351,21 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "ProtocolVersions", _sel: _getSel("transferOwnership(address)") }); _addSpec({ _name: "ProtocolVersions", _sel: _getSel("version()") }); + // ETHLockbox + _addSpec({ _name: "ETHLockbox", _sel: _getSel("version()") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("initialize(address,address[])") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("superchainConfig()") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("paused()") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("authorizedPortals(address)") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("authorizedLockboxes(address)") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("receiveLiquidity()") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("lockETH()") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("unlockETH(uint256)") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("authorizePortal(address)") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("authorizeLockbox(address)") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("migrateLiquidity(address)") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("proxyAdminOwner()") }); + // ResourceMetering _addSpec({ _name: "ResourceMetering", _sel: _getSel("params()") }); @@ -400,6 +437,9 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfig", _sel: _getSel("blobbasefeeScalar()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("maximumGasLimit()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("getAddresses()") }); + _addSpec({ _name: "SystemConfig", _sel: _getSel("l2ChainId()") }); + _addSpec({ _name: "SystemConfig", _sel: _getSel("upgrade(uint256)") }); + _addSpec({ _name: "SystemConfig", _sel: _getSel("initVersion()") }); // SystemConfigInterop _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("UNSAFE_BLOCK_SIGNER_SLOT()") }); @@ -487,7 +527,9 @@ contract Specification_Test is CommonTest { }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("dependencyManager()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("getAddresses()") }); - + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("l2ChainId()") }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("upgrade(uint256)") }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("initVersion()") }); // ProxyAdmin _addSpec({ _name: "ProxyAdmin", _sel: _getSel("addressManager()") }); _addSpec({ @@ -572,7 +614,7 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("anchors(uint32)") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("getAnchorRoot()") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("disputeGameFactory()") }); - _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("initialize(address,address,address,(bytes32,uint256))") }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("initialize(address,address,(bytes32,uint256),uint32)") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameBlacklisted(address)") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameClaimValid(address)") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameFinalized(address)") }); @@ -581,11 +623,17 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameResolved(address)") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameRespected(address)") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameRetired(address)") }); - _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("portal()") }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("paused()") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("respectedGameType()") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("setAnchorState(address)") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("superchainConfig()") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("version()") }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("disputeGameFinalityDelaySeconds()") }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("disputeGameBlacklist(address)") }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("retirementTimestamp()") }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("blacklistDisputeGame(address)"), _auth: Role.GUARDIAN }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("setRespectedGameType(uint32)"), _auth: Role.GUARDIAN }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("updateRetirementTimestamp()"), _auth: Role.GUARDIAN }); // PermissionedDisputeGame _addSpec({ _name: "PermissionedDisputeGame", _sel: _getSel("absolutePrestate()") }); @@ -939,7 +987,7 @@ contract Specification_Test is CommonTest { }); _addSpec({ _name: "DeputyGuardianModule", - _sel: _getSel("setAnchorState(address,address)"), + _sel: _getSel("updateRetirementTimestamp(address)"), _auth: Role.DEPUTYGUARDIAN }); _addSpec({ _name: "DeputyGuardianModule", _sel: _getSel("pause()"), _auth: Role.DEPUTYGUARDIAN }); @@ -1104,13 +1152,12 @@ contract Specification_Test is CommonTest { /// @notice Ensures that the DeputyGuardian is authorized to take all Guardian actions. function test_deputyGuardianAuth_works() public view { // Additional 2 roles for the DeputyPauseModule - // Additional role for `setAnchorState` which is in DGM but no longer role-restricted. - assertEq(specsByRole[Role.GUARDIAN].length, 4); - assertEq(specsByRole[Role.DEPUTYGUARDIAN].length, specsByRole[Role.GUARDIAN].length + 3); + assertEq(specsByRole[Role.GUARDIAN].length, 5); + assertEq(specsByRole[Role.DEPUTYGUARDIAN].length, specsByRole[Role.GUARDIAN].length + 2); mapping(bytes4 => Spec) storage dgmFuncSpecs = specs["DeputyGuardianModule"]; mapping(bytes4 => Spec) storage superchainConfigFuncSpecs = specs["SuperchainConfig"]; - mapping(bytes4 => Spec) storage portal2FuncSpecs = specs["OptimismPortal2"]; + mapping(bytes4 => Spec) storage asrSpecs = specs["AnchorStateRegistry"]; // Ensure that for each of the DeputyGuardianModule's methods there is a corresponding method on another // system contract authed to the Guardian role. @@ -1121,11 +1168,12 @@ contract Specification_Test is CommonTest { _assertRolesEq(superchainConfigFuncSpecs[_getSel("unpause()")].auth, Role.GUARDIAN); _assertRolesEq(dgmFuncSpecs[_getSel("blacklistDisputeGame(address,address)")].auth, Role.DEPUTYGUARDIAN); - _assertRolesEq(portal2FuncSpecs[_getSel("blacklistDisputeGame(address)")].auth, Role.GUARDIAN); + _assertRolesEq(asrSpecs[_getSel("blacklistDisputeGame(address)")].auth, Role.GUARDIAN); _assertRolesEq(dgmFuncSpecs[_getSel("setRespectedGameType(address,uint32)")].auth, Role.DEPUTYGUARDIAN); - _assertRolesEq(portal2FuncSpecs[_getSel("setRespectedGameType(uint32)")].auth, Role.GUARDIAN); + _assertRolesEq(asrSpecs[_getSel("setRespectedGameType(uint32)")].auth, Role.GUARDIAN); - _assertRolesEq(dgmFuncSpecs[_getSel("setAnchorState(address,address)")].auth, Role.DEPUTYGUARDIAN); + _assertRolesEq(dgmFuncSpecs[_getSel("updateRetirementTimestamp(address)")].auth, Role.DEPUTYGUARDIAN); + _assertRolesEq(asrSpecs[_getSel("updateRetirementTimestamp()")].auth, Role.GUARDIAN); } } diff --git a/packages/contracts-bedrock/test/vendor/Initializable.t.sol b/packages/contracts-bedrock/test/vendor/Initializable.t.sol index 9835e4e73a0af..3eb79187e65ff 100644 --- a/packages/contracts-bedrock/test/vendor/Initializable.t.sol +++ b/packages/contracts-bedrock/test/vendor/Initializable.t.sol @@ -18,8 +18,8 @@ import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { ProtocolVersion } from "interfaces/L1/IProtocolVersions.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; /// @title Initializer_Test /// @dev Ensures that the `initialize()` function on contracts cannot be called more than @@ -124,13 +124,7 @@ contract Initializer_Test is CommonTest { name: "OptimismPortal2Impl", target: EIP1967Helper.getImplementation(address(optimismPortal2)), initCalldata: abi.encodeCall( - optimismPortal2.initialize, - ( - disputeGameFactory, - systemConfig, - superchainConfig, - GameType.wrap(uint32(deploy.cfg().respectedGameType())) - ) + optimismPortal2.initialize, (systemConfig, superchainConfig, anchorStateRegistry, ethLockbox, false) ) }) ); @@ -140,16 +134,11 @@ contract Initializer_Test is CommonTest { name: "OptimismPortal2Proxy", target: address(optimismPortal2), initCalldata: abi.encodeCall( - optimismPortal2.initialize, - ( - disputeGameFactory, - systemConfig, - superchainConfig, - GameType.wrap(uint32(deploy.cfg().respectedGameType())) - ) + optimismPortal2.initialize, (systemConfig, superchainConfig, anchorStateRegistry, ethLockbox, false) ) }) ); + // SystemConfigImpl contracts.push( InitializeableContract({ @@ -180,7 +169,8 @@ contract Initializer_Test is CommonTest { disputeGameFactory: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0) - }) + }), + 0 ) ) }) @@ -215,7 +205,8 @@ contract Initializer_Test is CommonTest { disputeGameFactory: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0) - }) + }), + 0 ) ) }) @@ -314,8 +305,8 @@ contract Initializer_Test is CommonTest { ( ISuperchainConfig(address(0)), IDisputeGameFactory(address(0)), - IOptimismPortal2(payable(0)), - OutputRoot({ root: Hash.wrap(bytes32(0)), l2BlockNumber: 0 }) + OutputRoot({ root: Hash.wrap(bytes32(0)), l2BlockNumber: 0 }), + GameType.wrap(uint32(deploy.cfg().respectedGameType())) ) ) }) @@ -330,12 +321,34 @@ contract Initializer_Test is CommonTest { ( ISuperchainConfig(address(0)), IDisputeGameFactory(address(0)), - IOptimismPortal2(payable(0)), - OutputRoot({ root: Hash.wrap(bytes32(0)), l2BlockNumber: 0 }) + OutputRoot({ root: Hash.wrap(bytes32(0)), l2BlockNumber: 0 }), + GameType.wrap(uint32(deploy.cfg().respectedGameType())) ) ) }) ); + + // ETHLockboxImpl + contracts.push( + InitializeableContract({ + name: "ETHLockboxImpl", + target: EIP1967Helper.getImplementation(address(ethLockbox)), + initCalldata: abi.encodeCall( + ethLockbox.initialize, (ISuperchainConfig(address(0)), new IOptimismPortal2[](0)) + ) + }) + ); + + // ETHLockboxProxy + contracts.push( + InitializeableContract({ + name: "ETHLockboxProxy", + target: address(ethLockbox), + initCalldata: abi.encodeCall( + ethLockbox.initialize, (ISuperchainConfig(address(0)), new IOptimismPortal2[](0)) + ) + }) + ); } /// @notice Tests that: