Skip to content

Commit 515e4b3

Browse files
committed
core/vm, core/state: return error if gas is insufficient for cost
1 parent abb0094 commit 515e4b3

File tree

6 files changed

+242
-153
lines changed

6 files changed

+242
-153
lines changed

core/state/access_events.go

Lines changed: 137 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -91,67 +91,104 @@ func (ae *AccessEvents) Copy() *AccessEvents {
9191
return cpy
9292
}
9393

94-
// AddAccount returns the gas to be charged for each of the currently cold
95-
// member fields of an account.
96-
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool, availableGas uint64) uint64 {
97-
var gas uint64 // accumulate the consumed gas
98-
consumed, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
99-
if consumed < expected {
100-
return expected
101-
}
102-
gas += consumed
103-
consumed, expected = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas-consumed)
104-
if consumed < expected {
105-
return expected + gas
106-
}
107-
gas += expected
108-
return gas
94+
// AddAccount returns the gas cost for each currently cold member field of
95+
// an account. If the available gas is insufficient to cover the total cost,
96+
// a false flag is returned.
97+
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool, availableGas uint64) (uint64, bool) {
98+
var gas uint64
99+
cost, sufficient := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
100+
if !sufficient {
101+
return 0, false
102+
}
103+
gas += cost
104+
105+
cost, sufficient = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas-cost)
106+
if !sufficient {
107+
return 0, false
108+
}
109+
gas += cost
110+
return gas, true
109111
}
110112

111113
// MessageCallGas returns the gas to be charged for each of the currently
112-
// cold member fields of an account, that need to be touched when making a message
113-
// call to that account.
114-
func (ae *AccessEvents) MessageCallGas(destination common.Address, availableGas uint64) uint64 {
115-
_, expected := ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
116-
if expected == 0 {
117-
expected = params.WarmStorageReadCostEIP2929
118-
}
119-
return expected
114+
// cold member fields of an account, that need to be touched when making
115+
// a message call to that account. If the available gas is insufficient to
116+
// cover the total cost, a false flag is returned.
117+
func (ae *AccessEvents) MessageCallGas(destination common.Address, availableGas uint64) (uint64, bool) {
118+
cost, sufficient := ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
119+
if !sufficient {
120+
return 0, false
121+
}
122+
if cost == 0 {
123+
if availableGas < params.WarmStorageReadCostEIP2929 {
124+
return 0, false
125+
}
126+
cost = params.WarmStorageReadCostEIP2929
127+
}
128+
return cost, true
120129
}
121130

122131
// ValueTransferGas returns the gas to be charged for each of the currently
123-
// cold balance member fields of the caller and the callee accounts.
124-
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address, availableGas uint64) uint64 {
125-
_, expected1 := ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
126-
if expected1 > availableGas {
127-
return expected1
132+
// cold balance member fields of the caller and the callee accounts. If the
133+
// available gas is insufficient to cover the total cost, a false flag is
134+
// returned.
135+
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address, availableGas uint64) (uint64, bool) {
136+
var gas uint64
137+
cost, sufficient := ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
138+
if !sufficient {
139+
return 0, false
128140
}
129-
_, expected2 := ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas-expected1)
130-
if expected1+expected2 > availableGas {
131-
return params.WarmStorageReadCostEIP2929
141+
gas += cost
142+
143+
cost, sufficient = ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas-cost)
144+
if !sufficient {
145+
return 0, false
132146
}
133-
return expected1 + expected2
147+
gas += cost
148+
149+
return gas, true
134150
}
135151

136-
// ContractCreatePreCheckGas charges access costs before
137-
// a contract creation is initiated. It is just reads, because the
138-
// address collision is done before the transfer, and so no write
139-
// are guaranteed to happen at this point.
140-
func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address, availableGas uint64) uint64 {
141-
consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
142-
_, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, availableGas-consumed)
143-
return expected1 + expected2
152+
// ContractCreatePreCheckGas charges access costs before a contract creation is
153+
// initiated. It is just reads, because the address collision is done before
154+
// the transfer, and so no write are guaranteed to happen at this point.
155+
// If the available gas is insufficient to cover the total cost, a false flag is
156+
// returned.
157+
func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address, availableGas uint64) (uint64, bool) {
158+
var gas uint64
159+
cost, sufficient := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
160+
if !sufficient {
161+
return 0, false
162+
}
163+
gas += cost
164+
165+
cost, sufficient = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, availableGas-cost)
166+
if !sufficient {
167+
return 0, false
168+
}
169+
gas += cost
170+
171+
return gas, false
144172
}
145173

146174
// ContractCreateInitGas returns the access gas costs for the initialization of
147-
// a contract creation.
148-
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, availableGas uint64) (uint64, uint64) {
175+
// a contract creation. If the available gas is insufficient to cover the total
176+
// cost, a false flag is returned.
177+
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, availableGas uint64) (uint64, bool) {
149178
var gas uint64
150-
consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
151-
gas += consumed
152-
consumed, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, availableGas-consumed)
153-
gas += consumed
154-
return gas, expected1 + expected2
179+
cost, sufficient := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
180+
if !sufficient {
181+
return 0, false
182+
}
183+
gas += cost
184+
185+
cost, sufficient = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, availableGas-cost)
186+
if !sufficient {
187+
return 0, false
188+
}
189+
gas += cost
190+
191+
return gas, true
155192
}
156193

