From d7b79fa884493bf184a440327310e122003e77fa Mon Sep 17 00:00:00 2001 From: Yoshihito Aso Date: Wed, 8 Jul 2026 11:11:59 +0900 Subject: [PATCH 1/2] Add Prague/Osaka fork precompile gating --- core/vm/contracts.go | 53 ++++--- core/vm/evm.go | 13 ++ core/vm/evm_test.go | 72 ++++++++++ params/config.go | 56 +++++++- params/config_test.go | 145 ++++++++++++++++++++ tests/fuzzers/bls12381/precompile_fuzzer.go | 2 +- 6 files changed, 313 insertions(+), 28 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index e82d95fd7..845c05f8b 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -82,28 +82,20 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ // PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum // contracts used in the Berlin release. var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{0x01}): &ecrecover{}, - common.BytesToAddress([]byte{0x02}): &sha256hash{}, - common.BytesToAddress([]byte{0x03}): &ripemd160hash{}, - common.BytesToAddress([]byte{0x04}): &dataCopy{}, - common.BytesToAddress([]byte{0x05}): &bigModExp{eip2565: true}, - common.BytesToAddress([]byte{0x06}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{0x07}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{0x08}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{0x09}): &blake2F{}, - common.BytesToAddress([]byte{0x0b}): &bls12381G1Add{}, - common.BytesToAddress([]byte{0x0c}): &bls12381G1MultiExp{}, - common.BytesToAddress([]byte{0x0d}): &bls12381G2Add{}, - common.BytesToAddress([]byte{0x0e}): &bls12381G2MultiExp{}, - common.BytesToAddress([]byte{0x0f}): &bls12381Pairing{}, - common.BytesToAddress([]byte{0x10}): &bls12381MapG1{}, - common.BytesToAddress([]byte{0x11}): &bls12381MapG2{}, - common.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{}, + common.BytesToAddress([]byte{0x01}): &ecrecover{}, + common.BytesToAddress([]byte{0x02}): &sha256hash{}, + common.BytesToAddress([]byte{0x03}): &ripemd160hash{}, + common.BytesToAddress([]byte{0x04}): &dataCopy{}, + common.BytesToAddress([]byte{0x05}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{0x06}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{0x07}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{0x08}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{0x09}): &blake2F{}, } -// PrecompiledContractsBLS contains the set of pre-compiled Ethereum -// contracts specified in EIP-2537. These are exported for testing purposes. -var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ +// PrecompiledContractsPrague contains the set of pre-compiled Ethereum +// contracts activated in the Prague fork. These are exported for testing purposes. +var PrecompiledContractsPrague = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{0x0b}): &bls12381G1Add{}, common.BytesToAddress([]byte{0x0c}): &bls12381G1MultiExp{}, common.BytesToAddress([]byte{0x0d}): &bls12381G2Add{}, @@ -113,8 +105,16 @@ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{0x11}): &bls12381MapG2{}, } +// PrecompiledContractsOsaka contains the set of pre-compiled Ethereum +// contracts activated in the Osaka fork. +var PrecompiledContractsOsaka = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{}, +} + var ( PrecompiledAddressesBerlin []common.Address + PrecompiledAddressesPrague []common.Address + PrecompiledAddressesOsaka []common.Address PrecompiledAddressesIstanbul []common.Address PrecompiledAddressesByzantium []common.Address PrecompiledAddressesHomestead []common.Address @@ -133,6 +133,12 @@ func init() { for k := range PrecompiledContractsBerlin { PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k) } + for k := range PrecompiledContractsPrague { + PrecompiledAddressesPrague = append(PrecompiledAddressesPrague, k) + } + for k := range PrecompiledContractsOsaka { + PrecompiledAddressesOsaka = append(PrecompiledAddressesOsaka, k) + } } // ActivePrecompiles returns the precompiles enabled with the current configuration. @@ -148,6 +154,13 @@ func ActivePrecompiles(rules params.Rules) []common.Address { default: result = PrecompiledAddressesHomestead } + result = append([]common.Address{}, result...) + if rules.IsPrague { + result = append(result, PrecompiledAddressesPrague...) + } + if rules.IsOsaka { + result = append(result, PrecompiledAddressesOsaka...) + } if rules.IsPrivacyPrecompile { result = append(result, common.QuorumPrivacyPrecompileContractAddress()) } diff --git a/core/vm/evm.go b/core/vm/evm.go index ffdd9043b..4301b4e50 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -70,6 +70,19 @@ func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { precompiles = PrecompiledContractsHomestead } p, ok := precompiles[addr] + if ok { + return p, ok + } + if evm.chainRules.IsPrague { + if p, ok = PrecompiledContractsPrague[addr]; ok { + return p, ok + } + } + if evm.chainRules.IsOsaka { + if p, ok = PrecompiledContractsOsaka[addr]; ok { + return p, ok + } + } return p, ok } diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go index 73994ff7d..3814be8e0 100644 --- a/core/vm/evm_test.go +++ b/core/vm/evm_test.go @@ -22,6 +22,78 @@ func TestActivePrecompiles(t *testing.T) { IsPrivacyPrecompile: false, }, }, + want: []common.Address{ + common.BytesToAddress([]byte{0x01}), + common.BytesToAddress([]byte{0x02}), + common.BytesToAddress([]byte{0x03}), + common.BytesToAddress([]byte{0x04}), + common.BytesToAddress([]byte{0x05}), + common.BytesToAddress([]byte{0x06}), + common.BytesToAddress([]byte{0x07}), + common.BytesToAddress([]byte{0x08}), + common.BytesToAddress([]byte{0x09}), + }, + }, + { + name: "berlin-plus-prague", + evm: &EVM{ + chainRules: params.Rules{ + IsBerlin: true, + IsPrague: true, + IsPrivacyPrecompile: false, + }, + }, + want: []common.Address{ + common.BytesToAddress([]byte{0x01}), + common.BytesToAddress([]byte{0x02}), + common.BytesToAddress([]byte{0x03}), + common.BytesToAddress([]byte{0x04}), + common.BytesToAddress([]byte{0x05}), + common.BytesToAddress([]byte{0x06}), + common.BytesToAddress([]byte{0x07}), + common.BytesToAddress([]byte{0x08}), + common.BytesToAddress([]byte{0x09}), + common.BytesToAddress([]byte{0x0b}), + common.BytesToAddress([]byte{0x0c}), + common.BytesToAddress([]byte{0x0d}), + common.BytesToAddress([]byte{0x0e}), + common.BytesToAddress([]byte{0x0f}), + common.BytesToAddress([]byte{0x10}), + common.BytesToAddress([]byte{0x11}), + }, + }, + { + name: "berlin-plus-osaka", + evm: &EVM{ + chainRules: params.Rules{ + IsBerlin: true, + IsOsaka: true, + IsPrivacyPrecompile: false, + }, + }, + want: []common.Address{ + common.BytesToAddress([]byte{0x01}), + common.BytesToAddress([]byte{0x02}), + common.BytesToAddress([]byte{0x03}), + common.BytesToAddress([]byte{0x04}), + common.BytesToAddress([]byte{0x05}), + common.BytesToAddress([]byte{0x06}), + common.BytesToAddress([]byte{0x07}), + common.BytesToAddress([]byte{0x08}), + common.BytesToAddress([]byte{0x09}), + common.BytesToAddress([]byte{0x01, 0x00}), + }, + }, + { + name: "berlin-plus-prague-and-osaka", + evm: &EVM{ + chainRules: params.Rules{ + IsBerlin: true, + IsPrague: true, + IsOsaka: true, + IsPrivacyPrecompile: false, + }, + }, want: []common.Address{ common.BytesToAddress([]byte{0x01}), common.BytesToAddress([]byte{0x02}), diff --git a/params/config.go b/params/config.go index b6352baca..5731d04a0 100644 --- a/params/config.go +++ b/params/config.go @@ -248,21 +248,21 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, false, 32, 35, big.NewInt(0), big.NewInt(0), nil, nil, false, nil, nil} + AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, false, 32, 35, big.NewInt(0), big.NewInt(0), nil, nil, false, nil, nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Clique consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil, nil, nil, nil, false, 32, 32, big.NewInt(0), big.NewInt(0), nil, nil, false, nil, nil} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil, nil, nil, nil, false, 32, 32, big.NewInt(0), big.NewInt(0), nil, nil, false, nil, nil} // Quorum chainID should 10 - TestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, false, 32, 32, big.NewInt(0), big.NewInt(0), nil, nil, false, nil, nil} + TestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, false, 32, 32, big.NewInt(0), big.NewInt(0), nil, nil, false, nil, nil} TestRules = TestChainConfig.Rules(new(big.Int)) - QuorumTestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, true, 64, 32, big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), false, nil, nil} - QuorumMPSTestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, true, 64, 32, big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), true, nil, nil} + QuorumTestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, true, 64, 32, big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), false, nil, nil} + QuorumMPSTestChainConfig = &ChainConfig{big.NewInt(10), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil, new(EthashConfig), nil, nil, nil, nil, nil, true, 64, 32, big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), true, nil, nil} ) // TrustedCheckpoint represents a set of post-processed trie roots (CHT and @@ -344,6 +344,8 @@ type ChainConfig struct { IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul) MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated) BerlinBlock *big.Int `json:"berlinBlock,omitempty"` // Berlin switch block (nil = no fork, 0 = already on berlin) + PragueBlock *big.Int `json:"pragueBlock,omitempty"` // Prague switch block (nil = no fork, 0 = already on prague) + OsakaBlock *big.Int `json:"osakaBlock,omitempty"` // Osaka switch block (nil = no fork, 0 = already on osaka) YoloV3Block *big.Int `json:"yoloV3Block,omitempty"` // YOLO v3: Gas repricings TODO @holiman add EIP references EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) @@ -486,7 +488,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v IsQuorum: %v Constantinople: %v TransactionSizeLimit: %v MaxCodeSize: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v Catalyst: %v YOLO v3: %v PrivacyEnhancements: %v PrivacyPrecompile: %v EnableGasPriceBlock: %v Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v IsQuorum: %v Constantinople: %v TransactionSizeLimit: %v MaxCodeSize: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, Prague: %v, Osaka: %v Catalyst: %v YOLO v3: %v PrivacyEnhancements: %v PrivacyPrecompile: %v EnableGasPriceBlock: %v Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -503,6 +505,8 @@ func (c *ChainConfig) String() string { c.IstanbulBlock, c.MuirGlacierBlock, c.BerlinBlock, + c.PragueBlock, + c.OsakaBlock, c.CatalystBlock, c.YoloV3Block, c.PrivacyEnhancementsBlock, //Quorum @@ -582,6 +586,16 @@ func (c *ChainConfig) IsBerlin(num *big.Int) bool { return isForked(c.BerlinBlock, num) || isForked(c.YoloV3Block, num) } +// IsPrague returns whether num is either equal to the Prague fork block or greater. +func (c *ChainConfig) IsPrague(num *big.Int) bool { + return c.IsBerlin(num) && isForked(c.PragueBlock, num) +} + +// IsOsaka returns whether num is either equal to the Osaka fork block or greater. +func (c *ChainConfig) IsOsaka(num *big.Int) bool { + return c.IsPrague(num) && isForked(c.OsakaBlock, num) +} + // IsCatalyst returns whether num is either equal to the Merge fork block or greater. func (c *ChainConfig) IsCatalyst(num *big.Int) bool { return isForked(c.CatalystBlock, num) @@ -1060,6 +1074,26 @@ func (c *ChainConfig) CheckConfigForkOrder() error { lastFork = cur } } + if c.PragueBlock != nil { + if c.BerlinBlock == nil { + return fmt.Errorf("unsupported fork ordering: berlinBlock not enabled, but pragueBlock enabled at %v", + c.PragueBlock) + } + if c.BerlinBlock.Cmp(c.PragueBlock) > 0 { + return fmt.Errorf("unsupported fork ordering: berlinBlock enabled at %v, but pragueBlock enabled at %v", + c.BerlinBlock, c.PragueBlock) + } + } + if c.OsakaBlock != nil { + if c.PragueBlock == nil { + return fmt.Errorf("unsupported fork ordering: pragueBlock not enabled, but osakaBlock enabled at %v", + c.OsakaBlock) + } + if c.PragueBlock.Cmp(c.OsakaBlock) > 0 { + return fmt.Errorf("unsupported fork ordering: pragueBlock enabled at %v, but osakaBlock enabled at %v", + c.PragueBlock, c.OsakaBlock) + } + } return nil } @@ -1110,6 +1144,12 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int, isQuor if isForkIncompatible(c.BerlinBlock, newcfg.BerlinBlock, head) { return newCompatError("Berlin fork block", c.BerlinBlock, newcfg.BerlinBlock) } + if isForkIncompatible(c.PragueBlock, newcfg.PragueBlock, head) { + return newCompatError("Prague fork block", c.PragueBlock, newcfg.PragueBlock) + } + if isForkIncompatible(c.OsakaBlock, newcfg.OsakaBlock, head) { + return newCompatError("Osaka fork block", c.OsakaBlock, newcfg.OsakaBlock) + } if isForkIncompatible(c.YoloV3Block, newcfg.YoloV3Block, head) { return newCompatError("YOLOv3 fork block", c.YoloV3Block, newcfg.YoloV3Block) } @@ -1201,7 +1241,7 @@ type Rules struct { ChainID *big.Int IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool - IsBerlin, IsCatalyst bool + IsBerlin, IsPrague, IsOsaka, IsCatalyst bool // Quorum IsPrivacyEnhancementsEnabled bool IsPrivacyPrecompile bool @@ -1225,6 +1265,8 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), IsBerlin: c.IsBerlin(num), + IsPrague: c.IsPrague(num), + IsOsaka: c.IsOsaka(num), IsCatalyst: c.IsCatalyst(num), // Quorum IsPrivacyEnhancementsEnabled: c.IsPrivacyEnhancementsEnabled(num), diff --git a/params/config_test.go b/params/config_test.go index 4e927cc71..33f1f015e 100644 --- a/params/config_test.go +++ b/params/config_test.go @@ -245,6 +245,40 @@ func TestCheckCompatible(t *testing.T) { head: 4, wantErr: nil, }, + { + stored: &ChainConfig{PragueBlock: big.NewInt(10)}, + new: &ChainConfig{PragueBlock: big.NewInt(20)}, + head: 4, + wantErr: nil, + }, + { + stored: &ChainConfig{PragueBlock: big.NewInt(10)}, + new: &ChainConfig{PragueBlock: big.NewInt(20)}, + head: 30, + wantErr: &ConfigCompatError{ + What: "Prague fork block", + StoredConfig: big.NewInt(10), + NewConfig: big.NewInt(20), + RewindTo: 9, + }, + }, + { + stored: &ChainConfig{OsakaBlock: big.NewInt(10)}, + new: &ChainConfig{OsakaBlock: big.NewInt(20)}, + head: 4, + wantErr: nil, + }, + { + stored: &ChainConfig{OsakaBlock: big.NewInt(10)}, + new: &ChainConfig{OsakaBlock: big.NewInt(20)}, + head: 30, + wantErr: &ConfigCompatError{ + What: "Osaka fork block", + StoredConfig: big.NewInt(10), + NewConfig: big.NewInt(20), + RewindTo: 9, + }, + }, { stored: &ChainConfig{MaxCodeSizeConfig: storedMaxCodeConfig0}, new: &ChainConfig{MaxCodeSizeConfig: nil}, @@ -324,6 +358,66 @@ func TestCheckCompatible(t *testing.T) { } } +func TestCheckConfigForkOrderPragueOsaka(t *testing.T) { + config := func(berlinBlock, pragueBlock, osakaBlock *big.Int) *ChainConfig { + cfg := *TestChainConfig + cfg.BerlinBlock = berlinBlock + cfg.PragueBlock = pragueBlock + cfg.OsakaBlock = osakaBlock + return &cfg + } + + tests := []struct { + name string + config *ChainConfig + wantErr string + }{ + { + name: "prague after berlin", + config: config(big.NewInt(10), big.NewInt(20), nil), + }, + { + name: "osaka after prague", + config: config(big.NewInt(10), big.NewInt(20), big.NewInt(30)), + }, + { + name: "prague requires berlin", + config: config(nil, big.NewInt(20), nil), + wantErr: "unsupported fork ordering: berlinBlock not enabled, but pragueBlock enabled at 20", + }, + { + name: "prague cannot precede berlin", + config: config(big.NewInt(20), big.NewInt(10), nil), + wantErr: "unsupported fork ordering: berlinBlock enabled at 20, but pragueBlock enabled at 10", + }, + { + name: "osaka requires prague", + config: config(big.NewInt(10), nil, big.NewInt(30)), + wantErr: "unsupported fork ordering: pragueBlock not enabled, but osakaBlock enabled at 30", + }, + { + name: "osaka cannot precede prague", + config: config(big.NewInt(10), big.NewInt(30), big.NewInt(20)), + wantErr: "unsupported fork ordering: pragueBlock enabled at 30, but osakaBlock enabled at 20", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.config.CheckConfigForkOrder() + if tt.wantErr == "" { + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + return + } + if err == nil || err.Error() != tt.wantErr { + t.Fatalf("error mismatch:\nerr: %v\nwant: %v", err, tt.wantErr) + } + }) + } +} + func TestCheckTransitionsData(t *testing.T) { type test struct { stored *ChainConfig @@ -523,6 +617,57 @@ func TestIsQIP714(t *testing.T) { } } +func TestIsPragueAndOsaka(t *testing.T) { + tests := []struct { + name string + config *ChainConfig + block int64 + isPrague bool + isOsaka bool + }{ + { + name: "prague requires berlin", + config: &ChainConfig{PragueBlock: big.NewInt(0)}, + block: 0, + isPrague: false, + isOsaka: false, + }, + { + name: "osaka requires prague", + config: &ChainConfig{BerlinBlock: big.NewInt(0), OsakaBlock: big.NewInt(0)}, + block: 0, + isPrague: false, + isOsaka: false, + }, + { + name: "prague active after berlin and prague blocks", + config: &ChainConfig{BerlinBlock: big.NewInt(0), PragueBlock: big.NewInt(10)}, + block: 10, + isPrague: true, + isOsaka: false, + }, + { + name: "osaka active after prague and osaka blocks", + config: &ChainConfig{BerlinBlock: big.NewInt(0), PragueBlock: big.NewInt(10), OsakaBlock: big.NewInt(20)}, + block: 20, + isPrague: true, + isOsaka: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + block := big.NewInt(tt.block) + if got := tt.config.IsPrague(block); got != tt.isPrague { + t.Fatalf("IsPrague mismatch: got %v, want %v", got, tt.isPrague) + } + if got := tt.config.IsOsaka(block); got != tt.isOsaka { + t.Fatalf("IsOsaka mismatch: got %v, want %v", got, tt.isOsaka) + } + }) + } +} + func TestIsPrivacyEnhancementsEnabled(t *testing.T) { type test struct { config *ChainConfig diff --git a/tests/fuzzers/bls12381/precompile_fuzzer.go b/tests/fuzzers/bls12381/precompile_fuzzer.go index 238531362..4fd0b91ee 100644 --- a/tests/fuzzers/bls12381/precompile_fuzzer.go +++ b/tests/fuzzers/bls12381/precompile_fuzzer.go @@ -73,7 +73,7 @@ func checkInput(id byte, inputLen int) bool { // other values are reserved for future use. func fuzz(id byte, data []byte) int { // Even on bad input, it should not crash, so we still test the gas calc - precompile := vm.PrecompiledContractsBLS[common.BytesToAddress([]byte{id})] + precompile := vm.PrecompiledContractsPrague[common.BytesToAddress([]byte{id})] gas := precompile.RequiredGas(data) if !checkInput(id, len(data)) { return 0 From dc380bd1a7618be5a20d7df0e39d6faf2c0de0f3 Mon Sep 17 00:00:00 2001 From: Yoshihito Aso Date: Wed, 8 Jul 2026 13:49:57 +0900 Subject: [PATCH 2/2] Fix Prague/Osaka precompile selection --- core/vm/contracts.go | 35 +++++++++++++++++++++++++++++------ core/vm/evm.go | 17 ++++------------- core/vm/evm_test.go | 7 +++++++ 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 845c05f8b..ef2ef16d8 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -96,6 +96,15 @@ var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ // PrecompiledContractsPrague contains the set of pre-compiled Ethereum // contracts activated in the Prague fork. These are exported for testing purposes. var PrecompiledContractsPrague = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{0x01}): &ecrecover{}, + common.BytesToAddress([]byte{0x02}): &sha256hash{}, + common.BytesToAddress([]byte{0x03}): &ripemd160hash{}, + common.BytesToAddress([]byte{0x04}): &dataCopy{}, + common.BytesToAddress([]byte{0x05}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{0x06}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{0x07}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{0x08}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{0x09}): &blake2F{}, common.BytesToAddress([]byte{0x0b}): &bls12381G1Add{}, common.BytesToAddress([]byte{0x0c}): &bls12381G1MultiExp{}, common.BytesToAddress([]byte{0x0d}): &bls12381G2Add{}, @@ -108,6 +117,22 @@ var PrecompiledContractsPrague = map[common.Address]PrecompiledContract{ // PrecompiledContractsOsaka contains the set of pre-compiled Ethereum // contracts activated in the Osaka fork. var PrecompiledContractsOsaka = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{0x01}): &ecrecover{}, + common.BytesToAddress([]byte{0x02}): &sha256hash{}, + common.BytesToAddress([]byte{0x03}): &ripemd160hash{}, + common.BytesToAddress([]byte{0x04}): &dataCopy{}, + common.BytesToAddress([]byte{0x05}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{0x06}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{0x07}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{0x08}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{0x09}): &blake2F{}, + common.BytesToAddress([]byte{0x0b}): &bls12381G1Add{}, + common.BytesToAddress([]byte{0x0c}): &bls12381G1MultiExp{}, + common.BytesToAddress([]byte{0x0d}): &bls12381G2Add{}, + common.BytesToAddress([]byte{0x0e}): &bls12381G2MultiExp{}, + common.BytesToAddress([]byte{0x0f}): &bls12381Pairing{}, + common.BytesToAddress([]byte{0x10}): &bls12381MapG1{}, + common.BytesToAddress([]byte{0x11}): &bls12381MapG2{}, common.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{}, } @@ -145,6 +170,10 @@ func init() { func ActivePrecompiles(rules params.Rules) []common.Address { var result []common.Address switch { + case rules.IsOsaka: + result = PrecompiledAddressesOsaka + case rules.IsPrague: + result = PrecompiledAddressesPrague case rules.IsBerlin: result = PrecompiledAddressesBerlin case rules.IsIstanbul: @@ -155,12 +184,6 @@ func ActivePrecompiles(rules params.Rules) []common.Address { result = PrecompiledAddressesHomestead } result = append([]common.Address{}, result...) - if rules.IsPrague { - result = append(result, PrecompiledAddressesPrague...) - } - if rules.IsOsaka { - result = append(result, PrecompiledAddressesOsaka...) - } if rules.IsPrivacyPrecompile { result = append(result, common.QuorumPrivacyPrecompileContractAddress()) } diff --git a/core/vm/evm.go b/core/vm/evm.go index 4301b4e50..d297fd318 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -60,6 +60,10 @@ type ( func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { var precompiles map[common.Address]PrecompiledContract switch { + case evm.chainRules.IsOsaka: + precompiles = PrecompiledContractsOsaka + case evm.chainRules.IsPrague: + precompiles = PrecompiledContractsPrague case evm.chainRules.IsBerlin: precompiles = PrecompiledContractsBerlin case evm.chainRules.IsIstanbul: @@ -70,19 +74,6 @@ func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { precompiles = PrecompiledContractsHomestead } p, ok := precompiles[addr] - if ok { - return p, ok - } - if evm.chainRules.IsPrague { - if p, ok = PrecompiledContractsPrague[addr]; ok { - return p, ok - } - } - if evm.chainRules.IsOsaka { - if p, ok = PrecompiledContractsOsaka[addr]; ok { - return p, ok - } - } return p, ok } diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go index 3814be8e0..038c5e1a5 100644 --- a/core/vm/evm_test.go +++ b/core/vm/evm_test.go @@ -81,6 +81,13 @@ func TestActivePrecompiles(t *testing.T) { common.BytesToAddress([]byte{0x07}), common.BytesToAddress([]byte{0x08}), common.BytesToAddress([]byte{0x09}), + common.BytesToAddress([]byte{0x0b}), + common.BytesToAddress([]byte{0x0c}), + common.BytesToAddress([]byte{0x0d}), + common.BytesToAddress([]byte{0x0e}), + common.BytesToAddress([]byte{0x0f}), + common.BytesToAddress([]byte{0x10}), + common.BytesToAddress([]byte{0x11}), common.BytesToAddress([]byte{0x01, 0x00}), }, },