diff --git a/op-deployer/pkg/deployer/bootstrap/dispute_game.go b/op-deployer/pkg/deployer/bootstrap/dispute_game.go index 65ca4a731124..2b17a35be7bb 100644 --- a/op-deployer/pkg/deployer/bootstrap/dispute_game.go +++ b/op-deployer/pkg/deployer/bootstrap/dispute_game.go @@ -38,7 +38,7 @@ type DisputeGameConfig struct { MinProposalSizeBytes uint64 ChallengePeriodSeconds uint64 - MipsVersion uint8 + MipsVersion uint64 GameKind string GameType uint32 AbsolutePrestate common.Hash diff --git a/op-deployer/pkg/deployer/bootstrap/flags.go b/op-deployer/pkg/deployer/bootstrap/flags.go index a7b80daf6e7f..73f99e7b4325 100644 --- a/op-deployer/pkg/deployer/bootstrap/flags.go +++ b/op-deployer/pkg/deployer/bootstrap/flags.go @@ -28,6 +28,7 @@ const ( L2ChainIdFlagName = "l2-chain-id" ProposerFlagName = "proposer" ChallengerFlagName = "challenger" + PreimageOracleFlagName = "preimage-oracle" ) var ( @@ -140,6 +141,12 @@ var ( EnvVars: deployer.PrefixEnvVar("CHALLENGER"), Value: common.Address{}.Hex(), } + PreimageOracleFlag = &cli.StringFlag{ + Name: PreimageOracleFlagName, + Usage: "Preimage oracle address.", + EnvVars: deployer.PrefixEnvVar("PREIMAGE_ORACLE"), + Value: common.Address{}.Hex(), + } ) var OPCMFlags = []cli.Flag{ @@ -181,6 +188,14 @@ var DisputeGameFlags = []cli.Flag{ ChallengerFlag, } +var MIPSFlags = []cli.Flag{ + deployer.L1RPCURLFlag, + deployer.PrivateKeyFlag, + ArtifactsLocatorFlag, + PreimageOracleFlag, + MIPSVersionFlag, +} + var Commands = []*cli.Command{ { Name: "opcm", @@ -200,4 +215,10 @@ var Commands = []*cli.Command{ Flags: cliapp.ProtectFlags(DisputeGameFlags), Action: DisputeGameCLI, }, + { + Name: "mips", + Usage: "Bootstrap an instance of MIPS.", + Flags: cliapp.ProtectFlags(MIPSFlags), + Action: MIPSCLI, + }, } diff --git a/op-deployer/pkg/deployer/bootstrap/mips.go b/op-deployer/pkg/deployer/bootstrap/mips.go new file mode 100644 index 000000000000..42abae208eb5 --- /dev/null +++ b/op-deployer/pkg/deployer/bootstrap/mips.go @@ -0,0 +1,196 @@ +package bootstrap + +import ( + "context" + "crypto/ecdsa" + "fmt" + "strings" + + artifacts2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + + "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/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/urfave/cli/v2" +) + +type MIPSConfig struct { + L1RPCUrl string + PrivateKey string + Logger log.Logger + ArtifactsLocator *artifacts2.Locator + + privateKeyECDSA *ecdsa.PrivateKey + + PreimageOracle common.Address + MipsVersion uint64 +} + +func (c *MIPSConfig) 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") + } + + if c.PreimageOracle == (common.Address{}) { + return fmt.Errorf("preimage oracle must be specified") + } + + if c.MipsVersion == 0 { + return fmt.Errorf("mips version must be specified") + } + if c.MipsVersion != 1 && c.MipsVersion != 2 { + return fmt.Errorf("mips version must be either 1 or 2") + } + + return nil +} + +func MIPSCLI(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) + } + + mipsVersion := cliCtx.Uint64(MIPSVersionFlagName) + preimageOracle := common.HexToAddress(cliCtx.String(PreimageOracleFlagName)) + + ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) + + return MIPS(ctx, MIPSConfig{ + L1RPCUrl: l1RPCUrl, + PrivateKey: privateKey, + Logger: l, + ArtifactsLocator: artifactsLocator, + MipsVersion: mipsVersion, + PreimageOracle: preimageOracle, + }) +} + +func MIPS(ctx context.Context, cfg MIPSConfig) error { + if err := cfg.Check(); err != nil { + return fmt.Errorf("invalid config for MIPS: %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) + } + + 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.DeployMIPS( + host, + opcm.DeployMIPSInput{ + MipsVersion: cfg.MipsVersion, + PreimageOracle: cfg.PreimageOracle, + }, + ) + 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 +} diff --git a/op-deployer/pkg/deployer/opcm/dispute_game.go b/op-deployer/pkg/deployer/opcm/dispute_game.go index 001987afaeed..0e117c040d83 100644 --- a/op-deployer/pkg/deployer/opcm/dispute_game.go +++ b/op-deployer/pkg/deployer/opcm/dispute_game.go @@ -11,7 +11,7 @@ import ( type DeployDisputeGameInput struct { Release string StandardVersionsToml string - MipsVersion uint8 + MipsVersion uint64 MinProposalSizeBytes uint64 ChallengePeriodSeconds uint64 GameKind string diff --git a/op-deployer/pkg/deployer/opcm/mips.go b/op-deployer/pkg/deployer/opcm/mips.go new file mode 100644 index 000000000000..5d1a7798cedb --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/mips.go @@ -0,0 +1,65 @@ +package opcm + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-chain-ops/script" +) + +type DeployMIPSInput struct { + MipsVersion uint64 + PreimageOracle common.Address +} + +func (input *DeployMIPSInput) InputSet() bool { + return true +} + +type DeployMIPSOutput struct { + MipsSingleton common.Address +} + +func (output *DeployMIPSOutput) CheckOutput(input common.Address) error { + return nil +} + +type DeployMIPSScript struct { + Run func(input, output common.Address) error +} + +func DeployMIPS( + host *script.Host, + input DeployMIPSInput, +) (DeployMIPSOutput, error) { + var output DeployMIPSOutput + inputAddr := host.NewScriptAddress() + outputAddr := host.NewScriptAddress() + + cleanupInput, err := script.WithPrecompileAtAddress[*DeployMIPSInput](host, inputAddr, &input) + if err != nil { + return output, fmt.Errorf("failed to insert DeployMIPSInput precompile: %w", err) + } + defer cleanupInput() + + cleanupOutput, err := script.WithPrecompileAtAddress[*DeployMIPSOutput](host, outputAddr, &output, + script.WithFieldSetter[*DeployMIPSOutput]) + if err != nil { + return output, fmt.Errorf("failed to insert DeployMIPSOutput precompile: %w", err) + } + defer cleanupOutput() + + implContract := "DeployMIPS" + deployScript, cleanupDeploy, err := script.WithScript[DeployMIPSScript](host, "DeployMIPS.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 +} diff --git a/op-deployer/pkg/deployer/opcm/mips_test.go b/op-deployer/pkg/deployer/opcm/mips_test.go new file mode 100644 index 000000000000..04f8d93ae9bd --- /dev/null +++ b/op-deployer/pkg/deployer/opcm/mips_test.go @@ -0,0 +1,36 @@ +package opcm + +import ( + "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 TestDeployMIPS(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 := DeployMIPSInput{ + MipsVersion: 1, + PreimageOracle: common.Address{0xab}, + } + + output, err := DeployMIPS(host, input) + require.NoError(t, err) + + require.NotEmpty(t, output.MipsSingleton) +} diff --git a/packages/contracts-bedrock/scripts/deploy/DeployMIPS.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployMIPS.s.sol new file mode 100644 index 000000000000..5890630c9c22 --- /dev/null +++ b/packages/contracts-bedrock/scripts/deploy/DeployMIPS.s.sol @@ -0,0 +1,109 @@ +// 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"; + +// Interfaces +import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; +import { IMIPS } from "src/cannon/interfaces/IMIPS.sol"; + +/// @title DeployMIPSInput +contract DeployMIPSInput is BaseDeployIO { + // Specify the PreimageOracle to use + address internal _preimageOracle; + + // Specify which MIPS version to use. + uint256 internal _mipsVersion; + + function set(bytes4 _sel, uint256 _value) public { + if (_sel == this.mipsVersion.selector) { + require(_value == 1 || _value == 2, "DeployMIPS: unknown mips version"); + _mipsVersion = _value; + } else { + revert("DeployMIPS: unknown selector"); + } + } + + function set(bytes4 _sel, address _value) public { + if (_sel == this.preimageOracle.selector) { + require(_value != address(0), "DeployMIPS: preimageOracle cannot be empty"); + _preimageOracle = _value; + } else { + revert("DeployMIPS: unknown selector"); + } + } + + function mipsVersion() public view returns (uint256) { + require(_mipsVersion != 0, "DeployMIPS: mipsVersion not set"); + require(_mipsVersion == 1 || _mipsVersion == 2, "DeployMIPS: unknown mips version"); + return _mipsVersion; + } + + function preimageOracle() public view returns (address) { + require(_preimageOracle != address(0), "DeployMIPS: preimageOracle not set"); + return _preimageOracle; + } +} + +/// @title DeployMIPSOutput +contract DeployMIPSOutput is BaseDeployIO { + IMIPS internal _mipsSingleton; + + function set(bytes4 _sel, address _value) public { + if (_sel == this.mipsSingleton.selector) { + require(_value != address(0), "DeployMIPS: mipsSingleton cannot be zero address"); + _mipsSingleton = IMIPS(_value); + } else { + revert("DeployMIPS: unknown selector"); + } + } + + function checkOutput(DeployMIPSInput _mi) public view { + DeployUtils.assertValidContractAddress(address(_mipsSingleton)); + assertValidDeploy(_mi); + } + + function mipsSingleton() public view returns (IMIPS) { + DeployUtils.assertValidContractAddress(address(_mipsSingleton)); + return _mipsSingleton; + } + + function assertValidDeploy(DeployMIPSInput _mi) public view { + assertValidMipsSingleton(_mi); + } + + function assertValidMipsSingleton(DeployMIPSInput _mi) internal view { + IMIPS mips = mipsSingleton(); + + require(address(mips.oracle()) == address(_mi.preimageOracle()), "MIPS-10"); + } +} + +/// @title DeployMIPS +contract DeployMIPS is Script { + function run(DeployMIPSInput _mi, DeployMIPSOutput _mo) public { + deployMipsSingleton(_mi, _mo); + _mo.checkOutput(_mi); + } + + function deployMipsSingleton(DeployMIPSInput _mi, DeployMIPSOutput _mo) internal { + IMIPS singleton; + uint256 mipsVersion = _mi.mipsVersion(); + IPreimageOracle preimageOracle = IPreimageOracle(_mi.preimageOracle()); + vm.broadcast(msg.sender); + singleton = IMIPS( + DeployUtils.create1({ + _name: mipsVersion == 1 ? "MIPS" : "MIPS2", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IMIPS.__constructor__, (preimageOracle))) + }) + ); + + vm.label(address(singleton), "MIPSSingleton"); + _mo.set(_mo.mipsSingleton.selector, address(singleton)); + } +}