Commit cceb207a authored by protolambda's avatar protolambda Committed by GitHub

op-chain-ops,op-node: ecotone upgrade txs, fix l1-fee scalar migration (#8981)

* op-chain-ops,op-node: ecotone upgrade txs, fix l1-fee scalar migration
Co-authored-by: default avatarDanyal Prout <me@dany.al>
Co-authored-by: default avatarprotolambda <proto@protolambda.com>
Co-authored-by: default avatarEvanJRichard <evan@oplabs.co>

* op-chain-ops: execute ecotone upgrade txs on L2 genesis generation

* op-e2e: update fees test to cover regolith and ecotone fees incl. strict GPO comparison

* op-chain-ops: add StateDB interface assertion

* ecotone-upgrade: implement version naming review suggestion

* op-node: clean up system config scalars parsing, add unit tests

* op-service: fix version name

* op-node: improve L1info to sys-config reverse translation step

* op-node: fix sys-config ecotone scalars getter refactor bug

---------
Co-authored-by: default avatarDanyal Prout <me@dany.al>
Co-authored-by: default avatarEvanJRichard <evan@oplabs.co>
parent 9402ec64
package predeploys
import "github.com/ethereum/go-ethereum/common"
// EIP-4788 defines a deterministic deployment transaction that deploys the beacon-block-roots contract.
// To embed the contract in genesis, we want the deployment-result, not the contract-creation tx input code.
// Since the contract deployment result is deterministic and the same across every chain,
// the bytecode can be easily verified by comparing it with chains like Goerli.
// During deployment it does not modify any contract storage, the storage starts empty.
// See https://goerli.etherscan.io/tx/0xdf52c2d3bbe38820fff7b5eaab3db1b91f8e1412b56497d88388fb5d4ea1fde0
// And https://eips.ethereum.org/EIPS/eip-4788
var (
EIP4788ContractAddr = common.HexToAddress("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02")
EIP4788ContractCode = common.Hex2Bytes("0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500")
)
......@@ -233,7 +233,7 @@ type DeployConfig struct {
RecommendedProtocolVersion params.ProtocolVersion `json:"recommendedProtocolVersion"`
// When Cancun activates. Relative to L1 genesis.
L1CancunTimeOffset *uint64 `json:"l1CancunTimeOffset,omitempty"`
L1CancunTimeOffset *hexutil.Uint64 `json:"l1CancunTimeOffset,omitempty"`
}
// Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy
......
......@@ -155,6 +155,7 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) {
chainConfig.TerminalTotalDifficulty = big.NewInt(0)
chainConfig.TerminalTotalDifficultyPassed = true
chainConfig.ShanghaiTime = u64ptr(0)
chainConfig.CancunTime = u64ptr(0)
}
gasLimit := config.L1GenesisBlockGasLimit
......@@ -174,7 +175,7 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) {
timestamp = hexutil.Uint64(time.Now().Unix())
}
if !config.L1UseClique && config.L1CancunTimeOffset != nil {
cancunTime := uint64(timestamp) + *config.L1CancunTimeOffset
cancunTime := uint64(timestamp) + uint64(*config.L1CancunTimeOffset)
chainConfig.CancunTime = &cancunTime
}
......
......@@ -13,7 +13,9 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer"
"github.com/ethereum-optimism/optimism/op-chain-ops/immutables"
"github.com/ethereum-optimism/optimism/op-chain-ops/squash"
"github.com/ethereum-optimism/optimism/op-chain-ops/state"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-service/eth"
)
......@@ -112,5 +114,25 @@ func BuildL2Genesis(config *DeployConfig, l1StartBlock *types.Block) (*core.Gene
}
}
if err := PerformUpgradeTxs(db); err != nil {
return nil, fmt.Errorf("failed to perform upgrade txs: %w", err)
}
return db.Genesis(), nil
}
func PerformUpgradeTxs(db *state.MemoryStateDB) error {
// Only the Ecotone upgrade is performed with upgrade-txs.
if !db.Genesis().Config.IsEcotone(db.Genesis().Timestamp) {
return nil
}
sim := squash.NewSimulator(db)
ecotone, err := derive.EcotoneNetworkUpgradeTransactions()
if err != nil {
return fmt.Errorf("failed to build ecotone upgrade txs: %w", err)
}
if err := sim.AddUpgradeTxs(ecotone); err != nil {
return fmt.Errorf("failed to apply ecotone upgrade txs: %w", err)
}
return nil
}
package squash
import (
"encoding/binary"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/state"
)
type staticChain struct {
startTime uint64
blockTime uint64
}
func (d *staticChain) Engine() consensus.Engine {
return ethash.NewFullFaker()
}
func (d *staticChain) GetHeader(h common.Hash, n uint64) *types.Header {
parentHash := common.Hash{0: 0xff}
binary.BigEndian.PutUint64(parentHash[1:], n-1)
return &types.Header{
ParentHash: parentHash,
UncleHash: types.EmptyUncleHash,
Coinbase: common.Address{},
Root: common.Hash{},
TxHash: types.EmptyTxsHash,
ReceiptHash: types.EmptyReceiptsHash,
Bloom: types.Bloom{},
Difficulty: big.NewInt(0),
Number: new(big.Int).SetUint64(n),
GasLimit: 30_000_000,
GasUsed: 0,
Time: d.startTime + n*d.blockTime,
Extra: nil,
MixDigest: common.Hash{},
Nonce: types.BlockNonce{},
BaseFee: big.NewInt(7),
WithdrawalsHash: &types.EmptyWithdrawalsHash,
}
}
type simState struct {
*state.MemoryStateDB
snapshotIndex int
tempAccessList map[common.Address]map[common.Hash]struct{}
}
var _ vm.StateDB = (*simState)(nil)
func (db *simState) AddressInAccessList(addr common.Address) bool {
_, ok := db.tempAccessList[addr]
return ok
}
func (db *simState) AddLog(log *types.Log) {
// no-op
}
func (db *simState) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
// return the latest state, instead of the pre-tx state.
acc, ok := db.Genesis().Alloc[addr]
if !ok {
return common.Hash{}
}
return acc.Storage[hash]
}
func (db *simState) AddSlotToAccessList(addr common.Address, slot common.Hash) {
// things like the fee-vault-address get marked as warm
m, ok := db.tempAccessList[addr]
if !ok {
m = make(map[common.Hash]struct{})
db.tempAccessList[addr] = m
}
m[slot] = struct{}{}
}
func (db *simState) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) {
m, addressOk := db.tempAccessList[addr]
if !addressOk {
return false, false
}
_, slotOk = m[slot]
return true, slotOk
}
func (db *simState) GetRefund() uint64 {
return 0
}
func (db *simState) AddAddressToAccessList(addr common.Address) {
if _, ok := db.tempAccessList[addr]; !ok {
db.tempAccessList[addr] = make(map[common.Hash]struct{})
}
}
func (db *simState) RevertToSnapshot(int) {
panic("RevertToSnapshot not supported")
}
func (db *simState) Snapshot() int {
db.snapshotIndex += 1
return db.snapshotIndex
}
// SquashSim wraps an op-chain-ops MemporyStateDB,
// and applies EVM-messages as if they all exist in the same infinite EVM block.
// The result is squashing all the EVM execution changes into the state.
type SquashSim struct {
chainConfig *params.ChainConfig
state *simState
evm *vm.EVM
signer types.Signer
}
// AddMessage processes a message on top of the chain-state that is squashed into a genesis state allocation.
func (sim *SquashSim) AddMessage(msg *core.Message) (res *core.ExecutionResult, err error) {
defer func() {
if rErr := recover(); rErr != nil {
err = fmt.Errorf("critical error: %v", rErr)
}
}()
// reset access-list
sim.state.tempAccessList = make(map[common.Address]map[common.Hash]struct{})
gp := new(core.GasPool)
gp.AddGas(30_000_000)
rules := sim.evm.ChainConfig().Rules(sim.evm.Context.BlockNumber, true, sim.evm.Context.Time)
sim.evm.StateDB.Prepare(rules, msg.From, predeploys.SequencerFeeVaultAddr, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
if !sim.state.Exist(msg.From) {
sim.state.CreateAccount(msg.From)
}
return core.ApplyMessage(sim.evm, msg, gp)
}
func (sim *SquashSim) BlockContext() *vm.BlockContext {
return &sim.evm.Context
}
// AddUpgradeTxs traverses a list of encoded deposit transactions.
// These transactions should match what would be included in the live system upgrade.
// The resulting state changes are squashed together, such that the final state can then be used as genesis state.
func (sim *SquashSim) AddUpgradeTxs(txs []hexutil.Bytes) error {
for i, otx := range txs {
var tx types.Transaction
if err := tx.UnmarshalBinary(otx); err != nil {
return fmt.Errorf("failed to decode upgrade tx %d: %w", i, err)
}
msg, err := core.TransactionToMessage(&tx, sim.signer, sim.BlockContext().BaseFee)
if err != nil {
return fmt.Errorf("failed to turn upgrade tx %d into message: %w", i, err)
}
if !msg.IsDepositTx {
return fmt.Errorf("upgrade tx %d is not a depost", i)
}
if res, err := sim.AddMessage(msg); err != nil {
return fmt.Errorf("invalid upgrade tx %d, EVM invocation failed: %w", i, err)
} else {
if res.Err != nil {
return fmt.Errorf("failed to successfully execute upgrade tx %d: %w", i, err)
}
}
}
return nil
}
func NewSimulator(db *state.MemoryStateDB) *SquashSim {
offsetBlocks := uint64(0)
genesisTime := uint64(17_000_000)
blockTime := uint64(2)
bc := &staticChain{startTime: genesisTime, blockTime: blockTime}
header := bc.GetHeader(common.Hash{}, genesisTime+offsetBlocks)
chainCfg := db.Genesis().Config
blockContext := core.NewEVMBlockContext(header, bc, nil, chainCfg, db)
vmCfg := vm.Config{}
signer := types.LatestSigner(db.Genesis().Config)
simDB := &simState{MemoryStateDB: db}
env := vm.NewEVM(blockContext, vm.TxContext{}, simDB, chainCfg, vmCfg)
return &SquashSim{
chainConfig: chainCfg,
state: simDB,
evm: env,
signer: signer,
}
}
......@@ -3,18 +3,20 @@ package actions
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-service/testlog"
)
func TestDencunL1Fork(gt *testing.T) {
func TestDencunL1ForkAfterGenesis(gt *testing.T) {
t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
offset := uint64(24)
offset := hexutil.Uint64(24)
dp.DeployConfig.L1CancunTimeOffset = &offset
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug)
......@@ -57,7 +59,7 @@ func TestDencunL1Fork(gt *testing.T) {
func TestDencunL1ForkAtGenesis(gt *testing.T) {
t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
offset := uint64(0)
offset := hexutil.Uint64(0)
dp.DeployConfig.L1CancunTimeOffset = &offset
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug)
......
package actions
import (
"context"
"math/big"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"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/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"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-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-service/testlog"
)
var (
l1BlockCodeHash = common.FromHex("0xc88a313aa75dc4fbf0b6850d9f9ae41e04243b7008cf3eadb29256d4a71c1dfd")
gasPriceOracleCodeHash = common.FromHex("0x8b71360ea773b4cfaf1ae6d2bd15464a4e1e2e360f786e475f63aeaed8da0ae5")
)
func verifyCodeHashMatches(t *testing.T, client *ethclient.Client, address common.Address, expectedCodeHash []byte) {
code, err := client.CodeAt(context.Background(), address, nil)
require.NoError(t, err)
codeHash := crypto.Keccak256Hash(code)
require.Equal(t, expectedCodeHash, codeHash.Bytes())
}
func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) {
t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
genesisBlock := hexutil.Uint64(0)
ecotoneOffset := hexutil.Uint64(2)
dp.DeployConfig.L1CancunTimeOffset = &genesisBlock // can be removed once Cancun on L1 is the default
// Activate all forks at genesis, and schedule Ecotone the block after
dp.DeployConfig.L2GenesisRegolithTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisCanyonTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisDeltaTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisEcotoneTimeOffset = &ecotoneOffset
require.NoError(t, dp.DeployConfig.Check(), "must have valid config")
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug)
_, _, miner, sequencer, engine, verifier, _, _ := setupReorgTestActors(t, dp, sd, log)
// start op-nodes
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
// Get gas price from oracle
gasPriceOracle, err := bindings.NewGasPriceOracleCaller(predeploys.GasPriceOracleAddr, engine.EthClient())
require.NoError(t, err)
scalar, err := gasPriceOracle.Scalar(nil)
require.NoError(t, err)
require.True(t, scalar.Cmp(big.NewInt(0)) > 0, "scalar must start non-zero")
require.True(t, scalar.Cmp(new(big.Int).SetUint64(dp.DeployConfig.GasPriceOracleScalar)) == 0, "must match deploy config")
// Get current implementations addresses (by slot) for L1Block + GasPriceOracle
initialGasPriceOracleAddress, err := engine.EthClient().StorageAt(context.Background(), predeploys.GasPriceOracleAddr, genesis.ImplementationSlot, nil)
require.NoError(t, err)
initialL1BlockAddress, err := engine.EthClient().StorageAt(context.Background(), predeploys.L1BlockAddr, genesis.ImplementationSlot, nil)
require.NoError(t, err)
// Build to the ecotone block
sequencer.ActBuildL2ToEcotone(t)
// get latest block
latestBlock, err := engine.EthClient().BlockByNumber(context.Background(), nil)
require.NoError(t, err)
require.Equal(t, sequencer.L2Unsafe().Number, latestBlock.Number().Uint64())
transactions := latestBlock.Transactions()
// L1Block: 1 set-L1-info + 2 deploys + 2 upgradeTo + 1 enable ecotone on GPO + 1 4788 deploy
// See [derive.EcotoneNetworkUpgradeTransactions]
require.Equal(t, 7, len(transactions))
l1Info, err := derive.L1BlockInfoFromBytes(sd.RollupCfg, latestBlock.Time(), transactions[0].Data())
require.NoError(t, err)
require.Equal(t, derive.L1InfoBedrockLen, len(transactions[0].Data()))
require.Nil(t, l1Info.BlobBaseFee)
// All transactions are successful
for i := 1; i < 7; i++ {
txn := transactions[i]
receipt, err := engine.EthClient().TransactionReceipt(context.Background(), txn.Hash())
require.NoError(t, err)
require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status)
}
expectedL1BlockAddress := crypto.CreateAddress(derive.L1BlockDeployerAddress, 0)
expectedGasPriceOracleAddress := crypto.CreateAddress(derive.GasPriceOracleDeployerAddress, 0)
// Gas Price Oracle Proxy is updated
updatedGasPriceOracleAddress, err := engine.EthClient().StorageAt(context.Background(), predeploys.GasPriceOracleAddr, genesis.ImplementationSlot, latestBlock.Number())
require.NoError(t, err)
assert.Equal(t, expectedGasPriceOracleAddress, common.BytesToAddress(updatedGasPriceOracleAddress))
assert.NotEqualf(t, initialGasPriceOracleAddress, updatedGasPriceOracleAddress, "Gas Price Oracle Proxy address should have changed")
verifyCodeHashMatches(gt, engine.EthClient(), expectedGasPriceOracleAddress, gasPriceOracleCodeHash)
// L1Block Proxy is updated
updatedL1BlockAddress, err := engine.EthClient().StorageAt(context.Background(), predeploys.L1BlockAddr, genesis.ImplementationSlot, latestBlock.Number())
require.NoError(t, err)
assert.Equal(t, expectedL1BlockAddress, common.BytesToAddress(updatedL1BlockAddress))
assert.NotEqualf(t, initialL1BlockAddress, updatedL1BlockAddress, "L1Block Proxy address should have changed")
verifyCodeHashMatches(gt, engine.EthClient(), expectedL1BlockAddress, l1BlockCodeHash)
_, err = gasPriceOracle.Scalar(nil)
require.ErrorContains(t, err, "scalar() is deprecated")
cost, err := gasPriceOracle.GetL1Fee(nil, []byte{0, 1, 2, 3, 4})
require.NoError(t, err)
// Pre-ecotone the GPO getL1Fee contract erroneously returned the pre-regolith L1 fee.
// Thus we do not assert the exact value here.
require.Equal(t, cost.Uint64(), uint64(0), "expecting zero scalars within activation block")
// Check that Ecotone was activated
isEcotone, err := gasPriceOracle.IsEcotone(nil)
require.NoError(t, err)
require.True(t, isEcotone)
// 4788 contract is deployed
expected4788Address := crypto.CreateAddress(derive.EIP4788From, 0)
code, err := engine.EthClient().CodeAt(context.Background(), expected4788Address, latestBlock.Number())
require.NoError(t, err)
require.Equal(t, code, predeploys.EIP4788ContractCode)
// Build empty L2 block, to pass ecotone activation
sequencer.ActL2StartBlock(t)
sequencer.ActL2EndBlock(t)
// assert again, now that we are past activation
_, err = gasPriceOracle.Scalar(nil)
require.ErrorContains(t, err, "scalar() is deprecated")
// test if the migrated scalar matches the deploy config
basefeeScalar, err := gasPriceOracle.BaseFeeScalar(nil)
require.NoError(t, err)
require.True(t, uint64(basefeeScalar) == dp.DeployConfig.GasPriceOracleScalar, "must match deploy config")
cost, err = gasPriceOracle.GetL1Fee(nil, []byte{0, 1, 2, 3, 4})
require.NoError(t, err)
require.Greater(t, cost.Uint64(), uint64(0), "expecting non-zero scalars after activation block")
// Get L1Block info
l1Block, err := bindings.NewL1BlockCaller(predeploys.L1BlockAddr, engine.EthClient())
require.NoError(t, err)
l1BlockInfo, err := l1Block.Timestamp(nil)
require.NoError(t, err)
assert.Greater(t, l1BlockInfo, uint64(0))
l1OriginBlock, err := miner.EthClient().BlockByHash(context.Background(), sequencer.L2Unsafe().L1Origin.Hash)
require.NoError(t, err)
l1Basefee, err := l1Block.Basefee(nil)
require.NoError(t, err)
require.Equal(t, l1OriginBlock.BaseFee().Uint64(), l1Basefee.Uint64(), "basefee must match")
// calldataGas*(l1BaseFee*16*l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar)/16e6
// _getCalldataGas in GPO adds the cost of 68 non-zero bytes for signature/rlp overhead.
calldataGas := big.NewInt(4*16 + 1*4 + 68*16)
expectedL1Fee := new(big.Int).Mul(calldataGas, l1Basefee)
expectedL1Fee = expectedL1Fee.Mul(expectedL1Fee, big.NewInt(16))
expectedL1Fee = expectedL1Fee.Mul(expectedL1Fee, new(big.Int).SetUint64(uint64(basefeeScalar)))
expectedL1Fee = expectedL1Fee.Div(expectedL1Fee, big.NewInt(16e6))
require.Equal(t, expectedL1Fee, cost, "expecting cost based on regular base fee scalar alone")
}
......@@ -163,3 +163,11 @@ func (s *L2Sequencer) ActBuildL2ToTime(t Testing, target uint64) {
s.ActL2EndBlock(t)
}
}
func (s *L2Sequencer) ActBuildL2ToEcotone(t Testing) {
require.NotNil(t, s.rollupCfg.EcotoneTime, "cannot activate Ecotone when it is not scheduled")
for s.L2Unsafe().Time < *s.rollupCfg.EcotoneTime {
s.ActL2StartBlock(t)
s.ActL2EndBlock(t)
}
}
......@@ -161,7 +161,7 @@ func TestSystemE2EDencunAtGenesis(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t)
genesisActivation := uint64(0)
genesisActivation := hexutil.Uint64(0)
cfg.DeployConfig.L1CancunTimeOffset = &genesisActivation
sys, err := cfg.Start(t)
......@@ -179,7 +179,7 @@ func TestSystemE2EDencunAtGenesisWithBlobs(t *testing.T) {
cfg := DefaultSystemConfig(t)
//cancun is on from genesis:
genesisActivation := uint64(0)
genesisActivation := hexutil.Uint64(0)
cfg.DeployConfig.L1CancunTimeOffset = &genesisActivation // i.e. turn cancun on at genesis time + 0
sys, err := cfg.Start(t)
......@@ -1067,7 +1067,7 @@ func (sga *stateGetterAdapter) GetState(addr common.Address, key common.Hash) co
}
// TestFees checks that L1/L2 fees are handled.
func TestL1Fees(t *testing.T) {
func TestFees(t *testing.T) {
InitParallel(t)
t.Run("pre-regolith", func(t *testing.T) {
......@@ -1078,10 +1078,9 @@ func TestL1Fees(t *testing.T) {
cfg.DeployConfig.L2GenesisCanyonTimeOffset = nil
cfg.DeployConfig.L2GenesisDeltaTimeOffset = nil
cfg.DeployConfig.L2GenesisEcotoneTimeOffset = nil
testL1Fees(t, cfg)
testFees(t, cfg)
})
t.Run("regolith", func(t *testing.T) {
t.Skip("getL1GasUsed in GPO does not support Regolith, it returns the Bedrock L1 cost, incl 68*16 gas overhead")
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L1GenesisBlockBaseFeePerGas = (*hexutil.Big)(big.NewInt(7))
......@@ -1089,10 +1088,9 @@ func TestL1Fees(t *testing.T) {
cfg.DeployConfig.L2GenesisCanyonTimeOffset = nil
cfg.DeployConfig.L2GenesisDeltaTimeOffset = nil
cfg.DeployConfig.L2GenesisEcotoneTimeOffset = nil
testL1Fees(t, cfg)
testFees(t, cfg)
})
t.Run("ecotone", func(t *testing.T) {
t.Skip("when activating Ecotone at Genesis we do not yet call setEcotone() on GPO")
cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L1GenesisBlockBaseFeePerGas = (*hexutil.Big)(big.NewInt(7))
......@@ -1100,11 +1098,11 @@ func TestL1Fees(t *testing.T) {
cfg.DeployConfig.L2GenesisCanyonTimeOffset = new(hexutil.Uint64)
cfg.DeployConfig.L2GenesisDeltaTimeOffset = new(hexutil.Uint64)
cfg.DeployConfig.L2GenesisEcotoneTimeOffset = new(hexutil.Uint64)
testL1Fees(t, cfg)
testFees(t, cfg)
})
}
func testL1Fees(t *testing.T, cfg SystemConfig) {
func testFees(t *testing.T, cfg SystemConfig) {
sys, err := cfg.Start(t)
require.Nil(t, err, "Error starting up system")
......@@ -1135,16 +1133,25 @@ func testL1Fees(t *testing.T, cfg SystemConfig) {
gpoContract, err := bindings.NewGasPriceOracle(predeploys.GasPriceOracleAddr, l2Seq)
require.Nil(t, err)
if !sys.RollupConfig.IsEcotone(sys.L2GenesisCfg.Timestamp) {
overhead, err := gpoContract.Overhead(&bind.CallOpts{})
require.Nil(t, err, "reading gpo overhead")
decimals, err := gpoContract.Decimals(&bind.CallOpts{})
require.Nil(t, err, "reading gpo decimals")
require.Equal(t, overhead.Uint64(), cfg.DeployConfig.GasPriceOracleOverhead, "wrong gpo overhead")
scalar, err := gpoContract.Scalar(&bind.CallOpts{})
require.Nil(t, err, "reading gpo scalar")
require.Equal(t, scalar.Uint64(), cfg.DeployConfig.GasPriceOracleScalar, "wrong gpo scalar")
} else {
_, err := gpoContract.Overhead(&bind.CallOpts{})
require.ErrorContains(t, err, "deprecated")
_, err = gpoContract.Scalar(&bind.CallOpts{})
require.ErrorContains(t, err, "deprecated")
}
decimals, err := gpoContract.Decimals(&bind.CallOpts{})
require.Nil(t, err, "reading gpo decimals")
require.Equal(t, overhead.Uint64(), cfg.DeployConfig.GasPriceOracleOverhead, "wrong gpo overhead")
require.Equal(t, decimals.Uint64(), uint64(6), "wrong gpo decimals")
require.Equal(t, scalar.Uint64(), cfg.DeployConfig.GasPriceOracleScalar, "wrong gpo scalar")
// BaseFee Recipient
baseFeeRecipientStartBalance, err := l2Seq.BalanceAt(context.Background(), predeploys.BaseFeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64()))
......@@ -1227,17 +1234,32 @@ func testL1Fees(t *testing.T, cfg SystemConfig) {
l1Fee := l1CostFn(tx.RollupCostData(), header.Time)
require.Equalf(t, l1Fee, l1FeeRecipientDiff, "L1 fee mismatch: start balance %v, end balance %v", l1FeeRecipientStartBalance, l1FeeRecipientEndBalance)
gpoEcotone, err := gpoContract.IsEcotone(nil)
require.NoError(t, err)
require.Equal(t, sys.RollupConfig.IsEcotone(header.Time), gpoEcotone, "GPO and chain must have same ecotone view")
gpoL1Fee, err := gpoContract.GetL1Fee(&bind.CallOpts{}, bytes)
require.Nil(t, err)
require.Equal(t, l1Fee, gpoL1Fee, "GPO reports L1 fee mismatch")
adjustedGPOFee := gpoL1Fee
if sys.RollupConfig.IsRegolith(header.Time) {
// if post-regolith, adjust the GPO fee by removing the overhead it adds because of signature data
artificialGPOOverhead := big.NewInt(68 * 16) // it adds 68 bytes to cover signature and RLP data
l1BaseFee := big.NewInt(7) // we assume the L1 basefee is the minimum, 7
// in our case we already include that, so we subtract it, to do a 1:1 comparison
adjustedGPOFee = new(big.Int).Sub(gpoL1Fee, new(big.Int).Mul(artificialGPOOverhead, l1BaseFee))
}
require.Equal(t, l1Fee, adjustedGPOFee, "GPO reports L1 fee mismatch")
require.Equal(t, receipt.L1Fee, l1Fee, "l1 fee in receipt is correct")
if !sys.RollupConfig.IsEcotone(header.Time) { // FeeScalar receipt attribute is removed as of Ecotone
require.Equal(t,
new(big.Float).Mul(
new(big.Float).SetInt(l1Header.BaseFee),
new(big.Float).Mul(new(big.Float).SetInt(receipt.L1GasUsed), receipt.FeeScalar),
),
new(big.Float).SetInt(receipt.L1Fee), "fee field in receipt matches gas used times scalar times base fee")
}
// Calculate total fee
baseFeeRecipientDiff.Add(baseFeeRecipientDiff, coinbaseDiff)
......
......@@ -3,8 +3,6 @@ package derive
import (
"context"
"fmt"
"math"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
......@@ -75,7 +73,7 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex
return nil, NewCriticalError(fmt.Errorf("failed to derive some deposits: %w", err))
}
// apply sysCfg changes
if err := UpdateSystemConfigWithL1Receipts(&sysConfig, receipts, ba.rollupCfg); err != nil {
if err := UpdateSystemConfigWithL1Receipts(&sysConfig, receipts, ba.rollupCfg, info.Time()); err != nil {
return nil, NewCriticalError(fmt.Errorf("failed to apply derived L1 sysCfg updates: %w", err))
}
......@@ -102,28 +100,23 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex
l2Parent, nextL2Time, eth.ToBlockID(l1Info), l1Info.Time()))
}
l1InfoTx, err := L1InfoDepositBytes(ba.rollupCfg, sysConfig, seqNumber, l1Info, nextL2Time)
var upgradeTxs []hexutil.Bytes
if ba.rollupCfg.IsEcotoneActivationBlock(nextL2Time) {
upgradeTxs, err = EcotoneNetworkUpgradeTransactions()
if err != nil {
return nil, NewCriticalError(fmt.Errorf("failed to create l1InfoTx: %w", err))
return nil, NewCriticalError(fmt.Errorf("failed to build ecotone network upgrade txs: %w", err))
}
// If this is the Ecotone activation block we update the system config by copying over "Scalar"
// to "BaseFeeScalar". Note that after doing so, the L2 view of the system config differs from
// that on the L1 up until we receive a "type 4" log event that explicitly updates the new
// scalars.
if ba.rollupCfg.IsEcotoneActivationBlock(nextL2Time) {
// check if the scalar is too big to convert to uint32, and if so just use the uint32 max value
baseFeeScalar := uint32(math.MaxUint32)
scalar := new(big.Int).SetBytes(sysConfig.Scalar[:])
if scalar.Cmp(big.NewInt(math.MaxUint32)) < 0 {
baseFeeScalar = uint32(scalar.Int64())
}
sysConfig.BaseFeeScalar = baseFeeScalar
l1InfoTx, err := L1InfoDepositBytes(ba.rollupCfg, sysConfig, seqNumber, l1Info, nextL2Time)
if err != nil {
return nil, NewCriticalError(fmt.Errorf("failed to create l1InfoTx: %w", err))
}
txs := make([]hexutil.Bytes, 0, 1+len(depositTxs))
txs := make([]hexutil.Bytes, 0, 1+len(depositTxs)+len(upgradeTxs))
txs = append(txs, l1InfoTx)
txs = append(txs, depositTxs...)
txs = append(txs, upgradeTxs...)
var withdrawals *types.Withdrawals
if ba.rollupCfg.IsCanyon(nextL2Time) {
......
......@@ -15,6 +15,7 @@ type UserDepositSource struct {
const (
UserDepositSourceDomain = 0
L1InfoDepositSourceDomain = 1
UpgradeDepositSourceDomain = 2
)
func (dep *UserDepositSource) SourceHash() common.Hash {
......@@ -44,3 +45,21 @@ func (dep *L1InfoDepositSource) SourceHash() common.Hash {
copy(domainInput[32:], depositIDHash[:])
return crypto.Keccak256Hash(domainInput[:])
}
// UpgradeDepositSource implements the translation of upgrade-tx identity information to a deposit source-hash,
// which makes the deposit uniquely identifiable.
// System-upgrade transactions have their own domain for source-hashes,
// to not conflict with user-deposits or deposited L1 information.
// The intent identifies the upgrade-tx uniquely, in a human-readable way.
type UpgradeDepositSource struct {
Intent string
}
func (dep *UpgradeDepositSource) SourceHash() common.Hash {
intentHash := crypto.Keccak256Hash([]byte(dep.Intent))
var domainInput [32 * 2]byte
binary.BigEndian.PutUint64(domainInput[32-8:32], UpgradeDepositSourceDomain)
copy(domainInput[32:], intentHash[:])
return crypto.Keccak256Hash(domainInput[:])
}
package derive
import (
"testing"
"github.com/stretchr/testify/assert"
)
// TestEcotone4788ContractSourceHash
// cast keccak $(cast concat-hex 0x0000000000000000000000000000000000000000000000000000000000000002 $(cast keccak "Ecotone: L1 Block Proxy Update"))
// # 0x18acb38c5ff1c238a7460ebc1b421fa49ec4874bdf1e0a530d234104e5e67dbc
func TestDeposit(t *testing.T) {
source := UpgradeDepositSource{
Intent: "Ecotone: L1 Block Proxy Update",
}
actual := source.SourceHash()
expected := "0x18acb38c5ff1c238a7460ebc1b421fa49ec4874bdf1e0a530d234104e5e67dbc"
assert.Equal(t, expected, actual.Hex())
}
// TestEcotone4788ContractSourceHash tests that the source-hash of the 4788 deposit deployment tx is correct.
// As per specs, the hash is computed as:
// cast keccak $(cast concat-hex 0x0000000000000000000000000000000000000000000000000000000000000002 $(cast keccak "Ecotone: beacon block roots contract deployment"))
// # 0x69b763c48478b9dc2f65ada09b3d92133ec592ea715ec65ad6e7f3dc519dc00c
func TestEcotone4788ContractSourceHash(t *testing.T) {
source := UpgradeDepositSource{
Intent: "Ecotone: beacon block roots contract deployment",
}
actual := source.SourceHash()
expected := "0x69b763c48478b9dc2f65ada09b3d92133ec592ea715ec65ad6e7f3dc519dc00c"
assert.Equal(t, expected, actual.Hex())
}
package derive
import (
"bytes"
"fmt"
"math/big"
"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/crypto"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-service/solabi"
)
const UpgradeToFuncSignature = "upgradeTo(address)"
var (
// known address w/ zero txns
L1BlockDeployerAddress = common.HexToAddress("0x4210000000000000000000000000000000000000")
GasPriceOracleDeployerAddress = common.HexToAddress("0x4210000000000000000000000000000000000001")
newL1BlockAddress = crypto.CreateAddress(L1BlockDeployerAddress, 0)
newGasPriceOracleAddress = crypto.CreateAddress(GasPriceOracleDeployerAddress, 0)
deployL1BlockSource = UpgradeDepositSource{Intent: "Ecotone: L1 Block Deployment"}
deployGasPriceOracleSource = UpgradeDepositSource{Intent: "Ecotone: Gas Price Oracle Deployment"}
updateL1BlockProxySource = UpgradeDepositSource{Intent: "Ecotone: L1 Block Proxy Update"}
updateGasPriceOracleSource = UpgradeDepositSource{Intent: "Ecotone: Gas Price Oracle Proxy Update"}
enableEcotoneSource = UpgradeDepositSource{Intent: "Ecotone: Gas Price Oracle Set Ecotone"}
beaconRootsSource = UpgradeDepositSource{Intent: "Ecotone: beacon block roots contract deployment"}
enableEcotoneInput = crypto.Keccak256([]byte("setEcotone()"))[:4]
EIP4788From = common.HexToAddress("0x0B799C86a49DEeb90402691F1041aa3AF2d3C875")
eip4788CreationData = common.Hex2Bytes("0x60618060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500")
UpgradeToFuncBytes4 = crypto.Keccak256([]byte(UpgradeToFuncSignature))[:4]
l1BlockDeploymentBytecode = common.FromHex("0x608060405234801561001057600080fd5b5061053e806100206000396000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c80638381f58a11610097578063c598591811610066578063c598591814610229578063e591b28214610249578063e81b2c6d14610289578063f82061401461029257600080fd5b80638381f58a146101e35780638b239f73146101f75780639e8c496614610200578063b80777ea1461020957600080fd5b806354fd4d50116100d357806354fd4d50146101335780635cf249691461017c57806364ca23ef1461018557806368d5dca6146101b257600080fd5b8063015d8eb9146100fa57806309bd5a601461010f578063440a5e201461012b575b600080fd5b61010d61010836600461044c565b61029b565b005b61011860025481565b6040519081526020015b60405180910390f35b61010d6103da565b61016f6040518060400160405280600581526020017f312e322e3000000000000000000000000000000000000000000000000000000081525081565b60405161012291906104be565b61011860015481565b6003546101999067ffffffffffffffff1681565b60405167ffffffffffffffff9091168152602001610122565b6003546101ce9068010000000000000000900463ffffffff1681565b60405163ffffffff9091168152602001610122565b6000546101999067ffffffffffffffff1681565b61011860055481565b61011860065481565b6000546101999068010000000000000000900467ffffffffffffffff1681565b6003546101ce906c01000000000000000000000000900463ffffffff1681565b61026473deaddeaddeaddeaddeaddeaddeaddeaddead000181565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610122565b61011860045481565b61011860075481565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610342576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4c31426c6f636b3a206f6e6c7920746865206465706f7369746f72206163636f60448201527f756e742063616e20736574204c3120626c6f636b2076616c7565730000000000606482015260840160405180910390fd5b6000805467ffffffffffffffff98891668010000000000000000027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116998916999099179890981790975560019490945560029290925560038054919094167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009190911617909255600491909155600555600655565b3373deaddeaddeaddeaddeaddeaddeaddeaddead00011461040357633cc50b456000526004601cfd5b60043560801c60035560143560801c600055602435600155604435600755606435600255608435600455565b803567ffffffffffffffff8116811461044757600080fd5b919050565b600080600080600080600080610100898b03121561046957600080fd5b6104728961042f565b975061048060208a0161042f565b9650604089013595506060890135945061049c60808a0161042f565b979a969950949793969560a0850135955060c08501359460e001359350915050565b600060208083528351808285015260005b818110156104eb578581018301518582016040015282016104cf565b818111156104fd576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201604001939250505056fea164736f6c634300080f000a")
gasPriceOracleDeploymentBytecode = common.FromHex("0x608060405234801561001057600080fd5b50610fb5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c806354fd4d5011610097578063de26c4a111610066578063de26c4a1146101da578063f45e65d8146101ed578063f8206140146101f5578063fe173b97146101cc57600080fd5b806354fd4d501461016657806368d5dca6146101af5780636ef25c3a146101cc578063c5985918146101d257600080fd5b8063313ce567116100d3578063313ce5671461012757806349948e0e1461012e5780634ef6e22414610141578063519b4bd31461015e57600080fd5b80630c18c162146100fa57806322b90ab3146101155780632e0f26251461011f575b600080fd5b6101026101fd565b6040519081526020015b60405180910390f35b61011d61031e565b005b610102600681565b6006610102565b61010261013c366004610b73565b610541565b60005461014e9060ff1681565b604051901515815260200161010c565b610102610565565b6101a26040518060400160405280600581526020017f312e322e3000000000000000000000000000000000000000000000000000000081525081565b60405161010c9190610c42565b6101b76105c6565b60405163ffffffff909116815260200161010c565b48610102565b6101b761064b565b6101026101e8366004610b73565b6106ac565b610102610760565b610102610853565b6000805460ff1615610296576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f47617350726963654f7261636c653a206f76657268656164282920697320646560448201527f707265636174656400000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102f5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103199190610cb5565b905090565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663e591b2826040518163ffffffff1660e01b8152600401602060405180830381865afa15801561037d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103a19190610cce565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610481576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e2073657420697345636f746f6e6520666c6160648201527f6700000000000000000000000000000000000000000000000000000000000000608482015260a40161028d565b60005460ff1615610514576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a2045636f746f6e6520616c72656164792060448201527f6163746976650000000000000000000000000000000000000000000000000000606482015260840161028d565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b6000805460ff161561055c57610556826108b4565b92915050565b61055682610958565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16635cf249696040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102f5573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff166368d5dca66040518163ffffffff1660e01b8152600401602060405180830381865afa158015610627573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103199190610d04565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663c59859186040518163ffffffff1660e01b8152600401602060405180830381865afa158015610627573d6000803e3d6000fd5b6000806106b883610ab4565b60005490915060ff16156106cc5792915050565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa15801561072b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061074f9190610cb5565b6107599082610d59565b9392505050565b6000805460ff16156107f4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a207363616c61722829206973206465707260448201527f6563617465640000000000000000000000000000000000000000000000000000606482015260840161028d565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102f5573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663f82061406040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102f5573d6000803e3d6000fd5b6000806108c083610ab4565b905060006108cc610565565b6108d461064b565b6108df906010610d71565b63ffffffff166108ef9190610d9d565b905060006108fb610853565b6109036105c6565b63ffffffff166109139190610d9d565b905060006109218284610d59565b61092b9085610d9d565b90506109396006600a610efa565b610944906010610d9d565b61094e9082610f06565b9695505050505050565b60008061096483610ab4565b9050600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109eb9190610cb5565b6109f3610565565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a52573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a769190610cb5565b610a809085610d59565b610a8a9190610d9d565b610a949190610d9d565b9050610aa26006600a610efa565b610aac9082610f06565b949350505050565b80516000908190815b81811015610b3757848181518110610ad757610ad7610f41565b01602001517fff0000000000000000000000000000000000000000000000000000000000000016600003610b1757610b10600484610d59565b9250610b25565b610b22601084610d59565b92505b80610b2f81610f70565b915050610abd565b50610aac82610440610d59565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600060208284031215610b8557600080fd5b813567ffffffffffffffff80821115610b9d57600080fd5b818401915084601f830112610bb157600080fd5b813581811115610bc357610bc3610b44565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715610c0957610c09610b44565b81604052828152876020848701011115610c2257600080fd5b826020860160208301376000928101602001929092525095945050505050565b600060208083528351808285015260005b81811015610c6f57858101830151858201604001528201610c53565b81811115610c81576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b600060208284031215610cc757600080fd5b5051919050565b600060208284031215610ce057600080fd5b815173ffffffffffffffffffffffffffffffffffffffff8116811461075957600080fd5b600060208284031215610d1657600080fd5b815163ffffffff8116811461075957600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115610d6c57610d6c610d2a565b500190565b600063ffffffff80831681851681830481118215151615610d9457610d94610d2a565b02949350505050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615610dd557610dd5610d2a565b500290565b600181815b80851115610e3357817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115610e1957610e19610d2a565b80851615610e2657918102915b93841c9390800290610ddf565b509250929050565b600082610e4a57506001610556565b81610e5757506000610556565b8160018114610e6d5760028114610e7757610e93565b6001915050610556565b60ff841115610e8857610e88610d2a565b50506001821b610556565b5060208310610133831016604e8410600b8410161715610eb6575081810a610556565b610ec08383610dda565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115610ef257610ef2610d2a565b029392505050565b60006107598383610e3b565b600082610f3c577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610fa157610fa1610d2a565b506001019056fea164736f6c634300080f000a")
)
func EcotoneNetworkUpgradeTransactions() ([]hexutil.Bytes, error) {
upgradeTxns := make([]hexutil.Bytes, 0, 5)
deployL1BlockTransaction, err := types.NewTx(&types.DepositTx{
SourceHash: deployL1BlockSource.SourceHash(),
From: L1BlockDeployerAddress,
To: nil,
Mint: big.NewInt(0),
Value: big.NewInt(0),
Gas: 375_000,
IsSystemTransaction: false,
Data: l1BlockDeploymentBytecode,
}).MarshalBinary()
if err != nil {
return nil, err
}
upgradeTxns = append(upgradeTxns, deployL1BlockTransaction)
deployGasPriceOracle, err := types.NewTx(&types.DepositTx{
SourceHash: deployGasPriceOracleSource.SourceHash(),
From: GasPriceOracleDeployerAddress,
To: nil,
Mint: big.NewInt(0),
Value: big.NewInt(0),
Gas: 1_000_000,
IsSystemTransaction: false,
Data: gasPriceOracleDeploymentBytecode,
}).MarshalBinary()
if err != nil {
return nil, err
}
upgradeTxns = append(upgradeTxns, deployGasPriceOracle)
updateL1BlockProxy, err := types.NewTx(&types.DepositTx{
SourceHash: updateL1BlockProxySource.SourceHash(),
From: common.Address{},
To: &predeploys.L1BlockAddr,
Mint: big.NewInt(0),
Value: big.NewInt(0),
Gas: 50_000,
IsSystemTransaction: false,
Data: upgradeToCalldata(newL1BlockAddress),
}).MarshalBinary()
if err != nil {
return nil, err
}
upgradeTxns = append(upgradeTxns, updateL1BlockProxy)
updateGasPriceOracleProxy, err := types.NewTx(&types.DepositTx{
SourceHash: updateGasPriceOracleSource.SourceHash(),
From: common.Address{},
To: &predeploys.GasPriceOracleAddr,
Mint: big.NewInt(0),
Value: big.NewInt(0),
Gas: 50_000,
IsSystemTransaction: false,
Data: upgradeToCalldata(newGasPriceOracleAddress),
}).MarshalBinary()
if err != nil {
return nil, err
}
upgradeTxns = append(upgradeTxns, updateGasPriceOracleProxy)
enableEcotone, err := types.NewTx(&types.DepositTx{
SourceHash: enableEcotoneSource.SourceHash(),
From: L1InfoDepositerAddress,
To: &predeploys.GasPriceOracleAddr,
Mint: big.NewInt(0),
Value: big.NewInt(0),
Gas: 80_000,
IsSystemTransaction: false,
Data: enableEcotoneInput,
}).MarshalBinary()
if err != nil {
return nil, err
}
upgradeTxns = append(upgradeTxns, enableEcotone)
deployEIP4788, err := types.NewTx(&types.DepositTx{
From: EIP4788From,
To: nil, // contract-deployment tx
Mint: big.NewInt(0),
Value: big.NewInt(0),
Gas: 0x3d090, // hex constant, as defined in EIP-4788
Data: eip4788CreationData,
IsSystemTransaction: false,
SourceHash: beaconRootsSource.SourceHash(),
}).MarshalBinary()
if err != nil {
return nil, err
}
upgradeTxns = append(upgradeTxns, deployEIP4788)
return upgradeTxns, nil
}
func upgradeToCalldata(addr common.Address) []byte {
buf := bytes.NewBuffer(make([]byte, 0, 4+20))
if err := solabi.WriteSignature(buf, UpgradeToFuncBytes4); err != nil {
panic(fmt.Errorf("failed to write upgradeTo signature data: %w", err))
}
if err := solabi.WriteAddress(buf, addr); err != nil {
panic(fmt.Errorf("failed to write upgradeTo address data: %w", err))
}
return buf.Bytes()
}
package derive
import (
"math/big"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
)
func TestSourcesMatchSpec(t *testing.T) {
for _, test := range []struct {
source UpgradeDepositSource
expectedHash string
}{
{
source: deployL1BlockSource,
expectedHash: "0x877a6077205782ea15a6dc8699fa5ebcec5e0f4389f09cb8eda09488231346f8",
},
{
source: deployGasPriceOracleSource,
expectedHash: "0xa312b4510adf943510f05fcc8f15f86995a5066bd83ce11384688ae20e6ecf42",
},
{
source: updateL1BlockProxySource,
expectedHash: "0x18acb38c5ff1c238a7460ebc1b421fa49ec4874bdf1e0a530d234104e5e67dbc",
},
{
source: updateGasPriceOracleSource,
expectedHash: "0xee4f9385eceef498af0be7ec5862229f426dec41c8d42397c7257a5117d9230a",
},
{
source: enableEcotoneSource,
expectedHash: "0x0c1cb38e99dbc9cbfab3bb80863380b0905290b37eb3d6ab18dc01c1f3e75f93",
},
{
source: beaconRootsSource,
expectedHash: "0x69b763c48478b9dc2f65ada09b3d92133ec592ea715ec65ad6e7f3dc519dc00c",
},
} {
require.Equal(t, common.HexToHash(test.expectedHash), test.source.SourceHash())
}
}
func toDepositTxn(t *testing.T, data hexutil.Bytes) (common.Address, *types.Transaction) {
txn := new(types.Transaction)
err := txn.UnmarshalBinary(data)
require.NoError(t, err)
require.Truef(t, txn.IsDepositTx(), "expected deposit txn, got %v", txn.Type())
require.False(t, txn.IsSystemTx())
signer := types.NewLondonSigner(big.NewInt(420))
from, err := signer.Sender(txn)
require.NoError(t, err)
return from, txn
}
func TestEcotoneNetworkTransactions(t *testing.T) {
upgradeTxns, err := EcotoneNetworkUpgradeTransactions()
require.NoError(t, err)
require.Len(t, upgradeTxns, 6)
deployL1BlockSender, deployL1Block := toDepositTxn(t, upgradeTxns[0])
require.Equal(t, deployL1BlockSender, common.HexToAddress("0x4210000000000000000000000000000000000000"))
require.Equal(t, deployL1BlockSource.SourceHash(), deployL1Block.SourceHash())
require.Nil(t, deployL1Block.To())
require.Equal(t, uint64(375_000), deployL1Block.Gas())
require.Equal(t, bindings.L1BlockMetaData.Bin, hexutil.Bytes(deployL1Block.Data()).String())
deployGasPriceOracleSender, deployGasPriceOracle := toDepositTxn(t, upgradeTxns[1])
require.Equal(t, deployGasPriceOracleSender, common.HexToAddress("0x4210000000000000000000000000000000000001"))
require.Equal(t, deployGasPriceOracleSource.SourceHash(), deployGasPriceOracle.SourceHash())
require.Nil(t, deployGasPriceOracle.To())
require.Equal(t, uint64(1_000_000), deployGasPriceOracle.Gas())
require.Equal(t, bindings.GasPriceOracleMetaData.Bin, hexutil.Bytes(deployGasPriceOracle.Data()).String())
updateL1BlockProxySender, updateL1BlockProxy := toDepositTxn(t, upgradeTxns[2])
require.Equal(t, updateL1BlockProxySender, common.Address{})
require.Equal(t, updateL1BlockProxySource.SourceHash(), updateL1BlockProxy.SourceHash())
require.NotNil(t, updateL1BlockProxy.To())
require.Equal(t, *updateL1BlockProxy.To(), common.HexToAddress("0x4200000000000000000000000000000000000015"))
require.Equal(t, uint64(50_000), updateL1BlockProxy.Gas())
require.Equal(t, common.FromHex("0x3659cfe600000000000000000000000007dbe8500fc591d1852b76fee44d5a05e13097ff"), updateL1BlockProxy.Data())
updateGasPriceOracleSender, updateGasPriceOracle := toDepositTxn(t, upgradeTxns[3])
require.Equal(t, updateGasPriceOracleSender, common.Address{})
require.Equal(t, updateGasPriceOracleSource.SourceHash(), updateGasPriceOracle.SourceHash())
require.NotNil(t, updateGasPriceOracle.To())
require.Equal(t, *updateGasPriceOracle.To(), common.HexToAddress("0x420000000000000000000000000000000000000F"))
require.Equal(t, uint64(50_000), updateGasPriceOracle.Gas())
require.Equal(t, common.FromHex("0x3659cfe6000000000000000000000000b528d11cc114e026f138fe568744c6d45ce6da7a"), updateGasPriceOracle.Data())
gpoSetEcotoneSender, gpoSetEcotone := toDepositTxn(t, upgradeTxns[4])
require.Equal(t, gpoSetEcotoneSender, common.HexToAddress("0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001"))
require.Equal(t, enableEcotoneSource.SourceHash(), gpoSetEcotone.SourceHash())
require.NotNil(t, gpoSetEcotone.To())
require.Equal(t, *gpoSetEcotone.To(), common.HexToAddress("0x420000000000000000000000000000000000000F"))
require.Equal(t, uint64(80_000), gpoSetEcotone.Gas())
require.Equal(t, common.FromHex("0x22b90ab3"), gpoSetEcotone.Data())
beaconRootsSender, beaconRoots := toDepositTxn(t, upgradeTxns[5])
require.Equal(t, beaconRootsSender, common.HexToAddress("0x0B799C86a49DEeb90402691F1041aa3AF2d3C875"))
require.Equal(t, beaconRootsSource.SourceHash(), beaconRoots.SourceHash())
require.Nil(t, beaconRoots.To())
require.Equal(t, uint64(250_000), beaconRoots.Gas())
require.Equal(t, eip4788CreationData, beaconRoots.Data())
}
func TestEip4788Params(t *testing.T) {
require.Equal(t, EIP4788From, common.HexToAddress("0x0B799C86a49DEeb90402691F1041aa3AF2d3C875"))
require.Equal(t, eip4788CreationData, common.Hex2Bytes("0x60618060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500"))
}
......@@ -270,19 +270,31 @@ func L1InfoDeposit(rollupCfg *rollup.Config, sysCfg eth.SystemConfig, seqNumber
BatcherAddr: sysCfg.BatcherAddr,
}
var data []byte
var err error
if isEcotoneButNotFirstBlock(rollupCfg, l2BlockTime) {
l1BlockInfo.BlobBaseFee = block.BlobBaseFee()
l1BlockInfo.BlobBaseFeeScalar = sysCfg.BlobBaseFeeScalar
l1BlockInfo.BaseFeeScalar = sysCfg.BaseFeeScalar
data, err = l1BlockInfo.marshalBinaryEcotone()
if l1BlockInfo.BlobBaseFee == nil {
// The L2 spec states to use the MIN_BLOB_GASPRICE from EIP-4844 if not yet active on L1.
l1BlockInfo.BlobBaseFee = big.NewInt(1)
}
blobBaseFeeScalar, baseFeeScalar, err := sysCfg.EcotoneScalars()
if err != nil {
return nil, err
}
l1BlockInfo.BlobBaseFeeScalar = blobBaseFeeScalar
l1BlockInfo.BaseFeeScalar = baseFeeScalar
out, err := l1BlockInfo.marshalBinaryEcotone()
if err != nil {
return nil, fmt.Errorf("failed to marshal Ecotone l1 block info: %w", err)
}
data = out
} else {
l1BlockInfo.L1FeeOverhead = sysCfg.Overhead
l1BlockInfo.L1FeeScalar = sysCfg.Scalar
data, err = l1BlockInfo.marshalBinaryBedrock()
}
out, err := l1BlockInfo.marshalBinaryBedrock()
if err != nil {
return nil, fmt.Errorf("Failed to marshal l1 block info: %w", err)
return nil, fmt.Errorf("failed to marshal Bedrock l1 block info: %w", err)
}
data = out
}
source := L1InfoDepositSource{
......
......@@ -75,9 +75,9 @@ func (l1t *L1Traversal) AdvanceL1Block(ctx context.Context) error {
if err != nil {
return NewTemporaryError(fmt.Errorf("failed to fetch receipts of L1 block %s (parent: %s) for L1 sysCfg update: %w", nextL1Origin, origin, err))
}
if err := UpdateSystemConfigWithL1Receipts(&l1t.sysCfg, receipts, l1t.cfg); err != nil {
if err := UpdateSystemConfigWithL1Receipts(&l1t.sysCfg, receipts, l1t.cfg, nextL1Origin.Time); err != nil {
// the sysCfg changes should always be formatted correctly.
return NewCriticalError(fmt.Errorf("failed to update L1 sysCfg with receipts from block %s: %w", origin, err))
return NewCriticalError(fmt.Errorf("failed to update L1 sysCfg with receipts from block %s: %w", nextL1Origin, err))
}
l1t.block = nextL1Origin
......
package derive
import (
"encoding/binary"
"fmt"
"github.com/ethereum/go-ethereum/core/types"
......@@ -73,13 +74,19 @@ func PayloadToSystemConfig(rollupCfg *rollup.Config, payload *eth.ExecutionPaylo
if err != nil {
return eth.SystemConfig{}, fmt.Errorf("failed to parse L1 info deposit tx from L2 block: %w", err)
}
if isEcotoneButNotFirstBlock(rollupCfg, uint64(payload.Timestamp)) {
// Translate Ecotone values back into encoded scalar if needed.
// We do not know if it was derived from a v0 or v1 scalar,
// but v1 is fine, a 0 blob base fee has the same effect.
info.L1FeeScalar[0] = 1
binary.BigEndian.PutUint32(info.L1FeeScalar[24:28], info.BlobBaseFeeScalar)
binary.BigEndian.PutUint32(info.L1FeeScalar[28:32], info.BaseFeeScalar)
}
return eth.SystemConfig{
BatcherAddr: info.BatcherAddr,
Overhead: info.L1FeeOverhead,
Scalar: info.L1FeeScalar,
GasLimit: uint64(payload.GasLimit),
BaseFeeScalar: info.BaseFeeScalar,
BlobBaseFeeScalar: info.BlobBaseFeeScalar,
}, err
}
}
......@@ -2,10 +2,8 @@ package derive
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/hashicorp/go-multierror"
......@@ -23,19 +21,16 @@ var (
SystemConfigUpdateGasConfig = common.Hash{31: 1}
SystemConfigUpdateGasLimit = common.Hash{31: 2}
SystemConfigUpdateUnsafeBlockSigner = common.Hash{31: 3}
SystemConfigUpdateGasConfigEcotone = common.Hash{31: 4}
)
var (
ConfigUpdateEventABI = "ConfigUpdate(uint256,uint8,bytes)"
ConfigUpdateEventABIHash = crypto.Keccak256Hash([]byte(ConfigUpdateEventABI))
ConfigUpdateEventVersion0 = common.Hash{}
empty24 = make([]byte, 24)
)
// UpdateSystemConfigWithL1Receipts filters all L1 receipts to find config updates and applies the config updates to the given sysCfg
func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*types.Receipt, cfg *rollup.Config) error {
func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*types.Receipt, cfg *rollup.Config, l1Time uint64) error {
var result error
for i, rec := range receipts {
if rec.Status != types.ReceiptStatusSuccessful {
......@@ -43,7 +38,7 @@ func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*type
}
for j, log := range rec.Logs {
if log.Address == cfg.L1SystemConfigAddress && len(log.Topics) > 0 && log.Topics[0] == ConfigUpdateEventABIHash {
if err := ProcessSystemConfigUpdateLogEvent(sysCfg, log); err != nil {
if err := ProcessSystemConfigUpdateLogEvent(sysCfg, log, cfg, l1Time); err != nil {
result = multierror.Append(result, fmt.Errorf("malformatted L1 system sysCfg log in receipt %d, log %d: %w", i, j, err))
}
}
......@@ -61,7 +56,7 @@ func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*type
// UpdateType indexed updateType,
// bytes data
// );
func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.Log) error {
func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.Log, rollupCfg *rollup.Config, l1Time uint64) error {
if len(ev.Topics) != 3 {
return fmt.Errorf("expected 3 event topics (event identity, indexed version, indexed updateType), got %d", len(ev.Topics))
}
......@@ -116,8 +111,18 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L
if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes"))
}
if rollupCfg.IsEcotone(l1Time) {
if err := eth.CheckEcotoneL1SystemConfigScalar(scalar); err != nil {
return nil // ignore invalid scalars, retain the old system-config scalar
}
// retain the scalar data in encoded form
destSysCfg.Scalar = scalar
// zero out the overhead, it will not affect the state-transition after Ecotone
destSysCfg.Overhead = eth.Bytes32{}
} else {
destSysCfg.Overhead = overhead
destSysCfg.Scalar = scalar
}
return nil
case SystemConfigUpdateGasLimit:
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
......@@ -138,35 +143,6 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L
case SystemConfigUpdateUnsafeBlockSigner:
// Ignored in derivation. This configurable applies to runtime configuration outside of the derivation.
return nil
case SystemConfigUpdateGasConfigEcotone:
// TODO(optimism#8801): pull this deserialization logic out into a public handler for solidity
// diff/fuzz testing
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
return NewCriticalError(errors.New("invalid pointer field"))
}
if length, err := solabi.ReadUint64(reader); err != nil || length != 8 {
return NewCriticalError(errors.New("invalid length field"))
}
packed := make([]byte, 8)
_, err := io.ReadFull(reader, packed)
if err != nil {
return NewCriticalError(errors.New("invalid packed scalars field"))
}
// confirm there is 32-8=24 bytes of 0-padding left
zeros := make([]byte, 24)
_, err = io.ReadFull(reader, zeros)
if err != nil {
return NewCriticalError(errors.New("didn't find expected padding"))
}
if !bytes.Equal(zeros, empty24) {
return NewCriticalError(fmt.Errorf("expected padding to be all zeros, got %x", zeros))
}
if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes"))
}
destSysCfg.BaseFeeScalar = binary.BigEndian.Uint32(packed[0:4])
destSysCfg.BlobBaseFeeScalar = binary.BigEndian.Uint32(packed[4:8])
return nil
default:
return fmt.Errorf("unrecognized L1 sysCfg update type: %s", updateType)
}
......
......@@ -4,11 +4,14 @@ import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/eth"
)
var (
......@@ -42,6 +45,9 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) {
config eth.SystemConfig
hook func(*testing.T, *types.Log) *types.Log
err bool
// forks (optional)
ecotoneTime *uint64
l1Time uint64
}{
{
// The log data is ignored by consensus and no modifications to the
......@@ -145,25 +151,26 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) {
Topics: []common.Hash{
ConfigUpdateEventABIHash,
ConfigUpdateEventVersion0,
SystemConfigUpdateGasConfigEcotone,
SystemConfigUpdateGasConfig,
},
},
hook: func(t *testing.T, log *types.Log) *types.Log {
baseFeeScalar := big.NewInt(0xaa)
blobBaseFeeScalar := big.NewInt(0xbb)
packed := make([]byte, 8)
baseFeeScalar.FillBytes(packed[0:4])
blobBaseFeeScalar.FillBytes(packed[4:8])
data, err := bytesArgs.Pack(packed)
scalarData := common.Hash{0: 1, 24 + 3: 0xb3, 28 + 3: 0xbb}
scalar := scalarData.Big()
overhead := big.NewInt(0xff)
numberData, err := twoUint256.Pack(overhead, scalar)
require.NoError(t, err)
data, err := bytesArgs.Pack(numberData)
require.NoError(t, err)
log.Data = data
return log
},
config: eth.SystemConfig{
BaseFeeScalar: 0xaa,
BlobBaseFeeScalar: 0xbb,
Scalar: eth.Bytes32{0: 1, 24 + 3: 0xb3, 28 + 3: 0xbb},
},
err: false,
ecotoneTime: new(uint64), // activate ecotone
l1Time: 200,
},
{
name: "SystemConfigOneTopic",
......@@ -184,8 +191,9 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) {
test := test
t.Run(test.name, func(t *testing.T) {
config := eth.SystemConfig{}
rollupCfg := rollup.Config{EcotoneTime: test.ecotoneTime}
err := ProcessSystemConfigUpdateLogEvent(&config, test.hook(t, test.log))
err := ProcessSystemConfigUpdateLogEvent(&config, test.hook(t, test.log), &rollupCfg, test.l1Time)
if test.err {
require.Error(t, err)
} else {
......
......@@ -91,6 +91,12 @@ func (d *Sequencer) StartBuildingBlock(ctx context.Context) error {
// from the transaction pool.
attrs.NoTxPool = uint64(attrs.Timestamp) > l1Origin.Time+d.rollupCfg.MaxSequencerDrift
// For the Ecotone activation block we shouldn't include any sequencer transactions.
if d.rollupCfg.IsEcotoneActivationBlock(uint64(attrs.Timestamp)) {
attrs.NoTxPool = true
d.log.Info("Sequencing Ecotone upgrade block")
}
d.log.Debug("prepared attributes for new block",
"num", l2Head.Number+1, "time", uint64(attrs.Timestamp),
"origin", l1Origin, "origin_time", l1Origin.Time, "noTxPool", attrs.NoTxPool)
......
......@@ -302,7 +302,7 @@ func (c *Config) IsEcotone(timestamp uint64) bool {
}
// IsEcotoneActivationBlock returns whether the specified block is the first block subject to the
// Ecotone upgrade.
// Ecotone upgrade. Ecotone activation at genesis does not count.
func (c *Config) IsEcotoneActivationBlock(l2BlockTime uint64) bool {
return c.IsEcotone(l2BlockTime) &&
l2BlockTime >= c.BlockTime &&
......
......@@ -2,6 +2,7 @@ package eth
import (
"bytes"
"encoding/binary"
"fmt"
"math/big"
"reflect"
......@@ -310,19 +311,64 @@ type ForkchoiceUpdatedResult struct {
type SystemConfig struct {
// BatcherAddr identifies the batch-sender address used in batch-inbox data-transaction filtering.
BatcherAddr common.Address `json:"batcherAddr"`
// Overhead identifies the L1 fee overhead, and is passed through opaquely to op-geth.
// Overhead identifies the L1 fee overhead.
// Pre-Ecotone this is passed as-is to the engine.
// Post-Ecotone this is always zero, and not passed into the engine.
Overhead Bytes32 `json:"overhead"`
// Scalar identifies the L1 fee scalar, and is passed through opaquely to op-geth.
// Scalar identifies the L1 fee scalar
// Pre-Ecotone this is passed as-is to the engine.
// Post-Ecotone this encodes multiple pieces of scalar data.
Scalar Bytes32 `json:"scalar"`
// GasLimit identifies the L2 block gas limit
GasLimit uint64 `json:"gasLimit"`
// BaseFeeScalar scales the L1 calldata fee after the Ecotone upgrade
BaseFeeScalar uint32 `json:"baseFeeScalar"`
// BlobBaseFeeScalar scales the L1 blob fee after the Ecotone upgrade
BlobBaseFeeScalar uint32 `json:"blobBaseFeeScalar"`
// More fields can be added for future SystemConfig versions.
}
// The Ecotone upgrade introduces a versioned L1 scalar format
// that is backward-compatible with pre-Ecotone L1 scalar values.
const (
// L1ScalarBedrock is implied pre-Ecotone, encoding just a regular-gas scalar.
L1ScalarBedrock = byte(0)
// L1ScalarEcotone is new in Ecotone, allowing configuration of both a regular and a blobs scalar.
L1ScalarEcotone = byte(1)
)
func (sysCfg *SystemConfig) EcotoneScalars() (blobBaseFeeScalar, baseFeeScalar uint32, err error) {
if err := CheckEcotoneL1SystemConfigScalar(sysCfg.Scalar); err != nil {
return 0, 0, err
}
switch sysCfg.Scalar[0] {
case L1ScalarBedrock:
blobBaseFeeScalar = 0
baseFeeScalar = binary.BigEndian.Uint32(sysCfg.Scalar[28:32])
case L1ScalarEcotone:
blobBaseFeeScalar = binary.BigEndian.Uint32(sysCfg.Scalar[24:28])
baseFeeScalar = binary.BigEndian.Uint32(sysCfg.Scalar[28:32])
default:
err = fmt.Errorf("unexpected system config scalar: %s", sysCfg.Scalar)
}
return
}
func CheckEcotoneL1SystemConfigScalar(scalar [32]byte) error {
versionByte := scalar[0]
switch versionByte {
case L1ScalarBedrock:
if ([27]byte)(scalar[1:28]) != ([27]byte{}) { // check padding
return fmt.Errorf("invalid version 0 scalar padding: %x", scalar[1:28])
}
return nil
case L1ScalarEcotone:
if ([23]byte)(scalar[1:24]) != ([23]byte{}) { // check padding
return fmt.Errorf("invalid version 1 scalar padding: %x", scalar[1:24])
}
return nil
default:
// ignore the event if it's an unknown scalar format
return fmt.Errorf("unrecognized scalar version: %d", versionByte)
}
}
type Bytes48 [48]byte
func (b *Bytes48) UnmarshalJSON(text []byte) error {
......
......@@ -18,3 +18,38 @@ func TestInputError(t *testing.T) {
}
require.ErrorIs(t, err, InputError{}, "need to detect input error with errors.Is")
}
type scalarTest struct {
name string
val Bytes32
fail bool
blobBaseFeeScalar uint32
baseFeeScalar uint32
}
func TestEcotoneScalars(t *testing.T) {
testCases := []scalarTest{
{"invalid v0 scalar", Bytes32{0: 0, 27: 1, 31: 2}, true, 0, 0},
{"valid v0 scalar", Bytes32{0: 0, 27: 0, 31: 2}, false, 0, 2},
{"invalid v1 scalar", Bytes32{0: 0, 7: 1, 31: 2}, true, 0, 0},
{"valid v1 scalar with 0 blob scalar", Bytes32{0: 1, 27: 0, 31: 2}, false, 0, 2},
{"valid v1 scalar with non-0 blob scalar", Bytes32{0: 1, 27: 123, 31: 2}, false, 123, 2},
{"valid v1 scalar with non-0 blob scalar and 0 scalar", Bytes32{0: 1, 27: 123, 31: 0}, false, 123, 0},
{"zero v0 scalar", Bytes32{0: 0}, false, 0, 0},
{"zero v1 scalar", Bytes32{0: 1}, false, 0, 0},
{"unknown version", Bytes32{0: 2}, true, 0, 0},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
sysConfig := SystemConfig{Scalar: tc.val}
blobScalar, regScalar, err := sysConfig.EcotoneScalars()
if tc.fail {
require.NotNil(t, err)
} else {
require.Equal(t, tc.blobBaseFeeScalar, blobScalar)
require.Equal(t, tc.baseFeeScalar, regScalar)
}
})
}
}
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