Skip to content

Commit

Permalink
feat precompiled contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
SherLzp committed Mar 19, 2023
1 parent c97fcc4 commit 474aa5e
Show file tree
Hide file tree
Showing 6 changed files with 28 additions and 268 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ replace (
// use cosmos keyring
github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76
github.com/cosmos/cosmos-sdk => github.com/zecrey-labs/cosmos-sdk v0.46.7-blsv1.3
github.com/ethereum/go-ethereum => github.com/zecrey-labs/go-ethereum v1.10.26-zecreyosv0.2

// Fix upstream GHSA-h395-qcrw-5vmq vulnerability.
// TODO Remove it: https://github.com/cosmos/cosmos-sdk/issues/10409
Expand Down
65 changes: 23 additions & 42 deletions go.sum

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions x/evm/keeper/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package keeper

import (
"github.com/ethereum/go-ethereum/common/math"
"github.com/evmos/ethermint/x/evm/vm/geth"
"math/big"

tmtypes "github.com/tendermint/tendermint/types"
Expand Down Expand Up @@ -388,7 +387,7 @@ func (k *Keeper) ApplyMessageWithConfig(
stateDB.SetNonce(sender.Address(), msg.Nonce()+1)
} else {
// TODO set info here, ugly method
geth.SetTmpConfig(ctx, commit)
vm.SetTmpConfig(ctx, commit)
ret, leftoverGas, vmErr = nEVM.Call(sender, *msg.To(), msg.Data(), leftoverGas, msg.Value())
}

Expand Down
213 changes: 3 additions & 210 deletions x/evm/vm/geth/geth.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,17 @@
package geth

import (
"errors"
sdk "github.com/cosmos/cosmos-sdk/types"
"math/big"
"reflect"
"time"
"unsafe"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"

evm "github.com/evmos/ethermint/x/evm/vm"
)

var (
_ evm.EVM = (*EVM)(nil)
_ evm.Constructor = NewEVM
customPrecompiledContracts map[common.Address]evm.StatefulPrecompiledContract
tmpCtx sdk.Context
tmpCommit bool
_ evm.EVM = (*EVM)(nil)
_ evm.Constructor = NewEVM
)

func SetTmpConfig(ctx sdk.Context, commit bool) {
tmpCtx = ctx
tmpCommit = commit
}

// EVM is the wrapper for the go-ethereum EVM.
type EVM struct {
*vm.EVM
Expand Down Expand Up @@ -85,202 +69,11 @@ func (e EVM) Config() vm.Config {
func (e EVM) Precompile(addr common.Address) (p vm.PrecompiledContract, found bool) {
precompiles := GetPrecompiles(e.ChainConfig(), e.EVM.Context.BlockNumber)
p, found = precompiles[addr]
p, foundOnCustom := customPrecompiledContracts[addr]
return p, found || foundOnCustom
return p, found
}

// ActivePrecompiles returns a list of all the active precompiled contract addresses
// for the current chain configuration.
func (EVM) ActivePrecompiles(rules params.Rules) []common.Address {
return vm.ActivePrecompiles(rules)
}

// RunStatefulPrecompiledContract runs a stateful precompiled contract and ignores the address and
// value arguments. It uses the RunPrecompiledContract function from the geth vm package
func (e *EVM) RunStatefulPrecompiledContract(
ctx sdk.Context,
p evm.StatefulPrecompiledContract,
caller common.Address, // address arg is unused
input []byte,
suppliedGas uint64,
value *big.Int,
) (ret []byte, remainingGas uint64, err error) {
gasCost := p.RequiredGas(input)
if suppliedGas < gasCost {
return nil, 0, vm.ErrOutOfGas
}
suppliedGas -= gasCost
output, err := p.RunStateful(ctx, e, caller, input, value)
return output, suppliedGas, err
}

// PreRunStatefulPrecompiledContract runs a stateful precompiled contract and ignores the address and
// value arguments. It uses the RunPrecompiledContract function from the geth vm package
func (e *EVM) PreRunStatefulPrecompiledContract(
ctx sdk.Context,
p evm.StatefulPrecompiledContract,
caller common.Address, // address arg is unused
input []byte,
suppliedGas uint64,
value *big.Int,
) (ret []byte, remainingGas uint64, err error) {
gasCost := p.RequiredGas(input)
if suppliedGas < gasCost {
return nil, 0, vm.ErrOutOfGas
}
suppliedGas -= gasCost
//output, err := p.RunStateful(e, caller, input, value)
return nil, suppliedGas, err
}

// Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
func (evm *EVM) Call(caller vm.ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
// Fail if we're trying to execute above the call depth limit
oEVM := reflect.ValueOf(evm.EVM).Elem()
depthValue := oEVM.FieldByName("depth")
depth, ok := reflect.NewAt(depthValue.Type(), unsafe.Pointer(depthValue.UnsafeAddr())).Elem().Interface().(int)
if !ok {
return nil, gas, vm.ErrDepth
}
if depth > int(params.CallCreateDepth) {
return nil, gas, vm.ErrDepth
}
chainRulesValue := oEVM.FieldByName("chainRules")
chainRules, ok := reflect.NewAt(chainRulesValue.Type(), unsafe.Pointer(chainRulesValue.UnsafeAddr())).Elem().Interface().(params.Rules)
if !ok {
return nil, gas, errors.New("unable to get chain rules")
}
// Fail if we're trying to transfer more than the available balance
if value.Sign() != 0 && !evm.Context().CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, gas, vm.ErrInsufficientBalance
}
snapshot := evm.StateDB.Snapshot()
p, isPrecompile := evm.Precompile(addr)

if !evm.StateDB.Exist(addr) {
if !isPrecompile && chainRules.IsEIP158 && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer
if evm.Config().Debug {
if depth == 0 {
evm.Config().Tracer.CaptureStart(evm.EVM, caller.Address(), addr, false, input, gas, value)
evm.Config().Tracer.CaptureEnd(ret, 0, 0, nil)
} else {
evm.Config().Tracer.CaptureEnter(vm.CALL, caller.Address(), addr, input, gas, value)
evm.Config().Tracer.CaptureExit(ret, 0, nil)
}
}
return nil, gas, nil
}
evm.StateDB.CreateAccount(addr)
}
evm.Context().Transfer(evm.StateDB, caller.Address(), addr, value)

// Capture the tracer start/end events in debug mode
if evm.Config().Debug {
if depth == 0 {
evm.Config().Tracer.CaptureStart(evm.EVM, caller.Address(), addr, false, input, gas, value)
defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
evm.Config().Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
}(gas, time.Now())
} else {
// Handle tracer events for entering and exiting a call frame
evm.Config().Tracer.CaptureEnter(vm.CALL, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) {
evm.Config().Tracer.CaptureExit(ret, startGas-gas, err)
}(gas)
}
}

if isPrecompile {
if customPrecompiledContracts[addr] != nil {
if !tmpCommit {
ret, gas, err = evm.PreRunStatefulPrecompiledContract(tmpCtx, customPrecompiledContracts[addr], caller.Address(), input, gas, value)
} else {
ret, gas, err = evm.RunStatefulPrecompiledContract(tmpCtx, customPrecompiledContracts[addr], caller.Address(), input, gas, value)
}
} else {
ret, gas, err = vm.RunPrecompiledContract(p, input, gas)
}
} else {
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
code := evm.StateDB.GetCode(addr)
if len(code) == 0 {
ret, err = nil, nil // gas is unchanged
} else {
addrCopy := addr
// If the account has no code, we can abort here
// The depth-check is already done, and precompiles handled above
contract := vm.NewContract(caller, vm.AccountRef(addrCopy), value, gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
ret, err = evm.Interpreter().Run(contract, input, false)
gas = contract.Gas
}
}
// When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally
// when we're in homestead this also counts for code storage gas errors.
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
if err != vm.ErrExecutionReverted {
gas = 0
}
// TODO: consider clearing up unused snapshots:
//} else {
// evm.StateDB.DiscardSnapshot(snapshot)
}
return ret, gas, err
}

// DelegateCall executes the contract associated with the addr with the given input
// as parameters. It reverses the state in case of an execution error.
//
// DelegateCall differs from CallCode in the sense that it executes the given address'
// code with the caller as context and the caller is set to the caller of the caller.
func (evm *EVM) DelegateCall(caller vm.ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
// Fail if we're trying to execute above the call depth limit
oEVM := reflect.ValueOf(evm.EVM).Elem()
depthValue := oEVM.FieldByName("depth")
depth, ok := reflect.NewAt(depthValue.Type(), unsafe.Pointer(depthValue.UnsafeAddr())).Elem().Interface().(int)
if !ok {
return nil, gas, vm.ErrDepth
}
if depth > int(params.CallCreateDepth) {
return nil, gas, vm.ErrDepth
}
var snapshot = evm.StateDB.Snapshot()

// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config().Debug {
evm.Config().Tracer.CaptureEnter(vm.DELEGATECALL, caller.Address(), addr, input, gas, nil)
defer func(startGas uint64) {
evm.Config().Tracer.CaptureExit(ret, startGas-gas, err)
}(gas)
}

// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.Precompile(addr); isPrecompile {
if customPrecompiledContracts[addr] != nil {
ret, gas, err = evm.RunStatefulPrecompiledContract(tmpCtx, customPrecompiledContracts[addr], caller.Address(), input, gas, big.NewInt(0))
} else {
ret, gas, err = vm.RunPrecompiledContract(p, input, gas)
}
} else {
addrCopy := addr
// Initialise a new contract and make initialise the delegate values
contract := vm.NewContract(caller, vm.AccountRef(caller.Address()), nil, gas).AsDelegate()
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
ret, err = evm.Interpreter().Run(contract, input, false)
gas = contract.Gas
}
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
if err != vm.ErrExecutionReverted {
gas = 0
}
}
return ret, gas, err
}
5 changes: 0 additions & 5 deletions x/evm/vm/geth/precompiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package geth

import (
"github.com/ethereum/go-ethereum/common"
"math/big"

"github.com/ethereum/go-ethereum/core/vm"
Expand All @@ -41,7 +40,3 @@ func GetPrecompiles(cfg *params.ChainConfig, blockNumber *big.Int) evm.Precompil
}
return precompiles
}

func SetCustomPrecompiledContracts(customContracts map[common.Address]evm.StatefulPrecompiledContract) {
customPrecompiledContracts = customContracts
}
9 changes: 0 additions & 9 deletions x/evm/vm/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,6 @@ type EVM interface {

ActivePrecompiles(rules params.Rules) []common.Address
Precompile(addr common.Address) (vm.PrecompiledContract, bool)
RunStatefulPrecompiledContract(
ctx sdk.Context,
p StatefulPrecompiledContract,
addr common.Address,
input []byte,
suppliedGas uint64,
value *big.Int) (
ret []byte, remainingGas uint64, err error,
)
}

// Constructor defines the function used to instantiate the EVM on
Expand Down

0 comments on commit 474aa5e

Please sign in to comment.