From dec8c60acdada4286469e8fcb8123967be445991 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Tue, 5 Nov 2024 10:03:17 -0700 Subject: [PATCH] op-deployer: Add support for alt-DA deployments (#12798) * op-deployer: Add support for alt-DA deployments Gives users the ability to deploy an alt-DA chain by specifying an alt-DA config in their chain's intent. The chain will be deployed using OPCM, then an additional pipeline step will deploy the alt-DA challenge contracts. The owner of the challenge contract is set to the L1 proxy admin owner. To reflect the experimental nature of this feature, the field in the intent is prefixed with `Dangerous`. Users should not use this for production chains until we have performed further testing. This may not appear like an important feature on its surface. However, without it we cannot delete the legacy allocs files. Since it was low lift I figured I'd just knock it out, and get us one step closer to being able to rip out the legacy deployment scripts and tooling once and for all. * semgrep * forge fmt * label * flip args --- op-chain-ops/genesis/config.go | 12 +- op-chain-ops/script/cheatcodes_utilities.go | 11 + .../script/cheatcodes_utilities_test.go | 11 + op-deployer/pkg/deployer/apply.go | 5 + .../deployer/integration_test/apply_test.go | 43 +++ op-deployer/pkg/deployer/opcm/alt_da.go | 63 +++++ op-deployer/pkg/deployer/opcm/alt_da_test.go | 43 +++ op-deployer/pkg/deployer/pipeline/alt_da.go | 56 ++++ .../pkg/deployer/state/deploy_config.go | 5 + op-deployer/pkg/deployer/state/intent.go | 8 + op-deployer/pkg/deployer/state/state.go | 2 + .../scripts/deploy/DeployAltDA.s.sol | 199 +++++++++++++ .../test/opcm/DeployAltDA.t.sol | 266 ++++++++++++++++++ 13 files changed, 718 insertions(+), 6 deletions(-) create mode 100644 op-deployer/pkg/deployer/opcm/alt_da.go create mode 100644 op-deployer/pkg/deployer/opcm/alt_da_test.go create mode 100644 op-deployer/pkg/deployer/pipeline/alt_da.go create mode 100644 packages/contracts-bedrock/scripts/deploy/DeployAltDA.s.sol create mode 100644 packages/contracts-bedrock/test/opcm/DeployAltDA.t.sol diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index d89c124e6d31..7ed0a6e2f682 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -597,18 +597,18 @@ func (d *L2CoreDeployConfig) Check(log log.Logger) error { // AltDADeployConfig configures optional AltDA functionality. type AltDADeployConfig struct { // UseAltDA is a flag that indicates if the system is using op-alt-da - UseAltDA bool `json:"useAltDA"` + UseAltDA bool `json:"useAltDA" toml:"useAltDA"` // DACommitmentType specifies the allowed commitment - DACommitmentType string `json:"daCommitmentType"` + DACommitmentType string `json:"daCommitmentType" toml:"daCommitmentType"` // DAChallengeWindow represents the block interval during which the availability of a data commitment can be challenged. - DAChallengeWindow uint64 `json:"daChallengeWindow"` + DAChallengeWindow uint64 `json:"daChallengeWindow" toml:"daChallengeWindow"` // DAResolveWindow represents the block interval during which a data availability challenge can be resolved. - DAResolveWindow uint64 `json:"daResolveWindow"` + DAResolveWindow uint64 `json:"daResolveWindow" toml:"daResolveWindow"` // DABondSize represents the required bond size to initiate a data availability challenge. - DABondSize uint64 `json:"daBondSize"` + DABondSize uint64 `json:"daBondSize" toml:"daBondSize"` // DAResolverRefundPercentage represents the percentage of the resolving cost to be refunded to the resolver // such as 100 means 100% refund. - DAResolverRefundPercentage uint64 `json:"daResolverRefundPercentage"` + DAResolverRefundPercentage uint64 `json:"daResolverRefundPercentage" toml:"daResolverRefundPercentage"` } var _ ConfigChecker = (*AltDADeployConfig)(nil) diff --git a/op-chain-ops/script/cheatcodes_utilities.go b/op-chain-ops/script/cheatcodes_utilities.go index 022befa60627..4f7606d7e4fb 100644 --- a/op-chain-ops/script/cheatcodes_utilities.go +++ b/op-chain-ops/script/cheatcodes_utilities.go @@ -233,6 +233,17 @@ func (c *CheatCodesPrecompile) ParseTomlAddress_65e7c844(tomlStr string, key str panic("should never get here") } +func (c *CheatCodesPrecompile) ComputeCreate2Address_890c283b(salt, codeHash [32]byte) (common.Address, error) { + data := make([]byte, 1+20+32+32) + data[0] = 0xff + copy(data[1:], DeterministicDeployerAddress.Bytes()) + copy(data[1+20:], salt[:]) + copy(data[1+20+32:], codeHash[:]) + finalHash := crypto.Keccak256(data) + // Take the last 20 bytes of the hash to get the address + return common.BytesToAddress(finalHash[12:]), nil +} + // unsupported //func (c *CheatCodesPrecompile) CreateWallet() {} diff --git a/op-chain-ops/script/cheatcodes_utilities_test.go b/op-chain-ops/script/cheatcodes_utilities_test.go index 23936a10e344..4870ec8129db 100644 --- a/op-chain-ops/script/cheatcodes_utilities_test.go +++ b/op-chain-ops/script/cheatcodes_utilities_test.go @@ -57,3 +57,14 @@ func TestParseTomlAddress(t *testing.T) { require.NoError(t, err) require.Equal(t, common.HexToAddress("0xff4ce7b6a91a35c31d7d62b327d19617c8da6f23"), addr) } + +func TestComputeCreate2Address(t *testing.T) { + c := &CheatCodesPrecompile{} + var salt [32]byte + salt[31] = 'S' + var codeHash [32]byte + codeHash[31] = 'C' + addr, err := c.ComputeCreate2Address_890c283b(salt, codeHash) + require.NoError(t, err) + require.EqualValues(t, common.HexToAddress("0x2f29AF1b5a7083bf98C4A89976c2f17FF980735f"), addr) +} diff --git a/op-deployer/pkg/deployer/apply.go b/op-deployer/pkg/deployer/apply.go index f6eb38d11e08..bdfd5c55ea1e 100644 --- a/op-deployer/pkg/deployer/apply.go +++ b/op-deployer/pkg/deployer/apply.go @@ -236,6 +236,11 @@ func ApplyPipeline( return pipeline.DeployOPChainGenesisStrategy(env, intent, st, chainID) } }, + }, pipelineStage{ + fmt.Sprintf("deploy-alt-da-%s", chainID.Hex()), + func() error { + return pipeline.DeployAltDA(env, intent, st, chainID) + }, }, pipelineStage{ fmt.Sprintf("generate-l2-genesis-%s", chainID.Hex()), func() error { diff --git a/op-deployer/pkg/deployer/integration_test/apply_test.go b/op-deployer/pkg/deployer/integration_test/apply_test.go index f123953a3ea4..fa59c49dc8ac 100644 --- a/op-deployer/pkg/deployer/integration_test/apply_test.go +++ b/op-deployer/pkg/deployer/integration_test/apply_test.go @@ -11,6 +11,10 @@ import ( "testing" "time" + altda "github.com/ethereum-optimism/optimism/op-alt-da" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/inspect" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" "github.com/ethereum-optimism/optimism/op-chain-ops/script" @@ -404,6 +408,45 @@ func TestInteropDeployment(t *testing.T) { checkStorageSlot(t, st.L1StateDump.Data.Accounts, chainState.SystemConfigProxyAddress, depManagerSlot, proxyAdminOwnerHash) } +func TestAltDADeployment(t *testing.T) { + op_e2e.InitParallel(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + env, bundle, intent, st := setupGenesisChain(t) + altDACfg := genesis.AltDADeployConfig{ + UseAltDA: true, + DACommitmentType: altda.KeccakCommitmentString, + DAChallengeWindow: 10, + DAResolveWindow: 10, + DABondSize: 100, + DAResolverRefundPercentage: 50, + } + intent.Chains[0].DangerousAltDAConfig = altDACfg + + require.NoError(t, deployer.ApplyPipeline( + ctx, + env, + bundle, + intent, + st, + )) + + chainState := st.Chains[0] + require.NotEmpty(t, chainState.DataAvailabilityChallengeProxyAddress) + require.NotEmpty(t, chainState.DataAvailabilityChallengeImplAddress) + + _, rollupCfg, err := inspect.GenesisAndRollup(st, chainState.ID) + require.NoError(t, err) + require.EqualValues(t, &rollup.AltDAConfig{ + CommitmentType: altda.KeccakCommitmentString, + DAChallengeWindow: altDACfg.DAChallengeWindow, + DAChallengeAddress: chainState.DataAvailabilityChallengeProxyAddress, + DAResolveWindow: altDACfg.DAResolveWindow, + }, rollupCfg.AltDAConfig) +} + func TestInvalidL2Genesis(t *testing.T) { op_e2e.InitParallel(t) diff --git a/op-deployer/pkg/deployer/opcm/alt_da.go b/op-deployer/pkg/deployer/opcm/alt_da.go new file mode 100644 index 000000000000..7c05a42a7a5a --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/alt_da.go @@ -0,0 +1,63 @@ +package opcm + +import ( + "fmt" + "math/big" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script" + "github.com/ethereum/go-ethereum/common" +) + +type DeployAltDAInput struct { + Salt common.Hash + ProxyAdmin common.Address + ChallengeContractOwner common.Address + ChallengeWindow *big.Int + ResolveWindow *big.Int + BondSize *big.Int + ResolverRefundPercentage *big.Int +} + +type DeployAltDAOutput struct { + DataAvailabilityChallengeProxy common.Address + DataAvailabilityChallengeImpl common.Address +} + +type DeployAltDAScript struct { + Run func(input, output common.Address) error +} + +func DeployAltDA( + host *script.Host, + input DeployAltDAInput, +) (DeployAltDAOutput, error) { + var output DeployAltDAOutput + inputAddr := host.NewScriptAddress() + outputAddr := host.NewScriptAddress() + + cleanupInput, err := script.WithPrecompileAtAddress[*DeployAltDAInput](host, inputAddr, &input) + if err != nil { + return output, fmt.Errorf("failed to insert DeployAltDAInput precompile: %w", err) + } + defer cleanupInput() + + cleanupOutput, err := script.WithPrecompileAtAddress[*DeployAltDAOutput](host, outputAddr, &output, + script.WithFieldSetter[*DeployAltDAOutput]) + if err != nil { + return output, fmt.Errorf("failed to insert DeployAltDAOutput precompile: %w", err) + } + defer cleanupOutput() + + implContract := "DeployAltDA" + deployScript, cleanupDeploy, err := script.WithScript[DeployAltDAScript](host, "DeployAltDA.s.sol", implContract) + if err != nil { + return output, fmt.Errorf("failed to laod %s script: %w", implContract, err) + } + defer cleanupDeploy() + + if err := deployScript.Run(inputAddr, outputAddr); err != nil { + return output, fmt.Errorf("failed to run %s script: %w", implContract, err) + } + + return output, nil +} diff --git a/op-deployer/pkg/deployer/opcm/alt_da_test.go b/op-deployer/pkg/deployer/opcm/alt_da_test.go new file mode 100644 index 000000000000..20b93907ebea --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/alt_da_test.go @@ -0,0 +1,43 @@ +package opcm + +import ( + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/testutil" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +func TestDeployAltDA(t *testing.T) { + _, artifacts := testutil.LocalArtifacts(t) + + host, err := env.DefaultScriptHost( + broadcaster.NoopBroadcaster(), + testlog.Logger(t, log.LevelInfo), + common.Address{'D'}, + artifacts, + 0, + ) + require.NoError(t, err) + + input := DeployAltDAInput{ + Salt: common.HexToHash("0x1234"), + ProxyAdmin: common.Address{'P'}, + ChallengeContractOwner: common.Address{'O'}, + ChallengeWindow: big.NewInt(100), + ResolveWindow: big.NewInt(200), + BondSize: big.NewInt(300), + ResolverRefundPercentage: big.NewInt(50), // must be < 100 + } + + output, err := DeployAltDA(host, input) + require.NoError(t, err) + + require.NotEmpty(t, output.DataAvailabilityChallengeProxy) + require.NotEmpty(t, output.DataAvailabilityChallengeImpl) +} diff --git a/op-deployer/pkg/deployer/pipeline/alt_da.go b/op-deployer/pkg/deployer/pipeline/alt_da.go new file mode 100644 index 000000000000..62796832c93c --- /dev/null +++ b/op-deployer/pkg/deployer/pipeline/alt_da.go @@ -0,0 +1,56 @@ +package pipeline + +import ( + "fmt" + "math/big" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum/go-ethereum/common" +) + +func DeployAltDA(env *Env, intent *state.Intent, st *state.State, chainID common.Hash) error { + lgr := env.Logger.New("stage", "deploy-alt-da") + + chainIntent, err := intent.Chain(chainID) + if err != nil { + return fmt.Errorf("failed to get chain intent: %w", err) + } + + chainState, err := st.Chain(chainID) + if err != nil { + return fmt.Errorf("failed to get chain state: %w", err) + } + + if !shouldDeployAltDA(chainIntent, chainState) { + lgr.Info("alt-da deployment not needed") + return nil + } + + var dao opcm.DeployAltDAOutput + lgr.Info("deploying alt-da contracts") + dao, err = opcm.DeployAltDA(env.L1ScriptHost, opcm.DeployAltDAInput{ + Salt: st.Create2Salt, + ProxyAdmin: st.ImplementationsDeployment.OpcmProxyAddress, + ChallengeContractOwner: chainIntent.Roles.L1ProxyAdminOwner, + ChallengeWindow: new(big.Int).SetUint64(chainIntent.DangerousAltDAConfig.DAChallengeWindow), + ResolveWindow: new(big.Int).SetUint64(chainIntent.DangerousAltDAConfig.DAResolveWindow), + BondSize: new(big.Int).SetUint64(chainIntent.DangerousAltDAConfig.DABondSize), + ResolverRefundPercentage: new(big.Int).SetUint64(chainIntent.DangerousAltDAConfig.DAResolverRefundPercentage), + }) + if err != nil { + return fmt.Errorf("failed to deploy alt-da contracts: %w", err) + } + + chainState.DataAvailabilityChallengeProxyAddress = dao.DataAvailabilityChallengeProxy + chainState.DataAvailabilityChallengeImplAddress = dao.DataAvailabilityChallengeImpl + return nil +} + +func shouldDeployAltDA(chainIntent *state.ChainIntent, chainState *state.ChainState) bool { + if !chainIntent.DangerousAltDAConfig.UseAltDA { + return false + } + + return chainState.DataAvailabilityChallengeImplAddress == common.Address{} +} diff --git a/op-deployer/pkg/deployer/state/deploy_config.go b/op-deployer/pkg/deployer/state/deploy_config.go index 15d5491ce8b8..1a03c21d7e94 100644 --- a/op-deployer/pkg/deployer/state/deploy_config.go +++ b/op-deployer/pkg/deployer/state/deploy_config.go @@ -119,6 +119,11 @@ func CombineDeployConfig(intent *Intent, chainIntent *ChainIntent, state *State, } } + if chainIntent.DangerousAltDAConfig.UseAltDA { + cfg.AltDADeployConfig = chainIntent.DangerousAltDAConfig + cfg.L1DependenciesConfig.DAChallengeProxy = chainState.DataAvailabilityChallengeProxyAddress + } + // The below dummy variables are set in order to allow the deploy // config to pass validation. The validation checks are useful to // ensure that the L2 is properly configured. They are not used by diff --git a/op-deployer/pkg/deployer/state/intent.go b/op-deployer/pkg/deployer/state/intent.go index 681d166bc142..860944666e9e 100644 --- a/op-deployer/pkg/deployer/state/intent.go +++ b/op-deployer/pkg/deployer/state/intent.go @@ -4,6 +4,8 @@ import ( "fmt" "math/big" + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" @@ -163,6 +165,8 @@ type ChainIntent struct { Roles ChainRoles `json:"roles" toml:"roles"` DeployOverrides map[string]any `json:"deployOverrides" toml:"deployOverrides"` + + DangerousAltDAConfig genesis.AltDADeployConfig `json:"dangerousAltDAConfig,omitempty" toml:"dangerousAltDAConfig,omitempty"` } type ChainRoles struct { @@ -207,5 +211,9 @@ func (c *ChainIntent) Check() error { return fmt.Errorf("batcher must be set") } + if c.DangerousAltDAConfig.UseAltDA { + return c.DangerousAltDAConfig.Check(nil) + } + return nil } diff --git a/op-deployer/pkg/deployer/state/state.go b/op-deployer/pkg/deployer/state/state.go index 3df543c56cdd..e3974fa2a78c 100644 --- a/op-deployer/pkg/deployer/state/state.go +++ b/op-deployer/pkg/deployer/state/state.go @@ -95,6 +95,8 @@ type ChainState struct { PermissionedDisputeGameAddress common.Address `json:"permissionedDisputeGameAddress"` DelayedWETHPermissionedGameProxyAddress common.Address `json:"delayedWETHPermissionedGameProxyAddress"` DelayedWETHPermissionlessGameProxyAddress common.Address `json:"delayedWETHPermissionlessGameProxyAddress"` + DataAvailabilityChallengeProxyAddress common.Address `json:"dataAvailabilityChallengeProxyAddress"` + DataAvailabilityChallengeImplAddress common.Address `json:"dataAvailabilityChallengeImplAddress"` Allocs *GzipData[foundry.ForgeAllocs] `json:"allocs"` diff --git a/packages/contracts-bedrock/scripts/deploy/DeployAltDA.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployAltDA.s.sol new file mode 100644 index 000000000000..a5071474926b --- /dev/null +++ b/packages/contracts-bedrock/scripts/deploy/DeployAltDA.s.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; +import { IDataAvailabilityChallenge } from "src/L1/interfaces/IDataAvailabilityChallenge.sol"; +import { IProxy } from "src/universal/interfaces/IProxy.sol"; +import { Script } from "forge-std/Script.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; +import { IProxyAdmin } from "src/universal/interfaces/IProxyAdmin.sol"; +import { Solarray } from "scripts/libraries/Solarray.sol"; + +contract DeployAltDAInput is BaseDeployIO { + bytes32 internal _salt; + IProxyAdmin internal _proxyAdmin; + address internal _challengeContractOwner; + uint256 internal _challengeWindow; + uint256 internal _resolveWindow; + uint256 internal _bondSize; + uint256 internal _resolverRefundPercentage; + + function set(bytes4 _sel, bytes32 _val) public { + if (_sel == this.salt.selector) _salt = _val; + else revert("DeployAltDAInput: unknown selector"); + } + + function set(bytes4 _sel, address _addr) public { + require(_addr != address(0), "DeployAltDAInput: cannot set zero address"); + if (_sel == this.proxyAdmin.selector) _proxyAdmin = IProxyAdmin(_addr); + else if (_sel == this.challengeContractOwner.selector) _challengeContractOwner = _addr; + else revert("DeployAltDAInput: unknown selector"); + } + + function set(bytes4 _sel, uint256 _val) public { + if (_sel == this.challengeWindow.selector) _challengeWindow = _val; + else if (_sel == this.resolveWindow.selector) _resolveWindow = _val; + else if (_sel == this.bondSize.selector) _bondSize = _val; + else if (_sel == this.resolverRefundPercentage.selector) _resolverRefundPercentage = _val; + else revert("DeployAltDAInput: unknown selector"); + } + + function salt() public view returns (bytes32) { + require(_salt != 0, "DeployAltDAInput: salt not set"); + return _salt; + } + + function proxyAdmin() public view returns (IProxyAdmin) { + require(address(_proxyAdmin) != address(0), "DeployAltDAInput: proxyAdmin not set"); + return _proxyAdmin; + } + + function challengeContractOwner() public view returns (address) { + require(_challengeContractOwner != address(0), "DeployAltDAInput: challengeContractOwner not set"); + return _challengeContractOwner; + } + + function challengeWindow() public view returns (uint256) { + require(_challengeWindow != 0, "DeployAltDAInput: challengeWindow not set"); + return _challengeWindow; + } + + function resolveWindow() public view returns (uint256) { + require(_resolveWindow != 0, "DeployAltDAInput: resolveWindow not set"); + return _resolveWindow; + } + + function bondSize() public view returns (uint256) { + require(_bondSize != 0, "DeployAltDAInput: bondSize not set"); + return _bondSize; + } + + function resolverRefundPercentage() public view returns (uint256) { + require(_resolverRefundPercentage != 0, "DeployAltDAInput: resolverRefundPercentage not set"); + return _resolverRefundPercentage; + } +} + +contract DeployAltDAOutput is BaseDeployIO { + IDataAvailabilityChallenge internal _dataAvailabilityChallengeProxy; + IDataAvailabilityChallenge internal _dataAvailabilityChallengeImpl; + + function set(bytes4 _sel, address _addr) public { + require(_addr != address(0), "DeployAltDAOutput: cannot set zero address"); + if (_sel == this.dataAvailabilityChallengeProxy.selector) { + _dataAvailabilityChallengeProxy = IDataAvailabilityChallenge(payable(_addr)); + } else if (_sel == this.dataAvailabilityChallengeImpl.selector) { + _dataAvailabilityChallengeImpl = IDataAvailabilityChallenge(payable(_addr)); + } else { + revert("DeployAltDAOutput: unknown selector"); + } + } + + function dataAvailabilityChallengeProxy() public view returns (IDataAvailabilityChallenge) { + DeployUtils.assertValidContractAddress(address(_dataAvailabilityChallengeProxy)); + return _dataAvailabilityChallengeProxy; + } + + function dataAvailabilityChallengeImpl() public view returns (IDataAvailabilityChallenge) { + DeployUtils.assertValidContractAddress(address(_dataAvailabilityChallengeImpl)); + return _dataAvailabilityChallengeImpl; + } +} + +contract DeployAltDA is Script { + function run(DeployAltDAInput _dai, DeployAltDAOutput _dao) public { + deployDataAvailabilityChallengeProxy(_dai, _dao); + deployDataAvailabilityChallengeImpl(_dai, _dao); + initializeDataAvailabilityChallengeProxy(_dai, _dao); + + checkOutput(_dai, _dao); + } + + function deployDataAvailabilityChallengeProxy(DeployAltDAInput _dai, DeployAltDAOutput _dao) public { + bytes32 salt = _dai.salt(); + vm.broadcast(msg.sender); + IProxy proxy = IProxy( + DeployUtils.create2({ + _name: "Proxy", + _salt: salt, + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (msg.sender))) + }) + ); + vm.label(address(proxy), "DataAvailabilityChallengeProxy"); + _dao.set(_dao.dataAvailabilityChallengeProxy.selector, address(proxy)); + } + + function deployDataAvailabilityChallengeImpl(DeployAltDAInput _dai, DeployAltDAOutput _dao) public { + bytes32 salt = _dai.salt(); + vm.broadcast(msg.sender); + IDataAvailabilityChallenge impl = IDataAvailabilityChallenge( + DeployUtils.create2({ + _name: "DataAvailabilityChallenge", + _salt: salt, + _args: DeployUtils.encodeConstructor(abi.encodeCall(IDataAvailabilityChallenge.__constructor__, ())) + }) + ); + vm.label(address(impl), "DataAvailabilityChallengeImpl"); + _dao.set(_dao.dataAvailabilityChallengeImpl.selector, address(impl)); + } + + function initializeDataAvailabilityChallengeProxy(DeployAltDAInput _dai, DeployAltDAOutput _dao) public { + IProxy proxy = IProxy(payable(address(_dao.dataAvailabilityChallengeProxy()))); + IDataAvailabilityChallenge impl = _dao.dataAvailabilityChallengeImpl(); + IProxyAdmin proxyAdmin = IProxyAdmin(payable(address(_dai.proxyAdmin()))); + + address contractOwner = _dai.challengeContractOwner(); + uint256 challengeWindow = _dai.challengeWindow(); + uint256 resolveWindow = _dai.resolveWindow(); + uint256 bondSize = _dai.bondSize(); + uint256 resolverRefundPercentage = _dai.resolverRefundPercentage(); + + vm.startBroadcast(msg.sender); + proxy.upgradeToAndCall( + address(impl), + abi.encodeCall( + IDataAvailabilityChallenge.initialize, + (contractOwner, challengeWindow, resolveWindow, bondSize, resolverRefundPercentage) + ) + ); + proxy.changeAdmin(address(proxyAdmin)); + vm.stopBroadcast(); + } + + function checkOutput(DeployAltDAInput _dai, DeployAltDAOutput _dao) public { + address[] memory addresses = Solarray.addresses( + address(_dao.dataAvailabilityChallengeProxy()), address(_dao.dataAvailabilityChallengeImpl()) + ); + DeployUtils.assertValidContractAddresses(addresses); + + assertValidDataAvailabilityChallengeProxy(_dai, _dao); + assertValidDataAvailabilityChallengeImpl(_dao); + } + + function assertValidDataAvailabilityChallengeProxy(DeployAltDAInput _dai, DeployAltDAOutput _dao) public { + DeployUtils.assertERC1967ImplementationSet(address(_dao.dataAvailabilityChallengeProxy())); + + IProxy proxy = IProxy(payable(address(_dao.dataAvailabilityChallengeProxy()))); + vm.prank(address(0)); + address admin = proxy.admin(); + require(admin == address(_dai.proxyAdmin()), "DACP-10"); + + DeployUtils.assertInitialized({ _contractAddress: address(proxy), _slot: 0, _offset: 0 }); + + vm.prank(address(0)); + address impl = proxy.implementation(); + require(impl == address(_dao.dataAvailabilityChallengeImpl()), "DACP-20"); + + IDataAvailabilityChallenge dac = _dao.dataAvailabilityChallengeProxy(); + require(dac.owner() == _dai.challengeContractOwner(), "DACP-30"); + require(dac.challengeWindow() == _dai.challengeWindow(), "DACP-40"); + require(dac.resolveWindow() == _dai.resolveWindow(), "DACP-50"); + require(dac.bondSize() == _dai.bondSize(), "DACP-60"); + require(dac.resolverRefundPercentage() == _dai.resolverRefundPercentage(), "DACP-70"); + } + + function assertValidDataAvailabilityChallengeImpl(DeployAltDAOutput _dao) public view { + IDataAvailabilityChallenge dac = _dao.dataAvailabilityChallengeImpl(); + DeployUtils.assertInitialized({ _contractAddress: address(dac), _slot: 0, _offset: 0 }); + } +} diff --git a/packages/contracts-bedrock/test/opcm/DeployAltDA.t.sol b/packages/contracts-bedrock/test/opcm/DeployAltDA.t.sol new file mode 100644 index 000000000000..e48a9b37a9f8 --- /dev/null +++ b/packages/contracts-bedrock/test/opcm/DeployAltDA.t.sol @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { Test } from "forge-std/Test.sol"; + +import { DeployAltDAInput, DeployAltDAOutput, DeployAltDA } from "scripts/deploy/DeployAltDA.s.sol"; +import { IDataAvailabilityChallenge } from "src/L1/interfaces/IDataAvailabilityChallenge.sol"; +import { IProxyAdmin } from "src/universal/interfaces/IProxyAdmin.sol"; +import { IProxy } from "src/universal/interfaces/IProxy.sol"; +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +contract DeployAltDAInput_Test is Test { + DeployAltDAInput dai; + + // Define defaults + bytes32 salt = bytes32(uint256(1)); + address proxyAdminAddr = makeAddr("proxyAdmin"); + address challengeContractOwner = makeAddr("challengeContractOwner"); + uint256 challengeWindow = 100; + uint256 resolveWindow = 200; + uint256 bondSize = 1 ether; + uint256 resolverRefundPercentage = 10; + + function setUp() public { + dai = new DeployAltDAInput(); + } + + function test_set_succeeds() public { + dai.set(dai.salt.selector, salt); + dai.set(dai.proxyAdmin.selector, proxyAdminAddr); + dai.set(dai.challengeContractOwner.selector, challengeContractOwner); + dai.set(dai.challengeWindow.selector, challengeWindow); + dai.set(dai.resolveWindow.selector, resolveWindow); + dai.set(dai.bondSize.selector, bondSize); + dai.set(dai.resolverRefundPercentage.selector, resolverRefundPercentage); + + // Compare the default inputs to the getter methods + assertEq(salt, dai.salt(), "100"); + assertEq(proxyAdminAddr, address(dai.proxyAdmin()), "200"); + assertEq(challengeContractOwner, dai.challengeContractOwner(), "300"); + assertEq(challengeWindow, dai.challengeWindow(), "400"); + assertEq(resolveWindow, dai.resolveWindow(), "500"); + assertEq(bondSize, dai.bondSize(), "600"); + assertEq(resolverRefundPercentage, dai.resolverRefundPercentage(), "700"); + } + + function test_getters_whenNotSet_revert() public { + bytes memory expectedErr = "DeployAltDAInput: "; + + vm.expectRevert(abi.encodePacked(expectedErr, "salt not set")); + dai.salt(); + + vm.expectRevert(abi.encodePacked(expectedErr, "proxyAdmin not set")); + dai.proxyAdmin(); + + vm.expectRevert(abi.encodePacked(expectedErr, "challengeContractOwner not set")); + dai.challengeContractOwner(); + + vm.expectRevert(abi.encodePacked(expectedErr, "challengeWindow not set")); + dai.challengeWindow(); + + vm.expectRevert(abi.encodePacked(expectedErr, "resolveWindow not set")); + dai.resolveWindow(); + + vm.expectRevert(abi.encodePacked(expectedErr, "bondSize not set")); + dai.bondSize(); + + vm.expectRevert(abi.encodePacked(expectedErr, "resolverRefundPercentage not set")); + dai.resolverRefundPercentage(); + } + + function test_set_zeroAddress_reverts() public { + vm.expectRevert("DeployAltDAInput: cannot set zero address"); + dai.set(dai.proxyAdmin.selector, address(0)); + + vm.expectRevert("DeployAltDAInput: cannot set zero address"); + dai.set(dai.challengeContractOwner.selector, address(0)); + } + + function test_set_unknownSelector_reverts() public { + bytes4 unknownSelector = bytes4(keccak256("unknown()")); + + vm.expectRevert("DeployAltDAInput: unknown selector"); + dai.set(unknownSelector, bytes32(0)); + + vm.expectRevert("DeployAltDAInput: unknown selector"); + dai.set(unknownSelector, address(1)); + + vm.expectRevert("DeployAltDAInput: unknown selector"); + dai.set(unknownSelector, uint256(1)); + } +} + +contract DeployAltDAOutput_Test is Test { + DeployAltDAOutput dao; + + // Store contract references to avoid stack too deep + IDataAvailabilityChallenge internal dataAvailabilityChallengeImpl; + + function setUp() public { + dao = new DeployAltDAOutput(); + dataAvailabilityChallengeImpl = IDataAvailabilityChallenge(payable(makeAddr("dataAvailabilityChallengeImpl"))); + } + + function test_set_succeeds() public { + // Build the implementation with some bytecode + vm.etch(address(dataAvailabilityChallengeImpl), hex"01"); + + // Build proxy with implementation + (IProxy dataAvailabilityChallengeProxy) = + DeployUtils.buildERC1967ProxyWithImpl("dataAvailabilityChallengeProxy"); + + // Set the addresses + dao.set(dao.dataAvailabilityChallengeProxy.selector, address(dataAvailabilityChallengeProxy)); + dao.set(dao.dataAvailabilityChallengeImpl.selector, address(dataAvailabilityChallengeImpl)); + + // Verify the addresses were set correctly + assertEq(address(dataAvailabilityChallengeProxy), address(dao.dataAvailabilityChallengeProxy()), "100"); + assertEq(address(dataAvailabilityChallengeImpl), address(dao.dataAvailabilityChallengeImpl()), "200"); + } + + function test_getters_whenNotSet_revert() public { + vm.expectRevert("DeployUtils: zero address"); + dao.dataAvailabilityChallengeProxy(); + + vm.expectRevert("DeployUtils: zero address"); + dao.dataAvailabilityChallengeImpl(); + } + + function test_getters_whenAddrHasNoCode_reverts() public { + address emptyAddr = makeAddr("emptyAddr"); + bytes memory expectedErr = bytes(string.concat("DeployUtils: no code at ", vm.toString(emptyAddr))); + + dao.set(dao.dataAvailabilityChallengeProxy.selector, emptyAddr); + vm.expectRevert(expectedErr); + dao.dataAvailabilityChallengeProxy(); + + dao.set(dao.dataAvailabilityChallengeImpl.selector, emptyAddr); + vm.expectRevert(expectedErr); + dao.dataAvailabilityChallengeImpl(); + } + + function test_set_zeroAddress_reverts() public { + vm.expectRevert("DeployAltDAOutput: cannot set zero address"); + dao.set(dao.dataAvailabilityChallengeProxy.selector, address(0)); + + vm.expectRevert("DeployAltDAOutput: cannot set zero address"); + dao.set(dao.dataAvailabilityChallengeImpl.selector, address(0)); + } + + function test_set_unknownSelector_reverts() public { + bytes4 unknownSelector = bytes4(keccak256("unknown()")); + vm.expectRevert("DeployAltDAOutput: unknown selector"); + dao.set(unknownSelector, address(1)); + } +} + +contract DeployAltDA_Test is Test { + DeployAltDA deployer; + DeployAltDAInput dai; + DeployAltDAOutput dao; + + // Define defaults + bytes32 salt = bytes32(uint256(1)); + IProxyAdmin proxyAdmin; + address challengeContractOwner = makeAddr("challengeContractOwner"); + uint256 challengeWindow = 100; + uint256 resolveWindow = 200; + uint256 bondSize = 1 ether; + uint256 resolverRefundPercentage = 10; + + function setUp() public { + // Deploy the main contract and get input/output contracts + deployer = new DeployAltDA(); + (dai, dao) = _setupIOContracts(); + + // Setup proxyAdmin + proxyAdmin = IProxyAdmin( + DeployUtils.create1({ + _name: "ProxyAdmin", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxyAdmin.__constructor__, (msg.sender))) + }) + ); + + // Set the default values + dai.set(dai.salt.selector, salt); + dai.set(dai.proxyAdmin.selector, address(proxyAdmin)); + dai.set(dai.challengeContractOwner.selector, challengeContractOwner); + dai.set(dai.challengeWindow.selector, challengeWindow); + dai.set(dai.resolveWindow.selector, resolveWindow); + dai.set(dai.bondSize.selector, bondSize); + dai.set(dai.resolverRefundPercentage.selector, resolverRefundPercentage); + } + + function _setupIOContracts() internal returns (DeployAltDAInput, DeployAltDAOutput) { + DeployAltDAInput _dai = new DeployAltDAInput(); + DeployAltDAOutput _dao = new DeployAltDAOutput(); + return (_dai, _dao); + } + + function test_run_succeeds() public { + deployer.run(dai, dao); + + // Verify everything is set up correctly + IDataAvailabilityChallenge dac = dao.dataAvailabilityChallengeProxy(); + assertTrue(address(dac).code.length > 0, "100"); + assertTrue(address(dao.dataAvailabilityChallengeImpl()).code.length > 0, "200"); + + // Check all initialization parameters + assertEq(dac.owner(), challengeContractOwner, "300"); + assertEq(dac.challengeWindow(), challengeWindow, "400"); + assertEq(dac.resolveWindow(), resolveWindow, "500"); + assertEq(dac.bondSize(), bondSize, "600"); + assertEq(dac.resolverRefundPercentage(), resolverRefundPercentage, "700"); + // Make sure the proxy admin is set correctly. + vm.prank(address(0)); + assertEq(IProxy(payable(address(dac))).admin(), address(proxyAdmin), "800"); + } + + function test_checkOutput_whenNotInitialized_reverts() public { + vm.expectRevert("DeployUtils: zero address"); + deployer.checkOutput(dai, dao); + } + + function test_checkOutput_whenProxyNotInitialized_reverts() public { + // Deploy but don't initialize + deployer.deployDataAvailabilityChallengeProxy(dai, dao); + deployer.deployDataAvailabilityChallengeImpl(dai, dao); + + vm.expectRevert("DeployUtils: zero address"); + deployer.checkOutput(dai, dao); + } + + function testFuzz_run_withDifferentParameters( + uint256 _challengeWindow, + uint256 _resolveWindow, + uint256 _bondSize, + uint256 _resolverRefundPercentage + ) + public + { + // Bound the values to reasonable ranges + _challengeWindow = bound(_challengeWindow, 1, 365 days); + _resolveWindow = bound(_resolveWindow, 1, 365 days); + _bondSize = bound(_bondSize, 0.1 ether, 100 ether); + _resolverRefundPercentage = bound(_resolverRefundPercentage, 1, 100); + + // Set the new values + dai.set(dai.salt.selector, salt); + dai.set(dai.proxyAdmin.selector, address(proxyAdmin)); + dai.set(dai.challengeWindow.selector, _challengeWindow); + dai.set(dai.resolveWindow.selector, _resolveWindow); + dai.set(dai.bondSize.selector, _bondSize); + dai.set(dai.resolverRefundPercentage.selector, _resolverRefundPercentage); + + // Run deployment + deployer.run(dai, dao); + + // Verify values + IDataAvailabilityChallenge dac = dao.dataAvailabilityChallengeProxy(); + assertEq(dac.challengeWindow(), _challengeWindow, "100"); + assertEq(dac.resolveWindow(), _resolveWindow, "200"); + assertEq(dac.bondSize(), _bondSize, "300"); + assertEq(dac.resolverRefundPercentage(), _resolverRefundPercentage, "400"); + } +}