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:
parent
7ada89d4e6
commit
401354976b
|
@ -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) 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.
|
// AccessList returns the current accesslist maintained by the tracer.
|
||||||
func (a *AccessListTracer) AccessList() types.AccessList {
|
func (a *AccessListTracer) AccessList() types.AccessList {
|
||||||
return a.list.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)
|
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
|
||||||
|
|
||||||
// Capture the tracer start/end events in debug mode
|
// Capture the tracer start/end events in debug mode
|
||||||
if evm.Config.Debug && evm.depth == 0 {
|
if evm.Config.Debug {
|
||||||
evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
|
if evm.depth == 0 {
|
||||||
defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
|
evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
|
||||||
evm.Config.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
|
defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
|
||||||
}(gas, time.Now())
|
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 {
|
if isPrecompile {
|
||||||
|
@ -257,6 +265,14 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||||
}
|
}
|
||||||
var snapshot = evm.StateDB.Snapshot()
|
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
|
// It is allowed to call precompiles, even via delegatecall
|
||||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
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()
|
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
|
// It is allowed to call precompiles, even via delegatecall
|
||||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||||
|
@ -338,6 +362,14 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||||
// future scenarios
|
// future scenarios
|
||||||
evm.StateDB.AddBalance(addr, big0)
|
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 {
|
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||||
} else {
|
} else {
|
||||||
|
@ -377,7 +409,7 @@ func (c *codeAndHash) Hash() common.Hash {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create creates a new contract using code as deployment code.
|
// 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
|
// Depth check execution. Fail if we're trying to execute above the
|
||||||
// limit.
|
// limit.
|
||||||
if evm.depth > int(params.CallCreateDepth) {
|
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
|
return nil, address, gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if evm.Config.Debug && evm.depth == 0 {
|
if evm.Config.Debug {
|
||||||
evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
|
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()
|
start := time.Now()
|
||||||
|
|
||||||
ret, err := evm.interpreter.Run(contract, nil, false)
|
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 {
|
if evm.Config.Debug {
|
||||||
evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
|
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
|
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.
|
// 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) {
|
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()))
|
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.
|
// 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) {
|
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}
|
codeAndHash := &codeAndHash{code: code}
|
||||||
contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
|
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
|
// 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())
|
balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
|
||||||
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance)
|
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance)
|
||||||
interpreter.evm.StateDB.Suicide(scope.Contract.Address())
|
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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,8 @@ func (s *StructLog) ErrorString() string {
|
||||||
type Tracer interface {
|
type Tracer interface {
|
||||||
CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
|
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)
|
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)
|
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)
|
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.
|
// StructLogs returns the captured log entries.
|
||||||
func (l *StructLogger) StructLogs() []StructLog { return l.logs }
|
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",
|
fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n",
|
||||||
output, gasUsed, err)
|
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})
|
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/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"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
|
// benchmarkNonModifyingCode benchmarks code, but if the code modifies the
|
||||||
// state, this should not be used, since it does not reset the state between runs.
|
// 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)
|
cfg := new(Config)
|
||||||
setDefaults(cfg)
|
setDefaults(cfg)
|
||||||
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||||
cfg.GasLimit = gas
|
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 (
|
var (
|
||||||
destination = common.BytesToAddress([]byte("contract"))
|
destination = common.BytesToAddress([]byte("contract"))
|
||||||
vmenv = NewEnv(cfg)
|
vmenv = NewEnv(cfg)
|
||||||
|
@ -486,12 +497,12 @@ func BenchmarkSimpleLoop(b *testing.B) {
|
||||||
// Tracer: tracer,
|
// Tracer: tracer,
|
||||||
// }})
|
// }})
|
||||||
// 100M gas
|
// 100M gas
|
||||||
benchmarkNonModifyingCode(100000000, staticCallIdentity, "staticcall-identity-100M", b)
|
benchmarkNonModifyingCode(100000000, staticCallIdentity, "staticcall-identity-100M", "", b)
|
||||||
benchmarkNonModifyingCode(100000000, callIdentity, "call-identity-100M", b)
|
benchmarkNonModifyingCode(100000000, callIdentity, "call-identity-100M", "", b)
|
||||||
benchmarkNonModifyingCode(100000000, loopingCode, "loop-100M", b)
|
benchmarkNonModifyingCode(100000000, loopingCode, "loop-100M", "", b)
|
||||||
benchmarkNonModifyingCode(100000000, callInexistant, "call-nonexist-100M", b)
|
benchmarkNonModifyingCode(100000000, callInexistant, "call-nonexist-100M", "", b)
|
||||||
benchmarkNonModifyingCode(100000000, callEOA, "call-EOA-100M", b)
|
benchmarkNonModifyingCode(100000000, callEOA, "call-EOA-100M", "", b)
|
||||||
benchmarkNonModifyingCode(100000000, calllRevertingContractWithInput, "call-reverting-100M", b)
|
benchmarkNonModifyingCode(100000000, calllRevertingContractWithInput, "call-reverting-100M", "", b)
|
||||||
|
|
||||||
//benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b)
|
//benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b)
|
||||||
//benchmarkNonModifyingCode(10000000, loopingCode, "loop-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 ###")
|
fmt.Println("### stack ###")
|
||||||
if len(st.data) > 0 {
|
if len(st.data) > 0 {
|
||||||
for i, val := range st.data {
|
for i, val := range st.data {
|
||||||
fmt.Printf("%-3d %v\n", i, val)
|
fmt.Printf("%-3d %s\n", i, val.String())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("-- empty --")
|
fmt.Println("-- empty --")
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2021 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
@ -14,212 +14,81 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// callTracer is a full blown transaction tracer that extracts and reports all
|
|
||||||
// the internal calls made by a transaction, along with any useful information.
|
// callFrameTracer uses the new call frame tracing methods to report useful information
|
||||||
|
// about internal messages of a transaction.
|
||||||
{
|
{
|
||||||
// callstack is the current recursive call stack of the EVM execution.
|
callstack: [{}],
|
||||||
callstack: [{}],
|
fault: function(log, db) {
|
||||||
|
var len = this.callstack.length
|
||||||
// descended tracks whether we've just descended from an outer transaction into
|
if (len > 1) {
|
||||||
// an inner call.
|
var call = this.callstack.pop()
|
||||||
descended: false,
|
if (this.callstack[len-1].calls === undefined) {
|
||||||
|
this.callstack[len-1].calls = []
|
||||||
// step is invoked for every opcode that the VM executes.
|
}
|
||||||
step: function(log, db) {
|
this.callstack[len-1].calls.push(call)
|
||||||
// Capture any errors immediately
|
}
|
||||||
var error = log.getError();
|
},
|
||||||
if (error !== undefined) {
|
result: function(ctx, db) {
|
||||||
this.fault(log, db);
|
// Prepare outer message info
|
||||||
return;
|
var result = {
|
||||||
}
|
type: ctx.type,
|
||||||
// We only care about system opcodes, faster if we pre-check once
|
from: toHex(ctx.from),
|
||||||
var syscall = (log.op.toNumber() & 0xf0) == 0xf0;
|
to: toHex(ctx.to),
|
||||||
if (syscall) {
|
value: '0x' + ctx.value.toString(16),
|
||||||
var op = log.op.toString();
|
gas: '0x' + bigInt(ctx.gas).toString(16),
|
||||||
}
|
gasUsed: '0x' + bigInt(ctx.gasUsed).toString(16),
|
||||||
// If a new contract is being created, add to the call stack
|
input: toHex(ctx.input),
|
||||||
if (syscall && (op == 'CREATE' || op == "CREATE2")) {
|
output: toHex(ctx.output),
|
||||||
var inOff = log.stack.peek(1).valueOf();
|
}
|
||||||
var inEnd = inOff + log.stack.peek(2).valueOf();
|
if (this.callstack[0].calls !== undefined) {
|
||||||
|
result.calls = this.callstack[0].calls
|
||||||
// Assemble the internal call report and store for completion
|
}
|
||||||
var call = {
|
if (this.callstack[0].error !== undefined) {
|
||||||
type: op,
|
result.error = this.callstack[0].error
|
||||||
from: toHex(log.contract.getAddress()),
|
} else if (ctx.error !== undefined) {
|
||||||
input: toHex(log.memory.slice(inOff, inEnd)),
|
result.error = ctx.error
|
||||||
gasIn: log.getGas(),
|
}
|
||||||
gasCost: log.getCost(),
|
if (result.error !== undefined && (result.error !== "execution reverted" || result.output ==="0x")) {
|
||||||
value: '0x' + log.stack.peek(0).toString(16)
|
delete result.output
|
||||||
};
|
}
|
||||||
this.callstack.push(call);
|
|
||||||
this.descended = true
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If a contract is being self destructed, gather that as a subcall too
|
|
||||||
if (syscall && op == 'SELFDESTRUCT') {
|
|
||||||
var left = this.callstack.length;
|
|
||||||
if (this.callstack[left-1].calls === undefined) {
|
|
||||||
this.callstack[left-1].calls = [];
|
|
||||||
}
|
|
||||||
this.callstack[left-1].calls.push({
|
|
||||||
type: op,
|
|
||||||
from: toHex(log.contract.getAddress()),
|
|
||||||
to: toHex(toAddress(log.stack.peek(0).toString(16))),
|
|
||||||
gasIn: log.getGas(),
|
|
||||||
gasCost: log.getCost(),
|
|
||||||
value: '0x' + db.getBalance(log.contract.getAddress()).toString(16)
|
|
||||||
});
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// If a new method invocation is being done, add to the call stack
|
|
||||||
if (syscall && (op == 'CALL' || op == 'CALLCODE' || op == 'DELEGATECALL' || op == 'STATICCALL')) {
|
|
||||||
// Skip any pre-compile invocations, those are just fancy opcodes
|
|
||||||
var to = toAddress(log.stack.peek(1).toString(16));
|
|
||||||
if (isPrecompiled(to)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var off = (op == 'DELEGATECALL' || op == 'STATICCALL' ? 0 : 1);
|
|
||||||
|
|
||||||
var inOff = log.stack.peek(2 + off).valueOf();
|
|
||||||
var inEnd = inOff + log.stack.peek(3 + off).valueOf();
|
|
||||||
|
|
||||||
// Assemble the internal call report and store for completion
|
|
||||||
var call = {
|
|
||||||
type: op,
|
|
||||||
from: toHex(log.contract.getAddress()),
|
|
||||||
to: toHex(to),
|
|
||||||
input: toHex(log.memory.slice(inOff, inEnd)),
|
|
||||||
gasIn: log.getGas(),
|
|
||||||
gasCost: log.getCost(),
|
|
||||||
outOff: log.stack.peek(4 + off).valueOf(),
|
|
||||||
outLen: log.stack.peek(5 + off).valueOf()
|
|
||||||
};
|
|
||||||
if (op != 'DELEGATECALL' && op != 'STATICCALL') {
|
|
||||||
call.value = '0x' + log.stack.peek(2).toString(16);
|
|
||||||
}
|
|
||||||
this.callstack.push(call);
|
|
||||||
this.descended = true
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If we've just descended into an inner call, retrieve it's true allowance. We
|
|
||||||
// need to extract if from within the call as there may be funky gas dynamics
|
|
||||||
// with regard to requested and actually given gas (2300 stipend, 63/64 rule).
|
|
||||||
if (this.descended) {
|
|
||||||
if (log.getDepth() >= this.callstack.length) {
|
|
||||||
this.callstack[this.callstack.length - 1].gas = log.getGas();
|
|
||||||
} else {
|
|
||||||
// TODO(karalabe): The call was made to a plain account. We currently don't
|
|
||||||
// have access to the true gas amount inside the call and so any amount will
|
|
||||||
// mostly be wrong since it depends on a lot of input args. Skip gas for now.
|
|
||||||
}
|
|
||||||
this.descended = false;
|
|
||||||
}
|
|
||||||
// If an existing call is returning, pop off the call stack
|
|
||||||
if (syscall && op == 'REVERT') {
|
|
||||||
this.callstack[this.callstack.length - 1].error = "execution reverted";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (log.getDepth() == this.callstack.length - 1) {
|
|
||||||
// Pop off the last call and get the execution results
|
|
||||||
var call = this.callstack.pop();
|
|
||||||
|
|
||||||
if (call.type == 'CREATE' || call.type == "CREATE2") {
|
|
||||||
// If the call was a CREATE, retrieve the contract address and output code
|
|
||||||
call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost - log.getGas()).toString(16);
|
|
||||||
delete call.gasIn; delete call.gasCost;
|
|
||||||
|
|
||||||
var ret = log.stack.peek(0);
|
|
||||||
if (!ret.equals(0)) {
|
|
||||||
call.to = toHex(toAddress(ret.toString(16)));
|
|
||||||
call.output = toHex(db.getCode(toAddress(ret.toString(16))));
|
|
||||||
} else if (call.error === undefined) {
|
|
||||||
call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If the call was a contract call, retrieve the gas usage and output
|
|
||||||
if (call.gas !== undefined) {
|
|
||||||
call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost + call.gas - log.getGas()).toString(16);
|
|
||||||
}
|
|
||||||
var ret = log.stack.peek(0);
|
|
||||||
if (!ret.equals(0)) {
|
|
||||||
call.output = toHex(log.memory.slice(call.outOff, call.outOff + call.outLen));
|
|
||||||
} else if (call.error === undefined) {
|
|
||||||
call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
|
|
||||||
}
|
|
||||||
delete call.gasIn; delete call.gasCost;
|
|
||||||
delete call.outOff; delete call.outLen;
|
|
||||||
}
|
|
||||||
if (call.gas !== undefined) {
|
|
||||||
call.gas = '0x' + bigInt(call.gas).toString(16);
|
|
||||||
}
|
|
||||||
// Inject the call into the previous one
|
|
||||||
var left = this.callstack.length;
|
|
||||||
if (this.callstack[left-1].calls === undefined) {
|
|
||||||
this.callstack[left-1].calls = [];
|
|
||||||
}
|
|
||||||
this.callstack[left-1].calls.push(call);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// fault is invoked when the actual execution of an opcode fails.
|
|
||||||
fault: function(log, db) {
|
|
||||||
// If the topmost call already reverted, don't handle the additional fault again
|
|
||||||
if (this.callstack[this.callstack.length - 1].error !== undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Pop off the just failed call
|
|
||||||
var call = this.callstack.pop();
|
|
||||||
call.error = log.getError();
|
|
||||||
|
|
||||||
// Consume all available gas and clean any leftovers
|
|
||||||
if (call.gas !== undefined) {
|
|
||||||
call.gas = '0x' + bigInt(call.gas).toString(16);
|
|
||||||
call.gasUsed = call.gas
|
|
||||||
}
|
|
||||||
delete call.gasIn; delete call.gasCost;
|
|
||||||
delete call.outOff; delete call.outLen;
|
|
||||||
|
|
||||||
// Flatten the failed call into its parent
|
|
||||||
var left = this.callstack.length;
|
|
||||||
if (left > 0) {
|
|
||||||
if (this.callstack[left-1].calls === undefined) {
|
|
||||||
this.callstack[left-1].calls = [];
|
|
||||||
}
|
|
||||||
this.callstack[left-1].calls.push(call);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Last call failed too, leave it in the stack
|
|
||||||
this.callstack.push(call);
|
|
||||||
},
|
|
||||||
|
|
||||||
// result is invoked when all the opcodes have been iterated over and returns
|
|
||||||
// the final result of the tracing.
|
|
||||||
result: function(ctx, db) {
|
|
||||||
var result = {
|
|
||||||
type: ctx.type,
|
|
||||||
from: toHex(ctx.from),
|
|
||||||
to: toHex(ctx.to),
|
|
||||||
value: '0x' + ctx.value.toString(16),
|
|
||||||
gas: '0x' + bigInt(ctx.gas).toString(16),
|
|
||||||
gasUsed: '0x' + bigInt(ctx.gasUsed).toString(16),
|
|
||||||
input: toHex(ctx.input),
|
|
||||||
output: toHex(ctx.output),
|
|
||||||
time: ctx.time,
|
|
||||||
};
|
|
||||||
if (this.callstack[0].calls !== undefined) {
|
|
||||||
result.calls = this.callstack[0].calls;
|
|
||||||
}
|
|
||||||
if (this.callstack[0].error !== undefined) {
|
|
||||||
result.error = this.callstack[0].error;
|
|
||||||
} else if (ctx.error !== undefined) {
|
|
||||||
result.error = ctx.error;
|
|
||||||
}
|
|
||||||
if (result.error !== undefined && (result.error !== "execution reverted" || result.output ==="0x")) {
|
|
||||||
delete result.output;
|
|
||||||
}
|
|
||||||
return this.finalize(result);
|
|
||||||
},
|
|
||||||
|
|
||||||
|
return this.finalize(result)
|
||||||
|
},
|
||||||
|
enter: function(frame) {
|
||||||
|
var call = {
|
||||||
|
type: frame.getType(),
|
||||||
|
from: toHex(frame.getFrom()),
|
||||||
|
to: toHex(frame.getTo()),
|
||||||
|
input: toHex(frame.getInput()),
|
||||||
|
gas: '0x' + bigInt(frame.getGas()).toString('16'),
|
||||||
|
}
|
||||||
|
if (frame.getValue() !== undefined){
|
||||||
|
call.value='0x' + bigInt(frame.getValue()).toString(16)
|
||||||
|
}
|
||||||
|
this.callstack.push(call)
|
||||||
|
},
|
||||||
|
exit: function(frameResult) {
|
||||||
|
var len = this.callstack.length
|
||||||
|
if (len > 1) {
|
||||||
|
var call = this.callstack.pop()
|
||||||
|
call.gasUsed = '0x' + bigInt(frameResult.getGasUsed()).toString('16')
|
||||||
|
var error = frameResult.getError()
|
||||||
|
if (error === undefined) {
|
||||||
|
call.output = toHex(frameResult.getOutput())
|
||||||
|
} else {
|
||||||
|
call.error = error
|
||||||
|
if (call.type === 'CREATE' || call.type === 'CREATE2') {
|
||||||
|
delete call.to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
len -= 1
|
||||||
|
if (this.callstack[len-1].calls === undefined) {
|
||||||
|
this.callstack[len-1].calls = []
|
||||||
|
}
|
||||||
|
this.callstack[len-1].calls.push(call)
|
||||||
|
}
|
||||||
|
},
|
||||||
// finalize recreates a call object using the final desired field oder for json
|
// finalize recreates a call object using the final desired field oder for json
|
||||||
// serialization. This is a nicety feature to pass meaningfully ordered results
|
// serialization. This is a nicety feature to pass meaningfully ordered results
|
||||||
// to users who don't interpret it, just display it.
|
// to users who don't interpret it, just display it.
|
||||||
|
@ -239,14 +108,14 @@
|
||||||
}
|
}
|
||||||
for (var key in sorted) {
|
for (var key in sorted) {
|
||||||
if (sorted[key] === undefined) {
|
if (sorted[key] === undefined) {
|
||||||
delete sorted[key];
|
delete sorted[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sorted.calls !== undefined) {
|
if (sorted.calls !== undefined) {
|
||||||
for (var i=0; i<sorted.calls.length; i++) {
|
for (var i=0; i<sorted.calls.length; i++) {
|
||||||
sorted.calls[i] = this.finalize(sorted.calls[i]);
|
sorted.calls[i] = this.finalize(sorted.calls[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sorted;
|
return sorted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,252 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// callTracer is a full blown transaction tracer that extracts and reports all
|
||||||
|
// the internal calls made by a transaction, along with any useful information.
|
||||||
|
{
|
||||||
|
// callstack is the current recursive call stack of the EVM execution.
|
||||||
|
callstack: [{}],
|
||||||
|
|
||||||
|
// descended tracks whether we've just descended from an outer transaction into
|
||||||
|
// an inner call.
|
||||||
|
descended: false,
|
||||||
|
|
||||||
|
// step is invoked for every opcode that the VM executes.
|
||||||
|
step: function(log, db) {
|
||||||
|
// Capture any errors immediately
|
||||||
|
var error = log.getError();
|
||||||
|
if (error !== undefined) {
|
||||||
|
this.fault(log, db);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// We only care about system opcodes, faster if we pre-check once
|
||||||
|
var syscall = (log.op.toNumber() & 0xf0) == 0xf0;
|
||||||
|
if (syscall) {
|
||||||
|
var op = log.op.toString();
|
||||||
|
}
|
||||||
|
// If a new contract is being created, add to the call stack
|
||||||
|
if (syscall && (op == 'CREATE' || op == "CREATE2")) {
|
||||||
|
var inOff = log.stack.peek(1).valueOf();
|
||||||
|
var inEnd = inOff + log.stack.peek(2).valueOf();
|
||||||
|
|
||||||
|
// Assemble the internal call report and store for completion
|
||||||
|
var call = {
|
||||||
|
type: op,
|
||||||
|
from: toHex(log.contract.getAddress()),
|
||||||
|
input: toHex(log.memory.slice(inOff, inEnd)),
|
||||||
|
gasIn: log.getGas(),
|
||||||
|
gasCost: log.getCost(),
|
||||||
|
value: '0x' + log.stack.peek(0).toString(16)
|
||||||
|
};
|
||||||
|
this.callstack.push(call);
|
||||||
|
this.descended = true
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If a contract is being self destructed, gather that as a subcall too
|
||||||
|
if (syscall && op == 'SELFDESTRUCT') {
|
||||||
|
var left = this.callstack.length;
|
||||||
|
if (this.callstack[left-1].calls === undefined) {
|
||||||
|
this.callstack[left-1].calls = [];
|
||||||
|
}
|
||||||
|
this.callstack[left-1].calls.push({
|
||||||
|
type: op,
|
||||||
|
from: toHex(log.contract.getAddress()),
|
||||||
|
to: toHex(toAddress(log.stack.peek(0).toString(16))),
|
||||||
|
gasIn: log.getGas(),
|
||||||
|
gasCost: log.getCost(),
|
||||||
|
value: '0x' + db.getBalance(log.contract.getAddress()).toString(16)
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If a new method invocation is being done, add to the call stack
|
||||||
|
if (syscall && (op == 'CALL' || op == 'CALLCODE' || op == 'DELEGATECALL' || op == 'STATICCALL')) {
|
||||||
|
// Skip any pre-compile invocations, those are just fancy opcodes
|
||||||
|
var to = toAddress(log.stack.peek(1).toString(16));
|
||||||
|
if (isPrecompiled(to)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var off = (op == 'DELEGATECALL' || op == 'STATICCALL' ? 0 : 1);
|
||||||
|
|
||||||
|
var inOff = log.stack.peek(2 + off).valueOf();
|
||||||
|
var inEnd = inOff + log.stack.peek(3 + off).valueOf();
|
||||||
|
|
||||||
|
// Assemble the internal call report and store for completion
|
||||||
|
var call = {
|
||||||
|
type: op,
|
||||||
|
from: toHex(log.contract.getAddress()),
|
||||||
|
to: toHex(to),
|
||||||
|
input: toHex(log.memory.slice(inOff, inEnd)),
|
||||||
|
gasIn: log.getGas(),
|
||||||
|
gasCost: log.getCost(),
|
||||||
|
outOff: log.stack.peek(4 + off).valueOf(),
|
||||||
|
outLen: log.stack.peek(5 + off).valueOf()
|
||||||
|
};
|
||||||
|
if (op != 'DELEGATECALL' && op != 'STATICCALL') {
|
||||||
|
call.value = '0x' + log.stack.peek(2).toString(16);
|
||||||
|
}
|
||||||
|
this.callstack.push(call);
|
||||||
|
this.descended = true
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If we've just descended into an inner call, retrieve it's true allowance. We
|
||||||
|
// need to extract if from within the call as there may be funky gas dynamics
|
||||||
|
// with regard to requested and actually given gas (2300 stipend, 63/64 rule).
|
||||||
|
if (this.descended) {
|
||||||
|
if (log.getDepth() >= this.callstack.length) {
|
||||||
|
this.callstack[this.callstack.length - 1].gas = log.getGas();
|
||||||
|
} else {
|
||||||
|
// TODO(karalabe): The call was made to a plain account. We currently don't
|
||||||
|
// have access to the true gas amount inside the call and so any amount will
|
||||||
|
// mostly be wrong since it depends on a lot of input args. Skip gas for now.
|
||||||
|
}
|
||||||
|
this.descended = false;
|
||||||
|
}
|
||||||
|
// If an existing call is returning, pop off the call stack
|
||||||
|
if (syscall && op == 'REVERT') {
|
||||||
|
this.callstack[this.callstack.length - 1].error = "execution reverted";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (log.getDepth() == this.callstack.length - 1) {
|
||||||
|
// Pop off the last call and get the execution results
|
||||||
|
var call = this.callstack.pop();
|
||||||
|
|
||||||
|
if (call.type == 'CREATE' || call.type == "CREATE2") {
|
||||||
|
// If the call was a CREATE, retrieve the contract address and output code
|
||||||
|
call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost - log.getGas()).toString(16);
|
||||||
|
delete call.gasIn; delete call.gasCost;
|
||||||
|
|
||||||
|
var ret = log.stack.peek(0);
|
||||||
|
if (!ret.equals(0)) {
|
||||||
|
call.to = toHex(toAddress(ret.toString(16)));
|
||||||
|
call.output = toHex(db.getCode(toAddress(ret.toString(16))));
|
||||||
|
} else if (call.error === undefined) {
|
||||||
|
call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the call was a contract call, retrieve the gas usage and output
|
||||||
|
if (call.gas !== undefined) {
|
||||||
|
call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost + call.gas - log.getGas()).toString(16);
|
||||||
|
}
|
||||||
|
var ret = log.stack.peek(0);
|
||||||
|
if (!ret.equals(0)) {
|
||||||
|
call.output = toHex(log.memory.slice(call.outOff, call.outOff + call.outLen));
|
||||||
|
} else if (call.error === undefined) {
|
||||||
|
call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
|
||||||
|
}
|
||||||
|
delete call.gasIn; delete call.gasCost;
|
||||||
|
delete call.outOff; delete call.outLen;
|
||||||
|
}
|
||||||
|
if (call.gas !== undefined) {
|
||||||
|
call.gas = '0x' + bigInt(call.gas).toString(16);
|
||||||
|
}
|
||||||
|
// Inject the call into the previous one
|
||||||
|
var left = this.callstack.length;
|
||||||
|
if (this.callstack[left-1].calls === undefined) {
|
||||||
|
this.callstack[left-1].calls = [];
|
||||||
|
}
|
||||||
|
this.callstack[left-1].calls.push(call);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// fault is invoked when the actual execution of an opcode fails.
|
||||||
|
fault: function(log, db) {
|
||||||
|
// If the topmost call already reverted, don't handle the additional fault again
|
||||||
|
if (this.callstack[this.callstack.length - 1].error !== undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Pop off the just failed call
|
||||||
|
var call = this.callstack.pop();
|
||||||
|
call.error = log.getError();
|
||||||
|
|
||||||
|
// Consume all available gas and clean any leftovers
|
||||||
|
if (call.gas !== undefined) {
|
||||||
|
call.gas = '0x' + bigInt(call.gas).toString(16);
|
||||||
|
call.gasUsed = call.gas
|
||||||
|
}
|
||||||
|
delete call.gasIn; delete call.gasCost;
|
||||||
|
delete call.outOff; delete call.outLen;
|
||||||
|
|
||||||
|
// Flatten the failed call into its parent
|
||||||
|
var left = this.callstack.length;
|
||||||
|
if (left > 0) {
|
||||||
|
if (this.callstack[left-1].calls === undefined) {
|
||||||
|
this.callstack[left-1].calls = [];
|
||||||
|
}
|
||||||
|
this.callstack[left-1].calls.push(call);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Last call failed too, leave it in the stack
|
||||||
|
this.callstack.push(call);
|
||||||
|
},
|
||||||
|
|
||||||
|
// result is invoked when all the opcodes have been iterated over and returns
|
||||||
|
// the final result of the tracing.
|
||||||
|
result: function(ctx, db) {
|
||||||
|
var result = {
|
||||||
|
type: ctx.type,
|
||||||
|
from: toHex(ctx.from),
|
||||||
|
to: toHex(ctx.to),
|
||||||
|
value: '0x' + ctx.value.toString(16),
|
||||||
|
gas: '0x' + bigInt(ctx.gas).toString(16),
|
||||||
|
gasUsed: '0x' + bigInt(ctx.gasUsed).toString(16),
|
||||||
|
input: toHex(ctx.input),
|
||||||
|
output: toHex(ctx.output),
|
||||||
|
time: ctx.time,
|
||||||
|
};
|
||||||
|
if (this.callstack[0].calls !== undefined) {
|
||||||
|
result.calls = this.callstack[0].calls;
|
||||||
|
}
|
||||||
|
if (this.callstack[0].error !== undefined) {
|
||||||
|
result.error = this.callstack[0].error;
|
||||||
|
} else if (ctx.error !== undefined) {
|
||||||
|
result.error = ctx.error;
|
||||||
|
}
|
||||||
|
if (result.error !== undefined && (result.error !== "execution reverted" || result.output ==="0x")) {
|
||||||
|
delete result.output;
|
||||||
|
}
|
||||||
|
return this.finalize(result);
|
||||||
|
},
|
||||||
|
|
||||||
|
// finalize recreates a call object using the final desired field oder for json
|
||||||
|
// serialization. This is a nicety feature to pass meaningfully ordered results
|
||||||
|
// to users who don't interpret it, just display it.
|
||||||
|
finalize: function(call) {
|
||||||
|
var sorted = {
|
||||||
|
type: call.type,
|
||||||
|
from: call.from,
|
||||||
|
to: call.to,
|
||||||
|
value: call.value,
|
||||||
|
gas: call.gas,
|
||||||
|
gasUsed: call.gasUsed,
|
||||||
|
input: call.input,
|
||||||
|
output: call.output,
|
||||||
|
error: call.error,
|
||||||
|
time: call.time,
|
||||||
|
calls: call.calls,
|
||||||
|
}
|
||||||
|
for (var key in sorted) {
|
||||||
|
if (sorted[key] === undefined) {
|
||||||
|
delete sorted[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sorted.calls !== undefined) {
|
||||||
|
for (var i=0; i<sorted.calls.length; i++) {
|
||||||
|
sorted.calls[i] = this.finalize(sorted.calls[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"genesis": {
|
||||||
|
"difficulty": "117067574",
|
||||||
|
"extraData": "0xd783010502846765746887676f312e372e33856c696e7578",
|
||||||
|
"gasLimit": "4712380",
|
||||||
|
"hash": "0xe05db05eeb3f288041ecb10a787df121c0ed69499355716e17c307de313a4486",
|
||||||
|
"miner": "0x0c062b329265c965deef1eede55183b3acb8f611",
|
||||||
|
"mixHash": "0xb669ae39118a53d2c65fd3b1e1d3850dd3f8c6842030698ed846a2762d68b61d",
|
||||||
|
"nonce": "0x2b469722b8e28c45",
|
||||||
|
"number": "24973",
|
||||||
|
"stateRoot": "0x532a5c3f75453a696428db078e32ae283c85cb97e4d8560dbdf022adac6df369",
|
||||||
|
"timestamp": "1479891145",
|
||||||
|
"totalDifficulty": "1892250259406",
|
||||||
|
"alloc": {
|
||||||
|
"0x6c06b16512b332e6cd8293a2974872674716ce18": {
|
||||||
|
"balance": "0x0",
|
||||||
|
"nonce": "1",
|
||||||
|
"code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900480632e1a7d4d146036575b6000565b34600057604e60048080359060200190919050506050565b005b3373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f19350505050505b5056",
|
||||||
|
"storage": {}
|
||||||
|
},
|
||||||
|
"0x66fdfd05e46126a07465ad24e40cc0597bc1ef31": {
|
||||||
|
"balance": "0x229ebbb36c3e0f20",
|
||||||
|
"nonce": "3",
|
||||||
|
"code": "0x",
|
||||||
|
"storage": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"chainId": 3,
|
||||||
|
"homesteadBlock": 0,
|
||||||
|
"daoForkSupport": true,
|
||||||
|
"eip150Block": 0,
|
||||||
|
"eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d",
|
||||||
|
"eip155Block": 10,
|
||||||
|
"eip158Block": 10,
|
||||||
|
"byzantiumBlock": 1700000,
|
||||||
|
"constantinopleBlock": 4230000,
|
||||||
|
"petersburgBlock": 4939394,
|
||||||
|
"istanbulBlock": 6485846,
|
||||||
|
"muirGlacierBlock": 7117117,
|
||||||
|
"ethash": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"number": "24974",
|
||||||
|
"difficulty": "117067574",
|
||||||
|
"timestamp": "1479891162",
|
||||||
|
"gasLimit": "4712388",
|
||||||
|
"miner": "0xc822ef32e6d26e170b70cf761e204c1806265914"
|
||||||
|
},
|
||||||
|
"input": "0xf889038504a81557008301f97e946c06b16512b332e6cd8293a2974872674716ce1880a42e1a7d4d00000000000000000000000000000000000000000000000014d1120d7b1600002aa0e2a6558040c5d72bc59f2fb62a38993a314c849cd22fb393018d2c5af3112095a01bdb6d7ba32263ccc2ecc880d38c49d9f0c5a72d8b7908e3122b31356d349745",
|
||||||
|
"result": {
|
||||||
|
"type": "CALL",
|
||||||
|
"from": "0x66fdfd05e46126a07465ad24e40cc0597bc1ef31",
|
||||||
|
"to": "0x6c06b16512b332e6cd8293a2974872674716ce18",
|
||||||
|
"value": "0x0",
|
||||||
|
"gas": "0x1a466",
|
||||||
|
"gasUsed": "0x1dc6",
|
||||||
|
"input": "0x2e1a7d4d00000000000000000000000000000000000000000000000014d1120d7b160000",
|
||||||
|
"output": "0x",
|
||||||
|
"calls": []
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
{
|
||||||
|
"context": {
|
||||||
|
"difficulty": "3502894804",
|
||||||
|
"gasLimit": "4722976",
|
||||||
|
"miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724",
|
||||||
|
"number": "2289806",
|
||||||
|
"timestamp": "1513601314"
|
||||||
|
},
|
||||||
|
"genesis": {
|
||||||
|
"alloc": {
|
||||||
|
"0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": {
|
||||||
|
"balance": "0x0",
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "22",
|
||||||
|
"storage": {}
|
||||||
|
},
|
||||||
|
"0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": {
|
||||||
|
"balance": "0x4d87094125a369d9bd5",
|
||||||
|
"code": "0x61deadff",
|
||||||
|
"nonce": "1",
|
||||||
|
"storage": {}
|
||||||
|
},
|
||||||
|
"0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": {
|
||||||
|
"balance": "0x1780d77678137ac1b775",
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "29072",
|
||||||
|
"storage": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"byzantiumBlock": 1700000,
|
||||||
|
"chainId": 3,
|
||||||
|
"daoForkSupport": true,
|
||||||
|
"eip150Block": 0,
|
||||||
|
"eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d",
|
||||||
|
"eip155Block": 10,
|
||||||
|
"eip158Block": 10,
|
||||||
|
"ethash": {},
|
||||||
|
"homesteadBlock": 0
|
||||||
|
},
|
||||||
|
"difficulty": "3509749784",
|
||||||
|
"extraData": "0x4554482e45544846414e532e4f52472d4641313738394444",
|
||||||
|
"gasLimit": "4727564",
|
||||||
|
"hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440",
|
||||||
|
"miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3",
|
||||||
|
"mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada",
|
||||||
|
"nonce": "0x4eb12e19c16d43da",
|
||||||
|
"number": "2289805",
|
||||||
|
"stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f",
|
||||||
|
"timestamp": "1513601261",
|
||||||
|
"totalDifficulty": "7143276353481064"
|
||||||
|
},
|
||||||
|
"input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4",
|
||||||
|
"result": {
|
||||||
|
"calls": [
|
||||||
|
{
|
||||||
|
"from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe",
|
||||||
|
"gas": "0x0",
|
||||||
|
"gasUsed": "0x0",
|
||||||
|
"input": "0x",
|
||||||
|
"to": "0x000000000000000000000000000000000000dEaD",
|
||||||
|
"type": "SELFDESTRUCT",
|
||||||
|
"value": "0x4d87094125a369d9bd5"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb",
|
||||||
|
"gas": "0x10738",
|
||||||
|
"gasUsed": "0x7533",
|
||||||
|
"input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5",
|
||||||
|
"output": "0x",
|
||||||
|
"to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe",
|
||||||
|
"type": "CALL",
|
||||||
|
"value": "0x0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
{
|
||||||
|
"context": {
|
||||||
|
"difficulty": "3502894804",
|
||||||
|
"gasLimit": "4722976",
|
||||||
|
"miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724",
|
||||||
|
"number": "2289806",
|
||||||
|
"timestamp": "1513601314"
|
||||||
|
},
|
||||||
|
"genesis": {
|
||||||
|
"alloc": {
|
||||||
|
"0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": {
|
||||||
|
"balance": "0x0",
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "22",
|
||||||
|
"storage": {}
|
||||||
|
},
|
||||||
|
"0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": {
|
||||||
|
"balance": "0x4d87094125a369d9bd5",
|
||||||
|
"code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029",
|
||||||
|
"nonce": "1",
|
||||||
|
"storage": {
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb",
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000",
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c",
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": {
|
||||||
|
"balance": "0x1780d77678137ac1b775",
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "29072",
|
||||||
|
"storage": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"byzantiumBlock": 1700000,
|
||||||
|
"chainId": 3,
|
||||||
|
"daoForkSupport": true,
|
||||||
|
"eip150Block": 0,
|
||||||
|
"eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d",
|
||||||
|
"eip155Block": 10,
|
||||||
|
"eip158Block": 10,
|
||||||
|
"ethash": {},
|
||||||
|
"homesteadBlock": 0
|
||||||
|
},
|
||||||
|
"difficulty": "3509749784",
|
||||||
|
"extraData": "0x4554482e45544846414e532e4f52472d4641313738394444",
|
||||||
|
"gasLimit": "4727564",
|
||||||
|
"hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440",
|
||||||
|
"miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3",
|
||||||
|
"mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada",
|
||||||
|
"nonce": "0x4eb12e19c16d43da",
|
||||||
|
"number": "2289805",
|
||||||
|
"stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f",
|
||||||
|
"timestamp": "1513601261",
|
||||||
|
"totalDifficulty": "7143276353481064"
|
||||||
|
},
|
||||||
|
"input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4",
|
||||||
|
"result": {
|
||||||
|
"calls": [
|
||||||
|
{
|
||||||
|
"from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe",
|
||||||
|
"gas": "0x6d05",
|
||||||
|
"gasUsed": "0x0",
|
||||||
|
"input": "0x",
|
||||||
|
"to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5",
|
||||||
|
"type": "CALL",
|
||||||
|
"value": "0x6f05b59d3b20000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb",
|
||||||
|
"gas": "0x10738",
|
||||||
|
"gasUsed": "0x3ef9",
|
||||||
|
"input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5",
|
||||||
|
"output": "0x0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
"to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe",
|
||||||
|
"type": "CALL",
|
||||||
|
"value": "0x0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"context": {
|
||||||
|
"difficulty": "3755480783",
|
||||||
|
"gasLimit": "5401723",
|
||||||
|
"miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511",
|
||||||
|
"number": "2294702",
|
||||||
|
"timestamp": "1513676146"
|
||||||
|
},
|
||||||
|
"genesis": {
|
||||||
|
"alloc": {
|
||||||
|
"0x13e4acefe6a6700604929946e70e6443e4e73447": {
|
||||||
|
"balance": "0xcf3e0938579f000",
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "9",
|
||||||
|
"storage": {}
|
||||||
|
},
|
||||||
|
"0x7dc9c9730689ff0b0fd506c67db815f12d90a448": {
|
||||||
|
"balance": "0x0",
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "0",
|
||||||
|
"storage": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"byzantiumBlock": 1700000,
|
||||||
|
"chainId": 3,
|
||||||
|
"daoForkSupport": true,
|
||||||
|
"eip150Block": 0,
|
||||||
|
"eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d",
|
||||||
|
"eip155Block": 10,
|
||||||
|
"eip158Block": 10,
|
||||||
|
"ethash": {},
|
||||||
|
"homesteadBlock": 0
|
||||||
|
},
|
||||||
|
"difficulty": "3757315409",
|
||||||
|
"extraData": "0x566961425443",
|
||||||
|
"gasLimit": "5406414",
|
||||||
|
"hash": "0xae107f592eebdd9ff8d6ba00363676096e6afb0e1007a7d3d0af88173077378d",
|
||||||
|
"miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511",
|
||||||
|
"mixHash": "0xc927aa05a38bc3de864e95c33b3ae559d3f39c4ccd51cef6f113f9c50ba0caf1",
|
||||||
|
"nonce": "0x93363bbd2c95f410",
|
||||||
|
"number": "2294701",
|
||||||
|
"stateRoot": "0x6b6737d5bde8058990483e915866bd1578014baeff57bd5e4ed228a2bfad635c",
|
||||||
|
"timestamp": "1513676127",
|
||||||
|
"totalDifficulty": "7160808139332585"
|
||||||
|
},
|
||||||
|
"input": "0xf907ef098504e3b29200830897be8080b9079c606060405260405160208061077c83398101604052808051906020019091905050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415151561007d57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506001600460006101000a81548160ff02191690831515021790555050610653806101296000396000f300606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806305e4382a146100855780631c02708d146100ae5780632e1a7d4d146100c35780635114cb52146100e6578063a37dda2c146100fe578063ae200e7914610153578063b5769f70146101a8575b005b341561009057600080fd5b6100986101d1565b6040518082815260200191505060405180910390f35b34156100b957600080fd5b6100c16101d7565b005b34156100ce57600080fd5b6100e460048080359060200190919050506102eb565b005b6100fc6004808035906020019091905050610513565b005b341561010957600080fd5b6101116105d6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b6101666105fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101b357600080fd5b6101bb610621565b6040518082815260200191505060405180910390f35b60025481565b60011515600460009054906101000a900460ff1615151415156101f957600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102a15750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156102ac57600080fd5b6000600460006101000a81548160ff0219169083151502179055506003543073ffffffffffffffffffffffffffffffffffffffff163103600281905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806103935750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b151561039e57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561048357600060025411801561040757506002548111155b151561041257600080fd5b80600254036002819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561047e57600080fd5b610510565b600060035411801561049757506003548111155b15156104a257600080fd5b8060035403600381905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561050f57600080fd5b5b50565b60011515600460009054906101000a900460ff16151514151561053557600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614801561059657506003548160035401115b80156105bd575080600354013073ffffffffffffffffffffffffffffffffffffffff163110155b15156105c857600080fd5b806003540160038190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600354815600a165627a7a72305820c3b849e8440987ce43eae3097b77672a69234d516351368b03fe5b7de03807910029000000000000000000000000c65e620a3a55451316168d57e268f5702ef56a1129a01060f46676a5dff6f407f0f51eb6f37f5c8c54e238c70221e18e65fc29d3ea65a0557b01c50ff4ffaac8ed6e5d31237a4ecbac843ab1bfe8bb0165a0060df7c54f",
|
||||||
|
"result": {
|
||||||
|
"from": "0x13e4acefe6a6700604929946e70e6443e4e73447",
|
||||||
|
"gas": "0x5e106",
|
||||||
|
"gasUsed": "0x5e106",
|
||||||
|
"input": "0x606060405260405160208061077c83398101604052808051906020019091905050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415151561007d57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506001600460006101000a81548160ff02191690831515021790555050610653806101296000396000f300606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806305e4382a146100855780631c02708d146100ae5780632e1a7d4d146100c35780635114cb52146100e6578063a37dda2c146100fe578063ae200e7914610153578063b5769f70146101a8575b005b341561009057600080fd5b6100986101d1565b6040518082815260200191505060405180910390f35b34156100b957600080fd5b6100c16101d7565b005b34156100ce57600080fd5b6100e460048080359060200190919050506102eb565b005b6100fc6004808035906020019091905050610513565b005b341561010957600080fd5b6101116105d6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b6101666105fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101b357600080fd5b6101bb610621565b6040518082815260200191505060405180910390f35b60025481565b60011515600460009054906101000a900460ff1615151415156101f957600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102a15750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156102ac57600080fd5b6000600460006101000a81548160ff0219169083151502179055506003543073ffffffffffffffffffffffffffffffffffffffff163103600281905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806103935750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b151561039e57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561048357600060025411801561040757506002548111155b151561041257600080fd5b80600254036002819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561047e57600080fd5b610510565b600060035411801561049757506003548111155b15156104a257600080fd5b8060035403600381905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561050f57600080fd5b5b50565b60011515600460009054906101000a900460ff16151514151561053557600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614801561059657506003548160035401115b80156105bd575080600354013073ffffffffffffffffffffffffffffffffffffffff163110155b15156105c857600080fd5b806003540160038190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600354815600a165627a7a72305820c3b849e8440987ce43eae3097b77672a69234d516351368b03fe5b7de03807910029000000000000000000000000c65e620a3a55451316168d57e268f5702ef56a11",
|
||||||
|
"output": "0x606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806305e4382a146100855780631c02708d146100ae5780632e1a7d4d146100c35780635114cb52146100e6578063a37dda2c146100fe578063ae200e7914610153578063b5769f70146101a8575b005b341561009057600080fd5b6100986101d1565b6040518082815260200191505060405180910390f35b34156100b957600080fd5b6100c16101d7565b005b34156100ce57600080fd5b6100e460048080359060200190919050506102eb565b005b6100fc6004808035906020019091905050610513565b005b341561010957600080fd5b6101116105d6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b6101666105fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101b357600080fd5b6101bb610621565b6040518082815260200191505060405180910390f35b60025481565b60011515600460009054906101000a900460ff1615151415156101f957600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102a15750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156102ac57600080fd5b6000600460006101000a81548160ff0219169083151502179055506003543073ffffffffffffffffffffffffffffffffffffffff163103600281905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806103935750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b151561039e57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561048357600060025411801561040757506002548111155b151561041257600080fd5b80600254036002819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561047e57600080fd5b610510565b600060035411801561049757506003548111155b15156104a257600080fd5b8060035403600381905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561050f57600080fd5b5b50565b60011515600460009054906101000a900460ff16151514151561053557600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614801561059657506003548160035401115b80156105bd575080600354013073ffffffffffffffffffffffffffffffffffffffff163110155b15156105c857600080fd5b806003540160038190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600354815600a165627a7a72305820c3b849e8440987ce43eae3097b77672a69234d516351368b03fe5b7de03807910029",
|
||||||
|
"to": "0x7dc9c9730689ff0b0fd506c67db815f12d90a448",
|
||||||
|
"type": "CREATE",
|
||||||
|
"value": "0x0"
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"context": {
|
||||||
|
"difficulty": "3665057456",
|
||||||
|
"gasLimit": "5232723",
|
||||||
|
"miner": "0xf4d8e706cfb25c0decbbdd4d2e2cc10c66376a3f",
|
||||||
|
"number": "2294501",
|
||||||
|
"timestamp": "1513673601"
|
||||||
|
},
|
||||||
|
"genesis": {
|
||||||
|
"alloc": {
|
||||||
|
"0x0f6cef2b7fbb504782e35aa82a2207e816a2b7a9": {
|
||||||
|
"balance": "0x2a3fc32bcc019283",
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "10",
|
||||||
|
"storage": {}
|
||||||
|
},
|
||||||
|
"0xabbcd5b340c80b5f1c0545c04c987b87310296ae": {
|
||||||
|
"balance": "0x0",
|
||||||
|
"code": "0x606060405236156100755763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416632d0335ab811461007a578063548db174146100ab5780637f649783146100fc578063b092145e1461014d578063c3f44c0a14610186578063c47cf5de14610203575b600080fd5b341561008557600080fd5b610099600160a060020a0360043516610270565b60405190815260200160405180910390f35b34156100b657600080fd5b6100fa600460248135818101908301358060208181020160405190810160405280939291908181526020018383602002808284375094965061028f95505050505050565b005b341561010757600080fd5b6100fa600460248135818101908301358060208181020160405190810160405280939291908181526020018383602002808284375094965061029e95505050505050565b005b341561015857600080fd5b610172600160a060020a03600435811690602435166102ad565b604051901515815260200160405180910390f35b341561019157600080fd5b6100fa6004803560ff1690602480359160443591606435600160a060020a0316919060a49060843590810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965050509235600160a060020a031692506102cd915050565b005b341561020e57600080fd5b61025460046024813581810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965061056a95505050505050565b604051600160a060020a03909116815260200160405180910390f35b600160a060020a0381166000908152602081905260409020545b919050565b61029a816000610594565b5b50565b61029a816001610594565b5b50565b600160209081526000928352604080842090915290825290205460ff1681565b60008080600160a060020a038416158061030d5750600160a060020a038085166000908152600160209081526040808320339094168352929052205460ff165b151561031857600080fd5b6103218561056a565b600160a060020a038116600090815260208190526040808220549295507f19000000000000000000000000000000000000000000000000000000000000009230918891908b908b90517fff000000000000000000000000000000000000000000000000000000000000008089168252871660018201526c01000000000000000000000000600160a060020a038088168202600284015286811682026016840152602a8301869052841602604a820152605e810182805190602001908083835b6020831061040057805182525b601f1990920191602091820191016103e0565b6001836020036101000a0380198251168184511617909252505050919091019850604097505050505050505051809103902091506001828a8a8a6040516000815260200160405260006040516020015260405193845260ff90921660208085019190915260408085019290925260608401929092526080909201915160208103908084039060008661646e5a03f1151561049957600080fd5b5050602060405103519050600160a060020a03838116908216146104bc57600080fd5b600160a060020a0380841660009081526020819052604090819020805460010190559087169086905180828051906020019080838360005b8381101561050d5780820151818401525b6020016104f4565b50505050905090810190601f16801561053a5780820380516001836020036101000a031916815260200191505b5091505060006040518083038160008661646e5a03f1915050151561055e57600080fd5b5b505050505050505050565b600060248251101561057e5750600061028a565b600160a060020a0360248301511690505b919050565b60005b825181101561060157600160a060020a033316600090815260016020526040812083918584815181106105c657fe5b90602001906020020151600160a060020a031681526020810191909152604001600020805460ff19169115159190911790555b600101610597565b5b5050505600a165627a7a723058200027e8b695e9d2dea9f3629519022a69f3a1d23055ce86406e686ea54f31ee9c0029",
|
||||||
|
"nonce": "1",
|
||||||
|
"storage": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"byzantiumBlock": 1700000,
|
||||||
|
"chainId": 3,
|
||||||
|
"daoForkSupport": true,
|
||||||
|
"eip150Block": 0,
|
||||||
|
"eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d",
|
||||||
|
"eip155Block": 10,
|
||||||
|
"eip158Block": 10,
|
||||||
|
"ethash": {},
|
||||||
|
"homesteadBlock": 0
|
||||||
|
},
|
||||||
|
"difficulty": "3672229776",
|
||||||
|
"extraData": "0x4554482e45544846414e532e4f52472d4641313738394444",
|
||||||
|
"gasLimit": "5227619",
|
||||||
|
"hash": "0xa07b3d6c6bf63f5f981016db9f2d1d93033833f2c17e8bf7209e85f1faf08076",
|
||||||
|
"miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3",
|
||||||
|
"mixHash": "0x806e151ce2817be922e93e8d5921fa0f0d0fd213d6b2b9a3fa17458e74a163d0",
|
||||||
|
"nonce": "0xbc5d43adc2c30c7d",
|
||||||
|
"number": "2294500",
|
||||||
|
"stateRoot": "0xca645b335888352ef9d8b1ef083e9019648180b259026572e3139717270de97d",
|
||||||
|
"timestamp": "1513673552",
|
||||||
|
"totalDifficulty": "7160066586979149"
|
||||||
|
},
|
||||||
|
"input": "0xf9018b0a8505d21dba00832dc6c094abbcd5b340c80b5f1c0545c04c987b87310296ae80b9012473b40a5c000000000000000000000000400de2e016bda6577407dfc379faba9899bc73ef0000000000000000000000002cc31912b2b0f3075a87b3640923d45a26cef3ee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064d79d8e6c7265636f76657279416464726573730000000000000000000000000000000000000000000000000000000000383e3ec32dc0f66d8fe60dbdc2f6815bdf73a988383e3ec32dc0f66d8fe60dbdc2f6815bdf73a988000000000000000000000000000000000000000000000000000000000000000000000000000000001ba0fd659d76a4edbd2a823e324c93f78ad6803b30ff4a9c8bce71ba82798975c70ca06571eecc0b765688ec6c78942c5ee8b585e00988c0141b518287e9be919bc48a",
|
||||||
|
"result": {
|
||||||
|
"error": "execution reverted",
|
||||||
|
"from": "0x0f6cef2b7fbb504782e35aa82a2207e816a2b7a9",
|
||||||
|
"gas": "0x2d55e8",
|
||||||
|
"gasUsed": "0xc3",
|
||||||
|
"input": "0x73b40a5c000000000000000000000000400de2e016bda6577407dfc379faba9899bc73ef0000000000000000000000002cc31912b2b0f3075a87b3640923d45a26cef3ee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064d79d8e6c7265636f76657279416464726573730000000000000000000000000000000000000000000000000000000000383e3ec32dc0f66d8fe60dbdc2f6815bdf73a988383e3ec32dc0f66d8fe60dbdc2f6815bdf73a98800000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"to": "0xabbcd5b340c80b5f1c0545c04c987b87310296ae",
|
||||||
|
"type": "CALL",
|
||||||
|
"value": "0x0"
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,73 @@
|
||||||
|
{
|
||||||
|
"context": {
|
||||||
|
"difficulty": "3502894804",
|
||||||
|
"gasLimit": "4722976",
|
||||||
|
"miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724",
|
||||||
|
"number": "2289806",
|
||||||
|
"timestamp": "1513601314"
|
||||||
|
},
|
||||||
|
"genesis": {
|
||||||
|
"alloc": {
|
||||||
|
"0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": {
|
||||||
|
"balance": "0x0",
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "22",
|
||||||
|
"storage": {}
|
||||||
|
},
|
||||||
|
"0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": {
|
||||||
|
"balance": "0x4d87094125a369d9bd5",
|
||||||
|
"code": "0x61deadff",
|
||||||
|
"nonce": "1",
|
||||||
|
"storage": {}
|
||||||
|
},
|
||||||
|
"0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": {
|
||||||
|
"balance": "0x1780d77678137ac1b775",
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "29072",
|
||||||
|
"storage": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"byzantiumBlock": 1700000,
|
||||||
|
"chainId": 3,
|
||||||
|
"daoForkSupport": true,
|
||||||
|
"eip150Block": 0,
|
||||||
|
"eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d",
|
||||||
|
"eip155Block": 10,
|
||||||
|
"eip158Block": 10,
|
||||||
|
"ethash": {},
|
||||||
|
"homesteadBlock": 0
|
||||||
|
},
|
||||||
|
"difficulty": "3509749784",
|
||||||
|
"extraData": "0x4554482e45544846414e532e4f52472d4641313738394444",
|
||||||
|
"gasLimit": "4727564",
|
||||||
|
"hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440",
|
||||||
|
"miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3",
|
||||||
|
"mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada",
|
||||||
|
"nonce": "0x4eb12e19c16d43da",
|
||||||
|
"number": "2289805",
|
||||||
|
"stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f",
|
||||||
|
"timestamp": "1513601261",
|
||||||
|
"totalDifficulty": "7143276353481064"
|
||||||
|
},
|
||||||
|
"input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4",
|
||||||
|
"result": {
|
||||||
|
"calls": [
|
||||||
|
{
|
||||||
|
"from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe",
|
||||||
|
"input": "0x",
|
||||||
|
"to": "0x000000000000000000000000000000000000dEaD",
|
||||||
|
"type": "SELFDESTRUCT",
|
||||||
|
"value": "0x4d87094125a369d9bd5"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb",
|
||||||
|
"gas": "0x10738",
|
||||||
|
"gasUsed": "0x7533",
|
||||||
|
"input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5",
|
||||||
|
"output": "0x",
|
||||||
|
"to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe",
|
||||||
|
"type": "CALL",
|
||||||
|
"value": "0x0"
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -284,6 +284,85 @@ func (cw *contractWrapper) pushObject(vm *duktape.Context) {
|
||||||
vm.PutPropString(obj, "getInput")
|
vm.PutPropString(obj, "getInput")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type frame struct {
|
||||||
|
typ *string
|
||||||
|
from *common.Address
|
||||||
|
to *common.Address
|
||||||
|
input []byte
|
||||||
|
gas *uint
|
||||||
|
value *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFrame() *frame {
|
||||||
|
return &frame{
|
||||||
|
typ: new(string),
|
||||||
|
from: new(common.Address),
|
||||||
|
to: new(common.Address),
|
||||||
|
gas: new(uint),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *frame) pushObject(vm *duktape.Context) {
|
||||||
|
obj := vm.PushObject()
|
||||||
|
|
||||||
|
vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.typ); return 1 })
|
||||||
|
vm.PutPropString(obj, "getType")
|
||||||
|
|
||||||
|
vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.from); return 1 })
|
||||||
|
vm.PutPropString(obj, "getFrom")
|
||||||
|
|
||||||
|
vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.to); return 1 })
|
||||||
|
vm.PutPropString(obj, "getTo")
|
||||||
|
|
||||||
|
vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, f.input); return 1 })
|
||||||
|
vm.PutPropString(obj, "getInput")
|
||||||
|
|
||||||
|
vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.gas); return 1 })
|
||||||
|
vm.PutPropString(obj, "getGas")
|
||||||
|
|
||||||
|
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||||
|
if f.value != nil {
|
||||||
|
pushValue(ctx, f.value)
|
||||||
|
} else {
|
||||||
|
ctx.PushUndefined()
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
})
|
||||||
|
vm.PutPropString(obj, "getValue")
|
||||||
|
}
|
||||||
|
|
||||||
|
type frameResult struct {
|
||||||
|
gasUsed *uint
|
||||||
|
output []byte
|
||||||
|
errorValue *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFrameResult() *frameResult {
|
||||||
|
return &frameResult{
|
||||||
|
gasUsed: new(uint),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *frameResult) pushObject(vm *duktape.Context) {
|
||||||
|
obj := vm.PushObject()
|
||||||
|
|
||||||
|
vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *r.gasUsed); return 1 })
|
||||||
|
vm.PutPropString(obj, "getGasUsed")
|
||||||
|
|
||||||
|
vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, r.output); return 1 })
|
||||||
|
vm.PutPropString(obj, "getOutput")
|
||||||
|
|
||||||
|
vm.PushGoFunction(func(ctx *duktape.Context) int {
|
||||||
|
if r.errorValue != nil {
|
||||||
|
pushValue(ctx, *r.errorValue)
|
||||||
|
} else {
|
||||||
|
ctx.PushUndefined()
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
})
|
||||||
|
vm.PutPropString(obj, "getError")
|
||||||
|
}
|
||||||
|
|
||||||
// Tracer provides an implementation of Tracer that evaluates a Javascript
|
// Tracer provides an implementation of Tracer that evaluates a Javascript
|
||||||
// function for each VM execution step.
|
// function for each VM execution step.
|
||||||
type Tracer struct {
|
type Tracer struct {
|
||||||
|
@ -305,6 +384,9 @@ type Tracer struct {
|
||||||
errorValue *string // Swappable error value wrapped by a log accessor
|
errorValue *string // Swappable error value wrapped by a log accessor
|
||||||
refundValue *uint // Swappable refund value wrapped by a log accessor
|
refundValue *uint // Swappable refund value wrapped by a log accessor
|
||||||
|
|
||||||
|
frame *frame // Represents entry into call frame. Fields are swappable
|
||||||
|
frameResult *frameResult // Represents exit from a call frame. Fields are swappable
|
||||||
|
|
||||||
ctx map[string]interface{} // Transaction context gathered throughout execution
|
ctx map[string]interface{} // Transaction context gathered throughout execution
|
||||||
err error // Error, if one has occurred
|
err error // Error, if one has occurred
|
||||||
|
|
||||||
|
@ -312,6 +394,8 @@ type Tracer struct {
|
||||||
reason error // Textual reason for the interruption
|
reason error // Textual reason for the interruption
|
||||||
|
|
||||||
activePrecompiles []common.Address // Updated on CaptureStart based on given rules
|
activePrecompiles []common.Address // Updated on CaptureStart based on given rules
|
||||||
|
traceSteps bool // When true, will invoke step() on each opcode
|
||||||
|
traceCallFrames bool // When true, will invoke enter() and exit() js funcs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context contains some contextual infos for a transaction execution that is not
|
// Context contains some contextual infos for a transaction execution that is not
|
||||||
|
@ -343,6 +427,8 @@ func New(code string, ctx *Context) (*Tracer, error) {
|
||||||
costValue: new(uint),
|
costValue: new(uint),
|
||||||
depthValue: new(uint),
|
depthValue: new(uint),
|
||||||
refundValue: new(uint),
|
refundValue: new(uint),
|
||||||
|
frame: newFrame(),
|
||||||
|
frameResult: newFrameResult(),
|
||||||
}
|
}
|
||||||
if ctx.BlockHash != (common.Hash{}) {
|
if ctx.BlockHash != (common.Hash{}) {
|
||||||
tracer.ctx["blockHash"] = ctx.BlockHash
|
tracer.ctx["blockHash"] = ctx.BlockHash
|
||||||
|
@ -450,9 +536,7 @@ func New(code string, ctx *Context) (*Tracer, error) {
|
||||||
}
|
}
|
||||||
tracer.tracerObject = 0 // yeah, nice, eval can't return the index itself
|
tracer.tracerObject = 0 // yeah, nice, eval can't return the index itself
|
||||||
|
|
||||||
if !tracer.vm.GetPropString(tracer.tracerObject, "step") {
|
hasStep := tracer.vm.GetPropString(tracer.tracerObject, "step")
|
||||||
return nil, fmt.Errorf("trace object must expose a function step()")
|
|
||||||
}
|
|
||||||
tracer.vm.Pop()
|
tracer.vm.Pop()
|
||||||
|
|
||||||
if !tracer.vm.GetPropString(tracer.tracerObject, "fault") {
|
if !tracer.vm.GetPropString(tracer.tracerObject, "fault") {
|
||||||
|
@ -465,6 +549,23 @@ func New(code string, ctx *Context) (*Tracer, error) {
|
||||||
}
|
}
|
||||||
tracer.vm.Pop()
|
tracer.vm.Pop()
|
||||||
|
|
||||||
|
hasEnter := tracer.vm.GetPropString(tracer.tracerObject, "enter")
|
||||||
|
tracer.vm.Pop()
|
||||||
|
hasExit := tracer.vm.GetPropString(tracer.tracerObject, "exit")
|
||||||
|
tracer.vm.Pop()
|
||||||
|
|
||||||
|
if hasEnter != hasExit {
|
||||||
|
return nil, fmt.Errorf("trace object must expose either both or none of enter() and exit()")
|
||||||
|
}
|
||||||
|
if !hasStep {
|
||||||
|
// If there's no step function, the enter and exit must be present
|
||||||
|
if !hasEnter {
|
||||||
|
return nil, fmt.Errorf("trace object must expose either step() or both enter() and exit()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tracer.traceCallFrames = hasEnter
|
||||||
|
tracer.traceSteps = hasStep
|
||||||
|
|
||||||
// Tracer is valid, inject the big int library to access large numbers
|
// Tracer is valid, inject the big int library to access large numbers
|
||||||
tracer.vm.EvalString(bigIntegerJS)
|
tracer.vm.EvalString(bigIntegerJS)
|
||||||
tracer.vm.PutGlobalString("bigInt")
|
tracer.vm.PutGlobalString("bigInt")
|
||||||
|
@ -513,6 +614,12 @@ func New(code string, ctx *Context) (*Tracer, error) {
|
||||||
|
|
||||||
tracer.vm.PutPropString(tracer.stateObject, "log")
|
tracer.vm.PutPropString(tracer.stateObject, "log")
|
||||||
|
|
||||||
|
tracer.frame.pushObject(tracer.vm)
|
||||||
|
tracer.vm.PutPropString(tracer.stateObject, "frame")
|
||||||
|
|
||||||
|
tracer.frameResult.pushObject(tracer.vm)
|
||||||
|
tracer.vm.PutPropString(tracer.stateObject, "frameResult")
|
||||||
|
|
||||||
tracer.dbWrapper.pushObject(tracer.vm)
|
tracer.dbWrapper.pushObject(tracer.vm)
|
||||||
tracer.vm.PutPropString(tracer.stateObject, "db")
|
tracer.vm.PutPropString(tracer.stateObject, "db")
|
||||||
|
|
||||||
|
@ -594,6 +701,9 @@ func (jst *Tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr
|
||||||
|
|
||||||
// CaptureState implements the Tracer interface to trace a single step of VM execution.
|
// 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, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||||
|
if !jst.traceSteps {
|
||||||
|
return
|
||||||
|
}
|
||||||
if jst.err != nil {
|
if jst.err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -650,41 +760,70 @@ func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, er
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
|
||||||
|
func (jst *Tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||||
|
if !jst.traceCallFrames {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if jst.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If tracing was interrupted, set the error and stop
|
||||||
|
if atomic.LoadUint32(&jst.interrupt) > 0 {
|
||||||
|
jst.err = jst.reason
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
*jst.frame.typ = typ.String()
|
||||||
|
*jst.frame.from = from
|
||||||
|
*jst.frame.to = to
|
||||||
|
jst.frame.input = common.CopyBytes(input)
|
||||||
|
*jst.frame.gas = uint(gas)
|
||||||
|
jst.frame.value = nil
|
||||||
|
if value != nil {
|
||||||
|
jst.frame.value = new(big.Int).SetBytes(value.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := jst.call(true, "enter", "frame"); err != nil {
|
||||||
|
jst.err = wrapError("enter", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaptureExit is called when EVM exits a scope, even if the scope didn't
|
||||||
|
// execute any code.
|
||||||
|
func (jst *Tracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||||
|
if !jst.traceCallFrames {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if jst.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If tracing was interrupted, set the error and stop
|
||||||
|
if atomic.LoadUint32(&jst.interrupt) > 0 {
|
||||||
|
jst.err = jst.reason
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jst.frameResult.output = common.CopyBytes(output)
|
||||||
|
*jst.frameResult.gasUsed = uint(gasUsed)
|
||||||
|
jst.frameResult.errorValue = nil
|
||||||
|
if err != nil {
|
||||||
|
jst.frameResult.errorValue = new(string)
|
||||||
|
*jst.frameResult.errorValue = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := jst.call(true, "exit", "frameResult"); err != nil {
|
||||||
|
jst.err = wrapError("exit", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
|
// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
|
||||||
func (jst *Tracer) GetResult() (json.RawMessage, error) {
|
func (jst *Tracer) GetResult() (json.RawMessage, error) {
|
||||||
// Transform the context into a JavaScript object and inject into the state
|
// Transform the context into a JavaScript object and inject into the state
|
||||||
obj := jst.vm.PushObject()
|
obj := jst.vm.PushObject()
|
||||||
|
|
||||||
for key, val := range jst.ctx {
|
for key, val := range jst.ctx {
|
||||||
switch val := val.(type) {
|
jst.addToObj(obj, key, val)
|
||||||
case uint64:
|
|
||||||
jst.vm.PushUint(uint(val))
|
|
||||||
|
|
||||||
case string:
|
|
||||||
jst.vm.PushString(val)
|
|
||||||
|
|
||||||
case []byte:
|
|
||||||
ptr := jst.vm.PushFixedBuffer(len(val))
|
|
||||||
copy(makeSlice(ptr, uint(len(val))), val)
|
|
||||||
|
|
||||||
case common.Address:
|
|
||||||
ptr := jst.vm.PushFixedBuffer(20)
|
|
||||||
copy(makeSlice(ptr, 20), val[:])
|
|
||||||
|
|
||||||
case *big.Int:
|
|
||||||
pushBigInt(val, jst.vm)
|
|
||||||
|
|
||||||
case int:
|
|
||||||
jst.vm.PushInt(val)
|
|
||||||
|
|
||||||
case common.Hash:
|
|
||||||
ptr := jst.vm.PushFixedBuffer(32)
|
|
||||||
copy(makeSlice(ptr, 32), val[:])
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unsupported type: %T", val))
|
|
||||||
}
|
|
||||||
jst.vm.PutPropString(obj, key)
|
|
||||||
}
|
}
|
||||||
jst.vm.PutPropString(jst.stateObject, "ctx")
|
jst.vm.PutPropString(jst.stateObject, "ctx")
|
||||||
|
|
||||||
|
@ -699,3 +838,35 @@ func (jst *Tracer) GetResult() (json.RawMessage, error) {
|
||||||
|
|
||||||
return result, jst.err
|
return result, jst.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addToObj pushes a field to a JS object.
|
||||||
|
func (jst *Tracer) addToObj(obj int, key string, val interface{}) {
|
||||||
|
pushValue(jst.vm, val)
|
||||||
|
jst.vm.PutPropString(obj, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pushValue(ctx *duktape.Context, val interface{}) {
|
||||||
|
switch val := val.(type) {
|
||||||
|
case uint64:
|
||||||
|
ctx.PushUint(uint(val))
|
||||||
|
case string:
|
||||||
|
ctx.PushString(val)
|
||||||
|
case []byte:
|
||||||
|
ptr := ctx.PushFixedBuffer(len(val))
|
||||||
|
copy(makeSlice(ptr, uint(len(val))), val)
|
||||||
|
case common.Address:
|
||||||
|
ptr := ctx.PushFixedBuffer(20)
|
||||||
|
copy(makeSlice(ptr, 20), val[:])
|
||||||
|
case *big.Int:
|
||||||
|
pushBigInt(val, ctx)
|
||||||
|
case int:
|
||||||
|
ctx.PushInt(val)
|
||||||
|
case uint:
|
||||||
|
ctx.PushUint(val)
|
||||||
|
case common.Hash:
|
||||||
|
ptr := ctx.PushFixedBuffer(32)
|
||||||
|
copy(makeSlice(ptr, 32), val[:])
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unsupported type: %T", val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -236,3 +236,35 @@ func TestIsPrecompile(t *testing.T) {
|
||||||
t.Errorf("Tracer should consider blake2f as precompile in istanbul")
|
t.Errorf("Tracer should consider blake2f as precompile in istanbul")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEnterExit(t *testing.T) {
|
||||||
|
// test that either both or none of enter() and exit() are defined
|
||||||
|
if _, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(Context)); err == nil {
|
||||||
|
t.Fatal("tracer creation should've failed without exit() definition")
|
||||||
|
}
|
||||||
|
if _, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(Context)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test that the enter and exit method are correctly invoked and the values passed
|
||||||
|
tracer, err := New("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(Context))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := &vm.ScopeContext{
|
||||||
|
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
tracer.CaptureEnter(vm.CALL, scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int))
|
||||||
|
tracer.CaptureExit([]byte{}, 400, nil)
|
||||||
|
|
||||||
|
have, err := tracer.GetResult()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
want := `{"enters":1,"exits":1,"enterGas":1000,"gasUsed":400}`
|
||||||
|
if string(have) != want {
|
||||||
|
t.Errorf("Number of invocations of enter() and exit() is wrong. Have %s, want %s\n", have, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -203,21 +203,25 @@ func TestPrestateTracerCreate2(t *testing.T) {
|
||||||
|
|
||||||
// Iterates over all the input-output datasets in the tracer test harness and
|
// Iterates over all the input-output datasets in the tracer test harness and
|
||||||
// runs the JavaScript tracers against them.
|
// runs the JavaScript tracers against them.
|
||||||
func TestCallTracer(t *testing.T) {
|
func TestCallTracerLegacy(t *testing.T) {
|
||||||
files, err := ioutil.ReadDir("testdata")
|
testCallTracer("callTracerLegacy", "call_tracer_legacy", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCallTracer(tracer string, dirPath string, t *testing.T) {
|
||||||
|
files, err := ioutil.ReadDir(filepath.Join("testdata", dirPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to retrieve tracer test suite: %v", err)
|
t.Fatalf("failed to retrieve tracer test suite: %v", err)
|
||||||
}
|
}
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if !strings.HasPrefix(file.Name(), "call_tracer_") {
|
if !strings.HasSuffix(file.Name(), ".json") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
file := file // capture range variable
|
file := file // capture range variable
|
||||||
t.Run(camel(strings.TrimSuffix(strings.TrimPrefix(file.Name(), "call_tracer_"), ".json")), func(t *testing.T) {
|
t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Call tracer test found, read if from disk
|
// Call tracer test found, read if from disk
|
||||||
blob, err := ioutil.ReadFile(filepath.Join("testdata", file.Name()))
|
blob, err := ioutil.ReadFile(filepath.Join("testdata", dirPath, file.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to read testcase: %v", err)
|
t.Fatalf("failed to read testcase: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -248,7 +252,7 @@ func TestCallTracer(t *testing.T) {
|
||||||
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
|
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
|
||||||
|
|
||||||
// Create the tracer, the EVM environment and run it
|
// Create the tracer, the EVM environment and run it
|
||||||
tracer, err := New("callTracer", new(Context))
|
tracer, err := New(tracer, new(Context))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create call tracer: %v", err)
|
t.Fatalf("failed to create call tracer: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -283,6 +287,10 @@ func TestCallTracer(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCallTracer(t *testing.T) {
|
||||||
|
testCallTracer("callTracer", "call_tracer", t)
|
||||||
|
}
|
||||||
|
|
||||||
// jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
|
// jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
|
||||||
// comparison
|
// comparison
|
||||||
func jsonEqual(x, y interface{}) bool {
|
func jsonEqual(x, y interface{}) bool {
|
||||||
|
@ -378,3 +386,73 @@ func BenchmarkTransactionTrace(b *testing.B) {
|
||||||
tracer.Reset()
|
tracer.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkTracers(b *testing.B) {
|
||||||
|
files, err := ioutil.ReadDir(filepath.Join("testdata", "call_tracer"))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("failed to retrieve tracer test suite: %v", err)
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
if !strings.HasSuffix(file.Name(), ".json") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
file := file // capture range variable
|
||||||
|
b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) {
|
||||||
|
blob, err := ioutil.ReadFile(filepath.Join("testdata", "call_tracer", file.Name()))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("failed to read testcase: %v", err)
|
||||||
|
}
|
||||||
|
test := new(callTracerTest)
|
||||||
|
if err := json.Unmarshal(blob, test); err != nil {
|
||||||
|
b.Fatalf("failed to parse testcase: %v", err)
|
||||||
|
}
|
||||||
|
benchTracer("callTracer", test, b)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
|
||||||
|
// Configure a blockchain with the given prestate
|
||||||
|
tx := new(types.Transaction)
|
||||||
|
if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
|
||||||
|
b.Fatalf("failed to parse testcase input: %v", err)
|
||||||
|
}
|
||||||
|
signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
|
||||||
|
msg, err := tx.AsMessage(signer, nil)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("failed to prepare transaction for tracing: %v", err)
|
||||||
|
}
|
||||||
|
origin, _ := signer.Sender(tx)
|
||||||
|
txContext := vm.TxContext{
|
||||||
|
Origin: origin,
|
||||||
|
GasPrice: tx.GasPrice(),
|
||||||
|
}
|
||||||
|
context := vm.BlockContext{
|
||||||
|
CanTransfer: core.CanTransfer,
|
||||||
|
Transfer: core.Transfer,
|
||||||
|
Coinbase: test.Context.Miner,
|
||||||
|
BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
|
||||||
|
Time: new(big.Int).SetUint64(uint64(test.Context.Time)),
|
||||||
|
Difficulty: (*big.Int)(test.Context.Difficulty),
|
||||||
|
GasLimit: uint64(test.Context.GasLimit),
|
||||||
|
}
|
||||||
|
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
|
||||||
|
|
||||||
|
// Create the tracer, the EVM environment and run it
|
||||||
|
tracer, err := New(tracerName, new(Context))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("failed to create call tracer: %v", err)
|
||||||
|
}
|
||||||
|
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
snap := statedb.Snapshot()
|
||||||
|
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||||
|
if _, err = st.TransitionDb(); err != nil {
|
||||||
|
b.Fatalf("failed to execute transaction: %v", err)
|
||||||
|
}
|
||||||
|
statedb.RevertToSnapshot(snap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue