UNPKG

@hikaru-fi/sc-calculators

Version:

Package for pool calculations

615 lines (496 loc) 25.8 kB
import web3Utils from 'web3-utils'; const BZERO = web3Utils.toBN(0); const BONE = web3Utils.toBN(1); const _require = (b, message) => { if (!b) throw new Error(message); }; export class MathSol { /** * @dev Returns the addition of two unsigned integers of 256 bits, reverting on overflow. */ // add(a, b) { // const c = a + b; // // _require(c >= a, Errors.ADD_OVERFLOW); // return c; // } /** * @dev Returns the addition of two signed integers, reverting on overflow. */ static add(a, b) { const c = a.add(b); _require((b.gte(BZERO) && c.gte(a)) || (b.lt(BZERO) && c.lt(a)), 'Errors.ADD_OVERFLOW'); return c; } /** * @dev Returns the subtraction of two unsigned integers of 256 bits, reverting on overflow. */ static sub(a, b) { _require(b.lte(a), 'Errors.SUB_OVERFLOW'); const c = a.sub(b); return c; } /** * @dev Returns the subtraction of two signed integers, reverting on overflow. */ // sub(int256 a, int256 b) internal pure returns (int256) { // int256 c = a - b; // // _require((b >= 0 && c <= a) || (b < 0 && c > a), Errors.SUB_OVERFLOW); // return c; // } /** * @dev Returns the largest of two numbers of 256 bits. */ static max(a, b) { return a.gte(b) ? a : b; } /** * @dev Returns the smallest of two numbers of 256 bits. */ static min(a, b) { return a.lt(b) ? a : b; } static mul(a, b) { const c = a.mul(b); _require(a.eq(BZERO) || (c.div(a)).eq(b), 'Errors.MUL_OVERFLOW'); return c; } static div(a, b, roundUp) { return roundUp ? this.divUp(a, b) : this.divDown(a, b); } static divDown(a, b) { _require(!b.eq(BZERO), 'Errors.ZERO_DIVISION'); return a.div(b); } static divUp(a, b) { _require(!b.eq(BZERO), 'Errors.ZERO_DIVISION'); if (a.eq(BZERO)) { return BZERO; } else { return BONE.add((a.sub(BONE)).div(b)); } } // Modification: Taken from the fixed point class static ONE = web3Utils.toBN('1000000000000000000'); // 18 decimal places static MAX_POW_RELATIVE_ERROR = web3Utils.toBN(10000); static mulUpFixed(a, b) { const product = a.mul(b); _require(a.eq(BZERO) || (product.div(a)).eq(b), 'Errors.MUL_OVERFLOW'); if (product == BZERO) { return BZERO; } else { // The traditional divUp formula is: // divUp(x, y) := (x + y - 1) / y // To avoid intermediate overflow in the addition, we distribute the division and get: // divUp(x, y) := (x - 1) / y + 1 // Note that this requires x != 0, which we already tested for. return (product.sub(BONE)).div(this.ONE).add(BONE); } } // Modification: Taken from the fixed point class static divDownFixed(a, b) { _require(!b.eq(BZERO), 'Errors.ZERO_DIVISION'); if (a.eq(BZERO)) { return BZERO; } else { const aInflated = a.mul(this.ONE); // _require(aInflated / a == ONE, Errors.DIV_INTERNAL); // mul overflow return aInflated.div(b); } } // Modification: Taken from the fixed point class static divUpFixed(a, b) { _require(!b.eq(BZERO), 'Errors.ZERO_DIVISION'); if (a.eq(BZERO)) { return BZERO; } else { const aInflated = a.mul(this.ONE); _require((aInflated.div(a)).eq(this.ONE), 'Errors.DIV_INTERNAL'); // mul overflow // The traditional divUp formula is: // divUp(x, y) := (x + y - 1) / y // To avoid intermediate overflow in the addition, we distribute the division and get: // divUp(x, y) := (x - 1) / y + 1 // Note that this requires x != 0, which we already tested for. return (aInflated.sub(BONE)).div(b).add(BONE); } } // Modification: Taken from the fixed point class static powUpFixed(x, y) { const raw = LogExpMath.pow(x, y); const maxError = this.add( this.mulUpFixed(raw, this.MAX_POW_RELATIVE_ERROR), BONE ); return this.add(raw, maxError); } // Modification: Taken from the fixed point class static complementFixed(x) { return x.lt(this.ONE) ? this.ONE.sub(x) : BZERO; } static mulDownFixed(a, b) { const product = a.mul(b); _require(a.eq(BZERO) || (product.div(a)).eq(b), 'Errors.MUL_OVERFLOW'); return product.div(this.ONE); } } export class LogExpMath { // All fixed point multiplications and divisions are inlined. This means we need to divide by ONE when multiplying // two numbers, and multiply by ONE when dividing them. // All arguments and return values are 18 decimal fixed point numbers. static ONE_18 = web3Utils.toBN('1000000000000000000'); // Internally, intermediate values are computed with higher precision as 20 decimal fixed point numbers, and in the // case of ln36, 36 decimals. static ONE_20 = web3Utils.toBN('100000000000000000000'); static ONE_36 = web3Utils.toBN('1000000000000000000000000000000000000'); // The domain of natural exponentiation is bound by the word size and number of decimals used. // // Because internally the result will be stored using 20 decimals, the largest possible result is // (2^255 - 1) / 10^20, which makes the largest exponent ln((2^255 - 1) / 10^20) = 130.700829182905140221. // The smallest possible result is 10^(-18), which makes largest negative argument // ln(10^(-18)) = -41.446531673892822312. // We use 130.0 and -41.0 to have some safety margin. static MAX_NATURAL_EXPONENT = web3Utils.toBN('130000000000000000000'); static MIN_NATURAL_EXPONENT = web3Utils.toBN('-41000000000000000000'); // Bounds for ln_36's argument. Both ln(0.9) and ln(1.1) can be represented with 36 decimal places in a fixed point // 256 bit integer. static LN_36_LOWER_BOUND = (LogExpMath.ONE_18).sub(web3Utils.toBN('100000000000000000')); static LN_36_UPPER_BOUND = (LogExpMath.ONE_18).add(web3Utils.toBN('100000000000000000')); static MILD_EXPONENT_BOUND = (web3Utils.toBN(2)).pow(web3Utils.toBN(254)).div(LogExpMath.ONE_20); // 18 decimal constants static x0 = web3Utils.toBN('128000000000000000000'); // 2ˆ7 static a0 = web3Utils.toBN( '38877084059945950922200000000000000000000000000000000000' ); // eˆ(x0) (no decimals) static x1 = web3Utils.toBN('64000000000000000000'); // 2ˆ6 static a1 = web3Utils.toBN('6235149080811616882910000000'); // eˆ(x1) (no decimals) // 20 decimal constants static x2 = web3Utils.toBN('3200000000000000000000'); // 2ˆ5 static a2 = web3Utils.toBN('7896296018268069516100000000000000'); // eˆ(x2) static x3 = web3Utils.toBN('1600000000000000000000'); // 2ˆ4 static a3 = web3Utils.toBN('888611052050787263676000000'); // eˆ(x3) static x4 = web3Utils.toBN('800000000000000000000'); // 2ˆ3 static a4 = web3Utils.toBN('298095798704172827474000'); // eˆ(x4) static x5 = web3Utils.toBN('400000000000000000000'); // 2ˆ2 static a5 = web3Utils.toBN('5459815003314423907810'); // eˆ(x5) static x6 = web3Utils.toBN('200000000000000000000'); // 2ˆ1 static a6 = web3Utils.toBN('738905609893065022723'); // eˆ(x6) static x7 = web3Utils.toBN('100000000000000000000'); // 2ˆ0 static a7 = web3Utils.toBN('271828182845904523536'); // eˆ(x7) static x8 = web3Utils.toBN('50000000000000000000'); // 2ˆ-1 static a8 = web3Utils.toBN('164872127070012814685'); // eˆ(x8) static x9 = web3Utils.toBN('25000000000000000000'); // 2ˆ-2 static a9 = web3Utils.toBN('128402541668774148407'); // eˆ(x9) static x10 = web3Utils.toBN('12500000000000000000'); // 2ˆ-3 static a10 = web3Utils.toBN('113314845306682631683'); // eˆ(x10) static x11 = web3Utils.toBN('6250000000000000000'); // 2ˆ-4 static a11 = web3Utils.toBN('106449445891785942956'); // eˆ(x11) // All arguments and return values are 18 decimal fixed point numbers. static pow(x, y) { if (y === BZERO) { // We solve the 0^0 indetermination by making it equal one. return this.ONE_18; } if (x == BZERO) { return BZERO; } // Instead of computing x^y directly, we instead rely on the properties of logarithms and exponentiation to // arrive at that result. In particular, exp(ln(x)) = x, and ln(x^y) = y * ln(x). This means // x^y = exp(y * ln(x)). // The ln function takes a signed value, so we need to make sure x fits in the signed 256 bit range. _require( x.lt( web3Utils.toBN( '57896044618658097711785492504343953926634992332820282019728792003956564819968' ) ), 'Errors.X_OUT_OF_BOUNDS' ); const x_int256 = x; // We will compute y * ln(x) in a single step. Depending on the value of x, we can either use ln or ln_36. In // both cases, we leave the division by ONE_18 (due to fixed point multiplication) to the end. // This prevents y * ln(x) from overflowing, and at the same time guarantees y fits in the signed 256 bit range. _require(y.lt(this.MILD_EXPONENT_BOUND), 'Errors.Y_OUT_OF_BOUNDS'); const y_int256 = y; let logx_times_y; if ( this.LN_36_LOWER_BOUND.lt(x_int256) && x_int256.lt(this.LN_36_UPPER_BOUND) ) { const ln_36_x = this._ln_36(x_int256); // ln_36_x has 36 decimal places, so multiplying by y_int256 isn't as straightforward, since we can't just // bring y_int256 to 36 decimal places, as it might overflow. Instead, we perform two 18 decimal // multiplications and add the results: one with the first 18 decimals of ln_36_x, and one with the // (downscaled) last 18 decimals. logx_times_y = (ln_36_x.div(this.ONE_18)).mul(y_int256).add( ((ln_36_x.mod(this.ONE_18)).mul(y_int256)).div(this.ONE_18) ); } else { logx_times_y = this._ln(x_int256).mul(y_int256); } logx_times_y = logx_times_y.div(this.ONE_18); // Finally, we compute exp(y * ln(x)) to arrive at x^y _require( this.MIN_NATURAL_EXPONENT.lte(logx_times_y) && logx_times_y.lte(this.MAX_NATURAL_EXPONENT), 'Errors.PRODUCT_OUT_OF_BOUNDS' ); // return uint256(exp(logx_times_y)); return this.exp(logx_times_y); } static exp(x) { _require( x.gte(this.MIN_NATURAL_EXPONENT) && x.lte(this.MAX_NATURAL_EXPONENT), 'Errors.INVALID_EXPONENT' ); if (x.lt(BZERO)) { // We only handle positive exponents: e^(-x) is computed as 1 / e^x. We can safely make x positive since it // fits in the signed 256 bit range (as it is larger than MIN_NATURAL_EXPONENT). // Fixed point division requires multiplying by ONE_18. return (this.ONE_18.mul(this.ONE_18)).div(this.exp((web3Utils.toBN(-1)).mul(x))); } // First, we use the fact that e^(x+y) = e^x * e^y to decompose x into a sum of powers of two, which we call x_n, // where x_n == 2^(7 - n), and e^x_n = a_n has been precomputed. We choose the first x_n, x0, to equal 2^7 // because all larger powers are larger than MAX_NATURAL_EXPONENT, and therefore not present in the // decomposition. // At the end of this process we will have the product of all e^x_n = a_n that apply, and the remainder of this // decomposition, which will be lower than the smallest x_n. // exp(x) = k_0 * a_0 * k_1 * a_1 * ... + k_n * a_n * exp(remainder), where each k_n equals either 0 or 1. // We mutate x by subtracting x_n, making it the remainder of the decomposition. // The first two a_n (e^(2^7) and e^(2^6)) are too large if stored as 18 decimal numbers, and could cause // intermediate overflows. Instead we store them as plain integers, with 0 decimals. // Additionally, x0 + x1 is larger than MAX_NATURAL_EXPONENT, which means they will not both be present in the // decomposition. // For each x_n, we test if that term is present in the decomposition (if x is larger than it), and if so deduct // it and compute the accumulated product. let firstAN; if (x.gte(this.x0)) { x = x.sub(this.x0); firstAN = this.a0; } else if (x.gte(this.x1)) { x = x.sub(this.x1); firstAN = this.a1; } else { firstAN = web3Utils.toBN(1); // One with no decimal places } // We now transform x into a 20 decimal fixed point number, to have enhanced precision when computing the // smaller terms. x = x.mul(web3Utils.toBN(100)); // `product` is the accumulated product of all a_n (except a0 and a1), which starts at 20 decimal fixed point // one. Recall that fixed point multiplication requires dividing by ONE_20. let product = this.ONE_20; if (x.gte(this.x2)) { x = x.sub(this.x2); product = (product.mul(this.a2)).div(this.ONE_20); } if (x.gte(this.x3)) { x = x.sub(this.x3); product = (product.mul(this.a3)).div(this.ONE_20); } if (x.gte(this.x4)) { x = x.sub(this.x4); product = (product.mul(this.a4)).div(this.ONE_20); } if (x.gte(this.x5)) { x = x.sub(this.x5); product = (product.mul(this.a5)).div(this.ONE_20); } if (x.gte(this.x6)) { x = x.sub(this.x6); product = (product.mul(this.a6)).div(this.ONE_20); } if (x.gte(this.x7)) { x = x.sub(this.x7); product = (product.mul(this.a7)).div(this.ONE_20); } if (x.gte(this.x8)) { x = x.sub(this.x8); product = (product.mul(this.a8)).div(this.ONE_20); } if (x.gte(this.x9)) { x = x.sub(this.x9); product = (product.mul(this.a9)).div(this.ONE_20); } // x10 and x11 are unnecessary here since we have high enough precision already. // Now we need to compute e^x, where x is small (in particular, it is smaller than x9). We use the Taylor series // expansion for e^x: 1 + x + (x^2 / 2!) + (x^3 / 3!) + ... + (x^n / n!). let seriesSum = this.ONE_20; // The initial one in the sum, with 20 decimal places. let term; // Each term in the sum, where the nth term is (x^n / n!). // The first term is simply x. term = x; seriesSum = seriesSum.add(term); // Each term (x^n / n!) equals the previous one times x, divided by n. Since x is a fixed point number, // multiplying by it requires dividing by this.ONE_20, but dividing by the non-fixed point n values does not. term = (term.mul(x)).div(this.ONE_20).div(web3Utils.toBN(2)); seriesSum = seriesSum.add(term); term = (term.mul(x)).div(this.ONE_20).div(web3Utils.toBN(3)); seriesSum = seriesSum.add(term); term = (term.mul(x)).div(this.ONE_20).div(web3Utils.toBN(4)); seriesSum = seriesSum.add(term); term = (term.mul(x)).div(this.ONE_20).div(web3Utils.toBN(5)); seriesSum = seriesSum.add(term); term = (term.mul(x)).div(this.ONE_20).div(web3Utils.toBN(6)); seriesSum = seriesSum.add(term); term = (term.mul(x)).div(this.ONE_20).div(web3Utils.toBN(7)); seriesSum = seriesSum.add(term); term = (term.mul(x)).div(this.ONE_20).div(web3Utils.toBN(8)); seriesSum = seriesSum.add(term); term = (term.mul(x)).div(this.ONE_20).div(web3Utils.toBN(9)); seriesSum = seriesSum.add(term); term = (term.mul(x)).div(this.ONE_20).div(web3Utils.toBN(10)); seriesSum = seriesSum.add(term); term = (term.mul(x)).div(this.ONE_20).div(web3Utils.toBN(11)); seriesSum = seriesSum.add(term); term = (term.mul(x)).div(this.ONE_20).div(web3Utils.toBN(12)); seriesSum = seriesSum.add(term); // 12 Taylor terms are sufficient for 18 decimal precision. // We now have the first a_n (with no decimals), and the product of all other a_n present, and the Taylor // approximation of the exponentiation of the remainder (both with 20 decimals). All that remains is to multiply // all three (one 20 decimal fixed point multiplication, dividing by this.ONE_20, and one integer multiplication), // and then drop two digits to return an 18 decimal value. return (((product.mul(seriesSum)).div(this.ONE_20)).mul(firstAN)).div(web3Utils.toBN(100)); } static _ln_36(x) { // Since ln(1) = 0, a value of x close to one will yield a very small result, which makes using 36 digits // worthwhile. // First, we transform x to a 36 digit fixed point value. x = x.mul(this.ONE_18); // We will use the following Taylor expansion, which converges very rapidly. Let z = (x - 1) / (x + 1). // ln(x) = 2 * (z + z^3 / 3 + z^5 / 5 + z^7 / 7 + ... + z^(2 * n + 1) / (2 * n + 1)) // Recall that 36 digit fixed point division requires multiplying by ONE_36, and multiplication requires // division by ONE_36. const z = ((x.sub(this.ONE_36)).mul(this.ONE_36)).div(x.add(this.ONE_36)); const z_squared = (z.mul(z)).div(this.ONE_36); // num is the numerator of the series: the z^(2 * n + 1) term let num = z; // seriesSum holds the accumulated sum of each term in the series, starting with the initial z let seriesSum = num; // In each step, the numerator is multiplied by z^2 num = (num.mul(z_squared)).div(this.ONE_36); seriesSum = seriesSum.add(num.div(web3Utils.toBN(3))); num = (num.mul(z_squared)).div(this.ONE_36); seriesSum = seriesSum.add(num.div(web3Utils.toBN(5))); num = (num.mul(z_squared)).div(this.ONE_36); seriesSum = seriesSum.add(num.div(web3Utils.toBN(7))); num = (num.mul(z_squared)).div(this.ONE_36); seriesSum = seriesSum.add(num.div(web3Utils.toBN(9))); num = (num.mul(z_squared)).div(this.ONE_36); seriesSum = seriesSum.add(num.div(web3Utils.toBN(11))); num = (num.mul(z_squared)).div(this.ONE_36); seriesSum = seriesSum.add(num.div(web3Utils.toBN(13))); num = (num.mul(z_squared)).div(this.ONE_36); seriesSum = seriesSum.add(num.div(web3Utils.toBN(15))); // 8 Taylor terms are sufficient for 36 decimal precision. // All that remains is multiplying by 2 (non fixed point). return seriesSum.mul(web3Utils.toBN(2)); } /** * @dev Internal natural logarithm (ln(a)) with signed 18 decimal fixed point argument. */ static _ln(a) { if (a.lt(this.ONE_18)) { // Since ln(a^k) = k * ln(a), we can compute ln(a) as ln(a) = ln((1/a)^(-1)) = - ln((1/a)). If a is less // than one, 1/a will be greater than one, and this if statement will not be entered in the recursive call. // Fixed point division requires multiplying by this.ONE_18. return web3Utils.toBN(-1).mul(this._ln((this.ONE_18.mul(this.ONE_18)).div(a))); } // First, we use the fact that ln^(a.mul(b)) = ln(a) + ln(b) to decompose ln(a) into a sum of powers of two, which // we call x_n, where x_n == 2^(7 - n), which are the natural logarithm of precomputed quantities a_n (that is, // ln(a_n) = x_n). We choose the first x_n, x0, to equal 2^7 because the exponential of all larger powers cannot // be represented as 18 fixed point decimal numbers in 256 bits, and are therefore larger than a. // At the end of this process we will have the sum of all x_n = ln(a_n) that apply, and the remainder of this // decomposition, which will be lower than the smallest a_n. // ln(a) = k_0 * x_0 + k_1 * x_1 + ... + k_n * x_n + ln(remainder), where each k_n equals either 0 or 1. // We mutate a by subtracting a_n, making it the remainder of the decomposition. // For reasons related to how `exp` works, the first two a_n (e^(2^7) and e^(2^6)) are not stored as fixed point // numbers with 18 decimals, but instead as plain integers with 0 decimals, so we need to multiply them by // this.ONE_18 to convert them to fixed point. // For each a_n, we test if that term is present in the decomposition (if a is larger than it), and if so divide // by it and compute the accumulated sum. let sum = BZERO; if (a.gte(this.a0.mul(this.ONE_18))) { a = a.div(this.a0); // Integer, not fixed point division sum = sum.add(this.x0); } if (a.gte(this.a1.mul(this.ONE_18))) { a = a.div(this.a1); // Integer, not fixed point division sum = sum.add(this.x1); } // All other a_n and x_n are stored as 20 digit fixed point numbers, so we convert the sum and a to this format. sum = sum.mul(web3Utils.toBN(100)); a = a.mul(web3Utils.toBN(100)); // Because further a_n are 20 digit fixed point numbers, we multiply by ONE_20 when dividing by them. if (a.gte(this.a2)) { a = (a.mul(this.ONE_20)).div(this.a2); sum = sum.add(this.x2); } if (a.gte(this.a3)) { a = (a.mul(this.ONE_20)).div(this.a3); sum = sum.add(this.x3); } if (a.gte(this.a4)) { a = (a.mul(this.ONE_20)).div(this.a4); sum = sum.add(this.x4); } if (a.gte(this.a5)) { a = (a.mul(this.ONE_20)).div(this.a5); sum = sum.add(this.x5); } if (a.gte(this.a6)) { a = (a.mul(this.ONE_20)).div(this.a6); sum = sum.add(this.x6); } if (a.gte(this.a7)) { a = (a.mul(this.ONE_20)).div(this.a7); sum = sum.add(this.x7); } if (a.gte(this.a8)) { a = (a.mul(this.ONE_20)).div(this.a8); sum = sum.add(this.x8); } if (a.gte(this.a9)) { a = (a.mul(this.ONE_20)).div(this.a9); sum = sum.add(this.x9); } if (a.gte(this.a10)) { a = (a.mul(this.ONE_20)).div(this.a10); sum = sum.add(this.x10); } if (a.gte(this.a11)) { a = (a.mul(this.ONE_20)).div(this.a11); sum = sum.add(this.x11); } // a is now a small number (smaller than a_11, which roughly equals 1.06). This means we can use a Taylor series // that converges rapidly for values of `a` close to one - the same one used in ln_36. // Let z = (a - 1) / (a + 1). // ln(a) = 2 * (z + z^3 / 3 + z^5 / 5 + z^7 / 7 + ... + z^(2 * n + 1) / (2 * n + 1)) // Recall that 20 digit fixed point division requires multiplying by ONE_20, and multiplication requires // division by ONE_20. const z = ((a.sub(this.ONE_20)).mul(this.ONE_20)).div(a.add(this.ONE_20)); const z_squared = (z.mul(z)).div(this.ONE_20); // num is the numerator of the series: the z^(2 * n + 1) term let num = z; // seriesSum holds the accumulated sum of each term in the series, starting with the initial z let seriesSum = num; // In each step, the numerator is multiplied by z^2 num = (num.mul(z_squared)).div(this.ONE_20); seriesSum = seriesSum.add(num.div(web3Utils.toBN(3))); num = (num.mul(z_squared)).div(this.ONE_20); seriesSum = seriesSum.add(num.div(web3Utils.toBN(5))); num = (num.mul(z_squared)).div(this.ONE_20); seriesSum = seriesSum.add(num.div(web3Utils.toBN(7))); num = (num.mul(z_squared)).div(this.ONE_20); seriesSum = seriesSum.add(num.div(web3Utils.toBN(9))); num = (num.mul(z_squared)).div(this.ONE_20); seriesSum = seriesSum.add(num.div(web3Utils.toBN(11))); // 6 Taylor terms are sufficient for 36 decimal precision. // Finally, we multiply by 2 (non fixed point) to compute ln(remainder) seriesSum = seriesSum.mul(web3Utils.toBN(2)); // We now have the sum of all x_n present, and the Taylor approximation of the logarithm of the remainder (both // with 20 decimals). All that remains is to sum these two, and then drop two digits to return a 18 decimal // value. return (sum.add(seriesSum)).div(web3Utils.toBN(100)); } }