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"); + } +}