Skip to content

Commit

Permalink
return SwapToRatioResponse narrowing types + absoluteValue fix (Unisw…
Browse files Browse the repository at this point in the history
  • Loading branch information
ewilz authored Nov 19, 2021
1 parent 5006770 commit 1c9f1d3
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 60 deletions.
67 changes: 39 additions & 28 deletions cli/commands/quote-to-ratio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { Currency, Ether, Fraction, Percent } from '@uniswap/sdk-core';
import { Position } from '@uniswap/v3-sdk';
import dotenv from 'dotenv';
import { ethers } from 'ethers';
import { ID_TO_CHAIN_ID, parseAmount, SwapRoute } from '../../src';
import {
ID_TO_CHAIN_ID,
parseAmount,
SwapToRatioResponse,
SwapToRatioStatus,
} from '../../src';
import { BaseCommand } from '../base-command';

dotenv.config();
Expand Down Expand Up @@ -98,7 +103,7 @@ export class QuoteToRatio extends BaseCommand {
liquidity: 1,
});

let swapRoutes: SwapRoute | null;
let swapRoutes: SwapToRatioResponse;
swapRoutes = await router.routeToRatio(
tokenInBalance,
tokenOutBalance,
Expand Down Expand Up @@ -127,37 +132,43 @@ export class QuoteToRatio extends BaseCommand {
}
);

