Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Enable Shanghai, Cancun and Prague EIPs test by Cancun and Prague forks #154

Merged
merged 15 commits into from
Dec 4, 2024
26 changes: 26 additions & 0 deletions blockchain/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
package vm

import (
"bytes"
"crypto/ecdsa"
"crypto/sha256"
"encoding/binary"
Expand Down Expand Up @@ -216,6 +217,31 @@ func ActivePrecompiles(rules params.Rules) []common.Address {
}
}

// IsPrecompiledContractAddress returns true if this is used for TestExecutionSpecState and the input address is one of precompiled contract addresses.
func IsPrecompiledContractAddress(addr common.Address, rules params.Rules) bool {
if relaxPrecompileRangeForTest {
activePrecompiles := ActivePrecompiles(rules)
for _, pre := range activePrecompiles {
// skip 0x0a and 0x0b if before Prague
if !rules.IsPrague && (bytes.Compare(pre.Bytes(), []byte{10}) == 0 || bytes.Compare(pre.Bytes(), []byte{11}) == 0) {
continue
}
if bytes.Compare(pre.Bytes(), addr.Bytes()) == 0 {
return true
}
}
return false
}
return common.IsPrecompiledContractAddress(addr)
}

var relaxPrecompileRangeForTest bool

// Only for testing. Make sure to reset (false) after test.
func RelaxPrecompileRangeForTest(enable bool) {
relaxPrecompileRangeForTest = enable
}

// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract, evm *EVM) (ret []byte, computationCost uint64, err error) {
gas, computationCost := p.GetRequiredGasAndComputationCost(input)
Expand Down
4 changes: 2 additions & 2 deletions blockchain/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ func (evm *EVM) Call(caller types.ContractRef, addr common.Address, input []byte
)

