Skip to content

Commit

Permalink
feat(curve): Resolve coin addresses from on-chain again (Zapper-fi#976)
Browse files Browse the repository at this point in the history
  • Loading branch information
immasandwich committed Jul 29, 2022
1 parent cbe7d1f commit e882778
Show file tree
Hide file tree
Showing 20 changed files with 225 additions and 120 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { Inject } from '@nestjs/common';

import { Register } from '~app-toolkit/decorators';
import { CurvePoolTokenHelper } from '~apps/curve';
import { CurvePoolDefinition } from '~apps/curve/curve.types';
import {
CurvePoolOnChainCoinStrategy,
CurvePoolOnChainReserveStrategy,
CurvePoolTokenHelper,
CurvePoolVirtualPriceStrategy,
} from '~apps/curve';
import { CurvePoolDefinition, CurvePoolType } from '~apps/curve/curve.types';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { AppTokenPosition } from '~position/position.interface';
import { Network } from '~types/network.interface';
Expand All @@ -17,17 +22,23 @@ const network = Network.AURORA_MAINNET;
@Register.TokenPositionFetcher({ appId, groupId, network })
export class AuroraBastionProtocolSwapTokenFetcher implements PositionFetcher<AppTokenPosition> {
constructor(
@Inject(CurvePoolTokenHelper) private readonly curvePoolTokenHelper: CurvePoolTokenHelper,
@Inject(BastionProtocolContractFactory)
private readonly bastionProtocolContractFactory: BastionProtocolContractFactory,
@Inject(CurvePoolTokenHelper) private readonly curvePoolTokenHelper: CurvePoolTokenHelper,
@Inject(CurvePoolOnChainCoinStrategy)
private readonly curvePoolOnChainCoinStrategy: CurvePoolOnChainCoinStrategy,
@Inject(CurvePoolOnChainReserveStrategy)
private readonly curvePoolOnChainReserveStrategy: CurvePoolOnChainReserveStrategy,
@Inject(CurvePoolVirtualPriceStrategy)
private readonly curvePoolVirtualPriceStrategy: CurvePoolVirtualPriceStrategy,
) {}

async getPositions() {
const poolDefinitions: CurvePoolDefinition[] = [
{
swapAddress: '0x6287e912a9ccd4d5874ae15d3c89556b2a05f080',
tokenAddress: '0x0039f0641156cac478b0debab086d78b66a69a01',
coinAddresses: ['0x845e15a441cfc1871b7ac610b0e922019bad9826', '0xe5308dc623101508952948b141fd9eabd3337d99'],
poolType: CurvePoolType.CRYPTO,
},
];

Expand All @@ -50,22 +61,22 @@ export class AuroraBastionProtocolSwapTokenFetcher implements PositionFetcher<Ap
groupId,
dependencies: dependencies,
poolDefinitions: poolDefinitions,
resolvePoolContract: ({ network, definition }) =>
this.bastionProtocolContractFactory.bastionProtocolSwap({ address: definition.swapAddress, network }),
resolvePoolReserves: async ({ definition, multicall, poolContract }) =>
Promise.all(definition.coinAddresses.map((_, i) => multicall.wrap(poolContract).getTokenBalance(i))),
resolvePoolContract: ({ network, address }) =>
this.bastionProtocolContractFactory.bastionProtocolSwap({ address, network }),
resolvePoolCoinAddresses: this.curvePoolOnChainCoinStrategy.build({
resolveCoinAddress: ({ multicall, index, poolContract }) => multicall.wrap(poolContract).getToken(index),
}),
resolvePoolReserves: this.curvePoolOnChainReserveStrategy.build({
resolveReserve: ({ multicall, index, poolContract }) => multicall.wrap(poolContract).getTokenBalance(index),
}),
resolvePoolTokenPrice: this.curvePoolVirtualPriceStrategy.build({
resolveVirtualPrice: ({ multicall, poolContract }) => multicall.wrap(poolContract).getVirtualPrice(),
}),
resolvePoolFee: ({ multicall, poolContract }) =>
multicall
.wrap(poolContract)
.swapStorage()
.then(r => r.swapFee),
resolvePoolTokenPrice: async ({ tokens, reserves, poolContract, multicall, supply }) => {
const virtualPriceRaw = await multicall.wrap(poolContract).getVirtualPrice();
const virtualPrice = Number(virtualPriceRaw) / 10 ** 18;
const reservesUSD = tokens.map((t, i) => reserves[i] * t.price);
const liquidity = reservesUSD.reduce((total, r) => total + r, 0);
return virtualPrice > 0 ? virtualPrice * (liquidity / supply) : liquidity / supply;
},
});
}
}
10 changes: 9 additions & 1 deletion src/apps/bastion-protocol/bastion-protocol.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Register } from '~app-toolkit/decorators';
import { AbstractApp } from '~app/app.dynamic-module';
import { CurvePoolTokenHelper } from '~apps/curve';
import {
CurvePoolOnChainCoinStrategy,
CurvePoolOnChainReserveStrategy,
CurvePoolTokenHelper,
CurvePoolVirtualPriceStrategy,
} from '~apps/curve';

import { AuroraBastionProtocolBalanceFetcher } from './aurora/bastion-protocol.balance-fetcher';
import { AuroraBastionProtocolBorrowAuroraEcosystemContractPositionFetcher } from './aurora/bastion-protocol.borrow-aurora-ecosystem.contract-position-fetcher';
Expand Down Expand Up @@ -38,6 +43,9 @@ import { BastionSupplyTokenHelper } from './helper/bastion-protocol.supply.token
BastionSupplyTokenHelper,
BastionBorrowContractPositionHelper,
CurvePoolTokenHelper,
CurvePoolOnChainCoinStrategy,
CurvePoolOnChainReserveStrategy,
CurvePoolVirtualPriceStrategy,
],
})
export class BastionProtocolAppModule extends AbstractApp() {}
9 changes: 8 additions & 1 deletion src/apps/curve/curve.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { CurveGaugeIsActiveStrategy } from './helpers/curve.gauge.is-active-stra
import { CurveGaugeRegistry } from './helpers/curve.gauge.registry';
import { CurveGaugeRoiStrategy } from './helpers/curve.gauge.roi-strategy';
import { CurvePoolDefaultTokenHelper } from './helpers/curve.pool.default.token-helper';
import { CurvePoolOnChainCoinStrategy } from './helpers/curve.pool.on-chain.coin-strategy';
import { CurvePoolOnChainReserveStrategy } from './helpers/curve.pool.on-chain.reserve-strategy';
import { CurvePoolRegistry } from './helpers/curve.pool.registry';
import { CurvePoolTokenHelper } from './helpers/curve.pool.token-helper';
import { CurvePoolVirtualPriceStrategy } from './helpers/curve.pool.virtual.price-strategy';
Expand Down Expand Up @@ -76,18 +78,23 @@ import { PolygonCurvePoolTokenFetcher } from './polygon/curve.pool.token-fetcher
PolygonCurveBalanceFetcher,
PolygonCurvePoolTokenFetcher,
PolygonCurveGaugeContractPositionFetcher,
// Token Helpers
// API Helpers
CurveApiClient,
// Pool Token Helpers
CurvePoolRegistry,
CurvePoolTokenHelper,
CurvePoolDefaultTokenHelper,
CurvePoolVirtualPriceStrategy,
CurvePoolOnChainCoinStrategy,
CurvePoolOnChainReserveStrategy,
// Gauge Helpers
CurveGaugeRegistry,
CurveGaugeDefaultContractPositionHelper,
CurveGaugeDefaultContractPositionBalanceHelper,
CurveGaugeIsActiveStrategy,
CurveGaugeRoiStrategy,
CurveChildLiquidityGaugeRoiStrategy,
// Escrow Helpers
CurveVotingEscrowContractPositionHelper,
CurveVotingEscrowContractPositionBalanceHelper,
CurveVestingEscrowContractPositionHelper,
Expand Down
2 changes: 1 addition & 1 deletion src/apps/curve/curve.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export enum CurvePoolType {
export type CurvePoolDefinition = {
swapAddress: string;
tokenAddress: string;
coinAddresses: string[];
coinAddresses?: string[];
gaugeAddresses?: string[];
poolType?: CurvePoolType;
volume?: number;
Expand Down
35 changes: 23 additions & 12 deletions src/apps/curve/helpers/curve.pool.default.token-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { CurvePool } from '~apps/curve/contracts/ethers/CurvePool';
import { AppGroupsDefinition } from '~position/position.service';
import { Network } from '~types/network.interface';

import { CurveContractFactory } from '../contracts';
import { CurveContractFactory, CurvePoolLegacy } from '../contracts';
import { CURVE_DEFINITION } from '../curve.definition';

import { CurvePoolOnChainCoinStrategy } from './curve.pool.on-chain.coin-strategy';
import { CurvePoolOnChainReserveStrategy } from './curve.pool.on-chain.reserve-strategy';
import { CurvePoolRegistry } from './curve.pool.registry';
import { CurvePoolTokenHelper } from './curve.pool.token-helper';
import { CurvePoolVirtualPriceStrategy } from './curve.pool.virtual.price-strategy';
Expand All @@ -21,6 +23,10 @@ export class CurvePoolDefaultTokenHelper {
constructor(
@Inject(CurvePoolTokenHelper)
private readonly curvePoolTokenHelper: CurvePoolTokenHelper,
@Inject(CurvePoolOnChainCoinStrategy)
private readonly curvePoolOnChainCoinStrategy: CurvePoolOnChainCoinStrategy,
@Inject(CurvePoolOnChainReserveStrategy)
private readonly curvePoolOnChainReserveStrategy: CurvePoolOnChainReserveStrategy,
@Inject(CurvePoolVirtualPriceStrategy)
private readonly curvePoolVirtualPriceStrategy: CurvePoolVirtualPriceStrategy,
@Inject(CurveContractFactory)
Expand All @@ -30,25 +36,30 @@ export class CurvePoolDefaultTokenHelper {
) {}

async getTokens({ network, dependencies = [] }: CurvePoolDefaultTokenHelperParams) {
return this.curvePoolTokenHelper.getTokens<CurvePool>({
const poolDefinitions = await this.curvePoolRegistry.getPoolDefinitions(network);
const legacy = poolDefinitions.filter(v => v.isLegacy).map(v => v.swapAddress);

return this.curvePoolTokenHelper.getTokens<CurvePool | CurvePoolLegacy>({
network,
dependencies,
appId: CURVE_DEFINITION.id,
groupId: CURVE_DEFINITION.groups.pool.id,
poolDefinitions: await this.curvePoolRegistry.getPoolDefinitions(network),
poolDefinitions: poolDefinitions,
minLiquidity: 1000,
resolvePoolContract: ({ network, definition }) =>
this.curveContractFactory.curvePool({ network, address: definition.swapAddress }),
resolvePoolReserves: ({ definition, multicall }) => {
const contract = definition.isLegacy
? this.curveContractFactory.curvePoolLegacy({ address: definition.swapAddress, network })
: this.curveContractFactory.curvePool({ address: definition.swapAddress, network });
return Promise.all(definition.coinAddresses.map((_, i) => multicall.wrap(contract).balances(i)));
},
resolvePoolFee: ({ multicall, poolContract }) => multicall.wrap(poolContract).fee(),
resolvePoolContract: ({ network, address }) =>
legacy.includes(address)
? this.curveContractFactory.curvePoolLegacy({ address, network })
: this.curveContractFactory.curvePool({ address, network }),
resolvePoolCoinAddresses: this.curvePoolOnChainCoinStrategy.build({
resolveCoinAddress: ({ poolContract, multicall, index }) => multicall.wrap(poolContract).coins(index),
}),
resolvePoolReserves: this.curvePoolOnChainReserveStrategy.build({
resolveReserve: ({ poolContract, multicall, index }) => multicall.wrap(poolContract).balances(index),
}),
resolvePoolTokenPrice: this.curvePoolVirtualPriceStrategy.build({
resolveVirtualPrice: ({ multicall, poolContract }) => multicall.wrap(poolContract).get_virtual_price(),
}),
resolvePoolFee: ({ multicall, poolContract }) => multicall.wrap(poolContract).fee(),
});
}
}
29 changes: 29 additions & 0 deletions src/apps/curve/helpers/curve.pool.on-chain.coin-strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { compact, range } from 'lodash';

import { isMulticallUnderlyingError } from '~multicall/multicall.ethers';
import { IMulticallWrapper } from '~multicall/multicall.interface';

import { CurvePoolTokenHelperParams } from './curve.pool.token-helper';

export type CurvePoolOnChainCoinStrategyParams<T> = {
resolveCoinAddress: (opts: { multicall: IMulticallWrapper; poolContract: T; index: number }) => Promise<string>;
};

export class CurvePoolOnChainCoinStrategy {
build<T>({
resolveCoinAddress,
}: CurvePoolOnChainCoinStrategyParams<T>): CurvePoolTokenHelperParams<T>['resolvePoolCoinAddresses'] {
return async ({ multicall, poolContract }) => {
const coinAddresses = await Promise.all(
range(0, 4).map(index =>
resolveCoinAddress({ multicall, poolContract, index }).catch(err => {
if (isMulticallUnderlyingError(err)) return null;
throw err;
}),
),
);

return compact(coinAddresses).map(v => v.toLowerCase());
};
}
}
24 changes: 24 additions & 0 deletions src/apps/curve/helpers/curve.pool.on-chain.reserve-strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { BigNumberish } from 'ethers';
import { range } from 'lodash';

import { IMulticallWrapper } from '~multicall/multicall.interface';

import { CurvePoolTokenHelperParams } from './curve.pool.token-helper';

export type CurvePoolOnChainReserveStrategyParams<T> = {
resolveReserve: (opts: { multicall: IMulticallWrapper; poolContract: T; index: number }) => Promise<BigNumberish>;
};

export class CurvePoolOnChainReserveStrategy {
build<T>({
resolveReserve,
}: CurvePoolOnChainReserveStrategyParams<T>): CurvePoolTokenHelperParams<T>['resolvePoolReserves'] {
return async ({ multicall, poolContract, coinAddresses }) => {
const reserves = await Promise.all(
range(0, coinAddresses.length).map(index => resolveReserve({ multicall, poolContract, index })),
);

return reserves;
};
}
}
1 change: 0 additions & 1 deletion src/apps/curve/helpers/curve.pool.registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ export class CurvePoolRegistry {
swapAddress,
tokenAddress,
gaugeAddresses,
coinAddresses,
apy,
volume,
};
Expand Down
28 changes: 21 additions & 7 deletions src/apps/curve/helpers/curve.pool.token-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ export type CurvePoolTokenHelperParams<T> = {
minLiquidity?: number;
dependencies?: AppGroupsDefinition[];
baseCurveTokens?: AppTokenPosition[];
resolvePoolContract: (opts: { network: Network; definition: CurvePoolDefinition }) => T;
resolvePoolContract: (opts: { network: Network; address: string }) => T;
resolvePoolCoinAddresses: (opts: { multicall: IMulticallWrapper; poolContract: T }) => Promise<string[]>;
resolvePoolReserves: (opts: {
definition: CurvePoolDefinition;
coinAddresses: string[];
multicall: IMulticallWrapper;
poolContract: T;
}) => Promise<BigNumberish[]>;
Expand Down Expand Up @@ -75,10 +76,11 @@ export class CurvePoolTokenHelper {

const curvePoolTokens = await Promise.all(
poolDefinitions.map(async definition => {
const { swapAddress, tokenAddress, coinAddresses } = definition;
const poolContract = resolvePoolContract({ network, definition });
const { swapAddress, tokenAddress } = definition;
const coinAddresses = definition.coinAddresses!;
const poolContract = resolvePoolContract({ network, address: swapAddress });
const tokenContract = this.appToolkit.globalContracts.erc20({ network, address: definition.tokenAddress });
const reservesRaw = await resolvePoolReserves({ multicall, poolContract, definition });
const reservesRaw = await resolvePoolReserves({ multicall, poolContract, coinAddresses });

const maybeTokens = coinAddresses.map(tokenAddress => {
const baseToken = baseTokens.find(price => price.address === tokenAddress);
Expand Down Expand Up @@ -185,8 +187,20 @@ export class CurvePoolTokenHelper {
}

async getTokens<T>(params: CurvePoolTokenHelperParams<T>) {
const [metaPoolDefinitions, basePoolDefinitions] = partition(params.poolDefinitions, v =>
v.coinAddresses.some(t => params.poolDefinitions.find(p => p.tokenAddress === t)),
const { network, poolDefinitions, resolvePoolContract, resolvePoolCoinAddresses } = params;
const multicall = this.appToolkit.getMulticall(network);

const definitionsWithCoinAddresses = await Promise.all(
poolDefinitions.map(async definition => {
if (definition.coinAddresses) return definition;
const poolContract = resolvePoolContract({ network, address: definition.swapAddress });
const coinAddresses = await resolvePoolCoinAddresses({ multicall, poolContract });
return { ...definition, coinAddresses };
}),
);

const [metaPoolDefinitions, basePoolDefinitions] = partition(definitionsWithCoinAddresses, v =>
v.coinAddresses!.some(t => params.poolDefinitions.find(p => p.tokenAddress === t)),
);

const baseCurveTokens = await this._getTokens({ ...params, poolDefinitions: basePoolDefinitions });
Expand Down
2 changes: 2 additions & 0 deletions src/apps/curve/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export { CurveGaugeIsActiveStrategy } from './helpers/curve.gauge.is-active-stra
export { CurveGaugeRoiStrategy } from './helpers/curve.gauge.roi-strategy';
export { CurvePoolTokenHelper } from './helpers/curve.pool.token-helper';
export { CurvePoolVirtualPriceStrategy } from './helpers/curve.pool.virtual.price-strategy';
export { CurvePoolOnChainCoinStrategy } from './helpers/curve.pool.on-chain.coin-strategy';
export { CurvePoolOnChainReserveStrategy } from './helpers/curve.pool.on-chain.reserve-strategy';
export { CurveVestingEscrowContractPositionBalanceHelper } from './helpers/curve.vesting-escrow.contract-position-balance-helper';
export { CurveVestingEscrowContractPositionHelper } from './helpers/curve.vesting-escrow.contract-position-helper';
export { CurveVotingEscrowContractPositionBalanceHelper } from './helpers/curve.voting-escrow.contract-position-balance-helper';
Expand Down
10 changes: 0 additions & 10 deletions src/apps/kinesis-labs/evmos/kinesis-labs.pool.definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,10 @@ export const KINESIS_LABS_BASEPOOL_DEFINITIONS: CurvePoolDefinition[] = [
{
swapAddress: '0x49b97224655aad13832296b8f6185231afb8aacc',
tokenAddress: '0xfb25679ff0651cde4cf58887be266b68326ddab6',
coinAddresses: [
'0xe03494d0033687543a80c9b1ca7d6237f2ea8bd8',
'0x51e44ffad5c2b122c8b635671fcc8139dc636e82',
'0x7ff4a56b32ee13d7d4d405887e0ea37d61ed919e',
],
},
// Celer Base Pool celerUSDC/celerUSDT/FRAX
{
swapAddress: '0xbbd5a7ae45a484bd8dabdfeeeb33e4b859d2c95c',
tokenAddress: '0xf6b65b88a9e7846ed0de503e682cc230f892c2fa',
coinAddresses: [
'0xe03494d0033687543a80c9b1ca7d6237f2ea8bd8',
'0xe46910336479f254723710d57e7b683f3315b22b',
'0xb72a7567847aba28a2819b855d7fe679d4f59846',
],
},
];

0 comments on commit e882778

Please sign in to comment.