optimize price module perf (#93)

* optimize: getMostLiquidPool
* optimize: fetchTickArraysForPools
* fix: review comments
This commit is contained in:
yugure-orca 2023-03-31 13:41:32 +09:00 committed by GitHub
parent 1121065324
commit d18229eb20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 68 additions and 62 deletions

View File

@ -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];
}

View File

@ -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);

View File

@ -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);
});
});