UNPKG

hm-aftermath-ts-sdk

Version:
1,137 lines (1,136 loc) 62.3 kB
"use strict"; var _a; Object.defineProperty(exports, "__esModule", { value: true }); exports.CmmmCalculations = void 0; const utils_1 = require("../../../general/utils"); const fixedUtils_1 = require("../../../general/utils/fixedUtils"); // This file is the typescript version of on-chain calculations. See the .move file for license info. // These calculations are useful for estimating values on-chain but the JS number format is LESS PRECISE! // Do not expect these values to be identical to their on-chain counterparts. // The formula used here differs from that of Curve/Balancer. Our stables allow custom price pegs as opposed to // the constant 1:1 equal-weight peg. Also our pools do not have an upper coin type limit (practically). // Here is our construction: // Start with a pool with balances b1,...,bn > 0. Call the tuple B = (b1,...,bn). // Take weights w1,...,wn with 0 < wi < 1 and w1 + ... + wn = 1. // Let X stand for the tuple (x1,...,xn) in Rn. // For normalization we need the tuple T = (h,h,...,h) for some h > 0, solved for later. // The invariant is defined as the value of this h. // -- TODO: generalize this reference point T to lie on a chosen ray like (w1*h, w2*h, ..., wn*h). // -- This would allow setting the swap price to be centered at a chosen balance distribution instead // -- of the 1:1:...:1 diagonal balance distribution currently in use. // Define the sum function S: Rn -> R as S(X) = w1*x1 + ... + wn*xn. // Define the product function P: Rn -> R as P(X) = x1^w1 * ... * xn^wn. // Note P(T) = S(T) = h. // We want the sum to vanish on the coordinate hyperplanes too so instead use L where // L(X) = [2P(X) / (P(X) + P(T))] * S(X) // Then 0 <= L(X) < 2S(X) and L(T) = h. // The constant price surface is defined by the equation L(X) = L(B) and the product curve by // P(X) = P(B). Equivilantly by L(X) - L(B) = 0, P(X) - P(B) = 0. // Take a flatness parameter A, 0 <= A <= 1. Then (1-A) is the dual parameter: // 0 <= (1-A) <= 1 and A + (1-A) = 1. Take the linear combination of the defining functions // C(X) = A * L(X) + (1-A) * P(X). The stable curve is defined as the solution to C(X) = C(B). // Moreover we can solve for T from the equation C(T) = C(B), making all the following equal: // C(B) = L(T) = S(T) = P(T) = h. // The defining equation C(X) = C(B) can be rewritten in a computationally simpler form as // P(X) * (2A * S(X) + (1-A) * P(X)) = h * (A * P(X) + h). // To see these functions/equations in action check out https://www.desmos.com/calculator/eu5mfckuk9 class CmmmCalculations { } exports.CmmmCalculations = CmmmCalculations; _a = CmmmCalculations; CmmmCalculations.minWeight = 0.01; // Having a minimum normalized weight imposes a limit on the maximum number of tokens; // i.e., the largest possible pool is one where all tokens have exactly the minimum weight. CmmmCalculations.maxWeightedTokens = 100; // Pool limits that arise from limitations in the fixed point power function (and the imposed 1:100 maximum weight // ratio). // Swap limits: amounts swapped may not be larger than this percentage of total balance. CmmmCalculations.maxInRatio = 0.3; CmmmCalculations.maxOutRatio = 0.3; // Invariant shrink limit: non-proportional exits cannot cause the invariant to decrease by less than this ratio. CmmmCalculations.minInvariantRatio = 0.7; // Invariant growth limit: non-proportional joins cannot cause the invariant to increase by more than this ratio. CmmmCalculations.maxInvariantRatio = 3; CmmmCalculations.maxNewtonAttempts = 255; CmmmCalculations.convergenceBound = 1e-9; CmmmCalculations.tolerance = 1e-13; CmmmCalculations.validityTolerance = 0.000001; // Invariant is used to govern pool behavior. Swaps are operations which change the pool balances without changing // the invariant (ignoring fees) and investments change the invariant without changing the distribution of balances. // Invariant and pool lp are almost in 1:1 correspondence -- e.g. burning lp in a withdraw proportionally lowers the pool invariant. // The difference is as swap fees are absorbed they increase the invariant without incrasing total lp, increasing lp worth. // Every pool operation either explicitly or implicity calls this function. CmmmCalculations.calcInvariant = (pool) => { let flatness = fixedUtils_1.FixedUtils.directCast(pool.flatness); // The value for h which we want is the one for which the balances vector B lies on the curve through T. // That is, C(T) = C(B). This turns out to be a quadratic equation which can be solved with // h = [sqrt[P(B) * (P(B) * (A*A + 4*(1-A)) + 8*A*S(B))] - A*P(B)] / 2. let sum = 0; let prod = 0; let balance; let weight; for (let coin of Object.values(pool.coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); weight = fixedUtils_1.FixedUtils.directCast(coin.weight); sum += weight * balance; prod += weight * Math.log(balance); } prod = Math.exp(prod); return _a.calcInvariantQuadratic(prod, sum, flatness); }; // The invariant for stables comes from a quadratic equation coming from the reference point T = (h,h,...,h). // h = [sqrt[p * (p * (A*A + 4*(1-A)) + 8*A*s)] - A*p] / 2. CmmmCalculations.calcInvariantQuadratic = (prod, sum, flatness) => (Math.sqrt(prod * (prod * (flatness * flatness + (1 - flatness) * 4) + flatness * sum * 8)) - flatness * prod) / 2; // This function is used for 1d optimization. It computes the full invariant components and their // portions which omit contribution from the balance in the `index` coordinate. // It returns (prod, sum, p0, s0, h) where: // prod = b1^w1 * ... * bn^wn // sum = w1*b1 + ... + wn*bn // p0 = b1^w1 * ... * [bi^w1] * ... * bn^wn (remove bi from prod) // s0 = w1*b1 + ... + [wi*bi] + ... + wn*bn (remove bi from sum) // h is the invariant CmmmCalculations.calcInvariantComponents = (pool, index) => { let flatness = fixedUtils_1.FixedUtils.directCast(pool.flatness); let prod = 0; let sum = 0; let p0 = 0; let s0 = 0; let balance; let weight; let p; let s; for (let [coinType, coin] of Object.entries(pool.coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); weight = fixedUtils_1.FixedUtils.directCast(coin.weight); p = weight * Math.log(balance); s = weight * balance; prod = prod + p; sum = sum + s; if (coinType != index) { p0 = p0 + p; s0 = s0 + s; } } prod = Math.exp(prod); p0 = Math.exp(p0); return [ prod, sum, p0, s0, CmmmCalculations.calcInvariantQuadratic(prod, sum, flatness), ]; }; // spot price is given in units of Bin / Bout CmmmCalculations.calcSpotPrice = (pool, coinTypeIn, coinTypeOut) => CmmmCalculations.calcSpotPriceWithFees(pool, coinTypeIn, coinTypeOut, true); // spot price is given in units of Bin / Bout CmmmCalculations.calcSpotPriceWithFees = (pool, coinTypeIn, coinTypeOut, ignoreFees) => { var _b, _c; let a = fixedUtils_1.FixedUtils.directCast(pool.flatness); let part1 = CmmmCalculations.calcSpotPriceBody(pool); let coinIn = pool.coins[coinTypeIn]; let coinOut = pool.coins[coinTypeOut]; let balanceIn = fixedUtils_1.FixedUtils.directCast(coinIn.normalizedBalance); let balanceOut = fixedUtils_1.FixedUtils.directCast(coinOut.normalizedBalance); let weightIn = fixedUtils_1.FixedUtils.directCast(coinIn.weight); let weightOut = fixedUtils_1.FixedUtils.directCast(coinOut.weight); let swapFeeIn = ignoreFees ? 0 : fixedUtils_1.FixedUtils.directCast(coinIn.tradeFeeIn); let swapFeeOut = ignoreFees ? 0 : fixedUtils_1.FixedUtils.directCast(coinIn.tradeFeeOut); let sbi = weightOut * balanceIn; // this is the only place where fee values are used let sbo = (1 - (ignoreFees ? 0 : utils_1.Casting.bpsToPercentage((_c = (_b = pool.daoFeePoolObject) === null || _b === void 0 ? void 0 : _b.feeBps) !== null && _c !== void 0 ? _c : BigInt(0)))) * (1 - swapFeeIn) * (1 - swapFeeOut) * weightIn * balanceOut; return ((sbi * (part1 + 2 * a * balanceOut)) / (sbo * (part1 + 2 * a * balanceIn))); }; // The spot price formula contains a factor of C0^2 / P(B0) + (1-A)P(B0), this returns that CmmmCalculations.calcSpotPriceBody = (pool) => { // The spot price formula comes from the partial derivatives of Cf, specifically -(dCf / dxOut) / (dCf / dxIn) let a = fixedUtils_1.FixedUtils.directCast(pool.flatness); let ac = 1 - a; let prod = 0; let sum = 0; let balance; let weight; // The spot price formula requires knowing the value of the invariant. We need the prod and sum parts // also later on so no need to compute them twice by calling calcInvariant, just evaluate here. for (let coin of Object.values(pool.coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); weight = fixedUtils_1.FixedUtils.directCast(coin.weight); prod += weight * Math.log(balance); sum += weight * balance; } prod = Math.exp(prod); let invarnt = CmmmCalculations.calcInvariantQuadratic(prod, sum, a); return (invarnt * invarnt) / prod + ac * prod; }; // 1d optimized swap function for finding out given in. Returns the amount out. CmmmCalculations.calcOutGivenIn = (pool, coinTypeIn, coinTypeOut, amountIn) => { if (coinTypeIn === coinTypeOut) throw Error("in and out must be different coins"); let coinIn = pool.coins[coinTypeIn]; let coinOut = pool.coins[coinTypeOut]; let swapFeeIn = fixedUtils_1.FixedUtils.directCast(coinIn.tradeFeeIn); let swapFeeOut = fixedUtils_1.FixedUtils.directCast(coinOut.tradeFeeOut); if (swapFeeIn >= 1 || swapFeeOut >= 1) { // this swap is disabled return BigInt(0); } let flatness = fixedUtils_1.FixedUtils.directCast(pool.flatness); let oldIn = fixedUtils_1.FixedUtils.directCast(coinIn.normalizedBalance); let oldOut = fixedUtils_1.FixedUtils.directCast(coinOut.normalizedBalance); let wIn = fixedUtils_1.FixedUtils.directCast(coinIn.weight); let wOut = fixedUtils_1.FixedUtils.directCast(coinOut.weight); let [prod, , p0, s0, h] = CmmmCalculations.calcInvariantComponents(pool, coinTypeOut); let feedAmountIn = (1 - swapFeeIn) * fixedUtils_1.FixedUtils.castAndNormalize(coinIn.decimalsScalar, amountIn); let newIn = oldIn + feedAmountIn; let prodRatio = Math.pow(newIn / oldIn, wIn); let newP0 = p0 * prodRatio; // the initial estimate (xi) is from if there were only the product part of the curve let xi = Math.pow(prod / newP0, 1 / wOut); let newS0 = s0 + wIn * feedAmountIn; let tokenAmountOut = CmmmCalculations.getTokenBalanceGivenInvariantAndAllOtherBalances(flatness, wOut, h, xi, // initial estimate -- default can be (P(X) / p0)^n newP0, // P(B) / xi^(1/n) (everything but the missing part) newS0 // S(B) - xi / n (everything but the missing part) ); let amountOut = fixedUtils_1.FixedUtils.uncastAndUnnormalize(coinOut.decimalsScalar, (oldOut - tokenAmountOut) * (1 - swapFeeOut)); if (!CmmmCalculations.checkValid1dSwap(pool, coinTypeIn, coinTypeOut, amountIn, amountOut)) throw Error("invalid 1d swap"); return amountOut; }; // 1d optimized swap function for finding in given out. Returns the amount in. CmmmCalculations.calcInGivenOut = (pool, coinTypeIn, coinTypeOut, amountOut) => { if (coinTypeIn === coinTypeOut) throw Error("in and out must be different coins"); let coinIn = pool.coins[coinTypeIn]; let coinOut = pool.coins[coinTypeOut]; let swapFeeIn = fixedUtils_1.FixedUtils.directCast(coinIn.tradeFeeIn); let swapFeeOut = fixedUtils_1.FixedUtils.directCast(coinOut.tradeFeeOut); if (swapFeeIn >= 1 || swapFeeOut >= 1) { // this swap is disabled if (amountOut === BigInt(0)) return BigInt(0); throw Error("this swap is disabled"); } let flatness = fixedUtils_1.FixedUtils.directCast(pool.flatness); let oldIn = fixedUtils_1.FixedUtils.directCast(coinIn.normalizedBalance); let oldOut = fixedUtils_1.FixedUtils.directCast(coinOut.normalizedBalance); let wOut = fixedUtils_1.FixedUtils.directCast(coinOut.weight); let wIn = fixedUtils_1.FixedUtils.directCast(coinIn.weight); let [prod, , p0, s0, h] = CmmmCalculations.calcInvariantComponents(pool, coinTypeIn); let feedAmountOut = fixedUtils_1.FixedUtils.castAndNormalize(coinIn.decimalsScalar, amountOut) / (1 - swapFeeOut); let newOut = oldOut - feedAmountOut; let prodRatio = Math.pow(newOut / oldOut, wOut); let newP0 = p0 * prodRatio; // the initial estimate (xi) is from if there were only the product part of the curve let xi = Math.pow(prod / newP0, 1 / wIn); let newS0 = s0 - wOut * feedAmountOut; let tokenAmountIn = CmmmCalculations.getTokenBalanceGivenInvariantAndAllOtherBalances(flatness, wIn, h, xi, // initial estimate -- default can be (P(X) / p0)^n newP0, // P(B) / xi^(1/n) (everything but the missing part) newS0 // S(B) - xi / n (everything but the missing part) ); let amountIn = fixedUtils_1.FixedUtils.uncastAndUnnormalize(coinOut.decimalsScalar, (tokenAmountIn - oldIn) / (1 - swapFeeIn)); if (!CmmmCalculations.checkValid1dSwap(pool, coinTypeIn, coinTypeOut, amountIn, amountOut)) throw Error("invalid 1d swap"); return amountIn; }; // For computing swap amounts. Given the current balances (and any other parameters) and an amounts in vector, // and a expected amounts out vector, determine the value of t > 0 such that t*expected_amounts_out // is a valid swap from balances corresponding to adding amounts_in to the pool. The correct value of t is the one for which // calc_swap_invariant(balances, ...parameters, amounts_in, t*expected_amounts_out) === calc_invariant_full(balances, ...parameters). CmmmCalculations.calcSwapFixedIn = (pool, amountsIn, amountsOutDirection) => { let coins = pool.coins; let invariant = CmmmCalculations.calcInvariant(pool); let a = fixedUtils_1.FixedUtils.directCast(pool.flatness); let ac = 1 - a; let t = 1; // assume that the expected amounts out are close to the true amounts out // this allows faster convergence if the caller chooses expected_amounts_out well let prevT = t; let balance; let weight; let amountIn; let amountOut; let feeIn; let feeOut; let prod; let prod1; let sum; let sum1; let part1; let part2; let part3; let part4; let skip; let drainT = Number.POSITIVE_INFINITY; let shifter = 1; // make sure no disabled coin type is expected for (let [coinType, coin] of Object.entries(coins)) { amountOut = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsOutDirection[coinType] || BigInt(0)); feeOut = fixedUtils_1.FixedUtils.complement(fixedUtils_1.FixedUtils.directCast(coin.tradeFeeOut)); if (amountOut > 0) { if (feeOut === 0) { throw Error("this trade is disabled"); } else { // pool is drained when b + Ain * (1 - Sin) - t * Aout / (1 - Sout) = 0, or t = (b + Ain * (1 - Sin)) * (1 - So) / Aout t = ((fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance) + fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsIn[coinType] || BigInt(0)) * fixedUtils_1.FixedUtils.complement(fixedUtils_1.FixedUtils.directCast(coin.tradeFeeIn))) / amountOut) * feeOut; drainT = Math.min(drainT, t); } } } // drain_t is the maximum t can possibly be. It will be 0 if expected amounts out is way too high. if (drainT === 0) return BigInt(0); while (shifter >= drainT) shifter /= 2; t = 1; for (let i = 0; i < CmmmCalculations.maxNewtonAttempts; ++i) { prod = 0; prod1 = 0; sum = 0; sum1 = 0; skip = false; for (let [coinType, coin] of Object.entries(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); weight = fixedUtils_1.FixedUtils.directCast(coin.weight); amountIn = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsIn[coinType] || BigInt(0)); amountOut = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsOutDirection[coinType] || BigInt(0)); feeIn = fixedUtils_1.FixedUtils.complement(fixedUtils_1.FixedUtils.directCast(coin.tradeFeeIn)); feeOut = fixedUtils_1.FixedUtils.complement(fixedUtils_1.FixedUtils.directCast(coin.tradeFeeOut)); // pseudoin part1 = feeIn * amountIn; // pseudoout part2 = (t * amountOut) / feeOut; // pseudobalance if (part2 >= balance + part1 + 1) { skip = true; break; } part3 = balance + part1 - part2; // for derivatives: weight * expected_amounts_out / fee_out part4 = (weight * amountOut) / feeOut; prod += weight * Math.log(part3); prod1 += part4 / part3; sum += weight * part3; sum1 += part4; } prod = Math.exp(prod); part1 = a * sum; part2 = ac * prod; part3 = part1 + part2; part4 = a * invariant * prod1; t = (a * (sum + 2 * t * sum1) + part3 + 2 * prod1 * t * part3 - (t * part4 + invariant * (a + invariant / prod))) / (2 * (prod1 * part3 + a * sum1) - part4); if (utils_1.Helpers.closeEnough(t, prevT, CmmmCalculations.convergenceBound)) { if (!CmmmCalculations.checkValidSwap(pool, amountsIn, 1, amountsOutDirection, t)) throw Error("invalid swap"); return fixedUtils_1.FixedUtils.directUncast(t); } prevT = t; } throw Error("Newton diverged"); }; // Swaps but fixed amounts out. Given the pool's current state and a guaranteed out vector, and a expected in vector, // scale expected_amounts_in by t > 0 so that this swap is valid and return the correct value for t CmmmCalculations.calcSwapFixedOut = (pool, amountsInDirection, amountsOut) => { let coins = pool.coins; let invariant = CmmmCalculations.calcInvariant(pool); let a = fixedUtils_1.FixedUtils.directCast(pool.flatness); let ac = 1 - a; let t = 1; // assume that the expected amounts out are close to the true amounts out // this allows faster convergence if the caller chooses expected_amounts_out well let prevT = 0; let balance; let weight; let amountIn; let amountOut; let feeIn; let feeOut; let prod; let prod1; let sum; let sum1; let part1; let part2; let part3; let part4; // make sure no disabled coin type is expected for (let [coinType, coin] of Object.entries(coins)) { if (coin.tradeFeeOut >= fixedUtils_1.FixedUtils.fixedOneB && (amountsOut[coinType] || BigInt(0)) > BigInt(0)) throw Error("this trade is disabled"); } for (let i = 0; i < CmmmCalculations.maxNewtonAttempts; ++i) { prod = 0; prod1 = 0; sum = 0; sum1 = 0; for (let [coinType, coin] of Object.entries(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); weight = fixedUtils_1.FixedUtils.directCast(coin.weight); amountIn = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsInDirection[coinType] || BigInt(0)); amountOut = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsOut[coinType] || BigInt(0)); feeIn = 1 - fixedUtils_1.FixedUtils.directCast(coin.tradeFeeIn); feeOut = 1 - fixedUtils_1.FixedUtils.directCast(coin.tradeFeeOut); // pseudoin expected part1 = feeIn * amountIn; // pseudoout part2 = amountOut === 0 ? 0 : amountOut / feeOut; // pseudobalance part3 = balance + t * part1 - part2; // for derivatives: weight * fee_in * expected_amounts_in part4 = weight * part1; prod += weight * Math.log(part3); prod1 += part4 / part3; sum += weight * part3; sum1 += part4; } prod = Math.exp(prod); part1 = 2 * a * sum; part2 = ac * prod; part3 = part1 + part2; part4 = (part3 + part2) * prod1 + 2 * a * sum1 - a * invariant * prod1; t = (t * part4 + invariant * (a + invariant / prod) - part3) / part4; if (utils_1.Helpers.closeEnough(t, prevT, CmmmCalculations.convergenceBound)) { if (!CmmmCalculations.checkValidSwap(pool, amountsInDirection, 1, amountsOut, t)) throw Error("invalid swap"); return fixedUtils_1.FixedUtils.directUncast(t); } prevT = t; } throw Error("Newton diverged"); }; // Return the expected lp ratio for this deposit CmmmCalculations.calcDepositFixedAmounts = (pool, amountsIn) => { let invariant = CmmmCalculations.calcInvariant(pool); let coins = pool.coins; let a = fixedUtils_1.FixedUtils.directCast(pool.flatness); let ac = 1 - a; let balance; let weight; let amount; let prod = 0; let sum = 0; let r = CmmmCalculations.calcDepositFixedAmountsInitialEstimate(pool, amountsIn); let prevR = r; let fees = {}; for (let [coinType, coin] of Object.entries(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); amount = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsIn[coinType] || BigInt(0)); fees[coinType] = r * (balance + amount) >= balance ? 1 - fixedUtils_1.FixedUtils.directCast(coin.tradeFeeIn) : 1 / (1 - fixedUtils_1.FixedUtils.directCast(coin.tradeFeeOut)); } let i = 0; let prod1; let sum1; let fee; let part1; let part2; let part3; let part4; while (i < CmmmCalculations.maxNewtonAttempts) { prod = 0; prod1 = 0; sum = 0; sum1 = 0; for (let [coinType, coin] of Object.entries(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); weight = fixedUtils_1.FixedUtils.directCast(coin.weight); amount = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsIn[coinType] || BigInt(0)); fee = fees[coinType]; part1 = balance + amount; part2 = fee * r * part1 + balance - fee * balance; part3 = weight * fee * part1; prod += weight * Math.log(part2); prod1 += part3 / part2; sum += weight * part2; sum1 += part3; } prod = Math.exp(prod); part3 = a * invariant * prod1; part4 = 2 * prod1 * (a * sum + ac * prod) + 2 * a * sum1; r = (r * part4 + invariant * (1 + invariant / prod) - (r * part3 + 2 * a * sum + ac * (prod + invariant))) / (part4 - part3); if (utils_1.Helpers.closeEnough(r, prevR, CmmmCalculations.convergenceBound)) { let scalar = fixedUtils_1.FixedUtils.directUncast(r); if (!CmmmCalculations.checkValidDeposit(pool, amountsIn, scalar)) throw Error("invalid deposit"); return scalar; } prevR = r; i += 1; } throw Error("Newton diverged"); }; CmmmCalculations.calcDepositFixedAmountsInitialEstimate = (pool, amountsIn) => { let invariant = CmmmCalculations.calcInvariant(pool); let coins = pool.coins; let a = fixedUtils_1.FixedUtils.directCast(pool.flatness); let ac = 1 - a; let balance; let amount; let weight; let r; let rMin = 0; let cfMin = 0; let prod = 0; let sum = 0; let part1; // start cf_max as corresponding to r = 1 for (let [coinType, coin] of Object.entries(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); amount = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsIn[coinType] || BigInt(0)); weight = fixedUtils_1.FixedUtils.directCast(coin.weight); // this is all in so use fees in part1 = balance + (1 - fixedUtils_1.FixedUtils.directCast(coin.tradeFeeIn)) * amount; prod += weight * Math.log(part1); sum += weight * part1; // r_min portion of the loop r = (fixedUtils_1.FixedUtils.directCast(coin.tradeFeeOut) * balance) / (balance + amount); rMin = Math.max(r, rMin); } prod = Math.exp(prod); let cfMax = (2 * a * prod * sum) / (prod + invariant) + ac * prod; let rMax = 1; let cf; for (let [coinType, coin] of Object.entries(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); weight = fixedUtils_1.FixedUtils.directCast(coin.weight); amount = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsIn[coinType] || BigInt(0)); r = balance / (balance + amount); if (r <= rMin) continue; prod = 0; sum = 0; for (let [coinType2, coin2] of Object.entries(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin2.normalizedBalance); weight = fixedUtils_1.FixedUtils.directCast(coin2.weight); amount = fixedUtils_1.FixedUtils.castAndNormalize(coin2.decimalsScalar, amountsIn[coinType2] || BigInt(0)); part1 = r * (balance + amount); if (part1 >= balance) { // r * (B0 + Din) >= B0 so use fees in part1 = balance + (1 - fixedUtils_1.FixedUtils.directCast(coin2.tradeFeeIn)) * (part1 - balance); } else { // r * (B0 + Din) < B0 so use fees out part1 = balance - (balance - part1) / fixedUtils_1.FixedUtils.complement(fixedUtils_1.FixedUtils.directCast(coin.tradeFeeOut)); } prod += weight * Math.log(part1); sum += weight * part1; } prod = Math.exp(prod); cf = (2 * a * prod * sum) / (prod + invariant) + ac * prod; if (cf <= invariant) { // is a lower bound, check min if (cf >= cfMin) { rMin = r; cfMin = cf; } } if (cf >= invariant) { // is an upper bound, check max if (cf <= cfMax) { rMax = r; cfMax = cf; } } } r = cfMin === cfMax ? rMin : (rMin * cfMax + (rMax - rMin) * invariant - rMax * cfMin) / (cfMax - cfMin); return r; }; // Return the expected amounts out for this withdrawal CmmmCalculations.calcWithdrawFlpAmountsOut = (pool, amountsOutDirection, lpRatio) => { let invariant = CmmmCalculations.calcInvariant(pool); let coins = pool.coins; let lpr = lpRatio; let lpc = 1 - lpr; let scaledInvariant = invariant * lpr; let a = fixedUtils_1.FixedUtils.directCast(pool.flatness); let ac = 1 - a; let i; let prevR = 0; let balance; let weight; let amountOut; let fee; let prod; let prod1; let sum; let sum1; let part1; let part2; let part3; let part4; let skip; let shrinker = 1; let [r, rDrain] = CmmmCalculations.calcWithdrawFlpAmountsOutInitialEstimate(pool, amountsOutDirection, lpRatio); while (shrinker >= rDrain) shrinker /= 2; let fees = {}; for (let [coinType, coin] of Object.entries(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); amountOut = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsOutDirection[coinType] || BigInt(0)); fees[coinType] = balance * lpc >= r * amountOut ? 1 - fixedUtils_1.FixedUtils.directCast(coin.tradeFeeIn) : 1 / (1 - fixedUtils_1.FixedUtils.directCast(coin.tradeFeeOut)); } i = 0; while (i < CmmmCalculations.maxNewtonAttempts) { prod = 0; prod1 = 0; sum = 0; sum1 = 0; skip = false; for (let [coinType, coin] of Object.entries(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); weight = fixedUtils_1.FixedUtils.directCast(coin.weight); amountOut = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsOutDirection[coinType] || BigInt(0)); fee = fees[coinType]; part1 = balance * (lpr + lpc * fee); part2 = fee * r * amountOut; if (part2 + 1 >= part1) { // Overshot and drained pool. Set t to be closer to t_max and try again. skip = true; break; } else { part1 -= part2; } part2 = weight * fee * amountOut; prod += weight * Math.log(part1); prod1 += part2 / part1; sum += weight * part1; sum1 += part2; } if (skip) { r = rDrain - shrinker / Math.pow(2, i); i += 1; continue; } prod = Math.exp(prod); part1 = prod / scaledInvariant; part2 = 2 * a * sum; part3 = ac * (prod * part1 + 2 * prod + scaledInvariant) + part2; part4 = part3 * prod1 + 2 * a * (part1 + 1) * sum1; r = (r * part4 + part3 + part1 * part2 - prod - scaledInvariant * (2 + scaledInvariant / prod)) / part4; if (utils_1.Helpers.closeEnough(r, prevR, CmmmCalculations.convergenceBound)) { let returner = {}; for (let coinType of Object.keys(coins)) { returner[coinType] = fixedUtils_1.FixedUtils.directUncast(r * fixedUtils_1.FixedUtils.directCast(amountsOutDirection[coinType] || BigInt(0))); } if (!CmmmCalculations.checkValidWithdraw(pool, returner, lpRatio)) throw Error("invalid withdraw"); return returner; } prevR = r; i += 1; } throw Error("Newton diverged"); }; CmmmCalculations.calcWithdrawFlpAmountsOutInitialEstimate = (pool, amountsOutDirection, lpRatio) => { let invariant = CmmmCalculations.calcInvariant(pool); let coins = pool.coins; let lpr = lpRatio; let lpc = 1 - lpr; let scaledInvariant = invariant * lpr; let a = fixedUtils_1.FixedUtils.directCast(pool.flatness); let ac = 1 - a; let keepT; let tDrain; let t; let cf; let tMin; let cfMin; let tMax; let cfMax; let balance; let weight; let amountOut; let fee; let prod; let sum; let part1; let part2; let part3; // the biggest cfMax can possibly be is f(0) which is this: tMax = 0; prod = 0; sum = 0; for (let coin of Object.values(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); weight = fixedUtils_1.FixedUtils.directCast(coin.weight); fee = fixedUtils_1.FixedUtils.directCast(coin.tradeFeeIn); part1 = balance * (1 + lpr * fee - fee); prod += weight * Math.log(part1); sum += weight * part1; } prod = Math.exp(prod); cfMax = (2 * a * prod * sum) / (prod + scaledInvariant) + ac * prod; // the smallest cfMin can be is 0 which occurs when the pool is drained cfMin = 0; tMin = Number.POSITIVE_INFINITY; for (let [coinType, coin] of Object.entries(coins)) { amountOut = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsOutDirection[coinType] || BigInt(0)); if (amountOut === 0) continue; t = (fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance) * fixedUtils_1.FixedUtils.complement(fixedUtils_1.FixedUtils.directCast(coin.tradeFeeOut) * lpRatio)) / amountOut; if (t < tMin) tMin = t; } tDrain = tMin; // remaining test points are the CF discontinuities: where B0 - t*D = R*B0 for (let [coinTypeT, coinT] of Object.entries(coins)) { amountOut = fixedUtils_1.FixedUtils.castAndNormalize(coinT.decimalsScalar, amountsOutDirection[coinTypeT] || BigInt(0)); if (amountOut === 0) continue; balance = fixedUtils_1.FixedUtils.directCast(coinT.normalizedBalance); t = (balance * lpc) / amountOut; prod = 0; sum = 0; keepT = true; for (let [coinType, coin] of Object.entries(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); weight = fixedUtils_1.FixedUtils.directCast(coin.weight); amountOut = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsOutDirection[coinType] || BigInt(0)); part1 = t * amountOut; if (part1 >= balance) { // this t is too large to be a bound because B0 - t*D overdraws the pool keepT = false; break; } part1 = balance - part1; part2 = lpr * balance; part3 = part1 >= part2 ? part2 + fixedUtils_1.FixedUtils.complement(fixedUtils_1.FixedUtils.directCast(coin.tradeFeeIn)) * (part1 - part2) : part2 - (part2 - part1) / fixedUtils_1.FixedUtils.complement(fixedUtils_1.FixedUtils.directCast(coin.tradeFeeOut)); prod += weight * Math.log(part3); sum += weight * part3; } if (keepT) { prod = Math.exp(prod); cf = (2 * a * prod * sum) / (prod + scaledInvariant) + ac * prod; if (cf >= scaledInvariant) { // upper bound, check against cfMax if (cf <= cfMax) { tMax = t; cfMax = cf; } } if (cf <= scaledInvariant) { // lower bound, check against cfMin if (cf >= cfMin) { tMin = t; cfMin = cf; } } } } // initial estimate is the linear interpolation between discontinuity bounds t = cfMax === cfMin ? tMin : (tMin * cfMax + tMax * scaledInvariant - tMax * cfMin - tMin * scaledInvariant) / (cfMax - cfMin); return [t, tDrain]; }; // Dusty direct all-coin deposit, returns the number s >= 0 so that amounts_in = s*B0 + dust. // When performing an all-coin deposit, call this function to get t then split amounts_in into s*B0 + dust. // At least one coordinate of dust will be 0. Send the s*B0 balances into the pool and mint s*total_lp. // The caller keeps the dust. CmmmCalculations.calcAllCoinDeposit = (pool, amountsIn) => { let coins = pool.coins; let balance; let amountIn; let s; let sMin = Number.POSITIVE_INFINITY; for (let [coinType, coin] of Object.entries(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); amountIn = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsIn[coinType] || BigInt(0)); s = amountIn / balance; if (s < sMin) sMin = s; } let returner = {}; for (let coinType of Object.keys(coins)) returner[coinType] = utils_1.Helpers.blendedOperations.mulNBB(sMin, amountsIn[coinType] || BigInt(0)); return returner; }; // Dusty direct all-coin withdraw, returns the number s >= 0 so that amounts_out + dust = s*B0. // The normal all-coin withdraw (take this exact amount of lp and give however much balances out) // should be done directly without this function -- just burn the lp and give the user // lp/total_lp * balance_i in each coordinate. This function is for finding how much lp it takes to // ensure that at least amounts_out comes out. CmmmCalculations.calcAllCoinWithdraw = (pool, amountsOut) => { let coins = pool.coins; let balance; let amountOut; let s; let sMax = 0; for (let [coinType, coin] of Object.entries(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); amountOut = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsOut[coinType] || BigInt(0)); s = amountOut / balance; if (s > sMax) sMax = s; } let returner = {}; for (let coinType of Object.keys(coins)) returner[coinType] = utils_1.Helpers.blendedOperations.mulNBB(sMax, amountsOut[coinType] || BigInt(0)); return returner; }; // This function calculates the balance of a given token (index) given all the other balances (combined in p0, s0) // and the invariant along with an initial estimate. It is useful for 1d optimization. CmmmCalculations.getTokenBalanceGivenInvariantAndAllOtherBalances = (flatness, w, h, xi, // initial estimate -- default can be (P(X) / p0)^n p0, // P(B) / xi^(1/n) (everything but the missing part) s0 // S(B) - xi / n (everything but the missing part) ) => { if (isNaN(xi)) throw new Error("initial estimate is not a number"); // Standard Newton method used here // ---------------- setting constants ---------------- // c1 = 2*A*w*w // c2 = 2*(1-A)*w*p0 // c3 = A*(2*w*s0+t) // c4 = t*t/p0 // c5 = (1-A)*p0 // c6 = A*(2*s0+w*t) // c7 = 2*A*w*(w+1) // c8 = 2*(1-A)*p0 // c9 = 2*A*w*s0 // c10= A*w*t let ac = 1 - flatness; let aw = flatness * w; let acw = ac * w; let as0 = flatness * s0; let ah = flatness * h; let c1 = 2 * aw * w; let c2 = 2 * acw * p0; let c3 = 2 * w * as0 + ah; let c4 = (h * h) / p0; let c5 = ac * p0; let c6 = 2 * as0 + w * ah; let c7 = 2 * aw * (w + 1); let c8 = 2 * acw * p0; let c9 = 2 * aw * s0; let c10 = aw * h; // ---------------- iterating ---------------- //x = ( // x * ( // ( // x^w * ( // c1 * x + c2 * x^w + c3 // ) + c4 // ) - x^w * ( // c5 * x^w + c6 // ) // ) //) / ( // x^w * ( // ( // c7 * x + c8 * x^w + c9 // ) - c10 // ) //) let x = xi; let xw; // x^w let topPos; let topNeg; let bottomPos; //let bottomNeg; let prevX = x; let i = 0; while (i < CmmmCalculations.maxNewtonAttempts) { xw = Math.pow(x, w); topPos = x * (xw * (c1 * x + c2 * xw + c3) + c4); topNeg = x * (xw * (c5 * xw + c6)); bottomPos = c7 * x + c8 * xw + c9; //bottomNeg = c10; // If x jumps too much (bad initial estimate) then g(x) might overshoot into a negative number. // This only happens if x is supposed to be small. In this case, replace x with a small number and try again. // Once x is close enough to the true value g(x) won't overshoot anymore and this test will be skipped from then on. if (topPos < topNeg || bottomPos < c10) { x = 1 / Math.pow(2, i); i = i + 1; continue; } x = (topPos - topNeg) / (xw * (bottomPos - c10)); // using relative error here (easier to pass) because js numbers are less precise if (utils_1.Helpers.closeEnough(x, prevX, CmmmCalculations.convergenceBound)) { return x; } prevX = x; i = i + 1; } throw Error("Newton diverged"); }; // Compute the invariant before swap and pseudoinvariant (invariant considering fees) // after the swap and see if they are the same up to a tolerance. // It also checks that this balance does not drain the pool i.e. the final balance is at least 1. // The scalars are here to avoid unnecessary vector creation. In most calls one scalar will be 10^18 (1). CmmmCalculations.checkValidSwap = (pool, amountsIn, amountsInScalar, amountsOut, amountsOutScalar) => { let coins = pool.coins; let flatness = fixedUtils_1.FixedUtils.directCast(pool.flatness); // balance = balances[i] let balance; // pseudobalance = balance + feedAmountIn - feedAmountOut let pseudobalance; // postbalance = balance + amountIn - amountOut let postbalance; let weight; let amountIn; let amountOut; let feedAmountIn; let feedAmountOut; let preprod = 0; let presum = 0; let pseudoprod = 0; let pseudosum = 0; let postprod = 0; let postsum = 0; for (let [coinType, coin] of Object.entries(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); weight = fixedUtils_1.FixedUtils.directCast(coin.weight); amountIn = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsIn[coinType] || BigInt(0)) * amountsInScalar; amountOut = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsOut[coinType] || BigInt(0)) * amountsOutScalar; if (amountIn > 0 && amountOut > 0) return false; feedAmountIn = amountIn * (1 - fixedUtils_1.FixedUtils.directCast(coin.tradeFeeIn)); feedAmountOut = amountOut === 0 ? 0 : amountOut / (1 - fixedUtils_1.FixedUtils.directCast(coin.tradeFeeOut)); postbalance = balance + amountIn; if (amountOut > postbalance + 1) return false; postbalance -= -amountOut; pseudobalance = balance + feedAmountIn; if (feedAmountOut > pseudobalance + 1) return false; pseudobalance -= -feedAmountOut; preprod += weight * Math.log(balance); presum += weight * balance; postprod += weight * Math.log(postbalance); postsum += weight * postbalance; pseudoprod += weight * Math.log(pseudobalance); pseudosum += weight * pseudobalance; } preprod = Math.exp(preprod); postprod = Math.exp(postprod); pseudoprod = Math.exp(pseudoprod); let preinvariant = CmmmCalculations.calcInvariantQuadratic(preprod, presum, flatness); let postinvariant = CmmmCalculations.calcInvariantQuadratic(postprod, postsum, flatness); let pseudoinvariant = CmmmCalculations.calcInvariantQuadratic(pseudoprod, pseudosum, flatness); return (postinvariant * (1 + CmmmCalculations.tolerance) >= preinvariant && (utils_1.Helpers.veryCloseInt(preinvariant, pseudoinvariant, fixedUtils_1.FixedUtils.fixedOneN) || utils_1.Helpers.closeEnough(preinvariant, pseudoinvariant, CmmmCalculations.validityTolerance))); }; // Compute the invariant before swap and pseudoinvariant (invariant considering fees) // after the swap and see if they are the same up to a tolerance. // It also checks that this balance does not drain the pool i.e. the final balance is at least 1. CmmmCalculations.checkValid1dSwap = (pool, coinTypeIn, coinTypeOut, amountInB, amountOutB) => { if (coinTypeIn === coinTypeOut) return false; let coins = pool.coins; let coinIn = coins[coinTypeIn]; let coinOut = coins[coinTypeOut]; let flatness = fixedUtils_1.FixedUtils.directCast(pool.flatness); // balance = balances[i] let balance; // pseudobalance = balance + feed amount in - feed amount out let pseudobalance; // postbalance = balance + amount in - amount out let postbalance; let weight; let amountIn = fixedUtils_1.FixedUtils.castAndNormalize(coinIn.decimalsScalar, amountInB); let amountOut = fixedUtils_1.FixedUtils.castAndNormalize(coinOut.decimalsScalar, amountOutB); let feedAmountIn = amountIn * (1 - fixedUtils_1.FixedUtils.directCast(coinIn.tradeFeeIn)); let feedAmountOut = amountOut === 0 ? 0 : amountOut / (1 - fixedUtils_1.FixedUtils.directCast(coinOut.tradeFeeOut)); let preprod = 0; let presum = 0; let pseudoprod = 0; let pseudosum = 0; let postprod = 0; let postsum = 0; let p; let s; for (let [coinType, coin] of Object.entries(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); weight = fixedUtils_1.FixedUtils.directCast(coin.weight); p = weight * Math.log(balance); s = weight * balance; preprod += p; presum += s; if (coinType === coinTypeIn) { pseudobalance = balance + feedAmountIn; postbalance = balance + amountIn; pseudoprod += weight * Math.log(pseudobalance); pseudosum += weight * pseudobalance; postprod += weight * Math.log(postbalance); postsum += weight * postbalance; } else { if (coinType === coinTypeOut) { if (feedAmountOut > balance + 1 || amountOut > balance + 1) return false; pseudobalance = balance - feedAmountOut; postbalance = balance - amountOut; pseudoprod += weight * Math.log(pseudobalance); pseudosum += weight * pseudobalance; postprod += weight * Math.log(postbalance); postsum += weight * postbalance; } else { pseudoprod += p; pseudosum += s; postprod += p; postsum += s; } } } preprod = Math.exp(preprod); postprod = Math.exp(postprod); pseudoprod = Math.exp(pseudoprod); let preinvariant = CmmmCalculations.calcInvariantQuadratic(preprod, presum, flatness); let postinvariant = CmmmCalculations.calcInvariantQuadratic(postprod, postsum, flatness); let pseudoinvariant = CmmmCalculations.calcInvariantQuadratic(pseudoprod, pseudosum, flatness); return (postinvariant * (1 + CmmmCalculations.tolerance) >= preinvariant && (utils_1.Helpers.veryCloseInt(preinvariant, pseudoinvariant, fixedUtils_1.FixedUtils.fixedOneN) || utils_1.Helpers.closeEnough(preinvariant, pseudoinvariant, CmmmCalculations.validityTolerance))); }; // A fixed amount investment is a swap followed by an all coin investment. This function checks that the // intermediate swap is allowed and corresponds to the claimed lp ratio. CmmmCalculations.checkValidDeposit = (pool, amountsIn, lpRatioRaw) => { // The supposed swap is from B0 to R*(B0 + Din) // This test is check_valid_swap for those data let coins = pool.coins; let lpRatio = fixedUtils_1.FixedUtils.directCast(lpRatioRaw); if (lpRatio > 1) return false; let flatness = fixedUtils_1.FixedUtils.directCast(pool.flatness); // balance = balances[i] let balance; let weight; // amount = amountsIn[i] let amount; // postbalance = lpRatio * (balance + amount) let postbalance; // pseudobalance = fee(postbalance - balance) + balance let pseudobalance; // diff = postbalance - balance let diff; // pseudodiff = fee(diff) let pseudodiff; let preprod = 0; let presum = 0; let pseudoprod = 0; let pseudosum = 0; let postprod = 0; let postsum = 0; for (let [coinType, coin] of Object.entries(coins)) { balance = fixedUtils_1.FixedUtils.directCast(coin.normalizedBalance); weight = fixedUtils_1.FixedUtils.directCast(coin.weight); amount = fixedUtils_1.FixedUtils.castAndNormalize(coin.decimalsScalar, amountsIn[coinType] || BigInt(0)); postbalance = lpRatio * (balance + amount); if (postbalance >= balance) { // use fee in diff = postbalance - balance; pseudodiff = diff * (1 - fixedUtils_1.FixedUtils.directCast(coin.tradeFeeIn)); pseudobalance = balance + pseudodiff; } else { // use fee out diff = balance - postbalance; pseudodiff = diff === 0 ? 0 : diff / (1 - fixedUtils_1.FixedUtils.directCast(coin.tradeFeeOut)); if (pseudodiff >= balance + 1) return false; pseudobalance = balance - pseudodiff; } preprod += weight * Math.log(balance); presum += weight * balance; postprod += weight * Math.log(postbalance); postsum += weight * postbalance; pseudoprod += weight * Math.log(pseudobalance); pseudosum += weight * pseudobalance; } preprod = Math.exp(preprod); postprod = Math.exp(postprod); pseudoprod = Math.exp(pseudoprod); let preinvariant = CmmmCalculations.calcInvariantQuadratic(preprod, presum, flatness); let postinvariant = CmmmCalculations.calcInvariantQuadratic(postprod, postsum, flatness); let pseudoinvariant = CmmmCalculations.calcInvariantQuadratic(pseudoprod, pseudosum, flatness); return (postinvariant * (1 + CmmmCalculations.tolerance) >= preinvariant &&