Skip to content

Commit

Permalink
feat(vm): EVM active precompiles (backport ethereum#7) (ethereum#16)
Browse files Browse the repository at this point in the history
Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
Co-authored-by: Freddy Caceres <facs95@gmail.com>
Co-authored-by: Vladislav Varadinov <vladislav.varadinov@gmail.com>
  • Loading branch information
4 people committed Feb 3, 2023
1 parent 9afd9b4 commit 8d40791
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 67 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Improvements

* [#7](https://github.com/evmos/go-ethereum/pull/7) Implement custom active precompiles for the EVM.
* [#6](https://github.com/evmos/go-ethereum/pull/6) Refactor `Stack` implementation
* [#3](https://github.com/evmos/go-ethereum/pull/3) Move the `JumpTable` defaults to a separate function.
* [#2](https://github.com/evmos/go-ethereum/pull/2) Define `Interpreter` interface for the EVM.
23 changes: 13 additions & 10 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ The state transitioning model does all the necessary work to work out a valid ne
3) Create a new state object if the recipient is \0*32
4) Value transfer
== If contract creation ==
4a) Attempt to run transaction data
4b) If valid, use result as code for the new state object
4a) Attempt to run transaction data
4b) If valid, use result as code for the new state object
== end ==
5) Run Script section
6) Derive new state root
Expand Down Expand Up @@ -262,13 +264,13 @@ func (st *StateTransition) preCheck() error {
// TransitionDb will transition the state by applying the current message and
// returning the evm execution result with following fields.
//
// - used gas:
// total gas used (including gas being refunded)
// - returndata:
// the returned data from evm
// - concrete execution error:
// various **EVM** error which aborts the execution,
// e.g. ErrOutOfGas, ErrExecutionReverted
// - used gas:
// total gas used (including gas being refunded)
// - returndata:
// the returned data from evm
// - concrete execution error:
// various **EVM** error which aborts the execution,
// e.g. ErrOutOfGas, ErrExecutionReverted
//
// However if any consensus issue encountered, return the error directly with
// nil evm execution result.
Expand Down Expand Up @@ -319,8 +321,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {

// Set up the initial access list.
if rules.IsBerlin {
st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
st.state.PrepareAccessList(msg.From(), msg.To(), st.evm.ActivePrecompiles(rules), msg.AccessList())
}

var (
ret []byte
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
Expand Down
97 changes: 92 additions & 5 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
package vm

import (
"bytes"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -126,8 +128,8 @@ func init() {
}
}

// ActivePrecompiles returns the precompiles enabled with the current configuration.
func ActivePrecompiles(rules params.Rules) []common.Address {
// DefaultActivePrecompiles returns the set of precompiles enabled with the default configuration.
func DefaultActivePrecompiles(rules params.Rules) []common.Address {
switch {
case rules.IsBerlin:
return PrecompiledAddressesBerlin
Expand All @@ -140,6 +142,87 @@ func ActivePrecompiles(rules params.Rules) []common.Address {
}
}

// DefaultPrecompiles define the mapping of address and precompiles from the default configuration
func DefaultPrecompiles(rules params.Rules) (precompiles map[common.Address]PrecompiledContract) {
switch {
case rules.IsBerlin:
precompiles = PrecompiledContractsBerlin
case rules.IsIstanbul:
precompiles = PrecompiledContractsIstanbul
case rules.IsByzantium:
precompiles = PrecompiledContractsByzantium
default:
precompiles = PrecompiledContractsHomestead
}

return precompiles
}

// ActivePrecompiles returns the precompiles enabled with the current configuration.
//
// NOTE: The rules argument is ignored as the active precompiles can be set via the WithPrecompiles
// method according to the chain rules from the current block context.
func (evm *EVM) ActivePrecompiles(_ params.Rules) []common.Address {
return evm.activePrecompiles
}

// 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,
activePrecompiles []common.Address,
) {
evm.precompiles = precompiles
evm.activePrecompiles = activePrecompiles
}

// 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 map[common.Address]PrecompiledContract,
activePrecompiles []common.Address,
) error {
if len(precompiles) != len(activePrecompiles) {
return fmt.Errorf("precompiles length mismatch (expected %d, got %d)", len(precompiles), len(activePrecompiles))
}

dupActivePrecompiles := make(map[common.Address]bool)

for _, addr := range activePrecompiles {
if dupActivePrecompiles[addr] {
return fmt.Errorf("duplicate active precompile: %s", addr)
}

precompile, ok := precompiles[addr]
if !ok {
return fmt.Errorf("active precompile address doesn't exist in precompiles map: %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
}

// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
// It returns
// - the returned bytes,
Expand Down Expand Up @@ -203,6 +286,7 @@ 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)
return h[:], nil
Expand All @@ -218,6 +302,7 @@ 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) {
ripemd := ripemd160.New()
ripemd.Write(input)
Expand All @@ -234,6 +319,7 @@ 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 in, nil
}
Expand Down Expand Up @@ -264,9 +350,10 @@ var (
// modexpMultComplexity implements bigModexp multComplexity formula, as defined in EIP-198
//
// def mult_complexity(x):
// if x <= 64: return x ** 2
// elif x <= 1024: return x ** 2 // 4 + 96 * x - 3072
// else: return x ** 2 // 16 + 480 * x - 199680
//
// if x <= 64: return x ** 2
// elif x <= 1024: return x ** 2 // 4 + 96 * x - 3072
// else: return x ** 2 // 16 + 480 * x - 199680
//
// where is x is max(length_of_MODULUS, length_of_BASE)
func modexpMultComplexity(x *big.Int) *big.Int {
Expand Down
33 changes: 12 additions & 21 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,6 @@ type (
GetHashFunc func(uint64) common.Hash
)

func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
var precompiles map[common.Address]PrecompiledContract
switch {
case evm.chainRules.IsBerlin:
precompiles = PrecompiledContractsBerlin
case evm.chainRules.IsIstanbul:
precompiles = PrecompiledContractsIstanbul
case evm.chainRules.IsByzantium:
precompiles = PrecompiledContractsByzantium
default:
precompiles = PrecompiledContractsHomestead
}
p, ok := precompiles[addr]
return p, ok
}

// BlockContext provides the EVM with auxiliary information. Once provided
// it shouldn't be modified.
type BlockContext struct {
Expand Down Expand Up @@ -121,6 +105,10 @@ type EVM struct {
// available gas is calculated in gasCall* according to the 63/64 rule and later
// applied in opCall*.
callGasTemp uint64
// precompiles defines the precompiled contracts used by the EVM
precompiles map[common.Address]PrecompiledContract
// activePrecompiles defines the precompiles that are currently active
activePrecompiles []common.Address
}

// NewEVM returns a new EVM. The returned EVM is not thread safe and should
Expand All @@ -134,8 +122,11 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil),
}

// set the default precompiles
evm.activePrecompiles = DefaultActivePrecompiles(evm.chainRules)
evm.precompiles = DefaultPrecompiles(evm.chainRules)
evm.interpreter = NewEVMInterpreter(evm, config)

return evm
}

Expand Down Expand Up @@ -181,7 +172,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
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.IsEIP158 && value.Sign() == 0 {
Expand Down Expand Up @@ -280,7 +271,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
}

// 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)
} else {
addrCopy := addr
Expand Down Expand Up @@ -321,7 +312,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
}

// 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)
} else {
addrCopy := addr
Expand Down Expand Up @@ -370,7 +361,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
}(gas)
}

