UNPKG

rubic-sdk

Version:
448 lines • 18.7 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SyncSwapRouter = void 0; const ethers_1 = require("ethers"); const blockchain_1 = require("../../../../../../../../common/utils/blockchain"); const decorators_1 = require("../../../../../../../../common/utils/decorators"); const injector_1 = require("../../../../../../../../core/injector/injector"); const sync_swap_stable_pool_1 = require("../sync-swap-stable-pool"); const constants_1 = require("./constants"); const sync_swap_path_factory_1 = require("./sync-swap-path-factory"); const typings_1 = require("./typings"); class SyncSwapRouter { static async findBestAmountsForPathsExactIn(paths, amountInString, blockchainName, _ts) { const amountIn = ethers_1.BigNumber.from(amountInString); const pathAmounts = await SyncSwapRouter.splitAmount(amountIn, paths.length); const groups = []; const groupPromises = []; for (const amounts of pathAmounts) { const promise = new Promise((resolve, reject) => { SyncSwapRouter.calculateGroupAmounts(paths, amounts, blockchainName).then(group => { if (group === null) { reject(new Error('expired')); } else { groups.push(group); resolve(true); } }); }); groupPromises.push(promise); } await Promise.all(groupPromises); let bestPathsWithAmounts = []; let bestAmountOut = constants_1.ZERO; let bestQuoteOut = constants_1.ZERO; let bestPriceImpact = null; for (const group of groups) { const groupAmountOut = group.amountOut; if (!groupAmountOut.isZero() && !group.quoteOut.isZero()) { const amountLoss = group.quoteOut.sub(groupAmountOut); const groupPriceImpact = amountLoss.mul(constants_1.ETHER).div(group.quoteOut); if (bestPriceImpact === null || groupPriceImpact.lt(bestPriceImpact)) { bestPriceImpact = groupPriceImpact; } if (groupAmountOut.gt(bestAmountOut)) { bestPathsWithAmounts = group.pathsWithAmounts; bestAmountOut = groupAmountOut; bestQuoteOut = group.quoteOut; } } } return { found: bestAmountOut !== null, direction: typings_1.routeDirection.EXACT_IN, pathsWithAmounts: bestPathsWithAmounts, amountIn, amountOut: bestAmountOut, quote: bestQuoteOut, bestPriceImpact, gas: constants_1.ZERO }; } static copyStep(step) { const pool = step.pool; return { pool: { isAlpha: pool.isAlpha, vault: pool.vault, pool: pool.pool, tokenA: pool.tokenA, tokenB: pool.tokenB, poolType: pool.poolType, reserveA: ethers_1.BigNumber.from(pool.reserveA), reserveB: ethers_1.BigNumber.from(pool.reserveB), swapFeeAB: pool.swapFeeAB, swapFeeBA: pool.swapFeeBA }, tokenIn: step.tokenIn, swapFee: step.swapFee }; } static getAmountOutClassic(params, checkOverflow) { const amountIn = params.amount; const reserveIn = params.reserveIn; if (checkOverflow && reserveIn.add(amountIn).gt(constants_1.UINT128_MAX)) { throw Error('overflow'); } const amountInWithFee = amountIn.mul(constants_1.MAX_FEE.sub(params.swapFee)); if (checkOverflow && amountInWithFee.gt(constants_1.UINT256_MAX)) { throw Error('overflow'); } const numerator = amountInWithFee.mul(params.reserveOut); if (checkOverflow && numerator.gt(constants_1.UINT256_MAX)) { throw Error('overflow'); } const denominator = params.reserveIn.mul(constants_1.MAX_FEE).add(amountInWithFee); if (checkOverflow && denominator.gt(constants_1.UINT256_MAX)) { throw Error('overflow'); } return numerator.div(denominator); } static computeDFromAdjustedBalances(A, xp0, xp1, checkOverflow) { const s = xp0.add(xp1); if (s.isZero()) { return constants_1.ZERO; } let prevD; let d = s; const nA = A.mul(constants_1.TWO); for (let i = 0; i < constants_1.MAX_LOOP_LIMIT; i++) { const dSq = d.mul(d); if (checkOverflow && dSq.gt(constants_1.UINT256_MAX)) { throw Error('overflow'); } const d2 = dSq.div(xp0).mul(d); if (checkOverflow && d2.gt(constants_1.UINT256_MAX)) { throw Error('overflow'); } const dP = d2.div(xp1).div(constants_1.FOUR); prevD = d; const d0 = nA.mul(s).add(dP.mul(constants_1.TWO)).mul(d); if (checkOverflow && d0.gt(constants_1.UINT256_MAX)) { throw Error('overflow'); } d = d0.div(nA.sub(constants_1.ONE).mul(d).add(dP.mul(constants_1.THREE))); if (d.sub(prevD).abs().lte(constants_1.ONE)) { return d; } } return d; } static getY(A, x, d) { const nA = A.mul(constants_1.TWO); const c = d.mul(d).div(x.mul(constants_1.TWO)).mul(d).div(nA.mul(constants_1.TWO)); const b = d.div(nA).add(x); let yPrev; let y = d; for (let i = 0; i < constants_1.MAX_LOOP_LIMIT; i++) { yPrev = y; y = y.mul(y).add(c).div(y.mul(constants_1.TWO).add(b).sub(d)); if (y.sub(yPrev).abs().lte(constants_1.ONE)) { break; } } return y; } static getAmountOutStable(params, checkOverflow) { const adjustedReserveIn = params.reserveIn.mul(params.tokenInPrecisionMultiplier); if (checkOverflow && adjustedReserveIn.gt(constants_1.MAX_XP)) { throw Error('overflow'); } const adjustedReserveOut = params.reserveOut.mul(params.tokenOutPrecisionMultiplier); if (checkOverflow && adjustedReserveOut.gt(constants_1.MAX_XP)) { throw Error('overflow'); } const amountIn = params.amount; const feeDeductedAmountIn = amountIn.sub(amountIn.mul(params.swapFee).div(constants_1.MAX_FEE)); const d = SyncSwapRouter.computeDFromAdjustedBalances(constants_1.STABLE_POOL_A, adjustedReserveIn, adjustedReserveOut, checkOverflow); const x = adjustedReserveIn.add(feeDeductedAmountIn.mul(params.tokenInPrecisionMultiplier)); const y = SyncSwapRouter.getY(constants_1.STABLE_POOL_A, x, d); const dy = adjustedReserveOut.sub(y).sub(1); return dy.div(params.tokenOutPrecisionMultiplier); } static calculateAmountOut(params, checkOverflow) { if (params.amount.isZero()) { return constants_1.ZERO; } let amountOut; try { if (params.stable) { amountOut = SyncSwapRouter.getAmountOutStable(params, checkOverflow); } else { amountOut = SyncSwapRouter.getAmountOutClassic(params, checkOverflow); } } catch (error) { return constants_1.ZERO; } return amountOut; } static splitAmounts2(amount, granularity) { const oneSplit = amount.div(granularity); if (oneSplit.isZero()) { return []; } const amounts = []; for (let i = 0; i <= granularity; i++) { const a = oneSplit.mul(i); const b = oneSplit.mul(granularity - i); amounts.push([a, b]); } return amounts; } static splitAmounts3(amount, granularity) { const oneSplit = amount.div(granularity); if (oneSplit.isZero()) { return []; } const amounts = []; for (let i = 0; i <= granularity; i++) { const a = oneSplit.mul(i); const remaining = granularity - i; for (let j = 0; j <= remaining; j++) { const b = oneSplit.mul(j); const c = oneSplit.mul(remaining - j); amounts.push([a, b, c]); } } return amounts; } static splitAmounts4(amount, granularity) { const oneSplit = amount.div(granularity); if (oneSplit.isZero()) { return []; } const amounts = []; for (let i = 0; i <= granularity; i++) { const a = oneSplit.mul(i); const remaining = granularity - i; for (let j = 0; j <= remaining; j++) { const b = oneSplit.mul(j); const remaining2 = remaining - j; for (let k = 0; k <= remaining2; k++) { const c = oneSplit.mul(k); const d = oneSplit.mul(remaining2 - k); amounts.push([a, b, c, d]); } } } return amounts; } static splitAmounts5(amount, granularity) { const oneSplit = amount.div(granularity); if (oneSplit.isZero()) { return []; } const amounts = []; for (let i = 0; i <= granularity; i++) { const a = oneSplit.mul(i); const remaining = granularity - i; for (let j = 0; j <= remaining; j++) { const b = oneSplit.mul(j); const remaining2 = remaining - j; for (let k = 0; k <= remaining2; k++) { const c = oneSplit.mul(k); const remaining3 = remaining2 - k; for (let l = 0; l <= remaining3; l++) { const d = oneSplit.mul(l); const e = oneSplit.mul(remaining3 - l); amounts.push([a, b, c, d, e]); } } } } return amounts; } static fixSplitAmounts(amount, amounts) { for (const group of amounts) { let sum = constants_1.ZERO; for (const amount of group) { sum = sum.add(amount); } if (!sum.eq(amount)) { const diff = amount.sub(sum); for (const amount of group) { // only add diff to non-zero amount if (!amount.isZero()) { group[0] = group[0].add(diff); break; } } } } return amounts; } static async splitAmount(amount, parts) { if (parts === 0) { return []; } if (parts === 1) { return [[amount]]; } if (parts === 2) { return SyncSwapRouter.fixSplitAmounts(amount, SyncSwapRouter.splitAmounts2(amount, constants_1.granularity)); } if (parts === 3) { return SyncSwapRouter.fixSplitAmounts(amount, SyncSwapRouter.splitAmounts3(amount, constants_1.granularity)); } if (parts === 4) { return SyncSwapRouter.fixSplitAmounts(amount, SyncSwapRouter.splitAmounts4(amount, constants_1.granularity)); } if (parts === 5) { return SyncSwapRouter.fixSplitAmounts(amount, SyncSwapRouter.splitAmounts5(amount, constants_1.granularity)); } throw Error('Unsupported split parts'); } static async calculateAmountOutForStep(step, amountIn, quoteIn, blockchainName) { const isTokenAIn = step.pool.tokenA === step.tokenIn; const [reserveIn, reserveOut] = isTokenAIn ? [step.pool.reserveA, step.pool.reserveB] : [step.pool.reserveB, step.pool.reserveA]; let tokenInPrecisionMultiplier; let tokenOutPrecisionMultiplier; // create multiplier for stable pools const stable = step.pool.poolType === '2'; if (stable) { const [tokenInAddress] = isTokenAIn ? [step.pool.tokenA, step.pool.tokenB] : [step.pool.tokenB, step.pool.tokenA]; const { fromPrecisionMultiplier, toPrecisionMultiplier } = await this.getPoolPrecision(step.pool.pool, tokenInAddress, blockchainName); tokenInPrecisionMultiplier = ethers_1.BigNumber.from(fromPrecisionMultiplier); tokenOutPrecisionMultiplier = ethers_1.BigNumber.from(toPrecisionMultiplier); } else { tokenInPrecisionMultiplier = constants_1.ZERO; tokenOutPrecisionMultiplier = constants_1.ZERO; } const swapFee = ethers_1.BigNumber.from(step.swapFee); const amountOut = SyncSwapRouter.calculateAmountOut({ stable, amount: amountIn, reserveIn, reserveOut, swapFee, tokenInPrecisionMultiplier, tokenOutPrecisionMultiplier }, true); let quoteOut = constants_1.ZERO; let updatedStep = null; if (!amountOut.isZero()) { updatedStep = SyncSwapRouter.copyStep(step); if (isTokenAIn) { updatedStep.pool.reserveA = step.pool.reserveA.add(amountIn); updatedStep.pool.reserveB = step.pool.reserveB.sub(amountOut); } else { updatedStep.pool.reserveB = step.pool.reserveB.add(amountIn); updatedStep.pool.reserveA = step.pool.reserveA.sub(amountOut); } quoteOut = sync_swap_path_factory_1.SyncSwapPathFactory.calculateQuoteOut({ stable, amount: quoteIn, reserveIn, reserveOut, swapFee, tokenInPrecisionMultiplier, tokenOutPrecisionMultiplier }); } return [amountOut, quoteOut, updatedStep]; } static async calculatePathAmountsByInput(path, amountIn, _updateReserves, blockchainName) { const stepsWithAmount = []; let amountInNext = amountIn; let quoteInNext = amountIn; for (let i = 0; i < path.length; i++) { const step = path[i]; const [stepAmountOut, stepQuoteOut, updatedStep] = // eslint-disable-next-line no-await-in-loop await SyncSwapRouter.calculateAmountOutForStep(step, amountInNext, quoteInNext, blockchainName); if (stepAmountOut.isZero()) { return null; } stepsWithAmount.push({ ...step, updatedStep, amountIn: amountInNext }); amountInNext = stepAmountOut; quoteInNext = stepQuoteOut; } const pathAmountOut = amountInNext; const pathQuoteOut = quoteInNext; return { direction: typings_1.routeDirection.EXACT_IN, stepsWithAmount, amountOut: pathAmountOut, amountIn, quote: pathQuoteOut }; } static async calculateGroupAmounts(paths, amounts, blockchainName) { const pathsWithAmounts = []; let amountOut = constants_1.ZERO; let quoteOut = constants_1.ZERO; const groupPaths = paths; const lastSteps = new Map(); for (let i = 0; i < groupPaths.length; i++) { const pathAmountIn = amounts[i]; if (pathAmountIn.isZero()) { // eslint-disable-next-line no-continue continue; } const path = groupPaths[i]; for (let j = 0; j < path.length; j++) { const lastStep = lastSteps.get(path[j].pool.pool); if (lastStep !== undefined) { path[j] = SyncSwapRouter.copyStep(lastStep); } } const pathWithAmounts = // eslint-disable-next-line no-await-in-loop await SyncSwapRouter.calculatePathAmountsByInput(path, pathAmountIn, true, blockchainName); if (pathWithAmounts != null) { for (const step of pathWithAmounts.stepsWithAmount) { if (step.updatedStep !== null) { lastSteps.set(step.pool.pool, step.updatedStep); } } pathsWithAmounts.push(pathWithAmounts); amountOut = amountOut.add(pathWithAmounts.amountOut); quoteOut = quoteOut.add(pathWithAmounts.quote); } } return { pathsWithAmounts, amountOut, quoteOut }; } static async getPoolPrecision(address, fromAddress, blockchainName) { const web3Public = injector_1.Injector.web3PublicService.getWeb3Public(blockchainName); const token0 = await web3Public.callContractMethod(address, sync_swap_stable_pool_1.syncSwapStablePool, 'token0', []); const token0PM = await web3Public.callContractMethod(address, sync_swap_stable_pool_1.syncSwapStablePool, 'token0PrecisionMultiplier', []); const token1PM = await web3Public.callContractMethod(address, sync_swap_stable_pool_1.syncSwapStablePool, 'token1PrecisionMultiplier', []); return (0, blockchain_1.compareAddresses)(token0, fromAddress) ? { fromPrecisionMultiplier: token0PM, toPrecisionMultiplier: token1PM } : { fromPrecisionMultiplier: token1PM, toPrecisionMultiplier: token0PM }; } } exports.SyncSwapRouter = SyncSwapRouter; __decorate([ decorators_1.Cache ], SyncSwapRouter, "getPoolPrecision", null); //# sourceMappingURL=sync-swap-router.js.map