Skip to content

Commit

Permalink
Merge pull request #168 from balancer-labs/develop
Browse files Browse the repository at this point in the history
Release 0.1.27
  • Loading branch information
gmbronco authored Oct 7, 2022
2 parents 48c764b + 7a0889e commit b6e2abe
Show file tree
Hide file tree
Showing 17 changed files with 218 additions and 86 deletions.
12 changes: 4 additions & 8 deletions balancer-js/examples/pools/calculateLiquidity.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import {
Liquidity,
StaticPoolRepository,
StaticTokenPriceProvider,
Pool,
TokenPrices,
} from '../../src';
import { findable } from '../../src/test/factories/data';
import { formatFixed } from '@ethersproject/bignumber';
import { parseFixed } from '../../src/lib/utils/math';
import { FallbackPoolRepository } from '../../src/modules/data/pool';
import POOLS from './pools.json';
import DECORATED_POOLS from './decorated-pools.json';
import TOKENS from './tokens.json';
Expand All @@ -28,12 +27,9 @@ TOKENS.forEach((token) => {
}
});

// const sorPoolProvider = new SORPoolProvider(config);
const staticPoolProvider = new StaticPoolRepository(POOLS as Pool[]);
const poolProvider = new FallbackPoolRepository([
// sorPoolProvider,
staticPoolProvider,
]);
const pools = new Map<string, Pool>();
POOLS.forEach((pool) => pools.set(pool.id, pool as Pool));
const poolProvider = findable<Pool>(pools);
const tokenPriceProvider = new StaticTokenPriceProvider(tokenPrices);

const liquidity = new Liquidity(poolProvider, tokenPriceProvider);
Expand Down
22 changes: 22 additions & 0 deletions balancer-js/examples/pools/liquidity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { BalancerSDK } from '../../src';

const sdk = new BalancerSDK({
network: 1,
rpcUrl: 'https://eth-rpc.gateway.pokt.network',
});

const { pools } = sdk;

const main = async () => {
[
'0xa13a9247ea42d743238089903570127dda72fe4400000000000000000000035d',
].forEach(async (poolId) => {
const pool = await pools.find(poolId);
if (pool) {
const liquidity = await pools.liquidity(pool);
console.log(pool.totalShares, pool.totalLiquidity, liquidity);
}
});
};

main();
2 changes: 1 addition & 1 deletion balancer-js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@balancer-labs/sdk",
"version": "0.1.26",
"version": "0.1.27",
"description": "JavaScript SDK for interacting with the Balancer Protocol V2",
"license": "GPL-3.0-only",
"homepage": "https://github.com/balancer-labs/balancer-sdk/balancer-js#readme",
Expand Down
15 changes: 13 additions & 2 deletions balancer-js/src/modules/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ export * from './block-number';
import { BalancerNetworkConfig, BalancerDataRepositories } from '@/types';
import { PoolsSubgraphRepository } from './pool/subgraph';
import { BlockNumberRepository } from './block-number';
import { CoingeckoPriceRepository } from './token-prices/coingecko';
import {
CoingeckoPriceRepository,
AaveRates,
TokenPriceProvider,
} from './token-prices';
import { StaticTokenProvider } from './token/static';
import { LiquidityGaugeSubgraphRPCProvider } from './liquidity-gauges/provider';
import { FeeDistributorRepository } from './fee-distributor/repository';
Expand Down Expand Up @@ -68,11 +72,18 @@ export class Data implements BalancerDataRepositories {
.filter((t) => t.chainId == networkConfig.chainId)
.map((t) => t.address);

this.tokenPrices = new CoingeckoPriceRepository(
const coingeckoRepository = new CoingeckoPriceRepository(
tokenAddresses,
networkConfig.chainId
);

const aaveRates = new AaveRates(
networkConfig.addresses.contracts.multicall,
provider
);

this.tokenPrices = new TokenPriceProvider(coingeckoRepository, aaveRates);

this.tokenMeta = new StaticTokenProvider([]);

if (networkConfig.urls.gaugesSubgraph) {
Expand Down
21 changes: 19 additions & 2 deletions balancer-js/src/modules/data/pool/subgraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import {
SubgraphPool,
Pool_OrderBy,
OrderDirection,
SubgraphPoolTokenFragment,
} from '@/modules/subgraph/subgraph';
import {
GraphQLArgsBuilder,
SubgraphArgsFormatter,
} from '@/lib/graphql/args-builder';
import { GraphQLArgs } from '@/lib/graphql/types';
import { PoolAttribute, PoolsRepositoryFetchOptions } from './types';
import { GraphQLQuery, Pool, PoolType } from '@/types';
import { GraphQLQuery, Pool, PoolType, PoolToken } from '@/types';
import { Network } from '@/lib/constants/network';
import { PoolsQueryVariables } from '../../subgraph/subgraph';

Expand Down Expand Up @@ -153,7 +154,7 @@ export class PoolsSubgraphRepository
amp: subgraphPool.amp ?? undefined,
owner: subgraphPool.owner ?? undefined,
factory: subgraphPool.factory ?? undefined,
tokens: subgraphPool.tokens || [],
tokens: (subgraphPool.tokens || []).map(this.mapToken),
tokensList: subgraphPool.tokensList,
tokenAddresses: (subgraphPool.tokens || []).map((t) => t.address),
totalLiquidity: subgraphPool.totalLiquidity,
Expand All @@ -174,4 +175,20 @@ export class PoolsSubgraphRepository
totalWeight: subgraphPool.totalWeight || '1',
};
}

private mapToken(subgraphToken: SubgraphPoolTokenFragment): PoolToken {
let subgraphTokenPool = null;
if (subgraphToken.token?.pool) {
subgraphTokenPool = {
...subgraphToken.token.pool,
poolType: subgraphToken.token.pool.poolType as PoolType,
};
}
return {
...subgraphToken,
token: {
pool: subgraphTokenPool,
},
};
}
}
45 changes: 45 additions & 0 deletions balancer-js/src/modules/data/token-prices/aave-rates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Interface } from '@ethersproject/abi';
import { Contract } from '@ethersproject/contracts';
import { Provider } from '@ethersproject/providers';
import { formatUnits } from '@ethersproject/units';
import { Multicall } from '@/modules/contracts/multicall';
import { yieldTokens } from '../token-yields/tokens/aave';

