Commit dec8c60a authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

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
parent 9948426d
......@@ -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)
......
......@@ -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() {}
......
......@@ -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)
}
......@@ -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 {
......
......@@ -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)
......
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
}
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)
}
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{}
}
......@@ -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
......
......@@ -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
}
......@@ -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"`
......
// 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 });
}
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment