Skip to content

Commit f1a53d4

Browse files
authored
Merge pull request #592 from OffchainLabs/pmikolajczyk/saturation
multigas: Extract saturating arithmetics
2 parents 9032013 + 2d25a65 commit f1a53d4

File tree

1 file changed

+77
-116
lines changed

1 file changed

+77
-116
lines changed

arbitrum/multigas/resources.go

Lines changed: 77 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"fmt"
66
"io"
7+
"math"
78
"math/bits"
89

910
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -67,12 +68,10 @@ func NewMultiGas(kind ResourceKind, amount uint64) MultiGas {
6768
func MultiGasFromPairs(pairs ...Pair) MultiGas {
6869
var mg MultiGas
6970
for _, p := range pairs {
70-
newTotal, c := bits.Add64(mg.total, p.Amount, 0)
71-
if c != 0 {
72-
panic("multigas overflow")
73-
}
7471
mg.gas[p.Kind] = p.Amount
75-
mg.total = newTotal
72+
}
73+
if mg.recomputeTotal() {
74+
panic("multigas overflow")
7675
}
7776
return mg
7877
}
@@ -120,13 +119,14 @@ func (z MultiGas) Get(kind ResourceKind) uint64 {
120119
// With returns a copy of z with the given resource kind set to amount.
121120
// The total is adjusted accordingly. It returns the updated value and true if an overflow occurred.
122121
func (z MultiGas) With(kind ResourceKind, amount uint64) (MultiGas, bool) {
123-
res := z
124-
newTotal, c := bits.Add64(z.total-z.gas[kind], amount, 0)
125-
if c != 0 {
122+
res, overflow := z, false
123+
124+
res.total, overflow = saturatingScalarAdd(z.total-z.gas[kind], amount)
125+
if overflow {
126126
return z, true
127127
}
128+
128129
res.gas[kind] = amount
129-
res.total = newTotal
130130
return res, false
131131
}
132132

@@ -146,27 +146,23 @@ func (z MultiGas) WithRefund(amount uint64) MultiGas {
146146
// added to the values from x. It returns the updated value and true if
147147
// an overflow occurred.
148148
func (z MultiGas) SafeAdd(x MultiGas) (MultiGas, bool) {
149-
res := z
149+
res, overflow := z, false
150150

151151
for i := 0; i < int(NumResourceKind); i++ {
152-
v, c := bits.Add64(res.gas[i], x.gas[i], 0)
153-
if c != 0 {
152+
res.gas[i], overflow = saturatingScalarAdd(res.gas[i], x.gas[i])
153+
if overflow {
154154
return z, true
155155
}
156-
res.gas[i] = v
157156
}
158157

159-
t, c := bits.Add64(res.total, x.total, 0)
160-
if c != 0 {
158+
res.total, overflow = saturatingScalarAdd(res.total, x.total)
159+
if overflow {
161160
return z, true
162161
}
163-
res.total = t
164-
165-
r, c := bits.Add64(res.refund, x.refund, 0)
166-
if c != 0 {
162+
res.refund, overflow = saturatingScalarAdd(res.refund, x.refund)
163+
if overflow {
167164
return z, true
168165
}
169-
res.refund = r
170166

171167
return res, false
172168
}
@@ -178,25 +174,11 @@ func (z MultiGas) SaturatingAdd(x MultiGas) MultiGas {
178174
res := z
179175

180176
for i := 0; i < int(NumResourceKind); i++ {
181-
if v, c := bits.Add64(res.gas[i], x.gas[i], 0); c != 0 {
182-
res.gas[i] = ^uint64(0) // clamp
183-
} else {
184-
res.gas[i] = v
185-
}
186-
}
187-
188-
if t, c := bits.Add64(res.total, x.total, 0); c != 0 {
189-
res.total = ^uint64(0) // clamp
190-
} else {
191-
res.total = t
192-
}
193-
194-
if r, c := bits.Add64(res.refund, x.refund, 0); c != 0 {
195-
res.refund = ^uint64(0) // clamp
196-
} else {
197-
res.refund = r
177+
res.gas[i], _ = saturatingScalarAdd(res.gas[i], x.gas[i])
198178
}
199179

180+
res.total, _ = saturatingScalarAdd(res.total, x.total)
181+
res.refund, _ = saturatingScalarAdd(res.refund, x.refund)
200182
return res
201183
}
202184

@@ -205,49 +187,31 @@ func (z MultiGas) SaturatingAdd(x MultiGas) MultiGas {
205187
// This is a hot-path helper; the public immutable API remains preferred elsewhere.
206188
func (z *MultiGas) SaturatingAddInto(x MultiGas) {
207189
for i := 0; i < int(NumResourceKind); i++ {
208-
if v, c := bits.Add64(z.gas[i], x.gas[i], 0); c != 0 {
209-
z.gas[i] = ^uint64(0) // clamp
210-
} else {
211-
z.gas[i] = v
212-
}
213-
}
214-
if t, c := bits.Add64(z.total, x.total, 0); c != 0 {
215-
z.total = ^uint64(0) // clamp
216-
} else {
217-
z.total = t
218-
}
219-
if r, c := bits.Add64(z.refund, x.refund, 0); c != 0 {
220-
z.refund = ^uint64(0) // clamp
221-
} else {
222-
z.refund = r
190+
z.gas[i], _ = saturatingScalarAdd(z.gas[i], x.gas[i])
223191
}
192+
z.total, _ = saturatingScalarAdd(z.total, x.total)
193+
z.refund, _ = saturatingScalarAdd(z.refund, x.refund)
224194
}
225195

226196
// SafeSub returns a copy of z with the per-kind, total, and refund gas
227197
// subtracted by the values from x. It returns the updated value and true if
228198
// a underflow occurred.
229199
func (z MultiGas) SafeSub(x MultiGas) (MultiGas, bool) {
230-
res := z
200+
res, underflow := z, false
231201

232202
for i := 0; i < int(NumResourceKind); i++ {
233-
v, b := bits.Sub64(res.gas[i], x.gas[i], 0)
234-
if b != 0 {
203+
res.gas[i], underflow = saturatingScalarSub(res.gas[i], x.gas[i])
204+
if underflow {
235205
return z, true
236206
}
237-
res.gas[i] = v
238207
}
239208

240-
t, b := bits.Sub64(res.total, x.total, 0)
241-
if b != 0 {
209+
res.refund, underflow = saturatingScalarSub(res.refund, x.refund)
210+
if underflow {
242211
return z, true
243212
}
244-
res.total = t
245213

246-
r, b := bits.Sub64(res.refund, x.refund, 0)
247-
if b != 0 {
248-
return z, true
249-
}
250-
res.refund = r
214+
res.recomputeTotal()
251215

252216
return res, false
253217
}
@@ -257,68 +221,39 @@ func (z MultiGas) SafeSub(x MultiGas) (MultiGas, bool) {
257221
// clamped to zero.
258222
func (z MultiGas) SaturatingSub(x MultiGas) MultiGas {
259223
res := z
260-
261224
for i := 0; i < int(NumResourceKind); i++ {
262-
if v, c := bits.Sub64(res.gas[i], x.gas[i], 0); c != 0 {
263-
res.gas[i] = uint64(0) // clamp
264-
} else {
265-
res.gas[i] = v
266-
}
225+
res.gas[i], _ = saturatingScalarSub(res.gas[i], x.gas[i])
267226
}
268-
269-
if t, c := bits.Sub64(res.total, x.total, 0); c != 0 {
270-
res.total = uint64(0) // clamp
271-
} else {
272-
res.total = t
273-
}
274-
275-
if r, c := bits.Sub64(res.refund, x.refund, 0); c != 0 {
276-
res.refund = uint64(0) // clamp
277-
} else {
278-
res.refund = r
279-
}
280-
227+
res.refund, _ = saturatingScalarSub(res.refund, x.refund)
228+
res.recomputeTotal()
281229
return res
282230
}
283231

284232
// SafeIncrement returns a copy of z with the given resource kind
285233
// and the total incremented by gas. It returns the updated value and true if
286234
// an overflow occurred.
287235
func (z MultiGas) SafeIncrement(kind ResourceKind, gas uint64) (MultiGas, bool) {
288-
res := z
236+
res, overflow := z, false
289237

290-
newValue, c := bits.Add64(z.gas[kind], gas, 0)
291-
if c != 0 {
292-
return res, true
238+
res.gas[kind], overflow = saturatingScalarAdd(z.gas[kind], gas)
239+
if overflow {
240+
return z, true
293241
}
294242

295-
newTotal, c := bits.Add64(z.total, gas, 0)
296-
if c != 0 {
297-
return res, true
243+
res.total, overflow = saturatingScalarAdd(z.total, gas)
244+
if overflow {
245+
return z, true
298246
}
299247

300-
res.gas[kind] = newValue
301-
res.total = newTotal
302248
return res, false
303249
}
304250

305251
// SaturatingIncrement returns a copy of z with the given resource kind
306252
// and the total incremented by gas. On overflow, the field(s) are clamped to MaxUint64.
307253
func (z MultiGas) SaturatingIncrement(kind ResourceKind, gas uint64) MultiGas {
308254
res := z
309-
310-
if v, c := bits.Add64(res.gas[kind], gas, 0); c != 0 {
311-
res.gas[kind] = ^uint64(0) // clamp
312-
} else {
313-
res.gas[kind] = v
314-
}
315-
316-
if t, c := bits.Add64(res.total, gas, 0); c != 0 {
317-
res.total = ^uint64(0) // clamp
318-
} else {
319-
res.total = t
320-
}
321-
255+
res.gas[kind], _ = saturatingScalarAdd(z.gas[kind], gas)
256+
res.total, _ = saturatingScalarAdd(z.total, gas)
322257
return res
323258
}
324259

@@ -351,17 +286,8 @@ func (z MultiGas) SaturatingDecrement(kind ResourceKind, gas uint64) MultiGas {
351286
// Unlike SaturatingIncrement, this method mutates the receiver directly and
352287
// is intended for VM hot paths where avoiding value copies is critical.
353288
func (z *MultiGas) SaturatingIncrementInto(kind ResourceKind, gas uint64) {
354-
if v, c := bits.Add64(z.gas[kind], gas, 0); c != 0 {
355-
z.gas[kind] = ^uint64(0)
356-
} else {
357-
z.gas[kind] = v
358-
}
359-
360-
if t, c := bits.Add64(z.total, gas, 0); c != 0 {
361-
z.total = ^uint64(0)
362-
} else {
363-
z.total = t
364-
}
289+
z.gas[kind], _ = saturatingScalarAdd(z.gas[kind], gas)
290+
z.total, _ = saturatingScalarAdd(z.total, gas)
365291
}
366292

367293
// SingleGas returns the single-dimensional total gas.
@@ -477,3 +403,38 @@ func (z *MultiGas) DecodeRLP(s *rlp.Stream) error {
477403
z.refund = refund
478404
return nil
479405
}
406+
407+
// recomputeTotal recomputes the total gas from the per-kind amounts. Returns
408+
// true if an overflow occurred (and the total was set to MaxUint64).
409+
func (z *MultiGas) recomputeTotal() (overflow bool) {
410+
z.total = 0
411+
for i := 0; i < int(NumResourceKind); i++ {
412+
z.total, overflow = saturatingScalarAdd(z.total, z.gas[i])
413+
if overflow {
414+
return
415+
}
416+
}
417+
return
418+
}
419+
420+
// saturatingScalarAdd adds two uint64 values, returning the sum and a boolean
421+
// indicating whether an overflow occurred. If an overflow occurs, the sum is
422+
// set to math.MaxUint64.
423+
func saturatingScalarAdd(a, b uint64) (uint64, bool) {
424+
sum, carry := bits.Add64(a, b, 0)
425+
if carry != 0 {
426+
return math.MaxUint64, true
427+
}
428+
return sum, false
429+
}
430+
431+
// saturatingScalarSub subtracts two uint64 values, returning the difference and a boolean
432+
// indicating whether an underflow occurred. If an underflow occurs, the difference is
433+
// set to 0.
434+
func saturatingScalarSub(a, b uint64) (uint64, bool) {
435+
diff, borrow := bits.Sub64(a, b, 0)
436+
if borrow != 0 {
437+
return 0, true
438+
}
439+
return diff, false
440+
}

0 commit comments

Comments
 (0)