Commit 2d65f0d9 authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

state-surgery: genesis block generation (#3264)

* state-surgery: layer 2 genesis block generation

Generate the L2 or L1 genesis block given a hardhat and a deploy config.

* state-surgery: some comments

* state-surgery: delete dead comment
parent bfa22916
......@@ -420,6 +420,9 @@ jobs:
docker:
- image: ethereumoptimism/ci-builder:latest
steps:
- restore_cache:
keys:
- v2-cache-yarn-build-{{ .Revision }}
- checkout
- run:
name: Check if we should run
......@@ -709,11 +712,14 @@ workflows:
binary_name: bss-core
working_directory: bss-core
build: false
- state-surgery-tests:
name: state-surgery-tests
requires:
- yarn-monorepo
- geth-tests
- integration-tests
- semgrep-scan
- go-mod-tidy
- state-surgery-tests
- docker-publish:
name: op-node-publish-dev
docker_file: op-node/Dockerfile
......
This diff is collapsed.
......@@ -314,7 +314,7 @@ const config: HardhatUserConfig = {
},
gasPriceOracleScalar: {
type: 'number',
default: 1000_000,
default: 1_000_000,
},
gasPriceOracleDecimals: {
type: 'number',
......
package genesis
import (
"encoding/json"
"math/big"
"os"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/state-surgery/hardhat"
"github.com/ethereum-optimism/optimism/state-surgery/state"
)
// DeployConfig represents the deployment configuration for Optimism
type DeployConfig struct {
L1StartingBlockTag rpc.BlockNumberOrHash `json:"l1StartingBlockTag"`
L1ChainID *big.Int `json:"l1ChainID"`
L2ChainID *big.Int `json:"l2ChainID"`
L2BlockTime uint `json:"l2BlockTime"`
MaxSequencerDrift uint `json:"maxSequencerDrift"`
SequencerWindowSize uint `json:"sequencerWindowSize"`
ChannelTimeout uint `json:"channelTimeout"`
P2PSequencerAddress common.Address `json:"p2pSequencerAddress"`
OptimismL2FeeRecipient common.Address `json:"optimismL2FeeRecipient"`
BatchInboxAddress common.Address `json:"batchInboxAddress"`
BatchSenderAddress common.Address `json:"batchSenderAddress"`
L2OutputOracleSubmissionInterval uint `json:"l2OutputOracleSubmissionInterval"`
L2OutputOracleStartingTimestamp int `json:"l2OutputOracleStartingTimestamp"`
L2OutputOracleProposer common.Address `json:"l2OutputOracleProposer"`
L2OutputOracleOwner common.Address `json:"l2OutputOracleOwner"`
L1BlockTime uint64 `json:"l1BlockTime"`
CliqueSignerAddress common.Address `json:"cliqueSignerAddress"`
OptimismBaseFeeRecipient common.Address `json:"optimismBaseFeeRecipient"`
OptimismL1FeeRecipient common.Address `json:"optimismL1FeeRecipient"`
ProxyAdmin common.Address `json:"proxyAdmin"`
FundDevAccounts bool `json:"fundDevAccounts"`
GasPriceOracleOwner common.Address `json:"gasPriceOracleOwner"`
GasPriceOracleOverhead uint `json:"gasPriceOracleOverhead"`
GasPriceOracleScalar uint `json:"gasPriceOracleScalar"`
GasPriceOracleDecimals uint `json:"gasPriceOracleDecimals"`
L2CrossDomainMessengerOwner common.Address `json:"l2CrossDomainMessengerOwner"`
L2GenesisBlockNonce uint64 `json:"l2GenesisBlockNonce"`
L2GenesisBlockExtraData hexutil.Bytes `json:"l2GenesisBlockExtraData"`
L2GenesisBlockGasLimit uint64 `json:"l2GenesisBlockGasLimit"`
L2GenesisBlockDifficulty *big.Int `json:"l2GenesisBlockDifficulty"`
L2GenesisBlockMixHash common.Hash `json:"l2GenesisBlockMixHash"`
L2GenesisBlockCoinbase common.Address `json:"l2GenesisBlockCoinbase"`
L2GenesisBlockNumber uint64 `json:"l2GenesisBlockNumber"`
L2GenesisBlockGasUsed uint64 `json:"l2GenesisBlockGasUsed"`
L2GenesisBlockParentHash common.Hash `json:"l2GenesisBlockParentHash"`
L2GenesisBlockBaseFeePerGas *big.Int `json:"l2GenesisBlockBaseFeePerGas"`
L1GenesisBlockTimestamp uint64 `json:"l1GenesisBlockTimestamp"`
L1GenesisBlockNonce uint64 `json:"l1GenesisBlockNonce"`
L1GenesisBlockGasLimit uint64 `json:"l1GenesisBlockGasLimit"`
L1GenesisBlockDifficulty *big.Int `json:"l1GenesisBlockDifficulty"`
L1GenesisBlockMixHash common.Hash `json:"l1GenesisBlockMixHash"`
L1GenesisBlockCoinbase common.Address `json:"l1GenesisBlockCoinbase"`
L1GenesisBlockNumber uint64 `json:"l1GenesisBlockNumber"`
L1GenesisBlockGasUsed uint64 `json:"l1GenesisBlockGasUsed"`
L1GenesisBlockParentHash common.Hash `json:"l1GenesisBlockParentHash"`
L1GenesisBlockBaseFeePerGas *big.Int `json:"l1GenesisBlockBaseFeePerGas"`
}
// NewDeployConfig reads a config file given a path on the filesystem.
func NewDeployConfig(path string) (*DeployConfig, error) {
file, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var config DeployConfig
if err := json.Unmarshal(file, &config); err != nil {
return nil, err
}
return &config, nil
}
// StorageConfig represents the storage configuration for the L2 predeploy
// contracts.
type StorageConfig map[string]state.StorageValues
// NewStorageConfig will create a StorageConfig given an instance of a
// Hardhat and a DeployConfig.
func NewStorageConfig(hh *hardhat.Hardhat, config *DeployConfig, chain ethereum.ChainReader) (StorageConfig, error) {
storage := make(StorageConfig)
proxyL1StandardBridge, err := hh.GetDeployment("L1StandardBridgeProxy")
if err != nil {
return storage, err
}
proxyL1CrossDomainMessenger, err := hh.GetDeployment("L1CrossDomainMessengerProxy")
if err != nil {
return storage, err
}
block, err := getBlockFromTag(chain, config.L1StartingBlockTag)
if err != nil {
return storage, err
}
storage["L2ToL1MessagePasser"] = state.StorageValues{
"nonce": 0,
}
storage["L2CrossDomainMessenger"] = state.StorageValues{
"_initialized": 1,
"_owner": config.L2CrossDomainMessengerOwner,
// re-entrency lock
"_status": 1,
"_initializing": false,
"_paused": false,
"xDomainMsgSender": "0x000000000000000000000000000000000000dEaD",
"msgNonce": 0,
"otherMessenger": proxyL1CrossDomainMessenger.Address,
"blockedSystemAddresses": map[any]any{
predeploys.L2CrossDomainMessenger: true,
predeploys.L2ToL1MessagePasser: true,
},
}
storage["GasPriceOracle"] = state.StorageValues{
"_owner": config.GasPriceOracleOwner,
"overhead": config.GasPriceOracleOverhead,
"scalar": config.GasPriceOracleScalar,
"decimals": config.GasPriceOracleDecimals,
}
storage["L2StandardBridge"] = state.StorageValues{
"_initialized": true,
"_initializing": false,
"messenger": predeploys.L2CrossDomainMessenger,
"otherBridge": proxyL1StandardBridge.Address,
}
storage["SequencerFeeVault"] = state.StorageValues{
"l1FeeWallet": config.OptimismL1FeeRecipient,
}
storage["L1Block"] = state.StorageValues{
"number": block.Number(),
"timestamp": block.Time(),
"basefee": block.BaseFee(),
"hash": block.Hash(),
"sequenceNumber": 0,
}
storage["LegacyERC20ETH"] = state.StorageValues{
"bridge": predeploys.L2StandardBridge,
"remoteToken": common.Address{},
"_name": "Ether",
"_symbol": "ETH",
}
storage["WETH9"] = state.StorageValues{
"name": "Wrapped Ether",
"symbol": "WETH",
"decimals": 18,
}
storage["GovernanceToken"] = state.StorageValues{
"_name": "Optimism",
"_symbol": "OP",
"_owner": config.ProxyAdmin,
}
return storage, nil
}
package genesis
import (
"errors"
"math/big"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
)
// NewL2Genesis will create a new L2 genesis
func NewL2Genesis(config *DeployConfig, chain ethereum.ChainReader) (*core.Genesis, error) {
if config.L2ChainID == nil {
return nil, errors.New("must define L2 ChainID")
}
optimismChainConfig := params.ChainConfig{
ChainID: config.L2ChainID,
HomesteadBlock: big.NewInt(0),
DAOForkBlock: nil,
DAOForkSupport: false,
EIP150Block: big.NewInt(0),
EIP150Hash: common.Hash{},
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),
MergeNetsplitBlock: big.NewInt(0),
ShanghaiBlock: nil,
CancunBlock: nil,
TerminalTotalDifficulty: big.NewInt(0),
TerminalTotalDifficultyPassed: true,
Optimism: &params.OptimismConfig{
BaseFeeRecipient: config.OptimismBaseFeeRecipient,
L1FeeRecipient: config.OptimismL2FeeRecipient,
},
}
extraData := config.L2GenesisBlockExtraData
if len(extraData) == 0 {
extraData = common.Hash{}.Bytes()
}
gasLimit := config.L2GenesisBlockGasLimit
if gasLimit == 0 {
gasLimit = uint64(15_000_000)
}
baseFee := config.L2GenesisBlockBaseFeePerGas
if baseFee == nil {
baseFee = big.NewInt(params.InitialBaseFee)
}
difficulty := config.L2GenesisBlockDifficulty
if difficulty == nil {
difficulty = big.NewInt(1)
}
block, err := getBlockFromTag(chain, config.L1StartingBlockTag)
if err != nil {
return nil, err
}
return &core.Genesis{
Config: &optimismChainConfig,
Nonce: config.L2GenesisBlockNonce,
Timestamp: block.Time(),
ExtraData: extraData,
GasLimit: gasLimit,
Difficulty: difficulty,
Mixhash: config.L2GenesisBlockMixHash,
Coinbase: config.L2GenesisBlockCoinbase,
Number: config.L2GenesisBlockNumber,
GasUsed: config.L2GenesisBlockGasUsed,
ParentHash: config.L2GenesisBlockParentHash,
BaseFee: baseFee,
Alloc: map[common.Address]core.GenesisAccount{
common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover
common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256
common.BytesToAddress([]byte{3}): {Balance: big.NewInt(1)}, // RIPEMD
common.BytesToAddress([]byte{4}): {Balance: big.NewInt(1)}, // Identity
common.BytesToAddress([]byte{5}): {Balance: big.NewInt(1)}, // ModExp
common.BytesToAddress([]byte{6}): {Balance: big.NewInt(1)}, // ECAdd
common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
},
}, nil
}
// NewL1Genesis will create a new L1 genesis config
func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) {
if config.L1ChainID == nil {
return nil, errors.New("must define L1 ChainID")
}
chainConfig := *params.AllCliqueProtocolChanges
chainConfig.Clique = &params.CliqueConfig{
Period: config.L1BlockTime,
Epoch: 30000,
}
chainConfig.ChainID = config.L1ChainID
gasLimit := config.L1GenesisBlockGasLimit
if gasLimit == 0 {
gasLimit = uint64(15_000_000)
}
baseFee := config.L1GenesisBlockBaseFeePerGas
if baseFee == nil {
baseFee = big.NewInt(params.InitialBaseFee)
}
difficulty := config.L1GenesisBlockDifficulty
if difficulty == nil {
difficulty = big.NewInt(1)
}
timestamp := config.L1GenesisBlockTimestamp
if timestamp == 0 {
timestamp = uint64(time.Now().Unix())
}
extraData := append(append(make([]byte, 32), config.CliqueSignerAddress[:]...), make([]byte, crypto.SignatureLength)...)
return &core.Genesis{
Config: &chainConfig,
Nonce: config.L1GenesisBlockNonce,
Timestamp: timestamp,
ExtraData: extraData,
GasLimit: gasLimit,
Difficulty: difficulty,
Mixhash: config.L1GenesisBlockMixHash,
Coinbase: config.L1GenesisBlockCoinbase,
Number: config.L1GenesisBlockNumber,
GasUsed: config.L1GenesisBlockGasUsed,
ParentHash: config.L1GenesisBlockParentHash,
BaseFee: baseFee,
Alloc: map[common.Address]core.GenesisAccount{
common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover
common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256
common.BytesToAddress([]byte{3}): {Balance: big.NewInt(1)}, // RIPEMD
common.BytesToAddress([]byte{4}): {Balance: big.NewInt(1)}, // Identity
common.BytesToAddress([]byte{5}): {Balance: big.NewInt(1)}, // ModExp
common.BytesToAddress([]byte{6}): {Balance: big.NewInt(1)}, // ECAdd
common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
},
}, nil
}
package genesis
import (
"bytes"
"context"
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/state-surgery/hardhat"
"github.com/ethereum-optimism/optimism/state-surgery/immutables"
"github.com/ethereum-optimism/optimism/state-surgery/state"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/rpc"
)
var (
// codeNamespace represents the namespace of implementations of predeploys
codeNamespace = common.HexToAddress("0xc0D3C0d3C0d3C0D3c0d3C0d3c0D3C0d3c0d30000")
// predeployNamespace represents the namespace of predeploys
predeployNamespace = common.HexToAddress("0x4200000000000000000000000000000000000000")
// bigPredeployNamespace represents the predeploy namespace as a big.Int
bigPredeployNamespace = new(big.Int).SetBytes(predeployNamespace.Bytes())
// bigCodeNamespace represents the predeploy namespace as a big.Int
bigCodeNameSpace = new(big.Int).SetBytes(codeNamespace.Bytes())
// implementationSlot represents the EIP 1967 implementation storage slot
ImplementationSlot = common.HexToHash("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")
// implementationSlot represents the EIP 1967 admin storage slot
AdminSlot = common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")
)
// DevAccounts represent the standard hardhat development accounts.
// These are funded if the deploy config has funding development
// accounts enabled.
var DevAccounts = []common.Address{
common.HexToAddress("0x14dC79964da2C08b23698B3D3cc7Ca32193d9955"),
common.HexToAddress("0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65"),
common.HexToAddress("0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec"),
common.HexToAddress("0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f"),
common.HexToAddress("0x2546BcD3c84621e976D8185a91A922aE77ECEc30"),
common.HexToAddress("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"),
common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"),
common.HexToAddress("0x71bE63f3384f5fb98995898A86B02Fb2426c5788"),
common.HexToAddress("0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199"),
common.HexToAddress("0x90F79bf6EB2c4f870365E785982E1f101E93b906"),
common.HexToAddress("0x976EA74026E726554dB657fA54763abd0C3a0aa9"),
common.HexToAddress("0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc"),
common.HexToAddress("0xBcd4042DE499D14e55001CcbB24a551F3b954096"),
common.HexToAddress("0xFABB0ac9d68B0B445fB7357272Ff202C5651694a"),
common.HexToAddress("0xa0Ee7A142d267C1f36714E4a8F75612F20a79720"),
common.HexToAddress("0xbDA5747bFD65F08deb54cb465eB87D40e51B197E"),
common.HexToAddress("0xcd3B766CCDd6AE721141F452C550Ca635964ce71"),
common.HexToAddress("0xdD2FD4581271e230360230F9337D5c0430Bf44C0"),
common.HexToAddress("0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097"),
common.HexToAddress("0xde3829a23df1479438622a08a116e8eb3f620bb5"),
common.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"),
}
// The devBalance is the amount of wei that a dev account is funded with.
var devBalance = hexutil.MustDecodeBig("0x200000000000000000000000000000000000000000000000000000000000000")
// FundDevAccounts will fund each of the development accounts.
func FundDevAccounts(db vm.StateDB) {
for _, account := range DevAccounts {
db.CreateAccount(account)
db.AddBalance(account, devBalance)
}
}
// SetProxies will set each of the proxies in the state. It requires
// a Proxy and ProxyAdmin deployment present so that the Proxy bytecode
// can be set in state and the ProxyAdmin can be set as the admin of the
// Proxy.
func SetProxies(hh *hardhat.Hardhat, db vm.StateDB) error {
proxy, err := hh.GetArtifact("Proxy")
if err != nil {
return err
}
proxyAdmin, err := hh.GetDeployment("ProxyAdmin")
if err != nil {
return err
}
for i := uint64(0); i <= 2048; i++ {
bigAddr := new(big.Int).Or(bigPredeployNamespace, new(big.Int).SetUint64(i))
addr := common.BigToAddress(bigAddr)
// There is no proxy at the governance token address
if addr == predeploys.GovernanceTokenAddr {
continue
}
db.CreateAccount(addr)
db.SetCode(addr, proxy.DeployedBytecode)
db.SetState(addr, AdminSlot, proxyAdmin.Address.Hash())
}
return nil
}
// SetImplementations will set the implmentations of the contracts in the state
// and configure the proxies to point to the implementations. It also sets
// the appropriate storage values for each contract at the proxy address.
func SetImplementations(hh *hardhat.Hardhat, db vm.StateDB, storage StorageConfig) error {
deployResults, err := immutables.BuildOptimism()
if err != nil {
return err
}
for name, address := range predeploys.Predeploys {
// Get the hardhat artifact to access the deployed bytecode
artifact, err := hh.GetArtifact(name)
if err != nil {
return err
}
// Convert the address to the code address
var addr common.Address
switch *address {
case predeploys.GovernanceTokenAddr:
addr = predeploys.GovernanceTokenAddr
case predeploys.LegacyERC20ETHAddr:
addr = predeploys.LegacyERC20ETHAddr
default:
addr, err = AddressToCodeNamespace(*address)
if err != nil {
return err
}
// Set the implmentation slot in the predeploy proxy
db.SetState(*address, ImplementationSlot, addr.Hash())
}
// Create the account
db.CreateAccount(addr)
// Use the genrated bytecode when there are immutables
// otherwise use the artifact deployed bytecode
if bytecode, ok := deployResults[name]; ok {
db.SetCode(addr, bytecode)
} else {
db.SetCode(addr, artifact.DeployedBytecode)
}
// Set the storage values
if storageConfig, ok := storage[name]; ok {
layout, err := hh.GetStorageLayout(name)
if err != nil {
return err
}
slots, err := state.ComputeStorageSlots(layout, storageConfig)
if err != nil {
return err
}
// The storage values must go in the proxy address
for _, slot := range slots {
db.SetState(*address, slot.Key, slot.Value)
}
}
code := db.GetCode(addr)
if len(code) == 0 {
return fmt.Errorf("code not set for %s", name)
}
}
return nil
}
// AddressToCodeNamespace takes a predeploy address and computes
// the implmentation address that the implementation should be deployed at
func AddressToCodeNamespace(addr common.Address) (common.Address, error) {
bytesAddr := addr.Bytes()
if !bytes.Equal(bytesAddr[0:2], []byte{0x42, 0x00}) {
return common.Address{}, fmt.Errorf("cannot handle non predeploy: %s", addr)
}
bigAddress := new(big.Int).SetBytes(bytesAddr[18:])
num := new(big.Int).Or(bigCodeNameSpace, bigAddress)
return common.BigToAddress(num), nil
}
// Get the storage layout of the L2ToL1MessagePasser
// Iterate over the storage layout to know which storage slots to ignore
// Iterate over each storage slot, compute the migration
func MigrateDepositHashes(hh *hardhat.Hardhat, db vm.StateDB) error {
layout, err := hh.GetStorageLayout("L2ToL1MessagePasser")
if err != nil {
return err
}
// Build a list of storage slots to ignore. The values in the
// mapping are guaranteed to not be in this list because they are
// hashes.
ignore := make(map[common.Hash]bool)
for _, entry := range layout.Storage {
encoded, err := state.EncodeUintValue(entry.Slot, 0)
if err != nil {
return err
}
ignore[encoded] = true
}
db.ForEachStorage(predeploys.L2ToL1MessagePasserAddr, func(key, value common.Hash) bool {
if _, ok := ignore[key]; ok {
return true
}
// TODO(tynes): Do the value migration here
return true
})
return nil
}
// getBlockFromTag will resolve a Block given an rpc block tag
func getBlockFromTag(chain ethereum.ChainReader, tag rpc.BlockNumberOrHash) (*types.Block, error) {
if hash, ok := tag.Hash(); ok {
block, err := chain.BlockByHash(context.Background(), hash)
if err != nil {
return nil, err
}
return block, nil
} else if num, ok := tag.Number(); ok {
blockNumber := new(big.Int).SetInt64(num.Int64())
block, err := chain.BlockByNumber(context.Background(), blockNumber)
if err != nil {
return nil, err
}
return block, nil
} else {
return nil, fmt.Errorf("invalid block tag: %v", tag)
}
}
package genesis
import (
"github.com/ethereum-optimism/optimism/state-surgery/hardhat"
"github.com/ethereum-optimism/optimism/state-surgery/state"
"github.com/ethereum/go-ethereum/core"
)
// TODO(tynes): need bindings for all of the L1 contracts
func BuildL1DeveloperGenesis(hh *hardhat.Hardhat, config *DeployConfig) (*core.Genesis, error) {
genesis, err := NewL1Genesis(config)
if err != nil {
return nil, err
}
db := state.NewMemoryStateDB(genesis)
if config.FundDevAccounts {
FundDevAccounts(db)
}
return db.Genesis(), nil
}
package genesis
import (
"github.com/ethereum-optimism/optimism/state-surgery/hardhat"
"github.com/ethereum-optimism/optimism/state-surgery/state"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/core"
)
// BuildOptimismGenesis
func BuildOptimismGenesis(hh *hardhat.Hardhat, config *DeployConfig, chain ethereum.ChainReader) (*core.Genesis, error) {
genesis, err := NewL2Genesis(config, chain)
if err != nil {
return nil, err
}
db := state.NewMemoryStateDB(genesis)
if config.FundDevAccounts {
FundDevAccounts(db)
}
// TODO(tynes): need a function for clearing old, unused storage slots.
// Each deployed contract on L2 needs to have its existing storage
// inspected and then cleared if they are no longer used.
if err := SetProxies(hh, db); err != nil {
return nil, err
}
storage, err := NewStorageConfig(hh, config, chain)
if err != nil {
return nil, err
}
if err := SetImplementations(hh, db, storage); err != nil {
return nil, err
}
if err := MigrateDepositHashes(hh, db); err != nil {
return nil, err
}
return db.Genesis(), nil
}
package genesis_test
import (
"encoding/json"
"flag"
"io/ioutil"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/state-surgery/genesis"
"github.com/ethereum-optimism/optimism/state-surgery/hardhat"
"github.com/stretchr/testify/require"
)
var writeFile bool
func init() {
flag.BoolVar(&writeFile, "write-file", false, "write the genesis file to disk")
}
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
func TestBuildOptimismGenesis(t *testing.T) {
hh, err := hardhat.New(
"goerli",
[]string{
"../../packages/contracts-bedrock/artifacts",
"../../packages/contracts-governance/artifacts",
},
[]string{"../../packages/contracts-bedrock/deployments"},
)
require.Nil(t, err)
config, err := genesis.NewDeployConfig("../../packages/contracts-bedrock/deploy-config/devnetL1.json")
require.Nil(t, err)
backend := backends.NewSimulatedBackend(
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
},
15000000,
)
gen, err := genesis.BuildOptimismGenesis(hh, config, backend)
require.Nil(t, err)
require.NotNil(t, gen)
proxyAdmin, err := hh.GetDeployment("ProxyAdmin")
require.Nil(t, err)
for name, address := range predeploys.Predeploys {
addr := *address
account, ok := gen.Alloc[addr]
require.Equal(t, ok, true)
require.Greater(t, len(account.Code), 0)
if name == "GovernanceToken" || name == "LegacyERC20ETH" {
continue
}
adminSlot, ok := account.Storage[genesis.AdminSlot]
require.Equal(t, ok, true)
require.Equal(t, adminSlot, proxyAdmin.Address.Hash())
}
if writeFile {
file, _ := json.MarshalIndent(gen, "", " ")
_ = ioutil.WriteFile("genesis.json", file, 0644)
}
}
......@@ -31,13 +31,13 @@ func EncodeStorageKeyValue(value any, entry solc.StorageLayoutEntry, storageType
case "bool":
val, err := EncodeBoolValue(value, entry.Offset)
if err != nil {
return nil, fmt.Errorf("%s bool invalid: %w", entry.Label, err)
return nil, fmt.Errorf("cannot encode %s: %w", storageType.Encoding, err)
}
encoded = append(encoded, &EncodedStorage{key, val})
case "address":
val, err := EncodeAddressValue(value, entry.Offset)
if err != nil {
return nil, fmt.Errorf("%s address invalid: %w", entry.Label, err)
return nil, fmt.Errorf("cannot encode %s: %w", storageType.Encoding, err)
}
encoded = append(encoded, &EncodedStorage{key, val})
case "bytes":
......@@ -45,7 +45,7 @@ func EncodeStorageKeyValue(value any, entry solc.StorageLayoutEntry, storageType
case "bytes32":
val, err := EncodeBytes32Value(value, entry.Offset)
if err != nil {
return nil, err
return nil, fmt.Errorf("cannot encode %s: %w", storageType.Encoding, err)
}
encoded = append(encoded, &EncodedStorage{key, val})
default:
......@@ -53,18 +53,18 @@ func EncodeStorageKeyValue(value any, entry solc.StorageLayoutEntry, storageType
case strings.HasPrefix(label, "contract"):
val, err := EncodeAddressValue(value, entry.Offset)
if err != nil {
return nil, fmt.Errorf("%s address invalid: %w", entry.Label, err)
return nil, fmt.Errorf("cannot encode %s: %w", storageType.Encoding, err)
}
encoded = append(encoded, &EncodedStorage{key, val})
case strings.HasPrefix(label, "uint"):
val, err := EncodeUintValue(value, entry.Offset)
if err != nil {
return nil, err
return nil, fmt.Errorf("cannot encode %s: %w", storageType.Encoding, err)
}
encoded = append(encoded, &EncodedStorage{key, val})
default:
// structs are not supported
return nil, fmt.Errorf("%w: %s", errUnimplemented, label)
return nil, fmt.Errorf("cannot encode %s: %w", storageType.Encoding, errUnimplemented)
}
}
case "dynamic_array":
......@@ -73,7 +73,7 @@ func EncodeStorageKeyValue(value any, entry solc.StorageLayoutEntry, storageType
case "string":
val, err := EncodeStringValue(value, entry.Offset)
if err != nil {
return nil, err
return nil, fmt.Errorf("cannot encode %s: %w", storageType.Encoding, errUnimplemented)
}
encoded = append(encoded, &EncodedStorage{key, val})
default:
......@@ -86,8 +86,7 @@ func EncodeStorageKeyValue(value any, entry solc.StorageLayoutEntry, storageType
values, ok := value.(map[any]any)
if !ok {
return nil, fmt.Errorf("cannot parse mapping")
return nil, fmt.Errorf("mapping must be map[any]any")
}
keyEncoder, err := getElementEncoder(storageType, "key")
......@@ -122,7 +121,7 @@ func EncodeStorageKeyValue(value any, entry solc.StorageLayoutEntry, storageType
encoded = append(encoded, &EncodedStorage{key, val})
}
default:
return nil, fmt.Errorf("unknown encoding: %s", storageType.Encoding)
return nil, fmt.Errorf("unknown encoding %s: %w", storageType.Encoding, errUnimplemented)
}
return encoded, nil
}
......@@ -268,7 +267,7 @@ func encodeStringValue(value any) (common.Hash, error) {
func EncodeBoolValue(value any, offset uint) (common.Hash, error) {
val, err := encodeBoolValue(value)
if err != nil {
return common.Hash{}, err
return common.Hash{}, fmt.Errorf("invalid bool: %w", err)
}
return handleOffset(val, offset), nil
}
......@@ -308,7 +307,7 @@ func encodeBoolValue(value any) (common.Hash, error) {
func EncodeAddressValue(value any, offset uint) (common.Hash, error) {
val, err := encodeAddressValue(value)
if err != nil {
return common.Hash{}, err
return common.Hash{}, fmt.Errorf("invalid address: %w", err)
}
return handleOffset(val, offset), nil
}
......@@ -352,7 +351,7 @@ func encodeAddressValue(value any) (common.Hash, error) {
func EncodeUintValue(value any, offset uint) (common.Hash, error) {
val, err := encodeUintValue(value)
if err != nil {
return common.Hash{}, err
return common.Hash{}, fmt.Errorf("invalid uint: %w", err)
}
return handleOffset(val, offset), nil
}
......@@ -430,6 +429,16 @@ func encodeUintValue(value any) (common.Hash, error) {
}
}
return common.BigToHash(number), nil
case "bool":
val, ok := value.(bool)
if !ok {
return common.Hash{}, errInvalidType
}
if val {
return common.Hash{31: 0x01}, nil
} else {
return common.Hash{}, nil
}
case "Int":
val, ok := value.(*big.Int)
if !ok {
......
......@@ -24,7 +24,7 @@ type EncodedStorage struct {
// EncodedStorage will encode a storage layout
func EncodeStorage(entry solc.StorageLayoutEntry, value any, storageType solc.StorageLayoutType) ([]*EncodedStorage, error) {
if storageType.NumberOfBytes > 32 {
return nil, fmt.Errorf("%s is larger than 32 bytes", entry.Label)
return nil, fmt.Errorf("%s is larger than 32 bytes", storageType.Encoding)
}
encoded, err := EncodeStorageKeyValue(value, entry, storageType)
......@@ -60,7 +60,7 @@ func ComputeStorageSlots(layout *solc.StorageLayout, values StorageValues) ([]*E
storage, err := EncodeStorage(target, value, storageType)
if err != nil {
return nil, fmt.Errorf("cannot encode storage: %w", err)
return nil, fmt.Errorf("cannot encode storage for %s: %w", target.Label, err)
}
encodedStorage = append(encodedStorage, storage...)
......
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