Skip to content
Merged
10 changes: 10 additions & 0 deletions arbitrum/multigas/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ func (z *MultiGas) SetRefund(amount uint64) *MultiGas {
return z
}

// SafeAddRefund adds the given amount to the refund and checks for overflow.
func (z *MultiGas) SafeAddRefund(amount uint64) bool {
result, overflow := math.SafeAdd(z.refund, amount)
if overflow {
return overflow
}
z.refund = result
return false
}

// SafeAdd sets z to the sum x+y and returns z and checks for overflow.
func (z *MultiGas) SafeAdd(x *MultiGas, y *MultiGas) (*MultiGas, bool) {
for i := ResourceKindUnknown; i < NumResourceKind; i++ {
Expand Down
220 changes: 116 additions & 104 deletions core/vm/gas_table.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/vm/gas_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestMemoryGasCost(t *testing.T) {
{0x1fffffffe1, 0, true},
}
for i, tt := range tests {
v, err := memoryGasCost(&Memory{}, tt.size)
_, v, err := memoryGasCost(&Memory{}, tt.size)
if (err == ErrGasUintOverflow) != tt.overflow {
t.Errorf("test %d: overflow mismatch: have %v, want %v", i, err == ErrGasUintOverflow, tt.overflow)
}
Expand Down
2 changes: 1 addition & 1 deletion core/vm/instructions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -879,7 +879,7 @@ func TestOpMCopy(t *testing.T) {
}
// and the dynamic cost
var haveGas uint64
if dynamicCost, err := gasMcopy(evm, nil, stack, mem, memorySize); err != nil {
if _, dynamicCost, err := gasMcopy(evm, nil, stack, mem, memorySize); err != nil {
t.Error(err)
} else {
haveGas = GasFastestStep + dynamicCost
Expand Down
2 changes: 1 addition & 1 deletion core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// Consume the gas and return an error if not enough gas is available.
// cost is explicitly set so that the capture state defer method can get the proper cost
var dynamicCost uint64
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
_, dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
cost += dynamicCost // for tracing
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
Expand Down
5 changes: 4 additions & 1 deletion core/vm/jump_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ package vm
import (
"fmt"

"github.com/ethereum/go-ethereum/arbitrum/multigas"
"github.com/ethereum/go-ethereum/params"
)

type (
executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error)
gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64
// last parameter is the requested memory size as a uint64
// gasFunc returns multi-dimensional gas usage (MultiGas) and single-dimensional gas (uint64)
gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (*multigas.MultiGas, uint64, error)
// memorySizeFunc returns the required size, and whether the operation overflowed a uint64
memorySizeFunc func(*Stack) (size uint64, overflow bool)
)
Expand Down
213 changes: 213 additions & 0 deletions core/vm/operaions_gas_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package vm

import (
"testing"

"github.com/ethereum/go-ethereum/arbitrum/multigas"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)

func TestMakeGasSStoreFunc(t *testing.T) {
testCases := []struct {
name string
slotInAccessList bool
originalValue common.Hash // committed state value
currentValue common.Hash // current state value (may differ from original)
newValue common.Hash // value to set
refund bool // for recreate slot test, we need to simulate that a refund was added when it was deleted
expectedMultiGas *multigas.MultiGas
}{
// NOOP cases (current == value)
{
name: "noop - cold slot access",
slotInAccessList: false,
originalValue: common.HexToHash("0x1234"),
currentValue: common.HexToHash("0x1234"),
newValue: common.HexToHash("0x1234"),
expectedMultiGas: multigas.StorageAccessGas(params.ColdSloadCostEIP2929 + params.WarmStorageReadCostEIP2929),
},
{
name: "noop - warm slot access",
slotInAccessList: true,
originalValue: common.HexToHash("0x1234"),
currentValue: common.HexToHash("0x1234"),
newValue: common.HexToHash("0x1234"),
expectedMultiGas: multigas.StorageAccessGas(params.WarmStorageReadCostEIP2929),
},
// Cases where original == current
{
name: "create slot - warm slot access",
slotInAccessList: true,
originalValue: common.Hash{},
currentValue: common.Hash{},
newValue: common.HexToHash("0x1234"),
expectedMultiGas: multigas.StorageGrowthGas(params.SstoreSetGasEIP2200),
},
{
name: "delete slot - warm access",
slotInAccessList: true,
originalValue: common.HexToHash("0x1234"),
currentValue: common.HexToHash("0x1234"),
newValue: common.Hash{},
expectedMultiGas: multigas.StorageAccessGas(params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929).SetRefund(params.SstoreClearsScheduleRefundEIP2200),
},
{
name: "update slot - warm access",
slotInAccessList: true,
originalValue: common.HexToHash("0x1234"),
currentValue: common.HexToHash("0x1234"),
newValue: common.HexToHash("0x5678"),
expectedMultiGas: multigas.StorageAccessGas(params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929),
},
// Dirty update cases (original != current)
{
name: "dirty update - recreate slot - warm access",
slotInAccessList: true,
originalValue: common.HexToHash("0x1234"),
currentValue: common.Hash{}, // was deleted in current tx
newValue: common.HexToHash("0x5678"),
refund: true,
expectedMultiGas: multigas.StorageAccessGas(params.WarmStorageReadCostEIP2929).SetRefund(params.SstoreClearsScheduleRefundEIP2200),
},
{
name: "dirty update - delete slot - warm access",
slotInAccessList: true,
originalValue: common.HexToHash("0x1234"),
currentValue: common.HexToHash("0x5678"), // was changed in current tx
newValue: common.Hash{}, // delete
expectedMultiGas: multigas.StorageAccessGas(params.WarmStorageReadCostEIP2929).SetRefund(params.SstoreClearsScheduleRefundEIP2200),
},
{
name: "dirty update - change non-zero to different non-zero - warm access",
slotInAccessList: true,
originalValue: common.HexToHash("0x1234"),
currentValue: common.HexToHash("0x5678"),
newValue: common.HexToHash("0x9abc"),
expectedMultiGas: multigas.StorageAccessGas(params.WarmStorageReadCostEIP2929),
},
// Reset to original cases (original == value but original != current)
{
name: "reset to original empty slot - warm access",
slotInAccessList: true,
originalValue: common.Hash{},
currentValue: common.HexToHash("0x1234"), // was created in current tx
newValue: common.Hash{}, // back to original empty
expectedMultiGas: multigas.StorageAccessGas(params.WarmStorageReadCostEIP2929).SetRefund(params.SstoreSetGasEIP2200 - params.WarmStorageReadCostEIP2929),
},
{
name: "reset to original existing slot - warm access",
slotInAccessList: true,
originalValue: common.HexToHash("0x1234"),
currentValue: common.HexToHash("0x5678"), // was changed in current tx
newValue: common.HexToHash("0x1234"), // back to original value
expectedMultiGas: multigas.StorageAccessGas(params.WarmStorageReadCostEIP2929).SetRefund((params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929) - params.WarmStorageReadCostEIP2929),
},
{
name: "dirty update - create from nothing - warm access",
slotInAccessList: true,
originalValue: common.Hash{},
currentValue: common.HexToHash("0x1234"), // was created in current tx
newValue: common.HexToHash("0x5678"), // change to different value
expectedMultiGas: multigas.StorageAccessGas(params.WarmStorageReadCostEIP2929),
},
}

slotKey := common.HexToHash("0x01")
gasSStoreFunc := makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP2200)
contractGas := uint64(100000)

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
evm := NewEVM(BlockContext{}, statedb, params.TestChainConfig, Config{})

caller := common.Address{}
contractAddr := common.Address{1}
contract := NewContract(caller, contractAddr, new(uint256.Int), contractGas, nil)

stack := newstack()
mem := NewMemory()

if tc.slotInAccessList {
statedb.AddSlotToAccessList(contractAddr, slotKey)
}

// Set up state and stack
if tc.originalValue != (common.Hash{}) {
statedb.SetState(contractAddr, slotKey, tc.originalValue)
statedb.Commit(0, false, false)
}

statedb.SetState(contractAddr, slotKey, tc.currentValue)

stack.push(new(uint256.Int).SetBytes(tc.newValue.Bytes())) // y (value)
stack.push(new(uint256.Int).SetBytes(slotKey.Bytes())) // x (slot)

if tc.refund {
statedb.AddRefund(params.SstoreClearsScheduleRefundEIP2200)
}

multiGas, singleGas, err := gasSStoreFunc(evm, contract, stack, mem, 0)

if err != nil {
t.Fatalf("Unexpected error for test case %s: %v", tc.name, err)
}

if *multiGas != *tc.expectedMultiGas {
t.Errorf("Expected multi gas %d, got %d for test case: %s",
tc.expectedMultiGas, multiGas, tc.name)
}

expectedSingleGas, overflow := tc.expectedMultiGas.SingleGas()
if overflow {
t.Fatalf("Expected single gas overflow for test case %s", tc.name)
}

if singleGas != expectedSingleGas {
t.Errorf("Expected signle gas %d, got %d for test case: %s",
expectedSingleGas, singleGas, tc.name)
}
})
}
}

