Commit 9e9effa2 authored by smartcontracts's avatar smartcontracts Committed by GitHub

feat: DeployDisputeGame script (#12641)

Adds a new deployment script for deploying a new dispute game
implementation.
parent 2e0223f2
......@@ -188,7 +188,7 @@ func DelayedWETH(ctx context.Context, cfg DelayedWETHConfig) error {
},
)
if err != nil {
return fmt.Errorf("error deploying implementations: %w", err)
return fmt.Errorf("error deploying DelayedWETH: %w", err)
}
if _, err := bcaster.Broadcast(ctx); err != nil {
......
package bootstrap
import (
"context"
"crypto/ecdsa"
"fmt"
"strings"
artifacts2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/env"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum-optimism/optimism/op-service/ctxinterrupt"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
)
type DisputeGameConfig struct {
L1RPCUrl string
PrivateKey string
Logger log.Logger
ArtifactsLocator *artifacts2.Locator
privateKeyECDSA *ecdsa.PrivateKey
MinProposalSizeBytes uint64
ChallengePeriodSeconds uint64
MipsVersion uint8
GameKind string
GameType uint32
AbsolutePrestate common.Hash
MaxGameDepth uint64
SplitDepth uint64
ClockExtension uint64
MaxClockDuration uint64
DelayedWethProxy common.Address
AnchorStateRegistryProxy common.Address
L2ChainId uint64
Proposer common.Address
Challenger common.Address
}
func (c *DisputeGameConfig) Check() error {
if c.L1RPCUrl == "" {
return fmt.Errorf("l1RPCUrl must be specified")
}
if c.PrivateKey == "" {
return fmt.Errorf("private key must be specified")
}
privECDSA, err := crypto.HexToECDSA(strings.TrimPrefix(c.PrivateKey, "0x"))
if err != nil {
return fmt.Errorf("failed to parse private key: %w", err)
}
c.privateKeyECDSA = privECDSA
if c.Logger == nil {
return fmt.Errorf("logger must be specified")
}
if c.ArtifactsLocator == nil {
return fmt.Errorf("artifacts locator must be specified")
}
return nil
}
func DisputeGameCLI(cliCtx *cli.Context) error {
logCfg := oplog.ReadCLIConfig(cliCtx)
l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg)
oplog.SetGlobalLogHandler(l.Handler())
l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName)
privateKey := cliCtx.String(deployer.PrivateKeyFlagName)
artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName)
artifactsLocator := new(artifacts2.Locator)
if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil {
return fmt.Errorf("failed to parse artifacts URL: %w", err)
}
ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context)
return DisputeGame(ctx, DisputeGameConfig{
L1RPCUrl: l1RPCUrl,
PrivateKey: privateKey,
Logger: l,
ArtifactsLocator: artifactsLocator,
})
}
func DisputeGame(ctx context.Context, cfg DisputeGameConfig) error {
if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid config for DisputeGame: %w", err)
}
lgr := cfg.Logger
progressor := func(curr, total int64) {
lgr.Info("artifacts download progress", "current", curr, "total", total)
}
artifactsFS, cleanup, err := artifacts2.Download(ctx, cfg.ArtifactsLocator, progressor)
if err != nil {
return fmt.Errorf("failed to download artifacts: %w", err)
}
defer func() {
if err := cleanup(); err != nil {
lgr.Warn("failed to clean up artifacts", "err", err)
}
}()
l1Client, err := ethclient.Dial(cfg.L1RPCUrl)
if err != nil {
return fmt.Errorf("failed to connect to L1 RPC: %w", err)
}
chainID, err := l1Client.ChainID(ctx)
if err != nil {
return fmt.Errorf("failed to get chain ID: %w", err)
}
chainIDU64 := chainID.Uint64()
standardVersionsTOML, err := standard.L1VersionsDataFor(chainIDU64)
if err != nil {
return fmt.Errorf("error getting standard versions TOML: %w", err)
}
signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID))
chainDeployer := crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey)
bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
Logger: lgr,
ChainID: chainID,
Client: l1Client,
Signer: signer,
From: chainDeployer,
})
if err != nil {
return fmt.Errorf("failed to create broadcaster: %w", err)
}
nonce, err := l1Client.NonceAt(ctx, chainDeployer, nil)
if err != nil {
return fmt.Errorf("failed to get starting nonce: %w", err)
}
host, err := env.DefaultScriptHost(
bcaster,
lgr,
chainDeployer,
artifactsFS,
nonce,
)
if err != nil {
return fmt.Errorf("failed to create script host: %w", err)
}
var release string
if cfg.ArtifactsLocator.IsTag() {
release = cfg.ArtifactsLocator.Tag
} else {
release = "dev"
}
lgr.Info("deploying dispute game", "release", release)
dgo, err := opcm.DeployDisputeGame(
host,
opcm.DeployDisputeGameInput{
Release: release,
StandardVersionsToml: standardVersionsTOML,
MipsVersion: cfg.MipsVersion,
MinProposalSizeBytes: cfg.MinProposalSizeBytes,
ChallengePeriodSeconds: cfg.ChallengePeriodSeconds,
GameKind: cfg.GameKind,
GameType: cfg.GameType,
AbsolutePrestate: cfg.AbsolutePrestate,
MaxGameDepth: cfg.MaxGameDepth,
SplitDepth: cfg.SplitDepth,
ClockExtension: cfg.ClockExtension,
MaxClockDuration: cfg.MaxClockDuration,
DelayedWethProxy: cfg.DelayedWethProxy,
AnchorStateRegistryProxy: cfg.AnchorStateRegistryProxy,
L2ChainId: cfg.L2ChainId,
Proposer: cfg.Proposer,
Challenger: cfg.Challenger,
},
)
if err != nil {
return fmt.Errorf("error deploying dispute game: %w", err)
}
if _, err := bcaster.Broadcast(ctx); err != nil {
return fmt.Errorf("failed to broadcast: %w", err)
}
lgr.Info("deployed dispute game")
if err := jsonutil.WriteJSON(dgo, ioutil.ToStdOut()); err != nil {
return fmt.Errorf("failed to write output: %w", err)
}
return nil
}
......@@ -4,6 +4,7 @@ import (
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
)
......@@ -15,6 +16,18 @@ const (
ProofMaturityDelaySecondsFlagName = "proof-maturity-delay-seconds"
DisputeGameFinalityDelaySecondsFlagName = "dispute-game-finality-delay-seconds"
MIPSVersionFlagName = "mips-version"
GameKindFlagName = "game-kind"
GameTypeFlagName = "game-type"
AbsolutePrestateFlagName = "absolute-prestate"
MaxGameDepthFlagName = "max-game-depth"
SplitDepthFlagName = "split-depth"
ClockExtensionFlagName = "clock-extension"
MaxClockDurationFlagName = "max-clock-duration"
DelayedWethProxyFlagName = "delayed-weth-proxy"
AnchorStateRegistryProxyFlagName = "anchor-state-registry-proxy"
L2ChainIdFlagName = "l2-chain-id"
ProposerFlagName = "proposer"
ChallengerFlagName = "challenger"
)
var (
......@@ -59,6 +72,74 @@ var (
EnvVars: deployer.PrefixEnvVar("MIPS_VERSION"),
Value: standard.MIPSVersion,
}
GameKindFlag = &cli.StringFlag{
Name: GameKindFlagName,
Usage: "Game kind (FaultDisputeGame or PermissionedDisputeGame).",
EnvVars: deployer.PrefixEnvVar("GAME_KIND"),
Value: "FaultDisputeGame",
}
GameTypeFlag = &cli.StringFlag{
Name: GameTypeFlagName,
Usage: "Game type (integer or fractional).",
EnvVars: deployer.PrefixEnvVar("GAME_TYPE"),
}
AbsolutePrestateFlag = &cli.StringFlag{
Name: AbsolutePrestateFlagName,
Usage: "Absolute prestate.",
EnvVars: deployer.PrefixEnvVar("ABSOLUTE_PRESTATE"),
Value: standard.DisputeAbsolutePrestate.Hex(),
}
MaxGameDepthFlag = &cli.Uint64Flag{
Name: MaxGameDepthFlagName,
Usage: "Max game depth.",
EnvVars: deployer.PrefixEnvVar("MAX_GAME_DEPTH"),
Value: standard.DisputeMaxGameDepth,
}
SplitDepthFlag = &cli.Uint64Flag{
Name: SplitDepthFlagName,
Usage: "Split depth.",
EnvVars: deployer.PrefixEnvVar("SPLIT_DEPTH"),
Value: standard.DisputeSplitDepth,
}
ClockExtensionFlag = &cli.Uint64Flag{
Name: ClockExtensionFlagName,
Usage: "Clock extension.",
EnvVars: deployer.PrefixEnvVar("CLOCK_EXTENSION"),
Value: standard.DisputeClockExtension,
}
MaxClockDurationFlag = &cli.Uint64Flag{
Name: MaxClockDurationFlagName,
Usage: "Max clock duration.",
EnvVars: deployer.PrefixEnvVar("MAX_CLOCK_DURATION"),
Value: standard.DisputeMaxClockDuration,
}
DelayedWethProxyFlag = &cli.StringFlag{
Name: DelayedWethProxyFlagName,
Usage: "Delayed WETH proxy.",
EnvVars: deployer.PrefixEnvVar("DELAYED_WETH_PROXY"),
}
AnchorStateRegistryProxyFlag = &cli.StringFlag{
Name: AnchorStateRegistryProxyFlagName,
Usage: "Anchor state registry proxy.",
EnvVars: deployer.PrefixEnvVar("ANCHOR_STATE_REGISTRY_PROXY"),
}
L2ChainIdFlag = &cli.Uint64Flag{
Name: L2ChainIdFlagName,
Usage: "L2 chain ID.",
EnvVars: deployer.PrefixEnvVar("L2_CHAIN_ID"),
}
ProposerFlag = &cli.StringFlag{
Name: ProposerFlagName,
Usage: "Proposer address (permissioned game only).",
EnvVars: deployer.PrefixEnvVar("PROPOSER"),
Value: common.Address{}.Hex(),
}
ChallengerFlag = &cli.StringFlag{
Name: ChallengerFlagName,
Usage: "Challenger address (permissioned game only).",
EnvVars: deployer.PrefixEnvVar("CHALLENGER"),
Value: common.Address{}.Hex(),
}
)
var OPCMFlags = []cli.Flag{
......@@ -79,6 +160,27 @@ var DelayedWETHFlags = []cli.Flag{
ArtifactsLocatorFlag,
}
var DisputeGameFlags = []cli.Flag{
deployer.L1RPCURLFlag,
deployer.PrivateKeyFlag,
ArtifactsLocatorFlag,
MinProposalSizeBytesFlag,
ChallengePeriodSecondsFlag,
MIPSVersionFlag,
GameKindFlag,
GameTypeFlag,
AbsolutePrestateFlag,
MaxGameDepthFlag,
SplitDepthFlag,
ClockExtensionFlag,
MaxClockDurationFlag,
DelayedWethProxyFlag,
AnchorStateRegistryProxyFlag,
L2ChainIdFlag,
ProposerFlag,
ChallengerFlag,
}
var Commands = []*cli.Command{
{
Name: "opcm",
......@@ -92,4 +194,10 @@ var Commands = []*cli.Command{
Flags: cliapp.ProtectFlags(DelayedWETHFlags),
Action: DelayedWETHCLI,
},
{
Name: "disputegame",
Usage: "Bootstrap an instance of a FaultDisputeGame or PermissionedDisputeGame.",
Flags: cliapp.ProtectFlags(DisputeGameFlags),
Action: DisputeGameCLI,
},
}
package opcm
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
)
type DeployDisputeGameInput struct {
Release string
StandardVersionsToml string
MipsVersion uint8
MinProposalSizeBytes uint64
ChallengePeriodSeconds uint64
GameKind string
GameType uint32
AbsolutePrestate common.Hash
MaxGameDepth uint64
SplitDepth uint64
ClockExtension uint64
MaxClockDuration uint64
DelayedWethProxy common.Address
AnchorStateRegistryProxy common.Address
L2ChainId uint64
Proposer common.Address
Challenger common.Address
}
func (input *DeployDisputeGameInput) InputSet() bool {
return true
}
type DeployDisputeGameOutput struct {
DisputeGameImpl common.Address
MipsSingleton common.Address
PreimageOracleSingleton common.Address
}
func (output *DeployDisputeGameOutput) CheckOutput(input common.Address) error {
return nil
}
type DeployDisputeGameScript struct {
Run func(input, output common.Address) error
}
func DeployDisputeGame(
host *script.Host,
input DeployDisputeGameInput,
) (DeployDisputeGameOutput, error) {
var output DeployDisputeGameOutput
inputAddr := host.NewScriptAddress()
outputAddr := host.NewScriptAddress()
cleanupInput, err := script.WithPrecompileAtAddress[*DeployDisputeGameInput](host, inputAddr, &input)
if err != nil {
return output, fmt.Errorf("failed to insert DeployDisputeGameInput precompile: %w", err)
}
defer cleanupInput()
cleanupOutput, err := script.WithPrecompileAtAddress[*DeployDisputeGameOutput](host, outputAddr, &output,
script.WithFieldSetter[*DeployDisputeGameOutput])
if err != nil {
return output, fmt.Errorf("failed to insert DeployDisputeGameOutput precompile: %w", err)
}
defer cleanupOutput()
implContract := "DeployDisputeGame"
deployScript, cleanupDeploy, err := script.WithScript[DeployDisputeGameScript](host, "DeployDisputeGame.s.sol", implContract)
if err != nil {
return output, fmt.Errorf("failed to load %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 (
"testing"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard"
"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 TestDeployDisputeGame(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)
standardVersionsTOML, err := standard.L1VersionsDataFor(11155111)
require.NoError(t, err)
input := DeployDisputeGameInput{
Release: "dev",
StandardVersionsToml: standardVersionsTOML,
MipsVersion: 1,
MinProposalSizeBytes: standard.MinProposalSizeBytes,
ChallengePeriodSeconds: standard.ChallengePeriodSeconds,
GameKind: "PermissionedDisputeGame",
GameType: 1,
AbsolutePrestate: common.Hash{'A'},
MaxGameDepth: standard.DisputeMaxGameDepth,
SplitDepth: standard.DisputeSplitDepth,
ClockExtension: standard.DisputeClockExtension,
MaxClockDuration: standard.DisputeMaxClockDuration,
DelayedWethProxy: common.Address{'D'},
AnchorStateRegistryProxy: common.Address{'A'},
L2ChainId: 69,
Proposer: common.Address{'P'},
Challenger: common.Address{'C'},
}
output, err := DeployDisputeGame(host, input)
require.NoError(t, err)
require.NotEmpty(t, output.DisputeGameImpl)
require.NotEmpty(t, output.MipsSingleton)
require.NotEmpty(t, output.PreimageOracleSingleton)
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
// Forge
import { Script } from "forge-std/Script.sol";
// Scripts
import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol";
import { DeployUtils } from "scripts/libraries/DeployUtils.sol";
// Libraries
import { GameType, Claim, Duration } from "src/dispute/lib/Types.sol";
import { LibString } from "@solady/utils/LibString.sol";
// Interfaces
import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol";
import { IPermissionedDisputeGame } from "src/dispute/interfaces/IPermissionedDisputeGame.sol";
import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol";
import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol";
import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol";
import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol";
import { IMIPS } from "src/cannon/interfaces/IMIPS.sol";
/// @title DeployDisputeGameInput
contract DeployDisputeGameInput is BaseDeployIO {
// Common inputs.
string internal _release;
string internal _standardVersionsToml;
// Specify which MIPS version to use.
uint256 internal _mipsVersion;
// All inputs required to deploy PreimageOracle.
uint256 internal _minProposalSizeBytes;
uint256 internal _challengePeriodSeconds;
// Specify which game kind is being deployed here.
string internal _gameKind;
// All inputs required to deploy FaultDisputeGame.
uint256 internal _gameType;
bytes32 internal _absolutePrestate;
uint256 internal _maxGameDepth;
uint256 internal _splitDepth;
uint256 internal _clockExtension;
uint256 internal _maxClockDuration;
IDelayedWETH internal _delayedWethProxy;
IAnchorStateRegistry internal _anchorStateRegistryProxy;
uint256 internal _l2ChainId;
// Additional inputs required to deploy PermissionedDisputeGame.
address internal _proposer;
address internal _challenger;
function set(bytes4 _sel, uint256 _value) public {
if (_sel == this.mipsVersion.selector) {
require(_value == 1 || _value == 2, "DeployDisputeGame: unknown mips version");
_mipsVersion = _value;
} else if (_sel == this.minProposalSizeBytes.selector) {
require(_value != 0, "DeployDisputeGame: minProposalSizeBytes cannot be zero");
_minProposalSizeBytes = _value;
} else if (_sel == this.challengePeriodSeconds.selector) {
require(_value != 0, "DeployDisputeGame: challengePeriodSeconds cannot be zero");
_challengePeriodSeconds = _value;
} else if (_sel == this.gameType.selector) {
require(_value <= type(uint32).max, "DeployDisputeGame: gameType must fit inside uint32");
_gameType = _value;
} else if (_sel == this.maxGameDepth.selector) {
require(_value != 0, "DeployDisputeGame: maxGameDepth cannot be zero");
_maxGameDepth = _value;
} else if (_sel == this.splitDepth.selector) {
require(_value != 0, "DeployDisputeGame: splitDepth cannot be zero");
_splitDepth = _value;
} else if (_sel == this.clockExtension.selector) {
require(_value <= type(uint64).max, "DeployDisputeGame: clockExtension must fit inside uint64");
require(_value != 0, "DeployDisputeGame: clockExtension cannot be zero");
_clockExtension = _value;
} else if (_sel == this.maxClockDuration.selector) {
require(_value <= type(uint64).max, "DeployDisputeGame: maxClockDuration must fit inside uint64");
require(_value != 0, "DeployDisputeGame: maxClockDuration cannot be zero");
_maxClockDuration = _value;
} else if (_sel == this.l2ChainId.selector) {
require(_value != 0, "DeployDisputeGame: l2ChainId cannot be zero");
_l2ChainId = _value;
} else {
revert("DeployDisputeGame: unknown selector");
}
}
function set(bytes4 _sel, address _value) public {
if (_sel == this.delayedWethProxy.selector) {
require(_value != address(0), "DeployDisputeGame: delayedWethProxy cannot be zero address");
_delayedWethProxy = IDelayedWETH(payable(_value));
} else if (_sel == this.anchorStateRegistryProxy.selector) {
require(_value != address(0), "DeployDisputeGame: anchorStateRegistryProxy cannot be zero address");
_anchorStateRegistryProxy = IAnchorStateRegistry(payable(_value));
} else if (_sel == this.proposer.selector) {
require(_value != address(0), "DeployDisputeGame: proposer cannot be zero address");
_proposer = _value;
} else if (_sel == this.challenger.selector) {
require(_value != address(0), "DeployDisputeGame: challenger cannot be zero address");
_challenger = _value;
} else {
revert("DeployDisputeGame: unknown selector");
}
}
function set(bytes4 _sel, string memory _value) public {
if (_sel == this.gameKind.selector) {
require(
LibString.eq(_value, "FaultDisputeGame") || LibString.eq(_value, "PermissionedDisputeGame"),
"DeployDisputeGame: unknown game kind"
);
_gameKind = _value;
} else if (_sel == this.release.selector) {
require(!LibString.eq(_value, ""), "DeployDisputeGame: release cannot be empty");
_release = _value;
} else if (_sel == this.standardVersionsToml.selector) {
require(!LibString.eq(_value, ""), "DeployDisputeGame: standardVersionsToml cannot be empty");
_standardVersionsToml = _value;
} else {
revert("DeployDisputeGame: unknown selector");
}
}
function release() public view returns (string memory) {
require(!LibString.eq(_release, ""), "DeployDisputeGame: release not set");
return _release;
}
function standardVersionsToml() public view returns (string memory) {
require(!LibString.eq(_standardVersionsToml, ""), "DeployDisputeGame: standardVersionsToml not set");
return _standardVersionsToml;
}
function mipsVersion() public view returns (uint256) {
require(_mipsVersion != 0, "DeployDisputeGame: mipsVersion not set");
require(_mipsVersion == 1 || _mipsVersion == 2, "DeployDisputeGame: unknown mips version");
return _mipsVersion;
}
function minProposalSizeBytes() public view returns (uint256) {
require(_minProposalSizeBytes != 0, "DeployDisputeGame: minProposalSizeBytes not set");
return _minProposalSizeBytes;
}
function challengePeriodSeconds() public view returns (uint256) {
require(_challengePeriodSeconds != 0, "DeployDisputeGame: challengePeriodSeconds not set");
return _challengePeriodSeconds;
}
function gameKind() public view returns (string memory) {
require(
LibString.eq(_gameKind, "FaultDisputeGame") || LibString.eq(_gameKind, "PermissionedDisputeGame"),
"DeployDisputeGame: unknown game kind"
);
return _gameKind;
}
function gameType() public view returns (uint256) {
require(_gameType <= type(uint32).max, "DeployDisputeGame: gameType must fit inside uint32");
return _gameType;
}
function absolutePrestate() public view returns (bytes32) {
require(_absolutePrestate != bytes32(0), "DeployDisputeGame: absolutePrestate not set");
return _absolutePrestate;
}
function maxGameDepth() public view returns (uint256) {
require(_maxGameDepth != 0, "DeployDisputeGame: maxGameDepth not set");
return _maxGameDepth;
}
function splitDepth() public view returns (uint256) {
require(_splitDepth != 0, "DeployDisputeGame: splitDepth not set");
return _splitDepth;
}
function clockExtension() public view returns (uint256) {
require(_clockExtension <= type(uint64).max, "DeployDisputeGame: clockExtension must fit inside uint64");
require(_clockExtension != 0, "DeployDisputeGame: clockExtension not set");
return _clockExtension;
}
function maxClockDuration() public view returns (uint256) {
require(_maxClockDuration <= type(uint64).max, "DeployDisputeGame: maxClockDuration must fit inside uint64");
require(_maxClockDuration != 0, "DeployDisputeGame: maxClockDuration not set");
return _maxClockDuration;
}
function delayedWethProxy() public view returns (IDelayedWETH) {
require(address(_delayedWethProxy) != address(0), "DeployDisputeGame: delayedWethProxy not set");
return _delayedWethProxy;
}
function anchorStateRegistryProxy() public view returns (IAnchorStateRegistry) {
require(address(_anchorStateRegistryProxy) != address(0), "DeployDisputeGame: anchorStateRegistryProxy not set");
return _anchorStateRegistryProxy;
}
function l2ChainId() public view returns (uint256) {
require(_l2ChainId != 0, "DeployDisputeGame: l2ChainId not set");
return _l2ChainId;
}
function proposer() public view returns (address) {
if (LibString.eq(_gameKind, "FaultDisputeGame")) {
require(_proposer == address(0), "DeployDisputeGame: proposer must be empty");
} else {
require(_proposer != address(0), "DeployDisputeGame: proposer not set");
}
return _proposer;
}
function challenger() public view returns (address) {
if (LibString.eq(_gameKind, "FaultDisputeGame")) {
require(_challenger == address(0), "DeployDisputeGame: challenger must be empty");
} else {
require(_challenger != address(0), "DeployDisputeGame: challenger not set");
}
return _challenger;
}
}
/// @title DeployDisputeGameOutput
contract DeployDisputeGameOutput is BaseDeployIO {
// PermissionedDisputeGame is used as the type here because it has all of the same functions as
// FaultDisputeGame but with the added proposer and challenger fields.
IPermissionedDisputeGame internal _disputeGameImpl;
IMIPS internal _mipsSingleton;
IPreimageOracle internal _preimageOracleSingleton;
function set(bytes4 _sel, address _value) public {
if (_sel == this.disputeGameImpl.selector) {
require(_value != address(0), "DeployDisputeGame: disputeGameImpl cannot be zero address");
_disputeGameImpl = IPermissionedDisputeGame(_value);
} else if (_sel == this.mipsSingleton.selector) {
require(_value != address(0), "DeployDisputeGame: mipsSingleton cannot be zero address");
_mipsSingleton = IMIPS(_value);
} else if (_sel == this.preimageOracleSingleton.selector) {
require(_value != address(0), "DeployDisputeGame: preimageOracleSingleton cannot be zero address");
_preimageOracleSingleton = IPreimageOracle(_value);
} else {
revert("DeployDisputeGame: unknown selector");
}
}
function checkOutput(DeployDisputeGameInput _dgi) public view {
DeployUtils.assertValidContractAddress(address(_preimageOracleSingleton));
DeployUtils.assertValidContractAddress(address(_mipsSingleton));
DeployUtils.assertValidContractAddress(address(_disputeGameImpl));
assertValidDeploy(_dgi);
}
function preimageOracleSingleton() public view returns (IPreimageOracle) {
DeployUtils.assertValidContractAddress(address(_preimageOracleSingleton));
return _preimageOracleSingleton;
}
function mipsSingleton() public view returns (IMIPS) {
DeployUtils.assertValidContractAddress(address(_mipsSingleton));
return _mipsSingleton;
}
function disputeGameImpl() public view returns (IPermissionedDisputeGame) {
DeployUtils.assertValidContractAddress(address(_disputeGameImpl));
return _disputeGameImpl;
}
function assertValidDeploy(DeployDisputeGameInput _dgi) public view {
assertValidPreimageOracleSingleton(_dgi);
assertValidMipsSingleton(_dgi);
assertValidDisputeGameImpl(_dgi);
}
function assertValidPreimageOracleSingleton(DeployDisputeGameInput _dgi) internal view {
IPreimageOracle oracle = preimageOracleSingleton();
require(oracle.minProposalSize() == _dgi.minProposalSizeBytes(), "PO-10");
require(oracle.challengePeriod() == _dgi.challengePeriodSeconds(), "PO-20");
}
function assertValidMipsSingleton(DeployDisputeGameInput) internal view {
IMIPS mips = mipsSingleton();
require(address(mips.oracle()) == address(preimageOracleSingleton()), "MIPS-10");
}
function assertValidDisputeGameImpl(DeployDisputeGameInput _dgi) internal view {
IPermissionedDisputeGame game = disputeGameImpl();
require(game.gameType().raw() == uint32(_dgi.gameType()), "DG-10");
require(game.maxGameDepth() == _dgi.maxGameDepth(), "DG-20");
require(game.splitDepth() == _dgi.splitDepth(), "DG-30");
require(game.clockExtension().raw() == uint64(_dgi.clockExtension()), "DG-40");
require(game.maxClockDuration().raw() == uint64(_dgi.maxClockDuration()), "DG-50");
require(game.vm() == IBigStepper(address(mipsSingleton())), "DG-60");
require(game.weth() == _dgi.delayedWethProxy(), "DG-70");
require(game.anchorStateRegistry() == _dgi.anchorStateRegistryProxy(), "DG-80");
require(game.l2ChainId() == _dgi.l2ChainId(), "DG-90");
if (LibString.eq(_dgi.gameKind(), "PermissionedDisputeGame")) {
require(game.proposer() == _dgi.proposer(), "DG-100");
require(game.challenger() == _dgi.challenger(), "DG-110");
}
}
}
/// @title DeployDisputeGame
contract DeployDisputeGame is Script {
/// We need a struct for constructor args to avoid stack-too-deep errors.
struct DisputeGameConstructorArgs {
GameType gameType;
Claim absolutePrestate;
uint256 maxGameDepth;
uint256 splitDepth;
Duration clockExtension;
Duration maxClockDuration;
IBigStepper gameVm;
IDelayedWETH delayedWethProxy;
IAnchorStateRegistry anchorStateRegistryProxy;
uint256 l2ChainId;
address proposer;
address challenger;
}
function run(DeployDisputeGameInput _dgi, DeployDisputeGameOutput _dgo) public {
deployPreimageOracleSingleton(_dgi, _dgo);
deployMipsSingleton(_dgi, _dgo);
deployDisputeGameImpl(_dgi, _dgo);
_dgo.checkOutput(_dgi);
}
function deployPreimageOracleSingleton(DeployDisputeGameInput _dgi, DeployDisputeGameOutput _dgo) internal {
string memory release = _dgi.release();
string memory stdVerToml = _dgi.standardVersionsToml();
string memory contractName = "preimage_oracle";
IPreimageOracle singleton;
address existingImplementation = getReleaseAddress(release, contractName, stdVerToml);
if (existingImplementation != address(0)) {
singleton = IPreimageOracle(payable(existingImplementation));
} else if (isDevelopRelease(release)) {
uint256 minProposalSizeBytes = _dgi.minProposalSizeBytes();
uint256 challengePeriodSeconds = _dgi.challengePeriodSeconds();
vm.broadcast(msg.sender);
singleton = IPreimageOracle(
DeployUtils.create1({
_name: "PreimageOracle",
_args: DeployUtils.encodeConstructor(
abi.encodeCall(IPreimageOracle.__constructor__, (minProposalSizeBytes, challengePeriodSeconds))
)
})
);
} else {
revert(string.concat("DeployImplementations: failed to deploy release ", release));
}
vm.label(address(singleton), "PreimageOracleSingleton");
_dgo.set(_dgo.preimageOracleSingleton.selector, address(singleton));
}
function deployMipsSingleton(DeployDisputeGameInput _dgi, DeployDisputeGameOutput _dgo) internal {
string memory release = _dgi.release();
string memory stdVerToml = _dgi.standardVersionsToml();
string memory contractName = "mips";
IMIPS singleton;
address existingImplementation = getReleaseAddress(release, contractName, stdVerToml);
if (existingImplementation != address(0)) {
singleton = IMIPS(payable(existingImplementation));
} else if (isDevelopRelease(release)) {
uint256 mipsVersion = _dgi.mipsVersion();
IPreimageOracle preimageOracle = IPreimageOracle(address(_dgo.preimageOracleSingleton()));
vm.broadcast(msg.sender);
singleton = IMIPS(
DeployUtils.create1({
_name: mipsVersion == 1 ? "MIPS" : "MIPS2",
_args: DeployUtils.encodeConstructor(abi.encodeCall(IMIPS.__constructor__, (preimageOracle)))
})
);
} else {
revert(string.concat("DeployImplementations: failed to deploy release ", release));
}
vm.label(address(singleton), "MIPSSingleton");
_dgo.set(_dgo.mipsSingleton.selector, address(singleton));
}
function deployDisputeGameImpl(DeployDisputeGameInput _dgi, DeployDisputeGameOutput _dgo) internal {
// Shove the arguments into a struct to avoid stack-too-deep errors.
DisputeGameConstructorArgs memory args = DisputeGameConstructorArgs({
gameType: GameType.wrap(uint32(_dgi.gameType())),
absolutePrestate: Claim.wrap(_dgi.absolutePrestate()),
maxGameDepth: _dgi.maxGameDepth(),
splitDepth: _dgi.splitDepth(),
clockExtension: Duration.wrap(uint64(_dgi.clockExtension())),
maxClockDuration: Duration.wrap(uint64(_dgi.maxClockDuration())),
gameVm: IBigStepper(address(_dgo.mipsSingleton())),
delayedWethProxy: _dgi.delayedWethProxy(),
anchorStateRegistryProxy: _dgi.anchorStateRegistryProxy(),
l2ChainId: _dgi.l2ChainId(),
proposer: _dgi.proposer(),
challenger: _dgi.challenger()
});
// PermissionedDisputeGame is used as the type here because it is a superset of
// FaultDisputeGame. If the user requests to deploy a FaultDisputeGame, the user will get a
// FaultDisputeGame (and not a PermissionedDisputeGame).
vm.broadcast(msg.sender);
IPermissionedDisputeGame impl;
if (LibString.eq(_dgi.gameKind(), "FaultDisputeGame")) {
impl = IPermissionedDisputeGame(
DeployUtils.create1({
_name: "FaultDisputeGame",
_args: DeployUtils.encodeConstructor(
abi.encodeCall(
IFaultDisputeGame.__constructor__,
(
args.gameType,
args.absolutePrestate,
args.maxGameDepth,
args.splitDepth,
args.clockExtension,
args.maxClockDuration,
args.gameVm,
args.delayedWethProxy,
args.anchorStateRegistryProxy,
args.l2ChainId
)
)
)
})
);
} else {
impl = IPermissionedDisputeGame(
DeployUtils.create1({
_name: "PermissionedDisputeGame",
_args: DeployUtils.encodeConstructor(
abi.encodeCall(
IPermissionedDisputeGame.__constructor__,
(
args.gameType,
args.absolutePrestate,
args.maxGameDepth,
args.splitDepth,
args.clockExtension,
args.maxClockDuration,
args.gameVm,
args.delayedWethProxy,
args.anchorStateRegistryProxy,
args.l2ChainId,
args.proposer,
args.challenger
)
)
)
})
);
}
vm.label(address(impl), string.concat(_dgi.gameKind(), "Impl"));
_dgo.set(_dgo.disputeGameImpl.selector, address(impl));
}
// Zero address is returned if the address is not found in '_standardVersionsToml'.
function getReleaseAddress(
string memory _version,
string memory _contractName,
string memory _standardVersionsToml
)
internal
pure
returns (address addr_)
{
string memory baseKey = string.concat('.releases["', _version, '"].', _contractName);
string memory implAddressKey = string.concat(baseKey, ".implementation_address");
string memory addressKey = string.concat(baseKey, ".address");
try vm.parseTomlAddress(_standardVersionsToml, implAddressKey) returns (address parsedAddr_) {
addr_ = parsedAddr_;
} catch {
try vm.parseTomlAddress(_standardVersionsToml, addressKey) returns (address parsedAddr_) {
addr_ = parsedAddr_;
} catch {
addr_ = address(0);
}
}
}
// A release is considered a 'develop' release if it does not start with 'op-contracts'.
function isDevelopRelease(string memory _release) internal pure returns (bool) {
return !LibString.startsWith(_release, "op-contracts");
}
}
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