Skip to content

Commit 0d6ccb4

Browse files
committed
core, eth/tracers: seperate dynamic gas reporting
this is in preperation of merging memoryFunc/dynamicGasFunc into executionFuncs
1 parent 355228b commit 0d6ccb4

File tree

10 files changed

+256
-54
lines changed

10 files changed

+256
-54
lines changed

cmd/evm/internal/t8ntool/file_tracer.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,5 +148,10 @@ func (l *fileWritingTracer) hooks() *tracing.Hooks {
148148
l.inner.OnSystemCallEnd()
149149
}
150150
},
151+
OnGasChange: func(old, new uint64, reason tracing.GasChangeReason) {
152+
if l.inner.OnGasChange != nil {
153+
l.inner.OnGasChange(old, new, reason)
154+
}
155+
},
151156
}
152157
}

cmd/evm/testdata/32/trace-0-0x47806361c0fa084be3caa18afe8c48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@
4444
{"pc":38,"op":82,"gas":"0x65ee4","gasCost":"0x6","memSize":0,"stack":["0x10","0x0"],"depth":2,"refund":0,"opName":"MSTORE"}
4545
{"pc":39,"op":96,"gas":"0x65ede","gasCost":"0x3","memSize":32,"stack":[],"depth":2,"refund":0,"opName":"PUSH1"}
4646
{"pc":41,"op":96,"gas":"0x65edb","gasCost":"0x3","memSize":32,"stack":["0x20"],"depth":2,"refund":0,"opName":"PUSH1"}
47-
{"pc":43,"op":253,"gas":"0x65ed8","gasCost":"0x0","memSize":32,"stack":["0x20","0x0"],"depth":2,"refund":0,"opName":"REVERT"}
48-
{"pc":43,"op":253,"gas":"0x65ed8","gasCost":"0x0","memSize":32,"stack":[],"depth":2,"refund":0,"opName":"REVERT","error":"execution reverted"}
47+
{"pc":43,"op":253,"gas":"0x65ed8","gasCost":"0x0","memSize":32,"stack":["0x20","0x0"],"depth":2,"refund":0,"opName":"REVERT","error":"execution reverted"}
4948
{"output":"0000000000000000000000000000000000000000000000000000000000000010","gasUsed":"0x2e44","error":"execution reverted"}
5049
{"pc":69,"op":96,"gas":"0x67976","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
5150
{"pc":71,"op":85,"gas":"0x67973","gasCost":"0x1388","memSize":0,"stack":["0x0","0x2"],"depth":1,"refund":4800,"opName":"SSTORE"}

core/tracing/gen_gas_change_reason_stringer.go

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/tracing/hooks.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,9 @@ const (
339339
// GasChangeTxDataFloor is the amount of extra gas the transaction has to pay to reach the minimum gas requirement for the
340340
// transaction data. This change will always be a negative change.
341341
GasChangeTxDataFloor GasChangeReason = 19
342+
// GasChangeCallOpCodeDynamic is the amount of dynamic gas that will be charged for an opcode executed by the EVM.
343+
// It will be emitted after the `OnOpcode` callback. So the cost should be attributed to the last instance of `OnOpcode`.
344+
GasChangeCallOpCodeDynamic GasChangeReason = 20
342345

343346
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
344347
// it will be "manually" tracked by a direct emit of the gas change event.

core/vm/interpreter.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,8 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
195195
// For optimisation reason we're using uint64 as the program counter.
196196
// It's theoretically possible to go above 2^64. The YP defines the PC
197197
// to be uint256. Practically much less so feasible.
198-
pc = uint64(0) // program counter
199-
cost uint64
198+
pc = uint64(0) // program counter
199+
cost, dynamicCost uint64
200200
// copies used by tracer
201201
pcCopy uint64 // needed for the deferred EVMLogger
202202
gasCopy uint64 // for EVMLogger to log gas remaining before execution
@@ -234,7 +234,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
234234
for {
235235
if debug {
236236
// Capture pre-execution values for tracing.
237-
logged, pcCopy, gasCopy = false, pc, contract.Gas
237+
logged, pcCopy, gasCopy, dynamicCost = false, pc, contract.Gas, 0
238238
}
239239

240240
if in.evm.chainRules.IsEIP4762 && !contract.IsDeployment && !contract.IsSystemCall {
@@ -286,7 +286,6 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
286286
}
287287
// Consume the gas and return an error if not enough gas is available.
288288
// cost is explicitly set so that the capture state defer method can get the proper cost
289-
var dynamicCost uint64
290289
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
291290
cost += dynamicCost // for tracing
292291
if err != nil {
@@ -303,10 +302,15 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
303302
// Do tracing before potential memory expansion
304303
if debug {
305304
if in.evm.Config.Tracer.OnGasChange != nil {
306-
in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode)
305+
// Trace the constant cost only
306+
in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-operation.constantGas, tracing.GasChangeCallOpCode)
307307
}
308308
if in.evm.Config.Tracer.OnOpcode != nil {
309-
in.evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
309+
in.evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, operation.constantGas, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
310+
// If any, trace the dynamic cost as well
311+
if in.evm.Config.Tracer.OnGasChange != nil && dynamicCost > 0 {
312+
in.evm.Config.Tracer.OnGasChange(gasCopy-operation.constantGas, gasCopy-cost, tracing.GasChangeCallOpCodeDynamic)
313+
}
310314
logged = true
311315
}
312316
}

core/vm/runtime/runtime_test.go

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,11 @@ func TestColdAccountAccessCost(t *testing.T) {
667667
Execute(tc.code, nil, &Config{
668668
EVMConfig: vm.Config{
669669
Tracer: &tracing.Hooks{
670+
OnGasChange: func(old, new uint64, reason tracing.GasChangeReason) {
671+
if step-1 == tc.step && reason == tracing.GasChangeCallOpCodeDynamic {
672+
have += old - new
673+
}
674+
},
670675
OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
671676
// Uncomment to investigate failures:
672677
//t.Logf("%d: %v %d", step, vm.OpCode(op).String(), cost)
@@ -700,10 +705,15 @@ func TestRuntimeJSTracer(t *testing.T) {
700705
this.exits++;
701706
this.gasUsed = res.getGasUsed();
702707
}}`,
703-
`{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, steps:0,
708+
`{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, steps:0, dynamicGas:0,
704709
fault: function() {},
705710
result: function() {
706-
return [this.enters, this.exits,this.enterGas,this.gasUsed, this.steps].join(",")
711+
return [this.enters, this.exits,this.enterGas,this.gasUsed, this.steps, this.dynamicGas].join(",")
712+
},
713+
gas: function(change) {
714+
if (change.getReason() == "CallOpCodeDynamic") {
715+
this.dynamicGas += change.getOld() - change.getNew();
716+
}
707717
},
708718
enter: function(frame) {
709719
this.enters++;
@@ -726,7 +736,7 @@ func TestRuntimeJSTracer(t *testing.T) {
726736
Push(0). // value
727737
Op(vm.CREATE).
728738
Op(vm.POP).Bytes(),
729-
results: []string{`"1,1,952853,6,12"`, `"1,1,952853,6,0"`},
739+
results: []string{`"1,1,952853,6,12"`, `"1,1,952853,6,0,5"`},
730740
},
731741
{ // CREATE2
732742
code: program.New().MstoreSmall(initcode, 0).
@@ -736,27 +746,27 @@ func TestRuntimeJSTracer(t *testing.T) {
736746
Push(0). // value
737747
Op(vm.CREATE2).
738748
Op(vm.POP).Bytes(),
739-
results: []string{`"1,1,952844,6,13"`, `"1,1,952844,6,0"`},
749+
results: []string{`"1,1,952844,6,13"`, `"1,1,952844,6,0,11"`},
740750
},
741751
{ // CALL
742752
code: program.New().Call(nil, 0xbb, 0, 0, 0, 0, 0).Op(vm.POP).Bytes(),
743-
results: []string{`"1,1,981796,6,13"`, `"1,1,981796,6,0"`},
753+
results: []string{`"1,1,981796,6,13"`, `"1,1,981796,6,0,984296"`},
744754
},
745755
{ // CALLCODE
746756
code: program.New().CallCode(nil, 0xcc, 0, 0, 0, 0, 0).Op(vm.POP).Bytes(),
747-
results: []string{`"1,1,981796,6,13"`, `"1,1,981796,6,0"`},
757+
results: []string{`"1,1,981796,6,13"`, `"1,1,981796,6,0,984296"`},
748758
},
749759
{ // STATICCALL
750760
code: program.New().StaticCall(nil, 0xdd, 0, 0, 0, 0).Op(vm.POP).Bytes(),
751-
results: []string{`"1,1,981799,6,12"`, `"1,1,981799,6,0"`},
761+
results: []string{`"1,1,981799,6,12"`, `"1,1,981799,6,0,984299"`},
752762
},
753763
{ // DELEGATECALL
754764
code: program.New().DelegateCall(nil, 0xee, 0, 0, 0, 0).Op(vm.POP).Bytes(),
755-
results: []string{`"1,1,981799,6,12"`, `"1,1,981799,6,0"`},
765+
results: []string{`"1,1,981799,6,12"`, `"1,1,981799,6,0,984299"`},
756766
},
757767
{ // CALL self-destructing contract
758768
code: program.New().Call(nil, 0xff, 0, 0, 0, 0, 0).Op(vm.POP).Bytes(),
759-
results: []string{`"2,2,0,5003,12"`, `"2,2,0,5003,0"`},
769+
results: []string{`"2,2,0,5003,12"`, `"2,2,0,5003,0,984296"`},
760770
},
761771
}
762772
calleeCode := []byte{
@@ -921,6 +931,11 @@ func TestDelegatedAccountAccessCost(t *testing.T) {
921931
State: statedb,
922932
EVMConfig: vm.Config{
923933
Tracer: &tracing.Hooks{
934+
OnGasChange: func(old, new uint64, reason tracing.GasChangeReason) {
935+
if step-1 == tc.step && reason == tracing.GasChangeCallOpCodeDynamic {
936+
have += old - new
937+
}
938+
},
924939
OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
925940
// Uncomment to investigate failures:
926941
t.Logf("%d: %v %d", step, vm.OpCode(op).String(), cost)

eth/tracers/js/goja.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,17 +129,20 @@ type jsTracer struct {
129129
step goja.Callable
130130
enter goja.Callable
131131
exit goja.Callable
132+
gas goja.Callable
132133

133134
// Underlying structs being passed into JS
134135
log *steplog
135136
frame *callframe
136137
frameResult *callframeResult
138+
gasChange *gasChange
137139

138140
// Goja-wrapping of types prepared for JS consumption
139141
logValue goja.Value
140142
dbValue goja.Value
141143
frameValue goja.Value
142144
frameResultValue goja.Value
145+
gasChangeValue goja.Value
143146
}
144147

145148
// newJsTracer instantiates a new JS tracer instance. code is a
@@ -202,6 +205,7 @@ func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage, chainCo
202205
if hasEnter != hasExit {
203206
return nil, errors.New("trace object must expose either both or none of enter() and exit()")
204207
}
208+
t.gas, _ = goja.AssertFunction(obj.Get("gas"))
205209
t.traceFrame = hasEnter
206210
t.obj = obj
207211
t.step = step
@@ -230,18 +234,21 @@ func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage, chainCo
230234
}
231235
t.frame = &callframe{vm: vm, toBig: t.toBig, toBuf: t.toBuf}
232236
t.frameResult = &callframeResult{vm: vm, toBuf: t.toBuf}
237+
t.gasChange = &gasChange{vm: vm}
233238
t.frameValue = t.frame.setupObject()
234239
t.frameResultValue = t.frameResult.setupObject()
235240
t.logValue = t.log.setupObject()
241+
t.gasChangeValue = t.gasChange.setupObject()
236242

237243
return &tracers.Tracer{
238244
Hooks: &tracing.Hooks{
239-
OnTxStart: t.OnTxStart,
240-
OnTxEnd: t.OnTxEnd,
241-
OnEnter: t.OnEnter,
242-
OnExit: t.OnExit,
243-
OnOpcode: t.OnOpcode,
244-
OnFault: t.OnFault,
245+
OnTxStart: t.OnTxStart,
246+
OnTxEnd: t.OnTxEnd,
247+
OnEnter: t.OnEnter,
248+
OnExit: t.OnExit,
249+
OnOpcode: t.OnOpcode,
250+
OnFault: t.OnFault,
251+
OnGasChange: t.OnGasChange,
245252
},
246253
GetResult: t.GetResult,
247254
Stop: t.Stop,
@@ -329,6 +336,20 @@ func (t *jsTracer) onStart(from common.Address, to common.Address, create bool,
329336
t.ctx["value"] = valueBig
330337
}
331338

339+
// OnGasChange keeps track of the changes in available gas
340+
func (t *jsTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {
341+
if t.gas == nil {
342+
return
343+
}
344+
345+
t.gasChange.old = old
346+
t.gasChange.new = new
347+
t.gasChange.reason = reason
348+
if _, err := t.gas(t.obj, t.gasChangeValue); err != nil {
349+
t.onError("gas", err)
350+
}
351+
}
352+
332353
// OnOpcode implements the Tracer interface to trace a single step of VM execution.
333354
func (t *jsTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
334355
if !t.traceStep {
@@ -1043,3 +1064,23 @@ func (l *steplog) setupObject() *goja.Object {
10431064
o.Set("contract", l.contract.setupObject())
10441065
return o
10451066
}
1067+
1068+
type gasChange struct {
1069+
vm *goja.Runtime
1070+
1071+
old, new uint64
1072+
reason tracing.GasChangeReason
1073+
}
1074+
1075+
func (g *gasChange) GetOld() uint64 { return g.old }
1076+
func (g *gasChange) GetNew() uint64 { return g.new }
1077+
func (g *gasChange) GetReason() string { return g.reason.String() }
1078+
1079+
func (g *gasChange) setupObject() *goja.Object {
1080+
o := g.vm.NewObject()
1081+
// Setup basic fields.
1082+
o.Set("getOld", g.vm.ToValue(g.GetOld))
1083+
o.Set("getNew", g.vm.ToValue(g.GetNew))
1084+
o.Set("getReason", g.vm.ToValue(g.GetReason))
1085+
return o
1086+
}

eth/tracers/js/internal/tracers/call_tracer_legacy.js

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@
2424
// an inner call.
2525
descended: false,
2626

27+
// keeps track of last pushed call to attribute the next dynamic cost with
28+
lastPushedCall: undefined,
29+
2730
// step is invoked for every opcode that the VM executes.
2831
step: function(log, db) {
32+
this.lastPushedCall = undefined
2933
// Capture any errors immediately
3034
var error = log.getError();
3135
if (error !== undefined) {
@@ -52,6 +56,7 @@
5256
value: '0x' + log.stack.peek(0).toString(16)
5357
};
5458
this.callstack.push(call);
59+
this.lastPushedCall = call
5560
this.descended = true
5661
return;
5762
}
@@ -61,14 +66,16 @@
6166
if (this.callstack[left-1].calls === undefined) {
6267
this.callstack[left-1].calls = [];
6368
}
64-
this.callstack[left-1].calls.push({
65-
type: op,
66-
from: toHex(log.contract.getAddress()),
67-
to: toHex(toAddress(log.stack.peek(0).toString(16))),
68-
gasIn: log.getGas(),
69+
var call = {
70+
type: op,
71+
from: toHex(log.contract.getAddress()),
72+
to: toHex(toAddress(log.stack.peek(0).toString(16))),
73+
gasIn: log.getGas(),
6974
gasCost: log.getCost(),
70-
value: '0x' + db.getBalance(log.contract.getAddress()).toString(16)
71-
});
75+
value: '0x' + db.getBalance(log.contract.getAddress()).toString(16)
76+
}
77+
this.callstack[left - 1].calls.push(call);
78+
this.lastPushedCall = call
7279
return
7380
}
7481
// If a new method invocation is being done, add to the call stack
@@ -98,6 +105,7 @@
98105
call.value = '0x' + log.stack.peek(2).toString(16);
99106
}
100107
this.callstack.push(call);
108+
this.lastPushedCall = call
101109
this.descended = true
102110
return;
103111
}
@@ -161,6 +169,13 @@
161169
}
162170
},
163171

172+
gas: function(change) {
173+
if (this.lastPushedCall !== undefined && change.getReason() == "CallOpCodeDynamic") {
174+
this.lastPushedCall.gasCost += change.getOld() - change.getNew();
175+
this.lastPushedCall = undefined
176+
}
177+
},
178+
164179
// fault is invoked when the actual execution of an opcode fails.
165180
fault: function(log, db) {
166181
// If the topmost call already reverted, don't handle the additional fault again

0 commit comments

Comments
 (0)