Skip to content

Commit

Permalink
Merge pull request #411 from balancer/tri-hops-with-mid-pool
Browse files Browse the repository at this point in the history
Tri hops with mid pool
  • Loading branch information
John Grant authored Aug 9, 2023
2 parents 8916afb + 8f67bbe commit 0f8a503
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 9 deletions.
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

0 comments on commit 0f8a503

Please sign in to comment.