Commit b327c69a authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #6865 from ethereum-optimism/feat/deploy-config-checker

contracts-bedrock: add deploy config validator
parents 9d9a79a3 7548087e
......@@ -415,6 +415,10 @@ jobs:
pnpm autogen:invariant-docs
git diff --exit-code ./invariant-docs/*.md || echo "export INVARIANT_DOCS_STATUS=1" >> "$BASH_ENV"
working_directory: packages/contracts-bedrock
- run:
name: check deploy configs || echo "export DEPLOY_CONFIGS_STATUS=1" >> "$BASH_ENV"
command: pnpm validate-deploy-configs
working_directory: packages/contracts-bedrock
- run:
name: check statuses
command: |
......@@ -430,12 +434,16 @@ jobs:
echo "Storage snapshot failed, see job output for details."
FAILED=1
fi
if [[ "$SEMVER_LOCK_STATUS" -ne 0 ]]; then
echo "Semver lock failed, see job output for details."
FAILED=1
fi
if [[ "$INVARIANT_DOCS_STATUS" -ne 0 ]]; then
echo "Invariant docs failed, see job output for details."
FAILED=1
fi
if [[ "$SEMVER_LOCK_STATUS" -ne 0 ]]; then
echo "Semver lock failed, see job output for details."
if [[ "$DEPLOY_CONFIGS_STATUS" -ne 0 ]]; then
echo "Deploy config check failed, see job output for details."
FAILED=1
fi
if [[ "$FAILED" -ne 0 ]]; then
......
package main
import (
"os"
"path/filepath"
"strings"
"github.com/mattn/go-isatty"
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum/go-ethereum/log"
)
func main() {
log.Root().SetHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(isatty.IsTerminal(os.Stderr.Fd()))))
app := &cli.App{
Name: "check-deploy-config",
Usage: "Check that a deploy config is valid",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "path",
Required: true,
Usage: "File system path to the deploy config",
},
},
Action: entrypoint,
}
if err := app.Run(os.Args); err != nil {
log.Crit("error checking deploy config", "err", err)
}
}
func entrypoint(ctx *cli.Context) error {
path := ctx.String("path")
name := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
log.Info("Checking deploy config", "name", name, "path", path)
config, err := genesis.NewDeployConfig(path)
if err != nil {
return err
}
// Check the config, no need to call `CheckAddresses()`
if err := config.Check(); err != nil {
return err
}
log.Info("Valid deploy config")
return nil
}
package genesis
import (
"bytes"
"encoding/json"
"errors"
"fmt"
......@@ -31,6 +32,7 @@ var (
)
// DeployConfig represents the deployment configuration for an OP Stack chain.
// It is used to deploy the L1 contracts as well as create the L2 genesis state.
type DeployConfig struct {
// L1StartingBlockTag is used to fill in the storage of the L1Block info predeploy. The rollup
// config script uses this to fill the L1 genesis info for the rollup. The Output oracle deploy
......@@ -172,10 +174,10 @@ type DeployConfig struct {
EIP1559Elasticity uint64 `json:"eip1559Elasticity"`
// EIP1559Denominator is the denominator of EIP1559 base fee market.
EIP1559Denominator uint64 `json:"eip1559Denominator"`
// FundDevAccounts configures whether or not to fund the dev accounts. Should only be used
// during devnet deployments.
FundDevAccounts bool `json:"fundDevAccounts"`
// SystemConfigStartBlock represents the block at which the op-node should start syncing
// from. It is an override to set this value on legacy networks where it is not set by
// default. It can be removed once all networks have this value set in their storage.
SystemConfigStartBlock uint64 `json:"systemConfigStartBlock"`
// FaultGameAbsolutePrestate is the absolute prestate of Cannon. This is computed
// by generating a proof from the 0th -> 1st instruction and grabbing the prestate from
// the output JSON. All honest challengers should agree on the setup state of the program.
......@@ -193,6 +195,9 @@ type DeployConfig struct {
// game can run for before it is ready to be resolved. Each side receives half of this value
// on their chess clock at the inception of the dispute.
FaultGameMaxDuration uint64 `json:"faultGameMaxDuration"`
// FundDevAccounts configures whether or not to fund the dev accounts. Should only be used
// during devnet deployments.
FundDevAccounts bool `json:"fundDevAccounts"`
}
// Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy
......@@ -293,21 +298,6 @@ func (d *DeployConfig) Check() error {
if d.GasPriceOracleScalar == 0 {
return fmt.Errorf("%w: GasPriceOracleScalar cannot be 0", ErrInvalidDeployConfig)
}
if d.L1StandardBridgeProxy == (common.Address{}) {
return fmt.Errorf("%w: L1StandardBridgeProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.L1CrossDomainMessengerProxy == (common.Address{}) {
return fmt.Errorf("%w: L1CrossDomainMessengerProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.L1ERC721BridgeProxy == (common.Address{}) {
return fmt.Errorf("%w: L1ERC721BridgeProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.SystemConfigProxy == (common.Address{}) {
return fmt.Errorf("%w: SystemConfigProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.OptimismPortalProxy == (common.Address{}) {
return fmt.Errorf("%w: OptimismPortalProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.EIP1559Denominator == 0 {
return fmt.Errorf("%w: EIP1559Denominator cannot be 0", ErrInvalidDeployConfig)
}
......@@ -343,6 +333,29 @@ func (d *DeployConfig) Check() error {
return nil
}
// CheckAddresses will return an error if the addresses are not set.
// These values are required to create the L2 genesis state and are present in the deploy config
// even though the deploy config is required to deploy the contracts on L1. This creates a
// circular dependency that should be resolved in the future.
func (d *DeployConfig) CheckAddresses() error {
if d.L1StandardBridgeProxy == (common.Address{}) {
return fmt.Errorf("%w: L1StandardBridgeProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.L1CrossDomainMessengerProxy == (common.Address{}) {
return fmt.Errorf("%w: L1CrossDomainMessengerProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.L1ERC721BridgeProxy == (common.Address{}) {
return fmt.Errorf("%w: L1ERC721BridgeProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.SystemConfigProxy == (common.Address{}) {
return fmt.Errorf("%w: SystemConfigProxy cannot be address(0)", ErrInvalidDeployConfig)
}
if d.OptimismPortalProxy == (common.Address{}) {
return fmt.Errorf("%w: OptimismPortalProxy cannot be address(0)", ErrInvalidDeployConfig)
}
return nil
}
// SetDeployments will merge a Deployments into a DeployConfig.
func (d *DeployConfig) SetDeployments(deployments *L1Deployments) {
d.L1StandardBridgeProxy = deployments.L1StandardBridgeProxy
......@@ -469,8 +482,11 @@ func NewDeployConfig(path string) (*DeployConfig, error) {
return nil, fmt.Errorf("deploy config at %s not found: %w", path, err)
}
dec := json.NewDecoder(bytes.NewReader(file))
dec.DisallowUnknownFields()
var config DeployConfig
if err := json.Unmarshal(file, &config); err != nil {
if err := dec.Decode(&config); err != nil {
return nil, fmt.Errorf("cannot unmarshal deploy config: %w", err)
}
......
......@@ -66,5 +66,6 @@
"fundDevAccounts": true,
"faultGameAbsolutePrestate": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameMaxDepth": 63,
"faultGameMaxDuration": 604800
"faultGameMaxDuration": 604800,
"systemConfigStartBlock": 0
}
......@@ -63,11 +63,15 @@ var Subcommands = cli.Commands{
config.SetDeployments(deployments)
}
// Do the check after setting the deployments
if err := config.Check(); err != nil {
return fmt.Errorf("deploy config at %s invalid: %w", deployConfig, err)
}
// Check the addresses after setting the deployments
if err := config.CheckAddresses(); err != nil {
return fmt.Errorf("deploy config at %s invalid: %w", deployConfig, err)
}
var dump *state.Dump
if l1Allocs := ctx.String("l1-allocs"); l1Allocs != "" {
dump, err = genesis.NewStateDump(l1Allocs)
......
......@@ -5,7 +5,8 @@
"l1StartingBlockTag": "earliest",
"l1ChainID": 900,
"l2ChainID": 901,
"l2BlockTime": 2,
"l2BlockTime": 1,
"l1BlockTime": 2,
"maxSequencerDrift": 300,
"sequencerWindowSize": 15,
"channelTimeout": 40,
......@@ -35,7 +36,6 @@
"governanceTokenSymbol": "OP",
"governanceTokenOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc",
"finalizationPeriodSeconds": 2,
"numDeployConfirmations": 1,
"eip1559Denominator": 50,
"eip1559Elasticity": 10,
"l2GenesisRegolithTimeOffset": "0x0",
......
{
"finalSystemOwner": "0x858F0751ef8B4067f0d2668C076BDB50a8549fbF",
"portalGuardian": "0x858F0751ef8B4067f0d2668C076BDB50a8549fbF",
"controller": "0x2d30335B0b807bBa1682C487BaAFD2Ad6da5D675",
"l1StartingBlockTag": "0x19c7e6b18fe156e45f4cfef707294fd8f079fa9c30a7b7cd6ec1ce3682ec6a2e",
"l1ChainID": 5,
"l2ChainID": 998,
"l2BlockTime": 2,
"l1BlockTime": 12,
"maxSequencerDrift": 1200,
"sequencerWindowSize": 3600,
......
......@@ -5,6 +5,7 @@
"l1ChainID": 1,
"l2ChainID": 10,
"l2BlockTime": 2,
"l1BlockTime": 12,
"maxSequencerDrift": 600,
"sequencerWindowSize": 3600,
"channelTimeout": 300,
......@@ -32,7 +33,6 @@
"governanceTokenSymbol": "OP",
"governanceTokenOwner": "0x5C4e7Ba1E219E47948e6e3F55019A647bA501005",
"l2GenesisBlockGasLimit": "0x1c9c380",
"l2GenesisBlockCoinbase": "0x4200000000000000000000000000000000000011",
"l2GenesisBlockBaseFeePerGas": "0x3b9aca00",
"gasPriceOracleOverhead": 188,
"gasPriceOracleScalar": 684000,
......
......@@ -23,7 +23,7 @@
"gas-snapshot": "pnpm build:differential && pnpm build:fuzz && forge snapshot --no-match-test 'testDiff|testFuzz|invariant|generateArtifact'",
"storage-snapshot": "./scripts/storage-snapshot.sh",
"semver-lock": "forge script scripts/SemverLock.s.sol",
"validate-deploy-configs": "./scripts/validate-deploy-configs.sh",
"validate-deploy-configs": "./scripts/check-deploy-configs.sh",
"validate-spacers": "pnpm build && npx tsx scripts/validate-spacers.ts",
"slither": "./scripts/slither.sh",
"slither:triage": "TRIAGE_MODE=1 ./scripts/slither.sh",
......
#!/usr/bin/env bash
# This script is used to check for valid deploy configs.
# It should check all configs and return a non-zero exit code if any of them are invalid.
# getting-started.json isn't valid JSON so its skipped.
code=$?
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
CONTRACTS_BASE=$(dirname $SCRIPT_DIR)
MONOREPO_BASE=$(dirname $(dirname $CONTRACTS_BASE))
for config in $CONTRACTS_BASE/deploy-config/*.json; do
if grep -q "getting-started" <<< "$config"; then
echo "Skipping getting-started.json"
continue
fi
go run $MONOREPO_BASE/op-chain-ops/cmd/check-deploy-config/main.go --path $config
[ $? -eq 0 ] || code=$?
done
exit $code
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