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 { ...@@ -188,7 +188,7 @@ func DelayedWETH(ctx context.Context, cfg DelayedWETHConfig) error {
}, },
) )
if err != nil { 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 { 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 ( ...@@ -4,6 +4,7 @@ import (
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" "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-deployer/pkg/deployer/standard"
"github.com/ethereum-optimism/optimism/op-service/cliapp" "github.com/ethereum-optimism/optimism/op-service/cliapp"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
...@@ -15,6 +16,18 @@ const ( ...@@ -15,6 +16,18 @@ const (
ProofMaturityDelaySecondsFlagName = "proof-maturity-delay-seconds" ProofMaturityDelaySecondsFlagName = "proof-maturity-delay-seconds"
DisputeGameFinalityDelaySecondsFlagName = "dispute-game-finality-delay-seconds" DisputeGameFinalityDelaySecondsFlagName = "dispute-game-finality-delay-seconds"
MIPSVersionFlagName = "mips-version" 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 ( var (
...@@ -59,6 +72,74 @@ var ( ...@@ -59,6 +72,74 @@ var (
EnvVars: deployer.PrefixEnvVar("MIPS_VERSION"), EnvVars: deployer.PrefixEnvVar("MIPS_VERSION"),
Value: standard.MIPSVersion, 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{ var OPCMFlags = []cli.Flag{
...@@ -79,6 +160,27 @@ var DelayedWETHFlags = []cli.Flag{ ...@@ -79,6 +160,27 @@ var DelayedWETHFlags = []cli.Flag{
ArtifactsLocatorFlag, 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{ var Commands = []*cli.Command{
{ {
Name: "opcm", Name: "opcm",
...@@ -92,4 +194,10 @@ var Commands = []*cli.Command{ ...@@ -92,4 +194,10 @@ var Commands = []*cli.Command{
Flags: cliapp.ProtectFlags(DelayedWETHFlags), Flags: cliapp.ProtectFlags(DelayedWETHFlags),
Action: DelayedWETHCLI, 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)
}
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