core/vm: less allocations for various call variants (#21222)
* core/vm/runtime/tests: add more benchmarks * core/vm: initial work on improving alloc count for calls to precompiles name old time/op new time/op delta SimpleLoop/identity-precompile-10M-6 117ms ±75% 43ms ± 1% -63.09% (p=0.008 n=5+5) SimpleLoop/loop-10M-6 79.6ms ± 4% 70.5ms ± 1% -11.42% (p=0.008 n=5+5) name old alloc/op new alloc/op delta SimpleLoop/identity-precompile-10M-6 24.4MB ± 0% 4.9MB ± 0% -79.94% (p=0.008 n=5+5) SimpleLoop/loop-10M-6 13.2kB ± 0% 13.2kB ± 0% ~ (p=0.357 n=5+5) name old allocs/op new allocs/op delta SimpleLoop/identity-precompile-10M-6 382k ± 0% 153k ± 0% -59.99% (p=0.000 n=5+4) SimpleLoop/loop-10M-6 40.0 ± 0% 40.0 ± 0% ~ (all equal) * core/vm: don't allocate big.int for touch name old time/op new time/op delta SimpleLoop/identity-precompile-10M-6 43.3ms ± 1% 42.4ms ± 7% ~ (p=0.151 n=5+5) SimpleLoop/loop-10M-6 70.5ms ± 1% 76.7ms ± 1% +8.67% (p=0.008 n=5+5) name old alloc/op new alloc/op delta SimpleLoop/identity-precompile-10M-6 4.90MB ± 0% 2.46MB ± 0% -49.83% (p=0.008 n=5+5) SimpleLoop/loop-10M-6 13.2kB ± 0% 13.2kB ± 1% ~ (p=0.571 n=5+5) name old allocs/op new allocs/op delta SimpleLoop/identity-precompile-10M-6 153k ± 0% 76k ± 0% -49.98% (p=0.029 n=4+4) SimpleLoop/loop-10M-6 40.0 ± 0% 40.0 ± 0% ~ (all equal) * core/vm: reduce allocs in staticcall name old time/op new time/op delta SimpleLoop/identity-precompile-10M-6 42.4ms ± 7% 37.5ms ± 6% -11.68% (p=0.008 n=5+5) SimpleLoop/loop-10M-6 76.7ms ± 1% 69.1ms ± 1% -9.82% (p=0.008 n=5+5) name old alloc/op new alloc/op delta SimpleLoop/identity-precompile-10M-6 2.46MB ± 0% 0.02MB ± 0% -99.35% (p=0.008 n=5+5) SimpleLoop/loop-10M-6 13.2kB ± 1% 13.2kB ± 0% ~ (p=0.143 n=5+5) name old allocs/op new allocs/op delta SimpleLoop/identity-precompile-10M-6 76.4k ± 0% 0.1k ± 0% ~ (p=0.079 n=4+5) SimpleLoop/loop-10M-6 40.0 ± 0% 40.0 ± 0% ~ (all equal) * trie: better use of hasher keccakState * core/state/statedb: reduce allocations in getDeletedStateObject * core/vm: reduce allocations in all call derivates * core/vm: reduce allocations in call variants - Make returnstack `uint32` - Use a `sync.Pool` of `stack`s * core/vm: fix tests * core/vm: goimports * core/vm: tracer fix + staticcall gas fix * core/vm: add back snapshot to staticcall * core/vm: review concerns + make returnstack pooled + enable returndata in traces * core/vm: fix some test tracer method signatures * core/vm: run gencodec, minor comment polish Co-authored-by: Péter Szilágyi <peterke@gmail.com>
This commit is contained in:
parent
240d1851db
commit
295693759e
@ -38,6 +38,10 @@ var (
|
||||
Name: "trace.nostack",
|
||||
Usage: "Disable stack output in traces",
|
||||
}
|
||||
TraceDisableReturnDataFlag = cli.BoolFlag{
|
||||
Name: "trace.noreturndata",
|
||||
Usage: "Disable return data output in traces",
|
||||
}
|
||||
OutputAllocFlag = cli.StringFlag{
|
||||
Name: "output.alloc",
|
||||
Usage: "Determines where to put the `alloc` of the post-state.\n" +
|
||||
|
@ -85,6 +85,7 @@ func Main(ctx *cli.Context) error {
|
||||
logConfig := &vm.LogConfig{
|
||||
DisableStack: ctx.Bool(TraceDisableStackFlag.Name),
|
||||
DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name),
|
||||
DisableReturnData: ctx.Bool(TraceDisableReturnDataFlag.Name),
|
||||
Debug: true,
|
||||
}
|
||||
var prevFile *os.File
|
||||
|
@ -121,6 +121,14 @@ var (
|
||||
Name: "nostack",
|
||||
Usage: "disable stack output",
|
||||
}
|
||||
DisableStorageFlag = cli.BoolFlag{
|
||||
Name: "nostorage",
|
||||
Usage: "disable storage output",
|
||||
}
|
||||
DisableReturnDataFlag = cli.BoolFlag{
|
||||
Name: "noreturndata",
|
||||
Usage: "disable return data output",
|
||||
}
|
||||
EVMInterpreterFlag = cli.StringFlag{
|
||||
Name: "vm.evm",
|
||||
Usage: "External EVM configuration (default = built-in interpreter)",
|
||||
@ -137,6 +145,7 @@ var stateTransitionCommand = cli.Command{
|
||||
t8ntool.TraceFlag,
|
||||
t8ntool.TraceDisableMemoryFlag,
|
||||
t8ntool.TraceDisableStackFlag,
|
||||
t8ntool.TraceDisableReturnDataFlag,
|
||||
t8ntool.OutputAllocFlag,
|
||||
t8ntool.OutputResultFlag,
|
||||
t8ntool.InputAllocFlag,
|
||||
@ -172,6 +181,8 @@ func init() {
|
||||
ReceiverFlag,
|
||||
DisableMemoryFlag,
|
||||
DisableStackFlag,
|
||||
DisableStorageFlag,
|
||||
DisableReturnDataFlag,
|
||||
EVMInterpreterFlag,
|
||||
}
|
||||
app.Commands = []cli.Command{
|
||||
|
@ -110,6 +110,8 @@ func runCmd(ctx *cli.Context) error {
|
||||
logconfig := &vm.LogConfig{
|
||||
DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name),
|
||||
DisableStack: ctx.GlobalBool(DisableStackFlag.Name),
|
||||
DisableStorage: ctx.GlobalBool(DisableStorageFlag.Name),
|
||||
DisableReturnData: ctx.GlobalBool(DisableReturnDataFlag.Name),
|
||||
Debug: ctx.GlobalBool(DebugFlag.Name),
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,8 @@ func stateTestCmd(ctx *cli.Context) error {
|
||||
config := &vm.LogConfig{
|
||||
DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name),
|
||||
DisableStack: ctx.GlobalBool(DisableStackFlag.Name),
|
||||
DisableStorage: ctx.GlobalBool(DisableStorageFlag.Name),
|
||||
DisableReturnData: ctx.GlobalBool(DisableReturnDataFlag.Name),
|
||||
}
|
||||
var (
|
||||
tracer vm.Tracer
|
||||
|
@ -505,7 +505,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
|
||||
}
|
||||
// If no live objects are available, attempt to use snapshots
|
||||
var (
|
||||
data Account
|
||||
data *Account
|
||||
err error
|
||||
)
|
||||
if s.snap != nil {
|
||||
@ -517,11 +517,15 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
|
||||
if acc == nil {
|
||||
return nil
|
||||
}
|
||||
data.Nonce, data.Balance, data.CodeHash = acc.Nonce, acc.Balance, acc.CodeHash
|
||||
data = &Account{
|
||||
Nonce: acc.Nonce,
|
||||
Balance: acc.Balance,
|
||||
CodeHash: acc.CodeHash,
|
||||
Root: common.BytesToHash(acc.Root),
|
||||
}
|
||||
if len(data.CodeHash) == 0 {
|
||||
data.CodeHash = emptyCodeHash
|
||||
}
|
||||
data.Root = common.BytesToHash(acc.Root)
|
||||
if data.Root == (common.Hash{}) {
|
||||
data.Root = emptyRoot
|
||||
}
|
||||
@ -540,13 +544,14 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
|
||||
if len(enc) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := rlp.DecodeBytes(enc, &data); err != nil {
|
||||
data = new(Account)
|
||||
if err := rlp.DecodeBytes(enc, data); err != nil {
|
||||
log.Error("Failed to decode state object", "addr", addr, "err", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Insert into the live set
|
||||
obj := newObject(s, addr, data)
|
||||
obj := newObject(s, addr, *data)
|
||||
s.setStateObject(obj)
|
||||
return obj
|
||||
}
|
||||
|
@ -102,12 +102,18 @@ var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{
|
||||
}
|
||||
|
||||
// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
|
||||
func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) {
|
||||
gas := p.RequiredGas(input)
|
||||
if contract.UseGas(gas) {
|
||||
return p.Run(input)
|
||||
// It returns
|
||||
// - the returned bytes,
|
||||
// - the _remaining_ gas,
|
||||
// - any error that occurred
|
||||
func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
|
||||
gasCost := p.RequiredGas(input)
|
||||
if suppliedGas < gasCost {
|
||||
return nil, 0, ErrOutOfGas
|
||||
}
|
||||
return nil, ErrOutOfGas
|
||||
suppliedGas -= gasCost
|
||||
output, err := p.Run(input)
|
||||
return output, suppliedGas, err
|
||||
}
|
||||
|
||||
// ECRECOVER implemented as a native contract.
|
||||
@ -197,6 +203,7 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) {
|
||||
type bigModExp struct{}
|
||||
|
||||
var (
|
||||
big0 = big.NewInt(0)
|
||||
big1 = big.NewInt(1)
|
||||
big4 = big.NewInt(4)
|
||||
big8 = big.NewInt(8)
|
||||
|
@ -21,7 +21,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -72,10 +71,9 @@ var blake2FMalformedInputTests = []precompiledFailureTest{
|
||||
func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
|
||||
p := allPrecompiles[common.HexToAddress(addr)]
|
||||
in := common.Hex2Bytes(test.Input)
|
||||
contract := NewContract(AccountRef(common.HexToAddress("1337")),
|
||||
nil, new(big.Int), p.RequiredGas(in))
|
||||
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(t *testing.T) {
|
||||
if res, err := RunPrecompiledContract(p, in, contract); err != nil {
|
||||
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); err != nil {
|
||||
t.Error(err)
|
||||
} else if common.Bytes2Hex(res) != test.Expected {
|
||||
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
|
||||
@ -91,10 +89,10 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
|
||||
func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
|
||||
p := allPrecompiles[common.HexToAddress(addr)]
|
||||
in := common.Hex2Bytes(test.Input)
|
||||
contract := NewContract(AccountRef(common.HexToAddress("1337")),
|
||||
nil, new(big.Int), p.RequiredGas(in)-1)
|
||||
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(t *testing.T) {
|
||||
_, err := RunPrecompiledContract(p, in, contract)
|
||||
gas := p.RequiredGas(in) - 1
|
||||
|
||||
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
|
||||
_, _, err := RunPrecompiledContract(p, in, gas)
|
||||
if err.Error() != "out of gas" {
|
||||
t.Errorf("Expected error [out of gas], got [%v]", err)
|
||||
}
|
||||
@ -109,11 +107,9 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
|
||||
func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing.T) {
|
||||
p := allPrecompiles[common.HexToAddress(addr)]
|
||||
in := common.Hex2Bytes(test.Input)
|
||||
contract := NewContract(AccountRef(common.HexToAddress("31337")),
|
||||
nil, new(big.Int), p.RequiredGas(in))
|
||||
|
||||
gas := p.RequiredGas(in)
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
_, err := RunPrecompiledContract(p, in, contract)
|
||||
_, _, err := RunPrecompiledContract(p, in, gas)
|
||||
if err.Error() != test.ExpectedError {
|
||||
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
|
||||
}
|
||||
@ -132,8 +128,6 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
|
||||
p := allPrecompiles[common.HexToAddress(addr)]
|
||||
in := common.Hex2Bytes(test.Input)
|
||||
reqGas := p.RequiredGas(in)
|
||||
contract := NewContract(AccountRef(common.HexToAddress("1337")),
|
||||
nil, new(big.Int), reqGas)
|
||||
|
||||
var (
|
||||
res []byte
|
||||
@ -141,14 +135,13 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
|
||||
data = make([]byte, len(in))
|
||||
)
|
||||
|
||||
bench.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(bench *testing.B) {
|
||||
bench.Run(fmt.Sprintf("%s-Gas=%d", test.Name, reqGas), func(bench *testing.B) {
|
||||
bench.ReportAllocs()
|
||||
start := time.Now().Nanosecond()
|
||||
bench.ResetTimer()
|
||||
for i := 0; i < bench.N; i++ {
|
||||
contract.Gas = reqGas
|
||||
copy(data, in)
|
||||
res, err = RunPrecompiledContract(p, data, contract)
|
||||
res, _, err = RunPrecompiledContract(p, data, reqGas)
|
||||
}
|
||||
bench.StopTimer()
|
||||
elapsed := float64(time.Now().Nanosecond() - start)
|
||||
|
173
core/vm/evm.go
173
core/vm/evm.go
@ -25,6 +25,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// emptyCodeHash is used by create to ensure deployment is disallowed to already
|
||||
@ -41,23 +42,24 @@ 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.IsYoloV1:
|
||||
precompiles = PrecompiledContractsYoloV1
|
||||
case evm.chainRules.IsIstanbul:
|
||||
precompiles = PrecompiledContractsIstanbul
|
||||
case evm.chainRules.IsByzantium:
|
||||
precompiles = PrecompiledContractsByzantium
|
||||
default:
|
||||
precompiles = PrecompiledContractsHomestead
|
||||
}
|
||||
p, ok := precompiles[addr]
|
||||
return p, ok
|
||||
}
|
||||
|
||||
// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
|
||||
func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {
|
||||
if contract.CodeAddr != nil {
|
||||
precompiles := PrecompiledContractsHomestead
|
||||
if evm.chainRules.IsByzantium {
|
||||
precompiles = PrecompiledContractsByzantium
|
||||
}
|
||||
if evm.chainRules.IsIstanbul {
|
||||
precompiles = PrecompiledContractsIstanbul
|
||||
}
|
||||
if evm.chainRules.IsYoloV1 {
|
||||
precompiles = PrecompiledContractsYoloV1
|
||||
}
|
||||
if p := precompiles[*contract.CodeAddr]; p != nil {
|
||||
return RunPrecompiledContract(p, input, contract)
|
||||
}
|
||||
}
|
||||
for _, interpreter := range evm.interpreters {
|
||||
if interpreter.CanRun(contract.Code) {
|
||||
if evm.interpreter != interpreter {
|
||||
@ -199,22 +201,14 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||
return nil, gas, ErrDepth
|
||||
}
|
||||
// Fail if we're trying to transfer more than the available balance
|
||||
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||
if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||
return nil, gas, ErrInsufficientBalance
|
||||
}
|
||||
var (
|
||||
to = AccountRef(addr)
|
||||
snapshot = evm.StateDB.Snapshot()
|
||||
)
|
||||
snapshot := evm.StateDB.Snapshot()
|
||||
p, isPrecompile := evm.precompile(addr)
|
||||
|
||||
if !evm.StateDB.Exist(addr) {
|
||||
precompiles := PrecompiledContractsHomestead
|
||||
if evm.chainRules.IsByzantium {
|
||||
precompiles = PrecompiledContractsByzantium
|
||||
}
|
||||
if evm.chainRules.IsIstanbul {
|
||||
precompiles = PrecompiledContractsIstanbul
|
||||
}
|
||||
if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 {
|
||||
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
|
||||
// Calling a non existing account, don't do anything, but ping the tracer
|
||||
if evm.vmConfig.Debug && evm.depth == 0 {
|
||||
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
|
||||
@ -224,35 +218,47 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||
}
|
||||
evm.StateDB.CreateAccount(addr)
|
||||
}
|
||||
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
|
||||
// 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.
|
||||
contract := NewContract(caller, to, value, gas)
|
||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||
|
||||
// Even if the account has no code, we need to continue because it might be a precompile
|
||||
start := time.Now()
|
||||
evm.Transfer(evm.StateDB, caller.Address(), addr, value)
|
||||
|
||||
// Capture the tracer start/end events in debug mode
|
||||
if evm.vmConfig.Debug && evm.depth == 0 {
|
||||
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
|
||||
|
||||
defer func() { // Lazy evaluation of the parameters
|
||||
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
|
||||
}()
|
||||
defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
|
||||
evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
|
||||
}(gas, time.Now())
|
||||
}
|
||||
ret, err = run(evm, contract, input, false)
|
||||
|
||||
if isPrecompile {
|
||||
ret, gas, err = 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 := NewContract(caller, AccountRef(addrCopy), value, gas)
|
||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
|
||||
ret, err = run(evm, 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 != ErrExecutionReverted {
|
||||
contract.UseGas(contract.Gas)
|
||||
gas = 0
|
||||
}
|
||||
// TODO: consider clearing up unused snapshots:
|
||||
//} else {
|
||||
// evm.StateDB.DiscardSnapshot(snapshot)
|
||||
}
|
||||
return ret, contract.Gas, err
|
||||
return ret, gas, err
|
||||
}
|
||||
|
||||
// CallCode executes the contract associated with the addr with the given input
|
||||
@ -277,23 +283,27 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||
return nil, gas, ErrInsufficientBalance
|
||||
}
|
||||
var (
|
||||
snapshot = evm.StateDB.Snapshot()
|
||||
to = AccountRef(caller.Address())
|
||||
)
|
||||
var snapshot = evm.StateDB.Snapshot()
|
||||
|
||||
// It is allowed to call precompiles, even via delegatecall
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
} else {
|
||||
addrCopy := addr
|
||||
// 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.
|
||||
contract := NewContract(caller, to, value, gas)
|
||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||
|
||||
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
|
||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
ret, err = run(evm, contract, input, false)
|
||||
gas = contract.Gas
|
||||
}
|
||||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
contract.UseGas(contract.Gas)
|
||||
gas = 0
|
||||
}
|
||||
}
|
||||
return ret, contract.Gas, err
|
||||
return ret, gas, err
|
||||
}
|
||||
|
||||
// DelegateCall executes the contract associated with the addr with the given input
|
||||
@ -309,22 +319,26 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
}
|
||||
var (
|
||||
snapshot = evm.StateDB.Snapshot()
|
||||
to = AccountRef(caller.Address())
|
||||
)
|
||||
// Initialise a new contract and make initialise the delegate values
|
||||
contract := NewContract(caller, to, nil, gas).AsDelegate()
|
||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||
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)
|
||||
} else {
|
||||
addrCopy := addr
|
||||
// Initialise a new contract and make initialise the delegate values
|
||||
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
ret, err = run(evm, contract, input, false)
|
||||
gas = contract.Gas
|
||||
}
|
||||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
contract.UseGas(contract.Gas)
|
||||
gas = 0
|
||||
}
|
||||
}
|
||||
return ret, contract.Gas, err
|
||||
return ret, gas, err
|
||||
}
|
||||
|
||||
// StaticCall executes the contract associated with the addr with the given input
|
||||
@ -339,32 +353,43 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
}
|
||||
var (
|
||||
to = AccountRef(addr)
|
||||
snapshot = evm.StateDB.Snapshot()
|
||||
)
|
||||
// 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.
|
||||
contract := NewContract(caller, to, new(big.Int), gas)
|
||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||
// We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped.
|
||||
// However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced
|
||||
// after all empty accounts were deleted, so this is not required. However, if we omit this,
|
||||
// then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json.
|
||||
// We could change this, but for now it's left for legacy reasons
|
||||
var snapshot = evm.StateDB.Snapshot()
|
||||
|
||||
// We do an AddBalance of zero here, just in order to trigger a touch.
|
||||
// This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium,
|
||||
// but is the correct thing to do and matters on other networks, in tests, and potential
|
||||
// future scenarios
|
||||
evm.StateDB.AddBalance(addr, big.NewInt(0))
|
||||
evm.StateDB.AddBalance(addr, big0)
|
||||
|
||||
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
|
||||
// leak the 'contract' to the outer scope, and make allocation for 'contract'
|
||||
// even if the actual execution ends on RunPrecompiled above.
|
||||
addrCopy := addr
|
||||
// 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.
|
||||
contract := NewContract(caller, AccountRef(addrCopy), new(big.Int), gas)
|
||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
// 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.
|
||||
ret, err = run(evm, contract, input, true)
|
||||
gas = contract.Gas
|
||||
}
|
||||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
contract.UseGas(contract.Gas)
|
||||
gas = 0
|
||||
}
|
||||
}
|
||||
return ret, contract.Gas, err
|
||||
return ret, gas, err
|
||||
}
|
||||
|
||||
type codeAndHash struct {
|
||||
@ -466,9 +491,9 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
|
||||
//
|
||||
// The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:]
|
||||
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
|
||||
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||
codeAndHash := &codeAndHash{code: code}
|
||||
contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), codeAndHash.Hash().Bytes())
|
||||
contractAddr = crypto.CreateAddress2(caller.Address(), common.Hash(salt.Bytes32()), codeAndHash.Hash().Bytes())
|
||||
return evm.create(caller, codeAndHash, gas, endowment, contractAddr)
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
|
||||
MemorySize int `json:"memSize"`
|
||||
Stack []*math.HexOrDecimal256 `json:"stack"`
|
||||
ReturnStack []math.HexOrDecimal64 `json:"returnStack"`
|
||||
ReturnData []byte `json:"returnData"`
|
||||
Storage map[common.Hash]common.Hash `json:"-"`
|
||||
Depth int `json:"depth"`
|
||||
RefundCounter uint64 `json:"refund"`
|
||||
@ -50,6 +51,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
|
||||
enc.ReturnStack[k] = math.HexOrDecimal64(v)
|
||||
}
|
||||
}
|
||||
enc.ReturnData = s.ReturnData
|
||||
enc.Storage = s.Storage
|
||||
enc.Depth = s.Depth
|
||||
enc.RefundCounter = s.RefundCounter
|
||||
@ -70,6 +72,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
|
||||
MemorySize *int `json:"memSize"`
|
||||
Stack []*math.HexOrDecimal256 `json:"stack"`
|
||||
ReturnStack []math.HexOrDecimal64 `json:"returnStack"`
|
||||
ReturnData []byte `json:"returnData"`
|
||||
Storage map[common.Hash]common.Hash `json:"-"`
|
||||
Depth *int `json:"depth"`
|
||||
RefundCounter *uint64 `json:"refund"`
|
||||
@ -104,11 +107,14 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
|
||||
}
|
||||
}
|
||||
if dec.ReturnStack != nil {
|
||||
s.ReturnStack = make([]uint64, len(dec.ReturnStack))
|
||||
s.ReturnStack = make([]uint32, len(dec.ReturnStack))
|
||||
for k, v := range dec.ReturnStack {
|
||||
s.ReturnStack[k] = uint64(v)
|
||||
s.ReturnStack[k] = uint32(v)
|
||||
}
|
||||
}
|
||||
if dec.ReturnData != nil {
|
||||
s.ReturnData = dec.ReturnData
|
||||
}
|
||||
if dec.Storage != nil {
|
||||
s.Storage = dec.Storage
|
||||
}
|
||||
|
@ -563,7 +563,7 @@ func opJumpSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([
|
||||
if !callContext.contract.validJumpSubdest(posU64) {
|
||||
return nil, ErrInvalidJump
|
||||
}
|
||||
callContext.rstack.push(*pc)
|
||||
callContext.rstack.push(uint32(*pc))
|
||||
*pc = posU64 + 1
|
||||
return nil, nil
|
||||
}
|
||||
@ -575,7 +575,7 @@ func opReturnSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx)
|
||||
// Other than the check that the return stack is not empty, there is no
|
||||
// need to validate the pc from 'returns', since we only ever push valid
|
||||
//values onto it via jumpsub.
|
||||
*pc = callContext.rstack.pop() + 1
|
||||
*pc = uint64(callContext.rstack.pop()) + 1
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@ -608,7 +608,13 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]
|
||||
stackvalue := size
|
||||
|
||||
callContext.contract.UseGas(gas)
|
||||
res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, value.ToBig())
|
||||
//TODO: use uint256.Int instead of converting with toBig()
|
||||
var bigVal = big0
|
||||
if !value.IsZero() {
|
||||
bigVal = value.ToBig()
|
||||
}
|
||||
|
||||
res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, bigVal)
|
||||
// Push item on the stack based on the returned error. If the ruleset is
|
||||
// homestead we must check for CodeStoreOutOfGasError (homestead only
|
||||
// rule) and treat as an error, if the ruleset is frontier we must
|
||||
@ -643,8 +649,13 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([
|
||||
callContext.contract.UseGas(gas)
|
||||
// reuse size int for stackvalue
|
||||
stackvalue := size
|
||||
//TODO: use uint256.Int instead of converting with toBig()
|
||||
bigEndowment := big0
|
||||
if !endowment.IsZero() {
|
||||
bigEndowment = endowment.ToBig()
|
||||
}
|
||||
res, addr, returnGas, suberr := interpreter.evm.Create2(callContext.contract, input, gas,
|
||||
endowment.ToBig(), salt.ToBig())
|
||||
bigEndowment, &salt)
|
||||
// Push item on the stack based on the returned error.
|
||||
if suberr != nil {
|
||||
stackvalue.Clear()
|
||||
@ -672,10 +683,17 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by
|
||||
// Get the arguments from the memory.
|
||||
args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
|
||||
|
||||
var bigVal = big0
|
||||
//TODO: use uint256.Int instead of converting with toBig()
|
||||
// By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls),
|
||||
// but it would make more sense to extend the usage of uint256.Int
|
||||
if !value.IsZero() {
|
||||
gas += params.CallStipend
|
||||
bigVal = value.ToBig()
|
||||
}
|
||||
ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, value.ToBig())
|
||||
|
||||
ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, bigVal)
|
||||
|
||||
if err != nil {
|
||||
temp.Clear()
|
||||
} else {
|
||||
@ -702,10 +720,14 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) (
|
||||
// Get arguments from the memory.
|
||||
args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
|
||||
|
||||
//TODO: use uint256.Int instead of converting with toBig()
|
||||
var bigVal = big0
|
||||
if !value.IsZero() {
|
||||
gas += params.CallStipend
|
||||
bigVal = value.ToBig()
|
||||
}
|
||||
ret, returnGas, err := interpreter.evm.CallCode(callContext.contract, toAddr, args, gas, value.ToBig())
|
||||
|
||||
ret, returnGas, err := interpreter.evm.CallCode(callContext.contract, toAddr, args, gas, bigVal)
|
||||
if err != nil {
|
||||
temp.Clear()
|
||||
} else {
|
||||
|
@ -182,13 +182,20 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||
logged bool // deferred Tracer should ignore already logged steps
|
||||
res []byte // result of the opcode execution function
|
||||
)
|
||||
// Don't move this deferrred function, it's placed before the capturestate-deferred method,
|
||||
// so that it get's executed _after_: the capturestate needs the stacks before
|
||||
// they are returned to the pools
|
||||
defer func() {
|
||||
returnStack(stack)
|
||||
returnRStack(returns)
|
||||
}()
|
||||
contract.Input = input
|
||||
|
||||
if in.cfg.Debug {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if !logged {
|
||||
in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, contract, in.evm.depth, err)
|
||||
in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, in.returnData, contract, in.evm.depth, err)
|
||||
} else {
|
||||
in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, contract, in.evm.depth, err)
|
||||
}
|
||||
@ -272,7 +279,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||
}
|
||||
|
||||
if in.cfg.Debug {
|
||||
in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, returns, contract, in.evm.depth, err)
|
||||
in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, returns, in.returnData, contract, in.evm.depth, err)
|
||||
logged = true
|
||||
}
|
||||
|
||||
@ -281,7 +288,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||
// if the operation clears the return data (e.g. it has returning data)
|
||||
// set the last return to the result of the operation.
|
||||
if operation.returns {
|
||||
in.returnData = res
|
||||
in.returnData = common.CopyBytes(res)
|
||||
}
|
||||
|
||||
switch {
|
||||
|
@ -50,6 +50,7 @@ type LogConfig struct {
|
||||
DisableMemory bool // disable memory capture
|
||||
DisableStack bool // disable stack capture
|
||||
DisableStorage bool // disable storage capture
|
||||
DisableReturnData bool // disable return data capture
|
||||
Debug bool // print output during capture end
|
||||
Limit int // maximum length of output, but zero means unlimited
|
||||
}
|
||||
@ -66,7 +67,8 @@ type StructLog struct {
|
||||
Memory []byte `json:"memory"`
|
||||
MemorySize int `json:"memSize"`
|
||||
Stack []*big.Int `json:"stack"`
|
||||
ReturnStack []uint64 `json:"returnStack"`
|
||||
ReturnStack []uint32 `json:"returnStack"`
|
||||
ReturnData []byte `json:"returnData"`
|
||||
Storage map[common.Hash]common.Hash `json:"-"`
|
||||
Depth int `json:"depth"`
|
||||
RefundCounter uint64 `json:"refund"`
|
||||
@ -104,7 +106,7 @@ func (s *StructLog) ErrorString() string {
|
||||
// if you need to retain them beyond the current call.
|
||||
type Tracer interface {
|
||||
CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error
|
||||
CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error
|
||||
CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error
|
||||
CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error
|
||||
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error
|
||||
}
|
||||
@ -142,7 +144,7 @@ func (l *StructLogger) CaptureStart(from common.Address, to common.Address, crea
|
||||
// CaptureState logs a new structured log message and pushes it out to the environment
|
||||
//
|
||||
// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
|
||||
func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error {
|
||||
func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error {
|
||||
// check if already accumulated the specified number of logs
|
||||
if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
|
||||
return errTraceLimitReached
|
||||
@ -161,9 +163,9 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
|
||||
stck[i] = new(big.Int).Set(item.ToBig())
|
||||
}
|
||||
}
|
||||
var rstack []uint64
|
||||
var rstack []uint32
|
||||
if !l.cfg.DisableStack && rStack != nil {
|
||||
rstck := make([]uint64, len(rStack.data))
|
||||
rstck := make([]uint32, len(rStack.data))
|
||||
copy(rstck, rStack.data)
|
||||
}
|
||||
// Copy a snapshot of the current storage to a new container
|
||||
@ -192,8 +194,13 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
|
||||
}
|
||||
storage = l.storage[contract.Address()].Copy()
|
||||
}
|
||||
var rdata []byte
|
||||
if !l.cfg.DisableReturnData {
|
||||
rdata = make([]byte, len(rData))
|
||||
copy(rdata, rData)
|
||||
}
|
||||
// create a new snapshot of the EVM.
|
||||
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rstack, storage, depth, env.StateDB.GetRefund(), err}
|
||||
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rstack, rdata, storage, depth, env.StateDB.GetRefund(), err}
|
||||
l.logs = append(l.logs, log)
|
||||
return nil
|
||||
}
|
||||
@ -257,6 +264,10 @@ func WriteTrace(writer io.Writer, logs []StructLog) {
|
||||
fmt.Fprintf(writer, "%x: %x\n", h, item)
|
||||
}
|
||||
}
|
||||
if len(log.ReturnData) > 0 {
|
||||
fmt.Fprintln(writer, "ReturnData:")
|
||||
fmt.Fprint(writer, hex.Dump(log.ReturnData))
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
}
|
||||
@ -308,7 +319,7 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error {
|
||||
func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error {
|
||||
fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost)
|
||||
|
||||
if !t.cfg.DisableStack { // format stack
|
||||
|
@ -46,7 +46,7 @@ func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create
|
||||
}
|
||||
|
||||
// CaptureState outputs state information on the logger.
|
||||
func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error {
|
||||
func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error {
|
||||
log := StructLog{
|
||||
Pc: pc,
|
||||
Op: op,
|
||||
@ -70,6 +70,9 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint
|
||||
log.Stack = logstack
|
||||
log.ReturnStack = rStack.data
|
||||
}
|
||||
if !l.cfg.DisableReturnData {
|
||||
log.ReturnData = rData
|
||||
}
|
||||
return l.encoder.Encode(log)
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ func TestStoreCapture(t *testing.T) {
|
||||
stack.push(uint256.NewInt().SetUint64(1))
|
||||
stack.push(uint256.NewInt())
|
||||
var index common.Hash
|
||||
logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, rstack, contract, 0, nil)
|
||||
logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, rstack, nil, contract, 0, nil)
|
||||
if len(logger.storage[contract.Address()]) == 0 {
|
||||
t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.storage[contract.Address()]))
|
||||
}
|
||||
|
@ -57,8 +57,15 @@ func setDefaults(cfg *Config) {
|
||||
DAOForkBlock: new(big.Int),
|
||||
DAOForkSupport: false,
|
||||
EIP150Block: new(big.Int),
|
||||
EIP150Hash: common.Hash{},
|
||||
EIP155Block: new(big.Int),
|
||||
EIP158Block: new(big.Int),
|
||||
ByzantiumBlock: new(big.Int),
|
||||
ConstantinopleBlock: new(big.Int),
|
||||
PetersburgBlock: new(big.Int),
|
||||
IstanbulBlock: new(big.Int),
|
||||
MuirGlacierBlock: new(big.Int),
|
||||
YoloV1Block: nil,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -321,34 +321,6 @@ func TestBlockhash(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSimpleLoop test a pretty simple loop which loops
|
||||
// 1M (1 048 575) times.
|
||||
// Takes about 200 ms
|
||||
func BenchmarkSimpleLoop(b *testing.B) {
|
||||
// 0xfffff = 1048575 loops
|
||||
code := []byte{
|
||||
byte(vm.PUSH3), 0x0f, 0xff, 0xff,
|
||||
byte(vm.JUMPDEST), // [ count ]
|
||||
byte(vm.PUSH1), 1, // [count, 1]
|
||||
byte(vm.SWAP1), // [1, count]
|
||||
byte(vm.SUB), // [ count -1 ]
|
||||
byte(vm.DUP1), // [ count -1 , count-1]
|
||||
byte(vm.PUSH1), 4, // [count-1, count -1, label]
|
||||
byte(vm.JUMPI), // [ 0 ]
|
||||
byte(vm.STOP),
|
||||
}
|
||||
//tracer := vm.NewJSONLogger(nil, os.Stdout)
|
||||
//Execute(code, nil, &Config{
|
||||
// EVMConfig: vm.Config{
|
||||
// Debug: true,
|
||||
// Tracer: tracer,
|
||||
// }})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
Execute(code, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
type stepCounter struct {
|
||||
inner *vm.JSONLogger
|
||||
steps int
|
||||
@ -358,7 +330,7 @@ func (s *stepCounter) CaptureStart(from common.Address, to common.Address, creat
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, contract *vm.Contract, depth int, err error) error {
|
||||
func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, rData []byte, contract *vm.Contract, depth int, err error) error {
|
||||
s.steps++
|
||||
// Enable this for more output
|
||||
//s.inner.CaptureState(env, pc, op, gas, cost, memory, stack, rStack, contract, depth, err)
|
||||
@ -593,3 +565,160 @@ func DisabledTestEipExampleCases(t *testing.T) {
|
||||
"allowed, and causes an error", code)
|
||||
}
|
||||
}
|
||||
|
||||
// benchmarkNonModifyingCode benchmarks code, but if the code modifies the
|
||||
// state, this should not be used, since it does not reset the state between runs.
|
||||
func benchmarkNonModifyingCode(gas uint64, code []byte, name string, b *testing.B) {
|
||||
cfg := new(Config)
|
||||
setDefaults(cfg)
|
||||
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
cfg.GasLimit = gas
|
||||
var (
|
||||
destination = common.BytesToAddress([]byte("contract"))
|
||||
vmenv = NewEnv(cfg)
|
||||
sender = vm.AccountRef(cfg.Origin)
|
||||
)
|
||||
cfg.State.CreateAccount(destination)
|
||||
eoa := common.HexToAddress("E0")
|
||||
{
|
||||
cfg.State.CreateAccount(eoa)
|
||||
cfg.State.SetNonce(eoa, 100)
|
||||
}
|
||||
reverting := common.HexToAddress("EE")
|
||||
{
|
||||
cfg.State.CreateAccount(reverting)
|
||||
cfg.State.SetCode(reverting, []byte{
|
||||
byte(vm.PUSH1), 0x00,
|
||||
byte(vm.PUSH1), 0x00,
|
||||
byte(vm.REVERT),
|
||||
})
|
||||
}
|
||||
|
||||
//cfg.State.CreateAccount(cfg.Origin)
|
||||
// set the receiver's (the executing contract) code for execution.
|
||||
cfg.State.SetCode(destination, code)
|
||||
vmenv.Call(sender, destination, nil, gas, cfg.Value)
|
||||
|
||||
b.Run(name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
vmenv.Call(sender, destination, nil, gas, cfg.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkSimpleLoop test a pretty simple loop which loops until OOG
|
||||
// 55 ms
|
||||
func BenchmarkSimpleLoop(b *testing.B) {
|
||||
|
||||
staticCallIdentity := []byte{
|
||||
byte(vm.JUMPDEST), // [ count ]
|
||||
// push args for the call
|
||||
byte(vm.PUSH1), 0, // out size
|
||||
byte(vm.DUP1), // out offset
|
||||
byte(vm.DUP1), // out insize
|
||||
byte(vm.DUP1), // in offset
|
||||
byte(vm.PUSH1), 0x4, // address of identity
|
||||
byte(vm.GAS), // gas
|
||||
byte(vm.STATICCALL),
|
||||
byte(vm.POP), // pop return value
|
||||
byte(vm.PUSH1), 0, // jumpdestination
|
||||
byte(vm.JUMP),
|
||||
}
|
||||
|
||||
callIdentity := []byte{
|
||||
byte(vm.JUMPDEST), // [ count ]
|
||||
// push args for the call
|
||||
byte(vm.PUSH1), 0, // out size
|
||||
byte(vm.DUP1), // out offset
|
||||
byte(vm.DUP1), // out insize
|
||||
byte(vm.DUP1), // in offset
|
||||
byte(vm.DUP1), // value
|
||||
byte(vm.PUSH1), 0x4, // address of identity
|
||||
byte(vm.GAS), // gas
|
||||
byte(vm.CALL),
|
||||
byte(vm.POP), // pop return value
|
||||
byte(vm.PUSH1), 0, // jumpdestination
|
||||
byte(vm.JUMP),
|
||||
}
|
||||
|
||||
callInexistant := []byte{
|
||||
byte(vm.JUMPDEST), // [ count ]
|
||||
// push args for the call
|
||||
byte(vm.PUSH1), 0, // out size
|
||||
byte(vm.DUP1), // out offset
|
||||
byte(vm.DUP1), // out insize
|
||||
byte(vm.DUP1), // in offset
|
||||
byte(vm.DUP1), // value
|
||||
byte(vm.PUSH1), 0xff, // address of existing contract
|
||||
byte(vm.GAS), // gas
|
||||
byte(vm.CALL),
|
||||
byte(vm.POP), // pop return value
|
||||
byte(vm.PUSH1), 0, // jumpdestination
|
||||
byte(vm.JUMP),
|
||||
}
|
||||
|
||||
callEOA := []byte{
|
||||
byte(vm.JUMPDEST), // [ count ]
|
||||
// push args for the call
|
||||
byte(vm.PUSH1), 0, // out size
|
||||
byte(vm.DUP1), // out offset
|
||||
byte(vm.DUP1), // out insize
|
||||
byte(vm.DUP1), // in offset
|
||||
byte(vm.DUP1), // value
|
||||
byte(vm.PUSH1), 0xE0, // address of EOA
|
||||
byte(vm.GAS), // gas
|
||||
byte(vm.CALL),
|
||||
byte(vm.POP), // pop return value
|
||||
byte(vm.PUSH1), 0, // jumpdestination
|
||||
byte(vm.JUMP),
|
||||
}
|
||||
|
||||
loopingCode := []byte{
|
||||
byte(vm.JUMPDEST), // [ count ]
|
||||
// push args for the call
|
||||
byte(vm.PUSH1), 0, // out size
|
||||
byte(vm.DUP1), // out offset
|
||||
byte(vm.DUP1), // out insize
|
||||
byte(vm.DUP1), // in offset
|
||||
byte(vm.PUSH1), 0x4, // address of identity
|
||||
byte(vm.GAS), // gas
|
||||
|
||||
byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP),
|
||||
byte(vm.PUSH1), 0, // jumpdestination
|
||||
byte(vm.JUMP),
|
||||
}
|
||||
|
||||
calllRevertingContractWithInput := []byte{
|
||||
byte(vm.JUMPDEST), //
|
||||
// push args for the call
|
||||
byte(vm.PUSH1), 0, // out size
|
||||
byte(vm.DUP1), // out offset
|
||||
byte(vm.PUSH1), 0x20, // in size
|
||||
byte(vm.PUSH1), 0x00, // in offset
|
||||
byte(vm.PUSH1), 0x00, // value
|
||||
byte(vm.PUSH1), 0xEE, // address of reverting contract
|
||||
byte(vm.GAS), // gas
|
||||
byte(vm.CALL),
|
||||
byte(vm.POP), // pop return value
|
||||
byte(vm.PUSH1), 0, // jumpdestination
|
||||
byte(vm.JUMP),
|
||||
}
|
||||
|
||||
//tracer := vm.NewJSONLogger(nil, os.Stdout)
|
||||
//Execute(loopingCode, nil, &Config{
|
||||
// EVMConfig: vm.Config{
|
||||
// Debug: true,
|
||||
// Tracer: tracer,
|
||||
// }})
|
||||
// 100M gas
|
||||
benchmarkNonModifyingCode(100000000, staticCallIdentity, "staticcall-identity-100M", b)
|
||||
benchmarkNonModifyingCode(100000000, callIdentity, "call-identity-100M", b)
|
||||
benchmarkNonModifyingCode(100000000, loopingCode, "loop-100M", b)
|
||||
benchmarkNonModifyingCode(100000000, callInexistant, "call-nonexist-100M", b)
|
||||
benchmarkNonModifyingCode(100000000, callEOA, "call-EOA-100M", b)
|
||||
benchmarkNonModifyingCode(100000000, calllRevertingContractWithInput, "call-reverting-100M", b)
|
||||
|
||||
//benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b)
|
||||
//benchmarkNonModifyingCode(10000000, loopingCode, "loop-10M", b)
|
||||
}
|
||||
|
@ -18,10 +18,17 @@ package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
var stackPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &Stack{data: make([]uint256.Int, 0, 16)}
|
||||
},
|
||||
}
|
||||
|
||||
// Stack is an object for basic stack operations. Items popped to the stack are
|
||||
// expected to be changed and modified. stack does not take care of adding newly
|
||||
// initialised objects.
|
||||
@ -30,7 +37,12 @@ type Stack struct {
|
||||
}
|
||||
|
||||
func newstack() *Stack {
|
||||
return &Stack{data: make([]uint256.Int, 0, 16)}
|
||||
return stackPool.Get().(*Stack)
|
||||
}
|
||||
|
||||
func returnStack(s *Stack) {
|
||||
s.data = s.data[:0]
|
||||
stackPool.Put(s)
|
||||
}
|
||||
|
||||
// Data returns the underlying uint256.Int array.
|
||||
@ -87,20 +99,32 @@ func (st *Stack) Print() {
|
||||
fmt.Println("#############")
|
||||
}
|
||||
|
||||
var rStackPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &ReturnStack{data: make([]uint32, 0, 10)}
|
||||
},
|
||||
}
|
||||
|
||||
// ReturnStack is an object for basic return stack operations.
|
||||
type ReturnStack struct {
|
||||
data []uint64
|
||||
data []uint32
|
||||
}
|
||||
|
||||
func newReturnStack() *ReturnStack {
|
||||
return &ReturnStack{data: make([]uint64, 0, 1024)}
|
||||
return rStackPool.Get().(*ReturnStack)
|
||||
}
|
||||
|
||||
func (st *ReturnStack) push(d uint64) {
|
||||
func returnRStack(rs *ReturnStack) {
|
||||
rs.data = rs.data[:0]
|
||||
rStackPool.Put(rs)
|
||||
}
|
||||
|
||||
func (st *ReturnStack) push(d uint32) {
|
||||
st.data = append(st.data, d)
|
||||
}
|
||||
|
||||
func (st *ReturnStack) pop() (ret uint64) {
|
||||
// A uint32 is sufficient as for code below 4.2G
|
||||
func (st *ReturnStack) pop() (ret uint32) {
|
||||
ret = st.data[len(st.data)-1]
|
||||
st.data = st.data[:len(st.data)-1]
|
||||
return
|
||||
|
@ -541,7 +541,7 @@ func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create b
|
||||
}
|
||||
|
||||
// CaptureState implements the Tracer interface to trace a single step of VM execution.
|
||||
func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, contract *vm.Contract, depth int, err error) error {
|
||||
func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, rdata []byte, contract *vm.Contract, depth int, err error) error {
|
||||
if jst.err == nil {
|
||||
// Initialize the context if it wasn't done yet
|
||||
if !jst.inited {
|
||||
|
@ -169,10 +169,10 @@ func TestHaltBetweenSteps(t *testing.T) {
|
||||
env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
|
||||
contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0)
|
||||
|
||||
tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, contract, 0, nil)
|
||||
tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, nil, contract, 0, nil)
|
||||
timeout := errors.New("stahp")
|
||||
tracer.Stop(timeout)
|
||||
tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, contract, 0, nil)
|
||||
tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, nil, contract, 0, nil)
|
||||
|
||||
if _, err := tracer.GetResult(); err.Error() != timeout.Error() {
|
||||
t.Errorf("Expected timeout error, got %v", err)
|
||||
|
@ -179,9 +179,9 @@ func (t *SecureTrie) hashKey(key []byte) []byte {
|
||||
h := newHasher(false)
|
||||
h.sha.Reset()
|
||||
h.sha.Write(key)
|
||||
buf := h.sha.Sum(t.hashKeyBuf[:0])
|
||||
h.sha.Read(t.hashKeyBuf[:])
|
||||
returnHasherToPool(h)
|
||||
return buf
|
||||
return t.hashKeyBuf[:]
|
||||
}
|
||||
|
||||
// getSecKeyCache returns the current secure key cache, creating a new one if
|
||||
|
Loading…
Reference in New Issue
Block a user