├── .gitignore ├── README.md ├── hardhat.config.js ├── package-lock.json ├── package.json ├── project ├── contracts │ ├── AdvancedMath.sol │ ├── AnalyticMath.sol │ ├── BondingCurve.sol │ ├── DynamicCurve.sol │ ├── FractionMath.sol │ ├── IntegralMath.sol │ ├── common │ │ └── Uint256.sol │ └── helpers │ │ ├── AdvancedMathUser.sol │ │ ├── AnalyticMathUser.sol │ │ ├── BondingCurveUser.sol │ │ ├── DynamicCurveUser.sol │ │ ├── FractionMathUser.sol │ │ └── IntegralMathUser.sol ├── customization │ ├── PrintAdvancedMathConstants.py │ ├── PrintAdvancedMathLambertNeg1.py │ ├── PrintAdvancedMathLambertPos1.py │ ├── PrintAnalyticMathConstants.py │ ├── PrintAnalyticMathOptimalExp.py │ ├── PrintAnalyticMathOptimalLog.py │ ├── PrintTestConstants.py │ ├── constants.py │ ├── core │ │ ├── AdvancedMath.py │ │ ├── AnalyticMath.py │ │ └── __init__.py │ └── util │ │ └── __init__.py ├── emulation │ ├── FixedPoint │ │ ├── AdvancedMath.py │ │ ├── AnalyticMath.py │ │ ├── BondingCurve.py │ │ ├── DynamicCurve.py │ │ ├── FractionMath.py │ │ ├── IntegralMath.py │ │ ├── __init__.py │ │ └── common │ │ │ ├── BuiltIn.py │ │ │ └── Uint256.py │ ├── FloatPoint │ │ └── __init__.py │ ├── TestAdvancedMathLambertNeg1Exact.py │ ├── TestAdvancedMathLambertNeg1Quick.py │ ├── TestAdvancedMathLambertNeg2Exact.py │ ├── TestAdvancedMathLambertNeg2Quick.py │ ├── TestAdvancedMathLambertPos1Exact.py │ ├── TestAdvancedMathLambertPos1Quick.py │ ├── TestAdvancedMathLambertPos2Exact.py │ ├── TestAdvancedMathLambertPos2Quick.py │ ├── TestAdvancedMathLambertPos3Exact.py │ ├── TestAdvancedMathLambertPos3Quick.py │ ├── TestAdvancedMathLambertPos4Exact.py │ ├── TestAdvancedMathLambertPos4Quick.py │ ├── TestAdvancedMathSolveExact.py │ ├── TestAdvancedMathSolveQuick.py │ ├── TestAnalyticMathExp.py │ ├── TestAnalyticMathLog.py │ ├── TestAnalyticMathPow.py │ ├── TestBondingCurveBuy.py │ ├── TestBondingCurveConvert.py │ ├── TestBondingCurveDeposit.py │ ├── TestBondingCurveInvest.py │ ├── TestBondingCurveSell.py │ ├── TestBondingCurveWithdraw.py │ ├── TestDynamicCurveEqualizeExact.py │ ├── TestDynamicCurveEqualizeQuick.py │ └── TestScheme │ │ └── __init__.py └── tests │ ├── AdvancedMath.js │ ├── AnalyticMath.js │ ├── BondingCurve.js │ ├── DynamicCurve.js │ ├── FractionMath.js │ ├── IntegralMath.js │ └── helpers │ ├── Constants.js │ └── Utilities.js ├── readme ├── AdvancedMath.pdf ├── AdvancedMath.txt ├── DynamicCurve.pdf └── DynamicCurve.txt └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage 5 | coverage.json 6 | .coverage_artifacts 7 | .coverage_contracts 8 | __pycache__ 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Abstract 2 | 3 | This package consists of the following modules: 4 | - [IntegralMath](#integralmath) - a set of functions, each of which returning an integer result 5 | - [FractionMath](#fractionmath) - a set of functions, each of which returning a rational result 6 | - [AnalyticMath](#analyticmath) - a set of functions for exponential and logarithmic operations 7 | - [AdvancedMath](#advancedmath) - a set of functions for solving equations of the form xA^x = B 8 | - [BondingCurve](#bondingcurve) - a set of functions implementing the bonding-curve mechanism 9 | - [DynamicCurve](#dynamiccurve) - a set of functions for equalizing the weights in a bonding-curve model 10 | 11 | ### Class Hierarchy 12 | ``` 13 | IntegralMath < - - - - FractionMath 14 | ∧ ∧ 15 | | | 16 | | | 17 | | | 18 | AnalyticMath < - - - - AdvancedMath 19 | ∧ ∧ 20 | | | 21 | | | 22 | | | 23 | BondingCurve DynamicCurve 24 | ``` 25 | 26 |

27 | 28 | --- 29 | 30 |

31 | 32 | ## IntegralMath 33 | 34 | This module implements the following interface: 35 | - `function floorLog2(uint256 n)` => `(uint8)` 36 | - `function floorSqrt(uint256 n)` => `(uint256)` 37 | - `function ceilSqrt(uint256 n)` => `(uint256)` 38 | - `function floorCbrt(uint256 n)` => `(uint256)` 39 | - `function ceilCbrt(uint256 n)` => `(uint256)` 40 | - `function roundDiv(uint256 n, uint256 d)` => `(uint256)` 41 | - `function mulDivF(uint256 x, uint256 y, uint256 z)` => `(uint256)` 42 | - `function mulDivC(uint256 x, uint256 y, uint256 z)` => `(uint256)` 43 | - `function mulDivR(uint256 x, uint256 y, uint256 z)` => `(uint256)` 44 | - `function mulDivExF(uint256 x, uint256 y, uint256 z, uint256 w)` => `(uint256)` 45 | - `function mulDivExC(uint256 x, uint256 y, uint256 z, uint256 w)` => `(uint256)` 46 | 47 | Function `floorLog2(n)` computes the largest integer smaller than or equal to the binary logarithm of `n`. 48 | 49 | Function `floorSqrt(n)` computes the largest integer smaller than or equal to the square root of `n`. 50 | 51 | Function `ceilSqrt(n)` computes the smallest integer larger than or equal to the square root of `n`. 52 | 53 | Function `floorCbrt(n)` computes the largest integer smaller than or equal to the cubic root of `n`. 54 | 55 | Function `ceilCbrt(n)` computes the smallest integer larger than or equal to the cubic root of `n`. 56 | 57 | Function `roundDiv(n, d)` computes the nearest integer (half being rounded upwards) to `n / d`. 58 | 59 | Function `minFactor(x, y)` computes the smallest integer `z` such that `x * y / z <= 2 ^ 256 - 1`. 60 | 61 | Function `mulShrF(x, y, s)` computes the largest integer smaller than or equal to `x * y / 2 ^ s`. 62 | 63 | Function `mulShrC(x, y, s)` computes the smallest integer larger than or equal to `x * y / 2 ^ s`. 64 | 65 | Function `mulDivF(x, y, z)` computes the largest integer smaller than or equal to `x * y / z`. 66 | 67 | Function `mulDivC(x, y, z)` computes the smallest integer larger than or equal to `x * y / z`. 68 | 69 | Function `mulDivR(x, y, z)` computes the nearest integer (half being rounded upwards) to `x * y / z`. 70 | 71 | Function `mulDivExF(x, y, z, w)` computes the largest integer smaller than or equal to `(x * y) / (z * w)`. 72 | 73 | Function `mulDivExC(x, y, z, w)` computes the smallest integer larger than or equal to `(x * y) / (z * w)`. 74 | 75 | Note that each one of the 'mulDiv' functions reverts when the **actual** result is larger than 256 bits. 76 | 77 | Note that function `floorSqrt` and function `ceilSqrt` are guaranteed to return the correct output for every input. 78 | 79 | However, when compared with the **actual** square root, smaller input generally yields relatively lower accuracy of the output. 80 | 81 | For example, `floorSqrt(3)` returns 1, but the actual square root of 3 is ~1.73, which yields a relative accuracy of only ~57%. 82 | 83 | Note that function `floorCbrt` and function `ceilCbrt` are guaranteed to return the correct output for every input. 84 | 85 | However, when compared with the **actual** cubic root, smaller input generally yields relatively lower accuracy of the output. 86 | 87 | For example, `floorCbrt(7)` returns 1, but the actual cubic root of 7 is ~1.91, which yields a relative accuracy of only ~52%. 88 | 89 |

90 | 91 | --- 92 | 93 |

94 | 95 | ## FractionMath 96 | 97 | This module implements the following interface: 98 | - `function poweredRatioExact(uint256 n, uint256 d, uint256 exp)` => `(uint256, uint256)` 99 | - `function poweredRatioQuick(uint256 n, uint256 d, uint256 exp)` => `(uint256, uint256)` 100 | - `function productRatio(uint256 xn, uint256 yn, uint256 xd, uint256 yd)` => `(uint256, uint256)` 101 | - `function reducedRatio(uint256 n, uint256 d, uint256 max)` => `(uint256, uint256)` 102 | - `function normalizedRatio(uint256 n, uint256 d, uint256 scale)` => `(uint256, uint256)` 103 | 104 | ### Powered Ratio 105 | 106 | Function `poweredRatioExact` opts for accuracy and function `poweredRatioQuick` opts for performance. 107 | 108 | Each one of the two 'poweredRatio' functions computes the power of a given ratio by a given exponent. 109 | 110 | In order to avoid multiplication overflow, it may truncate the intermediate result on each iteration. 111 | 112 | Subsequently, the larger the input exponent is, the lower the accuracy of the output is likely to be. 113 | 114 | This module defines a maximum exponent of 4 bits (i.e., 15), which can be customized to fit the system requirements. 115 | 116 | ### Product Ratio 117 | 118 | Function `productRatio` computes the product of two ratios as a single ratio whose components are not larger than 256 bits. 119 | 120 | If either one of the intermediate components is larger than 256 bits, then both of them are reduced based on the larger one. 121 | 122 | ### Reduced Ratio 123 | 124 | Function `reducedRatio` computes the nearest ratio whose components (numerator and denominator) fit up to a given threshold. 125 | 126 | Note that function `reducedRatio` is not meant to replace GCD, nor does it strive to achieve better accuracy. 127 | 128 | GCD is not being used here, because the time-complexity of this method depends on the bit-length of the input. 129 | 130 | The worst case is when the two input valus are consecutive Fibonacci numbers, in the case of `uint256` - F369 and F370, which yield 367 iterations. 131 | 132 | Moreover, the main issue with using GCD for reducing an arbitrary ratio, is the fact that it doesn't even guarantee the desired reduction to begin with. 133 | 134 | Reducing an input ratio by its GCD in advance (at your own expense) can most certainly improve the output of function `reducedRatio` in terms of accuracy. 135 | 136 | However, without knowing specific characteristics of that ratio (e.g., each one of its components is a multiple of `0x1000`), doing so is generally useless. 137 | 138 | ### Normalized Ratio 139 | 140 | Function `normalizedRatio` computes the nearest ratio whose components (numerator and denominator) sum up to a given scale. 141 | 142 | Note that the output ratio can be larger than the input ratio in some cases, and smaller than the input ratio in other cases. 143 | 144 | For example: 145 | - `normalizedRatio(12, 34, 100)` returns `(26, 74)`; the output ratio is smaller than the input ratio (26 / 74 = 0.351 < 0.352 = 12 / 34) 146 | - `normalizedRatio(1234, 5678, 100)` returns `(18, 82)`; the output ratio is larger than the input ratio (18 / 82 = 0.219 > 0.217 = 1234 / 5678) 147 | 148 | Keep in mind that it is an important consideration to take when choosing to use this function. 149 | 150 | For example, when designing a sustainable financial model, it is imperative to never entitle more than the actual entitlement. 151 | 152 | The same consideration applies for all the other functions in this module. 153 | 154 |

155 | 156 | --- 157 | 158 |

159 | 160 | ## AnalyticMath 161 | 162 | This module implements the following interface: 163 | - `function pow(uint256 a, uint256 b, uint256 c, uint256 d)` => `(uint256, uint256)` 164 | - `function log(uint256 a, uint256 b)` => `(uint256, uint256)` 165 | - `function exp(uint256 a, uint256 b)` => `(uint256, uint256)` 166 | 167 | ### Exponentiation 168 | 169 | Function `pow(a, b, c, d)` approximates the power of `a / b` by `c / d`. 170 | 171 | When `a >= b`, the output of this function is guaranteed to be smaller than or equal to the actual value of (a / b) ^ (c / d). 172 | 173 | When `a <= b`, the output of this function is guaranteed to be larger than or equal to the actual value of (a / b) ^ (c / d). 174 | 175 | ### Natural Logarithm 176 | 177 | Function `log(a, b)` approximates the natural logarithm of `a / b`. 178 | 179 | The output of this function is guaranteed to be smaller than or equal to the actual value of log(a / b). 180 | 181 | It does not support `a < b`, because it relies on unsigned-integer arithmetic, and the output for such input would be negative. 182 | 183 | ### Natural Exponentiation 184 | 185 | Function `exp(a, b)` approximates the natural exponentiation of `a / b`. 186 | 187 | The output of this function is guaranteed to be smaller than or equal to the actual value of exp(a / b). 188 | 189 | ### Module Customization 190 | 191 | This module can be customized to support different input ranges (as a tradeoff with accuracy and/or performance). 192 | 193 | The full customization manual can be found [here](#customization). 194 | 195 |

196 | 197 | --- 198 | 199 |

200 | 201 | ## AdvancedMath 202 | 203 | This module implements the following interface: 204 | - `function solveExact(uint256 a, uint256 b, uint256 c, uint256 d)` => `(uint256, uint256)` 205 | - `function solveQuick(uint256 a, uint256 b, uint256 c, uint256 d)` => `(uint256, uint256)` 206 | 207 | ### Equation Solving 208 | 209 | Function `solveExact` opts for accuracy and function `solveQuick` opts for performance. 210 | 211 | Each one of these functions computes a value of x which satisfies the equation x * (a / b) ^ x = c / d. 212 | 213 | A detailed description of how each one of these functions works can be found [here](readme/AdvancedMath.pdf). 214 | 215 | ### Module Customization 216 | 217 | This module can be customized to support different input ranges (as a tradeoff with accuracy and/or performance). 218 | 219 | The full customization manual can be found [here](#customization). 220 | 221 |

222 | 223 | --- 224 | 225 |

226 | 227 | ## BondingCurve 228 | 229 | This module implements the following interface: 230 | - `function buy(uint256 supply, uint256 balance, uint256 weight, uint256 amount)` => `(uint256)` 231 | - `function sell(uint256 supply, uint256 balance, uint256 weight, uint256 amount)` => `(uint256)` 232 | - `function convert(uint256 balance1, uint256 weight1, uint256 balance2, uint256 weight2, uint256 amount)` => `(uint256)` 233 | - `function deposit(uint256 supply, uint256 balance, uint256 weights, uint256 amount)` => `(uint256)` 234 | - `function withdraw(uint256 supply, uint256 balance, uint256 weights, uint256 amount)` => `(uint256)` 235 | - `function invest(uint256 supply, uint256 balance, uint256 weights, uint256 amount)` => `(uint256)` 236 | 237 | ### Functionality 238 | 239 | ``` 240 | |----------------------------|--------------------------------------------------|---------------------------------------------| 241 | | Function | Compute the return of | Formula | 242 | |----------------------------|--------------------------------------------------|---------------------------------------------| 243 | | buy(s, b, w, x) | buying pool tokens with reserve tokens | s * ((1 + x / b) ^ (w / MAX_WEIGHT) - 1) | 244 | | sell(s, b, w, x) | selling pool tokens for reserve tokens | b * (1 - (1 - x / s) ^ (MAX_WEIGHT / w)) | 245 | | convert(b1, w1, b2, w2, x) | converting reserve tokens of one type to another | b2 * (1 - (b1 / (b1 + x)) ^ (w1 / w2)) | 246 | | deposit(s, b, ws, x) | depositing reserve tokens for pool tokens | s * ((x / b + 1) ^ (ws / MAX_WEIGHT) - 1) | 247 | | withdraw(s, b, ws, x) | withdrawing reserve tokens with pool tokens | b * (1 - ((s - x) / s) ^ (MAX_WEIGHT / ws)) | 248 | | invest(s, b, ws, x) | investing reserve tokens for pool tokens | b * (((s + x) / s) ^ (MAX_WEIGHT / ws) - 1) | 249 | |----------------------------|--------------------------------------------------|---------------------------------------------| 250 | ``` 251 | 252 | The bonding-curve model was conceived by [Bancor](https://github.com/bancorprotocol). 253 | 254 |

255 | 256 | --- 257 | 258 |

259 | 260 | ## DynamicCurve 261 | 262 | This module implements the following interface: 263 | - `function equalizeExact(uint256 t, uint256 s, uint256 r, uint256 q, uint256 p)` => `(uint256, uint256)` 264 | - `function equalizeQuick(uint256 t, uint256 s, uint256 r, uint256 q, uint256 p)` => `(uint256, uint256)` 265 | 266 | Function `equalizeExact` opts for accuracy and function `equalizeQuick` opts for performance. 267 | 268 | ### Equalization 269 | 270 | Consider a pool which implements the bonding-curve model over a primary reserve token and a secondary reserve token. 271 | 272 | Let 'on-chain price' denote the conversion rate between these tokens inside the pool (i.e., as determined by the pool). 273 | 274 | Let 'off-chain price' denote the conversion rate between these tokens outside the pool (i.e., as determined by the market). 275 | 276 | The arbitrage incentive is always to convert to the point where the on-chain price is equal to the off-chain price. 277 | 278 | We want this operation to also impact the primary reserve balance becoming equal to the primary reserve staked balance. 279 | 280 | In other words, we want the arbitrager to convert the difference between the reserve balance and the reserve staked balance. 281 | 282 | Hence we adjust the weights in order to create an arbitrage incentive which, when realized, will subsequently equalize the pool. 283 | 284 | Input: 285 | - Let t denote the primary reserve token staked balance 286 | - Let s denote the primary reserve token balance 287 | - Let r denote the secondary reserve token balance 288 | - Let q denote the numerator of the off-chain price 289 | - Let p denote the denominator of the off-chain price 290 | 291 | Where p primary tokens are equal to q secondary tokens 292 | 293 | Output: 294 | - Solve the equation x * (s / t) ^ x = (t / r) * (q / p) 295 | - Return x / (x + 1) as the weight of the primary reserve token 296 | - Return 1 / (x + 1) as the weight of the secondary reserve token 297 | 298 | A detailed reasoning of this method can be found [here](readme/DynamicCurve.pdf). 299 | 300 | If the rate-provider provides the rates for a common unit, for example: 301 | - P = 2 ==> 2 primary reserve tokens = 1 ether 302 | - Q = 3 ==> 3 secondary reserve tokens = 1 ether 303 | 304 | Then you can simply use p = P and q = Q 305 | 306 | If the rate-provider provides the rates for a single unit, for example: 307 | - P = 2 ==> 1 primary reserve token = 2 ethers 308 | - Q = 3 ==> 1 secondary reserve token = 3 ethers 309 | 310 | Then you can simply use p = Q and q = P 311 | 312 | The dynamic-curve method was conceived by [Bancor](https://github.com/bancorprotocol). 313 | 314 |

315 | 316 | --- 317 | 318 |

319 | 320 | ## Testing 321 | 322 | ### Prerequisites 323 | 324 | - `node 20.17.0` 325 | - `yarn 1.22.22` or `npm 10.8.2` 326 | 327 | ### Installation 328 | 329 | - `yarn install` or `npm install` 330 | 331 | ### Compilation 332 | 333 | - `yarn build` or `npm run build` 334 | 335 | ### Execution 336 | 337 | - `yarn test` or `npm run test` 338 | 339 | ### Verification 340 | 341 | - `yarn verify` or `npm run verify` 342 | 343 |

344 | 345 | --- 346 | 347 |

348 | 349 | ## Emulation 350 | 351 | ### Prerequisites 352 | 353 | - `python 3.12.3` 354 | 355 | ### Execution 356 | 357 | In order to allow rapid testing and verification, all modules have been ported from Solidity to Python: 358 | - The emulation modules themselves are located under [FixedPoint](project/emulation/FixedPoint) 359 | - The corresponding floating-point functionality is located under [FloatPoint](project/emulation/FloatPoint) 360 | - A set of unit-tests for various functions (one per function) is located under [emulation](project/emulation) 361 | 362 |

363 | 364 | --- 365 | 366 |

367 | 368 | ## Customization 369 | 370 | All customization parameters are located in [constants.py](project/customization/constants.py). 371 | 372 | When modifying **any** of them, one should regenerate **all** the code. 373 | 374 | The following scripts generate the code for [AnalyticMath.sol](project/contracts/AnalyticMath.sol): 375 | - [PrintAnalyticMathConstants.py ](project/customization/PrintAnalyticMathConstants.py ) 376 | - [PrintAnalyticMathOptimalLog.py](project/customization/PrintAnalyticMathOptimalLog.py) 377 | - [PrintAnalyticMathOptimalExp.py](project/customization/PrintAnalyticMathOptimalExp.py) 378 | 379 | The following scripts generate the code for [AdvancedMath.sol](project/contracts/AdvancedMath.sol): 380 | - [PrintAdvancedMathConstants.py ](project/customization/PrintAdvancedMathConstants.py ) 381 | - [PrintAdvancedMathLambertNeg1.py](project/customization/PrintAdvancedMathLambertNeg1.py) 382 | - [PrintAdvancedMathLambertPos1.py](project/customization/PrintAdvancedMathLambertPos1.py) 383 | 384 | In order to retain the [testing infrastructure](#testing), one should proceed by: 385 | - Running the script [PrintTestConstants.py](project/customization/PrintTestConstants.py) 386 | - Pasting the printout into [Constants.js](project/tests/helpers/Constants.js) 387 | 388 | In order to retain the [emulation infrastructure](#emulation), one should proceed by: 389 | - Porting all changes from [AnalyticMath.sol](project/contracts/AnalyticMath.sol) to [AnalyticMath.py](project/emulation/FixedPoint/AnalyticMath.py) 390 | - Porting all changes from [AdvancedMath.sol](project/contracts/AdvancedMath.sol) to [AdvancedMath.py](project/emulation/FixedPoint/AdvancedMath.py) 391 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomiclabs/hardhat-truffle5"); 2 | require("solidity-coverage"); 3 | 4 | const Decimal = require("decimal.js"); 5 | 6 | Decimal.set({precision: 156, rounding: Decimal.ROUND_DOWN}); 7 | 8 | module.exports = { 9 | solidity: { 10 | version: "0.8.28", 11 | settings: { 12 | optimizer: { 13 | enabled: true, 14 | runs: 20000 15 | } 16 | } 17 | }, 18 | paths: { 19 | sources: "./project/contracts", 20 | tests: "./project/tests", 21 | cache: "./project/cache", 22 | artifacts: "./project/artifacts" 23 | }, 24 | mocha: { 25 | timeout: 0, 26 | useColors: true, 27 | reporter: "list" // https://mochajs.org/#reporters 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solidity-math-utils", 3 | "version": "2.1.1", 4 | "description": "Solidity Mathematical Utilities", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/barakman/solidity-math-utils.git" 8 | }, 9 | "author": "Barak Manos", 10 | "license": "SEE LICENSE IN https://raw.githubusercontent.com/bancorprotocol/contracts-solidity/master/LICENSE", 11 | "files": [ 12 | "project/contracts/*.sol", 13 | "project/contracts/common/*.sol" 14 | ], 15 | "scripts": { 16 | "build": "hardhat compile", 17 | "test": "hardhat test --bail", 18 | "verify": "hardhat coverage" 19 | }, 20 | "devDependencies": { 21 | "@nomiclabs/hardhat-truffle5": "2.0.7", 22 | "@nomiclabs/hardhat-web3": "2.0.0", 23 | "decimal.js": "10.4.3", 24 | "hardhat": "2.22.17", 25 | "solidity-coverage": "0.8.14" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /project/contracts/AnalyticMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity 0.8.28; 3 | 4 | import "./IntegralMath.sol"; 5 | 6 | library AnalyticMath { 7 | // Auto-generated via 'PrintAnalyticMathConstants.py' 8 | uint8 internal constant SCALE_1 = 0x000000000000000000000000000000007f; 9 | uint256 internal constant FIXED_1 = 0x0080000000000000000000000000000000; 10 | uint256 internal constant LN2_MIN = 0x0058b90bfbe8e7bcd5e4f1d9cc01f97b57; 11 | uint256 internal constant LN2_MAX = 0x0058b90bfbe8e7bcd5e4f1d9cc01f97b58; 12 | uint256 internal constant LOG_MID = 0x015bf0a8b1457695355fb8ac404e7a79e4; 13 | uint256 internal constant EXP_MID = 0x0400000000000000000000000000000000; 14 | uint256 internal constant EXP_MAX = 0x2cb53f09f05cc627c85ddebfccfeb72758; 15 | 16 | /** 17 | * @dev Compute (a / b) ^ (c / d) 18 | */ 19 | function pow(uint256 a, uint256 b, uint256 c, uint256 d) internal pure returns (uint256, uint256) { unchecked { 20 | if (b == 0 || d == 0) 21 | revert("division by zero"); 22 | if (a == 0 || c == 0) 23 | return (a ** c, 1); 24 | if (a > b) 25 | return (fixedExp(IntegralMath.mulDivF(fixedLog(IntegralMath.mulDivF(FIXED_1, a, b)), c, d)), FIXED_1); 26 | if (b > a) 27 | return (FIXED_1, fixedExp(IntegralMath.mulDivF(fixedLog(IntegralMath.mulDivF(FIXED_1, b, a)), c, d))); 28 | return (1, 1); 29 | }} 30 | 31 | /** 32 | * @dev Compute log(a / b) 33 | */ 34 | function log(uint256 a, uint256 b) internal pure returns (uint256, uint256) { unchecked { 35 | return (fixedLog(IntegralMath.mulDivF(FIXED_1, a, b)), FIXED_1); 36 | }} 37 | 38 | /** 39 | * @dev Compute exp(a / b) 40 | */ 41 | function exp(uint256 a, uint256 b) internal pure returns (uint256, uint256) { unchecked { 42 | return (fixedExp(IntegralMath.mulDivF(FIXED_1, a, b)), FIXED_1); 43 | }} 44 | 45 | /** 46 | * @dev Compute log(x / FIXED_1) * FIXED_1 47 | * Input range: FIXED_1 <= x <= 2 ^ 256 - 1 48 | * Detailed description: 49 | * - For x < LOG_MID, compute log(x) 50 | * - For any other x, compute log(x / 2 ^ log2(x)) + log2(x) * log(2) 51 | * - The value of log(2) is represented as floor(log(2) * FIXED_1) 52 | * - With k = log2(x), this solution relies on the following identity: 53 | * log(x) = 54 | * log(x) + log(2 ^ k) - log(2 ^ k) = 55 | * log(x) - log(2 ^ k) + log(2 ^ k) = 56 | * log(x / log(2 ^ k)) + log(2 ^ k) = 57 | * log(x / log(2 ^ k)) + k * log(2) 58 | */ 59 | function fixedLog(uint256 x) internal pure returns (uint256) { unchecked { 60 | if (x < FIXED_1) 61 | revert("fixedLog: x < min"); 62 | if (x < LOG_MID) 63 | return optimalLog(x); 64 | uint8 count = IntegralMath.floorLog2(x / FIXED_1); 65 | return optimalLog(x >> count) + count * LN2_MIN; 66 | }} 67 | 68 | /** 69 | * @dev Compute exp(x / FIXED_1) * FIXED_1 70 | * Input range: 0 <= x <= EXP_MAX - 1 71 | * Detailed description: 72 | * - For x < EXP_MID, compute exp(x) 73 | * - For any other x, compute exp(x % log(2)) * 2 ^ (x / log(2)) 74 | * - The value of log(2) is represented as ceil(log(2) * FIXED_1) 75 | * - With k = x / log(2), this solution relies on the following identity: 76 | * exp(x) = 77 | * exp(x) * 2 ^ k / 2 ^ k = 78 | * exp(x) * 2 ^ k / exp(k * log(2)) = 79 | * exp(x) / exp(k * log(2)) * 2 ^ k = 80 | * exp(x - k * log(2)) * 2 ^ k 81 | */ 82 | function fixedExp(uint256 x) internal pure returns (uint256) { unchecked { 83 | if (x < EXP_MID) 84 | return optimalExp(x); 85 | if (x < EXP_MAX) 86 | return optimalExp(x % LN2_MAX) << (x / LN2_MAX); 87 | revert("fixedExp: x > max"); 88 | }} 89 | 90 | /** 91 | * @dev Compute log(x / FIXED_1) * FIXED_1 92 | * Input range: FIXED_1 <= x <= FIXED_1 * 4 - 1 93 | * Auto-generated via 'PrintAnalyticMathOptimalLog.py' 94 | * Detailed description: 95 | * - Rewrite the input as a product of natural exponents and a single residual r, such that 1 < r < 2 96 | * - The natural logarithm of each (pre-calculated) exponent is the degree of the exponent 97 | * - The natural logarithm of r is calculated via Taylor series for log(1 + x), where x = r - 1 98 | * - The natural logarithm of the input is calculated by summing up the intermediate results above 99 | * - For example: log(250) = log(e^4 * e^1 * e^0.5 * 1.021692859) = 4 + 1 + 0.5 + log(1 + 0.021692859) 100 | */ 101 | function optimalLog(uint256 x) internal pure returns (uint256) { unchecked { 102 | uint256 res = 0; 103 | 104 | uint256 y; 105 | uint256 z; 106 | uint256 w; 107 | 108 | if (x > 0xd3094c70f034de4b96ff7d5b6f99fcd8) {res |= 0x40000000000000000000000000000000; x = x * 0x0bc5ab1b16779be3575bd8f0520a9d5b9 / 0x1368b2fc6f9609fe7aceb46aa619b8003;} // add 2^(-1) 109 | if (x > 0xa45af1e1f40c333b3de1db4dd55f29a7) {res |= 0x20000000000000000000000000000000; x = x * 0x1368b2fc6f9609fe7aceb46aa619a2ef6 / 0x18ebef9eac820ae8682b9793ac6cffa93;} // add 2^(-2) 110 | if (x > 0x910b022db7ae67ce76b441c27035c6a1) {res |= 0x10000000000000000000000000000000; x = x * 0x18ebef9eac820ae8682b9793ac6d11622 / 0x1c3d6a24ed82218787d624d3e5eb9a8c6;} // add 2^(-3) 111 | if (x > 0x88415abbe9a76bead8d00cf112e4d4a8) {res |= 0x08000000000000000000000000000000; x = x * 0x1c3d6a24ed82218787d624d3e5eba8beb / 0x1e0fabfbc702a3ce5e31fe0609358b04d;} // add 2^(-4) 112 | if (x > 0x84102b00893f64c705e841d5d4064bd3) {res |= 0x04000000000000000000000000000000; x = x * 0x1e0fabfbc702a3ce5e31fe06093589585 / 0x1f03f56a88b5d7914b00abf9776270f29;} // add 2^(-5) 113 | if (x > 0x8204055aaef1c8bd5c3259f4822735a2) {res |= 0x02000000000000000000000000000000; x = x * 0x1f03f56a88b5d7914b00abf9776267973 / 0x1f80feabfeefa4927d10bdd54ead4eaf0;} // add 2^(-6) 114 | if (x > 0x810100ab00222d861931c15e39b44e99) {res |= 0x01000000000000000000000000000000; x = x * 0x1f80feabfeefa4927d10bdd54ead599f9 / 0x1fc03fd56aa224f97fcbf133298883271;} // add 2^(-7) 115 | if (x > 0x808040155aabbbe9451521693554f733) {res |= 0x00800000000000000000000000000000; x = x * 0x1fc03fd56aa224f97fcbf13329886bd10 / 0x1fe00ffaabffbbc71ad1e1184afc01529;} // add 2^(-8) 116 | 117 | z = y = x - FIXED_1; 118 | w = y * y >> SCALE_1; 119 | res += z * (0x100000000000000000000000000000000 - y) / 0x100000000000000000000000000000000; z = z * w >> SCALE_1; // add y^01 / 01 - y^02 / 02 120 | res += z * (0x0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - y) / 0x200000000000000000000000000000000; z = z * w >> SCALE_1; // add y^03 / 03 - y^04 / 04 121 | res += z * (0x099999999999999999999999999999999 - y) / 0x300000000000000000000000000000000; z = z * w >> SCALE_1; // add y^05 / 05 - y^06 / 06 122 | res += z * (0x092492492492492492492492492492492 - y) / 0x400000000000000000000000000000000; z = z * w >> SCALE_1; // add y^07 / 07 - y^08 / 08 123 | res += z * (0x08e38e38e38e38e38e38e38e38e38e38e - y) / 0x500000000000000000000000000000000; z = z * w >> SCALE_1; // add y^09 / 09 - y^10 / 10 124 | res += z * (0x08ba2e8ba2e8ba2e8ba2e8ba2e8ba2e8b - y) / 0x600000000000000000000000000000000; z = z * w >> SCALE_1; // add y^11 / 11 - y^12 / 12 125 | res += z * (0x089d89d89d89d89d89d89d89d89d89d89 - y) / 0x700000000000000000000000000000000; z = z * w >> SCALE_1; // add y^13 / 13 - y^14 / 14 126 | res += z * (0x088888888888888888888888888888888 - y) / 0x800000000000000000000000000000000; // add y^15 / 15 - y^16 / 16 127 | 128 | return res; 129 | }} 130 | 131 | /** 132 | * @dev Compute exp(x / FIXED_1) * FIXED_1 133 | * Input range: 0 <= x <= EXP_MID - 1 134 | * Auto-generated via 'PrintAnalyticMathOptimalExp.py' 135 | * Detailed description: 136 | * - Rewrite the input as a sum of binary exponents and a single residual r, as small as possible 137 | * - The exponentiation of each binary exponent is given (pre-calculated) 138 | * - The exponentiation of r is calculated via Taylor series for e^x, where x = r 139 | * - The exponentiation of the input is calculated by multiplying the intermediate results above 140 | * - For example: e^5.521692859 = e^(4 + 1 + 0.5 + 0.021692859) = e^4 * e^1 * e^0.5 * e^0.021692859 141 | */ 142 | function optimalExp(uint256 x) internal pure returns (uint256) { unchecked { 143 | uint256 res = 0; 144 | 145 | uint256 y; 146 | uint256 z; 147 | 148 | z = y = x % 0x10000000000000000000000000000000; // get the input modulo 2^(-3) 149 | z = z * y >> SCALE_1; res += z * 0x10e1b3be415a0000; // add y^02 * (20! / 02!) 150 | z = z * y >> SCALE_1; res += z * 0x05a0913f6b1e0000; // add y^03 * (20! / 03!) 151 | z = z * y >> SCALE_1; res += z * 0x0168244fdac78000; // add y^04 * (20! / 04!) 152 | z = z * y >> SCALE_1; res += z * 0x004807432bc18000; // add y^05 * (20! / 05!) 153 | z = z * y >> SCALE_1; res += z * 0x000c0135dca04000; // add y^06 * (20! / 06!) 154 | z = z * y >> SCALE_1; res += z * 0x0001b707b1cdc000; // add y^07 * (20! / 07!) 155 | z = z * y >> SCALE_1; res += z * 0x000036e0f639b800; // add y^08 * (20! / 08!) 156 | z = z * y >> SCALE_1; res += z * 0x00000618fee9f800; // add y^09 * (20! / 09!) 157 | z = z * y >> SCALE_1; res += z * 0x0000009c197dcc00; // add y^10 * (20! / 10!) 158 | z = z * y >> SCALE_1; res += z * 0x0000000e30dce400; // add y^11 * (20! / 11!) 159 | z = z * y >> SCALE_1; res += z * 0x000000012ebd1300; // add y^12 * (20! / 12!) 160 | z = z * y >> SCALE_1; res += z * 0x0000000017499f00; // add y^13 * (20! / 13!) 161 | z = z * y >> SCALE_1; res += z * 0x0000000001a9d480; // add y^14 * (20! / 14!) 162 | z = z * y >> SCALE_1; res += z * 0x00000000001c6380; // add y^15 * (20! / 15!) 163 | z = z * y >> SCALE_1; res += z * 0x000000000001c638; // add y^16 * (20! / 16!) 164 | z = z * y >> SCALE_1; res += z * 0x0000000000001ab8; // add y^17 * (20! / 17!) 165 | z = z * y >> SCALE_1; res += z * 0x000000000000017c; // add y^18 * (20! / 18!) 166 | z = z * y >> SCALE_1; res += z * 0x0000000000000014; // add y^19 * (20! / 19!) 167 | z = z * y >> SCALE_1; res += z * 0x0000000000000001; // add y^20 * (20! / 20!) 168 | res = res / 0x21c3677c82b40000 + y + FIXED_1; // divide by 20! and then add y^1 / 1! + y^0 / 0! 169 | 170 | if ((x & 0x010000000000000000000000000000000) != 0) res = res * 0x1c3d6a24ed82218787d624d3e5eba2a0f / 0x18ebef9eac820ae8682b9793ac6d1883a; // multiply by e^2^(-3) 171 | if ((x & 0x020000000000000000000000000000000) != 0) res = res * 0x18ebef9eac820ae8682b9793ac6d1e726 / 0x1368b2fc6f9609fe7aceb46aa619bae94; // multiply by e^2^(-2) 172 | if ((x & 0x040000000000000000000000000000000) != 0) res = res * 0x1368b2fc6f9609fe7aceb46aa6199a7d7 / 0x0bc5ab1b16779be3575bd8f0520a8b756; // multiply by e^2^(-1) 173 | if ((x & 0x080000000000000000000000000000000) != 0) res = res * 0x0bc5ab1b16779be3575bd8f0520a67cd2 / 0x0454aaa8efe072e7f6ddbab84b409101a; // multiply by e^2^(+0) 174 | if ((x & 0x100000000000000000000000000000000) != 0) res = res * 0x0454aaa8efe072e7f6ddbab84b408736f / 0x00960aadc109e7a3bf45780996156d0a3; // multiply by e^2^(+1) 175 | if ((x & 0x200000000000000000000000000000000) != 0) res = res * 0x00960aadc109e7a3bf45780996149ade3 / 0x0002bf84208204f5977f9a8cf01fd8f74; // multiply by e^2^(+2) 176 | 177 | return res; 178 | }} 179 | } 180 | -------------------------------------------------------------------------------- /project/contracts/BondingCurve.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity 0.8.28; 3 | 4 | import "./AnalyticMath.sol"; 5 | import "./IntegralMath.sol"; 6 | 7 | library BondingCurve { 8 | uint256 internal constant MAX_WEIGHT = 1000000; 9 | 10 | /** 11 | * @dev Buy pool tokens with reserve tokens 12 | * 13 | * @param supply The total amount of pool tokens 14 | * @param balance The amount of reserve tokens owned by the pool 15 | * @param weight The weight of the reserve (represented in ppm) 16 | * @param amount The amount of reserve tokens provided 17 | * 18 | * @return supply * ((1 + amount / balance) ^ (weight / MAX_WEIGHT) - 1) 19 | */ 20 | function buy(uint256 supply, uint256 balance, uint256 weight, uint256 amount) internal pure returns (uint256) { unchecked { 21 | require(supply > 0 && balance > 0 && weight > 0, "invalid input"); 22 | require(weight <= MAX_WEIGHT, "weight out of bound"); 23 | 24 | if (weight == MAX_WEIGHT) 25 | return IntegralMath.mulDivF(amount, supply, balance); 26 | 27 | (uint256 n, uint256 d) = AnalyticMath.pow(safeAdd(balance, amount), balance, weight, MAX_WEIGHT); 28 | return IntegralMath.mulDivF(supply, n, d) - supply; 29 | }} 30 | 31 | /** 32 | * @dev Sell pool tokens for reserve tokens 33 | * 34 | * @param supply The total amount of pool tokens 35 | * @param balance The amount of reserve tokens owned by the pool 36 | * @param weight The weight of the reserve (represented in ppm) 37 | * @param amount The amount of pool tokens provided 38 | * 39 | * @return balance * (1 - (1 - amount / supply) ^ (MAX_WEIGHT / weight)) 40 | */ 41 | function sell(uint256 supply, uint256 balance, uint256 weight, uint256 amount) internal pure returns (uint256) { unchecked { 42 | require(supply > 0 && balance > 0 && weight > 0, "invalid input"); 43 | require(weight <= MAX_WEIGHT, "weight out of bound"); 44 | require(amount <= supply, "amount larger than supply"); 45 | 46 | if (amount == supply) 47 | return balance; 48 | 49 | if (weight == MAX_WEIGHT) 50 | return IntegralMath.mulDivF(amount, balance, supply); 51 | 52 | (uint256 n, uint256 d) = AnalyticMath.pow(supply, supply - amount, MAX_WEIGHT, weight); 53 | return IntegralMath.mulDivF(balance, n - d, n); 54 | }} 55 | 56 | /** 57 | * @dev Convert reserve tokens of one type to another 58 | * 59 | * @param balance1 The amount of source reserve tokens owned by the pool 60 | * @param weight1 The weight of the source reserve (represented in ppm) 61 | * @param balance2 The amount of target reserve tokens owned by the pool 62 | * @param weight2 The weight of the target reserve (represented in ppm) 63 | * @param amount The amount of source reserve tokens provided 64 | * 65 | * @return balance2 * (1 - (balance1 / (balance1 + amount)) ^ (weight1 / weight2)) 66 | */ 67 | function convert(uint256 balance1, uint256 weight1, uint256 balance2, uint256 weight2, uint256 amount) internal pure returns (uint256) { unchecked { 68 | require(balance1 > 0 && balance2 > 0 && weight1 > 0 && weight2 > 0, "invalid input"); 69 | require(weight1 <= MAX_WEIGHT && weight2 <= MAX_WEIGHT, "weights out of bound"); 70 | 71 | if (weight1 == weight2) 72 | return IntegralMath.mulDivF(balance2, amount, safeAdd(balance1, amount)); 73 | 74 | (uint256 n, uint256 d) = AnalyticMath.pow(safeAdd(balance1, amount), balance1, weight1, weight2); 75 | return IntegralMath.mulDivF(balance2, n - d, n); 76 | }} 77 | 78 | /** 79 | * @dev Deposit reserve tokens for pool tokens 80 | * 81 | * @param supply The total amount of pool tokens 82 | * @param balance The amount of reserve tokens of the desired type owned by the pool 83 | * @param weights The combined weights of the reserves (represented in ppm) 84 | * @param amount The amount of reserve tokens of the desired type provided 85 | * 86 | * @return supply * ((amount / balance + 1) ^ (weights / MAX_WEIGHT) - 1) 87 | */ 88 | function deposit(uint256 supply, uint256 balance, uint256 weights, uint256 amount) internal pure returns (uint256) { unchecked { 89 | require(supply > 0 && balance > 0 && weights > 0, "invalid input"); 90 | require(weights <= MAX_WEIGHT * 2, "weights out of bound"); 91 | 92 | if (weights == MAX_WEIGHT) 93 | return IntegralMath.mulDivF(amount, supply, balance); 94 | 95 | (uint256 n, uint256 d) = AnalyticMath.pow(safeAdd(balance, amount), balance, weights, MAX_WEIGHT); 96 | return IntegralMath.mulDivF(supply, n, d) - supply; 97 | }} 98 | 99 | /** 100 | * @dev Withdraw reserve tokens with pool tokens 101 | * 102 | * @param supply The total amount of pool tokens 103 | * @param balance The amount of reserve tokens of the desired type owned by the pool 104 | * @param weights The combined weights of the reserves (represented in ppm) 105 | * @param amount The amount of pool tokens provided 106 | * 107 | * @return balance * (1 - ((supply - amount) / supply) ^ (MAX_WEIGHT / weights)) 108 | */ 109 | function withdraw(uint256 supply, uint256 balance, uint256 weights, uint256 amount) internal pure returns (uint256) { unchecked { 110 | require(supply > 0 && balance > 0 && weights > 0, "invalid input"); 111 | require(weights <= MAX_WEIGHT * 2, "weights out of bound"); 112 | require(amount <= supply, "amount larger than supply"); 113 | 114 | if (amount == supply) 115 | return balance; 116 | 117 | if (weights == MAX_WEIGHT) 118 | return IntegralMath.mulDivF(amount, balance, supply); 119 | 120 | (uint256 n, uint256 d) = AnalyticMath.pow(supply, supply - amount, MAX_WEIGHT, weights); 121 | return IntegralMath.mulDivF(balance, n - d, n); 122 | }} 123 | 124 | /** 125 | * @dev Invest reserve tokens for pool tokens 126 | * 127 | * @param supply The total amount of pool tokens 128 | * @param balance The amount of reserve tokens of the desired type owned by the pool 129 | * @param weights The combined weights of the reserves (represented in ppm) 130 | * @param amount The amount of pool tokens desired 131 | * 132 | * @return balance * (((supply + amount) / supply) ^ (MAX_WEIGHT / weights) - 1) 133 | */ 134 | function invest(uint256 supply, uint256 balance, uint256 weights, uint256 amount) internal pure returns (uint256) { unchecked { 135 | require(supply > 0 && balance > 0 && weights > 0, "invalid input"); 136 | require(weights <= MAX_WEIGHT * 2, "weights out of bound"); 137 | 138 | if (weights == MAX_WEIGHT) 139 | return IntegralMath.mulDivC(amount, balance, supply); 140 | 141 | (uint256 n, uint256 d) = AnalyticMath.pow(safeAdd(supply, amount), supply, MAX_WEIGHT, weights); 142 | return IntegralMath.mulDivC(balance, n, d) - balance; 143 | }} 144 | } 145 | -------------------------------------------------------------------------------- /project/contracts/DynamicCurve.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity 0.8.28; 3 | 4 | import "./AdvancedMath.sol"; 5 | import "./FractionMath.sol"; 6 | 7 | library DynamicCurve { 8 | uint256 internal constant MAX_WEIGHT = 1000000; 9 | 10 | // Abstract: 11 | // Consider a pool which implements the bonding-curve model over a primary reserve token and a secondary reserve token. 12 | // Let 'on-chain price' denote the conversion rate between these tokens inside the pool (i.e., as determined by the pool). 13 | // Let 'off-chain price' denote the conversion rate between these tokens outside the pool (i.e., as determined by the market). 14 | // The arbitrage incentive is always to convert to the point where the on-chain price is equal to the off-chain price. 15 | // We want this operation to also impact the primary reserve balance becoming equal to the primary reserve staked balance. 16 | // In other words, we want the arbitrager to convert the difference between the reserve balance and the reserve staked balance. 17 | // Hence we adjust the weights in order to create an arbitrage incentive which, when realized, will subsequently equalize the pool. 18 | // 19 | // Input: 20 | // - Let t denote the primary reserve token staked balance 21 | // - Let s denote the primary reserve token balance 22 | // - Let r denote the secondary reserve token balance 23 | // - Let q denote the numerator of the off-chain price 24 | // - Let p denote the denominator of the off-chain price 25 | // Where p primary tokens are equal to q secondary tokens 26 | // 27 | // Output: 28 | // - Solve the equation x * (s / t) ^ x = (t / r) * (q / p) 29 | // - Return x / (x + 1) as the weight of the primary reserve token 30 | // - Return 1 / (x + 1) as the weight of the secondary reserve token 31 | // 32 | // If the rate-provider provides the rates for a common unit, for example: 33 | // - P = 2 ==> 2 primary reserve tokens = 1 ether 34 | // - Q = 3 ==> 3 secondary reserve tokens = 1 ether 35 | // Then you can simply use p = P and q = Q 36 | // 37 | // If the rate-provider provides the rates for a single unit, for example: 38 | // - P = 2 ==> 1 primary reserve token = 2 ethers 39 | // - Q = 3 ==> 1 secondary reserve token = 3 ethers 40 | // Then you can simply use p = Q and q = P 41 | 42 | /** 43 | * @dev Equalize the weights of a given pool while opting for accuracy over performance 44 | * 45 | * @param t The primary reserve token staked balance 46 | * @param s The primary reserve token balance 47 | * @param r The secondary reserve token balance 48 | * @param q The numerator of the off-chain price 49 | * @param p The denominator of the off-chain price 50 | * 51 | * Note that `numerator / denominator` should represent the amount of secondary tokens equal to one primary token 52 | * 53 | * @return The weight of the primary reserve token and the weight of the secondary reserve token, both in ppm units 54 | */ 55 | function equalizeExact(uint256 t, uint256 s, uint256 r, uint256 q, uint256 p) internal pure returns (uint256, uint256) { unchecked { 56 | return equalize(t, s, r, q, p, AdvancedMath.solveExact); 57 | }} 58 | 59 | /** 60 | * @dev Equalize the weights of a given pool while opting for performance over accuracy 61 | * 62 | * @param t The primary reserve token staked balance 63 | * @param s The primary reserve token balance 64 | * @param r The secondary reserve token balance 65 | * @param q The numerator of the off-chain price 66 | * @param p The denominator of the off-chain price 67 | * 68 | * Note that `numerator / denominator` should represent the amount of secondary tokens equal to one primary token 69 | * 70 | * @return The weight of the primary reserve token and the weight of the secondary reserve token, both in ppm units 71 | */ 72 | function equalizeQuick(uint256 t, uint256 s, uint256 r, uint256 q, uint256 p) internal pure returns (uint256, uint256) { unchecked { 73 | return equalize(t, s, r, q, p, AdvancedMath.solveQuick); 74 | }} 75 | 76 | /** 77 | * @dev Equalize the weights of a given pool 78 | * 79 | * @param t The primary reserve token staked balance 80 | * @param s The primary reserve token balance 81 | * @param r The secondary reserve token balance 82 | * @param q The numerator of the off-chain price 83 | * @param p The denominator of the off-chain price 84 | * @param AdvancedMath_solveFunction An equation solver 85 | * 86 | * Note that `numerator / denominator` should represent the amount of secondary tokens equal to one primary token 87 | * 88 | * @return The weight of the primary reserve token and the weight of the secondary reserve token, both in ppm units 89 | */ 90 | function equalize( 91 | uint256 t, uint256 s, uint256 r, uint256 q, uint256 p, 92 | function (uint256, uint256, uint256, uint256) pure returns (uint256, uint256) AdvancedMath_solveFunction 93 | ) private pure returns (uint256, uint256) { unchecked { 94 | if (t == s) 95 | require(t > 0 || r > 0, "invalid balance"); 96 | else 97 | require(t > 0 && s > 0 && r > 0, "invalid balance"); 98 | require(q > 0 && p > 0, "invalid rate"); 99 | 100 | (uint256 tq, uint256 rp) = FractionMath.productRatio(t, q, r, p); 101 | (uint256 xn, uint256 xd) = AdvancedMath_solveFunction(s, t, tq, rp); 102 | (uint256 w1, uint256 w2) = FractionMath.normalizedRatio(xn, xd, MAX_WEIGHT); 103 | 104 | return (w1, w2); 105 | }} 106 | } 107 | -------------------------------------------------------------------------------- /project/contracts/FractionMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity 0.8.28; 3 | 4 | import "./IntegralMath.sol"; 5 | 6 | library FractionMath { 7 | uint256 internal constant MAX_EXP_BIT_LEN = 4; 8 | uint256 internal constant MAX_EXP = 2 ** MAX_EXP_BIT_LEN - 1; 9 | uint256 internal constant MAX_UINT128 = type(uint128).max; 10 | 11 | /** 12 | * @dev Compute the power of a given ratio while opting for accuracy over performance 13 | * 14 | * @param n The ratio numerator 15 | * @param d The ratio denominator 16 | * @param exp The exponentiation value 17 | * 18 | * @return The powered ratio numerator 19 | * @return The powered ratio denominator 20 | */ 21 | function poweredRatioExact(uint256 n, uint256 d, uint256 exp) internal pure returns (uint256, uint256) { unchecked { 22 | return poweredRatio(n, d, exp, productRatio); 23 | }} 24 | 25 | /** 26 | * @dev Compute the power of a given ratio while opting for performance over accuracy 27 | * 28 | * @param n The ratio numerator 29 | * @param d The ratio denominator 30 | * @param exp The exponentiation value 31 | * 32 | * @return The powered ratio numerator 33 | * @return The powered ratio denominator 34 | */ 35 | function poweredRatioQuick(uint256 n, uint256 d, uint256 exp) internal pure returns (uint256, uint256) { unchecked { 36 | return poweredRatio(n, d, exp, mulRatio128); 37 | }} 38 | 39 | /** 40 | * @dev Compute the product of two given ratios 41 | * 42 | * @param xn The 1st ratio numerator 43 | * @param yn The 2nd ratio numerator 44 | * @param xd The 1st ratio denominator 45 | * @param yd The 2nd ratio denominator 46 | * 47 | * @return The product ratio numerator 48 | * @return The product ratio denominator 49 | */ 50 | function productRatio(uint256 xn, uint256 yn, uint256 xd, uint256 yd) internal pure returns (uint256, uint256) { unchecked { 51 | uint256 n = IntegralMath.minFactor(xn, yn); 52 | uint256 d = IntegralMath.minFactor(xd, yd); 53 | uint256 z = n > d ? n : d; 54 | return (IntegralMath.mulDivC(xn, yn, z), IntegralMath.mulDivC(xd, yd, z)); 55 | }} 56 | 57 | /** 58 | * @dev Reduce the components of a given ratio to fit up to a given threshold 59 | * 60 | * @param n The ratio numerator 61 | * @param d The ratio denominator 62 | * @param cap The desired threshold 63 | * 64 | * @return The reduced ratio numerator 65 | * @return The reduced ratio denominator 66 | */ 67 | function reducedRatio(uint256 n, uint256 d, uint256 cap) internal pure returns (uint256, uint256) { unchecked { 68 | if (n < d) 69 | (n, d) = reducedRatioCalc(n, d, cap); 70 | else 71 | (d, n) = reducedRatioCalc(d, n, cap); 72 | return (n, d); 73 | }} 74 | 75 | /** 76 | * @dev Normalize the components of a given ratio to sum up to a given scale 77 | * 78 | * @param n The ratio numerator 79 | * @param d The ratio denominator 80 | * @param scale The desired scale 81 | * 82 | * @return The normalized ratio numerator 83 | * @return The normalized ratio denominator 84 | */ 85 | function normalizedRatio(uint256 n, uint256 d, uint256 scale) internal pure returns (uint256, uint256) { unchecked { 86 | if (n < d) 87 | (n, d) = normalizedRatioCalc(n, d, scale); 88 | else 89 | (d, n) = normalizedRatioCalc(d, n, scale); 90 | return (n, d); 91 | }} 92 | 93 | /** 94 | * @dev Compute the power of a given ratio 95 | * 96 | * @param n The ratio numerator 97 | * @param d The ratio denominator 98 | * @param exp The exponentiation value 99 | * @param safeRatio The computing function 100 | * 101 | * @return The powered ratio numerator 102 | * @return The powered ratio denominator 103 | */ 104 | function poweredRatio( 105 | uint256 n, uint256 d, uint256 exp, 106 | function (uint256, uint256, uint256, uint256) pure returns (uint256, uint256) safeRatio 107 | ) private pure returns (uint256, uint256) { unchecked { 108 | require(exp <= MAX_EXP, "exp too large"); 109 | 110 | uint256[MAX_EXP_BIT_LEN] memory ns; 111 | uint256[MAX_EXP_BIT_LEN] memory ds; 112 | 113 | (ns[0], ds[0]) = safeRatio(n, 1, d, 1); 114 | for (uint256 i = 0; (exp >> i) > 1; ++i) { 115 | (ns[i + 1], ds[i + 1]) = safeRatio(ns[i], ns[i], ds[i], ds[i]); 116 | } 117 | 118 | n = 1; 119 | d = 1; 120 | 121 | for (uint256 i = 0; (exp >> i) > 0; ++i) { 122 | if (((exp >> i) & 1) > 0) { 123 | (n, d) = safeRatio(n, ns[i], d, ds[i]); 124 | } 125 | } 126 | 127 | return (n, d); 128 | }} 129 | 130 | /** 131 | * @dev Reduce the components of a given ratio to fit up to a given threshold, 132 | * under the implicit assumption that the ratio is smaller than or equal to 1 133 | * 134 | * @param n The ratio numerator 135 | * @param d The ratio denominator 136 | * @param cap The desired threshold 137 | * 138 | * @return The reduced ratio numerator 139 | * @return The reduced ratio denominator 140 | */ 141 | function reducedRatioCalc(uint256 n, uint256 d, uint256 cap) private pure returns (uint256, uint256) { unchecked { 142 | if (d > cap) { 143 | n = IntegralMath.mulDivR(n, cap, d); 144 | d = cap; 145 | } 146 | return (n, d); 147 | }} 148 | 149 | /** 150 | * @dev Normalize the components of a given ratio to sum up to a given scale, 151 | * under the implicit assumption that the ratio is smaller than or equal to 1 152 | * 153 | * @param n The ratio numerator 154 | * @param d The ratio denominator 155 | * @param scale The desired scale 156 | * 157 | * @return The normalized ratio numerator 158 | * @return The normalized ratio denominator 159 | */ 160 | function normalizedRatioCalc(uint256 n, uint256 d, uint256 scale) private pure returns (uint256, uint256) { unchecked { 161 | if (n > ~d) { 162 | uint256 x = unsafeAdd(n, d) + 1; 163 | uint256 y = IntegralMath.mulDivF(x, n / 2, n / 2 + d / 2); 164 | n -= y; 165 | d -= x - y; 166 | } 167 | uint256 z = IntegralMath.mulDivR(scale, n, n + d); 168 | return(z, scale - z); 169 | }} 170 | 171 | /** 172 | * @dev Compute the product of two ratios and reduce the components of the result to 128 bits, 173 | * under the implicit assumption that the components of the product are not larger than 256 bits 174 | * 175 | * @param xn The 1st ratio numerator 176 | * @param yn The 2nd ratio numerator 177 | * @param xd The 1st ratio denominator 178 | * @param yd The 2nd ratio denominator 179 | * 180 | * @return The product ratio numerator 181 | * @return The product ratio denominator 182 | */ 183 | function mulRatio128(uint256 xn, uint256 yn, uint256 xd, uint256 yd) private pure returns (uint256, uint256) { unchecked { 184 | return reducedRatio(xn * yn, xd * yd, MAX_UINT128); 185 | }} 186 | } 187 | -------------------------------------------------------------------------------- /project/contracts/IntegralMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity 0.8.28; 3 | 4 | import "./common/Uint256.sol"; 5 | 6 | library IntegralMath { 7 | /** 8 | * @dev Compute the largest integer smaller than or equal to the binary logarithm of `n` 9 | */ 10 | function floorLog2(uint256 n) internal pure returns (uint8) { unchecked { 11 | uint8 res = 0; 12 | 13 | if (n < 256) { 14 | // at most 8 iterations 15 | while (n > 1) { 16 | n >>= 1; 17 | res += 1; 18 | } 19 | } 20 | else { 21 | // exactly 8 iterations 22 | for (uint8 s = 128; s > 0; s >>= 1) { 23 | if (n >= 1 << s) { 24 | n >>= s; 25 | res |= s; 26 | } 27 | } 28 | } 29 | 30 | return res; 31 | }} 32 | 33 | /** 34 | * @dev Compute the largest integer smaller than or equal to the square root of `n` 35 | */ 36 | function floorSqrt(uint256 n) internal pure returns (uint256) { unchecked { 37 | if (n > 63) { 38 | uint256 x = n; 39 | uint256 y = 1 << (floorLog2(n) / 2 + 1); 40 | while (x > y) { 41 | x = y; 42 | y = (x + n / x) >> 1; 43 | } 44 | return x; 45 | } 46 | return 0x7777777777777776666666666666555555555554444444443333333222221110 >> (n * 4) & 0xf; 47 | }} 48 | 49 | /** 50 | * @dev Compute the smallest integer larger than or equal to the square root of `n` 51 | */ 52 | function ceilSqrt(uint256 n) internal pure returns (uint256) { unchecked { 53 | uint256 x = floorSqrt(n); 54 | return x ** 2 == n ? x : x + 1; 55 | }} 56 | 57 | /** 58 | * @dev Compute the largest integer smaller than or equal to the cubic root of `n` 59 | */ 60 | function floorCbrt(uint256 n) internal pure returns (uint256) { unchecked { 61 | if (n > 84) { 62 | uint256 x = n; 63 | uint256 y = 1 << (floorLog2(n) / 3 + 1); 64 | while (x > y) { 65 | x = y; 66 | y = ((x << 1) + n / x ** 2) / 3; 67 | } 68 | return x; 69 | } 70 | return 0x49249249249249246db6db6db6db6db6db6db6db6db692492492492492249248 >> (n * 3) & 0x7; 71 | }} 72 | 73 | /** 74 | * @dev Compute the smallest integer larger than or equal to the cubic root of `n` 75 | */ 76 | function ceilCbrt(uint256 n) internal pure returns (uint256) { unchecked { 77 | uint256 x = floorCbrt(n); 78 | return x ** 3 == n ? x : x + 1; 79 | }} 80 | 81 | /** 82 | * @dev Compute the nearest integer (half being rounded upwards) to `n / d` 83 | */ 84 | function roundDiv(uint256 n, uint256 d) internal pure returns (uint256) { unchecked { 85 | return n / d + (n % d) / (d - d / 2); 86 | }} 87 | 88 | /** 89 | * @dev Compute the smallest integer `z` such that `x * y / z <= 2 ^ 256 - 1` 90 | */ 91 | function minFactor(uint256 x, uint256 y) internal pure returns (uint256) { unchecked { 92 | (uint256 hi, uint256 lo) = mul512(x, y); 93 | return hi > ~lo ? hi + 2 : hi + 1; 94 | // General: 95 | // - find the smallest integer `z` such that `x * y / z <= 2 ^ 256 - 1` 96 | // - the value of `x * y` is represented via `2 ^ 256 * hi + lo` 97 | // - the expression `~lo` is equivalent to `2 ^ 256 - 1 - lo` 98 | // 99 | // Safety: 100 | // - if `x < 2 ^ 256 - 1` or `y < 2 ^ 256 - 1` 101 | // then `hi < 2 ^ 256 - 2` 102 | // hence neither `hi + 1` nor `hi + 2` overflows 103 | // - if `x = 2 ^ 256 - 1` and `y = 2 ^ 256 - 1` 104 | // then `hi = 2 ^ 256 - 2 = ~lo` 105 | // hence `hi + 1`, which does not overflow, is computed 106 | // 107 | // Symbols: 108 | // - let `H` denote `hi` 109 | // - let `L` denote `lo` 110 | // - let `N` denote `2 ^ 256 - 1` 111 | // 112 | // Inference: 113 | // `x * y / z <= 2 ^ 256 - 1` <--> 114 | // `x * y / (2 ^ 256 - 1) <= z` <--> 115 | // `((N + 1) * H + L) / N <= z` <--> 116 | // `(N * H + H + L) / N <= z` <--> 117 | // `H + (H + L) / N <= z` 118 | // 119 | // Inference: 120 | // `0 <= H <= N && 0 <= L <= N` <--> 121 | // `0 <= H + L <= N + N` <--> 122 | // `0 <= H + L <= N * 2` <--> 123 | // `0 <= (H + L) / N <= 2` 124 | // 125 | // Inference: 126 | // - `0 = (H + L) / N` --> `H + L = 0` --> `x * y = 0` --> `z = 1 = H + 1` 127 | // - `0 < (H + L) / N <= 1` --> `H + (H + L) / N <= H + 1` --> `z = H + 1` 128 | // - `1 < (H + L) / N <= 2` --> `H + (H + L) / N <= H + 2` --> `z = H + 2` 129 | // 130 | // Implementation: 131 | // - if `hi > ~lo`: 132 | // `~L < H <= N` <--> 133 | // `N - L < H <= N` <--> 134 | // `N < H + L <= N + L` <--> 135 | // `1 < (H + L) / N <= 2` <--> 136 | // `H + 1 < H + (H + L) / N <= H + 2` <--> 137 | // `z = H + 2` 138 | // - if `hi <= ~lo`: 139 | // `H <= ~L` <--> 140 | // `H <= N - L` <--> 141 | // `H + L <= N` <--> 142 | // `(H + L) / N <= 1` <--> 143 | // `H + (H + L) / N <= H + 1` <--> 144 | // `z = H + 1` 145 | }} 146 | 147 | /** 148 | * @dev Compute the largest integer smaller than or equal to `x * y / 2 ^ s` 149 | */ 150 | function mulShrF(uint256 x, uint256 y, uint8 s) internal pure returns (uint256) { unchecked { 151 | (uint256 xyh, uint256 xyl) = mul512(x, y); 152 | require(xyh < 1 << s); 153 | return (xyh << (256 - s)) | (xyl >> s); 154 | }} 155 | 156 | /** 157 | * @dev Compute the smallest integer larger than or equal to `x * y / 2 ^ s` 158 | */ 159 | function mulShrC(uint256 x, uint256 y, uint8 s) internal pure returns (uint256) { unchecked { 160 | uint256 w = mulShrF(x, y, s); 161 | if (mulmod(x, y, 1 << s) > 0) 162 | return safeAdd(w, 1); 163 | return w; 164 | }} 165 | 166 | /** 167 | * @dev Compute the largest integer smaller than or equal to `x * y / z` 168 | */ 169 | function mulDivF(uint256 x, uint256 y, uint256 z) internal pure returns (uint256) { unchecked { 170 | (uint256 xyh, uint256 xyl) = mul512(x, y); 171 | if (xyh == 0) { // `x * y < 2 ^ 256` 172 | return xyl / z; 173 | } 174 | if (xyh < z) { // `x * y / z < 2 ^ 256` 175 | uint256 m = mulmod(x, y, z); // `m = x * y % z` 176 | (uint256 nh, uint256 nl) = sub512(xyh, xyl, m); // `n = x * y - m` hence `n / z = floor(x * y / z)` 177 | if (nh == 0) { // `n < 2 ^ 256` 178 | return nl / z; 179 | } 180 | uint256 p = unsafeSub(0, z) & z; // `p` is the largest power of 2 which `z` is divisible by 181 | uint256 q = div512(nh, nl, p); // `n` is divisible by `p` because `n` is divisible by `z` and `z` is divisible by `p` 182 | uint256 r = inv256(z / p); // `z / p = 1 mod 2` hence `inverse(z / p) = 1 mod 2 ^ 256` 183 | return unsafeMul(q, r); // `q * r = (n / p) * inverse(z / p) = n / z` 184 | } 185 | revert(); // `x * y / z >= 2 ^ 256` 186 | }} 187 | 188 | /** 189 | * @dev Compute the smallest integer larger than or equal to `x * y / z` 190 | */ 191 | function mulDivC(uint256 x, uint256 y, uint256 z) internal pure returns (uint256) { unchecked { 192 | uint256 w = mulDivF(x, y, z); 193 | if (mulmod(x, y, z) > 0) 194 | return safeAdd(w, 1); 195 | return w; 196 | }} 197 | 198 | /** 199 | * @dev Compute the nearest integer (half being rounded upwards) to `x * y / z` 200 | */ 201 | function mulDivR(uint256 x, uint256 y, uint256 z) internal pure returns (uint256) { unchecked { 202 | uint256 w = mulDivF(x, y, z); 203 | if (mulmod(x, y, z) > (z - 1) / 2) 204 | return safeAdd(w, 1); 205 | return w; 206 | }} 207 | 208 | /** 209 | * @dev Compute the largest integer smaller than or equal to `(x * y) / (z * w)` 210 | */ 211 | function mulDivExF(uint256 x, uint256 y, uint256 z, uint256 w) internal pure returns (uint256) { unchecked { 212 | (uint256 zwh, uint256 zwl) = mul512(z, w); 213 | if (zwh > 0) { 214 | uint256 res = 0; 215 | (uint256 xyh, uint256 xyl) = mul512(x, y); 216 | if (xyh > zwh) { 217 | uint8 xyhn = floorLog2(xyh); 218 | uint8 zwhn = floorLog2(zwh); 219 | while (xyhn > zwhn) { 220 | uint8 n = xyhn - zwhn - 1; 221 | res += 1 << n; // set `res = res + 2 ^ n` 222 | (xyh, xyl) = sub512Ex(xyh, xyl, (zwh << n) | (zwl >> (256 - n)), zwl << n); // set `xy = xy - zw * 2 ^ n` 223 | xyhn = floorLog2(xyh); 224 | } 225 | } 226 | if (xyh > zwh || (xyh == zwh && xyl >= zwl)) // `xy >= zw` 227 | return res + 1; 228 | return res; 229 | } 230 | return mulDivF(x, y, zwl); 231 | }} 232 | 233 | /** 234 | * @dev Compute the smallest integer larger than or equal to `(x * y) / (z * w)` 235 | */ 236 | function mulDivExC(uint256 x, uint256 y, uint256 z, uint256 w) internal pure returns (uint256) { unchecked { 237 | uint256 v = mulDivExF(x, y, z, w); 238 | (uint256 xyh, uint256 xyl) = mul512(x, y); 239 | (uint256 zwh, uint256 zwl) = mul512(z, w); 240 | (uint256 vzwlh, uint256 vzwll) = mul512(v, zwl); 241 | if (xyh == v * zwh + vzwlh && xyl == vzwll) 242 | return v; 243 | return safeAdd(v, 1); 244 | }} 245 | 246 | /** 247 | * @dev Compute the value of `x * y` 248 | */ 249 | function mul512(uint256 x, uint256 y) private pure returns (uint256, uint256) { unchecked { 250 | uint256 p = mulModMax(x, y); 251 | uint256 q = unsafeMul(x, y); 252 | if (p >= q) 253 | return (p - q, q); 254 | return (unsafeSub(p, q) - 1, q); 255 | }} 256 | 257 | /** 258 | * @dev Compute the value of `2 ^ 256 * xh + xl - y`, where `2 ^ 256 * xh + xl >= y` 259 | */ 260 | function sub512(uint256 xh, uint256 xl, uint256 y) private pure returns (uint256, uint256) { unchecked { 261 | if (xl >= y) 262 | return (xh, xl - y); 263 | return (xh - 1, unsafeSub(xl, y)); 264 | }} 265 | 266 | /** 267 | * @dev Compute the value of `(2 ^ 256 * xh + xl) / pow2n`, where `xl` is divisible by `pow2n` 268 | */ 269 | function div512(uint256 xh, uint256 xl, uint256 pow2n) private pure returns (uint256) { unchecked { 270 | uint256 pow2nInv = unsafeAdd(unsafeSub(0, pow2n) / pow2n, 1); // `1 << (256 - n)` 271 | return unsafeMul(xh, pow2nInv) | (xl / pow2n); // `(xh << (256 - n)) | (xl >> n)` 272 | }} 273 | 274 | /** 275 | * @dev Compute the inverse of `d` modulo `2 ^ 256`, where `d` is congruent to `1` modulo `2` 276 | */ 277 | function inv256(uint256 d) private pure returns (uint256) { unchecked { 278 | // approximate the root of `f(x) = 1 / x - d` using the newton–raphson convergence method 279 | uint256 x = 1; 280 | for (uint256 i = 0; i < 8; ++i) 281 | x = unsafeMul(x, unsafeSub(2, unsafeMul(x, d))); // `x = x * (2 - x * d) mod 2 ^ 256` 282 | return x; 283 | }} 284 | 285 | /** 286 | * @dev Compute the value of `(2 ^ 256 * xh + xl) - (2 ^ 256 * yh + yl)`, where `2 ^ 256 * xh + xl >= 2 ^ 256 * yh + yl` 287 | */ 288 | function sub512Ex(uint256 xh, uint256 xl, uint256 yh, uint256 yl) private pure returns (uint256, uint256) { unchecked { 289 | if (xl >= yl) 290 | return (xh - yh, xl - yl); 291 | return (xh - yh - 1, unsafeSub(xl, yl)); 292 | }} 293 | } 294 | -------------------------------------------------------------------------------- /project/contracts/common/Uint256.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity 0.8.28; 3 | 4 | uint256 constant MAX_VAL = type(uint256).max; 5 | 6 | // reverts on overflow 7 | function safeAdd(uint256 x, uint256 y) pure returns (uint256) { 8 | return x + y; 9 | } 10 | 11 | // does not revert on overflow 12 | function unsafeAdd(uint256 x, uint256 y) pure returns (uint256) { unchecked { 13 | return x + y; 14 | }} 15 | 16 | // does not revert on overflow 17 | function unsafeSub(uint256 x, uint256 y) pure returns (uint256) { unchecked { 18 | return x - y; 19 | }} 20 | 21 | // does not revert on overflow 22 | function unsafeMul(uint256 x, uint256 y) pure returns (uint256) { unchecked { 23 | return x * y; 24 | }} 25 | 26 | // does not overflow 27 | function mulModMax(uint256 x, uint256 y) pure returns (uint256) { unchecked { 28 | return mulmod(x, y, MAX_VAL); 29 | }} 30 | -------------------------------------------------------------------------------- /project/contracts/helpers/AdvancedMathUser.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity 0.8.28; 3 | 4 | import "../AdvancedMath.sol"; 5 | 6 | contract AdvancedMathUser { 7 | function solveExact(uint256 a, uint256 b, uint256 c, uint256 d) external pure returns (uint256, uint256) { 8 | return AdvancedMath.solveExact(a, b, c, d); 9 | } 10 | 11 | function solveQuick(uint256 a, uint256 b, uint256 c, uint256 d) external pure returns (uint256, uint256) { 12 | return AdvancedMath.solveQuick(a, b, c, d); 13 | } 14 | 15 | function lambertNegExact(uint256 x) external pure returns (uint256) { 16 | return AdvancedMath.lambertNegExact(x); 17 | } 18 | 19 | function lambertPosExact(uint256 x) external pure returns (uint256) { 20 | return AdvancedMath.lambertPosExact(x); 21 | } 22 | 23 | function lambertNegQuick(uint256 x) external pure returns (uint256) { 24 | return AdvancedMath.lambertNegQuick(x); 25 | } 26 | 27 | function lambertPosQuick(uint256 x) external pure returns (uint256) { 28 | return AdvancedMath.lambertPosQuick(x); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /project/contracts/helpers/AnalyticMathUser.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity 0.8.28; 3 | 4 | import "../AnalyticMath.sol"; 5 | 6 | contract AnalyticMathUser { 7 | function pow(uint256 a, uint256 b, uint256 c, uint256 d) external pure returns (uint256, uint256) { 8 | return AnalyticMath.pow(a, b, c, d); 9 | } 10 | 11 | function log(uint256 a, uint256 b) external pure returns (uint256, uint256) { 12 | return AnalyticMath.log(a, b); 13 | } 14 | 15 | function exp(uint256 a, uint256 b) external pure returns (uint256, uint256) { 16 | return AnalyticMath.exp(a, b); 17 | } 18 | 19 | function fixedLog(uint256 x) external pure returns (uint256) { 20 | return AnalyticMath.fixedLog(x); 21 | } 22 | 23 | function fixedExp(uint256 x) external pure returns (uint256) { 24 | return AnalyticMath.fixedExp(x); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /project/contracts/helpers/BondingCurveUser.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity 0.8.28; 3 | 4 | import "../BondingCurve.sol"; 5 | 6 | contract BondingCurveUser { 7 | function buy(uint256 supply, uint256 balance, uint256 weight, uint256 amount) external pure returns (uint256) { 8 | return BondingCurve.buy(supply, balance, weight, amount); 9 | } 10 | 11 | function sell(uint256 supply, uint256 balance, uint256 weight, uint256 amount) external pure returns (uint256) { 12 | return BondingCurve.sell(supply, balance, weight, amount); 13 | } 14 | 15 | function convert(uint256 balance1, uint256 weight1, uint256 balance2, uint256 weight2, uint256 amount) external pure returns (uint256) { 16 | return BondingCurve.convert(balance1, weight1, balance2, weight2, amount); 17 | } 18 | 19 | function deposit(uint256 supply, uint256 balance, uint256 weights, uint256 amount) external pure returns (uint256) { 20 | return BondingCurve.deposit(supply, balance, weights, amount); 21 | } 22 | 23 | function withdraw(uint256 supply, uint256 balance, uint256 weights, uint256 amount) external pure returns (uint256) { 24 | return BondingCurve.withdraw(supply, balance, weights, amount); 25 | } 26 | 27 | function invest(uint256 supply, uint256 balance, uint256 weights, uint256 amount) external pure returns (uint256) { 28 | return BondingCurve.invest(supply, balance, weights, amount); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /project/contracts/helpers/DynamicCurveUser.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity 0.8.28; 3 | 4 | import "../DynamicCurve.sol"; 5 | 6 | contract DynamicCurveUser { 7 | function equalizeExact(uint256 t, uint256 s, uint256 r, uint256 q, uint256 p) external pure returns (uint256, uint256) { 8 | return DynamicCurve.equalizeExact(t, s, r, q, p); 9 | } 10 | 11 | function equalizeQuick(uint256 t, uint256 s, uint256 r, uint256 q, uint256 p) external pure returns (uint256, uint256) { 12 | return DynamicCurve.equalizeQuick(t, s, r, q, p); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /project/contracts/helpers/FractionMathUser.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity 0.8.28; 3 | 4 | import "../FractionMath.sol"; 5 | 6 | contract FractionMathUser { 7 | function poweredRatioExact(uint256 n, uint256 d, uint256 exp) external pure returns (uint256, uint256) { 8 | return FractionMath.poweredRatioExact(n, d, exp); 9 | } 10 | 11 | function poweredRatioQuick(uint256 n, uint256 d, uint256 exp) external pure returns (uint256, uint256) { 12 | return FractionMath.poweredRatioQuick(n, d, exp); 13 | } 14 | 15 | function productRatio(uint256 xn, uint256 yn, uint256 xd, uint256 yd) external pure returns (uint256, uint256) { 16 | return FractionMath.productRatio(xn, yn, xd, yd); 17 | } 18 | 19 | function reducedRatio(uint256 n, uint256 d, uint256 max) external pure returns (uint256, uint256) { 20 | return FractionMath.reducedRatio(n, d, max); 21 | } 22 | 23 | function normalizedRatio(uint256 n, uint256 d, uint256 scale) external pure returns (uint256, uint256) { 24 | return FractionMath.normalizedRatio(n, d, scale); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /project/contracts/helpers/IntegralMathUser.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity 0.8.28; 3 | 4 | import "../IntegralMath.sol"; 5 | 6 | contract IntegralMathUser { 7 | function floorLog2(uint256 n) external pure returns (uint8) { 8 | return IntegralMath.floorLog2(n); 9 | } 10 | 11 | function floorSqrt(uint256 n) external pure returns (uint256) { 12 | return IntegralMath.floorSqrt(n); 13 | } 14 | 15 | function ceilSqrt(uint256 n) external pure returns (uint256) { 16 | return IntegralMath.ceilSqrt(n); 17 | } 18 | 19 | function floorCbrt(uint256 n) external pure returns (uint256) { 20 | return IntegralMath.floorCbrt(n); 21 | } 22 | 23 | function ceilCbrt(uint256 n) external pure returns (uint256) { 24 | return IntegralMath.ceilCbrt(n); 25 | } 26 | 27 | function roundDiv(uint256 n, uint256 d) external pure returns (uint256) { 28 | return IntegralMath.roundDiv(n, d); 29 | } 30 | 31 | function minFactor(uint256 x, uint256 y) external pure returns (uint256) { 32 | return IntegralMath.minFactor(x, y); 33 | } 34 | 35 | function mulShrF(uint256 x, uint256 y, uint8 s) external pure returns (uint256) { 36 | return IntegralMath.mulShrF(x, y, s); 37 | } 38 | 39 | function mulShrC(uint256 x, uint256 y, uint8 s) external pure returns (uint256) { 40 | return IntegralMath.mulShrC(x, y, s); 41 | } 42 | 43 | function mulDivF(uint256 x, uint256 y, uint256 z) external pure returns (uint256) { 44 | return IntegralMath.mulDivF(x, y, z); 45 | } 46 | 47 | function mulDivC(uint256 x, uint256 y, uint256 z) external pure returns (uint256) { 48 | return IntegralMath.mulDivC(x, y, z); 49 | } 50 | 51 | function mulDivR(uint256 x, uint256 y, uint256 z) external pure returns (uint256) { 52 | return IntegralMath.mulDivR(x, y, z); 53 | } 54 | 55 | function mulDivExF(uint256 x, uint256 y, uint256 z, uint256 w) external pure returns (uint256) { 56 | return IntegralMath.mulDivExF(x, y, z, w); 57 | } 58 | 59 | function mulDivExC(uint256 x, uint256 y, uint256 z, uint256 w) external pure returns (uint256) { 60 | return IntegralMath.mulDivExC(x, y, z, w); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /project/customization/PrintAdvancedMathConstants.py: -------------------------------------------------------------------------------- 1 | from util import hex_len 2 | from core import AdvancedMath 3 | from constants import SCALE_1 4 | from constants import LAMBERT_NEG2_SIZE_N 5 | from constants import LAMBERT_NEG2_SIZE_D 6 | from constants import LAMBERT_NEG2_SAMPLES 7 | from constants import LAMBERT_POS2_SIZE_N 8 | from constants import LAMBERT_POS2_SIZE_D 9 | from constants import LAMBERT_POS2_SAMPLES 10 | 11 | 12 | lambertNegParams = AdvancedMath.lambertNegParams(1<> SCALE_1; res += xi * {}; // add x^({}-1) * ({}! * {}^({}-1) / {}!)'.format(str2,str3,str1,str3,str3,str3)) 26 | print('') 27 | print(' return res / {} + FIXED_1 + x; // divide by {}! and then add x^(1-1) * (1^(1-1) / 1!) + x^(2-1) * (2^(2-1) / 2!)'.format(str0,str1)) 28 | print(' }}') 29 | -------------------------------------------------------------------------------- /project/customization/PrintAdvancedMathLambertPos1.py: -------------------------------------------------------------------------------- 1 | from util import dec_str 2 | from util import hex_str 3 | from core import AdvancedMath 4 | from constants import SCALE_1 5 | from constants import LAMBERT_POS1_TERMS 6 | from constants import LAMBERT_POS2_SIZE_N 7 | from constants import LAMBERT_POS2_SIZE_D 8 | from constants import LAMBERT_POS2_SAMPLES 9 | 10 | 11 | terms = AdvancedMath.lambertPos1Terms(1<> SCALE_1; res {}= xi * {}; // {} x^({}-1) * ({}! * {}^({}-1) / {}!)'.format(str4,str2,str5,str3,str1,str3,str3,str3)) 28 | print('') 29 | print(' return res / {} + FIXED_1 - x; // divide by {}! and then add x^(1-1) * (1^(1-1) / 1!) - x^(2-1) * (2^(2-1) / 2!)'.format(str0,str1)) 30 | print(' }}') 31 | -------------------------------------------------------------------------------- /project/customization/PrintAnalyticMathConstants.py: -------------------------------------------------------------------------------- 1 | from util import hex_len 2 | from core import AnalyticMath 3 | from constants import SCALE_1 4 | from constants import LOG_MAX_HI_TERM_VAL 5 | from constants import EXP_MAX_HI_TERM_VAL 6 | 7 | 8 | FIXED_1 = 1 << SCALE_1 9 | LN2_MIN = AnalyticMath.ln2Min(FIXED_1) 10 | LN2_MAX = AnalyticMath.ln2Max(FIXED_1) 11 | LOG_MID = AnalyticMath.logMid(FIXED_1,LOG_MAX_HI_TERM_VAL) 12 | EXP_MID = AnalyticMath.expMid(FIXED_1,EXP_MAX_HI_TERM_VAL) 13 | EXP_MAX = LN2_MAX*(259-len(bin(FIXED_1*2-1))) 14 | 15 | 16 | maxLen = hex_len(max([FIXED_1,LN2_MIN,LN2_MAX,LOG_MID,EXP_MID,EXP_MAX])) 17 | 18 | 19 | print(f' uint8 internal constant SCALE_1 = {SCALE_1:#0{maxLen}x};') 20 | print(f' uint256 internal constant FIXED_1 = {FIXED_1:#0{maxLen}x};') 21 | print(f' uint256 internal constant LN2_MIN = {LN2_MIN:#0{maxLen}x};') 22 | print(f' uint256 internal constant LN2_MAX = {LN2_MAX:#0{maxLen}x};') 23 | print(f' uint256 internal constant LOG_MID = {LOG_MID:#0{maxLen}x};') 24 | print(f' uint256 internal constant EXP_MID = {EXP_MID:#0{maxLen}x};') 25 | print(f' uint256 internal constant EXP_MAX = {EXP_MAX:#0{maxLen}x};') 26 | -------------------------------------------------------------------------------- /project/customization/PrintAnalyticMathOptimalExp.py: -------------------------------------------------------------------------------- 1 | from util import dec_str 2 | from util import hex_str 3 | from core import AnalyticMath 4 | from constants import SCALE_1 5 | from constants import EXP_MAX_HI_TERM_VAL 6 | from constants import EXP_NUM_OF_HI_TERMS 7 | 8 | 9 | hiTerms,loTerms = AnalyticMath.optimalExpTerms(1<> SCALE_1; res += z * {}; // add y^{} * ({}! / {}!)'.format(str2,str3,len(loTerms),str3)) 32 | print(' res = res / {} + y + FIXED_1; // divide by {}! and then add y^1 / 1! + y^0 / 0!'.format(str1,len(loTerms))) 33 | print('') 34 | for n in range(len(hiTerms)-1): 35 | str4 = hex_str(hiTerms[n].bit,hiTerms[-1].bit) 36 | str5 = hex_str(hiTerms[n].num,hiTerms[+0].num) 37 | str6 = hex_str(hiTerms[n].den,hiTerms[+0].den) 38 | str7 = '{0:+{1}d}'.format(hiTermIndexMin+n,hiTermIndexLen) 39 | print(' if ((x & {}) != 0) res = res * {} / {}; // multiply by e^2^({})'.format(str4,str5,str6,str7)) 40 | print('') 41 | print(' return res;') 42 | print(' }}') 43 | -------------------------------------------------------------------------------- /project/customization/PrintAnalyticMathOptimalLog.py: -------------------------------------------------------------------------------- 1 | from util import dec_str 2 | from util import hex_str 3 | from core import AnalyticMath 4 | from constants import SCALE_1 5 | from constants import LOG_MAX_HI_TERM_VAL 6 | from constants import LOG_NUM_OF_HI_TERMS 7 | 8 | 9 | hiTerms,loTerms = AnalyticMath.optimalLogTerms(1< {}) {{res |= {}; x = x * {} / {};}} // add 2^({})'.format(str0,str1,str2,str3,str4)) 31 | print('') 32 | print(' z = y = x - FIXED_1;') 33 | print(' w = y * y >> SCALE_1;') 34 | for n in range(len(loTerms)): 35 | str5 = hex_str(loTerms[n].num,loTerms[+0].num) 36 | str6 = hex_str(loTerms[n].den,loTerms[-1].den) 37 | str7 = dec_str(2*n+1,len(loTerms)*2-1) 38 | str8 = dec_str(2*n+2,len(loTerms)*2-0) 39 | str9 = ''.join([[c,' '][(n+1)//len(loTerms)] for c in 'z = z * w >> SCALE_1;']) 40 | print(' res += z * ({} - y) / {}; {} // add y^{} / {} - y^{} / {}'.format(str5,str6,str9,str7,str7,str8,str8)) 41 | print('') 42 | print(' return res;') 43 | print(' }}') 44 | -------------------------------------------------------------------------------- /project/customization/PrintTestConstants.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import constants 4 | 5 | 6 | stdout = sys.stdout 7 | sys.stdout = open(os.devnull,'w') 8 | import PrintAnalyticMathConstants as AnalyticMath 9 | import PrintAdvancedMathConstants as AdvancedMath 10 | sys.stdout.close() 11 | sys.stdout = stdout 12 | 13 | 14 | print(f'module.exports.LOG_MAX_HI_TERM_VAL = {constants.LOG_MAX_HI_TERM_VAL};') 15 | print(f'module.exports.LOG_NUM_OF_HI_TERMS = {constants.LOG_NUM_OF_HI_TERMS};') 16 | print(f'module.exports.EXP_MAX_HI_TERM_VAL = {constants.EXP_MAX_HI_TERM_VAL};') 17 | print(f'module.exports.EXP_NUM_OF_HI_TERMS = {constants.EXP_NUM_OF_HI_TERMS};') 18 | print(f'module.exports.FIXED_1 = "{AnalyticMath.FIXED_1:#0{AnalyticMath.maxLen}x}";') 19 | print(f'module.exports.LOG_MID = "{AnalyticMath.LOG_MID:#0{AnalyticMath.maxLen}x}";') 20 | print(f'module.exports.EXP_MID = "{AnalyticMath.EXP_MID:#0{AnalyticMath.maxLen}x}";') 21 | print(f'module.exports.EXP_MAX = "{AnalyticMath.EXP_MAX:#0{AnalyticMath.maxLen}x}";') 22 | print(f'module.exports.LAMBERT_NEG1_MAXVAL = "{AdvancedMath.LAMBERT_NEG1_MAXVAL:#0{AdvancedMath.maxLen}x}";') 23 | print(f'module.exports.LAMBERT_NEG2_MAXVAL = "{AdvancedMath.LAMBERT_NEG2_MAXVAL:#0{AdvancedMath.maxLen}x}";') 24 | print(f'module.exports.LAMBERT_POS1_MAXVAL = "{AdvancedMath.LAMBERT_POS1_MAXVAL:#0{AdvancedMath.maxLen}x}";') 25 | print(f'module.exports.LAMBERT_POS2_MAXVAL = "{AdvancedMath.LAMBERT_POS2_MAXVAL:#0{AdvancedMath.maxLen}x}";') 26 | print(f'module.exports.LAMBERT_EXACT_LIMIT = "{AdvancedMath.LAMBERT_EXACT_LIMIT:#066x}";') 27 | -------------------------------------------------------------------------------- /project/customization/constants.py: -------------------------------------------------------------------------------- 1 | SCALE_1 = 127 # The scaling factor 2 | 3 | 4 | LOG_MAX_HI_TERM_VAL = 0 # The input to function 'optimalLog' must be smaller than e ^ 2 ^ LOG_MAX_HI_TERM_VAL 5 | LOG_NUM_OF_HI_TERMS = 8 # Compute 2 ^ (LOG_MAX_HI_TERM_VAL - LOG_NUM_OF_HI_TERMS + n - 1) for n = LOG_NUM_OF_HI_TERMS to 1 6 | 7 | 8 | EXP_MAX_HI_TERM_VAL = 3 # The input to function 'optimalExp' must be smaller than 2 ^ EXP_MAX_HI_TERM_VAL 9 | EXP_NUM_OF_HI_TERMS = 6 # Compute e ^ 2 ^ (EXP_MAX_HI_TERM_VAL - EXP_NUM_OF_HI_TERMS + n - 1) for n = 1 to EXP_NUM_OF_HI_TERMS 10 | 11 | 12 | LAMBERT_NEG1_TERMS = 48 # The maximum number of terms used for approximating the Lambert W Function inside the negative convergence radius 13 | LAMBERT_NEG2_SIZE_N = 1 # The portion of the negative convergence radius to approximate using a lookup table instead of with a Taylor series 14 | LAMBERT_NEG2_SIZE_D = 100 # The portion of the negative convergence radius to approximate using a lookup table instead of with a Taylor series 15 | LAMBERT_NEG2_SAMPLES = 16 # The number of samples used for approximating the Lambert W Function towards the end of the negative convergence radius 16 | 17 | 18 | LAMBERT_POS1_TERMS = 48 # The maximum number of terms used for approximating the Lambert W Function inside the positive convergence radius 19 | LAMBERT_POS2_SIZE_N = 24 # The range above the positive convergence radius to approximate using a lookup table instead of with a Taylor series 20 | LAMBERT_POS2_SIZE_D = 1 # The range above the positive convergence radius to approximate using a lookup table instead of with a Taylor series 21 | LAMBERT_POS2_SAMPLES = 128 # The number of samples used for approximating the Lambert W Function beyond the end of the positive convergence radius 22 | -------------------------------------------------------------------------------- /project/customization/core/AdvancedMath.py: -------------------------------------------------------------------------------- 1 | from core import Decimal 2 | from core import MAX_VAL 3 | from core import INV_EXP 4 | from core import checked 5 | from core import bsearch 6 | from math import factorial 7 | 8 | 9 | def lambertExactLimit(fixed1): 10 | return bsearch(lambertPosExact, fixed1, MAX_VAL, fixed1) 11 | 12 | 13 | def lambertNeg1Terms(fixed1, maxNumOfTerms, sizeN, sizeD, numOfSamples): 14 | maxVal, _ = lambertNegLimits(fixed1, sizeN, sizeD, numOfSamples) 15 | return bsearch(lambertNeg1, 0, maxNumOfTerms, maxVal, fixed1) 16 | 17 | 18 | def lambertPos1Terms(fixed1, maxNumOfTerms, sizeN, sizeD, numOfSamples): 19 | maxVal, _ = lambertPosLimits(fixed1, sizeN, sizeD, numOfSamples) 20 | return bsearch(lambertPos1, 0, maxNumOfTerms, maxVal, fixed1) 21 | 22 | 23 | def lambertNegParams(fixed1, sizeN, sizeD, numOfSamples): 24 | bgn, end = lambertNegLimits(fixed1, sizeN, sizeD, numOfSamples) 25 | return bgn, end, *lambertLutParams(fixed1, numOfSamples, bgn, end, -1) 26 | 27 | 28 | def lambertPosParams(fixed1, sizeN, sizeD, numOfSamples): 29 | bgn, end = lambertPosLimits(fixed1, sizeN, sizeD, numOfSamples) 30 | return bgn, end, *lambertLutParams(fixed1, numOfSamples, bgn, end, +1) 31 | 32 | 33 | def lambertNegLimits(fixed1, sizeN, sizeD, numOfSamples): 34 | radius = int(INV_EXP * fixed1) 35 | return radius - radius * sizeN // sizeD // (numOfSamples - 1) * (numOfSamples - 1), radius 36 | 37 | 38 | def lambertPosLimits(fixed1, sizeN, sizeD, numOfSamples): 39 | radius = int(INV_EXP * fixed1) 40 | return radius, radius + fixed1 * sizeN // sizeD // (numOfSamples - 1) * (numOfSamples - 1) 41 | 42 | 43 | def lambertLutParams(fixed1, numOfSamples, bgn, end, sign): 44 | sample = (end - (bgn + 1)) // (numOfSamples - 1) + 1 45 | values = [int(lambertRatio(Decimal(sign * (bgn + 1 + sample * i)) / fixed1) * fixed1) for i in range(numOfSamples)] 46 | t_size = (len(bin(max(values))) - 3) // 8 + 1 47 | t_mask = (1 << (t_size * 8)) - 1 48 | return sample, t_size, t_mask, values 49 | 50 | 51 | def lambertPosExact(x, fixed1): 52 | y = int((Decimal(x) / fixed1).ln() * fixed1) 53 | z = y * y // fixed1 54 | y = fixed1 * (z + fixed1) // (y + fixed1) 55 | for _ in range(7): 56 | e = checked(int((Decimal(y) / fixed1).exp() * fixed1)) 57 | f = checked(y * e // fixed1) 58 | g = checked(y * f // fixed1) 59 | y = checked(fixed1 * checked(g + x) // checked(f + e)) 60 | return x 61 | 62 | 63 | def lambertNeg1(numOfTerms, x, fixed1): 64 | terms = lambertBinomial(numOfTerms) 65 | res = 0 66 | xi = x 67 | for i in range(2, len(terms)): 68 | xi = checked(xi * x) // fixed1 69 | res = checked(res + checked(xi * terms[i])) 70 | res = checked(checked(res // terms[0] + fixed1) + x) 71 | return terms 72 | 73 | 74 | def lambertPos1(numOfTerms, x, fixed1): 75 | terms = lambertBinomial(numOfTerms) 76 | res = 0 77 | xi = x 78 | for i in range(2, len(terms)): 79 | xi = checked(xi * x) // fixed1 80 | res = checked(res + checked(xi * terms[i]) * (-1) ** i) 81 | res = checked(checked(res // terms[0] + fixed1) - x) 82 | return terms 83 | 84 | 85 | def lambertBinomial(numOfTerms): 86 | maxFactorial = factorial(numOfTerms - 1) 87 | return [maxFactorial // factorial(i) * i ** (i - 1) for i in range(1, numOfTerms)] 88 | 89 | 90 | def lambertRatio(x): 91 | y = x if x < 1 else x.ln() 92 | for _ in range(8): 93 | e = y.exp() 94 | f = y * e 95 | if f == x: break 96 | y = (y * f + x) / (f + e) 97 | return y / x 98 | -------------------------------------------------------------------------------- /project/customization/core/AnalyticMath.py: -------------------------------------------------------------------------------- 1 | from core import Decimal 2 | from core import MAX_VAL 3 | from core import LOG_TWO 4 | from core import two_pow 5 | from core import checked 6 | from math import factorial 7 | from collections import namedtuple 8 | 9 | 10 | def ln2Min(fixed1): 11 | return (LOG_TWO * fixed1).__floor__() 12 | 13 | 14 | def ln2Max(fixed1): 15 | return (LOG_TWO * fixed1).__ceil__() 16 | 17 | 18 | def logMid(fixed1, maxHiTermVal): 19 | return int(two_pow(maxHiTermVal).exp() * fixed1) + 1 20 | 21 | 22 | def expMid(fixed1, maxHiTermVal): 23 | return int(two_pow(maxHiTermVal) * fixed1 - 1) + 1 24 | 25 | 26 | def optimalLogTerms(fixed1, maxHiTermVal, numOfHiTerms): 27 | HiTerm = namedtuple('HiTerm', 'exp, bit, num, den') 28 | LoTerm = namedtuple('LoTerm', 'num, den') 29 | 30 | hiTerms = [] 31 | loTerms = [LoTerm(fixed1 * 2, fixed1 * 2)] 32 | 33 | top = int(two_pow(maxHiTermVal).exp() * fixed1) 34 | for n in range(numOfHiTerms): 35 | cur = two_pow(maxHiTermVal - n - 1) 36 | exp = int(fixed1 * cur.exp()) 37 | bit = int(fixed1 * cur) 38 | num, den = epow(-cur, top) 39 | assert num * (exp + 1) >= fixed1 * den 40 | hiTerms.append(HiTerm(exp, bit, num, den)) 41 | top = exp 42 | 43 | mid = logMid(fixed1, maxHiTermVal) - 1 44 | res = optimalLog(mid, hiTerms, loTerms, fixed1) 45 | 46 | while True: 47 | n = len(loTerms) 48 | val = fixed1 * (2 * n + 2) 49 | loTermsNext = loTerms + [LoTerm(val // (2 * n + 1), val)] 50 | resNext = optimalLog(mid, hiTerms, loTermsNext, fixed1) 51 | if res < resNext: 52 | res = resNext 53 | loTerms = loTermsNext 54 | else: 55 | return hiTerms, loTerms 56 | 57 | 58 | def optimalExpTerms(fixed1, maxHiTermVal, numOfHiTerms): 59 | HiTerm = namedtuple('HiTerm', 'bit, num, den') 60 | LoTerm = namedtuple('LoTerm', 'val, ind') 61 | 62 | hiTerms = [] 63 | loTerms = [LoTerm(1, 1)] 64 | 65 | top = int(two_pow(maxHiTermVal - numOfHiTerms).exp() * fixed1) - 1 66 | for n in range(numOfHiTerms + 1): 67 | cur = two_pow(maxHiTermVal - numOfHiTerms + n) 68 | bit = int(fixed1 * cur) 69 | num, den = epow(cur, top) 70 | assert num <= den * cur.exp() 71 | hiTerms.append(HiTerm(bit, num, den)) 72 | top = top * num // den 73 | 74 | mid = expMid(fixed1, maxHiTermVal) - 1 75 | res = optimalExp(mid, hiTerms, loTerms, fixed1) 76 | 77 | while True: 78 | n = len(loTerms) 79 | val = factorial(n + 1) 80 | loTermsNext = [LoTerm(val // factorial(i + 1), i + 1) for i in range(n + 1)] 81 | resNext = optimalExp(mid, hiTerms, loTermsNext, fixed1) 82 | if res < resNext: 83 | res = resNext 84 | loTerms = loTermsNext 85 | else: 86 | return hiTerms, loTerms 87 | 88 | 89 | def optimalLog(x, hiTerms, loTerms, fixed1): 90 | res = 0 91 | for term in hiTerms: 92 | if x > term.exp: 93 | res |= term.bit 94 | x = checked(x * term.num) // term.den 95 | z = y = checked(x - fixed1) 96 | w = checked(y * y) // fixed1 97 | for term in loTerms[:-1]: 98 | res = checked(res + checked(z * checked(term.num - y)) // term.den) 99 | z = checked(z * w) // fixed1 100 | res = checked(res + checked(z * checked(loTerms[-1].num - y)) // loTerms[-1].den) 101 | return res 102 | 103 | 104 | def optimalExp(x, hiTerms, loTerms, fixed1): 105 | res = 0 106 | z = y = x % hiTerms[0].bit 107 | for term in loTerms[+1:]: 108 | z = checked(z * y) // fixed1 109 | res = checked(res + checked(z * term.val)) 110 | res = checked(checked(res // loTerms[0].val + y) + fixed1) 111 | for term in hiTerms[:-1]: 112 | if x & term.bit: 113 | res = checked(res * term.num) // term.den 114 | return res 115 | 116 | 117 | def epow(cur, top): 118 | e = cur.exp() 119 | lists = [ 120 | list(func(e, top)) for func in [ 121 | lambda e, top: epow_n_first(e, top, int.__sub__ , 0, 100000), 122 | lambda e, top: epow_d_first(e, top, int.__sub__ , 0, 100000), 123 | lambda e, top: epow_n_first(e, top, int.__floordiv__, 1, 10000), 124 | lambda e, top: epow_d_first(e, top, int.__floordiv__, 1, 10000), 125 | lambda e, top: epow_n_first(e, top, int.__rshift__ , 0, 100), 126 | lambda e, top: epow_d_first(e, top, int.__rshift__ , 0, 100), 127 | ] 128 | ] 129 | return max(sum(lists, []), key = lambda x: Decimal(x[0]) / Decimal(x[1])) 130 | 131 | 132 | def epow_n_first(e, top, op, bgn, end): 133 | m = MAX_VAL // top 134 | ns = [op(m, i) for i in range(bgn, end)] 135 | ds = [(n / e).__ceil__() for n in ns] 136 | return zip(ns, ds) 137 | 138 | 139 | def epow_d_first(e, top, op, bgn, end): 140 | m = int(MAX_VAL / (top * e)) 141 | ds = [op(m, i) for i in range(bgn, end)] 142 | ns = [(d * e).__floor__() for d in ds] 143 | return zip(ns, ds) 144 | -------------------------------------------------------------------------------- /project/customization/core/__init__.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from decimal import getcontext 3 | from decimal import ROUND_HALF_DOWN 4 | 5 | 6 | getcontext().prec = 100 7 | getcontext().rounding = ROUND_HALF_DOWN 8 | 9 | 10 | MAX_VAL = 2 ** 256 - 1 11 | LOG_TWO = Decimal(2).ln() 12 | INV_EXP = Decimal(-1).exp() 13 | 14 | 15 | def two_pow(x): 16 | return Decimal(2) ** x 17 | 18 | 19 | def checked(x): 20 | assert 0 <= x <= MAX_VAL 21 | return x 22 | 23 | 24 | def bsearch(func, lo, hi, *args): 25 | while lo + 1 < hi: 26 | mid = (lo + hi) // 2 27 | try: 28 | func(mid, *args) 29 | lo = mid 30 | except: 31 | hi = mid 32 | try: 33 | return func(hi, *args) 34 | except: 35 | return func(lo, *args) 36 | -------------------------------------------------------------------------------- /project/customization/util/__init__.py: -------------------------------------------------------------------------------- 1 | def hex_len(n): 2 | return 44 if 41 <= len(hex(n)) <= 44 else len(hex(n)) 3 | 4 | 5 | def dec_str(value, max_value): 6 | return '{0:0{1}d}'.format(value, len(str(max_value))) 7 | 8 | 9 | def hex_str(value, max_value): 10 | return '{0:#0{1}x}'.format(value, hex_len(max_value)) 11 | -------------------------------------------------------------------------------- /project/emulation/FixedPoint/AnalyticMath.py: -------------------------------------------------------------------------------- 1 | from .common.BuiltIn import * 2 | from . import IntegralMath 3 | 4 | # Auto-generated via 'PrintAnalyticMathConstants.py' 5 | SCALE_1 = 0x000000000000000000000000000000007f; 6 | FIXED_1 = 0x0080000000000000000000000000000000; 7 | LN2_MIN = 0x0058b90bfbe8e7bcd5e4f1d9cc01f97b57; 8 | LN2_MAX = 0x0058b90bfbe8e7bcd5e4f1d9cc01f97b58; 9 | LOG_MID = 0x015bf0a8b1457695355fb8ac404e7a79e4; 10 | EXP_MID = 0x0400000000000000000000000000000000; 11 | EXP_MAX = 0x2cb53f09f05cc627c85ddebfccfeb72758; 12 | 13 | ''' 14 | @dev Compute (a / b) ^ (c / d) 15 | ''' 16 | def pow(a, b, c, d): 17 | if (b == 0 or d == 0): 18 | revert("division by zero"); 19 | if (a == 0 or c == 0): 20 | return (a ** c, 1); 21 | if (a > b): 22 | return (fixedExp(IntegralMath.mulDivF(fixedLog(IntegralMath.mulDivF(FIXED_1, a, b)), c, d)), FIXED_1); 23 | if (b > a): 24 | return (FIXED_1, fixedExp(IntegralMath.mulDivF(fixedLog(IntegralMath.mulDivF(FIXED_1, b, a)), c, d))); 25 | return (1, 1); 26 | 27 | ''' 28 | @dev Compute log(a / b) 29 | ''' 30 | def log(a, b): 31 | return (fixedLog(IntegralMath.mulDivF(FIXED_1, a, b)), FIXED_1); 32 | 33 | ''' 34 | @dev Compute exp(a / b) 35 | ''' 36 | def exp(a, b): 37 | return (fixedExp(IntegralMath.mulDivF(FIXED_1, a, b)), FIXED_1); 38 | 39 | ''' 40 | @dev Compute log(x / FIXED_1) * FIXED_1 41 | Input range: FIXED_1 <= x <= 2 ^ 256 - 1 42 | Detailed description: 43 | - For x < LOG_MID, compute log(x) 44 | - For any other x, compute log(x / 2 ^ log2(x)) + log2(x) * log(2) 45 | - The value of log(2) is represented as floor(log(2) * FIXED_1) 46 | - With k = log2(x), this solution relies on the following identity: 47 | log(x) = 48 | log(x) + log(2 ^ k) - log(2 ^ k) = 49 | log(x) - log(2 ^ k) + log(2 ^ k) = 50 | log(x / log(2 ^ k)) + log(2 ^ k) = 51 | log(x / log(2 ^ k)) + k * log(2) 52 | ''' 53 | def fixedLog(x): 54 | if (x < FIXED_1): 55 | revert("fixedLog: x < min"); 56 | if (x < LOG_MID): 57 | return optimalLog(x); 58 | count = IntegralMath.floorLog2(x // FIXED_1); 59 | return optimalLog(x >> count) + count * LN2_MIN; 60 | 61 | ''' 62 | @dev Compute exp(x / FIXED_1) * FIXED_1 63 | Input range: 0 <= x <= EXP_MAX - 1 64 | Detailed description: 65 | - For x < EXP_MID, compute exp(x) 66 | - For any other x, compute exp(x % log(2)) * 2 ^ (x / log(2)) 67 | - The value of log(2) is represented as ceil(log(2) * FIXED_1) 68 | - With k = x / log(2), this solution relies on the following identity: 69 | exp(x) = 70 | exp(x) * 2 ^ k / 2 ^ k = 71 | exp(x) * 2 ^ k / exp(k * log(2)) = 72 | exp(x) / exp(k * log(2)) * 2 ^ k = 73 | exp(x - k * log(2)) * 2 ^ k 74 | ''' 75 | def fixedExp(x): 76 | if (x < EXP_MID): 77 | return optimalExp(x); 78 | if (x < EXP_MAX): 79 | return optimalExp(x % LN2_MAX) << (x // LN2_MAX); 80 | revert("fixedExp: x > max"); 81 | 82 | ''' 83 | @dev Compute log(x / FIXED_1) * FIXED_1 84 | Input range: FIXED_1 <= x <= FIXED_1 * 4 - 1 85 | Auto-generated via 'PrintAnalyticMathOptimalLog.py' 86 | Detailed description: 87 | - Rewrite the input as a product of natural exponents and a single residual r, such that 1 < r < 2 88 | - The natural logarithm of each (pre-calculated) exponent is the degree of the exponent 89 | - The natural logarithm of r is calculated via Taylor series for log(1 + x), where x = r - 1 90 | - The natural logarithm of the input is calculated by summing up the intermediate results above 91 | - For example: log(250) = log(e^4 * e^1 * e^0.5 * 1.021692859) = 4 + 1 + 0.5 + log(1 + 0.021692859) 92 | ''' 93 | def optimalLog(x): 94 | res = 0; 95 | 96 | if (x > 0xd3094c70f034de4b96ff7d5b6f99fcd8): res |= 0x40000000000000000000000000000000; x = x * 0x0bc5ab1b16779be3575bd8f0520a9d5b9 // 0x1368b2fc6f9609fe7aceb46aa619b8003; # add 2^(-1) 97 | if (x > 0xa45af1e1f40c333b3de1db4dd55f29a7): res |= 0x20000000000000000000000000000000; x = x * 0x1368b2fc6f9609fe7aceb46aa619a2ef6 // 0x18ebef9eac820ae8682b9793ac6cffa93; # add 2^(-2) 98 | if (x > 0x910b022db7ae67ce76b441c27035c6a1): res |= 0x10000000000000000000000000000000; x = x * 0x18ebef9eac820ae8682b9793ac6d11622 // 0x1c3d6a24ed82218787d624d3e5eb9a8c6; # add 2^(-3) 99 | if (x > 0x88415abbe9a76bead8d00cf112e4d4a8): res |= 0x08000000000000000000000000000000; x = x * 0x1c3d6a24ed82218787d624d3e5eba8beb // 0x1e0fabfbc702a3ce5e31fe0609358b04d; # add 2^(-4) 100 | if (x > 0x84102b00893f64c705e841d5d4064bd3): res |= 0x04000000000000000000000000000000; x = x * 0x1e0fabfbc702a3ce5e31fe06093589585 // 0x1f03f56a88b5d7914b00abf9776270f29; # add 2^(-5) 101 | if (x > 0x8204055aaef1c8bd5c3259f4822735a2): res |= 0x02000000000000000000000000000000; x = x * 0x1f03f56a88b5d7914b00abf9776267973 // 0x1f80feabfeefa4927d10bdd54ead4eaf0; # add 2^(-6) 102 | if (x > 0x810100ab00222d861931c15e39b44e99): res |= 0x01000000000000000000000000000000; x = x * 0x1f80feabfeefa4927d10bdd54ead599f9 // 0x1fc03fd56aa224f97fcbf133298883271; # add 2^(-7) 103 | if (x > 0x808040155aabbbe9451521693554f733): res |= 0x00800000000000000000000000000000; x = x * 0x1fc03fd56aa224f97fcbf13329886bd10 // 0x1fe00ffaabffbbc71ad1e1184afc01529; # add 2^(-8) 104 | 105 | z = y = x - FIXED_1; 106 | w = y * y >> SCALE_1; 107 | res += z * (0x100000000000000000000000000000000 - y) // 0x100000000000000000000000000000000; z = z * w >> SCALE_1; # add y^01 / 01 - y^02 / 02 108 | res += z * (0x0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - y) // 0x200000000000000000000000000000000; z = z * w >> SCALE_1; # add y^03 / 03 - y^04 / 04 109 | res += z * (0x099999999999999999999999999999999 - y) // 0x300000000000000000000000000000000; z = z * w >> SCALE_1; # add y^05 / 05 - y^06 / 06 110 | res += z * (0x092492492492492492492492492492492 - y) // 0x400000000000000000000000000000000; z = z * w >> SCALE_1; # add y^07 / 07 - y^08 / 08 111 | res += z * (0x08e38e38e38e38e38e38e38e38e38e38e - y) // 0x500000000000000000000000000000000; z = z * w >> SCALE_1; # add y^09 / 09 - y^10 / 10 112 | res += z * (0x08ba2e8ba2e8ba2e8ba2e8ba2e8ba2e8b - y) // 0x600000000000000000000000000000000; z = z * w >> SCALE_1; # add y^11 / 11 - y^12 / 12 113 | res += z * (0x089d89d89d89d89d89d89d89d89d89d89 - y) // 0x700000000000000000000000000000000; z = z * w >> SCALE_1; # add y^13 / 13 - y^14 / 14 114 | res += z * (0x088888888888888888888888888888888 - y) // 0x800000000000000000000000000000000; # add y^15 / 15 - y^16 / 16 115 | 116 | return res; 117 | 118 | ''' 119 | @dev Compute exp(x / FIXED_1) * FIXED_1 120 | Input range: 0 <= x <= EXP_MID - 1 121 | Auto-generated via 'PrintAnalyticMathOptimalExp.py' 122 | Detailed description: 123 | - Rewrite the input as a sum of binary exponents and a single residual r, as small as possible 124 | - The exponentiation of each binary exponent is given (pre-calculated) 125 | - The exponentiation of r is calculated via Taylor series for e^x, where x = r 126 | - The exponentiation of the input is calculated by multiplying the intermediate results above 127 | - For example: e^5.521692859 = e^(4 + 1 + 0.5 + 0.021692859) = e^4 * e^1 * e^0.5 * e^0.021692859 128 | ''' 129 | def optimalExp(x): 130 | res = 0; 131 | 132 | z = y = x % 0x10000000000000000000000000000000; # get the input modulo 2^(-3) 133 | z = z * y >> SCALE_1; res += z * 0x10e1b3be415a0000; # add y^02 * (20! / 02!) 134 | z = z * y >> SCALE_1; res += z * 0x05a0913f6b1e0000; # add y^03 * (20! / 03!) 135 | z = z * y >> SCALE_1; res += z * 0x0168244fdac78000; # add y^04 * (20! / 04!) 136 | z = z * y >> SCALE_1; res += z * 0x004807432bc18000; # add y^05 * (20! / 05!) 137 | z = z * y >> SCALE_1; res += z * 0x000c0135dca04000; # add y^06 * (20! / 06!) 138 | z = z * y >> SCALE_1; res += z * 0x0001b707b1cdc000; # add y^07 * (20! / 07!) 139 | z = z * y >> SCALE_1; res += z * 0x000036e0f639b800; # add y^08 * (20! / 08!) 140 | z = z * y >> SCALE_1; res += z * 0x00000618fee9f800; # add y^09 * (20! / 09!) 141 | z = z * y >> SCALE_1; res += z * 0x0000009c197dcc00; # add y^10 * (20! / 10!) 142 | z = z * y >> SCALE_1; res += z * 0x0000000e30dce400; # add y^11 * (20! / 11!) 143 | z = z * y >> SCALE_1; res += z * 0x000000012ebd1300; # add y^12 * (20! / 12!) 144 | z = z * y >> SCALE_1; res += z * 0x0000000017499f00; # add y^13 * (20! / 13!) 145 | z = z * y >> SCALE_1; res += z * 0x0000000001a9d480; # add y^14 * (20! / 14!) 146 | z = z * y >> SCALE_1; res += z * 0x00000000001c6380; # add y^15 * (20! / 15!) 147 | z = z * y >> SCALE_1; res += z * 0x000000000001c638; # add y^16 * (20! / 16!) 148 | z = z * y >> SCALE_1; res += z * 0x0000000000001ab8; # add y^17 * (20! / 17!) 149 | z = z * y >> SCALE_1; res += z * 0x000000000000017c; # add y^18 * (20! / 18!) 150 | z = z * y >> SCALE_1; res += z * 0x0000000000000014; # add y^19 * (20! / 19!) 151 | z = z * y >> SCALE_1; res += z * 0x0000000000000001; # add y^20 * (20! / 20!) 152 | res = res // 0x21c3677c82b40000 + y + FIXED_1; # divide by 20! and then add y^1 / 1! + y^0 / 0! 153 | 154 | if ((x & 0x010000000000000000000000000000000) != 0): res = res * 0x1c3d6a24ed82218787d624d3e5eba2a0f // 0x18ebef9eac820ae8682b9793ac6d1883a; # multiply by e^2^(-3) 155 | if ((x & 0x020000000000000000000000000000000) != 0): res = res * 0x18ebef9eac820ae8682b9793ac6d1e726 // 0x1368b2fc6f9609fe7aceb46aa619bae94; # multiply by e^2^(-2) 156 | if ((x & 0x040000000000000000000000000000000) != 0): res = res * 0x1368b2fc6f9609fe7aceb46aa6199a7d7 // 0x0bc5ab1b16779be3575bd8f0520a8b756; # multiply by e^2^(-1) 157 | if ((x & 0x080000000000000000000000000000000) != 0): res = res * 0x0bc5ab1b16779be3575bd8f0520a67cd2 // 0x0454aaa8efe072e7f6ddbab84b409101a; # multiply by e^2^(+0) 158 | if ((x & 0x100000000000000000000000000000000) != 0): res = res * 0x0454aaa8efe072e7f6ddbab84b408736f // 0x00960aadc109e7a3bf45780996156d0a3; # multiply by e^2^(+1) 159 | if ((x & 0x200000000000000000000000000000000) != 0): res = res * 0x00960aadc109e7a3bf45780996149ade3 // 0x0002bf84208204f5977f9a8cf01fd8f74; # multiply by e^2^(+2) 160 | 161 | return res; 162 | -------------------------------------------------------------------------------- /project/emulation/FixedPoint/BondingCurve.py: -------------------------------------------------------------------------------- 1 | from .common.BuiltIn import * 2 | from .common.Uint256 import * 3 | from . import AnalyticMath 4 | from . import IntegralMath 5 | 6 | MAX_WEIGHT = 1000000; 7 | 8 | ''' 9 | @dev Buy pool tokens with reserve tokens 10 | 11 | @param supply The total amount of pool tokens 12 | @param balance The amount of reserve tokens owned by the pool 13 | @param weight The weight of the reserve (represented in ppm) 14 | @param amount The amount of reserve tokens provided 15 | 16 | @return supply * ((1 + amount / balance) ^ (weight / MAX_WEIGHT) - 1) 17 | ''' 18 | def buy(supply, balance, weight, amount): 19 | require(supply > 0 and balance > 0 and weight > 0, "invalid input"); 20 | require(weight <= MAX_WEIGHT, "weight out of bound"); 21 | 22 | if (weight == MAX_WEIGHT): 23 | return IntegralMath.mulDivF(amount, supply, balance); 24 | 25 | (n, d) = AnalyticMath.pow(safeAdd(balance, amount), balance, weight, MAX_WEIGHT); 26 | return IntegralMath.mulDivF(supply, n, d) - supply; 27 | 28 | ''' 29 | @dev Sell pool tokens for reserve tokens 30 | 31 | @param supply The total amount of pool tokens 32 | @param balance The amount of reserve tokens owned by the pool 33 | @param weight The weight of the reserve (represented in ppm) 34 | @param amount The amount of pool tokens provided 35 | 36 | @return balance * (1 - (1 - amount / supply) ^ (MAX_WEIGHT / weight)) 37 | ''' 38 | def sell(supply, balance, weight, amount): 39 | require(supply > 0 and balance > 0 and weight > 0, "invalid input"); 40 | require(weight <= MAX_WEIGHT, "weight out of bound"); 41 | require(amount <= supply, "amount larger than supply"); 42 | 43 | if (amount == supply): 44 | return balance; 45 | 46 | if (weight == MAX_WEIGHT): 47 | return IntegralMath.mulDivF(amount, balance, supply); 48 | 49 | (n, d) = AnalyticMath.pow(supply, supply - amount, MAX_WEIGHT, weight); 50 | return IntegralMath.mulDivF(balance, n - d, n); 51 | 52 | ''' 53 | @dev Convert reserve tokens of one type to another 54 | 55 | @param balance1 The amount of source reserve tokens owned by the pool 56 | @param weight1 The weight of the source reserve (represented in ppm) 57 | @param balance2 The amount of target reserve tokens owned by the pool 58 | @param weight2 The weight of the target reserve (represented in ppm) 59 | @param amount The amount of source reserve tokens provided 60 | 61 | @return balance2 * (1 - (balance1 / (balance1 + amount)) ^ (weight1 / weight2)) 62 | ''' 63 | def convert(balance1, weight1, balance2, weight2, amount): 64 | require(balance1 > 0 and balance2 > 0 and weight1 > 0 and weight2 > 0, "invalid input"); 65 | require(weight1 <= MAX_WEIGHT and weight2 <= MAX_WEIGHT, "weights out of bound"); 66 | 67 | if (weight1 == weight2): 68 | return IntegralMath.mulDivF(balance2, amount, safeAdd(balance1, amount)); 69 | 70 | (n, d) = AnalyticMath.pow(safeAdd(balance1, amount), balance1, weight1, weight2); 71 | return IntegralMath.mulDivF(balance2, n - d, n); 72 | 73 | ''' 74 | @dev Deposit reserve tokens for pool tokens 75 | 76 | @param supply The total amount of pool tokens 77 | @param balance The amount of reserve tokens of the desired type owned by the pool 78 | @param weights The combined weights of the reserves (represented in ppm) 79 | @param amount The amount of reserve tokens of the desired type provided 80 | 81 | @return supply * ((amount / balance + 1) ^ (weights / MAX_WEIGHT) - 1) 82 | ''' 83 | def deposit(supply, balance, weights, amount): 84 | require(supply > 0 and balance > 0 and weights > 0, "invalid input"); 85 | require(weights <= MAX_WEIGHT * 2, "weights out of bound"); 86 | 87 | if (weights == MAX_WEIGHT): 88 | return IntegralMath.mulDivF(amount, supply, balance); 89 | 90 | (n, d) = AnalyticMath.pow(safeAdd(balance, amount), balance, weights, MAX_WEIGHT); 91 | return IntegralMath.mulDivF(supply, n, d) - supply; 92 | 93 | ''' 94 | @dev Withdraw reserve tokens with pool tokens 95 | 96 | @param supply The total amount of pool tokens 97 | @param balance The amount of reserve tokens of the desired type owned by the pool 98 | @param weights The combined weights of the reserves (represented in ppm) 99 | @param amount The amount of pool tokens provided 100 | 101 | @return balance * (1 - ((supply - amount) / supply) ^ (MAX_WEIGHT / weights)) 102 | ''' 103 | def withdraw(supply, balance, weights, amount): 104 | require(supply > 0 and balance > 0 and weights > 0, "invalid input"); 105 | require(weights <= MAX_WEIGHT * 2, "weights out of bound"); 106 | require(amount <= supply, "amount larger than supply"); 107 | 108 | if (amount == supply): 109 | return balance; 110 | 111 | if (weights == MAX_WEIGHT): 112 | return IntegralMath.mulDivF(amount, balance, supply); 113 | 114 | (n, d) = AnalyticMath.pow(supply, supply - amount, MAX_WEIGHT, weights); 115 | return IntegralMath.mulDivF(balance, n - d, n); 116 | 117 | ''' 118 | @dev Invest reserve tokens for pool tokens 119 | 120 | @param supply The total amount of pool tokens 121 | @param balance The amount of reserve tokens of the desired type owned by the pool 122 | @param weights The combined weights of the reserves (represented in ppm) 123 | @param amount The amount of pool tokens desired 124 | 125 | @return balance * (((supply + amount) / supply) ^ (MAX_WEIGHT / weights) - 1) 126 | ''' 127 | def invest(supply, balance, weights, amount): 128 | require(supply > 0 and balance > 0 and weights > 0, "invalid input"); 129 | require(weights <= MAX_WEIGHT * 2, "weights out of bound"); 130 | 131 | if (weights == MAX_WEIGHT): 132 | return IntegralMath.mulDivC(amount, balance, supply); 133 | 134 | (n, d) = AnalyticMath.pow(safeAdd(supply, amount), supply, MAX_WEIGHT, weights); 135 | return IntegralMath.mulDivC(balance, n, d) - balance; 136 | -------------------------------------------------------------------------------- /project/emulation/FixedPoint/DynamicCurve.py: -------------------------------------------------------------------------------- 1 | from .common.BuiltIn import * 2 | from . import AdvancedMath 3 | from . import FractionMath 4 | 5 | MAX_WEIGHT = 1000000; 6 | 7 | # Abstract: 8 | # Consider a pool which implements the bonding-curve model over a primary reserve token and a secondary reserve token. 9 | # Let 'on-chain price' denote the conversion rate between these tokens inside the pool (i.e., as determined by the pool). 10 | # Let 'off-chain price' denote the conversion rate between these tokens outside the pool (i.e., as determined by the market). 11 | # The arbitrage incentive is always to convert to the point where the on-chain price is equal to the off-chain price. 12 | # We want this operation to also impact the primary reserve balance becoming equal to the primary reserve staked balance. 13 | # In other words, we want the arbitrager to convert the difference between the reserve balance and the reserve staked balance. 14 | # Hence we adjust the weights in order to create an arbitrage incentive which, when realized, will subsequently equalize the pool. 15 | # 16 | # Input: 17 | # - Let t denote the primary reserve token staked balance 18 | # - Let s denote the primary reserve token balance 19 | # - Let r denote the secondary reserve token balance 20 | # - Let q denote the numerator of the off-chain price 21 | # - Let p denote the denominator of the off-chain price 22 | # Where p primary tokens are equal to q secondary tokens 23 | # 24 | # Output: 25 | # - Solve the equation x * (s // t) ^ x = (t // r) * (q // p) 26 | # - Return x // (x + 1) as the weight of the primary reserve token 27 | # - Return 1 // (x + 1) as the weight of the secondary reserve token 28 | # 29 | # If the rate-provider provides the rates for a common unit, for example: 30 | # - P = 2 ==> 2 primary reserve tokens = 1 ether 31 | # - Q = 3 ==> 3 secondary reserve tokens = 1 ether 32 | # Then you can simply use p = P and q = Q 33 | # 34 | # If the rate-provider provides the rates for a single unit, for example: 35 | # - P = 2 ==> 1 primary reserve token = 2 ethers 36 | # - Q = 3 ==> 1 secondary reserve token = 3 ethers 37 | # Then you can simply use p = Q and q = P 38 | 39 | ''' 40 | @dev Equalize the weights of a given pool while opting for accuracy over performance 41 | 42 | @param t The primary reserve token staked balance 43 | @param s The primary reserve token balance 44 | @param r The secondary reserve token balance 45 | @param q The numerator of the off-chain price 46 | @param p The denominator of the off-chain price 47 | 48 | Note that `numerator / denominator` should represent the amount of secondary tokens equal to one primary token 49 | 50 | @return The weight of the primary reserve token and the weight of the secondary reserve token, both in ppm units 51 | ''' 52 | def equalizeExact(t, s, r, q, p): 53 | return equalize(t, s, r, q, p, AdvancedMath.solveExact); 54 | 55 | ''' 56 | @dev Equalize the weights of a given pool while opting for performance over accuracy 57 | 58 | @param t The primary reserve token staked balance 59 | @param s The primary reserve token balance 60 | @param r The secondary reserve token balance 61 | @param q The numerator of the off-chain price 62 | @param p The denominator of the off-chain price 63 | 64 | Note that `numerator / denominator` should represent the amount of secondary tokens equal to one primary token 65 | 66 | @return The weight of the primary reserve token and the weight of the secondary reserve token, both in ppm units 67 | ''' 68 | def equalizeQuick(t, s, r, q, p): 69 | return equalize(t, s, r, q, p, AdvancedMath.solveQuick); 70 | 71 | ''' 72 | @dev Equalize the weights of a given pool 73 | 74 | @param t The primary reserve token staked balance 75 | @param s The primary reserve token balance 76 | @param r The secondary reserve token balance 77 | @param q The numerator of the off-chain price 78 | @param p The denominator of the off-chain price 79 | @param AdvancedMath_solveFunction An equation solver 80 | 81 | Note that `numerator / denominator` should represent the amount of secondary tokens equal to one primary token 82 | 83 | @return The weight of the primary reserve token and the weight of the secondary reserve token, both in ppm units 84 | ''' 85 | def equalize(t, s, r, q, p, AdvancedMath_solveFunction): 86 | if (t == s): 87 | require(t > 0 or r > 0, "invalid balance"); 88 | else: 89 | require(t > 0 and s > 0 and r > 0, "invalid balance"); 90 | require(q > 0 and p > 0, "invalid rate"); 91 | 92 | (tq, rp) = FractionMath.productRatio(t, q, r, p); 93 | (xn, xd) = AdvancedMath_solveFunction(s, t, tq, rp); 94 | (w1, w2) = FractionMath.normalizedRatio(xn, xd, MAX_WEIGHT); 95 | 96 | return (w1, w2); 97 | -------------------------------------------------------------------------------- /project/emulation/FixedPoint/FractionMath.py: -------------------------------------------------------------------------------- 1 | from .common.BuiltIn import * 2 | from .common.Uint256 import * 3 | from . import IntegralMath 4 | 5 | MAX_EXP_BIT_LEN = 4; 6 | MAX_EXP = 2 ** MAX_EXP_BIT_LEN - 1; 7 | MAX_UINT128 = 2 ** 128 - 1; 8 | 9 | ''' 10 | @dev Compute the power of a given ratio while opting for accuracy over performance 11 | 12 | @param n The ratio numerator 13 | @param d The ratio denominator 14 | @param exp The exponentiation value 15 | 16 | @return The powered ratio numerator 17 | @return The powered ratio denominator 18 | ''' 19 | def poweredRatioExact(n, d, exp): 20 | return poweredRatio(n, d, exp, productRatio); 21 | 22 | ''' 23 | @dev Compute the power of a given ratio while opting for performance over accuracy 24 | 25 | @param n The ratio numerator 26 | @param d The ratio denominator 27 | @param exp The exponentiation value 28 | 29 | @return The powered ratio numerator 30 | @return The powered ratio denominator 31 | ''' 32 | def poweredRatioQuick(n, d, exp): 33 | return poweredRatio(n, d, exp, mulRatio128); 34 | 35 | ''' 36 | @dev Compute the product of two given ratios 37 | 38 | @param xn The 1st ratio numerator 39 | @param yn The 2nd ratio numerator 40 | @param xd The 1st ratio denominator 41 | @param yd The 2nd ratio denominator 42 | 43 | @return The product ratio numerator 44 | @return The product ratio denominator 45 | ''' 46 | def productRatio(xn, yn, xd, yd): 47 | n = IntegralMath.minFactor(xn, yn); 48 | d = IntegralMath.minFactor(xd, yd); 49 | z = n if n > d else d; 50 | return (IntegralMath.mulDivC(xn, yn, z), IntegralMath.mulDivC(xd, yd, z)); 51 | 52 | ''' 53 | @dev Reduce the components of a given ratio to fit up to a given threshold 54 | 55 | @param n The ratio numerator 56 | @param d The ratio denominator 57 | @param cap The desired threshold 58 | 59 | @return The reduced ratio numerator 60 | @return The reduced ratio denominator 61 | ''' 62 | def reducedRatio(n, d, cap): 63 | if (n < d): 64 | (n, d) = reducedRatioCalc(n, d, cap); 65 | else: 66 | (d, n) = reducedRatioCalc(d, n, cap); 67 | return (n, d); 68 | 69 | ''' 70 | @dev Normalize the components of a given ratio to sum up to a given scale 71 | 72 | @param n The ratio numerator 73 | @param d The ratio denominator 74 | @param scale The desired scale 75 | 76 | @return The normalized ratio numerator 77 | @return The normalized ratio denominator 78 | ''' 79 | def normalizedRatio(n, d, scale): 80 | if (n < d): 81 | (n, d) = normalizedRatioCalc(n, d, scale); 82 | else: 83 | (d, n) = normalizedRatioCalc(d, n, scale); 84 | return (n, d); 85 | 86 | ''' 87 | @dev Compute the power of a given ratio 88 | 89 | @param n The ratio numerator 90 | @param d The ratio denominator 91 | @param exp The exponentiation value 92 | @param safeRatio The computing function 93 | 94 | @return The powered ratio numerator 95 | @return The powered ratio denominator 96 | ''' 97 | def poweredRatio(n, d, exp, safeRatio): 98 | require(exp <= MAX_EXP, "exp too large"); 99 | 100 | ns = [0] * MAX_EXP_BIT_LEN; 101 | ds = [0] * MAX_EXP_BIT_LEN; 102 | 103 | (ns[0], ds[0]) = safeRatio(n, 1, d, 1); 104 | for i in range(len(bin(exp)) - 3): 105 | (ns[i + 1], ds[i + 1]) = safeRatio(ns[i], ns[i], ds[i], ds[i]); 106 | 107 | n = 1; 108 | d = 1; 109 | 110 | for i in range(len(bin(exp)) - 2): 111 | if (((exp >> i) & 1) > 0): 112 | (n, d) = safeRatio(n, ns[i], d, ds[i]); 113 | 114 | return (n, d); 115 | 116 | ''' 117 | @dev Reduce the components of a given ratio to fit up to a given threshold, 118 | under the implicit assumption that the ratio is smaller than or equal to 1 119 | 120 | @param n The ratio numerator 121 | @param d The ratio denominator 122 | @param cap The desired threshold 123 | 124 | @return The reduced ratio numerator 125 | @return The reduced ratio denominator 126 | ''' 127 | def reducedRatioCalc(n, d, cap): 128 | if (d > cap): 129 | n = IntegralMath.mulDivR(n, cap, d); 130 | d = cap; 131 | return (n, d); 132 | 133 | ''' 134 | @dev Normalize the components of a given ratio to sum up to a given scale, 135 | under the implicit assumption that the ratio is smaller than or equal to 1 136 | 137 | @param n The ratio numerator 138 | @param d The ratio denominator 139 | @param scale The desired scale 140 | 141 | @return The normalized ratio numerator 142 | @return The normalized ratio denominator 143 | ''' 144 | def normalizedRatioCalc(n, d, scale): 145 | if (n > MAX_VAL - d): 146 | x = unsafeAdd(n, d) + 1; 147 | y = IntegralMath.mulDivF(x, n // 2, n // 2 + d // 2); 148 | n -= y; 149 | d -= x - y; 150 | z = IntegralMath.mulDivR(scale, n, n + d); 151 | return(z, scale - z); 152 | 153 | ''' 154 | @dev Compute the product of two ratios and reduce the components of the result to 128 bits, 155 | under the implicit assumption that the components of the product are not larger than 256 bits 156 | 157 | @param xn The 1st ratio numerator 158 | @param yn The 2nd ratio numerator 159 | @param xd The 1st ratio denominator 160 | @param yd The 2nd ratio denominator 161 | 162 | @return The product ratio numerator 163 | @return The product ratio denominator 164 | ''' 165 | def mulRatio128(xn, yn, xd, yd): 166 | return reducedRatio(xn * yn, xd * yd, MAX_UINT128); 167 | -------------------------------------------------------------------------------- /project/emulation/FixedPoint/IntegralMath.py: -------------------------------------------------------------------------------- 1 | from .common.BuiltIn import * 2 | from .common.Uint256 import * 3 | 4 | ''' 5 | @dev Compute the largest integer smaller than or equal to the binary logarithm of `n` 6 | ''' 7 | def floorLog2(n): 8 | res = 0; 9 | 10 | if (n < 256): 11 | # at most 8 iterations 12 | while (n > 1): 13 | n >>= 1; 14 | res += 1; 15 | else: 16 | # exactly 8 iterations 17 | for s in [1 << (8 - 1 - k) for k in range(8)]: 18 | if (n >= 1 << s): 19 | n >>= s; 20 | res |= s; 21 | 22 | return res; 23 | 24 | ''' 25 | @dev Compute the largest integer smaller than or equal to the square root of `n` 26 | ''' 27 | def floorSqrt(n): 28 | if (n > 63): 29 | x = n; 30 | y = 1 << (floorLog2(n) // 2 + 1); 31 | while (x > y): 32 | x = y; 33 | y = (x + n // x) >> 1; 34 | return x; 35 | return 0x7777777777777776666666666666555555555554444444443333333222221110 >> (n * 4) & 0xf; 36 | 37 | ''' 38 | @dev Compute the smallest integer larger than or equal to the square root of `n` 39 | ''' 40 | def ceilSqrt(n): 41 | x = floorSqrt(n); 42 | return x if x ** 2 == n else x + 1; 43 | 44 | ''' 45 | @dev Compute the largest integer smaller than or equal to the cubic root of `n` 46 | ''' 47 | def floorCbrt(n): 48 | if (n > 84): 49 | x = n; 50 | y = 1 << (floorLog2(n) // 3 + 1); 51 | while (x > y): 52 | x = y; 53 | y = ((x << 1) + n // x ** 2) // 3; 54 | return x; 55 | return 0x49249249249249246db6db6db6db6db6db6db6db6db692492492492492249248 >> (n * 3) & 0x7; 56 | 57 | ''' 58 | @dev Compute the smallest integer larger than or equal to the cubic root of `n` 59 | ''' 60 | def ceilCbrt(n): 61 | x = floorCbrt(n); 62 | return x if x ** 3 == n else x + 1; 63 | 64 | ''' 65 | @dev Compute the nearest integer (half being rounded upwards) to `n / d` 66 | ''' 67 | def roundDiv(n, d): 68 | return n // d + (n % d) // (d - d // 2); 69 | 70 | ''' 71 | @dev Compute the smallest integer `z` such that `x * y / z <= 2 ^ 256 - 1` 72 | ''' 73 | def minFactor(x, y): 74 | (hi, lo) = mul512(x, y); 75 | return hi + 2 if hi > MAX_VAL - lo else hi + 1; 76 | # General: 77 | # - find the smallest integer `z` such that `x * y / z <= 2 ^ 256 - 1` 78 | # - the value of `x * y` is represented via `2 ^ 256 * hi + lo` 79 | # - the expression `~lo` is equivalent to `2 ^ 256 - 1 - lo` 80 | # 81 | # Safety: 82 | # - if `x < 2 ^ 256 - 1` or `y < 2 ^ 256 - 1` 83 | # then `hi < 2 ^ 256 - 2` 84 | # hence neither `hi + 1` nor `hi + 2` overflows 85 | # - if `x = 2 ^ 256 - 1` and `y = 2 ^ 256 - 1` 86 | # then `hi = 2 ^ 256 - 2 = ~lo` 87 | # hence `hi + 1`, which does not overflow, is computed 88 | # 89 | # Symbols: 90 | # - let `H` denote `hi` 91 | # - let `L` denote `lo` 92 | # - let `N` denote `2 ^ 256 - 1` 93 | # 94 | # Inference: 95 | # `x * y / z <= 2 ^ 256 - 1` <--> 96 | # `x * y / (2 ^ 256 - 1) <= z` <--> 97 | # `((N + 1) * H + L) / N <= z` <--> 98 | # `(N * H + H + L) / N <= z` <--> 99 | # `H + (H + L) / N <= z` 100 | # 101 | # Inference: 102 | # `0 <= H <= N && 0 <= L <= N` <--> 103 | # `0 <= H + L <= N + N` <--> 104 | # `0 <= H + L <= N * 2` <--> 105 | # `0 <= (H + L) / N <= 2` 106 | # 107 | # Inference: 108 | # - `0 = (H + L) / N` --> `H + L = 0` --> `x * y = 0` --> `z = 1 = H + 1` 109 | # - `0 < (H + L) / N <= 1` --> `H + (H + L) / N <= H + 1` --> `z = H + 1` 110 | # - `1 < (H + L) / N <= 2` --> `H + (H + L) / N <= H + 2` --> `z = H + 2` 111 | # 112 | # Implementation: 113 | # - if `hi > ~lo`: 114 | # `~L < H <= N` <--> 115 | # `N - L < H <= N` <--> 116 | # `N < H + L <= N + L` <--> 117 | # `1 < (H + L) / N <= 2` <--> 118 | # `H + 1 < H + (H + L) / N <= H + 2` <--> 119 | # `z = H + 2` 120 | # - if `hi <= ~lo`: 121 | # `H <= ~L` <--> 122 | # `H <= N - L` <--> 123 | # `H + L <= N` <--> 124 | # `(H + L) / N <= 1` <--> 125 | # `H + (H + L) / N <= H + 1` <--> 126 | # `z = H + 1` 127 | 128 | ''' 129 | @dev Compute the largest integer smaller than or equal to `x * y / 2 ^ s` 130 | ''' 131 | def mulShrF(x, y, s): 132 | (xyh, xyl) = mul512(x, y); 133 | require(xyh < 1 << s); 134 | return (xyh << (256 - s)) | (xyl >> s); 135 | 136 | ''' 137 | @dev Compute the smallest integer larger than or equal to `x * y / 2 ^ s` 138 | ''' 139 | def mulShrC(x, y, s): 140 | w = mulShrF(x, y, s); 141 | if (mulmod(x, y, 1 << s) > 0): 142 | return safeAdd(w, 1); 143 | return w; 144 | 145 | ''' 146 | @dev Compute the largest integer smaller than or equal to `x * y / z` 147 | ''' 148 | def mulDivF(x, y, z): 149 | (xyh, xyl) = mul512(x, y); 150 | if (xyh == 0): # `x * y < 2 ^ 256` 151 | return xyl // z; 152 | if (xyh < z): # `x * y / z < 2 ^ 256` 153 | m = mulmod(x, y, z); # `m = x * y % z` 154 | (nh, nl) = sub512(xyh, xyl, m); # `n = x * y - m` hence `n / z = floor(x * y / z)` 155 | if (nh == 0): # `n < 2 ^ 256` 156 | return nl // z; 157 | p = unsafeSub(0, z) & z; # `p` is the largest power of 2 which `z` is divisible by 158 | q = div512(nh, nl, p); # `n` is divisible by `p` because `n` is divisible by `z` and `z` is divisible by `p` 159 | r = inv256(z // p); # `z / p = 1 mod 2` hence `inverse(z / p) = 1 mod 2 ^ 256` 160 | return unsafeMul(q, r); # `q * r = (n / p) * inverse(z / p) = n / z` 161 | revert(); # `x * y / z >= 2 ^ 256` 162 | 163 | ''' 164 | @dev Compute the smallest integer larger than or equal to `x * y / z` 165 | ''' 166 | def mulDivC(x, y, z): 167 | w = mulDivF(x, y, z); 168 | if (mulmod(x, y, z) > 0): 169 | return safeAdd(w, 1); 170 | return w; 171 | 172 | ''' 173 | @dev Compute the nearest integer (half being rounded upwards) to `x * y / z` 174 | ''' 175 | def mulDivR(x, y, z): 176 | w = mulDivF(x, y, z); 177 | if (mulmod(x, y, z) > (z - 1) // 2): 178 | return safeAdd(w, 1); 179 | return w; 180 | 181 | ''' 182 | @dev Compute the largest integer smaller than or equal to `(x * y) / (z * w)` 183 | ''' 184 | def mulDivExF(x, y, z, w): 185 | (zwh, zwl) = mul512(z, w); 186 | if (zwh > 0): 187 | res = 0; 188 | (xyh, xyl) = mul512(x, y); 189 | if (xyh > zwh): 190 | xyhn = floorLog2(xyh); 191 | zwhn = floorLog2(zwh); 192 | while (xyhn > zwhn): 193 | n = xyhn - zwhn - 1; 194 | res += 1 << n; # set `res = res + 2 ^ n` 195 | (xyh, xyl) = sub512Ex(xyh, xyl, (zwh << n) | (zwl >> (256 - n)), zwl << n); # set `xy = xy - zw * 2 ^ n` 196 | xyhn = floorLog2(xyh); 197 | if (xyh > zwh or (xyh == zwh and xyl >= zwl)): # `xy >= zw` 198 | return res + 1; 199 | return res; 200 | return mulDivF(x, y, zwl); 201 | 202 | ''' 203 | @dev Compute the smallest integer larger than or equal to `(x * y) / (z * w)` 204 | ''' 205 | def mulDivExC(x, y, z, w): 206 | v = mulDivExF(x, y, z, w); 207 | (xyh, xyl) = mul512(x, y); 208 | (zwh, zwl) = mul512(z, w); 209 | (vzwlh, vzwll) = mul512(v, zwl); 210 | if (xyh == v * zwh + vzwlh and xyl == vzwll): 211 | return v; 212 | return safeAdd(v, 1); 213 | 214 | ''' 215 | @dev Compute the value of `x * y` 216 | ''' 217 | def mul512(x, y): 218 | p = mulModMax(x, y); 219 | q = unsafeMul(x, y); 220 | if (p >= q): 221 | return (p - q, q); 222 | return (unsafeSub(p, q) - 1, q); 223 | 224 | ''' 225 | @dev Compute the value of `2 ^ 256 * xh + xl - y`, where `2 ^ 256 * xh + xl >= y` 226 | ''' 227 | def sub512(xh, xl, y): 228 | if (xl >= y): 229 | return (xh, xl - y); 230 | return (xh - 1, unsafeSub(xl, y)); 231 | 232 | ''' 233 | @dev Compute the value of `(2 ^ 256 * xh + xl) / pow2n`, where `xl` is divisible by `pow2n` 234 | ''' 235 | def div512(xh, xl, pow2n): 236 | pow2nInv = unsafeAdd(unsafeSub(0, pow2n) // pow2n, 1); # `1 << (256 - n)` 237 | return unsafeMul(xh, pow2nInv) | (xl // pow2n); # `(xh << (256 - n)) | (xl >> n)` 238 | 239 | ''' 240 | @dev Compute the inverse of `d` modulo `2 ^ 256`, where `d` is congruent to `1` modulo `2` 241 | ''' 242 | def inv256(d): 243 | # approximate the root of `f(x) = 1 / x - d` using the newton–raphson convergence method 244 | x = 1; 245 | for i in range(8): 246 | x = unsafeMul(x, unsafeSub(2, unsafeMul(x, d))); # `x = x * (2 - x * d) mod 2 ^ 256` 247 | return x; 248 | 249 | ''' 250 | @dev Compute the value of `(2 ^ 256 * xh + xl) - (2 ^ 256 * yh + yl)`, where `2 ^ 256 * xh + xl >= 2 ^ 256 * yh + yl` 251 | ''' 252 | def sub512Ex(xh, xl, yh, yl): 253 | if (xl >= yl): 254 | return (xh - yh, xl - yl); 255 | return (xh - yh - 1, unsafeSub(xl, yl)); 256 | -------------------------------------------------------------------------------- /project/emulation/FixedPoint/__init__.py: -------------------------------------------------------------------------------- 1 | from .AnalyticMath import pow 2 | from .AnalyticMath import log 3 | from .AnalyticMath import exp 4 | from .AdvancedMath import solveExact 5 | from .AdvancedMath import solveQuick 6 | from .AdvancedMath import lambertNegExact 7 | from .AdvancedMath import lambertPosExact 8 | from .AdvancedMath import lambertNegQuick 9 | from .AdvancedMath import lambertPosQuick 10 | from .BondingCurve import buy 11 | from .BondingCurve import sell 12 | from .BondingCurve import convert 13 | from .BondingCurve import deposit 14 | from .BondingCurve import withdraw 15 | from .BondingCurve import invest 16 | from .DynamicCurve import equalizeExact 17 | from .DynamicCurve import equalizeQuick 18 | -------------------------------------------------------------------------------- /project/emulation/FixedPoint/common/BuiltIn.py: -------------------------------------------------------------------------------- 1 | def mulmod(x, y, z): return x * y % z 2 | def revert(msg = ""): assert False, msg 3 | def require(cond, msg = ""): assert cond, msg 4 | -------------------------------------------------------------------------------- /project/emulation/FixedPoint/common/Uint256.py: -------------------------------------------------------------------------------- 1 | MAX_VAL = (1 << 256) - 1 2 | 3 | # reverts on overflow 4 | def safeAdd(x, y): 5 | assert x + y <= MAX_VAL 6 | return x + y 7 | 8 | # does not revert on overflow 9 | def unsafeAdd(x, y): 10 | return (x + y) & MAX_VAL 11 | 12 | # does not revert on overflow 13 | def unsafeSub(x, y): 14 | return (x - y) & MAX_VAL 15 | 16 | # does not revert on overflow 17 | def unsafeMul(x, y): 18 | return (x * y) & MAX_VAL 19 | 20 | # does not overflow 21 | def mulModMax(x, y): 22 | return (x * y) % MAX_VAL 23 | -------------------------------------------------------------------------------- /project/emulation/FloatPoint/__init__.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from decimal import getcontext 3 | from decimal import ROUND_HALF_DOWN 4 | 5 | 6 | getcontext().prec = 100 7 | getcontext().rounding = ROUND_HALF_DOWN 8 | 9 | 10 | one = Decimal(1) 11 | parse = lambda args: [Decimal(arg) for arg in args] 12 | 13 | 14 | def pow(*args): 15 | a, b, c, d = parse(args) 16 | return (a / b) ** (c / d) 17 | 18 | 19 | def log(*args): 20 | a, b = parse(args) 21 | return (a / b).ln() 22 | 23 | 24 | def exp(*args): 25 | a, b = parse(args) 26 | return (a / b).exp() 27 | 28 | 29 | def solve(*args): 30 | a, b, c, d, x, y = parse(args) 31 | return (c / d) / (a / b) ** (x / y) 32 | 33 | 34 | def lambert(*args): 35 | x, factor = parse(args) 36 | return lambertRatio(x / factor) * factor 37 | 38 | 39 | def buy(*args): 40 | supply, balance, weight, amount, max_weight = parse(args) 41 | return supply * ((one + amount / balance) ** (weight / max_weight) - one) 42 | 43 | 44 | def sell(*args): 45 | supply, balance, weight, amount, max_weight = parse(args) 46 | return balance * (one - (one - amount / supply) ** (max_weight / weight)) 47 | 48 | 49 | def convert(*args): 50 | balance1, weight1, balance2, weight2, amount = parse(args) 51 | return balance2 * (one - (balance1 / (balance1 + amount)) ** (weight1 / weight2)) 52 | 53 | 54 | def deposit(*args): 55 | supply, balance, weights, amount, max_weight = parse(args) 56 | return supply * ((amount / balance + one) ** (weights / max_weight) - one) 57 | 58 | 59 | def withdraw(*args): 60 | supply, balance, weights, amount, max_weight = parse(args) 61 | return balance * (one - ((supply - amount) / supply) ** (max_weight / weights)) 62 | 63 | 64 | def invest(*args): 65 | supply, balance, weights, amount, max_weight = parse(args) 66 | return balance * (((supply + amount) / supply) ** (max_weight / weights) - one) 67 | 68 | 69 | def equalize(*args): 70 | staked1, balance1, balance2, rate1, rate2, weight1, weight2 = parse(args) 71 | amount1 = staked1 - balance1 72 | amount2 = convert(balance1, weight1, balance2, weight2, amount1) 73 | return ((balance1 + amount1) * rate1) / ((balance2 - amount2) * rate2) 74 | 75 | 76 | def lambertRatio(x): 77 | y = x if x < 1 else x.ln() 78 | for _ in range(8): 79 | e = y.exp() 80 | f = y * e 81 | if f == x: break 82 | y = (y * f + x) / (f + e) 83 | return y / x 84 | -------------------------------------------------------------------------------- /project/emulation/TestAdvancedMathLambertNeg1Exact.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | FIXED_1 = FixedPoint.AdvancedMath.FIXED_1 8 | MAX_VAL = FixedPoint.AdvancedMath.LAMBERT_NEG1_MAXVAL + 1 9 | 10 | 11 | def getInput(): 12 | x = random.randrange(1, MAX_VAL) 13 | return dict(x=x) 14 | 15 | 16 | def getOutput(x): 17 | fixedPoint = FixedPoint.lambertNegExact(x) 18 | floatPoint = FloatPoint.lambert(-x, FIXED_1) 19 | return fixedPoint, floatPoint 20 | 21 | 22 | def isValid(ratio): 23 | return ratio <= 1 24 | 25 | 26 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAdvancedMathLambertNeg1Quick.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | FIXED_1 = FixedPoint.AdvancedMath.FIXED_1 8 | MAX_VAL = FixedPoint.AdvancedMath.LAMBERT_NEG1_MAXVAL + 1 9 | 10 | 11 | def getInput(): 12 | x = random.randrange(1, MAX_VAL) 13 | return dict(x=x) 14 | 15 | 16 | def getOutput(x): 17 | fixedPoint = FixedPoint.lambertNegQuick(x) 18 | floatPoint = FloatPoint.lambert(-x, FIXED_1) 19 | return fixedPoint, floatPoint 20 | 21 | 22 | def isValid(ratio): 23 | return ratio <= 1 24 | 25 | 26 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAdvancedMathLambertNeg2Exact.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | FIXED_1 = FixedPoint.AdvancedMath.FIXED_1 8 | MIN_VAL = FixedPoint.AdvancedMath.LAMBERT_NEG1_MAXVAL + 1 9 | MAX_VAL = FixedPoint.AdvancedMath.LAMBERT_NEG2_MAXVAL + 1 10 | 11 | 12 | def getInput(): 13 | x = random.randrange(MIN_VAL, MAX_VAL) 14 | return dict(x=x) 15 | 16 | 17 | def getOutput(x): 18 | fixedPoint = FixedPoint.lambertNegExact(x) 19 | floatPoint = FloatPoint.lambert(-x, FIXED_1) 20 | return fixedPoint, floatPoint 21 | 22 | 23 | def isValid(ratio): 24 | return ratio <= 1 25 | 26 | 27 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAdvancedMathLambertNeg2Quick.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | FIXED_1 = FixedPoint.AdvancedMath.FIXED_1 8 | MIN_VAL = FixedPoint.AdvancedMath.LAMBERT_NEG1_MAXVAL + 1 9 | MAX_VAL = FixedPoint.AdvancedMath.LAMBERT_NEG2_MAXVAL + 1 10 | 11 | 12 | def getInput(): 13 | x = random.randrange(MIN_VAL, MAX_VAL) 14 | return dict(x=x) 15 | 16 | 17 | def getOutput(x): 18 | fixedPoint = FixedPoint.lambertNegQuick(x) 19 | floatPoint = FloatPoint.lambert(-x, FIXED_1) 20 | return fixedPoint, floatPoint 21 | 22 | 23 | def isValid(ratio): 24 | return True 25 | 26 | 27 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAdvancedMathLambertPos1Exact.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | FIXED_1 = FixedPoint.AdvancedMath.FIXED_1 8 | MAX_VAL = FixedPoint.AdvancedMath.LAMBERT_POS1_MAXVAL + 1 9 | 10 | 11 | def getInput(): 12 | x = random.randrange(1, MAX_VAL) 13 | return dict(x=x) 14 | 15 | 16 | def getOutput(x): 17 | fixedPoint = FixedPoint.lambertPosExact(x) 18 | floatPoint = FloatPoint.lambert(+x, FIXED_1) 19 | return fixedPoint, floatPoint 20 | 21 | 22 | def isValid(ratio): 23 | return True 24 | 25 | 26 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAdvancedMathLambertPos1Quick.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | FIXED_1 = FixedPoint.AdvancedMath.FIXED_1 8 | MAX_VAL = FixedPoint.AdvancedMath.LAMBERT_POS1_MAXVAL + 1 9 | 10 | 11 | def getInput(): 12 | x = random.randrange(1, MAX_VAL) 13 | return dict(x=x) 14 | 15 | 16 | def getOutput(x): 17 | fixedPoint = FixedPoint.lambertPosQuick(x) 18 | floatPoint = FloatPoint.lambert(+x, FIXED_1) 19 | return fixedPoint, floatPoint 20 | 21 | 22 | def isValid(ratio): 23 | return True 24 | 25 | 26 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAdvancedMathLambertPos2Exact.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | FIXED_1 = FixedPoint.AdvancedMath.FIXED_1 8 | MIN_VAL = FixedPoint.AdvancedMath.LAMBERT_POS1_MAXVAL + 1 9 | MAX_VAL = FixedPoint.AdvancedMath.LAMBERT_POS2_MAXVAL + 1 10 | 11 | 12 | def getInput(): 13 | x = random.randrange(MIN_VAL, MAX_VAL) 14 | return dict(x=x) 15 | 16 | 17 | def getOutput(x): 18 | fixedPoint = FixedPoint.lambertPosExact(x) 19 | floatPoint = FloatPoint.lambert(+x, FIXED_1) 20 | return fixedPoint, floatPoint 21 | 22 | 23 | def isValid(ratio): 24 | return True 25 | 26 | 27 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAdvancedMathLambertPos2Quick.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | FIXED_1 = FixedPoint.AdvancedMath.FIXED_1 8 | MIN_VAL = FixedPoint.AdvancedMath.LAMBERT_POS1_MAXVAL + 1 9 | MAX_VAL = FixedPoint.AdvancedMath.LAMBERT_POS2_MAXVAL + 1 10 | 11 | 12 | def getInput(): 13 | x = random.randrange(MIN_VAL, MAX_VAL) 14 | return dict(x=x) 15 | 16 | 17 | def getOutput(x): 18 | fixedPoint = FixedPoint.lambertPosQuick(x) 19 | floatPoint = FloatPoint.lambert(+x, FIXED_1) 20 | return fixedPoint, floatPoint 21 | 22 | 23 | def isValid(ratio): 24 | return True 25 | 26 | 27 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAdvancedMathLambertPos3Exact.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | FIXED_1 = FixedPoint.AdvancedMath.FIXED_1 8 | MIN_VAL = FixedPoint.AdvancedMath.LAMBERT_POS2_MAXVAL + 1 9 | MAX_VAL = FixedPoint.AdvancedMath.LAMBERT_EXACT_LIMIT + 1 10 | 11 | 12 | def getInput(): 13 | x = random.randrange(MIN_VAL, MAX_VAL) 14 | return dict(x=x) 15 | 16 | 17 | def getOutput(x): 18 | fixedPoint = FixedPoint.lambertPosExact(x) 19 | floatPoint = FloatPoint.lambert(+x, FIXED_1) 20 | return fixedPoint, floatPoint 21 | 22 | 23 | def isValid(ratio): 24 | return ratio <= 1 25 | 26 | 27 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAdvancedMathLambertPos3Quick.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | FIXED_1 = FixedPoint.AdvancedMath.FIXED_1 8 | MIN_VAL = FixedPoint.AdvancedMath.LAMBERT_POS2_MAXVAL + 1 9 | MAX_VAL = FixedPoint.AdvancedMath.LAMBERT_EXACT_LIMIT + 1 10 | 11 | 12 | def getInput(): 13 | x = random.randrange(MIN_VAL, MAX_VAL) 14 | return dict(x=x) 15 | 16 | 17 | def getOutput(x): 18 | fixedPoint = FixedPoint.lambertPosQuick(x) 19 | floatPoint = FloatPoint.lambert(+x, FIXED_1) 20 | return fixedPoint, floatPoint 21 | 22 | 23 | def isValid(ratio): 24 | return True 25 | 26 | 27 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAdvancedMathLambertPos4Exact.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | FIXED_1 = FixedPoint.AdvancedMath.FIXED_1 8 | MIN_VAL = FixedPoint.AdvancedMath.LAMBERT_EXACT_LIMIT + 1 9 | MAX_VAL = 2 ** 256 10 | 11 | 12 | def getInput(): 13 | x = random.randrange(MIN_VAL, MAX_VAL) 14 | return dict(x=x) 15 | 16 | 17 | def getOutput(x): 18 | fixedPoint = FixedPoint.lambertPosExact(x) 19 | floatPoint = FloatPoint.lambert(+x, FIXED_1) 20 | return fixedPoint, floatPoint 21 | 22 | 23 | def isValid(ratio): 24 | return ratio <= 1 25 | 26 | 27 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAdvancedMathLambertPos4Quick.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | FIXED_1 = FixedPoint.AdvancedMath.FIXED_1 8 | MIN_VAL = FixedPoint.AdvancedMath.LAMBERT_EXACT_LIMIT + 1 9 | MAX_VAL = 2 ** 256 10 | 11 | 12 | def getInput(): 13 | x = random.randrange(MIN_VAL, MAX_VAL) 14 | return dict(x=x) 15 | 16 | 17 | def getOutput(x): 18 | fixedPoint = FixedPoint.lambertPosQuick(x) 19 | floatPoint = FloatPoint.lambert(+x, FIXED_1) 20 | return fixedPoint, floatPoint 21 | 22 | 23 | def isValid(ratio): 24 | return ratio <= 1 25 | 26 | 27 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAdvancedMathSolveExact.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | def getInput(): 8 | a = random.randrange(1, 1000) 9 | b = random.randrange(1, 1000) 10 | c = random.randrange(1, 1000) 11 | d = random.randrange(1, 1000) 12 | return dict(a=a, b=b, c=c, d=d) 13 | 14 | 15 | def getOutput(a, b, c, d): 16 | fixedPoint = FixedPoint.solveExact(a, b, c, d) 17 | floatPoint = FloatPoint.solve(a, b, c, d, *fixedPoint) 18 | return fixedPoint, floatPoint 19 | 20 | 21 | def isValid(ratio): 22 | return True 23 | 24 | 25 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAdvancedMathSolveQuick.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | def getInput(): 8 | a = random.randrange(1, 1000) 9 | b = random.randrange(1, 1000) 10 | c = random.randrange(1, 1000) 11 | d = random.randrange(1, 1000) 12 | return dict(a=a, b=b, c=c, d=d) 13 | 14 | 15 | def getOutput(a, b, c, d): 16 | fixedPoint = FixedPoint.solveQuick(a, b, c, d) 17 | floatPoint = FloatPoint.solve(a, b, c, d, *fixedPoint) 18 | return fixedPoint, floatPoint 19 | 20 | 21 | def isValid(ratio): 22 | return True 23 | 24 | 25 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAnalyticMathExp.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | def getInput(): 8 | a = random.randrange(10, 10 ** 26) 9 | b = random.randrange(a // 10, a * 10) 10 | return dict(a=a, b=b) 11 | 12 | 13 | def getOutput(a, b): 14 | fixedPoint = FixedPoint.exp(a, b) 15 | floatPoint = FloatPoint.exp(a, b) 16 | return fixedPoint, floatPoint 17 | 18 | 19 | def isValid(ratio): 20 | return ratio <= 1 21 | 22 | 23 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAnalyticMathLog.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | def getInput(): 8 | a = random.randrange(2, 10 ** 26) 9 | b = random.randrange(1, a) 10 | return dict(a=a, b=b) 11 | 12 | 13 | def getOutput(a, b): 14 | fixedPoint = FixedPoint.log(a, b) 15 | floatPoint = FloatPoint.log(a, b) 16 | return fixedPoint, floatPoint 17 | 18 | 19 | def isValid(ratio): 20 | return ratio <= 1 21 | 22 | 23 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestAnalyticMathPow.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | def getInput(): 8 | a = random.randrange(2, 10 ** 26) 9 | b = random.randrange(1, a) 10 | c = random.randrange(2, 10 ** 26) 11 | d = random.randrange(c // 2, 10 ** 26) 12 | return dict(a=a, b=b, c=c, d=d) 13 | 14 | 15 | def getOutput(a, b, c, d): 16 | fixedPoint = FixedPoint.pow(a, b, c, d) 17 | floatPoint = FloatPoint.pow(a, b, c, d) 18 | return fixedPoint, floatPoint 19 | 20 | 21 | def isValid(ratio): 22 | return ratio <= 1 23 | 24 | 25 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestBondingCurveBuy.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | MAX_WEIGHT = FixedPoint.BondingCurve.MAX_WEIGHT 8 | 9 | 10 | def getInput(): 11 | supply = random.randrange(2, 10 ** 26) 12 | balance = random.randrange(1, 10 ** 23) 13 | weight = random.randrange(1, MAX_WEIGHT + 1) 14 | amount = random.randrange(1, balance * 10) 15 | return dict(supply=supply, balance=balance, weight=weight, amount=amount) 16 | 17 | 18 | def getOutput(supply, balance, weight, amount): 19 | fixedPoint = FixedPoint.buy(supply, balance, weight, amount) 20 | floatPoint = FloatPoint.buy(supply, balance, weight, amount, MAX_WEIGHT) 21 | return fixedPoint, floatPoint 22 | 23 | 24 | def isValid(ratio): 25 | return ratio <= 1 26 | 27 | 28 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestBondingCurveConvert.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | MAX_WEIGHT = FixedPoint.BondingCurve.MAX_WEIGHT 8 | 9 | 10 | def getInput(): 11 | balance1 = random.randrange(1, 10 ** 23) 12 | weight1 = random.randrange(1, MAX_WEIGHT + 1) 13 | balance2 = random.randrange(1, 10 ** 23) 14 | weight2 = random.randrange(1, MAX_WEIGHT + 1) 15 | amount = random.randrange(1, balance1 * 10) 16 | return dict(balance1=balance1, weight1=weight1, balance2=balance2, weight2=weight2, amount=amount) 17 | 18 | 19 | def getOutput(balance1, weight1, balance2, weight2, amount): 20 | fixedPoint = FixedPoint.convert(balance1, weight1, balance2, weight2, amount) 21 | floatPoint = FloatPoint.convert(balance1, weight1, balance2, weight2, amount) 22 | return fixedPoint, floatPoint 23 | 24 | 25 | def isValid(ratio): 26 | return ratio <= 1 27 | 28 | 29 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestBondingCurveDeposit.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | MAX_WEIGHT = FixedPoint.BondingCurve.MAX_WEIGHT 8 | 9 | 10 | def getInput(): 11 | supply = random.randrange(2, 10 ** 26) 12 | balance = random.randrange(1, 10 ** 23) 13 | weights = random.randrange(MAX_WEIGHT // 100, MAX_WEIGHT * 2 + 1) 14 | amount = random.randrange(1, balance * 10) 15 | return dict(supply=supply, balance=balance, weights=weights, amount=amount) 16 | 17 | 18 | def getOutput(supply, balance, weights, amount): 19 | fixedPoint = FixedPoint.deposit(supply, balance, weights, amount) 20 | floatPoint = FloatPoint.deposit(supply, balance, weights, amount, MAX_WEIGHT) 21 | return fixedPoint, floatPoint 22 | 23 | 24 | def isValid(ratio): 25 | return ratio <= 1 26 | 27 | 28 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestBondingCurveInvest.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | MAX_WEIGHT = FixedPoint.BondingCurve.MAX_WEIGHT 8 | 9 | 10 | def getInput(): 11 | supply = random.randrange(2, 10 ** 26) 12 | balance = random.randrange(1, 10 ** 23) 13 | weights = random.randrange(MAX_WEIGHT // 100, MAX_WEIGHT * 2 + 1) 14 | amount = random.randrange(1, supply // 10) 15 | return dict(supply=supply, balance=balance, weights=weights, amount=amount) 16 | 17 | 18 | def getOutput(supply, balance, weights, amount): 19 | fixedPoint = FixedPoint.invest(supply, balance, weights, amount) 20 | floatPoint = FloatPoint.invest(supply, balance, weights, amount, MAX_WEIGHT) 21 | return fixedPoint, floatPoint 22 | 23 | 24 | def isValid(ratio): 25 | return ratio >= 1 26 | 27 | 28 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestBondingCurveSell.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | MAX_WEIGHT = FixedPoint.BondingCurve.MAX_WEIGHT 8 | 9 | 10 | def getInput(): 11 | supply = random.randrange(2, 10 ** 26) 12 | balance = random.randrange(1, 10 ** 23) 13 | weight = random.randrange(1, MAX_WEIGHT + 1) 14 | amount = random.randrange(1, supply) 15 | return dict(supply=supply, balance=balance, weight=weight, amount=amount) 16 | 17 | 18 | def getOutput(supply, balance, weight, amount): 19 | fixedPoint = FixedPoint.sell(supply, balance, weight, amount) 20 | floatPoint = FloatPoint.sell(supply, balance, weight, amount, MAX_WEIGHT) 21 | return fixedPoint, floatPoint 22 | 23 | 24 | def isValid(ratio): 25 | return ratio <= 1 26 | 27 | 28 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestBondingCurveWithdraw.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | MAX_WEIGHT = FixedPoint.BondingCurve.MAX_WEIGHT 8 | 9 | 10 | def getInput(): 11 | supply = random.randrange(2, 10 ** 26) 12 | balance = random.randrange(1, 10 ** 23) 13 | weights = random.randrange(MAX_WEIGHT // 100, MAX_WEIGHT * 2 + 1) 14 | amount = random.randrange(1, supply // 10) 15 | return dict(supply=supply, balance=balance, weights=weights, amount=amount) 16 | 17 | 18 | def getOutput(supply, balance, weights, amount): 19 | fixedPoint = FixedPoint.withdraw(supply, balance, weights, amount) 20 | floatPoint = FloatPoint.withdraw(supply, balance, weights, amount, MAX_WEIGHT) 21 | return fixedPoint, floatPoint 22 | 23 | 24 | def isValid(ratio): 25 | return ratio <= 1 26 | 27 | 28 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestDynamicCurveEqualizeExact.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | def getInput(): 8 | rate = random.randrange(10 ** 18, 10 ** 21) 9 | staked1 = random.randrange(10 ** 24, 10 ** 27) 10 | balance1 = staked1 * random.randrange(75, 150) // 100 11 | balance2 = staked1 * random.randrange(75, 150) // 100 12 | rate1 = rate * random.randrange(75, 150) // 100 13 | rate2 = rate * random.randrange(75, 150) // 100 14 | return dict(staked1=staked1, balance1=balance1, balance2=balance2, rate1=rate1, rate2=rate2) 15 | 16 | 17 | def getOutput(staked1, balance1, balance2, rate1, rate2): 18 | fixedPoint = FixedPoint.equalizeExact(staked1, balance1, balance2, rate1, rate2) 19 | floatPoint = FloatPoint.equalize(staked1, balance1, balance2, rate1, rate2, *fixedPoint) 20 | return fixedPoint, floatPoint 21 | 22 | 23 | def isValid(ratio): 24 | return True 25 | 26 | 27 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestDynamicCurveEqualizeQuick.py: -------------------------------------------------------------------------------- 1 | import random 2 | import FixedPoint 3 | import FloatPoint 4 | import TestScheme 5 | 6 | 7 | def getInput(): 8 | rate = random.randrange(10 ** 18, 10 ** 21) 9 | staked1 = random.randrange(10 ** 24, 10 ** 27) 10 | balance1 = staked1 * random.randrange(75, 150) // 100 11 | balance2 = staked1 * random.randrange(75, 150) // 100 12 | rate1 = rate * random.randrange(75, 150) // 100 13 | rate2 = rate * random.randrange(75, 150) // 100 14 | return dict(staked1=staked1, balance1=balance1, balance2=balance2, rate1=rate1, rate2=rate2) 15 | 16 | 17 | def getOutput(staked1, balance1, balance2, rate1, rate2): 18 | fixedPoint = FixedPoint.equalizeQuick(staked1, balance1, balance2, rate1, rate2) 19 | floatPoint = FloatPoint.equalize(staked1, balance1, balance2, rate1, rate2, *fixedPoint) 20 | return fixedPoint, floatPoint 21 | 22 | 23 | def isValid(ratio): 24 | return True 25 | 26 | 27 | TestScheme.run(getInput, getOutput, isValid) -------------------------------------------------------------------------------- /project/emulation/TestScheme/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def run(getInput, getOutput, isValid): 5 | tests = int(sys.argv[1] if len(sys.argv) > 1 else input('How many tests would you like to execute? ')) 6 | 7 | minRatio = float('+inf') 8 | maxRatio = float('-inf') 9 | failures = 0 10 | 11 | for test in range(tests): 12 | inputArgs = getInput() 13 | try: 14 | fixedPoint, floatPoint = getOutput(**inputArgs) 15 | except AssertionError: 16 | ratio = 0 17 | failures += 1 18 | else: 19 | ratio = div(fixedPoint, floatPoint) 20 | if isValid(ratio): 21 | minRatio = min(minRatio, ratio) 22 | maxRatio = max(maxRatio, ratio) 23 | else: 24 | alert(inputArgs, fixedPoint, floatPoint) 25 | break 26 | print(f'Test #{test}: ratio = {ratio:.40f}, min = {minRatio:.40f}, max = {maxRatio:.40f}, failures = {failures}') 27 | 28 | 29 | def div(fixedPoint, floatPoint): 30 | return { 31 | int: lambda fixedPoint, floatPoint: fixedPoint / floatPoint, 32 | tuple: lambda fixedPoint, floatPoint: fixedPoint[0] / (fixedPoint[1] * floatPoint) 33 | }[type(fixedPoint)](fixedPoint, floatPoint) 34 | 35 | 36 | def alert(inputArgs, fixedPoint, floatPoint): 37 | maxKeyLen = max(len(key) for key in inputArgs) 38 | error = [f'Implementation Error:'] 39 | error.append(f'- Input:') 40 | error.extend(f' - {key.ljust(maxKeyLen)} = {val}' for key, val in inputArgs.items()) 41 | error.append(f'- Output:') 42 | error.append(f' - fixedPoint = {fixedPoint}') 43 | error.append(f' - floatPoint = {floatPoint:f}') 44 | print('\n'.join(error)) 45 | -------------------------------------------------------------------------------- /project/tests/AdvancedMath.js: -------------------------------------------------------------------------------- 1 | const TestContract = artifacts.require("AdvancedMathUser"); 2 | const Constants = require("./helpers/Constants.js"); 3 | const Utilities = require("./helpers/Utilities.js"); 4 | const Decimal = require("decimal.js"); 5 | 6 | const W_MIN_X = Decimal(-1).exp().neg(); 7 | const FIXED_1 = Decimal(Constants.FIXED_1); 8 | 9 | const LAMBERT_NEG0 = Decimal(0); 10 | const LAMBERT_NEG1 = Decimal(Constants.LAMBERT_NEG1_MAXVAL); 11 | const LAMBERT_NEG2 = Decimal(Constants.LAMBERT_NEG2_MAXVAL); 12 | const LAMBERT_POS0 = Decimal(0); 13 | const LAMBERT_POS1 = Decimal(Constants.LAMBERT_POS1_MAXVAL); 14 | const LAMBERT_POS2 = Decimal(Constants.LAMBERT_POS2_MAXVAL); 15 | const LAMBERT_POS3 = Decimal(Constants.LAMBERT_EXACT_LIMIT); 16 | const LAMBERT_POS4 = Decimal(2).pow(256).sub(1); 17 | 18 | const SIGN = { 19 | lambertNegExact: -1, 20 | lambertPosExact: +1, 21 | lambertNegQuick: -1, 22 | lambertPosQuick: +1, 23 | }; 24 | 25 | function solvable(a, b, c, d) { 26 | return Decimal(a).div(b).ln().mul(c).div(d).gte(W_MIN_X); 27 | } 28 | 29 | function lambertRatio(x) { 30 | assert(x.gte(W_MIN_X)); 31 | let y = x.lt(1) ? x : x.ln(); 32 | for (let n = 0; n < 8; n++) { 33 | const e = y.exp(); 34 | const f = y.mul(e); 35 | if (f.eq(x)) break; 36 | y = y.mul(f).add(x).div(f.add(e)); 37 | } 38 | return y.div(x); 39 | } 40 | 41 | describe(TestContract.contractName, () => { 42 | let testContract; 43 | 44 | before(async () => { 45 | testContract = await TestContract.new(); 46 | }); 47 | 48 | for (const a of [1, 2, 3, 4, 5]) { 49 | for (const b of [1, 2, 3, 4, 5]) { 50 | for (const c of [1, 2, 3, 4, 5]) { 51 | for (const d of [1, 2, 3, 4, 5]) { 52 | testSolve("solveExact", a, b, c, d, "0.00000000000000000000010142175418052637"); 53 | testSolve("solveQuick", a, b, c, d, "0.00478611537034482992866132560056904476"); 54 | } 55 | } 56 | } 57 | } 58 | 59 | for (const a of [1, 2, 3, 4, 5].map(n => n + 1000)) { 60 | for (const b of [1, 2, 3, 4, 5].map(n => n + 1000)) { 61 | for (const c of [1, 2, 3, 4, 5].map(n => n + 1000)) { 62 | for (const d of [1, 2, 3, 4, 5].map(n => n + 1000)) { 63 | testSolve("solveExact", a, b, c, d, "0.00000000000000000000000000000000001082"); 64 | testSolve("solveQuick", a, b, c, d, "0.00000000000000000000000000000000103095"); 65 | } 66 | } 67 | } 68 | } 69 | 70 | for (let percent = 0; percent <= 100; percent++) { 71 | testSuccess("lambertNegExact", percent, LAMBERT_NEG0, LAMBERT_NEG1, "0.00000000000000000000000000000000000088"); 72 | testSuccess("lambertNegExact", percent, LAMBERT_NEG1, LAMBERT_NEG2, "0.00000000000000000000000000000000000259"); 73 | testSuccess("lambertPosExact", percent, LAMBERT_POS0, LAMBERT_POS1, "0.00000000000000000000000000000000000097"); 74 | testSuccess("lambertPosExact", percent, LAMBERT_POS1, LAMBERT_POS2, "0.00000000000000000000000000000000000006"); 75 | testSuccess("lambertPosExact", percent, LAMBERT_POS2, LAMBERT_POS3, "0.00001793478206167702476852962356511687"); 76 | testSuccess("lambertPosExact", percent, LAMBERT_POS3, LAMBERT_POS4, "0.04076265753190620535671784581901863592"); 77 | } 78 | 79 | for (let percent = 0; percent <= 100; percent++) { 80 | testSuccess("lambertNegQuick", percent, LAMBERT_NEG0, LAMBERT_NEG1, "0.04458843601911142612093766473768569069"); 81 | testSuccess("lambertNegQuick", percent, LAMBERT_NEG1, LAMBERT_NEG2, "0.00656991066935660006721266366675943493"); 82 | testSuccess("lambertPosQuick", percent, LAMBERT_POS0, LAMBERT_POS1, "0.00352502537296632393189150614600911966"); 83 | testSuccess("lambertPosQuick", percent, LAMBERT_POS1, LAMBERT_POS2, "0.00202834415207521945800897906620169050"); 84 | testSuccess("lambertPosQuick", percent, LAMBERT_POS2, LAMBERT_POS3, "0.00250533328316541877372004036689769731"); 85 | testSuccess("lambertPosQuick", percent, LAMBERT_POS3, LAMBERT_POS4, "0.04076265753190620535671784581901863592"); 86 | } 87 | 88 | testFailure("lambertPosExact", LAMBERT_POS0.add(0), "lambertPosExact: x < min"); 89 | testFailure("lambertPosQuick", LAMBERT_POS0.add(0), "lambertPosQuick: x < min"); 90 | 91 | testFailure("lambertNegExact", LAMBERT_NEG0.add(0), "lambertNegExact: x < min"); 92 | testFailure("lambertNegQuick", LAMBERT_NEG0.add(0), "lambertNegQuick: x < min"); 93 | 94 | testFailure("lambertNegExact", LAMBERT_NEG2.add(1), "lambertNegExact: x > max"); 95 | testFailure("lambertNegQuick", LAMBERT_NEG2.add(1), "lambertNegQuick: x > max"); 96 | 97 | function testSolve(methodName, a, b, c, d, maxError) { 98 | it(`${methodName}(${a}, ${b}, ${c}, ${d})`, async () => { 99 | if (solvable(a, b, c, d)) { 100 | const result = await testContract[methodName](a, b, c, d); 101 | const x = Decimal(result[0].toString()).div(result[1].toString()); 102 | const error = x.mul(Decimal(a).div(b).pow(x)).div(c).mul(d).sub(1).abs(); 103 | assert(error.lte(maxError), `error = ${error.toFixed()}`); 104 | } 105 | else { 106 | await Utilities.assertRevert(testContract[methodName](a, b, c, d)); 107 | } 108 | }); 109 | } 110 | 111 | function testSuccess(methodName, percent, minVal, maxVal, maxError) { 112 | it(`${methodName}(${percent}%)`, async () => { 113 | const val = minVal.add(1).add(maxVal.sub(minVal.add(1)).mul(percent).divToInt(100)); 114 | const actual = Decimal((await testContract[methodName](val.toFixed())).toString()); 115 | const expected = lambertRatio(val.mul(SIGN[methodName]).div(FIXED_1)).mul(FIXED_1); 116 | if (!actual.eq(expected)) { 117 | const error = actual.div(expected).sub(1).abs(); 118 | assert(error.lte(maxError), `error = ${error.toFixed()}`); 119 | } 120 | }); 121 | } 122 | 123 | function testFailure(methodName, val, errorMsg) { 124 | it(`${methodName}(${val.toHex()})`, async () => { 125 | await Utilities.assertRevert(testContract[methodName](val.toFixed()), errorMsg); 126 | }); 127 | } 128 | }); 129 | -------------------------------------------------------------------------------- /project/tests/AnalyticMath.js: -------------------------------------------------------------------------------- 1 | const TestContract = artifacts.require("AnalyticMathUser"); 2 | const Constants = require("./helpers/Constants.js"); 3 | const Utilities = require("./helpers/Utilities.js"); 4 | const Decimal = require("decimal.js"); 5 | 6 | const ZERO = Decimal(0); 7 | const ONE = Decimal(1); 8 | const TWO = Decimal(2); 9 | 10 | const FIXED_1 = Decimal(Constants.FIXED_1); 11 | const LOG_MID = Decimal(Constants.LOG_MID); 12 | const EXP_MID = Decimal(Constants.EXP_MID); 13 | const EXP_MAX = Decimal(Constants.EXP_MAX); 14 | 15 | const MAX_U32 = TWO.pow(32).sub(1) 16 | const MAX_VAL = TWO.pow(256).sub(1); 17 | const MAX_MUL = MAX_VAL.div(FIXED_1).floor(); 18 | 19 | const pow = (a, b, c, d) => a.div(b).pow(c.div(d)); 20 | const log = (a, b) => a.div(b).ln(); 21 | const exp = (a, b) => a.div(b).exp(); 22 | const fixedLog = (x) => log(x, FIXED_1).mul(FIXED_1); 23 | const fixedExp = (x) => exp(x, FIXED_1).mul(FIXED_1); 24 | 25 | const equalTo = (x, y) => x.eq(y); 26 | const lessThan = (x, y) => x.lt(y); 27 | const moreThan = (x, y) => x.gt(y); 28 | const toInteger = (value) => Decimal(value.toString()); 29 | const toFraction = (tuple) => Decimal(tuple[0].toString()).div(tuple[1].toString()); 30 | 31 | const portion = (min, max, percent) => min.add(max.sub(min).mul(percent).divToInt(100)); 32 | 33 | const terms = (num, max) => [...Array(2 ** num - 1).keys()].map(i => TWO.pow(max - num).mul(i + 1)); 34 | 35 | describe(TestContract.contractName, () => { 36 | let testContract; 37 | 38 | before(async () => { 39 | testContract = await TestContract.new(); 40 | }); 41 | 42 | for (const a of [ZERO, ONE, MAX_VAL]) { 43 | for (const b of [ZERO, ONE, MAX_VAL]) { 44 | for (const c of [ZERO, ONE, MAX_VAL]) { 45 | for (const d of [ZERO, ONE, MAX_VAL]) { 46 | if (b.eq(0) || d.eq(0)) { 47 | testFailure(pow, "division by zero", a, b, c, d); 48 | } 49 | else if (a.eq(0) || c.eq(0)) { 50 | testSuccess(pow, toFraction, equalTo, "0", a, b, c, d); 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | for (let a = 1; a < 5; a++) { 58 | for (let b = 1; b <= a; b++) { 59 | for (let c = 1; c < 5; c++) { 60 | for (let d = 1; d < 5; d++) { 61 | testSuccess(pow, toFraction, lessThan, "0.000000000000000000000000000000000000171", a, b, c, d); 62 | testSuccess(pow, toFraction, lessThan, "0.000000000000000000000000000000000000620", 1000000 - a, b, c, d); 63 | testSuccess(pow, toFraction, lessThan, "0.000000000000000000000000000000000000615", 1000000 + a, b, c, d); 64 | testSuccess(pow, toFraction, moreThan, "0.000000000000000000000000000000000000171", b, a, c, d); 65 | testSuccess(pow, toFraction, moreThan, "0.000000000000000000000000000000000000620", b, 1000000 - a, c, d); 66 | testSuccess(pow, toFraction, moreThan, "0.000000000000000000000000000000000000615", b, 1000000 + a, c, d); 67 | } 68 | } 69 | testSuccess(log, toFraction, lessThan, "0.000000000000000000000000000000000000110", a, b); 70 | testSuccess(log, toFraction, lessThan, "0.000000000000000000000000000000000000023", 1000000 - a, 100000 + b); 71 | testSuccess(log, toFraction, lessThan, "0.000000000000000000000000000000000000023", 1000000 + a, 100000 - b); 72 | testSuccess(exp, toFraction, lessThan, "0.000000000000000000000000000000000000014", a, b); 73 | testSuccess(exp, toFraction, lessThan, "0.000000000000000000000000000000000000046", 1000000 - a, 100000 + b); 74 | testSuccess(exp, toFraction, lessThan, "0.000000000000000000000000000000000000045", 1000000 + a, 100000 - b); 75 | } 76 | } 77 | 78 | for (let percent = 1; percent <= 100; percent++) { 79 | testSuccess(pow, toFraction, lessThan, "0.000000000000000000000000000000000000002", MAX_MUL, MAX_MUL.sub(1), portion(ZERO, MAX_U32, percent), MAX_U32); 80 | testSuccess(pow, toFraction, lessThan, "0.000000000000000000000000000000000000147", MAX_MUL, MAX_MUL.sub(1), MAX_U32, portion(ZERO, MAX_U32, percent)); 81 | testSuccess(pow, toFraction, lessThan, "0.000000000000000000000000000000000000070", MAX_MUL, portion(MAX_U32, MAX_MUL, percent), MAX_U32.sub(1), MAX_U32); 82 | testSuccess(pow, toFraction, lessThan, "0.000000000000000000000000000000000000072", MAX_MUL, portion(MAX_U32, MAX_MUL, percent), MAX_U32, MAX_U32.sub(1)); 83 | testSuccess(pow, toFraction, lessThan, "0.000000000000000000000000000000000000584", MAX_MUL, MAX_U32, portion(ZERO, MAX_U32, percent), MAX_U32); 84 | testSuccess(pow, toFraction, lessThan, "0.000000000000000000000000000000000000795", MAX_MUL, ONE, portion(ZERO, MAX_U32, percent), MAX_U32); 85 | } 86 | 87 | for (let percent = 0; percent < 100; percent++) { 88 | testSuccess(fixedLog, toInteger, lessThan, "0.000000000000000000000000000000000001054", portion(FIXED_1, LOG_MID, percent)); 89 | testSuccess(fixedLog, toInteger, lessThan, "0.000000000000000000000000000000000000055", portion(LOG_MID, FIXED_1.mul(4), percent)); 90 | testSuccess(fixedLog, toInteger, lessThan, "0.000000000000000000000000000000000000006", portion(FIXED_1.mul(4), MAX_VAL, percent)); 91 | testSuccess(fixedExp, toInteger, lessThan, "0.000000000000000000000000000000000000014", portion(ZERO, EXP_MID, percent)); 92 | testSuccess(fixedExp, toInteger, lessThan, "0.000000000000000000000000000000000000061", portion(EXP_MID, EXP_MID.mul(2), percent)); 93 | testSuccess(fixedExp, toInteger, lessThan, "0.000000000000000000000000000000000000290", portion(EXP_MID.mul(2), EXP_MAX, percent)); 94 | } 95 | 96 | testSuccess(pow, toFraction, lessThan, "0.000000000000000000000000000000000000587", MAX_MUL, MAX_U32.sub(1), MAX_U32, MAX_U32); 97 | testSuccess(pow, toFraction, lessThan, "0.000000000000000000000000000000000000593", MAX_MUL, MAX_U32, MAX_U32.sub(1), MAX_U32); 98 | testSuccess(pow, toFraction, lessThan, "0.000000000000000000000000000000000000588", MAX_MUL, MAX_U32, MAX_U32, MAX_U32.sub(1)); 99 | 100 | testSuccess(fixedLog, toInteger, lessThan, "0.000000000000000000000000000000000000040", LOG_MID.sub(1)); 101 | testSuccess(fixedLog, toInteger, lessThan, "0.000000000000000000000000000000000000050", LOG_MID.add(1)); 102 | testSuccess(fixedLog, toInteger, lessThan, "0.000000000000000000000000000000000000006", MAX_VAL); 103 | 104 | testSuccess(fixedExp, toInteger, lessThan, "0.000000000000000000000000000000000000014", EXP_MID.sub(1)); 105 | testSuccess(fixedExp, toInteger, lessThan, "0.000000000000000000000000000000000000031", EXP_MID.add(1)); 106 | testSuccess(fixedExp, toInteger, lessThan, "0.000000000000000000000000000000000000289", EXP_MAX.sub(1)); 107 | 108 | for (const term of terms(Constants.LOG_NUM_OF_HI_TERMS, Constants.LOG_MAX_HI_TERM_VAL)) { 109 | const n = term.exp().mul(FIXED_1).floor(); 110 | for (const k of [-1, 0, 1]) { 111 | testSuccess(fixedLog, toInteger, lessThan, "0.000000000000000000000000000000000004722", n.add(k)); 112 | } 113 | } 114 | 115 | for (const term of terms(Constants.EXP_NUM_OF_HI_TERMS, Constants.EXP_MAX_HI_TERM_VAL)) { 116 | const n = term.mul(FIXED_1); 117 | for (const k of [-1, 0, 1]) { 118 | testSuccess(fixedExp, toInteger, lessThan, "0.000000000000000000000000000000000000014", n.add(k)); 119 | } 120 | } 121 | 122 | testFailure(pow, "without a reason", MAX_MUL.add(1), ONE, ONE, ONE); 123 | testFailure(pow, "without a reason", ONE, MAX_MUL.add(1), ONE, ONE); 124 | testFailure(log, "without a reason", MAX_MUL.add(1), ONE); 125 | testFailure(exp, "without a reason", MAX_MUL.add(1), ONE); 126 | 127 | testFailure(log, "fixedLog: x < min", 1, 2); 128 | testFailure(exp, "fixedExp: x > max", 178, 1); 129 | testFailure(fixedLog, "fixedLog: x < min", FIXED_1.sub(1)); 130 | testFailure(fixedExp, "fixedExp: x > max", EXP_MAX.sub(0)); 131 | 132 | function testSuccess(method, toActual, check, maxError, ...args) { 133 | it(`${method.name}(${args.map(x => x.toFixed()).join(", ")})`, async () => { 134 | const actual = toActual(await testContract[method.name](...args.map(x => x.toFixed()))); 135 | const expected = method(...args.map(x => Decimal(x))); 136 | if (!actual.eq(expected)) { 137 | const error = actual.div(expected).sub(1).abs(); 138 | assert(check(actual, expected), "critical error"); 139 | assert(error.lte(maxError), `error = ${error.toFixed()}`); 140 | } 141 | }); 142 | } 143 | 144 | function testFailure(method, errorMsg, ...args) { 145 | it(`${method.name}(${args.map(x => x.toFixed()).join(", ")})`, async () => { 146 | await Utilities.assertRevert(testContract[method.name](...args.map(x => x.toFixed())), errorMsg); 147 | }); 148 | } 149 | }); 150 | -------------------------------------------------------------------------------- /project/tests/BondingCurve.js: -------------------------------------------------------------------------------- 1 | const TestContract = artifacts.require("BondingCurveUser"); 2 | const Decimal = require("decimal.js"); 3 | 4 | const ONE = Decimal(1); 5 | const MAX_WEIGHT = Decimal(1000000); 6 | 7 | const lt = max => arg => Decimal(arg).lt(max); 8 | 9 | const buy = (supply, balance, weight, amount) => supply.mul((ONE.add(amount.div(balance))).pow(weight.div(MAX_WEIGHT)).sub(ONE)); 10 | const sell = (supply, balance, weight, amount) => balance.mul(ONE.sub(ONE.sub(amount.div(supply)).pow((MAX_WEIGHT.div(weight))))); 11 | const convert = (balance1, weight1, balance2, weight2, amount) => balance2.mul(ONE.sub(balance1.div(balance1.add(amount)).pow(weight1.div(weight2)))); 12 | const deposit = (supply, balance, weights, amount) => supply.mul(amount.div(balance).add(ONE).pow(weights.div(MAX_WEIGHT)).sub(ONE)); 13 | const withdraw = (supply, balance, weights, amount) => balance.mul(ONE.sub(supply.sub(amount).div(supply).pow(MAX_WEIGHT.div(weights)))); 14 | const invest = (supply, balance, weights, amount) => balance.mul(supply.add(amount).div(supply).pow(MAX_WEIGHT.div(weights)).sub(ONE)); 15 | 16 | describe(TestContract.contractName, () => { 17 | let testContract; 18 | 19 | before(async () => { 20 | testContract = await TestContract.new(); 21 | }); 22 | 23 | for (const supply of [1, 2, 3, 4].map(n => `${n}`.repeat(21 + n))) { 24 | for (const balance of [1, 2, 3, 4].map(n => `${n}`.repeat(21 + n))) { 25 | for (const weight of [10, 20, 90, 100].map(p => `${p * 10000}`)) { 26 | for (const amount of [0, 1, 2, 3, 4].map(n => `${n}`.repeat(18 + n))) { 27 | test(buy, "0.000000000000015", supply, balance, weight, amount); 28 | } 29 | } 30 | } 31 | } 32 | 33 | for (const supply of [1, 2, 3, 4].map(n => `${n}`.repeat(21 + n))) { 34 | for (const balance of [1, 2, 3, 4].map(n => `${n}`.repeat(21 + n))) { 35 | for (const weight of [10, 20, 90, 100].map(p => `${p * 10000}`)) { 36 | for (const amount of [0, 1, 2, 3, 4].map(n => `${n}`.repeat(18 + n)).filter(lt(supply)).concat(supply)) { 37 | test(sell, "0.000000000000003", supply, balance, weight, amount); 38 | } 39 | } 40 | } 41 | } 42 | 43 | for (const balance1 of [1, 2, 3, 4].map(n => `${n}`.repeat(21 + n))) { 44 | for (const weight1 of [10, 20, 50, 100].map(p => `${p * 10000}`)) { 45 | for (const balance2 of [1, 2, 3, 4].map(n => `${n}`.repeat(21 + n))) { 46 | for (const weight2 of [10, 20, 50, 100].map(p => `${p * 10000}`)) { 47 | for (const amount of [0, 1, 2, 3, 4].map(n => `${n}`.repeat(18 + n))) { 48 | test(convert, "0.000000000000014", balance1, weight1, balance2, weight2, amount); 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | for (const supply of [1, 2, 3, 4].map(n => `${n}`.repeat(21 + n))) { 56 | for (const balance of [1, 2, 3, 4].map(n => `${n}`.repeat(21 + n))) { 57 | for (const weights of [10, 50, 100, 200].map(p => `${p * 10000}`)) { 58 | for (const amount of [0, 1, 2, 3, 4].map(n => `${n}`.repeat(18 + n))) { 59 | test(deposit, "0.000000000000010", supply, balance, weights, amount); 60 | } 61 | } 62 | } 63 | } 64 | 65 | for (const supply of [1, 2, 3, 4].map(n => `${n}`.repeat(21 + n))) { 66 | for (const balance of [1, 2, 3, 4].map(n => `${n}`.repeat(21 + n))) { 67 | for (const weights of [10, 50, 100, 200].map(p => `${p * 10000}`)) { 68 | for (const amount of [0, 1, 2, 3, 4].map(n => `${n}`.repeat(18 + n)).filter(lt(supply)).concat(supply)) { 69 | test(withdraw, "0.000000000000004", supply, balance, weights, amount); 70 | } 71 | } 72 | } 73 | } 74 | 75 | for (const supply of [1, 2, 3, 4].map(n => `${n}`.repeat(21 + n))) { 76 | for (const balance of [1, 2, 3, 4].map(n => `${n}`.repeat(21 + n))) { 77 | for (const weights of [10, 50, 100, 200].map(p => `${p * 10000}`)) { 78 | for (const amount of [0, 1, 2, 3, 4].map(n => `${n}`.repeat(18 + n))) { 79 | test(invest, "0.000000000000005", supply, balance, weights, amount); 80 | } 81 | } 82 | } 83 | } 84 | 85 | function test(method, maxError, ...args) { 86 | it(`${method.name}(${args.join(", ")})`, async () => { 87 | const expected = method(...args.map(x => Decimal(x))); 88 | const actual = Decimal((await testContract[method.name](...args)).toString()); 89 | if (!actual.eq(expected)) { 90 | const error = actual.div(expected).sub(1).abs(); 91 | assert(error.lte(maxError), `error = ${error.toFixed()}`); 92 | } 93 | }); 94 | } 95 | }); 96 | -------------------------------------------------------------------------------- /project/tests/DynamicCurve.js: -------------------------------------------------------------------------------- 1 | const TestContract = artifacts.require("DynamicCurveUser"); 2 | const Decimal = require("decimal.js"); 3 | 4 | function convert(t, s, r, q, p, w1, w2) { 5 | [t, s, r, q, p, w1, w2] = [t, s, r, q, p, w1, w2].map(x => Decimal(x)); 6 | const x1 = t.sub(s); 7 | const x2 = r.mul(Decimal(1).sub(s.div(s.add(x1)).pow(w1.div(w2)))); 8 | const y1 = s.add(x1).mul(q).div(w1); 9 | const y2 = r.sub(x2).mul(p).div(w2); 10 | return [y1, y2]; 11 | } 12 | 13 | async function call(promise) { 14 | try { 15 | return await promise; 16 | } 17 | catch (error) { 18 | return null; 19 | } 20 | } 21 | 22 | describe(TestContract.contractName, () => { 23 | let testContract; 24 | 25 | before(async () => { 26 | testContract = await TestContract.new(); 27 | }); 28 | 29 | for (const t of [1, 2, 3, 4, 5]) { 30 | for (const s of [1, 2, 3, 4, 5]) { 31 | for (const r of [1, 2, 3, 4, 5]) { 32 | for (const q of [1, 2, 3, 4, 5]) { 33 | for (const p of [1, 2, 3, 4, 5]) { 34 | test("equalizeExact", t, s, r, q, p, "0.00002"); 35 | test("equalizeQuick", t, s, r, q, p, "0.00477"); 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | for (const t of [1, 2, 3, 4, 5].map(n => `${n}`.repeat(21 + (n >> 1)))) { 43 | for (const s of [1, 2, 3, 4, 5].map(n => `${n}`.repeat(21 + (n >> 1)))) { 44 | for (const r of [1, 2, 3, 4, 5].map(n => `${n}`.repeat(21 + (n >> 1)))) { 45 | for (const q of [1, 2, 3, 4, 5].map(n => `${n}`.repeat(21 + (n >> 1)))) { 46 | for (const p of [1, 2, 3, 4, 5].map(n => `${n}`.repeat(21 + (n >> 1)))) { 47 | test("equalizeExact", t, s, r, q, p, "0.04167"); 48 | test("equalizeQuick", t, s, r, q, p, "0.04167"); 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | function test(methodName, t, s, r, q, p, maxError) { 56 | it(`${methodName}(${[t, s, r, q, p]})`, async () => { 57 | const weights = await call(testContract[methodName](t, s, r, q, p)); 58 | if (weights) { 59 | const w = [0, 1].map(n => weights[n].toString()); 60 | const equities = convert(t, s, r, q, p, w[0], w[1]); 61 | const error = equities[0].div(equities[1]).sub(1).abs(); 62 | assert(error.lte(maxError), `error = ${error.toFixed()}`); 63 | } 64 | }); 65 | } 66 | }); 67 | -------------------------------------------------------------------------------- /project/tests/FractionMath.js: -------------------------------------------------------------------------------- 1 | const TestContract = artifacts.require("FractionMathUser"); 2 | const Decimal = require("decimal.js"); 3 | 4 | const MAX_EXP = Decimal(2).pow(4).sub(1); 5 | const MAX_UINT128 = Decimal(2).pow(128).sub(1); 6 | const MAX_UINT256 = Decimal(2).pow(256).sub(1); 7 | 8 | const poweredRatioExact = (n, d, exp) => [n.pow(exp), d.pow(exp)]; 9 | const poweredRatioQuick = (n, d, exp) => [n.pow(exp), d.pow(exp)]; 10 | const productRatio = (xn, yn, xd, yd) => [xn.mul(yn), xd.mul(yd)]; 11 | const reducedRatio = (n, d, max) => [n, d].map(x => x.div(Decimal.max(n, d).div(max).ceil())); 12 | const normalizedRatio = (n, d, scale) => [n, d].map(x => x.mul(scale).div(n.add(d))); 13 | 14 | const noCheck = () => (n, d) => true; 15 | const reducedRatioCheck = (max) => (n, d) => [n, d].every((x) => Decimal(x.toString()).lte(max)); 16 | const normalizedRatioCheck = (scale) => (n, d) => [n, d].reduce((sum, x) => sum.add(x.toString()), Decimal(0)).eq(scale); 17 | 18 | describe(TestContract.contractName, () => { 19 | let testContract; 20 | 21 | before(async () => { 22 | testContract = await TestContract.new(); 23 | }); 24 | 25 | for (let exp = 0; exp <= MAX_EXP; exp++) { 26 | for (let n = 0; n < 10; n++) { 27 | for (let d = 1; d <= 10; d++) { 28 | test(poweredRatioExact, noCheck, "0", n, d, exp); 29 | test(poweredRatioQuick, noCheck, "0", n, d, exp); 30 | } 31 | } 32 | } 33 | 34 | for (let exp = 0; exp <= MAX_EXP; exp++) { 35 | for (const n of [0, 1, 2, 3].map(k => MAX_UINT128.sub(k))) { 36 | for (const d of [0, 1, 2, 3].map(k => MAX_UINT128.sub(k))) { 37 | test(poweredRatioExact, noCheck, "0.00000000000000000000000000000000000000000000000000000000000000000000000000004", n.toHex(), d.toHex(), exp); 38 | test(poweredRatioQuick, noCheck, "0.00000000000000000000000000000000000000000000000000000000000000000000000000817", n.toHex(), d.toHex(), exp); 39 | } 40 | } 41 | } 42 | 43 | for (let exp = 0; exp <= MAX_EXP; exp++) { 44 | for (const n of [0, 1, 2, 3].map(k => MAX_UINT256.sub(k))) { 45 | for (const d of [0, 1, 2, 3].map(k => MAX_UINT256.sub(k))) { 46 | test(poweredRatioExact, noCheck, "0.00000000000000000000000000000000000000000000000000000000000000000000000000013", n.toHex(), d.toHex(), exp); 47 | test(poweredRatioQuick, noCheck, "0.00000000000000000000000000000000000000000000000000000000000000000000000000039", n.toHex(), d.toHex(), exp); 48 | } 49 | } 50 | } 51 | 52 | for (let exp = 0; exp <= MAX_EXP; exp++) { 53 | for (let i = 1; i <= 10; i++) { 54 | const n = MAX_UINT128.mul(i).add(1); 55 | for (let j = 1; j <= 10; j++) { 56 | const d = MAX_UINT128.mul(j).add(1); 57 | test(poweredRatioExact, noCheck, "0.00000000000000000000000000000000000000000000000000000000000000683775553857226", n.toHex(), d.toHex(), exp); 58 | test(poweredRatioQuick, noCheck, "0.00000000000000000000000115365434694928281518027476953063103593095316751502886", n.toHex(), d.toHex(), exp); 59 | } 60 | } 61 | } 62 | 63 | for (let exp = 0; exp <= MAX_EXP; exp++) { 64 | for (let i = 1; i <= 10; i++) { 65 | const n = MAX_UINT256.sub(MAX_UINT128).divToInt(i); 66 | for (let j = 1; j <= 10; j++) { 67 | const d = MAX_UINT256.sub(MAX_UINT128).divToInt(j); 68 | test(poweredRatioExact, noCheck, "0.00000000000000000000000000000000000000000000000000000000000000856782943372566", n.toHex(), d.toHex(), exp); 69 | test(poweredRatioQuick, noCheck, "0.00000000000000000000000115365434694932248811461502173402498083485327146452151", n.toHex(), d.toHex(), exp); 70 | } 71 | } 72 | } 73 | 74 | for (const xn of [MAX_UINT128, MAX_UINT256.divToInt(2), MAX_UINT256.sub(MAX_UINT128), MAX_UINT256]) { 75 | for (const yn of [MAX_UINT128, MAX_UINT256.divToInt(2), MAX_UINT256.sub(MAX_UINT128), MAX_UINT256]) { 76 | for (const xd of [MAX_UINT128, MAX_UINT256.divToInt(2), MAX_UINT256.sub(MAX_UINT128), MAX_UINT256]) { 77 | for (const yd of [MAX_UINT128, MAX_UINT256.divToInt(2), MAX_UINT256.sub(MAX_UINT128), MAX_UINT256]) { 78 | test(productRatio, noCheck, "0.000000000000000000000000000000000000006", xn.toHex(), xd.toHex(), yn.toHex(), yd.toHex()); 79 | } 80 | } 81 | } 82 | } 83 | 84 | for (const z of [Decimal("1e6"), Decimal("1e18"), Decimal("1e30"), MAX_UINT128]) { 85 | for (let n = 0; n < 10; n++) { 86 | for (let d = 1; d <= 10; d++) { 87 | test(reducedRatio , reducedRatioCheck (z), "0.0000000", n, d, z.toHex()); 88 | test(normalizedRatio, normalizedRatioCheck(z), "0.0000025", n, d, z.toHex()); 89 | } 90 | } 91 | } 92 | 93 | for (const z of [Decimal("1e6"), Decimal("1e18"), Decimal("1e30"), MAX_UINT128]) { 94 | for (let i = Decimal(1); i.lte(z); i = i.mul(10)) { 95 | const n = MAX_UINT256.divToInt(z).mul(i).add(1); 96 | for (let j = Decimal(1); j.lte(z); j = j.mul(10)) { 97 | const d = MAX_UINT256.divToInt(z).mul(j).add(1); 98 | test(reducedRatio , reducedRatioCheck (z), "0.1342746", n.toHex(), d.toHex(), z.toHex()); 99 | test(normalizedRatio, normalizedRatioCheck(z), "0.1342746", n.toHex(), d.toHex(), z.toHex()); 100 | } 101 | } 102 | } 103 | 104 | for (const z of [1023, 1024, 1025]) { 105 | for (const n of [ 106 | MAX_UINT256.div(5), 107 | MAX_UINT256.div(3), 108 | MAX_UINT256.div(2).floor(), 109 | MAX_UINT256.div(2).ceil(), 110 | MAX_UINT256.mul(2).div(3), 111 | MAX_UINT256.mul(3).div(4).floor(), 112 | MAX_UINT256.mul(3).div(4).ceil(), 113 | MAX_UINT256.sub(MAX_UINT128), 114 | MAX_UINT256.sub(1), 115 | MAX_UINT256 116 | ]) { 117 | for (const d of [MAX_UINT256.sub(1), MAX_UINT256]) { 118 | test(reducedRatio , reducedRatioCheck (z), "0.0019513", n.toHex(), d.toHex(), z); 119 | test(normalizedRatio, normalizedRatioCheck(z), "0.0035088", n.toHex(), d.toHex(), z); 120 | } 121 | } 122 | } 123 | 124 | for (const n of [1, 2, 3, 4]) { 125 | for (const d of [0, 1, 2, 3].map(k => MAX_UINT128.sub(k))) { 126 | for (const z of [0, 1, 2, 3].map(k => MAX_UINT128.sub(k))) { 127 | test(reducedRatio , reducedRatioCheck (z), "0.00000000000000000000000000000000000000881620763116715630976552402916684258367", n, d.toHex(), z.toHex()); 128 | test(normalizedRatio, normalizedRatioCheck(z), "0.00000000000000000000000000000000000002057115113939003138945288940138929936189", n, d.toHex(), z.toHex()); 129 | } 130 | } 131 | } 132 | 133 | for (const n of [1, 2, 3, 4]) { 134 | for (const d of [0, 1, 2, 3].map(k => MAX_UINT256.sub(k))) { 135 | for (const z of [0, 1, 2, 3].map(k => MAX_UINT256.sub(k))) { 136 | test(reducedRatio , reducedRatioCheck (z), "0.00000000000000000000000000000000000000000000000000000000000000000000000000003", n, d.toHex(), z.toHex()); 137 | test(normalizedRatio, normalizedRatioCheck(z), "0.00000000000000000000000000000000000000000000000000000000000000000000000000007", n, d.toHex(), z.toHex()); 138 | } 139 | } 140 | } 141 | 142 | for (const n of [0, 1, 2, 3].map(k => MAX_UINT256.sub(k))) { 143 | for (const d of [0, 1, 2, 3].map(k => MAX_UINT256.sub(k))) { 144 | for (const z of [0, 1, 2, 3].map(k => MAX_UINT128.sub(k))) { 145 | test(reducedRatio , reducedRatioCheck (z), "0.00000000000000000000000000000000000000000000000000000000000000000000000000003", n.toHex(), d.toHex(), z.toHex()); 146 | test(normalizedRatio, normalizedRatioCheck(z), "0.00000000000000000000000000000000000000587747175411143753984368268611122838918", n.toHex(), d.toHex(), z.toHex()); 147 | } 148 | } 149 | } 150 | 151 | function test(method, check, maxError, ...args) { 152 | it(`${method.name}(${args.join(", ")})`, async () => { 153 | const expected = method(...args.map(x => Decimal(x))); 154 | const actual = await testContract[method.name](...args); 155 | const x = expected[0].mul(actual[1].toString()); 156 | const y = expected[1].mul(actual[0].toString()); 157 | if (!x.eq(y)) { 158 | const error = x.sub(y).abs().div(y); 159 | assert(check(actual[0], actual[1]), "critical error"); 160 | assert(error.lte(maxError), `error = ${error.toFixed()}`); 161 | } 162 | }); 163 | } 164 | }); 165 | -------------------------------------------------------------------------------- /project/tests/IntegralMath.js: -------------------------------------------------------------------------------- 1 | const TestContract = artifacts.require("IntegralMathUser"); 2 | const Utilities = require("./helpers/Utilities.js"); 3 | const Decimal = require("decimal.js"); 4 | 5 | const pow2 = k => Decimal(2).pow(k); 6 | const pow3 = k => Decimal(3).pow(k); 7 | 8 | const MAX_UINT256 = pow2(256).sub(1); 9 | 10 | const floorLog2 = (n) => n.log(2).floor(); 11 | const floorSqrt = (n) => n.sqrt().floor(); 12 | const ceilSqrt = (n) => n.sqrt().ceil(); 13 | const floorCbrt = (n) => n.cbrt().floor(); 14 | const ceilCbrt = (n) => n.cbrt().ceil(); 15 | const roundDiv = (n, d) => n.div(d).add(0.5).floor(); 16 | const mulShrF = (x, y, s) => x.mul(y).div(pow2(s)).floor(); 17 | const mulShrC = (x, y, s) => x.mul(y).div(pow2(s)).ceil(); 18 | const mulDivF = (x, y, z) => x.mul(y).div(z).floor(); 19 | const mulDivC = (x, y, z) => x.mul(y).div(z).ceil(); 20 | const mulDivR = (x, y, z) => x.mul(y).div(z).add(0.5).floor(); 21 | const mulDivExF = (x, y, z, w) => x.mul(y).div(z.mul(w)).floor(); 22 | const mulDivExC = (x, y, z, w) => x.mul(y).div(z.mul(w)).ceil(); 23 | const minFactor = (x, y) => Decimal.max(mulDivC(x, y, MAX_UINT256), 1); 24 | 25 | describe(TestContract.contractName, () => { 26 | let testContract; 27 | 28 | before(async () => { 29 | testContract = await TestContract.new(); 30 | }); 31 | 32 | for (const method of [floorSqrt, ceilSqrt, floorCbrt, ceilCbrt]) { 33 | test(method, 0); 34 | } 35 | 36 | for (const method of [floorSqrt, ceilSqrt, floorCbrt, ceilCbrt, floorLog2]) { 37 | for (let i = 0; i < 400; i++) { 38 | for (const j of [i + 1, MAX_UINT256.sub(i).toHex()]) { 39 | test(method, j); 40 | } 41 | } 42 | } 43 | 44 | for (const method of [floorSqrt, ceilSqrt, floorCbrt, ceilCbrt, floorLog2]) { 45 | for (let i = 9; i <= 255; i++) { 46 | for (let j = -3; j <= 3; j++) { 47 | test(method, pow2(i).add(j).toHex()); 48 | } 49 | } 50 | } 51 | 52 | for (const method of [floorSqrt, ceilSqrt, floorCbrt, ceilCbrt, floorLog2]) { 53 | for (let i = 6; i <= 85; i++) { 54 | for (let j = -3; j <= 3; j++) { 55 | test(method, pow3(i).add(j).toHex()); 56 | } 57 | } 58 | } 59 | 60 | for (let i = 0; i < 10; i++) { 61 | const x = MAX_UINT256.sub(i).toHex(); 62 | for (let j = 1; j <= 10; j++) { 63 | const y = MAX_UINT256.sub(j - 1).toHex(); 64 | for (const [n, d] of [[i, j], [x, j], [i, y], [x, y]]) { 65 | test(roundDiv, n, d); 66 | } 67 | } 68 | } 69 | 70 | for (const px of [0, 64, 128, 192, 255, 256]) { 71 | for (const py of [0, 64, 128, 192, 255, 256]) { 72 | for (const ax of px < 256 ? [-1, 0, +1] : [-1]) { 73 | for (const ay of py < 256 ? [-1, 0, +1] : [-1]) { 74 | const x = pow2(px).add(ax).toHex(); 75 | const y = pow2(py).add(ay).toHex(); 76 | test(minFactor, x, y); 77 | } 78 | } 79 | } 80 | } 81 | 82 | for (const px of [64, 128, 192, 256]) { 83 | for (const py of [64, 128, 192, 256]) { 84 | for (const ax of [pow2(px >> 1), 1]) { 85 | for (const ay of [pow2(py >> 1), 1]) { 86 | const x = pow2(px).sub(ax).toHex(); 87 | const y = pow2(py).sub(ay).toHex(); 88 | test(minFactor, x, y); 89 | } 90 | } 91 | } 92 | } 93 | 94 | for (const px of [128, 192, 256]) { 95 | for (const py of [128, 192, 256]) { 96 | for (const ax of [3, 5, 7]) { 97 | for (const ay of [3, 5, 7]) { 98 | const x = pow2(px).divToInt(ax).toHex(); 99 | const y = pow2(py).divToInt(ay).toHex(); 100 | test(minFactor, x, y); 101 | } 102 | } 103 | } 104 | } 105 | 106 | for (const method of [mulShrF, mulShrC]) { 107 | for (const px of [0, 64, 128, 192, 255, 256]) { 108 | for (const py of [0, 64, 128, 192, 255, 256]) { 109 | for (const ax of px < 256 ? [-1, 0, +1] : [-1]) { 110 | for (const ay of py < 256 ? [-1, 0, +1] : [-1]) { 111 | for (const s of [0, 1, 64, 127, 128, 191, 254, 255]) { 112 | const x = pow2(px).add(ax).toHex(); 113 | const y = pow2(py).add(ay).toHex(); 114 | test(method, x, y, s); 115 | } 116 | } 117 | } 118 | } 119 | } 120 | } 121 | 122 | for (const method of [mulShrF, mulShrC]) { 123 | for (const px of [64, 128, 192, 256]) { 124 | for (const py of [64, 128, 192, 256]) { 125 | for (const ax of [pow2(px >> 1), 1]) { 126 | for (const ay of [pow2(py >> 1), 1]) { 127 | for (const s of [0, 1, 64, 127, 128, 191, 254, 255]) { 128 | const x = pow2(px).sub(ax).toHex(); 129 | const y = pow2(py).sub(ay).toHex(); 130 | test(method, x, y, s); 131 | } 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | for (const method of [mulShrF, mulShrC]) { 139 | for (const px of [128, 192, 256]) { 140 | for (const py of [128, 192, 256]) { 141 | for (const ax of [3, 5, 7]) { 142 | for (const ay of [3, 5, 7]) { 143 | for (const s of [0, 1, 64, 127, 128, 191, 254, 255]) { 144 | const x = pow2(px).divToInt(ax).toHex(); 145 | const y = pow2(py).divToInt(ay).toHex(); 146 | test(method, x, y, s); 147 | } 148 | } 149 | } 150 | } 151 | } 152 | } 153 | 154 | for (const method of [mulDivF, mulDivC, mulDivR, mulDivExF, mulDivExC]) { 155 | for (const px of [0, 64, 128, 192, 255, 256]) { 156 | for (const py of [0, 64, 128, 192, 255, 256]) { 157 | for (const pz of [1, 64, 128, 192, 255, 256]) { 158 | for (const ax of px < 256 ? [-1, 0, +1] : [-1]) { 159 | for (const ay of py < 256 ? [-1, 0, +1] : [-1]) { 160 | for (const az of pz < 256 ? [-1, 0, +1] : [-1]) { 161 | const x = pow2(px).add(ax).toHex(); 162 | const y = pow2(py).add(ay).toHex(); 163 | const z = pow2(pz).add(az).toHex(); 164 | test(method, ...[x, y, z, MAX_UINT256.toHex()].slice(0, method.length)); 165 | } 166 | } 167 | } 168 | } 169 | } 170 | } 171 | } 172 | 173 | for (const method of [mulDivF, mulDivC, mulDivR, mulDivExF, mulDivExC]) { 174 | for (const px of [64, 128, 192, 256]) { 175 | for (const py of [64, 128, 192, 256]) { 176 | for (const pz of [64, 128, 192, 256]) { 177 | for (const ax of [pow2(px >> 1), 1]) { 178 | for (const ay of [pow2(py >> 1), 1]) { 179 | for (const az of [pow2(pz >> 1), 1]) { 180 | const x = pow2(px).sub(ax).toHex(); 181 | const y = pow2(py).sub(ay).toHex(); 182 | const z = pow2(pz).sub(az).toHex(); 183 | test(method, ...[x, y, z, MAX_UINT256.toHex()].slice(0, method.length)); 184 | } 185 | } 186 | } 187 | } 188 | } 189 | } 190 | } 191 | 192 | for (const method of [mulDivF, mulDivC, mulDivR, mulDivExF, mulDivExC]) { 193 | for (const px of [128, 192, 256]) { 194 | for (const py of [128, 192, 256]) { 195 | for (const pz of [128, 192, 256]) { 196 | for (const ax of [3, 5, 7]) { 197 | for (const ay of [3, 5, 7]) { 198 | for (const az of [3, 5, 7]) { 199 | const x = pow2(px).divToInt(ax).toHex(); 200 | const y = pow2(py).divToInt(ay).toHex(); 201 | const z = pow2(pz).divToInt(az).toHex(); 202 | test(method, ...[x, y, z, MAX_UINT256.toHex()].slice(0, method.length)); 203 | } 204 | } 205 | } 206 | } 207 | } 208 | } 209 | } 210 | 211 | for (const method of [mulDivExF, mulDivExC]) { 212 | for (const pz of [128, 129, 130]) { 213 | for (const pw of [128, 129, 130]) { 214 | for (const az of [0, 1, 2]) { 215 | for (const aw of [0, 1, 2]) { 216 | const z = pow2(pz).add(az).toHex(); 217 | const w = pow2(pw).add(aw).toHex(); 218 | test(method, MAX_UINT256.toHex(), MAX_UINT256.toHex(), z, w); 219 | } 220 | } 221 | } 222 | } 223 | } 224 | 225 | for (const method of [mulDivExF, mulDivExC]) { 226 | for (const d of [1, 7, 11, 17, 23]) { 227 | for (let pw = 1; pw <= 256 - d; pw++) { 228 | for (const aw of [-1, 0, +1]) { 229 | const n = MAX_UINT256.divToInt(d).toHex(); 230 | const w = pow2(pw).add(aw).toHex(); 231 | test(method, n, n, n, w); 232 | } 233 | } 234 | } 235 | } 236 | 237 | function test(method, ...args) { 238 | it(`${method.name}(${args.join(", ")})`, async () => { 239 | const expected = method(...args.map(x => Decimal(x))); 240 | if (expected.lte(MAX_UINT256)) { 241 | const actual = await testContract[method.name](...args); 242 | assert.equal(actual.toString(), expected.toFixed()); 243 | } 244 | else { 245 | await Utilities.assertRevert(testContract[method.name](...args)); 246 | } 247 | }); 248 | } 249 | }); 250 | -------------------------------------------------------------------------------- /project/tests/helpers/Constants.js: -------------------------------------------------------------------------------- 1 | // Auto-generated via 'PrintTestConstants.py' 2 | module.exports.LOG_MAX_HI_TERM_VAL = 0; 3 | module.exports.LOG_NUM_OF_HI_TERMS = 8; 4 | module.exports.EXP_MAX_HI_TERM_VAL = 3; 5 | module.exports.EXP_NUM_OF_HI_TERMS = 6; 6 | module.exports.FIXED_1 = "0x0080000000000000000000000000000000"; 7 | module.exports.LOG_MID = "0x015bf0a8b1457695355fb8ac404e7a79e4"; 8 | module.exports.EXP_MID = "0x0400000000000000000000000000000000"; 9 | module.exports.EXP_MAX = "0x2cb53f09f05cc627c85ddebfccfeb72758"; 10 | module.exports.LAMBERT_NEG1_MAXVAL = "0x002e9e207581ee21a2fdc7e0328c907634"; 11 | module.exports.LAMBERT_NEG2_MAXVAL = "0x002f16ac6c59de6f8d5d6f63c1482a7c86"; 12 | module.exports.LAMBERT_POS1_MAXVAL = "0x002f16ac6c59de6f8d5d6f63c1482a7c86"; 13 | module.exports.LAMBERT_POS2_MAXVAL = "0x0c2f16ac6c59de6f8d5d6f63c1482a7c56"; 14 | module.exports.LAMBERT_EXACT_LIMIT = "0x001abb58194f861a372fc4a5e2a64806c77ee7849dd0a347ea7ddf2ba582711b"; 15 | -------------------------------------------------------------------------------- /project/tests/helpers/Utilities.js: -------------------------------------------------------------------------------- 1 | module.exports.assertRevert = async function (promise, reason) { 2 | try { 3 | await promise; 4 | throw null; 5 | } 6 | catch (error) { 7 | assert(error, "expected an error but did not get one"); 8 | assert.include(error.message, "revert"); 9 | if (reason) 10 | assert.include(error.message, reason); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /readme/AdvancedMath.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barakman/solidity-math-utils/89b2371d741f413c64f61324e76977bdb655f428/readme/AdvancedMath.pdf -------------------------------------------------------------------------------- /readme/AdvancedMath.txt: -------------------------------------------------------------------------------- 1 | **Library Interface:** 2 | --- 3 | 4 | This library implements two different methods for solving the equation $x\cdot\big(\frac{a}{b}\big)^x=\frac{c}{d}$: 5 | 6 | - Function `solveExact(a, b, c, d)`, which opts for accuracy over performance 7 | - Function `solveQuick(a, b, c, d)`, which opts for performance over accuracy 8 | 9 | --- 10 | 11 | The solution to: 12 | 13 | $$x\cdot\big(\frac{a}{b}\big)^x=\frac{c}{d}$$ 14 | 15 | Can be computed via: 16 | 17 | $$x=\frac{W\Big(\log\big(\frac{a}{b}\big)\cdot\frac{c}{d}\Big)}{log\big(\frac{a}{b}\big)}$$ 18 | 19 | Where $W(z)$ computes a value of $x$ which solves the equation $x\cdot{e}^x=z$. 20 | 21 | --- 22 | 23 | The function $f(x)=x\cdot{e}^x$ reaches a global minimum at $x=-1$: 24 | 25 | - $f'(x)=(x+1)\cdot{e}^x$ hence $f'(x)=0\iff{x=-1}$ 26 | - $f''(x)=(x+2)\cdot{e}^x$ hence $f''(-1)=1/e>0$ 27 | 28 | Since $f(-1)=-1/e$, the equation $x\cdot{e}^x=z$ has no solution for $z<-1/e$. 29 | 30 | In order to handle the rest of the input domain, we split it into several sections. 31 | 32 | --- 33 | 34 | **Opting For Accuracy:** 35 | --- 36 | 37 | When opting for accuracy, we split the input domain $z=\log\big(\frac{a}{b}\big)\cdot\frac{c}{d}$ into: 38 | 39 | $$\underbrace{-1/e\ldots0}\bigg|\underbrace{0\ldots\infty}$$ 40 | 41 | --- 42 | 43 | For $-1/e\leq{z}<0$, we approximate $W(z)$ via the Newton-Raphson converging sequence: 44 | 45 | $$\large\begin{cases}y_0=-z\\y_{n+1}=\frac{y_n^2+e^{y_n}\cdot{z}}{y_n-1}\end{cases}$$ 46 | 47 | --- 48 | 49 | For $0\leq{z}<\infty$, we approximate $W(z)$ via the Newton-Raphson converging sequence: 50 | 51 | $$\large\begin{cases}y_0=\small\begin{cases}z&z<1\\\log(z)&z\geq1\end{cases}\\y_{n+1}=\frac{y_n^2\cdot{e}^{y_n}+z}{(y_n+1)\cdot{e}^{y_n}}\end{cases}$$ 52 | 53 | --- 54 | 55 | **Opting For Performance:** 56 | --- 57 | 58 | When opting for performance, we split the input domain $z=\log\big(\frac{a}{b}\big)\cdot\frac{c}{d}$ into: 59 | 60 | $$\underbrace{-1/e\ldots0}\bigg|\underbrace{0\ldots1/e}\bigg|\underbrace{1/e\ldots24+1/e}\bigg|\underbrace{24+1/e\ldots\infty}$$ 61 | 62 | --- 63 | 64 | For $-1/e\leq{z}\leq+1/e$, you may observe that $W(z)=\frac{W\Big(\log\big(\frac{a}{b}\big)\cdot\frac{c}{d}\Big)}{log\big(\frac{a}{b}\big)}=\frac{c}{d}\cdot\sum\limits_{n=1}^{\infty}\frac{(-n)^{n-1}}{n!}\cdot\Big(\log\big(\frac{a}{b}\big)\cdot\frac{c}{d}\Big)^{n-1}$: 65 | 66 | - For $-1/e\leq{z}\leq0$, which implies that ${a}\leq{b}$, we compute $W(z)=\frac{c}{d}\cdot\sum\limits_{n=1}^{\infty}\frac{(+n)^{n-1}}{n!}\cdot\Big(\log\big(\frac{b}{a}\big)\cdot\frac{c}{d}\Big)^{n-1}$ 67 | - For $0\leq{z}\leq+1/e$, which implies that ${a}\geq{b}$, we compute $W(z)=\frac{c}{d}\cdot\sum\limits_{n=1}^{\infty}\frac{(-n)^{n-1}}{n!}\cdot\Big(\log\big(\frac{a}{b}\big)\cdot\frac{c}{d}\Big)^{n-1}$ 68 | 69 | As you can see, when $a=b$, both formulas can be reduced to $W(z)=\frac{c}{d}$. 70 | 71 | Note that as the value of $z$ approaches $-1/e$, the value of $W(z)$ approaches $-1$ but it never actually reaches there. 72 | 73 | The function becomes increasingly steep, making its approximation via the Taylor series above increasingly inaccurate. 74 | 75 | Hence for the smallest $\%1$ of the range $-1/e\leq{z}\leq0$: 76 | 77 | - We precalculate a lookup table which maps $16$ uniformly distributed values of $z$ to values of $W(z)$ 78 | - During runtime, we calculate $W(z)$ as the weighted-average of $W(z_0)$ and $W(z_1)$, where $z_0\leq{z}s$. Our goal is to set the weights of the pool, such that the arbitrage incentive of equalizing the on-chain 46 | price and the off-chain price will subsequently increase $s$ to become equal to $t$. In other words, we want the arbitrager to 47 | transfer $t−s$ units of F to the pool, in exchange for units of G. 48 | 49 | Suppose that we’ve set the weights: 50 | - $w_1=$ F reserve weight 51 | - $w_2=$ G reserve weight 52 | 53 | Then: 54 | 55 | - A user converting $t−s$ units of F will get $r\cdot\Big(1−\big(\frac{s}{t}\big)^{w_1/w_2}\Big)$ units of G 56 | - F reserve balance after the arbitrage conversion will be $t$ of course 57 | - G reserve balance after the arbitrage conversion will be $r-r\cdot\Big(1−\big(\frac{s}{t}\big)^{w_1/w_2}\Big)$ 58 | - F/G on-chain price after the arbitrage conversion will be $\large\frac{t\cdot{w_2}/{w_1}}{r-r\cdot\Big(1−\big(\frac{s}{t}\big)^{w_1/w_2}\Big)}$ 59 | - F/G off-chain price is of course $\frac{p}{q}$ (or $\frac{q}{p}$ if the inverse rates are provided) 60 | 61 | When either $t$ or $p/q$ change, we want to recalculate $w_1$ and $w_2$ such that the arbitrage incentive of making the on-chain price 62 | equal to the off-chain price will be equivalent to converting $t−s$ units of F to units of G, thus increasing F reserve balance ($s$) 63 | to be equal to F staked balance ($t$). 64 | 65 | In other words, we want to recalculate $w_1$ and $w_2$ such that $\large\frac{t\cdot{w_2}/{w_1}}{r-r\cdot\Big(1−\big(\frac{s}{t}\big)^{w_1/w_2}\Big)}=\frac{p}{q}$. 66 | 67 | Let $x$ denote $w_1/w_2$, then: 68 | 69 | $\large\frac{t/x}{r-r\cdot\Big(1−\big(\frac{s}{t}\big)^{x}\Big)}=\frac{p}{q}\rightarrow$ 70 | 71 | $\large\frac{t/x}{r\cdot\big(\frac{s}{t}\big)^{x}}=\frac{p}{q}\rightarrow$ 72 | 73 | $\large{x}\cdot\big(\frac{s}{t}\big)^{x}=\frac{tq}{rp}\rightarrow$ 74 | 75 | $\large{x}=\frac{W\Big(\log\big(\frac{s}{t}\big)\cdot\frac{tq}{rp}\Big)}{log\big(\frac{s}{t}\big)}$, where W is the Lambert W Function. 76 | 77 | After computing $x$, we can represent it as a quotient of integers, i.e., $x=a/b$. 78 | 79 | Then, since $x=w_1/w_2$ and $w_2=1-w_1$, we can calculate: 80 | 81 | - $\large{w_1}=\frac{x}{x+1}=\frac{a/b}{1+a/b}=\frac{a}{a+b}$ 82 | - $\large{w_2}=\frac{1}{x+1}=\frac{1}{1+a/b}=\frac{b}{a+b}$ 83 | --------------------------------------------------------------------------------