Smart Contract
DeFiActionsMathUtils
A.92195d814edf9cb0.DeFiActionsMathUtils
1/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
2/// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT
3/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
4///
5/// DeFiActionsMathUtils
6///
7/// This contract contains mathematical utility methods for DeFiActions connectors
8/// using UInt128 for high-precision fixed-point arithmetic.
9///
10access(all) contract DeFiActionsMathUtils {
11
12 /// Constant for 10^24 (used for 24-decimal fixed-point math)
13 access(all) let e24: UInt128
14 /// Constant for 10^8 (UFix64 precision)
15 access(all) let e8: UInt128
16 /// Standard decimal precision for internal calculations
17 access(all) let decimals: UInt8
18 /// UFix64 decimal precision for internal calculations
19 access(self) let ufix64Decimals: UInt8
20 /// Scale factor for UInt128 <-> UFix64 conversions
21 access(self) let scaleFactor: UInt128
22
23 access(all) enum RoundingMode: UInt8 {
24 /// Rounds down to the nearest decimal
25 access(all) case RoundDown
26 /// Rounds up to the nearest decimal
27 access(all) case RoundUp
28 /// Normal rounding: < 5 - round down | >= 5 - round up
29 access(all) case RoundHalfUp
30 /// TODO: comment about rounding pattern
31 access(all) case RoundEven
32 }
33
34 /************************
35 * CONVERSION UTILITIES *
36 ************************/
37
38 /// Converts a UFix64 value to UInt128 with 24 decimal precision
39 ///
40 /// @param value: The UFix64 value to convert
41 /// @return: The UInt128 value scaled to 24 decimals
42 access(all) view fun toUInt128(_ value: UFix64): UInt128 {
43 let rawUInt64 = UInt64.fromBigEndianBytes(value.toBigEndianBytes())!
44 let scaledValue: UInt128 = UInt128(rawUInt64) * self.scaleFactor
45
46 return scaledValue
47 }
48
49 /// Converts a UInt128 value with 24 decimal precision to UFix64
50 ///
51 /// @param value: The UInt128 value to convert
52 /// @return: The UFix64 value
53 access(all) view fun toUFix64(_ value: UInt128, _ roundingMode: RoundingMode): UFix64 {
54 var integerPart = value / self.e24
55 var fractionalPart = value % self.e24 / self.scaleFactor
56 let remainder = (value % self.e24) % self.scaleFactor
57
58 if self.shouldRoundUp(roundingMode, fractionalPart, remainder) {
59 fractionalPart = fractionalPart + UInt128(1)
60
61 if fractionalPart >= self.e8 {
62 fractionalPart = fractionalPart - self.e8
63 integerPart = integerPart + UInt128(1)
64 }
65 }
66
67
68 self.assertWithinUFix64Bounds(integerPart, fractionalPart, value)
69
70 let scaled = UFix64(integerPart) + UFix64(fractionalPart)/UFix64(self.e8)
71
72 // Convert to UFix64 — `scaled` is now at 1e8 base so fractional precision is preserved
73 return UFix64(scaled)
74 }
75
76 /// Helper to determine rounding condition
77 access(self) view fun shouldRoundUp(
78 _ roundingMode: RoundingMode,
79 _ fractionalPart: UInt128,
80 _ remainder: UInt128,
81 ): Bool {
82 switch roundingMode {
83 case self.RoundingMode.RoundUp:
84 return remainder > UInt128(0)
85
86 case self.RoundingMode.RoundHalfUp:
87 return remainder >= self.scaleFactor / UInt128(2)
88
89 case self.RoundingMode.RoundEven:
90 return remainder > self.scaleFactor / UInt128(2) ||
91 (remainder == self.scaleFactor / UInt128(2) && fractionalPart % UInt128(2) != UInt128(0))
92 }
93 return false
94 }
95
96 /// Helper to handle overflow assertion
97 access(self) view fun assertWithinUFix64Bounds(
98 _ integerPart: UInt128,
99 _ fractionalPart: UInt128,
100 _ originalValue: UInt128
101 ) {
102 assert(
103 integerPart <= UInt128(UFix64.max),
104 message: "Integer part \(integerPart.toString()) exceeds UFix64 max"
105 )
106
107 let MAX_FRACTIONAL_PART = self.toUInt128(0.09551616)
108 assert(
109 integerPart != UInt128(UFix64.max) || fractionalPart < MAX_FRACTIONAL_PART,
110 message: "Fractional part \(fractionalPart.toString()) of scaled integer value \(originalValue.toString()) exceeds max UFix64"
111 )
112 }
113
114 /***********************
115 * FIXED POINT MATH *
116 ***********************/
117
118 /// Multiplies two 24-decimal fixed-point numbers
119 ///
120 /// @param x: First operand (scaled by 10^24)
121 /// @param y: Second operand (scaled by 10^24)
122 /// @return: Product scaled by 10^24
123 access(all) view fun mul(_ x: UInt128, _ y: UInt128): UInt128 {
124 return UInt128(UInt256(x) * UInt256(y) / UInt256(self.e24))
125 }
126
127 /// Divides two 24-decimal fixed-point numbers
128 ///
129 /// @param x: Dividend (scaled by 10^24)
130 /// @param y: Divisor (scaled by 10^24)
131 /// @return: Quotient scaled by 10^24
132 access(all) view fun div(_ x: UInt128, _ y: UInt128): UInt128 {
133 pre {
134 y > 0: "Division by zero"
135 }
136 return UInt128((UInt256(x) * UInt256(self.e24)) / UInt256(y))
137 }
138
139 /// Divides two UFix64 values with configurable rounding mode.
140 ///
141 /// Converts both UFix64 inputs to internal UInt128 (24-decimal fixed-point),
142 /// performs division, then converts the result back to UFix64, applying the chosen rounding mode.
143 ///
144 /// @param x: Dividend (UFix64)
145 /// @param y: Divisor (UFix64)
146 /// @param roundingMode: Rounding mode to use (RoundHalfUp, RoundUp, RoundDown, etc.)
147 /// @return: UFix64 quotient, rounded per roundingMode
148 access(self) view fun divUFix64(_ x: UFix64, _ y: UFix64, _ roundingMode: RoundingMode): UFix64 {
149 pre {
150 y > 0.0: "Division by zero"
151 }
152 let uintX: UInt128 = self.toUInt128(x)
153 let uintY: UInt128 = self.toUInt128(y)
154 let uintResult = self.div(uintX, uintY)
155 let result = self.toUFix64(uintResult, roundingMode)
156
157 return result
158 }
159
160 /// Divide two UFix64 values and round to the nearest (ties go up).
161 ///
162 /// Equivalent to dividing with standard financial "round to nearest" mode.
163 access(all) view fun divUFix64WithRounding(_ x: UFix64, _ y: UFix64): UFix64 {
164 return self.divUFix64(x, y, self.RoundingMode.RoundHalfUp)
165 }
166
167 /// Divide two UFix64 values and always round up (ceiling).
168 ///
169 /// Use for cases where over-estimation is safer (e.g., fee calculations).
170 access(all) view fun divUFix64WithRoundingUp(_ x: UFix64, _ y: UFix64): UFix64 {
171 return self.divUFix64(x, y, self.RoundingMode.RoundUp)
172 }
173
174 /// Divide two UFix64 values and always round down (truncate/floor).
175 ///
176 /// Use for cases where under-estimation is safer (e.g., payout calculations).
177 access(all) view fun divUFix64WithRoundingDown(_ x: UFix64, _ y: UFix64): UFix64 {
178 return self.divUFix64(x, y, self.RoundingMode.RoundDown)
179 }
180
181 /*******************
182 * HELPER METHODS *
183 *******************/
184
185 /// Rounds a UInt128 value (24 decimals) to a UFix64 value (8 decimals) using round-to-nearest (ties go up).
186 ///
187 /// Example conversions:
188 /// 1e24 -> 1.0
189 /// 123456000000000000000 -> 1.23456000
190 /// 123456789012345678901 -> 1.23456789
191 /// 123456789999999999999 -> 1.23456790 (shows rounding)
192 ///
193 /// @param value: The UInt128 value to convert and round
194 /// @return: The UFix64 value, rounded to the nearest 8 decimals
195 access(all) view fun toUFix64Round(_ value: UInt128): UFix64 {
196 // Use standard round-half-up (nearest neighbor; ties round away from zero)
197 return self.toUFix64(value, self.RoundingMode.RoundHalfUp)
198 }
199
200 /// Rounds a UInt128 value (24 decimals) to UFix64 (8 decimals), always rounding down (truncate).
201 ///
202 /// Use when you want to avoid overestimating user balances or payouts.
203 access(all) view fun toUFix64RoundDown(_ value: UInt128): UFix64 {
204 return self.toUFix64(value, self.RoundingMode.RoundDown)
205 }
206
207 /// Rounds a UInt128 value (24 decimals) to UFix64 (8 decimals), always rounding up (ceiling).
208 ///
209 /// Use when you want to avoid underestimating liabilities or fees.
210 access(all) view fun toUFix64RoundUp(_ value: UInt128): UFix64 {
211 return self.toUFix64(value, self.RoundingMode.RoundUp)
212 }
213
214 /// Raises base to the power of exponent
215 ///
216 /// @param base: The base number
217 /// @param to: The exponent
218 /// @return: base^to
219 access(self) view fun pow(_ base: UInt128, to: UInt8): UInt128 {
220 if to == 0 {
221 return 1
222 }
223
224 var accum = base
225 var exp: UInt8 = to
226 var r: UInt128 = 1
227 while exp != 0 {
228 if exp & 1 == 1 {
229 r = r * UInt128(accum)
230 }
231 accum = accum * accum
232 exp = exp / 2
233 }
234
235 return r
236 }
237
238 init() {
239 self.e24 = 1_000_000_000_000_000_000_000_000
240 self.e8 = 100_000_000
241 self.decimals = 24
242 self.ufix64Decimals = 8
243 self.scaleFactor = self.pow(10, to: self.decimals - self.ufix64Decimals)
244 }
245}
246