fullmath.gno
3.44 Kb · 104 lines
1// REF: https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/FullMath.sol
2
3// fullmath implements Uniswap V3's FullMath library.
4//
5// This library provides advanced fixed-point math operations that are essential
6// for Uniswap V3's tick math and liquidity calculations. It enables precise
7// calculations of (a * b / denominator) with full 512-bit intermediate precision.
8//
9// NOTE: Unlike other arithmetic functions in the uint256 package that return errors,
10// functions in this file panic on invalid inputs to maintain behavioral compatibility
11// with the original Solidity implementation which uses require() statements.
12//
13// This design choice is intentional because:
14// 1. These functions are typically used in hot paths where error handling would add overhead
15// 2. Invalid inputs (like zero denominator) represent programming errors, not runtime conditions
16// 3. Staying close to the Solidity implementation makes protocol porting more reliable
17//
18// If you need error-returning versions, wrap these functions with appropriate error handling.
19package uint256
20
21var MAX_UINT256_FROM_DECIMAL *Uint
22
23func init() {
24 MAX_UINT256_FROM_DECIMAL = MustFromDecimal(MAX_UINT256)
25}
26
27// MulDiv calculates (a * b) / denominator with full 512-bit intermediate precision.
28// Panics if denominator is zero or if the result overflows 256 bits.
29func MulDiv(a, b, denominator *Uint) *Uint {
30 if denominator.IsZero() {
31 panic("denominator must be greater than 0")
32 }
33
34 // 512-bit product (8 limbs of 64 bits)
35 p := umul(a, b)
36
37 if (p[4] | p[5] | p[6] | p[7]) == 0 {
38 var lo Uint
39 lo[0], lo[1], lo[2], lo[3] = p[0], p[1], p[2], p[3]
40 return new(Uint).Div(&lo, denominator)
41 }
42
43 // optional early overflow check:
44 // If hi >= denominator then floor((hi*2^256 + lo) / denominator) >= 2^256, which is overflow.
45 {
46 var hi Uint
47 hi[0], hi[1], hi[2], hi[3] = p[4], p[5], p[6], p[7]
48 if denominator.Lte(&hi) {
49 panic("overflow: denominator(" + denominator.ToString() + ") must be greater than hi(" + hi.ToString() + ")")
50 }
51 }
52
53 // perform 512 / 256 division
54 // udivrem stores quotient into `quot` (len(u) - len(d) + 1 words)
55 // we pass 8 words to be safe.
56 var quot [8]uint64
57 udivrem(quot[:], p[:], denominator) // ignore remainder
58
59 if (quot[4] | quot[5] | quot[6] | quot[7]) != 0 {
60 panic("uint256: MulDiv overflow (high quotient words non-zero)")
61 }
62
63 // return lower 256 bits of quotient
64 var z Uint
65 copy(z[:], quot[:4])
66 return &z
67}
68
69// MulDivRoundingUp calculates ceil((a * b) / denominator) with full 512-bit intermediate precision.
70// Panics if denominator is zero or if the result overflows 256 bits.
71func MulDivRoundingUp(a, b, denominator *Uint) *Uint {
72 result := MulDiv(a, b, denominator)
73
74 // Check if there's a remainder
75 mulModResult := new(Uint).MulMod(a, b, denominator)
76
77 // If there's no remainder, return the result as-is
78 if mulModResult.IsZero() {
79 return result
80 }
81
82 // Add 1 to round up, but check for overflow
83 if result.Eq(MAX_UINT256_FROM_DECIMAL) {
84 panic("overflow: result(" + result.ToString() + ") + 1 would exceed MAX_UINT256")
85 }
86
87 return result.Add(result, &Uint{1, 0, 0, 0})
88}
89
90// DivRoundingUp calculates ceil(x / y) and returns the result.
91// Panics if y is zero.
92func DivRoundingUp(x, y *Uint) *Uint {
93 div := new(Uint).Div(x, y)
94 mod := new(Uint).Mod(x, y)
95 return new(Uint).Add(div, gt(mod, &Uint{0, 0, 0, 0}))
96}
97
98// gt returns One() if x > y, otherwise returns Zero().
99func gt(x, y *Uint) *Uint {
100 if x.Gt(y) {
101 return &Uint{1, 0, 0, 0}
102 }
103 return &Uint{0, 0, 0, 0}
104}