fullmath.gno
3.32 Kb · 98 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
21// MulDiv calculates (a * b) / denominator with full 512-bit intermediate precision.
22// Panics if denominator is zero or if the result overflows 256 bits.
23func MulDiv(a, b, denominator *Uint) *Uint {
24 if denominator.IsZero() {
25 panic("denominator must be greater than 0")
26 }
27
28 // 512-bit product (8 limbs of 64 bits)
29 p := umul(a, b)
30
31 if (p[4] | p[5] | p[6] | p[7]) == 0 {
32 var lo Uint
33 lo[0], lo[1], lo[2], lo[3] = p[0], p[1], p[2], p[3]
34 return new(Uint).Div(&lo, denominator)
35 }
36
37 // optional early overflow check:
38 // If hi >= denominator then floor((hi*2^256 + lo) / denominator) >= 2^256, which is overflow.
39 {
40 var hi Uint
41 hi[0], hi[1], hi[2], hi[3] = p[4], p[5], p[6], p[7]
42 if denominator.Lte(&hi) {
43 panic("overflow: denominator(" + denominator.ToString() + ") must be greater than hi(" + hi.ToString() + ")")
44 }
45 }
46
47 // perform 512 / 256 division
48 // udivrem stores quotient into `quot` (len(u) - len(d) + 1 words)
49 // we pass 8 words to be safe.
50 var quot [8]uint64
51 udivrem(quot[:], p[:], denominator) // ignore remainder
52
53 if (quot[4] | quot[5] | quot[6] | quot[7]) != 0 {
54 panic("uint256: MulDiv overflow (high quotient words non-zero)")
55 }
56
57 // return lower 256 bits of quotient
58 var z Uint
59 copy(z[:], quot[:4])
60 return &z
61}
62
63// MulDivRoundingUp calculates ceil((a * b) / denominator) with full 512-bit intermediate precision.
64// Panics if denominator is zero or if the result overflows 256 bits.
65func MulDivRoundingUp(a, b, denominator *Uint) *Uint {
66 result := MulDiv(a, b, denominator)
67
68 // Check if there's a remainder
69 mulModResult := new(Uint).MulMod(a, b, denominator)
70
71 // If there's no remainder, return the result as-is
72 if mulModResult.IsZero() {
73 return result
74 }
75
76 // Add 1 to round up, but check for overflow
77 if result.Eq(MaxUint256()) {
78 panic("overflow: result(" + result.ToString() + ") + 1 would exceed MAX_UINT256")
79 }
80
81 return result.Add(result, &Uint{1, 0, 0, 0})
82}
83
84// DivRoundingUp calculates ceil(x / y) and returns the result.
85// Panics if y is zero.
86func DivRoundingUp(x, y *Uint) *Uint {
87 div := new(Uint).Div(x, y)
88 mod := new(Uint).Mod(x, y)
89 return new(Uint).Add(div, gt(mod, &Uint{0, 0, 0, 0}))
90}
91
92// gt returns One() if x > y, otherwise returns Zero().
93func gt(x, y *Uint) *Uint {
94 if x.Gt(y) {
95 return &Uint{1, 0, 0, 0}
96 }
97 return &Uint{0, 0, 0, 0}
98}