Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create SorFactory #3

Open
wants to merge 1 commit into
base: daniel/migrate-abis-from-sor
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions balancer-js/src/sor/pool-data/onChainData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import { formatFixed } from '@ethersproject/bignumber';
import { Provider } from '@ethersproject/providers';
import { PoolFilter, SubgraphPoolBase } from '@balancer-labs/sor';
import { Multicaller } from '../../utils/multiCaller';
import { isSameAddress } from '../../utils';

// TODO: decide whether we want to trim these ABIs down to the relevant functions
import vaultAbi from '../../abi/Vault.json';
import aTokenRateProvider from '../../abi/StaticATokenRateProvider.json';
import weightedPoolAbi from '../../abi/WeightedPool.json';
import stablePoolAbi from '../../abi/StablePool.json';
import elementPoolAbi from '../../abi/ConvergentCurvePool.json';
import linearPoolAbi from '../../abi/LinearPool.json';

export async function getOnChainBalances(
subgraphPoolsOriginal: SubgraphPoolBase[],
multiAddress: string,
vaultAddress: string,
provider: Provider
): Promise<SubgraphPoolBase[]> {
if (subgraphPoolsOriginal.length === 0) return subgraphPoolsOriginal;

const abis: any = Object.values(
// Remove duplicate entries using their names
Object.fromEntries(
[
...vaultAbi,
...aTokenRateProvider,
...weightedPoolAbi,
...stablePoolAbi,
...elementPoolAbi,
...linearPoolAbi,
].map((row) => [row.name, row])
)
);

const multiPool = new Multicaller(multiAddress, provider, abis);

const supportedPoolTypes: string[] = Object.values(PoolFilter);
const subgraphPools: SubgraphPoolBase[] = [];
subgraphPoolsOriginal.forEach((pool) => {
if (!supportedPoolTypes.includes(pool.poolType)) {
console.error(`Unknown pool type: ${pool.poolType} ${pool.id}`);
return;
}

subgraphPools.push(pool);

multiPool.call(`${pool.id}.poolTokens`, vaultAddress, 'getPoolTokens', [
pool.id,
]);
multiPool.call(`${pool.id}.totalSupply`, pool.address, 'totalSupply');

// TO DO - Make this part of class to make more flexible?
if (
pool.poolType === 'Weighted' ||
pool.poolType === 'LiquidityBootstrapping' ||
pool.poolType === 'Investment'
) {
multiPool.call(
`${pool.id}.weights`,
pool.address,
'getNormalizedWeights'
);
multiPool.call(
`${pool.id}.swapFee`,
pool.address,
'getSwapFeePercentage'
);
} else if (
pool.poolType === 'Stable' ||
pool.poolType === 'MetaStable' ||
pool.poolType === 'StablePhantom'
) {
// MetaStable & StablePhantom is the same as Stable for multicall purposes
multiPool.call(
`${pool.id}.amp`,
pool.address,
'getAmplificationParameter'
);
multiPool.call(
`${pool.id}.swapFee`,
pool.address,
'getSwapFeePercentage'
);
} else if (pool.poolType === 'Element') {
multiPool.call(`${pool.id}.swapFee`, pool.address, 'percentFee');
} else if (pool.poolType === 'AaveLinear') {
multiPool.call(
`${pool.id}.swapFee`,
pool.address,
'getSwapFeePercentage'
);

multiPool.call(`${pool.id}.targets`, pool.address, 'getTargets');
multiPool.call(
`${pool.id}.rate`,
pool.address,
'getWrappedTokenRate'
);
}
});

let pools = {} as Record<
string,
{
amp?: string[];
swapFee: string;
weights?: string[];
targets?: string[];
poolTokens: {
tokens: string[];
balances: string[];
};
rate?: string;
}
>;

try {
pools = (await multiPool.execute()) as Record<
string,
{
amp?: string[];
swapFee: string;
weights?: string[];
poolTokens: {
tokens: string[];
balances: string[];
};
rate?: string;
}
>;
} catch (err) {
throw `Issue with multicall execution.`;
}

const onChainPools: SubgraphPoolBase[] = [];

Object.entries(pools).forEach(([poolId, onchainData], index) => {
try {
const { poolTokens, swapFee, weights } = onchainData;

if (
subgraphPools[index].poolType === 'Stable' ||
subgraphPools[index].poolType === 'MetaStable' ||
subgraphPools[index].poolType === 'StablePhantom'
) {
if (!onchainData.amp) {
console.error(`Stable Pool Missing Amp: ${poolId}`);
return;
} else {
// Need to scale amp by precision to match expected Subgraph scale
// amp is stored with 3 decimals of precision
subgraphPools[index].amp = formatFixed(
onchainData.amp[0],
3
);
}
}

if (subgraphPools[index].poolType === 'AaveLinear') {
if (!onchainData.targets) {
console.error(`Linear Pool Missing Targets: ${poolId}`);
return;
} else {
subgraphPools[index].lowerTarget = formatFixed(
onchainData.targets[0],
18
);
subgraphPools[index].upperTarget = formatFixed(
onchainData.targets[1],
18
);
}

const wrappedIndex = subgraphPools[index].wrappedIndex;
if (
wrappedIndex === undefined ||
onchainData.rate === undefined
) {
console.error(
`Linear Pool Missing WrappedIndex or PriceRate: ${poolId}`
);
return;
}
// Update priceRate of wrappedToken
subgraphPools[index].tokens[wrappedIndex].priceRate =
formatFixed(onchainData.rate, 18);
}

subgraphPools[index].swapFee = formatFixed(swapFee, 18);

poolTokens.tokens.forEach((token, i) => {
const T = subgraphPools[index].tokens.find((t) =>
isSameAddress(t.address, token)
);
if (!T) throw `Pool Missing Expected Token: ${poolId} ${token}`;
T.balance = formatFixed(poolTokens.balances[i], T.decimals);
if (weights) {
// Only expected for WeightedPools
T.weight = formatFixed(weights[i], 18);
}
});
onChainPools.push(subgraphPools[index]);
} catch (err) {
throw `Issue with pool onchain data: ${err}`;
}
});

return onChainPools;
}
86 changes: 86 additions & 0 deletions balancer-js/src/sor/pool-data/subgraphPoolDataService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { PoolDataService, SubgraphPoolBase } from '@balancer-labs/sor';
import {
OrderDirection,
Pool_OrderBy,
SubgraphClient,
} from '../../subgraph/subgraph';
import { parseInt } from 'lodash';
import { getOnChainBalances } from './onChainData';
import { Provider } from '@ethersproject/providers';
import { Network } from '../../constants/network';
import { BalancerNetworkConfig, BalancerSdkSorConfig } from '../../types';

