From 75bcaaf9a9ac693f1e31c24aa3799c1d12b39645 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Wed, 28 May 2025 13:49:48 -0700 Subject: [PATCH 1/5] Eric/add workflows (#5) * Add workflows * Don't change this --- .github/workflows/build.yml | 33 +++++++++++++ .github/workflows/dependencies.yml | 28 +++++++++++ .github/workflows/lint.yml | 72 ++++++++++++++++++++++++++++ .github/workflows/markdown-links.yml | 29 +++++++++++ .github/workflows/security.yml | 37 ++++++++++++++ .github/workflows/super-linter.yml | 38 +++++++++++++++ .github/workflows/test.yml | 35 ++++++++++++++ 7 files changed, 272 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/dependencies.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/markdown-links.yml create mode 100644 .github/workflows/security.yml create mode 100644 .github/workflows/super-linter.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000000..ab82de990a1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,33 @@ +name: Build +on: + pull_request: + branches: + - master + +jobs: + cleanup-runs: + runs-on: ubuntu-latest + steps: + - uses: rokroskar/workflow-run-cleanup-action@master + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + if: "!startsWith(github.ref, 'refs/tags/') && github.ref != 'refs/heads/main'" + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: 1.19 + check-latest: true + - uses: technote-space/get-diff-action@v6.1.2 + id: git_diff + with: + PATTERNS: | + **/**.go + go.mod + go.sum + - run: | + make build + if: env.GIT_DIFF diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml new file mode 100644 index 00000000000..8ab944a13f7 --- /dev/null +++ b/.github/workflows/dependencies.yml @@ -0,0 +1,28 @@ +name: "Dependency Review" +on: pull_request + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v3 + with: + go-version: 1.19 + check-latest: true + - name: "Checkout Repository" + uses: actions/checkout@v3 + - uses: technote-space/get-diff-action@v6.1.2 + with: + PATTERNS: | + **/**.go + go.mod + go.sum + - name: "Dependency Review" + uses: actions/dependency-review-action@v3 + if: env.GIT_DIFF + - name: "Go vulnerability check" + run: make vulncheck + if: env.GIT_DIFF diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000000..877682899e6 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,72 @@ +name: Lint +# Lint runs golangci-lint over the entire ethermint repository This workflow is +# run on every pull request and push to main The `golangci` will pass without +# running if no *.{go, mod, sum} files have been changed. +on: + pull_request: + push: + branches: + - master +jobs: + golangci: + name: Run golangci-lint + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + # Required: setup-go, for all versions v3.0.0+ of golangci-lint + - uses: actions/setup-go@v3 + with: + go-version: 1.19 + check-latest: true + - uses: actions/checkout@v3 + - uses: technote-space/get-diff-action@v6.1.2 + with: + PATTERNS: | + **/**.go + go.mod + go.sum + - uses: golangci/golangci-lint-action@v3.3.1 + with: + # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. + version: latest + args: --timeout 10m + github-token: ${{ secrets.github_token }} + # Check only if there are differences in the source code + if: env.GIT_DIFF + markdown-lint: + name: Run markdown-lint + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v3 + - uses: technote-space/get-diff-action@v6.1.2 + with: + PATTERNS: | + docs/**/*.md + x/**/*.md + README.md + - uses: nosborn/github-action-markdown-cli@v3.2.0 + with: + files: . + config_file: .markdownlint.yml + ignore_path: .markdownlintignore + # Check only if there are differences in the source code + if: env.GIT_DIFF + gomod2nix: + name: Check gomod2nix.toml file is up to date + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.3.4 + - uses: cachix/install-nix-action@v18 + - uses: cachix/cachix-action@v12 + with: + name: ethermint + - uses: technote-space/get-diff-action@v6.1.2 + with: + PATTERNS: | + **/**.py + - name: run gomod2nix + run: | + nix run -f ./nix gomod2nix + git diff --no-ext-diff --exit-code + if: env.GIT_DIFF diff --git a/.github/workflows/markdown-links.yml b/.github/workflows/markdown-links.yml new file mode 100644 index 00000000000..f1fefae8436 --- /dev/null +++ b/.github/workflows/markdown-links.yml @@ -0,0 +1,29 @@ +name: Check Markdown links +on: + pull_request: + paths: + - '**.md' + push: + branches: + - master + paths: + - '**.md' + +jobs: + markdown-link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: technote-space/get-diff-action@v6.1.2 + id: git_diff + with: + PATTERNS: | + **/**.md + - uses: gaurav-nelson/github-action-markdown-link-check@master + with: + folder-path: "docs" + check-modified-files-only: "yes" + use-quiet-mode: "yes" + base-branch: "main" + config-file: "mlc_config.json" + if: env.GIT_DIFF diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 00000000000..df9be0ec6bb --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,37 @@ +name: Run Gosec +on: + pull_request: + push: + branches: + - master + +jobs: + Gosec: + permissions: + security-events: write + + runs-on: ubuntu-latest + env: + GO111MODULE: on + steps: + - name: Checkout Source + uses: actions/checkout@v3 + - name: Get Diff + uses: technote-space/get-diff-action@v6.1.2 + with: + PATTERNS: | + **/*.go + go.mod + go.sum + - name: Run Gosec Security Scanner + uses: cosmos/gosec@master + with: + # we let the report trigger content trigger a failure using the GitHub Security features. + args: "-no-fail -fmt sarif -out results.sarif ./..." + if: "env.GIT_DIFF_FILTERED != ''" + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@v2 + with: + # Path to SARIF file relative to the root of the repository + sarif_file: results.sarif + if: "env.GIT_DIFF_FILTERED != ''" diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml new file mode 100644 index 00000000000..7a5b55f5a14 --- /dev/null +++ b/.github/workflows/super-linter.yml @@ -0,0 +1,38 @@ +# This workflow executes several linters on changed files based on languages used in your code base whenever +# you push a code or open a pull request. +# +# You can adjust the behavior by modifying this file. +# For more information, see: +# https://github.com/github/super-linter +--- +name: Lint Code Base + +on: + push: + branches: ["master"] + pull_request: + branches: ["master"] +jobs: + run-lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + # Full git history is needed to get a proper list of changed files within `super-linter` + fetch-depth: 0 + + - name: Lint Code Base + uses: github/super-linter@v4 + env: + LINTER_RULES_PATH: / + YAML_CONFIG_FILE: .yamllint + VALIDATE_ALL_CODEBASE: false + MARKDOWN_CONFIG_FILE: .markdownlint.yml + PROTOBUF_CONFIG_FILE: .protolint.yml + VALIDATE_NATURAL_LANGUAGE: false + VALIDATE_OPENAPI: false + VALIDATE_JSCPD: false + VALIDATE_GO: false + DEFAULT_BRANCH: "master" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000000..2b9aa299a70 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,35 @@ +name: Tests +on: + pull_request: + push: + branches: + - master + - release/** + +jobs: + cleanup-runs: + runs-on: ubuntu-latest + steps: + - uses: rokroskar/workflow-run-cleanup-action@master + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + if: "!startsWith(github.ref, 'refs/tags/') && github.ref != 'refs/heads/master'" + + test-all: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v3 + with: + go-version: 1.19 + check-latest: true + - uses: actions/checkout@v3 + - uses: technote-space/get-diff-action@v6.1.2 + with: + PATTERNS: | + **/**.go + go.mod + go.sum + - name: Test and Create Coverage Report + run: | + make test all + if: env.GIT_DIFF From 98795aa589d54a34a51a1805ffc97edd94f76d51 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Wed, 28 May 2025 14:28:10 -0700 Subject: [PATCH 2/5] Merge patches onto upstream geth 1.15 (#4) * Merge patches onto upstream geth 1.15 * Fix test failures * Tests pass * Update go version in CI * Update go version in CI in all places --- .github/workflows/build.yml | 2 +- .github/workflows/dependencies.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- core/state_transition.go | 2 +- core/vm/contract.go | 16 +- core/vm/contracts.go | 326 ++++++++++++++------ core/vm/contracts_fuzz_test.go | 4 +- core/vm/contracts_test.go | 11 +- core/vm/custom_contract.go | 30 ++ core/vm/custom_contracts.go | 62 ++++ core/vm/custom_eip.go | 151 +++++++++ core/vm/custom_eip_test.go | 192 ++++++++++++ core/vm/custom_eip_testing.go | 30 ++ core/vm/evm.go | 60 +++- core/vm/instructions_test.go | 26 +- core/vm/interface.go | 20 ++ core/vm/interpreter.go | 27 ++ core/vm/jump_table.go | 2 +- core/vm/opcode_hooks.go | 48 +++ internal/ethapi/api_test.go | 4 +- internal/ethapi/override/override_test.go | 6 +- tests/fuzzers/bls12381/precompile_fuzzer.go | 4 +- 23 files changed, 897 insertions(+), 132 deletions(-) create mode 100644 core/vm/custom_contract.go create mode 100644 core/vm/custom_contracts.go create mode 100644 core/vm/custom_eip.go create mode 100644 core/vm/custom_eip_test.go create mode 100644 core/vm/custom_eip_testing.go create mode 100644 core/vm/opcode_hooks.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ab82de990a1..da5f5796675 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: 1.19 + go-version: 1.23 check-latest: true - uses: technote-space/get-diff-action@v6.1.2 id: git_diff diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 8ab944a13f7..fef353d4c5e 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/setup-go@v3 with: - go-version: 1.19 + go-version: 1.23 check-latest: true - name: "Checkout Repository" uses: actions/checkout@v3 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 877682899e6..2ee61cb2e52 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: # Required: setup-go, for all versions v3.0.0+ of golangci-lint - uses: actions/setup-go@v3 with: - go-version: 1.19 + go-version: 1.23 check-latest: true - uses: actions/checkout@v3 - uses: technote-space/get-diff-action@v6.1.2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2b9aa299a70..054edf1ffd1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/setup-go@v3 with: - go-version: 1.19 + go-version: 1.23 check-latest: true - uses: actions/checkout@v3 - uses: technote-space/get-diff-action@v6.1.2 diff --git a/core/state_transition.go b/core/state_transition.go index 3ec718c52c3..ca270f27c7d 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -478,7 +478,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList) + st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, st.evm.ActivePrecompiles(), msg.AccessList) var ( ret []byte diff --git a/core/vm/contract.go b/core/vm/contract.go index 0eaa91d9596..cc6019623fe 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -42,8 +42,9 @@ type Contract struct { IsDeployment bool IsSystemCall bool - Gas uint64 - value *uint256.Int + Gas uint64 + value *uint256.Int + isPrecompile bool } // NewContract returns a new contract environment for the execution of EVM. @@ -62,6 +63,10 @@ func NewContract(caller common.Address, address common.Address, value *uint256.I } func (c *Contract) validJumpdest(dest *uint256.Int) bool { + if c.isPrecompile { + return false + } + udest, overflow := dest.Uint64WithOverflow() // PC cannot go beyond len(code) and certainly can't be bigger than 63bits. // Don't bother checking for JUMPDEST in that case. @@ -78,6 +83,10 @@ func (c *Contract) validJumpdest(dest *uint256.Int) bool { // isCode returns true if the provided PC location is an actual opcode, as // opposed to a data-segment following a PUSHN operation. func (c *Contract) isCode(udest uint64) bool { + if c.isPrecompile { + return false + } + // Do we already have an analysis laying around? if c.analysis != nil { return c.analysis.codeSegment(udest) @@ -160,6 +169,9 @@ func (c *Contract) Value() *uint256.Int { // SetCallCode sets the code of the contract, func (c *Contract) SetCallCode(hash common.Hash, code []byte) { + if c.isPrecompile { + return + } c.Code = code c.CodeHash = hash } diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 92a4e7d0166..166c11426a6 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/crypto/bn256" "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" "golang.org/x/crypto/ripemd160" ) @@ -43,8 +44,9 @@ import ( // requires a deterministic gas count based on the input size of the Run method of the // contract. type PrecompiledContract interface { - RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use - Run(input []byte) ([]byte, error) // Run runs the precompiled contract + Address() common.Address + RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use + Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) // Run runs the precompiled contract } // PrecompiledContracts contains the precompiled contracts supported at the given fork. @@ -248,48 +250,71 @@ func ActivePrecompiles(rules params.Rules) []common.Address { // - the returned bytes, // - the _remaining_ gas, // - any error that occurred -func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, logger *tracing.Hooks) (ret []byte, remainingGas uint64, err error) { +func (evm *EVM) RunPrecompiledContract( + p PrecompiledContract, + caller common.Address, + input []byte, + suppliedGas uint64, + value *uint256.Int, + readOnly bool, + logger *tracing.Hooks, +) (ret []byte, remainingGas uint64, err error) { + return runPrecompiledContract(evm, p, caller, input, suppliedGas, value, readOnly, logger) +} + +func runPrecompiledContract(evm *EVM, p PrecompiledContract, caller common.Address, input []byte, suppliedGas uint64, + value *uint256.Int, readOnly bool, logger *tracing.Hooks) (ret []byte, remainingGas uint64, err error) { + addrCopy := p.Address() + inputCopy := make([]byte, len(input)) + copy(inputCopy, input) + + contract := NewPrecompile(caller, addrCopy, value, suppliedGas) + contract.Input = inputCopy + gasCost := p.RequiredGas(input) - if suppliedGas < gasCost { + if !contract.UseGas(gasCost, logger, tracing.GasChangeCallPrecompiledContract) { return nil, 0, ErrOutOfGas } - if logger != nil && logger.OnGasChange != nil { - logger.OnGasChange(suppliedGas, suppliedGas-gasCost, tracing.GasChangeCallPrecompiledContract) - } - suppliedGas -= gasCost - output, err := p.Run(input) - return output, suppliedGas, err + + output, err := p.Run(evm, contract, readOnly) + return output, contract.Gas, err } // ecrecover implemented as a native contract. type ecrecover struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (ecrecover) Address() common.Address { + return common.BytesToAddress([]byte{1}) +} + func (c *ecrecover) RequiredGas(input []byte) uint64 { return params.EcrecoverGas } -func (c *ecrecover) Run(input []byte) ([]byte, error) { +func (c *ecrecover) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { const ecRecoverInputLength = 128 - input = common.RightPadBytes(input, ecRecoverInputLength) + contract.Input = common.RightPadBytes(contract.Input, ecRecoverInputLength) // "input" is (hash, v, r, s), each 32 bytes // but for ecrecover we want (r, s, v) - r := new(big.Int).SetBytes(input[64:96]) - s := new(big.Int).SetBytes(input[96:128]) - v := input[63] - 27 + r := new(big.Int).SetBytes(contract.Input[64:96]) + s := new(big.Int).SetBytes(contract.Input[96:128]) + v := contract.Input[63] - 27 // tighter sig s values input homestead only apply to tx sigs - if !allZero(input[32:63]) || !crypto.ValidateSignatureValues(v, r, s, false) { + if !allZero(contract.Input[32:63]) || !crypto.ValidateSignatureValues(v, r, s, false) { return nil, nil } // We must make sure not to modify the 'input', so placing the 'v' along with // the signature needs to be done on a new allocation sig := make([]byte, 65) - copy(sig, input[64:128]) + copy(sig, contract.Input[64:128]) sig[64] = v // v needs to be at the end for libsecp256k1 - pubKey, err := crypto.Ecrecover(input[:32], sig) + pubKey, err := crypto.Ecrecover(contract.Input[:32], sig) // make sure the public key is a valid one if err != nil { return nil, nil @@ -302,6 +327,12 @@ func (c *ecrecover) Run(input []byte) ([]byte, error) { // SHA256 implemented as a native contract. type sha256hash struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (sha256hash) Address() common.Address { + return common.BytesToAddress([]byte{2}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. // // This method does not require any overflow checking as the input size gas costs @@ -309,14 +340,21 @@ type sha256hash struct{} func (c *sha256hash) RequiredGas(input []byte) uint64 { return uint64(len(input)+31)/32*params.Sha256PerWordGas + params.Sha256BaseGas } -func (c *sha256hash) Run(input []byte) ([]byte, error) { - h := sha256.Sum256(input) + +func (c *sha256hash) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { + h := sha256.Sum256(contract.Input) return h[:], nil } // RIPEMD160 implemented as a native contract. type ripemd160hash struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (ripemd160hash) Address() common.Address { + return common.BytesToAddress([]byte{3}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. // // This method does not require any overflow checking as the input size gas costs @@ -324,15 +362,22 @@ type ripemd160hash struct{} func (c *ripemd160hash) RequiredGas(input []byte) uint64 { return uint64(len(input)+31)/32*params.Ripemd160PerWordGas + params.Ripemd160BaseGas } -func (c *ripemd160hash) Run(input []byte) ([]byte, error) { + +func (c *ripemd160hash) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { ripemd := ripemd160.New() - ripemd.Write(input) + ripemd.Write(contract.Input) return common.LeftPadBytes(ripemd.Sum(nil), 32), nil } // data copy implemented as a native contract. type dataCopy struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (dataCopy) Address() common.Address { + return common.BytesToAddress([]byte{4}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. // // This method does not require any overflow checking as the input size gas costs @@ -340,8 +385,9 @@ type dataCopy struct{} func (c *dataCopy) RequiredGas(input []byte) uint64 { return uint64(len(input)+31)/32*params.IdentityPerWordGas + params.IdentityBaseGas } -func (c *dataCopy) Run(in []byte) ([]byte, error) { - return common.CopyBytes(in), nil + +func (c *dataCopy) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { + return common.CopyBytes(contract.Input), nil } // bigModExp implements a native big integer exponential modular operation. @@ -393,6 +439,12 @@ func modexpMultComplexity(x *big.Int) *big.Int { return x } +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (bigModExp) Address() common.Address { + return common.BytesToAddress([]byte{5}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bigModExp) RequiredGas(input []byte) uint64 { var ( @@ -487,16 +539,16 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 { return gas.Uint64() } -func (c *bigModExp) Run(input []byte) ([]byte, error) { +func (c *bigModExp) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { var ( - baseLen = new(big.Int).SetBytes(getData(input, 0, 32)).Uint64() - expLen = new(big.Int).SetBytes(getData(input, 32, 32)).Uint64() - modLen = new(big.Int).SetBytes(getData(input, 64, 32)).Uint64() + baseLen = new(big.Int).SetBytes(getData(contract.Input, 0, 32)).Uint64() + expLen = new(big.Int).SetBytes(getData(contract.Input, 32, 32)).Uint64() + modLen = new(big.Int).SetBytes(getData(contract.Input, 64, 32)).Uint64() ) - if len(input) > 96 { - input = input[96:] + if len(contract.Input) > 96 { + contract.Input = contract.Input[96:] } else { - input = input[:0] + contract.Input = contract.Input[:0] } // Handle a special case when both the base and mod length is zero if baseLen == 0 && modLen == 0 { @@ -508,9 +560,9 @@ func (c *bigModExp) Run(input []byte) ([]byte, error) { } // Retrieve the operands and execute the exponentiation var ( - base = new(big.Int).SetBytes(getData(input, 0, baseLen)) - exp = new(big.Int).SetBytes(getData(input, baseLen, expLen)) - mod = new(big.Int).SetBytes(getData(input, baseLen+expLen, modLen)) + base = new(big.Int).SetBytes(getData(contract.Input, 0, baseLen)) + exp = new(big.Int).SetBytes(getData(contract.Input, baseLen, expLen)) + mod = new(big.Int).SetBytes(getData(contract.Input, baseLen+expLen, modLen)) v []byte ) switch { @@ -566,26 +618,38 @@ func runBn256Add(input []byte) ([]byte, error) { // Istanbul consensus rules. type bn256AddIstanbul struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (bn256AddIstanbul) Address() common.Address { + return common.BytesToAddress([]byte{6}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bn256AddIstanbul) RequiredGas(input []byte) uint64 { return params.Bn256AddGasIstanbul } -func (c *bn256AddIstanbul) Run(input []byte) ([]byte, error) { - return runBn256Add(input) +func (c *bn256AddIstanbul) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { + return runBn256Add(contract.Input) } // bn256AddByzantium implements a native elliptic curve point addition // conforming to Byzantium consensus rules. type bn256AddByzantium struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (bn256AddByzantium) Address() common.Address { + return common.BytesToAddress([]byte{6}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bn256AddByzantium) RequiredGas(input []byte) uint64 { return params.Bn256AddGasByzantium } -func (c *bn256AddByzantium) Run(input []byte) ([]byte, error) { - return runBn256Add(input) +func (c *bn256AddByzantium) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { + return runBn256Add(contract.Input) } // runBn256ScalarMul implements the Bn256ScalarMul precompile, referenced by @@ -604,26 +668,38 @@ func runBn256ScalarMul(input []byte) ([]byte, error) { // multiplication conforming to Istanbul consensus rules. type bn256ScalarMulIstanbul struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (bn256ScalarMulIstanbul) Address() common.Address { + return common.BytesToAddress([]byte{7}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bn256ScalarMulIstanbul) RequiredGas(input []byte) uint64 { return params.Bn256ScalarMulGasIstanbul } -func (c *bn256ScalarMulIstanbul) Run(input []byte) ([]byte, error) { - return runBn256ScalarMul(input) +func (c *bn256ScalarMulIstanbul) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { + return runBn256ScalarMul(contract.Input) } // bn256ScalarMulByzantium implements a native elliptic curve scalar // multiplication conforming to Byzantium consensus rules. type bn256ScalarMulByzantium struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (bn256ScalarMulByzantium) Address() common.Address { + return common.BytesToAddress([]byte{7}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bn256ScalarMulByzantium) RequiredGas(input []byte) uint64 { return params.Bn256ScalarMulGasByzantium } -func (c *bn256ScalarMulByzantium) Run(input []byte) ([]byte, error) { - return runBn256ScalarMul(input) +func (c *bn256ScalarMulByzantium) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { + return runBn256ScalarMul(contract.Input) } var ( @@ -672,30 +748,48 @@ func runBn256Pairing(input []byte) ([]byte, error) { // conforming to Istanbul consensus rules. type bn256PairingIstanbul struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (bn256PairingIstanbul) Address() common.Address { + return common.BytesToAddress([]byte{8}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bn256PairingIstanbul) RequiredGas(input []byte) uint64 { return params.Bn256PairingBaseGasIstanbul + uint64(len(input)/192)*params.Bn256PairingPerPointGasIstanbul } -func (c *bn256PairingIstanbul) Run(input []byte) ([]byte, error) { - return runBn256Pairing(input) +func (c *bn256PairingIstanbul) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { + return runBn256Pairing(contract.Input) } // bn256PairingByzantium implements a pairing pre-compile for the bn256 curve // conforming to Byzantium consensus rules. type bn256PairingByzantium struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (bn256PairingByzantium) Address() common.Address { + return common.BytesToAddress([]byte{8}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bn256PairingByzantium) RequiredGas(input []byte) uint64 { return params.Bn256PairingBaseGasByzantium + uint64(len(input)/192)*params.Bn256PairingPerPointGasByzantium } -func (c *bn256PairingByzantium) Run(input []byte) ([]byte, error) { - return runBn256Pairing(input) +func (c *bn256PairingByzantium) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { + return runBn256Pairing(contract.Input) } type blake2F struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (blake2F) Address() common.Address { + return common.BytesToAddress([]byte{9}) +} + func (c *blake2F) RequiredGas(input []byte) uint64 { // If the input is malformed, we can't calculate the gas, return 0 and let the // actual call choke and fault. @@ -716,18 +810,18 @@ var ( errBlake2FInvalidFinalFlag = errors.New("invalid final flag") ) -func (c *blake2F) Run(input []byte) ([]byte, error) { +func (c *blake2F) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { // Make sure the input is valid (correct length and final flag) - if len(input) != blake2FInputLength { + if len(contract.Input) != blake2FInputLength { return nil, errBlake2FInvalidInputLength } - if input[212] != blake2FNonFinalBlockBytes && input[212] != blake2FFinalBlockBytes { + if contract.Input[212] != blake2FNonFinalBlockBytes && contract.Input[212] != blake2FFinalBlockBytes { return nil, errBlake2FInvalidFinalFlag } // Parse the input into the Blake2b call parameters var ( - rounds = binary.BigEndian.Uint32(input[0:4]) - final = input[212] == blake2FFinalBlockBytes + rounds = binary.BigEndian.Uint32(contract.Input[0:4]) + final = contract.Input[212] == blake2FFinalBlockBytes h [8]uint64 m [16]uint64 @@ -735,14 +829,14 @@ func (c *blake2F) Run(input []byte) ([]byte, error) { ) for i := 0; i < 8; i++ { offset := 4 + i*8 - h[i] = binary.LittleEndian.Uint64(input[offset : offset+8]) + h[i] = binary.LittleEndian.Uint64(contract.Input[offset : offset+8]) } for i := 0; i < 16; i++ { offset := 68 + i*8 - m[i] = binary.LittleEndian.Uint64(input[offset : offset+8]) + m[i] = binary.LittleEndian.Uint64(contract.Input[offset : offset+8]) } - t[0] = binary.LittleEndian.Uint64(input[196:204]) - t[1] = binary.LittleEndian.Uint64(input[204:212]) + t[0] = binary.LittleEndian.Uint64(contract.Input[196:204]) + t[1] = binary.LittleEndian.Uint64(contract.Input[204:212]) // Execute the compression function, extract and return the result blake2b.F(&h, m, t, final, rounds) @@ -765,27 +859,33 @@ var ( // bls12381G1Add implements EIP-2537 G1Add precompile. type bls12381G1Add struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (bls12381G1Add) Address() common.Address { + return common.BytesToAddress([]byte{10}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bls12381G1Add) RequiredGas(input []byte) uint64 { return params.Bls12381G1AddGas } -func (c *bls12381G1Add) Run(input []byte) ([]byte, error) { +func (c *bls12381G1Add) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { // Implements EIP-2537 G1Add precompile. // > G1 addition call expects `256` bytes as an input that is interpreted as byte concatenation of two G1 points (`128` bytes each). // > Output is an encoding of addition operation result - single G1 point (`128` bytes). - if len(input) != 256 { + if len(contract.Input) != 256 { return nil, errBLS12381InvalidInputLength } var err error var p0, p1 *bls12381.G1Affine // Decode G1 point p_0 - if p0, err = decodePointG1(input[:128]); err != nil { + if p0, err = decodePointG1(contract.Input[:128]); err != nil { return nil, err } // Decode G1 point p_1 - if p1, err = decodePointG1(input[128:]); err != nil { + if p1, err = decodePointG1(contract.Input[128:]); err != nil { return nil, err } @@ -801,6 +901,12 @@ func (c *bls12381G1Add) Run(input []byte) ([]byte, error) { // bls12381G1MultiExp implements EIP-2537 G1MultiExp precompile. type bls12381G1MultiExp struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (bls12381G1MultiExp) Address() common.Address { + return common.BytesToAddress([]byte{12}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bls12381G1MultiExp) RequiredGas(input []byte) uint64 { // Calculate G1 point, scalar value pair length @@ -820,12 +926,12 @@ func (c *bls12381G1MultiExp) RequiredGas(input []byte) uint64 { return (uint64(k) * params.Bls12381G1MulGas * discount) / 1000 } -func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) { +func (c *bls12381G1MultiExp) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { // Implements EIP-2537 G1MultiExp precompile. // G1 multiplication call expects `160*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32` bytes). // Output is an encoding of multiexponentiation operation result - single G1 point (`128` bytes). - k := len(input) / 160 - if len(input) == 0 || len(input)%160 != 0 { + k := len(contract.Input) / 160 + if len(contract.Input) == 0 || len(contract.Input)%160 != 0 { return nil, errBLS12381InvalidInputLength } points := make([]bls12381.G1Affine, k) @@ -836,7 +942,7 @@ func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) { off := 160 * i t0, t1, t2 := off, off+128, off+160 // Decode G1 point - p, err := decodePointG1(input[t0:t1]) + p, err := decodePointG1(contract.Input[t0:t1]) if err != nil { return nil, err } @@ -847,7 +953,7 @@ func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) { } points[i] = *p // Decode scalar value - scalars[i] = *new(fr.Element).SetBytes(input[t1:t2]) + scalars[i] = *new(fr.Element).SetBytes(contract.Input[t1:t2]) } // Compute r = e_0 * p_0 + e_1 * p_1 + ... + e_(k-1) * p_(k-1) @@ -861,27 +967,33 @@ func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) { // bls12381G2Add implements EIP-2537 G2Add precompile. type bls12381G2Add struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (bls12381G2Add) Address() common.Address { + return common.BytesToAddress([]byte{13}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bls12381G2Add) RequiredGas(input []byte) uint64 { return params.Bls12381G2AddGas } -func (c *bls12381G2Add) Run(input []byte) ([]byte, error) { +func (c *bls12381G2Add) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { // Implements EIP-2537 G2Add precompile. // > G2 addition call expects `512` bytes as an input that is interpreted as byte concatenation of two G2 points (`256` bytes each). // > Output is an encoding of addition operation result - single G2 point (`256` bytes). - if len(input) != 512 { + if len(contract.Input) != 512 { return nil, errBLS12381InvalidInputLength } var err error var p0, p1 *bls12381.G2Affine // Decode G2 point p_0 - if p0, err = decodePointG2(input[:256]); err != nil { + if p0, err = decodePointG2(contract.Input[:256]); err != nil { return nil, err } // Decode G2 point p_1 - if p1, err = decodePointG2(input[256:]); err != nil { + if p1, err = decodePointG2(contract.Input[256:]); err != nil { return nil, err } @@ -898,6 +1010,12 @@ func (c *bls12381G2Add) Run(input []byte) ([]byte, error) { // bls12381G2MultiExp implements EIP-2537 G2MultiExp precompile. type bls12381G2MultiExp struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (bls12381G2MultiExp) Address() common.Address { + return common.BytesToAddress([]byte{15}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bls12381G2MultiExp) RequiredGas(input []byte) uint64 { // Calculate G2 point, scalar value pair length @@ -917,12 +1035,12 @@ func (c *bls12381G2MultiExp) RequiredGas(input []byte) uint64 { return (uint64(k) * params.Bls12381G2MulGas * discount) / 1000 } -func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) { +func (c *bls12381G2MultiExp) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { // Implements EIP-2537 G2MultiExp precompile logic // > G2 multiplication call expects `288*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G2 point (`256` bytes) and encoding of a scalar value (`32` bytes). // > Output is an encoding of multiexponentiation operation result - single G2 point (`256` bytes). - k := len(input) / 288 - if len(input) == 0 || len(input)%288 != 0 { + k := len(contract.Input) / 288 + if len(contract.Input) == 0 || len(contract.Input)%288 != 0 { return nil, errBLS12381InvalidInputLength } points := make([]bls12381.G2Affine, k) @@ -933,7 +1051,7 @@ func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) { off := 288 * i t0, t1, t2 := off, off+256, off+288 // Decode G2 point - p, err := decodePointG2(input[t0:t1]) + p, err := decodePointG2(contract.Input[t0:t1]) if err != nil { return nil, err } @@ -944,7 +1062,7 @@ func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) { } points[i] = *p // Decode scalar value - scalars[i] = *new(fr.Element).SetBytes(input[t1:t2]) + scalars[i] = *new(fr.Element).SetBytes(contract.Input[t1:t2]) } // Compute r = e_0 * p_0 + e_1 * p_1 + ... + e_(k-1) * p_(k-1) @@ -958,20 +1076,26 @@ func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) { // bls12381Pairing implements EIP-2537 Pairing precompile. type bls12381Pairing struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (bls12381Pairing) Address() common.Address { + return common.BytesToAddress([]byte{16}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bls12381Pairing) RequiredGas(input []byte) uint64 { return params.Bls12381PairingBaseGas + uint64(len(input)/384)*params.Bls12381PairingPerPairGas } -func (c *bls12381Pairing) Run(input []byte) ([]byte, error) { +func (c *bls12381Pairing) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { // Implements EIP-2537 Pairing precompile logic. // > Pairing call expects `384*k` bytes as an inputs that is interpreted as byte concatenation of `k` slices. Each slice has the following structure: // > - `128` bytes of G1 point encoding // > - `256` bytes of G2 point encoding // > Output is a `32` bytes where last single byte is `0x01` if pairing result is equal to multiplicative identity in a pairing target field and `0x00` otherwise // > (which is equivalent of Big Endian encoding of Solidity values `uint256(1)` and `uin256(0)` respectively). - k := len(input) / 384 - if len(input) == 0 || len(input)%384 != 0 { + k := len(contract.Input) / 384 + if len(contract.Input) == 0 || len(contract.Input)%384 != 0 { return nil, errBLS12381InvalidInputLength } @@ -986,12 +1110,12 @@ func (c *bls12381Pairing) Run(input []byte) ([]byte, error) { t0, t1, t2 := off, off+128, off+384 // Decode G1 point - p1, err := decodePointG1(input[t0:t1]) + p1, err := decodePointG1(contract.Input[t0:t1]) if err != nil { return nil, err } // Decode G2 point - p2, err := decodePointG2(input[t1:t2]) + p2, err := decodePointG2(contract.Input[t1:t2]) if err != nil { return nil, err } @@ -1110,21 +1234,27 @@ func encodePointG2(p *bls12381.G2Affine) []byte { // bls12381MapG1 implements EIP-2537 MapG1 precompile. type bls12381MapG1 struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (bls12381MapG1) Address() common.Address { + return common.BytesToAddress([]byte{17}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bls12381MapG1) RequiredGas(input []byte) uint64 { return params.Bls12381MapG1Gas } -func (c *bls12381MapG1) Run(input []byte) ([]byte, error) { +func (c *bls12381MapG1) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { // Implements EIP-2537 Map_To_G1 precompile. // > Field-to-curve call expects an `64` bytes input that is interpreted as an element of the base field. // > Output of this call is `128` bytes and is G1 point following respective encoding rules. - if len(input) != 64 { + if len(contract.Input) != 64 { return nil, errBLS12381InvalidInputLength } // Decode input field element - fe, err := decodeBLS12381FieldElement(input) + fe, err := decodeBLS12381FieldElement(contract.Input) if err != nil { return nil, err } @@ -1139,25 +1269,31 @@ func (c *bls12381MapG1) Run(input []byte) ([]byte, error) { // bls12381MapG2 implements EIP-2537 MapG2 precompile. type bls12381MapG2 struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (bls12381MapG2) Address() common.Address { + return common.BytesToAddress([]byte{18}) +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bls12381MapG2) RequiredGas(input []byte) uint64 { return params.Bls12381MapG2Gas } -func (c *bls12381MapG2) Run(input []byte) ([]byte, error) { +func (c *bls12381MapG2) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { // Implements EIP-2537 Map_FP2_TO_G2 precompile logic. // > Field-to-curve call expects an `128` bytes input that is interpreted as an element of the quadratic extension field. // > Output of this call is `256` bytes and is G2 point following respective encoding rules. - if len(input) != 128 { + if len(contract.Input) != 128 { return nil, errBLS12381InvalidInputLength } // Decode input field element - c0, err := decodeBLS12381FieldElement(input[:64]) + c0, err := decodeBLS12381FieldElement(contract.Input[:64]) if err != nil { return nil, err } - c1, err := decodeBLS12381FieldElement(input[64:]) + c1, err := decodeBLS12381FieldElement(contract.Input[64:]) if err != nil { return nil, err } @@ -1172,6 +1308,12 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) { // kzgPointEvaluation implements the EIP-4844 point evaluation precompile. type kzgPointEvaluation struct{} +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (kzgPointEvaluation) Address() common.Address { + return common.BytesToAddress([]byte{0x0a}) +} + // RequiredGas estimates the gas required for running the point evaluation precompile. func (b *kzgPointEvaluation) RequiredGas(input []byte) uint64 { return params.BlobTxPointEvaluationPrecompileGas @@ -1190,33 +1332,33 @@ var ( ) // Run executes the point evaluation precompile. -func (b *kzgPointEvaluation) Run(input []byte) ([]byte, error) { - if len(input) != blobVerifyInputLength { +func (b *kzgPointEvaluation) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { + if len(contract.Input) != blobVerifyInputLength { return nil, errBlobVerifyInvalidInputLength } // versioned hash: first 32 bytes var versionedHash common.Hash - copy(versionedHash[:], input[:]) + copy(versionedHash[:], contract.Input[:]) var ( point kzg4844.Point claim kzg4844.Claim ) // Evaluation point: next 32 bytes - copy(point[:], input[32:]) + copy(point[:], contract.Input[32:]) // Expected output: next 32 bytes - copy(claim[:], input[64:]) + copy(claim[:], contract.Input[64:]) // input kzg point: next 48 bytes var commitment kzg4844.Commitment - copy(commitment[:], input[96:]) + copy(commitment[:], contract.Input[96:]) if kZGToVersionedHash(commitment) != versionedHash { return nil, errBlobVerifyMismatchedVersion } // Proof: next 48 bytes var proof kzg4844.Proof - copy(proof[:], input[144:]) + copy(proof[:], contract.Input[144:]) if err := kzg4844.VerifyProof(commitment, point, claim, proof); err != nil { return nil, fmt.Errorf("%w: %v", errBlobVerifyKZGProof, err) diff --git a/core/vm/contracts_fuzz_test.go b/core/vm/contracts_fuzz_test.go index 1e5cc800747..59c62ad1495 100644 --- a/core/vm/contracts_fuzz_test.go +++ b/core/vm/contracts_fuzz_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" ) func FuzzPrecompiledContracts(f *testing.F) { @@ -36,7 +37,8 @@ func FuzzPrecompiledContracts(f *testing.F) { return } inWant := string(input) - RunPrecompiledContract(p, input, gas, nil) + + runPrecompiledContract(nil, p, common.Address{}, input, gas, new(uint256.Int), false, nil) if inHave := string(input); inWant != inHave { t.Errorf("Precompiled %v modified input data", a) } diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index f86a8919a9a..6595777d98d 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -25,6 +25,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" ) // precompiledTest defines the input/output pairs for precompiled contract tests. @@ -97,7 +98,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - if res, _, err := RunPrecompiledContract(p, in, gas, nil); err != nil { + if res, _, err := runPrecompiledContract(nil, p, common.Address{}, in, gas, new(uint256.Int), false, nil); err != nil { t.Error(err) } else if common.Bytes2Hex(res) != test.Expected { t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) @@ -119,7 +120,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) { gas := p.RequiredGas(in) - 1 t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - _, _, err := RunPrecompiledContract(p, in, gas, nil) + _, _, err := runPrecompiledContract(nil, p, common.Address{}, in, gas, new(uint256.Int), false, nil) if err.Error() != "out of gas" { t.Errorf("Expected error [out of gas], got [%v]", err) } @@ -136,7 +137,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(test.Name, func(t *testing.T) { - _, _, err := RunPrecompiledContract(p, in, gas, nil) + _, _, err := runPrecompiledContract(nil, p, common.Address{}, in, gas, new(uint256.Int), false, nil) if err.Error() != test.ExpectedError { t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) } @@ -168,7 +169,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { copy(data, in) - res, _, err = RunPrecompiledContract(p, data, reqGas, nil) + res, _, err = runPrecompiledContract(nil, p, common.Address{}, in, reqGas, new(uint256.Int), false, nil) } bench.StopTimer() elapsed := uint64(time.Since(start)) @@ -180,7 +181,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { // Keep it as uint64, multiply 100 to get two digit float later mgasps := (100 * 1000 * gasUsed) / elapsed bench.ReportMetric(float64(mgasps)/100, "mgas/s") - //Check if it is correct + // Check if it is correct if err != nil { bench.Error(err) return diff --git a/core/vm/custom_contract.go b/core/vm/custom_contract.go new file mode 100644 index 00000000000..de644431fc6 --- /dev/null +++ b/core/vm/custom_contract.go @@ -0,0 +1,30 @@ +package vm + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" +) + +// AccountRef implements ContractRef. +// +// Account references are used during EVM initialisation and +// its primary use is to fetch addresses. Removing this object +// proves difficult because of the cached jump destinations which +// are fetched from the parent contract (i.e. the caller), which +// is a ContractRef. +type AccountRef common.Address + +// Address casts AccountRef to an Address +func (ar AccountRef) Address() common.Address { return (common.Address)(ar) } + +// NewPrecompile returns a new instance of a precompiled contract environment for the execution of EVM. +func NewPrecompile(caller, address common.Address, value *uint256.Int, gas uint64) *Contract { + c := NewContract(caller, address, value, gas, nil) + c.isPrecompile = true + return c +} + +// IsPrecompile returns true if the contract is a precompiled contract environment +func (c *Contract) IsPrecompile() bool { + return c.isPrecompile +} diff --git a/core/vm/custom_contracts.go b/core/vm/custom_contracts.go new file mode 100644 index 00000000000..9eafbe66111 --- /dev/null +++ b/core/vm/custom_contracts.go @@ -0,0 +1,62 @@ +package vm + +import ( + "bytes" + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +// ActivePrecompiles returns the precompiles enabled with the current configuration. +func (evm *EVM) ActivePrecompiles() []common.Address { + addrs := make([]common.Address, len(evm.precompiles)) + i := 0 + for addr, _ := range evm.precompiles { + addrs[i] = addr + i++ + } + return addrs +} + +// Precompile returns a precompiled contract for the given address. This +// function returns false if the address is not a registered precompile. +func (evm *EVM) Precompile(addr common.Address) (PrecompiledContract, bool) { + p, ok := evm.precompiles[addr] + return p, ok +} + +// WithPrecompiles sets the precompiled contracts and the slice of actives precompiles. +// IMPORTANT: This function does NOT validate the precompiles provided to the EVM. The caller should +// use the ValidatePrecompiles function for this purpose prior to calling WithPrecompiles. +func (evm *EVM) WithPrecompiles(precompiles map[common.Address]PrecompiledContract) { + evm.precompiles = precompiles +} + +// ValidatePrecompiles validates the precompile map against the active +// precompile slice. +// It returns an error if the precompiled contract map has a different length +// than the slice of active contract addresses. This function also checks for +// duplicates, invalid addresses and empty precompile contract instances. +func ValidatePrecompiles( + precompiles PrecompiledContracts, +) error { + dupActivePrecompiles := make(map[common.Address]bool) + + for addr, precompile := range precompiles { + if dupActivePrecompiles[addr] { + return fmt.Errorf("duplicate active precompile: %s", addr) + } + + if precompile == nil { + return fmt.Errorf("precompile contract cannot be nil: %s", addr) + } + + if bytes.Equal(addr.Bytes(), common.Address{}.Bytes()) { + return fmt.Errorf("precompile cannot be the zero address: %s", addr) + } + + dupActivePrecompiles[addr] = true + } + + return nil +} diff --git a/core/vm/custom_eip.go b/core/vm/custom_eip.go new file mode 100644 index 00000000000..235ca500a9c --- /dev/null +++ b/core/vm/custom_eip.go @@ -0,0 +1,151 @@ +package vm + +import ( + "fmt" + "sort" + "strings" + + "golang.org/x/exp/maps" +) + +// OpCodeInfo contains information required to identify an EVM operation. +type OpCodeInfo struct { + Number OpCode + Name string +} + +// Operation is an utility struct that wraps the private type +// operation. +type Operation struct { + Op *operation +} + +// ExtendActivators allows to merge the go ethereum activators map +// with additional custom activators. +func ExtendActivators(eips map[int]func(*JumpTable)) error { + // Catch early duplicated eip. + keys := make([]int, 0, len(eips)) + for k := range eips { + if ValidEip(k) { + return fmt.Errorf("duplicate activation: %d is already present in %s", k, ActivateableEips()) + } + keys = append(keys, k) + } + + // Sorting keys to ensure deterministic execution. + sort.Ints(keys) + + for _, k := range keys { + activators[k] = eips[k] + } + return nil +} + +// GetActivatorsEipNumbers returns the name of EIPs registered in +// the activators map. +// Used only in tests. +func GetActivatorsEipNumbers() []int { + keys := maps.Keys(activators) + + sort.Ints(keys) + return keys +} + +// ExtendOperations returns an instance of the new operation and register it in the list +// of available ones. +// Return an error if an operation with the same name is already present. +// This function is used to prevent the overwrite of an already existent operation. +func ExtendOperations( + opInfo OpCodeInfo, + execute executionFunc, + constantGas uint64, + dynamicGas gasFunc, + minStack int, + maxStack int, + memorySize memorySizeFunc, +) (*Operation, error) { + opName := strings.ToUpper(strings.TrimSpace(opInfo.Name)) + if err := extendOpCodeStringLists(opInfo.Number, opName); err != nil { + return nil, err + } + + operation := newOperation(execute, constantGas, dynamicGas, minStack, maxStack, memorySize) + op := &Operation{operation} + + return op, nil +} + +// newOperation returns an instance of a new EVM operation. +func newOperation( + execute executionFunc, + constantGas uint64, + dynamicGas gasFunc, + minStack int, + maxStack int, + memorySize memorySizeFunc, +) *operation { + return &operation{ + execute: execute, + constantGas: constantGas, + dynamicGas: dynamicGas, + minStack: minStack, + maxStack: maxStack, + memorySize: memorySize, + } +} + +// GetConstantGas return the constant gas used by the operation. +func (o *operation) GetConstantGas() uint64 { + return o.constantGas +} + +// SetExecute sets the execution function of the operation. +func (o *operation) SetExecute(ef executionFunc) { + o.execute = ef +} + +// SetConstantGas changes the constant gas of the operation. +func (o *operation) SetConstantGas(gas uint64) { + o.constantGas = gas +} + +// SetDynamicGas sets the dynamic gas function of the operation. +func (o *operation) SetDynamicGas(gf gasFunc) { + o.dynamicGas = gf +} + +// SetMinStack sets the minimum stack size required for the operation. +func (o *operation) SetMinStack(minStack int) { + o.minStack = minStack +} + +// SetMaxStack sets the maximum stack size for the operation. +func (o *operation) SetMaxStack(maxStack int) { + o.maxStack = maxStack +} + +// SetMemorySize sets the memory size function for the operation. +func (o *operation) SetMemorySize(msf memorySizeFunc) { + o.memorySize = msf +} + +// extendOpCodeStringLists updates the lists mapping opcode number to the name +// and viceversa. Return an error if the key is already set. +// +// ASSUMPTION: no opcode is registered as an empty string. +func extendOpCodeStringLists(newOpCode OpCode, newOpName string) error { + opName := opCodeToString[newOpCode] + if opName != "" { + return fmt.Errorf("opcode %d already exists: %s", newOpCode, opName) + } + opNumber := stringToOp[newOpName] + // We need to check against the STOP opcode name because we have to discriminate + // between 0x00 of this opcode and the default value of an empty key. + stopName := opCodeToString[STOP] + if opNumber != 0x00 || newOpName == stopName { + return fmt.Errorf("opcode with name %s already exists", newOpName) + } + opCodeToString[newOpCode] = newOpName + stringToOp[newOpName] = newOpCode + return nil +} diff --git a/core/vm/custom_eip_test.go b/core/vm/custom_eip_test.go new file mode 100644 index 00000000000..d49d76fbfb9 --- /dev/null +++ b/core/vm/custom_eip_test.go @@ -0,0 +1,192 @@ +package vm + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestExtendActivators(t *testing.T) { + eips_snapshot := GetActivatorsEipNumbers() + + testCases := []struct { + name string + newActivators map[int]func(*JumpTable) + expPass bool + errContains string + postCheck func() + }{ + { + "success - nil new activators", + nil, + true, + "", + func() { + eips := GetActivatorsEipNumbers() + require.ElementsMatch(t, eips_snapshot, eips, "expected eips number to be equal") + }, + }, + { + "success - single new activator", + map[int]func(*JumpTable){ + 0o000: func(jt *JumpTable) {}, + }, + true, + "", + func() { + eips := GetActivatorsEipNumbers() + require.ElementsMatch(t, append(eips_snapshot, 0), eips, "expected eips number to be equal") + }, + }, + { + "success - multiple new activators", + map[int]func(*JumpTable){ + 0o001: func(jt *JumpTable) {}, + 0o002: func(jt *JumpTable) {}, + }, + true, + "", + func() { + eips := GetActivatorsEipNumbers() + // since we are working with a global function, tests are not independent + require.ElementsMatch(t, append(eips_snapshot, 0, 1, 2), eips, + "expected eips number to be equal") + }, + }, + { + "fail - repeated activator", + map[int]func(*JumpTable){ + 3855: func(jt *JumpTable) {}, + }, + false, + "", + func() { + eips := GetActivatorsEipNumbers() + // since we are working with a global function, tests are not independent + require.ElementsMatch(t, append(eips_snapshot, 0, 1, 2), eips, + "expected eips number to be equal") + }, + }, + { + "fail - valid activator is not stored if a repeated is present", + map[int]func(*JumpTable){ + 0o003: func(jt *JumpTable) {}, + 3855: func(jt *JumpTable) {}, + }, + false, + "", + func() { + eips := GetActivatorsEipNumbers() + // since we are working with a global function, tests are not independent + require.ElementsMatch(t, append(eips_snapshot, 0o000, 0o001, 0o002), eips, + "expected eips number to be equal") + }, + }, + } + + for _, tc := range testCases { + err := ExtendActivators(tc.newActivators) + if tc.expPass { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errContains, "expected different error") + } + + tc.postCheck() + } +} + +func TestAddOperation(t *testing.T) { + // Functions used to create an operation. + customExecute := func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + // no - op + return nil, nil + } + customDynamicGas := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // no-op + return 0, nil + } + customMemorySize := func(stack *Stack) (uint64, bool) { + // no-op + return 0, false + } + + const ( + EXISTENT OpCode = STOP + NEW OpCode = 0xf + ) + + testCases := []struct { + name string + opName string + opNumber OpCode + expPass bool + errContains string + postCheck func() + }{ + { + "fail - operation with same number already exists", + "TEST", + EXISTENT, + false, + "already exists", + func() { + name := EXISTENT.String() + require.Equal(t, "STOP", name) + }, + }, + { + "fail - operation with same name already exists", + "CREATE", + NEW, + false, + "already exists", + func() { + name := NEW.String() + require.Contains(t, name, "not defined") + }, + }, + { + "fail - operation with same name of STOP", + "STOP", + NEW, + false, + "already exists", + func() { + name := NEW.String() + require.Contains(t, name, "not defined") + }, + }, + { + "pass - new operation added to the list", + "TEST", + NEW, + true, + "", + func() { + name := NEW.String() + require.Equal(t, "TEST", name) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + opInfo := OpCodeInfo{ + Number: tc.opNumber, + Name: tc.opName, + } + _, err := ExtendOperations(opInfo, customExecute, 0, customDynamicGas, 0, 0, customMemorySize) + + if tc.expPass { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errContains, "expected different error") + } + + tc.postCheck() + }) + } +} diff --git a/core/vm/custom_eip_testing.go b/core/vm/custom_eip_testing.go new file mode 100644 index 00000000000..272397990b4 --- /dev/null +++ b/core/vm/custom_eip_testing.go @@ -0,0 +1,30 @@ +//go:build test +// +build test + +// This file is used to allow the testing of EVM configuration initialization +// without the need to introduce testing requirements in the final binary. In +// this case, the file provides the possibility to restore the EIP activator +// functions to the initial state without the need to compile ResetActivators +// in the final binary. + +package vm + +var originalActivators = make(map[int]func(*JumpTable)) + +func init() { + keys := GetActivatorsEipNumbers() + + originalActivators = make(map[int]func(*JumpTable), len(keys)) + + for _, k := range keys { + originalActivators[k] = activators[k] + } +} + +// ResetActivators resets activators to the original go ethereum activators map +func ResetActivators() { + activators = make(map[int]func(*JumpTable)) + for k, v := range originalActivators { + activators[k] = v + } +} diff --git a/core/vm/evm.go b/core/vm/evm.go index b45a4345453..1abdab021d9 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -110,7 +110,6 @@ type EVM struct { // global (to this context) ethereum virtual machine used throughout // the execution of the tx interpreter *EVMInterpreter - // abort is used to abort the EVM calling operations abort atomic.Bool @@ -125,6 +124,11 @@ type EVM struct { // jumpDests is the aggregated result of JUMPDEST analysis made through // the life cycle of EVM. jumpDests map[common.Hash]bitvec + + // hooks is a set of functions that can be used to intercept and modify the + // behavior of the EVM when executing certain opcodes. + // The hooks are called before the execution of the respective opcodes. + hooks OpCodeHooks } // NewEVM constructs an EVM instance with the supplied block context, state @@ -139,9 +143,21 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon chainConfig: chainConfig, chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), jumpDests: make(map[common.Hash]bitvec), + hooks: newNoopOpCodeHooks(), } evm.precompiles = activePrecompiledContracts(evm.chainRules) evm.interpreter = NewEVMInterpreter(evm) + + return evm +} + +// NewEVMWithHooks returns a new EVM and takes a custom OpCodeHooks. The returned EVM is +// not thread safe and should only ever be used *once*. +func NewEVMWithHooks(hooks OpCodeHooks, blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, config Config) *EVM { + evm := NewEVM(blockCtx, statedb, chainConfig, config) + evm.hooks = hooks + evm.TxContext = txCtx + return evm } @@ -177,6 +193,11 @@ func (evm *EVM) Interpreter() *EVMInterpreter { return evm.interpreter } +// WithInterpreter sets the interpreter to the EVM instance +func (evm *EVM) WithInterpreter(interpreter *EVMInterpreter) { + evm.interpreter = interpreter +} + func isSystemCall(caller common.Address) bool { return caller == params.SystemAddress } @@ -193,6 +214,10 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) }(gas) } + if err = evm.hooks.CallHook(evm, caller, addr); err != nil { + return nil, gas, err + } + // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth @@ -201,8 +226,9 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g if !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) { return nil, gas, ErrInsufficientBalance } + snapshot := evm.StateDB.Snapshot() - p, isPrecompile := evm.precompile(addr) + p, isPrecompile := evm.Precompile(addr) if !evm.StateDB.Exist(addr) { if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) { @@ -230,7 +256,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g evm.Context.Transfer(evm.StateDB, caller, addr, value) if isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) + ret, gas, err = evm.RunPrecompiledContract(p, caller, input, gas, value, false, evm.Config.Tracer) } else { // Initialise a new contract and set the code that is to be used by the EVM. code := evm.resolveCode(addr) @@ -279,6 +305,9 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) }(gas) } + if err = evm.hooks.CallHook(evm, caller, addr); err != nil { + return nil, gas, err + } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth @@ -293,8 +322,8 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt var snapshot = evm.StateDB.Snapshot() // It is allowed to call precompiles, even via delegatecall - if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) + if p, isPrecompile := evm.Precompile(addr); isPrecompile { + ret, gas, err = evm.RunPrecompiledContract(p, caller, input, gas, value, true, evm.Config.Tracer) } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. @@ -329,6 +358,9 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) }(gas) } + if err = evm.hooks.CallHook(evm, caller, addr); err != nil { + return nil, gas, err + } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth @@ -336,8 +368,8 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, var snapshot = evm.StateDB.Snapshot() // It is allowed to call precompiles, even via delegatecall - if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) + if p, isPrecompile := evm.Precompile(addr); isPrecompile { + ret, gas, err = evm.RunPrecompiledContract(p, caller, input, gas, nil, true, evm.Config.Tracer) } else { // Initialise a new contract and make initialise the delegate values // @@ -371,6 +403,9 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) }(gas) } + if err = evm.hooks.CallHook(evm, caller, addr); err != nil { + return nil, gas, err + } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth @@ -388,8 +423,8 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b // future scenarios evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount) - if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) + if p, isPrecompile := evm.Precompile(addr); isPrecompile { + ret, gas, err = evm.RunPrecompiledContract(p, caller, input, gas, new(uint256.Int), true, evm.Config.Tracer) } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. @@ -554,6 +589,9 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b // Create creates a new contract using code as deployment code. func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { + if err = evm.hooks.CreateHook(evm, caller); err != nil { + return nil, common.Address{}, gas, err + } contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller)) return evm.create(caller, code, gas, value, contractAddr, CREATE) } @@ -563,6 +601,10 @@ func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *ui // The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:] // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. func (evm *EVM) Create2(caller common.Address, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { + if err = evm.hooks.CreateHook(evm, caller); err != nil { + return nil, common.Address{}, gas, err + } + inithash := crypto.HashData(evm.interpreter.hasher, code) contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:]) return evm.create(caller, code, gas, endowment, contractAddr, CREATE2) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 0902d17c547..43b048344d1 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -107,7 +107,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected)) stack.push(x) stack.push(y) - opFn(&pc, evm.interpreter, &ScopeContext{nil, stack, nil}) + opFn(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{nil, stack, nil}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data)) } @@ -221,7 +221,7 @@ func TestAddMod(t *testing.T) { stack.push(z) stack.push(y) stack.push(x) - opAddmod(&pc, evm.interpreter, &ScopeContext{nil, stack, nil}) + opAddmod(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{nil, stack, nil}) actual := stack.pop() if actual.Cmp(expected) != 0 { t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual) @@ -247,7 +247,7 @@ func TestWriteExpectedValues(t *testing.T) { y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y)) stack.push(x) stack.push(y) - opFn(&pc, evm.interpreter, &ScopeContext{nil, stack, nil}) + opFn(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{nil, stack, nil}) actual := stack.pop() result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} } @@ -296,7 +296,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { for _, arg := range intArgs { stack.push(arg) } - op(&pc, evm.interpreter, scope) + op(&pc, evm.interpreter.(*EVMInterpreter), scope) stack.pop() } bench.StopTimer() @@ -528,13 +528,13 @@ func TestOpMstore(t *testing.T) { v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v))) stack.push(new(uint256.Int)) - opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{mem, stack, nil}) if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { t.Fatalf("Mstore fail, got %v, expected %v", got, v) } stack.push(new(uint256.Int).SetUint64(0x1)) stack.push(new(uint256.Int)) - opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{mem, stack, nil}) if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { t.Fatalf("Mstore failed to overwrite previous value") } @@ -555,7 +555,7 @@ func BenchmarkOpMstore(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(value) stack.push(memStart) - opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{mem, stack, nil}) } } @@ -581,14 +581,14 @@ func TestOpTstore(t *testing.T) { stack.push(new(uint256.Int).SetBytes(value)) // push the location to the stack stack.push(new(uint256.Int)) - opTstore(&pc, evm.interpreter, &scopeContext) + opTstore(&pc, evm.interpreter.(*EVMInterpreter), &scopeContext) // there should be no elements on the stack after TSTORE if stack.len() != 0 { t.Fatal("stack wrong size") } // push the location to the stack stack.push(new(uint256.Int)) - opTload(&pc, evm.interpreter, &scopeContext) + opTload(&pc, evm.interpreter.(*EVMInterpreter), &scopeContext) // there should be one element on the stack after TLOAD if stack.len() != 1 { t.Fatal("stack wrong size") @@ -613,7 +613,7 @@ func BenchmarkOpKeccak256(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(uint256.NewInt(32)) stack.push(start) - opKeccak256(&pc, evm.interpreter, &ScopeContext{mem, stack, nil}) + opKeccak256(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{mem, stack, nil}) } } @@ -707,7 +707,7 @@ func TestRandom(t *testing.T) { stack = newstack() pc = uint64(0) ) - opRandom(&pc, evm.interpreter, &ScopeContext{nil, stack, nil}) + opRandom(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{nil, stack, nil}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) } @@ -749,7 +749,7 @@ func TestBlobHash(t *testing.T) { ) evm.SetTxContext(TxContext{BlobHashes: tt.hashes}) stack.push(uint256.NewInt(tt.idx)) - opBlobHash(&pc, evm.interpreter, &ScopeContext{nil, stack, nil}) + opBlobHash(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{nil, stack, nil}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) } @@ -889,7 +889,7 @@ func TestOpMCopy(t *testing.T) { mem.Resize(memorySize) } // Do the copy - opMcopy(&pc, evm.interpreter, &ScopeContext{mem, stack, nil}) + opMcopy(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{mem, stack, nil}) want := common.FromHex(strings.ReplaceAll(tc.want, " ", "")) if have := mem.store; !bytes.Equal(want, have) { t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have) diff --git a/core/vm/interface.go b/core/vm/interface.go index 86e8c56ab0e..033a9c122f8 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -102,3 +102,23 @@ type StateDB interface { // Finalise must be invoked at the end of a transaction Finalise(bool) } + +// Interpreter is used to run Ethereum based contracts and will utilize the +// passed environment to query external sources for state information. +// The Interpreter will run the byte code VM based on the passed +// configuration. +type Interpreter interface { + // EVM returns the EVM instance + EVM() *EVM + // Config returns the configuration of the interpreter + Config() Config + // ReadOnly returns whether the interpreter is in read-only mode + ReadOnly() bool + // ReturnData gets the last CALL's return data for subsequent reuse + ReturnData() []byte + // SetReturnData sets the last CALL's return data + SetReturnData([]byte) + // Run loops and evaluates the contract's code with the given input data and returns + // the return byte-slice and an error if one occurred. + Run(contract *Contract, input []byte, static bool) ([]byte, error) +} diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index d0e5967e6e7..a6e1031ffdd 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -27,6 +27,8 @@ import ( "github.com/holiman/uint256" ) +var _ Interpreter = &EVMInterpreter{} + // Config are the configuration options for the Interpreter type Config struct { Tracer *tracing.Hooks @@ -324,3 +326,28 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( return res, err } + +// EVM returns the EVM instance +func (in *EVMInterpreter) EVM() *EVM { + return in.evm +} + +// Config returns the configuration of the interpreter +func (in EVMInterpreter) Config() Config { + return in.evm.Config +} + +// ReadOnly returns whether the interpreter is in read-only mode +func (in EVMInterpreter) ReadOnly() bool { + return in.readOnly +} + +// ReturnData gets the last CALL's return data for subsequent reuse +func (in *EVMInterpreter) ReturnData() []byte { + return in.returnData +} + +// SetReturnData sets the last CALL's return data +func (in *EVMInterpreter) SetReturnData(data []byte) { + in.returnData = data +} diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 17ac738c988..4b81de01fc0 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -24,7 +24,7 @@ import ( type ( executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error) - gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64 + gasFunc func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) // memorySizeFunc returns the required size, and whether the operation overflowed a uint64 memorySizeFunc func(*Stack) (size uint64, overflow bool) ) diff --git a/core/vm/opcode_hooks.go b/core/vm/opcode_hooks.go new file mode 100644 index 00000000000..afb8b2cd9e3 --- /dev/null +++ b/core/vm/opcode_hooks.go @@ -0,0 +1,48 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +import "github.com/ethereum/go-ethereum/common" + +// OpCodeHooks is a set of hooks that can be used to intercept and modify the +// behavior of the EVM when executing certain opcodes. +// The hooks are called before the execution of the respective opcodes. +type OpCodeHooks interface { + // CallHook is called before executing a CALL, CALLCODE, DELEGATECALL and STATICCALL opcodes. + CallHook(evm *EVM, caller common.Address, recipient common.Address) error + // CreateHook is called before executing a CREATE and CREATE2 opcodes. + CreateHook(evm *EVM, caller common.Address) error +} + +type NoopOpCodeHooks struct { +} + +func (NoopOpCodeHooks) CallHook(evm *EVM, caller common.Address, recipient common.Address) error { + return nil +} + +func (NoopOpCodeHooks) CreateHook(evm *EVM, caller common.Address) error { + return nil +} + +func newNoopOpCodeHooks() OpCodeHooks { + return NoopOpCodeHooks{} +} + +func NewDefaultOpCodeHooks() OpCodeHooks { + return newNoopOpCodeHooks() +} diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 1c9be836fc0..f0f73c79c03 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1731,13 +1731,13 @@ func TestSimulateV1(t *testing.T) { want: []blockRes{{ Number: "0xb", GasLimit: "0x47e7c4", - GasUsed: "0x52f6", + GasUsed: "0x5cba", Miner: coinbase, BaseFeePerGas: "0x0", Calls: []callRes{{ // Caller is in this case the contract that invokes ecrecover. ReturnValue: strings.ToLower(randomAccounts[2].addr.String()), - GasUsed: "0x52f6", + GasUsed: "0x5cba", Logs: []log{}, Status: "0x1", }}, diff --git a/internal/ethapi/override/override_test.go b/internal/ethapi/override/override_test.go index 02a17c13317..ab4f96c1514 100644 --- a/internal/ethapi/override/override_test.go +++ b/internal/ethapi/override/override_test.go @@ -31,9 +31,13 @@ import ( type precompileContract struct{} +func (p *precompileContract) Address() common.Address { return common.Address{} } + func (p *precompileContract) RequiredGas(input []byte) uint64 { return 0 } -func (p *precompileContract) Run(input []byte) ([]byte, error) { return nil, nil } +func (p *precompileContract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ([]byte, error) { + return nil, nil +} func TestStateOverrideMovePrecompile(t *testing.T) { db := state.NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil) diff --git a/tests/fuzzers/bls12381/precompile_fuzzer.go b/tests/fuzzers/bls12381/precompile_fuzzer.go index 33b2ca79887..d2428cf9ce1 100644 --- a/tests/fuzzers/bls12381/precompile_fuzzer.go +++ b/tests/fuzzers/bls12381/precompile_fuzzer.go @@ -76,7 +76,9 @@ func fuzz(id byte, data []byte) int { } cpy := make([]byte, len(data)) copy(cpy, data) - _, err := precompile.Run(cpy) + contract := vm.NewPrecompile(common.Address{}, precompile.Address(), common.U2560, gas) + contract.Input = cpy + _, err := precompile.Run(nil, contract, false) if !bytes.Equal(cpy, data) { panic(fmt.Sprintf("input data modified, precompile %d: %x %x", id, data, cpy)) } From 1b6bbbe3e291a8b0d1a48b3621193244e8623dda Mon Sep 17 00:00:00 2001 From: yihuang Date: Wed, 6 Aug 2025 05:09:51 +0800 Subject: [PATCH 3/5] Problem: precompile address don't match (#6) Solution: - fix addresses and add validation rule --- core/vm/contracts.go | 14 +++++++------- core/vm/custom_contracts.go | 4 ++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 166c11426a6..979774a820c 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -862,7 +862,7 @@ type bls12381G1Add struct{} // Address defines the precompiled contract address. This MUST match the address // set in the precompiled contract map. func (bls12381G1Add) Address() common.Address { - return common.BytesToAddress([]byte{10}) + return common.BytesToAddress([]byte{0x0b}) } // RequiredGas returns the gas required to execute the pre-compiled contract. @@ -904,7 +904,7 @@ type bls12381G1MultiExp struct{} // Address defines the precompiled contract address. This MUST match the address // set in the precompiled contract map. func (bls12381G1MultiExp) Address() common.Address { - return common.BytesToAddress([]byte{12}) + return common.BytesToAddress([]byte{0x0c}) } // RequiredGas returns the gas required to execute the pre-compiled contract. @@ -970,7 +970,7 @@ type bls12381G2Add struct{} // Address defines the precompiled contract address. This MUST match the address // set in the precompiled contract map. func (bls12381G2Add) Address() common.Address { - return common.BytesToAddress([]byte{13}) + return common.BytesToAddress([]byte{0x0d}) } // RequiredGas returns the gas required to execute the pre-compiled contract. @@ -1013,7 +1013,7 @@ type bls12381G2MultiExp struct{} // Address defines the precompiled contract address. This MUST match the address // set in the precompiled contract map. func (bls12381G2MultiExp) Address() common.Address { - return common.BytesToAddress([]byte{15}) + return common.BytesToAddress([]byte{0x0e}) } // RequiredGas returns the gas required to execute the pre-compiled contract. @@ -1079,7 +1079,7 @@ type bls12381Pairing struct{} // Address defines the precompiled contract address. This MUST match the address // set in the precompiled contract map. func (bls12381Pairing) Address() common.Address { - return common.BytesToAddress([]byte{16}) + return common.BytesToAddress([]byte{0x0f}) } // RequiredGas returns the gas required to execute the pre-compiled contract. @@ -1237,7 +1237,7 @@ type bls12381MapG1 struct{} // Address defines the precompiled contract address. This MUST match the address // set in the precompiled contract map. func (bls12381MapG1) Address() common.Address { - return common.BytesToAddress([]byte{17}) + return common.BytesToAddress([]byte{0x10}) } // RequiredGas returns the gas required to execute the pre-compiled contract. @@ -1272,7 +1272,7 @@ type bls12381MapG2 struct{} // Address defines the precompiled contract address. This MUST match the address // set in the precompiled contract map. func (bls12381MapG2) Address() common.Address { - return common.BytesToAddress([]byte{18}) + return common.BytesToAddress([]byte{0x11}) } // RequiredGas returns the gas required to execute the pre-compiled contract. diff --git a/core/vm/custom_contracts.go b/core/vm/custom_contracts.go index 9eafbe66111..9bd23e80993 100644 --- a/core/vm/custom_contracts.go +++ b/core/vm/custom_contracts.go @@ -55,6 +55,10 @@ func ValidatePrecompiles( return fmt.Errorf("precompile cannot be the zero address: %s", addr) } + if !bytes.Equal(addr.Bytes(), precompile.Address().Bytes()) { + return fmt.Errorf("precompile address mismatch: %s != %s", addr, precompile.Address()) + } + dupActivePrecompiles[addr] = true } From 155f082ab7a4ae519ebc448e3143bf52a9bbfdb1 Mon Sep 17 00:00:00 2001 From: yihuang Date: Thu, 7 Aug 2025 01:02:49 +0800 Subject: [PATCH 4/5] fix build --- core/vm/contracts.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 204a869e5a1..09819fb9f03 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -1396,16 +1396,16 @@ func (c *p256Verify) RequiredGas(input []byte) uint64 { } // Run executes the precompiled contract with given 160 bytes of param, returning the output and the used gas -func (c *p256Verify) Run(input []byte) ([]byte, error) { +func (c *p256Verify) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) { const p256VerifyInputLength = 160 - if len(input) != p256VerifyInputLength { + if len(contract.Input) != p256VerifyInputLength { return nil, nil } // Extract hash, r, s, x, y from the input. - hash := input[0:32] - r, s := new(big.Int).SetBytes(input[32:64]), new(big.Int).SetBytes(input[64:96]) - x, y := new(big.Int).SetBytes(input[96:128]), new(big.Int).SetBytes(input[128:160]) + hash := contract.Input[0:32] + r, s := new(big.Int).SetBytes(contract.Input[32:64]), new(big.Int).SetBytes(contract.Input[64:96]) + x, y := new(big.Int).SetBytes(contract.Input[96:128]), new(big.Int).SetBytes(contract.Input[128:160]) // Verify the signature. if secp256r1.Verify(hash, r, s, x, y) { @@ -1413,3 +1413,9 @@ func (c *p256Verify) Run(input []byte) ([]byte, error) { } return nil, nil } + +// Address defines the precompiled contract address. This MUST match the address +// set in the precompiled contract map. +func (c *p256Verify) Address() common.Address { + return common.BytesToAddress([]byte{0x1, 0x00}) +} From 73b27345e6e3965da47332f30dc84f6dea3c368e Mon Sep 17 00:00:00 2001 From: yihuang Date: Thu, 7 Aug 2025 01:55:24 +0800 Subject: [PATCH 5/5] fix test --- core/vm/instructions_test.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index fb8ea304244..8a82de5d8b1 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -107,7 +107,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected)) stack.push(x) stack.push(y) - opFn(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{nil, stack, nil}) + opFn(&pc, evm.interpreter, &ScopeContext{nil, stack, nil}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data)) } @@ -221,7 +221,7 @@ func TestAddMod(t *testing.T) { stack.push(z) stack.push(y) stack.push(x) - opAddmod(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{nil, stack, nil}) + opAddmod(&pc, evm.interpreter, &ScopeContext{nil, stack, nil}) actual := stack.pop() if actual.Cmp(expected) != 0 { t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual) @@ -247,7 +247,7 @@ func TestWriteExpectedValues(t *testing.T) { y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y)) stack.push(x) stack.push(y) - opFn(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{nil, stack, nil}) + opFn(&pc, evm.interpreter, &ScopeContext{nil, stack, nil}) actual := stack.pop() result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} } @@ -296,7 +296,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { for _, arg := range intArgs { stack.push(arg) } - op(&pc, evm.interpreter.(*EVMInterpreter), scope) + op(&pc, evm.interpreter, scope) stack.pop() } bench.StopTimer() @@ -528,13 +528,13 @@ func TestOpMstore(t *testing.T) { v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v))) stack.push(new(uint256.Int)) - opMstore(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{mem, stack, nil}) + opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil}) if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { t.Fatalf("Mstore fail, got %v, expected %v", got, v) } stack.push(new(uint256.Int).SetUint64(0x1)) stack.push(new(uint256.Int)) - opMstore(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{mem, stack, nil}) + opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil}) if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { t.Fatalf("Mstore failed to overwrite previous value") } @@ -555,7 +555,7 @@ func BenchmarkOpMstore(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(value) stack.push(memStart) - opMstore(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{mem, stack, nil}) + opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil}) } } @@ -581,14 +581,14 @@ func TestOpTstore(t *testing.T) { stack.push(new(uint256.Int).SetBytes(value)) // push the location to the stack stack.push(new(uint256.Int)) - opTstore(&pc, evm.interpreter.(*EVMInterpreter), &scopeContext) + opTstore(&pc, evm.interpreter, &scopeContext) // there should be no elements on the stack after TSTORE if stack.len() != 0 { t.Fatal("stack wrong size") } // push the location to the stack stack.push(new(uint256.Int)) - opTload(&pc, evm.interpreter.(*EVMInterpreter), &scopeContext) + opTload(&pc, evm.interpreter, &scopeContext) // there should be one element on the stack after TLOAD if stack.len() != 1 { t.Fatal("stack wrong size") @@ -613,7 +613,7 @@ func BenchmarkOpKeccak256(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(uint256.NewInt(32)) stack.push(start) - opKeccak256(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{mem, stack, nil}) + opKeccak256(&pc, evm.interpreter, &ScopeContext{mem, stack, nil}) } } @@ -707,7 +707,7 @@ func TestRandom(t *testing.T) { stack = newstack() pc = uint64(0) ) - opRandom(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{nil, stack, nil}) + opRandom(&pc, evm.interpreter, &ScopeContext{nil, stack, nil}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) } @@ -749,7 +749,7 @@ func TestBlobHash(t *testing.T) { ) evm.SetTxContext(TxContext{BlobHashes: tt.hashes}) stack.push(uint256.NewInt(tt.idx)) - opBlobHash(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{nil, stack, nil}) + opBlobHash(&pc, evm.interpreter, &ScopeContext{nil, stack, nil}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) } @@ -889,7 +889,7 @@ func TestOpMCopy(t *testing.T) { mem.Resize(memorySize) } // Do the copy - opMcopy(&pc, evm.interpreter.(*EVMInterpreter), &ScopeContext{mem, stack, nil}) + opMcopy(&pc, evm.interpreter, &ScopeContext{mem, stack, nil}) want := common.FromHex(strings.ReplaceAll(tc.want, " ", "")) if have := mem.store; !bytes.Equal(want, have) { t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have)