|
|
|
@ -17,7 +17,6 @@ |
|
|
|
|
package tracers |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"bytes" |
|
|
|
|
"encoding/json" |
|
|
|
|
"errors" |
|
|
|
|
"math/big" |
|
|
|
@ -50,94 +49,77 @@ type dummyStatedb struct { |
|
|
|
|
|
|
|
|
|
func (*dummyStatedb) GetRefund() uint64 { return 1337 } |
|
|
|
|
|
|
|
|
|
func runTrace(tracer *Tracer) (json.RawMessage, error) { |
|
|
|
|
env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) |
|
|
|
|
|
|
|
|
|
contract := vm.NewContract(account{}, account{}, big.NewInt(0), 10000) |
|
|
|
|
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} |
|
|
|
|
|
|
|
|
|
_, err := env.Interpreter().Run(contract, []byte{}, false) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
return tracer.GetResult() |
|
|
|
|
type vmContext struct { |
|
|
|
|
blockCtx vm.BlockContext |
|
|
|
|
txCtx vm.TxContext |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory access
|
|
|
|
|
func TestRegressionPanicSlice(t *testing.T) { |
|
|
|
|
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}") |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
if _, err = runTrace(tracer); err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TestRegressionPanicSlice tests that we don't panic on bad arguments to stack peeks
|
|
|
|
|
func TestRegressionPanicPeek(t *testing.T) { |
|
|
|
|
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}") |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
if _, err = runTrace(tracer); err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory getUint
|
|
|
|
|
func TestRegressionPanicGetUint(t *testing.T) { |
|
|
|
|
tracer, err := New("{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}") |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
if _, err = runTrace(tracer); err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
func testCtx() *vmContext { |
|
|
|
|
return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestTracing(t *testing.T) { |
|
|
|
|
tracer, err := New("{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}") |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ret, err := runTrace(tracer) |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
if !bytes.Equal(ret, []byte("3")) { |
|
|
|
|
t.Errorf("Expected return value to be 3, got %s", string(ret)) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestStack(t *testing.T) { |
|
|
|
|
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}") |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) { |
|
|
|
|
env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) |
|
|
|
|
var ( |
|
|
|
|
startGas uint64 = 10000 |
|
|
|
|
value = big.NewInt(0) |
|
|
|
|
) |
|
|
|
|
contract := vm.NewContract(account{}, account{}, value, startGas) |
|
|
|
|
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} |
|
|
|
|
|
|
|
|
|
ret, err := runTrace(tracer) |
|
|
|
|
tracer.CaptureStart(contract.Caller(), contract.Address(), false, []byte{}, startGas, value) |
|
|
|
|
ret, err := env.Interpreter().Run(contract, []byte{}, false) |
|
|
|
|
tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err) |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
if !bytes.Equal(ret, []byte("[0,1,2]")) { |
|
|
|
|
t.Errorf("Expected return value to be [0,1,2], got %s", string(ret)) |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
return tracer.GetResult() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestOpcodes(t *testing.T) { |
|
|
|
|
tracer, err := New("{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}") |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ret, err := runTrace(tracer) |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
if !bytes.Equal(ret, []byte("[\"PUSH1\",\"PUSH1\",\"STOP\"]")) { |
|
|
|
|
t.Errorf("Expected return value to be [\"PUSH1\",\"PUSH1\",\"STOP\"], got %s", string(ret)) |
|
|
|
|
func TestTracer(t *testing.T) { |
|
|
|
|
execTracer := func(code string) []byte { |
|
|
|
|
t.Helper() |
|
|
|
|
ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} |
|
|
|
|
tracer, err := New(code, ctx.txCtx) |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
ret, err := runTrace(tracer, ctx) |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
return ret |
|
|
|
|
} |
|
|
|
|
for i, tt := range []struct { |
|
|
|
|
code string |
|
|
|
|
want string |
|
|
|
|
}{ |
|
|
|
|
{ // tests that we don't panic on bad arguments to memory access
|
|
|
|
|
code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}", |
|
|
|
|
want: `[{},{},{}]`, |
|
|
|
|
}, { // tests that we don't panic on bad arguments to stack peeks
|
|
|
|
|
code: "{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}", |
|
|
|
|
want: `["0","0","0"]`, |
|
|
|
|
}, { // tests that we don't panic on bad arguments to memory getUint
|
|
|
|
|
code: "{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}", |
|
|
|
|
want: `["0","0","0"]`, |
|
|
|
|
}, { // tests some general counting
|
|
|
|
|
code: "{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}", |
|
|
|
|
want: `3`, |
|
|
|
|
}, { // tests that depth is reported correctly
|
|
|
|
|
code: "{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}", |
|
|
|
|
want: `[0,1,2]`, |
|
|
|
|
}, { // tests to-string of opcodes
|
|
|
|
|
code: "{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}", |
|
|
|
|
want: `["PUSH1","PUSH1","STOP"]`, |
|
|
|
|
}, { // tests intrinsic gas
|
|
|
|
|
code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx) { return ctx.gasPrice+'.'+ctx.gasUsed+'.'+ctx.intrinsicGas; }}", |
|
|
|
|
want: `"100000.6.21000"`, |
|
|
|
|
}, |
|
|
|
|
} { |
|
|
|
|
if have := execTracer(tt.code); tt.want != string(have) { |
|
|
|
|
t.Errorf("testcase %d: expected return value to be %s got %s\n\tcode: %v", i, tt.want, string(have), tt.code) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -145,7 +127,8 @@ 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; }}") |
|
|
|
|
vmctx := testCtx() |
|
|
|
|
tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}", vmctx.txCtx) |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
@ -155,17 +138,17 @@ func TestHalt(t *testing.T) { |
|
|
|
|
tracer.Stop(timeout) |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
if _, err = runTrace(tracer); err.Error() != "stahp in server-side tracer function 'step'" { |
|
|
|
|
if _, err = runTrace(tracer, vmctx); err.Error() != "stahp in server-side tracer function 'step'" { |
|
|
|
|
t.Errorf("Expected timeout error, got %v", err) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestHaltBetweenSteps(t *testing.T) { |
|
|
|
|
tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}") |
|
|
|
|
vmctx := testCtx() |
|
|
|
|
tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", vmctx.txCtx) |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) |
|
|
|
|
contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0) |
|
|
|
|
|
|
|
|
|