Refactor PoolsCache (part 1) [TKR-500] (#525)
* Make _refreshPoolCacheIfRequiredAsync type-safe and remove Promise.all * Factor out PoolsCache key logic into a function * Use Map instead of object in PoolsCache and increase the default timeout * Clean up PoolsCache and simplify its public interface
This commit is contained in:
parent
9856e78609
commit
14dcee5bb6
@ -862,14 +862,9 @@ export class MarketOperationUtils {
|
||||
}
|
||||
|
||||
private async _refreshPoolCacheIfRequiredAsync(takerToken: string, makerToken: string): Promise<void> {
|
||||
void Promise.all(
|
||||
Object.values(this._sampler.poolsCaches).map(async cache => {
|
||||
if (!cache || cache.isFresh(takerToken, makerToken)) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
return cache.getFreshPoolsForPairAsync(takerToken, makerToken);
|
||||
}),
|
||||
);
|
||||
_.values(this._sampler.poolsCaches)
|
||||
.filter(cache => cache !== undefined && !cache.isFresh(takerToken, makerToken))
|
||||
.forEach(cache => cache?.getFreshPoolsForPairAsync(takerToken, makerToken));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ interface BalancerPoolResponse {
|
||||
export class BalancerPoolsCache extends PoolsCache {
|
||||
constructor(
|
||||
private readonly _subgraphUrl: string = BALANCER_SUBGRAPH_URL,
|
||||
cache: { [key: string]: CacheValue } = {},
|
||||
cache: Map<string, CacheValue> = new Map(),
|
||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
||||
private readonly _topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED,
|
||||
private readonly _warningLogger: LogFunction = DEFAULT_WARNING_LOGGER,
|
||||
|
@ -63,7 +63,7 @@ export class BalancerV2PoolsCache extends PoolsCache {
|
||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
||||
private readonly _topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED,
|
||||
private readonly _warningLogger: LogFunction = DEFAULT_WARNING_LOGGER,
|
||||
cache: { [key: string]: CacheValue } = {},
|
||||
cache: Map<string, CacheValue> = new Map(),
|
||||
) {
|
||||
super(cache);
|
||||
void this._loadTopPoolsAsync();
|
||||
|
@ -7,7 +7,7 @@ import { CacheValue, PoolsCache } from './pools_cache';
|
||||
|
||||
export class CreamPoolsCache extends PoolsCache {
|
||||
constructor(
|
||||
_cache: { [key: string]: CacheValue } = {},
|
||||
_cache: Map<string, CacheValue> = new Map(),
|
||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
||||
) {
|
||||
super(_cache);
|
||||
|
@ -10,15 +10,23 @@ export interface CacheValue {
|
||||
// tslint:disable:custom-no-magic-numbers
|
||||
// Cache results for 30mins
|
||||
const DEFAULT_CACHE_TIME_MS = (ONE_HOUR_IN_SECONDS / 2) * ONE_SECOND_MS;
|
||||
const DEFAULT_TIMEOUT_MS = 1000;
|
||||
const DEFAULT_TIMEOUT_MS = 3000;
|
||||
// tslint:enable:custom-no-magic-numbers
|
||||
|
||||
export abstract class PoolsCache {
|
||||
protected static _isExpired(value: CacheValue): boolean {
|
||||
protected static _getKey(takerToken: string, makerToken: string): string {
|
||||
return `${takerToken}-${makerToken}`;
|
||||
}
|
||||
|
||||
protected static _isExpired(value: CacheValue | undefined): boolean {
|
||||
if (value === undefined) {
|
||||
return true;
|
||||
}
|
||||
return Date.now() >= value.expiresAt;
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected readonly _cache: { [key: string]: CacheValue },
|
||||
protected readonly _cache: Map<string, CacheValue>,
|
||||
protected readonly _cacheTimeMs: number = DEFAULT_CACHE_TIME_MS,
|
||||
) {}
|
||||
|
||||
@ -31,47 +39,42 @@ export abstract class PoolsCache {
|
||||
return Promise.race([this._getAndSaveFreshPoolsForPairAsync(takerToken, makerToken), timeout]);
|
||||
}
|
||||
|
||||
public getCachedPoolAddressesForPair(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
ignoreExpired: boolean = true,
|
||||
): string[] | undefined {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
const value = this._cache[key];
|
||||
if (ignoreExpired) {
|
||||
return value === undefined ? [] : value.pools.map(pool => pool.id);
|
||||
}
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
if (PoolsCache._isExpired(value)) {
|
||||
return undefined;
|
||||
}
|
||||
return (value || []).pools.map(pool => pool.id);
|
||||
/**
|
||||
* Returns pool addresses (can be stale) for a pair.
|
||||
*
|
||||
* An empty array will be returned if cache does not exist.
|
||||
*/
|
||||
public getPoolAddressesForPair(takerToken: string, makerToken: string): string[] {
|
||||
const value = this._getValue(takerToken, makerToken);
|
||||
return value === undefined ? [] : value.pools.map(pool => pool.id);
|
||||
}
|
||||
|
||||
public isFresh(takerToken: string, makerToken: string): boolean {
|
||||
const cached = this.getCachedPoolAddressesForPair(takerToken, makerToken, false);
|
||||
return cached !== undefined;
|
||||
const value = this._getValue(takerToken, makerToken);
|
||||
return !PoolsCache._isExpired(value);
|
||||
}
|
||||
|
||||
protected _getValue(takerToken: string, makerToken: string): CacheValue | undefined {
|
||||
const key = PoolsCache._getKey(takerToken, makerToken);
|
||||
return this._cache.get(key);
|
||||
}
|
||||
|
||||
protected async _getAndSaveFreshPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
const value = this._cache[key];
|
||||
if (value === undefined || value.expiresAt >= Date.now()) {
|
||||
const pools = await this._fetchPoolsForPairAsync(takerToken, makerToken);
|
||||
const expiresAt = Date.now() + this._cacheTimeMs;
|
||||
this._cachePoolsForPair(takerToken, makerToken, pools, expiresAt);
|
||||
const key = PoolsCache._getKey(takerToken, makerToken);
|
||||
const value = this._cache.get(key);
|
||||
if (!PoolsCache._isExpired(value)) {
|
||||
return value!.pools;
|
||||
}
|
||||
return this._cache[key].pools;
|
||||
|
||||
const pools = await this._fetchPoolsForPairAsync(takerToken, makerToken);
|
||||
const expiresAt = Date.now() + this._cacheTimeMs;
|
||||
this._cachePoolsForPair(takerToken, makerToken, pools, expiresAt);
|
||||
return pools;
|
||||
}
|
||||
|
||||
protected _cachePoolsForPair(takerToken: string, makerToken: string, pools: Pool[], expiresAt: number): void {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
this._cache[key] = {
|
||||
pools,
|
||||
expiresAt,
|
||||
};
|
||||
const key = PoolsCache._getKey(takerToken, makerToken);
|
||||
this._cache.set(key, { pools, expiresAt });
|
||||
}
|
||||
|
||||
protected abstract _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]>;
|
||||
|
@ -1592,20 +1592,17 @@ export class SamplerOperations {
|
||||
),
|
||||
];
|
||||
case ERC20BridgeSource.Balancer:
|
||||
return (
|
||||
this.poolsCaches[ERC20BridgeSource.Balancer].getCachedPoolAddressesForPair(
|
||||
takerToken,
|
||||
makerToken,
|
||||
) || []
|
||||
).map(balancerPool =>
|
||||
this.getBalancerSellQuotes(
|
||||
balancerPool,
|
||||
makerToken,
|
||||
takerToken,
|
||||
takerFillAmounts,
|
||||
ERC20BridgeSource.Balancer,
|
||||
),
|
||||
);
|
||||
return this.poolsCaches[ERC20BridgeSource.Balancer]
|
||||
.getPoolAddressesForPair(takerToken, makerToken)
|
||||
.map(balancerPool =>
|
||||
this.getBalancerSellQuotes(
|
||||
balancerPool,
|
||||
makerToken,
|
||||
takerToken,
|
||||
takerFillAmounts,
|
||||
ERC20BridgeSource.Balancer,
|
||||
),
|
||||
);
|
||||
case ERC20BridgeSource.BalancerV2: {
|
||||
const cache = this.poolsCaches[source];
|
||||
if (!cache) {
|
||||
@ -1627,15 +1624,14 @@ export class SamplerOperations {
|
||||
if (cache === undefined) {
|
||||
return [];
|
||||
}
|
||||
const poolIds = cache.getCachedPoolAddressesForPair(takerToken, makerToken) || [];
|
||||
|
||||
const poolAddresses = cache.getPoolAddressesForPair(takerToken, makerToken);
|
||||
const vault = BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN[this.chainId];
|
||||
if (vault === NULL_ADDRESS) {
|
||||
return [];
|
||||
}
|
||||
return poolIds.map(poolId =>
|
||||
return poolAddresses.map(poolAddress =>
|
||||
this.getBalancerV2SellQuotes(
|
||||
{ poolId, vault },
|
||||
{ poolId: poolAddress, vault },
|
||||
makerToken,
|
||||
takerToken,
|
||||
takerFillAmounts,
|
||||
@ -1644,20 +1640,17 @@ export class SamplerOperations {
|
||||
);
|
||||
}
|
||||
case ERC20BridgeSource.Cream:
|
||||
return (
|
||||
this.poolsCaches[ERC20BridgeSource.Cream].getCachedPoolAddressesForPair(
|
||||
takerToken,
|
||||
makerToken,
|
||||
) || []
|
||||
).map(creamPool =>
|
||||
this.getBalancerSellQuotes(
|
||||
creamPool,
|
||||
makerToken,
|
||||
takerToken,
|
||||
takerFillAmounts,
|
||||
ERC20BridgeSource.Cream,
|
||||
),
|
||||
);
|
||||
return this.poolsCaches[ERC20BridgeSource.Cream]
|
||||
.getPoolAddressesForPair(takerToken, makerToken)
|
||||
.map(creamPool =>
|
||||
this.getBalancerSellQuotes(
|
||||
creamPool,
|
||||
makerToken,
|
||||
takerToken,
|
||||
takerFillAmounts,
|
||||
ERC20BridgeSource.Cream,
|
||||
),
|
||||
);
|
||||
case ERC20BridgeSource.Dodo:
|
||||
if (!isValidAddress(DODOV1_CONFIG_BY_CHAIN_ID[this.chainId].registry)) {
|
||||
return [];
|
||||
@ -1948,20 +1941,17 @@ export class SamplerOperations {
|
||||
),
|
||||
];
|
||||
case ERC20BridgeSource.Balancer:
|
||||
return (
|
||||
this.poolsCaches[ERC20BridgeSource.Balancer].getCachedPoolAddressesForPair(
|
||||
takerToken,
|
||||
makerToken,
|
||||
) || []
|
||||
).map(poolAddress =>
|
||||
this.getBalancerBuyQuotes(
|
||||
poolAddress,
|
||||
makerToken,
|
||||
takerToken,
|
||||
makerFillAmounts,
|
||||
ERC20BridgeSource.Balancer,
|
||||
),
|
||||
);
|
||||
return this.poolsCaches[ERC20BridgeSource.Balancer]
|
||||
.getPoolAddressesForPair(takerToken, makerToken)
|
||||
.map(poolAddress =>
|
||||
this.getBalancerBuyQuotes(
|
||||
poolAddress,
|
||||
makerToken,
|
||||
takerToken,
|
||||
makerFillAmounts,
|
||||
ERC20BridgeSource.Balancer,
|
||||
),
|
||||
);
|
||||
case ERC20BridgeSource.BalancerV2: {
|
||||
const cache = this.poolsCaches[source];
|
||||
if (!cache) {
|
||||
@ -1989,8 +1979,7 @@ export class SamplerOperations {
|
||||
if (cache === undefined) {
|
||||
return [];
|
||||
}
|
||||
const poolIds = cache.getCachedPoolAddressesForPair(takerToken, makerToken) || [];
|
||||
|
||||
const poolIds = cache.getPoolAddressesForPair(takerToken, makerToken) || [];
|
||||
const vault = BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN[this.chainId];
|
||||
if (vault === NULL_ADDRESS) {
|
||||
return [];
|
||||
@ -2006,20 +1995,17 @@ export class SamplerOperations {
|
||||
);
|
||||
}
|
||||
case ERC20BridgeSource.Cream:
|
||||
return (
|
||||
this.poolsCaches[ERC20BridgeSource.Cream].getCachedPoolAddressesForPair(
|
||||
takerToken,
|
||||
makerToken,
|
||||
) || []
|
||||
).map(poolAddress =>
|
||||
this.getBalancerBuyQuotes(
|
||||
poolAddress,
|
||||
makerToken,
|
||||
takerToken,
|
||||
makerFillAmounts,
|
||||
ERC20BridgeSource.Cream,
|
||||
),
|
||||
);
|
||||
return this.poolsCaches[ERC20BridgeSource.Cream]
|
||||
.getPoolAddressesForPair(takerToken, makerToken)
|
||||
.map(poolAddress =>
|
||||
this.getBalancerBuyQuotes(
|
||||
poolAddress,
|
||||
makerToken,
|
||||
takerToken,
|
||||
makerFillAmounts,
|
||||
ERC20BridgeSource.Cream,
|
||||
),
|
||||
);
|
||||
case ERC20BridgeSource.Dodo:
|
||||
if (!isValidAddress(DODOV1_CONFIG_BY_CHAIN_ID[this.chainId].registry)) {
|
||||
return [];
|
||||
|
@ -100,7 +100,7 @@ async function getMarketBuyOrdersAsync(
|
||||
|
||||
class MockPoolsCache extends PoolsCache {
|
||||
constructor(private readonly _handler: (takerToken: string, makerToken: string) => Pool[]) {
|
||||
super({});
|
||||
super(new Map());
|
||||
}
|
||||
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||
return this._handler(takerToken, makerToken);
|
||||
|
@ -28,7 +28,7 @@ describe('Pools Caches for Balancer-based sampling', () => {
|
||||
expect(pools.length).greaterThan(0, `Failed to find any pools for ${takerToken} and ${makerToken}`);
|
||||
expect(pools[0]).not.undefined();
|
||||
expect(Object.keys(pools[0])).to.include.members(poolKeys);
|
||||
const cachedPoolIds = cache.getCachedPoolAddressesForPair(takerToken, makerToken);
|
||||
const cachedPoolIds = cache.getPoolAddressesForPair(takerToken, makerToken);
|
||||
expect(cachedPoolIds).to.deep.equal(pools.map(p => p.id));
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user