Skip to content

Latest commit

 

History

History
386 lines (237 loc) · 15.9 KB

README.md

File metadata and controls

386 lines (237 loc) · 15.9 KB

Abstract

This package consists of the following modules:

  • IntegralMath - a set of functions, each of which returning an integer result
  • FractionMath - a set of functions, each of which returning a rational result
  • AnalyticMath - a set of functions for exponential and logarithmic operations
  • AdvancedMath - a set of functions for solving equations of the form xA^x = B
  • BondingCurve - a set of functions implementing the bonding-curve mechanism
  • DynamicCurve - a set of functions for equalizing the weights in a bonding-curve model

Class Hierarchy

IntegralMath < - - - - FractionMath
      ∧                      ∧
      |                      |
      |                      |
      |                      |
AnalyticMath < - - - - AdvancedMath
      ∧                      ∧
      |                      |
      |                      |
      |                      |
BondingCurve           DynamicCurve






IntegralMath

This module implements the following interface:

  • function floorLog2(uint256 n) => (uint8)
  • function floorSqrt(uint256 n) => (uint256)
  • function ceilSqrt(uint256 n) => (uint256)
  • function floorCbrt(uint256 n) => (uint256)
  • function ceilCbrt(uint256 n) => (uint256)
  • function roundDiv(uint256 n, uint256 d) => (uint256)
  • function mulDivF(uint256 x, uint256 y, uint256 z) => (uint256)
  • function mulDivC(uint256 x, uint256 y, uint256 z) => (uint256)
  • function mulDivR(uint256 x, uint256 y, uint256 z) => (uint256)
  • function mulDivExF(uint256 x, uint256 y, uint256 z, uint256 w) => (uint256)
  • function mulDivExC(uint256 x, uint256 y, uint256 z, uint256 w) => (uint256)

Function floorLog2(n) computes the largest integer smaller than or equal to the binary logarithm of n.

Function floorSqrt(n) computes the largest integer smaller than or equal to the square root of n.

Function ceilSqrt(n) computes the smallest integer larger than or equal to the square root of n.

Function floorCbrt(n) computes the largest integer smaller than or equal to the cubic root of n.

Function ceilCbrt(n) computes the smallest integer larger than or equal to the cubic root of n.

Function roundDiv(n, d) computes the nearest integer to the quotient of n and d (or n / d).

Function minFactor(x, y) computes the smallest integer z such that x * y / z <= 2 ^ 256 - 1.

Function mulDivF(x, y, z) computes the largest integer smaller than or equal to x * y / z.

Function mulDivC(x, y, z) computes the smallest integer larger than or equal to x * y / z.

Function mulDivR(x, y, z) computes the nearest integer smaller than or larger than x * y / z.

Function mulDivExF(x, y, z, w) computes the largest integer smaller than or equal to (x * y) / (z * w).

Function mulDivExC(x, y, z, w) computes the smallest integer larger than or equal to (x * y) / (z * w).

Note that each one of the 'mulDiv' functions reverts when the actual result is larger than 256 bits.

Note that function floorSqrt and function ceilSqrt are guaranteed to return the correct output for every input.

However, when compared with the actual square root, smaller input generally yields relatively lower accuracy of the output.

For example, floorSqrt(3) returns 1, but the actual square root of 3 is ~1.73, which yields a relative accuracy of only ~57%.

Note that function floorCbrt and function ceilCbrt are guaranteed to return the correct output for every input.

However, when compared with the actual cubic root, smaller input generally yields relatively lower accuracy of the output.

For example, floorCbrt(7) returns 1, but the actual cubic root of 7 is ~1.91, which yields a relative accuracy of only ~52%.






FractionMath

This module implements the following interface:

  • function poweredRatioExact(uint256 n, uint256 d, uint256 exp) => (uint256, uint256)
  • function poweredRatioQuick(uint256 n, uint256 d, uint256 exp) => (uint256, uint256)
  • function productRatio(uint256 xn, uint256 yn, uint256 xd, uint256 yd) => (uint256, uint256)
  • function reducedRatio(uint256 n, uint256 d, uint256 max) => (uint256, uint256)
  • function normalizedRatio(uint256 n, uint256 d, uint256 scale) => (uint256, uint256)

Powered Ratio

Function poweredRatioExact opts for accuracy and function poweredRatioQuick opts for performance.

Each one of the two 'poweredRatio' functions computes the power of a given ratio by a given exponent.

In order to avoid multiplication overflow, it may truncate the intermediate result on each iteration.

Subsequently, the larger the input exponent is, the lower the accuracy of the output is likely to be.

This module defines a maximum exponent of 4 bits (i.e., 15), which can be customized to fit the system requirements.

Product Ratio

Function productRatio computes the product of two ratios as a single ratio whose components are not larger than 256 bits.

If either one of the intermediate components is larger than 256 bits, then both of them are reduced based on the larger one.

Reduced Ratio

Function reducedRatio computes the nearest ratio whose components (numerator and denominator) fit up to a given threshold.

Note that function reducedRatio is not meant to replace GCD, nor does it strive to achieve better accuracy.

GCD is not being used here, because the time-complexity of this method depends on the bit-length of the input.

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.

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.

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.

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.

Normalized Ratio

Function normalizedRatio computes the nearest ratio whose components (numerator and denominator) sum up to a given scale.

Note that the output ratio can be larger than the input ratio in some cases, and smaller than the input ratio in other cases.

For example:

  • normalizedRatio(12, 34, 100) returns (26, 74); the output ratio is smaller than the input ratio (26 / 74 = 0.351 < 0.352 = 12 / 34)
  • normalizedRatio(1234, 5678, 100) returns (18, 82); the output ratio is larger than the input ratio (18 / 82 = 0.219 > 0.217 = 1234 / 5678)

Keep in mind that it is an important consideration to take when choosing to use this function.

For example, when designing a sustainable financial model, it is imperative to never entitle more than the actual entitlement.

The same consideration applies for all the other functions in this module.






AnalyticMath

This module implements the following interface:

  • function pow(uint256 a, uint256 b, uint256 c, uint256 d) => (uint256, uint256)
  • function log(uint256 a, uint256 b) => (uint256, uint256)
  • function exp(uint256 a, uint256 b) => (uint256, uint256)

Exponentiation

Function pow(a, b, c, d) approximates the power of a / b by c / d.

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).

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).

Natural Logarithm

Function log(a, b) approximates the natural logarithm of a / b.

The output of this function is guaranteed to be smaller than or equal to the actual value of log(a / b).

It does not support a < b, because it relies on unsigned-integer arithmetic, and the output for such input would be negative.

Natural Exponentiation

Function exp(a, b) approximates the natural exponentiation of a / b.

The output of this function is guaranteed to be smaller than or equal to the actual value of exp(a / b).

Module Customization

This module can be customized to support different input ranges (as a tradeoff with accuracy and/or performance).

The full customization manual can be found here.






AdvancedMath

This module implements the following interface:

  • function solveExact(uint256 a, uint256 b, uint256 c, uint256 d) => (uint256, uint256)
  • function solveQuick(uint256 a, uint256 b, uint256 c, uint256 d) => (uint256, uint256)

Equation Solving

Function solveExact opts for accuracy and function solveQuick opts for performance.

Each one of these functions computes a value of x which satisfies the equation x * (a / b) ^ x = c / d.

A detailed description of how each one of these functions works can be found here.

Module Customization

This module can be customized to support different input ranges (as a tradeoff with accuracy and/or performance).

The full customization manual can be found here.






BondingCurve

This module implements the following interface:

  • function buy(uint256 supply, uint256 balance, uint256 weight, uint256 amount) => (uint256)
  • function sell(uint256 supply, uint256 balance, uint256 weight, uint256 amount) => (uint256)
  • function convert(uint256 balance1, uint256 weight1, uint256 balance2, uint256 weight2, uint256 amount) => (uint256)
  • function deposit(uint256 supply, uint256 balance, uint256 weights, uint256 amount) => (uint256)
  • function withdraw(uint256 supply, uint256 balance, uint256 weights, uint256 amount) => (uint256)
  • function invest(uint256 supply, uint256 balance, uint256 weights, uint256 amount) => (uint256)

Functionality

