├── .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 |
--------------------------------------------------------------------------------