Smart Contract

DeFiActionsMathUtils

A.92195d814edf9cb0.DeFiActionsMathUtils

Valid From

122,643,804

Deployed

6d ago
Feb 22, 2026, 06:34:46 AM UTC

Dependents

0 imports
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