feat: port cosmos patch to 1.16 (#7)

* Eric/add workflows (#5)

* Add workflows

* Don't change this

* 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

* Problem: precompile address don't match (#6)

Solution:
- fix addresses and add validation rule

* fix build

* fix test

---------

Co-authored-by: Eric Warehime <eric.warehime@gmail.com>
This commit is contained in:
yihuang 2025-08-07 03:35:35 +08:00 committed by GitHub
parent dd1ebac117
commit 2fc7571efa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 1167 additions and 120 deletions

33
.github/workflows/build.yml vendored Normal file
View file

@ -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.23
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

28
.github/workflows/dependencies.yml vendored Normal file
View file

@ -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.23
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

72
.github/workflows/lint.yml vendored Normal file
View file

@ -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.23
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

29
.github/workflows/markdown-links.yml vendored Normal file
View file

@ -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

37
.github/workflows/security.yml vendored Normal file
View file

@ -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 != ''"

38
.github/workflows/super-linter.yml vendored Normal file
View file

@ -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 }}

35
.github/workflows/test.yml vendored Normal file
View file

@ -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.23
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

View file

@ -486,7 +486,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// Execute the preparatory steps for state transition which includes: // Execute the preparatory steps for state transition which includes:
// - prepare accessList(post-berlin) // - prepare accessList(post-berlin)
// - reset transient storage(eip 1153) // - 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 ( var (
ret []byte ret []byte

View file

