Skip to content

Instrument the CALL, CALLCODE, DELEGATECALL and STATICCALL opcodes #486

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: cbp-feature
Choose a base branch
from
74 changes: 42 additions & 32 deletions core/vm/gas_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (*multigas.MultiGas, uint64,
fee := newTotalFee - mem.lastGasCost
mem.lastGasCost = newTotalFee

return multigas.ZeroGas(), fee, nil
return multigas.ComputationGas(fee), fee, nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you decide to allocate this to computation gas?
Is that what Tyler's tracer did?
I would have guessed (possibly, incorrectly) that gas for expanding memory would be state growth gas.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My assumption was based on this statement, and here and in all following places I have treated memory operations as computation cost since there are no appeals on statedb. Please correct me if I'm wrong

Copy link
Member

@eljobe eljobe Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. So, this is, at least, consistent with what Tyler has done in the tracer. Good.

Now, though, I do need to understand why that's the right behavior.

Maybe I don't understand what the difference is between memory expansion and the storage in the statedb.

By way of analogy, are we saying that the "memory" of the EVM is similar to RAM on a real machine. And, that entries in the statedb are more like "disk"? And, so, when we talk about "state growth" and "state access" we are only talking about the equivalent of disk growth and i/o? And that "memory" is only what has to be paged into the working memory of the VM to support the computations which are happening while processing the transactions and even though the values of the allocated memory are part of what makes up the global state root, it's not really part of the "state" we're talking about when we talk about "state growth" or "state access?"

@tsahee, @relyt29 and @magicxyyz am I understanding this right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes that is exactly correct

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. Thanks. I feel like we should put some abbreviated explanation of this reasoning in the inline documentation so that we remember why we're treating memory expansion gas as computation gas. Keep it brief, but, let's do explain it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the documentation for this. Did you decide not to add something?

}
return multigas.ZeroGas(), 0, nil
}
Expand Down Expand Up @@ -404,82 +404,92 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor

func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (*multigas.MultiGas, uint64, error) {
var (
gas uint64
multiGas = multigas.ZeroGas()
transfersValue = !stack.Back(2).IsZero()
address = common.Address(stack.Back(1).Bytes20())
)

if evm.chainRules.IsEIP158 {
if transfersValue && evm.StateDB.Empty(address) {
gas += params.CallNewAccountGas
multiGas.SafeIncrement(multigas.ResourceKindStorageGrowth, params.CallNewAccountGas)
}
} else if !evm.StateDB.Exist(address) {
gas += params.CallNewAccountGas
multiGas.SafeIncrement(multigas.ResourceKindStorageGrowth, params.CallNewAccountGas)
}

if transfersValue && !evm.chainRules.IsEIP4762 {
gas += params.CallValueTransferGas
multiGas.SafeIncrement(multigas.ResourceKindComputation, params.CallValueTransferGas)
}
multiGas, memoryGas, err := memoryGasCost(mem, memorySize)

memoryMultiGas, _, err := memoryGasCost(mem, memorySize)
if err != nil {
return multigas.ZeroGas(), 0, err
}
// TODO(NIT-3484): Update multi dimensional gas here
var overflow bool
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
multiGas, overflow := multiGas.SafeAdd(multiGas, memoryMultiGas)
if overflow {
return multigas.ZeroGas(), 0, ErrGasUintOverflow
}

if evm.chainRules.IsEIP4762 && !contract.IsSystemCall {
if transfersValue {
gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
if overflow {
valueTransferGas := evm.AccessEvents.ValueTransferGas(contract.Address(), address)
if overflow := multiGas.SafeIncrement(multigas.ResourceKindStorageAccess, valueTransferGas); overflow {
return multigas.ZeroGas(), 0, ErrGasUintOverflow
}
}
}
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))

singleGas, _ := multiGas.SingleGas()
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, singleGas, stack.Back(0))
if err != nil {
return multigas.ZeroGas(), 0, err
}
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
if overflow = multiGas.SafeIncrement(multigas.ResourceKindComputation, evm.callGasTemp); overflow {
return multigas.ZeroGas(), 0, ErrGasUintOverflow
}

return multiGas, gas, nil
singleGas, _ = multiGas.SingleGas()
return multiGas, singleGas, nil
}

