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: ...@@ -415,6 +415,10 @@ jobs:
pnpm autogen:invariant-docs pnpm autogen:invariant-docs
git diff --exit-code ./invariant-docs/*.md || echo "export INVARIANT_DOCS_STATUS=1" >> "$BASH_ENV" git diff --exit-code ./invariant-docs/*.md || echo "export INVARIANT_DOCS_STATUS=1" >> "$BASH_ENV"
working_directory: packages/contracts-bedrock 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: - run:
name: check statuses name: check statuses
command: | command: |
...@@ -430,12 +434,16 @@ jobs: ...@@ -430,12 +434,16 @@ jobs:
echo "Storage snapshot failed, see job output for details." echo "Storage snapshot failed, see job output for details."
FAILED=1 FAILED=1
fi 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 if [[ "$INVARIANT_DOCS_STATUS" -ne 0 ]]; then
echo "Invariant docs failed, see job output for details." echo "Invariant docs failed, see job output for details."
FAILED=1 FAILED=1
fi fi
if [[ "$SEMVER_LOCK_STATUS" -ne 0 ]]; then if [[ "$DEPLOY_CONFIGS_STATUS" -ne 0 ]]; then
echo "Semver lock failed, see job output for details." echo "Deploy config check failed, see job output for details."
FAILED=1 FAILED=1
fi fi
if [[ "$FAILED" -ne 0 ]]; then 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 package genesis
import ( import (
"bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
...@@ -31,6 +32,7 @@ var ( ...@@ -31,6 +32,7 @@ var (
) )
// DeployConfig represents the deployment configuration for an OP Stack chain. // 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 { type DeployConfig struct {
// L1StartingBlockTag is used to fill in the storage of the L1Block info predeploy. The rollup // 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 // config script uses this to fill the L1 genesis info for the rollup. The Output oracle deploy
...@@ -172,10 +174,10 @@ type DeployConfig struct { ...@@ -172,10 +174,10 @@ type DeployConfig struct {
EIP1559Elasticity uint64 `json:"eip1559Elasticity"` EIP1559Elasticity uint64 `json:"eip1559Elasticity"`
// EIP1559Denominator is the denominator of EIP1559 base fee market. // EIP1559Denominator is the denominator of EIP1559 base fee market.
EIP1559Denominator uint64 `json:"eip1559Denominator"` EIP1559Denominator uint64 `json:"eip1559Denominator"`
// FundDevAccounts configures whether or not to fund the dev accounts. Should only be used // SystemConfigStartBlock represents the block at which the op-node should start syncing
// during devnet deployments. // from. It is an override to set this value on legacy networks where it is not set by
FundDevAccounts bool `json:"fundDevAccounts"` // 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 // FaultGameAbsolutePrestate is the absolute prestate of Cannon. This is computed
// by generating a proof from the 0th -> 1st instruction and grabbing the prestate from // 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. // the output JSON. All honest challengers should agree on the setup state of the program.
...@@ -193,6 +195,9 @@ type DeployConfig struct { ...@@ -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 // 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. // on their chess clock at the inception of the dispute.
FaultGameMaxDuration uint64 `json:"faultGameMaxDuration"` 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 // Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy
...@@ -293,21 +298,6 @@ func (d *DeployConfig) Check() error { ...@@ -293,21 +298,6 @@ func (d *DeployConfig) Check() error {
if d.GasPriceOracleScalar == 0 { if d.GasPriceOracleScalar == 0 {
return fmt.Errorf("%w: GasPriceOracleScalar cannot be 0", ErrInvalidDeployConfig) 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 { if d.EIP1559Denominator == 0 {
return fmt.Errorf("%w: EIP1559Denominator cannot be 0", ErrInvalidDeployConfig) return fmt.Errorf("%w: EIP1559Denominator cannot be 0", ErrInvalidDeployConfig)
} }
...@@ -343,6 +333,29 @@ func (d *DeployConfig) Check() error { ...@@ -343,6 +333,29 @@ func (d *DeployConfig) Check() error {
return nil 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. // SetDeployments will merge a Deployments into a DeployConfig.
func (d *DeployConfig) SetDeployments(deployments *L1Deployments) { func (d *DeployConfig) SetDeployments(deployments *L1Deployments) {
d.L1StandardBridgeProxy = deployments.L1StandardBridgeProxy d.L1StandardBridgeProxy = deployments.L1StandardBridgeProxy
...@@ -469,8 +482,11 @@ func NewDeployConfig(path string) (*DeployConfig, error) { ...@@ -469,8 +482,11 @@ func NewDeployConfig(path string) (*DeployConfig, error) {
return nil, fmt.Errorf("deploy config at %s not found: %w", path, err) return nil, fmt.Errorf("deploy config at %s not found: %w", path, err)
} }
dec := json.NewDecoder(bytes.NewReader(file))
dec.DisallowUnknownFields()
var config DeployConfig 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) return nil, fmt.Errorf("cannot unmarshal deploy config: %w", err)
} }
......
...@@ -66,5 +66,6 @@ ...@@ -66,5 +66,6 @@
"fundDevAccounts": true, "fundDevAccounts": true,
"faultGameAbsolutePrestate": "0x0000000000000000000000000000000000000000000000000000000000000000", "faultGameAbsolutePrestate": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameMaxDepth": 63, "faultGameMaxDepth": 63,
"faultGameMaxDuration": 604800 "faultGameMaxDuration": 604800,
"systemConfigStartBlock": 0
} }
...@@ -63,11 +63,15 @@ var Subcommands = cli.Commands{ ...@@ -63,11 +63,15 @@ var Subcommands = cli.Commands{
config.SetDeployments(deployments) config.SetDeployments(deployments)
} }
// Do the check after setting the deployments
if err := config.Check(); err != nil { if err := config.Check(); err != nil {
return fmt.Errorf("deploy config at %s invalid: %w", deployConfig, err) 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 var dump *state.Dump
if l1Allocs := ctx.String("l1-allocs"); l1Allocs != "" { if l1Allocs := ctx.String("l1-allocs"); l1Allocs != "" {
dump, err = genesis.NewStateDump(l1Allocs) dump, err = genesis.NewStateDump(l1Allocs)
......
...@@ -5,7 +5,8 @@ ...@@ -5,7 +5,8 @@
"l1StartingBlockTag": "earliest", "l1StartingBlockTag": "earliest",
"l1ChainID": 900, "l1ChainID": 900,
"l2ChainID": 901, "l2ChainID": 901,
"l2BlockTime": 2, "l2BlockTime": 1,
"l1BlockTime": 2,
"maxSequencerDrift": 300, "maxSequencerDrift": 300,
"sequencerWindowSize": 15, "sequencerWindowSize": 15,
"channelTimeout": 40, "channelTimeout": 40,
...@@ -35,7 +36,6 @@ ...@@ -35,7 +36,6 @@
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "governanceTokenOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc",
"finalizationPeriodSeconds": 2, "finalizationPeriodSeconds": 2,
"numDeployConfirmations": 1,
"eip1559Denominator": 50, "eip1559Denominator": 50,
"eip1559Elasticity": 10, "eip1559Elasticity": 10,
"l2GenesisRegolithTimeOffset": "0x0", "l2GenesisRegolithTimeOffset": "0x0",
......
{ {
"finalSystemOwner": "0x858F0751ef8B4067f0d2668C076BDB50a8549fbF", "finalSystemOwner": "0x858F0751ef8B4067f0d2668C076BDB50a8549fbF",
"portalGuardian": "0x858F0751ef8B4067f0d2668C076BDB50a8549fbF", "portalGuardian": "0x858F0751ef8B4067f0d2668C076BDB50a8549fbF",
"controller": "0x2d30335B0b807bBa1682C487BaAFD2Ad6da5D675",
"l1StartingBlockTag": "0x19c7e6b18fe156e45f4cfef707294fd8f079fa9c30a7b7cd6ec1ce3682ec6a2e", "l1StartingBlockTag": "0x19c7e6b18fe156e45f4cfef707294fd8f079fa9c30a7b7cd6ec1ce3682ec6a2e",
"l1ChainID": 5, "l1ChainID": 5,
"l2ChainID": 998, "l2ChainID": 998,
"l2BlockTime": 2, "l2BlockTime": 2,
"l1BlockTime": 12,
"maxSequencerDrift": 1200, "maxSequencerDrift": 1200,
"sequencerWindowSize": 3600, "sequencerWindowSize": 3600,
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
"l1ChainID": 1, "l1ChainID": 1,
"l2ChainID": 10, "l2ChainID": 10,
"l2BlockTime": 2, "l2BlockTime": 2,
"l1BlockTime": 12,
"maxSequencerDrift": 600, "maxSequencerDrift": 600,
"sequencerWindowSize": 3600, "sequencerWindowSize": 3600,
"channelTimeout": 300, "channelTimeout": 300,
...@@ -32,7 +33,6 @@ ...@@ -32,7 +33,6 @@
"governanceTokenSymbol": "OP", "governanceTokenSymbol": "OP",
"governanceTokenOwner": "0x5C4e7Ba1E219E47948e6e3F55019A647bA501005", "governanceTokenOwner": "0x5C4e7Ba1E219E47948e6e3F55019A647bA501005",
"l2GenesisBlockGasLimit": "0x1c9c380", "l2GenesisBlockGasLimit": "0x1c9c380",
"l2GenesisBlockCoinbase": "0x4200000000000000000000000000000000000011",
"l2GenesisBlockBaseFeePerGas": "0x3b9aca00", "l2GenesisBlockBaseFeePerGas": "0x3b9aca00",
"gasPriceOracleOverhead": 188, "gasPriceOracleOverhead": 188,
"gasPriceOracleScalar": 684000, "gasPriceOracleScalar": 684000,
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
"gas-snapshot": "pnpm build:differential && pnpm build:fuzz && forge snapshot --no-match-test 'testDiff|testFuzz|invariant|generateArtifact'", "gas-snapshot": "pnpm build:differential && pnpm build:fuzz && forge snapshot --no-match-test 'testDiff|testFuzz|invariant|generateArtifact'",
"storage-snapshot": "./scripts/storage-snapshot.sh", "storage-snapshot": "./scripts/storage-snapshot.sh",
"semver-lock": "forge script scripts/SemverLock.s.sol", "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", "validate-spacers": "pnpm build && npx tsx scripts/validate-spacers.ts",
"slither": "./scripts/slither.sh", "slither": "./scripts/slither.sh",
"slither:triage": "TRIAGE_MODE=1 ./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