diff --git a/go/interpreter/lfvm/gas.go b/go/interpreter/lfvm/gas.go index df3a1ce87..23786cb67 100644 --- a/go/interpreter/lfvm/gas.go +++ b/go/interpreter/lfvm/gas.go @@ -457,26 +457,17 @@ func gasEip2929AccountCheck(c *context, address tosca.Address) error { return nil } -func addressInAccessList(c *context) (warmAccess bool, coldCost tosca.Gas, err error) { - warmAccess = true - if c.isAtLeast(tosca.R09_Berlin) { - addr := tosca.Address(c.stack.peekN(1).Bytes20()) +func calculateAccessCost(address tosca.Address, revision tosca.Revision, runContext tosca.RunContext) (isWarm bool, accessCost tosca.Gas) { + isWarm = true + if revision >= tosca.R09_Berlin { // Check slot presence in the access list //lint:ignore SA1019 deprecated functions to be migrated in #616 - warmAccess = c.context.IsAddressInAccessList(addr) + isWarm = runContext.IsAddressInAccessList(address) // 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 = ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929 - if !warmAccess { - c.context.AccessAccount(addr) - // Charge the remaining difference here already, to correctly calculate available - // gas for call - if !c.useGas(coldCost) { - return false, 0, errOutOfGas - } - } + accessCost = ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929 } - return warmAccess, coldCost, nil + return isWarm, accessCost } func gasSelfdestruct(c *context) tosca.Gas { diff --git a/go/interpreter/lfvm/gas_test.go b/go/interpreter/lfvm/gas_test.go index a145a82a4..bcea4f4f9 100644 --- a/go/interpreter/lfvm/gas_test.go +++ b/go/interpreter/lfvm/gas_test.go @@ -11,7 +11,6 @@ package lfvm import ( - "errors" "testing" "github.com/Fantom-foundation/Tosca/go/tosca" @@ -62,66 +61,44 @@ func TestGas_CallGasCalculation(t *testing.T) { } } -func TestGas_AddressInAccessList(t *testing.T) { +func TestGas_calculateAccessCost(t *testing.T) { tests := map[string]struct { - setup func(*context) - accessStatus bool - coldCost tosca.Gas + revision tosca.Revision + warmAccess bool + coldCost tosca.Gas }{ "istanbul": { - setup: func(c *context) { - c.params.Revision = tosca.R07_Istanbul - }, - accessStatus: true, + revision: tosca.R07_Istanbul, + warmAccess: true, }, "berlin_warm": { - setup: func(c *context) { - c.params.Revision = tosca.R09_Berlin - c.context.(*tosca.MockRunContext).EXPECT().IsAddressInAccessList(gomock.Any()).Return(true) - c.stack.push(uint256.NewInt(1)) - c.stack.push(uint256.NewInt(2)) - }, - accessStatus: true, - coldCost: ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929, + revision: tosca.R09_Berlin, + warmAccess: true, + coldCost: ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929, }, "berlin_cold_enough_gas": { - setup: func(c *context) { - c.params.Revision = tosca.R09_Berlin - c.context.(*tosca.MockRunContext).EXPECT().IsAddressInAccessList(gomock.Any()).Return(false) - c.context.(*tosca.MockRunContext).EXPECT().AccessAccount(gomock.Any()).Return(tosca.ColdAccess) - c.stack.push(uint256.NewInt(1)) - c.stack.push(uint256.NewInt(2)) - c.gas = ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929 - }, - accessStatus: false, - coldCost: ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929, + revision: tosca.R09_Berlin, + warmAccess: false, + coldCost: ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { ctrl := gomock.NewController(t) - runContext := tosca.NewMockRunContext(ctrl) - - ctxt := context{ - status: statusRunning, - params: tosca.Parameters{}, - context: runContext, - stack: NewStack(), - memory: NewMemory(), - gas: 1 << 20, - } - test.setup(&ctxt) - - warmGot, coldCostGot, errGot := addressInAccessList(&ctxt) - - if errGot != nil { - t.Errorf("unexpected error, wanted %v, got %v", nil, errGot) - } - - if warmGot != test.accessStatus { - t.Errorf("unexpected warm access status, wanted %v, got %v", test.accessStatus, warmGot) + mockRunContext := tosca.NewMockRunContext(ctrl) + mockRunContext.EXPECT().IsAddressInAccessList(gomock.Any()).Return(test.warmAccess).AnyTimes() + mockRunContext.EXPECT().AccessAccount(gomock.Any()).Return(tosca.ColdAccess).AnyTimes() + + warmGot, coldCostGot := calculateAccessCost( + tosca.Address{1}, + test.revision, + mockRunContext, + ) + + if warmGot != test.warmAccess { + t.Errorf("unexpected warm access status, wanted %v, got %v", test.warmAccess, warmGot) } if coldCostGot != test.coldCost { @@ -131,32 +108,3 @@ func TestGas_AddressInAccessList(t *testing.T) { }) } } - -func TestGas_AddressInAccessListReportsOutOfGas(t *testing.T) { - - ctrl := gomock.NewController(t) - runContext := tosca.NewMockRunContext(ctrl) - ctxt := context{ - status: statusRunning, - params: tosca.Parameters{ - BlockParameters: tosca.BlockParameters{ - Revision: tosca.R09_Berlin, - }, - }, - context: runContext, - stack: NewStack(), - memory: NewMemory(), - gas: ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929 - 1, - } - - ctxt.context.(*tosca.MockRunContext).EXPECT().IsAddressInAccessList(gomock.Any()).Return(false) - ctxt.context.(*tosca.MockRunContext).EXPECT().AccessAccount(gomock.Any()).Return(tosca.ColdAccess) - ctxt.stack.push(uint256.NewInt(1)) - ctxt.stack.push(uint256.NewInt(2)) - - _, _, errGot := addressInAccessList(&ctxt) - if !errors.Is(errGot, errOutOfGas) { - t.Errorf("unexpected error, wanted %v, got %v", errOutOfGas, errGot) - } - -} diff --git a/go/interpreter/lfvm/instructions.go b/go/interpreter/lfvm/instructions.go index a647398f4..75a438c59 100644 --- a/go/interpreter/lfvm/instructions.go +++ b/go/interpreter/lfvm/instructions.go @@ -1044,10 +1044,7 @@ func neededMemorySize(c *context, offset, size *uint256.Int) (uint64, error) { } func genericCall(c *context, kind tosca.CallKind) { - warmAccess, coldCost, err := addressInAccessList(c) - if err != nil { - return - } + stack := c.stack value := uint256.NewInt(0) @@ -1079,6 +1076,15 @@ func genericCall(c *context, kind tosca.CallKind) { needed_memory_size = ret_memory_size } + isWarm, accessCost := calculateAccessCost(toAddr, c.params.Revision, c.context) + if !isWarm { + c.context.AccessAccount(toAddr) + if !c.useGas(accessCost) { + c.status = statusOutOfGas + return + } + } + baseGas := c.memory.ExpansionCosts(needed_memory_size) checkGas := func(cost tosca.Gas) bool { return 0 <= cost && cost <= c.gas @@ -1109,13 +1115,13 @@ func genericCall(c *context, kind tosca.CallKind) { } cost := callGas(c.gas, baseGas, provided_gas) - if !warmAccess { + if !isWarm { // 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. - c.gas += coldCost - baseGas += coldCost + c.gas += accessCost + baseGas += accessCost } if !c.useGas(baseGas + cost) { return