@ -44,6 +44,7 @@ type Contract struct {
Gas uint64 Gas uint64
value *uint256.Int value *uint256.Int
isPrecompile bool
} }
// NewContract returns a new contract environment for the execution of EVM. // 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 { func (c *Contract) validJumpdest(dest *uint256.Int) bool {
if c.isPrecompile {
return false
}
udest, overflow := dest.Uint64WithOverflow() udest, overflow := dest.Uint64WithOverflow()
// PC cannot go beyond len(code) and certainly can't be bigger than 63bits. // PC cannot go beyond len(code) and certainly can't be bigger than 63bits.
// Don't bother checking for JUMPDEST in that case. // 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 // isCode returns true if the provided PC location is an actual opcode, as
// opposed to a data-segment following a PUSHN operation. // opposed to a data-segment following a PUSHN operation.
func (c *Contract) isCode(udest uint64) bool { func (c *Contract) isCode(udest uint64) bool {
if c.isPrecompile {
return false
}
// Do we already have an analysis laying around? // Do we already have an analysis laying around?
if c.analysis != nil { if c.analysis != nil {
return c.analysis.codeSegment(udest) return c.analysis.codeSegment(udest)
@ -160,6 +169,9 @@ func (c *Contract) Value() *uint256.Int {
// SetCallCode sets the code of the contract, // SetCallCode sets the code of the contract,
func (c *Contract) SetCallCode(hash common.Hash, code []byte) { func (c *Contract) SetCallCode(hash common.Hash, code []byte) {
if c.isPrecompile {
return
}
c.Code = code c.Code = code
c.CodeHash = hash c.CodeHash = hash
} }

View file

@ -37,6 +37,7 @@ import (
"github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/crypto/secp256r1" "github.com/ethereum/go-ethereum/crypto/secp256r1"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
"golang.org/x/crypto/ripemd160" "golang.org/x/crypto/ripemd160"
) )
@ -44,8 +45,9 @@ import (
// requires a deterministic gas count based on the input size of the Run method of the // requires a deterministic gas count based on the input size of the Run method of the
// contract. // contract.
type PrecompiledContract interface { type PrecompiledContract interface {
Address() common.Address
RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use
Run(input []byte) ([]byte, error) // Run runs the precompiled contract Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) // Run runs the precompiled contract
} }
// PrecompiledContracts contains the precompiled contracts supported at the given fork. // PrecompiledContracts contains the precompiled contracts supported at the given fork.
@ -257,48 +259,71 @@ func ActivePrecompiles(rules params.Rules) []common.Address {
// - the returned bytes, // - the returned bytes,
// - the _remaining_ gas, // - the _remaining_ gas,
// - any error that occurred // - 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) gasCost := p.RequiredGas(input)
if suppliedGas < gasCost { if !contract.UseGas(gasCost, logger, tracing.GasChangeCallPrecompiledContract) {
return nil, 0, ErrOutOfGas return nil, 0, ErrOutOfGas
} }
if logger != nil && logger.OnGasChange != nil {
logger.OnGasChange(suppliedGas, suppliedGas-gasCost, tracing.GasChangeCallPrecompiledContract) output, err := p.Run(evm, contract, readOnly)
} return output, contract.Gas, err
suppliedGas -= gasCost
output, err := p.Run(input)
return output, suppliedGas, err
} }
// ecrecover implemented as a native contract. // ecrecover implemented as a native contract.
type ecrecover struct{} 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 { func (c *ecrecover) RequiredGas(input []byte) uint64 {
return params.EcrecoverGas 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 const ecRecoverInputLength = 128
input = common.RightPadBytes(input, ecRecoverInputLength) contract.Input = common.RightPadBytes(contract.Input, ecRecoverInputLength)
// "input" is (hash, v, r, s), each 32 bytes // "input" is (hash, v, r, s), each 32 bytes
// but for ecrecover we want (r, s, v) // but for ecrecover we want (r, s, v)
r := new(big.Int).SetBytes(input[64:96]) r := new(big.Int).SetBytes(contract.Input[64:96])
s := new(big.Int).SetBytes(input[96:128]) s := new(big.Int).SetBytes(contract.Input[96:128])
v := input[63] - 27 v := contract.Input[63] - 27
// tighter sig s values input homestead only apply to tx sigs // 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 return nil, nil
} }
// We must make sure not to modify the 'input', so placing the 'v' along with // 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 // the signature needs to be done on a new allocation
sig := make([]byte, 65) sig := make([]byte, 65)
copy(sig, input[64:128]) copy(sig, contract.Input[64:128])
sig[64] = v sig[64] = v
// v needs to be at the end for libsecp256k1 // 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 // make sure the public key is a valid one
if err != nil { if err != nil {
return nil, nil return nil, nil
@ -311,6 +336,12 @@ func (c *ecrecover) Run(input []byte) ([]byte, error) {
// SHA256 implemented as a native contract. // SHA256 implemented as a native contract.
type sha256hash struct{} 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. // 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 // This method does not require any overflow checking as the input size gas costs
@ -318,14 +349,21 @@ type sha256hash struct{}
func (c *sha256hash) RequiredGas(input []byte) uint64 { func (c *sha256hash) RequiredGas(input []byte) uint64 {
return uint64(len(input)+31)/32*params.Sha256PerWordGas + params.Sha256BaseGas 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 return h[:], nil
} }
// RIPEMD160 implemented as a native contract. // RIPEMD160 implemented as a native contract.
type ripemd160hash struct{} 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. // 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 // This method does not require any overflow checking as the input size gas costs
@ -333,15 +371,22 @@ type ripemd160hash struct{}
func (c *ripemd160hash) RequiredGas(input []byte) uint64 { func (c *ripemd160hash) RequiredGas(input []byte) uint64 {
return uint64(len(input)+31)/32*params.Ripemd160PerWordGas + params.Ripemd160BaseGas 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 := ripemd160.New()
ripemd.Write(input) ripemd.Write(contract.Input)
return common.LeftPadBytes(ripemd.Sum(nil), 32), nil return common.LeftPadBytes(ripemd.Sum(nil), 32), nil
} }
// data copy implemented as a native contract. // data copy implemented as a native contract.
type dataCopy struct{} 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. // 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 // This method does not require any overflow checking as the input size gas costs
@ -349,8 +394,9 @@ type dataCopy struct{}
func (c *dataCopy) RequiredGas(input []byte) uint64 { func (c *dataCopy) RequiredGas(input []byte) uint64 {
return uint64(len(input)+31)/32*params.IdentityPerWordGas + params.IdentityBaseGas 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. // bigModExp implements a native big integer exponential modular operation.
@ -402,6 +448,12 @@ func modexpMultComplexity(x *big.Int) *big.Int {
return x 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. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bigModExp) RequiredGas(input []byte) uint64 { func (c *bigModExp) RequiredGas(input []byte) uint64 {
var ( var (
@ -498,16 +550,16 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 {
return gas.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 ( var (
baseLen = new(big.Int).SetBytes(getData(input, 0, 32)).Uint64() baseLen = new(big.Int).SetBytes(getData(contract.Input, 0, 32)).Uint64()
expLen = new(big.Int).SetBytes(getData(input, 32, 32)).Uint64() expLen = new(big.Int).SetBytes(getData(contract.Input, 32, 32)).Uint64()
modLen = new(big.Int).SetBytes(getData(input, 64, 32)).Uint64() modLen = new(big.Int).SetBytes(getData(contract.Input, 64, 32)).Uint64()
) )
if len(input) > 96 { if len(contract.Input) > 96 {
input = input[96:] contract.Input = contract.Input[96:]
} else { } else {
input = input[:0] contract.Input = contract.Input[:0]
} }
// Handle a special case when both the base and mod length is zero // Handle a special case when both the base and mod length is zero
if baseLen == 0 && modLen == 0 { if baseLen == 0 && modLen == 0 {
@ -519,9 +571,9 @@ func (c *bigModExp) Run(input []byte) ([]byte, error) {
} }
// Retrieve the operands and execute the exponentiation // Retrieve the operands and execute the exponentiation
var ( var (
base = new(big.Int).SetBytes(getData(input, 0, baseLen)) base = new(big.Int).SetBytes(getData(contract.Input, 0, baseLen))
exp = new(big.Int).SetBytes(getData(input, baseLen, expLen)) exp = new(big.Int).SetBytes(getData(contract.Input, baseLen, expLen))
mod = new(big.Int).SetBytes(getData(input, baseLen+expLen, modLen)) mod = new(big.Int).SetBytes(getData(contract.Input, baseLen+expLen, modLen))
v []byte v []byte
) )
switch { switch {
@ -577,26 +629,38 @@ func runBn256Add(input []byte) ([]byte, error) {
// Istanbul consensus rules. // Istanbul consensus rules.
type bn256AddIstanbul struct{} 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. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bn256AddIstanbul) RequiredGas(input []byte) uint64 { func (c *bn256AddIstanbul) RequiredGas(input []byte) uint64 {
return params.Bn256AddGasIstanbul return params.Bn256AddGasIstanbul
} }
func (c *bn256AddIstanbul) Run(input []byte) ([]byte, error) { func (c *bn256AddIstanbul) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) {
return runBn256Add(input) return runBn256Add(contract.Input)
} }
// bn256AddByzantium implements a native elliptic curve point addition // bn256AddByzantium implements a native elliptic curve point addition
// conforming to Byzantium consensus rules. // conforming to Byzantium consensus rules.
type bn256AddByzantium struct{} 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. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bn256AddByzantium) RequiredGas(input []byte) uint64 { func (c *bn256AddByzantium) RequiredGas(input []byte) uint64 {
return params.Bn256AddGasByzantium return params.Bn256AddGasByzantium
} }
func (c *bn256AddByzantium) Run(input []byte) ([]byte, error) { func (c *bn256AddByzantium) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) {
return runBn256Add(input) return runBn256Add(contract.Input)
} }
// runBn256ScalarMul implements the Bn256ScalarMul precompile, referenced by // runBn256ScalarMul implements the Bn256ScalarMul precompile, referenced by
@ -615,26 +679,38 @@ func runBn256ScalarMul(input []byte) ([]byte, error) {
// multiplication conforming to Istanbul consensus rules. // multiplication conforming to Istanbul consensus rules.
type bn256ScalarMulIstanbul struct{} 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. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bn256ScalarMulIstanbul) RequiredGas(input []byte) uint64 { func (c *bn256ScalarMulIstanbul) RequiredGas(input []byte) uint64 {
return params.Bn256ScalarMulGasIstanbul return params.Bn256ScalarMulGasIstanbul
} }
func (c *bn256ScalarMulIstanbul) Run(input []byte) ([]byte, error) { func (c *bn256ScalarMulIstanbul) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) {
return runBn256ScalarMul(input) return runBn256ScalarMul(contract.Input)
} }
// bn256ScalarMulByzantium implements a native elliptic curve scalar // bn256ScalarMulByzantium implements a native elliptic curve scalar
// multiplication conforming to Byzantium consensus rules. // multiplication conforming to Byzantium consensus rules.
type bn256ScalarMulByzantium struct{} 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. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bn256ScalarMulByzantium) RequiredGas(input []byte) uint64 { func (c *bn256ScalarMulByzantium) RequiredGas(input []byte) uint64 {
return params.Bn256ScalarMulGasByzantium return params.Bn256ScalarMulGasByzantium
} }
func (c *bn256ScalarMulByzantium) Run(input []byte) ([]byte, error) { func (c *bn256ScalarMulByzantium) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) {
return runBn256ScalarMul(input) return runBn256ScalarMul(contract.Input)
} }
var ( var (
@ -683,30 +759,48 @@ func runBn256Pairing(input []byte) ([]byte, error) {
// conforming to Istanbul consensus rules. // conforming to Istanbul consensus rules.
type bn256PairingIstanbul struct{} 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. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bn256PairingIstanbul) RequiredGas(input []byte) uint64 { func (c *bn256PairingIstanbul) RequiredGas(input []byte) uint64 {
return params.Bn256PairingBaseGasIstanbul + uint64(len(input)/192)*params.Bn256PairingPerPointGasIstanbul return params.Bn256PairingBaseGasIstanbul + uint64(len(input)/192)*params.Bn256PairingPerPointGasIstanbul
} }
func (c *bn256PairingIstanbul) Run(input []byte) ([]byte, error) { func (c *bn256PairingIstanbul) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) {
return runBn256Pairing(input) return runBn256Pairing(contract.Input)
} }
// bn256PairingByzantium implements a pairing pre-compile for the bn256 curve // bn256PairingByzantium implements a pairing pre-compile for the bn256 curve
// conforming to Byzantium consensus rules. // conforming to Byzantium consensus rules.
type bn256PairingByzantium struct{} 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. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bn256PairingByzantium) RequiredGas(input []byte) uint64 { func (c *bn256PairingByzantium) RequiredGas(input []byte) uint64 {
return params.Bn256PairingBaseGasByzantium + uint64(len(input)/192)*params.Bn256PairingPerPointGasByzantium return params.Bn256PairingBaseGasByzantium + uint64(len(input)/192)*params.Bn256PairingPerPointGasByzantium
} }
func (c *bn256PairingByzantium) Run(input []byte) ([]byte, error) { func (c *bn256PairingByzantium) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) {
return runBn256Pairing(input) return runBn256Pairing(contract.Input)
} }
type blake2F struct{} 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 { func (c *blake2F) RequiredGas(input []byte) uint64 {
// If the input is malformed, we can't calculate the gas, return 0 and let the // If the input is malformed, we can't calculate the gas, return 0 and let the
// actual call choke and fault. // actual call choke and fault.
@ -727,18 +821,18 @@ var (
errBlake2FInvalidFinalFlag = errors.New("invalid final flag") 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) // Make sure the input is valid (correct length and final flag)
if len(input) != blake2FInputLength { if len(contract.Input) != blake2FInputLength {
return nil, errBlake2FInvalidInputLength return nil, errBlake2FInvalidInputLength
} }
if input[212] != blake2FNonFinalBlockBytes && input[212] != blake2FFinalBlockBytes { if contract.Input[212] != blake2FNonFinalBlockBytes && contract.Input[212] != blake2FFinalBlockBytes {
return nil, errBlake2FInvalidFinalFlag return nil, errBlake2FInvalidFinalFlag
} }
// Parse the input into the Blake2b call parameters // Parse the input into the Blake2b call parameters
var ( var (
rounds = binary.BigEndian.Uint32(input[0:4]) rounds = binary.BigEndian.Uint32(contract.Input[0:4])
final = input[212] == blake2FFinalBlockBytes final = contract.Input[212] == blake2FFinalBlockBytes
h [8]uint64 h [8]uint64
m [16]uint64 m [16]uint64
@ -746,14 +840,14 @@ func (c *blake2F) Run(input []byte) ([]byte, error) {
) )
for i := 0; i < 8; i++ { for i := 0; i < 8; i++ {
offset := 4 + i*8 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++ { for i := 0; i < 16; i++ {
offset := 68 + i*8 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[0] = binary.LittleEndian.Uint64(contract.Input[196:204])
t[1] = binary.LittleEndian.Uint64(input[204:212]) t[1] = binary.LittleEndian.Uint64(contract.Input[204:212])
// Execute the compression function, extract and return the result // Execute the compression function, extract and return the result
blake2b.F(&h, m, t, final, rounds) blake2b.F(&h, m, t, final, rounds)
@ -776,27 +870,33 @@ var (
// bls12381G1Add implements EIP-2537 G1Add precompile. // bls12381G1Add implements EIP-2537 G1Add precompile.
type bls12381G1Add struct{} 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{0x0b})
}
// RequiredGas returns the gas required to execute the pre-compiled contract. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bls12381G1Add) RequiredGas(input []byte) uint64 { func (c *bls12381G1Add) RequiredGas(input []byte) uint64 {
return params.Bls12381G1AddGas 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. // 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). // > 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). // > 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 return nil, errBLS12381InvalidInputLength
} }
var err error var err error
var p0, p1 *bls12381.G1Affine var p0, p1 *bls12381.G1Affine
// Decode G1 point p_0 // 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 return nil, err
} }
// Decode G1 point p_1 // 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 return nil, err
} }
@ -812,6 +912,12 @@ func (c *bls12381G1Add) Run(input []byte) ([]byte, error) {
// bls12381G1MultiExp implements EIP-2537 G1MultiExp precompile. // bls12381G1MultiExp implements EIP-2537 G1MultiExp precompile.
type bls12381G1MultiExp struct{} 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{0x0c})
}
// RequiredGas returns the gas required to execute the pre-compiled contract. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bls12381G1MultiExp) RequiredGas(input []byte) uint64 { func (c *bls12381G1MultiExp) RequiredGas(input []byte) uint64 {
// Calculate G1 point, scalar value pair length // Calculate G1 point, scalar value pair length
@ -831,12 +937,12 @@ func (c *bls12381G1MultiExp) RequiredGas(input []byte) uint64 {
return (uint64(k) * params.Bls12381G1MulGas * discount) / 1000 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. // 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). // 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). // Output is an encoding of multiexponentiation operation result - single G1 point (`128` bytes).
k := len(input) / 160 k := len(contract.Input) / 160
if len(input) == 0 || len(input)%160 != 0 { if len(contract.Input) == 0 || len(contract.Input)%160 != 0 {
return nil, errBLS12381InvalidInputLength return nil, errBLS12381InvalidInputLength
} }
points := make([]bls12381.G1Affine, k) points := make([]bls12381.G1Affine, k)
@ -847,7 +953,7 @@ func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) {
off := 160 * i off := 160 * i
t0, t1, t2 := off, off+128, off+160 t0, t1, t2 := off, off+128, off+160
// Decode G1 point // Decode G1 point
p, err := decodePointG1(input[t0:t1]) p, err := decodePointG1(contract.Input[t0:t1])
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -858,7 +964,7 @@ func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) {
} }
points[i] = *p points[i] = *p
// Decode scalar value // 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) // Compute r = e_0 * p_0 + e_1 * p_1 + ... + e_(k-1) * p_(k-1)
@ -872,27 +978,33 @@ func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) {
// bls12381G2Add implements EIP-2537 G2Add precompile. // bls12381G2Add implements EIP-2537 G2Add precompile.
type bls12381G2Add struct{} 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{0x0d})
}
// RequiredGas returns the gas required to execute the pre-compiled contract. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bls12381G2Add) RequiredGas(input []byte) uint64 { func (c *bls12381G2Add) RequiredGas(input []byte) uint64 {
return params.Bls12381G2AddGas 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. // 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). // > 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). // > 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 return nil, errBLS12381InvalidInputLength
} }
var err error var err error
var p0, p1 *bls12381.G2Affine var p0, p1 *bls12381.G2Affine
// Decode G2 point p_0 // 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 return nil, err
} }
// Decode G2 point p_1 // 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 return nil, err
} }
@ -909,6 +1021,12 @@ func (c *bls12381G2Add) Run(input []byte) ([]byte, error) {
// bls12381G2MultiExp implements EIP-2537 G2MultiExp precompile. // bls12381G2MultiExp implements EIP-2537 G2MultiExp precompile.
type bls12381G2MultiExp struct{} 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{0x0e})
}
// RequiredGas returns the gas required to execute the pre-compiled contract. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bls12381G2MultiExp) RequiredGas(input []byte) uint64 { func (c *bls12381G2MultiExp) RequiredGas(input []byte) uint64 {
// Calculate G2 point, scalar value pair length // Calculate G2 point, scalar value pair length
@ -928,12 +1046,12 @@ func (c *bls12381G2MultiExp) RequiredGas(input []byte) uint64 {
return (uint64(k) * params.Bls12381G2MulGas * discount) / 1000 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 // 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). // > 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). // > Output is an encoding of multiexponentiation operation result - single G2 point (`256` bytes).
k := len(input) / 288 k := len(contract.Input) / 288
if len(input) == 0 || len(input)%288 != 0 { if len(contract.Input) == 0 || len(contract.Input)%288 != 0 {
return nil, errBLS12381InvalidInputLength return nil, errBLS12381InvalidInputLength
} }
points := make([]bls12381.G2Affine, k) points := make([]bls12381.G2Affine, k)
@ -944,7 +1062,7 @@ func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) {
off := 288 * i off := 288 * i
t0, t1, t2 := off, off+256, off+288 t0, t1, t2 := off, off+256, off+288
// Decode G2 point // Decode G2 point
p, err := decodePointG2(input[t0:t1]) p, err := decodePointG2(contract.Input[t0:t1])
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -955,7 +1073,7 @@ func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) {
} }
points[i] = *p points[i] = *p
// Decode scalar value // 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) // Compute r = e_0 * p_0 + e_1 * p_1 + ... + e_(k-1) * p_(k-1)
@ -969,20 +1087,26 @@ func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) {
// bls12381Pairing implements EIP-2537 Pairing precompile. // bls12381Pairing implements EIP-2537 Pairing precompile.
type bls12381Pairing struct{} 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{0x0f})
}
// RequiredGas returns the gas required to execute the pre-compiled contract. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bls12381Pairing) RequiredGas(input []byte) uint64 { func (c *bls12381Pairing) RequiredGas(input []byte) uint64 {
return params.Bls12381PairingBaseGas + uint64(len(input)/384)*params.Bls12381PairingPerPairGas 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. // 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: // > 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 // > - `128` bytes of G1 point encoding
// > - `256` bytes of G2 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 // > 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). // > (which is equivalent of Big Endian encoding of Solidity values `uint256(1)` and `uin256(0)` respectively).
k := len(input) / 384 k := len(contract.Input) / 384
if len(input) == 0 || len(input)%384 != 0 { if len(contract.Input) == 0 || len(contract.Input)%384 != 0 {
return nil, errBLS12381InvalidInputLength return nil, errBLS12381InvalidInputLength
} }
@ -997,12 +1121,12 @@ func (c *bls12381Pairing) Run(input []byte) ([]byte, error) {
t0, t1, t2 := off, off+128, off+384 t0, t1, t2 := off, off+128, off+384
// Decode G1 point // Decode G1 point
p1, err := decodePointG1(input[t0:t1]) p1, err := decodePointG1(contract.Input[t0:t1])
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Decode G2 point // Decode G2 point
p2, err := decodePointG2(input[t1:t2]) p2, err := decodePointG2(contract.Input[t1:t2])
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1121,21 +1245,27 @@ func encodePointG2(p *bls12381.G2Affine) []byte {
// bls12381MapG1 implements EIP-2537 MapG1 precompile. // bls12381MapG1 implements EIP-2537 MapG1 precompile.
type bls12381MapG1 struct{} 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{0x10})
}
// RequiredGas returns the gas required to execute the pre-compiled contract. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bls12381MapG1) RequiredGas(input []byte) uint64 { func (c *bls12381MapG1) RequiredGas(input []byte) uint64 {
return params.Bls12381MapG1Gas 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. // 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. // > 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. // > 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 return nil, errBLS12381InvalidInputLength
} }
// Decode input field element // Decode input field element
fe, err := decodeBLS12381FieldElement(input) fe, err := decodeBLS12381FieldElement(contract.Input)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1150,25 +1280,31 @@ func (c *bls12381MapG1) Run(input []byte) ([]byte, error) {
// bls12381MapG2 implements EIP-2537 MapG2 precompile. // bls12381MapG2 implements EIP-2537 MapG2 precompile.
type bls12381MapG2 struct{} 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{0x11})
}
// RequiredGas returns the gas required to execute the pre-compiled contract. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bls12381MapG2) RequiredGas(input []byte) uint64 { func (c *bls12381MapG2) RequiredGas(input []byte) uint64 {
return params.Bls12381MapG2Gas 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. // 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. // > 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. // > 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 return nil, errBLS12381InvalidInputLength
} }
// Decode input field element // Decode input field element
c0, err := decodeBLS12381FieldElement(input[:64]) c0, err := decodeBLS12381FieldElement(contract.Input[:64])
if err != nil { if err != nil {
return nil, err return nil, err
} }
c1, err := decodeBLS12381FieldElement(input[64:]) c1, err := decodeBLS12381FieldElement(contract.Input[64:])
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1183,6 +1319,12 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) {
// kzgPointEvaluation implements the EIP-4844 point evaluation precompile. // kzgPointEvaluation implements the EIP-4844 point evaluation precompile.
type kzgPointEvaluation struct{} 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. // RequiredGas estimates the gas required for running the point evaluation precompile.
func (b *kzgPointEvaluation) RequiredGas(input []byte) uint64 { func (b *kzgPointEvaluation) RequiredGas(input []byte) uint64 {
return params.BlobTxPointEvaluationPrecompileGas return params.BlobTxPointEvaluationPrecompileGas
@ -1201,33 +1343,33 @@ var (
) )
// Run executes the point evaluation precompile. // Run executes the point evaluation precompile.
func (b *kzgPointEvaluation) Run(input []byte) ([]byte, error) { func (b *kzgPointEvaluation) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) {
if len(input) != blobVerifyInputLength { if len(contract.Input) != blobVerifyInputLength {
return nil, errBlobVerifyInvalidInputLength return nil, errBlobVerifyInvalidInputLength
} }
// versioned hash: first 32 bytes // versioned hash: first 32 bytes
var versionedHash common.Hash var versionedHash common.Hash
copy(versionedHash[:], input[:]) copy(versionedHash[:], contract.Input[:])
var ( var (
point kzg4844.Point point kzg4844.Point
claim kzg4844.Claim claim kzg4844.Claim
) )
// Evaluation point: next 32 bytes // Evaluation point: next 32 bytes
copy(point[:], input[32:]) copy(point[:], contract.Input[32:])
// Expected output: next 32 bytes // Expected output: next 32 bytes
copy(claim[:], input[64:]) copy(claim[:], contract.Input[64:])
// input kzg point: next 48 bytes // input kzg point: next 48 bytes
var commitment kzg4844.Commitment var commitment kzg4844.Commitment
copy(commitment[:], input[96:]) copy(commitment[:], contract.Input[96:])
if kZGToVersionedHash(commitment) != versionedHash { if kZGToVersionedHash(commitment) != versionedHash {
return nil, errBlobVerifyMismatchedVersion return nil, errBlobVerifyMismatchedVersion
} }
// Proof: next 48 bytes // Proof: next 48 bytes
var proof kzg4844.Proof var proof kzg4844.Proof
copy(proof[:], input[144:]) copy(proof[:], contract.Input[144:])
if err := kzg4844.VerifyProof(commitment, point, claim, proof); err != nil { if err := kzg4844.VerifyProof(commitment, point, claim, proof); err != nil {
return nil, fmt.Errorf("%w: %v", errBlobVerifyKZGProof, err) return nil, fmt.Errorf("%w: %v", errBlobVerifyKZGProof, err)
@ -1254,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 // 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 const p256VerifyInputLength = 160
if len(input) != p256VerifyInputLength { if len(contract.Input) != p256VerifyInputLength {
return nil, nil return nil, nil
} }
// Extract hash, r, s, x, y from the input. // Extract hash, r, s, x, y from the input.
hash := input[0:32] hash := contract.Input[0:32]
r, s := new(big.Int).SetBytes(input[32:64]), new(big.Int).SetBytes(input[64:96]) r, s := new(big.Int).SetBytes(contract.Input[32:64]), new(big.Int).SetBytes(contract.Input[64:96])
x, y := new(big.Int).SetBytes(input[96:128]), new(big.Int).SetBytes(input[128:160]) x, y := new(big.Int).SetBytes(contract.Input[96:128]), new(big.Int).SetBytes(contract.Input[128:160])
// Verify the signature. // Verify the signature.
if secp256r1.Verify(hash, r, s, x, y) { if secp256r1.Verify(hash, r, s, x, y) {
@ -1271,3 +1413,9 @@ func (c *p256Verify) Run(input []byte) ([]byte, error) {
} }
return nil, nil 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})
}

View file

@ -20,6 +20,7 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256"
) )
func FuzzPrecompiledContracts(f *testing.F) { func FuzzPrecompiledContracts(f *testing.F) {
@ -36,7 +37,8 @@ func FuzzPrecompiledContracts(f *testing.F) {
return return
} }
inWant := string(input) 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 { if inHave := string(input); inWant != inHave {
t.Errorf("Precompiled %v modified input data", a) t.Errorf("Precompiled %v modified input data", a)
} }

View file

@ -25,6 +25,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256"
) )
// precompiledTest defines the input/output pairs for precompiled contract tests. // precompiledTest defines the input/output pairs for precompiled contract tests.
@ -99,7 +100,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
in := common.Hex2Bytes(test.Input) in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { 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) t.Error(err)
} else if common.Bytes2Hex(res) != test.Expected { } else if common.Bytes2Hex(res) != test.Expected {
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
@ -121,7 +122,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
gas := p.RequiredGas(in) - 1 gas := p.RequiredGas(in) - 1
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { 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" { if err.Error() != "out of gas" {
t.Errorf("Expected error [out of gas], got [%v]", err) t.Errorf("Expected error [out of gas], got [%v]", err)
} }
@ -138,7 +139,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing
in := common.Hex2Bytes(test.Input) in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(test.Name, func(t *testing.T) { 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 { if err.Error() != test.ExpectedError {
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
} }
@ -170,7 +171,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
bench.ResetTimer() bench.ResetTimer()
for i := 0; i < bench.N; i++ { for i := 0; i < bench.N; i++ {
copy(data, in) 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() bench.StopTimer()
elapsed := uint64(time.Since(start)) elapsed := uint64(time.Since(start))
@ -182,7 +183,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
// Keep it as uint64, multiply 100 to get two digit float later // Keep it as uint64, multiply 100 to get two digit float later
mgasps := (100 * 1000 * gasUsed) / elapsed mgasps := (100 * 1000 * gasUsed) / elapsed
bench.ReportMetric(float64(mgasps)/100, "mgas/s") bench.ReportMetric(float64(mgasps)/100, "mgas/s")
//Check if it is correct // Check if it is correct
if err != nil { if err != nil {
bench.Error(err) bench.Error(err)
return return

View file

@ -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
}

View file

@ -0,0 +1,66 @@
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)
}
if !bytes.Equal(addr.Bytes(), precompile.Address().Bytes()) {
return fmt.Errorf("precompile address mismatch: %s != %s", addr, precompile.Address())
}
dupActivePrecompiles[addr] = true
}
return nil
}

151
core/vm/custom_eip.go Normal file
View file

@ -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
}