const NETWORKS_WITH_LINEAR_POOLS = [
Network.MAINNET,
Network.ROPSTEN,
Network.RINKEBY,
Network.GÖRLI,
Network.KOVAN,
];

export class SubgraphPoolDataService implements PoolDataService {
constructor(
private readonly client: SubgraphClient,
private readonly provider: Provider,
private readonly network: BalancerNetworkConfig,
private readonly sorConfig: BalancerSdkSorConfig
) {}

public async getPools(): Promise<SubgraphPoolBase[]> {
const pools = this.supportsLinearPools
? await this.getLinearPools()
: await this.getNonLinearPools();

const mapped = pools.map((pool) => ({
...pool,
poolType: pool.poolType || '',
tokens: (pool.tokens || []).map((token) => ({
...token,
weight: token.weight || null,
})),
totalWeight: pool.totalWeight || undefined,
amp: pool.amp || undefined,
expiryTime: pool.expiryTime ? parseInt(pool.expiryTime) : undefined,
unitSeconds: pool.unitSeconds
? parseInt(pool.unitSeconds)
: undefined,
principalToken: pool.principalToken || undefined,
baseToken: pool.baseToken || undefined,
}));

if (this.sorConfig.fetchOnChainBalances === false) {
return mapped;
}

return getOnChainBalances(
mapped,
this.network.multicall,
this.network.vault,
this.provider
);
}

private get supportsLinearPools() {
return NETWORKS_WITH_LINEAR_POOLS.includes(this.network.chainId);
}

private async getLinearPools() {
const { pools } = await this.client.SubgraphPools({
where: { swapEnabled: true },
orderBy: Pool_OrderBy.TotalLiquidity,
orderDirection: OrderDirection.Desc,
});

return pools;
}

private async getNonLinearPools() {
const { pools } = await this.client.SubgraphPoolsWithoutLinear({
where: { swapEnabled: true },
orderBy: Pool_OrderBy.TotalLiquidity,
orderDirection: OrderDirection.Desc,
});

return pools;
}
}
61 changes: 61 additions & 0 deletions balancer-js/src/sor/sorFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { SOR, TokenPriceService } from '@balancer-labs/sor';
import { Provider } from '@ethersproject/providers';
import { SubgraphPoolDataService } from './pool-data/subgraphPoolDataService';
import { CoingeckoTokenPriceService } from './token-price/coingeckoTokenPriceService';
import { SubgraphClient } from '../subgraph/subgraph';
import { BalancerNetworkConfig, BalancerSdkSorConfig } from '../types';
import { SubgraphTokenPriceService } from './token-price/subgraphTokenPriceService';

export class SorFactory {
public static createSor(
network: BalancerNetworkConfig,
sorConfig: BalancerSdkSorConfig,
provider: Provider,
subgraphClient: SubgraphClient
): SOR {
const poolDataService = SorFactory.getPoolDataService(
network,
sorConfig,
provider,
subgraphClient
);

const tokenPriceService = SorFactory.getTokenPriceService(
network,
sorConfig,
subgraphClient
);

return new SOR(provider, network, poolDataService, tokenPriceService);
}

private static getPoolDataService(
network: BalancerNetworkConfig,
sorConfig: BalancerSdkSorConfig,
provider: Provider,
subgraphClient: SubgraphClient
) {
return typeof sorConfig.poolDataService === 'object'
? sorConfig.poolDataService
: new SubgraphPoolDataService(
subgraphClient,
provider,
network,
sorConfig
);
}

private static getTokenPriceService(
network: BalancerNetworkConfig,
sorConfig: BalancerSdkSorConfig,
subgraphClient: SubgraphClient
): TokenPriceService {
if (typeof sorConfig.tokenPriceService === 'object') {
return sorConfig.tokenPriceService;
} else if (sorConfig.tokenPriceService === 'subgraph') {
new SubgraphTokenPriceService(subgraphClient, network.weth);
}

return new CoingeckoTokenPriceService(network.chainId);
}
}
Loading