func TestGasSStore4762(t *testing.T) {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
evm := NewEVM(BlockContext{}, statedb, params.TestChainConfig, Config{})

caller := common.Address{}
contractAddr := common.Address{1}
contractGas := uint64(100000)
contract := NewContract(caller, contractAddr, new(uint256.Int), contractGas, nil)

stack := newstack()
mem := NewMemory()

// Setup access list and stack
accessList := state.NewAccessEvents(evm.StateDB.PointCache())
accessList.AddAccount(caller, false)
evm.AccessEvents = accessList

slotKey := common.HexToHash("0xdeadbeef") // any dummy key
stack.push(new(uint256.Int).SetBytes(slotKey.Bytes()))

expectedSingleGas := params.WitnessBranchReadCost + params.WitnessChunkReadCost +
params.WitnessBranchWriteCost + params.WitnessChunkWriteCost
expectedMultiGas := multigas.StorageAccessGas(expectedSingleGas)

multiGas, singleGas, err := gasSStore4762(evm, contract, stack, mem, 0)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

if *multiGas != *expectedMultiGas {
t.Errorf("Expected multi gas %d, got %d", expectedMultiGas, multiGas)
}
if singleGas != expectedSingleGas {
t.Errorf("Expected single gas %d, got %d", expectedSingleGas, singleGas)
}
}
Loading
Loading