157194
// AddTxOrigin adds the member fields of the sender account to the access event list,
@@ -169,18 +206,28 @@ func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue, doesnt
169206
}
170207

171208
// SlotGas returns the amount of gas to be charged for a cold storage access.
172-
func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
209+
// If the available gas is insufficient to cover the total cost, a false flag
210+
// is returned.
211+
func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool, availableGas uint64, chargeWarmCosts bool) (uint64, bool) {
173212
treeIndex, subIndex := utils.StorageIndex(slot.Bytes())
174-
_, expected := ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, availableGas)
175-
if expected == 0 && chargeWarmCosts {
176-
expected = params.WarmStorageReadCostEIP2929
213+
cost, sufficient := ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, availableGas)
214+
if !sufficient {
215+
return 0, false
216+
}
217+
if cost == 0 && chargeWarmCosts {
218+
if availableGas < params.WarmStorageReadCostEIP2929 {
219+
return 0, false
220+
}
221+
cost = params.WarmStorageReadCostEIP2929
177222
}
178-
return expected
223+
return cost, true
179224
}
180225

181-
// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the
182-
// consumed and required gas.
183-
func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool, availableGas uint64) (uint64, uint64) {
226+
// touchAddressAndChargeGas adds a missing access event to the access list, if needed,
227+
// and returns the cold access cost to be charged. Additionally, it returns a flag
228+
// indicating whether there is enough available gas to cover the cost. If not,
229+
// the event will not be included.
230+
func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool, availableGas uint64) (uint64, bool) {
184231
branchKey := newBranchAccessKey(addr, treeIndex)
185232
chunkKey := newChunkAccessKey(branchKey, subIndex)
186233

@@ -224,8 +271,7 @@ func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex
224271
}
225272

226273
if availableGas < gas {
227-
// consumed != expected
228-
return availableGas, gas
274+
return 0, false
229275
}
230276

231277
if branchRead {
@@ -241,8 +287,8 @@ func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex
241287
ae.chunks[chunkKey] |= AccessWitnessWriteFlag
242288
}
243289

244-
// consumed == expected
245-
return gas, gas
290+
// consumed == wanted
291+
return gas, true
246292
}
247293

248294
type branchAccessKey struct {
@@ -269,16 +315,18 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey {
269315
return lk
270316
}
271317

272-
// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs
273-
func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool, availableGas uint64) (uint64, uint64) {
274-
// note that in the case where the copied code is outside the range of the
318+
// CodeChunksRangeGas is a helper function to touch every chunk in a code range
319+
// and charge witness gas costs. If the available gas is insufficient to cover
320+
// the total cost, a false flag is returned.
321+
func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool, availableGas uint64) (uint64, bool) {
322+
// Note that in the case where the copied code is outside the range of the
275323
// contract code but touches the last leaf with contract code in it,
276-
// we don't include the last leaf of code in the AccessWitness. The
324+
// we don't include the last leaf of code in the AccessWitness. The
277325
// reason that we do not need the last leaf is the account's code size
278326
// is already in the AccessWitness so a stateless verifier can see that
279327
// the code from the last leaf is not needed.
280328
if (codeLen == 0 && size == 0) || startPC > codeLen {
281-
return 0, 0
329+
return 0, true
282330
}
283331

284332
endPC := startPC + size
@@ -293,48 +341,55 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC,
293341
for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ {
294342
treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
295343
subIndex := byte((chunkNumber + 128) % 256)
296-
consumed, expected := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, availableGas)
344+
cost, sufficient := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, availableGas)
297345
// did we OOG ?
298-
if expected > consumed {
299-
return statelessGasCharged + consumed, statelessGasCharged + expected
346+
if !sufficient {
347+
return 0, false
300348
}
301349
var overflow bool
302-
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, consumed)
350+
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, cost)
303351
if overflow {
352+
//return 0, false
304353
panic("overflow when adding gas")
305354
}
306-
availableGas -= consumed
355+
availableGas -= cost
307356
}
308-
return statelessGasCharged, statelessGasCharged
357+
return statelessGasCharged, true
309358
}
310359

311360
// BasicDataGas adds the account's basic data to the accessed data, and returns the
312361
// amount of gas that it costs.
313362
// Note that an access in write mode implies an access in read mode, whereas an
314363
// access in read mode does not imply an access in write mode.
315-
func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
316-
_, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
317-
if expected == 0 && chargeWarmCosts {
364+
func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) (uint64, bool) {
365+
cost, sufficient := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
366+
if !sufficient {
367+
return 0, false
368+
}
369+
if cost == 0 && chargeWarmCosts {
318370
if availableGas < params.WarmStorageReadCostEIP2929 {
319-
return availableGas
371+
return 0, false
320372
}
321-
expected = params.WarmStorageReadCostEIP2929
373+
cost = params.WarmStorageReadCostEIP2929
322374
}
323-
return expected
375+
return cost, true
324376
}
325377