const wrappedATokenInterface = new Interface([
'function rate() view returns (uint256)',
]);

export class AaveRates {
multicall: Contract;
rates?: Promise<{ [wrappedATokenAddress: string]: number }>;

constructor(multicallAddress: string, provider: Provider) {
this.multicall = Multicall(multicallAddress, provider);
}

private async fetch(): Promise<{ [wrappedATokenAddress: string]: number }> {
console.time('Fetching aave rates');
const addresses = Object.values(yieldTokens);
const payload = addresses.map((wrappedATokenAddress) => [
wrappedATokenAddress,
wrappedATokenInterface.encodeFunctionData('rate', []),
]);
const [, res] = await this.multicall.aggregate(payload);

const rates = addresses.reduce((p: { [key: string]: number }, a, i) => {
p[a] ||= res[i] == '0x' ? 0 : parseFloat(formatUnits(res[i], 27));
return p;
}, {});
console.timeEnd('Fetching aave rates');

return rates;
}

async getRate(wrappedAToken: string): Promise<number> {
if (!this.rates) {
this.rates = this.fetch();
}

return (await this.rates)[wrappedAToken];
}
}
6 changes: 4 additions & 2 deletions balancer-js/src/modules/data/token-prices/coingecko.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ describe('coingecko repository', () => {
});

