Skip to content

Commit

Permalink
Always include direct swap pools even if not in subgraph
Browse files Browse the repository at this point in the history
  • Loading branch information
willpote committed Nov 29, 2021
1 parent 463c34e commit c2b7ebf
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 26 deletions.
9 changes: 7 additions & 2 deletions src/providers/v2/subgraph-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,14 @@ export class V2SubgraphProvider implements IV2SubgraphProvider {
}
);

// filter pools that have liquidity less than threshold
// Filter pools that have tracked reserve ETH less than threshold.
// trackedReserveETH filters pools that do not involve a pool from this
// allowlist: https://github.com/Uniswap/v2-subgraph/blob/7c82235cad7aee4cfce8ea82f0030af3d224833e/src/mappings/pricing.ts#L43
// Which helps filter pools with manipulated prices/liquidity.
const poolsSanitized: V2SubgraphPool[] = pools
.filter((pool) => parseFloat(pool.trackedReserveETH) > threshold)
.filter((pool) => {
return parseFloat(pool.trackedReserveETH) > threshold;
})
.map((pool) => {
return {
...pool,
Expand Down
69 changes: 55 additions & 14 deletions src/routers/alpha-router/functions/get-candidate-pools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
V3SubgraphPool,
} from '../../../providers/v3/subgraph-provider';
import { ChainId } from '../../../util';
import { parseFeeAmount } from '../../../util/amounts';
import { parseFeeAmount, unparseFeeAmount } from '../../../util/amounts';
import { log } from '../../../util/log';
import { metric, MetricLoggerUnit } from '../../../util/metric';
import { AlphaRouterConfig } from '../alpha-router';
Expand Down Expand Up @@ -224,7 +224,7 @@ export async function getV3CandidatePools({
addToAddressSet(topByBaseWithTokenOut);
}

const top2DirectSwapPool = _(subgraphPoolsSorted)
let top2DirectSwapPool = _(subgraphPoolsSorted)
.filter((subgraphPool) => {
return (
!poolAddressesSoFar.has(subgraphPool.id) &&
Expand All @@ -237,6 +237,35 @@ export async function getV3CandidatePools({
.slice(0, topNDirectSwaps)
.value();

if (top2DirectSwapPool.length == 0 && topNDirectSwaps > 0) {
// If we requested direct swap pools but did not find any in the subgraph query.
// Optimistically add them into the query regardless. Invalid pools ones will be dropped anyway
// when we query the pool on-chain. Ensures that new pools for new pairs can be swapped on immediately.
top2DirectSwapPool = _.map(
[FeeAmount.HIGH, FeeAmount.MEDIUM, FeeAmount.LOW, FeeAmount.LOWEST],
(feeAmount) => {
const { token0, token1, poolAddress } = poolProvider.getPoolAddress(
tokenIn,
tokenOut,
feeAmount
);
return {
id: poolAddress,
feeTier: unparseFeeAmount(feeAmount),
liquidity: '10000',
token0: {
id: token0.address,
},
token1: {
id: token1.address,
},
tvlETH: 10000,
tvlUSD: 10000,
};
}
);
}

addToAddressSet(top2DirectSwapPool);

const wethAddress = WETH9[chainId]!.address;
Expand Down Expand Up @@ -606,18 +635,30 @@ export async function getV2CandidatePools({
addToAddressSet(topByBaseWithTokenOut);
}

const top2DirectSwapPool = _(subgraphPoolsSorted)
.filter((subgraphPool) => {
return (
!poolAddressesSoFar.has(subgraphPool.id) &&
((subgraphPool.token0.id == tokenInAddress &&
subgraphPool.token1.id == tokenOutAddress) ||
(subgraphPool.token1.id == tokenInAddress &&
subgraphPool.token0.id == tokenOutAddress))
);
})
.slice(0, topNDirectSwaps)
.value();
// Always add the direct swap pool into the mix regardless of if it exists in the subgraph pool list.
// Ensures that new pools can be swapped on immediately, and that if a pool was filtered out of the
// subgraph query for some reason (e.g. trackedReserveETH was 0), then we still consider it.
let top2DirectSwapPool: V2SubgraphPool[] = [];
if (topNDirectSwaps != 0) {
const { token0, token1, poolAddress } = poolProvider.getPoolAddress(
tokenIn,
tokenOut
);

top2DirectSwapPool = [
{
id: poolAddress,
token0: {
id: token0.address,
},
token1: {
id: token1.address,
},
supply: 10000, // Not used. Set to arbitrary number.
reserve: 10000, // Not used. Set to arbitrary number.
},
];
}

addToAddressSet(top2DirectSwapPool);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TradeType } from '@uniswap/sdk-core';
import { FeeAmount } from '@uniswap/v3-sdk';
import { Token, TradeType } from '@uniswap/sdk-core';
import { encodeSqrtRatioX96, FeeAmount, Pool } from '@uniswap/v3-sdk';
import _ from 'lodash';
import sinon from 'sinon';
import {
Expand Down Expand Up @@ -59,6 +59,15 @@ describe('get candidate pools', () => {
forceCrossProtocol: false,
};

const mockTokens = [USDC, DAI, WETH9[1], USDT];
const mockPools = [
USDC_DAI_LOW,
USDC_DAI_MEDIUM,
USDC_WETH_LOW,
WETH9_USDT_LOW,
DAI_USDT_LOW,
];

beforeEach(() => {
mockTokenProvider = sinon.createStubInstance(TokenProvider);
mockV3PoolProvider = sinon.createStubInstance(V3PoolProvider);
Expand All @@ -67,21 +76,22 @@ describe('get candidate pools', () => {
CachingTokenListProvider
);

const mockTokens = [USDC, DAI, WETH9[1], USDT];
const mockPools = [
USDC_DAI_LOW,
USDC_DAI_MEDIUM,
USDC_WETH_LOW,
WETH9_USDT_LOW,
DAI_USDT_LOW,
];
const mockSubgraphPools: V3SubgraphPool[] = _.map(
mockPools,
poolToV3SubgraphPool
);

mockV3SubgraphProvider.getPools.resolves(mockSubgraphPools);
mockV3PoolProvider.getPools.resolves(buildMockV3PoolAccessor(mockPools));
mockV3PoolProvider.getPoolAddress.callsFake(
(t1: Token, t2: Token, f: FeeAmount) => {
return {
poolAddress: Pool.getAddress(t1, t2, f),
token0: t1.sortsBefore(t2) ? t1 : t2,
token1: t1.sortsBefore(t2) ? t2 : t1,
};
}
);
mockTokenProvider.getTokens.resolves(buildMockTokenAccessor(mockTokens));
});

Expand Down Expand Up @@ -165,4 +175,61 @@ describe('get candidate pools', () => {
])
).toBeTruthy();
});

test('succeeds to get direct swap pools even if they dont exist in the subgraph', async () => {
// Mock so that DAI_WETH exists on chain, but not in the subgraph
const poolsOnSubgraph = [
USDC_DAI_LOW,
USDC_DAI_MEDIUM,
USDC_WETH_LOW,
WETH9_USDT_LOW,
DAI_USDT_LOW,
];

const subgraphPools: V3SubgraphPool[] = _.map(
poolsOnSubgraph,
poolToV3SubgraphPool
);

mockV3SubgraphProvider.getPools.resolves(subgraphPools);

const DAI_WETH_LOW = new Pool(
DAI,
WETH9[1],
FeeAmount.LOW,
encodeSqrtRatioX96(1, 1),
10,
0
);
mockV3PoolProvider.getPools.resolves(
buildMockV3PoolAccessor([...poolsOnSubgraph, DAI_WETH_LOW])
);

await getV3CandidatePools({
tokenIn: WETH9[1],
tokenOut: DAI,
routeType: TradeType.EXACT_INPUT,
routingConfig: {
...ROUTING_CONFIG,
v3PoolSelection: {
...ROUTING_CONFIG.v3PoolSelection,
topNDirectSwaps: 1,
},
},
poolProvider: mockV3PoolProvider,
subgraphProvider: mockV3SubgraphProvider,
tokenProvider: mockTokenProvider,
blockedTokenListProvider: mockBlockTokenListProvider,
chainId: ChainId.MAINNET,
});

expect(
mockV3PoolProvider.getPools.calledWithExactly([
[DAI, WETH9[1], FeeAmount.HIGH],
[DAI, WETH9[1], FeeAmount.MEDIUM],
[DAI, WETH9[1], FeeAmount.LOW],
[DAI, WETH9[1], FeeAmount.LOWEST],
])
).toBeTruthy();
});
});

0 comments on commit c2b7ebf

Please sign in to comment.