optimize price module perf (#93)
* optimize: getMostLiquidPool * optimize: fetchTickArraysForPools * fix: review comments
This commit is contained in:
parent
1121065324
commit
d18229eb20
|
@ -1,5 +1,5 @@
|
|||
import { AddressUtil, DecimalUtil, Percentage } from "@orca-so/common-sdk";
|
||||
import { Address, translateAddress } from "@project-serum/anchor";
|
||||
import { Address } from "@project-serum/anchor";
|
||||
import { u64 } from "@solana/spl-token";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import Decimal from "decimal.js";
|
||||
|
@ -15,7 +15,6 @@ import {
|
|||
import { swapQuoteWithParams } from "../quotes/public/swap-quote";
|
||||
import { TickArray, WhirlpoolData } from "../types/public";
|
||||
import { PoolUtil, PriceMath, SwapUtils } from "../utils/public";
|
||||
import { PDAUtil } from "../utils/public/pda-utils";
|
||||
|
||||
function checkLiquidity(
|
||||
pool: WhirlpoolData,
|
||||
|
@ -71,38 +70,31 @@ function checkLiquidity(
|
|||
}
|
||||
|
||||
type PoolObject = { pool: WhirlpoolData; address: PublicKey };
|
||||
function getMostLiquidPool(
|
||||
mintA: Address,
|
||||
mintB: Address,
|
||||
function getMostLiquidPools(
|
||||
quoteTokenMint: PublicKey,
|
||||
poolMap: PoolMap,
|
||||
config = defaultGetPricesConfig
|
||||
): PoolObject | null {
|
||||
const { tickSpacings, programId, whirlpoolsConfig } = config;
|
||||
const pools = tickSpacings
|
||||
.map((tickSpacing) => {
|
||||
const pda = PDAUtil.getWhirlpool(
|
||||
programId,
|
||||
whirlpoolsConfig,
|
||||
AddressUtil.toPubKey(mintA),
|
||||
AddressUtil.toPubKey(mintB),
|
||||
tickSpacing
|
||||
);
|
||||
): Record<string, PoolObject> {
|
||||
const mostLiquidPools = new Map<string, PoolObject>();
|
||||
Object.entries(poolMap).forEach(([address, pool]) => {
|
||||
const mintA = pool.tokenMintA.toBase58();
|
||||
const mintB = pool.tokenMintB.toBase58();
|
||||
|
||||
return { address: pda.publicKey, pool: poolMap[pda.publicKey.toBase58()] };
|
||||
})
|
||||
.filter(({ pool }) => pool != null);
|
||||
if (pool.liquidity.isZero()) {
|
||||
return;
|
||||
}
|
||||
if (!pool.tokenMintA.equals(quoteTokenMint) && !pool.tokenMintB.equals(quoteTokenMint)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pools.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const baseTokenMint = pool.tokenMintA.equals(quoteTokenMint) ? mintB : mintA;
|
||||
|
||||
return pools.slice(1).reduce<PoolObject>((acc, { address, pool }) => {
|
||||
if (pool.liquidity.lt(acc.pool.liquidity)) {
|
||||
return acc;
|
||||
}
|
||||
const existingPool = mostLiquidPools.get(baseTokenMint);
|
||||
if (!existingPool || pool.liquidity.gt(existingPool.pool.liquidity)) {
|
||||
mostLiquidPools.set(baseTokenMint, { address: AddressUtil.toPubKey(address), pool });
|
||||
}
|
||||
});
|
||||
|
||||
return { pool, address };
|
||||
}, pools[0]);
|
||||
return Object.fromEntries(mostLiquidPools);
|
||||
}
|
||||
|
||||
export function calculatePricesForQuoteToken(
|
||||
|
@ -114,6 +106,8 @@ export function calculatePricesForQuoteToken(
|
|||
config: GetPricesConfig,
|
||||
thresholdConfig: GetPricesThresholdConfig
|
||||
): PriceMap {
|
||||
const mostLiquidPools = getMostLiquidPools(quoteTokenMint, poolMap);
|
||||
|
||||
return Object.fromEntries(
|
||||
mints.map((mintAddr) => {
|
||||
const mint = AddressUtil.toPubKey(mintAddr);
|
||||
|
@ -125,10 +119,11 @@ export function calculatePricesForQuoteToken(
|
|||
|
||||
// The quote token is the output token.
|
||||
// Therefore, if the quote token is mintB, then we are swapping from mintA to mintB.
|
||||
const aToB = translateAddress(mintB).equals(quoteTokenMint);
|
||||
const aToB = AddressUtil.toPubKey(mintB).equals(quoteTokenMint);
|
||||
|
||||
const poolCandidate = getMostLiquidPool(mintA, mintB, poolMap, config);
|
||||
if (poolCandidate == null) {
|
||||
const baseTokenMint = aToB ? mintA : mintB;
|
||||
const poolCandidate = mostLiquidPools[AddressUtil.toString(baseTokenMint)];
|
||||
if (poolCandidate === undefined) {
|
||||
return [mint.toBase58(), null];
|
||||
}
|
||||
|
||||
|
|
|
@ -280,33 +280,37 @@ export class PriceModuleUtils {
|
|||
refresh = true
|
||||
): Promise<TickArrayMap> {
|
||||
const { programId } = config;
|
||||
const tickArrayAddresses = Object.entries(pools)
|
||||
.map(([poolAddress, pool]): PublicKey[] => {
|
||||
const aToBTickArrayPublicKeys = SwapUtils.getTickArrayPublicKeys(
|
||||
pool.tickCurrentIndex,
|
||||
pool.tickSpacing,
|
||||
true,
|
||||
programId,
|
||||
new PublicKey(poolAddress)
|
||||
);
|
||||
|
||||
const bToATickArrayPublicKeys = SwapUtils.getTickArrayPublicKeys(
|
||||
pool.tickCurrentIndex,
|
||||
pool.tickSpacing,
|
||||
false,
|
||||
programId,
|
||||
new PublicKey(poolAddress)
|
||||
);
|
||||
const getQuoteTokenOrder = (mint: PublicKey) => {
|
||||
const index = config.quoteTokens.findIndex((quoteToken) => quoteToken.equals(mint));
|
||||
return index === -1 ? config.quoteTokens.length : index;
|
||||
}
|
||||
|
||||
// Fetch tick arrays in both directions
|
||||
if (aToBTickArrayPublicKeys[0].equals(bToATickArrayPublicKeys[0])) {
|
||||
return aToBTickArrayPublicKeys.concat(bToATickArrayPublicKeys.slice(1));
|
||||
} else {
|
||||
return aToBTickArrayPublicKeys.concat(bToATickArrayPublicKeys);
|
||||
}
|
||||
})
|
||||
.flat()
|
||||
.map((tickArray): string => tickArray.toBase58());
|
||||
// select tick arrays based on the direction of swapQuote
|
||||
// TickArray is a large account, which affects decoding time.
|
||||
// Fetching can be performed in parallel, but it is preferable to fetch the minimum number of accounts necessary.
|
||||
const tickArrayAddressSet = new Set<string>();
|
||||
Object.entries(pools).forEach(([address, pool]) => {
|
||||
const orderA = getQuoteTokenOrder(pool.tokenMintA);
|
||||
const orderB = getQuoteTokenOrder(pool.tokenMintB);
|
||||
|
||||
if (orderA === orderB) {
|
||||
// neither tokenMintA nor tokenMintB is a quote token
|
||||
return;
|
||||
}
|
||||
|
||||
const aToB = orderA > orderB;
|
||||
|
||||
const tickArrayPubkeys = SwapUtils.getTickArrayPublicKeys(
|
||||
pool.tickCurrentIndex,
|
||||
pool.tickSpacing,
|
||||
aToB,
|
||||
programId,
|
||||
new PublicKey(address),
|
||||
);
|
||||
tickArrayPubkeys.forEach((p) => tickArrayAddressSet.add(p.toBase58()));
|
||||
});
|
||||
const tickArrayAddresses = Array.from(tickArrayAddressSet);
|
||||
|
||||
const tickArrays = await ctx.fetcher.listTickArrays(tickArrayAddresses, refresh);
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ describe("get_pool_prices", () => {
|
|||
|
||||
async function fetchMaps(context: WhirlpoolContext, mints: PublicKey[], config: GetPricesConfig) {
|
||||
const poolMap = await PriceModuleUtils.fetchPoolDataFromMints(context, mints, config);
|
||||
const tickArrayMap = await PriceModuleUtils.fetchTickArraysForPools(context, poolMap);
|
||||
const tickArrayMap = await PriceModuleUtils.fetchTickArraysForPools(context, poolMap, config);
|
||||
const decimalsMap = await PriceModuleUtils.fetchDecimalsForMints(context, mints);
|
||||
|
||||
return { poolMap, tickArrayMap, decimalsMap };
|
||||
|
@ -86,7 +86,7 @@ describe("get_pool_prices", () => {
|
|||
);
|
||||
|
||||
assert.equal(Object.keys(poolMap).length, 1);
|
||||
assert.equal(Object.keys(tickArrayMap).length, 3);
|
||||
assert.equal(Object.keys(tickArrayMap).length, 1); // mintA to mintB direction
|
||||
|
||||
assert.equal(Object.keys(priceMap).length, 2);
|
||||
});
|
||||
|
@ -143,8 +143,12 @@ describe("get_pool_prices", () => {
|
|||
thresholdConfig
|
||||
);
|
||||
|
||||
// mints are sorted (mintKeys[0] < mintKeys[1] < mintKeys[2])
|
||||
const fetchedTickArrayForPool0 = 1; // A to B direction (mintKeys[0] to mintKeys[1])
|
||||
const fetchedTickArrayForPool1 = 3; // B to A direction (mintKeys[2] to mintKeys[1])
|
||||
|
||||
assert.equal(Object.keys(poolMap).length, 2);
|
||||
assert.equal(Object.keys(tickArrayMap).length, 6);
|
||||
assert.equal(Object.keys(tickArrayMap).length, fetchedTickArrayForPool0 + fetchedTickArrayForPool1);
|
||||
assert.equal(Object.keys(priceMap).length, 3);
|
||||
});
|
||||
|
||||
|
@ -261,9 +265,12 @@ describe("get_pool_prices", () => {
|
|||
thresholdConfig
|
||||
);
|
||||
|
||||
// mints are sorted (mintKeys[0] < mintKeys[1] < mintKeys[2])
|
||||
const fetchedTickArrayForPool0 = 1; // A to B direction (mintKeys[0] to mintKeys[1])
|
||||
const fetchedTickArrayForPool1 = 1; // A to B direction (mintKeys[1] to mintKeys[2])
|
||||
|
||||
assert.equal(Object.keys(poolMap).length, 2);
|
||||
assert.equal(Object.keys(tickArrayMap).length, 6);
|
||||
|
||||
assert.equal(Object.keys(tickArrayMap).length, fetchedTickArrayForPool0 + fetchedTickArrayForPool1);
|
||||
assert.equal(Object.keys(priceMap).length, 3);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue