Commit af169dba authored by clabby's avatar clabby Committed by GitHub

feat(op-deployer): `asterisc` bootstrap CLI (#13113)

* feat(op-deployer): `asterisc` bootstrap CLI

* rename file

* flags

* mslipper review

* golint

---------
Co-authored-by: default avatarMatthew Slipper <me@matthewslipper.com>
parent cf13a17a
package bootstrap
import (
"context"
"crypto/ecdsa"
"fmt"
"strings"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
"github.com/ethereum-optimism/optimism/op-chain-ops/script/forking"
"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/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
)
type AsteriscConfig struct {
L1RPCUrl string
PrivateKey string
Logger log.Logger
ArtifactsLocator *artifacts.Locator
privateKeyECDSA *ecdsa.PrivateKey
PreimageOracle common.Address
}
func (c *AsteriscConfig) 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")
}
return nil
}
func AsteriscCLI(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(artifacts.Locator)
if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil {
return fmt.Errorf("failed to parse artifacts URL: %w", err)
}
preimageOracle := common.HexToAddress(cliCtx.String(PreimageOracleFlagName))
ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context)
return Asterisc(ctx, AsteriscConfig{
L1RPCUrl: l1RPCUrl,
PrivateKey: privateKey,
Logger: l,
ArtifactsLocator: artifactsLocator,
PreimageOracle: preimageOracle,
})
}
func Asterisc(ctx context.Context, cfg AsteriscConfig) error {
if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid config for Asterisc: %w", err)
}
lgr := cfg.Logger
progressor := func(curr, total int64) {
lgr.Info("artifacts download progress", "current", curr, "total", total)
}
artifactsFS, cleanup, err := artifacts.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)
}
l1RPC, err := rpc.Dial(cfg.L1RPCUrl)
if err != nil {
return fmt.Errorf("failed to connect to L1 RPC: %w", err)
}
l1Host, err := env.DefaultScriptHost(
bcaster,
lgr,
chainDeployer,
artifactsFS,
script.WithForkHook(func(cfg *script.ForkConfig) (forking.ForkSource, error) {
src, err := forking.RPCSourceByNumber(cfg.URLOrAlias, l1RPC, *cfg.BlockNumber)
if err != nil {
return nil, fmt.Errorf("failed to create RPC fork source: %w", err)
}
return forking.Cache(src), nil
}),
)
if err != nil {
return fmt.Errorf("failed to create script host: %w", err)
}
dgo, err := opcm.DeployAsterisc(
l1Host,
opcm.DeployAsteriscInput{
PreimageOracle: cfg.PreimageOracle,
},
)
if err != nil {
return fmt.Errorf("error deploying asterisc VM: %w", err)
}
if _, err := bcaster.Broadcast(ctx); err != nil {
return fmt.Errorf("failed to broadcast: %w", err)
}
lgr.Info("deployed asterisc VM")
if err := jsonutil.WriteJSON(dgo, ioutil.ToStdOut()); err != nil {
return fmt.Errorf("failed to write output: %w", err)
}
return nil
}
...@@ -194,14 +194,17 @@ var DisputeGameFlags = []cli.Flag{ ...@@ -194,14 +194,17 @@ var DisputeGameFlags = []cli.Flag{
ChallengerFlag, ChallengerFlag,
} }
var MIPSFlags = []cli.Flag{ var BaseFPVMFlags = []cli.Flag{
deployer.L1RPCURLFlag, deployer.L1RPCURLFlag,
deployer.PrivateKeyFlag, deployer.PrivateKeyFlag,
ArtifactsLocatorFlag, ArtifactsLocatorFlag,
PreimageOracleFlag, PreimageOracleFlag,
MIPSVersionFlag,
} }
var MIPSFlags = append(BaseFPVMFlags, MIPSVersionFlag)
var AsteriscFlags = BaseFPVMFlags
var Commands = []*cli.Command{ var Commands = []*cli.Command{
{ {
Name: "opcm", Name: "opcm",
...@@ -227,4 +230,10 @@ var Commands = []*cli.Command{ ...@@ -227,4 +230,10 @@ var Commands = []*cli.Command{
Flags: cliapp.ProtectFlags(MIPSFlags), Flags: cliapp.ProtectFlags(MIPSFlags),
Action: MIPSCLI, Action: MIPSCLI,
}, },
{
Name: "asterisc",
Usage: "Bootstrap an instance of Asterisc.",
Flags: cliapp.ProtectFlags(AsteriscFlags),
Action: AsteriscCLI,
},
} }
package opcm
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
)
type DeployAsteriscInput struct {
PreimageOracle common.Address
}
func (input *DeployAsteriscInput) InputSet() bool {
return true
}
type DeployAsteriscOutput struct {
AsteriscSingleton common.Address
}
func (output *DeployAsteriscOutput) CheckOutput(input common.Address) error {
return nil
}
type DeployAsteriscScript struct {
Run func(input, output common.Address) error
}
func DeployAsterisc(
host *script.Host,
input DeployAsteriscInput,
) (DeployAsteriscOutput, error) {
var output DeployAsteriscOutput
inputAddr := host.NewScriptAddress()
outputAddr := host.NewScriptAddress()
cleanupInput, err := script.WithPrecompileAtAddress[*DeployAsteriscInput](host, inputAddr, &input)
if err != nil {
return output, fmt.Errorf("failed to insert DeployAsteriscInput precompile: %w", err)
}
defer cleanupInput()
cleanupOutput, err := script.WithPrecompileAtAddress[*DeployAsteriscOutput](host, outputAddr, &output,
script.WithFieldSetter[*DeployAsteriscOutput])
if err != nil {
return output, fmt.Errorf("failed to insert DeployAsteriscOutput precompile: %w", err)
}
defer cleanupOutput()
implContract := "DeployAsterisc"
deployScript, cleanupDeploy, err := script.WithScript[DeployAsteriscScript](host, "DeployAsterisc.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/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 TestDeployAsterisc(t *testing.T) {
_, artifacts := testutil.LocalArtifacts(t)
host, err := env.DefaultScriptHost(
broadcaster.NoopBroadcaster(),
testlog.Logger(t, log.LevelInfo),
common.Address{'D'},
artifacts,
)
require.NoError(t, err)
input := DeployAsteriscInput{
PreimageOracle: common.Address{0xab},
}
output, err := DeployAsterisc(host, input)
require.NoError(t, err)
require.NotEmpty(t, output.AsteriscSingleton)
}
// 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 { IRISCV } from "src/vendor/asterisc/interfaces/IRISCV.sol";
/// @title DeployAsteriscInput
contract DeployAsteriscInput is BaseDeployIO {
// Specify the PreimageOracle to use
address internal _preimageOracle;
function set(bytes4 _sel, address _value) public {
if (_sel == this.preimageOracle.selector) {
require(_value != address(0), "DeployAsterisc: preimageOracle cannot be empty");
_preimageOracle = _value;
} else {
revert("DeployAsterisc: unknown selector");
}
}
function preimageOracle() public view returns (address) {
require(_preimageOracle != address(0), "DeployAsterisc: preimageOracle not set");
return _preimageOracle;
}
}
/// @title DeployAsteriscOutput
contract DeployAsteriscOutput is BaseDeployIO {
IRISCV internal _asteriscSingleton;
function set(bytes4 _sel, address _value) public {
if (_sel == this.asteriscSingleton.selector) {
require(_value != address(0), "DeployAsterisc: asteriscSingleton cannot be zero address");
_asteriscSingleton = IRISCV(_value);
} else {
revert("DeployAsterisc: unknown selector");
}
}
function checkOutput(DeployAsteriscInput _mi) public view {
DeployUtils.assertValidContractAddress(address(_asteriscSingleton));
assertValidDeploy(_mi);
}
function asteriscSingleton() public view returns (IRISCV) {
DeployUtils.assertValidContractAddress(address(_asteriscSingleton));
return _asteriscSingleton;
}
function assertValidDeploy(DeployAsteriscInput _mi) public view {
assertValidAsteriscSingleton(_mi);
}
function assertValidAsteriscSingleton(DeployAsteriscInput _mi) internal view {
IRISCV asterisc = asteriscSingleton();
require(address(asterisc.oracle()) == address(_mi.preimageOracle()), "ASTERISC-10");
}
}
/// @title DeployAsterisc
contract DeployAsterisc is Script {
function run(DeployAsteriscInput _mi, DeployAsteriscOutput _mo) public {
DeployAsteriscSingleton(_mi, _mo);
_mo.checkOutput(_mi);
}
function DeployAsteriscSingleton(DeployAsteriscInput _mi, DeployAsteriscOutput _mo) internal {
IPreimageOracle preimageOracle = IPreimageOracle(_mi.preimageOracle());
vm.broadcast(msg.sender);
IRISCV singleton = IRISCV(
DeployUtils.create1({
_name: "RISCV",
_args: DeployUtils.encodeConstructor(abi.encodeCall(IRISCV.__constructor__, (preimageOracle)))
})
);
vm.label(address(singleton), "AsteriscSingleton");
_mo.set(_mo.asteriscSingleton.selector, address(singleton));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol";
/// @title IRISCV
/// @notice Interface for the RISCV contract.
interface IRISCV is ISemver {
function oracle() external view returns (IPreimageOracle);
function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32);
function __constructor__(IPreimageOracle _oracle) external;
}
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