func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (*multigas.MultiGas, uint64, error) {
multiGas, memoryGas, err := memoryGasCost(mem, memorySize)
memoryMultiGas, _, err := memoryGasCost(mem, memorySize)
if err != nil {
return multigas.ZeroGas(), 0, err
}
var (
gas uint64
multiGas = multigas.ZeroGas()
overflow bool
)
if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 {
gas += params.CallValueTransferGas
multiGas.SafeIncrement(multigas.ResourceKindComputation, params.CallValueTransferGas)
}
// TODO(NIT-3484): Update multi dimensional gas here
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
multiGas, overflow = multiGas.SafeAdd(multiGas, memoryMultiGas)
if overflow {
return multigas.ZeroGas(), 0, ErrGasUintOverflow
}
if evm.chainRules.IsEIP4762 && !contract.IsSystemCall {
address := common.Address(stack.Back(1).Bytes20())
transfersValue := !stack.Back(2).IsZero()
if transfersValue {
gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
if overflow {
valueTransferGas := evm.AccessEvents.ValueTransferGas(contract.Address(), address)
if overflow = multiGas.SafeIncrement(multigas.ResourceKindStorageAccess, valueTransferGas); overflow {
return multigas.ZeroGas(), 0, ErrGasUintOverflow
}
}
}
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))

singleGas, _ := multiGas.SingleGas()
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, singleGas, stack.Back(0))
if err != nil {
return multigas.ZeroGas(), 0, err
}
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
if overflow = multiGas.SafeIncrement(multigas.ResourceKindComputation, evm.callGasTemp); overflow {
return multigas.ZeroGas(), 0, ErrGasUintOverflow
}
return multiGas, gas, nil

singleGas, _ = multiGas.SingleGas()
return multiGas, singleGas, nil
}

func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (*multigas.MultiGas, uint64, error) {
Expand All @@ -491,12 +501,12 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
if err != nil {
return multigas.ZeroGas(), 0, err
}
var overflow bool
// TODO(NIT-3484): Update multi dimensional gas here
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
if overflow := multiGas.SafeIncrement(multigas.ResourceKindComputation, evm.callGasTemp); overflow {
return multigas.ZeroGas(), 0, ErrGasUintOverflow
}
return multiGas, gas, nil

singleGas, _ := multiGas.SingleGas()
return multiGas, singleGas, nil
}

func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (*multigas.MultiGas, uint64, error) {
Expand All @@ -508,12 +518,12 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
if err != nil {
return multigas.ZeroGas(), 0, err
}
var overflow bool
// TODO(NIT-3484): Update multi dimensional gas here
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
if overflow := multiGas.SafeIncrement(multigas.ResourceKindComputation, evm.callGasTemp); overflow {
return multigas.ZeroGas(), 0, ErrGasUintOverflow
}
return multiGas, gas, nil

singleGas, _ := multiGas.SingleGas()
return multiGas, singleGas, nil
}

func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (*multigas.MultiGas, uint64, error) {
Expand Down
24 changes: 13 additions & 11 deletions core/vm/operations_acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,12 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g
// also become correctly reported to tracers.
contract.Gas += coldCost

// TODO(NIT-3484): Update multi dimensional gas here
var overflow bool
if gas, overflow = math.SafeAdd(gas, coldCost); overflow {
if overflow := multiGas.SafeIncrement(multigas.ResourceKindStorageAccess, coldCost); overflow {
return multigas.ZeroGas(), 0, ErrGasUintOverflow
}
return multigas.ZeroGas(), gas, nil

singleGas, _ := multiGas.SingleGas()
return multiGas, singleGas, nil
}
}

Expand Down Expand Up @@ -265,8 +265,8 @@ var (
func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (*multigas.MultiGas, uint64, error) {
var (
total uint64 // total dynamic gas used
addr = common.Address(stack.Back(1).Bytes20())
multiGas = multigas.ZeroGas() // total dynamic gas used
addr = common.Address(stack.Back(1).Bytes20())
)

// Check slot presence in the access list
Expand All @@ -280,7 +280,7 @@ func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return multigas.ZeroGas(), 0, ErrOutOfGas
}
total += coldCost
multiGas.SafeIncrement(multigas.ResourceKindStorageAccess, coldCost)
}

// Check if code is a delegation and if so, charge for resolution.
Expand All @@ -295,7 +295,7 @@ func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
if !contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return multigas.ZeroGas(), 0, ErrOutOfGas
}
total += cost
multiGas.SafeIncrement(multigas.ResourceKindStorageAccess, cost)
}

// Now call the old calculator, which takes into account
Expand All @@ -312,12 +312,14 @@ func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
// 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
contract.Gas += multiGas.Get(multigas.ResourceKindStorageAccess)

var overflow bool
if total, overflow = math.SafeAdd(old, total); overflow {
if multiGas, overflow = multiGas.SafeAdd(multiGas, multiOld); overflow {
return multigas.ZeroGas(), 0, ErrGasUintOverflow
}
return multigas.ZeroGas(), total, nil

singleGas, _ := multiGas.SingleGas()
return multiGas, singleGas, nil
}
}
Loading
Loading