@hikaru-fi/sc-calculators
Version:
Package for pool calculations
615 lines (496 loc) • 25.8 kB
JavaScript
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));
}
}