it('finds prices', async () => {
const [price1, price2, price3, price4, price5] = await Promise.all([
const [price1, price2, price3, price4, price5, price6] = await Promise.all([
repository.find(addresses[0]),
repository.find(addresses[0].toUpperCase()),
repository.find(addresses[1]),
repository.find(addresses[2]),
repository.find(addresses[3]),
Expand All @@ -51,7 +52,8 @@ describe('coingecko repository', () => {
expect(price1?.usd).to.be.gt(0);
expect(price2?.usd).to.be.gt(0);
expect(price3?.usd).to.be.gt(0);
expect(price4?.usd).to.be.undefined;
expect(price4?.usd).to.be.gt(0);
expect(price5?.usd).to.be.undefined;
expect(price6?.usd).to.be.undefined;
});
});
3 changes: 2 additions & 1 deletion balancer-js/src/modules/data/token-prices/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './types';
export * from './static';
export * from './coingecko';
export * from './provider';
export * from './aave-rates';
31 changes: 31 additions & 0 deletions balancer-js/src/modules/data/token-prices/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Findable, Price } from '@/types';
import { AaveRates } from './aave-rates';
import { CoingeckoPriceRepository } from './coingecko';

export class TokenPriceProvider implements Findable<Price> {
constructor(
private coingeckoRepository: CoingeckoPriceRepository,
private aaveRates: AaveRates
) {}

async find(address: string): Promise<Price | undefined> {
const price = await this.coingeckoRepository.find(address);
const rate = (await this.aaveRates.getRate(address)) || 1;
if (price && price.usd) {
return {
...price,
usd: (parseFloat(price.usd) * rate).toString(),
};
} else {
return price;
}
}

async findBy(attribute: string, value: string): Promise<Price | undefined> {
if (attribute === 'address') {
return this.find(value);
} else {
throw `Token price search by ${attribute} not implemented`;
}
}
}
38 changes: 38 additions & 0 deletions balancer-js/src/modules/data/token-prices/static.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { TokenPrices } from '@/types';
import { expect } from 'chai';
import { StaticTokenPriceProvider } from './static';

const TOKENS = {
BAL: '0x9a71012B13CA4d3D0Cdc72A177DF3ef03b0E76A3',
WMATIC: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
};

let staticTokenPriceProvider: StaticTokenPriceProvider;

describe('static token prices repository', () => {
it('Should store token addresses as lower case internally', async () => {
const tokenPrices: TokenPrices = {
[TOKENS.BAL]: {
usd: '10',
},
};
staticTokenPriceProvider = new StaticTokenPriceProvider(tokenPrices);
expect(
await staticTokenPriceProvider.find(TOKENS.BAL.toLowerCase())
).to.deep.eq({
usd: '10',
});
});

it('When finding by upper case address it converts to lower case', async () => {
const tokenPrices: TokenPrices = {
[TOKENS.BAL.toLowerCase()]: {
usd: '10',
},
};
staticTokenPriceProvider = new StaticTokenPriceProvider(tokenPrices);
expect(await staticTokenPriceProvider.find(TOKENS.BAL)).to.deep.eq({
usd: '10',
});
});
});
63 changes: 12 additions & 51 deletions balancer-js/src/modules/data/token-prices/static.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,18 @@
import { BigNumber, formatFixed } from '@ethersproject/bignumber';
import { parseFixed } from '@/lib/utils/math';
import { Price, TokenPrices } from '@/types';
import { TokenPriceProvider } from './types';

const SCALING_FACTOR = 18;

export class StaticTokenPriceProvider implements TokenPriceProvider {
constructor(private tokenPrices: TokenPrices) {
this.calculateUSDPrices();
}

/**
* Iterates through all tokens and calculates USD prices
* based on data the tokens already have.
*/
calculateUSDPrices(): void {
const USDAssets = [
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
'0x6b175474e89094c44da98b954eedeac495271d0f',
'0x6B175474E89094C44Da98b954EedeAC495271d0F',
'0xdac17f958d2ee523a2206206994597c13d831ec7',
'0xdAC17F958D2ee523a2206206994597C13D831ec7',
];
let assetsAvailable = 0;
let assetValueSum = BigNumber.from(0);

USDAssets.forEach((address) => {
const tokenPrice = this.tokenPrices[address];
if (tokenPrice?.eth) {
const scaledPrice = parseFixed(tokenPrice?.eth, SCALING_FACTOR);
assetValueSum = assetValueSum.add(scaledPrice);
assetsAvailable++;
}
});

if (assetsAvailable === 0) return;
const NativeAssetUSDPrice = assetValueSum.div(assetsAvailable);

for (const token in this.tokenPrices) {
const price = this.tokenPrices[token];
if (price.eth && !price.usd) {
const usdPrice = parseFixed('1', SCALING_FACTOR)
.mul(parseFixed(price.eth, SCALING_FACTOR))
.div(NativeAssetUSDPrice)
.toString();
price.usd = formatFixed(usdPrice, SCALING_FACTOR);
}
}
import { Findable, Price, TokenPrices } from '@/types';

export class StaticTokenPriceProvider implements Findable<Price> {
tokenPrices: TokenPrices;
constructor(tokenPrices: TokenPrices) {
this.tokenPrices = Object.fromEntries(
Object.entries(tokenPrices).map(([address, price]) => {
return [address.toLowerCase(), price];
})
);
}

async find(address: string): Promise<Price | undefined> {
const price = this.tokenPrices[address];
const lowercaseAddress = address.toLowerCase();
const price = this.tokenPrices[lowercaseAddress];
if (!price) return;
return price;
}
Expand Down
5 changes: 0 additions & 5 deletions balancer-js/src/modules/data/token-prices/types.ts

This file was deleted.

5 changes: 2 additions & 3 deletions balancer-js/src/modules/liquidity/liquidity.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Findable, Pool, PoolToken } from '@/types';
import { Findable, Pool, PoolToken, Price } from '@/types';
import { PoolAttribute } from '../data';
import { TokenPriceProvider } from '../data';
import { PoolTypeConcerns } from '../pools/pool-type-concerns';
import { BigNumber } from '@ethersproject/bignumber';
import { formatFixed, parseFixed } from '@/lib/utils/math';
Expand All @@ -15,7 +14,7 @@ export interface PoolBPTValue {
export class Liquidity {
constructor(
private pools: Findable<Pool, PoolAttribute>,
private tokenPrices: TokenPriceProvider
private tokenPrices: Findable<Price>
) {}

async getLiquidity(pool: Pool): Promise<string> {
Expand Down
7 changes: 7 additions & 0 deletions balancer-js/src/modules/pools/apr/apr.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,13 @@ describe('pool apr', () => {
).rewardAprs(poolData);

expect(apr.total).to.eq(20000);

const aprBreakdownSum = Object.values(apr.breakdown).reduce(
(total, current) => (total += current),
0
);

expect(aprBreakdownSum).to.eq(apr.total);
});
});
});
Loading

0 comments on commit b6e2abe

Please sign in to comment.