if (!swapRoutes) {
if (swapRoutes.status === SwapToRatioStatus.SUCCESS) {
const {
blockNumber,
estimatedGasUsed,
estimatedGasUsedQuoteToken,
estimatedGasUsedUSD,
gasPriceWei,
methodParameters,
quote,
quoteGasAdjusted,
route: routeAmounts,
} = swapRoutes.result;

this.logSwapResults(
routeAmounts,
quote,
quoteGasAdjusted,
estimatedGasUsedQuoteToken,
estimatedGasUsedUSD,
methodParameters,
blockNumber,
estimatedGasUsed,
gasPriceWei
);
return;
} else if (swapRoutes.status === SwapToRatioStatus.NO_ROUTE_FOUND) {
log.error(
`Could not find route. ${
`${swapRoutes.error}. ${
debug ? '' : 'Run in debug mode for more info'
}.`
);
return;
} else if (swapRoutes.status === SwapToRatioStatus.NO_SWAP_NEEDED) {
log.error(
`no swap needed. ${debug ? '' : 'Run in debug mode for more info'}.`
);
return;
}

const {
blockNumber,
estimatedGasUsed,
estimatedGasUsedQuoteToken,
estimatedGasUsedUSD,
gasPriceWei,
methodParameters,
quote,
quoteGasAdjusted,
route: routeAmounts,
} = swapRoutes;

this.logSwapResults(
routeAmounts,
quote,
quoteGasAdjusted,
estimatedGasUsedQuoteToken,
estimatedGasUsedUSD,
methodParameters,
blockNumber,
estimatedGasUsed,
gasPriceWei
);
}
}
43 changes: 33 additions & 10 deletions src/routers/alpha-router/alpha-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ import {
ISwapToRatio,
SwapConfig,
SwapRoute,
SwapToRatioRoute,
SwapToRatioStatus,
SwapToRatioResponse,
} from '../router';
import {
RouteWithValidQuote,
Expand Down Expand Up @@ -323,7 +324,7 @@ export class AlphaRouter
swapAndAddConfig: SwapAndAddConfig,
swapConfig?: SwapConfig,
routingConfig: Partial<AlphaRouterConfig> = DEFAULT_CONFIG
): Promise<SwapToRatioRoute | null> {
): Promise<SwapToRatioResponse> {
if (
token1Balance.currency.wrapped.sortsBefore(token0Balance.currency.wrapped)
) {
Expand Down Expand Up @@ -367,7 +368,11 @@ export class AlphaRouter
while (!ratioAchieved) {
n++;
if (n > swapAndAddConfig.maxIterations) {
return null;
log.info('max iterations exceeded')
return {
status: SwapToRatioStatus.NO_ROUTE_FOUND,
error: 'max iterations exceeded'
}
}

let amountToSwap = calculateRatioAmountIn(
Expand All @@ -376,6 +381,12 @@ export class AlphaRouter
inputBalance,
outputBalance
);
if (amountToSwap.equalTo(0)) {
log.info(`no swap needed`)
return {
status: SwapToRatioStatus.NO_SWAP_NEEDED,
}
}

swap = await this.route(
amountToSwap,
Expand All @@ -386,7 +397,10 @@ export class AlphaRouter
);

if (!swap) {
return null;
return {
status: SwapToRatioStatus.NO_ROUTE_FOUND,
error: 'no route found'
}
}

let inputBalanceUpdated = inputBalance.subtract(swap.trade!.inputAmount);
Expand Down Expand Up @@ -447,10 +461,16 @@ export class AlphaRouter
}

if (!swap) {
return null;
return {
status: SwapToRatioStatus.NO_ROUTE_FOUND,
error: 'no route found'
}
}

return { ...swap, optimalRatio, postSwapTargetPool };
return {
status: SwapToRatioStatus.SUCCESS,
result: { ...swap, optimalRatio, postSwapTargetPool }
}
}

public async route(
Expand Down Expand Up @@ -1170,9 +1190,12 @@ export class AlphaRouter
}

private absoluteValue(fraction: Fraction): Fraction {
if (fraction.lessThan(0)) {
return fraction.multiply(-1);
}
return fraction;
const numeratorAbs = JSBI.lessThan(fraction.numerator, JSBI.BigInt(0))
? JSBI.unaryMinus(fraction.numerator)
: fraction.numerator
const denominatorAbs = JSBI.lessThan(fraction.denominator, JSBI.BigInt(0))
? JSBI.unaryMinus(fraction.denominator)
: fraction.denominator
return new Fraction(numeratorAbs, denominatorAbs)
}
}
29 changes: 27 additions & 2 deletions src/routers/router.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Trade } from '@uniswap/router-sdk';
import {
Currency,
Fraction,
Percent,
Token,
TradeType,
} from '@uniswap/sdk-core';
import { Trade } from '@uniswap/router-sdk';
import { Route as V2RouteRaw } from '@uniswap/v2-sdk';
import {
MethodParameters,
Expand Down Expand Up @@ -38,6 +38,31 @@ export type SwapToRatioRoute = SwapRoute & {
postSwapTargetPool: Pool;
};

export enum SwapToRatioStatus {
SUCCESS = 1,
NO_ROUTE_FOUND = 2,
NO_SWAP_NEEDED = 3,
}

export type SwapToRatioSuccess = {
status: SwapToRatioStatus.SUCCESS;
result: SwapToRatioRoute;
};

export type SwapToRatioFail = {
status: SwapToRatioStatus.NO_ROUTE_FOUND;
error: string;
};

export type SwapToRatioNoSwapNeeded = {
status: SwapToRatioStatus.NO_SWAP_NEEDED;
};

export type SwapToRatioResponse =
| SwapToRatioSuccess
| SwapToRatioFail
| SwapToRatioNoSwapNeeded;

export type SwapConfig = {
recipient: string;
slippageTolerance: Percent;
Expand Down Expand Up @@ -76,5 +101,5 @@ export abstract class ISwapToRatio<RoutingConfig, SwapAndAddConfig> {
swapAndAddConfig: SwapAndAddConfig,
swapConfig?: SwapConfig,
routingConfig?: RoutingConfig
): Promise<SwapToRatioRoute | null>;
): Promise<SwapToRatioResponse>;
}
107 changes: 87 additions & 20 deletions test/unit/routers/alpha-router/alpha-router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ETHGasStationInfoProvider,
parseAmount,
SwapAndAddConfig,
SwapToRatioStatus,
TokenProvider,
UniswapMulticallProvider,
USDC_MAINNET as USDC,
Expand Down Expand Up @@ -1116,7 +1117,7 @@ describe('alpha router', () => {

const spy = sinon.spy(alphaRouter, 'route');

const result = await alphaRouter.routeToRatio(
const route = await alphaRouter.routeToRatio(
token0Balance,
token1Balance,
position,
Expand All @@ -1125,19 +1126,23 @@ describe('alpha router', () => {
ROUTING_CONFIG
);

expect(result!.optimalRatio).toBeDefined();
expect(result!.postSwapTargetPool).toBeDefined();
if (route.status === SwapToRatioStatus.SUCCESS) {
expect(route.result.optimalRatio).toBeDefined();
expect(route.result.postSwapTargetPool).toBeDefined();

const exactAmountInBalance = parseAmount('7.5', USDC);
const exactAmountInBalance = parseAmount('7.5', USDC);

const exactInputParameters = spy.firstCall.args;
expect(exactInputParameters[0]).toEqual(exactAmountInBalance);
expect(exactInputParameters[1]).toEqual(token1Balance.currency);
const exactInputParameters = spy.firstCall.args;
expect(exactInputParameters[0]).toEqual(exactAmountInBalance);
expect(exactInputParameters[1]).toEqual(token1Balance.currency);
} else {
throw('routeToRatio unsuccessful')
}
});

test('with out of range position calls routeExactIn with correct parameters', async () => {
const token0Balance = parseAmount('20', USDC);
const token1Balance = parseAmount('5', USDT);
const token1Balance = parseAmount('0', USDT);

const position = new Position({
pool: USDC_USDT_MEDIUM,
Expand Down Expand Up @@ -1281,12 +1286,65 @@ describe('alpha router', () => {

const exactAmountInBalance = parseAmount('7500000000000', USDC);

const exactInputParameters = spy.firstCall.args;
expect(exactInputParameters[0]).toEqual(exactAmountInBalance);
expect(exactInputParameters[1]).toEqual(token1Balance.currency);
const exactInputParameters = spy.firstCall.args
expect(exactInputParameters[0].currency).toEqual(token0Balance.currency)
expect(exactInputParameters[1]).toEqual(token1Balance.currency)
expect(exactInputParameters[0]).toEqual(exactAmountInBalance)
})
})

test('returns null for range order already fulfilled with token0', async () => {
const token0Balance = parseAmount('50', USDC);
const token1Balance = parseAmount('0', USDT);

const position = new Position({
pool: USDC_USDT_MEDIUM,
tickLower: 60,
tickUpper: 120,
liquidity: 1,
});

const spy = sinon.spy(alphaRouter, 'route')

const result = await alphaRouter.routeToRatio(
token0Balance,
token1Balance,
position,
SWAP_AND_ADD_CONFIG,
undefined,
ROUTING_CONFIG
);

expect(spy.firstCall).toEqual(null)
expect(result.status).toEqual(SwapToRatioStatus.NO_SWAP_NEEDED)
})

test('returns null for range order already fulfilled with token1', async () => {
const token0Balance = parseAmount('0', USDC);
const token1Balance = parseAmount('50', USDT);

const position = new Position({
pool: USDC_USDT_MEDIUM,
tickLower: -120,
tickUpper: -60,
liquidity: 1,
});
});
});

const spy = sinon.spy(alphaRouter, 'route')

const result = await alphaRouter.routeToRatio(
token0Balance,
token1Balance,
position,
SWAP_AND_ADD_CONFIG,
undefined,
ROUTING_CONFIG
);

expect(spy.firstCall).toEqual(null)
expect(result.status).toEqual(SwapToRatioStatus.NO_SWAP_NEEDED)
})
})

describe('iterative scenario', () => {
let spy: sinon.SinonSpy<any[], any>;
Expand Down Expand Up @@ -1336,7 +1394,12 @@ describe('alpha router', () => {
ROUTING_CONFIG
);

expect(swap).toEqual(null);
if (swap.status === SwapToRatioStatus.NO_ROUTE_FOUND) {
expect(swap.status).toEqual(SwapToRatioStatus.NO_ROUTE_FOUND);
expect(swap.error).toEqual('max iterations exceeded');
} else {
throw('routeToRatio: unexpected response')
}
});

describe('when there is excess of token0', () => {
Expand Down Expand Up @@ -1681,12 +1744,16 @@ describe('alpha router', () => {
ROUTING_CONFIG
);

expect(swap?.optimalRatio.toFixed(1)).toEqual(
new Fraction(1, 2).toFixed(1)
);
expect(swap?.postSwapTargetPool.sqrtRatioX96).toEqual(
JSBI.BigInt(oneHalfX96.toString())
);
if (swap.status == SwapToRatioStatus.SUCCESS) {
expect(swap.result.optimalRatio.toFixed(1)).toEqual(
new Fraction(1, 2).toFixed(1)
);
expect(swap.result.postSwapTargetPool.sqrtRatioX96).toEqual(
JSBI.BigInt(oneHalfX96.toString())
);
} else {
throw('swap was not successful')
}
});

test('when trade moves sqrtPrice in target pool out of range it calls again with new optimalRatio of 0', async () => {
Expand Down

0 comments on commit 1c9f1d3

Please sign in to comment.