Commit 06f1406e authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

Add support for pre-existing OPSM (#12099)

* Add support for pre-existing OPSM

This PR adds support for deploying OP Chains against an existing OPSM deployment. It adds a new `OPSMAddress` field to the intent to hold the address of the OPSM. When specified, the `ContractsRelease` field is ignored. In the future, this field will be pulled from the Superchain Registry.

Since the Go Forge tooling doesn't support forking yet, the deployment is performed using a raw call to `eth_sendRawTransaction`. Data about the Superchain deployments is pulled from the OPSM itself via `eth_call`. To expose the deployment output following the Superchain deployment, I updated the `Deployed` event to emit the ABI-encoded bytes of the `DeployOutput` struct to avoid stack-too-deep errors. This isn't ideal, but at least it gets me all of the fields I need in a log event without more invasive changes.

* chore: add version identifer to Deployed event

* chore: emit msg.sender in Deployed event

* Fix merge issues

* test: fix specs test

* semver-lock

* code review updates

---------
Co-authored-by: default avatarMatt Solomon <matt@mattsolomon.dev>
parent 36180d78
......@@ -162,7 +162,7 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er
)
}
results = append(results, outRes)
results[i] = outRes
}
return results, txErr.ErrorOrNil()
}
......
......@@ -65,9 +65,10 @@ func Init(cfg InitConfig) error {
}
intent := &state.Intent{
L1ChainID: cfg.L1ChainID,
UseFaultProofs: true,
FundDevAccounts: true,
L1ChainID: cfg.L1ChainID,
UseFaultProofs: true,
FundDevAccounts: true,
ContractsRelease: "dev",
}
l1ChainIDBig := intent.L1ChainIDBig()
......
......@@ -93,18 +93,104 @@ func TestEndToEndApply(t *testing.T) {
id := uint256.NewInt(1)
addrFor := func(key devkeys.Key) common.Address {
addr, err := dk.Address(key)
require.NoError(t, err)
return addr
}
deployerAddr, err := dk.Address(depKey)
require.NoError(t, err)
env := &pipeline.Env{
Workdir: t.TempDir(),
L1Client: l1Client,
Signer: signer,
Deployer: addrFor(depKey),
Deployer: deployerAddr,
Logger: lgr,
}
t.Run("initial chain", func(t *testing.T) {
intent, st := makeIntent(t, l1ChainID, artifactsURL, dk, id)
require.NoError(t, deployer.ApplyPipeline(
ctx,
env,
intent,
st,
))
addrs := []struct {
name string
addr common.Address
}{
{"SuperchainProxyAdmin", st.SuperchainDeployment.ProxyAdminAddress},
{"SuperchainConfigProxy", st.SuperchainDeployment.SuperchainConfigProxyAddress},
{"SuperchainConfigImpl", st.SuperchainDeployment.SuperchainConfigImplAddress},
{"ProtocolVersionsProxy", st.SuperchainDeployment.ProtocolVersionsProxyAddress},
{"ProtocolVersionsImpl", st.SuperchainDeployment.ProtocolVersionsImplAddress},
{"OpcmProxy", st.ImplementationsDeployment.OpcmProxyAddress},
{"DelayedWETHImpl", st.ImplementationsDeployment.DelayedWETHImplAddress},
{"OptimismPortalImpl", st.ImplementationsDeployment.OptimismPortalImplAddress},
{"PreimageOracleSingleton", st.ImplementationsDeployment.PreimageOracleSingletonAddress},
{"MipsSingleton", st.ImplementationsDeployment.MipsSingletonAddress},
{"SystemConfigImpl", st.ImplementationsDeployment.SystemConfigImplAddress},
{"L1CrossDomainMessengerImpl", st.ImplementationsDeployment.L1CrossDomainMessengerImplAddress},
{"L1ERC721BridgeImpl", st.ImplementationsDeployment.L1ERC721BridgeImplAddress},
{"L1StandardBridgeImpl", st.ImplementationsDeployment.L1StandardBridgeImplAddress},
{"OptimismMintableERC20FactoryImpl", st.ImplementationsDeployment.OptimismMintableERC20FactoryImplAddress},
{"DisputeGameFactoryImpl", st.ImplementationsDeployment.DisputeGameFactoryImplAddress},
}
for _, addr := range addrs {
t.Run(addr.name, func(t *testing.T) {
code, err := l1Client.CodeAt(ctx, addr.addr, nil)
require.NoError(t, err)
require.NotEmpty(t, code, "contracts %s at %s has no code", addr.name, addr.addr)
})
}
validateOPChainDeployment(t, ctx, l1Client, st)
})
t.Run("subsequent chain", func(t *testing.T) {
newID := uint256.NewInt(2)
intent, st := makeIntent(t, l1ChainID, artifactsURL, dk, newID)
env.Workdir = t.TempDir()
require.NoError(t, deployer.ApplyPipeline(
ctx,
env,
intent,
st,
))
addrs := []struct {
name string
addr common.Address
}{
{"SuperchainConfigProxy", st.SuperchainDeployment.SuperchainConfigProxyAddress},
{"ProtocolVersionsProxy", st.SuperchainDeployment.ProtocolVersionsProxyAddress},
{"OpcmProxy", st.ImplementationsDeployment.OpcmProxyAddress},
}
for _, addr := range addrs {
t.Run(addr.name, func(t *testing.T) {
code, err := l1Client.CodeAt(ctx, addr.addr, nil)
require.NoError(t, err)
require.NotEmpty(t, code, "contracts %s at %s has no code", addr.name, addr.addr)
})
}
validateOPChainDeployment(t, ctx, l1Client, st)
})
}
func makeIntent(
t *testing.T,
l1ChainID *big.Int,
artifactsURL *url.URL,
dk *devkeys.MnemonicDevKeys,
l2ChainID *uint256.Int,
) (*state.Intent, *state.State) {
addrFor := func(key devkeys.Key) common.Address {
addr, err := dk.Address(key)
require.NoError(t, err)
return addr
}
intent := &state.Intent{
L1ChainID: l1ChainID.Uint64(),
SuperchainRoles: state.SuperchainRoles{
......@@ -118,7 +204,7 @@ func TestEndToEndApply(t *testing.T) {
ContractsRelease: "dev",
Chains: []*state.ChainIntent{
{
ID: id.Bytes32(),
ID: l2ChainID.Bytes32(),
Roles: state.ChainRoles{
ProxyAdminOwner: addrFor(devkeys.L2ProxyAdminOwnerRole.Key(l1ChainID)),
SystemConfigOwner: addrFor(devkeys.SystemConfigOwner.Key(l1ChainID)),
......@@ -134,43 +220,10 @@ func TestEndToEndApply(t *testing.T) {
st := &state.State{
Version: 1,
}
return intent, st
}
require.NoError(t, deployer.ApplyPipeline(
ctx,
env,
intent,
st,
))
addrs := []struct {
name string
addr common.Address
}{
{"SuperchainProxyAdmin", st.SuperchainDeployment.ProxyAdminAddress},
{"SuperchainConfigProxy", st.SuperchainDeployment.SuperchainConfigProxyAddress},
{"SuperchainConfigImpl", st.SuperchainDeployment.SuperchainConfigImplAddress},
{"ProtocolVersionsProxy", st.SuperchainDeployment.ProtocolVersionsProxyAddress},
{"ProtocolVersionsImpl", st.SuperchainDeployment.ProtocolVersionsImplAddress},
{"OpcmProxy", st.ImplementationsDeployment.OpcmProxyAddress},
{"DelayedWETHImpl", st.ImplementationsDeployment.DelayedWETHImplAddress},
{"OptimismPortalImpl", st.ImplementationsDeployment.OptimismPortalImplAddress},
{"PreimageOracleSingleton", st.ImplementationsDeployment.PreimageOracleSingletonAddress},
{"MipsSingleton", st.ImplementationsDeployment.MipsSingletonAddress},
{"SystemConfigImpl", st.ImplementationsDeployment.SystemConfigImplAddress},
{"L1CrossDomainMessengerImpl", st.ImplementationsDeployment.L1CrossDomainMessengerImplAddress},
{"L1ERC721BridgeImpl", st.ImplementationsDeployment.L1ERC721BridgeImplAddress},
{"L1StandardBridgeImpl", st.ImplementationsDeployment.L1StandardBridgeImplAddress},
{"OptimismMintableERC20FactoryImpl", st.ImplementationsDeployment.OptimismMintableERC20FactoryImplAddress},
{"DisputeGameFactoryImpl", st.ImplementationsDeployment.DisputeGameFactoryImplAddress},
}
for _, addr := range addrs {
t.Run(addr.name, func(t *testing.T) {
code, err := l1Client.CodeAt(ctx, addr.addr, nil)
require.NoError(t, err)
require.NotEmpty(t, code, "contracts %s at %s has no code", addr.name, addr.addr)
})
}
func validateOPChainDeployment(t *testing.T, ctx context.Context, l1Client *ethclient.Client, st *state.State) {
for _, chainState := range st.Chains {
chainAddrs := []struct {
name string
......@@ -197,7 +250,7 @@ func TestEndToEndApply(t *testing.T) {
if addr.name == "FaultDisputeGameAddress" {
continue
}
t.Run(fmt.Sprintf("chain %s - %s", chainState.ID, addr.name), func(t *testing.T) {
t.Run(addr.name, func(t *testing.T) {
code, err := l1Client.CodeAt(ctx, addr.addr, nil)
require.NoError(t, err)
require.NotEmpty(t, code, "contracts %s at %s for chain %s has no code", addr.name, addr.addr, chainState.ID)
......
package opcm
import (
"bytes"
"context"
"fmt"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
type Contract struct {
addr common.Address
client *ethclient.Client
}
func NewContract(addr common.Address, client *ethclient.Client) *Contract {
return &Contract{addr: addr, client: client}
}
func (c *Contract) SuperchainConfig(ctx context.Context) (common.Address, error) {
return c.getAddress(ctx, "superchainConfig")
}
func (c *Contract) ProtocolVersions(ctx context.Context) (common.Address, error) {
return c.getAddress(ctx, "protocolVersions")
}
func (c *Contract) getAddress(ctx context.Context, name string) (common.Address, error) {
method := abi.NewMethod(
name,
name,
abi.Function,
"view",
true,
false,
abi.Arguments{},
abi.Arguments{
abi.Argument{
Name: "address",
Type: mustType("address"),
Indexed: false,
},
},
)
calldata, err := method.Inputs.Pack()
if err != nil {
return common.Address{}, fmt.Errorf("failed to pack inputs: %w", err)
}
msg := ethereum.CallMsg{
To: &c.addr,
Data: append(bytes.Clone(method.ID), calldata...),
}
result, err := c.client.CallContract(ctx, msg, nil)
if err != nil {
return common.Address{}, fmt.Errorf("failed to call contract: %w", err)
}
out, err := method.Outputs.Unpack(result)
if err != nil {
return common.Address{}, fmt.Errorf("failed to unpack result: %w", err)
}
if len(out) != 1 {
return common.Address{}, fmt.Errorf("unexpected output length: %d", len(out))
}
addr, ok := out[0].(common.Address)
if !ok {
return common.Address{}, fmt.Errorf("unexpected type: %T", out[0])
}
return addr, nil
}
func mustType(t string) abi.Type {
typ, err := abi.NewType(t, "", nil)
if err != nil {
panic(err)
}
return typ
}
package opcm
import (
"context"
"fmt"
"math/big"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/holiman/uint256"
)
// PermissionedGameStartingAnchorRoots is a root of bytes32(hex"dead") for the permissioned game at block 0,
......@@ -45,7 +52,6 @@ type DeployOPChainOutput struct {
OptimismMintableERC20FactoryProxy common.Address
L1StandardBridgeProxy common.Address
L1CrossDomainMessengerProxy common.Address
// Fault proof contracts below.
OptimismPortalProxy common.Address
DisputeGameFactoryProxy common.Address
......@@ -97,3 +103,203 @@ func DeployOPChain(host *script.Host, input DeployOPChainInput) (DeployOPChainOu
return dco, nil
}
// opcmRoles is an internal struct used to pass the roles to OPSM. See opcmDeployInput for more info.
type opcmRoles struct {
OpChainProxyAdminOwner common.Address
SystemConfigOwner common.Address
Batcher common.Address
UnsafeBlockSigner common.Address
Proposer common.Address
Challenger common.Address
}
// opcmDeployInput is the input struct for the deploy method of the OPStackManager contract. We
// define a separate struct here to match what the OPSM contract expects.
type opcmDeployInput struct {
Roles opcmRoles
BasefeeScalar uint32
BlobBasefeeScalar uint32
L2ChainId *big.Int
StartingAnchorRoots []byte
}
// decodeOutputABIJSON defines an ABI for a fake method called "decodeOutput" that returns the
// DeployOutput struct. This allows the code in the deployer to decode directly into a struct
// using Geth's ABI library.
const decodeOutputABIJSON = `
[
{
"type": "function",
"name": "decodeOutput",
"inputs": [],
"outputs": [
{
"name": "output",
"indexed": false,
"type": "tuple",
"components": [
{
"name": "opChainProxyAdmin",
"type": "address"
},
{
"name": "addressManager",
"type": "address"
},
{
"name": "l1ERC721BridgeProxy",
"type": "address"
},
{
"name": "systemConfigProxy",
"type": "address"
},
{
"name": "optimismMintableERC20FactoryProxy",
"type": "address"
},
{
"name": "l1StandardBridgeProxy",
"type": "address"
},
{
"name": "l1CrossDomainMessengerProxy",
"type": "address"
},
{
"name": "optimismPortalProxy",
"type": "address"
},
{
"name": "disputeGameFactoryProxy",
"type": "address"
},
{
"name": "anchorStateRegistryProxy",
"type": "address"
},
{
"name": "anchorStateRegistryImpl",
"type": "address"
},
{
"name": "faultDisputeGame",
"type": "address",
"internalType": "contract FaultDisputeGame"
},
{
"name": "permissionedDisputeGame",
"type": "address"
},
{
"name": "delayedWETHPermissionedGameProxy",
"type": "address"
},
{
"name": "delayedWETHPermissionlessGameProxy",
"type": "address"
}
]
}
]
}
]
`
var decodeOutputABI abi.ABI
// DeployOPChainRaw deploys an OP Chain using a raw call to a pre-deployed OPSM contract.
func DeployOPChainRaw(
ctx context.Context,
l1 *ethclient.Client,
bcast broadcaster.Broadcaster,
deployer common.Address,
artifacts foundry.StatDirFs,
input DeployOPChainInput,
) (DeployOPChainOutput, error) {
var out DeployOPChainOutput
artifactsFS := &foundry.ArtifactsFS{FS: artifacts}
opcmArtifacts, err := artifactsFS.ReadArtifact("OPContractsManager.sol", "OPContractsManager")
if err != nil {
return out, fmt.Errorf("failed to read OPStackManager artifact: %w", err)
}
opcmABI := opcmArtifacts.ABI
calldata, err := opcmABI.Pack("deploy", opcmDeployInput{
Roles: opcmRoles{
OpChainProxyAdminOwner: input.OpChainProxyAdminOwner,
SystemConfigOwner: input.SystemConfigOwner,
Batcher: input.Batcher,
UnsafeBlockSigner: input.UnsafeBlockSigner,
Proposer: input.Proposer,
Challenger: input.Challenger,
},
BasefeeScalar: input.BasefeeScalar,
BlobBasefeeScalar: input.BlobBaseFeeScalar,
L2ChainId: input.L2ChainId,
StartingAnchorRoots: input.StartingAnchorRoots(),
})
if err != nil {
return out, fmt.Errorf("failed to pack deploy input: %w", err)
}
nonce, err := l1.NonceAt(ctx, deployer, nil)
if err != nil {
return out, fmt.Errorf("failed to read nonce: %w", err)
}
bcast.Hook(script.Broadcast{
From: deployer,
To: input.OpcmProxy,
Input: calldata,
Value: (*hexutil.U256)(uint256.NewInt(0)),
// use hardcoded 19MM gas for now since this is roughly what we've seen this deployment cost.
GasUsed: 19_000_000,
Type: script.BroadcastCall,
Nonce: nonce,
})
results, err := bcast.Broadcast(ctx)
if err != nil {
return out, fmt.Errorf("failed to broadcast OP chain deployment: %w", err)
}
deployedEvent := opcmABI.Events["Deployed"]
res := results[0]
for _, log := range res.Receipt.Logs {
if log.Topics[0] != deployedEvent.ID {
continue
}
type EventData struct {
DeployOutput []byte
}
var data EventData
if err := opcmABI.UnpackIntoInterface(&data, "Deployed", log.Data); err != nil {
return out, fmt.Errorf("failed to unpack Deployed event: %w", err)
}
type OutputData struct {
Output DeployOPChainOutput
}
var outData OutputData
if err := decodeOutputABI.UnpackIntoInterface(&outData, "decodeOutput", data.DeployOutput); err != nil {
return out, fmt.Errorf("failed to unpack DeployOutput: %w", err)
}
return outData.Output, nil
}
return out, fmt.Errorf("failed to find Deployed event")
}
func init() {
var err error
decodeOutputABI, err = abi.JSON(strings.NewReader(decodeOutputABIJSON))
if err != nil {
panic(fmt.Sprintf("failed to parse decodeOutput ABI: %v", err))
}
}
......@@ -5,6 +5,7 @@ import (
"crypto/rand"
"fmt"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
......@@ -34,6 +35,40 @@ func Init(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *
}
}
if intent.OPCMAddress != (common.Address{}) {
env.Logger.Info("using provided OPCM address, populating state", "address", intent.OPCMAddress.Hex())
if intent.ContractsRelease == "dev" {
env.Logger.Warn("using dev release with existing OPCM, this field will be ignored")
}
opcmContract := opcm.NewContract(intent.OPCMAddress, env.L1Client)
protocolVersions, err := opcmContract.ProtocolVersions(ctx)
if err != nil {
return fmt.Errorf("error getting protocol versions address: %w", err)
}
superchainConfig, err := opcmContract.SuperchainConfig(ctx)
if err != nil {
return fmt.Errorf("error getting superchain config address: %w", err)
}
env.Logger.Debug(
"populating protocol versions and superchain config addresses",
"protocolVersions", protocolVersions.Hex(),
"superchainConfig", superchainConfig.Hex(),
)
// The below fields are the only ones required to perform an OP Chain
// deployment via an existing OPCM contract. All the others are used
// for deploying the OPCM itself, which isn't necessary in this case.
st.SuperchainDeployment = &state.SuperchainDeployment{
ProtocolVersionsProxyAddress: protocolVersions,
SuperchainConfigProxyAddress: superchainConfig,
}
st.ImplementationsDeployment = &state.ImplementationsDeployment{
OpcmProxyAddress: intent.OPCMAddress,
}
}
// If the state has never been applied, we don't need to perform
// any additional checks.
if st.AppliedIntent == nil {
......
......@@ -5,6 +5,8 @@ import (
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
......@@ -27,45 +29,73 @@ func DeployOPChain(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs,
return fmt.Errorf("failed to get chain intent: %w", err)
}
input := opcm.DeployOPChainInput{
OpChainProxyAdminOwner: thisIntent.Roles.ProxyAdminOwner,
SystemConfigOwner: thisIntent.Roles.SystemConfigOwner,
Batcher: thisIntent.Roles.Batcher,
UnsafeBlockSigner: thisIntent.Roles.UnsafeBlockSigner,
Proposer: thisIntent.Roles.Proposer,
Challenger: thisIntent.Roles.Challenger,
BasefeeScalar: 1368,
BlobBaseFeeScalar: 801949,
L2ChainId: chainID.Big(),
OpcmProxy: st.ImplementationsDeployment.OpcmProxyAddress,
}
var dco opcm.DeployOPChainOutput
err = CallScriptBroadcast(
ctx,
CallScriptBroadcastOpts{
L1ChainID: big.NewInt(int64(intent.L1ChainID)),
Logger: lgr,
ArtifactsFS: artifactsFS,
Deployer: env.Deployer,
Signer: env.Signer,
Client: env.L1Client,
Broadcaster: KeyedBroadcaster,
Handler: func(host *script.Host) error {
host.ImportState(st.ImplementationsDeployment.StateDump)
dco, err = opcm.DeployOPChain(
host,
opcm.DeployOPChainInput{
OpChainProxyAdminOwner: thisIntent.Roles.ProxyAdminOwner,
SystemConfigOwner: thisIntent.Roles.SystemConfigOwner,
Batcher: thisIntent.Roles.Batcher,
UnsafeBlockSigner: thisIntent.Roles.UnsafeBlockSigner,
Proposer: thisIntent.Roles.Proposer,
Challenger: thisIntent.Roles.Challenger,
BasefeeScalar: 1368,
BlobBaseFeeScalar: 801949,
L2ChainId: chainID.Big(),
OpcmProxy: st.ImplementationsDeployment.OpcmProxyAddress,
},
)
return err
if intent.OPCMAddress == (common.Address{}) {
err = CallScriptBroadcast(
ctx,
CallScriptBroadcastOpts{
L1ChainID: big.NewInt(int64(intent.L1ChainID)),
Logger: lgr,
ArtifactsFS: artifactsFS,
Deployer: env.Deployer,
Signer: env.Signer,
Client: env.L1Client,
Broadcaster: KeyedBroadcaster,
Handler: func(host *script.Host) error {
host.ImportState(st.ImplementationsDeployment.StateDump)
dco, err = opcm.DeployOPChain(
host,
input,
)
return err
},
},
},
)
if err != nil {
return fmt.Errorf("error deploying OP chain: %w", err)
)
if err != nil {
return fmt.Errorf("error deploying OP chain: %w", err)
}
} else {
lgr.Info("deploying using existing OPCM", "address", intent.OPCMAddress.Hex())
bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
Logger: lgr,
ChainID: big.NewInt(int64(intent.L1ChainID)),
Client: env.L1Client,
Signer: env.Signer,
From: env.Deployer,
})
if err != nil {
return fmt.Errorf("failed to create broadcaster: %w", err)
}
dco, err = opcm.DeployOPChainRaw(
ctx,
env.L1Client,
bcaster,
env.Deployer,
artifactsFS,
input,
)
if err != nil {
return fmt.Errorf("error deploying OP chain: %w", err)
}
}
st.Chains = append(st.Chains, &state.ChainState{
ID: chainID,
ID: chainID,
ProxyAdminAddress: dco.OpChainProxyAdmin,
AddressManagerAddress: dco.AddressManager,
L1ERC721BridgeProxyAddress: dco.L1ERC721BridgeProxy,
......
......@@ -3,6 +3,7 @@ package state
import (
"fmt"
"math/big"
"strings"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
......@@ -26,6 +27,8 @@ type Intent struct {
ContractsRelease string `json:"contractsVersion" toml:"contractsVersion"`
OPCMAddress common.Address `json:"opcmAddress" toml:"opcmAddress"`
Chains []*ChainIntent `json:"chains" toml:"chains"`
GlobalDeployOverrides map[string]any `json:"globalDeployOverrides" toml:"globalDeployOverrides"`
......@@ -60,6 +63,10 @@ func (c *Intent) Check() error {
return fmt.Errorf("contractArtifactsURL must be set")
}
if c.ContractsRelease != "dev" && !strings.HasPrefix(c.ContractsRelease, "op-contracts/") {
return fmt.Errorf("contractsVersion must be either the literal \"dev\" or start with \"op-contracts/\"")
}
return nil
}
......
......@@ -32,8 +32,8 @@
"sourceCodeHash": "0xde4df0f9633dc0cdb1c9f634003ea5b0f7c5c1aebc407bc1b2f44c0ecf938649"
},
"src/L1/OPContractsManager.sol": {
"initCodeHash": "0x92c72b75206e756742df25d67d295e4479e65db1473948b8f53cb4ca642025d5",
"sourceCodeHash": "0x5e04124ee67298d2f1245139baf7de79dee421d2c031c6e5abe0cd3b1bdbdb32"
"initCodeHash": "0x7903f225091334a1910470bb1b5c111f13f6f2572faf03e0c74ad625e4c0d6f5",
"sourceCodeHash": "0x3a25b0ac70b1d434773c86f46b1f2a995722e33d3273762fd5abbb541bffa7db"
},
"src/L1/OptimismPortal.sol": {
"initCodeHash": "0xbe2c0c81b3459014f287d8c89cdc0d27dde5d1f44e5d024fa1e4773ddc47c190",
......
......@@ -15,6 +15,19 @@
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "OUTPUT_VERSION",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "blueprints",
......@@ -448,6 +461,12 @@
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "outputVersion",
"type": "uint256"
},
{
"indexed": true,
"internalType": "uint256",
......@@ -456,9 +475,15 @@
},
{
"indexed": true,
"internalType": "contract SystemConfig",
"name": "systemConfig",
"internalType": "address",
"name": "deployer",
"type": "address"
},
{
"indexed": false,
"internalType": "bytes",
"name": "deployOutput",
"type": "bytes"
}
],
"name": "Deployed",
......
......@@ -15,6 +15,19 @@
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "OUTPUT_VERSION",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "blueprints",
......@@ -448,6 +461,12 @@
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "outputVersion",
"type": "uint256"
},
{
"indexed": true,
"internalType": "uint256",
......@@ -456,9 +475,15 @@
},
{
"indexed": true,
"internalType": "contract SystemConfig",
"name": "systemConfig",
"internalType": "address",
"name": "deployer",
"type": "address"
},
{
"indexed": false,
"internalType": "bytes",
"name": "deployOutput",
"type": "bytes"
}
],
"name": "Deployed",
......
......@@ -124,8 +124,12 @@ contract OPContractsManager is ISemver, Initializable {
// -------- Constants and Variables --------
/// @custom:semver 1.0.0-beta.6
string public constant version = "1.0.0-beta.6";
/// @custom:semver 1.0.0-beta.7
string public constant version = "1.0.0-beta.7";
/// @notice Represents the interface version so consumers know how to decode the DeployOutput struct
/// that's emitted in the `Deployed` event. Whenever that struct changes, a new version should be used.
uint256 public constant OUTPUT_VERSION = 0;
/// @notice Address of the SuperchainConfig contract shared by all chains.
SuperchainConfig public immutable superchainConfig;
......@@ -155,9 +159,13 @@ contract OPContractsManager is ISemver, Initializable {
// -------- Events --------
/// @notice Emitted when a new OP Stack chain is deployed.
/// @param l2ChainId The chain ID of the new chain.
/// @param systemConfig The address of the new chain's SystemConfig contract.
event Deployed(uint256 indexed l2ChainId, SystemConfig indexed systemConfig);
/// @param outputVersion Version that indicates how to decode the `deployOutput` argument.
/// @param l2ChainId Chain ID of the new chain.
/// @param deployer Address that deployed the chain.
/// @param deployOutput ABI-encoded output of the deployment.
event Deployed(
uint256 indexed outputVersion, uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput
);
// -------- Errors --------
......@@ -334,7 +342,7 @@ contract OPContractsManager is ISemver, Initializable {
// Transfer ownership of the ProxyAdmin from this contract to the specified owner.
output.opChainProxyAdmin.transferOwnership(_input.roles.opChainProxyAdminOwner);
emit Deployed(l2ChainId, output.systemConfigProxy);
emit Deployed(OUTPUT_VERSION, l2ChainId, msg.sender, abi.encode(output));
return output;
}
......
......@@ -34,7 +34,9 @@ contract OPContractsManager_Harness is OPContractsManager {
contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase {
using stdStorage for StdStorage;
event Deployed(uint256 indexed l2ChainId, SystemConfig indexed systemConfig);
event Deployed(
uint256 indexed outputVersion, uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput
);
function setUp() public override {
DeployOPChain_TestBase.setUp();
......@@ -86,8 +88,8 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase {
}
function test_deploy_succeeds() public {
vm.expectEmit(true, false, true, true); // TODO precompute the system config address.
emit Deployed(doi.l2ChainId(), SystemConfig(address(1)));
vm.expectEmit(true, true, true, false); // TODO precompute the expected `deployOutput`.
emit Deployed(0, doi.l2ChainId(), address(this), bytes(""));
opcm.deploy(toOPCMDeployInput(doi));
}
}
......
......@@ -843,6 +843,7 @@ contract Specification_Test is CommonTest {
_addSpec({ _name: "OPContractsManager", _sel: _getSel("latestRelease()") });
_addSpec({ _name: "OPContractsManager", _sel: _getSel("implementations(string,string)") });
_addSpec({ _name: "OPContractsManager", _sel: _getSel("systemConfigs(uint256)") });
_addSpec({ _name: "OPContractsManager", _sel: _getSel("OUTPUT_VERSION()") });
_addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.initialize.selector });
_addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.deploy.selector });
_addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.blueprints.selector });
......@@ -855,6 +856,7 @@ contract Specification_Test is CommonTest {
_addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("latestRelease()") });
_addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("implementations(string,string)") });
_addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("systemConfigs(uint256)") });
_addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("OUTPUT_VERSION()") });
_addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.initialize.selector });
_addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.deploy.selector });
_addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.blueprints.selector });
......
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