|----------------------------|--------------------------------------------------|---------------------------------------------|
| Function                   | Compute the return of                            | Formula                                     |
|----------------------------|--------------------------------------------------|---------------------------------------------|
| buy(s, b, w, x)            | buying pool tokens with reserve tokens           | s * ((1 + x / b) ^ (w / MAX_WEIGHT) - 1)    |
| sell(s, b, w, x)           | selling pool tokens for reserve tokens           | b * (1 - (1 - x / s) ^ (MAX_WEIGHT / w))    |
| convert(b1, w1, b2, w2, x) | converting reserve tokens of one type to another | b2 * (1 - (b1 / (b1 + x)) ^ (w1 / w2))      |
| deposit(s, b, ws, x)       | depositing reserve tokens for pool tokens        | s * ((x / b + 1) ^ (ws / MAX_WEIGHT) - 1)   |
| withdraw(s, b, ws, x)      | withdrawing reserve tokens with pool tokens      | b * (1 - ((s - x) / s) ^ (MAX_WEIGHT / ws)) |
| invest(s, b, ws, x)        | investing reserve tokens for pool tokens         | b * (((s + x) / s) ^ (MAX_WEIGHT / ws) - 1) |
|----------------------------|--------------------------------------------------|---------------------------------------------|

The bonding-curve model was conceived by Bancor.






DynamicCurve

This module implements the following interface:

  • function equalizeExact(uint256 t, uint256 s, uint256 r, uint256 q, uint256 p) => (uint256, uint256)
  • function equalizeQuick(uint256 t, uint256 s, uint256 r, uint256 q, uint256 p) => (uint256, uint256)

Function equalizeExact opts for accuracy and function equalizeQuick opts for performance.

Equalization

Consider a pool which implements the bonding-curve model over a primary reserve token and a secondary reserve token.

Let 'on-chain price' denote the conversion rate between these tokens inside the pool (i.e., as determined by the pool).

Let 'off-chain price' denote the conversion rate between these tokens outside the pool (i.e., as determined by the market).

The arbitrage incentive is always to convert to the point where the on-chain price is equal to the off-chain price.

We want this operation to also impact the primary reserve balance becoming equal to the primary reserve staked balance.

In other words, we want the arbitrager to convert the difference between the reserve balance and the reserve staked balance.

Hence we adjust the weights in order to create an arbitrage incentive which, when realized, will subsequently equalize the pool.

Input:

  • Let t denote the primary reserve token staked balance
  • Let s denote the primary reserve token balance
  • Let r denote the secondary reserve token balance
  • Let q denote the numerator of the off-chain price
  • Let p denote the denominator of the off-chain price

Where p primary tokens are equal to q secondary tokens

Output:

  • Solve the equation x * (s / t) ^ x = (t / r) * (q / p)
  • Return x / (x + 1) as the weight of the primary reserve token
  • Return 1 / (x + 1) as the weight of the secondary reserve token

A detailed reasoning of this method can be found here.

If the rate-provider provides the rates for a common unit, for example:

  • P = 2 ==> 2 primary reserve tokens = 1 ether
  • Q = 3 ==> 3 secondary reserve tokens = 1 ether

Then you can simply use p = P and q = Q

If the rate-provider provides the rates for a single unit, for example:

  • P = 2 ==> 1 primary reserve token = 2 ethers
  • Q = 3 ==> 1 secondary reserve token = 3 ethers

Then you can simply use p = Q and q = P

The dynamic-curve method was conceived by Bancor.






Testing

Prerequisites

  • node 20.17.0
  • yarn 1.22.22 or npm 10.8.2

Installation

  • yarn install or npm install

Compilation

  • yarn build or npm run build

Execution

  • yarn test or npm run test

Verification

  • yarn verify or npm run verify






Emulation

Prerequisites

  • python 3.12.3

Execution

In order to allow rapid testing and verification, all modules have been ported from Solidity to Python:

  • The emulation modules themselves are located under FixedPoint
  • The corresponding floating-point functionality is located under FloatPoint
  • A set of unit-tests for various functions (one per function) is located under emulation






Customization

All customization parameters are located in constants.py.

When modifying any of them, one should regenerate all the code.

The following scripts generate the code for AnalyticMath.sol:

The following scripts generate the code for AdvancedMath.sol:

In order to retain the testing infrastructure, one should proceed by:

In order to retain the emulation infrastructure, one should proceed by: