Skip to content

Commit

Permalink
refactor: add more comments to CurveV1Factory
Browse files Browse the repository at this point in the history
  • Loading branch information
Verisana committed Nov 7, 2022
1 parent c699f38 commit af96d91
Show file tree
Hide file tree
Showing 9 changed files with 64 additions and 3 deletions.
4 changes: 4 additions & 0 deletions src/dex/curve-v1-factory/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -677,13 +677,16 @@ export const Adapters: Record<number, AdapterMappings> = {
},
};

// This become quite ugly :(
// I just wanted to make sure that every address is lowercased and it is not missed it config changes at some point
const configAddressesNormalizer = (
config: DexConfigMap<DexParams>,
): DexConfigMap<DexParams> => {
for (const dexKey of Object.keys(config)) {
for (const network of Object.keys(config[dexKey])) {
const _config = config[dexKey][+network];

// Normalize custom pool fields
Object.keys(_config.customPools).forEach(p => {
_config.customPools[p].address =
_config.customPools[p].address.toLowerCase();
Expand All @@ -709,6 +712,7 @@ const configAddressesNormalizer = (
{},
);

// Unite everything into top level config
const normalizedConfig: DexParams = {
factoryAddress: _config.factoryAddress
? _config.factoryAddress.toLowerCase()
Expand Down
2 changes: 1 addition & 1 deletion src/dex/curve-v1-factory/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export const MAX_ALLOWED_STATE_DELAY_MS = 30 * 1000;

export const POOL_EXCHANGE_GAS_COST = 200 * 1000;

// Pooltracker relevant variables
export const CURVE_API_URL = 'https://api.curve.fi/api/getPools';

export const NETWORK_ID_TO_NAME: Record<number, string> = {
[Network.MAINNET]: 'ethereum',
[Network.POLYGON]: 'polygon',
Expand Down
9 changes: 9 additions & 0 deletions src/dex/curve-v1-factory/curve-v1-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ export class CurveV1Factory
threePool: new Interface(ThreePoolABI as JsonFragment[]),
};

// I had to put this initialization into CurveV1 because I have to have access to mapping
// from address to custom pool
const allPriceHandlers = Object.values(
this.config.factoryPoolImplementations,
).reduce<Record<string, PriceHandler>>((acc, curr) => {
Expand Down Expand Up @@ -244,6 +246,7 @@ export class CurveV1Factory
callData: this.ifaces.factory.encodeFunctionData('pool_count'),
decodeFunction: uint256DecodeToNumber,
},
// This is used later to request all available implementations. In particular meta implementations
{
target: factoryAddress,
callData: this.ifaces.factory.encodeFunctionData('base_pool_count'),
Expand Down Expand Up @@ -326,6 +329,8 @@ export class CurveV1Factory
// This is divider between pools related results and implementations
const factoryResultsDivider = callDataFromFactoryPools.length;

// Implementations must be requested from factory, but it accepts as arg basePool address
// for metaPools
callDataFromFactoryPools = callDataFromFactoryPools.concat(
...basePoolAddresses.map(basePoolAddress => ({
target: factoryAddress,
Expand All @@ -341,6 +346,10 @@ export class CurveV1Factory
parsed => parsed[0].map((p: string) => p.toLowerCase()),
),
})),
// To receive plain pool implementation address, you have to call plain_implementations
// with two variables: N_COINS and implementations_index
// N_COINS is between 2-4. Currently more than 4 coins is not supported
// as for implementation index, there are only 0-9 indexes
..._.flattenDeep(
_.range(2, FACTORY_MAX_PLAIN_COINS).map(coinNumber =>
_.range(FACTORY_MAX_PLAIN_IMPLEMENTATIONS_FOR_COIN).map(implInd => ({
Expand Down
13 changes: 11 additions & 2 deletions src/dex/curve-v1-factory/curve-v1-pool-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,20 @@ import { PriceHandler } from './price-handlers/price-handler';
import { BasePoolPolling } from './state-polling-pools/base-pool-polling';
import { StatePollingManager } from './state-polling-pools/polling-manager';

/*
* The idea of FactoryPoolManager is to try to abstract both pool types: fully event based
* semi event based into one wall `PoolManager`. Currently we only support only
* semi event based, but it may be extended in future when we make full transition from CurveV1
*/

export class CurveV1FactoryPoolManager {
// This is needed because we initialize all factory pools + 3 custom pools
// That 3 custom pools are not fully supported. I need them only in meta pools
// This is needed because we initialize all factory pools + custom pools
// Custom pools are not fully supported. I need them only in meta pools as base pool
// to get poolState, but not for pricing requests.
// It appears from CurveV1 and CurveV1Factory duality
// Sometimes it happens that as customPool we have factory plain pool, in that case I use
// isUsedForPricing flag to identify if it must be used for pricing or not. If yes,
// it goes to statePollingPoolsFromId
private poolsForOnlyState: Record<string, BasePoolPolling> = {};

// poolsForOnly State and statePollingPoolsFromId must not have overlapping in pool
Expand Down
15 changes: 15 additions & 0 deletions src/dex/curve-v1-factory/price-handlers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,21 @@ export type get_y = (
xp_: bigint[],
) => bigint;

/*
* This is customized self context that is used for pricing. It gives us big flexibility
* to include any available function implementation we want.
* There is one problem. Not all classes ahs all functions and not all functions we need
* That is solved by throwing function: notExist and notImplemented.
* If something is not implemented or not exist we except to not call it ever.
* If it happens by mistake we will know by raised exception and it means something is missed
*
* Here we have pool constants. They can not be requested from RPC, so it must be set from source code
* Instead of having implementationAddress, I operated on Name level. Address level is put on config level
* since different metaPool implementations have different addresses on different chains, but
* pools may be duplicated and that is resembled in name.
* But mostly custom pools are quite unique. Actually, not very unique, there are many implementations
* that looks different, but essentially the same. I tried to not unite them into one implementations for simplicity
*/
export interface IPoolContext {
readonly _basePool?: IPoolContext;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Address } from 'paraswap-core';
export type MulticallReturnedTypes = bigint | bigint[];

export abstract class BasePoolPolling {
// Used for logger. Better to have which class is failing
readonly CLASS_NAME = this.constructor.name;

readonly fullName: string;
Expand All @@ -19,12 +20,15 @@ export abstract class BasePoolPolling {

protected abiCoder = Web3EthAbi as unknown as AbiCoder;

// This values is used in PoolTracker
liquidityUSD = 0;

readonly isMetaPool: boolean;
readonly underlyingCoins: string[];
readonly underlyingDecimals: number[];

// Custom pools usually are not used for pricing. But there examples, when
// factory plain goes as custom one. In that case we must use that for pricing
readonly isUsedForPricing: boolean = true;

constructor(
Expand Down Expand Up @@ -61,6 +65,8 @@ export abstract class BasePoolPolling {
updatedAt: number,
): void;

// Each type of implementation: currently two (Factory and Custom) may have different
// set of multicall requests. It is useful to make calls tha shouldn't fail
abstract getStateMultiCalldata(): MultiCallParams<MulticallReturnedTypes>[];

getState(): PoolState | null {
Expand All @@ -82,6 +88,8 @@ export abstract class BasePoolPolling {
return null;
}

// It must be called once every calculation is done and we are ready to return
// result from getPricesVolume
getPoolData(srcAddress: Address, destAddress: Address): CurveV1FactoryData {
const iC = this.poolConstants.COINS.indexOf(srcAddress);
const jC = this.poolConstants.COINS.indexOf(destAddress);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ type FunctionToCall =
| 'get_virtual_price'
| 'offpeg_fee_multiplier';

// There are many ABIs from different contracts. In order to not bring all of them
// in repository, I just picked only the ones I am using. I think it is more neat and readable,
// rather then having all ABIs
const ContractABIs: Record<FunctionToCall, AbiItem> = {
get_virtual_price: {
name: 'get_virtual_price',
Expand Down Expand Up @@ -217,6 +220,8 @@ export class CustomBasePoolForFactory extends BasePoolPolling {
.map(e => e.returnData) as bigint[];

lastEndIndex += this.useLending.length;
// We had array with booleans and I filtered of `false` and sent request.
// So, now I must map that results to original indices. That is the reason of this complication
const indicesToFill = this.useLending.reduce<number[]>((acc, curr, i) => {
if (curr) {
acc.push(i);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { MultiWrapper } from '../../../lib/multi-wrapper';
import { BasePoolPolling } from './base-pool-polling';

/*
* Since we are updating all pools state at once, I need some generalized iterator without state,
* just to go for every pool, get multicall requests and apply them into new state
*/
export class StatePollingManager {
static async updatePoolsInBatch(
multiWrapper: MultiWrapper,
Expand Down
7 changes: 7 additions & 0 deletions src/dex/curve-v1-factory/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Interface } from '@ethersproject/abi';
import { Address } from '../../types';

// The difference between PoolContextConstants and PoolConstants lies in the fact
// that PoolContextConstants can not be requested from RPC. They are hardcoded
// never be changed. Contrary PoolConstants may change or may be initialized with different values
// for different addresses or chains

export type PoolContextConstants = {
// These are not actually context relevant fro pricing constants, but helpful in identifying different
// aspects of implementation
Expand Down Expand Up @@ -121,6 +126,8 @@ export enum CustomImplementationNames {
CUSTOM_POLYGON_3COIN_LENDING = 'custom_polygon_3coin_lending',
}

// This is just a hack to blend to enums into one. One must blend actual values
// and then export type as well
export const ImplementationNames = {
...CustomImplementationNames,
...FactoryImplementationNames,
Expand Down

0 comments on commit af96d91

Please sign in to comment.