core,eth: call frame tracing (#23087)
This change introduces 2 new optional methods; `enter()` and `exit()` for js tracers, and makes `step()` optiona. The two new methods are invoked when entering and exiting a call frame (but not invoked for the outermost scope, which has it's own methods). Currently these are the data fields passed to each of them: enter: type (opcode), from, to, input, gas, value exit: output, gasUsed, error The PR also comes with a re-write of the callTracer. As a backup we keep the previous tracing script under the name `callTracerLegacy`. Behaviour of both tracers are equivalent for the most part, although there are some small differences (improvements), where the new tracer is more correct / has more information.
This commit is contained in:
@ -166,6 +166,11 @@ func (*AccessListTracer) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost
|
||||
|
||||
func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {}
|
||||
|
||||
func (*AccessListTracer) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (*AccessListTracer) CaptureExit(output []byte, gasUsed uint64, err error) {}
|
||||
|
||||
// AccessList returns the current accesslist maintained by the tracer.
|
||||
func (a *AccessListTracer) AccessList() types.AccessList {
|
||||
return a.list.accessList()
|
||||
|
@ -193,11 +193,19 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
|
||||
|
||||
// Capture the tracer start/end events in debug mode
|
||||
if evm.Config.Debug && evm.depth == 0 {
|
||||
evm.Config.Tracer.CaptureStart(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())
|
||||
if evm.Config.Debug {
|
||||
if evm.depth == 0 {
|
||||
evm.Config.Tracer.CaptureStart(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(CALL, caller.Address(), addr, input, gas, value)
|
||||
defer func(startGas uint64) {
|
||||
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
|
||||
}(gas)
|
||||
}
|
||||
}
|
||||
|
||||
if isPrecompile {
|
||||
@ -257,6 +265,14 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||
}
|
||||
var snapshot = evm.StateDB.Snapshot()
|
||||
|
||||
// Invoke tracer hooks that signal entering/exiting a call frame
|
||||
if evm.Config.Debug {
|
||||
evm.Config.Tracer.CaptureEnter(CALLCODE, caller.Address(), addr, input, gas, value)
|
||||
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 {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
@ -293,6 +309,14 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||
}
|
||||
var snapshot = evm.StateDB.Snapshot()
|
||||
|
||||
// Invoke tracer hooks that signal entering/exiting a call frame
|
||||
if evm.Config.Debug {
|
||||
evm.Config.Tracer.CaptureEnter(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 {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
@ -338,6 +362,14 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||
// future scenarios
|
||||
evm.StateDB.AddBalance(addr, big0)
|
||||
|
||||
// Invoke tracer hooks that signal entering/exiting a call frame
|
||||
if evm.Config.Debug {
|
||||
evm.Config.Tracer.CaptureEnter(STATICCALL, caller.Address(), addr, input, gas, nil)
|
||||
defer func(startGas uint64) {
|
||||
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
|
||||
}(gas)
|
||||
}
|
||||
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
} else {
|
||||
@ -377,7 +409,7 @@ func (c *codeAndHash) Hash() common.Hash {
|
||||
}
|
||||
|
||||
// create creates a new contract using code as deployment code.
|
||||
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) {
|
||||
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) {
|
||||
// Depth check execution. Fail if we're trying to execute above the
|
||||
// limit.
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
@ -415,9 +447,14 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||
return nil, address, gas, nil
|
||||
}
|
||||
|
||||
if evm.Config.Debug && evm.depth == 0 {
|
||||
evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
|
||||
if evm.Config.Debug {
|
||||
if evm.depth == 0 {
|
||||
evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
|
||||
} else {
|
||||
evm.Config.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value)
|
||||
}
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
|
||||
ret, err := evm.interpreter.Run(contract, nil, false)
|
||||
@ -455,8 +492,12 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||
}
|
||||
}
|
||||
|
||||
if evm.Config.Debug && evm.depth == 0 {
|
||||
evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
|
||||
if evm.Config.Debug {
|
||||
if evm.depth == 0 {
|
||||
evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
|
||||
} else {
|
||||
evm.Config.Tracer.CaptureExit(ret, gas-contract.Gas, err)
|
||||
}
|
||||
}
|
||||
return ret, address, contract.Gas, err
|
||||
}
|
||||
@ -464,7 +505,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||
// Create creates a new contract using code as deployment code.
|
||||
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||
contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address()))
|
||||
return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr)
|
||||
return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, CREATE)
|
||||
}
|
||||
|
||||
// Create2 creates a new contract using code as deployment code.
|
||||
@ -474,7 +515,7 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
|
||||
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(), salt.Bytes32(), codeAndHash.Hash().Bytes())
|
||||
return evm.create(caller, codeAndHash, gas, endowment, contractAddr)
|
||||
return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2)
|
||||
}
|
||||
|
||||
// ChainConfig returns the environment's chain configuration
|
||||
|
@ -791,6 +791,10 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
||||
balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
|
||||
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance)
|
||||
interpreter.evm.StateDB.Suicide(scope.Contract.Address())
|
||||
if interpreter.cfg.Debug {
|
||||
interpreter.cfg.Tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance)
|
||||
interpreter.cfg.Tracer.CaptureExit([]byte{}, 0, nil)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -106,6 +106,8 @@ func (s *StructLog) ErrorString() string {
|
||||
type Tracer interface {
|
||||
CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
|
||||
CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
|
||||
CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)
|
||||
CaptureExit(output []byte, gasUsed uint64, err error)
|
||||
CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
|
||||
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error)
|
||||
}
|
||||
@ -225,6 +227,11 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration
|
||||
}
|
||||
}
|
||||
|
||||
func (l *StructLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
|
||||
|
||||
// StructLogs returns the captured log entries.
|
||||
func (l *StructLogger) StructLogs() []StructLog { return l.logs }
|
||||
|
||||
@ -342,3 +349,8 @@ func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, e
|
||||
fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n",
|
||||
output, gasUsed, err)
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
|
||||
|
@ -87,3 +87,8 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration,
|
||||
}
|
||||
l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, errMsg})
|
||||
}
|
||||
|
||||
func (l *JSONLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
|
||||
|
@ -33,6 +33,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
@ -342,11 +343,21 @@ func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, co
|
||||
|
||||
// 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) {
|
||||
func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode string, b *testing.B) {
|
||||
cfg := new(Config)
|
||||
setDefaults(cfg)
|
||||
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
cfg.GasLimit = gas
|
||||
if len(tracerCode) > 0 {
|
||||
tracer, err := tracers.New(tracerCode, new(tracers.Context))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
cfg.EVMConfig = vm.Config{
|
||||
Debug: true,
|
||||
Tracer: tracer,
|
||||
}
|
||||
}
|
||||
var (
|
||||
destination = common.BytesToAddress([]byte("contract"))
|
||||
vmenv = NewEnv(cfg)
|
||||
@ -486,12 +497,12 @@ func BenchmarkSimpleLoop(b *testing.B) {
|
||||
// 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(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)
|
||||
@ -688,3 +699,241 @@ func TestColdAccountAccessCost(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuntimeJSTracer(t *testing.T) {
|
||||
jsTracers := []string{
|
||||
`{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, steps:0,
|
||||
step: function() { this.steps++},
|
||||
fault: function() {},
|
||||
result: function() {
|
||||
return [this.enters, this.exits,this.enterGas,this.gasUsed, this.steps].join(",")
|
||||
},
|
||||
enter: function(frame) {
|
||||
this.enters++;
|
||||
this.enterGas = frame.getGas();
|
||||
},
|
||||
exit: function(res) {
|
||||
this.exits++;
|
||||
this.gasUsed = res.getGasUsed();
|
||||
}}`,
|
||||
`{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, steps:0,
|
||||
fault: function() {},
|
||||
result: function() {
|
||||
return [this.enters, this.exits,this.enterGas,this.gasUsed, this.steps].join(",")
|
||||
},
|
||||
enter: function(frame) {
|
||||
this.enters++;
|
||||
this.enterGas = frame.getGas();
|
||||
},
|
||||
exit: function(res) {
|
||||
this.exits++;
|
||||
this.gasUsed = res.getGasUsed();
|
||||
}}`}
|
||||
tests := []struct {
|
||||
code []byte
|
||||
// One result per tracer
|
||||
results []string
|
||||
}{
|
||||
{
|
||||
// CREATE
|
||||
code: []byte{
|
||||
// Store initcode in memory at 0x00 (5 bytes left-padded to 32 bytes)
|
||||
byte(vm.PUSH5),
|
||||
// Init code: PUSH1 0, PUSH1 0, RETURN (3 steps)
|
||||
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN),
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.MSTORE),
|
||||
// length, offset, value
|
||||
byte(vm.PUSH1), 5, byte(vm.PUSH1), 27, byte(vm.PUSH1), 0,
|
||||
byte(vm.CREATE),
|
||||
byte(vm.POP),
|
||||
},
|
||||
results: []string{`"1,1,4294935775,6,12"`, `"1,1,4294935775,6,0"`},
|
||||
},
|
||||
{
|
||||
// CREATE2
|
||||
code: []byte{
|
||||
// Store initcode in memory at 0x00 (5 bytes left-padded to 32 bytes)
|
||||
byte(vm.PUSH5),
|
||||
// Init code: PUSH1 0, PUSH1 0, RETURN (3 steps)
|
||||
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN),
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.MSTORE),
|
||||
// salt, length, offset, value
|
||||
byte(vm.PUSH1), 1, byte(vm.PUSH1), 5, byte(vm.PUSH1), 27, byte(vm.PUSH1), 0,
|
||||
byte(vm.CREATE2),
|
||||
byte(vm.POP),
|
||||
},
|
||||
results: []string{`"1,1,4294935766,6,13"`, `"1,1,4294935766,6,0"`},
|
||||
},
|
||||
{
|
||||
// CALL
|
||||
code: []byte{
|
||||
// outsize, outoffset, insize, inoffset
|
||||
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
|
||||
byte(vm.PUSH1), 0, // value
|
||||
byte(vm.PUSH1), 0xbb, //address
|
||||
byte(vm.GAS), // gas
|
||||
byte(vm.CALL),
|
||||
byte(vm.POP),
|
||||
},
|
||||
results: []string{`"1,1,4294964716,6,13"`, `"1,1,4294964716,6,0"`},
|
||||
},
|
||||
{
|
||||
// CALLCODE
|
||||
code: []byte{
|
||||
// outsize, outoffset, insize, inoffset
|
||||
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
|
||||
byte(vm.PUSH1), 0, // value
|
||||
byte(vm.PUSH1), 0xcc, //address
|
||||
byte(vm.GAS), // gas
|
||||
byte(vm.CALLCODE),
|
||||
byte(vm.POP),
|
||||
},
|
||||
results: []string{`"1,1,4294964716,6,13"`, `"1,1,4294964716,6,0"`},
|
||||
},
|
||||
{
|
||||
// STATICCALL
|
||||
code: []byte{
|
||||
// outsize, outoffset, insize, inoffset
|
||||
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
|
||||
byte(vm.PUSH1), 0xdd, //address
|
||||
byte(vm.GAS), // gas
|
||||
byte(vm.STATICCALL),
|
||||
byte(vm.POP),
|
||||
},
|
||||
results: []string{`"1,1,4294964719,6,12"`, `"1,1,4294964719,6,0"`},
|
||||
},
|
||||
{
|
||||
// DELEGATECALL
|
||||
code: []byte{
|
||||
// outsize, outoffset, insize, inoffset
|
||||
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
|
||||
byte(vm.PUSH1), 0xee, //address
|
||||
byte(vm.GAS), // gas
|
||||
byte(vm.DELEGATECALL),
|
||||
byte(vm.POP),
|
||||
},
|
||||
results: []string{`"1,1,4294964719,6,12"`, `"1,1,4294964719,6,0"`},
|
||||
},
|
||||
{
|
||||
// CALL self-destructing contract
|
||||
code: []byte{
|
||||
// outsize, outoffset, insize, inoffset
|
||||
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
|
||||
byte(vm.PUSH1), 0, // value
|
||||
byte(vm.PUSH1), 0xff, //address
|
||||
byte(vm.GAS), // gas
|
||||
byte(vm.CALL),
|
||||
byte(vm.POP),
|
||||
},
|
||||
results: []string{`"2,2,0,5003,12"`, `"2,2,0,5003,0"`},
|
||||
},
|
||||
}
|
||||
calleeCode := []byte{
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.RETURN),
|
||||
}
|
||||
depressedCode := []byte{
|
||||
byte(vm.PUSH1), 0xaa,
|
||||
byte(vm.SELFDESTRUCT),
|
||||
}
|
||||
main := common.HexToAddress("0xaa")
|
||||
for i, jsTracer := range jsTracers {
|
||||
for j, tc := range tests {
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
statedb.SetCode(main, tc.code)
|
||||
statedb.SetCode(common.HexToAddress("0xbb"), calleeCode)
|
||||
statedb.SetCode(common.HexToAddress("0xcc"), calleeCode)
|
||||
statedb.SetCode(common.HexToAddress("0xdd"), calleeCode)
|
||||
statedb.SetCode(common.HexToAddress("0xee"), calleeCode)
|
||||
statedb.SetCode(common.HexToAddress("0xff"), depressedCode)
|
||||
|
||||
tracer, err := tracers.New(jsTracer, new(tracers.Context))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, _, err = Call(main, nil, &Config{
|
||||
State: statedb,
|
||||
EVMConfig: vm.Config{
|
||||
Debug: true,
|
||||
Tracer: tracer,
|
||||
}})
|
||||
if err != nil {
|
||||
t.Fatal("didn't expect error", err)
|
||||
}
|
||||
res, err := tracer.GetResult()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if have, want := string(res), tc.results[i]; have != want {
|
||||
t.Errorf("wrong result for tracer %d testcase %d, have \n%v\nwant\n%v\n", i, j, have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSTracerCreateTx(t *testing.T) {
|
||||
jsTracer := `
|
||||
{enters: 0, exits: 0,
|
||||
step: function() {},
|
||||
fault: function() {},
|
||||
result: function() { return [this.enters, this.exits].join(",") },
|
||||
enter: function(frame) { this.enters++ },
|
||||
exit: function(res) { this.exits++ }}`
|
||||
code := []byte{byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN)}
|
||||
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
tracer, err := tracers.New(jsTracer, new(tracers.Context))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, _, _, err = Create(code, &Config{
|
||||
State: statedb,
|
||||
EVMConfig: vm.Config{
|
||||
Debug: true,
|
||||
Tracer: tracer,
|
||||
}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := tracer.GetResult()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if have, want := string(res), `"0,0"`; have != want {
|
||||
t.Errorf("wrong result for tracer, have \n%v\nwant\n%v\n", have, want)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTracerStepVsCallFrame(b *testing.B) {
|
||||
// Simply pushes and pops some values in a loop
|
||||
code := []byte{
|
||||
byte(vm.JUMPDEST),
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.PUSH1), 0,
|
||||
byte(vm.POP),
|
||||
byte(vm.POP),
|
||||
byte(vm.PUSH1), 0, // jumpdestination
|
||||
byte(vm.JUMP),
|
||||
}
|
||||
|
||||
stepTracer := `
|
||||
{
|
||||
step: function() {},
|
||||
fault: function() {},
|
||||
result: function() {},
|
||||
}`
|
||||
callFrameTracer := `
|
||||
{
|
||||
enter: function() {},
|
||||
exit: function() {},
|
||||
fault: function() {},
|
||||
result: function() {},
|
||||
}`
|
||||
|
||||
benchmarkNonModifyingCode(10000000, code, "tracer-step-10M", stepTracer, b)
|
||||
benchmarkNonModifyingCode(10000000, code, "tracer-call-frame-10M", callFrameTracer, b)
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ func (st *Stack) Print() {
|
||||
fmt.Println("### stack ###")
|
||||
if len(st.data) > 0 {
|
||||
for i, val := range st.data {
|
||||
fmt.Printf("%-3d %v\n", i, val)
|
||||
fmt.Printf("%-3d %s\n", i, val.String())
|
||||
}
|
||||
} else {
|
||||
fmt.Println("-- empty --")
|
||||
|
Reference in New Issue
Block a user