if p, isPrecompile := evm.precompile(addr); isPrecompile {
if p, isPrecompile := evm.Precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
} else {
// At this point, we use a copy of address. If we don't, the go compiler will
Expand Down
8 changes: 5 additions & 3 deletions core/vm/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,9 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
sender = vm.AccountRef(cfg.Origin)
)
if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil); rules.IsBerlin {
cfg.State.PrepareAccessList(cfg.Origin, &address, vm.ActivePrecompiles(rules), nil)
cfg.State.PrepareAccessList(cfg.Origin, &address, vm.DefaultActivePrecompiles(rules), nil)
}

cfg.State.CreateAccount(address)
// set the receiver's (the executing contract) code for execution.
cfg.State.SetCode(address, code)
Expand Down Expand Up @@ -151,8 +152,9 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
sender = vm.AccountRef(cfg.Origin)
)
if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil); rules.IsBerlin {
cfg.State.PrepareAccessList(cfg.Origin, nil, vm.ActivePrecompiles(rules), nil)
cfg.State.PrepareAccessList(cfg.Origin, nil, vm.DefaultActivePrecompiles(rules), nil)
}

// Call the code with the given configuration.
code, address, leftOverGas, err := vmenv.Create(
sender,
Expand All @@ -177,7 +179,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
statedb := cfg.State

if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil); rules.IsBerlin {
statedb.PrepareAccessList(cfg.Origin, &address, vm.ActivePrecompiles(rules), nil)
statedb.PrepareAccessList(cfg.Origin, &address, vm.DefaultActivePrecompiles(rules), nil)
}
// Call the code with the given configuration.
ret, leftOverGas, err := vmenv.Call(
Expand Down
10 changes: 6 additions & 4 deletions eth/tracers/js/goja.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ func init() {
// hex strings into big ints.
var bigIntProgram = goja.MustCompile("bigInt", bigIntegerJS, false)

type toBigFn = func(vm *goja.Runtime, val string) (goja.Value, error)
type toBufFn = func(vm *goja.Runtime, val []byte) (goja.Value, error)
type fromBufFn = func(vm *goja.Runtime, buf goja.Value, allowString bool) ([]byte, error)
type (
toBigFn = func(vm *goja.Runtime, val string) (goja.Value, error)
toBufFn = func(vm *goja.Runtime, val []byte) (goja.Value, error)
fromBufFn = func(vm *goja.Runtime, buf goja.Value, allowString bool) ([]byte, error)
)

func toBuf(vm *goja.Runtime, bufType goja.Value, val []byte) (goja.Value, error) {
// bufType is usually Uint8Array. This is equivalent to `new Uint8Array(val)` in JS.
Expand Down Expand Up @@ -238,7 +240,7 @@ func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr
t.ctx["block"] = t.vm.ToValue(env.Context.BlockNumber.Uint64())
// Update list of precompiles based on current block
rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil)
t.activePrecompiles = vm.ActivePrecompiles(rules)
t.activePrecompiles = env.ActivePrecompiles(rules)
t.ctx["intrinsicGas"] = t.vm.ToValue(t.gasLimit - gas)
}

Expand Down
19 changes: 10 additions & 9 deletions eth/tracers/native/4byte.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@ func init() {
// a reversed signature can be matched against the size of the data.
//
// Example:
// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"})
// {
// 0x27dc297e-128: 1,
// 0x38cc4831-0: 2,
// 0x524f3889-96: 1,
// 0xadf59f99-288: 1,
// 0xc281d19e-0: 1
// }
//
// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"})
// {
// 0x27dc297e-128: 1,
// 0x38cc4831-0: 2,
// 0x524f3889-96: 1,
// 0xadf59f99-288: 1,
// 0xc281d19e-0: 1
// }
type fourByteTracer struct {
env *vm.EVM
ids map[string]int // ids aggregates the 4byte ids found
Expand Down Expand Up @@ -84,7 +85,7 @@ func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to commo

// Update list of precompiles based on current block
rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil)
t.activePrecompiles = vm.ActivePrecompiles(rules)
t.activePrecompiles = env.ActivePrecompiles(rules)

// Save the outer calldata also
if len(input) >= 4 {
Expand Down
Loading

0 comments on commit 8d40791

Please sign in to comment.