326378
// CodeHashGas adds the account's code hash to the accessed data, and returns the
327379
// amount of gas that it costs.
328380
// in write mode. If false, the charged gas corresponds to an access in read mode.
329381
// Note that an access in write mode implies an access in read mode, whereas an access in
330382
// read mode does not imply an access in write mode.
331-
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
332-
_, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas)
333-
if expected == 0 && chargeWarmCosts {
383+
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) (uint64, bool) {
384+
cost, sufficient := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas)
385+
if !sufficient {
386+
return 0, false
387+
}
388+
if cost == 0 && chargeWarmCosts {
334389
if availableGas < params.WarmStorageReadCostEIP2929 {
335-
return availableGas
390+
return 0, false
336391
}
337-
expected = params.WarmStorageReadCostEIP2929
392+
cost = params.WarmStorageReadCostEIP2929
338393
}
339-
return expected
394+
return cost, true
340395
}

core/state/access_events_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,50 +41,50 @@ func TestAccountHeaderGas(t *testing.T) {
4141
ae := NewAccessEvents(utils.NewPointCache(1024))
4242

4343
// Check cold read cost
44-
gas := ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
44+
gas, _ := ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
4545
if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want {
4646
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
4747
}
4848

4949
// Check warm read cost
50-
gas = ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
50+
gas, _ = ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
5151
if gas != 0 {
5252
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
5353
}
5454

5555
// Check cold read costs in the same group no longer incur the branch read cost
56-
gas = ae.CodeHashGas(testAddr, false, math.MaxUint64, false)
56+
gas, _ = ae.CodeHashGas(testAddr, false, math.MaxUint64, false)
5757
if gas != params.WitnessChunkReadCost {
5858
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
5959
}
6060

6161
// Check cold write cost
62-
gas = ae.BasicDataGas(testAddr, true, math.MaxUint64, false)
62+
gas, _ = ae.BasicDataGas(testAddr, true, math.MaxUint64, false)
6363
if want := params.WitnessBranchWriteCost + params.WitnessChunkWriteCost; gas != want {
6464
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
6565
}
6666

6767
// Check warm write cost
68-
gas = ae.BasicDataGas(testAddr, true, math.MaxUint64, false)
68+
gas, _ = ae.BasicDataGas(testAddr, true, math.MaxUint64, false)
6969
if gas != 0 {
7070
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
7171
}
7272

7373
// Check a write without a read charges both read and write costs
74-
gas = ae.BasicDataGas(testAddr2, true, math.MaxUint64, false)
74+
gas, _ = ae.BasicDataGas(testAddr2, true, math.MaxUint64, false)
7575
if want := params.WitnessBranchReadCost + params.WitnessBranchWriteCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want {
7676
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
7777
}
7878

7979
// Check that a write followed by a read charges nothing
80-
gas = ae.BasicDataGas(testAddr2, false, math.MaxUint64, false)
80+
gas, _ = ae.BasicDataGas(testAddr2, false, math.MaxUint64, false)
8181
if gas != 0 {
8282
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
8383
}
8484

8585
// Check that reading a slot from the account header only charges the
8686
// chunk read cost.
87-
gas = ae.SlotGas(testAddr, common.Hash{}, false, math.MaxUint64, false)
87+
gas, _ = ae.SlotGas(testAddr, common.Hash{}, false, math.MaxUint64, false)
8888
if gas != params.WitnessChunkReadCost {
8989
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
9090
}
@@ -119,23 +119,23 @@ func TestMessageCallGas(t *testing.T) {
119119
ae := NewAccessEvents(utils.NewPointCache(1024))
120120

121121
// Check cold read cost, without a value
122-
gas := ae.MessageCallGas(testAddr, math.MaxUint64)
122+
gas, _ := ae.MessageCallGas(testAddr, math.MaxUint64)
123123
if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want {
124124
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
125125
}
126126

127127
// Check that reading the basic data and code hash of the same account does not incur the branch read cost
128-
gas = ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
128+
gas, _ = ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
129129
if gas != 0 {
130130
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
131131
}
132-
gas = ae.CodeHashGas(testAddr, false, math.MaxUint64, false)
132+
gas, _ = ae.CodeHashGas(testAddr, false, math.MaxUint64, false)
133133
if gas != params.WitnessChunkReadCost {
134134
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
135135
}
136136

137137
// Check warm read cost
138-
gas = ae.MessageCallGas(testAddr, math.MaxUint64)
138+
gas, _ = ae.MessageCallGas(testAddr, math.MaxUint64)
139139
if gas != params.WarmStorageReadCostEIP2929 {
140140
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WarmStorageReadCostEIP2929)
141141
}

0 commit comments

Comments
 (0)