mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 11:20:45 +00:00
core/vm: implement access cost check in SSTORE
This commit is contained in:
parent
2919267b64
commit
c5d36e7496
2 changed files with 65 additions and 5 deletions
|
|
@ -235,3 +235,49 @@ func TestEIP8038SelfdestructAccountWrite(t *testing.T) {
|
||||||
t.Fatalf("state gas = %d, want %d", res.UsedStateGas, want)
|
t.Fatalf("state gas = %d, want %d", res.UsedStateGas, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EIP-7928: SSTORE must cover the slot's access cost before its implicit read
|
||||||
|
// of the current value, so that a failing SSTORE does not leak the slot into
|
||||||
|
// the block access list. The stipend sentry alone cannot guard the read, as
|
||||||
|
// COLD_STORAGE_ACCESS (3000) exceeds GAS_CALL_STIPEND (2300).
|
||||||
|
func TestSStoreBALAccessCostCheck(t *testing.T) {
|
||||||
|
self := common.BytesToAddress([]byte("self"))
|
||||||
|
slot := common.BytesToHash([]byte{0})
|
||||||
|
|
||||||
|
// run executes a noop write (1 -> 1) on a cold slot with block access list
|
||||||
|
// recording enabled and returns the recorded storage reads of the contract.
|
||||||
|
run := func(regular uint64) (map[common.Hash]struct{}, error) {
|
||||||
|
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||||
|
statedb.CreateAccount(self)
|
||||||
|
statedb.SetCode(self, sstore(0, 1), tracing.CodeChangeUnspecified)
|
||||||
|
statedb.SetState(self, slot, common.BytesToHash([]byte{1}))
|
||||||
|
statedb.Finalise(true)
|
||||||
|
|
||||||
|
evm := amsterdam8037EVM(statedb)
|
||||||
|
statedb.Prepare(evm.chainRules, common.Address{}, common.Address{}, &self, nil, nil)
|
||||||
|
_, _, err := evm.Call(common.Address{}, self, nil, NewGasBudget(regular, 0), new(uint256.Int))
|
||||||
|
acct := statedb.Finalise(true).Accounts[self]
|
||||||
|
if acct == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return acct.StorageReads, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// gas_left at the SSTORE clears the stipend sentry but falls one short of
|
||||||
|
// the cold access cost: the op must fail without touching the slot.
|
||||||
|
reads, err := run(6 + params.ColdStorageAccessAmsterdam - 1)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected OOG below the cold access cost")
|
||||||
|
}
|
||||||
|
if _, ok := reads[slot]; ok {
|
||||||
|
t.Fatal("failing SSTORE leaked the slot into the block access list")
|
||||||
|
}
|
||||||
|
// Exactly the cold access cost succeeds and records the read.
|
||||||
|
reads, err = run(6 + params.ColdStorageAccessAmsterdam)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected failure at the cold access cost: %v", err)
|
||||||
|
}
|
||||||
|
if _, ok := reads[slot]; !ok {
|
||||||
|
t.Fatal("successful SSTORE missing from the block access list")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -696,17 +696,31 @@ func gasSStore8037And8038(evm *EVM, contract *Contract, stack *Stack, mem *Memor
|
||||||
return GasCosts{}, errors.New("not enough gas for reentrancy sentry")
|
return GasCosts{}, errors.New("not enough gas for reentrancy sentry")
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
y, x = stack.back(1), stack.peek()
|
y, x = stack.back(1), stack.peek()
|
||||||
slot = common.Hash(x.Bytes32())
|
slot = common.Hash(x.Bytes32())
|
||||||
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
|
|
||||||
value = common.Hash(y.Bytes32())
|
|
||||||
)
|
)
|
||||||
// Check slot presence in the access list
|
// Check slot presence in the access list
|
||||||
access := params.WarmStorageReadCostEIP2929
|
access := params.WarmStorageReadCostEIP2929
|
||||||
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
|
_, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot)
|
||||||
|
if !slotPresent {
|
||||||
access = params.ColdStorageAccessAmsterdam
|
access = params.ColdStorageAccessAmsterdam
|
||||||
|
}
|
||||||
|
// SSTORE performs an implicit read of the current storage value for the gas
|
||||||
|
// calculation below. Since the cold access cost exceeds GAS_CALL_STIPEND
|
||||||
|
// after the Amsterdam repricing, the reentrancy sentry alone no longer
|
||||||
|
// guards this read: the frame must also cover the slot's access cost
|
||||||
|
// beforehand, otherwise the slot must not be recorded in the block access
|
||||||
|
// list (EIP-7928).
|
||||||
|
if contract.Gas.RegularGas < access {
|
||||||
|
return GasCosts{}, ErrOutOfGas
|
||||||
|
}
|
||||||
|
if !slotPresent {
|
||||||
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
|
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
|
||||||
|
value = common.Hash(y.Bytes32())
|
||||||
|
)
|
||||||
stateSet := params.StorageCreationSize * evm.Context.CostPerStateByte
|
stateSet := params.StorageCreationSize * evm.Context.CostPerStateByte
|
||||||
|
|
||||||
if current == value { // noop (1)
|
if current == value { // noop (1)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue