Adding custom precompiled support

These changes were migrated from EVMOS v1.10.26-evmos-rc2 tag. Relevant
precompile EVMOS commits that were part of this tag:

- 8d407912cad95d41db1e472f35a1eba6dc7dc363
- fcf5e42ce33b315dc294d200ad0c3da96fbc441f
- f24eefdf82c19088c36fee898d66370a8489c9e7
- 359caee7e31063a6fa8a01832cabe0a35d383fff
- d7a659397e07fca3a3516851aa8feefc0d632f1d

The above changes were added on top of the latest available go-ethereum
tag which is v1.14.8 Now we have the latest go-ethereum code with all
the latest fixes along with the EVMOS like custom precompile support.
This commit is contained in:
Dmitry 2024-08-22 11:00:42 +02:00
parent a9523b6428
commit 3d0e225ba1
No known key found for this signature in database
GPG key ID: 3F312C99346343E5
16 changed files with 490 additions and 218 deletions

View file

@ -431,7 +431,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// Execute the preparatory steps for state transition which includes:
// - prepare accessList(post-berlin)
// - reset transient storage(eip 1153)
st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, st.evm.ActivePrecompiles(rules), msg.AccessList)
var (
ret []byte

View file

@ -60,8 +60,9 @@ type Contract struct {
// is the execution frame represented by this object a contract deployment
IsDeployment bool
Gas uint64
value *uint256.Int
Gas uint64
value *uint256.Int
isPrecompile bool
}
// NewContract returns a new contract environment for the execution of EVM.
@ -84,7 +85,34 @@ func NewContract(caller ContractRef, object ContractRef, value *uint256.Int, gas
return c
}
// NewPrecompile returns a new instance of a precompiled contract environment for the execution of EVM.
func NewPrecompile(caller, object ContractRef, value *uint256.Int, gas uint64) *Contract {
c := &Contract{
CallerAddress: caller.Address(),
caller: caller,
self: object,
isPrecompile: true,
}
// Gas should be a pointer so it can safely be reduced through the run
// This pointer will be off the state transition
c.Gas = gas
// ensures a value is set
c.value = value
return c
}
// IsPrecompile returns true if the contract is a precompiled contract environment
func (c Contract) IsPrecompile() bool {
return c.isPrecompile
}
func (c *Contract) validJumpdest(dest *uint256.Int) bool {
if c.isPrecompile {
return false
}
udest, overflow := dest.Uint64WithOverflow()
// PC cannot go beyond len(code) and certainly can't be bigger than 63bits.
// Don't bother checking for JUMPDEST in that case.
@ -101,6 +129,10 @@ func (c *Contract) validJumpdest(dest *uint256.Int) bool {
// isCode returns true if the provided PC location is an actual opcode, as
// opposed to a data-segment following a PUSHN operation.
func (c *Contract) isCode(udest uint64) bool {
if c.isPrecompile {
return false
}
// Do we already have an analysis laying around?
if c.analysis != nil {
return c.analysis.codeSegment(udest)
@ -134,6 +166,9 @@ func (c *Contract) isCode(udest uint64) bool {
// AsDelegate sets the contract to be a delegate call and returns the current
// contract (for chaining calls)
func (c *Contract) AsDelegate() *Contract {
if c.isPrecompile {
return c
}
// NOTE: caller must, at all times be a contract. It should never happen
// that caller is something other than a Contract.
parent := c.caller.(*Contract)
@ -196,6 +231,9 @@ func (c *Contract) Value() *uint256.Int {
// SetCallCode sets the code of the contract and address of the backing data
// object
func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) {
if c.isPrecompile {
return
}
c.Code = code
c.CodeHash = hash
c.CodeAddr = addr
@ -204,6 +242,9 @@ func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []by
// SetCodeOptionalHash can be used to provide code, but it's optional to provide hash.
// In case hash is not provided, the jumpdest analysis will not be saved to the parent context
func (c *Contract) SetCodeOptionalHash(addr *common.Address, codeAndHash *codeAndHash) {
if c.isPrecompile {
return
}
c.Code = codeAndHash.code
c.CodeHash = codeAndHash.hash
c.CodeAddr = addr

File diff suppressed because it is too large Load diff

View file

@ -20,6 +20,7 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256"
)
func FuzzPrecompiledContracts(f *testing.F) {
@ -36,7 +37,7 @@ func FuzzPrecompiledContracts(f *testing.F) {
return
}
inWant := string(input)
RunPrecompiledContract(p, input, gas, nil)
runPrecompiledContract(nil, p, AccountRef(common.Address{}), input, gas, new(uint256.Int), false)
if inHave := string(input); inWant != inHave {
t.Errorf("Precompiled %v modified input data", a)
}

View file

@ -23,6 +23,7 @@ import (
"os"
"testing"
"time"
"github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/common"
)
@ -98,7 +99,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
if res, _, err := RunPrecompiledContract(p, in, gas, nil); err != nil {
if res, _, err := runPrecompiledContract(nil, p, AccountRef(common.Address{}), in, gas, new(uint256.Int), false); err != nil {
t.Error(err)
} else if common.Bytes2Hex(res) != test.Expected {
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
@ -120,7 +121,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
gas := p.RequiredGas(in) - 1
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
_, _, err := RunPrecompiledContract(p, in, gas, nil)
_, _, err := runPrecompiledContract(nil, p, AccountRef(common.Address{}), in, gas, new(uint256.Int), false)
if err.Error() != "out of gas" {
t.Errorf("Expected error [out of gas], got [%v]", err)
}
@ -137,7 +138,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing
in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in)
t.Run(test.Name, func(t *testing.T) {
_, _, err := RunPrecompiledContract(p, in, gas, nil)
_, _, err := runPrecompiledContract(nil, p, AccountRef(common.Address{}), in, gas, new(uint256.Int), false)
if err.Error() != test.ExpectedError {
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
}
@ -169,7 +170,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
copy(data, in)
res, _, err = RunPrecompiledContract(p, data, reqGas, nil)
res, _, err = runPrecompiledContract(nil, p, AccountRef(common.Address{}), in, reqGas, new(uint256.Int), false)
}
bench.StopTimer()
elapsed := uint64(time.Since(start))

View file

@ -40,28 +40,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.IsVerkle:
precompiles = PrecompiledContractsVerkle
case evm.chainRules.IsPrague:
precompiles = PrecompiledContractsPrague
case evm.chainRules.IsCancun:
precompiles = PrecompiledContractsCancun
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 {
@ -129,6 +107,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
@ -153,6 +135,9 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
}
// set the default precompiles
evm.activePrecompiles = DefaultActivePrecompiles(evm.chainRules)
evm.precompiles = DefaultPrecompiles(evm.chainRules)
evm.interpreter = NewEVMInterpreter(evm)
return evm
}
@ -204,7 +189,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.IsEIP4762 {
@ -225,8 +210,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
}
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
// It is allowed to call precompiles, even via call -- as opposed to callcode, staticcall and delegatecall it can also modify state
if isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
ret, gas, err = evm.RunPrecompiledContract(p, caller, input, gas, value, false)
} 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.
@ -293,9 +279,9 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
}
var snapshot = evm.StateDB.Snapshot()
// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
// It is allowed to call precompiles, even via callcode, but only for reading
if p, isPrecompile := evm.Precompile(addr); isPrecompile {
ret, gas, err = evm.RunPrecompiledContract(p, caller, input, gas, value, true)
} else {
addrCopy := addr
// Initialise a new contract and set the code that is to be used by the EVM.
@ -345,8 +331,8 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
var snapshot = evm.StateDB.Snapshot()
// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
if p, isPrecompile := evm.Precompile(addr); isPrecompile {
ret, gas, err = evm.RunPrecompiledContract(p, caller, input, gas, nil, true)
} else {
addrCopy := addr
// Initialise a new contract and make initialise the delegate values
@ -399,8 +385,8 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
// future scenarios
evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount)
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
if p, isPrecompile := evm.Precompile(addr); isPrecompile {
ret, gas, err = evm.RunPrecompiledContract(p, caller, input, gas, new(uint256.Int), true)
} else {
// At this point, we use a copy of address. If we don't, the go compiler will
// leak the 'contract' to the outer scope, and make allocation for 'contract'

View file

@ -49,7 +49,7 @@ func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
address := stack.peek().Bytes20()
if _, isPrecompile := evm.precompile(address); isPrecompile {
if _, isPrecompile := evm.Precompile(address); isPrecompile {
return 0, nil
}
gas := evm.AccessEvents.VersionGas(address, false)
@ -62,7 +62,7 @@ func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
address := stack.peek().Bytes20()
if _, isPrecompile := evm.precompile(address); isPrecompile {
if _, isPrecompile := evm.Precompile(address); isPrecompile {
return 0, nil
}
gas := evm.AccessEvents.CodeHashGas(address, false)
@ -78,7 +78,7 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc {
if err != nil {
return 0, err
}
if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile {
if _, isPrecompile := evm.Precompile(contract.Address()); isPrecompile {
return gas, nil
}
witnessGas := evm.AccessEvents.MessageCallGas(contract.Address())
@ -98,7 +98,7 @@ var (
func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
beneficiaryAddr := common.Address(stack.peek().Bytes20())
if _, isPrecompile := evm.precompile(beneficiaryAddr); isPrecompile {
if _, isPrecompile := evm.Precompile(beneficiaryAddr); isPrecompile {
return 0, nil
}
contractAddr := contract.Address()

View file

@ -142,7 +142,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
// Execute the preparatory steps for state transition which includes:
// - prepare accessList(post-berlin)
// - reset transient storage(eip 1153)
cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil)
cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.DefaultActivePrecompiles(rules), nil)
cfg.State.CreateAccount(address)
// set the receiver's (the executing contract) code for execution.
cfg.State.SetCode(address, code)
@ -178,7 +178,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
// Execute the preparatory steps for state transition which includes:
// - prepare accessList(post-berlin)
// - reset transient storage(eip 1153)
cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil)
cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.DefaultActivePrecompiles(rules), nil)
// Call the code with the given configuration.
code, address, leftOverGas, err := vmenv.Create(
sender,
@ -209,7 +209,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
// Execute the preparatory steps for state transition which includes:
// - prepare accessList(post-berlin)
// - reset transient storage(eip 1153)
statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil)
statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.DefaultActivePrecompiles(rules), nil)
// Call the code with the given configuration.
ret, leftOverGas, err := vmenv.Call(

View file

@ -245,7 +245,7 @@ func (t *jsTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from
t.dbValue = db.setupObject()
// Update list of precompiles based on current block
rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time)
t.activePrecompiles = vm.ActivePrecompiles(rules)
t.activePrecompiles = vm.DefaultActivePrecompiles(rules)
t.ctx["block"] = t.vm.ToValue(t.env.BlockNumber.Uint64())
t.ctx["gas"] = t.vm.ToValue(tx.Gas())
gasPriceBig, err := t.toBig(t.vm, env.GasPrice.String())

View file

@ -89,7 +89,7 @@ func (t *fourByteTracer) store(id []byte, size int) {
func (t *fourByteTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
// Update list of precompiles based on current block
rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time)
t.activePrecompiles = vm.ActivePrecompiles(rules)
t.activePrecompiles = vm.DefaultActivePrecompiles(rules)
}
// OnEnter is called when EVM enters a new scope (via call, create or selfdestruct).

View file

@ -207,7 +207,7 @@ func (t *flatCallTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction
t.tracer.OnTxStart(env, tx, from)
// Update list of precompiles based on current block
rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time)
t.activePrecompiles = vm.ActivePrecompiles(rules)
t.activePrecompiles = vm.DefaultActivePrecompiles(rules)
}
func (t *flatCallTracer) OnTxEnd(receipt *types.Receipt, err error) {

1
go.mod
View file

@ -44,7 +44,6 @@ require (
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c
github.com/jackpal/go-nat-pmp v1.0.2
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267
github.com/julienschmidt/httprouter v1.3.0
github.com/karalabe/hid v1.0.1-0.20240306101548-573246063e52
github.com/kilic/bls12-381 v0.1.0
github.com/kylelemons/godebug v1.1.0

1
go.sum
View file

@ -339,7 +339,6 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/karalabe/hid v1.0.1-0.20240306101548-573246063e52 h1:msKODTL1m0wigztaqILOtla9HeW1ciscYG4xjLtvk5I=
github.com/karalabe/hid v1.0.1-0.20240306101548-573246063e52/go.mod h1:qk1sX/IBgppQNcGCRoj90u6EGC056EBoIc1oEjCWla8=

View file

@ -1521,7 +1521,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
}
isPostMerge := header.Difficulty.Sign() == 0
// Retrieve the precompiles since they don't need to be added to the access list
precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time))
precompiles := vm.DefaultActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time))
// Create an initial tracer
prevTracer := logger.NewAccessListTracer(nil, args.from(), to, precompiles)

View file

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/holiman/uint256"
)
const (
@ -82,7 +83,9 @@ func fuzz(id byte, data []byte) int {
}
cpy := make([]byte, len(data))
copy(cpy, data)
_, err := precompile.Run(cpy)
contract := vm.NewPrecompile(vm.AccountRef(common.Address{}), precompile, new(uint256.Int).SetUint64(0), gas)
contract.Input = cpy
_, err := precompile.Run(nil, contract, false)
if !bytes.Equal(cpy, data) {
panic(fmt.Sprintf("input data modified, precompile %d: %x %x", id, data, cpy))
}

View file

@ -319,7 +319,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
snapshot := state.StateDB.Snapshot()
state.StateDB.Prepare(rules, msg.From, context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
state.StateDB.Prepare(rules, msg.From, context.Coinbase, msg.To, vm.DefaultActivePrecompiles(rules), msg.AccessList)
b.StartTimer()
start := time.Now()