192
core/vm/custom_eip_test.go Normal file
View file

@ -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()
})
}
}

View file

@ -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
}
}

View file

@ -110,7 +110,6 @@ type EVM struct {
// global (to this context) ethereum virtual machine used throughout // global (to this context) ethereum virtual machine used throughout
// the execution of the tx // the execution of the tx
interpreter *EVMInterpreter interpreter *EVMInterpreter
// abort is used to abort the EVM calling operations // abort is used to abort the EVM calling operations
abort atomic.Bool abort atomic.Bool
@ -124,6 +123,11 @@ type EVM struct {
// jumpDests stores results of JUMPDEST analysis. // jumpDests stores results of JUMPDEST analysis.
jumpDests JumpDestCache jumpDests JumpDestCache
// 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 // NewEVM constructs an EVM instance with the supplied block context, state
@ -138,9 +142,21 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon
chainConfig: chainConfig, chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
jumpDests: newMapJumpDests(), jumpDests: newMapJumpDests(),
hooks: newNoopOpCodeHooks(),
} }
evm.precompiles = activePrecompiledContracts(evm.chainRules) evm.precompiles = activePrecompiledContracts(evm.chainRules)
evm.interpreter = NewEVMInterpreter(evm) 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 return evm
} }
@ -181,6 +197,11 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
return evm.interpreter 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 { func isSystemCall(caller common.Address) bool {
return caller == params.SystemAddress return caller == params.SystemAddress
} }
@ -197,6 +218,10 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
}(gas) }(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 // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
@ -205,8 +230,9 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
if !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) { if !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) {
return nil, gas, ErrInsufficientBalance return nil, gas, ErrInsufficientBalance
} }
snapshot := evm.StateDB.Snapshot() snapshot := evm.StateDB.Snapshot()
p, isPrecompile := evm.precompile(addr) p, isPrecompile := evm.Precompile(addr)
if !evm.StateDB.Exist(addr) { if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) { if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) {
@ -234,7 +260,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
evm.Context.Transfer(evm.StateDB, caller, addr, value) evm.Context.Transfer(evm.StateDB, caller, addr, value)
if isPrecompile { 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 { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // Initialise a new contract and set the code that is to be used by the EVM.
code := evm.resolveCode(addr) code := evm.resolveCode(addr)
@ -283,6 +309,9 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
}(gas) }(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 // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
@ -297,8 +326,8 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
var snapshot = evm.StateDB.Snapshot() var snapshot = evm.StateDB.Snapshot()
// It is allowed to call precompiles, even via delegatecall // It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile { if p, isPrecompile := evm.Precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) ret, gas, err = evm.RunPrecompiledContract(p, caller, input, gas, value, true, evm.Config.Tracer)
} else { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // 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. // The contract is a scoped environment for this execution context only.
@ -333,6 +362,9 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
}(gas) }(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 // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
@ -340,8 +372,8 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
var snapshot = evm.StateDB.Snapshot() var snapshot = evm.StateDB.Snapshot()
// It is allowed to call precompiles, even via delegatecall // It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile { if p, isPrecompile := evm.Precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) ret, gas, err = evm.RunPrecompiledContract(p, caller, input, gas, nil, true, evm.Config.Tracer)
} else { } else {
// Initialise a new contract and make initialise the delegate values // Initialise a new contract and make initialise the delegate values
// //
@ -375,6 +407,9 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
}(gas) }(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 // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
@ -392,8 +427,8 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
// future scenarios // future scenarios
evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount) evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount)
if p, isPrecompile := evm.precompile(addr); isPrecompile { if p, isPrecompile := evm.Precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) ret, gas, err = evm.RunPrecompiledContract(p, caller, input, gas, new(uint256.Int), true, evm.Config.Tracer)
} else { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // 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. // The contract is a scoped environment for this execution context only.
@ -558,6 +593,9 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
// Create creates a new contract using code as deployment code. // 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) { 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)) contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller))
return evm.create(caller, code, gas, value, contractAddr, CREATE) return evm.create(caller, code, gas, value, contractAddr, CREATE)
} }
@ -567,6 +605,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:] // 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. // 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) { 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) inithash := crypto.HashData(evm.interpreter.hasher, code)
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:]) contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:])
return evm.create(caller, code, gas, endowment, contractAddr, CREATE2) return evm.create(caller, code, gas, endowment, contractAddr, CREATE2)

