Skip to content

Commit 7aa085e

Browse files
committed
eth/tracers/internal: tests for log memexpansion and stack depletion
eth/tracers: refactor mem-copy and add test eth/tracers/internal: add test producing OOM on prestate-tracer eth/tracers: fix OOM on prestate-tracer eth/tracers/js: make memory.slice use padding eth/tracers/js: rm unused 'min'-method
1 parent 2d09e18 commit 7aa085e

File tree

6 files changed

+155
-156
lines changed

6 files changed

+155
-156
lines changed

eth/tracers/internal/tracetest/calltrace_test.go

Lines changed: 97 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import (
3131
"github.com/ethereum/go-ethereum/core/rawdb"
3232
"github.com/ethereum/go-ethereum/core/types"
3333
"github.com/ethereum/go-ethereum/core/vm"
34-
"github.com/ethereum/go-ethereum/crypto"
3534
"github.com/ethereum/go-ethereum/eth/tracers"
3635
"github.com/ethereum/go-ethereum/params"
3736
"github.com/ethereum/go-ethereum/rlp"
@@ -260,80 +259,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
260259
}
261260
}
262261

263-
// TestZeroValueToNotExitCall tests the calltracer(s) on the following:
264-
// Tx to A, A calls B with zero value. B does not already exist.
265-
// Expected: that enter/exit is invoked and the inner call is shown in the result
266-
func TestZeroValueToNotExitCall(t *testing.T) {
267-
var to = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
268-
privkey, err := crypto.HexToECDSA("0000000000000000deadbeef00000000000000000000000000000000deadbeef")
269-
if err != nil {
270-
t.Fatalf("err %v", err)
271-
}
272-
signer := types.NewEIP155Signer(big.NewInt(1))
273-
tx, err := types.SignNewTx(privkey, signer, &types.LegacyTx{
274-
GasPrice: big.NewInt(0),
275-
Gas: 50000,
276-
To: &to,
277-
})
278-
if err != nil {
279-
t.Fatalf("err %v", err)
280-
}
281-
origin, _ := signer.Sender(tx)
282-
txContext := vm.TxContext{
283-
Origin: origin,
284-
GasPrice: big.NewInt(1),
285-
}
286-
context := vm.BlockContext{
287-
CanTransfer: core.CanTransfer,
288-
Transfer: core.Transfer,
289-
Coinbase: common.Address{},
290-
BlockNumber: new(big.Int).SetUint64(8000000),
291-
Time: 5,
292-
Difficulty: big.NewInt(0x30000),
293-
GasLimit: uint64(6000000),
294-
}
295-
var code = []byte{
296-
byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero
297-
byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS
298-
byte(vm.CALL),
299-
}
300-
var alloc = core.GenesisAlloc{
301-
to: core.GenesisAccount{
302-
Nonce: 1,
303-
Code: code,
304-
},
305-
origin: core.GenesisAccount{
306-
Nonce: 0,
307-
Balance: big.NewInt(500000000000000),
308-
},
309-
}
310-
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
311-
// Create the tracer, the EVM environment and run it
312-
tracer, err := tracers.DefaultDirectory.New("callTracer", nil, nil)
313-
if err != nil {
314-
t.Fatalf("failed to create call tracer: %v", err)
315-
}
316-
evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer})
317-
msg, err := core.TransactionToMessage(tx, signer, nil)
318-
if err != nil {
319-
t.Fatalf("failed to prepare transaction for tracing: %v", err)
320-
}
321-
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
322-
if _, err = st.TransitionDb(); err != nil {
323-
t.Fatalf("failed to execute transaction: %v", err)
324-
}
325-
// Retrieve the trace result and compare against the etalon
326-
res, err := tracer.GetResult()
327-
if err != nil {
328-
t.Fatalf("failed to retrieve trace result: %v", err)
329-
}
330-
wantStr := `{"from":"0x682a80a6f560eec50d54e63cbeda1c324c5f8d1b","gas":"0x7148","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0x6cbf","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`
331-
if string(res) != wantStr {
332-
t.Fatalf("trace mismatch\n have: %v\n want: %v\n", string(res), wantStr)
333-
}
334-
}
335-
336-
func TestMemExpansion(t *testing.T) {
262+
func TestInternals(t *testing.T) {
337263
var (
338264
to = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
339265
origin = common.HexToAddress("0x00000000000000000000000000000000feed")
@@ -350,53 +276,104 @@ func TestMemExpansion(t *testing.T) {
350276
Difficulty: big.NewInt(0x30000),
351277
GasLimit: uint64(6000000),
352278
}
353-
alloc = core.GenesisAlloc{
354-
to: core.GenesisAccount{
355-
Code: []byte{
356-
byte(vm.PUSH1), 0x1,
357-
byte(vm.PUSH1), 0x0,
358-
byte(vm.MSTORE),
359-
byte(vm.PUSH1), 0xff,
360-
byte(vm.PUSH1), 0x0,
361-
byte(vm.LOG0),
362-
},
363-
},
364-
origin: core.GenesisAccount{
365-
Nonce: 0,
366-
Balance: big.NewInt(500000000000000),
367-
},
368-
}
369279
)
370-
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
371-
// Create the tracer, the EVM environment and run it
372-
tracer, err := tracers.DefaultDirectory.New("callTracer", nil, json.RawMessage(`{ "withLog": true }`))
373-
if err != nil {
374-
t.Fatalf("failed to create call tracer: %v", err)
280+
mkTracer := func(name string, cfg json.RawMessage) tracers.Tracer {
281+
tr, err := tracers.DefaultDirectory.New(name, nil, cfg)
282+
if err != nil {
283+
t.Fatalf("failed to create call tracer: %v", err)
284+
}
285+
return tr
375286
}
376-
evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer})
377-
378-
var nonce uint64 = 0
379-
var gasLimit uint64 = 50000
380-
value := big.NewInt(0)
381-
gasPrice := big.NewInt(1)
382-
gasFeeCap := big.NewInt(0)
383-
gasTipCap := big.NewInt(0)
384-
data := make([]byte, 0)
385-
accessList := types.AccessList{}
386-
isFake := false
387287

388-
msg := types.NewMessage(origin, &to, nonce, value, gasLimit, gasPrice, gasFeeCap, gasTipCap, data, accessList, isFake)
389-
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(gasLimit))
390-
if _, err = st.TransitionDb(); err != nil {
391-
t.Fatalf("failed to execute transaction: %v", err)
392-
}
393-
// Retrieve the trace result and compare against the etalon
394-
res, err := tracer.GetResult()
395-
if err != nil {
396-
t.Fatalf("failed to retrieve trace result: %v", err)
397-
}
398-
wantStr := `TODO fix this`
399-
if string(res) != wantStr {
400-
t.Fatalf("trace mismatch\n have: %v\n want: %v\n", string(res), wantStr)
288+
for _, tc := range []struct {
289+
name string
290+
code []byte
291+
tracer tracers.Tracer
292+
want string
293+
}{
294+
{
295+
// TestZeroValueToNotExitCall tests the calltracer(s) on the following:
296+
// Tx to A, A calls B with zero value. B does not already exist.
297+
// Expected: that enter/exit is invoked and the inner call is shown in the result
298+
name: "ZeroValueToNotExitCall",
299+
code: []byte{
300+
byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero
301+
byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS
302+
byte(vm.CALL),
303+
},
304+
tracer: mkTracer("callTracer", nil),
305+
want: `{"from":"0x000000000000000000000000000000000000feed","gas":"0x7148","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0x6cbf","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`,
306+
},
307+
{
308+
name: "Stack depletion in LOG0",
309+
code: []byte{byte(vm.LOG3)},
310+
tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)),
311+
want: `{"from":"0x000000000000000000000000000000000000feed","gas":"0x7148","gasUsed":"0xc350","to":"0x00000000000000000000000000000000deadbeef","input":"0x","error":"stack underflow (0 \u003c=\u003e 5)","value":"0x0","type":"CALL"}`,
312+
},
313+
{
314+
name: "Mem expansion in LOG0",
315+
code: []byte{
316+
byte(vm.PUSH1), 0x1,
317+
byte(vm.PUSH1), 0x0,
318+
byte(vm.MSTORE),
319+
byte(vm.PUSH1), 0xff,
320+
byte(vm.PUSH1), 0x0,
321+
byte(vm.LOG0),
322+
},
323+
tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)),
324+
want: `{"from":"0x000000000000000000000000000000000000feed","gas":"0x7148","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}],"value":"0x0","type":"CALL"}`,
325+
},
326+
{
327+
// Leads to OOM on the prestate tracer
328+
name: "Prestate-tracer - mem expansion in CREATE2",
329+
code: []byte{
330+
byte(vm.PUSH1), 0x1,
331+
byte(vm.PUSH1), 0x0,
332+
byte(vm.MSTORE),
333+
byte(vm.PUSH1), 0x1,
334+
byte(vm.PUSH5), 0xff, 0xff, 0xff, 0xff, 0xff,
335+
byte(vm.PUSH1), 0x1,
336+
byte(vm.PUSH1), 0x0,
337+
byte(vm.CREATE2),
338+
byte(vm.PUSH1), 0xff,
339+
byte(vm.PUSH1), 0x0,
340+
byte(vm.LOG0),
341+
},
342+
tracer: mkTracer("prestateTracer", json.RawMessage(`{ "withLog": true }`)),
343+
want: `{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x000000000000000000000000000000000000feed":{"balance":"0x1c6bf52640350"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600164ffffffffff60016000f560ff6000a0"}}`,
344+
},
345+
} {
346+
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(),
347+
core.GenesisAlloc{
348+
to: core.GenesisAccount{
349+
Code: tc.code,
350+
},
351+
origin: core.GenesisAccount{
352+
Balance: big.NewInt(500000000000000),
353+
},
354+
}, false)
355+
evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tc.tracer})
356+
msg := &core.Message{
357+
To: &to,
358+
From: origin,
359+
Value: big.NewInt(0),
360+
GasLimit: 50000,
361+
GasPrice: big.NewInt(0),
362+
GasFeeCap: big.NewInt(0),
363+
GasTipCap: big.NewInt(0),
364+
SkipAccountChecks: false,
365+
}
366+
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(msg.GasLimit))
367+
if _, err := st.TransitionDb(); err != nil {
368+
t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err)
369+
}
370+
// Retrieve the trace result and compare against the expected
371+
res, err := tc.tracer.GetResult()
372+
if err != nil {
373+
t.Fatalf("test %v: failed to retrieve trace result: %v", tc.name, err)
374+
}
375+
if string(res) != tc.want {
376+
t.Fatalf("test %v: trace mismatch\n have: %v\n want: %v\n", tc.name, string(res), tc.want)
377+
}
401378
}
402379
}