// Filter out invalid precompiled address calls, and create a precompiled contract object if it is not exist.
if common.IsPrecompiledContractAddress(addr) {
if IsPrecompiledContractAddress(addr, evm.chainRules) {
precompiles := evm.GetPrecompiledContractMap(caller.Address())
if precompiles[addr] == nil || value.Sign() != 0 {
// Return an error if an enabled precompiled address is called or a value is transferred to a precompiled address.
Expand Down Expand Up @@ -527,7 +527,7 @@ func (evm *EVM) create(caller types.ContractRef, codeAndHash *codeAndHash, gas u
return nil, common.Address{}, 0, ErrContractAddressCollision
}

if common.IsPrecompiledContractAddress(address) {
if IsPrecompiledContractAddress(address, evm.chainRules) {
return nil, common.Address{}, gas, kerrors.ErrPrecompiledContractAddress
}

Expand Down
7 changes: 4 additions & 3 deletions build/checksums.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# This file contains sha256 checksums of optional build dependencies.

# version:spec-tests 2.1.0
# version:spec-tests 3.0.0
# https://github.com/ethereum/execution-spec-tests/releases
# https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/
ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz
# https://github.com/ethereum/execution-spec-tests/releases/download/v3.0.0/
4a750a0554158b4a47def19aa1409f5f75ccbebad223d41a2148790b06399528 fixtures_develop.tar.gz
9d12f331d21cdc580f658a9891133ac26da703351d2585077935e6abde72851d fixtures_pectra-devnet-4.tar.gz
22 changes: 22 additions & 0 deletions build/ci.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ func doTest(cmdline []string) {
// Get test fixtures.
csdb := build.MustLoadChecksums("build/checksums.txt")
downloadSpecTestFixtures(csdb, *cachedir)
downloadPragueSpecTestFixtures(csdb, *cachedir)

packages := []string{"./..."}
if len(flag.CommandLine.Args()) > 0 {
Expand Down Expand Up @@ -317,6 +318,27 @@ func downloadSpecTestFixtures(csdb *build.ChecksumDB, cachedir string) string {
return filepath.Join(cachedir, base)
}

// downloadPragueSpecTestFixtures downloads and extracts the execution-spec-tests fixtures for Prague.
func downloadPragueSpecTestFixtures(csdb *build.ChecksumDB, cachedir string) string {
hyunsooda marked this conversation as resolved.
Show resolved Hide resolved
// remove prague of v3
os.RemoveAll(executionSpecTestsDir + "/fixtures/blockchain_tests/prague")
os.RemoveAll(executionSpecTestsDir + "/fixtures/blockchain_tests_engine/prague")
os.RemoveAll(executionSpecTestsDir + "/fixtures/state_tests/prague")

executionSpecTestsVersion := "[email protected]"
ext := ".tar.gz"
base := "fixtures_pectra-devnet-4"
url := fmt.Sprintf("https://github.com/ethereum/execution-spec-tests/releases/download/%s/%s%s", executionSpecTestsVersion, base, ext)
archivePath := filepath.Join(cachedir, base+ext)
if err := csdb.DownloadFile(url, archivePath); err != nil {
log.Fatal(err)
}
if err := build.ExtractArchive(archivePath, executionSpecTestsDir); err != nil {
log.Fatal(err)
}
return filepath.Join(cachedir, base)
}

func doCover(cmdline []string) {
var (
parallel = flag.Int("p", 0, "The number of parallel coverage test executions (default: the number of CPUs available)")
Expand Down
8 changes: 0 additions & 8 deletions common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,20 +357,12 @@ var relaxPrecompileRangeForTest bool

// IsPrecompiledContractAddress returns true if the input address is in the range of precompiled contract addresses.
func IsPrecompiledContractAddress(addr Address) bool {
if relaxPrecompileRangeForTest {
return false
}
if bytes.Compare(addr.Bytes(), lastPrecompiledContractAddressHex) > 0 || addr == (Address{}) {
return false
}
return true
}

// Only for testing. Make sure to reset (false) after test.
func RelaxPrecompileRangeForTest(enable bool) {
relaxPrecompileRangeForTest = enable
}

// IsHexAddress verifies whether a string can represent a valid hex-encoded
// Kaia address or not.
func IsHexAddress(s string) bool {
Expand Down
18 changes: 13 additions & 5 deletions tests/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ type ExecutionSpecStateTestSuite struct {
}

func (suite *ExecutionSpecStateTestSuite) SetupSuite() {
common.RelaxPrecompileRangeForTest(true)
vm.RelaxPrecompileRangeForTest(true)
}

func (suite *ExecutionSpecStateTestSuite) TearDownSuite() {
common.RelaxPrecompileRangeForTest(false)
vm.RelaxPrecompileRangeForTest(false)
}

func (suite *ExecutionSpecStateTestSuite) TestExecutionSpecState() {
Expand All @@ -91,9 +91,17 @@ func (suite *ExecutionSpecStateTestSuite) TestExecutionSpecState() {
st.skipLoad(`^frontier\/`)
st.skipLoad(`^homestead\/`)
st.skipLoad(`^byzantium\/`)
st.skipLoad(`^constantinople\/`)
st.skipLoad(`^istanbul\/`)
st.skipLoad(`^berlin\/`)
st.skipLoad(`^cancun\/`)

// tests to skip
// unsupported EIPs
st.skipLoad(`^cancun\/eip4788_beacon_root\/`)
st.skipLoad(`^cancun\/eip4844_blobs\/`)
st.skipLoad(`^prague\/eip7702_set_code_tx\/`)
// calculate the different consumed gas because 0x0a and 0x0b contract is set to access list by ActivePrecompiles in Cancun
st.skipLoad(`^prague\/eip2537_bls_12_381_precompiles\/bls12_precompiles_before_fork\/precompile_before_fork.json\/tests\/prague\/eip2537_bls_12_381_precompiles\/test_bls12_precompiles_before_fork.py::test_precompile_before_fork`)

st.walk(t, executionSpecStateTestDir, func(t *testing.T, name string, test *StateTest) {
execStateTest(t, st, test, name, []string{
Expand All @@ -105,7 +113,7 @@ func (suite *ExecutionSpecStateTestSuite) TestExecutionSpecState() {
"Istanbul",
"Berlin",
"London",
"Merge",
"Merge", "Paris",
ulbqb marked this conversation as resolved.
Show resolved Hide resolved
"Shanghai",
// "Cancun",
// "Prague",
Expand All @@ -129,7 +137,7 @@ func execStateTest(t *testing.T, st *testMatcher, test *StateTest, name string,
}
}
withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error {
_, err := test.Run(subtest, vmconfig, isTestExecutionSpecState)
err := test.Run(subtest, vmconfig, isTestExecutionSpecState)
return st.checkFailure(t, name, err)
})
})
Expand Down
124 changes: 97 additions & 27 deletions tests/state_test_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,11 @@ type stJSON struct {
}

type stPostState struct {
Root common.UnprefixedHash `json:"hash"`
Logs common.UnprefixedHash `json:"logs"`
Indexes struct {
Root common.UnprefixedHash `json:"hash"`
Logs common.UnprefixedHash `json:"logs"`
TxBytes hexutil.Bytes `json:"txbytes"`
ExpectException string `json:"expectException"`
Indexes struct {
Data int `json:"data"`
Gas int `json:"gas"`
Value int `json:"value"`
Expand Down Expand Up @@ -156,11 +158,64 @@ func (t *StateTest) Subtests() []StateSubtest {
return sub
}

// Run executes a specific subtest.
func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, isTestExecutionSpecState bool) (*state.StateDB, error) {
// checkError checks if the error returned by the state transition matches any expected error.
// A failing expectation returns a wrapped version of the original error, if any,
// or a new error detailing the failing expectation.
// This function does not return or modify the original error, it only evaluates and returns expectations for the error.
func (t *StateTest) checkError(subtest StateSubtest, err error) error {
expectedError := t.json.Post[subtest.Fork][subtest.Index].ExpectException
if err == nil && expectedError == "" {
return nil
}
if err == nil && expectedError != "" {
return fmt.Errorf("expected error %q, got no error", expectedError)
}
if err != nil && expectedError == "" {
return fmt.Errorf("unexpected error: %w", err)
}
if err != nil && expectedError != "" {
// Ignore expected errors (TODO MariusVanDerWijden check error string)
return nil
}
return nil
}

func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, isTestExecutionSpecState bool) (result error) {
st, root, err := t.RunNoVerify(subtest, vmconfig, isTestExecutionSpecState)

// kaia-core-tests doesn't support ExpectException
if isTestExecutionSpecState {
checkedErr := t.checkError(subtest, err)
if checkedErr != nil {
return checkedErr
}
}

// The error has been checked; if it was unexpected, it's already returned.
if err != nil {
ian0371 marked this conversation as resolved.
Show resolved Hide resolved
// Here, an error exists but it was expected.
// We do not check the post state or logs.
return nil
}
post := t.json.Post[subtest.Fork][subtest.Index]
// N.B: We need to do this in a two-step process, because the first Commit takes care
// of self-destructs, and we need to touch the coinbase _after_ it has potentially self-destructed.
if root != common.Hash(post.Root) {
return fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root)
}
if logs := rlpHash(st.Logs()); logs != common.Hash(post.Logs) {
return fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs)
}
st, _ = state.New(root, st.Database(), nil, nil)
return nil
}

// RunNoVerify runs a specific subtest and returns the statedb and post-state root.
// Remember to call state.Close after verifying the test result!
func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, isTestExecutionSpecState bool) (st *state.StateDB, root common.Hash, err error) {
config, eips, err := getVMConfig(subtest.Fork)
if err != nil {
return nil, UnsupportedForkError{subtest.Fork}
return st, common.Hash{}, UnsupportedForkError{subtest.Fork}
}

if isTestExecutionSpecState {
Expand All @@ -175,63 +230,77 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, isTestExecutio
blockchain.InitDeriveSha(config)
block := t.genesis(config).ToBlock(common.Hash{}, nil)
memDBManager := database.NewMemoryDBManager()
statedb := MakePreState(memDBManager, t.json.Pre)
st = MakePreState(memDBManager, t.json.Pre)

post := t.json.Post[subtest.Fork][subtest.Index]
rules := config.Rules(block.Number())
msg, err := t.json.Tx.toMessage(post, rules, isTestExecutionSpecState)
if err != nil {
return nil, err
return st, common.Hash{}, err
}

// Try to recover tx with current signer
if len(post.TxBytes) != 0 {
var ttx types.Transaction
hyunsooda marked this conversation as resolved.
Show resolved Hide resolved
txBytes := post.TxBytes
if post.TxBytes[0] <= 4 {
txBytes = append([]byte{byte(types.EthereumTxTypeEnvelope)}, txBytes...)
}
err = ttx.UnmarshalBinary(txBytes)
if err != nil {
return st, common.Hash{}, err
}
if _, err := types.Sender(types.LatestSigner(config), &ttx); err != nil {
return st, common.Hash{}, err
}
}

txContext := blockchain.NewEVMTxContext(msg, block.Header(), config)
if isTestExecutionSpecState {
txContext.GasPrice, err = useEthGasPrice(rules, &t.json)
if err != nil {
return nil, err
return st, common.Hash{}, err
}
}
blockContext := blockchain.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase)
blockContext.GetHash = vmTestBlockHash
evm := vm.NewEVM(blockContext, txContext, statedb, config, &vmconfig)
if isTestExecutionSpecState {
blockContext.GasLimit = t.json.Env.GasLimit
}
evm := vm.NewEVM(blockContext, txContext, st, config, &vmconfig)

if isTestExecutionSpecState {
useEthOpCodeGas(rules, evm)
}

snapshot := statedb.Snapshot()
snapshot := st.Snapshot()
result, err := blockchain.ApplyMessage(evm, msg)
if err != nil {
statedb.RevertToSnapshot(snapshot)
}

if isTestExecutionSpecState && err == nil {
useEthMiningReward(statedb, evm, &t.json.Tx, t.json.Env.BaseFee, result.UsedGas, txContext.GasPrice)
st.RevertToSnapshot(snapshot)
Mdaiki0730 marked this conversation as resolved.
Show resolved Hide resolved
return st, common.Hash{}, err
}

if logs := rlpHash(statedb.Logs()); logs != common.Hash(post.Logs) {
return statedb, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs)
if isTestExecutionSpecState {
useEthMiningReward(st, evm, &t.json.Tx, t.json.Env.BaseFee, result.UsedGas, txContext.GasPrice)
}

root, _ := statedb.Commit(true)
root, _ = st.Commit(true)
// Add 0-value mining reward. This only makes a difference in the cases
// where
// - the coinbase self-destructed, or
// - there are only 'bad' transactions, which aren't executed. In those cases,
// the coinbase gets no txfee, so isn't created, and thus needs to be touched
statedb.AddBalance(block.Rewardbase(), new(big.Int))
st.AddBalance(block.Rewardbase(), new(big.Int))
// And _now_ get the state root
root = statedb.IntermediateRoot(true)
root = st.IntermediateRoot(true)

if isTestExecutionSpecState {
root, err = useEthStateRoot(statedb)
root, err = useEthStateRoot(st)
if err != nil {
return nil, err
return st, common.Hash{}, err
}
}
if root != common.Hash(post.Root) {
return statedb, fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root)
}
return statedb, nil
return st, root, err
}

func (t *StateTest) gasLimit(subtest StateSubtest) uint64 {
Expand All @@ -243,6 +312,7 @@ func MakePreState(db database.DBManager, accounts blockchain.GenesisAlloc) *stat
statedb, _ := state.New(common.Hash{}, sdb, nil, nil)
for addr, a := range accounts {
if len(a.Code) != 0 {
statedb.CreateSmartContractAccount(addr, params.CodeFormatEVM, params.Rules{IsIstanbul: true})
statedb.SetCode(addr, a.Code)
}
for k, v := range a.Storage {
Expand Down
Loading