Smart Contract

DeFiMath

A.ca7ee55e4fc3251a.DeFiMath

Valid From

134,619,500

Deployed

5d ago
Feb 22, 2026, 02:49:55 AM UTC

Dependents

12 imports
1/// DeFiMath: High-precision DeFi math utilities using 128-bit fixed-point arithmetic
2///
3/// This contract provides utilities for:
4/// - Slippage calculations using basis points (bps)
5/// - Weighted average price tracking using FP128
6/// - Safe fixed-point arithmetic operations
7///
8/// Educational Notes:
9/// - Basis Points (bps): 1 bps = 0.01%, 100 bps = 1%
10/// - Fixed-Point 128: Uses 128-bit integers to represent decimals with high precision
11/// - All price calculations maintain precision to avoid rounding errors in DCA
12access(all) contract DeFiMath {
13
14    /// Basis points scale: 10000 bps = 100%
15    access(all) let BPS_SCALE: UInt64
16
17    /// Fixed-point 128 scale factor (2^64 for 64.64 fixed point representation)
18    access(all) let FP128_SCALE: UInt128
19
20    init() {
21        self.BPS_SCALE = 10000
22        self.FP128_SCALE = 18446744073709551616 // 2^64
23    }
24
25    /// Calculate minimum output amount after applying slippage protection
26    ///
27    /// @param amountIn: The input amount (in native token units)
28    /// @param expectedPrice: Expected output per input unit (FP128 format)
29    /// @param slippageBps: Maximum acceptable slippage in basis points
30    /// @return minAmountOut: Minimum acceptable output amount
31    ///
32    /// Example:
33    /// - amountIn = 10.0 FLOW (10_00000000 in UFix64)
34    /// - expectedPrice = 2.5 BEAVER per FLOW (in FP128)
35    /// - slippageBps = 100 (1% slippage)
36    /// - Result: minAmountOut = 24.75 BEAVER (2.5 * 10 * 0.99)
37    access(all) fun calculateMinOutWithSlippage(
38        amountIn: UFix64,
39        expectedPriceFP128: UInt128,
40        slippageBps: UInt64
41    ): UFix64 {
42        pre {
43            slippageBps <= self.BPS_SCALE: "Slippage cannot exceed 100%"
44            expectedPriceFP128 > 0: "Expected price must be positive"
45        }
46
47        // Calculate expected output in FP128
48        let amountInScaled = UInt128(amountIn * 100000000.0) // Convert UFix64 to UInt128 (8 decimals)
49        let expectedOutFP128 = (amountInScaled * expectedPriceFP128) / self.FP128_SCALE
50
51        // Apply slippage: minOut = expectedOut * (BPS_SCALE - slippageBps) / BPS_SCALE
52        let slippageMultiplier = UInt128(self.BPS_SCALE - slippageBps)
53        let minOutFP128 = (expectedOutFP128 * slippageMultiplier) / UInt128(self.BPS_SCALE)
54
55        // Convert back to UFix64 (round down for safety)
56        return UFix64(minOutFP128) / 100000000.0
57    }
58
59    /// Update weighted average price using new execution data
60    ///
61    /// Formula: newAvg = (prevAvg * totalPrevIn + executionPrice * newIn) / (totalPrevIn + newIn)
62    ///
63    /// @param previousAvgPriceFP128: Previous weighted average price (FP128)
64    /// @param totalPreviousIn: Total amount previously invested
65    /// @param newAmountIn: New investment amount in this execution
66    /// @param newAmountOut: Output amount received in this execution
67    /// @return Updated weighted average price in FP128 format
68    ///
69    /// Educational Note:
70    /// This calculation maintains a running weighted average of execution prices,
71    /// which is crucial for DCA performance tracking. Each purchase is weighted
72    /// by the amount invested.
73    access(all) fun updateWeightedAveragePriceFP128(
74        previousAvgPriceFP128: UInt128,
75        totalPreviousIn: UFix64,
76        newAmountIn: UFix64,
77        newAmountOut: UFix64
78    ): UInt128 {
79        pre {
80            newAmountIn > 0.0: "New amount in must be positive"
81            newAmountOut > 0.0: "New amount out must be positive"
82        }
83
84        // If this is the first execution, return the execution price directly
85        if totalPreviousIn == 0.0 {
86            return self.calculatePriceFP128(amountIn: newAmountIn, amountOut: newAmountOut)
87        }
88
89        // Calculate new execution price in FP128
90        let newPriceFP128 = self.calculatePriceFP128(amountIn: newAmountIn, amountOut: newAmountOut)
91
92        // Convert amounts to UInt128 for high-precision math
93        let prevInScaled = UInt128(totalPreviousIn * 100000000.0)
94        let newInScaled = UInt128(newAmountIn * 100000000.0)
95        let totalInScaled = prevInScaled + newInScaled
96
97        // Weighted average: (prevAvg * prevIn + newPrice * newIn) / totalIn
98        let prevWeighted = (previousAvgPriceFP128 * prevInScaled) / self.FP128_SCALE
99        let newWeighted = (newPriceFP128 * newInScaled) / self.FP128_SCALE
100
101        return ((prevWeighted + newWeighted) * self.FP128_SCALE) / totalInScaled
102    }
103
104    /// Calculate price as output/input in FP128 format
105    ///
106    /// @param amountIn: Input amount
107    /// @param amountOut: Output amount
108    /// @return Price in FP128 format (output per unit input)
109    access(all) fun calculatePriceFP128(amountIn: UFix64, amountOut: UFix64): UInt128 {
110        pre {
111            amountIn > 0.0: "Amount in must be positive"
112            amountOut > 0.0: "Amount out must be positive"
113        }
114
115        // Price = amountOut / amountIn, scaled to FP128
116        let amountInScaled = UInt128(amountIn * 100000000.0)
117        let amountOutScaled = UInt128(amountOut * 100000000.0)
118
119        return (amountOutScaled * self.FP128_SCALE) / amountInScaled
120    }
121
122    /// Convert FP128 price to human-readable UFix64
123    ///
124    /// @param priceFP128: Price in FP128 format
125    /// @return Price as UFix64 for display purposes
126    ///
127    /// Note: This is for display/logging only. Use FP128 for all calculations.
128    access(all) view fun fp128ToUFix64(priceFP128: UInt128): UFix64 {
129        // Divide by FP128_SCALE and convert to UFix64
130        let scaled = priceFP128 / (self.FP128_SCALE / 100000000)
131        return UFix64(scaled) / 100000000.0
132    }
133
134    /// Validate slippage basis points are within acceptable range
135    ///
136    /// @param slippageBps: Slippage in basis points
137    /// @return true if valid, false otherwise
138    access(all) view fun isValidSlippage(slippageBps: UInt64): Bool {
139        // Typically DCA should use 0.1% - 5% slippage (10 - 500 bps)
140        // But we allow up to 100% for flexibility
141        return slippageBps <= self.BPS_SCALE
142    }
143}
144