Skip to content

Commit

Permalink
contracts-bedrock: fix deploy config for mainnet MCP upgrade (#9865)
Browse files Browse the repository at this point in the history
* contracts-bedrock: fix deploy config for mainnet MCP upgrade

The config param for the scalar was not updated in the
deploy config when it was changed on chain. If we can enforce
that the deploy config is always used as the source of truth
for when doing on chain config changes, it can help to scale
the team as other teams can use the same config file and know
that the values in there represent the truth. This is a fundamental
problem with the `initialize` pattern, we need to move away from
it eventually.

The deploy config is updated with the value that is used on
mainnet and the parsing is updated to handle the new ecotone
style config, which tightly packs the values into a single
bytes32.

* op-chain-ops: more cleanup

* op-e2e: fix build

* cleanup: modularize scalar encoding and decoding

Ensures that the same consensus code is used
to encode and decode the scalar in various places.

* op-chain-ops: fix L2 genesis generation

* config: fix serialization

* op-chain-ops: refactor config

Make backwards compatible

* op-chain-ops: fix build

* deploy-config: update mainnet fee scalar config

Should match mainnet values

* op-chain-ops: fix test

* genesis: test L1Block predeploy state setting

* op-upgrade: delete dead code

* build: fix

* op-chain-ops: add deprecation warning

Co-authored-by: Sebastian Stammler <seb@oplabs.co>

* deploy-config: use mainnet values

Co-authored-by: Sebastian Stammler <seb@oplabs.co>

* deploy-config: use mainnet values

Co-authored-by: Sebastian Stammler <seb@oplabs.co>

* op-service: end to end encode/decode scalar tests

* tests: cleanup

* op-chain-ops: fix nits, adapt to breaking simulated backend changes

* op-chain-ops: fix comment and address-type conversion nits

---------

Co-authored-by: Sebastian Stammler <seb@oplabs.co>
Co-authored-by: protolambda <proto@protolambda.com>
  • Loading branch information
3 people committed Apr 4, 2024
1 parent 35c0ad8 commit 8167f36
Show file tree
Hide file tree
Showing 16 changed files with 246 additions and 67 deletions.
15 changes: 8 additions & 7 deletions op-chain-ops/cmd/ecotone-scalar/main.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package main

import (
"encoding/binary"
"flag"
"fmt"
"math"
"math/big"
"os"

"github.com/ethereum-optimism/optimism/op-service/eth"
)

func main() {
Expand All @@ -26,15 +27,15 @@ func main() {
os.Exit(2)
}

var n [32]byte
n[0] = 1 // version
binary.BigEndian.PutUint32(n[32-4:], uint32(scalar))
binary.BigEndian.PutUint32(n[32-8:], uint32(blobScalar))
i := new(big.Int).SetBytes(n[:])
encoded := eth.EncodeScalar(eth.EcostoneScalars{
BlobBaseFeeScalar: uint32(blobScalar),
BaseFeeScalar: uint32(scalar),
})
i := new(big.Int).SetBytes(encoded[:])

fmt.Println("# base fee scalar :", scalar)
fmt.Println("# blob base fee scalar:", blobScalar)
fmt.Printf("# v1 hex encoding : 0x%x\n", n[:])
fmt.Printf("# v1 hex encoding : 0x%x\n", encoded[:])
fmt.Println("# uint value for the 'scalar' parameter in SystemConfigProxy.setGasConfig():")
fmt.Println(i)
}
9 changes: 7 additions & 2 deletions op-chain-ops/cmd/op-upgrade-mcp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func main() {
oplog.SetGlobalLogHandler(log.NewTerminalHandler(os.Stderr, color))

app := &cli.App{
Name: "op-upgrade",
Name: "op-upgrade-mcp",
Usage: "Build transactions useful for upgrading the Superchain",
Flags: []cli.Flag{
&cli.StringFlag{
Expand Down Expand Up @@ -179,6 +179,11 @@ func entrypoint(ctx *cli.Context) error {
return fmt.Errorf("no chain config for chain ID %d", l2ChainID)
}

superchainConfig, ok := superchain.Superchains[chainConfig.Superchain]
if !ok {
return fmt.Errorf("no superchain config for superchain %s", chainConfig.Superchain)
}

log.Info("Upgrading to the following versions")
log.Info("L1CrossDomainMessenger", "version", list.L1CrossDomainMessenger.Version, "address", list.L1CrossDomainMessenger.Address)
log.Info("L1ERC721Bridge", "version", list.L1ERC721Bridge.Version, "address", list.L1ERC721Bridge.Address)
Expand All @@ -193,7 +198,7 @@ func entrypoint(ctx *cli.Context) error {
}

// Build the batch
if err := upgrades.L1(&batch, list, *proxyAddresses, config, chainConfig, clients.L1Client); err != nil {
if err := upgrades.L1(&batch, list, *proxyAddresses, config, chainConfig, superchainConfig, clients.L1Client); err != nil {
return fmt.Errorf("cannot build L1 upgrade batch: %w", err)
}

Expand Down
4 changes: 3 additions & 1 deletion op-chain-ops/cmd/op-upgrade/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ func entrypoint(ctx *cli.Context) error {
}

// Build the batch
if err := upgrades.L1(&batch, list, *addresses, config, chainConfig, clients.L1Client); err != nil {
// op-upgrade assumes a superchain config for L1 contract-implementations set.
// The nil superchainConfig here is a placeholder, until op-upgrade and op-upgrade-mcp are consolidated.
if err := upgrades.L1(&batch, list, *addresses, config, chainConfig, nil, clients.L1Client); err != nil {
return err
}
}
Expand Down
56 changes: 45 additions & 11 deletions op-chain-ops/genesis/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
gstate "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
Expand Down Expand Up @@ -169,9 +170,15 @@ type DeployConfig struct {
// as part of the derivation pipeline.
OptimismPortalProxy common.Address `json:"optimismPortalProxy"`
// GasPriceOracleOverhead represents the initial value of the gas overhead in the GasPriceOracle predeploy.
// Deprecated: Since Ecotone, this field is superseded by GasPriceOracleBaseFeeScalar and GasPriceOracleBlobBaseFeeScalar.
GasPriceOracleOverhead uint64 `json:"gasPriceOracleOverhead"`
// GasPriceOracleScalar represents the initial value of the gas scalar in the GasPriceOracle predeploy.
// Deprecated: Since Ecotone, this field is superseded by GasPriceOracleBaseFeeScalar and GasPriceOracleBlobBaseFeeScalar.
GasPriceOracleScalar uint64 `json:"gasPriceOracleScalar"`
// GasPriceOracleBaseFeeScalar represents the value of the base fee scalar used for fee calculations.
GasPriceOracleBaseFeeScalar uint32 `json:"gasPriceOracleBaseFeeScalar"`
// GasPriceOracleBlobBaseFeeScalar represents the value of the blob base fee scalar used for fee calculations.
GasPriceOracleBlobBaseFeeScalar uint32 `json:"gasPriceOracleBlobBaseFeeScalar"`
// EnableGovernance configures whether or not include governance token predeploy.
EnableGovernance bool `json:"enableGovernance"`
// GovernanceTokenSymbol represents the ERC20 symbol of the GovernanceToken.
Expand Down Expand Up @@ -356,7 +363,13 @@ func (d *DeployConfig) Check() error {
log.Warn("GasPriceOracleOverhead is 0")
}
if d.GasPriceOracleScalar == 0 {
return fmt.Errorf("%w: GasPriceOracleScalar cannot be 0", ErrInvalidDeployConfig)
log.Warn("GasPriceOracleScalar is 0")
}
if d.GasPriceOracleBaseFeeScalar == 0 {
log.Warn("GasPriceOracleBaseFeeScalar is 0")
}
if d.GasPriceOracleBlobBaseFeeScalar == 0 {
log.Warn("GasPriceOracleBlobBaseFeeScalar is 0")
}
if d.EIP1559Denominator == 0 {
return fmt.Errorf("%w: EIP1559Denominator cannot be 0", ErrInvalidDeployConfig)
Expand Down Expand Up @@ -447,6 +460,18 @@ func (d *DeployConfig) Check() error {
return nil
}

// FeeScalar returns the raw serialized fee scalar. Uses pre-Ecotone if legacy config is present,
// otherwise uses the post-Ecotone scalar serialization.
func (d *DeployConfig) FeeScalar() [32]byte {
if d.GasPriceOracleScalar != 0 {
return common.BigToHash(big.NewInt(int64(d.GasPriceOracleScalar)))
}
return eth.EncodeScalar(eth.EcostoneScalars{
BlobBaseFeeScalar: d.GasPriceOracleBlobBaseFeeScalar,
BaseFeeScalar: d.GasPriceOracleBaseFeeScalar,
})
}

// 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
Expand Down Expand Up @@ -573,7 +598,7 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Block, l2GenesisBlockHas
SystemConfig: eth.SystemConfig{
BatcherAddr: d.BatchSenderAddress,
Overhead: eth.Bytes32(common.BigToHash(new(big.Int).SetUint64(d.GasPriceOracleOverhead))),
Scalar: eth.Bytes32(common.BigToHash(new(big.Int).SetUint64(d.GasPriceOracleScalar))),
Scalar: eth.Bytes32(d.FeeScalar()),
GasLimit: uint64(d.L2GenesisBlockGasLimit),
},
},
Expand Down Expand Up @@ -874,7 +899,7 @@ func NewL2ImmutableConfig(config *DeployConfig, block *types.Block) (*immutables
return &cfg, nil
}

// NewL2StorageConfig will create a StorageConfig given an instance of a DeployConfig and genesis block.
// NewL2StorageConfig will create a StorageConfig given an instance of a DeployConfig and genesis L1 anchor block.
func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.StorageConfig, error) {
storage := make(state.StorageConfig)

Expand Down Expand Up @@ -912,15 +937,24 @@ func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.Storage
"_initializing": false,
"bridge": predeploys.L2StandardBridgeAddr,
}

excessBlobGas := block.ExcessBlobGas()
if excessBlobGas == nil {
excessBlobGas = u64ptr(0)
}

storage["L1Block"] = state.StorageValues{
"number": block.Number(),
"timestamp": block.Time(),
"basefee": block.BaseFee(),
"hash": block.Hash(),
"sequenceNumber": 0,
"batcherHash": eth.AddressAsLeftPaddedHash(config.BatchSenderAddress),
"l1FeeOverhead": config.GasPriceOracleOverhead,
"l1FeeScalar": config.GasPriceOracleScalar,
"number": block.Number(),
"timestamp": block.Time(),
"basefee": block.BaseFee(),
"hash": block.Hash(),
"sequenceNumber": 0,
"blobBaseFeeScalar": config.GasPriceOracleBlobBaseFeeScalar,
"baseFeeScalar": config.GasPriceOracleBaseFeeScalar,
"batcherHash": eth.AddressAsLeftPaddedHash(config.BatchSenderAddress),
"l1FeeOverhead": config.GasPriceOracleOverhead,
"l1FeeScalar": config.GasPriceOracleScalar,
"blobBaseFee": eip4844.CalcBlobFee(*excessBlobGas),
}
storage["LegacyERC20ETH"] = state.StorageValues{
"_name": "Ether",
Expand Down
100 changes: 100 additions & 0 deletions op-chain-ops/genesis/layer_two_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@ import (

"github.com/stretchr/testify/require"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"

"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig"
)

var writeFile bool
Expand Down Expand Up @@ -47,9 +51,52 @@ func testBuildL2Genesis(t *testing.T, config *genesis.DeployConfig) *core.Genesi
proxyBytecode, err := bindings.GetDeployedBytecode("Proxy")
require.NoError(t, err)

// for simulation we need a regular EVM, not with system-deposit information.
chainConfig := params.ChainConfig{
ChainID: big.NewInt(1337),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: nil,
DAOForkSupport: false,
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: big.NewInt(0),
// Activated proof of stake. We manually build/commit blocks in the simulator anyway,
// and the timestamp verification of PoS is not against the wallclock,
// preventing blocks from getting stuck temporarily in the future-blocks queue, decreasing setup time a lot.
MergeNetsplitBlock: big.NewInt(0),
TerminalTotalDifficulty: big.NewInt(0),
TerminalTotalDifficultyPassed: true,
ShanghaiTime: new(uint64),
}

// Apply the genesis to the backend
cfg := ethconfig.Defaults
cfg.Preimages = true
cfg.Genesis = &core.Genesis{
Config: &chainConfig,
Timestamp: 1234567,
Difficulty: big.NewInt(0),
Alloc: gen.Alloc,
GasLimit: 30_000_000,
}
backend = backends.NewSimulatedBackendFromConfig(cfg)

for name, predeploy := range predeploys.Predeploys {
addr := predeploy.Address

if addr == predeploys.L1BlockAddr {
testL1Block(t, backend, config, block)
}

account, ok := gen.Alloc[addr]
require.Equal(t, true, ok, name)
require.Greater(t, len(account.Code), 0)
Expand Down Expand Up @@ -84,6 +131,59 @@ func testBuildL2Genesis(t *testing.T, config *genesis.DeployConfig) *core.Genesi
return gen
}

// testL1Block tests that the state is set correctly in the L1Block predeploy
func testL1Block(t *testing.T, caller bind.ContractCaller, config *genesis.DeployConfig, block *types.Block) {
contract, err := bindings.NewL1BlockCaller(predeploys.L1BlockAddr, caller)
require.NoError(t, err)

number, err := contract.Number(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, block.Number().Uint64(), number)

timestamp, err := contract.Timestamp(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, block.Time(), timestamp)

basefee, err := contract.Basefee(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, block.BaseFee(), basefee)

hash, err := contract.Hash(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, block.Hash(), common.Hash(hash))

sequenceNumber, err := contract.SequenceNumber(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, uint64(0), sequenceNumber)

blobBaseFeeScalar, err := contract.BlobBaseFeeScalar(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, config.GasPriceOracleBlobBaseFeeScalar, blobBaseFeeScalar)

baseFeeScalar, err := contract.BaseFeeScalar(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, config.GasPriceOracleBaseFeeScalar, baseFeeScalar)

batcherHeader, err := contract.BatcherHash(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, eth.AddressAsLeftPaddedHash(config.BatchSenderAddress), common.Hash(batcherHeader))

l1FeeOverhead, err := contract.L1FeeOverhead(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, config.GasPriceOracleOverhead, l1FeeOverhead.Uint64())

l1FeeScalar, err := contract.L1FeeScalar(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, config.GasPriceOracleScalar, l1FeeScalar.Uint64())

blobBaseFee, err := contract.BlobBaseFee(&bind.CallOpts{})
require.NoError(t, err)
if excessBlobGas := block.ExcessBlobGas(); excessBlobGas != nil {
require.Equal(t, uint64(0), *excessBlobGas)
}
require.Equal(t, big.NewInt(1), blobBaseFee)
}

func TestBuildL2MainnetGenesis(t *testing.T) {
config, err := genesis.NewDeployConfig("./testdata/test-deploy-config-devnet-l1.json")
require.Nil(t, err)
Expand Down
2 changes: 2 additions & 0 deletions op-chain-ops/genesis/testdata/test-deploy-config-full.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
"systemConfigProxy": "0x4200000000000000000000000000000000000061",
"optimismPortalProxy": "0x4200000000000000000000000000000000000062",
"proxyAdminOwner": "0x0000000000000000000000000000000000000222",
"gasPriceOracleBaseFeeScalar": 0,
"gasPriceOracleBlobBaseFeeScalar": 0,
"gasPriceOracleOverhead": 2100,
"gasPriceOracleScalar": 1000000,
"enableGovernance": true,
Expand Down

0 comments on commit 8167f36

Please sign in to comment.