Skip to content

Commit

Permalink
Add caching v2 pool provider by block (Uniswap#168)
Browse files Browse the repository at this point in the history
  • Loading branch information
willpote authored Sep 9, 2022
1 parent 5957d04 commit 98c58bd
Show file tree
Hide file tree
Showing 6 changed files with 770 additions and 447 deletions.
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ Make sure the `.env` file is configured to connect to mainnet and other chains.
npm run integ-test
```

### Tenderly Simulations

Quotes can be simulated on Tenderly

Ensure you set the following environment variables:

```
process.env.TENDERLY_BASE_URL!,
process.env.TENDERLY_USER!,
process.env.TENDERLY_PROJECT!,
process.env.TENDERLY_ACCESS_KEY!,
```

### CLI

The package can be run as a CLI for testing purposes.
Expand All @@ -35,13 +48,18 @@ JSON_RPC_PROVIDER = '<JSON_RPC_PROVIDER>'
To run on chains other than mainnet set up a connection by specifying the environment variable

```
JSON_RPC_PROVIDER_{CHAIN} = '<JSON_RPC_PROVIDER>'
```

For example, specifying a provider for Optimism :

```
JSON_RPC_PROVIDER_ROPSTEN = '<JSON_RPC_PROVIDER>'
JSON_RPC_PROVIDER_RINKEBY = '<JSON_RPC_PROVIDER>'
JSON_RPC_PROVIDER_GORLI = '<JSON_RPC_PROVIDER>'
JSON_RPC_PROVIDER_KOVAN = '<JSON_RPC_PROVIDER>'
JSON_RPC_PROVIDER_OPTIMISM = '<JSON_RPC_PROVIDER>'
JSON_RPC_PROVIDER_OPTIMISTIC_KOVAN = '<JSON_RPC_PROVIDER>'
JSON_RPC_PROVIDER_ARBITRUM_ONE = '<JSON_RPC_PROVIDER>'
JSON_RPC_PROVIDER_ARBITRUM_RINKEBY = '<JSON_RPC_PROVIDER>'
JSON_RPC_PROVIDER_POLYGON = '<JSON_RPC_PROVIDER>'
JSON_RPC_PROVIDER_POLYGON_MUMBAI = '<JSON_RPC_PROVIDER>'
JSON_RPC_PROVIDER_CELO = '<JSON_RPC_PROVIDER>'
JSON_RPC_PROVIDER_CELO_ALFAJORES = '<JSON_RPC_PROVIDER>'
```

Then from the root directory you can execute the CLI.
Expand Down
1 change: 1 addition & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export * from './tenderly-simulation-provider';
export * from './token-provider';
export * from './token-validator-provider';
export * from './uri-subgraph-provider';
export * from './v2/caching-pool-provider';
export * from './v2/caching-subgraph-provider';
export * from './v2/pool-provider';
export * from './v2/quote-provider';
Expand Down
130 changes: 130 additions & 0 deletions src/providers/v2/caching-pool-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { Token } from '@uniswap/sdk-core';
import { Pair } from '@uniswap/v2-sdk';
import _ from 'lodash';

import { ChainId } from '../../util/chains';
import { log } from '../../util/log';

import { ICache } from './../cache';
import { ProviderConfig } from './../provider';
import { IV2PoolProvider, V2PoolAccessor } from './pool-provider';

/**
* Provider for getting V2 pools, with functionality for caching the results per block.
*
* @export
* @class CachingV2PoolProvider
*/
export class CachingV2PoolProvider implements IV2PoolProvider {
private POOL_KEY = (chainId: ChainId, address: string) =>
`pool-${chainId}-${address}`;

/**
* Creates an instance of CachingV3PoolProvider.
* @param chainId The chain id to use.
* @param poolProvider The provider to use to get the pools when not in the cache.
* @param cache Cache instance to hold cached pools.
*/
constructor(
protected chainId: ChainId,
protected poolProvider: IV2PoolProvider,
// Cache is block aware. For V2 pools we need to use the current blocks reserves values since
// we compute quotes off-chain.
// If no block is specified in the call to getPools we just return whatever is in the cache.
private cache: ICache<{ pair: Pair; block?: number }>
) {}

public async getPools(
tokenPairs: [Token, Token][],
providerConfig?: ProviderConfig
): Promise<V2PoolAccessor> {
const poolAddressSet: Set<string> = new Set<string>();
const poolsToGetTokenPairs: Array<[Token, Token]> = [];
const poolsToGetAddresses: string[] = [];
const poolAddressToPool: { [poolAddress: string]: Pair } = {};

const blockNumber = await providerConfig?.blockNumber;

for (const [tokenA, tokenB] of tokenPairs) {
const { poolAddress, token0, token1 } = this.getPoolAddress(
tokenA,
tokenB
);

if (poolAddressSet.has(poolAddress)) {
continue;
}

poolAddressSet.add(poolAddress);

const cachedPool = await this.cache.get(
this.POOL_KEY(this.chainId, poolAddress)
);

if (cachedPool) {
// If a block was specified by the caller, ensure that the result in our cache matches the
// expected block number. If a block number is not specified, just return whatever is in the
// cache.
if (!blockNumber || (blockNumber && cachedPool.block == blockNumber)) {
poolAddressToPool[poolAddress] = cachedPool.pair;
continue;
}
}

poolsToGetTokenPairs.push([token0, token1]);
poolsToGetAddresses.push(poolAddress);
}

log.info(
{
poolsFound: _.map(
Object.values(poolAddressToPool),
(p) => p.token0.symbol + ' ' + p.token1.symbol
),
poolsToGetTokenPairs: _.map(
poolsToGetTokenPairs,
(t) => t[0].symbol + ' ' + t[1].symbol
),
},
`Found ${
Object.keys(poolAddressToPool).length
} V2 pools already in local cache for block ${blockNumber}. About to get reserves for ${
poolsToGetTokenPairs.length
} pools.`
);

if (poolsToGetAddresses.length > 0) {
const poolAccessor = await this.poolProvider.getPools(
poolsToGetTokenPairs,
providerConfig
);
for (const address of poolsToGetAddresses) {
const pool = poolAccessor.getPoolByAddress(address);
if (pool) {
poolAddressToPool[address] = pool;
await this.cache.set(this.POOL_KEY(this.chainId, address), {
pair: pool,
block: blockNumber,
});
}
}
}

return {
getPool: (tokenA: Token, tokenB: Token): Pair | undefined => {
const { poolAddress } = this.getPoolAddress(tokenA, tokenB);
return poolAddressToPool[poolAddress];
},
getPoolByAddress: (address: string): Pair | undefined =>
poolAddressToPool[address],
getAllPools: (): Pair[] => Object.values(poolAddressToPool),
};
}

public getPoolAddress(
tokenA: Token,
tokenB: Token
): { poolAddress: string; token0: Token; token1: Token } {
return this.poolProvider.getPoolAddress(tokenA, tokenB);
}
}
15 changes: 14 additions & 1 deletion src/providers/v3/caching-pool-provider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Token } from '@uniswap/sdk-core';
import { FeeAmount, Pool } from '@uniswap/v3-sdk';
import _ from 'lodash';

import { ChainId } from '../../util/chains';
import { log } from '../../util/log';
Expand All @@ -10,6 +11,8 @@ import { IV3PoolProvider, V3PoolAccessor } from './pool-provider';

/**
* Provider for getting V3 pools, with functionality for caching the results.
* Does not cache by block because we compute quotes using the on-chain quoter
* so do not mind if the liquidity values are out of date.
*
* @export
* @class CachingV3PoolProvider
Expand Down Expand Up @@ -65,9 +68,19 @@ export class CachingV3PoolProvider implements IV3PoolProvider {
}

log.info(
{
poolsFound: _.map(
Object.values(poolAddressToPool),
(p) => `${p.token0.symbol} ${p.token1.symbol} ${p.fee}`
),
poolsToGetTokenPairs: _.map(
poolsToGetTokenPairs,
(t) => `${t[0].symbol} ${t[1].symbol} ${t[2]}`
),
},
`Found ${
Object.keys(poolAddressToPool).length
} pools already in local cache. About to get liquidity and slot0s for ${
} V3 pools already in local cache. About to get liquidity and slot0s for ${
poolsToGetTokenPairs.length
} pools.`
);
Expand Down
9 changes: 8 additions & 1 deletion src/routers/alpha-router/alpha-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import NodeCache from 'node-cache';
import {
CachingGasStationProvider,
CachingTokenProviderWithFallback,
CachingV2PoolProvider,
CachingV2SubgraphProvider,
CachingV3PoolProvider,
CachingV3SubgraphProvider,
Expand Down Expand Up @@ -510,7 +511,13 @@ export class AlphaRouter
}

this.v2PoolProvider =
v2PoolProvider ?? new V2PoolProvider(chainId, this.multicall2Provider);
v2PoolProvider ??
new CachingV2PoolProvider(
chainId,
new V2PoolProvider(chainId, this.multicall2Provider),
new NodeJSCache(new NodeCache({ stdTTL: 60, useClones: false }))
);

this.v2QuoteProvider = v2QuoteProvider ?? new V2QuoteProvider();

this.blockedTokenListProvider =
Expand Down
Loading

0 comments on commit 98c58bd

Please sign in to comment.