diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go
index b79f4a400b..d363ccd983 100644
--- a/cmd/evm/internal/t8ntool/execution.go
+++ b/cmd/evm/internal/t8ntool/execution.go
@@ -94,7 +94,7 @@ type rejectedTx struct {
// Apply applies a set of transactions to a pre-state
func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
txs types.Transactions, miningReward int64,
- getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.Tracer, err error)) (*state.StateDB, *ExecutionResult, error) {
+ getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error)) (*state.StateDB, *ExecutionResult, error) {
// Capture errors for BLOCKHASH operation, if we haven't been supplied the
// required blockhashes
diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go
index 4f5fe82e7e..0559037fca 100644
--- a/cmd/evm/internal/t8ntool/transition.go
+++ b/cmd/evm/internal/t8ntool/transition.go
@@ -92,10 +92,10 @@ func Transition(ctx *cli.Context) error {
var (
err error
- tracer vm.Tracer
+ tracer vm.EVMLogger
baseDir = ""
)
- var getTracer func(txIndex int, txHash common.Hash) (vm.Tracer, error)
+ var getTracer func(txIndex int, txHash common.Hash) (vm.EVMLogger, error)
// If user specified a basedir, make sure it exists
if ctx.IsSet(OutputBasedir.Name) {
@@ -122,7 +122,7 @@ func Transition(ctx *cli.Context) error {
prevFile.Close()
}
}()
- getTracer = func(txIndex int, txHash common.Hash) (vm.Tracer, error) {
+ getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) {
if prevFile != nil {
prevFile.Close()
}
@@ -134,7 +134,7 @@ func Transition(ctx *cli.Context) error {
return vm.NewJSONLogger(logConfig, traceFile), nil
}
} else {
- getTracer = func(txIndex int, txHash common.Hash) (tracer vm.Tracer, err error) {
+ getTracer = func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error) {
return nil, nil
}
}
diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go
index b24867ccc6..b9acf8f5fe 100644
--- a/cmd/evm/runner.go
+++ b/cmd/evm/runner.go
@@ -116,7 +116,7 @@ func runCmd(ctx *cli.Context) error {
}
var (
- tracer vm.Tracer
+ tracer vm.EVMLogger
debugLogger *vm.StructLogger
statedb *state.StateDB
chainConfig *params.ChainConfig
diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go
index 30195d4ced..e621bee8de 100644
--- a/cmd/evm/staterunner.go
+++ b/cmd/evm/staterunner.go
@@ -65,7 +65,7 @@ func stateTestCmd(ctx *cli.Context) error {
EnableReturnData: !ctx.GlobalBool(DisableReturnDataFlag.Name),
}
var (
- tracer vm.Tracer
+ tracer vm.EVMLogger
debugger *vm.StructLogger
)
switch {
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index b033b90c43..ba798d2434 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -41,6 +41,11 @@ import (
"github.com/celo-org/celo-blockchain/log"
"github.com/celo-org/celo-blockchain/metrics"
"github.com/celo-org/celo-blockchain/node"
+
+ // Force-load the tracer engines to trigger registration
+ _ "github.com/celo-org/celo-blockchain/eth/tracers/js"
+ _ "github.com/celo-org/celo-blockchain/eth/tracers/native"
+
"gopkg.in/urfave/cli.v1"
)
diff --git a/core/vm/access_list_tracer.go b/core/vm/access_list_tracer.go
index ad5b50ed80..e613c7fbc3 100644
--- a/core/vm/access_list_tracer.go
+++ b/core/vm/access_list_tracer.go
@@ -141,7 +141,7 @@ func (a *AccessListTracer) CaptureStart(env *EVM, from common.Address, to common
}
// CaptureState captures all opcodes that touch storage or addresses and adds them to the accesslist.
-func (a *AccessListTracer) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
+func (a *AccessListTracer) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
stack := scope.Stack
if (op == SLOAD || op == SSTORE) && stack.len() >= 1 {
slot := common.Hash(stack.data[stack.len()-1].Bytes32())
@@ -161,7 +161,7 @@ func (a *AccessListTracer) CaptureState(env *EVM, pc uint64, op OpCode, gas, cos
}
}
-func (*AccessListTracer) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
+func (*AccessListTracer) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
}
func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {}
diff --git a/core/vm/evm.go b/core/vm/evm.go
index 0900dc0df0..6e499f828e 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -207,9 +207,14 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer
- if evm.Config.Debug && evm.depth == 0 {
- evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
- evm.Config.Tracer.CaptureEnd(ret, 0, 0, nil)
+ if evm.Config.Debug {
+ if evm.depth == 0 {
+ evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
+ evm.Config.Tracer.CaptureEnd(ret, 0, 0, nil)
+ } else {
+ evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value)
+ evm.Config.Tracer.CaptureExit(ret, 0, nil)
+ }
}
return nil, gas, nil
}
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index 982072a50e..5c406f7e70 100644
--- a/core/vm/interpreter.go
+++ b/core/vm/interpreter.go
@@ -27,11 +27,11 @@ import (
// Config are the configuration options for the Interpreter
type Config struct {
- Debug bool // Enables debugging
- Tracer Tracer // Opcode logger
- NoRecursion bool // Disables call, callcode, delegate call and create
- NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
- EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
+ Debug bool // Enables debugging
+ Tracer EVMLogger // Opcode logger
+ NoRecursion bool // Disables call, callcode, delegate call and create
+ NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
+ EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
JumpTable [256]*operation // EVM instruction table, automatically populated if unset
@@ -152,9 +152,9 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
pc = uint64(0) // program counter
cost uint64
// copies used by tracer
- pcCopy uint64 // needed for the deferred Tracer
- gasCopy uint64 // for Tracer to log gas remaining before execution
- logged bool // deferred Tracer should ignore already logged steps
+ pcCopy uint64 // needed for the deferred EVMLogger
+ gasCopy uint64 // for EVMLogger to log gas remaining before execution
+ logged bool // deferred EVMLogger should ignore already logged steps
res []byte // result of the opcode execution function
)
// Don't move this deferrred function, it's placed before the capturestate-deferred method,
@@ -169,9 +169,9 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
defer func() {
if err != nil {
if !logged {
- in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
+ in.cfg.Tracer.CaptureState(pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
} else {
- in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err)
+ in.cfg.Tracer.CaptureFault(pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err)
}
}
}()
@@ -256,7 +256,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
}
if in.cfg.Debug {
- in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
+ in.cfg.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
logged = true
}
diff --git a/core/vm/logger.go b/core/vm/logger.go
index 541ad740f9..c2ad957f59 100644
--- a/core/vm/logger.go
+++ b/core/vm/logger.go
@@ -98,27 +98,28 @@ func (s *StructLog) ErrorString() string {
return ""
}
-// Tracer is used to collect execution traces from an EVM transaction
+// EVMLogger is used to collect execution traces from an EVM transaction
// execution. CaptureState is called for each step of the VM with the
// current VM state.
// Note that reference types are actual VM data structures; make copies
// if you need to retain them beyond the current call.
-type Tracer interface {
+type EVMLogger interface {
CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
- CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
+ CaptureState(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(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error)
}
-// StructLogger is an EVM state logger and implements Tracer.
+// StructLogger is an EVM state logger and implements EVMLogger.
//
// StructLogger can capture state based on the given Log configuration and also keeps
// a track record of modified storage which is used in reporting snapshots of the
// contract their storage.
type StructLogger struct {
cfg LogConfig
+ env *EVM
storage map[common.Address]Storage
logs []StructLog
@@ -145,14 +146,15 @@ func (l *StructLogger) Reset() {
l.err = nil
}
-// CaptureStart implements the Tracer interface to initialize the tracing operation.
+// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+ l.env = env
}
// CaptureState logs a new structured log message and pushes it out to the environment
//
// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
-func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
+func (l *StructLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
memory := scope.Memory
stack := scope.Stack
contract := scope.Contract
@@ -186,7 +188,7 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
if op == SLOAD && stack.len() >= 1 {
var (
address = common.Hash(stack.data[stack.len()-1].Bytes32())
- value = env.StateDB.GetState(contract.Address(), address)
+ value = l.env.StateDB.GetState(contract.Address(), address)
)
l.storage[contract.Address()][address] = value
storage = l.storage[contract.Address()].Copy()
@@ -206,13 +208,13 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
copy(rdata, rData)
}
// create a new snapshot of the EVM.
- log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, env.StateDB.GetRefund(), err}
+ log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err}
l.logs = append(l.logs, log)
}
-// CaptureFault implements the Tracer interface to trace an execution fault
+// CaptureFault implements the EVMLogger interface to trace an execution fault
// while running an opcode.
-func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
+func (l *StructLogger) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
}
// CaptureEnd is called after the call finishes to finalize the tracing.
@@ -291,12 +293,13 @@ func WriteLogs(writer io.Writer, logs []*types.Log) {
type mdLogger struct {
out io.Writer
cfg *LogConfig
+ env *EVM
}
// NewMarkdownLogger creates a logger which outputs information in a format adapted
// for human readability, and is also a valid markdown table
func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger {
- l := &mdLogger{writer, cfg}
+ l := &mdLogger{out: writer, cfg: cfg}
if l.cfg == nil {
l.cfg = &LogConfig{}
}
@@ -304,6 +307,7 @@ func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger {
}
func (t *mdLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+ t.env = env
if !create {
fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",
from.String(), to.String(),
@@ -321,7 +325,7 @@ func (t *mdLogger) CaptureStart(env *EVM, from common.Address, to common.Address
}
// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
-func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
+func (t *mdLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
stack := scope.Stack
fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost)
@@ -334,14 +338,14 @@ func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64
b := fmt.Sprintf("[%v]", strings.Join(a, ","))
fmt.Fprintf(t.out, "%10v |", b)
}
- fmt.Fprintf(t.out, "%10v |", env.StateDB.GetRefund())
+ fmt.Fprintf(t.out, "%10v |", t.env.StateDB.GetRefund())
fmt.Fprintln(t.out, "")
if err != nil {
fmt.Fprintf(t.out, "Error: %v\n", err)
}
}
-func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
+func (t *mdLogger) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
}
diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go
index a3f13925a0..ba13b92dbc 100644
--- a/core/vm/logger_json.go
+++ b/core/vm/logger_json.go
@@ -29,12 +29,13 @@ import (
type JSONLogger struct {
encoder *json.Encoder
cfg *LogConfig
+ env *EVM
}
// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
// into the provided stream.
func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger {
- l := &JSONLogger{json.NewEncoder(writer), cfg}
+ l := &JSONLogger{encoder: json.NewEncoder(writer), cfg: cfg}
if l.cfg == nil {
l.cfg = &LogConfig{}
}
@@ -42,12 +43,13 @@ func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger {
}
func (l *JSONLogger) CaptureStart(env *EVM, from, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+ l.env = env
}
-func (l *JSONLogger) CaptureFault(*EVM, uint64, OpCode, uint64, uint64, *ScopeContext, int, error) {}
+func (l *JSONLogger) CaptureFault(uint64, OpCode, uint64, uint64, *ScopeContext, int, error) {}
// CaptureState outputs state information on the logger.
-func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
+func (l *JSONLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
memory := scope.Memory
stack := scope.Stack
@@ -58,7 +60,7 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint
GasCost: cost,
MemorySize: memory.Len(),
Depth: depth,
- RefundCounter: env.StateDB.GetRefund(),
+ RefundCounter: l.env.StateDB.GetRefund(),
Err: err,
}
if l.cfg.EnableMemory {
diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go
index a08fe543f8..60cb134cbf 100644
--- a/core/vm/logger_test.go
+++ b/core/vm/logger_test.go
@@ -62,7 +62,8 @@ func TestStoreCapture(t *testing.T) {
scope.Stack.push(uint256.NewInt(1))
scope.Stack.push(new(uint256.Int))
var index common.Hash
- logger.CaptureState(env, 0, SSTORE, 0, 0, scope, nil, 0, nil)
+ logger.CaptureStart(env, common.Address{}, contract.Address(), false, nil, 0, nil)
+ logger.CaptureState(0, SSTORE, 0, 0, scope, nil, 0, nil)
if len(logger.storage[contract.Address()]) == 0 {
t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(),
len(logger.storage[contract.Address()]))
diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go
index 0afb57d448..ee123ed608 100644
--- a/core/vm/runtime/runtime_test.go
+++ b/core/vm/runtime/runtime_test.go
@@ -35,6 +35,9 @@ import (
"github.com/celo-org/celo-blockchain/core/vm/vmcontext"
"github.com/celo-org/celo-blockchain/eth/tracers"
"github.com/celo-org/celo-blockchain/params"
+
+ // force-load js tracers to trigger registration
+ _ "github.com/celo-org/celo-blockchain/eth/tracers/js"
)
func TestDefaults(t *testing.T) {
@@ -344,12 +347,12 @@ type stepCounter struct {
func (s *stepCounter) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
}
-func (s *stepCounter) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
+func (s *stepCounter) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
}
func (s *stepCounter) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {}
-func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+func (s *stepCounter) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
s.steps++
// Enable this for more output
//s.inner.CaptureState(env, pc, op, gas, cost, memory, stack, rStack, contract, depth, err)
@@ -526,7 +529,7 @@ func BenchmarkSimpleLoop(b *testing.B) {
// TestEip2929Cases contains various testcases that are used for
// EIP-2929 about gas repricings
func TestEip2929Cases(t *testing.T) {
-
+ t.Skip("Test only useful for generating documentation")
id := 1
prettyPrint := func(comment string, code []byte) {
@@ -801,30 +804,30 @@ func TestColdAccountAccessCostPreGFork(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++;
+ 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++;
+ },
+ 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++;
+ 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++;
+ },
+ exit: function(res) {
+ this.exits++;
this.gasUsed = res.getGasUsed();
}}`}
tests := []struct {
diff --git a/e2e_test/e2e_test.go b/e2e_test/e2e_test.go
index e14362e35c..d43c08e2b8 100644
--- a/e2e_test/e2e_test.go
+++ b/e2e_test/e2e_test.go
@@ -2,6 +2,7 @@ package e2e_test
import (
"context"
+ "encoding/json"
"errors"
"fmt"
"math/big"
@@ -23,6 +24,10 @@ import (
"github.com/celo-org/celo-blockchain/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+
+ // Force-load native and js packages, to trigger registration
+ _ "github.com/celo-org/celo-blockchain/eth/tracers/js"
+ _ "github.com/celo-org/celo-blockchain/eth/tracers/native"
)
func init() {
@@ -91,6 +96,81 @@ func TestTraceSendCeloViaGoldToken(t *testing.T) {
// Check top level gas values
require.Equal(t, "0x45d6", result["gasUsed"])
require.Equal(t, "0x4653", result["gas"])
+ // TODO add more specific trace-checking as part of
+ // this issue: https://github.com/celo-org/celo-blockchain/issues/2078
+}
+
+// Moved from API tests because registering the callTracer (necessary after the
+// go native tracer refactor) causes a circular import.
+// Use the callTracer to trace a native CELO transfer.
+func TestCallTraceTransactionNativeTransfer(t *testing.T) {
+ ac := test.AccountConfig(1, 2)
+ gc, ec, err := test.BuildConfig(ac)
+ require.NoError(t, err)
+ network, shutdown, err := test.NewNetwork(ac, gc, ec)
+ require.NoError(t, err)
+ defer shutdown()
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
+ defer cancel()
+
+ accounts := test.Accounts(ac.DeveloperAccounts(), gc.ChainConfig())
+
+ // Send one celo from external account 0 to 1 via node 0.
+ tx, err := accounts[0].SendCelo(ctx, accounts[1].Address, 1, network[0])
+ require.NoError(t, err)
+
+ // Wait for the whole network to process the transaction.
+ err = network.AwaitTransactions(ctx, tx)
+ require.NoError(t, err)
+ c, err := rpc.DialContext(ctx, network[0].WSEndpoint())
+ require.NoError(t, err)
+
+ var result map[string]interface{}
+ tracerStr := "callTracer"
+ err = c.CallContext(ctx, &result, "debug_traceTransaction", tx.Hash().String(), tracers.TraceConfig{Tracer: &tracerStr})
+ require.NoError(t, err)
+ res_json, err := json.Marshal(result)
+ if err != nil {
+ t.Fatalf("failed to marshal result: %v", err)
+ }
+ expectedTraceStr := fmt.Sprintf(`{"from":"0x%x","gas":"0x0","gasUsed":"0x0","input":"0x","output":"0x","to":"0x%x","type":"CALL","value":"0x1"}`, accounts[0].Address, accounts[1].Address)
+ require.JSONEq(t, expectedTraceStr, string(res_json))
+}
+
+// Moved from API tests because registering the prestateTracer (necessary after the
+// go native tracer refactor) causes a circular import.
+// Use the prestateTracer to trace a native CELO transfer.
+func TestPrestateTransactionNativeTransfer(t *testing.T) {
+ ac := test.AccountConfig(1, 2)
+ gc, ec, err := test.BuildConfig(ac)
+ require.NoError(t, err)
+ network, shutdown, err := test.NewNetwork(ac, gc, ec)
+ require.NoError(t, err)
+ defer shutdown()
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
+ defer cancel()
+
+ accounts := test.Accounts(ac.DeveloperAccounts(), gc.ChainConfig())
+
+ // Send one celo from external account 0 to 1 via node 0.
+ tx, err := accounts[0].SendCelo(ctx, accounts[1].Address, 1, network[0])
+ require.NoError(t, err)
+
+ // Wait for the whole network to process the transaction.
+ err = network.AwaitTransactions(ctx, tx)
+ require.NoError(t, err)
+ c, err := rpc.DialContext(ctx, network[0].WSEndpoint())
+ require.NoError(t, err)
+
+ var result map[string]interface{}
+ tracerStr := "prestateTracer"
+ err = c.CallContext(ctx, &result, "debug_traceTransaction", tx.Hash().String(), tracers.TraceConfig{Tracer: &tracerStr})
+ require.NoError(t, err)
+
+ toAddrLowercase := strings.ToLower(accounts[1].Address.String())
+ if _, has := result[toAddrLowercase]; !has {
+ t.Fatalf("Expected %s in result", toAddrLowercase)
+ }
}
// This test verifies correct behavior in a network of size one, in the case that
diff --git a/eth/api_backend.go b/eth/api_backend.go
index dfd24631e2..6f05df9732 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -442,8 +442,8 @@ func (b *EthAPIBackend) StartMining() error {
return b.eth.StartMining()
}
-func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) {
- return b.eth.stateAtBlock(block, reexec, base, checkLive)
+func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive, preferDisk bool) (*state.StateDB, error) {
+ return b.eth.stateAtBlock(block, reexec, base, checkLive, preferDisk)
}
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, vm.EVMRunner, *state.StateDB, error) {
diff --git a/eth/state_accessor.go b/eth/state_accessor.go
index 164fcb0604..90ba9cbb67 100644
--- a/eth/state_accessor.go
+++ b/eth/state_accessor.go
@@ -36,7 +36,17 @@ import (
// are attempted to be reexecuted to generate the desired state. The optional
// base layer statedb can be passed then it's regarded as the statedb of the
// parent block.
-func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (statedb *state.StateDB, err error) {
+// Parameters:
+// - block: The block for which we want the state (== state at the stateRoot of the parent)
+// - reexec: The maximum number of blocks to reprocess trying to obtain the desired state
+// - base: If the caller is tracing multiple blocks, the caller can provide the parent state
+// continuously from the callsite.
+// - checklive: if true, then the live 'blockchain' state database is used. If the caller want to
+// perform Commit or other 'save-to-disk' changes, this should be set to false to avoid
+// storing trash persistently
+// - preferDisk: this arg can be used by the caller to signal that even though the 'base' is provided,
+// it would be preferrable to start from a fresh state, if we have it on disk.
+func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) {
var (
current *types.Block
database state.Database
@@ -51,6 +61,15 @@ func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state
}
}
if base != nil {
+ if preferDisk {
+ // Create an ephemeral trie.Database for isolating the live one. Otherwise
+ // the internal junks created by tracing will be persisted into the disk.
+ database = state.NewDatabaseWithConfig(eth.chainDb, &trie.Config{Cache: 16})
+ if statedb, err = state.New(block.Root(), database, nil); err == nil {
+ log.Info("Found disk backend for state trie", "root", block.Root(), "number", block.Number())
+ return statedb, nil
+ }
+ }
// The optional base statedb is given, mark the start point as parent block
statedb, database, report = base, base.Database(), false
current = eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
@@ -153,7 +172,7 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec
}
// Lookup the statedb of parent block from the live database,
// otherwise regenerate it on the flight.
- statedb, err := eth.stateAtBlock(parent, reexec, nil, true)
+ statedb, err := eth.stateAtBlock(parent, reexec, nil, true, false)
if err != nil {
return nil, vm.BlockContext{}, nil, nil, err
}
diff --git a/eth/tracers/api.go b/eth/tracers/api.go
index ba96e75dc5..2cfa51bf2b 100644
--- a/eth/tracers/api.go
+++ b/eth/tracers/api.go
@@ -54,6 +54,13 @@ const (
// and reexecute to produce missing historical state necessary to run a specific
// trace.
defaultTraceReexec = uint64(128)
+
+ // defaultTracechainMemLimit is the size of the triedb, at which traceChain
+ // switches over and tries to use a disk-backed database instead of building
+ // on top of memory.
+ // For non-archive nodes, this limit _will_ be overblown, as disk-backed tries
+ // will only be found every ~15K blocks or so.
+ defaultTracechainMemLimit = common.StorageSize(500 * 1024 * 1024)
)
// Backend interface provides the common API services (that are provided by
@@ -68,7 +75,10 @@ type Backend interface {
ChainConfig() *params.ChainConfig
Engine() consensus.Engine
ChainDb() ethdb.Database
- StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error)
+ // StateAtBlock returns the state corresponding to the stateroot of the block.
+ // N.B: For executing transactions on block N, the required stateRoot is block N-1,
+ // so this method should be called with the parent.
+ StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive, preferDisk bool) (*state.StateDB, error)
StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, vm.EVMRunner, *state.StateDB, error)
NewEVMRunner(*types.Header, vm.StateDB) vm.EVMRunner
}
@@ -342,6 +352,7 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config
}
close(results)
}()
+ var preferDisk bool
// Feed all the blocks both into the tracer, as well as fast process concurrently
for number = start.NumberU64(); number < end.NumberU64(); number++ {
// Stop tracing if interruption was requested
@@ -371,18 +382,24 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config
}
// Prepare the statedb for tracing. Don't use the live database for
// tracing to avoid persisting state junks into the database.
- statedb, err = api.backend.StateAtBlock(localctx, block, reexec, statedb, false)
+ statedb, err = api.backend.StateAtBlock(localctx, block, reexec, statedb, false, preferDisk)
if err != nil {
failed = err
break
}
- if statedb.Database().TrieDB() != nil {
+ if trieDb := statedb.Database().TrieDB(); trieDb != nil {
// Hold the reference for tracer, will be released at the final stage
- statedb.Database().TrieDB().Reference(block.Root(), common.Hash{})
+ trieDb.Reference(block.Root(), common.Hash{})
// Release the parent state because it's already held by the tracer
if parent != (common.Hash{}) {
- statedb.Database().TrieDB().Dereference(parent)
+ trieDb.Dereference(parent)
+ }
+ // Prefer disk if the trie db memory grows too much
+ s1, s2 := trieDb.Size()
+ if !preferDisk && (s1+s2) > defaultTracechainMemLimit {
+ log.Info("Switching to prefer-disk mode for tracing", "size", s1+s2)
+ preferDisk = true
}
}
parent = block.Root()
@@ -518,7 +535,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
if config != nil && config.Reexec != nil {
reexec = *config.Reexec
}
- statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true)
+ statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
if err != nil {
return nil, err
}
@@ -586,7 +603,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
if config != nil && config.Reexec != nil {
reexec = *config.Reexec
}
- statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true)
+ statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
if err != nil {
return nil, err
}
@@ -603,7 +620,6 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
if threads > len(txs) {
threads = len(txs)
}
- blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
blockHash := block.Hash()
isEspresso := api.backend.ChainConfig().IsEspresso(block.Number())
@@ -614,6 +630,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
for th := 0; th < threads; th++ {
pend.Add(1)
go func() {
+ blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
defer pend.Done()
// Fetch and execute the next transaction trace tasks
for task := range jobs {
@@ -637,6 +654,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
vmRunner := api.backend.NewEVMRunner(block.Header(), statedb)
// Feed the transactions into the tracers and return
var failed error
+ blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
for i, tx := range txs {
// Send the trace task over for execution
jobs <- &txTraceTask{statedb: statedb.Copy(), index: i}
@@ -685,7 +703,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
if config != nil && config.Reexec != nil {
reexec = *config.Reexec
}
- statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true)
+ statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
if err != nil {
return nil, err
}
@@ -834,7 +852,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
if err != nil {
return nil, err
}
- sysStateDB, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true)
+ sysStateDB, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
if err != nil {
return nil, err
}
@@ -878,7 +896,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
if config != nil && config.Reexec != nil {
reexec = *config.Reexec
}
- statedb, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true)
+ statedb, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true, false)
if err != nil {
return nil, err
}
@@ -917,12 +935,14 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, vmRunner vm.EVMRunner, statedb *state.StateDB, sysCtx *core.SysContractCallCtx, config *TraceConfig) (interface{}, error) {
// Assemble the structured logger or the JavaScript tracer
var (
- tracer vm.Tracer
+ tracer vm.EVMLogger
err error
txContext = core.NewEVMTxContext(message)
)
switch {
- case config != nil && config.Tracer != nil:
+ case config == nil:
+ tracer = vm.NewStructLogger(nil)
+ case config.Tracer != nil:
// Define a meaningful timeout of a single transaction trace
timeout := defaultTraceTimeout
if config.Timeout != nil {
@@ -930,23 +950,19 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
return nil, err
}
}
- // Constuct the JavaScript tracer to execute with
- if tracer, err = New(*config.Tracer, txctx); err != nil {
+ if t, err := New(*config.Tracer, txctx); err != nil {
return nil, err
+ } else {
+ deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
+ go func() {
+ <-deadlineCtx.Done()
+ if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
+ t.Stop(errors.New("execution timeout"))
+ }
+ }()
+ defer cancel()
+ tracer = t
}
- // Handle timeouts and RPC cancellations
- deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
- go func() {
- <-deadlineCtx.Done()
- if deadlineCtx.Err() == context.DeadlineExceeded {
- tracer.(*Tracer).Stop(errors.New("execution timeout"))
- }
- }()
- defer cancel()
-
- case config == nil:
- tracer = vm.NewStructLogger(nil)
-
default:
tracer = vm.NewStructLogger(config.LogConfig)
}
@@ -976,7 +992,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
}, nil
- case *Tracer:
+ case Tracer:
return tracer.GetResult()
default:
diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go
index fd485dfa84..1c12c501b3 100644
--- a/eth/tracers/api_test.go
+++ b/eth/tracers/api_test.go
@@ -29,12 +29,10 @@ import (
"testing"
"time"
- "github.com/celo-org/celo-blockchain/accounts/abi"
"github.com/celo-org/celo-blockchain/common"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/celo-org/celo-blockchain/consensus"
mockEngine "github.com/celo-org/celo-blockchain/consensus/consensustest"
- "github.com/celo-org/celo-blockchain/contracts"
"github.com/celo-org/celo-blockchain/contracts/testutil"
"github.com/celo-org/celo-blockchain/core"
"github.com/celo-org/celo-blockchain/core/rawdb"
@@ -150,7 +148,7 @@ func (b *testBackend) ChainDb() ethdb.Database {
return b.chaindb
}
-func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) {
+func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (*state.StateDB, error) {
statedb, err := b.chain.StateAt(block.Root())
if err != nil {
return nil, errStateNotFound
@@ -339,19 +337,36 @@ func TestOverriddenTraceCall(t *testing.T) {
// Transfer from account[0] to account[1]
// value: 1000 wei
// fee: 0 wei
- tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.MinimumGasPrice(nil), nil), signer, accounts[0].key)
+ tx, _ := types.SignTx(
+ types.NewCeloTransaction(
+ uint64(i),
+ accounts[1].addr,
+ big.NewInt(1000),
+ params.TxGas,
+ common.Big0,
+ nil,
+ nil,
+ nil,
+ nil),
+ signer,
+ accounts[0].key,
+ )
b.AddTx(tx)
}))
- randomAccounts, tracer := newAccounts(3), "callTracer"
-
+ randomAccounts := newAccounts(3)
+ type res struct {
+ Gas int
+ Failed bool
+ returnValue string
+ }
var testSuite = []struct {
blockNumber rpc.BlockNumber
call ethapi.TransactionArgs
config *TraceCallConfig
expectErr error
- expect *callTrace
+ want string
}{
- // Succcessful call with state overriding
+ // Call which can only succeed if state is state overridden
{
blockNumber: rpc.PendingBlockNumber,
call: ethapi.TransactionArgs{
@@ -360,20 +375,11 @@ func TestOverriddenTraceCall(t *testing.T) {
Value: (*hexutil.Big)(big.NewInt(1000)),
},
config: &TraceCallConfig{
- Tracer: &tracer,
StateOverrides: ðapi.StateOverride{
randomAccounts[0].addr: ethapi.OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))},
},
},
- expectErr: nil,
- expect: &callTrace{
- Type: "CALL",
- From: randomAccounts[0].addr,
- To: randomAccounts[1].addr,
- Gas: newRPCUint64(24979000),
- GasUsed: newRPCUint64(0),
- Value: (*hexutil.Big)(big.NewInt(1000)),
- },
+ want: `{"gas":21000,"failed":false,"returnValue":""}`,
},
// Invalid call without state overriding
{
@@ -383,12 +389,8 @@ func TestOverriddenTraceCall(t *testing.T) {
To: &randomAccounts[1].addr,
Value: (*hexutil.Big)(big.NewInt(1000)),
},
- config: &TraceCallConfig{
- Tracer: &tracer,
- },
- // After espresso, an error is first thrown while checking if fees can be paid.
+ config: &TraceCallConfig{},
expectErr: core.ErrInsufficientFunds,
- expect: nil,
},
// Successful simple contract call
//
@@ -414,7 +416,7 @@ func TestOverriddenTraceCall(t *testing.T) {
Data: newRPCBytes(common.Hex2Bytes("8381f58a")), // call number()
},
config: &TraceCallConfig{
- Tracer: &tracer,
+ //Tracer: &tracer,
StateOverrides: ðapi.StateOverride{
randomAccounts[2].addr: ethapi.OverrideAccount{
Code: newRPCBytes(common.Hex2Bytes("6080604052348015600f57600080fd5b506004361060285760003560e01c80638381f58a14602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000548156fea2646970667358221220eab35ffa6ab2adfe380772a48b8ba78e82a1b820a18fcb6f59aa4efb20a5f60064736f6c63430007040033")),
@@ -422,49 +424,38 @@ func TestOverriddenTraceCall(t *testing.T) {
},
},
},
- expectErr: nil,
- expect: &callTrace{
- Type: "CALL",
- From: randomAccounts[0].addr,
- To: randomAccounts[2].addr,
- Input: hexutil.Bytes(common.Hex2Bytes("8381f58a")),
- Output: hexutil.Bytes(common.BigToHash(big.NewInt(123)).Bytes()),
- Gas: newRPCUint64(24978936),
- GasUsed: newRPCUint64(2283),
- Value: (*hexutil.Big)(big.NewInt(0)),
- },
+ want: `{"gas":23347,"failed":false,"returnValue":"000000000000000000000000000000000000000000000000000000000000007b"}`,
},
}
- for i, testspec := range testSuite {
- result, err := api.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config)
- if testspec.expectErr != nil {
+ for i, tc := range testSuite {
+ result, err := api.TraceCall(context.Background(), tc.call, rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, tc.config)
+ if tc.expectErr != nil {
if err == nil {
- t.Errorf("test %d: want error %v, have nothing", i, testspec.expectErr)
+ t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr)
continue
}
- if !errors.Is(err, testspec.expectErr) {
- t.Errorf("test %d: error mismatch, want %v, have %v", i, testspec.expectErr, err)
- }
- } else {
- if err != nil {
- t.Errorf("test %d: want no error, have %v", i, err)
- continue
- }
- ret := new(callTrace)
- if err := json.Unmarshal(result.(json.RawMessage), ret); err != nil {
- t.Fatalf("test %d: failed to unmarshal trace result: %v", i, err)
- }
- if !jsonEqual(ret, testspec.expect) {
- // uncomment this for easier debugging
- //have, _ := json.MarshalIndent(ret, "", " ")
- //want, _ := json.MarshalIndent(testspec.expect, "", " ")
- //t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
- t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, testspec.expect)
+ if !errors.Is(err, tc.expectErr) {
+ t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err)
}
+ continue
+ }
+ if err != nil {
+ t.Errorf("test %d: want no error, have %v", i, err)
+ continue
+ }
+ // Turn result into res-struct
+ var (
+ have res
+ want res
+ )
+ resBytes, _ := json.Marshal(result)
+ json.Unmarshal(resBytes, &have)
+ json.Unmarshal([]byte(tc.want), &want)
+ if !reflect.DeepEqual(have, want) {
+ t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, string(resBytes), want)
}
}
}
-
func TestTraceTransaction(t *testing.T) {
t.Parallel()
@@ -498,243 +489,6 @@ func TestTraceTransaction(t *testing.T) {
}
}
-// Regression test for debug get/set logic in the EVM & Interpreter.
-// Include registry to ensure that Celo-specific EVM Call's within
-// Tobin Tax and fee distribution logic are triggered.
-func TestTraceTransactionWithRegistryDeployed(t *testing.T) {
- t.Parallel()
-
- // Initialize test accounts
- accounts := newAccounts(2)
- genesis := &core.Genesis{Alloc: core.GenesisAlloc{
- accounts[0].addr: {Balance: big.NewInt(params.Ether)},
- accounts[1].addr: {Balance: big.NewInt(params.Ether)},
- common.HexToAddress("0xce10"): { // Registry Proxy
- Code: testutil.RegistryProxyOpcodes,
- Storage: map[common.Hash]common.Hash{
- common.HexToHash("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"): common.HexToHash("0xce11"), // Registry Implementation
- common.HexToHash("0x91646b8507bf2e54d7c3de9155442ba111546b81af1cbdd1f68eeb6926b98d58"): common.HexToHash("0xd023"), // Governance Proxy
- },
- Balance: big.NewInt(0),
- },
- common.HexToAddress("0xce11"): { // Registry Implementation
- Code: testutil.RegistryOpcodes,
- Balance: big.NewInt(0),
- },
- }}
-
- target := common.Hash{}
- signer := types.HomesteadSigner{}
- api := NewAPI(newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) {
- // Transfer from account[0] to account[1]
- // value: 1000 wei
- // fee: 0 wei
- tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.MinimumGasPrice(nil), nil), signer, accounts[0].key)
- b.AddTx(tx)
- target = tx.Hash()
- }))
- result, err := api.TraceTransaction(context.Background(), target, nil)
- if err != nil {
- t.Errorf("Failed to trace transaction %v", err)
- }
- if !reflect.DeepEqual(result, ðapi.ExecutionResult{
- Gas: params.TxGas,
- Failed: false,
- ReturnValue: "",
- StructLogs: []ethapi.StructLogRes{},
- }) {
- t.Error("Transaction tracing result is different")
- }
-}
-
-// Use the callTracer to trace a native CELO transfer after the
-// registry has been deployed, as above.
-func TestCallTraceTransactionNativeTransfer(t *testing.T) {
- t.Parallel()
-
- // Initialize test accounts
- accounts := newAccounts(2)
- genesis := &core.Genesis{Alloc: core.GenesisAlloc{
- accounts[0].addr: {Balance: big.NewInt(params.Ether)},
- accounts[1].addr: {Balance: big.NewInt(params.Ether)},
- common.HexToAddress("0xce10"): { // Registry Proxy
- Code: testutil.RegistryProxyOpcodes,
- Storage: map[common.Hash]common.Hash{
- // Hashes represent the storage slot for Registry.sol's `registry` mapping
- // which is stored in the RegistryProxy's storage.
- // Hashes are computed by taking: keccack(packed(config.GoldTokenRegistryId, 1)),
- // where 1 is the storage offset)
- common.HexToHash("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"): common.HexToHash("0xce11"), // Registry Implementation
- common.HexToHash("0x91646b8507bf2e54d7c3de9155442ba111546b81af1cbdd1f68eeb6926b98d58"): common.HexToHash("0xd023"), // Governance Proxy
- },
- Balance: big.NewInt(0),
- },
- common.HexToAddress("0xce11"): { // Registry Implementation
- Code: testutil.RegistryOpcodes,
- Balance: big.NewInt(0),
- },
- }}
-
- target := common.Hash{}
- signer := types.HomesteadSigner{}
- transferVal := big.NewInt(1000)
- api := NewAPI(newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) {
- // Transfer from account[0] to account[1]
- // value: 1000 wei
- // fee: 0 wei
- tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, transferVal, params.TxGas, b.MinimumGasPrice(nil), nil), signer, accounts[0].key)
- b.AddTx(tx)
- target = tx.Hash()
- }))
- tracerStr := "callTracer"
- result, err := api.TraceTransaction(context.Background(), target, &TraceConfig{Tracer: &tracerStr})
- if err != nil {
- t.Errorf("Failed to trace transaction %v", err)
- }
-
- ret := new(callTrace)
- if err := json.Unmarshal(result.(json.RawMessage), ret); err != nil {
- t.Fatalf("failed to unmarshal trace result: %v", err)
- }
- expectedTrace := &callTrace{
- Type: "CALL",
- From: accounts[0].addr,
- To: accounts[1].addr,
- Input: hexutil.Bytes(common.Hex2Bytes("0x")),
- Output: hexutil.Bytes(common.Hex2Bytes("0x")),
- Gas: newRPCUint64(0),
- GasUsed: newRPCUint64(0),
- Value: (*hexutil.Big)(transferVal),
- }
- if !jsonEqual(ret, expectedTrace) {
- t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, expectedTrace)
- }
-}
-
-// Use the callTracer to trace a CELO transfer made via the transfer
-// precompile (by sending this from the registered GoldToken contract).
-func TestCallTraceTransactionPrecompileTransfer(t *testing.T) {
- // Invoke the transfer precompile by sending a transfer from an account
- // registered as GoldToken in the mock registry
- t.Parallel()
- // Initialize test accounts
- accounts := newAccountsWithoutBytesWithZero(3)
- goldToken := accounts[0]
- registryProxyAddr := common.HexToAddress("0xce10")
- registryImplAddr := common.HexToAddress("0xce11")
- genesis := &core.Genesis{Alloc: core.GenesisAlloc{
- goldToken.addr: {Balance: big.NewInt(params.Ether)},
- accounts[1].addr: {Balance: big.NewInt(params.Ether)},
- accounts[2].addr: {Balance: big.NewInt(params.Ether)},
- registryProxyAddr: { // Registry Proxy
- Code: testutil.RegistryProxyOpcodes,
- Storage: map[common.Hash]common.Hash{
- common.HexToHash("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"): common.HexToHash("0xce11"), // Registry Implementation
- common.HexToHash("0x91646b8507bf2e54d7c3de9155442ba111546b81af1cbdd1f68eeb6926b98d58"): common.HexToHash("0xd023"), // Governance Proxy
- common.HexToHash("0x773cc8652456781771d48fb3d560d7ba3d6d1882654492b68beec583d2c590aa"): goldToken.addr.Hash(), // GoldToken Implementation
- common.HexToHash("0x2131a4338f6fb8d4507e234a7c72af8efefbbf2f1817ed570bce33eb6667feb9"): common.HexToHash("0xd007"), // Reserve Implementation
- },
- Balance: big.NewInt(0),
- },
- registryImplAddr: { // Registry Implementation
- Code: testutil.RegistryOpcodes,
- Balance: big.NewInt(0),
- },
- }}
-
- target := common.Hash{}
- signer := types.HomesteadSigner{}
- transferVal := big.NewInt(1000)
-
- // Construct and pack data for transfer precompile representing [from, to, value]
- addressTy, err := abi.NewType("address", "", nil)
- if err != nil {
- t.Fatalf("failed to create address type: %v", err)
- }
- uint256Ty, err := abi.NewType("uint256", "", nil)
- if err != nil {
- t.Fatalf("failed to create uint256 type: %v", err)
- }
-
- transferArgs := abi.Arguments{
- {
- Type: addressTy,
- },
- {
- Type: addressTy,
- },
- {
- Type: uint256Ty,
- },
- }
- data, err := transferArgs.Pack(accounts[1].addr, accounts[2].addr, transferVal)
- if err != nil {
- t.Fatalf("failed to pack args: %v", err)
- }
- transferPrecompile := common.HexToAddress("0xfd")
- gas := params.TxGas * 2
- api := NewAPI(newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) {
- // Transfer via transfer precompile by sending tx from GoldToken addr
- tx, _ := types.SignTx(types.NewTransaction(uint64(i), transferPrecompile, big.NewInt(0), gas, b.MinimumGasPrice(nil), data), signer, goldToken.key)
- b.AddTx(tx)
- target = tx.Hash()
- }))
- tracerStr := "callTracer"
- result, err := api.TraceTransaction(context.Background(), target, &TraceConfig{Tracer: &tracerStr})
-
- if err != nil {
- t.Errorf("Failed to trace transaction %v", err)
- }
-
- ret := new(callTrace)
- if err := json.Unmarshal(result.(json.RawMessage), ret); err != nil {
- t.Fatalf("failed to unmarshal trace result: %v", err)
- }
-
- // Registry ABI packed version of Registry.getAddressFor("GoldToken")
- packedGetAddressForGoldToken := common.FromHex("0xdd927233d7e89ade8430819f08bf97a087285824af3351ee12d72a2d132b0c6c0687bfaf")
- expectedTrace := &callTrace{
- // Outer transaction call
- Type: "CALL",
- From: goldToken.addr,
- To: transferPrecompile,
- Input: data,
- Output: data,
- Gas: newRPCUint64(20112),
- // Note that top-level traces do not currently include intrinsic gas
- GasUsed: newRPCUint64(params.CallValueTransferGas),
- Value: (*hexutil.Big)(big.NewInt(0)),
- Calls: []callTrace{
- {
- // Lookup of GoldToken contract address with capped gas
- Type: "STATICCALL",
- From: common.ZeroAddress,
- To: registryProxyAddr,
- Input: packedGetAddressForGoldToken,
- Output: common.LeftPadBytes(goldToken.addr.Bytes(), 32),
- Gas: newRPCUint64(contracts.MaxGasForGetAddressFor),
- GasUsed: newRPCUint64(0),
- Calls: []callTrace{
- {
- // RegistryProxy -> RegistryImpl delegate call to
- // retrieve GoldToken address
- Type: "DELEGATECALL",
- From: registryProxyAddr,
- To: registryImplAddr,
- Input: packedGetAddressForGoldToken,
- Output: common.LeftPadBytes(goldToken.addr.Bytes(), 32),
- Gas: newRPCUint64(0),
- GasUsed: newRPCUint64(0),
- },
- },
- },
- },
- }
- if !jsonEqual(ret, expectedTrace) {
- t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, expectedTrace)
- }
-}
-
func TestTraceBlock(t *testing.T) {
t.Parallel()
@@ -758,90 +512,55 @@ func TestTraceBlock(t *testing.T) {
var testSuite = []struct {
blockNumber rpc.BlockNumber
config *TraceConfig
- expect interface{}
+ want string
expectErr error
}{
// Trace genesis block, expect error
{
blockNumber: rpc.BlockNumber(0),
- config: nil,
- expect: nil,
expectErr: errors.New("genesis is not traceable"),
},
// Trace head block
{
blockNumber: rpc.BlockNumber(genBlocks),
- config: nil,
- expectErr: nil,
- expect: []*txTraceResult{
- {
- Result: ðapi.ExecutionResult{
- Gas: params.TxGas,
- Failed: false,
- ReturnValue: "",
- StructLogs: []ethapi.StructLogRes{},
- },
- },
- },
+ want: `[{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`,
},
// Trace non-existent block
{
blockNumber: rpc.BlockNumber(genBlocks + 1),
- config: nil,
expectErr: fmt.Errorf("block #%d not found", genBlocks+1),
- expect: nil,
},
// Trace latest block
{
blockNumber: rpc.LatestBlockNumber,
- config: nil,
- expectErr: nil,
- expect: []*txTraceResult{
- {
- Result: ðapi.ExecutionResult{
- Gas: params.TxGas,
- Failed: false,
- ReturnValue: "",
- StructLogs: []ethapi.StructLogRes{},
- },
- },
- },
+ want: `[{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`,
},
// Trace pending block
{
blockNumber: rpc.PendingBlockNumber,
- config: nil,
- expectErr: nil,
- expect: []*txTraceResult{
- {
- Result: ðapi.ExecutionResult{
- Gas: params.TxGas,
- Failed: false,
- ReturnValue: "",
- StructLogs: []ethapi.StructLogRes{},
- },
- },
- },
+ want: `[{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`,
},
}
- for _, testspec := range testSuite {
- result, err := api.TraceBlockByNumber(context.Background(), testspec.blockNumber, testspec.config)
- if testspec.expectErr != nil {
+ for i, tc := range testSuite {
+ result, err := api.TraceBlockByNumber(context.Background(), tc.blockNumber, tc.config)
+ if tc.expectErr != nil {
if err == nil {
- t.Errorf("Expect error %v, get nothing", testspec.expectErr)
- continue
- }
- if !reflect.DeepEqual(err, testspec.expectErr) {
- t.Errorf("Error mismatch, want %v, get %v", testspec.expectErr, err)
- }
- } else {
- if err != nil {
- t.Errorf("Expect no error, get %v", err)
+ t.Errorf("test %d, want error %v", i, tc.expectErr)
continue
}
- if !reflect.DeepEqual(result, testspec.expect) {
- t.Errorf("Result mismatch, want %v, get %v", testspec.expect, result)
+ if !reflect.DeepEqual(err, tc.expectErr) {
+ t.Errorf("test %d: error mismatch, want %v, get %v", i, tc.expectErr, err)
}
+ continue
+ }
+ if err != nil {
+ t.Errorf("test %d, want no error, have %v", i, err)
+ continue
+ }
+ have, _ := json.Marshal(result)
+ want := tc.want
+ if string(have) != want {
+ t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, string(have), want)
}
}
}
@@ -922,6 +641,55 @@ func TestTraceBlockWithEIP1559Tx(t *testing.T) {
}
}
+// Regression test for debug get/set logic in the EVM & Interpreter.
+// Include registry to ensure that Celo-specific EVM Call's within
+// Tobin Tax and fee distribution logic are triggered.
+func TestTraceTransactionWithRegistryDeployed(t *testing.T) {
+ t.Parallel()
+
+ // Initialize test accounts
+ accounts := newAccounts(2)
+ genesis := &core.Genesis{Alloc: core.GenesisAlloc{
+ accounts[0].addr: {Balance: big.NewInt(params.Ether)},
+ accounts[1].addr: {Balance: big.NewInt(params.Ether)},
+ common.HexToAddress("0xce10"): { // Registry Proxy
+ Code: testutil.RegistryProxyOpcodes,
+ Storage: map[common.Hash]common.Hash{
+ common.HexToHash("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"): common.HexToHash("0xce11"), // Registry Implementation
+ common.HexToHash("0x91646b8507bf2e54d7c3de9155442ba111546b81af1cbdd1f68eeb6926b98d58"): common.HexToHash("0xd023"), // Governance Proxy
+ },
+ Balance: big.NewInt(0),
+ },
+ common.HexToAddress("0xce11"): { // Registry Implementation
+ Code: testutil.RegistryOpcodes,
+ Balance: big.NewInt(0),
+ },
+ }}
+
+ target := common.Hash{}
+ signer := types.HomesteadSigner{}
+ api := NewAPI(newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) {
+ // Transfer from account[0] to account[1]
+ // value: 1000 wei
+ // fee: 0 wei
+ tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.MinimumGasPrice(nil), nil), signer, accounts[0].key)
+ b.AddTx(tx)
+ target = tx.Hash()
+ }))
+ result, err := api.TraceTransaction(context.Background(), target, nil)
+ if err != nil {
+ t.Errorf("Failed to trace transaction %v", err)
+ }
+ if !reflect.DeepEqual(result, ðapi.ExecutionResult{
+ Gas: params.TxGas,
+ Failed: false,
+ ReturnValue: "",
+ StructLogs: []ethapi.StructLogRes{},
+ }) {
+ t.Error("Transaction tracing result is different")
+ }
+}
+
type Account struct {
key *ecdsa.PrivateKey
addr common.Address
@@ -974,11 +742,6 @@ func newRPCBalance(balance *big.Int) **hexutil.Big {
return &rpcBalance
}
-func newRPCUint64(number uint64) *hexutil.Uint64 {
- rpcUint64 := hexutil.Uint64(number)
- return &rpcUint64
-}
-
func newRPCBytes(bytes []byte) *hexutil.Bytes {
rpcBytes := hexutil.Bytes(bytes)
return &rpcBytes
diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go
new file mode 100644
index 0000000000..9d784fcde6
--- /dev/null
+++ b/eth/tracers/internal/tracetest/calltrace_test.go
@@ -0,0 +1,393 @@
+// Copyright 2021 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 .
+
+package tracetest
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "math/big"
+ "path/filepath"
+ "reflect"
+ "strings"
+ "testing"
+ "unicode"
+
+ "github.com/celo-org/celo-blockchain/common"
+ "github.com/celo-org/celo-blockchain/common/hexutil"
+ "github.com/celo-org/celo-blockchain/common/math"
+ "github.com/celo-org/celo-blockchain/contracts/testutil"
+ "github.com/celo-org/celo-blockchain/core"
+ "github.com/celo-org/celo-blockchain/core/rawdb"
+ "github.com/celo-org/celo-blockchain/core/types"
+ "github.com/celo-org/celo-blockchain/core/vm"
+ "github.com/celo-org/celo-blockchain/core/vm/vmcontext"
+ "github.com/celo-org/celo-blockchain/crypto"
+ "github.com/celo-org/celo-blockchain/eth/tracers"
+ "github.com/celo-org/celo-blockchain/params"
+ "github.com/celo-org/celo-blockchain/rlp"
+ "github.com/celo-org/celo-blockchain/tests"
+
+ // Force-load native and js pacakges, to trigger registration
+ _ "github.com/celo-org/celo-blockchain/eth/tracers/js"
+ _ "github.com/celo-org/celo-blockchain/eth/tracers/native"
+)
+
+// To generate a new callTracer test, copy paste the makeTest method below into
+// a Geth console and call it with a transaction hash you which to export.
+
+/*
+// makeTest generates a callTracer test by running a prestate reassembled and a
+// call trace run, assembling all the gathered information into a test case.
+var makeTest = function(tx, rewind) {
+ // Generate the genesis block from the block, transaction and prestate data
+ var block = eth.getBlock(eth.getTransaction(tx).blockHash);
+ var genesis = eth.getBlock(block.parentHash);
+
+ delete genesis.gasUsed;
+ delete genesis.logsBloom;
+ delete genesis.parentHash;
+ delete genesis.receiptsRoot;
+ delete genesis.sha3Uncles;
+ delete genesis.size;
+ delete genesis.transactions;
+ delete genesis.transactionsRoot;
+ delete genesis.uncles;
+
+ genesis.gasLimit = genesis.gasLimit.toString();
+ genesis.number = genesis.number.toString();
+ genesis.timestamp = genesis.timestamp.toString();
+
+ genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer", rewind: rewind});
+ for (var key in genesis.alloc) {
+ genesis.alloc[key].nonce = genesis.alloc[key].nonce.toString();
+ }
+ genesis.config = admin.nodeInfo.protocols.eth.config;
+
+ // Generate the call trace and produce the test input
+ var result = debug.traceTransaction(tx, {tracer: "callTracer", rewind: rewind});
+ delete result.time;
+
+ console.log(JSON.stringify({
+ genesis: genesis,
+ context: {
+ number: block.number.toString(),
+ difficulty: block.difficulty,
+ timestamp: block.timestamp.toString(),
+ gasLimit: block.gasLimit.toString(),
+ miner: block.miner,
+ },
+ input: eth.getRawTransaction(tx),
+ result: result,
+ }, null, 2));
+}
+*/
+
+type callContext struct {
+ Number math.HexOrDecimal64 `json:"number"`
+ Difficulty *math.HexOrDecimal256 `json:"difficulty"`
+ Time math.HexOrDecimal64 `json:"timestamp"`
+ GasLimit math.HexOrDecimal64 `json:"gasLimit"`
+ Miner common.Address `json:"miner"`
+}
+
+// callTrace is the result of a callTracer run.
+type callTrace struct {
+ Type string `json:"type"`
+ From common.Address `json:"from"`
+ To common.Address `json:"to"`
+ Input hexutil.Bytes `json:"input"`
+ Output hexutil.Bytes `json:"output"`
+ Gas *hexutil.Uint64 `json:"gas,omitempty"`
+ GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"`
+ Value *hexutil.Big `json:"value,omitempty"`
+ Error string `json:"error,omitempty"`
+ Calls []callTrace `json:"calls,omitempty"`
+}
+
+// callTracerTest defines a single test to check the call tracer against.
+type callTracerTest struct {
+ Genesis *core.Genesis `json:"genesis"`
+ Context *callContext `json:"context"`
+ Input string `json:"input"`
+ Result *callTrace `json:"result"`
+}
+
+// Iterates over all the input-output datasets in the tracer test harness and
+// runs the JavaScript tracers against them.
+func TestCallTracerLegacy(t *testing.T) {
+ testCallTracer("callTracerLegacy", "call_tracer_legacy", t)
+}
+
+func TestCallTracerJs(t *testing.T) {
+ testCallTracer("callTracerJs", "call_tracer", t)
+}
+
+func TestCallTracerNative(t *testing.T) {
+ testCallTracer("callTracer", "call_tracer", t)
+}
+
+func testCallTracer(tracerName string, dirPath string, t *testing.T) {
+ celoMock := testutil.NewCeloMock()
+ files, err := ioutil.ReadDir(filepath.Join("testdata", dirPath))
+ if err != nil {
+ t.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
+ t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
+ t.Parallel()
+
+ var (
+ test = new(callTracerTest)
+ tx = new(types.Transaction)
+ )
+ // Call tracer test found, read if from disk
+ if blob, err := ioutil.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil {
+ t.Fatalf("failed to read testcase: %v", err)
+ } else if err := json.Unmarshal(blob, test); err != nil {
+ t.Fatalf("failed to parse testcase: %v", err)
+ }
+ if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
+ t.Fatalf("failed to parse testcase input: %v", err)
+ }
+ // Configure a blockchain with the given prestate
+ var (
+ signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
+ origin, _ = signer.Sender(tx)
+ txContext = vm.TxContext{
+ Origin: origin,
+ GasPrice: tx.GasPrice(),
+ }
+ context = vm.BlockContext{
+ CanTransfer: vmcontext.CanTransfer,
+ Transfer: vmcontext.TobinTransfer,
+ Coinbase: test.Context.Miner,
+ BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
+ Time: new(big.Int).SetUint64(uint64(test.Context.Time)),
+ }
+ _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
+ )
+ tracer, err := tracers.New(tracerName, new(tracers.Context))
+ if err != nil {
+ t.Fatalf("failed to create call tracer: %v", err)
+ }
+ evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
+ msg, err := tx.AsMessage(signer, nil)
+ if err != nil {
+ t.Fatalf("failed to prepare transaction for tracing: %v", err)
+ }
+ st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), celoMock.Runner, nil)
+ if _, err = st.TransitionDb(); err != nil {
+ t.Fatalf("failed to execute transaction: %v", err)
+ }
+ // Retrieve the trace result and compare against the etalon
+ res, err := tracer.GetResult()
+ if err != nil {
+ t.Fatalf("failed to retrieve trace result: %v", err)
+ }
+ ret := new(callTrace)
+ if err := json.Unmarshal(res, ret); err != nil {
+ t.Fatalf("failed to unmarshal trace result: %v", err)
+ }
+
+ if !jsonEqual(ret, test.Result) {
+ // uncomment this for easier debugging
+ //have, _ := json.MarshalIndent(ret, "", " ")
+ //want, _ := json.MarshalIndent(test.Result, "", " ")
+ //t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
+ t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result)
+ }
+ })
+ }
+}
+
+// jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
+// comparison
+func jsonEqual(x, y interface{}) bool {
+ xTrace := new(callTrace)
+ yTrace := new(callTrace)
+ if xj, err := json.Marshal(x); err == nil {
+ json.Unmarshal(xj, xTrace)
+ } else {
+ return false
+ }
+ if yj, err := json.Marshal(y); err == nil {
+ json.Unmarshal(yj, yTrace)
+ } else {
+ return false
+ }
+ return reflect.DeepEqual(xTrace, yTrace)
+}
+
+// camel converts a snake cased input string into a camel cased output.
+func camel(str string) string {
+ pieces := strings.Split(str, "_")
+ for i := 1; i < len(pieces); i++ {
+ pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
+ }
+ return strings.Join(pieces, "")
+}
+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("callTracerNative", test, b)
+ })
+ }
+}
+
+func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
+ celoMock := testutil.NewCeloMock()
+ // 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: vmcontext.CanTransfer,
+ Transfer: vmcontext.TobinTransfer,
+ Coinbase: test.Context.Miner,
+ BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
+ Time: new(big.Int).SetUint64(uint64(test.Context.Time)),
+ }
+ _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
+
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ tracer, err := tracers.New(tracerName, new(tracers.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})
+ snap := statedb.Snapshot()
+ st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), celoMock.Runner, nil)
+ if _, err = st.TransitionDb(); err != nil {
+ b.Fatalf("failed to execute transaction: %v", err)
+ }
+ if _, err = tracer.GetResult(); err != nil {
+ b.Fatal(err)
+ }
+ statedb.RevertToSnapshot(snap)
+ }
+}
+
+// TestZeroValueToNotExitCall tests the calltracer(s) on the following:
+// Tx to A, A calls B with zero value. B does not already exist.
+// Expected: that enter/exit is invoked and the inner call is shown in the result
+func TestZeroValueToNotExitCall(t *testing.T) {
+ celoMock := testutil.NewCeloMock()
+ var to = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
+ privkey, err := crypto.HexToECDSA("0000000000000000deadbeef00000000000000000000000000000000deadbeef")
+ if err != nil {
+ t.Fatalf("err %v", err)
+ }
+ signer := types.NewEIP155Signer(big.NewInt(1))
+ tx, err := types.SignNewTx(privkey, signer, &types.LegacyTx{
+ GasPrice: big.NewInt(0),
+ Gas: 50000,
+ To: &to,
+ })
+ if err != nil {
+ t.Fatalf("err %v", err)
+ }
+ origin, _ := signer.Sender(tx)
+ txContext := vm.TxContext{
+ Origin: origin,
+ GasPrice: big.NewInt(1),
+ }
+ context := vm.BlockContext{
+ CanTransfer: vmcontext.CanTransfer,
+ Transfer: vmcontext.TobinTransfer,
+ Coinbase: common.Address{},
+ BlockNumber: new(big.Int).SetUint64(8000000),
+ Time: new(big.Int).SetUint64(5),
+ }
+ var code = []byte{
+ byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero
+ byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS
+ byte(vm.CALL),
+ }
+ var alloc = core.GenesisAlloc{
+ to: core.GenesisAccount{
+ Nonce: 1,
+ Code: code,
+ },
+ origin: core.GenesisAccount{
+ Nonce: 0,
+ Balance: big.NewInt(500000000000000),
+ },
+ }
+ _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
+ // Create the tracer, the EVM environment and run it
+ tracer, err := tracers.New("callTracer", nil)
+ if err != nil {
+ t.Fatalf("failed to create call tracer: %v", err)
+ }
+ evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer})
+ msg, err := tx.AsMessage(signer, nil)
+ if err != nil {
+ t.Fatalf("failed to prepare transaction for tracing: %v", err)
+ }
+ st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), celoMock.Runner, nil)
+ if _, err = st.TransitionDb(); err != nil {
+ t.Fatalf("failed to execute transaction: %v", err)
+ }
+ // Retrieve the trace result and compare against the etalon
+ res, err := tracer.GetResult()
+ if err != nil {
+ t.Fatalf("failed to retrieve trace result: %v", err)
+ }
+ have := new(callTrace)
+ if err := json.Unmarshal(res, have); err != nil {
+ t.Fatalf("failed to unmarshal trace result: %v", err)
+ }
+ wantStr := `{"type":"CALL","from":"0x682a80a6f560eec50d54e63cbeda1c324c5f8d1b","to":"0x00000000000000000000000000000000deadbeef","value":"0x0","gas":"0x7148","gasUsed":"0x2d0","input":"0x","output":"0x","calls":[{"type":"CALL","from":"0x00000000000000000000000000000000deadbeef","to":"0x00000000000000000000000000000000000000ff","value":"0x0","gas":"0x6cbf","gasUsed":"0x0","input":"0x","output":"0x"}]}`
+ want := new(callTrace)
+ json.Unmarshal([]byte(wantStr), want)
+ if !jsonEqual(have, want) {
+ t.Error("have != want")
+ }
+}
diff --git a/eth/tracers/testdata/call_tracer/create.json b/eth/tracers/internal/tracetest/testdata/call_tracer/create.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer/create.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer/create.json
diff --git a/eth/tracers/testdata/call_tracer/deep_calls.json b/eth/tracers/internal/tracetest/testdata/call_tracer/deep_calls.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer/deep_calls.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer/deep_calls.json
diff --git a/eth/tracers/testdata/call_tracer/delegatecall.json b/eth/tracers/internal/tracetest/testdata/call_tracer/delegatecall.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer/delegatecall.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer/delegatecall.json
diff --git a/eth/tracers/testdata/call_tracer/inner_create_oog_outer_throw.json b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_create_oog_outer_throw.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer/inner_create_oog_outer_throw.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer/inner_create_oog_outer_throw.json
diff --git a/eth/tracers/testdata/call_tracer/inner_instafail.json b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_instafail.json
similarity index 99%
rename from eth/tracers/testdata/call_tracer/inner_instafail.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer/inner_instafail.json
index 2e97de626b..e6c27b8162 100644
--- a/eth/tracers/testdata/call_tracer/inner_instafail.json
+++ b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_instafail.json
@@ -38,6 +38,7 @@
"constantinopleBlock": 4230000,
"petersburgBlock": 4939394,
"istanbulBlock": 6485846,
+ "donutBlock": 0,
"muirGlacierBlock": 7117117,
"ethash": {}
}
diff --git a/eth/tracers/testdata/call_tracer/inner_throw_outer_revert.json b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_throw_outer_revert.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer/inner_throw_outer_revert.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer/inner_throw_outer_revert.json
diff --git a/eth/tracers/testdata/call_tracer/oog.json b/eth/tracers/internal/tracetest/testdata/call_tracer/oog.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer/oog.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer/oog.json
diff --git a/eth/tracers/testdata/call_tracer/revert.json b/eth/tracers/internal/tracetest/testdata/call_tracer/revert.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer/revert.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer/revert.json
diff --git a/eth/tracers/testdata/call_tracer/revert_reason.json b/eth/tracers/internal/tracetest/testdata/call_tracer/revert_reason.json
similarity index 99%
rename from eth/tracers/testdata/call_tracer/revert_reason.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer/revert_reason.json
index fda0b2d4f1..ba3c15231b 100644
--- a/eth/tracers/testdata/call_tracer/revert_reason.json
+++ b/eth/tracers/internal/tracetest/testdata/call_tracer/revert_reason.json
@@ -27,7 +27,7 @@
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
- "IstanbulBlock":1561651,
+ "IstanbulBlock": 1561651,
"chainId": 5,
"daoForkSupport": true,
"eip150Block": 0,
diff --git a/eth/tracers/testdata/call_tracer/selfdestruct.json b/eth/tracers/internal/tracetest/testdata/call_tracer/selfdestruct.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer/selfdestruct.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer/selfdestruct.json
diff --git a/eth/tracers/testdata/call_tracer/simple.json b/eth/tracers/internal/tracetest/testdata/call_tracer/simple.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer/simple.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer/simple.json
diff --git a/eth/tracers/testdata/call_tracer/throw.json b/eth/tracers/internal/tracetest/testdata/call_tracer/throw.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer/throw.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer/throw.json
diff --git a/eth/tracers/testdata/call_tracer_legacy/create.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/create.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer_legacy/create.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer_legacy/create.json
diff --git a/eth/tracers/testdata/call_tracer_legacy/deep_calls.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/deep_calls.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer_legacy/deep_calls.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer_legacy/deep_calls.json
diff --git a/eth/tracers/testdata/call_tracer_legacy/delegatecall.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/delegatecall.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer_legacy/delegatecall.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer_legacy/delegatecall.json
diff --git a/eth/tracers/testdata/call_tracer_legacy/inner_create_oog_outer_throw.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_create_oog_outer_throw.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer_legacy/inner_create_oog_outer_throw.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_create_oog_outer_throw.json
diff --git a/eth/tracers/testdata/call_tracer_legacy/inner_instafail.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_instafail.json
similarity index 97%
rename from eth/tracers/testdata/call_tracer_legacy/inner_instafail.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_instafail.json
index 4da82bbb17..ee392c198b 100644
--- a/eth/tracers/testdata/call_tracer_legacy/inner_instafail.json
+++ b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_instafail.json
@@ -28,6 +28,7 @@
"config": {
"chainId": 3,
"homesteadBlock": 0,
+ "donutBlock": 0,
"daoForkSupport": true,
"eip150Block": 0,
"eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d",
@@ -65,7 +66,7 @@
"from": "0x6c06b16512b332e6cd8293a2974872674716ce18",
"to": "0x66fdfd05e46126a07465ad24e40cc0597bc1ef31",
"value": "0x14d1120d7b160000",
- "error":"internal failure",
+ "error": "internal failure",
"input": "0x"
}
]
diff --git a/eth/tracers/testdata/call_tracer_legacy/inner_throw_outer_revert.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_throw_outer_revert.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer_legacy/inner_throw_outer_revert.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_throw_outer_revert.json
diff --git a/eth/tracers/testdata/call_tracer_legacy/oog.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/oog.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer_legacy/oog.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer_legacy/oog.json
diff --git a/eth/tracers/testdata/call_tracer_legacy/revert.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/revert.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer_legacy/revert.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer_legacy/revert.json
diff --git a/eth/tracers/testdata/call_tracer_legacy/revert_reason.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/revert_reason.json
similarity index 99%
rename from eth/tracers/testdata/call_tracer_legacy/revert_reason.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer_legacy/revert_reason.json
index fda0b2d4f1..ba3c15231b 100644
--- a/eth/tracers/testdata/call_tracer_legacy/revert_reason.json
+++ b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/revert_reason.json
@@ -27,7 +27,7 @@
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
- "IstanbulBlock":1561651,
+ "IstanbulBlock": 1561651,
"chainId": 5,
"daoForkSupport": true,
"eip150Block": 0,
diff --git a/eth/tracers/testdata/call_tracer_legacy/selfdestruct.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/selfdestruct.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer_legacy/selfdestruct.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer_legacy/selfdestruct.json
diff --git a/eth/tracers/testdata/call_tracer_legacy/simple.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/simple.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer_legacy/simple.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer_legacy/simple.json
diff --git a/eth/tracers/testdata/call_tracer_legacy/throw.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/throw.json
similarity index 100%
rename from eth/tracers/testdata/call_tracer_legacy/throw.json
rename to eth/tracers/internal/tracetest/testdata/call_tracer_legacy/throw.json
diff --git a/eth/tracers/tracer.go b/eth/tracers/js/bigint.go
similarity index 50%
rename from eth/tracers/tracer.go
rename to eth/tracers/js/bigint.go
index e56f61089c..9aeb330420 100644
--- a/eth/tracers/tracer.go
+++ b/eth/tracers/js/bigint.go
@@ -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.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
@@ -14,856 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package tracers
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "math/big"
- "sync/atomic"
- "time"
- "unsafe"
-
- "github.com/celo-org/celo-blockchain/common"
- "github.com/celo-org/celo-blockchain/common/hexutil"
- "github.com/celo-org/celo-blockchain/core"
- "github.com/celo-org/celo-blockchain/core/vm"
- "github.com/celo-org/celo-blockchain/crypto"
- "github.com/celo-org/celo-blockchain/log"
- "gopkg.in/olebedev/go-duktape.v3"
-)
+package js
// bigIntegerJS is the minified version of https://github.com/peterolson/BigInteger.js.
const bigIntegerJS = `var bigInt=function(undefined){"use strict";var BASE=1e7,LOG_BASE=7,MAX_INT=9007199254740992,MAX_INT_ARR=smallToArray(MAX_INT),LOG_MAX_INT=Math.log(MAX_INT);function Integer(v,radix){if(typeof v==="undefined")return Integer[0];if(typeof radix!=="undefined")return+radix===10?parseValue(v):parseBase(v,radix);return parseValue(v)}function BigInteger(value,sign){this.value=value;this.sign=sign;this.isSmall=false}BigInteger.prototype=Object.create(Integer.prototype);function SmallInteger(value){this.value=value;this.sign=value<0;this.isSmall=true}SmallInteger.prototype=Object.create(Integer.prototype);function isPrecise(n){return-MAX_INT0)return Math.floor(n);return Math.ceil(n)}function add(a,b){var l_a=a.length,l_b=b.length,r=new Array(l_a),carry=0,base=BASE,sum,i;for(i=0;i=base?1:0;r[i]=sum-carry*base}while(i0)r.push(carry);return r}function addAny(a,b){if(a.length>=b.length)return add(a,b);return add(b,a)}function addSmall(a,carry){var l=a.length,r=new Array(l),base=BASE,sum,i;for(i=0;i0){r[i++]=carry%base;carry=Math.floor(carry/base)}return r}BigInteger.prototype.add=function(v){var n=parseValue(v);if(this.sign!==n.sign){return this.subtract(n.negate())}var a=this.value,b=n.value;if(n.isSmall){return new BigInteger(addSmall(a,Math.abs(b)),this.sign)}return new BigInteger(addAny(a,b),this.sign)};BigInteger.prototype.plus=BigInteger.prototype.add;SmallInteger.prototype.add=function(v){var n=parseValue(v);var a=this.value;if(a<0!==n.sign){return this.subtract(n.negate())}var b=n.value;if(n.isSmall){if(isPrecise(a+b))return new SmallInteger(a+b);b=smallToArray(Math.abs(b))}return new BigInteger(addSmall(b,Math.abs(a)),a<0)};SmallInteger.prototype.plus=SmallInteger.prototype.add;function subtract(a,b){var a_l=a.length,b_l=b.length,r=new Array(a_l),borrow=0,base=BASE,i,difference;for(i=0;i=0){value=subtract(a,b)}else{value=subtract(b,a);sign=!sign}value=arrayToSmall(value);if(typeof value==="number"){if(sign)value=-value;return new SmallInteger(value)}return new BigInteger(value,sign)}function subtractSmall(a,b,sign){var l=a.length,r=new Array(l),carry=-b,base=BASE,i,difference;for(i=0;i=0)};SmallInteger.prototype.minus=SmallInteger.prototype.subtract;BigInteger.prototype.negate=function(){return new BigInteger(this.value,!this.sign)};SmallInteger.prototype.negate=function(){var sign=this.sign;var small=new SmallInteger(-this.value);small.sign=!sign;return small};BigInteger.prototype.abs=function(){return new BigInteger(this.value,false)};SmallInteger.prototype.abs=function(){return new SmallInteger(Math.abs(this.value))};function multiplyLong(a,b){var a_l=a.length,b_l=b.length,l=a_l+b_l,r=createArray(l),base=BASE,product,carry,i,a_i,b_j;for(i=0;i0){r[i++]=carry%base;carry=Math.floor(carry/base)}return r}function shiftLeft(x,n){var r=[];while(n-- >0)r.push(0);return r.concat(x)}function multiplyKaratsuba(x,y){var n=Math.max(x.length,y.length);if(n<=30)return multiplyLong(x,y);n=Math.ceil(n/2);var b=x.slice(n),a=x.slice(0,n),d=y.slice(n),c=y.slice(0,n);var ac=multiplyKaratsuba(a,c),bd=multiplyKaratsuba(b,d),abcd=multiplyKaratsuba(addAny(a,b),addAny(c,d));var product=addAny(addAny(ac,shiftLeft(subtract(subtract(abcd,ac),bd),n)),shiftLeft(bd,2*n));trim(product);return product}function useKaratsuba(l1,l2){return-.012*l1-.012*l2+15e-6*l1*l2>0}BigInteger.prototype.multiply=function(v){var n=parseValue(v),a=this.value,b=n.value,sign=this.sign!==n.sign,abs;if(n.isSmall){if(b===0)return Integer[0];if(b===1)return this;if(b===-1)return this.negate();abs=Math.abs(b);if(abs=0;shift--){quotientDigit=base-1;if(remainder[shift+b_l]!==divisorMostSignificantDigit){quotientDigit=Math.floor((remainder[shift+b_l]*base+remainder[shift+b_l-1])/divisorMostSignificantDigit)}carry=0;borrow=0;l=divisor.length;for(i=0;ib_l){highx=(highx+1)*base}guess=Math.ceil(highx/highy);do{check=multiplySmall(b,guess);if(compareAbs(check,part)<=0)break;guess--}while(guess);result.push(guess);part=subtract(part,check)}result.reverse();return[arrayToSmall(result),arrayToSmall(part)]}function divModSmall(value,lambda){var length=value.length,quotient=createArray(length),base=BASE,i,q,remainder,divisor;remainder=0;for(i=length-1;i>=0;--i){divisor=remainder*base+value[i];q=truncate(divisor/lambda);remainder=divisor-q*lambda;quotient[i]=q|0}return[quotient,remainder|0]}function divModAny(self,v){var value,n=parseValue(v);var a=self.value,b=n.value;var quotient;if(b===0)throw new Error("Cannot divide by zero");if(self.isSmall){if(n.isSmall){return[new SmallInteger(truncate(a/b)),new SmallInteger(a%b)]}return[Integer[0],self]}if(n.isSmall){if(b===1)return[self,Integer[0]];if(b==-1)return[self.negate(),Integer[0]];var abs=Math.abs(b);if(absb.length?1:-1}for(var i=a.length-1;i>=0;i--){if(a[i]!==b[i])return a[i]>b[i]?1:-1}return 0}BigInteger.prototype.compareAbs=function(v){var n=parseValue(v),a=this.value,b=n.value;if(n.isSmall)return 1;return compareAbs(a,b)};SmallInteger.prototype.compareAbs=function(v){var n=parseValue(v),a=Math.abs(this.value),b=n.value;if(n.isSmall){b=Math.abs(b);return a===b?0:a>b?1:-1}return-1};BigInteger.prototype.compare=function(v){if(v===Infinity){return-1}if(v===-Infinity){return 1}var n=parseValue(v),a=this.value,b=n.value;if(this.sign!==n.sign){return n.sign?1:-1}if(n.isSmall){return this.sign?-1:1}return compareAbs(a,b)*(this.sign?-1:1)};BigInteger.prototype.compareTo=BigInteger.prototype.compare;SmallInteger.prototype.compare=function(v){if(v===Infinity){return-1}if(v===-Infinity){return 1}var n=parseValue(v),a=this.value,b=n.value;if(n.isSmall){return a==b?0:a>b?1:-1}if(a<0!==n.sign){return a<0?-1:1}return a<0?1:-1};SmallInteger.prototype.compareTo=SmallInteger.prototype.compare;BigInteger.prototype.equals=function(v){return this.compare(v)===0};SmallInteger.prototype.eq=SmallInteger.prototype.equals=BigInteger.prototype.eq=BigInteger.prototype.equals;BigInteger.prototype.notEquals=function(v){return this.compare(v)!==0};SmallInteger.prototype.neq=SmallInteger.prototype.notEquals=BigInteger.prototype.neq=BigInteger.prototype.notEquals;BigInteger.prototype.greater=function(v){return this.compare(v)>0};SmallInteger.prototype.gt=SmallInteger.prototype.greater=BigInteger.prototype.gt=BigInteger.prototype.greater;BigInteger.prototype.lesser=function(v){return this.compare(v)<0};SmallInteger.prototype.lt=SmallInteger.prototype.lesser=BigInteger.prototype.lt=BigInteger.prototype.lesser;BigInteger.prototype.greaterOrEquals=function(v){return this.compare(v)>=0};SmallInteger.prototype.geq=SmallInteger.prototype.greaterOrEquals=BigInteger.prototype.geq=BigInteger.prototype.greaterOrEquals;BigInteger.prototype.lesserOrEquals=function(v){return this.compare(v)<=0};SmallInteger.prototype.leq=SmallInteger.prototype.lesserOrEquals=BigInteger.prototype.leq=BigInteger.prototype.lesserOrEquals;BigInteger.prototype.isEven=function(){return(this.value[0]&1)===0};SmallInteger.prototype.isEven=function(){return(this.value&1)===0};BigInteger.prototype.isOdd=function(){return(this.value[0]&1)===1};SmallInteger.prototype.isOdd=function(){return(this.value&1)===1};BigInteger.prototype.isPositive=function(){return!this.sign};SmallInteger.prototype.isPositive=function(){return this.value>0};BigInteger.prototype.isNegative=function(){return this.sign};SmallInteger.prototype.isNegative=function(){return this.value<0};BigInteger.prototype.isUnit=function(){return false};SmallInteger.prototype.isUnit=function(){return Math.abs(this.value)===1};BigInteger.prototype.isZero=function(){return false};SmallInteger.prototype.isZero=function(){return this.value===0};BigInteger.prototype.isDivisibleBy=function(v){var n=parseValue(v);var value=n.value;if(value===0)return false;if(value===1)return true;if(value===2)return this.isEven();return this.mod(n).equals(Integer[0])};SmallInteger.prototype.isDivisibleBy=BigInteger.prototype.isDivisibleBy;function isBasicPrime(v){var n=v.abs();if(n.isUnit())return false;if(n.equals(2)||n.equals(3)||n.equals(5))return true;if(n.isEven()||n.isDivisibleBy(3)||n.isDivisibleBy(5))return false;if(n.lesser(25))return true}BigInteger.prototype.isPrime=function(){var isPrime=isBasicPrime(this);if(isPrime!==undefined)return isPrime;var n=this.abs(),nPrev=n.prev();var a=[2,3,5,7,11,13,17,19],b=nPrev,d,t,i,x;while(b.isEven())b=b.divide(2);for(i=0;i-MAX_INT)return new SmallInteger(value-1);return new BigInteger(MAX_INT_ARR,true)};var powersOfTwo=[1];while(2*powersOfTwo[powersOfTwo.length-1]<=BASE)powersOfTwo.push(2*powersOfTwo[powersOfTwo.length-1]);var powers2Length=powersOfTwo.length,highestPower2=powersOfTwo[powers2Length-1];function shift_isSmall(n){return(typeof n==="number"||typeof n==="string")&&+Math.abs(n)<=BASE||n instanceof BigInteger&&n.value.length<=1}BigInteger.prototype.shiftLeft=function(n){if(!shift_isSmall(n)){throw new Error(String(n)+" is too large for shifting.")}n=+n;if(n<0)return this.shiftRight(-n);var result=this;while(n>=powers2Length){result=result.multiply(highestPower2);n-=powers2Length-1}return result.multiply(powersOfTwo[n])};SmallInteger.prototype.shiftLeft=BigInteger.prototype.shiftLeft;BigInteger.prototype.shiftRight=function(n){var remQuo;if(!shift_isSmall(n)){throw new Error(String(n)+" is too large for shifting.")}n=+n;if(n<0)return this.shiftLeft(-n);var result=this;while(n>=powers2Length){if(result.isZero())return result;remQuo=divModAny(result,highestPower2);result=remQuo[1].isNegative()?remQuo[0].prev():remQuo[0];n-=powers2Length-1}remQuo=divModAny(result,powersOfTwo[n]);return remQuo[1].isNegative()?remQuo[0].prev():remQuo[0]};SmallInteger.prototype.shiftRight=BigInteger.prototype.shiftRight;function bitwise(x,y,fn){y=parseValue(y);var xSign=x.isNegative(),ySign=y.isNegative();var xRem=xSign?x.not():x,yRem=ySign?y.not():y;var xDigit=0,yDigit=0;var xDivMod=null,yDivMod=null;var result=[];while(!xRem.isZero()||!yRem.isZero()){xDivMod=divModAny(xRem,highestPower2);xDigit=xDivMod[1].toJSNumber();if(xSign){xDigit=highestPower2-1-xDigit}yDivMod=divModAny(yRem,highestPower2);yDigit=yDivMod[1].toJSNumber();if(ySign){yDigit=highestPower2-1-yDigit}xRem=xDivMod[0];yRem=yDivMod[0];result.push(fn(xDigit,yDigit))}var sum=fn(xSign?1:0,ySign?1:0)!==0?bigInt(-1):bigInt(0);for(var i=result.length-1;i>=0;i-=1){sum=sum.multiply(highestPower2).add(bigInt(result[i]))}return sum}BigInteger.prototype.not=function(){return this.negate().prev()};SmallInteger.prototype.not=BigInteger.prototype.not;BigInteger.prototype.and=function(n){return bitwise(this,n,function(a,b){return a&b})};SmallInteger.prototype.and=BigInteger.prototype.and;BigInteger.prototype.or=function(n){return bitwise(this,n,function(a,b){return a|b})};SmallInteger.prototype.or=BigInteger.prototype.or;BigInteger.prototype.xor=function(n){return bitwise(this,n,function(a,b){return a^b})};SmallInteger.prototype.xor=BigInteger.prototype.xor;var LOBMASK_I=1<<30,LOBMASK_BI=(BASE&-BASE)*(BASE&-BASE)|LOBMASK_I;function roughLOB(n){var v=n.value,x=typeof v==="number"?v|LOBMASK_I:v[0]+v[1]*BASE|LOBMASK_BI;return x&-x}function max(a,b){a=parseValue(a);b=parseValue(b);return a.greater(b)?a:b}function min(a,b){a=parseValue(a);b=parseValue(b);return a.lesser(b)?a:b}function gcd(a,b){a=parseValue(a).abs();b=parseValue(b).abs();if(a.equals(b))return a;if(a.isZero())return b;if(b.isZero())return a;var c=Integer[1],d,t;while(a.isEven()&&b.isEven()){d=Math.min(roughLOB(a),roughLOB(b));a=a.divide(d);b=b.divide(d);c=c.multiply(d)}while(a.isEven()){a=a.divide(roughLOB(a))}do{while(b.isEven()){b=b.divide(roughLOB(b))}if(a.greater(b)){t=b;b=a;a=t}b=b.subtract(a)}while(!b.isZero());return c.isUnit()?a:a.multiply(c)}function lcm(a,b){a=parseValue(a).abs();b=parseValue(b).abs();return a.divide(gcd(a,b)).multiply(b)}function randBetween(a,b){a=parseValue(a);b=parseValue(b);var low=min(a,b),high=max(a,b);var range=high.subtract(low).add(1);if(range.isSmall)return low.add(Math.floor(Math.random()*range));var length=range.value.length-1;var result=[],restricted=true;for(var i=length;i>=0;i--){var top=restricted?range.value[i]:BASE;var digit=truncate(Math.random()*top);result.unshift(digit);if(digit=absBase){if(c==="1"&&absBase===1)continue;throw new Error(c+" is not a valid digit in base "+base+".")}else if(c.charCodeAt(0)-87>=absBase){throw new Error(c+" is not a valid digit in base "+base+".")}}}if(2<=base&&base<=36){if(length<=LOG_MAX_INT/Math.log(base)){var result=parseInt(text,base);if(isNaN(result)){throw new Error(c+" is not a valid digit in base "+base+".")}return new SmallInteger(parseInt(text,base))}}base=parseValue(base);var digits=[];var isNegative=text[0]==="-";for(i=isNegative?1:0;i");digits.push(parseValue(text.slice(start+1,i)))}else throw new Error(c+" is not a valid character")}return parseBaseFromArray(digits,base,isNegative)};function parseBaseFromArray(digits,base,isNegative){var val=Integer[0],pow=Integer[1],i;for(i=digits.length-1;i>=0;i--){val=val.add(digits[i].times(pow));pow=pow.times(base)}return isNegative?val.negate():val}function stringify(digit){var v=digit.value;if(typeof v==="number")v=[v];if(v.length===1&&v[0]<=35){return"0123456789abcdefghijklmnopqrstuvwxyz".charAt(v[0])}return"<"+v+">"}function toBase(n,base){base=bigInt(base);if(base.isZero()){if(n.isZero())return"0";throw new Error("Cannot convert nonzero numbers to base 0.")}if(base.equals(-1)){if(n.isZero())return"0";if(n.isNegative())return new Array(1-n).join("10");return"1"+new Array(+n).join("01")}var minusSign="";if(n.isNegative()&&base.isPositive()){minusSign="-";n=n.abs()}if(base.equals(1)){if(n.isZero())return"0";return minusSign+new Array(+n+1).join(1)}var out=[];var left=n,divmod;while(left.isNegative()||left.compareAbs(base)>=0){divmod=left.divmod(base);left=divmod.quotient;var digit=divmod.remainder;if(digit.isNegative()){digit=base.minus(digit).abs();left=left.next()}out.push(stringify(digit))}out.push(stringify(left));return minusSign+out.reverse().join("")}BigInteger.prototype.toString=function(radix){if(radix===undefined)radix=10;if(radix!==10)return toBase(this,radix);var v=this.value,l=v.length,str=String(v[--l]),zeros="0000000",digit;while(--l>=0){digit=String(v[l]);str+=zeros.slice(digit.length)+digit}var sign=this.sign?"-":"";return sign+str};SmallInteger.prototype.toString=function(radix){if(radix===undefined)radix=10;if(radix!=10)return toBase(this,radix);return String(this.value)};BigInteger.prototype.toJSON=SmallInteger.prototype.toJSON=function(){return this.toString()};BigInteger.prototype.valueOf=function(){return+this.toString()};BigInteger.prototype.toJSNumber=BigInteger.prototype.valueOf;SmallInteger.prototype.valueOf=function(){return this.value};SmallInteger.prototype.toJSNumber=SmallInteger.prototype.valueOf;function parseStringValue(v){if(isPrecise(+v)){var x=+v;if(x===truncate(x))return new SmallInteger(x);throw"Invalid integer: "+v}var sign=v[0]==="-";if(sign)v=v.slice(1);var split=v.split(/e/i);if(split.length>2)throw new Error("Invalid integer: "+split.join("e"));if(split.length===2){var exp=split[1];if(exp[0]==="+")exp=exp.slice(1);exp=+exp;if(exp!==truncate(exp)||!isPrecise(exp))throw new Error("Invalid integer: "+exp+" is not a valid exponent.");var text=split[0];var decimalPlace=text.indexOf(".");if(decimalPlace>=0){exp-=text.length-decimalPlace-1;text=text.slice(0,decimalPlace)+text.slice(decimalPlace+1)}if(exp<0)throw new Error("Cannot include negative exponent part for integers");text+=new Array(exp+1).join("0");v=text}var isValid=/^([0-9][0-9]*)$/.test(v);if(!isValid)throw new Error("Invalid integer: "+v);var r=[],max=v.length,l=LOG_BASE,min=max-l;while(max>0){r.push(+v.slice(min,max));min-=l;if(min<0)min=0;max-=l}trim(r);return new BigInteger(r,sign)}function parseNumberValue(v){if(isPrecise(v)){if(v!==truncate(v))throw new Error(v+" is not an integer.");return new SmallInteger(v)}return parseStringValue(v.toString())}function parseValue(v){if(typeof v==="number"){return parseNumberValue(v)}if(typeof v==="string"){return parseStringValue(v)}return v}for(var i=0;i<1e3;i++){Integer[i]=new SmallInteger(i);if(i>0)Integer[-i]=new SmallInteger(-i)}Integer.one=Integer[1];Integer.zero=Integer[0];Integer.minusOne=Integer[-1];Integer.max=max;Integer.min=min;Integer.gcd=gcd;Integer.lcm=lcm;Integer.isInstance=function(x){return x instanceof BigInteger||x instanceof SmallInteger};Integer.randBetween=randBetween;Integer.fromArray=function(digits,base,isNegative){return parseBaseFromArray(digits.map(parseValue),parseValue(base||10),isNegative)};return Integer}();if(typeof module!=="undefined"&&module.hasOwnProperty("exports")){module.exports=bigInt}if(typeof define==="function"&&define.amd){define("big-integer",[],function(){return bigInt})}; bigInt`
-
-// makeSlice convert an unsafe memory pointer with the given type into a Go byte
-// slice.
-//
-// Note, the returned slice uses the same memory area as the input arguments.
-// If those are duktape stack items, popping them off **will** make the slice
-// contents change.
-func makeSlice(ptr unsafe.Pointer, size uint) []byte {
- var sl = struct {
- addr uintptr
- len int
- cap int
- }{uintptr(ptr), int(size), int(size)}
-
- return *(*[]byte)(unsafe.Pointer(&sl))
-}
-
-// popSlice pops a buffer off the JavaScript stack and returns it as a slice.
-func popSlice(ctx *duktape.Context) []byte {
- blob := common.CopyBytes(makeSlice(ctx.GetBuffer(-1)))
- ctx.Pop()
- return blob
-}
-
-// pushBigInt create a JavaScript BigInteger in the VM.
-func pushBigInt(n *big.Int, ctx *duktape.Context) {
- ctx.GetGlobalString("bigInt")
- ctx.PushString(n.String())
- ctx.Call(1)
-}
-
-// opWrapper provides a JavaScript wrapper around OpCode.
-type opWrapper struct {
- op vm.OpCode
-}
-
-// pushObject assembles a JSVM object wrapping a swappable opcode and pushes it
-// onto the VM stack.
-func (ow *opWrapper) pushObject(vm *duktape.Context) {
- obj := vm.PushObject()
-
- vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(int(ow.op)); return 1 })
- vm.PutPropString(obj, "toNumber")
-
- vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushString(ow.op.String()); return 1 })
- vm.PutPropString(obj, "toString")
-
- vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushBoolean(ow.op.IsPush()); return 1 })
- vm.PutPropString(obj, "isPush")
-}
-
-// memoryWrapper provides a JavaScript wrapper around vm.Memory.
-type memoryWrapper struct {
- memory *vm.Memory
-}
-
-// slice returns the requested range of memory as a byte slice.
-func (mw *memoryWrapper) slice(begin, end int64) []byte {
- if end == begin {
- return []byte{}
- }
- if end < begin || begin < 0 {
- // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
- // runtime goes belly up https://github.com/golang/go/issues/15639.
- log.Warn("Tracer accessed out of bound memory", "offset", begin, "end", end)
- return nil
- }
- if mw.memory.Len() < int(end) {
- // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
- // runtime goes belly up https://github.com/golang/go/issues/15639.
- log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", begin, "size", end-begin)
- return nil
- }
- return mw.memory.GetCopy(begin, end-begin)
-}
-
-// getUint returns the 32 bytes at the specified address interpreted as a uint.
-func (mw *memoryWrapper) getUint(addr int64) *big.Int {
- if mw.memory.Len() < int(addr)+32 || addr < 0 {
- // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
- // runtime goes belly up https://github.com/golang/go/issues/15639.
- log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", addr, "size", 32)
- return new(big.Int)
- }
- return new(big.Int).SetBytes(mw.memory.GetPtr(addr, 32))
-}
-
-// pushObject assembles a JSVM object wrapping a swappable memory and pushes it
-// onto the VM stack.
-func (mw *memoryWrapper) pushObject(vm *duktape.Context) {
- obj := vm.PushObject()
-
- // Generate the `slice` method which takes two ints and returns a buffer
- vm.PushGoFunction(func(ctx *duktape.Context) int {
- blob := mw.slice(int64(ctx.GetInt(-2)), int64(ctx.GetInt(-1)))
- ctx.Pop2()
-
- ptr := ctx.PushFixedBuffer(len(blob))
- copy(makeSlice(ptr, uint(len(blob))), blob)
- return 1
- })
- vm.PutPropString(obj, "slice")
-
- // Generate the `getUint` method which takes an int and returns a bigint
- vm.PushGoFunction(func(ctx *duktape.Context) int {
- offset := int64(ctx.GetInt(-1))
- ctx.Pop()
-
- pushBigInt(mw.getUint(offset), ctx)
- return 1
- })
- vm.PutPropString(obj, "getUint")
-}
-
-// stackWrapper provides a JavaScript wrapper around vm.Stack.
-type stackWrapper struct {
- stack *vm.Stack
-}
-
-// peek returns the nth-from-the-top element of the stack.
-func (sw *stackWrapper) peek(idx int) *big.Int {
- if len(sw.stack.Data()) <= idx || idx < 0 {
- // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
- // runtime goes belly up https://github.com/golang/go/issues/15639.
- log.Warn("Tracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx)
- return new(big.Int)
- }
- return sw.stack.Back(idx).ToBig()
-}
-
-// pushObject assembles a JSVM object wrapping a swappable stack and pushes it
-// onto the VM stack.
-func (sw *stackWrapper) pushObject(vm *duktape.Context) {
- obj := vm.PushObject()
-
- vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(len(sw.stack.Data())); return 1 })
- vm.PutPropString(obj, "length")
-
- // Generate the `peek` method which takes an int and returns a bigint
- vm.PushGoFunction(func(ctx *duktape.Context) int {
- offset := ctx.GetInt(-1)
- ctx.Pop()
-
- pushBigInt(sw.peek(offset), ctx)
- return 1
- })
- vm.PutPropString(obj, "peek")
-}
-
-// dbWrapper provides a JavaScript wrapper around vm.Database.
-type dbWrapper struct {
- db vm.StateDB
-}
-
-// pushObject assembles a JSVM object wrapping a swappable database and pushes it
-// onto the VM stack.
-func (dw *dbWrapper) pushObject(vm *duktape.Context) {
- obj := vm.PushObject()
-
- // Push the wrapper for statedb.GetBalance
- vm.PushGoFunction(func(ctx *duktape.Context) int {
- pushBigInt(dw.db.GetBalance(common.BytesToAddress(popSlice(ctx))), ctx)
- return 1
- })
- vm.PutPropString(obj, "getBalance")
-
- // Push the wrapper for statedb.GetNonce
- vm.PushGoFunction(func(ctx *duktape.Context) int {
- ctx.PushInt(int(dw.db.GetNonce(common.BytesToAddress(popSlice(ctx)))))
- return 1
- })
- vm.PutPropString(obj, "getNonce")
-
- // Push the wrapper for statedb.GetCode
- vm.PushGoFunction(func(ctx *duktape.Context) int {
- code := dw.db.GetCode(common.BytesToAddress(popSlice(ctx)))
-
- ptr := ctx.PushFixedBuffer(len(code))
- copy(makeSlice(ptr, uint(len(code))), code)
- return 1
- })
- vm.PutPropString(obj, "getCode")
-
- // Push the wrapper for statedb.GetState
- vm.PushGoFunction(func(ctx *duktape.Context) int {
- hash := popSlice(ctx)
- addr := popSlice(ctx)
-
- state := dw.db.GetState(common.BytesToAddress(addr), common.BytesToHash(hash))
-
- ptr := ctx.PushFixedBuffer(len(state))
- copy(makeSlice(ptr, uint(len(state))), state[:])
- return 1
- })
- vm.PutPropString(obj, "getState")
-
- // Push the wrapper for statedb.Exists
- vm.PushGoFunction(func(ctx *duktape.Context) int {
- ctx.PushBoolean(dw.db.Exist(common.BytesToAddress(popSlice(ctx))))
- return 1
- })
- vm.PutPropString(obj, "exists")
-}
-
-// contractWrapper provides a JavaScript wrapper around vm.Contract
-type contractWrapper struct {
- contract *vm.Contract
-}
-
-// pushObject assembles a JSVM object wrapping a swappable contract and pushes it
-// onto the VM stack.
-func (cw *contractWrapper) pushObject(vm *duktape.Context) {
- obj := vm.PushObject()
-
- // Push the wrapper for contract.Caller
- vm.PushGoFunction(func(ctx *duktape.Context) int {
- ptr := ctx.PushFixedBuffer(20)
- copy(makeSlice(ptr, 20), cw.contract.Caller().Bytes())
- return 1
- })
- vm.PutPropString(obj, "getCaller")
-
- // Push the wrapper for contract.Address
- vm.PushGoFunction(func(ctx *duktape.Context) int {
- ptr := ctx.PushFixedBuffer(20)
- copy(makeSlice(ptr, 20), cw.contract.Address().Bytes())
- return 1
- })
- vm.PutPropString(obj, "getAddress")
-
- // Push the wrapper for contract.Value
- vm.PushGoFunction(func(ctx *duktape.Context) int {
- pushBigInt(cw.contract.Value(), ctx)
- return 1
- })
- vm.PutPropString(obj, "getValue")
-
- // Push the wrapper for contract.Input
- vm.PushGoFunction(func(ctx *duktape.Context) int {
- blob := cw.contract.Input
-
- ptr := ctx.PushFixedBuffer(len(blob))
- copy(makeSlice(ptr, uint(len(blob))), blob)
- return 1
- })
- 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
-// function for each VM execution step.
-type Tracer struct {
- vm *duktape.Context // Javascript VM instance
-
- tracerObject int // Stack index of the tracer JavaScript object
- stateObject int // Stack index of the global state to pull arguments from
-
- opWrapper *opWrapper // Wrapper around the VM opcode
- stackWrapper *stackWrapper // Wrapper around the VM stack
- memoryWrapper *memoryWrapper // Wrapper around the VM memory
- contractWrapper *contractWrapper // Wrapper around the contract object
- dbWrapper *dbWrapper // Wrapper around the VM environment
-
- pcValue *uint // Swappable pc value wrapped by a log accessor
- gasValue *uint // Swappable gas value wrapped by a log accessor
- costValue *uint // Swappable cost value wrapped by a log accessor
- depthValue *uint // Swappable depth 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
-
- 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
- err error // Error, if one has occurred
-
- interrupt uint32 // Atomic flag to signal execution interruption
- reason error // Textual reason for the interruption
-
- 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
-// available from within the EVM object.
-type Context struct {
- BlockHash common.Hash // Hash of the block the tx is contained within (zero if dangling tx or call)
- TxIndex int // Index of the transaction within a block (zero if dangling tx or call)
- TxHash common.Hash // Hash of the transaction being traced (zero if dangling call)
-}
-
-// New instantiates a new tracer instance. code specifies a Javascript snippet,
-// which must evaluate to an expression returning an object with 'step', 'fault'
-// and 'result' functions.
-func New(code string, ctx *Context) (*Tracer, error) {
- // Resolve any tracers by name and assemble the tracer object
- if tracer, ok := tracer(code); ok {
- code = tracer
- }
- tracer := &Tracer{
- vm: duktape.New(),
- ctx: make(map[string]interface{}),
- opWrapper: new(opWrapper),
- stackWrapper: new(stackWrapper),
- memoryWrapper: new(memoryWrapper),
- contractWrapper: new(contractWrapper),
- dbWrapper: new(dbWrapper),
- pcValue: new(uint),
- gasValue: new(uint),
- costValue: new(uint),
- depthValue: new(uint),
- refundValue: new(uint),
- frame: newFrame(),
- frameResult: newFrameResult(),
- }
- if ctx.BlockHash != (common.Hash{}) {
- tracer.ctx["blockHash"] = ctx.BlockHash
-
- if ctx.TxHash != (common.Hash{}) {
- tracer.ctx["txIndex"] = ctx.TxIndex
- tracer.ctx["txHash"] = ctx.TxHash
- }
- }
- // Set up builtins for this environment
- tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int {
- ctx.PushString(hexutil.Encode(popSlice(ctx)))
- return 1
- })
- tracer.vm.PushGlobalGoFunction("toWord", func(ctx *duktape.Context) int {
- var word common.Hash
- if ptr, size := ctx.GetBuffer(-1); ptr != nil {
- word = common.BytesToHash(makeSlice(ptr, size))
- } else {
- word = common.HexToHash(ctx.GetString(-1))
- }
- ctx.Pop()
- copy(makeSlice(ctx.PushFixedBuffer(32), 32), word[:])
- return 1
- })
- tracer.vm.PushGlobalGoFunction("toAddress", func(ctx *duktape.Context) int {
- var addr common.Address
- if ptr, size := ctx.GetBuffer(-1); ptr != nil {
- addr = common.BytesToAddress(makeSlice(ptr, size))
- } else {
- addr = common.HexToAddress(ctx.GetString(-1))
- }
- ctx.Pop()
- copy(makeSlice(ctx.PushFixedBuffer(20), 20), addr[:])
- return 1
- })
- tracer.vm.PushGlobalGoFunction("toContract", func(ctx *duktape.Context) int {
- var from common.Address
- if ptr, size := ctx.GetBuffer(-2); ptr != nil {
- from = common.BytesToAddress(makeSlice(ptr, size))
- } else {
- from = common.HexToAddress(ctx.GetString(-2))
- }
- nonce := uint64(ctx.GetInt(-1))
- ctx.Pop2()
-
- contract := crypto.CreateAddress(from, nonce)
- copy(makeSlice(ctx.PushFixedBuffer(20), 20), contract[:])
- return 1
- })
- tracer.vm.PushGlobalGoFunction("toContract2", func(ctx *duktape.Context) int {
- var from common.Address
- if ptr, size := ctx.GetBuffer(-3); ptr != nil {
- from = common.BytesToAddress(makeSlice(ptr, size))
- } else {
- from = common.HexToAddress(ctx.GetString(-3))
- }
- // Retrieve salt hex string from js stack
- salt := common.HexToHash(ctx.GetString(-2))
- // Retrieve code slice from js stack
- var code []byte
- if ptr, size := ctx.GetBuffer(-1); ptr != nil {
- code = common.CopyBytes(makeSlice(ptr, size))
- } else {
- code = common.FromHex(ctx.GetString(-1))
- }
- codeHash := crypto.Keccak256(code)
- ctx.Pop3()
- contract := crypto.CreateAddress2(from, salt, codeHash)
- copy(makeSlice(ctx.PushFixedBuffer(20), 20), contract[:])
- return 1
- })
- tracer.vm.PushGlobalGoFunction("isPrecompiled", func(ctx *duktape.Context) int {
- addr := common.BytesToAddress(popSlice(ctx))
- for _, p := range tracer.activePrecompiles {
- if p == addr {
- ctx.PushBoolean(true)
- return 1
- }
- }
- ctx.PushBoolean(false)
- return 1
- })
- tracer.vm.PushGlobalGoFunction("slice", func(ctx *duktape.Context) int {
- start, end := ctx.GetInt(-2), ctx.GetInt(-1)
- ctx.Pop2()
-
- blob := popSlice(ctx)
- size := end - start
-
- if start < 0 || start > end || end > len(blob) {
- // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
- // runtime goes belly up https://github.com/golang/go/issues/15639.
- log.Warn("Tracer accessed out of bound memory", "available", len(blob), "offset", start, "size", size)
- ctx.PushFixedBuffer(0)
- return 1
- }
- copy(makeSlice(ctx.PushFixedBuffer(size), uint(size)), blob[start:end])
- return 1
- })
- // Push the JavaScript tracer as object #0 onto the JSVM stack and validate it
- if err := tracer.vm.PevalString("(" + code + ")"); err != nil {
- log.Warn("Failed to compile tracer", "err", err)
- return nil, err
- }
- tracer.tracerObject = 0 // yeah, nice, eval can't return the index itself
-
- hasStep := tracer.vm.GetPropString(tracer.tracerObject, "step")
- tracer.vm.Pop()
-
- if !tracer.vm.GetPropString(tracer.tracerObject, "fault") {
- return nil, fmt.Errorf("trace object must expose a function fault()")
- }
- tracer.vm.Pop()
-
- if !tracer.vm.GetPropString(tracer.tracerObject, "result") {
- return nil, fmt.Errorf("trace object must expose a function result()")
- }
- 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.vm.EvalString(bigIntegerJS)
- tracer.vm.PutGlobalString("bigInt")
-
- // Push the global environment state as object #1 into the JSVM stack
- tracer.stateObject = tracer.vm.PushObject()
-
- logObject := tracer.vm.PushObject()
-
- tracer.opWrapper.pushObject(tracer.vm)
- tracer.vm.PutPropString(logObject, "op")
-
- tracer.stackWrapper.pushObject(tracer.vm)
- tracer.vm.PutPropString(logObject, "stack")
-
- tracer.memoryWrapper.pushObject(tracer.vm)
- tracer.vm.PutPropString(logObject, "memory")
-
- tracer.contractWrapper.pushObject(tracer.vm)
- tracer.vm.PutPropString(logObject, "contract")
-
- tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.pcValue); return 1 })
- tracer.vm.PutPropString(logObject, "getPC")
-
- tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.gasValue); return 1 })
- tracer.vm.PutPropString(logObject, "getGas")
-
- tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.costValue); return 1 })
- tracer.vm.PutPropString(logObject, "getCost")
-
- tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.depthValue); return 1 })
- tracer.vm.PutPropString(logObject, "getDepth")
-
- tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.refundValue); return 1 })
- tracer.vm.PutPropString(logObject, "getRefund")
-
- tracer.vm.PushGoFunction(func(ctx *duktape.Context) int {
- if tracer.errorValue != nil {
- ctx.PushString(*tracer.errorValue)
- } else {
- ctx.PushUndefined()
- }
- return 1
- })
- tracer.vm.PutPropString(logObject, "getError")
-
- 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.vm.PutPropString(tracer.stateObject, "db")
-
- return tracer, nil
-}
-
-// Stop terminates execution of the tracer at the first opportune moment.
-func (jst *Tracer) Stop(err error) {
- jst.reason = err
- atomic.StoreUint32(&jst.interrupt, 1)
-}
-
-// call executes a method on a JS object, catching any errors, formatting and
-// returning them as error objects.
-func (jst *Tracer) call(noret bool, method string, args ...string) (json.RawMessage, error) {
- // Execute the JavaScript call and return any error
- jst.vm.PushString(method)
- for _, arg := range args {
- jst.vm.GetPropString(jst.stateObject, arg)
- }
- code := jst.vm.PcallProp(jst.tracerObject, len(args))
- defer jst.vm.Pop()
-
- if code != 0 {
- err := jst.vm.SafeToString(-1)
- return nil, errors.New(err)
- }
- // No error occurred, extract return value and return
- if noret {
- return nil, nil
- }
- // Push a JSON marshaller onto the stack. We can't marshal from the out-
- // side because duktape can crash on large nestings and we can't catch
- // C++ exceptions ourselves from Go. TODO(karalabe): Yuck, why wrap?!
- jst.vm.PushString("(JSON.stringify)")
- jst.vm.Eval()
-
- jst.vm.Swap(-1, -2)
- if code = jst.vm.Pcall(1); code != 0 {
- err := jst.vm.SafeToString(-1)
- return nil, errors.New(err)
- }
- return json.RawMessage(jst.vm.SafeToString(-1)), nil
-}
-
-func wrapError(context string, err error) error {
- return fmt.Errorf("%v in server-side tracer function '%v'", err, context)
-}
-
-// CaptureStart implements the Tracer interface to initialize the tracing operation.
-func (jst *Tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
- jst.ctx["type"] = "CALL"
- if create {
- jst.ctx["type"] = "CREATE"
- }
- jst.ctx["from"] = from
- jst.ctx["to"] = to
- jst.ctx["input"] = input
- jst.ctx["gas"] = gas
- jst.ctx["gasPrice"] = env.TxContext.GasPrice
- jst.ctx["value"] = value
-
- // Initialize the context
- jst.ctx["block"] = env.Context.BlockNumber.Uint64()
- jst.dbWrapper.db = env.StateDB
- // Update list of precompiles based on current block
- rules := env.ChainConfig().Rules(env.Context.BlockNumber)
- jst.activePrecompiles = vm.ActivePrecompiles(rules)
-
- // Compute intrinsic gas
- isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
- isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
- intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul, nil, 0)
- if err != nil {
- return
- }
- jst.ctx["intrinsicGas"] = intrinsicGas
-}
-
-// 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) {
- if !jst.traceSteps {
- 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
- env.Cancel()
- return
- }
- jst.opWrapper.op = op
- jst.stackWrapper.stack = scope.Stack
- jst.memoryWrapper.memory = scope.Memory
- jst.contractWrapper.contract = scope.Contract
-
- *jst.pcValue = uint(pc)
- *jst.gasValue = uint(gas)
- *jst.costValue = uint(cost)
- *jst.depthValue = uint(depth)
- *jst.refundValue = uint(env.StateDB.GetRefund())
-
- jst.errorValue = nil
- if err != nil {
- jst.errorValue = new(string)
- *jst.errorValue = err.Error()
- }
-
- if _, err := jst.call(true, "step", "log", "db"); err != nil {
- jst.err = wrapError("step", err)
- }
-}
-
-// CaptureFault implements the Tracer interface to trace an execution fault
-func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
- if jst.err != nil {
- return
- }
- // Apart from the error, everything matches the previous invocation
- jst.errorValue = new(string)
- *jst.errorValue = err.Error()
-
- if _, err := jst.call(true, "fault", "log", "db"); err != nil {
- jst.err = wrapError("fault", err)
- }
-}
-
-// CaptureEnd is called after the call finishes to finalize the tracing.
-func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
- jst.ctx["output"] = output
- jst.ctx["time"] = t.String()
- jst.ctx["gasUsed"] = gasUsed
-
- if err != nil {
- jst.ctx["error"] = err.Error()
- }
-}
-
-// 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 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
-func (jst *Tracer) GetResult() (json.RawMessage, error) {
- // Transform the context into a JavaScript object and inject into the state
- obj := jst.vm.PushObject()
-
- for key, val := range jst.ctx {
- jst.addToObj(obj, key, val)
- }
- jst.vm.PutPropString(jst.stateObject, "ctx")
-
- // Finalize the trace and return the results
- result, err := jst.call(false, "result", "ctx", "db")
- if err != nil {
- jst.err = wrapError("result", err)
- }
- // Clean up the JavaScript environment
- jst.vm.DestroyHeap()
- jst.vm.Destroy()
-
- 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))
- }
-}
diff --git a/eth/tracers/internal/tracers/4byte_tracer.js b/eth/tracers/js/internal/tracers/4byte_tracer.js
similarity index 100%
rename from eth/tracers/internal/tracers/4byte_tracer.js
rename to eth/tracers/js/internal/tracers/4byte_tracer.js
diff --git a/eth/tracers/internal/tracers/4byte_tracer_legacy.js b/eth/tracers/js/internal/tracers/4byte_tracer_legacy.js
similarity index 100%
rename from eth/tracers/internal/tracers/4byte_tracer_legacy.js
rename to eth/tracers/js/internal/tracers/4byte_tracer_legacy.js
diff --git a/eth/tracers/internal/tracers/assets.go b/eth/tracers/js/internal/tracers/assets.go
similarity index 91%
rename from eth/tracers/internal/tracers/assets.go
rename to eth/tracers/js/internal/tracers/assets.go
index d1d4353478..ffd707cf70 100644
--- a/eth/tracers/internal/tracers/assets.go
+++ b/eth/tracers/js/internal/tracers/assets.go
@@ -3,7 +3,7 @@
// 4byte_tracer.js (2.224kB)
// 4byte_tracer_legacy.js (2.933kB)
// bigram_tracer.js (1.712kB)
-// call_tracer.js (3.497kB)
+// call_tracer_js.js (3.497kB)
// call_tracer_legacy.js (8.956kB)
// evmdis_tracer.js (4.195kB)
// noop_tracer.js (1.271kB)
@@ -138,22 +138,22 @@ func bigram_tracerJs() (*asset, error) {
return a, nil
}
-var _call_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x56\x5f\x6f\xdb\x38\x0c\x7f\x8e\x3f\x05\xaf\x0f\x4b\x82\x65\x71\xbb\x03\xf6\xd0\x2d\x03\x72\x45\xbb\x05\xe8\xb5\x45\x9a\xde\x50\x14\x7d\x50\x6c\xda\xd6\xa6\x48\x86\x44\x37\xcd\x6d\xfd\xee\x07\x4a\x76\x6a\x67\x59\x6f\x2f\x06\x2c\x92\x3f\xfe\xfb\x51\x54\x1c\xc3\x89\x29\x37\x56\xe6\x05\xc1\xdb\xc3\xb7\x47\xb0\x28\x10\x72\xf3\x06\xa9\x40\x8b\xd5\x0a\xa6\x15\x15\xc6\xba\x28\x8e\x61\x51\x48\x07\x99\x54\x08\xd2\x41\x29\x2c\x81\xc9\x80\x76\xf4\x95\x5c\x5a\x61\x37\xe3\x28\x8e\x83\xcd\x5e\x31\x23\x64\x16\x11\x9c\xc9\x68\x2d\x2c\x1e\xc3\xc6\x54\x90\x08\x0d\x16\x53\xe9\xc8\xca\x65\x45\x08\x92\x40\xe8\x34\x36\x16\x56\x26\x95\xd9\x86\x21\x25\x41\xa5\x53\xb4\xde\x35\xa1\x5d\xb9\x26\x8e\x4f\x17\x37\x70\x8e\xce\xa1\x85\x4f\xa8\xd1\x0a\x05\x57\xd5\x52\xc9\x04\xce\x65\x82\xda\x21\x08\x07\x25\x9f\xb8\x02\x53\x58\x7a\x38\x36\x3c\xe3\x50\xae\xeb\x50\xe0\xcc\x54\x3a\x15\x24\x8d\x1e\x01\x4a\x8e\x1c\x1e\xd0\x3a\x69\x34\xfc\xd9\xb8\xaa\x01\x47\x60\x2c\x83\x0c\x04\x71\x02\x16\x4c\xc9\x76\x43\x10\x7a\x03\x4a\xd0\xb3\xe9\x6f\x14\xe4\x39\xef\x14\xa4\xf6\x6e\x0a\x53\x22\x50\x21\x88\xb3\x5e\x4b\xa5\x60\x89\x50\x39\xcc\x2a\x35\x62\xb4\x65\x45\xf0\x65\xb6\xf8\x7c\x79\xb3\x80\xe9\xc5\x2d\x7c\x99\xce\xe7\xd3\x8b\xc5\xed\x7b\x58\x4b\x2a\x4c\x45\x80\x0f\x18\xa0\xe4\xaa\x54\x12\x53\x58\x0b\x6b\x85\xa6\x0d\x98\x8c\x11\xfe\x3e\x9d\x9f\x7c\x9e\x5e\x2c\xa6\x7f\xcd\xce\x67\x8b\x5b\x30\x16\xce\x66\x8b\x8b\xd3\xeb\x6b\x38\xbb\x9c\xc3\x14\xae\xa6\xf3\xc5\xec\xe4\xe6\x7c\x3a\x87\xab\x9b\xf9\xd5\xe5\xf5\xe9\x18\xae\x91\xa3\x42\xb6\xff\xff\x9a\x67\xbe\x7b\x16\x21\x45\x12\x52\xb9\xa6\x12\xb7\xa6\x02\x57\x98\x4a\xa5\x50\x88\x07\x04\x8b\x09\xca\x07\x4c\x41\x40\x62\xca\xcd\x6f\x37\x95\xb1\x84\x32\x3a\xf7\x39\xff\x92\x90\x30\xcb\x40\x1b\x1a\x81\x43\x84\x0f\x05\x51\x79\x1c\xc7\xeb\xf5\x7a\x9c\xeb\x6a\x6c\x6c\x1e\xab\x00\xe7\xe2\x8f\xe3\x28\x62\xd0\x44\x28\x75\x66\xc5\x0a\x17\x56\x24\x68\xb9\xee\xce\xc3\x6b\x5c\x7b\x21\x64\x2c\x05\xb2\x22\x91\x3a\x87\x15\x52\x61\x52\x07\x64\xc0\x62\x69\x2c\xd5\x9d\x02\xa9\x33\x63\x57\x9e\x51\x3e\xd8\x25\x37\x46\x6a\x42\xab\x85\x82\x15\x3a\x27\x72\xf4\x2c\x16\x0c\xa6\x9d\x48\xc8\x53\xe6\x7b\xd4\x63\x3f\x8e\x44\xf2\xed\x18\xee\xbe\x3f\xdd\x8f\xa2\x5e\x26\x2a\x45\xc7\x90\x55\xda\x6b\x0d\x94\xc9\x47\x90\x2e\x87\xf0\xfd\x69\x14\xf5\x2c\xba\xae\x38\xa1\xc7\x5a\x1c\xf5\x7a\x71\x0c\x57\x16\x4b\x66\xb9\xa9\x98\x9d\xb5\x73\x1f\x62\xd4\xeb\x3d\x08\x0b\x01\x01\x26\xde\xa0\x47\x9b\x12\x8f\x01\x00\x12\x7a\x1c\xf3\xcf\x88\x4f\x33\x6b\x56\xfe\x94\xcc\x67\x7c\x64\x1f\x63\x3e\x1a\x7a\x21\x19\x2f\x6a\x0b\xc9\x04\xd1\x83\x50\x95\x87\xeb\x1f\x3e\xf6\xe1\xb5\x07\xf5\x67\x63\x32\xd7\x64\xa5\xce\x07\x47\xef\x82\x6a\x2e\x5c\x80\xa9\x55\x97\x32\x9f\x69\xf2\x68\xb9\x70\xc3\xbd\x06\x37\x0e\xd3\xe3\xfd\x06\x2c\xda\x63\x24\x75\x59\xd1\x71\x27\x56\x7f\x14\xa4\xa6\xa2\x20\x7e\x96\x86\x23\x2f\x7e\x8a\x7a\x3d\x99\xc1\x80\x0a\xe9\xc6\xdb\x3e\xdd\x1d\xde\x87\x1f\xf8\x63\x32\xf1\x37\x55\x26\x35\xa6\xa1\xfe\x75\x7b\x6a\x85\x09\xfc\xc2\xf4\x45\x70\xb4\xd6\xd8\x97\xc0\x83\xc2\x3e\x70\x2f\x61\x70\x40\xe5\x10\x18\x9f\x73\xfa\x6d\xc4\xad\x72\x2b\xc0\x8e\x4a\x07\x03\x5e\xbd\xda\x23\x3e\xc0\x47\x4c\x2a\xa6\x26\x58\x7c\x40\x4b\x98\x1e\xc0\x8f\x1f\x35\xed\xea\xfa\xc2\x64\x32\x39\x38\x7c\x3c\x18\xd6\x71\xa4\xa8\x90\xb0\xab\xe3\x63\x88\x38\x46\xaa\xac\x0e\xd9\x66\x52\x0b\x25\xff\xc5\xda\xed\x30\xea\xf1\x4c\x20\x8f\x5a\x6b\x24\xfc\xd8\x06\x64\x26\xbc\x1f\xe5\x0e\xdd\xbd\xc2\x38\x47\x5a\x6c\x4a\x1c\x0c\x5b\x94\x0f\x44\xd8\xca\xcf\xac\x59\x0d\x86\xcf\xb4\xdf\x11\x2f\x4c\x23\xac\x79\xb6\x23\x9f\xf1\x69\xa3\xe2\x09\xdf\xe5\xee\x56\xf1\x93\x70\x83\x61\x8b\xbe\xfd\xa3\x77\xfd\x0e\x07\xb7\x9a\xff\xf0\x34\x0d\x86\x3b\xdd\xf4\xb9\x71\x9e\x61\xda\x26\xbf\x70\x53\x1b\x77\xe7\xa4\xf6\xd2\x65\xd3\xb8\xac\x5c\x31\xe0\xdf\xa6\xc6\x8f\x92\x76\x4b\x3c\x0f\x4d\xd8\x16\x5a\xa1\xfe\x89\x96\x63\x85\x3a\xa7\xa2\x4e\x83\x35\x3e\xc2\x51\xdd\xf5\x56\x73\x76\xbd\x9b\x72\x30\xdc\xe6\x54\x8f\x37\x4c\xf6\x95\x2f\x04\x51\x17\x91\xd5\x7e\x2e\x64\xe3\xab\xa1\xf9\x8e\xdd\x29\x1f\x07\x77\x1c\x63\xad\xb5\x67\x5a\x42\x34\x0d\x83\xdb\xcd\x7e\x06\xbb\xf4\xd2\xc1\xd0\xc3\xd5\x73\xd8\x32\x6e\x42\x68\xa6\x2c\xb8\xf4\x22\xa6\xa6\x77\xdb\x3f\x99\x9f\x4e\x17\xa7\x7d\x9e\x9a\xbd\x92\xb7\xfd\x26\xa0\x66\x70\x82\x9a\xf1\x67\x4f\x51\xf3\xe1\x6a\xbf\x99\xc0\x51\x93\xd9\xce\x85\xa1\x50\xbf\x39\x6a\x2e\xb3\xbd\xf9\xbe\x68\x00\x77\xf7\x5b\x4f\x2f\x28\x76\x98\xc4\xda\xcc\xa6\x38\x86\x66\x94\xf9\x5d\x60\x51\x10\x3a\x7e\x18\x30\x1b\xcc\xf2\x2b\x26\xbc\x5c\x79\xe9\xf2\x3e\xf6\xaa\x90\xa2\x93\x16\x53\xc8\x24\xaa\x14\x0c\xbf\x10\xf9\xe9\xf1\xd5\x19\xed\x01\x1d\x5a\xc9\x88\x7e\x0f\x8f\xc3\x6b\x56\x32\xa8\x96\x09\xd2\x06\x32\x14\x54\x59\xe4\xf5\x5d\x0a\xe7\x60\x85\x42\x4b\x9d\x67\x95\x52\x1b\x30\x36\x45\x06\x0f\xf7\x8a\xf3\x80\x64\x78\xc1\x5b\x07\xeb\xc2\x40\x6a\x74\xbf\x5e\xea\xa5\x45\x7e\xaf\x8d\xe0\x6b\xe5\x88\x5f\x75\xa5\x12\x1b\x90\x34\x8e\x7a\x4d\x52\xed\xfd\xcc\x99\x6f\x47\xc4\x19\xbe\x10\x7f\x5e\xbe\x4d\x9b\xbb\xdb\xd7\x1f\xf3\x5f\x77\xef\xd6\xdd\xee\x6e\xdc\xe7\xe9\xef\xae\xd7\x66\x82\xba\x3b\xb4\x3d\x57\xdd\x45\xe9\x25\xfe\xaf\xbb\x22\x5b\xdc\xf7\x02\xcf\xe0\xad\x81\xff\x0b\x51\xca\x55\x3b\x27\xb9\x0a\xf1\x78\x2e\x6c\xd5\xfd\x5f\x73\xbf\x71\x17\x07\x5c\x9c\x6f\xb8\xe1\x87\x71\xa8\x51\xcd\x41\xe6\x6d\x38\xb8\xfb\x86\x9b\xfb\xfd\x3c\xad\xa7\xa0\xa5\xd7\x30\xb3\xb9\x3f\x83\xe8\x85\xc5\xbd\x0d\x42\x4e\x0e\xdf\x83\xfc\xd0\x36\xa8\xef\xb0\xf7\x20\x5f\xbf\x6e\x5c\xb6\xe5\x77\xf2\xbe\xb9\xc2\xb6\x0b\x6a\x47\x3e\x6c\x07\x54\x6f\xb4\xa0\x12\xf5\x9e\xa2\xa7\xe8\xbf\x00\x00\x00\xff\xff\x2a\xac\x9f\xff\xa9\x0d\x00\x00")
+var _call_tracer_jsJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x56\x5f\x6f\xdb\x38\x0c\x7f\x8e\x3f\x05\xaf\x0f\x4b\x82\x65\x71\xbb\x03\xf6\xd0\x2d\x03\x72\x45\xbb\x05\xe8\xb5\x45\x9a\xde\x50\x14\x7d\x50\x6c\xda\xd6\xa6\x48\x86\x44\x37\xcd\x6d\xfd\xee\x07\x4a\x76\x6a\x67\x59\x6f\x2f\x06\x2c\x92\x3f\xfe\xfb\x51\x54\x1c\xc3\x89\x29\x37\x56\xe6\x05\xc1\xdb\xc3\xb7\x47\xb0\x28\x10\x72\xf3\x06\xa9\x40\x8b\xd5\x0a\xa6\x15\x15\xc6\xba\x28\x8e\x61\x51\x48\x07\x99\x54\x08\xd2\x41\x29\x2c\x81\xc9\x80\x76\xf4\x95\x5c\x5a\x61\x37\xe3\x28\x8e\x83\xcd\x5e\x31\x23\x64\x16\x11\x9c\xc9\x68\x2d\x2c\x1e\xc3\xc6\x54\x90\x08\x0d\x16\x53\xe9\xc8\xca\x65\x45\x08\x92\x40\xe8\x34\x36\x16\x56\x26\x95\xd9\x86\x21\x25\x41\xa5\x53\xb4\xde\x35\xa1\x5d\xb9\x26\x8e\x4f\x17\x37\x70\x8e\xce\xa1\x85\x4f\xa8\xd1\x0a\x05\x57\xd5\x52\xc9\x04\xce\x65\x82\xda\x21\x08\x07\x25\x9f\xb8\x02\x53\x58\x7a\x38\x36\x3c\xe3\x50\xae\xeb\x50\xe0\xcc\x54\x3a\x15\x24\x8d\x1e\x01\x4a\x8e\x1c\x1e\xd0\x3a\x69\x34\xfc\xd9\xb8\xaa\x01\x47\x60\x2c\x83\x0c\x04\x71\x02\x16\x4c\xc9\x76\x43\x10\x7a\x03\x4a\xd0\xb3\xe9\x6f\x14\xe4\x39\xef\x14\xa4\xf6\x6e\x0a\x53\x22\x50\x21\x88\xb3\x5e\x4b\xa5\x60\x89\x50\x39\xcc\x2a\x35\x62\xb4\x65\x45\xf0\x65\xb6\xf8\x7c\x79\xb3\x80\xe9\xc5\x2d\x7c\x99\xce\xe7\xd3\x8b\xc5\xed\x7b\x58\x4b\x2a\x4c\x45\x80\x0f\x18\xa0\xe4\xaa\x54\x12\x53\x58\x0b\x6b\x85\xa6\x0d\x98\x8c\x11\xfe\x3e\x9d\x9f\x7c\x9e\x5e\x2c\xa6\x7f\xcd\xce\x67\x8b\x5b\x30\x16\xce\x66\x8b\x8b\xd3\xeb\x6b\x38\xbb\x9c\xc3\x14\xae\xa6\xf3\xc5\xec\xe4\xe6\x7c\x3a\x87\xab\x9b\xf9\xd5\xe5\xf5\xe9\x18\xae\x91\xa3\x42\xb6\xff\xff\x9a\x67\xbe\x7b\x16\x21\x45\x12\x52\xb9\xa6\x12\xb7\xa6\x02\x57\x98\x4a\xa5\x50\x88\x07\x04\x8b\x09\xca\x07\x4c\x41\x40\x62\xca\xcd\x6f\x37\x95\xb1\x84\x32\x3a\xf7\x39\xff\x92\x90\x30\xcb\x40\x1b\x1a\x81\x43\x84\x0f\x05\x51\x79\x1c\xc7\xeb\xf5\x7a\x9c\xeb\x6a\x6c\x6c\x1e\xab\x00\xe7\xe2\x8f\xe3\x28\x62\xd0\x44\x28\x75\x66\xc5\x0a\x17\x56\x24\x68\xb9\xee\xce\xc3\x6b\x5c\x7b\x21\x64\x2c\x05\xb2\x22\x91\x3a\x87\x15\x52\x61\x52\x07\x64\xc0\x62\x69\x2c\xd5\x9d\x02\xa9\x33\x63\x57\x9e\x51\x3e\xd8\x25\x37\x46\x6a\x42\xab\x85\x82\x15\x3a\x27\x72\xf4\x2c\x16\x0c\xa6\x9d\x48\xc8\x53\xe6\x7b\xd4\x63\x3f\x8e\x44\xf2\xed\x18\xee\xbe\x3f\xdd\x8f\xa2\x5e\x26\x2a\x45\xc7\x90\x55\xda\x6b\x0d\x94\xc9\x47\x90\x2e\x87\xf0\xfd\x69\x14\xf5\x2c\xba\xae\x38\xa1\xc7\x5a\x1c\xf5\x7a\x71\x0c\x57\x16\x4b\x66\xb9\xa9\x98\x9d\xb5\x73\x1f\x62\xd4\xeb\x3d\x08\x0b\x01\x01\x26\xde\xa0\x47\x9b\x12\x8f\x01\x00\x12\x7a\x1c\xf3\xcf\x88\x4f\x33\x6b\x56\xfe\x94\xcc\x67\x7c\x64\x1f\x63\x3e\x1a\x7a\x21\x19\x2f\x6a\x0b\xc9\x04\xd1\x83\x50\x95\x87\xeb\x1f\x3e\xf6\xe1\xb5\x07\xf5\x67\x63\x32\xd7\x64\xa5\xce\x07\x47\xef\x82\x6a\x2e\x5c\x80\xa9\x55\x97\x32\x9f\x69\xf2\x68\xb9\x70\xc3\xbd\x06\x37\x0e\xd3\xe3\xfd\x06\x2c\xda\x63\x24\x75\x59\xd1\x71\x27\x56\x7f\x14\xa4\xa6\xa2\x20\x7e\x96\x86\x23\x2f\x7e\x8a\x7a\x3d\x99\xc1\x80\x0a\xe9\xc6\xdb\x3e\xdd\x1d\xde\x87\x1f\xf8\x63\x32\xf1\x37\x55\x26\x35\xa6\xa1\xfe\x75\x7b\x6a\x85\x09\xfc\xc2\xf4\x45\x70\xb4\xd6\xd8\x97\xc0\x83\xc2\x3e\x70\x2f\x61\x70\x40\xe5\x10\x18\x9f\x73\xfa\x6d\xc4\xad\x72\x2b\xc0\x8e\x4a\x07\x03\x5e\xbd\xda\x23\x3e\xc0\x47\x4c\x2a\xa6\x26\x58\x7c\x40\x4b\x98\x1e\xc0\x8f\x1f\x35\xed\xea\xfa\xc2\x64\x32\x39\x38\x7c\x3c\x18\xd6\x71\xa4\xa8\x90\xb0\xab\xe3\x63\x88\x38\x46\xaa\xac\x0e\xd9\x66\x52\x0b\x25\xff\xc5\xda\xed\x30\xea\xf1\x4c\x20\x8f\x5a\x6b\x24\xfc\xd8\x06\x64\x26\xbc\x1f\xe5\x0e\xdd\xbd\xc2\x38\x47\x5a\x6c\x4a\x1c\x0c\x5b\x94\x0f\x44\xd8\xca\xcf\xac\x59\x0d\x86\xcf\xb4\xdf\x11\x2f\x4c\x23\xac\x79\xb6\x23\x9f\xf1\x69\xa3\xe2\x09\xdf\xe5\xee\x56\xf1\x93\x70\x83\x61\x8b\xbe\xfd\xa3\x77\xfd\x0e\x07\xb7\x9a\xff\xf0\x34\x0d\x86\x3b\xdd\xf4\xb9\x71\x9e\x61\xda\x26\xbf\x70\x53\x1b\x77\xe7\xa4\xf6\xd2\x65\xd3\xb8\xac\x5c\x31\xe0\xdf\xa6\xc6\x8f\x92\x76\x4b\x3c\x0f\x4d\xd8\x16\x5a\xa1\xfe\x89\x96\x63\x85\x3a\xa7\xa2\x4e\x83\x35\x3e\xc2\x51\xdd\xf5\x56\x73\x76\xbd\x9b\x72\x30\xdc\xe6\x54\x8f\x37\x4c\xf6\x95\x2f\x04\x51\x17\x91\xd5\x7e\x2e\x64\xe3\xab\xa1\xf9\x8e\xdd\x29\x1f\x07\x77\x1c\x63\xad\xb5\x67\x5a\x42\x34\x0d\x83\xdb\xcd\x7e\x06\xbb\xf4\xd2\xc1\xd0\xc3\xd5\x73\xd8\x32\x6e\x42\x68\xa6\x2c\xb8\xf4\x22\xa6\xa6\x77\xdb\x3f\x99\x9f\x4e\x17\xa7\x7d\x9e\x9a\xbd\x92\xb7\xfd\x26\xa0\x66\x70\x82\x9a\xf1\x67\x4f\x51\xf3\xe1\x6a\xbf\x99\xc0\x51\x93\xd9\xce\x85\xa1\x50\xbf\x39\x6a\x2e\xb3\xbd\xf9\xbe\x68\x00\x77\xf7\x5b\x4f\x2f\x28\x76\x98\xc4\xda\xcc\xa6\x38\x86\x66\x94\xf9\x5d\x60\x51\x10\x3a\x7e\x18\x30\x1b\xcc\xf2\x2b\x26\xbc\x5c\x79\xe9\xf2\x3e\xf6\xaa\x90\xa2\x93\x16\x53\xc8\x24\xaa\x14\x0c\xbf\x10\xf9\xe9\xf1\xd5\x19\xed\x01\x1d\x5a\xc9\x88\x7e\x0f\x8f\xc3\x6b\x56\x32\xa8\x96\x09\xd2\x06\x32\x14\x54\x59\xe4\xf5\x5d\x0a\xe7\x60\x85\x42\x4b\x9d\x67\x95\x52\x1b\x30\x36\x45\x06\x0f\xf7\x8a\xf3\x80\x64\x78\xc1\x5b\x07\xeb\xc2\x40\x6a\x74\xbf\x5e\xea\xa5\x45\x7e\xaf\x8d\xe0\x6b\xe5\x88\x5f\x75\xa5\x12\x1b\x90\x34\x8e\x7a\x4d\x52\xed\xfd\xcc\x99\x6f\x47\xc4\x19\xbe\x10\x7f\x5e\xbe\x4d\x9b\xbb\xdb\xd7\x1f\xf3\x5f\x77\xef\xd6\xdd\xee\x6e\xdc\xe7\xe9\xef\xae\xd7\x66\x82\xba\x3b\xb4\x3d\x57\xdd\x45\xe9\x25\xfe\xaf\xbb\x22\x5b\xdc\xf7\x02\xcf\xe0\xad\x81\xff\x0b\x51\xca\x55\x3b\x27\xb9\x0a\xf1\x78\x2e\x6c\xd5\xfd\x5f\x73\xbf\x71\x17\x07\x5c\x9c\x6f\xb8\xe1\x87\x71\xa8\x51\xcd\x41\xe6\x6d\x38\xb8\xfb\x86\x9b\xfb\xfd\x3c\xad\xa7\xa0\xa5\xd7\x30\xb3\xb9\x3f\x83\xe8\x85\xc5\xbd\x0d\x42\x4e\x0e\xdf\x83\xfc\xd0\x36\xa8\xef\xb0\xf7\x20\x5f\xbf\x6e\x5c\xb6\xe5\x77\xf2\xbe\xb9\xc2\xb6\x0b\x6a\x47\x3e\x6c\x07\x54\x6f\xb4\xa0\x12\xf5\x9e\xa2\xa7\xe8\xbf\x00\x00\x00\xff\xff\x2a\xac\x9f\xff\xa9\x0d\x00\x00")
-func call_tracerJsBytes() ([]byte, error) {
+func call_tracer_jsJsBytes() ([]byte, error) {
return bindataRead(
- _call_tracerJs,
- "call_tracer.js",
+ _call_tracer_jsJs,
+ "call_tracer_js.js",
)
}
-func call_tracerJs() (*asset, error) {
- bytes, err := call_tracerJsBytes()
+func call_tracer_jsJs() (*asset, error) {
+ bytes, err := call_tracer_jsJsBytes()
if err != nil {
return nil, err
}
- info := bindataFileInfo{name: "call_tracer.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+ info := bindataFileInfo{name: "call_tracer_js.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x42, 0x13, 0x7a, 0x14, 0xbf, 0xa7, 0x49, 0x4f, 0xb4, 0x4f, 0x45, 0x1, 0xbc, 0x9e, 0xd1, 0x8e, 0xc7, 0xee, 0x61, 0xfa, 0x82, 0x52, 0xa4, 0x78, 0xfe, 0xff, 0xb1, 0x68, 0x1d, 0xcc, 0x1d, 0x8e}}
return a, nil
}
@@ -392,7 +392,7 @@ var _bindata = map[string]func() (*asset, error){
"4byte_tracer.js": _4byte_tracerJs,
"4byte_tracer_legacy.js": _4byte_tracer_legacyJs,
"bigram_tracer.js": bigram_tracerJs,
- "call_tracer.js": call_tracerJs,
+ "call_tracer_js.js": call_tracer_jsJs,
"call_tracer_legacy.js": call_tracer_legacyJs,
"evmdis_tracer.js": evmdis_tracerJs,
"noop_tracer.js": noop_tracerJs,
@@ -451,7 +451,7 @@ var _bintree = &bintree{nil, map[string]*bintree{
"4byte_tracer.js": {_4byte_tracerJs, map[string]*bintree{}},
"4byte_tracer_legacy.js": {_4byte_tracer_legacyJs, map[string]*bintree{}},
"bigram_tracer.js": {bigram_tracerJs, map[string]*bintree{}},
- "call_tracer.js": {call_tracerJs, map[string]*bintree{}},
+ "call_tracer_js.js": {call_tracer_jsJs, map[string]*bintree{}},
"call_tracer_legacy.js": {call_tracer_legacyJs, map[string]*bintree{}},
"evmdis_tracer.js": {evmdis_tracerJs, map[string]*bintree{}},
"noop_tracer.js": {noop_tracerJs, map[string]*bintree{}},
diff --git a/eth/tracers/internal/tracers/bigram_tracer.js b/eth/tracers/js/internal/tracers/bigram_tracer.js
similarity index 100%
rename from eth/tracers/internal/tracers/bigram_tracer.js
rename to eth/tracers/js/internal/tracers/bigram_tracer.js
diff --git a/eth/tracers/internal/tracers/call_tracer.js b/eth/tracers/js/internal/tracers/call_tracer_js.js
similarity index 100%
rename from eth/tracers/internal/tracers/call_tracer.js
rename to eth/tracers/js/internal/tracers/call_tracer_js.js
diff --git a/eth/tracers/internal/tracers/call_tracer_legacy.js b/eth/tracers/js/internal/tracers/call_tracer_legacy.js
similarity index 100%
rename from eth/tracers/internal/tracers/call_tracer_legacy.js
rename to eth/tracers/js/internal/tracers/call_tracer_legacy.js
diff --git a/eth/tracers/internal/tracers/evmdis_tracer.js b/eth/tracers/js/internal/tracers/evmdis_tracer.js
similarity index 100%
rename from eth/tracers/internal/tracers/evmdis_tracer.js
rename to eth/tracers/js/internal/tracers/evmdis_tracer.js
diff --git a/eth/tracers/internal/tracers/noop_tracer.js b/eth/tracers/js/internal/tracers/noop_tracer.js
similarity index 100%
rename from eth/tracers/internal/tracers/noop_tracer.js
rename to eth/tracers/js/internal/tracers/noop_tracer.js
diff --git a/eth/tracers/internal/tracers/opcount_tracer.js b/eth/tracers/js/internal/tracers/opcount_tracer.js
similarity index 100%
rename from eth/tracers/internal/tracers/opcount_tracer.js
rename to eth/tracers/js/internal/tracers/opcount_tracer.js
diff --git a/eth/tracers/internal/tracers/prestate_tracer.js b/eth/tracers/js/internal/tracers/prestate_tracer.js
similarity index 100%
rename from eth/tracers/internal/tracers/prestate_tracer.js
rename to eth/tracers/js/internal/tracers/prestate_tracer.js
diff --git a/eth/tracers/internal/tracers/tracers.go b/eth/tracers/js/internal/tracers/tracers.go
similarity index 100%
rename from eth/tracers/internal/tracers/tracers.go
rename to eth/tracers/js/internal/tracers/tracers.go
diff --git a/eth/tracers/internal/tracers/trigram_tracer.js b/eth/tracers/js/internal/tracers/trigram_tracer.js
similarity index 100%
rename from eth/tracers/internal/tracers/trigram_tracer.js
rename to eth/tracers/js/internal/tracers/trigram_tracer.js
diff --git a/eth/tracers/internal/tracers/unigram_tracer.js b/eth/tracers/js/internal/tracers/unigram_tracer.js
similarity index 100%
rename from eth/tracers/internal/tracers/unigram_tracer.js
rename to eth/tracers/js/internal/tracers/unigram_tracer.js
diff --git a/eth/tracers/js/tracer.go b/eth/tracers/js/tracer.go
new file mode 100644
index 0000000000..38047135af
--- /dev/null
+++ b/eth/tracers/js/tracer.go
@@ -0,0 +1,880 @@
+// 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 .
+
+// package js is a collection of tracers written in javascript.
+package js
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "math/big"
+ "strings"
+ "sync/atomic"
+ "time"
+ "unicode"
+ "unsafe"
+
+ "github.com/celo-org/celo-blockchain/common"
+ "github.com/celo-org/celo-blockchain/common/hexutil"
+ "github.com/celo-org/celo-blockchain/core"
+ "github.com/celo-org/celo-blockchain/core/vm"
+ "github.com/celo-org/celo-blockchain/crypto"
+ tracers2 "github.com/celo-org/celo-blockchain/eth/tracers"
+ "github.com/celo-org/celo-blockchain/eth/tracers/js/internal/tracers"
+ "github.com/celo-org/celo-blockchain/log"
+ "gopkg.in/olebedev/go-duktape.v3"
+)
+
+// camel converts a snake cased input string into a camel cased output.
+func camel(str string) string {
+ pieces := strings.Split(str, "_")
+ for i := 1; i < len(pieces); i++ {
+ pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
+ }
+ return strings.Join(pieces, "")
+}
+
+var assetTracers = make(map[string]string)
+
+// init retrieves the JavaScript transaction tracers included in go-ethereum.
+func init() {
+ for _, file := range tracers.AssetNames() {
+ name := camel(strings.TrimSuffix(file, ".js"))
+ assetTracers[name] = string(tracers.MustAsset(file))
+ }
+ tracers2.RegisterLookup(true, newJsTracer)
+}
+
+// makeSlice convert an unsafe memory pointer with the given type into a Go byte
+// slice.
+//
+// Note, the returned slice uses the same memory area as the input arguments.
+// If those are duktape stack items, popping them off **will** make the slice
+// contents change.
+func makeSlice(ptr unsafe.Pointer, size uint) []byte {
+ var sl = struct {
+ addr uintptr
+ len int
+ cap int
+ }{uintptr(ptr), int(size), int(size)}
+
+ return *(*[]byte)(unsafe.Pointer(&sl))
+}
+
+// popSlice pops a buffer off the JavaScript stack and returns it as a slice.
+func popSlice(ctx *duktape.Context) []byte {
+ blob := common.CopyBytes(makeSlice(ctx.GetBuffer(-1)))
+ ctx.Pop()
+ return blob
+}
+
+// pushBigInt create a JavaScript BigInteger in the VM.
+func pushBigInt(n *big.Int, ctx *duktape.Context) {
+ ctx.GetGlobalString("bigInt")
+ ctx.PushString(n.String())
+ ctx.Call(1)
+}
+
+// opWrapper provides a JavaScript wrapper around OpCode.
+type opWrapper struct {
+ op vm.OpCode
+}
+
+// pushObject assembles a JSVM object wrapping a swappable opcode and pushes it
+// onto the VM stack.
+func (ow *opWrapper) pushObject(vm *duktape.Context) {
+ obj := vm.PushObject()
+
+ vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(int(ow.op)); return 1 })
+ vm.PutPropString(obj, "toNumber")
+
+ vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushString(ow.op.String()); return 1 })
+ vm.PutPropString(obj, "toString")
+
+ vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushBoolean(ow.op.IsPush()); return 1 })
+ vm.PutPropString(obj, "isPush")
+}
+
+// memoryWrapper provides a JavaScript wrapper around vm.Memory.
+type memoryWrapper struct {
+ memory *vm.Memory
+}
+
+// slice returns the requested range of memory as a byte slice.
+func (mw *memoryWrapper) slice(begin, end int64) []byte {
+ if end == begin {
+ return []byte{}
+ }
+ if end < begin || begin < 0 {
+ // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
+ // runtime goes belly up https://github.com/golang/go/issues/15639.
+ log.Warn("Tracer accessed out of bound memory", "offset", begin, "end", end)
+ return nil
+ }
+ if mw.memory.Len() < int(end) {
+ // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
+ // runtime goes belly up https://github.com/golang/go/issues/15639.
+ log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", begin, "size", end-begin)
+ return nil
+ }
+ return mw.memory.GetCopy(begin, end-begin)
+}
+
+// getUint returns the 32 bytes at the specified address interpreted as a uint.
+func (mw *memoryWrapper) getUint(addr int64) *big.Int {
+ if mw.memory.Len() < int(addr)+32 || addr < 0 {
+ // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
+ // runtime goes belly up https://github.com/golang/go/issues/15639.
+ log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", addr, "size", 32)
+ return new(big.Int)
+ }
+ return new(big.Int).SetBytes(mw.memory.GetPtr(addr, 32))
+}
+
+// pushObject assembles a JSVM object wrapping a swappable memory and pushes it
+// onto the VM stack.
+func (mw *memoryWrapper) pushObject(vm *duktape.Context) {
+ obj := vm.PushObject()
+
+ // Generate the `slice` method which takes two ints and returns a buffer
+ vm.PushGoFunction(func(ctx *duktape.Context) int {
+ blob := mw.slice(int64(ctx.GetInt(-2)), int64(ctx.GetInt(-1)))
+ ctx.Pop2()
+
+ ptr := ctx.PushFixedBuffer(len(blob))
+ copy(makeSlice(ptr, uint(len(blob))), blob)
+ return 1
+ })
+ vm.PutPropString(obj, "slice")
+
+ // Generate the `getUint` method which takes an int and returns a bigint
+ vm.PushGoFunction(func(ctx *duktape.Context) int {
+ offset := int64(ctx.GetInt(-1))
+ ctx.Pop()
+
+ pushBigInt(mw.getUint(offset), ctx)
+ return 1
+ })
+ vm.PutPropString(obj, "getUint")
+}
+
+// stackWrapper provides a JavaScript wrapper around vm.Stack.
+type stackWrapper struct {
+ stack *vm.Stack
+}
+
+// peek returns the nth-from-the-top element of the stack.
+func (sw *stackWrapper) peek(idx int) *big.Int {
+ if len(sw.stack.Data()) <= idx || idx < 0 {
+ // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
+ // runtime goes belly up https://github.com/golang/go/issues/15639.
+ log.Warn("Tracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx)
+ return new(big.Int)
+ }
+ return sw.stack.Back(idx).ToBig()
+}
+
+// pushObject assembles a JSVM object wrapping a swappable stack and pushes it
+// onto the VM stack.
+func (sw *stackWrapper) pushObject(vm *duktape.Context) {
+ obj := vm.PushObject()
+
+ vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(len(sw.stack.Data())); return 1 })
+ vm.PutPropString(obj, "length")
+
+ // Generate the `peek` method which takes an int and returns a bigint
+ vm.PushGoFunction(func(ctx *duktape.Context) int {
+ offset := ctx.GetInt(-1)
+ ctx.Pop()
+
+ pushBigInt(sw.peek(offset), ctx)
+ return 1
+ })
+ vm.PutPropString(obj, "peek")
+}
+
+// dbWrapper provides a JavaScript wrapper around vm.Database.
+type dbWrapper struct {
+ db vm.StateDB
+}
+
+// pushObject assembles a JSVM object wrapping a swappable database and pushes it
+// onto the VM stack.
+func (dw *dbWrapper) pushObject(vm *duktape.Context) {
+ obj := vm.PushObject()
+
+ // Push the wrapper for statedb.GetBalance
+ vm.PushGoFunction(func(ctx *duktape.Context) int {
+ pushBigInt(dw.db.GetBalance(common.BytesToAddress(popSlice(ctx))), ctx)
+ return 1
+ })
+ vm.PutPropString(obj, "getBalance")
+
+ // Push the wrapper for statedb.GetNonce
+ vm.PushGoFunction(func(ctx *duktape.Context) int {
+ ctx.PushInt(int(dw.db.GetNonce(common.BytesToAddress(popSlice(ctx)))))
+ return 1
+ })
+ vm.PutPropString(obj, "getNonce")
+
+ // Push the wrapper for statedb.GetCode
+ vm.PushGoFunction(func(ctx *duktape.Context) int {
+ code := dw.db.GetCode(common.BytesToAddress(popSlice(ctx)))
+
+ ptr := ctx.PushFixedBuffer(len(code))
+ copy(makeSlice(ptr, uint(len(code))), code)
+ return 1
+ })
+ vm.PutPropString(obj, "getCode")
+
+ // Push the wrapper for statedb.GetState
+ vm.PushGoFunction(func(ctx *duktape.Context) int {
+ hash := popSlice(ctx)
+ addr := popSlice(ctx)
+
+ state := dw.db.GetState(common.BytesToAddress(addr), common.BytesToHash(hash))
+
+ ptr := ctx.PushFixedBuffer(len(state))
+ copy(makeSlice(ptr, uint(len(state))), state[:])
+ return 1
+ })
+ vm.PutPropString(obj, "getState")
+
+ // Push the wrapper for statedb.Exists
+ vm.PushGoFunction(func(ctx *duktape.Context) int {
+ ctx.PushBoolean(dw.db.Exist(common.BytesToAddress(popSlice(ctx))))
+ return 1
+ })
+ vm.PutPropString(obj, "exists")
+}
+
+// contractWrapper provides a JavaScript wrapper around vm.Contract
+type contractWrapper struct {
+ contract *vm.Contract
+}
+
+// pushObject assembles a JSVM object wrapping a swappable contract and pushes it
+// onto the VM stack.
+func (cw *contractWrapper) pushObject(vm *duktape.Context) {
+ obj := vm.PushObject()
+
+ // Push the wrapper for contract.Caller
+ vm.PushGoFunction(func(ctx *duktape.Context) int {
+ ptr := ctx.PushFixedBuffer(20)
+ copy(makeSlice(ptr, 20), cw.contract.Caller().Bytes())
+ return 1
+ })
+ vm.PutPropString(obj, "getCaller")
+
+ // Push the wrapper for contract.Address
+ vm.PushGoFunction(func(ctx *duktape.Context) int {
+ ptr := ctx.PushFixedBuffer(20)
+ copy(makeSlice(ptr, 20), cw.contract.Address().Bytes())
+ return 1
+ })
+ vm.PutPropString(obj, "getAddress")
+
+ // Push the wrapper for contract.Value
+ vm.PushGoFunction(func(ctx *duktape.Context) int {
+ pushBigInt(cw.contract.Value(), ctx)
+ return 1
+ })
+ vm.PutPropString(obj, "getValue")
+
+ // Push the wrapper for contract.Input
+ vm.PushGoFunction(func(ctx *duktape.Context) int {
+ blob := cw.contract.Input
+
+ ptr := ctx.PushFixedBuffer(len(blob))
+ copy(makeSlice(ptr, uint(len(blob))), blob)
+ return 1
+ })
+ 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")
+}
+
+// jsTracer provides an implementation of Tracer that evaluates a Javascript
+// function for each VM execution step.
+type jsTracer struct {
+ vm *duktape.Context // Javascript VM instance
+ env *vm.EVM // EVM instance executing the code being traced
+
+ tracerObject int // Stack index of the tracer JavaScript object
+ stateObject int // Stack index of the global state to pull arguments from
+
+ opWrapper *opWrapper // Wrapper around the VM opcode
+ stackWrapper *stackWrapper // Wrapper around the VM stack
+ memoryWrapper *memoryWrapper // Wrapper around the VM memory
+ contractWrapper *contractWrapper // Wrapper around the contract object
+ dbWrapper *dbWrapper // Wrapper around the VM environment
+
+ pcValue *uint // Swappable pc value wrapped by a log accessor
+ gasValue *uint // Swappable gas value wrapped by a log accessor
+ costValue *uint // Swappable cost value wrapped by a log accessor
+ depthValue *uint // Swappable depth 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
+
+ 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
+ err error // Error, if one has occurred
+
+ interrupt uint32 // Atomic flag to signal execution interruption
+ reason error // Textual reason for the interruption
+
+ 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
+}
+
+// New instantiates a new tracer instance. code specifies a Javascript snippet,
+// which must evaluate to an expression returning an object with 'step', 'fault'
+// and 'result' functions.
+func newJsTracer(code string, ctx *tracers2.Context) (tracers2.Tracer, error) {
+ if c, ok := assetTracers[code]; ok {
+ code = c
+ }
+ if ctx == nil {
+ ctx = new(tracers2.Context)
+ }
+ tracer := &jsTracer{
+ vm: duktape.New(),
+ ctx: make(map[string]interface{}),
+ opWrapper: new(opWrapper),
+ stackWrapper: new(stackWrapper),
+ memoryWrapper: new(memoryWrapper),
+ contractWrapper: new(contractWrapper),
+ dbWrapper: new(dbWrapper),
+ pcValue: new(uint),
+ gasValue: new(uint),
+ costValue: new(uint),
+ depthValue: new(uint),
+ refundValue: new(uint),
+ frame: newFrame(),
+ frameResult: newFrameResult(),
+ }
+ if ctx.BlockHash != (common.Hash{}) {
+ tracer.ctx["blockHash"] = ctx.BlockHash
+
+ if ctx.TxHash != (common.Hash{}) {
+ tracer.ctx["txIndex"] = ctx.TxIndex
+ tracer.ctx["txHash"] = ctx.TxHash
+ }
+ }
+ // Set up builtins for this environment
+ tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int {
+ ctx.PushString(hexutil.Encode(popSlice(ctx)))
+ return 1
+ })
+ tracer.vm.PushGlobalGoFunction("toWord", func(ctx *duktape.Context) int {
+ var word common.Hash
+ if ptr, size := ctx.GetBuffer(-1); ptr != nil {
+ word = common.BytesToHash(makeSlice(ptr, size))
+ } else {
+ word = common.HexToHash(ctx.GetString(-1))
+ }
+ ctx.Pop()
+ copy(makeSlice(ctx.PushFixedBuffer(32), 32), word[:])
+ return 1
+ })
+ tracer.vm.PushGlobalGoFunction("toAddress", func(ctx *duktape.Context) int {
+ var addr common.Address
+ if ptr, size := ctx.GetBuffer(-1); ptr != nil {
+ addr = common.BytesToAddress(makeSlice(ptr, size))
+ } else {
+ addr = common.HexToAddress(ctx.GetString(-1))
+ }
+ ctx.Pop()
+ copy(makeSlice(ctx.PushFixedBuffer(20), 20), addr[:])
+ return 1
+ })
+ tracer.vm.PushGlobalGoFunction("toContract", func(ctx *duktape.Context) int {
+ var from common.Address
+ if ptr, size := ctx.GetBuffer(-2); ptr != nil {
+ from = common.BytesToAddress(makeSlice(ptr, size))
+ } else {
+ from = common.HexToAddress(ctx.GetString(-2))
+ }
+ nonce := uint64(ctx.GetInt(-1))
+ ctx.Pop2()
+
+ contract := crypto.CreateAddress(from, nonce)
+ copy(makeSlice(ctx.PushFixedBuffer(20), 20), contract[:])
+ return 1
+ })
+ tracer.vm.PushGlobalGoFunction("toContract2", func(ctx *duktape.Context) int {
+ var from common.Address
+ if ptr, size := ctx.GetBuffer(-3); ptr != nil {
+ from = common.BytesToAddress(makeSlice(ptr, size))
+ } else {
+ from = common.HexToAddress(ctx.GetString(-3))
+ }
+ // Retrieve salt hex string from js stack
+ salt := common.HexToHash(ctx.GetString(-2))
+ // Retrieve code slice from js stack
+ var code []byte
+ if ptr, size := ctx.GetBuffer(-1); ptr != nil {
+ code = common.CopyBytes(makeSlice(ptr, size))
+ } else {
+ code = common.FromHex(ctx.GetString(-1))
+ }
+ codeHash := crypto.Keccak256(code)
+ ctx.Pop3()
+ contract := crypto.CreateAddress2(from, salt, codeHash)
+ copy(makeSlice(ctx.PushFixedBuffer(20), 20), contract[:])
+ return 1
+ })
+ tracer.vm.PushGlobalGoFunction("isPrecompiled", func(ctx *duktape.Context) int {
+ addr := common.BytesToAddress(popSlice(ctx))
+ for _, p := range tracer.activePrecompiles {
+ if p == addr {
+ ctx.PushBoolean(true)
+ return 1
+ }
+ }
+ ctx.PushBoolean(false)
+ return 1
+ })
+ tracer.vm.PushGlobalGoFunction("slice", func(ctx *duktape.Context) int {
+ start, end := ctx.GetInt(-2), ctx.GetInt(-1)
+ ctx.Pop2()
+
+ blob := popSlice(ctx)
+ size := end - start
+
+ if start < 0 || start > end || end > len(blob) {
+ // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
+ // runtime goes belly up https://github.com/golang/go/issues/15639.
+ log.Warn("Tracer accessed out of bound memory", "available", len(blob), "offset", start, "size", size)
+ ctx.PushFixedBuffer(0)
+ return 1
+ }
+ copy(makeSlice(ctx.PushFixedBuffer(size), uint(size)), blob[start:end])
+ return 1
+ })
+ // Push the JavaScript tracer as object #0 onto the JSVM stack and validate it
+ if err := tracer.vm.PevalString("(" + code + ")"); err != nil {
+ log.Warn("Failed to compile tracer", "err", err)
+ return nil, err
+ }
+ tracer.tracerObject = 0 // yeah, nice, eval can't return the index itself
+
+ hasStep := tracer.vm.GetPropString(tracer.tracerObject, "step")
+ tracer.vm.Pop()
+
+ if !tracer.vm.GetPropString(tracer.tracerObject, "fault") {
+ return nil, fmt.Errorf("trace object must expose a function fault()")
+ }
+ tracer.vm.Pop()
+
+ if !tracer.vm.GetPropString(tracer.tracerObject, "result") {
+ return nil, fmt.Errorf("trace object must expose a function result()")
+ }
+ 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()")
+ }
+ tracer.traceCallFrames = hasEnter && hasExit
+ tracer.traceSteps = hasStep
+
+ // Tracer is valid, inject the big int library to access large numbers
+ tracer.vm.EvalString(bigIntegerJS)
+ tracer.vm.PutGlobalString("bigInt")
+
+ // Push the global environment state as object #1 into the JSVM stack
+ tracer.stateObject = tracer.vm.PushObject()
+
+ logObject := tracer.vm.PushObject()
+
+ tracer.opWrapper.pushObject(tracer.vm)
+ tracer.vm.PutPropString(logObject, "op")
+
+ tracer.stackWrapper.pushObject(tracer.vm)
+ tracer.vm.PutPropString(logObject, "stack")
+
+ tracer.memoryWrapper.pushObject(tracer.vm)
+ tracer.vm.PutPropString(logObject, "memory")
+
+ tracer.contractWrapper.pushObject(tracer.vm)
+ tracer.vm.PutPropString(logObject, "contract")
+
+ tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.pcValue); return 1 })
+ tracer.vm.PutPropString(logObject, "getPC")
+
+ tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.gasValue); return 1 })
+ tracer.vm.PutPropString(logObject, "getGas")
+
+ tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.costValue); return 1 })
+ tracer.vm.PutPropString(logObject, "getCost")
+
+ tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.depthValue); return 1 })
+ tracer.vm.PutPropString(logObject, "getDepth")
+
+ tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.refundValue); return 1 })
+ tracer.vm.PutPropString(logObject, "getRefund")
+
+ tracer.vm.PushGoFunction(func(ctx *duktape.Context) int {
+ if tracer.errorValue != nil {
+ ctx.PushString(*tracer.errorValue)
+ } else {
+ ctx.PushUndefined()
+ }
+ return 1
+ })
+ tracer.vm.PutPropString(logObject, "getError")
+
+ 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.vm.PutPropString(tracer.stateObject, "db")
+
+ return tracer, nil
+}
+
+// Stop terminates execution of the tracer at the first opportune moment.
+func (jst *jsTracer) Stop(err error) {
+ jst.reason = err
+ atomic.StoreUint32(&jst.interrupt, 1)
+}
+
+// call executes a method on a JS object, catching any errors, formatting and
+// returning them as error objects.
+func (jst *jsTracer) call(noret bool, method string, args ...string) (json.RawMessage, error) {
+ // Execute the JavaScript call and return any error
+ jst.vm.PushString(method)
+ for _, arg := range args {
+ jst.vm.GetPropString(jst.stateObject, arg)
+ }
+ code := jst.vm.PcallProp(jst.tracerObject, len(args))
+ defer jst.vm.Pop()
+
+ if code != 0 {
+ err := jst.vm.SafeToString(-1)
+ return nil, errors.New(err)
+ }
+ // No error occurred, extract return value and return
+ if noret {
+ return nil, nil
+ }
+ // Push a JSON marshaller onto the stack. We can't marshal from the out-
+ // side because duktape can crash on large nestings and we can't catch
+ // C++ exceptions ourselves from Go. TODO(karalabe): Yuck, why wrap?!
+ jst.vm.PushString("(JSON.stringify)")
+ jst.vm.Eval()
+
+ jst.vm.Swap(-1, -2)
+ if code = jst.vm.Pcall(1); code != 0 {
+ err := jst.vm.SafeToString(-1)
+ return nil, errors.New(err)
+ }
+ return json.RawMessage(jst.vm.SafeToString(-1)), nil
+}
+
+func wrapError(context string, err error) error {
+ return fmt.Errorf("%v in server-side tracer function '%v'", err, context)
+}
+
+// CaptureStart implements the Tracer interface to initialize the tracing operation.
+func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+ jst.env = env
+ jst.ctx["type"] = "CALL"
+ if create {
+ jst.ctx["type"] = "CREATE"
+ }
+ jst.ctx["from"] = from
+ jst.ctx["to"] = to
+ jst.ctx["input"] = input
+ jst.ctx["gas"] = gas
+ jst.ctx["gasPrice"] = env.TxContext.GasPrice
+ jst.ctx["value"] = value
+
+ // Initialize the context
+ jst.ctx["block"] = env.Context.BlockNumber.Uint64()
+ jst.dbWrapper.db = env.StateDB
+ // Update list of precompiles based on current block
+ rules := env.ChainConfig().Rules(env.Context.BlockNumber)
+ jst.activePrecompiles = vm.ActivePrecompiles(rules)
+
+ // Compute intrinsic gas
+ isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
+ isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
+ intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul, nil, 0)
+ if err != nil {
+ return
+ }
+ jst.ctx["intrinsicGas"] = intrinsicGas
+}
+
+// CaptureState implements the Tracer interface to trace a single step of VM execution.
+func (jst *jsTracer) CaptureState(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 {
+ return
+ }
+ // If tracing was interrupted, set the error and stop
+ if atomic.LoadUint32(&jst.interrupt) > 0 {
+ jst.err = jst.reason
+ jst.env.Cancel()
+ return
+ }
+ jst.opWrapper.op = op
+ jst.stackWrapper.stack = scope.Stack
+ jst.memoryWrapper.memory = scope.Memory
+ jst.contractWrapper.contract = scope.Contract
+
+ *jst.pcValue = uint(pc)
+ *jst.gasValue = uint(gas)
+ *jst.costValue = uint(cost)
+ *jst.depthValue = uint(depth)
+ *jst.refundValue = uint(jst.env.StateDB.GetRefund())
+
+ jst.errorValue = nil
+ if err != nil {
+ jst.errorValue = new(string)
+ *jst.errorValue = err.Error()
+ }
+
+ if _, err := jst.call(true, "step", "log", "db"); err != nil {
+ jst.err = wrapError("step", err)
+ }
+}
+
+// CaptureFault implements the Tracer interface to trace an execution fault
+func (jst *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
+ if jst.err != nil {
+ return
+ }
+ // Apart from the error, everything matches the previous invocation
+ jst.errorValue = new(string)
+ *jst.errorValue = err.Error()
+
+ if _, err := jst.call(true, "fault", "log", "db"); err != nil {
+ jst.err = wrapError("fault", err)
+ }
+}
+
+// CaptureEnd is called after the call finishes to finalize the tracing.
+func (jst *jsTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
+ jst.ctx["output"] = output
+ jst.ctx["time"] = t.String()
+ jst.ctx["gasUsed"] = gasUsed
+
+ if err != nil {
+ jst.ctx["error"] = err.Error()
+ }
+}
+
+// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
+func (jst *jsTracer) 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 *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
+ if !jst.traceCallFrames {
+ 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
+func (jst *jsTracer) GetResult() (json.RawMessage, error) {
+ // Transform the context into a JavaScript object and inject into the state
+ obj := jst.vm.PushObject()
+
+ for key, val := range jst.ctx {
+ jst.addToObj(obj, key, val)
+ }
+ jst.vm.PutPropString(jst.stateObject, "ctx")
+
+ // Finalize the trace and return the results
+ result, err := jst.call(false, "result", "ctx", "db")
+ if err != nil {
+ jst.err = wrapError("result", err)
+ }
+ // Clean up the JavaScript environment
+ jst.vm.DestroyHeap()
+ jst.vm.Destroy()
+
+ return result, jst.err
+}
+
+// addToObj pushes a field to a JS object.
+func (jst *jsTracer) 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))
+ }
+}
diff --git a/eth/tracers/tracer_test.go b/eth/tracers/js/tracer_test.go
similarity index 75%
rename from eth/tracers/tracer_test.go
rename to eth/tracers/js/tracer_test.go
index 67723fdd20..d2748e6dde 100644
--- a/eth/tracers/tracer_test.go
+++ b/eth/tracers/js/tracer_test.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package tracers
+package js
import (
"encoding/json"
@@ -26,6 +26,7 @@ import (
"github.com/celo-org/celo-blockchain/common"
"github.com/celo-org/celo-blockchain/core/state"
"github.com/celo-org/celo-blockchain/core/vm"
+ "github.com/celo-org/celo-blockchain/eth/tracers"
"github.com/celo-org/celo-blockchain/params"
)
@@ -58,13 +59,13 @@ func testCtx() *vmContext {
return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
}
-func runTrace(tracer *Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (json.RawMessage, error) {
- env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer})
+func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (json.RawMessage, error) {
var (
+ env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer})
startGas uint64 = 10000
value = big.NewInt(0)
+ contract = vm.NewContract(account{}, account{}, value, startGas)
)
- contract := vm.NewContract(account{}, account{}, value, startGas)
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value)
@@ -79,14 +80,11 @@ func runTrace(tracer *Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (j
func TestTracer(t *testing.T) {
execTracer := func(code string) ([]byte, string) {
t.Helper()
- tracer, err := New(code, new(Context))
+ tracer, err := newJsTracer(code, nil)
if err != nil {
t.Fatal(err)
}
- ret, err := runTrace(tracer, &vmContext{
- blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)},
- txCtx: vm.TxContext{GasPrice: big.NewInt(100000)},
- }, params.TestChainConfig)
+ ret, err := runTrace(tracer, testCtx(), params.TestChainConfig)
if err != nil {
return nil, err.Error() // Stringify to allow comparison without nil checks
}
@@ -131,9 +129,8 @@ func TestTracer(t *testing.T) {
func TestHalt(t *testing.T) {
t.Skip("duktape doesn't support abortion")
-
timeout := errors.New("stahp")
- tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}", new(Context))
+ tracer, err := newJsTracer("{step: function() { while(1); }, result: function() { return null; }, fault: function(){}}", nil)
if err != nil {
t.Fatal(err)
}
@@ -147,19 +144,20 @@ func TestHalt(t *testing.T) {
}
func TestHaltBetweenSteps(t *testing.T) {
- tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", new(Context))
+ tracer, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }}", nil)
if err != nil {
t.Fatal(err)
}
- env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.IstanbulTestChainConfig, vm.Config{Debug: true, Tracer: tracer})
+ env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
scope := &vm.ScopeContext{
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
}
- tracer.CaptureState(env, 0, 0, 0, 0, scope, nil, 0, nil)
+ tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 0, big.NewInt(0))
+ tracer.CaptureState(0, 0, 0, 0, scope, nil, 0, nil)
timeout := errors.New("stahp")
tracer.Stop(timeout)
- tracer.CaptureState(env, 0, 0, 0, 0, scope, nil, 0, nil)
+ tracer.CaptureState(0, 0, 0, 0, scope, nil, 0, nil)
if _, err := tracer.GetResult(); err.Error() != timeout.Error() {
t.Errorf("Expected timeout error, got %v", err)
@@ -169,24 +167,16 @@ func TestHaltBetweenSteps(t *testing.T) {
// TestNoStepExec tests a regular value transfer (no exec), and accessing the statedb
// in 'result'
func TestNoStepExec(t *testing.T) {
- runEmptyTrace := func(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) {
- env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
- startGas := uint64(10000)
- contract := vm.NewContract(account{}, account{}, big.NewInt(0), startGas)
- tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, big.NewInt(0))
- tracer.CaptureEnd(nil, startGas-contract.Gas, 1, nil)
- return tracer.GetResult()
- }
execTracer := func(code string) []byte {
t.Helper()
- tracer, err := New(code, new(Context))
+ tracer, err := newJsTracer(code, nil)
if err != nil {
t.Fatal(err)
}
- ret, err := runEmptyTrace(tracer, &vmContext{
- blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)},
- txCtx: vm.TxContext{GasPrice: big.NewInt(100000)},
- })
+ env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(100)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
+ tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 1000, big.NewInt(0))
+ tracer.CaptureEnd(nil, 0, 1, nil)
+ ret, err := tracer.GetResult()
if err != nil {
t.Fatal(err)
}
@@ -213,7 +203,7 @@ func TestIsPrecompile(t *testing.T) {
chaincfg.IstanbulBlock = big.NewInt(200)
chaincfg.EspressoBlock = big.NewInt(300)
txCtx := vm.TxContext{GasPrice: big.NewInt(100000)}
- tracer, err := New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", new(Context))
+ tracer, err := newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
if err != nil {
t.Fatal(err)
}
@@ -227,7 +217,7 @@ func TestIsPrecompile(t *testing.T) {
t.Errorf("Tracer should not consider blake2f as precompile in byzantium")
}
- tracer, _ = New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", new(Context))
+ tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
blockCtx = vm.BlockContext{BlockNumber: big.NewInt(250)}
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
if err != nil {
@@ -240,23 +230,20 @@ func TestIsPrecompile(t *testing.T) {
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 {
+ if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(tracers.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 {
+ if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.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))
+ tracer, err := newJsTracer("{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(tracers.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)
diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go
new file mode 100644
index 0000000000..6597d76850
--- /dev/null
+++ b/eth/tracers/native/call.go
@@ -0,0 +1,182 @@
+// Copyright 2021 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 .
+
+package native
+
+import (
+ "encoding/json"
+ "errors"
+ "math/big"
+ "strconv"
+ "strings"
+ "sync/atomic"
+ "time"
+
+ "github.com/celo-org/celo-blockchain/common"
+ "github.com/celo-org/celo-blockchain/core/vm"
+ "github.com/celo-org/celo-blockchain/eth/tracers"
+)
+
+func init() {
+ register("callTracer", newCallTracer)
+}
+
+type callFrame struct {
+ Type string `json:"type"`
+ From string `json:"from"`
+ To string `json:"to,omitempty"`
+ Value string `json:"value,omitempty"`
+ Gas string `json:"gas"`
+ GasUsed string `json:"gasUsed"`
+ Input string `json:"input"`
+ Output string `json:"output,omitempty"`
+ Error string `json:"error,omitempty"`
+ Calls []callFrame `json:"calls,omitempty"`
+}
+
+type callTracer struct {
+ env *vm.EVM
+ callstack []callFrame
+ interrupt uint32 // Atomic flag to signal execution interruption
+ reason error // Textual reason for the interruption
+}
+
+// newCallTracer returns a native go tracer which tracks
+// call frames of a tx, and implements vm.EVMLogger.
+func newCallTracer() tracers.Tracer {
+ // First callframe contains tx context info
+ // and is populated on start and end.
+ t := &callTracer{callstack: make([]callFrame, 1)}
+ return t
+}
+
+// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
+func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+ t.env = env
+ t.callstack[0] = callFrame{
+ Type: "CALL",
+ From: addrToHex(from),
+ To: addrToHex(to),
+ Input: bytesToHex(input),
+ Gas: uintToHex(gas),
+ Value: bigToHex(value),
+ }
+ if create {
+ t.callstack[0].Type = "CREATE"
+ }
+}
+
+// CaptureEnd is called after the call finishes to finalize the tracing.
+func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
+ t.callstack[0].GasUsed = uintToHex(gasUsed)
+ if err != nil {
+ t.callstack[0].Error = err.Error()
+ if err.Error() == "execution reverted" && len(output) > 0 {
+ t.callstack[0].Output = bytesToHex(output)
+ }
+ } else {
+ t.callstack[0].Output = bytesToHex(output)
+ }
+}
+
+// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
+func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+}
+
+// CaptureFault implements the EVMLogger interface to trace an execution fault.
+func (t *callTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
+}
+
+// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
+func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+ // Skip if tracing was interrupted
+ if atomic.LoadUint32(&t.interrupt) > 0 {
+ t.env.Cancel()
+ return
+ }
+
+ call := callFrame{
+ Type: typ.String(),
+ From: addrToHex(from),
+ To: addrToHex(to),
+ Input: bytesToHex(input),
+ Gas: uintToHex(gas),
+ Value: bigToHex(value),
+ }
+ t.callstack = append(t.callstack, call)
+}
+
+// CaptureExit is called when EVM exits a scope, even if the scope didn't
+// execute any code.
+func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
+ size := len(t.callstack)
+ if size <= 1 {
+ return
+ }
+ // pop call
+ call := t.callstack[size-1]
+ t.callstack = t.callstack[:size-1]
+ size -= 1
+
+ call.GasUsed = uintToHex(gasUsed)
+ if err == nil {
+ call.Output = bytesToHex(output)
+ } else {
+ call.Error = err.Error()
+ if call.Type == "CREATE" || call.Type == "CREATE2" {
+ call.To = ""
+ }
+ }
+ t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
+}
+
+// GetResult returns the json-encoded nested list of call traces, and any
+// error arising from the encoding or forceful termination (via `Stop`).
+func (t *callTracer) GetResult() (json.RawMessage, error) {
+ if len(t.callstack) != 1 {
+ return nil, errors.New("incorrect number of top-level calls")
+ }
+ res, err := json.Marshal(t.callstack[0])
+ if err != nil {
+ return nil, err
+ }
+ return json.RawMessage(res), t.reason
+}
+
+// Stop terminates execution of the tracer at the first opportune moment.
+func (t *callTracer) Stop(err error) {
+ t.reason = err
+ atomic.StoreUint32(&t.interrupt, 1)
+}
+
+func bytesToHex(s []byte) string {
+ return "0x" + common.Bytes2Hex(s)
+}
+
+func bigToHex(n *big.Int) string {
+ if n == nil {
+ return ""
+ }
+ return "0x" + n.Text(16)
+}
+
+func uintToHex(n uint64) string {
+ return "0x" + strconv.FormatUint(n, 16)
+}
+
+func addrToHex(a common.Address) string {
+ return strings.ToLower(a.Hex())
+}
diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go
new file mode 100644
index 0000000000..a36c3fdee1
--- /dev/null
+++ b/eth/tracers/native/noop.go
@@ -0,0 +1,74 @@
+// Copyright 2021 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 .
+
+package native
+
+import (
+ "encoding/json"
+ "math/big"
+ "time"
+
+ "github.com/celo-org/celo-blockchain/common"
+ "github.com/celo-org/celo-blockchain/core/vm"
+ "github.com/celo-org/celo-blockchain/eth/tracers"
+)
+
+func init() {
+ register("noopTracerNative", newNoopTracer)
+}
+
+// noopTracer is a go implementation of the Tracer interface which
+// performs no action. It's mostly useful for testing purposes.
+type noopTracer struct{}
+
+// newNoopTracer returns a new noop tracer.
+func newNoopTracer() tracers.Tracer {
+ return &noopTracer{}
+}
+
+// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
+func (t *noopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+}
+
+// CaptureEnd is called after the call finishes to finalize the tracing.
+func (t *noopTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
+}
+
+// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
+func (t *noopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+}
+
+// CaptureFault implements the EVMLogger interface to trace an execution fault.
+func (t *noopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
+}
+
+// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
+func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+}
+
+// CaptureExit is called when EVM exits a scope, even if the scope didn't
+// execute any code.
+func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
+}
+
+// GetResult returns an empty json object.
+func (t *noopTracer) GetResult() (json.RawMessage, error) {
+ return json.RawMessage(`{}`), nil
+}
+
+// Stop terminates execution of the tracer at the first opportune moment.
+func (t *noopTracer) Stop(err error) {
+}
diff --git a/eth/tracers/native/tracer.go b/eth/tracers/native/tracer.go
new file mode 100644
index 0000000000..0cb9df3097
--- /dev/null
+++ b/eth/tracers/native/tracer.go
@@ -0,0 +1,81 @@
+// Copyright 2021 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 .
+
+/*
+Package native is a collection of tracers written in go.
+
+In order to add a native tracer and have it compiled into the binary, a new
+file needs to be added to this folder, containing an implementation of the
+`eth.tracers.Tracer` interface.
+
+Aside from implementing the tracer, it also needs to register itself, using the
+`register` method -- and this needs to be done in the package initialization.
+
+Example:
+
+```golang
+
+ func init() {
+ register("noopTracerNative", newNoopTracer)
+ }
+
+```
+*/
+package native
+
+import (
+ "errors"
+
+ "github.com/celo-org/celo-blockchain/eth/tracers"
+)
+
+// init registers itself this packages as a lookup for tracers.
+func init() {
+ tracers.RegisterLookup(false, lookup)
+}
+
+/*
+ctors is a map of package-local tracer constructors.
+
+We cannot be certain about the order of init-functions within a package,
+The go spec (https://golang.org/ref/spec#Package_initialization) says
+
+> To ensure reproducible initialization behavior, build systems
+> are encouraged to present multiple files belonging to the same
+> package in lexical file name order to a compiler.
+
+Hence, we cannot make the map in init, but must make it upon first use.
+*/
+var ctors map[string]func() tracers.Tracer
+
+// register is used by native tracers to register their presence.
+func register(name string, ctor func() tracers.Tracer) {
+ if ctors == nil {
+ ctors = make(map[string]func() tracers.Tracer)
+ }
+ ctors[name] = ctor
+}
+
+// lookup returns a tracer, if one can be matched to the given name.
+func lookup(name string, ctx *tracers.Context) (tracers.Tracer, error) {
+ if ctors == nil {
+ ctors = make(map[string]func() tracers.Tracer)
+ }
+ if ctor, ok := ctors[name]; ok {
+ return ctor(), nil
+ }
+ return nil, errors.New("no tracer found")
+}
diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go
index dc8e007d5e..98a2e1d389 100644
--- a/eth/tracers/tracers.go
+++ b/eth/tracers/tracers.go
@@ -14,40 +14,59 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-// Package tracers is a collection of JavaScript transaction tracers.
+// Package tracers is a manager for transaction tracing engines.
package tracers
import (
- "strings"
- "unicode"
+ "encoding/json"
+ "errors"
- "github.com/celo-org/celo-blockchain/eth/tracers/internal/tracers"
+ "github.com/celo-org/celo-blockchain/common"
+ "github.com/celo-org/celo-blockchain/core/vm"
)
-// all contains all the built in JavaScript tracers by name.
-var all = make(map[string]string)
+// Context contains some contextual infos for a transaction execution that is not
+// available from within the EVM object.
+type Context struct {
+ BlockHash common.Hash // Hash of the block the tx is contained within (zero if dangling tx or call)
+ TxIndex int // Index of the transaction within a block (zero if dangling tx or call)
+ TxHash common.Hash // Hash of the transaction being traced (zero if dangling call)
+}
-// camel converts a snake cased input string into a camel cased output.
-func camel(str string) string {
- pieces := strings.Split(str, "_")
- for i := 1; i < len(pieces); i++ {
- pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
- }
- return strings.Join(pieces, "")
+// Tracer interface extends vm.EVMLogger and additionally
+// allows collecting the tracing result.
+type Tracer interface {
+ vm.EVMLogger
+ GetResult() (json.RawMessage, error)
+ // Stop terminates execution of the tracer at the first opportune moment.
+ Stop(err error)
}
-// init retrieves the JavaScript transaction tracers included in go-ethereum.
-func init() {
- for _, file := range tracers.AssetNames() {
- name := camel(strings.TrimSuffix(file, ".js"))
- all[name] = string(tracers.MustAsset(file))
+type lookupFunc func(string, *Context) (Tracer, error)
+
+var (
+ lookups []lookupFunc
+)
+
+// RegisterLookup registers a method as a lookup for tracers, meaning that
+// users can invoke a named tracer through that lookup. If 'wildcard' is true,
+// then the lookup will be placed last. This is typically meant for interpreted
+// engines (js) which can evaluate dynamic user-supplied code.
+func RegisterLookup(wildcard bool, lookup lookupFunc) {
+ if wildcard {
+ lookups = append(lookups, lookup)
+ } else {
+ lookups = append([]lookupFunc{lookup}, lookups...)
}
}
-// tracer retrieves a specific JavaScript tracer by name.
-func tracer(name string) (string, bool) {
- if tracer, ok := all[name]; ok {
- return tracer, true
+// New returns a new instance of a tracer, by iterating through the
+// registered lookups.
+func New(code string, ctx *Context) (Tracer, error) {
+ for _, lookup := range lookups {
+ if tracer, err := lookup(code, ctx); err == nil {
+ return tracer, nil
+ }
}
- return "", false
+ return nil, errors.New("tracer not found")
}
diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go
index 2c862b2c6e..6442ac474a 100644
--- a/eth/tracers/tracers_test.go
+++ b/eth/tracers/tracers_test.go
@@ -16,19 +16,10 @@
package tracers
import (
- "crypto/ecdsa"
- "crypto/rand"
- "encoding/json"
- "io/ioutil"
"math/big"
- "path/filepath"
- "reflect"
- "strings"
"testing"
"github.com/celo-org/celo-blockchain/common"
- "github.com/celo-org/celo-blockchain/common/hexutil"
- "github.com/celo-org/celo-blockchain/common/math"
"github.com/celo-org/celo-blockchain/contracts/testutil"
"github.com/celo-org/celo-blockchain/core"
"github.com/celo-org/celo-blockchain/core/rawdb"
@@ -37,345 +28,9 @@ import (
"github.com/celo-org/celo-blockchain/core/vm/vmcontext"
"github.com/celo-org/celo-blockchain/crypto"
"github.com/celo-org/celo-blockchain/params"
- "github.com/celo-org/celo-blockchain/rlp"
"github.com/celo-org/celo-blockchain/tests"
)
-// To generate a new callTracer test, copy paste the makeTest method below into
-// a Geth console and call it with a transaction hash you which to export.
-
-/*
-// makeTest generates a callTracer test by running a prestate reassembled and a
-// call trace run, assembling all the gathered information into a test case.
-var makeTest = function(tx, rewind) {
- // Generate the genesis block from the block, transaction and prestate data
- var block = eth.getBlock(eth.getTransaction(tx).blockHash);
- var genesis = eth.getBlock(block.parentHash);
-
- delete genesis.gasUsed;
- delete genesis.logsBloom;
- delete genesis.parentHash;
- delete genesis.receiptsRoot;
- delete genesis.size;
- delete genesis.transactions;
- delete genesis.transactionsRoot;
-
- genesis.gasLimit = genesis.gasLimit.toString();
- genesis.number = genesis.number.toString();
- genesis.timestamp = genesis.timestamp.toString();
-
- genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer", rewind: rewind});
- for (var key in genesis.alloc) {
- genesis.alloc[key].nonce = genesis.alloc[key].nonce.toString();
- }
- genesis.config = admin.nodeInfo.protocols.eth.config;
-
- // Generate the call trace and produce the test input
- var result = debug.traceTransaction(tx, {tracer: "callTracer", rewind: rewind});
- delete result.time;
-
- console.log(JSON.stringify({
- genesis: genesis,
- context: {
- number: block.number.toString(),
- timestamp: block.timestamp.toString(),
- gasLimit: block.gasLimit.toString(),
- miner: block.miner,
- },
- input: eth.getRawTransaction(tx),
- result: result,
- }, null, 2));
-}
-*/
-
-// callTrace is the result of a callTracer run.
-type callTrace struct {
- Type string `json:"type"`
- From common.Address `json:"from"`
- To common.Address `json:"to"`
- Input hexutil.Bytes `json:"input"`
- Output hexutil.Bytes `json:"output"`
- Gas *hexutil.Uint64 `json:"gas,omitempty"`
- GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"`
- Value *hexutil.Big `json:"value,omitempty"`
- Error string `json:"error,omitempty"`
- Calls []callTrace `json:"calls,omitempty"`
-}
-
-type callContext struct {
- Number math.HexOrDecimal64 `json:"number"`
- Difficulty *math.HexOrDecimal256 `json:"difficulty"`
- Time math.HexOrDecimal64 `json:"timestamp"`
- GasLimit math.HexOrDecimal64 `json:"gasLimit"`
- Miner common.Address `json:"miner"`
-}
-
-// callTracerTest defines a single test to check the call tracer against.
-type callTracerTest struct {
- Genesis *core.Genesis `json:"genesis"`
- Context *callContext `json:"context"`
- Input string `json:"input"`
- Result *callTrace `json:"result"`
-}
-
-func TestPrestateTracerCreate2(t *testing.T) {
- celoMock := testutil.NewCeloMock()
-
- unsignedTx := types.NewTransaction(1, common.HexToAddress("0x00000000000000000000000000000000deadbeef"), new(big.Int), 5000000, big.NewInt(1), []byte{})
-
- privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader)
- if err != nil {
- t.Fatalf("err %v", err)
- }
- signer := types.NewEIP155Signer(big.NewInt(1))
- tx, err := types.SignTx(unsignedTx, signer, privateKeyECDSA)
- if err != nil {
- t.Fatalf("err %v", err)
- }
- /**
- This comes from one of the test-vectors on the Skinny Create2 - EIP
-
- address 0x00000000000000000000000000000000deadbeef
- salt 0x00000000000000000000000000000000000000000000000000000000cafebabe
- init_code 0xdeadbeef
- gas (assuming no mem expansion): 32006
- result: 0x60f3f640a8508fC6a86d45DF051962668E1e8AC7
- */
- origin, _ := signer.Sender(tx)
- txContext := vm.TxContext{
- Origin: origin,
- GasPrice: big.NewInt(1),
- }
- context := vm.BlockContext{
- CanTransfer: vmcontext.CanTransfer,
- Transfer: vmcontext.TobinTransfer,
- Coinbase: common.Address{},
- BlockNumber: new(big.Int).SetUint64(8000000),
- Time: new(big.Int).SetUint64(5),
- IsGoldTokenAddress: vmcontext.IsGoldTokenAddress,
- }
- alloc := core.GenesisAlloc{}
-
- // The code pushes 'deadbeef' into memory, then the other params, and calls CREATE2, then returns
- // the address
- alloc[common.HexToAddress("0x00000000000000000000000000000000deadbeef")] = core.GenesisAccount{
- Nonce: 1,
- Code: hexutil.MustDecode("0x63deadbeef60005263cafebabe6004601c6000F560005260206000F3"),
- Balance: big.NewInt(1),
- }
- alloc[origin] = core.GenesisAccount{
- Nonce: 1,
- Code: []byte{},
- Balance: big.NewInt(500000000000000),
- }
- _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
-
- // Create the tracer, the EVM environment and run it
- tracer, err := New("prestateTracer", new(Context))
- if err != nil {
- t.Fatalf("failed to create call tracer: %v", err)
- }
- vmConfig := vm.Config{Debug: true, Tracer: tracer}
- evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vmConfig)
-
- msg, err := tx.AsMessage(signer, nil)
- if err != nil {
- t.Fatalf("failed to prepare transaction for tracing: %v", err)
- }
-
- st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), celoMock.Runner, nil)
- if _, err = st.TransitionDb(); err != nil {
- t.Fatalf("failed to execute transaction: %v", err)
- }
- // Retrieve the trace result and compare against the etalon
- res, err := tracer.GetResult()
- if err != nil {
- t.Fatalf("failed to retrieve trace result: %v", err)
- }
- ret := make(map[string]interface{})
- if err := json.Unmarshal(res, &ret); err != nil {
- t.Fatalf("failed to unmarshal trace result: %v", err)
- }
- if _, has := ret["0x60f3f640a8508fc6a86d45df051962668e1e8ac7"]; !has {
- t.Fatalf("Expected 0x60f3f640a8508fc6a86d45df051962668e1e8ac7 in result")
- }
-}
-
-func TestPrestateTracerTransfer(t *testing.T) {
- celoMock := testutil.NewCeloMock()
-
- toAddr := "0x00000000000000000000000000000000deadbeef"
- unsignedTx := types.NewTransaction(1, common.HexToAddress(toAddr), new(big.Int), 5000000, big.NewInt(1), []byte{})
-
- privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader)
- if err != nil {
- t.Fatalf("err %v", err)
- }
- signer := types.NewEIP155Signer(big.NewInt(1))
- tx, err := types.SignTx(unsignedTx, signer, privateKeyECDSA)
- if err != nil {
- t.Fatalf("err %v", err)
- }
- origin, _ := signer.Sender(tx)
- txContext := vm.TxContext{
- Origin: origin,
- GasPrice: big.NewInt(1),
- }
- context := vm.BlockContext{
- CanTransfer: vmcontext.CanTransfer,
- Transfer: vmcontext.TobinTransfer,
- Coinbase: common.Address{},
- BlockNumber: new(big.Int).SetUint64(8000000),
- Time: new(big.Int).SetUint64(5),
- IsGoldTokenAddress: vmcontext.IsGoldTokenAddress,
- }
- alloc := core.GenesisAlloc{}
- alloc[origin] = core.GenesisAccount{
- Nonce: 1,
- Code: []byte{},
- Balance: big.NewInt(500000000000000),
- }
- _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
-
- // Create the tracer, the EVM environment and run it
- tracer, err := New("prestateTracer", new(Context))
- if err != nil {
- t.Fatalf("failed to create prestate tracer: %v", err)
- }
- vmConfig := vm.Config{Debug: true, Tracer: tracer}
- evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vmConfig)
-
- msg, err := tx.AsMessage(signer, nil)
- if err != nil {
- t.Fatalf("failed to prepare transaction for tracing: %v", err)
- }
-
- st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), celoMock.Runner, nil)
- if _, err = st.TransitionDb(); err != nil {
- t.Fatalf("failed to execute transaction: %v", err)
- }
- // Retrieve the trace result and compare against the etalon
- res, err := tracer.GetResult()
- if err != nil {
- t.Fatalf("failed to retrieve trace result: %v", err)
- }
- ret := make(map[string]interface{})
- if err := json.Unmarshal(res, &ret); err != nil {
- t.Fatalf("failed to unmarshal trace result: %v", err)
- }
- if _, has := ret[toAddr]; !has {
- t.Fatalf("Expected %s in result", toAddr)
- }
-}
-
-// Iterates over all the input-output datasets in the tracer test harness and
-// runs the JavaScript tracers against them.
-func TestCallTracerLegacy(t *testing.T) {
- testCallTracer("callTracerLegacy", "call_tracer_legacy", t)
-}
-
-func testCallTracer(tracer string, dirPath string, t *testing.T) {
- celoMock := testutil.NewCeloMock()
- files, err := ioutil.ReadDir(filepath.Join("testdata", dirPath))
- if err != nil {
- t.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
- t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
- t.Parallel()
-
- // Call tracer test found, read if from disk
- blob, err := ioutil.ReadFile(filepath.Join("testdata", dirPath, file.Name()))
- if err != nil {
- t.Fatalf("failed to read testcase: %v", err)
- }
- test := new(callTracerTest)
- if err := json.Unmarshal(blob, test); err != nil {
- t.Fatalf("failed to parse testcase: %v", err)
- }
- // Configure a blockchain with the given prestate
- tx := new(types.Transaction)
- if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
- t.Fatalf("failed to parse testcase input: %v", err)
- }
- signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
- origin, _ := signer.Sender(tx)
- txContext := vm.TxContext{
- Origin: origin,
- GasPrice: tx.GasPrice(),
- }
- context := vm.BlockContext{
- CanTransfer: vmcontext.CanTransfer,
- Transfer: vmcontext.TobinTransfer,
- Coinbase: test.Context.Miner,
- BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
- Time: new(big.Int).SetUint64(uint64(test.Context.Time)),
- }
- _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
-
- // Create the tracer, the EVM environment and run it
- tracer, err := New(tracer, new(Context))
- if err != nil {
- t.Fatalf("failed to create call tracer: %v", err)
- }
- evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
-
- msg, err := tx.AsMessage(signer, nil)
- if err != nil {
- t.Fatalf("failed to prepare transaction for tracing: %v", err)
- }
- st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), celoMock.Runner, nil)
- if _, err = st.TransitionDb(); err != nil {
- t.Fatalf("failed to execute transaction: %v", err)
- }
- // Retrieve the trace result and compare against the etalon
- res, err := tracer.GetResult()
- if err != nil {
- t.Fatalf("failed to retrieve trace result: %v", err)
- }
- ret := new(callTrace)
- if err := json.Unmarshal(res, ret); err != nil {
- t.Fatalf("failed to unmarshal trace result: %v", err)
- }
-
- if !jsonEqual(ret, test.Result) {
- // uncomment this for easier debugging
- //have, _ := json.MarshalIndent(ret, "", " ")
- //want, _ := json.MarshalIndent(test.Result, "", " ")
- //t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
- t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result)
- }
- })
- }
-}
-
-func TestCallTracer(t *testing.T) {
- testCallTracer("callTracer", "call_tracer", t)
-}
-
-// jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
-// comparison
-func jsonEqual(x, y interface{}) bool {
- xTrace := new(callTrace)
- yTrace := new(callTrace)
- if xj, err := json.Marshal(x); err == nil {
- json.Unmarshal(xj, xTrace)
- } else {
- return false
- }
- if yj, err := json.Marshal(y); err == nil {
- json.Unmarshal(yj, yTrace)
- } else {
- return false
- }
- return reflect.DeepEqual(xTrace, yTrace)
-}
-
func BenchmarkTransactionTrace(b *testing.B) {
celoMock := testutil.NewCeloMock()
key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
@@ -453,69 +108,3 @@ func BenchmarkTransactionTrace(b *testing.B) {
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{
- Coinbase: test.Context.Miner,
- BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
- Time: new(big.Int).SetUint64(uint64(test.Context.Time)),
- }
- _, 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()), nil, nil)
- if _, err = st.TransitionDb(); err != nil {
- b.Fatalf("failed to execute transaction: %v", err)
- }
- statedb.RevertToSnapshot(snap)
- }
-}
diff --git a/les/api_backend.go b/les/api_backend.go
index a80bd72af9..967334d5ea 100644
--- a/les/api_backend.go
+++ b/les/api_backend.go
@@ -409,7 +409,7 @@ func (b *LesApiBackend) CurrentHeader() *types.Header {
return b.eth.blockchain.CurrentHeader()
}
-func (b *LesApiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) {
+func (b *LesApiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (*state.StateDB, error) {
return b.eth.stateAtBlock(ctx, block, reexec)
}