Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tri hops with mid pool #411

Merged
merged 6 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/routeProposal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
SorConfig,
PoolDictionary,
} from '../types';
import { getTriPaths } from './triPaths';

export class RouteProposer {
cache: Record<string, { paths: NewPath[] }> = {};
Expand Down Expand Up @@ -79,9 +80,17 @@ export class RouteProposer {
this.config
);

const triPaths = getTriPaths(
tokenIn,
tokenOut,
poolsAllDict,
this.config.triPathMidPoolIds ?? []
);

const combinedPathData = pathData
.concat(...boostedPaths)
.concat(...pathsUsingStaBal);
.concat(...pathsUsingStaBal)
.concat(...triPaths);
const [paths] = calculatePathLimits(combinedPathData, swapType);

this.cache[`${tokenIn}${tokenOut}${swapType}${swapOptions.timestamp}`] =
Expand Down
164 changes: 164 additions & 0 deletions src/routeProposal/triPaths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { PoolDictionary, NewPath, PoolBase, PoolPairBase } from '../types';
import { createPath, getHighestLiquidityPool } from './filtering';

type TokenWithPools = {
token: string;
mostLiquidPoolIn: PoolBase<PoolPairBase> | null;
mostLiquidPoolOut: PoolBase<PoolPairBase> | null;
};

/**
* For each midpool construct 3 hop paths like: tokenIn[poolA]tokenA[MidPool]tokenB[poolB]tokenOut.
* tokenA/B are midpool pool tokens.
* poolA/B are most liquid pools connecting tokenIn/Out to tokenA/B.
* @param tokenIn
* @param tokenOut
* @param poolsAllDict
* @param midPoolsId
* @returns
*/
export function getTriPaths(
tokenIn: string,
tokenOut: string,
poolsAllDict: PoolDictionary,
midPoolsId: string[]
): NewPath[] {
const triPaths: NewPath[] = [];
midPoolsId.forEach((midPoolId) => {
const midPoolTriPaths = getMidPoolTriPaths(
tokenIn,
tokenOut,
poolsAllDict,
midPoolId
);
triPaths.push(...midPoolTriPaths);
});
return triPaths;
}

function getMidPoolTriPaths(
tokenIn: string,
tokenOut: string,
poolsAllDict: PoolDictionary,
midPoolId: string
): NewPath[] {
// We only want to use a pool as middle hop if tokenIn/Out aren't it's pool tokens as normal algo should take care of that path.
const midPool = getValidPool(tokenIn, tokenOut, poolsAllDict, midPoolId);
if (midPool === null) return [];
// For each midPool pool token find the most liquid pool connecting tokenIn/Out
const tokenPools = getTokenPools(tokenIn, tokenOut, poolsAllDict, midPool);
// Construct all possible paths via midPool using most liquid connecting pools
return constructPaths(tokenIn, tokenOut, tokenPools, midPool);
}

/**
* Construct all possible paths for tokenIn>tokenOut via midPool using most liquid connecting pools
* @param tokenIn
* @param tokenOut
* @param tokensWithPools
* @param midPool
* @returns
*/
function constructPaths(
tokenIn: string,
tokenOut: string,
tokensWithPools: TokenWithPools[],
midPool: PoolBase<PoolPairBase>
): NewPath[] {
const paths: NewPath[] = [];
// For each valid mostLiquidPoolIn create a path via midPool and any valid mostLiquidPoolOut
tokensWithPools.forEach((tokenWithPoolsIn, i) => {
const mostLiquidPoolIn = tokenWithPoolsIn.mostLiquidPoolIn;
if (!mostLiquidPoolIn) return;
const remainingTokensWithPools = [
...tokensWithPools.slice(0, i),
...tokensWithPools.slice(i + 1),
];
remainingTokensWithPools.forEach((tokenWithPoolsOut) => {
if (!tokenWithPoolsOut.mostLiquidPoolOut) return;
// console.log(
// `tokenIn[${mostLiquidPoolIn.id}]${tokenWithPoolsIn.token}[${midPool.id}]${tokenWithPoolsOut.token}[${tokenWithPoolsOut.mostLiquidPoolOut.id}]tokenOut`
// );
const tokens = [
tokenIn,
tokenWithPoolsIn.token,
tokenWithPoolsOut.token,
tokenOut,
];
const pools = [
mostLiquidPoolIn,
midPool,
tokenWithPoolsOut.mostLiquidPoolOut,
];
paths.push(createPath(tokens, pools));
});
});
return paths;
}

/**
* For each token in pool find the most liquid pool connecting tokenIn/Out
* @param tokenIn
* @param tokenOut
* @param poolsAllDict
* @param pool
* @returns
*/
function getTokenPools(
tokenIn: string,
tokenOut: string,
poolsAllDict: PoolDictionary,
pool: PoolBase<PoolPairBase>
): TokenWithPools[] {
const tokenPools: TokenWithPools[] = pool.tokensList.map((token) => {
return { token, mostLiquidPoolIn: null, mostLiquidPoolOut: null };
});

tokenPools.forEach((t) => {
const mostLiquidInId = getHighestLiquidityPool(
tokenIn,
t.token,
poolsAllDict
);
const mostLiquidOutId = getHighestLiquidityPool(
t.token,
tokenOut,
poolsAllDict
);
t.mostLiquidPoolIn = mostLiquidInId
? poolsAllDict[mostLiquidInId]
: null;
t.mostLiquidPoolOut = mostLiquidOutId
? poolsAllDict[mostLiquidOutId]
: null;
});
return tokenPools;
}

/**
* We only want to use a pool as middle hop if tokenIn/Out aren't it's pool tokens as normal algo should take care of that path.
* @param tokenIn
* @param tokenOut
* @param poolsAllDict
* @param poolId
* @returns
*/
function getValidPool(
tokenIn: string,
tokenOut: string,
poolsAllDict: PoolDictionary,
poolId: string
): PoolBase<PoolPairBase> | null {
const pool = poolsAllDict[poolId];
if (!pool) return null;
if (
pool.tokensList.some(
(t) =>
t.toLowerCase() === tokenIn.toLowerCase() ||
t.toLowerCase() === tokenOut.toLowerCase()
)
) {
return null;
}
return pool;
}
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface SorConfig {
usdcConnectingPool?: { id: string; usdc: string };
wETHwstETH?: { id: string; address: string };
lbpRaisingTokens?: string[];
triPathMidPoolIds?: string[];
}

export type NoNullableField<T> = {
Expand Down
1 change: 1 addition & 0 deletions test/lib/subgraphPoolDataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export const Query: { [chainId: number]: string } = {
100: queryWithLinear,
1101: queryWithLinear,
43114: queryWithLinear,
8453: queryWithLinear,
};

export class SubgraphPoolDataService implements PoolDataService {
Expand Down
54 changes: 54 additions & 0 deletions test/testScripts/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum Network {
GNOSIS = 100,
ZKEVM = 1101,
AVALANCHE = 43114,
BASE = 8453,
}

export const SOR_CONFIG: Record<Network, SorConfig> = {
Expand Down Expand Up @@ -141,6 +142,24 @@ export const SOR_CONFIG: Record<Network, SorConfig> = {
},
],
},
[Network.BASE]: {
chainId: Network.BASE,
vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8',
weth: '0x4200000000000000000000000000000000000006',
connectingTokens: [
{
symbol: 'WETH',
address: '0x4200000000000000000000000000000000000006',
},
],
staBal3Pool: {
id: '0x6fbfcf88db1aada31f34215b2a1df7fafb4883e900000000000000000000000c',
address: '0x6FbFcf88DB1aADA31F34215b2a1Df7fafb4883e9',
},
triPathMidPoolIds: [
'0x2db50a0e0310723ef0c2a165cb9a9f80d772ba2f00020000000000000000000d',
],
},
};

export const PROVIDER_URLS = {
Expand All @@ -151,6 +170,7 @@ export const PROVIDER_URLS = {
[Network.GNOSIS]: process.env.RPC_URL_GNOSIS,
[Network.ZKEVM]: process.env.RPC_URL_ZKEVM,
[Network.AVALANCHE]: process.env.RPC_URL_AVALANCHE,
[Network.BASE]: process.env.RPC_URL_BASE,
};

export const MULTIADDR: { [chainId: number]: string } = {
Expand All @@ -165,6 +185,7 @@ export const MULTIADDR: { [chainId: number]: string } = {
100: '0xbb6fab6b627947dae0a75808250d8b2652952cb5',
1101: '0xca11bde05977b3631167028862be2a173976ca11',
43114: '0xcA11bde05977b3631167028862bE2a173976CA11',
8453: '0xcA11bde05977b3631167028862bE2a173976CA11',
};

export const SUBGRAPH_URLS = {
Expand All @@ -178,6 +199,7 @@ export const SUBGRAPH_URLS = {
[Network.GNOSIS]: `https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gnosis-chain-v2`,
[Network.ZKEVM]: `https://api.studio.thegraph.com/query/24660/balancer-polygon-zk-v2/version/latest`,
[Network.AVALANCHE]: `https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-avalanche-v2`,
[Network.BASE]: `https://api.studio.thegraph.com/query/24660/balancer-base-v2/version/latest`,
};

// This is the same across networks
Expand Down Expand Up @@ -551,10 +573,42 @@ export const ADDRESSES = {
decimals: 18,
symbol: 'sAVAX',
},
BPT: {
address: '0xA154009870E9B6431305F19b09F9cfD7284d4E7A',
decimals: 18,
symbol: 'BPT',
},
STETH: {
address: 'TOD',
decimals: 6,
symbol: 'stETH',
},
},
[Network.BASE]: {
STETH: {
address: 'TOD0',
decimals: 18,
symbol: 'stETH',
},
WETH: {
address: '0x4200000000000000000000000000000000000006',
decimals: 18,
symbol: 'WETH',
},
USDC: {
address: '0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA',
decimals: 6,
symbol: 'USDC',
},
DAI: {
address: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb',
decimals: 18,
symbol: 'DAI',
},
BALD: {
address: '0x27D2DECb4bFC9C76F0309b8E88dec3a601Fe25a8',
decimals: 18,
symbol: 'BALD',
},
},
};
12 changes: 4 additions & 8 deletions test/testScripts/swapExample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,17 @@ function setUp(networkId: Network, provider: JsonRpcProvider): SOR {
}

export async function swap(): Promise<void> {
const networkId = Network.AVALANCHE;
const networkId = Network.BASE;
const provider = new JsonRpcProvider(PROVIDER_URLS[networkId]);
// gasPrice is used by SOR as a factor to determine how many pools to swap against.
// i.e. higher cost means more costly to trade against lots of different pools.
const gasPrice = BigNumber.from('14000000000');
// This determines the max no of pools the SOR will use to swap.
const maxPools = 4;
const tokenIn = ADDRESSES[networkId].BETS;
const tokenOut = ADDRESSES[networkId].WAVAX;
const tokenIn = ADDRESSES[networkId].DAI;
const tokenOut = ADDRESSES[networkId].BALD;
const swapType: SwapTypes = SwapTypes.SwapExactIn;
const swapAmount = parseFixed('1', 18);
// BETS -> https://snowtrace.io//address/0x94025780a1ab58868d9b2dbbb775f44b32e8e6e5 (18)
// BETS -> WAVAX https://snowtrace.io//address/0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7 (18)
// BETS -> USDC ()
// via sAVAX: https://snowtrace.io//address/0x2b2c81e08f1af8835a78bb2a90ae924ace0ea4be (18)
const swapAmount = parseFixed('900', 18);

const sor = setUp(networkId, provider);

Expand Down
Loading