From e98e68662b5459d9e5ce4a7d5d362140a9fb9513 Mon Sep 17 00:00:00 2001 From: lightclient Date: Mon, 9 Dec 2024 19:55:40 -0700 Subject: [PATCH] core/vm: report gas used warming 7702 delegation targets correctly in tracer --- core/vm/operations_acl.go | 50 ++++++++++++--------- core/vm/runtime/runtime_test.go | 80 +++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 22 deletions(-) diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 4245e7ae4a..517fd84637 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -253,55 +253,61 @@ var ( func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - addr := common.Address(stack.Back(1).Bytes20()) + var ( + total uint64 // total dynamic gas used + addr = common.Address(stack.Back(1).Bytes20()) + ) + // Check slot presence in the access list - warmAccess := evm.StateDB.AddressInAccessList(addr) - // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so - // the cost to charge for cold access, if any, is Cold - Warm - coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 - if !warmAccess { + if !evm.StateDB.AddressInAccessList(addr) { evm.StateDB.AddAddressToAccessList(addr) + // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so + // the cost to charge for cold access, if any, is Cold - Warm + coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 // Charge the remaining difference here already, to correctly calculate available // gas for call if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return 0, ErrOutOfGas } + total += coldCost } // Check if code is a delegation and if so, charge for resolution. - if addr, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok { + if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok { var cost uint64 - if evm.StateDB.AddressInAccessList(addr) { - cost += params.WarmStorageReadCostEIP2929 + if evm.StateDB.AddressInAccessList(target) { + cost = params.WarmStorageReadCostEIP2929 } else { - evm.StateDB.AddAddressToAccessList(addr) - cost += params.ColdAccountAccessCostEIP2929 + evm.StateDB.AddAddressToAccessList(target) + cost = params.ColdAccountAccessCostEIP2929 } if !contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return 0, ErrOutOfGas } - coldCost += cost + total += cost } + // Now call the old calculator, which takes into account // - create new account // - transfer value // - memory expansion // - 63/64ths rule - gas, err := oldCalculator(evm, contract, stack, mem, memorySize) - if warmAccess || err != nil { - return gas, err + old, err := oldCalculator(evm, contract, stack, mem, memorySize) + if err != nil { + return old, err } - // In case of a cold access, we temporarily add the cold charge back, and also - // add it to the returned gas. By adding it to the return, it will be charged - // outside of this function, as part of the dynamic gas, and that will make it - // also become correctly reported to tracers. - contract.Gas += coldCost + + // Temporarily add the gas charge back to the contract and return value. By + // adding it to the return, it will be charged outside of this function, as + // part of the dynamic gas. This will ensure it is correctly reported to + // tracers. + contract.Gas += total var overflow bool - if gas, overflow = math.SafeAdd(gas, coldCost); overflow { + if total, overflow = math.SafeAdd(old, total); overflow { return 0, ErrGasUintOverflow } - return gas, nil + return total, nil } } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 0e774a01c2..6074e9a096 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -866,3 +866,83 @@ func BenchmarkTracerStepVsCallFrame(b *testing.B) { benchmarkNonModifyingCode(10000000, code, "tracer-step-10M", stepTracer, b) benchmarkNonModifyingCode(10000000, code, "tracer-call-frame-10M", callFrameTracer, b) } + +// TestDelegatedAccountAccessCost tests that calling an account with an EIP-7702 +// delegation designator incurs the correct amount of gas based on the tracer. +func TestDelegatedAccountAccessCost(t *testing.T) { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) + statedb.SetCode(common.HexToAddress("0xff"), types.AddressToDelegation(common.HexToAddress("0xaa"))) + statedb.SetCode(common.HexToAddress("0xaa"), program.New().Return(0, 0).Bytes()) + + for i, tc := range []struct { + code []byte + step int + want uint64 + }{ + { // CALL(0xff) + code: []byte{ + byte(vm.PUSH1), 0x0, + byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.CALL), byte(vm.POP), + }, + step: 7, + want: 5455, + }, + { // CALLCODE(0xff) + code: []byte{ + byte(vm.PUSH1), 0x0, + byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.CALLCODE), byte(vm.POP), + }, + step: 7, + want: 5455, + }, + { // DELEGATECALL(0xff) + code: []byte{ + byte(vm.PUSH1), 0x0, + byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.DELEGATECALL), byte(vm.POP), + }, + step: 6, + want: 5455, + }, + { // STATICCALL(0xff) + code: []byte{ + byte(vm.PUSH1), 0x0, + byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.STATICCALL), byte(vm.POP), + }, + step: 6, + want: 5455, + }, + { // SELFDESTRUCT(0xff): should not be affected by resolution + code: []byte{ + byte(vm.PUSH1), 0xff, byte(vm.SELFDESTRUCT), + }, + step: 1, + want: 7600, + }, + } { + var step = 0 + var have = uint64(0) + Execute(tc.code, nil, &Config{ + ChainConfig: params.MergedTestChainConfig, + State: statedb, + EVMConfig: vm.Config{ + Tracer: &tracing.Hooks{ + OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + // Uncomment to investigate failures: + t.Logf("%d: %v %d", step, vm.OpCode(op).String(), cost) + if step == tc.step { + have = cost + } + step++ + }, + }, + }, + }) + if want := tc.want; have != want { + t.Fatalf("testcase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want) + } + } +}