View file

@ -102,3 +102,23 @@ type StateDB interface {
// Finalise must be invoked at the end of a transaction // Finalise must be invoked at the end of a transaction
Finalise(bool) 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)
}

View file

@ -27,6 +27,8 @@ import (
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
var _ Interpreter = &EVMInterpreter{}
// Config are the configuration options for the Interpreter // Config are the configuration options for the Interpreter
type Config struct { type Config struct {
Tracer *tracing.Hooks Tracer *tracing.Hooks
@ -328,3 +330,28 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
return res, err 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
}

View file

@ -24,7 +24,7 @@ import (
type ( type (
executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error) 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 returns the required size, and whether the operation overflowed a uint64
memorySizeFunc func(*Stack) (size uint64, overflow bool) memorySizeFunc func(*Stack) (size uint64, overflow bool)
) )

48
core/vm/opcode_hooks.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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()
}

View file

@ -1731,13 +1731,13 @@ func TestSimulateV1(t *testing.T) {
want: []blockRes{{ want: []blockRes{{
Number: "0xb", Number: "0xb",
GasLimit: "0x47e7c4", GasLimit: "0x47e7c4",
GasUsed: "0x52f6", GasUsed: "0x5cba",
Miner: coinbase, Miner: coinbase,
BaseFeePerGas: "0x0", BaseFeePerGas: "0x0",
Calls: []callRes{{ Calls: []callRes{{
// Caller is in this case the contract that invokes ecrecover. // Caller is in this case the contract that invokes ecrecover.
ReturnValue: strings.ToLower(randomAccounts[2].addr.String()), ReturnValue: strings.ToLower(randomAccounts[2].addr.String()),
GasUsed: "0x52f6", GasUsed: "0x5cba",
Logs: []log{}, Logs: []log{},
Status: "0x1", Status: "0x1",
}}, }},

View file

@ -31,9 +31,13 @@ import (
type precompileContract struct{} type precompileContract struct{}
func (p *precompileContract) Address() common.Address { return common.Address{} }
func (p *precompileContract) RequiredGas(input []byte) uint64 { return 0 } 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) { func TestStateOverrideMovePrecompile(t *testing.T) {
db := state.NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil) db := state.NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil)

View file

@ -76,7 +76,9 @@ func fuzz(id byte, data []byte) int {
} }
cpy := make([]byte, len(data)) cpy := make([]byte, len(data))
copy(cpy, 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) { if !bytes.Equal(cpy, data) {
panic(fmt.Sprintf("input data modified, precompile %d: %x %x", id, data, cpy)) panic(fmt.Sprintf("input data modified, precompile %d: %x %x", id, data, cpy))
} }