safe_math.gno
4.03 Kb · 164 lines
1package gnsmath
2
3import (
4 "math"
5
6 ufmt "gno.land/p/nt/ufmt/v0"
7
8 i256 "gno.land/p/gnoswap/int256"
9 u256 "gno.land/p/gnoswap/uint256"
10)
11
12// Range bounds used by the safe conversion helpers.
13const (
14 maxInt64Decimal = "9223372036854775807" // 2^63 - 1
15 maxInt128Decimal = "170141183460469231731687303715884105727" // 2^127 - 1
16)
17
18// maxInt128 is the largest value representable in a signed 128-bit integer.
19// It is used by SafeConvertToInt128 to bound liquidity deltas.
20func MaxInt128() *i256.Int {
21 return i256.MustFromDecimal(maxInt128Decimal)
22}
23
24// SafeAddInt64 returns a + b, panicking on int64 overflow or underflow.
25func SafeAddInt64(a, b int64) int64 {
26 if a > 0 && b > math.MaxInt64-a {
27 panic("int64 addition overflow")
28 }
29 if a < 0 && b < math.MinInt64-a {
30 panic("int64 addition underflow")
31 }
32 return a + b
33}
34
35// SafeSubInt64 returns a - b, panicking on int64 overflow or underflow.
36func SafeSubInt64(a, b int64) int64 {
37 if b > 0 && a < math.MinInt64+b {
38 panic("int64 subtraction underflow")
39 }
40 if b < 0 && a > math.MaxInt64+b {
41 panic("int64 subtraction overflow")
42 }
43 return a - b
44}
45
46// SafeMulInt64 returns a * b, panicking on int64 overflow or underflow.
47func SafeMulInt64(a, b int64) int64 {
48 if a == 0 || b == 0 {
49 return 0
50 }
51
52 if a > 0 && b > 0 {
53 if a > math.MaxInt64/b {
54 panic("int64 multiplication overflow")
55 }
56 } else if a < 0 && b < 0 {
57 if a < math.MaxInt64/b {
58 panic("int64 multiplication overflow")
59 }
60 } else if a > 0 && b < 0 {
61 if b < math.MinInt64/a {
62 panic("int64 multiplication underflow")
63 }
64 } else { // a < 0 && b > 0
65 if a < math.MinInt64/b {
66 panic("int64 multiplication underflow")
67 }
68 }
69
70 return a * b
71}
72
73// SafeMulDivInt64 returns (a * b) / c, computing the intermediate product in
74// 256-bit signed arithmetic so a*b need not fit in int64. The final quotient
75// must fit in int64, otherwise it panics. It panics on overflow.
76func SafeMulDivInt64(a, b, c int64) int64 {
77 if a == 0 || b == 0 {
78 return 0
79 }
80
81 result, overflow := i256.Zero().MulOverflow(i256.NewInt(a), i256.NewInt(b))
82 if overflow {
83 panic(errSafeMathOverflow)
84 }
85
86 result = i256.Zero().Div(result, i256.NewInt(c))
87 if !result.IsInt64() {
88 panic(errSafeMathOverflow)
89 }
90
91 return result.Int64()
92}
93
94// SafeAbsInt64 returns the absolute value of a, panicking when a is math.MinInt64
95// since its absolute value is not representable in int64.
96func SafeAbsInt64(a int64) int64 {
97 if a == math.MinInt64 {
98 panic(errSafeMathOverflow)
99 }
100 if a < 0 {
101 return -a
102 }
103 return a
104}
105
106// SafeAddUint64 returns a + b, panicking on uint64 overflow.
107func SafeAddUint64(a, b uint64) uint64 {
108 if a > math.MaxUint64-b {
109 panic("uint64 addition overflow")
110 }
111 return a + b
112}
113
114// SafeSubUint64 returns a - b, panicking on uint64 underflow.
115func SafeSubUint64(a, b uint64) uint64 {
116 if a < b {
117 panic("uint64 subtraction underflow")
118 }
119 return a - b
120}
121
122// SafeUint64ToInt64 converts a uint64 to int64, panicking when the value
123// exceeds the int64 range (2^63 - 1).
124func SafeUint64ToInt64(value uint64) int64 {
125 if value > uint64(math.MaxInt64) {
126 panic(ufmt.Sprintf(
127 "amount(%d) overflows int64 range (max: %s)",
128 value, maxInt64Decimal,
129 ))
130 }
131 return int64(value)
132}
133
134// SafeConvertToInt64 converts a *u256.Uint to int64, panicking when the value
135// is nil or exceeds the int64 range (2^63 - 1).
136func SafeConvertToInt64(value *u256.Uint) int64 {
137 if value == nil {
138 panic("SafeConvertToInt64: value is nil")
139 }
140 res, overflow := value.Uint64WithOverflow()
141 if overflow || res > uint64(math.MaxInt64) {
142 panic(ufmt.Sprintf(
143 "amount(%s) overflows int64 range (max: %s)",
144 value.ToString(), maxInt64Decimal,
145 ))
146 }
147 return int64(res)
148}
149
150// SafeConvertToInt128 converts a *u256.Uint to an *i256.Int, panicking when the
151// value exceeds the signed int128 range (2^127 - 1).
152func SafeConvertToInt128(value *u256.Uint) *i256.Int {
153 if value == nil {
154 panic("SafeConvertToInt128: value is nil")
155 }
156 converted := i256.FromUint256(value)
157 if converted.Gt(MaxInt128()) {
158 panic(ufmt.Sprintf(
159 "amount(%s) overflows int128 range",
160 value.ToString(),
161 ))
162 }
163 return converted
164}