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 { ...@@ -233,7 +233,7 @@ type DeployConfig struct {
RecommendedProtocolVersion params.ProtocolVersion `json:"recommendedProtocolVersion"` RecommendedProtocolVersion params.ProtocolVersion `json:"recommendedProtocolVersion"`
// When Cancun activates. Relative to L1 genesis. // 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 // Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy
......
...@@ -155,6 +155,7 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) { ...@@ -155,6 +155,7 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) {
chainConfig.TerminalTotalDifficulty = big.NewInt(0) chainConfig.TerminalTotalDifficulty = big.NewInt(0)
chainConfig.TerminalTotalDifficultyPassed = true chainConfig.TerminalTotalDifficultyPassed = true
chainConfig.ShanghaiTime = u64ptr(0) chainConfig.ShanghaiTime = u64ptr(0)
chainConfig.CancunTime = u64ptr(0)
} }
gasLimit := config.L1GenesisBlockGasLimit gasLimit := config.L1GenesisBlockGasLimit
...@@ -174,7 +175,7 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) { ...@@ -174,7 +175,7 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) {
timestamp = hexutil.Uint64(time.Now().Unix()) timestamp = hexutil.Uint64(time.Now().Unix())
} }
if !config.L1UseClique && config.L1CancunTimeOffset != nil { if !config.L1UseClique && config.L1CancunTimeOffset != nil {
cancunTime := uint64(timestamp) + *config.L1CancunTimeOffset cancunTime := uint64(timestamp) + uint64(*config.L1CancunTimeOffset)
chainConfig.CancunTime = &cancunTime chainConfig.CancunTime = &cancunTime
} }
......
...@@ -13,7 +13,9 @@ import ( ...@@ -13,7 +13,9 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "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/deployer"
"github.com/ethereum-optimism/optimism/op-chain-ops/immutables" "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-chain-ops/state"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
) )
...@@ -112,5 +114,25 @@ func BuildL2Genesis(config *DeployConfig, l1StartBlock *types.Block) (*core.Gene ...@@ -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 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 ...@@ -3,18 +3,20 @@ package actions
import ( import (
"testing" "testing"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log" "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-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
) )
func TestDencunL1Fork(gt *testing.T) { func TestDencunL1ForkAfterGenesis(gt *testing.T) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
offset := uint64(24) offset := hexutil.Uint64(24)
dp.DeployConfig.L1CancunTimeOffset = &offset dp.DeployConfig.L1CancunTimeOffset = &offset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
...@@ -57,7 +59,7 @@ func TestDencunL1Fork(gt *testing.T) { ...@@ -57,7 +59,7 @@ func TestDencunL1Fork(gt *testing.T) {
func TestDencunL1ForkAtGenesis(gt *testing.T) { func TestDencunL1ForkAtGenesis(gt *testing.T) {
t := NewDefaultTesting(gt) t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
offset := uint64(0) offset := hexutil.Uint64(0)
dp.DeployConfig.L1CancunTimeOffset = &offset dp.DeployConfig.L1CancunTimeOffset = &offset
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) 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) { ...@@ -163,3 +163,11 @@ func (s *L2Sequencer) ActBuildL2ToTime(t Testing, target uint64) {
s.ActL2EndBlock(t) 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) { ...@@ -161,7 +161,7 @@ func TestSystemE2EDencunAtGenesis(t *testing.T) {
InitParallel(t) InitParallel(t)
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
genesisActivation := uint64(0) genesisActivation := hexutil.Uint64(0)
cfg.DeployConfig.L1CancunTimeOffset = &genesisActivation cfg.DeployConfig.L1CancunTimeOffset = &genesisActivation
sys, err := cfg.Start(t) sys, err := cfg.Start(t)
...@@ -179,7 +179,7 @@ func TestSystemE2EDencunAtGenesisWithBlobs(t *testing.T) { ...@@ -179,7 +179,7 @@ func TestSystemE2EDencunAtGenesisWithBlobs(t *testing.T) {
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
//cancun is on from genesis: //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 cfg.DeployConfig.L1CancunTimeOffset = &genesisActivation // i.e. turn cancun on at genesis time + 0
sys, err := cfg.Start(t) sys, err := cfg.Start(t)
...@@ -1067,7 +1067,7 @@ func (sga *stateGetterAdapter) GetState(addr common.Address, key common.Hash) co ...@@ -1067,7 +1067,7 @@ func (sga *stateGetterAdapter) GetState(addr common.Address, key common.Hash) co
} }
// TestFees checks that L1/L2 fees are handled. // TestFees checks that L1/L2 fees are handled.
func TestL1Fees(t *testing.T) { func TestFees(t *testing.T) {
InitParallel(t) InitParallel(t)
t.Run("pre-regolith", func(t *testing.T) { t.Run("pre-regolith", func(t *testing.T) {
...@@ -1078,10 +1078,9 @@ func TestL1Fees(t *testing.T) { ...@@ -1078,10 +1078,9 @@ func TestL1Fees(t *testing.T) {
cfg.DeployConfig.L2GenesisCanyonTimeOffset = nil cfg.DeployConfig.L2GenesisCanyonTimeOffset = nil
cfg.DeployConfig.L2GenesisDeltaTimeOffset = nil cfg.DeployConfig.L2GenesisDeltaTimeOffset = nil
cfg.DeployConfig.L2GenesisEcotoneTimeOffset = nil cfg.DeployConfig.L2GenesisEcotoneTimeOffset = nil
testL1Fees(t, cfg) testFees(t, cfg)
}) })
t.Run("regolith", func(t *testing.T) { 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 := DefaultSystemConfig(t)
cfg.DeployConfig.L1GenesisBlockBaseFeePerGas = (*hexutil.Big)(big.NewInt(7)) cfg.DeployConfig.L1GenesisBlockBaseFeePerGas = (*hexutil.Big)(big.NewInt(7))
...@@ -1089,10 +1088,9 @@ func TestL1Fees(t *testing.T) { ...@@ -1089,10 +1088,9 @@ func TestL1Fees(t *testing.T) {
cfg.DeployConfig.L2GenesisCanyonTimeOffset = nil cfg.DeployConfig.L2GenesisCanyonTimeOffset = nil
cfg.DeployConfig.L2GenesisDeltaTimeOffset = nil cfg.DeployConfig.L2GenesisDeltaTimeOffset = nil
cfg.DeployConfig.L2GenesisEcotoneTimeOffset = nil cfg.DeployConfig.L2GenesisEcotoneTimeOffset = nil
testL1Fees(t, cfg) testFees(t, cfg)
}) })
t.Run("ecotone", func(t *testing.T) { 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 := DefaultSystemConfig(t)
cfg.DeployConfig.L1GenesisBlockBaseFeePerGas = (*hexutil.Big)(big.NewInt(7)) cfg.DeployConfig.L1GenesisBlockBaseFeePerGas = (*hexutil.Big)(big.NewInt(7))
...@@ -1100,11 +1098,11 @@ func TestL1Fees(t *testing.T) { ...@@ -1100,11 +1098,11 @@ func TestL1Fees(t *testing.T) {
cfg.DeployConfig.L2GenesisCanyonTimeOffset = new(hexutil.Uint64) cfg.DeployConfig.L2GenesisCanyonTimeOffset = new(hexutil.Uint64)
cfg.DeployConfig.L2GenesisDeltaTimeOffset = new(hexutil.Uint64) cfg.DeployConfig.L2GenesisDeltaTimeOffset = new(hexutil.Uint64)
cfg.DeployConfig.L2GenesisEcotoneTimeOffset = 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) sys, err := cfg.Start(t)
require.Nil(t, err, "Error starting up system") require.Nil(t, err, "Error starting up system")
...@@ -1135,16 +1133,25 @@ func testL1Fees(t *testing.T, cfg SystemConfig) { ...@@ -1135,16 +1133,25 @@ func testL1Fees(t *testing.T, cfg SystemConfig) {
gpoContract, err := bindings.NewGasPriceOracle(predeploys.GasPriceOracleAddr, l2Seq) gpoContract, err := bindings.NewGasPriceOracle(predeploys.GasPriceOracleAddr, l2Seq)
require.Nil(t, err) require.Nil(t, err)
if !sys.RollupConfig.IsEcotone(sys.L2GenesisCfg.Timestamp) {
overhead, err := gpoContract.Overhead(&bind.CallOpts{}) overhead, err := gpoContract.Overhead(&bind.CallOpts{})
require.Nil(t, err, "reading gpo overhead") require.Nil(t, err, "reading gpo overhead")
decimals, err := gpoContract.Decimals(&bind.CallOpts{}) require.Equal(t, overhead.Uint64(), cfg.DeployConfig.GasPriceOracleOverhead, "wrong gpo overhead")
require.Nil(t, err, "reading gpo decimals")
scalar, err := gpoContract.Scalar(&bind.CallOpts{}) scalar, err := gpoContract.Scalar(&bind.CallOpts{})
require.Nil(t, err, "reading gpo scalar") 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, decimals.Uint64(), uint64(6), "wrong gpo decimals")
require.Equal(t, scalar.Uint64(), cfg.DeployConfig.GasPriceOracleScalar, "wrong gpo scalar")
// BaseFee Recipient // BaseFee Recipient
baseFeeRecipientStartBalance, err := l2Seq.BalanceAt(context.Background(), predeploys.BaseFeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) baseFeeRecipientStartBalance, err := l2Seq.BalanceAt(context.Background(), predeploys.BaseFeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64()))
...@@ -1227,17 +1234,32 @@ func testL1Fees(t *testing.T, cfg SystemConfig) { ...@@ -1227,17 +1234,32 @@ func testL1Fees(t *testing.T, cfg SystemConfig) {
l1Fee := l1CostFn(tx.RollupCostData(), header.Time) l1Fee := l1CostFn(tx.RollupCostData(), header.Time)
require.Equalf(t, l1Fee, l1FeeRecipientDiff, "L1 fee mismatch: start balance %v, end balance %v", l1FeeRecipientStartBalance, l1FeeRecipientEndBalance) 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) gpoL1Fee, err := gpoContract.GetL1Fee(&bind.CallOpts{}, bytes)
require.Nil(t, err) 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") 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, require.Equal(t,
new(big.Float).Mul( new(big.Float).Mul(
new(big.Float).SetInt(l1Header.BaseFee), new(big.Float).SetInt(l1Header.BaseFee),
new(big.Float).Mul(new(big.Float).SetInt(receipt.L1GasUsed), receipt.FeeScalar), 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") new(big.Float).SetInt(receipt.L1Fee), "fee field in receipt matches gas used times scalar times base fee")
}
// Calculate total fee // Calculate total fee
baseFeeRecipientDiff.Add(baseFeeRecipientDiff, coinbaseDiff) baseFeeRecipientDiff.Add(baseFeeRecipientDiff, coinbaseDiff)
......
...@@ -3,8 +3,6 @@ package derive ...@@ -3,8 +3,6 @@ package derive
import ( import (
"context" "context"
"fmt" "fmt"
"math"
"math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
...@@ -75,7 +73,7 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex ...@@ -75,7 +73,7 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex
return nil, NewCriticalError(fmt.Errorf("failed to derive some deposits: %w", err)) return nil, NewCriticalError(fmt.Errorf("failed to derive some deposits: %w", err))
} }
// apply sysCfg changes // 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)) 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 ...@@ -102,28 +100,23 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex
l2Parent, nextL2Time, eth.ToBlockID(l1Info), l1Info.Time())) 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 { 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, l1InfoTx)
txs = append(txs, depositTxs...) txs = append(txs, depositTxs...)
txs = append(txs, upgradeTxs...)
var withdrawals *types.Withdrawals var withdrawals *types.Withdrawals
if ba.rollupCfg.IsCanyon(nextL2Time) { if ba.rollupCfg.IsCanyon(nextL2Time) {
......
...@@ -15,6 +15,7 @@ type UserDepositSource struct { ...@@ -15,6 +15,7 @@ type UserDepositSource struct {
const ( const (
UserDepositSourceDomain = 0 UserDepositSourceDomain = 0
L1InfoDepositSourceDomain = 1 L1InfoDepositSourceDomain = 1
UpgradeDepositSourceDomain = 2
) )
func (dep *UserDepositSource) SourceHash() common.Hash { func (dep *UserDepositSource) SourceHash() common.Hash {
...@@ -44,3 +45,21 @@ func (dep *L1InfoDepositSource) SourceHash() common.Hash { ...@@ -44,3 +45,21 @@ func (dep *L1InfoDepositSource) SourceHash() common.Hash {
copy(domainInput[32:], depositIDHash[:]) copy(domainInput[32:], depositIDHash[:])
return crypto.Keccak256Hash(domainInput[:]) 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())
}
This diff is collapsed.
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 ...@@ -270,19 +270,31 @@ func L1InfoDeposit(rollupCfg *rollup.Config, sysCfg eth.SystemConfig, seqNumber
BatcherAddr: sysCfg.BatcherAddr, BatcherAddr: sysCfg.BatcherAddr,
} }
var data []byte var data []byte
var err error
if isEcotoneButNotFirstBlock(rollupCfg, l2BlockTime) { if isEcotoneButNotFirstBlock(rollupCfg, l2BlockTime) {
l1BlockInfo.BlobBaseFee = block.BlobBaseFee() l1BlockInfo.BlobBaseFee = block.BlobBaseFee()
l1BlockInfo.BlobBaseFeeScalar = sysCfg.BlobBaseFeeScalar if l1BlockInfo.BlobBaseFee == nil {
l1BlockInfo.BaseFeeScalar = sysCfg.BaseFeeScalar // The L2 spec states to use the MIN_BLOB_GASPRICE from EIP-4844 if not yet active on L1.
data, err = l1BlockInfo.marshalBinaryEcotone() 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 { } else {
l1BlockInfo.L1FeeOverhead = sysCfg.Overhead l1BlockInfo.L1FeeOverhead = sysCfg.Overhead
l1BlockInfo.L1FeeScalar = sysCfg.Scalar l1BlockInfo.L1FeeScalar = sysCfg.Scalar
data, err = l1BlockInfo.marshalBinaryBedrock() out, err := l1BlockInfo.marshalBinaryBedrock()
}
if err != nil { 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{ source := L1InfoDepositSource{
......
...@@ -75,9 +75,9 @@ func (l1t *L1Traversal) AdvanceL1Block(ctx context.Context) error { ...@@ -75,9 +75,9 @@ func (l1t *L1Traversal) AdvanceL1Block(ctx context.Context) error {
if err != nil { 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)) 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. // 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 l1t.block = nextL1Origin
......
package derive package derive
import ( import (
"encoding/binary"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
...@@ -73,13 +74,19 @@ func PayloadToSystemConfig(rollupCfg *rollup.Config, payload *eth.ExecutionPaylo ...@@ -73,13 +74,19 @@ func PayloadToSystemConfig(rollupCfg *rollup.Config, payload *eth.ExecutionPaylo
if err != nil { if err != nil {
return eth.SystemConfig{}, fmt.Errorf("failed to parse L1 info deposit tx from L2 block: %w", err) 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{ return eth.SystemConfig{
BatcherAddr: info.BatcherAddr, BatcherAddr: info.BatcherAddr,
Overhead: info.L1FeeOverhead, Overhead: info.L1FeeOverhead,
Scalar: info.L1FeeScalar, Scalar: info.L1FeeScalar,
GasLimit: uint64(payload.GasLimit), GasLimit: uint64(payload.GasLimit),
BaseFeeScalar: info.BaseFeeScalar,
BlobBaseFeeScalar: info.BlobBaseFeeScalar,
}, err }, err
} }
} }
...@@ -2,10 +2,8 @@ package derive ...@@ -2,10 +2,8 @@ package derive
import ( import (
"bytes" "bytes"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"io"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
...@@ -23,19 +21,16 @@ var ( ...@@ -23,19 +21,16 @@ var (
SystemConfigUpdateGasConfig = common.Hash{31: 1} SystemConfigUpdateGasConfig = common.Hash{31: 1}
SystemConfigUpdateGasLimit = common.Hash{31: 2} SystemConfigUpdateGasLimit = common.Hash{31: 2}
SystemConfigUpdateUnsafeBlockSigner = common.Hash{31: 3} SystemConfigUpdateUnsafeBlockSigner = common.Hash{31: 3}
SystemConfigUpdateGasConfigEcotone = common.Hash{31: 4}
) )
var ( var (
ConfigUpdateEventABI = "ConfigUpdate(uint256,uint8,bytes)" ConfigUpdateEventABI = "ConfigUpdate(uint256,uint8,bytes)"
ConfigUpdateEventABIHash = crypto.Keccak256Hash([]byte(ConfigUpdateEventABI)) ConfigUpdateEventABIHash = crypto.Keccak256Hash([]byte(ConfigUpdateEventABI))
ConfigUpdateEventVersion0 = common.Hash{} 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 // 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 var result error
for i, rec := range receipts { for i, rec := range receipts {
if rec.Status != types.ReceiptStatusSuccessful { if rec.Status != types.ReceiptStatusSuccessful {
...@@ -43,7 +38,7 @@ func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*type ...@@ -43,7 +38,7 @@ func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*type
} }
for j, log := range rec.Logs { for j, log := range rec.Logs {
if log.Address == cfg.L1SystemConfigAddress && len(log.Topics) > 0 && log.Topics[0] == ConfigUpdateEventABIHash { 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)) 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 ...@@ -61,7 +56,7 @@ func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*type
// UpdateType indexed updateType, // UpdateType indexed updateType,
// bytes data // 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 { if len(ev.Topics) != 3 {
return fmt.Errorf("expected 3 event topics (event identity, indexed version, indexed updateType), got %d", len(ev.Topics)) 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 ...@@ -116,8 +111,18 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L
if !solabi.EmptyReader(reader) { if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes")) 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.Overhead = overhead
destSysCfg.Scalar = scalar destSysCfg.Scalar = scalar
}
return nil return nil
case SystemConfigUpdateGasLimit: case SystemConfigUpdateGasLimit:
if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 { if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
...@@ -138,35 +143,6 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L ...@@ -138,35 +143,6 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L
case SystemConfigUpdateUnsafeBlockSigner: case SystemConfigUpdateUnsafeBlockSigner:
// Ignored in derivation. This configurable applies to runtime configuration outside of the derivation. // Ignored in derivation. This configurable applies to runtime configuration outside of the derivation.
return nil 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: default:
return fmt.Errorf("unrecognized L1 sysCfg update type: %s", updateType) return fmt.Errorf("unrecognized L1 sysCfg update type: %s", updateType)
} }
......
...@@ -4,11 +4,14 @@ import ( ...@@ -4,11 +4,14 @@ import (
"math/big" "math/big"
"testing" "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/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "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 ( var (
...@@ -42,6 +45,9 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { ...@@ -42,6 +45,9 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) {
config eth.SystemConfig config eth.SystemConfig
hook func(*testing.T, *types.Log) *types.Log hook func(*testing.T, *types.Log) *types.Log
err bool err bool
// forks (optional)
ecotoneTime *uint64
l1Time uint64
}{ }{
{ {
// The log data is ignored by consensus and no modifications to the // The log data is ignored by consensus and no modifications to the
...@@ -145,25 +151,26 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { ...@@ -145,25 +151,26 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) {
Topics: []common.Hash{ Topics: []common.Hash{
ConfigUpdateEventABIHash, ConfigUpdateEventABIHash,
ConfigUpdateEventVersion0, ConfigUpdateEventVersion0,
SystemConfigUpdateGasConfigEcotone, SystemConfigUpdateGasConfig,
}, },
}, },
hook: func(t *testing.T, log *types.Log) *types.Log { hook: func(t *testing.T, log *types.Log) *types.Log {
baseFeeScalar := big.NewInt(0xaa) scalarData := common.Hash{0: 1, 24 + 3: 0xb3, 28 + 3: 0xbb}
blobBaseFeeScalar := big.NewInt(0xbb) scalar := scalarData.Big()
packed := make([]byte, 8) overhead := big.NewInt(0xff)
baseFeeScalar.FillBytes(packed[0:4]) numberData, err := twoUint256.Pack(overhead, scalar)
blobBaseFeeScalar.FillBytes(packed[4:8]) require.NoError(t, err)
data, err := bytesArgs.Pack(packed) data, err := bytesArgs.Pack(numberData)
require.NoError(t, err) require.NoError(t, err)
log.Data = data log.Data = data
return log return log
}, },
config: eth.SystemConfig{ config: eth.SystemConfig{
BaseFeeScalar: 0xaa, Scalar: eth.Bytes32{0: 1, 24 + 3: 0xb3, 28 + 3: 0xbb},
BlobBaseFeeScalar: 0xbb,
}, },
err: false, err: false,
ecotoneTime: new(uint64), // activate ecotone
l1Time: 200,
}, },
{ {
name: "SystemConfigOneTopic", name: "SystemConfigOneTopic",
...@@ -184,8 +191,9 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { ...@@ -184,8 +191,9 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
config := eth.SystemConfig{} 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 { if test.err {
require.Error(t, err) require.Error(t, err)
} else { } else {
......
...@@ -91,6 +91,12 @@ func (d *Sequencer) StartBuildingBlock(ctx context.Context) error { ...@@ -91,6 +91,12 @@ func (d *Sequencer) StartBuildingBlock(ctx context.Context) error {
// from the transaction pool. // from the transaction pool.
attrs.NoTxPool = uint64(attrs.Timestamp) > l1Origin.Time+d.rollupCfg.MaxSequencerDrift 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", d.log.Debug("prepared attributes for new block",
"num", l2Head.Number+1, "time", uint64(attrs.Timestamp), "num", l2Head.Number+1, "time", uint64(attrs.Timestamp),
"origin", l1Origin, "origin_time", l1Origin.Time, "noTxPool", attrs.NoTxPool) "origin", l1Origin, "origin_time", l1Origin.Time, "noTxPool", attrs.NoTxPool)
......
...@@ -302,7 +302,7 @@ func (c *Config) IsEcotone(timestamp uint64) bool { ...@@ -302,7 +302,7 @@ func (c *Config) IsEcotone(timestamp uint64) bool {
} }
// IsEcotoneActivationBlock returns whether the specified block is the first block subject to the // 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 { func (c *Config) IsEcotoneActivationBlock(l2BlockTime uint64) bool {
return c.IsEcotone(l2BlockTime) && return c.IsEcotone(l2BlockTime) &&
l2BlockTime >= c.BlockTime && l2BlockTime >= c.BlockTime &&
......
...@@ -2,6 +2,7 @@ package eth ...@@ -2,6 +2,7 @@ package eth
import ( import (
"bytes" "bytes"
"encoding/binary"
"fmt" "fmt"
"math/big" "math/big"
"reflect" "reflect"
...@@ -310,19 +311,64 @@ type ForkchoiceUpdatedResult struct { ...@@ -310,19 +311,64 @@ type ForkchoiceUpdatedResult struct {
type SystemConfig struct { type SystemConfig struct {
// BatcherAddr identifies the batch-sender address used in batch-inbox data-transaction filtering. // BatcherAddr identifies the batch-sender address used in batch-inbox data-transaction filtering.
BatcherAddr common.Address `json:"batcherAddr"` 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"` 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"` Scalar Bytes32 `json:"scalar"`
// GasLimit identifies the L2 block gas limit // GasLimit identifies the L2 block gas limit
GasLimit uint64 `json:"gasLimit"` 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. // 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 type Bytes48 [48]byte
func (b *Bytes48) UnmarshalJSON(text []byte) error { func (b *Bytes48) UnmarshalJSON(text []byte) error {
......
...@@ -18,3 +18,38 @@ func TestInputError(t *testing.T) { ...@@ -18,3 +18,38 @@ func TestInputError(t *testing.T) {
} }
require.ErrorIs(t, err, InputError{}, "need to detect input error with errors.Is") 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