eth/tracers/js/goja.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope
254254
if !t.traceStep {
255255
return
256256
}
257-
if t.err != nil || err != nil {
257+
if t.err != nil {
258258
return
259259
}
260260

@@ -567,9 +567,6 @@ func (mo *memoryObj) slice(begin, end int64) ([]byte, error) {
567567
if end < begin || begin < 0 {
568568
return nil, fmt.Errorf("tracer accessed out of bound memory: offset %d, end %d", begin, end)
569569
}
570-
mlen := mo.memory.Len()
571-
slice := make([]byte, end-begin)
572-
end = min(end, int64(mlen))
573570
slice, err := tracers.GetMemoryCopyPadded(mo.memory, begin, end-begin)
574571
if err != nil {
575572
return nil, err
@@ -954,10 +951,3 @@ func (l *steplog) setupObject() *goja.Object {
954951
o.Set("contract", l.contract.setupObject())
955952
return o
956953
}
957-
958-
func min(a, b int64) int64 {
959-
if a < b {
960-
return a
961-
}
962-
return b
963-
}

eth/tracers/js/tracer_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,12 @@ func TestTracer(t *testing.T) {
150150
}, {
151151
code: "{res: [], step: function(log) { if (log.op.toString() === 'STOP') { this.res.push(log.memory.slice(5, 1025 * 1024)) } }, fault: function() {}, result: function() { return this.res }}",
152152
want: "",
153-
fail: "tracer reached limit for padding memory slice: end 1049600, memorySize 32 at step (<eval>:1:83(20)) in server-side tracer function 'step'",
153+
fail: "reached limit for padding memory slice: 1049568 at step (<eval>:1:83(20)) in server-side tracer function 'step'",
154154
contract: []byte{byte(vm.PUSH1), byte(0xff), byte(vm.PUSH1), byte(0x00), byte(vm.MSTORE8), byte(vm.STOP)},
155155
},
156156
} {
157157
if have, err := execTracer(tt.code, tt.contract); tt.want != string(have) || tt.fail != err {
158-
t.Errorf("testcase %d: expected return value to be '%s' got '%s', error to be '%s' got '%s'\n\tcode: %v", i, tt.want, string(have), tt.fail, err, tt.code)
158+
t.Errorf("testcase %d: expected return value to be \n'%s'\n\tgot\n'%s'\nerror to be\n'%s'\n\tgot\n'%s'\n\tcode: %v", i, tt.want, string(have), tt.fail, err, tt.code)
159159
}
160160
}
161161
}

eth/tracers/native/prestate.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ func (t *prestateTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
133133

134134
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
135135
func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
136+
if err != nil {
137+
return
138+
}
136139
stack := scope.Stack
137140
stackData := stack.Data()
138141
stackLen := len(stackData)

eth/tracers/tracers.go

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -104,28 +104,19 @@ const (
104104
// GetMemoryCopyPadded returns offset + size as a new slice.
105105
// It zero-pads the slice if it extends beyond memory bounds.
106106
func GetMemoryCopyPadded(m *vm.Memory, offset, size int64) ([]byte, error) {
107-
if offset < 0 {
108-
return nil, fmt.Errorf("offset cannot be smaller than zero")
107+
if offset < 0 || size < 0 {
108+
return nil, fmt.Errorf("offset or size must not be negative")
109109
}
110-
111-
if int(offset) >= m.Len() {
112-
// case 1: the slice falls entirely outside memory bounds
113-
if size > memoryPadLimit {
114-
return nil, fmt.Errorf("reached limit for padding memory slice: offset %d, size %d", offset, size)
115-
}
116-
padded := make([]byte, size)
117-
return padded, nil
118-
} else if int(offset+size) >= m.Len() {
119-
// case 2: the slice overlaps memory bounds
120-
if int(offset+size)-m.Len() > memoryPadLimit {
121-
return nil, fmt.Errorf("reached limit for padding memory slice: offset %d, size %d", offset, size)
122-
}
123-
padded := make([]byte, size)
124-
memSlice := m.GetCopy(offset, int64(m.Len()) - offset)
125-
copy(padded[0:len(memSlice)], memSlice)
126-
return padded, nil
127-
}
128-
// case 3: the slice falls inside memory bounds
129-
memSlice := m.GetCopy(offset, size)
130-
return memSlice, nil
110+
if int(offset+size) < m.Len() { // slice fully inside memory
111+
return m.GetCopy(offset, size), nil
112+
}
113+
paddingNeeded := int(offset+size) - m.Len()
114+
if paddingNeeded > memoryPadLimit {
115+
return nil, fmt.Errorf("reached limit for padding memory slice: %d", paddingNeeded)
116+
}
117+
cpy := make([]byte, size)
118+
if overlap := int64(m.Len()) - offset; overlap > 0 {
119+
copy(cpy, m.GetPtr(offset, overlap))
120+
}
121+
return cpy, nil
131122
}

eth/tracers/tracers_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,41 @@ func BenchmarkTransactionTrace(b *testing.B) {
109109
tracer.Reset()
110110
}
111111
}
112+
113+
func TestMemCopying(t *testing.T) {
114+
for i, tc := range []struct {
115+
memsize int64
116+
offset int64
117+
size int64
118+
wantErr string
119+
wantSize int
120+
}{
121+
{0, 0, 100, "", 100}, // Should pad up to 100
122+
{0, 100, 0, "", 0}, // No need to pad (0 size)
123+
{100, 50, 100, "", 100}, // Should pad 100-150
124+
{100, 50, 5, "", 5}, // Wanted range fully within memory
125+
{100, -50, 0, "offset or size must not be negative", 0}, // Errror
126+
{0, 1, 1024*1024 + 1, "reached limit for padding memory slice: 1048578", 0}, // Errror
127+
{10, 0, 1024*1024 + 100, "reached limit for padding memory slice: 1048666", 0}, // Errror
128+
129+
} {
130+
mem := vm.NewMemory()
131+
mem.Resize(uint64(tc.memsize))
132+
cpy, err := GetMemoryCopyPadded(mem, tc.offset, tc.size)
133+
if want := tc.wantErr; want != "" {
134+
if err == nil {
135+
t.Fatalf("test %d: want '%v' have no error", i, want)
136+
}
137+
if have := err.Error(); want != have {
138+
t.Fatalf("test %d: want '%v' have '%v'", i, want, have)
139+
}
140+
continue
141+
}
142+
if err != nil {
143+
t.Fatalf("test %d: unexpected error: %v", i, err)
144+
}
145+
if want, have := tc.wantSize, len(cpy); have != want {
146+
t.Fatalf("test %d: want %v have %v", i, want, have)
147+
}
148+
}
149+
}

0 commit comments

Comments
 (0)