From cc83478cf39f1b883301d9a8c49391ce0d982ca0 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 14 Jul 2022 16:56:11 -0300 Subject: [PATCH 01/65] Extracted parsePoolInfo into poolHelper --- balancer-js/src/lib/utils/index.ts | 1 + balancer-js/src/lib/utils/poolHelper.ts | 39 ++++++ .../concerns/weighted/join.concern.ts | 124 ++++++------------ 3 files changed, 77 insertions(+), 87 deletions(-) create mode 100644 balancer-js/src/lib/utils/poolHelper.ts diff --git a/balancer-js/src/lib/utils/index.ts b/balancer-js/src/lib/utils/index.ts index d854d7baf..91032e08d 100644 --- a/balancer-js/src/lib/utils/index.ts +++ b/balancer-js/src/lib/utils/index.ts @@ -5,6 +5,7 @@ export * from './permit'; export * from './signatures'; export * from './assetHelpers'; export * from './aaveHelpers'; +export * from './poolHelper'; export const isSameAddress = (address1: string, address2: string): boolean => getAddress(address1) === getAddress(address2); diff --git a/balancer-js/src/lib/utils/poolHelper.ts b/balancer-js/src/lib/utils/poolHelper.ts new file mode 100644 index 000000000..eace59dd8 --- /dev/null +++ b/balancer-js/src/lib/utils/poolHelper.ts @@ -0,0 +1,39 @@ +import { parseFixed } from '@ethersproject/bignumber'; +import { Pool } from '../../types'; + +const AMP_PRECISION = 3; // number of decimals -> precision 1000 + +/** + * Parse pool info into EVM amounts + * @param {Pool} pool + * @returns parsed pool info + */ +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const parsePoolInfo = (pool: Pool) => { + const parsedTokens = pool.tokens.map((token) => token.address); + const parsedBalances = pool.tokens.map((token) => + parseFixed(token.balance, token.decimals).toString() + ); + const parsedWeights = pool.tokens.map((token) => { + return token.weight ? parseFixed(token.weight, 18).toString() : undefined; + }); + const parsedPriceRates = pool.tokens.map((token) => { + return token.priceRate + ? parseFixed(token.priceRate, 18).toString() + : undefined; + }); + const parsedAmp = pool.amp + ? parseFixed(pool.amp, AMP_PRECISION).toString() // Solidity maths uses precison method for amp that must be replicated + : undefined; + const parsedTotalShares = parseFixed(pool.totalShares, 18).toString(); + const parsedSwapFee = parseFixed(pool.swapFee, 18).toString(); + return { + parsedTokens, + parsedBalances, + parsedWeights, + parsedPriceRates, + parsedAmp, + parsedTotalShares, + parsedSwapFee, + }; +}; diff --git a/balancer-js/src/modules/pools/pool-types/concerns/weighted/join.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/weighted/join.concern.ts index 6e41207e1..cf5bf6c2d 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/weighted/join.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/weighted/join.concern.ts @@ -1,4 +1,3 @@ -import { parseUnits } from '@ethersproject/units'; import OldBigNumber from 'bignumber.js'; import * as SDK from '@georgeroman/balancer-v2-pools'; @@ -9,12 +8,11 @@ import { JoinPoolAttributes, JoinPoolParameters, } from '../types'; -import { Pool } from '@/types'; import { subSlippage } from '@/lib/utils/slippageHelper'; -import { AssetHelpers } from '@/lib/utils'; +import { AssetHelpers, parsePoolInfo } from '@/lib/utils'; import { balancerVault } from '@/lib/constants/config'; import { Vault__factory } from '@balancer-labs/typechain'; -import { BigNumber, parseFixed } from '@ethersproject/bignumber'; +import { BigNumber } from '@ethersproject/bignumber'; import { AddressZero } from '@ethersproject/constants'; import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; @@ -80,22 +78,40 @@ export class WeightedPoolJoin implements JoinConcern { throw new BalancerError(BalancerErrorCode.INPUT_LENGTH_MISMATCH); } - const parsedPoolInfo = this.parsePoolInfo(pool); // Parse pool info into EVM amounts in order to match amountsIn scalling - const sortedCalcInputs = this.sortCalcInputs( - parsedPoolInfo.tokens, - parsedPoolInfo.balances, - parsedPoolInfo.weights, - parsedPoolInfo.decimals, + // Check if there's any relevant weighted pool info missing + if (pool.tokens.some((token) => !token.decimals)) + throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); + if (pool.tokens.some((token) => !token.weight)) + throw new BalancerError(BalancerErrorCode.MISSING_WEIGHT); + + // Parse pool info into EVM amounts in order to match amountsIn scalling + const { + parsedTokens, + parsedBalances, + parsedWeights, + parsedTotalShares, + parsedSwapFee, + } = parsePoolInfo(pool); + + const assetHelpers = new AssetHelpers(wrappedNativeAsset); + // sort inputs + const [sortedTokens, sortedAmounts] = assetHelpers.sortTokens( tokensIn, - amountsIn, - wrappedNativeAsset - ); + amountsIn + ) as [string[], string[]]; + // sort pool info + const [, sortedBalances, sortedWeights] = assetHelpers.sortTokens( + parsedTokens, + parsedBalances, + parsedWeights + ) as [string[], string[], string[]]; + const expectedBPTOut = SDK.WeightedMath._calcBptOutGivenExactTokensIn( - sortedCalcInputs.balances.map((b) => new OldBigNumber(b)), - sortedCalcInputs.weights.map((w) => new OldBigNumber(w)), - sortedCalcInputs.amounts.map((a) => new OldBigNumber(a)), - new OldBigNumber(parsedPoolInfo.totalShares), - new OldBigNumber(parsedPoolInfo.swapFee) + sortedBalances.map((b) => new OldBigNumber(b)), + sortedWeights.map((w) => new OldBigNumber(w)), + sortedAmounts.map((a) => new OldBigNumber(a)), + new OldBigNumber(parsedTotalShares), + new OldBigNumber(parsedSwapFee) ).toString(); const minBPTOut = subSlippage( @@ -104,7 +120,7 @@ export class WeightedPoolJoin implements JoinConcern { ).toString(); const userData = WeightedPoolEncoder.joinExactTokensInForBPTOut( - sortedCalcInputs.amounts, + sortedAmounts, minBPTOut ); @@ -115,8 +131,8 @@ export class WeightedPoolJoin implements JoinConcern { sender: joiner, recipient: joiner, joinPoolRequest: { - assets: sortedCalcInputs.tokens, - maxAmountsIn: sortedCalcInputs.amounts, + assets: sortedTokens, + maxAmountsIn: sortedAmounts, userData, fromInternalBalance: false, }, @@ -126,71 +142,5 @@ export class WeightedPoolJoin implements JoinConcern { const value = values[0] ? BigNumber.from(values[0]) : undefined; return { to, functionName, attributes, data, value, minBPTOut }; - } - - // Helper methods - - /** - * Sort BPT calc. inputs alphabetically by token addresses as required by calcBptOutGivenExactTokensIn - */ - private sortCalcInputs = ( - poolTokens: string[], - poolBalances: string[], - poolWeights: string[], - poolDecimals: number[], - tokensIn: string[], - amountsIn: string[], - wrappedNativeAsset: string - ) => { - const assetHelpers = new AssetHelpers(wrappedNativeAsset); - const [tokens, amounts] = assetHelpers.sortTokens(tokensIn, amountsIn) as [ - string[], - string[] - ]; - const [, balances, weights, decimals] = assetHelpers.sortTokens( - poolTokens, - poolBalances, - poolWeights, - poolDecimals - ) as [string[], string[], string[], number[]]; - return { - tokens, - balances, - weights, - amounts, - decimals, - }; - }; - - /** - * Parse pool info into EVM amounts - * @param {Pool} pool - * @returns parsed pool info - */ - private parsePoolInfo = (pool: Pool) => { - const decimals = pool.tokens.map((token) => { - if (!token.decimals) - throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); - return token.decimals; - }); - const weights = pool.tokens.map((token) => { - if (!token.weight) - throw new BalancerError(BalancerErrorCode.MISSING_WEIGHT); - return parseUnits(token.weight).toString(); - }); - const tokens = pool.tokens.map((token) => token.address); - const balances = pool.tokens.map((token) => - parseFixed(token.balance, token.decimals).toString() - ); - const totalShares = parseUnits(pool.totalShares).toString(); - const swapFee = parseUnits(pool.swapFee).toString(); - return { - tokens, - balances, - weights, - decimals, - totalShares, - swapFee, - }; }; } From 8db485fdd2402d051983d7d9f4cb0be6628b5c5e Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 21 Jul 2022 13:06:46 -0300 Subject: [PATCH 02/65] Refactor exit concern to use extracted parsePoolInfo --- .../concerns/weighted/exit.concern.ts | 173 ++++++------------ 1 file changed, 60 insertions(+), 113 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/weighted/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/weighted/exit.concern.ts index 38ce2f928..835e7d044 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/weighted/exit.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/weighted/exit.concern.ts @@ -1,5 +1,4 @@ import { BigNumber, parseFixed } from '@ethersproject/bignumber'; -import { parseUnits } from '@ethersproject/units'; import OldBigNumber from 'bignumber.js'; import * as SDK from '@georgeroman/balancer-v2-pools'; import { @@ -9,12 +8,11 @@ import { ExitPool, ExitPoolAttributes, } from '../types'; -import { AssetHelpers } from '@/lib/utils'; +import { AssetHelpers, parsePoolInfo } from '@/lib/utils'; import { Vault__factory } from '@balancer-labs/typechain'; import { WeightedPoolEncoder } from '@/pool-weighted'; import { addSlippage, subSlippage } from '@/lib/utils/slippageHelper'; import { balancerVault } from '@/lib/constants/config'; -import { Pool } from '@/types'; import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; export class WeightedPoolExit implements ExitConcern { @@ -44,29 +42,43 @@ export class WeightedPoolExit implements ExitConcern { throw new BalancerError(BalancerErrorCode.TOKEN_MISMATCH); } - const parsedPoolInfo = this.parsePoolInfo(pool); // Parse pool info into EVM amounts in order to match amountsIn scalling - const sortedPoolInfo = this.sortPoolInfo( - parsedPoolInfo.tokens, - parsedPoolInfo.balances, - parsedPoolInfo.weights, - parsedPoolInfo.decimals - ); + // Check if there's any relevant weighted pool info missing + if (pool.tokens.some((token) => !token.decimals)) + throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); + if (pool.tokens.some((token) => !token.weight)) + throw new BalancerError(BalancerErrorCode.MISSING_WEIGHT); + + const { + parsedTokens, + parsedBalances, + parsedWeights, + parsedTotalShares, + parsedSwapFee, + } = parsePoolInfo(pool); - let minAmountsOut = Array(parsedPoolInfo.tokens.length).fill('0'); + const WETH = '0x000000000000000000000000000000000000000F'; // TODO: check if it should be possible to exit with ETH instead of WETH + const assetHelpers = new AssetHelpers(WETH); + const [sortedTokens, sortedBalances, sortedWeights] = + assetHelpers.sortTokens(parsedTokens, parsedBalances, parsedWeights) as [ + string[], + string[], + string[] + ]; + + let minAmountsOut = Array(parsedTokens.length).fill('0'); let userData: string; if (singleTokenMaxOut) { // Exit pool with single token using exact bptIn - const singleTokenMaxOutIndex = - parsedPoolInfo.tokens.indexOf(singleTokenMaxOut); + const singleTokenMaxOutIndex = parsedTokens.indexOf(singleTokenMaxOut); const amountOut = SDK.WeightedMath._calcTokenOutGivenExactBptIn( - new OldBigNumber(parsedPoolInfo.balances[singleTokenMaxOutIndex]), - new OldBigNumber(parsedPoolInfo.weights[singleTokenMaxOutIndex]), + new OldBigNumber(sortedBalances[singleTokenMaxOutIndex]), + new OldBigNumber(sortedWeights[singleTokenMaxOutIndex]), new OldBigNumber(bptIn), - new OldBigNumber(parsedPoolInfo.totalShares), - new OldBigNumber(parsedPoolInfo.swapFee) + new OldBigNumber(parsedTotalShares), + new OldBigNumber(parsedSwapFee) ).toString(); minAmountsOut[singleTokenMaxOutIndex] = amountOut; @@ -79,9 +91,9 @@ export class WeightedPoolExit implements ExitConcern { // Exit pool with all tokens proportinally const amountsOut = SDK.WeightedMath._calcTokensOutGivenExactBptIn( - sortedPoolInfo.balances.map((b) => new OldBigNumber(b)), + sortedBalances.map((b) => new OldBigNumber(b)), new OldBigNumber(bptIn), - new OldBigNumber(parsedPoolInfo.totalShares) + new OldBigNumber(parsedTotalShares) ).map((amount) => amount.toString()); minAmountsOut = amountsOut.map((amount) => { @@ -102,7 +114,7 @@ export class WeightedPoolExit implements ExitConcern { sender: exiter, recipient: exiter, exitPoolRequest: { - assets: sortedPoolInfo.tokens, + assets: sortedTokens, minAmountsOut, userData, toInternalBalance: false, @@ -150,21 +162,32 @@ export class WeightedPoolExit implements ExitConcern { throw new BalancerError(BalancerErrorCode.INPUT_LENGTH_MISMATCH); } - const parsedPoolInfo = this.parsePoolInfo(pool); - const sortedPoolInfo = this.sortPoolInfo( - parsedPoolInfo.tokens, - parsedPoolInfo.balances, - parsedPoolInfo.weights, - parsedPoolInfo.decimals - ); - const sortedInputs = this.sortInputs(tokensOut, amountsOut); + const { + parsedTokens, + parsedBalances, + parsedWeights, + parsedTotalShares, + parsedSwapFee, + } = parsePoolInfo(pool); + + const WETH = '0x000000000000000000000000000000000000000F'; // TODO: check if it should be possible to exit with ETH instead of WETH + const assetHelpers = new AssetHelpers(WETH); + const [, sortedBalances, sortedWeights] = assetHelpers.sortTokens( + parsedTokens, + parsedBalances, + parsedWeights + ) as [string[], string[], string[]]; + const [sortedTokens, sortedAmounts] = assetHelpers.sortTokens( + tokensOut, + amountsOut + ) as [string[], string[]]; const bptIn = SDK.WeightedMath._calcBptInGivenExactTokensOut( - sortedPoolInfo.balances.map((b) => new OldBigNumber(b)), - sortedPoolInfo.weights.map((w) => new OldBigNumber(w)), - sortedInputs.amounts.map((a) => new OldBigNumber(a)), - new OldBigNumber(parsedPoolInfo.totalShares), - new OldBigNumber(parsedPoolInfo.swapFee) + sortedBalances.map((b) => new OldBigNumber(b)), + sortedWeights.map((w) => new OldBigNumber(w)), + sortedAmounts.map((a) => new OldBigNumber(a)), + new OldBigNumber(parsedTotalShares), + new OldBigNumber(parsedSwapFee) ).toString(); const maxBPTIn = addSlippage( @@ -173,7 +196,7 @@ export class WeightedPoolExit implements ExitConcern { ).toString(); const userData = WeightedPoolEncoder.exitBPTInForExactTokensOut( - sortedInputs.amounts, + sortedAmounts, maxBPTIn ); @@ -184,8 +207,8 @@ export class WeightedPoolExit implements ExitConcern { sender: exiter, recipient: exiter, exitPoolRequest: { - assets: sortedInputs.tokens, - minAmountsOut: sortedInputs.amounts, + assets: sortedTokens, + minAmountsOut: sortedAmounts, userData, toInternalBalance: false, }, @@ -204,84 +227,8 @@ export class WeightedPoolExit implements ExitConcern { functionName, attributes, data, - minAmountsOut: sortedInputs.amounts, + minAmountsOut: sortedAmounts, maxBPTIn, }; }; - - // Helper methods - - /** - * Sort pool info alphabetically by token addresses as required by gerogeroman SDK - */ - private sortPoolInfo = ( - poolTokens: string[], - poolBalances: string[], - poolWeights: string[], - poolDecimals: number[] - ) => { - const WETH = '0x000000000000000000000000000000000000000F'; // TODO: check if it should be possible to exit with ETH instead of WETH - const assetHelpers = new AssetHelpers(WETH); - const [tokens, balances, weights, decimals] = assetHelpers.sortTokens( - poolTokens, - poolBalances, - poolWeights, - poolDecimals - ) as [string[], string[], string[], number[]]; - - return { - tokens, - balances, - weights, - decimals, - }; - }; - - /** - * Sort inputs alphabetically by token addresses as required by gerogeroman SDK - */ - private sortInputs = (tokens: string[], amounts: string[]) => { - const WETH = '0x000000000000000000000000000000000000000F'; // TODO: check if it should be possible to exit with ETH instead of WETH - const assetHelpers = new AssetHelpers(WETH); - const [_tokens, _amounts] = assetHelpers.sortTokens(tokens, amounts) as [ - string[], - string[] - ]; - return { - tokens: _tokens, - amounts: _amounts, - }; - }; - - /** - * Parse pool info into EVM amounts - * @param {Pool} pool - * @returns parsed pool info - */ - private parsePoolInfo = (pool: Pool) => { - const decimals = pool.tokens.map((token) => { - if (!token.decimals) - throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); - return token.decimals; - }); - const weights = pool.tokens.map((token) => { - if (!token.weight) - throw new BalancerError(BalancerErrorCode.MISSING_WEIGHT); - return parseUnits(token.weight).toString(); - }); - const tokens = pool.tokens.map((token) => token.address); - const balances = pool.tokens.map((token) => - parseFixed(token.balance, token.decimals).toString() - ); - const totalShares = parseUnits(pool.totalShares).toString(); - const swapFee = parseUnits(pool.swapFee).toString(); - return { - tokens, - balances, - weights, - decimals, - totalShares, - swapFee, - }; - }; } From 2f35d42fbe2831b2c038ec44f8bb8e35b5f181a5 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 22 Jul 2022 14:12:42 -0300 Subject: [PATCH 03/65] Add amp to Pool --- balancer-js/src/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/balancer-js/src/types.ts b/balancer-js/src/types.ts index 6bfe70a5b..c93287dcb 100644 --- a/balancer-js/src/types.ts +++ b/balancer-js/src/types.ts @@ -198,6 +198,7 @@ export interface Pool { feesSnapshot?: string; boost?: string; symbol?: string; + amp?: string; } export interface PoolModel extends Pool { From 3fa590d5e84a614fd7fd78ca2db321435b405e96 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 22 Jul 2022 14:13:46 -0300 Subject: [PATCH 04/65] Add exit Stable pool --- balancer-js/src/balancerErrors.ts | 3 + .../stable/exit.concern.integration.spec.ts | 297 ++++++++++++++++++ .../concerns/stable/exit.concern.spec.ts | 77 +++++ .../concerns/stable/exit.concern.ts | 214 ++++++++++++- 4 files changed, 585 insertions(+), 6 deletions(-) create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.spec.ts diff --git a/balancer-js/src/balancerErrors.ts b/balancer-js/src/balancerErrors.ts index e1396ae6c..f9a79f1a4 100644 --- a/balancer-js/src/balancerErrors.ts +++ b/balancer-js/src/balancerErrors.ts @@ -10,6 +10,7 @@ export enum BalancerErrorCode { INPUT_OUT_OF_BOUNDS = 'INPUT_OUT_OF_BOUNDS', INPUT_LENGTH_MISMATCH = 'INPUT_LENGTH_MISMATCH', TOKEN_MISMATCH = 'TOKEN_MISMATCH', + MISSING_AMP = 'MISSING_AMP', MISSING_DECIMALS = 'MISSING_DECIMALS', MISSING_TOKENS = 'MISSING_TOKENS', MISSING_WEIGHT = 'MISSING_WEIGHT', @@ -45,6 +46,8 @@ export class BalancerError extends Error { return 'input length mismatch'; case BalancerErrorCode.TOKEN_MISMATCH: return 'token mismatch'; + case BalancerErrorCode.MISSING_AMP: + return 'missing amp'; case BalancerErrorCode.MISSING_DECIMALS: return 'missing decimals'; case BalancerErrorCode.MISSING_TOKENS: diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts new file mode 100644 index 000000000..af8d40477 --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts @@ -0,0 +1,297 @@ +import dotenv from 'dotenv'; +import { expect } from 'chai'; +import { + BalancerError, + BalancerErrorCode, + BalancerSDK, + Network, + Pool, + PoolModel, + PoolToken, + StaticPoolRepository, +} from '@/.'; +import hardhat from 'hardhat'; + +import { TransactionReceipt } from '@ethersproject/providers'; +import { BigNumber, parseFixed } from '@ethersproject/bignumber'; + +import { forkSetup } from '@/test/lib/utils'; +import pools_14717479 from '@/test/lib/pools_14717479.json'; +import { PoolsProvider } from '@/modules/pools/provider'; + +dotenv.config(); + +const { ALCHEMY_URL: jsonRpcUrl } = process.env; +const { ethers } = hardhat; + +let balancer: BalancerSDK; +const rpcUrl = 'http://127.0.0.1:8545'; +const provider = new ethers.providers.JsonRpcProvider(rpcUrl, 1); +const signer = provider.getSigner(); + +// Slots used to set the account balance for each token through hardhat_setStorageAt +// Info fetched using npm package slot20 +const BPT_SLOT = 0; + +const initialBalance = '100000'; +const amountsOutDiv = '100000000'; +const bptIn = parseFixed('10', 18).toString(); +const slippage = '3500'; + +let tokensOut: PoolToken[]; +let amountsOut: string[]; +let transactionReceipt: TransactionReceipt; +let bptBalanceBefore: BigNumber; +let bptBalanceAfter: BigNumber; +let bptMaxBalanceDecrease: BigNumber; +let tokensBalanceBefore: BigNumber[]; +let tokensBalanceAfter: BigNumber[]; +let tokensMinBalanceIncrease: BigNumber[]; +let signerAddress: string; +let pool: PoolModel; + +// Setup + +const setupPool = async (provider: PoolsProvider, poolId: string) => { + const _pool = await provider.find(poolId); + if (!_pool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); + const pool = _pool; + return pool; +}; + +const tokenBalance = async (tokenAddress: string, signerAddress: string) => { + const balance: Promise = balancer.contracts + .ERC20(tokenAddress, signer.provider) + .balanceOf(signerAddress); + return balance; +}; + +const updateBalances = async (pool: Pool) => { + const bptBalance = tokenBalance(pool.address, signerAddress); + const balances = []; + for (let i = 0; i < pool.tokensList.length; i++) { + balances[i] = tokenBalance(pool.tokensList[i], signerAddress); + } + return Promise.all([bptBalance, ...balances]); +}; + +const testExitPool = (poolId: string) => { + describe('exit execution', async () => { + // Setup chain + before(async function () { + this.timeout(20000); + + const sdkConfig = { + network: Network.MAINNET, + rpcUrl, + }; + // Using a static repository to make test consistent over time + const poolsProvider = new PoolsProvider( + sdkConfig, + new StaticPoolRepository(pools_14717479 as Pool[]) + ); + balancer = new BalancerSDK( + sdkConfig, + undefined, + undefined, + undefined, + poolsProvider + ); + pool = await setupPool(poolsProvider, poolId); + tokensOut = pool.tokens; + await forkSetup( + balancer, + signer, + [pool.address], + [BPT_SLOT], + [parseFixed(initialBalance, 18).toString()], + jsonRpcUrl as string, + 14717479 // holds the same state as the static repository + ); + signerAddress = await signer.getAddress(); + }); + + context('exitExactBPTIn - exit with encoded data', async () => { + before(async function () { + this.timeout(20000); + [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); + + const { to, data, minAmountsOut, maxBPTIn } = pool.buildExitExactBPTIn( + signerAddress, + bptIn, + slippage + ); + const tx = { to, data }; // , gasPrice: '600000000000', gasLimit: '2000000' }; + + bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); + tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); + const transactionResponse = await signer.sendTransaction(tx); + transactionReceipt = await transactionResponse.wait(); + [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); + }); + + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); + }); + + it('tokens balance should increase', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .gte(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); + + it('bpt balance should decrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)) + .to.be.true; + }); + }); + + context('exitExactBPTIn - exit with params', async () => { + before(async function () { + this.timeout(20000); + [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); + + const { attributes, minAmountsOut, maxBPTIn } = + pool.buildExitExactBPTIn(signerAddress, bptIn, slippage); + + bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); + tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); + + const transactionResponse = await balancer.contracts.vault + .connect(signer) + .exitPool( + attributes.poolId, + attributes.sender, + attributes.recipient, + attributes.exitPoolRequest + ); + transactionReceipt = await transactionResponse.wait(); + [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); + }); + + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); + }); + + it('tokens balance should increase', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .gte(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); + + it('bpt balance should decrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)) + .to.be.true; + }); + }); + + context('exitExactTokensOut - exit with encoded data', async () => { + before(async function () { + this.timeout(20000); + [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); + + amountsOut = pool.tokens.map((t) => + parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() + ); + const { to, data, minAmountsOut, maxBPTIn } = + pool.buildExitExactTokensOut( + signerAddress, + tokensOut.map((t) => t.address), + amountsOut, + slippage + ); + const tx = { to, data }; // , gasPrice: '600000000000', gasLimit: '2000000' }; + + bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); + tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); + const transactionResponse = await signer.sendTransaction(tx); + transactionReceipt = await transactionResponse.wait(); + [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); + }); + + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); + }); + + it('tokens balance should increase', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .eq(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); + + it('bpt balance should decrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + .to.be.true; + }); + }); + + context('exitExactTokensOut - exit with params', async () => { + before(async function () { + this.timeout(20000); + [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); + + amountsOut = pool.tokens.map((t) => + parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() + ); + const { attributes, minAmountsOut, maxBPTIn } = + pool.buildExitExactTokensOut( + signerAddress, + tokensOut.map((t) => t.address), + amountsOut, + slippage + ); + + bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); + tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); + + const transactionResponse = await balancer.contracts.vault + .connect(signer) + .exitPool( + attributes.poolId, + attributes.sender, + attributes.recipient, + attributes.exitPoolRequest + ); + transactionReceipt = await transactionResponse.wait(); + [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); + }); + + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); + }); + + it('tokens balance should increase', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .eq(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); + + it('bpt balance should decrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + .to.be.true; + }); + }); + }).timeout(20000); +}; + +// Test Scenarios + +testExitPool( + '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063' // Balancer USD Stable Pool - staBAL3 +); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.spec.ts new file mode 100644 index 000000000..9ac6a9332 --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.spec.ts @@ -0,0 +1,77 @@ +import { expect } from 'chai'; +import { + BalancerError, + BalancerErrorCode, + BalancerSdkConfig, + Network, + Pool, + StaticPoolRepository, +} from '@/.'; +import { PoolsProvider } from '@/modules/pools/provider'; + +import pools_14717479 from '@/test/lib/pools_14717479.json'; +import { parseFixed } from '@/lib/utils/math'; + +const dai_usdc_usdt_pool_id = + '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063'; // Balancer USD Stable Pool - staBAL3 + +const exiter = '0x35f5a330FD2F8e521ebd259FA272bA8069590741'; +const slippage = '100'; // 100 bps + +describe('exit module', async () => { + const sdkConfig: BalancerSdkConfig = { + network: Network.MAINNET, + rpcUrl: ``, + }; + const pools = new PoolsProvider( + sdkConfig, + new StaticPoolRepository(pools_14717479 as Pool[]) + ); + const pool = await pools.find(dai_usdc_usdt_pool_id); + if (!pool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); + + describe('buildExitExactBPTIn', () => { + const bptIn = parseFixed('100', 18).toString(); // bpt in EVM amounts + context('proportional amounts out', () => { + it('should return encoded params', async () => { + const { data } = pool.buildExitExactBPTIn(exiter, bptIn, slippage); + + expect(data).to.equal( + '0x8bdb391306df3b2bbb68adc8b0e302443692037ed9f91b4200000000000000000000006300000000000000000000000035f5a330fd2f8e521ebd259fa272ba806959074100000000000000000000000035f5a330fd2f8e521ebd259fa272ba80695907410000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000001cd70c57f08a1d48f0000000000000000000000000000000000000000000000000000000001ef82990000000000000000000000000000000000000000000000000000000002047879000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000056bc75e2d63100000' + ); + }); + }); + context('single token max out', () => { + it('should return encoded params', async () => { + const { data } = pool.buildExitExactBPTIn( + exiter, + bptIn, + slippage, + pool.tokensList[0] + ); + + expect(data).to.equal( + '0x8bdb391306df3b2bbb68adc8b0e302443692037ed9f91b4200000000000000000000006300000000000000000000000035f5a330fd2f8e521ebd259fa272ba806959074100000000000000000000000035f5a330fd2f8e521ebd259fa272ba80695907410000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000003a432d1bf1a20356300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000000000000' + ); + }); + }); + }); + describe('buildExitExactTokensOut', () => { + const tokensOut = pool.tokensList; + const amountsOut = pool.tokens.map((t) => + parseFixed(t.balance, t.decimals).div('100000').toString() + ); // EVM amounts) + it('should return encoded params', async () => { + const { data } = await pool.buildExitExactTokensOut( + exiter, + tokensOut, + amountsOut, + slippage + ); + + expect(data).to.equal( + '0x8bdb391306df3b2bbb68adc8b0e302443692037ed9f91b4200000000000000000000006300000000000000000000000035f5a330fd2f8e521ebd259fa272ba806959074100000000000000000000000035f5a330fd2f8e521ebd259fa272ba80695907410000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000022253457fb06e3bf280000000000000000000000000000000000000000000000000000000024aa9879000000000000000000000000000000000000000000000000000000002637a7d900000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000022253457fb06e3bf280000000000000000000000000000000000000000000000000000000024aa9879000000000000000000000000000000000000000000000000000000002637a7d9' + ); + }); + }); +}); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts index f90672148..bf4e521cd 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts @@ -1,11 +1,30 @@ +import { BigNumber, parseFixed } from '@ethersproject/bignumber'; +import OldBigNumber from 'bignumber.js'; +import * as SDK from '@georgeroman/balancer-v2-pools'; import { ExitConcern, ExitExactBPTInParameters, ExitExactTokensOutParameters, + ExitPool, ExitPoolAttributes, } from '../types'; +import { AssetHelpers, parsePoolInfo } from '@/lib/utils'; +import { Vault__factory } from '@balancer-labs/typechain'; +import { WeightedPoolEncoder } from '@/pool-weighted'; +import { addSlippage, subSlippage } from '@/lib/utils/slippageHelper'; +import { balancerVault } from '@/lib/constants/config'; +import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; export class StablePoolExit implements ExitConcern { + /** + * Build exit pool transaction parameters with exact BPT in and minimum token amounts out based on slippage tolerance + * @param {string} exiter - Account address exiting pool + * @param {Pool} pool - Subgraph pool object of pool being exited + * @param {string} bptIn - BPT provided for exiting pool + * @param {string} slippage - Maximum slippage tolerance in percentage. i.e. 0.05 = 5% + * @param {string} singleTokenMaxOut - Optional: token address that if provided will exit to given token + * @returns transaction request ready to send with signer.sendTransaction + */ buildExitExactBPTIn = ({ exiter, pool, @@ -13,11 +32,123 @@ export class StablePoolExit implements ExitConcern { slippage, singleTokenMaxOut, }: ExitExactBPTInParameters): ExitPoolAttributes => { - // TODO implementation - console.log(exiter, pool, bptIn, slippage, singleTokenMaxOut); - throw new Error('To be implemented'); + if (!bptIn.length || parseFixed(bptIn, 18).isNegative()) { + throw new BalancerError(BalancerErrorCode.INPUT_OUT_OF_BOUNDS); + } + if ( + singleTokenMaxOut && + !pool.tokens.map((t) => t.address).some((a) => a === singleTokenMaxOut) + ) { + throw new BalancerError(BalancerErrorCode.TOKEN_MISMATCH); + } + + // Check if there's any relevant weighted pool info missing + if (pool.tokens.some((token) => !token.decimals)) + throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); + if (!pool.amp) throw new BalancerError(BalancerErrorCode.MISSING_AMP); + + const { + parsedTokens, + parsedBalances, + parsedAmp, + parsedTotalShares, + parsedSwapFee, + } = parsePoolInfo(pool); + + const WETH = '0x000000000000000000000000000000000000000F'; // TODO: check if it should be possible to exit with ETH instead of WETH + const assetHelpers = new AssetHelpers(WETH); + const [sortedTokens, sortedBalances] = assetHelpers.sortTokens( + parsedTokens, + parsedBalances + ) as [string[], string[]]; + + let minAmountsOut = Array(parsedTokens.length).fill('0'); + let userData: string; + + if (singleTokenMaxOut) { + // Exit pool with single token using exact bptIn + + const singleTokenMaxOutIndex = parsedTokens.indexOf(singleTokenMaxOut); + + const amountOut = SDK.StableMath._calcTokenOutGivenExactBptIn( + new OldBigNumber(parsedAmp as string), + sortedBalances.map((b) => new OldBigNumber(b)), + singleTokenMaxOutIndex, + new OldBigNumber(bptIn), + new OldBigNumber(parsedTotalShares), + new OldBigNumber(parsedSwapFee) + ).toString(); + + minAmountsOut[singleTokenMaxOutIndex] = subSlippage( + BigNumber.from(amountOut), + BigNumber.from(slippage) + ).toString(); + + userData = WeightedPoolEncoder.exitExactBPTInForOneTokenOut( + bptIn, + singleTokenMaxOutIndex + ); + } else { + // Exit pool with all tokens proportinally + + const amountsOut = SDK.StableMath._calcTokensOutGivenExactBptIn( + sortedBalances.map((b) => new OldBigNumber(b)), + new OldBigNumber(bptIn), + new OldBigNumber(parsedTotalShares) + ).map((amount) => amount.toString()); + + minAmountsOut = amountsOut.map((amount) => { + const minAmount = subSlippage( + BigNumber.from(amount), + BigNumber.from(slippage) + ); + return minAmount.toString(); + }); + + userData = WeightedPoolEncoder.exitExactBPTInForTokensOut(bptIn); + } + + const to = balancerVault; + const functionName = 'exitPool'; + const attributes: ExitPool = { + poolId: pool.id, + sender: exiter, + recipient: exiter, + exitPoolRequest: { + assets: sortedTokens, + minAmountsOut, + userData, + toInternalBalance: false, + }, + }; + const vaultInterface = Vault__factory.createInterface(); + // encode transaction data into an ABI byte string which can be sent to the network to be executed + const data = vaultInterface.encodeFunctionData(functionName, [ + attributes.poolId, + attributes.sender, + attributes.recipient, + attributes.exitPoolRequest, + ]); + + return { + to, + functionName, + attributes, + data, + minAmountsOut, + maxBPTIn: bptIn, + }; }; + /** + * Build exit pool transaction parameters with exact tokens out and maximum BPT in based on slippage tolerance + * @param {string} exiter - Account address exiting pool + * @param {Pool} pool - Subgraph pool object of pool being exited + * @param {string[]} tokensOut - Tokens provided for exiting pool + * @param {string[]} amountsOut - Amoutns provided for exiting pool + * @param {string} slippage - Maximum slippage tolerance in percentage. i.e. 0.05 = 5% + * @returns transaction request ready to send with signer.sendTransaction + */ buildExitExactTokensOut = ({ exiter, pool, @@ -25,8 +156,79 @@ export class StablePoolExit implements ExitConcern { amountsOut, slippage, }: ExitExactTokensOutParameters): ExitPoolAttributes => { - // TODO implementation - console.log(exiter, pool, tokensOut, amountsOut, slippage); - throw new Error('To be implemented'); + if ( + tokensOut.length != amountsOut.length || + tokensOut.length != pool.tokensList.length + ) { + throw new BalancerError(BalancerErrorCode.INPUT_LENGTH_MISMATCH); + } + + const { + parsedTokens, + parsedBalances, + parsedAmp, + parsedTotalShares, + parsedSwapFee, + } = parsePoolInfo(pool); + + const WETH = '0x000000000000000000000000000000000000000F'; // TODO: check if it should be possible to exit with ETH instead of WETH + const assetHelpers = new AssetHelpers(WETH); + const [, sortedBalances] = assetHelpers.sortTokens( + parsedTokens, + parsedBalances + ) as [string[], string[]]; + const [sortedTokens, sortedAmounts] = assetHelpers.sortTokens( + tokensOut, + amountsOut + ) as [string[], string[]]; + + const bptIn = SDK.StableMath._calcBptInGivenExactTokensOut( + new OldBigNumber(parsedAmp as string), + sortedBalances.map((b) => new OldBigNumber(b)), + sortedAmounts.map((a) => new OldBigNumber(a)), + new OldBigNumber(parsedTotalShares), + new OldBigNumber(parsedSwapFee) + ).toString(); + + const maxBPTIn = addSlippage( + BigNumber.from(bptIn), + BigNumber.from(slippage) + ).toString(); + + const userData = WeightedPoolEncoder.exitBPTInForExactTokensOut( + sortedAmounts, + maxBPTIn + ); + + const to = balancerVault; + const functionName = 'exitPool'; + const attributes: ExitPool = { + poolId: pool.id, + sender: exiter, + recipient: exiter, + exitPoolRequest: { + assets: sortedTokens, + minAmountsOut: sortedAmounts, + userData, + toInternalBalance: false, + }, + }; + const vaultInterface = Vault__factory.createInterface(); + // encode transaction data into an ABI byte string which can be sent to the network to be executed + const data = vaultInterface.encodeFunctionData(functionName, [ + attributes.poolId, + attributes.sender, + attributes.recipient, + attributes.exitPoolRequest, + ]); + + return { + to, + functionName, + attributes, + data, + minAmountsOut: sortedAmounts, + maxBPTIn, + }; }; } From 78055e59eed9bfad29cdf703752b95a70197e2b6 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 22 Jul 2022 14:14:02 -0300 Subject: [PATCH 05/65] Add exit MetaStable pool --- balancer-js/src/balancerErrors.ts | 3 + .../exit.concern.integration.spec.ts | 297 ++++++++++++++++++ .../concerns/metaStable/exit.concern.spec.ts | 77 +++++ .../concerns/metaStable/exit.concern.ts | 259 ++++++++++++++- 4 files changed, 630 insertions(+), 6 deletions(-) create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.spec.ts diff --git a/balancer-js/src/balancerErrors.ts b/balancer-js/src/balancerErrors.ts index f9a79f1a4..cbb530f7a 100644 --- a/balancer-js/src/balancerErrors.ts +++ b/balancer-js/src/balancerErrors.ts @@ -12,6 +12,7 @@ export enum BalancerErrorCode { TOKEN_MISMATCH = 'TOKEN_MISMATCH', MISSING_AMP = 'MISSING_AMP', MISSING_DECIMALS = 'MISSING_DECIMALS', + MISSING_PRICE_RATE = 'MISSING_PRICE_RATE', MISSING_TOKENS = 'MISSING_TOKENS', MISSING_WEIGHT = 'MISSING_WEIGHT', } @@ -50,6 +51,8 @@ export class BalancerError extends Error { return 'missing amp'; case BalancerErrorCode.MISSING_DECIMALS: return 'missing decimals'; + case BalancerErrorCode.MISSING_PRICE_RATE: + return 'missing price rate'; case BalancerErrorCode.MISSING_TOKENS: return 'missing tokens'; case BalancerErrorCode.MISSING_WEIGHT: diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts new file mode 100644 index 000000000..f2e42a183 --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts @@ -0,0 +1,297 @@ +import dotenv from 'dotenv'; +import { expect } from 'chai'; +import { + BalancerError, + BalancerErrorCode, + BalancerSDK, + Network, + Pool, + PoolModel, + PoolToken, + StaticPoolRepository, +} from '@/.'; +import hardhat from 'hardhat'; + +import { TransactionReceipt } from '@ethersproject/providers'; +import { BigNumber, parseFixed } from '@ethersproject/bignumber'; + +import { forkSetup } from '@/test/lib/utils'; +import pools_14717479 from '@/test/lib/pools_14717479.json'; +import { PoolsProvider } from '@/modules/pools/provider'; + +dotenv.config(); + +const { ALCHEMY_URL: jsonRpcUrl } = process.env; +const { ethers } = hardhat; + +let balancer: BalancerSDK; +const rpcUrl = 'http://127.0.0.1:8545'; +const provider = new ethers.providers.JsonRpcProvider(rpcUrl, 1); +const signer = provider.getSigner(); + +// Slots used to set the account balance for each token through hardhat_setStorageAt +// Info fetched using npm package slot20 +const BPT_SLOT = 0; + +const initialBalance = '100000'; +const amountsOutDiv = '1000000'; +const bptIn = parseFixed('10', 18).toString(); +const slippage = '100'; + +let tokensOut: PoolToken[]; +let amountsOut: string[]; +let transactionReceipt: TransactionReceipt; +let bptBalanceBefore: BigNumber; +let bptBalanceAfter: BigNumber; +let bptMaxBalanceDecrease: BigNumber; +let tokensBalanceBefore: BigNumber[]; +let tokensBalanceAfter: BigNumber[]; +let tokensMinBalanceIncrease: BigNumber[]; +let signerAddress: string; +let pool: PoolModel; + +// Setup + +const setupPool = async (provider: PoolsProvider, poolId: string) => { + const _pool = await provider.find(poolId); + if (!_pool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); + const pool = _pool; + return pool; +}; + +const tokenBalance = async (tokenAddress: string, signerAddress: string) => { + const balance: Promise = balancer.contracts + .ERC20(tokenAddress, signer.provider) + .balanceOf(signerAddress); + return balance; +}; + +const updateBalances = async (pool: Pool) => { + const bptBalance = tokenBalance(pool.address, signerAddress); + const balances = []; + for (let i = 0; i < pool.tokensList.length; i++) { + balances[i] = tokenBalance(pool.tokensList[i], signerAddress); + } + return Promise.all([bptBalance, ...balances]); +}; + +const testExitPool = (poolId: string) => { + describe('exit execution', async () => { + // Setup chain + before(async function () { + this.timeout(20000); + + const sdkConfig = { + network: Network.MAINNET, + rpcUrl, + }; + // Using a static repository to make test consistent over time + const poolsProvider = new PoolsProvider( + sdkConfig, + new StaticPoolRepository(pools_14717479 as Pool[]) + ); + balancer = new BalancerSDK( + sdkConfig, + undefined, + undefined, + undefined, + poolsProvider + ); + pool = await setupPool(poolsProvider, poolId); + tokensOut = pool.tokens; + await forkSetup( + balancer, + signer, + [pool.address], + [BPT_SLOT], + [parseFixed(initialBalance, 18).toString()], + jsonRpcUrl as string, + 14717479 // holds the same state as the static repository + ); + signerAddress = await signer.getAddress(); + }); + + context('exitExactBPTIn - exit with encoded data', async () => { + before(async function () { + this.timeout(20000); + [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); + + const { to, data, minAmountsOut, maxBPTIn } = pool.buildExitExactBPTIn( + signerAddress, + bptIn, + slippage + ); + const tx = { to, data }; // , gasPrice: '600000000000', gasLimit: '2000000' }; + + bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); + tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); + const transactionResponse = await signer.sendTransaction(tx); + transactionReceipt = await transactionResponse.wait(); + [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); + }); + + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); + }); + + it('tokens balance should increase', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .gte(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); + + it('bpt balance should decrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)) + .to.be.true; + }); + }); + + context('exitExactBPTIn - exit with params', async () => { + before(async function () { + this.timeout(20000); + [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); + + const { attributes, minAmountsOut, maxBPTIn } = + pool.buildExitExactBPTIn(signerAddress, bptIn, slippage); + + bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); + tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); + + const transactionResponse = await balancer.contracts.vault + .connect(signer) + .exitPool( + attributes.poolId, + attributes.sender, + attributes.recipient, + attributes.exitPoolRequest + ); + transactionReceipt = await transactionResponse.wait(); + [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); + }); + + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); + }); + + it('tokens balance should increase', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .gte(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); + + it('bpt balance should decrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)) + .to.be.true; + }); + }); + + context('exitExactTokensOut - exit with encoded data', async () => { + before(async function () { + this.timeout(20000); + [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); + + amountsOut = pool.tokens.map((t) => + parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() + ); + const { to, data, minAmountsOut, maxBPTIn } = + pool.buildExitExactTokensOut( + signerAddress, + tokensOut.map((t) => t.address), + amountsOut, + slippage + ); + const tx = { to, data }; // , gasPrice: '600000000000', gasLimit: '2000000' }; + + bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); + tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); + const transactionResponse = await signer.sendTransaction(tx); + transactionReceipt = await transactionResponse.wait(); + [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); + }); + + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); + }); + + it('tokens balance should increase', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .eq(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); + + it('bpt balance should decrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + .to.be.true; + }); + }); + + context('exitExactTokensOut - exit with params', async () => { + before(async function () { + this.timeout(20000); + [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); + + amountsOut = pool.tokens.map((t) => + parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() + ); + const { attributes, minAmountsOut, maxBPTIn } = + pool.buildExitExactTokensOut( + signerAddress, + tokensOut.map((t) => t.address), + amountsOut, + slippage + ); + + bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); + tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); + + const transactionResponse = await balancer.contracts.vault + .connect(signer) + .exitPool( + attributes.poolId, + attributes.sender, + attributes.recipient, + attributes.exitPoolRequest + ); + transactionReceipt = await transactionResponse.wait(); + [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); + }); + + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); + }); + + it('tokens balance should increase', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .eq(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); + + it('bpt balance should decrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + .to.be.true; + }); + }); + }).timeout(20000); +}; + +// Test Scenarios + +testExitPool( + '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080' // Balancer stETH Stable Pool +); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.spec.ts new file mode 100644 index 000000000..846a186ba --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.spec.ts @@ -0,0 +1,77 @@ +import { expect } from 'chai'; +import { + BalancerError, + BalancerErrorCode, + BalancerSdkConfig, + Network, + Pool, + StaticPoolRepository, +} from '@/.'; +import { PoolsProvider } from '@/modules/pools/provider'; + +import pools_14717479 from '@/test/lib/pools_14717479.json'; +import { parseFixed } from '@/lib/utils/math'; + +const stETH_stable_pool_id = + '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080'; // Balancer stETH Stable Pool + +const exiter = '0x35f5a330FD2F8e521ebd259FA272bA8069590741'; +const slippage = '100'; // 100 bps + +describe('exit module', async () => { + const sdkConfig: BalancerSdkConfig = { + network: Network.MAINNET, + rpcUrl: ``, + }; + const pools = new PoolsProvider( + sdkConfig, + new StaticPoolRepository(pools_14717479 as Pool[]) + ); + const pool = await pools.find(stETH_stable_pool_id); + if (!pool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); + + describe('buildExitExactBPTIn', () => { + const bptIn = parseFixed('100', 18).toString(); // bpt in EVM amounts + context('proportional amounts out', () => { + it('should return encoded params', async () => { + const { data } = pool.buildExitExactBPTIn(exiter, bptIn, slippage); + + expect(data).to.equal( + '0x8bdb391306df3b2bbb68adc8b0e302443692037ed9f91b4200000000000000000000006300000000000000000000000035f5a330fd2f8e521ebd259fa272ba806959074100000000000000000000000035f5a330fd2f8e521ebd259fa272ba80695907410000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000001cd70c57f08a1d48f0000000000000000000000000000000000000000000000000000000001ef82990000000000000000000000000000000000000000000000000000000002047879000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000056bc75e2d63100000' + ); + }); + }); + context('single token max out', () => { + it('should return encoded params', async () => { + const { data } = pool.buildExitExactBPTIn( + exiter, + bptIn, + slippage, + pool.tokensList[0] + ); + + expect(data).to.equal( + '0x8bdb391306df3b2bbb68adc8b0e302443692037ed9f91b4200000000000000000000006300000000000000000000000035f5a330fd2f8e521ebd259fa272ba806959074100000000000000000000000035f5a330fd2f8e521ebd259fa272ba80695907410000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000003a432d1bf1a20356300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000000000000' + ); + }); + }); + }); + describe('buildExitExactTokensOut', () => { + const tokensOut = pool.tokensList; + const amountsOut = pool.tokens.map((t) => + parseFixed(t.balance, t.decimals).div('100000').toString() + ); // EVM amounts) + it('should return encoded params', async () => { + const { data } = await pool.buildExitExactTokensOut( + exiter, + tokensOut, + amountsOut, + slippage + ); + + expect(data).to.equal( + '0x8bdb391306df3b2bbb68adc8b0e302443692037ed9f91b4200000000000000000000006300000000000000000000000035f5a330fd2f8e521ebd259fa272ba806959074100000000000000000000000035f5a330fd2f8e521ebd259fa272ba80695907410000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000022253457fb06e3bf280000000000000000000000000000000000000000000000000000000024aa9879000000000000000000000000000000000000000000000000000000002637a7d900000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000022253457fb06e3bf280000000000000000000000000000000000000000000000000000000024aa9879000000000000000000000000000000000000000000000000000000002637a7d9' + ); + }); + }); +}); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts index e4f310157..344c459ad 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts @@ -1,11 +1,30 @@ +import { BigNumber, parseFixed } from '@ethersproject/bignumber'; +import OldBigNumber from 'bignumber.js'; +import * as SDK from '@georgeroman/balancer-v2-pools'; import { ExitConcern, ExitExactBPTInParameters, ExitExactTokensOutParameters, + ExitPool, ExitPoolAttributes, } from '../types'; +import { AssetHelpers, parsePoolInfo } from '@/lib/utils'; +import { Vault__factory } from '@balancer-labs/typechain'; +import { WeightedPoolEncoder } from '@/pool-weighted'; +import { addSlippage, subSlippage } from '@/lib/utils/slippageHelper'; +import { balancerVault } from '@/lib/constants/config'; +import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; export class MetaStablePoolExit implements ExitConcern { + /** + * Build exit pool transaction parameters with exact BPT in and minimum token amounts out based on slippage tolerance + * @param {string} exiter - Account address exiting pool + * @param {Pool} pool - Subgraph pool object of pool being exited + * @param {string} bptIn - BPT provided for exiting pool + * @param {string} slippage - Maximum slippage tolerance in percentage. i.e. 0.05 = 5% + * @param {string} singleTokenMaxOut - Optional: token address that if provided will exit to given token + * @returns transaction request ready to send with signer.sendTransaction + */ buildExitExactBPTIn = ({ exiter, pool, @@ -13,11 +32,150 @@ export class MetaStablePoolExit implements ExitConcern { slippage, singleTokenMaxOut, }: ExitExactBPTInParameters): ExitPoolAttributes => { - // TODO implementation - console.log(exiter, pool, bptIn, slippage, singleTokenMaxOut); - throw new Error('To be implemented'); + if (!bptIn.length || parseFixed(bptIn, 18).isNegative()) { + throw new BalancerError(BalancerErrorCode.INPUT_OUT_OF_BOUNDS); + } + if ( + singleTokenMaxOut && + !pool.tokens.map((t) => t.address).some((a) => a === singleTokenMaxOut) + ) { + throw new BalancerError(BalancerErrorCode.TOKEN_MISMATCH); + } + + // Check if there's any relevant weighted pool info missing + if (pool.tokens.some((token) => !token.decimals)) + throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); + if (!pool.amp) throw new BalancerError(BalancerErrorCode.MISSING_AMP); + if (pool.tokens.some((token) => !token.priceRate)) + throw new BalancerError(BalancerErrorCode.MISSING_PRICE_RATE); + + const { + parsedTokens, + parsedBalances, + parsedAmp, + parsedPriceRates, + parsedTotalShares, + parsedSwapFee, + } = parsePoolInfo(pool); + + const WETH = '0x000000000000000000000000000000000000000F'; // TODO: check if it should be possible to exit with ETH instead of WETH + const assetHelpers = new AssetHelpers(WETH); + const [sortedTokens, sortedBalances, sortedPriceRates] = + assetHelpers.sortTokens( + parsedTokens, + parsedBalances, + parsedPriceRates + ) as [string[], string[], string[]]; + + // scale balances based on price rate for each token + const scaledBalances = sortedBalances.map((balance, i) => { + return BigNumber.from(balance) + .mul(BigNumber.from(sortedPriceRates[i])) + .div(parseFixed('1', 18)) + .toString(); + }); + + let minAmountsOut = Array(parsedTokens.length).fill('0'); + let userData: string; + + if (singleTokenMaxOut) { + // Exit pool with single token using exact bptIn + + const singleTokenMaxOutIndex = parsedTokens.indexOf(singleTokenMaxOut); + + const scaledAmountOut = SDK.StableMath._calcTokenOutGivenExactBptIn( + new OldBigNumber(parsedAmp as string), + scaledBalances.map((b) => new OldBigNumber(b)), + singleTokenMaxOutIndex, + new OldBigNumber(bptIn), + new OldBigNumber(parsedTotalShares), + new OldBigNumber(parsedSwapFee) + ).toString(); + + // reverse scaled amount out based on token price rate + const amountOut = BigNumber.from(scaledAmountOut) + .div(BigNumber.from(sortedPriceRates[singleTokenMaxOutIndex])) + .mul(parseFixed('1', 18)) + .toString(); + + minAmountsOut[singleTokenMaxOutIndex] = subSlippage( + BigNumber.from(amountOut), + BigNumber.from(slippage) + ).toString(); + + userData = WeightedPoolEncoder.exitExactBPTInForOneTokenOut( + bptIn, + singleTokenMaxOutIndex + ); + } else { + // Exit pool with all tokens proportinally + + const scaledAmountsOut = SDK.StableMath._calcTokensOutGivenExactBptIn( + scaledBalances.map((b) => new OldBigNumber(b)), + new OldBigNumber(bptIn), + new OldBigNumber(parsedTotalShares) + ).map((amount) => amount.toString()); + + // reverse scaled amounts out based on token price rate + const amountsOut = scaledAmountsOut.map((amount, i) => { + return BigNumber.from(amount) + .div(BigNumber.from(sortedPriceRates[i])) + .mul(parseFixed('1', 18)) + .toString(); + }); + + minAmountsOut = amountsOut.map((amount) => { + const minAmount = subSlippage( + BigNumber.from(amount), + BigNumber.from(slippage) + ); + return minAmount.toString(); + }); + + userData = WeightedPoolEncoder.exitExactBPTInForTokensOut(bptIn); + } + + const to = balancerVault; + const functionName = 'exitPool'; + const attributes: ExitPool = { + poolId: pool.id, + sender: exiter, + recipient: exiter, + exitPoolRequest: { + assets: sortedTokens, + minAmountsOut, + userData, + toInternalBalance: false, + }, + }; + const vaultInterface = Vault__factory.createInterface(); + // encode transaction data into an ABI byte string which can be sent to the network to be executed + const data = vaultInterface.encodeFunctionData(functionName, [ + attributes.poolId, + attributes.sender, + attributes.recipient, + attributes.exitPoolRequest, + ]); + + return { + to, + functionName, + attributes, + data, + minAmountsOut, + maxBPTIn: bptIn, + }; }; + /** + * Build exit pool transaction parameters with exact tokens out and maximum BPT in based on slippage tolerance + * @param {string} exiter - Account address exiting pool + * @param {Pool} pool - Subgraph pool object of pool being exited + * @param {string[]} tokensOut - Tokens provided for exiting pool + * @param {string[]} amountsOut - Amoutns provided for exiting pool + * @param {string} slippage - Maximum slippage tolerance in percentage. i.e. 0.05 = 5% + * @returns transaction request ready to send with signer.sendTransaction + */ buildExitExactTokensOut = ({ exiter, pool, @@ -25,8 +183,97 @@ export class MetaStablePoolExit implements ExitConcern { amountsOut, slippage, }: ExitExactTokensOutParameters): ExitPoolAttributes => { - // TODO implementation - console.log(exiter, pool, tokensOut, amountsOut, slippage); - throw new Error('To be implemented'); + if ( + tokensOut.length != amountsOut.length || + tokensOut.length != pool.tokensList.length + ) { + throw new BalancerError(BalancerErrorCode.INPUT_LENGTH_MISMATCH); + } + + const { + parsedTokens, + parsedBalances, + parsedPriceRates, + parsedAmp, + parsedTotalShares, + parsedSwapFee, + } = parsePoolInfo(pool); + + const WETH = '0x000000000000000000000000000000000000000F'; // TODO: check if it should be possible to exit with ETH instead of WETH + const assetHelpers = new AssetHelpers(WETH); + const [, sortedBalances, sortedPriceRates] = assetHelpers.sortTokens( + parsedTokens, + parsedBalances, + parsedPriceRates + ) as [string[], string[], string[]]; + const [sortedTokens, sortedAmounts] = assetHelpers.sortTokens( + tokensOut, + amountsOut + ) as [string[], string[]]; + + // scale amounts in based on price rate for each token + const scaledAmounts = sortedAmounts.map((amount, i) => { + return BigNumber.from(amount) + .mul(BigNumber.from(sortedPriceRates[i])) + .div(parseFixed('1', 18)) + .toString(); + }); + + // scale balances based on price rate for each token + const scaledBalances = sortedBalances.map((balance, i) => { + return BigNumber.from(balance) + .mul(BigNumber.from(sortedPriceRates[i])) + .div(parseFixed('1', 18)) + .toString(); + }); + + const bptIn = SDK.StableMath._calcBptInGivenExactTokensOut( + new OldBigNumber(parsedAmp as string), + scaledBalances.map((b) => new OldBigNumber(b)), + scaledAmounts.map((a) => new OldBigNumber(a)), + new OldBigNumber(parsedTotalShares), + new OldBigNumber(parsedSwapFee) + ).toString(); + + const maxBPTIn = addSlippage( + BigNumber.from(bptIn), + BigNumber.from(slippage) + ).toString(); + + const userData = WeightedPoolEncoder.exitBPTInForExactTokensOut( + sortedAmounts, + maxBPTIn + ); + + const to = balancerVault; + const functionName = 'exitPool'; + const attributes: ExitPool = { + poolId: pool.id, + sender: exiter, + recipient: exiter, + exitPoolRequest: { + assets: sortedTokens, + minAmountsOut: sortedAmounts, + userData, + toInternalBalance: false, + }, + }; + const vaultInterface = Vault__factory.createInterface(); + // encode transaction data into an ABI byte string which can be sent to the network to be executed + const data = vaultInterface.encodeFunctionData(functionName, [ + attributes.poolId, + attributes.sender, + attributes.recipient, + attributes.exitPoolRequest, + ]); + + return { + to, + functionName, + attributes, + data, + minAmountsOut: sortedAmounts, + maxBPTIn, + }; }; } From 90e31ebe28def34904bf991afe6c55a22c9efd52 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 4 Aug 2022 18:14:10 -0300 Subject: [PATCH 06/65] Enforce lower case to wrappedNativeAsset address on provider --- balancer-js/src/modules/pools/provider.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/balancer-js/src/modules/pools/provider.ts b/balancer-js/src/modules/pools/provider.ts index 01f7353ae..7df25ef9a 100644 --- a/balancer-js/src/modules/pools/provider.ts +++ b/balancer-js/src/modules/pools/provider.ts @@ -15,6 +15,8 @@ export class PoolsProvider { static wrap(data: Pool, config: BalancerSdkConfig): PoolModel { const methods = PoolMethods.from(data.poolType); const networkConfig = getNetworkConfig(config); + const wrappedNativeAsset = + networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase(); return { ...data, liquidity: async () => methods.liquidity.calcTotal(data.tokens), @@ -25,7 +27,7 @@ export class PoolsProvider { tokensIn, amountsIn, slippage, - wrappedNativeAsset: networkConfig.addresses.tokens.wrappedNativeAsset, + wrappedNativeAsset, }), buildExitExactBPTIn: ( exiter, @@ -40,7 +42,7 @@ export class PoolsProvider { bptIn, slippage, shouldUnwrapNativeAsset, - wrappedNativeAsset: networkConfig.addresses.tokens.wrappedNativeAsset, + wrappedNativeAsset, singleTokenMaxOut, }), buildExitExactTokensOut: (exiter, tokensOut, amountsOut, slippage) => @@ -50,7 +52,7 @@ export class PoolsProvider { tokensOut, amountsOut, slippage, - wrappedNativeAsset: networkConfig.addresses.tokens.wrappedNativeAsset, + wrappedNativeAsset, }), // TODO: spotPrice fails, because it needs a subgraphType, // either we refetch or it needs a type transformation from SDK internal to SOR (subgraph) From 4860389d4495fc2910a3777ecb1317a6d451d7c0 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 4 Aug 2022 18:15:13 -0300 Subject: [PATCH 07/65] Add exit with ETH for stable pools --- .../stable/exit.concern.integration.spec.ts | 433 ++++++++---------- .../concerns/stable/exit.concern.ts | 72 ++- 2 files changed, 263 insertions(+), 242 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts index af8d40477..36a500d90 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts @@ -1,8 +1,6 @@ import dotenv from 'dotenv'; import { expect } from 'chai'; import { - BalancerError, - BalancerErrorCode, BalancerSDK, Network, Pool, @@ -14,10 +12,12 @@ import hardhat from 'hardhat'; import { TransactionReceipt } from '@ethersproject/providers'; import { BigNumber, parseFixed } from '@ethersproject/bignumber'; +import { forkSetup, setupPool, getBalances } from '@/test/lib/utils'; +import { PoolsProvider } from '@/modules/pools/provider'; -import { forkSetup } from '@/test/lib/utils'; import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { PoolsProvider } from '@/modules/pools/provider'; +import { ExitPoolAttributes } from '../types'; +import { AddressZero } from '@ethersproject/constants'; dotenv.config(); @@ -26,17 +26,18 @@ const { ethers } = hardhat; let balancer: BalancerSDK; const rpcUrl = 'http://127.0.0.1:8545'; -const provider = new ethers.providers.JsonRpcProvider(rpcUrl, 1); +const network = Network.MAINNET; +const provider = new ethers.providers.JsonRpcProvider(rpcUrl, network); const signer = provider.getSigner(); // Slots used to set the account balance for each token through hardhat_setStorageAt // Info fetched using npm package slot20 const BPT_SLOT = 0; - const initialBalance = '100000'; -const amountsOutDiv = '100000000'; -const bptIn = parseFixed('10', 18).toString(); -const slippage = '3500'; +const amountsOutDiv = '1000000000'; +const slippage = '100'; +const poolId = + '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063'; // Balancer USD Stable Pool - staBAL3 let tokensOut: PoolToken[]; let amountsOut: string[]; @@ -47,251 +48,223 @@ let bptMaxBalanceDecrease: BigNumber; let tokensBalanceBefore: BigNumber[]; let tokensBalanceAfter: BigNumber[]; let tokensMinBalanceIncrease: BigNumber[]; +let transactionCost: BigNumber; let signerAddress: string; let pool: PoolModel; -// Setup - -const setupPool = async (provider: PoolsProvider, poolId: string) => { - const _pool = await provider.find(poolId); - if (!_pool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); - const pool = _pool; - return pool; -}; - -const tokenBalance = async (tokenAddress: string, signerAddress: string) => { - const balance: Promise = balancer.contracts - .ERC20(tokenAddress, signer.provider) - .balanceOf(signerAddress); - return balance; -}; - -const updateBalances = async (pool: Pool) => { - const bptBalance = tokenBalance(pool.address, signerAddress); - const balances = []; - for (let i = 0; i < pool.tokensList.length; i++) { - balances[i] = tokenBalance(pool.tokensList[i], signerAddress); - } - return Promise.all([bptBalance, ...balances]); -}; +describe('exit execution', async () => { + // Setup chain + before(async function () { + this.timeout(20000); + + const sdkConfig = { + network, + rpcUrl, + }; + // Using a static repository to make test consistent over time + const poolsProvider = new PoolsProvider( + sdkConfig, + new StaticPoolRepository(pools_14717479 as Pool[]) + ); + balancer = new BalancerSDK( + sdkConfig, + undefined, + undefined, + undefined, + poolsProvider + ); + pool = await setupPool(poolsProvider, poolId); + tokensOut = pool.tokens; + await forkSetup( + balancer, + signer, + [pool.address], + [BPT_SLOT], + [parseFixed(initialBalance, 18).toString()], + jsonRpcUrl as string, + 14717479 // holds the same state as the static repository + ); + signerAddress = await signer.getAddress(); + }); + + const testFlow = async ( + { to, data, maxBPTIn, minAmountsOut }: ExitPoolAttributes, + exitTokens: string[], + exitWithETH = false + ) => { + // Check balances before transaction to confirm success + [bptBalanceBefore, ...tokensBalanceBefore] = await getBalances( + [pool.address, ...exitTokens], + signer, + signerAddress + ); + + // Get expected balances out of transaction + bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); + tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); + + // Send transaction to local fork + const transactionResponse = await signer.sendTransaction({ to, data }); + transactionReceipt = await transactionResponse.wait(); + + // Check balances after transaction to confirm success + [bptBalanceAfter, ...tokensBalanceAfter] = await getBalances( + [pool.address, ...exitTokens], + signer, + signerAddress + ); + + // add transaction cost to ETH balance when exiting with ETH + if (exitWithETH) { + transactionCost = transactionReceipt.gasUsed.mul( + transactionReceipt.effectiveGasPrice + ); + tokensBalanceAfter = tokensBalanceAfter.map((balance, i) => { + if ( + pool.tokensList[i] === + balancer.networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase() + ) { + return balance.add(transactionCost); + } + return balance; + }); + } + }; -const testExitPool = (poolId: string) => { - describe('exit execution', async () => { - // Setup chain + context('exitExactBPTIn', async () => { before(async function () { this.timeout(20000); - - const sdkConfig = { - network: Network.MAINNET, - rpcUrl, - }; - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) + const bptIn = parseFixed('10', 18).toString(); + await testFlow( + pool.buildExitExactBPTIn(signerAddress, bptIn, slippage), + pool.tokensList ); - balancer = new BalancerSDK( - sdkConfig, - undefined, - undefined, - undefined, - poolsProvider - ); - pool = await setupPool(poolsProvider, poolId); - tokensOut = pool.tokens; - await forkSetup( - balancer, - signer, - [pool.address], - [BPT_SLOT], - [parseFixed(initialBalance, 18).toString()], - jsonRpcUrl as string, - 14717479 // holds the same state as the static repository - ); - signerAddress = await signer.getAddress(); }); - context('exitExactBPTIn - exit with encoded data', async () => { - before(async function () { - this.timeout(20000); - [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); - - const { to, data, minAmountsOut, maxBPTIn } = pool.buildExitExactBPTIn( - signerAddress, - bptIn, - slippage - ); - const tx = { to, data }; // , gasPrice: '600000000000', gasLimit: '2000000' }; - - bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); - tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); - const transactionResponse = await signer.sendTransaction(tx); - transactionReceipt = await transactionResponse.wait(); - [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); - }); - - it('should work', async () => { - expect(transactionReceipt.status).to.eql(1); - }); - - it('tokens balance should increase', async () => { - for (let i = 0; i < tokensBalanceAfter.length; i++) { - expect( - tokensBalanceAfter[i] - .sub(tokensBalanceBefore[i]) - .gte(tokensMinBalanceIncrease[i]) - ).to.be.true; - } - }); - - it('bpt balance should decrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)) - .to.be.true; - }); + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); }); - context('exitExactBPTIn - exit with params', async () => { - before(async function () { - this.timeout(20000); - [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); - - const { attributes, minAmountsOut, maxBPTIn } = - pool.buildExitExactBPTIn(signerAddress, bptIn, slippage); - - bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); - tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); + it('tokens balance should increase by at least minAmountsOut', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .gte(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); - const transactionResponse = await balancer.contracts.vault - .connect(signer) - .exitPool( - attributes.poolId, - attributes.sender, - attributes.recipient, - attributes.exitPoolRequest - ); - transactionReceipt = await transactionResponse.wait(); - [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); - }); + it('bpt balance should decrease by exact bptMaxBalanceDecrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)).to + .be.true; + }); + }); - it('should work', async () => { - expect(transactionReceipt.status).to.eql(1); - }); + context('exitExactTokensOut', async () => { + before(async function () { + amountsOut = pool.tokens.map((t) => + parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() + ); - it('tokens balance should increase', async () => { - for (let i = 0; i < tokensBalanceAfter.length; i++) { - expect( - tokensBalanceAfter[i] - .sub(tokensBalanceBefore[i]) - .gte(tokensMinBalanceIncrease[i]) - ).to.be.true; - } - }); + await testFlow( + pool.buildExitExactTokensOut( + signerAddress, + tokensOut.map((t) => t.address), + amountsOut, + slippage + ), + pool.tokensList + ); + }); - it('bpt balance should decrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)) - .to.be.true; - }); + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); }); - context('exitExactTokensOut - exit with encoded data', async () => { - before(async function () { - this.timeout(20000); - [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); + it('tokens balance should increase by exact amountsOut', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .eq(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); - amountsOut = pool.tokens.map((t) => - parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() - ); - const { to, data, minAmountsOut, maxBPTIn } = - pool.buildExitExactTokensOut( - signerAddress, - tokensOut.map((t) => t.address), - amountsOut, - slippage - ); - const tx = { to, data }; // , gasPrice: '600000000000', gasLimit: '2000000' }; + it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + .to.be.true; + }); + }); - bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); - tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); - const transactionResponse = await signer.sendTransaction(tx); - transactionReceipt = await transactionResponse.wait(); - [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); - }); + context('exit with ETH', async () => { + before(async function () { + this.timeout(20000); + amountsOut = pool.tokens.map((t) => + parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() + ); - it('should work', async () => { - expect(transactionReceipt.status).to.eql(1); - }); + const exitTokens = pool.tokensList.map((token) => + token === + balancer.networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase() + ? AddressZero + : token + ); - it('tokens balance should increase', async () => { - for (let i = 0; i < tokensBalanceAfter.length; i++) { - expect( - tokensBalanceAfter[i] - .sub(tokensBalanceBefore[i]) - .eq(tokensMinBalanceIncrease[i]) - ).to.be.true; - } - }); + await testFlow( + pool.buildExitExactTokensOut( + signerAddress, + exitTokens, + amountsOut, + slippage + ), + exitTokens, + true + ); + }); - it('bpt balance should decrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) - .to.be.true; - }); + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); }); - context('exitExactTokensOut - exit with params', async () => { - before(async function () { - this.timeout(20000); - [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); + it('tokens balance should increase by exact amountsOut', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .eq(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); - amountsOut = pool.tokens.map((t) => - parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() - ); - const { attributes, minAmountsOut, maxBPTIn } = - pool.buildExitExactTokensOut( + it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + .to.be.true; + }); + }); + + context('exit with ETH - conflicting inputs', async () => { + it('should fail', async () => { + let errorMessage = ''; + try { + const bptIn = parseFixed('10', 18).toString(); + await testFlow( + pool.buildExitExactBPTIn( signerAddress, - tokensOut.map((t) => t.address), - amountsOut, - slippage - ); - - bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); - tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); - - const transactionResponse = await balancer.contracts.vault - .connect(signer) - .exitPool( - attributes.poolId, - attributes.sender, - attributes.recipient, - attributes.exitPoolRequest - ); - transactionReceipt = await transactionResponse.wait(); - [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); - }); - - it('should work', async () => { - expect(transactionReceipt.status).to.eql(1); - }); - - it('tokens balance should increase', async () => { - for (let i = 0; i < tokensBalanceAfter.length; i++) { - expect( - tokensBalanceAfter[i] - .sub(tokensBalanceBefore[i]) - .eq(tokensMinBalanceIncrease[i]) - ).to.be.true; - } - }); - - it('bpt balance should decrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) - .to.be.true; - }); + bptIn, + slippage, + false, + AddressZero + ), + pool.tokensList + ); + } catch (error) { + errorMessage = (error as Error).message; + } + expect(errorMessage).to.eql( + 'shouldUnwrapNativeAsset and singleTokenMaxOut should not have conflicting values' + ); }); - }).timeout(20000); -}; - -// Test Scenarios - -testExitPool( - '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063' // Balancer USD Stable Pool - staBAL3 -); + }); +}).timeout(20000); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts index 7e282d7b7..ba4871005 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts @@ -1,4 +1,5 @@ import { BigNumber, parseFixed } from '@ethersproject/bignumber'; +import { AddressZero } from '@ethersproject/constants'; import OldBigNumber from 'bignumber.js'; import * as SDK from '@georgeroman/balancer-v2-pools'; import { @@ -10,10 +11,10 @@ import { } from '../types'; import { AssetHelpers, parsePoolInfo } from '@/lib/utils'; import { Vault__factory } from '@balancer-labs/typechain'; -import { WeightedPoolEncoder } from '@/pool-weighted'; import { addSlippage, subSlippage } from '@/lib/utils/slippageHelper'; import { balancerVault } from '@/lib/constants/config'; import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; +import { StablePoolEncoder } from '@/pool-stable'; export class StablePoolExit implements ExitConcern { /** @@ -22,6 +23,8 @@ export class StablePoolExit implements ExitConcern { * @param {Pool} pool - Subgraph pool object of pool being exited * @param {string} bptIn - BPT provided for exiting pool * @param {string} slippage - Maximum slippage tolerance in percentage. i.e. 0.05 = 5% + * @param {boolean} shouldUnwrapNativeAsset - Indicates wether wrapped native asset should be unwrapped after exit. + * @param {string} wrappedNativeAsset - Address of wrapped native asset for specific network config. Required for exiting to native asset. * @param {string} singleTokenMaxOut - Optional: token address that if provided will exit to given token * @returns transaction request ready to send with signer.sendTransaction */ @@ -39,16 +42,23 @@ export class StablePoolExit implements ExitConcern { } if ( singleTokenMaxOut && + singleTokenMaxOut !== AddressZero && !pool.tokens.map((t) => t.address).some((a) => a === singleTokenMaxOut) ) { throw new BalancerError(BalancerErrorCode.TOKEN_MISMATCH); } - // Check if there's any relevant weighted pool info missing + if (!shouldUnwrapNativeAsset && singleTokenMaxOut === AddressZero) + throw new Error( + 'shouldUnwrapNativeAsset and singleTokenMaxOut should not have conflicting values' + ); + + // Check if there's any relevant stable pool info missing if (pool.tokens.some((token) => !token.decimals)) throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); if (!pool.amp) throw new BalancerError(BalancerErrorCode.MISSING_AMP); + // Parse pool info into EVM amounts in order to match amountsIn scalling const { parsedTokens, parsedBalances, @@ -57,21 +67,28 @@ export class StablePoolExit implements ExitConcern { parsedSwapFee, } = parsePoolInfo(pool); - const WETH = '0x000000000000000000000000000000000000000F'; // TODO: check if it should be possible to exit with ETH instead of WETH - const assetHelpers = new AssetHelpers(WETH); + // Replace WETH address with ETH - required for exiting with ETH + const unwrappedTokens = parsedTokens.map((token) => + token === wrappedNativeAsset ? AddressZero : token + ); + + // Sort pool info based on tokens addresses + const assetHelpers = new AssetHelpers(wrappedNativeAsset); const [sortedTokens, sortedBalances] = assetHelpers.sortTokens( - parsedTokens, + shouldUnwrapNativeAsset ? unwrappedTokens : parsedTokens, parsedBalances ) as [string[], string[]]; let minAmountsOut = Array(parsedTokens.length).fill('0'); let userData: string; + let value: BigNumber | undefined; if (singleTokenMaxOut) { // Exit pool with single token using exact bptIn const singleTokenMaxOutIndex = parsedTokens.indexOf(singleTokenMaxOut); + // Calculate amount out given BPT in const amountOut = SDK.StableMath._calcTokenOutGivenExactBptIn( new OldBigNumber(parsedAmp as string), sortedBalances.map((b) => new OldBigNumber(b)), @@ -81,24 +98,31 @@ export class StablePoolExit implements ExitConcern { new OldBigNumber(parsedSwapFee) ).toString(); + // Apply slippage tolerance minAmountsOut[singleTokenMaxOutIndex] = subSlippage( BigNumber.from(amountOut), BigNumber.from(slippage) ).toString(); - userData = WeightedPoolEncoder.exitExactBPTInForOneTokenOut( + userData = StablePoolEncoder.exitExactBPTInForOneTokenOut( bptIn, singleTokenMaxOutIndex ); + + if (shouldUnwrapNativeAsset) { + value = BigNumber.from(amountOut); + } } else { // Exit pool with all tokens proportinally + // Calculate amount out given BPT in const amountsOut = SDK.StableMath._calcTokensOutGivenExactBptIn( sortedBalances.map((b) => new OldBigNumber(b)), new OldBigNumber(bptIn), new OldBigNumber(parsedTotalShares) ).map((amount) => amount.toString()); + // Apply slippage tolerance minAmountsOut = amountsOut.map((amount) => { const minAmount = subSlippage( BigNumber.from(amount), @@ -107,7 +131,14 @@ export class StablePoolExit implements ExitConcern { return minAmount.toString(); }); - userData = WeightedPoolEncoder.exitExactBPTInForTokensOut(bptIn); + userData = StablePoolEncoder.exitExactBPTInForTokensOut(bptIn); + + if (shouldUnwrapNativeAsset) { + const amount = amountsOut.find( + (amount, i) => sortedTokens[i] == AddressZero + ); + value = amount ? BigNumber.from(amount) : undefined; + } } const to = balancerVault; @@ -123,8 +154,9 @@ export class StablePoolExit implements ExitConcern { toInternalBalance: false, }, }; + + // Encode transaction data into an ABI byte string which can be sent to the network to be executed const vaultInterface = Vault__factory.createInterface(); - // encode transaction data into an ABI byte string which can be sent to the network to be executed const data = vaultInterface.encodeFunctionData(functionName, [ attributes.poolId, attributes.sender, @@ -137,6 +169,7 @@ export class StablePoolExit implements ExitConcern { functionName, attributes, data, + value, minAmountsOut, maxBPTIn: bptIn, }; @@ -149,6 +182,7 @@ export class StablePoolExit implements ExitConcern { * @param {string[]} tokensOut - Tokens provided for exiting pool * @param {string[]} amountsOut - Amoutns provided for exiting pool * @param {string} slippage - Maximum slippage tolerance in percentage. i.e. 0.05 = 5% + * @param {string} wrappedNativeAsset - Address of wrapped native asset for specific network config. Required for exiting with ETH. * @returns transaction request ready to send with signer.sendTransaction */ buildExitExactTokensOut = ({ @@ -157,6 +191,7 @@ export class StablePoolExit implements ExitConcern { tokensOut, amountsOut, slippage, + wrappedNativeAsset, }: ExitExactTokensOutParameters): ExitPoolAttributes => { if ( tokensOut.length != amountsOut.length || @@ -165,6 +200,12 @@ export class StablePoolExit implements ExitConcern { throw new BalancerError(BalancerErrorCode.INPUT_LENGTH_MISMATCH); } + // Check if there's any relevant stable pool info missing + if (pool.tokens.some((token) => !token.decimals)) + throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); + if (!pool.amp) throw new BalancerError(BalancerErrorCode.MISSING_AMP); + + // Parse pool info into EVM amounts in order to match amountsOut scalling const { parsedTokens, parsedBalances, @@ -173,8 +214,8 @@ export class StablePoolExit implements ExitConcern { parsedSwapFee, } = parsePoolInfo(pool); - const WETH = '0x000000000000000000000000000000000000000F'; // TODO: check if it should be possible to exit with ETH instead of WETH - const assetHelpers = new AssetHelpers(WETH); + // Sort pool info based on tokens addresses + const assetHelpers = new AssetHelpers(wrappedNativeAsset); const [, sortedBalances] = assetHelpers.sortTokens( parsedTokens, parsedBalances @@ -184,6 +225,7 @@ export class StablePoolExit implements ExitConcern { amountsOut ) as [string[], string[]]; + // Calculate expected BPT in given tokens out const bptIn = SDK.StableMath._calcBptInGivenExactTokensOut( new OldBigNumber(parsedAmp as string), sortedBalances.map((b) => new OldBigNumber(b)), @@ -192,12 +234,13 @@ export class StablePoolExit implements ExitConcern { new OldBigNumber(parsedSwapFee) ).toString(); + // Apply slippage tolerance const maxBPTIn = addSlippage( BigNumber.from(bptIn), BigNumber.from(slippage) ).toString(); - const userData = WeightedPoolEncoder.exitBPTInForExactTokensOut( + const userData = StablePoolEncoder.exitBPTInForExactTokensOut( sortedAmounts, maxBPTIn ); @@ -215,8 +258,9 @@ export class StablePoolExit implements ExitConcern { toInternalBalance: false, }, }; - const vaultInterface = Vault__factory.createInterface(); + // encode transaction data into an ABI byte string which can be sent to the network to be executed + const vaultInterface = Vault__factory.createInterface(); const data = vaultInterface.encodeFunctionData(functionName, [ attributes.poolId, attributes.sender, @@ -224,11 +268,15 @@ export class StablePoolExit implements ExitConcern { attributes.exitPoolRequest, ]); + const amount = amountsOut.find((amount, i) => tokensOut[i] == AddressZero); // find native asset (e.g. ETH) amount + const value = amount ? BigNumber.from(amount) : undefined; + return { to, functionName, attributes, data, + value, minAmountsOut: sortedAmounts, maxBPTIn, }; From d9f896009b3c03f018dd9f57a2fcd965d1a05067 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 4 Aug 2022 18:15:26 -0300 Subject: [PATCH 08/65] Add exit with ETH for meta stable pools --- .../exit.concern.integration.spec.ts | 437 +++++++++--------- .../concerns/metaStable/exit.concern.ts | 94 +++- 2 files changed, 282 insertions(+), 249 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts index f2e42a183..ca915a980 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts @@ -1,8 +1,6 @@ import dotenv from 'dotenv'; import { expect } from 'chai'; import { - BalancerError, - BalancerErrorCode, BalancerSDK, Network, Pool, @@ -14,10 +12,12 @@ import hardhat from 'hardhat'; import { TransactionReceipt } from '@ethersproject/providers'; import { BigNumber, parseFixed } from '@ethersproject/bignumber'; +import { forkSetup, setupPool, getBalances } from '@/test/lib/utils'; +import { PoolsProvider } from '@/modules/pools/provider'; -import { forkSetup } from '@/test/lib/utils'; import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { PoolsProvider } from '@/modules/pools/provider'; +import { ExitPoolAttributes } from '../types'; +import { AddressZero } from '@ethersproject/constants'; dotenv.config(); @@ -26,17 +26,18 @@ const { ethers } = hardhat; let balancer: BalancerSDK; const rpcUrl = 'http://127.0.0.1:8545'; -const provider = new ethers.providers.JsonRpcProvider(rpcUrl, 1); +const network = Network.MAINNET; +const provider = new ethers.providers.JsonRpcProvider(rpcUrl, network); const signer = provider.getSigner(); // Slots used to set the account balance for each token through hardhat_setStorageAt // Info fetched using npm package slot20 const BPT_SLOT = 0; - const initialBalance = '100000'; -const amountsOutDiv = '1000000'; -const bptIn = parseFixed('10', 18).toString(); -const slippage = '100'; +const amountsOutDiv = '100000000'; +const slippage = '10000'; +const poolId = + '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080'; // Balancer stETH Stable Pool let tokensOut: PoolToken[]; let amountsOut: string[]; @@ -47,251 +48,227 @@ let bptMaxBalanceDecrease: BigNumber; let tokensBalanceBefore: BigNumber[]; let tokensBalanceAfter: BigNumber[]; let tokensMinBalanceIncrease: BigNumber[]; +let transactionCost: BigNumber; let signerAddress: string; let pool: PoolModel; -// Setup - -const setupPool = async (provider: PoolsProvider, poolId: string) => { - const _pool = await provider.find(poolId); - if (!_pool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); - const pool = _pool; - return pool; -}; - -const tokenBalance = async (tokenAddress: string, signerAddress: string) => { - const balance: Promise = balancer.contracts - .ERC20(tokenAddress, signer.provider) - .balanceOf(signerAddress); - return balance; -}; - -const updateBalances = async (pool: Pool) => { - const bptBalance = tokenBalance(pool.address, signerAddress); - const balances = []; - for (let i = 0; i < pool.tokensList.length; i++) { - balances[i] = tokenBalance(pool.tokensList[i], signerAddress); - } - return Promise.all([bptBalance, ...balances]); -}; +describe('exit execution', async () => { + // Setup chain + before(async function () { + this.timeout(20000); + + const sdkConfig = { + network, + rpcUrl, + }; + // Using a static repository to make test consistent over time + const poolsProvider = new PoolsProvider( + sdkConfig, + new StaticPoolRepository(pools_14717479 as Pool[]) + ); + balancer = new BalancerSDK( + sdkConfig, + undefined, + undefined, + undefined, + poolsProvider + ); + pool = await setupPool(poolsProvider, poolId); + tokensOut = pool.tokens; + await forkSetup( + balancer, + signer, + [pool.address], + [BPT_SLOT], + [parseFixed(initialBalance, 18).toString()], + jsonRpcUrl as string, + 14717479 // holds the same state as the static repository + ); + signerAddress = await signer.getAddress(); + }); + + const testFlow = async ( + { to, data, maxBPTIn, minAmountsOut }: ExitPoolAttributes, + exitTokens: string[], + exitWithETH = false + ) => { + // Check balances before transaction to confirm success + [bptBalanceBefore, ...tokensBalanceBefore] = await getBalances( + [pool.address, ...exitTokens], + signer, + signerAddress + ); + + // Get expected balances out of transaction + bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); + tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); + + // Send transaction to local fork + const transactionResponse = await signer.sendTransaction({ to, data }); + transactionReceipt = await transactionResponse.wait(); + + // Check balances after transaction to confirm success + [bptBalanceAfter, ...tokensBalanceAfter] = await getBalances( + [pool.address, ...exitTokens], + signer, + signerAddress + ); + console.log(tokensBalanceBefore); + console.log(tokensBalanceAfter); + console.log(tokensMinBalanceIncrease); + + // add transaction cost to ETH balance when exiting with ETH + if (exitWithETH) { + transactionCost = transactionReceipt.gasUsed.mul( + transactionReceipt.effectiveGasPrice + ); + tokensBalanceAfter = tokensBalanceAfter.map((balance, i) => { + if ( + pool.tokensList[i] === + balancer.networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase() + ) { + return balance.add(transactionCost); + } + return balance; + }); + } + }; -const testExitPool = (poolId: string) => { - describe('exit execution', async () => { - // Setup chain + context('exitExactBPTIn', async () => { before(async function () { this.timeout(20000); - - const sdkConfig = { - network: Network.MAINNET, - rpcUrl, - }; - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) + const bptIn = parseFixed('10', 18).toString(); + await testFlow( + pool.buildExitExactBPTIn(signerAddress, bptIn, slippage), + pool.tokensList ); - balancer = new BalancerSDK( - sdkConfig, - undefined, - undefined, - undefined, - poolsProvider - ); - pool = await setupPool(poolsProvider, poolId); - tokensOut = pool.tokens; - await forkSetup( - balancer, - signer, - [pool.address], - [BPT_SLOT], - [parseFixed(initialBalance, 18).toString()], - jsonRpcUrl as string, - 14717479 // holds the same state as the static repository - ); - signerAddress = await signer.getAddress(); }); - context('exitExactBPTIn - exit with encoded data', async () => { - before(async function () { - this.timeout(20000); - [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); - - const { to, data, minAmountsOut, maxBPTIn } = pool.buildExitExactBPTIn( - signerAddress, - bptIn, - slippage - ); - const tx = { to, data }; // , gasPrice: '600000000000', gasLimit: '2000000' }; - - bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); - tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); - const transactionResponse = await signer.sendTransaction(tx); - transactionReceipt = await transactionResponse.wait(); - [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); - }); - - it('should work', async () => { - expect(transactionReceipt.status).to.eql(1); - }); - - it('tokens balance should increase', async () => { - for (let i = 0; i < tokensBalanceAfter.length; i++) { - expect( - tokensBalanceAfter[i] - .sub(tokensBalanceBefore[i]) - .gte(tokensMinBalanceIncrease[i]) - ).to.be.true; - } - }); - - it('bpt balance should decrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)) - .to.be.true; - }); + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); }); - context('exitExactBPTIn - exit with params', async () => { - before(async function () { - this.timeout(20000); - [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); - - const { attributes, minAmountsOut, maxBPTIn } = - pool.buildExitExactBPTIn(signerAddress, bptIn, slippage); - - bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); - tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); + it('tokens balance should increase by at least minAmountsOut', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .gte(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); - const transactionResponse = await balancer.contracts.vault - .connect(signer) - .exitPool( - attributes.poolId, - attributes.sender, - attributes.recipient, - attributes.exitPoolRequest - ); - transactionReceipt = await transactionResponse.wait(); - [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); - }); + it('bpt balance should decrease by exact bptMaxBalanceDecrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)).to + .be.true; + }); + }); - it('should work', async () => { - expect(transactionReceipt.status).to.eql(1); - }); + context('exitExactTokensOut', async () => { + before(async function () { + amountsOut = pool.tokens.map((t) => + parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() + ); + console.log('amounts out', amountsOut); - it('tokens balance should increase', async () => { - for (let i = 0; i < tokensBalanceAfter.length; i++) { - expect( - tokensBalanceAfter[i] - .sub(tokensBalanceBefore[i]) - .gte(tokensMinBalanceIncrease[i]) - ).to.be.true; - } - }); + await testFlow( + pool.buildExitExactTokensOut( + signerAddress, + tokensOut.map((t) => t.address), + amountsOut, + slippage + ), + tokensOut.map((t) => t.address) + ); + }); - it('bpt balance should decrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)) - .to.be.true; - }); + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); }); - context('exitExactTokensOut - exit with encoded data', async () => { - before(async function () { - this.timeout(20000); - [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); + it('tokens balance should increase by exact amountsOut', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .eq(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); - amountsOut = pool.tokens.map((t) => - parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() - ); - const { to, data, minAmountsOut, maxBPTIn } = - pool.buildExitExactTokensOut( - signerAddress, - tokensOut.map((t) => t.address), - amountsOut, - slippage - ); - const tx = { to, data }; // , gasPrice: '600000000000', gasLimit: '2000000' }; + it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + .to.be.true; + }); + }); - bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); - tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); - const transactionResponse = await signer.sendTransaction(tx); - transactionReceipt = await transactionResponse.wait(); - [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); - }); + context('exit with ETH', async () => { + before(async function () { + this.timeout(20000); + amountsOut = pool.tokens.map((t) => + parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() + ); - it('should work', async () => { - expect(transactionReceipt.status).to.eql(1); - }); + const exitTokens = pool.tokensList.map((token) => + token === + balancer.networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase() + ? AddressZero + : token + ); - it('tokens balance should increase', async () => { - for (let i = 0; i < tokensBalanceAfter.length; i++) { - expect( - tokensBalanceAfter[i] - .sub(tokensBalanceBefore[i]) - .eq(tokensMinBalanceIncrease[i]) - ).to.be.true; - } - }); + await testFlow( + pool.buildExitExactTokensOut( + signerAddress, + exitTokens, + amountsOut, + slippage + ), + exitTokens, + true + ); + }); - it('bpt balance should decrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) - .to.be.true; - }); + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); }); - context('exitExactTokensOut - exit with params', async () => { - before(async function () { - this.timeout(20000); - [bptBalanceBefore, ...tokensBalanceBefore] = await updateBalances(pool); + it('tokens balance should increase by exact amountsOut', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .eq(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); - amountsOut = pool.tokens.map((t) => - parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() - ); - const { attributes, minAmountsOut, maxBPTIn } = - pool.buildExitExactTokensOut( + it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + .to.be.true; + }); + }); + + context('exit with ETH - conflicting inputs', async () => { + it('should fail', async () => { + let errorMessage = ''; + try { + const bptIn = parseFixed('10', 18).toString(); + await testFlow( + pool.buildExitExactBPTIn( signerAddress, - tokensOut.map((t) => t.address), - amountsOut, - slippage - ); - - bptMaxBalanceDecrease = BigNumber.from(maxBPTIn); - tokensMinBalanceIncrease = minAmountsOut.map((a) => BigNumber.from(a)); - - const transactionResponse = await balancer.contracts.vault - .connect(signer) - .exitPool( - attributes.poolId, - attributes.sender, - attributes.recipient, - attributes.exitPoolRequest - ); - transactionReceipt = await transactionResponse.wait(); - [bptBalanceAfter, ...tokensBalanceAfter] = await updateBalances(pool); - }); - - it('should work', async () => { - expect(transactionReceipt.status).to.eql(1); - }); - - it('tokens balance should increase', async () => { - for (let i = 0; i < tokensBalanceAfter.length; i++) { - expect( - tokensBalanceAfter[i] - .sub(tokensBalanceBefore[i]) - .eq(tokensMinBalanceIncrease[i]) - ).to.be.true; - } - }); - - it('bpt balance should decrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) - .to.be.true; - }); + bptIn, + slippage, + false, + AddressZero + ), + pool.tokensList + ); + } catch (error) { + errorMessage = (error as Error).message; + } + expect(errorMessage).to.eql( + 'shouldUnwrapNativeAsset and singleTokenMaxOut should not have conflicting values' + ); }); - }).timeout(20000); -}; - -// Test Scenarios - -testExitPool( - '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080' // Balancer stETH Stable Pool -); + }); +}).timeout(20000); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts index d6ddc0778..0a2a42de3 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts @@ -10,10 +10,11 @@ import { } from '../types'; import { AssetHelpers, parsePoolInfo } from '@/lib/utils'; import { Vault__factory } from '@balancer-labs/typechain'; -import { WeightedPoolEncoder } from '@/pool-weighted'; import { addSlippage, subSlippage } from '@/lib/utils/slippageHelper'; import { balancerVault } from '@/lib/constants/config'; import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; +import { AddressZero } from '@ethersproject/constants'; +import { StablePoolEncoder } from '@/pool-stable'; export class MetaStablePoolExit implements ExitConcern { /** @@ -22,6 +23,8 @@ export class MetaStablePoolExit implements ExitConcern { * @param {Pool} pool - Subgraph pool object of pool being exited * @param {string} bptIn - BPT provided for exiting pool * @param {string} slippage - Maximum slippage tolerance in percentage. i.e. 0.05 = 5% + * @param {boolean} shouldUnwrapNativeAsset - Indicates wether wrapped native asset should be unwrapped after exit. + * @param {string} wrappedNativeAsset - Address of wrapped native asset for specific network config. Required for exiting to native asset. * @param {string} singleTokenMaxOut - Optional: token address that if provided will exit to given token * @returns transaction request ready to send with signer.sendTransaction */ @@ -39,18 +42,25 @@ export class MetaStablePoolExit implements ExitConcern { } if ( singleTokenMaxOut && + singleTokenMaxOut !== AddressZero && !pool.tokens.map((t) => t.address).some((a) => a === singleTokenMaxOut) ) { throw new BalancerError(BalancerErrorCode.TOKEN_MISMATCH); } - // Check if there's any relevant weighted pool info missing + if (!shouldUnwrapNativeAsset && singleTokenMaxOut === AddressZero) + throw new Error( + 'shouldUnwrapNativeAsset and singleTokenMaxOut should not have conflicting values' + ); + + // Check if there's any relevant meta stable pool info missing if (pool.tokens.some((token) => !token.decimals)) throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); if (!pool.amp) throw new BalancerError(BalancerErrorCode.MISSING_AMP); if (pool.tokens.some((token) => !token.priceRate)) throw new BalancerError(BalancerErrorCode.MISSING_PRICE_RATE); + // Parse pool info into EVM amounts in order to match amountsIn scalling const { parsedTokens, parsedBalances, @@ -60,16 +70,21 @@ export class MetaStablePoolExit implements ExitConcern { parsedSwapFee, } = parsePoolInfo(pool); - const WETH = '0x000000000000000000000000000000000000000F'; // TODO: check if it should be possible to exit with ETH instead of WETH - const assetHelpers = new AssetHelpers(WETH); + // Replace WETH address with ETH - required for exiting with ETH + const unwrappedTokens = parsedTokens.map((token) => + token === wrappedNativeAsset ? AddressZero : token + ); + + // Sort pool info based on tokens addresses + const assetHelpers = new AssetHelpers(wrappedNativeAsset); const [sortedTokens, sortedBalances, sortedPriceRates] = assetHelpers.sortTokens( - parsedTokens, + shouldUnwrapNativeAsset ? unwrappedTokens : parsedTokens, parsedBalances, parsedPriceRates ) as [string[], string[], string[]]; - // scale balances based on price rate for each token + // Scale balances based on price rate for each token const scaledBalances = sortedBalances.map((balance, i) => { return BigNumber.from(balance) .mul(BigNumber.from(sortedPriceRates[i])) @@ -79,12 +94,21 @@ export class MetaStablePoolExit implements ExitConcern { let minAmountsOut = Array(parsedTokens.length).fill('0'); let userData: string; + let value: BigNumber | undefined; if (singleTokenMaxOut) { // Exit pool with single token using exact bptIn - const singleTokenMaxOutIndex = parsedTokens.indexOf(singleTokenMaxOut); + const singleTokenMaxOutIndex = sortedTokens.indexOf(singleTokenMaxOut); + + console.log(parsedAmp); + console.log(scaledBalances); + console.log(singleTokenMaxOutIndex); + console.log(bptIn); + console.log(parsedTotalShares); + console.log(parsedSwapFee); + // Calculate amount out given BPT in const scaledAmountOut = SDK.StableMath._calcTokenOutGivenExactBptIn( new OldBigNumber(parsedAmp as string), scaledBalances.map((b) => new OldBigNumber(b)), @@ -94,7 +118,7 @@ export class MetaStablePoolExit implements ExitConcern { new OldBigNumber(parsedSwapFee) ).toString(); - // reverse scaled amount out based on token price rate + // Reverse scaled amount out based on token price rate const amountOut = BigNumber.from(scaledAmountOut) .div(BigNumber.from(sortedPriceRates[singleTokenMaxOutIndex])) .mul(parseFixed('1', 18)) @@ -105,20 +129,25 @@ export class MetaStablePoolExit implements ExitConcern { BigNumber.from(slippage) ).toString(); - userData = WeightedPoolEncoder.exitExactBPTInForOneTokenOut( + userData = StablePoolEncoder.exitExactBPTInForOneTokenOut( bptIn, singleTokenMaxOutIndex ); + + if (shouldUnwrapNativeAsset) { + value = BigNumber.from(amountOut); + } } else { // Exit pool with all tokens proportinally + // Calculate amount out given BPT in const scaledAmountsOut = SDK.StableMath._calcTokensOutGivenExactBptIn( scaledBalances.map((b) => new OldBigNumber(b)), new OldBigNumber(bptIn), new OldBigNumber(parsedTotalShares) ).map((amount) => amount.toString()); - // reverse scaled amounts out based on token price rate + // Reverse scaled amounts out based on token price rate const amountsOut = scaledAmountsOut.map((amount, i) => { return BigNumber.from(amount) .div(BigNumber.from(sortedPriceRates[i])) @@ -126,6 +155,7 @@ export class MetaStablePoolExit implements ExitConcern { .toString(); }); + // Apply slippage tolerance minAmountsOut = amountsOut.map((amount) => { const minAmount = subSlippage( BigNumber.from(amount), @@ -134,7 +164,14 @@ export class MetaStablePoolExit implements ExitConcern { return minAmount.toString(); }); - userData = WeightedPoolEncoder.exitExactBPTInForTokensOut(bptIn); + userData = StablePoolEncoder.exitExactBPTInForTokensOut(bptIn); + + if (shouldUnwrapNativeAsset) { + const amount = amountsOut.find( + (amount, i) => sortedTokens[i] == AddressZero + ); + value = amount ? BigNumber.from(amount) : undefined; + } } const to = balancerVault; @@ -150,8 +187,9 @@ export class MetaStablePoolExit implements ExitConcern { toInternalBalance: false, }, }; - const vaultInterface = Vault__factory.createInterface(); + // encode transaction data into an ABI byte string which can be sent to the network to be executed + const vaultInterface = Vault__factory.createInterface(); const data = vaultInterface.encodeFunctionData(functionName, [ attributes.poolId, attributes.sender, @@ -164,6 +202,7 @@ export class MetaStablePoolExit implements ExitConcern { functionName, attributes, data, + value, minAmountsOut, maxBPTIn: bptIn, }; @@ -176,6 +215,7 @@ export class MetaStablePoolExit implements ExitConcern { * @param {string[]} tokensOut - Tokens provided for exiting pool * @param {string[]} amountsOut - Amoutns provided for exiting pool * @param {string} slippage - Maximum slippage tolerance in percentage. i.e. 0.05 = 5% + * @param {string} wrappedNativeAsset - Address of wrapped native asset for specific network config. Required for exiting with ETH. * @returns transaction request ready to send with signer.sendTransaction */ buildExitExactTokensOut = ({ @@ -184,6 +224,7 @@ export class MetaStablePoolExit implements ExitConcern { tokensOut, amountsOut, slippage, + wrappedNativeAsset, }: ExitExactTokensOutParameters): ExitPoolAttributes => { if ( tokensOut.length != amountsOut.length || @@ -192,6 +233,14 @@ export class MetaStablePoolExit implements ExitConcern { throw new BalancerError(BalancerErrorCode.INPUT_LENGTH_MISMATCH); } + // Check if there's any relevant meta stable pool info missing + if (pool.tokens.some((token) => !token.decimals)) + throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); + if (!pool.amp) throw new BalancerError(BalancerErrorCode.MISSING_AMP); + if (pool.tokens.some((token) => !token.priceRate)) + throw new BalancerError(BalancerErrorCode.MISSING_PRICE_RATE); + + // Parse pool info into EVM amounts in order to match amountsOut scalling const { parsedTokens, parsedBalances, @@ -201,8 +250,8 @@ export class MetaStablePoolExit implements ExitConcern { parsedSwapFee, } = parsePoolInfo(pool); - const WETH = '0x000000000000000000000000000000000000000F'; // TODO: check if it should be possible to exit with ETH instead of WETH - const assetHelpers = new AssetHelpers(WETH); + // Sort pool info based on tokens addresses + const assetHelpers = new AssetHelpers(wrappedNativeAsset); const [, sortedBalances, sortedPriceRates] = assetHelpers.sortTokens( parsedTokens, parsedBalances, @@ -213,7 +262,7 @@ export class MetaStablePoolExit implements ExitConcern { amountsOut ) as [string[], string[]]; - // scale amounts in based on price rate for each token + // Scale amounts out based on price rate for each token const scaledAmounts = sortedAmounts.map((amount, i) => { return BigNumber.from(amount) .mul(BigNumber.from(sortedPriceRates[i])) @@ -221,7 +270,7 @@ export class MetaStablePoolExit implements ExitConcern { .toString(); }); - // scale balances based on price rate for each token + // Scale balances based on price rate for each token const scaledBalances = sortedBalances.map((balance, i) => { return BigNumber.from(balance) .mul(BigNumber.from(sortedPriceRates[i])) @@ -229,6 +278,7 @@ export class MetaStablePoolExit implements ExitConcern { .toString(); }); + // Calculate expected BPT in given tokens out const bptIn = SDK.StableMath._calcBptInGivenExactTokensOut( new OldBigNumber(parsedAmp as string), scaledBalances.map((b) => new OldBigNumber(b)), @@ -237,13 +287,14 @@ export class MetaStablePoolExit implements ExitConcern { new OldBigNumber(parsedSwapFee) ).toString(); + // Apply slippage tolerance const maxBPTIn = addSlippage( BigNumber.from(bptIn), BigNumber.from(slippage) ).toString(); - const userData = WeightedPoolEncoder.exitBPTInForExactTokensOut( - sortedAmounts, + const userData = StablePoolEncoder.exitBPTInForExactTokensOut( + ['0', '0'], // must not use scaledAmounts because it should match amountsOut provided by the user maxBPTIn ); @@ -260,8 +311,9 @@ export class MetaStablePoolExit implements ExitConcern { toInternalBalance: false, }, }; - const vaultInterface = Vault__factory.createInterface(); + // encode transaction data into an ABI byte string which can be sent to the network to be executed + const vaultInterface = Vault__factory.createInterface(); const data = vaultInterface.encodeFunctionData(functionName, [ attributes.poolId, attributes.sender, @@ -269,11 +321,15 @@ export class MetaStablePoolExit implements ExitConcern { attributes.exitPoolRequest, ]); + const amount = amountsOut.find((amount, i) => tokensOut[i] == AddressZero); // find native asset (e.g. ETH) amount + const value = amount ? BigNumber.from(amount) : undefined; + return { to, functionName, attributes, data, + value, minAmountsOut: sortedAmounts, maxBPTIn, }; From f883e027f0f73689ccdd97d0e67cc99e7f4507c4 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 5 Aug 2022 09:51:35 -0300 Subject: [PATCH 09/65] Move exit parameters description to the interface --- .../concerns/metaStable/exit.concern.ts | 21 --------------- .../concerns/stable/exit.concern.ts | 21 --------------- .../pools/pool-types/concerns/types.ts | 21 +++++++++++++++ .../concerns/weighted/exit.concern.ts | 27 +++---------------- 4 files changed, 24 insertions(+), 66 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts index 0a2a42de3..cbbab8536 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts @@ -17,17 +17,6 @@ import { AddressZero } from '@ethersproject/constants'; import { StablePoolEncoder } from '@/pool-stable'; export class MetaStablePoolExit implements ExitConcern { - /** - * Build exit pool transaction parameters with exact BPT in and minimum token amounts out based on slippage tolerance - * @param {string} exiter - Account address exiting pool - * @param {Pool} pool - Subgraph pool object of pool being exited - * @param {string} bptIn - BPT provided for exiting pool - * @param {string} slippage - Maximum slippage tolerance in percentage. i.e. 0.05 = 5% - * @param {boolean} shouldUnwrapNativeAsset - Indicates wether wrapped native asset should be unwrapped after exit. - * @param {string} wrappedNativeAsset - Address of wrapped native asset for specific network config. Required for exiting to native asset. - * @param {string} singleTokenMaxOut - Optional: token address that if provided will exit to given token - * @returns transaction request ready to send with signer.sendTransaction - */ buildExitExactBPTIn = ({ exiter, pool, @@ -208,16 +197,6 @@ export class MetaStablePoolExit implements ExitConcern { }; }; - /** - * Build exit pool transaction parameters with exact tokens out and maximum BPT in based on slippage tolerance - * @param {string} exiter - Account address exiting pool - * @param {Pool} pool - Subgraph pool object of pool being exited - * @param {string[]} tokensOut - Tokens provided for exiting pool - * @param {string[]} amountsOut - Amoutns provided for exiting pool - * @param {string} slippage - Maximum slippage tolerance in percentage. i.e. 0.05 = 5% - * @param {string} wrappedNativeAsset - Address of wrapped native asset for specific network config. Required for exiting with ETH. - * @returns transaction request ready to send with signer.sendTransaction - */ buildExitExactTokensOut = ({ exiter, pool, diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts index ba4871005..f50c7256d 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts @@ -17,17 +17,6 @@ import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; import { StablePoolEncoder } from '@/pool-stable'; export class StablePoolExit implements ExitConcern { - /** - * Build exit pool transaction parameters with exact BPT in and minimum token amounts out based on slippage tolerance - * @param {string} exiter - Account address exiting pool - * @param {Pool} pool - Subgraph pool object of pool being exited - * @param {string} bptIn - BPT provided for exiting pool - * @param {string} slippage - Maximum slippage tolerance in percentage. i.e. 0.05 = 5% - * @param {boolean} shouldUnwrapNativeAsset - Indicates wether wrapped native asset should be unwrapped after exit. - * @param {string} wrappedNativeAsset - Address of wrapped native asset for specific network config. Required for exiting to native asset. - * @param {string} singleTokenMaxOut - Optional: token address that if provided will exit to given token - * @returns transaction request ready to send with signer.sendTransaction - */ buildExitExactBPTIn = ({ exiter, pool, @@ -175,16 +164,6 @@ export class StablePoolExit implements ExitConcern { }; }; - /** - * Build exit pool transaction parameters with exact tokens out and maximum BPT in based on slippage tolerance - * @param {string} exiter - Account address exiting pool - * @param {Pool} pool - Subgraph pool object of pool being exited - * @param {string[]} tokensOut - Tokens provided for exiting pool - * @param {string[]} amountsOut - Amoutns provided for exiting pool - * @param {string} slippage - Maximum slippage tolerance in percentage. i.e. 0.05 = 5% - * @param {string} wrappedNativeAsset - Address of wrapped native asset for specific network config. Required for exiting with ETH. - * @returns transaction request ready to send with signer.sendTransaction - */ buildExitExactTokensOut = ({ exiter, pool, diff --git a/balancer-js/src/modules/pools/pool-types/concerns/types.ts b/balancer-js/src/modules/pools/pool-types/concerns/types.ts index d109b749e..b32214d35 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/types.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/types.ts @@ -28,6 +28,17 @@ export interface JoinConcern { } export interface ExitConcern { + /** + * Build exit pool transaction parameters with exact BPT in and minimum token amounts out based on slippage tolerance + * @param exiter Account address exiting pool + * @param pool Subgraph pool object of pool being exited + * @param bptIn BPT provided for exiting pool + * @param slippage Maximum slippage tolerance in percentage. i.e. 0.05 = 5% + * @param shouldUnwrapNativeAsset Indicates whether wrapped native asset should be unwrapped after exit. + * @param wrappedNativeAsset Wrapped native asset address for network being used. Required for exiting with native asset. + * @param singleTokenMaxOut Optional: token address that if provided will exit to given token + * @returns transaction request ready to send with signer.sendTransaction + */ buildExitExactBPTIn: ({ exiter, pool, @@ -38,6 +49,16 @@ export interface ExitConcern { singleTokenMaxOut, }: ExitExactBPTInParameters) => ExitPoolAttributes; + /** + * Build exit pool transaction parameters with exact tokens out and maximum BPT in based on slippage tolerance + * @param exiter Account address exiting pool + * @param pool Subgraph pool object of pool being exited + * @param tokensOut Tokens provided for exiting pool + * @param amountsOut Amounts provided for exiting pool + * @param slippage Maximum slippage tolerance in percentage. i.e. 0.05 = 5% + * @param wrappedNativeAsset Wrapped native asset address for network being used. Required for exiting with native asset. + * @returns transaction request ready to send with signer.sendTransaction + */ buildExitExactTokensOut: ({ exiter, pool, diff --git a/balancer-js/src/modules/pools/pool-types/concerns/weighted/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/weighted/exit.concern.ts index a2d6529f7..2db471524 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/weighted/exit.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/weighted/exit.concern.ts @@ -17,17 +17,6 @@ import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; import { AddressZero } from '@ethersproject/constants'; export class WeightedPoolExit implements ExitConcern { - /** - * Build exit pool transaction parameters with exact BPT in and minimum token amounts out based on slippage tolerance - * @param {string} exiter - Account address exiting pool - * @param {Pool} pool - Subgraph pool object of pool being exited - * @param {string} bptIn - BPT provided for exiting pool - * @param {string} slippage - Maximum slippage tolerance in percentage. i.e. 0.05 = 5% - * @param {boolean} shouldUnwrapNativeAsset - Indicates wether wrapped native asset should be unwrapped after exit. - * @param {string} wrappedNativeAsset - Address of wrapped native asset for specific network config. Required for exiting to native asset. - * @param {string} singleTokenMaxOut - Optional: token address that if provided will exit to given token - * @returns transaction request ready to send with signer.sendTransaction - */ buildExitExactBPTIn = ({ exiter, pool, @@ -93,7 +82,7 @@ export class WeightedPoolExit implements ExitConcern { new OldBigNumber(parsedSwapFee) ).toString(); - // Apply slippage + // Apply slippage tolerance minAmountsOut[singleTokenMaxOutIndex] = subSlippage( BigNumber.from(amountOut), BigNumber.from(slippage) @@ -117,7 +106,7 @@ export class WeightedPoolExit implements ExitConcern { new OldBigNumber(parsedTotalShares) ).map((amount) => amount.toString()); - // Apply slippage + // Apply slippage tolerance minAmountsOut = amountsOut.map((amount) => { const minAmount = subSlippage( BigNumber.from(amount), @@ -170,16 +159,6 @@ export class WeightedPoolExit implements ExitConcern { }; }; - /** - * Build exit pool transaction parameters with exact tokens out and maximum BPT in based on slippage tolerance - * @param {string} exiter - Account address exiting pool - * @param {Pool} pool - Subgraph pool object of pool being exited - * @param {string[]} tokensOut - Tokens provided for exiting pool - * @param {string[]} amountsOut - Amoutns provided for exiting pool - * @param {string} slippage - Maximum slippage tolerance in percentage. i.e. 0.05 = 5% - * @param {string} wrappedNativeAsset - Address of wrapped native asset for specific network config. Required for exiting with ETH. - * @returns transaction request ready to send with signer.sendTransaction - */ buildExitExactTokensOut = ({ exiter, pool, @@ -225,7 +204,7 @@ export class WeightedPoolExit implements ExitConcern { new OldBigNumber(parsedSwapFee) ).toString(); - // Apply slippage + // Apply slippage tolerance const maxBPTIn = addSlippage( BigNumber.from(bptIn), BigNumber.from(slippage) From fc3a92707e1104cf1f07318212f628198f7af220 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 5 Aug 2022 10:54:23 -0300 Subject: [PATCH 10/65] Add exit with single token test scenarios for stable and metaStable pools --- .../exit.concern.integration.spec.ts | 182 +++++++++++------- .../concerns/metaStable/exit.concern.ts | 9 +- .../stable/exit.concern.integration.spec.ts | 165 +++++++--------- 3 files changed, 184 insertions(+), 172 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts index ca915a980..4cd49eeb6 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts @@ -35,7 +35,7 @@ const signer = provider.getSigner(); const BPT_SLOT = 0; const initialBalance = '100000'; const amountsOutDiv = '100000000'; -const slippage = '10000'; +const slippage = '1000'; const poolId = '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080'; // Balancer stETH Stable Pool @@ -113,9 +113,6 @@ describe('exit execution', async () => { signer, signerAddress ); - console.log(tokensBalanceBefore); - console.log(tokensBalanceAfter); - console.log(tokensMinBalanceIncrease); // add transaction cost to ETH balance when exiting with ETH if (exitWithETH) { @@ -165,86 +162,129 @@ describe('exit execution', async () => { }); context('exitExactTokensOut', async () => { - before(async function () { - amountsOut = pool.tokens.map((t) => - parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() - ); - console.log('amounts out', amountsOut); + context('all tokens out', async () => { + before(async function () { + amountsOut = pool.tokens.map((t, i) => + parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() + ); - await testFlow( - pool.buildExitExactTokensOut( - signerAddress, - tokensOut.map((t) => t.address), - amountsOut, - slippage - ), - tokensOut.map((t) => t.address) - ); - }); + try { + await testFlow( + pool.buildExitExactTokensOut( + signerAddress, + tokensOut.map((t) => t.address), + amountsOut, + slippage + ), + tokensOut.map((t) => t.address) + ); + } catch (error) { + // remove try/catch after fixing issues + } + }); - it('should work', async () => { - expect(transactionReceipt.status).to.eql(1); - }); + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); + }); - it('tokens balance should increase by exact amountsOut', async () => { - for (let i = 0; i < tokensBalanceAfter.length; i++) { - expect( - tokensBalanceAfter[i] - .sub(tokensBalanceBefore[i]) - .eq(tokensMinBalanceIncrease[i]) - ).to.be.true; - } + it('tokens balance should increase by exact amountsOut', async () => { + expect('test failing due to stable math calculation issues').to.eql(''); + // for (let i = 0; i < tokensBalanceAfter.length; i++) { + // expect( + // tokensBalanceAfter[i] + // .sub(tokensBalanceBefore[i]) + // .eq(tokensMinBalanceIncrease[i]) + // ).to.be.true; + // } + }); + + it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + .to.be.true; + }); }); - it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) - .to.be.true; + context('single token out', async () => { + before(async function () { + amountsOut = pool.tokens.map((t, i) => + parseFixed(t.balance, t.decimals).div(amountsOutDiv).mul(i).toString() + ); + + await testFlow( + pool.buildExitExactTokensOut( + signerAddress, + tokensOut.map((t) => t.address), + amountsOut, + slippage + ), + tokensOut.map((t) => t.address) + ); + }); + + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); + }); + + it('tokens balance should increase by exact amountsOut', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .eq(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); + + it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + .to.be.true; + }); }); - }); - context('exit with ETH', async () => { - before(async function () { - this.timeout(20000); - amountsOut = pool.tokens.map((t) => - parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() - ); + context('exit with ETH', async () => { + before(async function () { + this.timeout(20000); + amountsOut = pool.tokens.map((t, i) => + parseFixed(t.balance, t.decimals).div(amountsOutDiv).mul(i).toString() + ); - const exitTokens = pool.tokensList.map((token) => - token === - balancer.networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase() - ? AddressZero - : token - ); + const exitTokens = pool.tokensList.map((token) => + token === + balancer.networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase() + ? AddressZero + : token + ); - await testFlow( - pool.buildExitExactTokensOut( - signerAddress, + await testFlow( + pool.buildExitExactTokensOut( + signerAddress, + exitTokens, + amountsOut, + slippage + ), exitTokens, - amountsOut, - slippage - ), - exitTokens, - true - ); - }); + true + ); + }); - it('should work', async () => { - expect(transactionReceipt.status).to.eql(1); - }); + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); + }); - it('tokens balance should increase by exact amountsOut', async () => { - for (let i = 0; i < tokensBalanceAfter.length; i++) { - expect( - tokensBalanceAfter[i] - .sub(tokensBalanceBefore[i]) - .eq(tokensMinBalanceIncrease[i]) - ).to.be.true; - } - }); + it('tokens balance should increase by exact amountsOut', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .eq(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); - it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) - .to.be.true; + it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + .to.be.true; + }); }); }); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts index cbbab8536..279077602 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts @@ -90,13 +90,6 @@ export class MetaStablePoolExit implements ExitConcern { const singleTokenMaxOutIndex = sortedTokens.indexOf(singleTokenMaxOut); - console.log(parsedAmp); - console.log(scaledBalances); - console.log(singleTokenMaxOutIndex); - console.log(bptIn); - console.log(parsedTotalShares); - console.log(parsedSwapFee); - // Calculate amount out given BPT in const scaledAmountOut = SDK.StableMath._calcTokenOutGivenExactBptIn( new OldBigNumber(parsedAmp as string), @@ -273,7 +266,7 @@ export class MetaStablePoolExit implements ExitConcern { ).toString(); const userData = StablePoolEncoder.exitBPTInForExactTokensOut( - ['0', '0'], // must not use scaledAmounts because it should match amountsOut provided by the user + sortedAmounts, // must not use scaledAmounts because it should match amountsOut provided by the user maxBPTIn ); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts index 36a500d90..acf07fcb5 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts @@ -34,8 +34,8 @@ const signer = provider.getSigner(); // Info fetched using npm package slot20 const BPT_SLOT = 0; const initialBalance = '100000'; -const amountsOutDiv = '1000000000'; -const slippage = '100'; +const amountsOutDiv = '100000000'; +const slippage = '1000'; const poolId = '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063'; // Balancer USD Stable Pool - staBAL3 @@ -162,109 +162,88 @@ describe('exit execution', async () => { }); context('exitExactTokensOut', async () => { - before(async function () { - amountsOut = pool.tokens.map((t) => - parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() - ); - - await testFlow( - pool.buildExitExactTokensOut( - signerAddress, - tokensOut.map((t) => t.address), - amountsOut, - slippage - ), - pool.tokensList - ); - }); - - it('should work', async () => { - expect(transactionReceipt.status).to.eql(1); - }); - - it('tokens balance should increase by exact amountsOut', async () => { - for (let i = 0; i < tokensBalanceAfter.length; i++) { - expect( - tokensBalanceAfter[i] - .sub(tokensBalanceBefore[i]) - .eq(tokensMinBalanceIncrease[i]) - ).to.be.true; - } - }); - - it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) - .to.be.true; - }); - }); - - context('exit with ETH', async () => { - before(async function () { - this.timeout(20000); - amountsOut = pool.tokens.map((t) => - parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() - ); + context('all tokens out', async () => { + before(async function () { + amountsOut = pool.tokens.map((t, i) => + parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() + ); - const exitTokens = pool.tokensList.map((token) => - token === - balancer.networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase() - ? AddressZero - : token - ); + try { + await testFlow( + pool.buildExitExactTokensOut( + signerAddress, + tokensOut.map((t) => t.address), + amountsOut, + slippage + ), + pool.tokensList + ); + } catch (error) { + // remove try/catch after fixing issues + } + }); - await testFlow( - pool.buildExitExactTokensOut( - signerAddress, - exitTokens, - amountsOut, - slippage - ), - exitTokens, - true - ); - }); + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); + }); - it('should work', async () => { - expect(transactionReceipt.status).to.eql(1); - }); + it('tokens balance should increase by exact amountsOut', async () => { + expect('test failing due to stable math calculation issues').to.eql(''); + // for (let i = 0; i < tokensBalanceAfter.length; i++) { + // expect( + // tokensBalanceAfter[i] + // .sub(tokensBalanceBefore[i]) + // .eq(tokensMinBalanceIncrease[i]) + // ).to.be.true; + // } + }); - it('tokens balance should increase by exact amountsOut', async () => { - for (let i = 0; i < tokensBalanceAfter.length; i++) { - expect( - tokensBalanceAfter[i] - .sub(tokensBalanceBefore[i]) - .eq(tokensMinBalanceIncrease[i]) - ).to.be.true; - } + it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + .to.be.true; + }); }); - it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) - .to.be.true; - }); - }); + context('single token out', async () => { + before(async function () { + amountsOut = pool.tokens.map((t, i) => { + if (i === 0) { + return parseFixed(t.balance, t.decimals) + .div(amountsOutDiv) + .toString(); + } + return '0'; + }); - context('exit with ETH - conflicting inputs', async () => { - it('should fail', async () => { - let errorMessage = ''; - try { - const bptIn = parseFixed('10', 18).toString(); await testFlow( - pool.buildExitExactBPTIn( + pool.buildExitExactTokensOut( signerAddress, - bptIn, - slippage, - false, - AddressZero + tokensOut.map((t) => t.address), + amountsOut, + slippage ), pool.tokensList ); - } catch (error) { - errorMessage = (error as Error).message; - } - expect(errorMessage).to.eql( - 'shouldUnwrapNativeAsset and singleTokenMaxOut should not have conflicting values' - ); + }); + + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); + }); + + it('tokens balance should increase by exact amountsOut', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .eq(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); + + it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + .to.be.true; + }); }); }); }).timeout(20000); From 43a44a1e55d95c62903fac4d7f33ecd75cc71bbd Mon Sep 17 00:00:00 2001 From: biancabuzea200 Date: Fri, 26 Aug 2022 16:47:15 +0300 Subject: [PATCH 11/65] updated README to include instructions on how to run js examples --- balancer-js/README.md | 71 ++++++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/balancer-js/README.md b/balancer-js/README.md index be519a890..a51e02b69 100644 --- a/balancer-js/README.md +++ b/balancer-js/README.md @@ -2,6 +2,28 @@ A JavaScript SDK which provides commonly used utilties for interacting with Balancer Protocol V2. +## How to run the examples (Javascript)? + +**In order to run the examples provided, you need to follow the next steps:** + +1. git clone https://github.com/balancer-labs/balancer-sdk.git +2. cd balancer-sdk +3. cd balancer-js +4. Create a .env file in the balancer-js folder +5. In the .env file you will need to define and initialize the following variables + + ALCHEMY_URL=[ALCHEMY HTTPS ENDPOINT] + INFURA=[Infura API KEY] + TRADER_KEY=[MetaMask PRIVATE KEY] + We have defined both Alchemy and Infura, because some of the examples use Infura, others use Alchemy. However, feel free to modify accordingly and use your favourite one. + +6. Run 'npm run node', this runs a local Hardhat Network +7. Open a new terminal +8. cd to balancer-js +9. Install ts-node using: npm install ts-node +10. Install tsconfig-paths using: npm install --save-dev tsconfig-paths +11. Run one of the provided examples (eg: npm run examples:run -- examples/join.ts) + ## Installation ## Getting Started @@ -10,8 +32,8 @@ A JavaScript SDK which provides commonly used utilties for interacting with Bala import { BalancerSDK, BalancerSdkConfig, Network } from '@balancer-labs/sdk'; const config: BalancerSdkConfig = { - network: Network.MAINNET, - rpcUrl: `https://mainnet.infura.io/v3/${process.env.INFURA}`, + network: Network.MAINNET, + rpcUrl: `https://mainnet.infura.io/v3/${process.env.INFURA}`, }; const balancer = new BalancerSDK(config); ``` @@ -20,16 +42,18 @@ In some examples we present a way to make end to end trades against mainnet stat Installation instructions for: -* [Hardhat](https://hardhat.org/getting-started/#installation) +- [Hardhat](https://hardhat.org/getting-started/#installation) To start a forked node: + ``` npm run node ``` -* [Anvil](https://github.com/foundry-rs/foundry/tree/master/anvil#installation) - use with caution, still experimental. - +- [Anvil](https://github.com/foundry-rs/foundry/tree/master/anvil#installation) - use with caution, still experimental. + To start a forked node: + ``` anvil -f FORKABLE_RPC_URL (optional pinned block: --fork-block-number XXX) ``` @@ -41,30 +65,29 @@ Exposes complete functionality for token swapping. An example of using the modul ```js // Uses SOR to find optimal route for a trading pair and amount const route = balancer.swaps.findRouteGivenIn({ - tokenIn, - tokenOut, - amount, - gasPrice, - maxPools, -}) + tokenIn, + tokenOut, + amount, + gasPrice, + maxPools, +}); // Prepares transaction attributes based on the route const transactionAttributes = balancer.swaps.buildSwap({ - userAddress, - swapInfo: route, - kind: 0, // 0 - givenIn, 1 - givenOut - deadline, - maxSlippage, -}) + userAddress, + swapInfo: route, + kind: 0, // 0 - givenIn, 1 - givenOut + deadline, + maxSlippage, +}); // Extract parameters required for sendTransaction -const { to, data, value } = transactionAttributes +const { to, data, value } = transactionAttributes; // Execution with ethers.js -const transactionResponse = await signer.sendTransaction({ to, data, value }) +const transactionResponse = await signer.sendTransaction({ to, data, value }); ``` - ## SwapsService The SwapsService provides function to query and make swaps using Balancer V2 liquidity. @@ -168,9 +191,9 @@ complex flash swaps, you will have to use batch swap directly. Gotchas: -- Both pools must have both assets (tokens) for swaps to work -- No pool token balances can be zero -- If the flash swap isn't profitable, the internal flash loan will fail. +- Both pools must have both assets (tokens) for swaps to work +- No pool token balances can be zero +- If the flash swap isn't profitable, the internal flash loan will fail. ### #encodeSimpleFlashSwap @@ -234,7 +257,7 @@ Calculates Spot Price for a token pair - for specific pool if ID otherwise finds @param { string } tokenOut Token out address. @param { string } poolId Optional - if specified this pool will be used for SP calculation. @param { SubgraphPoolBase[] } pools Optional - Pool data. Will be fetched via dataProvider if not supplied. -@returns { string } Spot price. +@returns { string } Spot price. ```js async getSpotPrice( From 6554d83d83fc2f54fa89cb326e6c90bee7a29278 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 30 Aug 2022 15:51:43 +0100 Subject: [PATCH 12/65] WIP. Update spot price to work with Provider. Weighted pool. --- balancer-js/examples/priceImpact.ts | 1 - .../pools/pool-types/concerns/types.ts | 8 +- .../concerns/weighted/spotPrice.concern.ts | 83 ++++++++++- .../concerns/weighted/spotPrice.spec.ts | 49 ++++++ balancer-js/src/modules/pools/provider.ts | 6 +- .../modules/pricing/pricing.module.spec.ts | 141 ------------------ .../src/modules/pricing/pricing.module.ts | 47 ++---- balancer-js/src/types.ts | 2 + 8 files changed, 146 insertions(+), 191 deletions(-) create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.spec.ts diff --git a/balancer-js/examples/priceImpact.ts b/balancer-js/examples/priceImpact.ts index fad23cdbd..b18eab7ab 100644 --- a/balancer-js/examples/priceImpact.ts +++ b/balancer-js/examples/priceImpact.ts @@ -11,7 +11,6 @@ Example showing how to use SDK to get price impact for a join or exit operation. async function getPriceImpact() { const network = Network.MAINNET; const rpcUrl = 'http://127.0.0.1:8545'; - const provider = new JsonRpcProvider(rpcUrl, network); const WBTCWETHId = '0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e'; // 50/50 WBTC/WETH Pool diff --git a/balancer-js/src/modules/pools/pool-types/concerns/types.ts b/balancer-js/src/modules/pools/pool-types/concerns/types.ts index a20048b36..ffc2b9c86 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/types.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/types.ts @@ -1,6 +1,4 @@ /* eslint @typescript-eslint/no-explicit-any: ["error", { "ignoreRestArgs": true }] */ - -import { SubgraphPoolBase } from '@balancer-labs/sor'; import { ExitPoolRequest, JoinPoolRequest, Pool } from '@/types'; import { BigNumber } from '@ethersproject/bignumber'; @@ -9,11 +7,7 @@ export interface LiquidityConcern { } export interface SpotPriceConcern { - calcPoolSpotPrice: ( - tokenIn: string, - tokenOut: string, - pool: SubgraphPoolBase - ) => string; + calcPoolSpotPrice: (tokenIn: string, tokenOut: string, pool: Pool) => string; } export interface PriceImpactConcern { diff --git a/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.concern.ts index 77c247ba2..4e25d0c3c 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.concern.ts @@ -1,13 +1,84 @@ import { SpotPriceConcern } from '../types'; import { SubgraphPoolBase, WeightedPool, ZERO } from '@balancer-labs/sor'; +import { Pool } from '@/types'; export class WeightedPoolSpotPrice implements SpotPriceConcern { - calcPoolSpotPrice( - tokenIn: string, - tokenOut: string, - pool: SubgraphPoolBase - ): string { - const weightedPool = WeightedPool.fromPool(pool); + calcPoolSpotPrice(tokenIn: string, tokenOut: string, pool: Pool): string { + /* + id: string; + address: string; + poolType: PoolType; + swapFee: string; + swapEnabled: boolean; + totalShares: string; + tokensList: string[]; + amp?: string; + + owner?: string; + factory?: string; + tokens: PoolToken[]; + tokenAddresses?: string[]; + totalLiquidity?: string; + totalSwapFee?: string; + totalSwapVolume?: string; + onchain?: OnchainPoolData; + createTime?: number; + mainTokens?: string[]; + wrappedTokens?: string[]; + unwrappedTokens?: string[]; + isNew?: boolean; + volumeSnapshot?: string; + feesSnapshot?: string; + boost?: string; + symbol?: string; + } + interface SubgraphPoolBase { + id: string; + address: string; + poolType: string; + swapFee: string; + swapEnabled: boolean; + totalShares: string; + tokensList: string[]; + amp?: string; + + totalWeight?: string; + expiryTime?: number; + unitSeconds?: number; + principalToken?: string; + baseToken?: string; + mainIndex?: number; + wrappedIndex?: number; + lowerTarget?: string; + upperTarget?: string; + sqrtAlpha?: string; + sqrtBeta?: string; + root3Alpha?: string; + tokens: SubgraphToken[]; + } + + declare type SubgraphToken = { + address: string; + balance: string; + decimals: number; + priceRate: string; + weight: string | null; + }; + + export interface Token { + address: string; + decimals?: number; + symbol?: string; + price?: Price; + } + + export interface PoolToken extends Token { + balance: string; + priceRate?: string; + weight?: string | null; + } + */ + const weightedPool = WeightedPool.fromPool(pool as SubgraphPoolBase); const poolPairData = weightedPool.parsePoolPairData(tokenIn, tokenOut); return weightedPool ._spotPriceAfterSwapExactTokenInForTokenOut(poolPairData, ZERO) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.spec.ts new file mode 100644 index 000000000..fc47f1f28 --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.spec.ts @@ -0,0 +1,49 @@ +import dotenv from 'dotenv'; +import { expect } from 'chai'; +import pools_14717479 from '@/test/lib/pools_14717479.json'; +import { StaticPoolRepository } from '@/modules/data'; +import { PoolsProvider } from '@/modules/pools/provider'; +import { PoolModel, Pool } from '@/types'; +import { Network } from '@/.'; +import { setupPool } from '@/test/lib/utils'; +import { WeightedPoolSpotPrice } from './spotPrice.concern'; +import { ADDRESSES } from '@/test/lib/constants'; + +dotenv.config(); + +const network = Network.MAINNET; +const rpcUrl = 'http://127.0.0.1:8545'; + +const spotPriceCalc = new WeightedPoolSpotPrice(); +const weth_bal_pool_id = + '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014'; + +describe('weighted pool spot price', () => { + let pool: PoolModel | undefined; + + // Setup chain + before(async function () { + const sdkConfig = { + network, + rpcUrl, + }; + // Using a static repository to make test consistent over time + const poolsProvider = new PoolsProvider( + sdkConfig, + new StaticPoolRepository(pools_14717479 as Pool[]) + ); + pool = await setupPool(poolsProvider, weth_bal_pool_id); + }); + + context('bpt zero price impact', () => { + it('two token pool', () => { + const spotPrice = spotPriceCalc.calcPoolSpotPrice( + ADDRESSES[network].WETH.address, + ADDRESSES[network].BAL.address, + pool as PoolModel + ); + + expect(spotPrice).to.eq('0.004981212133448337'); + }); + }); +}); diff --git a/balancer-js/src/modules/pools/provider.ts b/balancer-js/src/modules/pools/provider.ts index 99ab203aa..bf1ef9ef7 100644 --- a/balancer-js/src/modules/pools/provider.ts +++ b/balancer-js/src/modules/pools/provider.ts @@ -60,10 +60,8 @@ export class PoolsProvider { slippage, wrappedNativeAsset: networkConfig.addresses.tokens.wrappedNativeAsset, }), - // TODO: spotPrice fails, because it needs a subgraphType, - // either we refetch or it needs a type transformation from SDK internal to SOR (subgraph) - // spotPrice: async (tokenIn: string, tokenOut: string) => - // methods.spotPriceCalculator.calcPoolSpotPrice(tokenIn, tokenOut, data), + calcSpotPrice: (tokenIn: string, tokenOut: string) => + methods.spotPriceCalculator.calcPoolSpotPrice(tokenIn, tokenOut, data), }; } diff --git a/balancer-js/src/modules/pricing/pricing.module.spec.ts b/balancer-js/src/modules/pricing/pricing.module.spec.ts index a31a7ce08..d42e9af8d 100644 --- a/balancer-js/src/modules/pricing/pricing.module.spec.ts +++ b/balancer-js/src/modules/pricing/pricing.module.spec.ts @@ -17,11 +17,6 @@ let sdkConfig: BalancerSdkConfig; dotenv.config(); -const weth_usdc_pool_id = - '0x96646936b91d6b9d7d0c47c496afbf3d6ec7b6f8000200000000000000000019'; -const weth_bal_pool_id = - '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014'; - describe('pricing module', () => { before(() => { // Mainnet pool snapshot taken at block 14717479 @@ -59,94 +54,6 @@ describe('pricing module', () => { }); }); - context('spot price for pool', () => { - describe('via module', () => { - it('should fetch pools from poolDataService if no pools passed as param', async () => { - const pricing = new Pricing(sdkConfig); - const sp = await pricing.getSpotPrice( - ADDRESSES[Network.MAINNET].WETH.address, - ADDRESSES[Network.MAINNET].USDC.address, - weth_usdc_pool_id - ); - expect(sp).to.deep.eq('0.0003423365526722167'); - }); - - it('should fetch pools from poolDataService if empty pools passed as param', async () => { - const pricing = new Pricing(sdkConfig); - const sp = await pricing.getSpotPrice( - ADDRESSES[Network.MAINNET].USDC.address, - ADDRESSES[Network.MAINNET].WETH.address, - weth_usdc_pool_id, - [] - ); - expect(sp).to.deep.eq('2925.488620398681'); - }); - - it('should throw if poolDataService returns no pools', async () => { - const emptyPoolDataService = new MockPoolDataService([]); - const sorConfig: BalancerSdkSorConfig = { - tokenPriceService: 'coingecko', - poolDataService: emptyPoolDataService, - fetchOnChainBalances: false, - }; - const sdkConfig: BalancerSdkConfig = { - network: Network.MAINNET, - rpcUrl: ``, - sor: sorConfig, - }; - const balancer = new BalancerSDK(sdkConfig); - let error = null; - try { - await balancer.pricing.getSpotPrice( - ADDRESSES[Network.MAINNET].WETH.address, - ADDRESSES[Network.MAINNET].USDC.address, - weth_usdc_pool_id - ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (err: any) { - error = err.message; - } - expect(error).to.eq( - BalancerError.getMessage(BalancerErrorCode.POOL_DOESNT_EXIST) - ); - }); - - it('should throw with unsupported pool type', async () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const nonValidPool = { ...(pools_14717479[0] as any) }; - nonValidPool.poolType = 'UnsupportedPool'; - - const balancer = new BalancerSDK(sdkConfig); - let error = null; - try { - await balancer.pricing.getSpotPrice( - ADDRESSES[Network.MAINNET].WETH.address, - ADDRESSES[Network.MAINNET].BAL.address, - pools_14717479[0].id, - [nonValidPool] - ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (err: any) { - error = err.message; - } - expect(error).to.eq( - BalancerError.getMessage(BalancerErrorCode.UNSUPPORTED_POOL_TYPE) - ); - }); - }); - describe('via SDK', () => { - it('should fetch pools with no pools data param', async () => { - const balancer = new BalancerSDK(sdkConfig); - const sp = await balancer.pricing.getSpotPrice( - ADDRESSES[Network.MAINNET].WETH.address, - ADDRESSES[Network.MAINNET].BAL.address, - weth_bal_pool_id - ); - expect(sp).to.deep.eq('0.004981212133448337'); - }); - }); - }); - context('spot price without pool - finds most liquid path', () => { describe('via module', () => { it('should throw for pair with no liquidity', async () => { @@ -192,53 +99,5 @@ describe('pricing module', () => { expect(sp).to.deep.eq('0.0003423365526722167'); }); }); - - describe('stable pool', () => { - it('should fetch correct sp', async () => { - const balancer = new BalancerSDK(sdkConfig); - const sp = await balancer.pricing.getSpotPrice( - ADDRESSES[Network.MAINNET].DAI.address, - ADDRESSES[Network.MAINNET].USDC.address, - '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063' - ); - expect(sp).to.deep.eq('1.000051911328148725'); - }); - }); - - describe('metastable pool', () => { - it('should fetch correct sp', async () => { - const balancer = new BalancerSDK(sdkConfig); - const sp = await balancer.pricing.getSpotPrice( - ADDRESSES[Network.MAINNET].WETH.address, - ADDRESSES[Network.MAINNET].wSTETH.address, - '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080' - ); - expect(sp).to.deep.eq('1.070497605163895290828158545877174735'); - }); - }); - - describe('phantomstable pool', () => { - it('should fetch correct sp', async () => { - const balancer = new BalancerSDK(sdkConfig); - const sp = await balancer.pricing.getSpotPrice( - ADDRESSES[Network.MAINNET].bbausd.address, - ADDRESSES[Network.MAINNET].bbausdc.address, - '0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe' - ); - expect(sp).to.deep.eq('0.997873677414938406552928560423740375'); - }); - }); - - describe('linear pool', () => { - it('should fetch correct sp', async () => { - const balancer = new BalancerSDK(sdkConfig); - const sp = await balancer.pricing.getSpotPrice( - ADDRESSES[Network.MAINNET].USDC.address, - ADDRESSES[Network.MAINNET].bbausdc.address, - '0x9210f1204b5a24742eba12f710636d76240df3d00000000000000000000000fc' - ); - expect(sp).to.deep.eq('1.008078200925769181'); - }); - }); }); }); diff --git a/balancer-js/src/modules/pricing/pricing.module.ts b/balancer-js/src/modules/pricing/pricing.module.ts index 7fea92ef7..77119d611 100644 --- a/balancer-js/src/modules/pricing/pricing.module.ts +++ b/balancer-js/src/modules/pricing/pricing.module.ts @@ -1,5 +1,5 @@ import { Swaps } from '@/modules/swaps/swaps.module'; -import { BalancerSdkConfig, PoolType } from '@/types'; +import { BalancerSdkConfig } from '@/types'; import { SubgraphPoolBase, ZERO, @@ -39,17 +39,15 @@ export class Pricing { } /** - * Calculates Spot Price for a token pair - for specific pool if ID otherwise finds most liquid path and uses this as reference SP. + * Calculates Spot Price for a token pair - finds most liquid path and uses this as reference SP. * @param { string } tokenIn Token in address. * @param { string } tokenOut Token out address. - * @param { string } poolId Optional - if specified this pool will be used for SP calculation. * @param { SubgraphPoolBase[] } pools Optional - Pool data. Will be fetched via dataProvider if not supplied. * @returns { string } Spot price. */ async getSpotPrice( tokenIn: string, tokenOut: string, - poolId = '', pools: SubgraphPoolBase[] = [] ): Promise { // If pools list isn't supplied fetch it from swaps data provider @@ -58,34 +56,19 @@ export class Pricing { pools = this.getPools(); } - // If a poolId isn't specified we find the path for the pair with the highest liquidity and use this as the ref SP - if (poolId === '') { - const poolsDict = parseToPoolsDict(pools, 0); - // This creates all paths for tokenIn>Out ordered by liquidity - const paths = this.swaps.sor.routeProposer.getCandidatePathsFromDict( - tokenIn, - tokenOut, - 0, - poolsDict, - 4 - ); + // We find the path for the pair with the highest liquidity and use this as the ref SP + const poolsDict = parseToPoolsDict(pools, 0); + // This creates all paths for tokenIn>Out ordered by liquidity + const paths = this.swaps.sor.routeProposer.getCandidatePathsFromDict( + tokenIn, + tokenOut, + 0, + poolsDict, + 4 + ); - if (paths.length === 0) - throw new BalancerError(BalancerErrorCode.UNSUPPORTED_PAIR); - return getSpotPriceAfterSwapForPath(paths[0], 0, ZERO).toString(); - } else { - // Find pool of interest from pools list - const poolData = pools.find( - (p) => p.id.toLowerCase() === poolId.toLowerCase() - ); - if (!poolData) - throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); - const pool = Pools.from(poolData.poolType as PoolType); - return pool.spotPriceCalculator.calcPoolSpotPrice( - tokenIn, - tokenOut, - poolData - ); - } + if (paths.length === 0) + throw new BalancerError(BalancerErrorCode.UNSUPPORTED_PAIR); + return getSpotPriceAfterSwapForPath(paths[0], 0, ZERO).toString(); } } diff --git a/balancer-js/src/types.ts b/balancer-js/src/types.ts index 8df0ce07d..6597e4bbb 100644 --- a/balancer-js/src/types.ts +++ b/balancer-js/src/types.ts @@ -200,6 +200,7 @@ export interface Pool { symbol?: string; swapEnabled: boolean; amp?: string; + totalWeight: string; } export interface PoolModel extends Pool { @@ -224,4 +225,5 @@ export interface PoolModel extends Pool { amountsOut: string[], slippage: string ) => ExitPoolAttributes; + calcSpotPrice: (tokenIn: string, tokenOut: string) => string; } From 3326b77c55d26c02ee19905b12b17aa4ccfef662 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 31 Aug 2022 09:28:43 +0100 Subject: [PATCH 13/65] Add Stable spot price. --- .../concerns/stable/spotPrice.concern.ts | 13 ++-- .../concerns/stable/spotPrice.spec.ts | 48 ++++++++++++ .../concerns/weighted/spotPrice.concern.ts | 74 ------------------- .../concerns/weighted/spotPrice.spec.ts | 4 +- 4 files changed, 55 insertions(+), 84 deletions(-) create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/stable/spotPrice.spec.ts diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/spotPrice.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/spotPrice.concern.ts index de55a6c35..ebd704b99 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/spotPrice.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/spotPrice.concern.ts @@ -1,15 +1,12 @@ import { SpotPriceConcern } from '../types'; import { SubgraphPoolBase, StablePool, ZERO } from '@balancer-labs/sor'; +import { Pool } from '@/types'; export class StablePoolSpotPrice implements SpotPriceConcern { - calcPoolSpotPrice( - tokenIn: string, - tokenOut: string, - pool: SubgraphPoolBase - ): string { - const poolClass = StablePool.fromPool(pool); - const poolPairData = poolClass.parsePoolPairData(tokenIn, tokenOut); - return poolClass + calcPoolSpotPrice(tokenIn: string, tokenOut: string, pool: Pool): string { + const stablePool = StablePool.fromPool(pool as SubgraphPoolBase); + const poolPairData = stablePool.parsePoolPairData(tokenIn, tokenOut); + return stablePool ._spotPriceAfterSwapExactTokenInForTokenOut(poolPairData, ZERO) .toString(); } diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/spotPrice.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/spotPrice.spec.ts new file mode 100644 index 000000000..6dfb9a6dd --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/spotPrice.spec.ts @@ -0,0 +1,48 @@ +import dotenv from 'dotenv'; +import { expect } from 'chai'; +import pools_14717479 from '@/test/lib/pools_14717479.json'; +import { StaticPoolRepository } from '@/modules/data'; +import { PoolsProvider } from '@/modules/pools/provider'; +import { PoolModel, Pool } from '@/types'; +import { Network } from '@/.'; +import { setupPool } from '@/test/lib/utils'; +import { StablePoolSpotPrice } from './spotPrice.concern'; +import { ADDRESSES } from '@/test/lib/constants'; + +dotenv.config(); + +const network = Network.MAINNET; +const rpcUrl = 'http://127.0.0.1:8545'; + +const spotPriceCalc = new StablePoolSpotPrice(); +const stable_pool_id = + '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063'; + +describe('stable pool spot price', () => { + let pool: PoolModel | undefined; + + // Setup chain + before(async function () { + const sdkConfig = { + network, + rpcUrl, + }; + // Using a static repository to make test consistent over time + const poolsProvider = new PoolsProvider( + sdkConfig, + new StaticPoolRepository(pools_14717479 as Pool[]) + ); + pool = await setupPool(poolsProvider, stable_pool_id); + }); + + context('calcPoolSpotPrice', () => { + it('should calculate spot price for pair', () => { + const spotPrice = spotPriceCalc.calcPoolSpotPrice( + ADDRESSES[network].DAI.address, + ADDRESSES[network].USDC.address, + pool as PoolModel + ); + expect(spotPrice).to.eq('1.000051911328148725'); + }); + }); +}); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.concern.ts index 4e25d0c3c..6c0580a13 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.concern.ts @@ -4,80 +4,6 @@ import { Pool } from '@/types'; export class WeightedPoolSpotPrice implements SpotPriceConcern { calcPoolSpotPrice(tokenIn: string, tokenOut: string, pool: Pool): string { - /* - id: string; - address: string; - poolType: PoolType; - swapFee: string; - swapEnabled: boolean; - totalShares: string; - tokensList: string[]; - amp?: string; - - owner?: string; - factory?: string; - tokens: PoolToken[]; - tokenAddresses?: string[]; - totalLiquidity?: string; - totalSwapFee?: string; - totalSwapVolume?: string; - onchain?: OnchainPoolData; - createTime?: number; - mainTokens?: string[]; - wrappedTokens?: string[]; - unwrappedTokens?: string[]; - isNew?: boolean; - volumeSnapshot?: string; - feesSnapshot?: string; - boost?: string; - symbol?: string; - } - interface SubgraphPoolBase { - id: string; - address: string; - poolType: string; - swapFee: string; - swapEnabled: boolean; - totalShares: string; - tokensList: string[]; - amp?: string; - - totalWeight?: string; - expiryTime?: number; - unitSeconds?: number; - principalToken?: string; - baseToken?: string; - mainIndex?: number; - wrappedIndex?: number; - lowerTarget?: string; - upperTarget?: string; - sqrtAlpha?: string; - sqrtBeta?: string; - root3Alpha?: string; - tokens: SubgraphToken[]; - } - - declare type SubgraphToken = { - address: string; - balance: string; - decimals: number; - priceRate: string; - weight: string | null; - }; - - export interface Token { - address: string; - decimals?: number; - symbol?: string; - price?: Price; - } - - export interface PoolToken extends Token { - balance: string; - priceRate?: string; - weight?: string | null; - } - */ const weightedPool = WeightedPool.fromPool(pool as SubgraphPoolBase); const poolPairData = weightedPool.parsePoolPairData(tokenIn, tokenOut); return weightedPool diff --git a/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.spec.ts index fc47f1f28..64c7ca4ce 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.spec.ts @@ -35,8 +35,8 @@ describe('weighted pool spot price', () => { pool = await setupPool(poolsProvider, weth_bal_pool_id); }); - context('bpt zero price impact', () => { - it('two token pool', () => { + context('calcPoolSpotPrice', () => { + it('should calculate spot price for pair', () => { const spotPrice = spotPriceCalc.calcPoolSpotPrice( ADDRESSES[network].WETH.address, ADDRESSES[network].BAL.address, From 80dd80d1e85e06c9e8e5213ccb5580b6032042e9 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 31 Aug 2022 09:32:09 +0100 Subject: [PATCH 14/65] Add MetaStable spot price. --- .../concerns/metaStable/spotPrice.concern.ts | 13 ++--- .../concerns/metaStable/spotPrice.spec.ts | 48 +++++++++++++++++++ 2 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/metaStable/spotPrice.spec.ts diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/spotPrice.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/spotPrice.concern.ts index 0e8eae8e7..f66751037 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/spotPrice.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/spotPrice.concern.ts @@ -1,15 +1,12 @@ import { SpotPriceConcern } from '../types'; import { SubgraphPoolBase, MetaStablePool, ZERO } from '@balancer-labs/sor'; +import { Pool } from '@/types'; export class MetaStablePoolSpotPrice implements SpotPriceConcern { - calcPoolSpotPrice( - tokenIn: string, - tokenOut: string, - pool: SubgraphPoolBase - ): string { - const poolClass = MetaStablePool.fromPool(pool); - const poolPairData = poolClass.parsePoolPairData(tokenIn, tokenOut); - return poolClass + calcPoolSpotPrice(tokenIn: string, tokenOut: string, pool: Pool): string { + const metaStablePool = MetaStablePool.fromPool(pool as SubgraphPoolBase); + const poolPairData = metaStablePool.parsePoolPairData(tokenIn, tokenOut); + return metaStablePool ._spotPriceAfterSwapExactTokenInForTokenOut(poolPairData, ZERO) .toString(); } diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/spotPrice.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/spotPrice.spec.ts new file mode 100644 index 000000000..490651ddc --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/spotPrice.spec.ts @@ -0,0 +1,48 @@ +import dotenv from 'dotenv'; +import { expect } from 'chai'; +import pools_14717479 from '@/test/lib/pools_14717479.json'; +import { StaticPoolRepository } from '@/modules/data'; +import { PoolsProvider } from '@/modules/pools/provider'; +import { PoolModel, Pool } from '@/types'; +import { Network } from '@/.'; +import { setupPool } from '@/test/lib/utils'; +import { MetaStablePoolSpotPrice } from './spotPrice.concern'; +import { ADDRESSES } from '@/test/lib/constants'; + +dotenv.config(); + +const network = Network.MAINNET; +const rpcUrl = 'http://127.0.0.1:8545'; + +const spotPriceCalc = new MetaStablePoolSpotPrice(); +const metaStableId = + '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080'; + +describe('metaStable pool spot price', () => { + let pool: PoolModel | undefined; + + // Setup chain + before(async function () { + const sdkConfig = { + network, + rpcUrl, + }; + // Using a static repository to make test consistent over time + const poolsProvider = new PoolsProvider( + sdkConfig, + new StaticPoolRepository(pools_14717479 as Pool[]) + ); + pool = await setupPool(poolsProvider, metaStableId); + }); + + context('calcPoolSpotPrice', () => { + it('should calculate spot price for pair', () => { + const spotPrice = spotPriceCalc.calcPoolSpotPrice( + ADDRESSES[network].WETH.address, + ADDRESSES[network].wSTETH.address, + pool as PoolModel + ); + expect(spotPrice).to.eq('1.070497605163895290828158545877174735'); + }); + }); +}); From 226cedf0b652fbe71d0a3075ae7eedaddae07a3d Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 31 Aug 2022 09:38:37 +0100 Subject: [PATCH 15/65] Add StablePhantom spot price. --- .../stablePhantom/spotPrice.concern.ts | 15 +++--- .../concerns/stablePhantom/spotPrice.spec.ts | 48 +++++++++++++++++++ .../pools/pool-types/stablePhantom.module.ts | 4 +- 3 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/spotPrice.spec.ts diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/spotPrice.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/spotPrice.concern.ts index cfc7048a1..a71eaf430 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/spotPrice.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/spotPrice.concern.ts @@ -1,15 +1,12 @@ import { SpotPriceConcern } from '../types'; import { SubgraphPoolBase, PhantomStablePool, ZERO } from '@balancer-labs/sor'; +import { Pool } from '@/types'; -export class StablePhantomPoolSpotPrice implements SpotPriceConcern { - calcPoolSpotPrice( - tokenIn: string, - tokenOut: string, - pool: SubgraphPoolBase - ): string { - const poolClass = PhantomStablePool.fromPool(pool); - const poolPairData = poolClass.parsePoolPairData(tokenIn, tokenOut); - return poolClass +export class PhantomStablePoolSpotPrice implements SpotPriceConcern { + calcPoolSpotPrice(tokenIn: string, tokenOut: string, pool: Pool): string { + const metaStablePool = PhantomStablePool.fromPool(pool as SubgraphPoolBase); + const poolPairData = metaStablePool.parsePoolPairData(tokenIn, tokenOut); + return metaStablePool ._spotPriceAfterSwapExactTokenInForTokenOut(poolPairData, ZERO) .toString(); } diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/spotPrice.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/spotPrice.spec.ts new file mode 100644 index 000000000..9de3d9b6a --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/spotPrice.spec.ts @@ -0,0 +1,48 @@ +import dotenv from 'dotenv'; +import { expect } from 'chai'; +import pools_14717479 from '@/test/lib/pools_14717479.json'; +import { StaticPoolRepository } from '@/modules/data'; +import { PoolsProvider } from '@/modules/pools/provider'; +import { PoolModel, Pool } from '@/types'; +import { Network } from '@/.'; +import { setupPool } from '@/test/lib/utils'; +import { PhantomStablePoolSpotPrice } from './spotPrice.concern'; +import { ADDRESSES } from '@/test/lib/constants'; + +dotenv.config(); + +const network = Network.MAINNET; +const rpcUrl = 'http://127.0.0.1:8545'; + +const spotPriceCalc = new PhantomStablePoolSpotPrice(); +const phantomStableId = + '0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe'; + +describe('phantomStable pool spot price', () => { + let pool: PoolModel | undefined; + + // Setup chain + before(async function () { + const sdkConfig = { + network, + rpcUrl, + }; + // Using a static repository to make test consistent over time + const poolsProvider = new PoolsProvider( + sdkConfig, + new StaticPoolRepository(pools_14717479 as Pool[]) + ); + pool = await setupPool(poolsProvider, phantomStableId); + }); + + context('calcPoolSpotPrice', () => { + it('should calculate spot price for pair', () => { + const spotPrice = spotPriceCalc.calcPoolSpotPrice( + ADDRESSES[network].bbausd.address, + ADDRESSES[network].bbausdc.address, + pool as PoolModel + ); + expect(spotPrice).to.eq('0.997873677414938406552928560423740375'); + }); + }); +}); diff --git a/balancer-js/src/modules/pools/pool-types/stablePhantom.module.ts b/balancer-js/src/modules/pools/pool-types/stablePhantom.module.ts index 55d91dff0..5e63ea6ae 100644 --- a/balancer-js/src/modules/pools/pool-types/stablePhantom.module.ts +++ b/balancer-js/src/modules/pools/pool-types/stablePhantom.module.ts @@ -1,7 +1,7 @@ import { StablePhantomPoolExit } from './concerns/stablePhantom/exit.concern'; import { StablePhantomPoolJoin } from './concerns/stablePhantom/join.concern'; import { StablePhantomPoolLiquidity } from './concerns/stablePhantom/liquidity.concern'; -import { StablePhantomPoolSpotPrice } from './concerns/stablePhantom/spotPrice.concern'; +import { PhantomStablePoolSpotPrice } from './concerns/stablePhantom/spotPrice.concern'; import { StablePhantomPriceImpact } from './concerns/stablePhantom/priceImpact.concern'; import { PoolType } from './pool-type.interface'; import { @@ -17,7 +17,7 @@ export class StablePhantom implements PoolType { public exit: ExitConcern = new StablePhantomPoolExit(), public join: JoinConcern = new StablePhantomPoolJoin(), public liquidity: LiquidityConcern = new StablePhantomPoolLiquidity(), - public spotPriceCalculator: SpotPriceConcern = new StablePhantomPoolSpotPrice(), + public spotPriceCalculator: SpotPriceConcern = new PhantomStablePoolSpotPrice(), public priceImpactCalculator: PriceImpactConcern = new StablePhantomPriceImpact() ) {} } From b0de800fcc97d0e21341d7ba131bb968a4ccb80c Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 31 Aug 2022 09:43:08 +0100 Subject: [PATCH 16/65] Add Linear spot price. --- .../concerns/linear/spotPrice.concern.ts | 13 ++--- .../concerns/linear/spotPrice.spec.ts | 48 +++++++++++++++++++ 2 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/linear/spotPrice.spec.ts diff --git a/balancer-js/src/modules/pools/pool-types/concerns/linear/spotPrice.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/linear/spotPrice.concern.ts index b34d75f92..0848123db 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/linear/spotPrice.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/linear/spotPrice.concern.ts @@ -1,15 +1,12 @@ import { SpotPriceConcern } from '../types'; import { SubgraphPoolBase, LinearPool, ZERO } from '@balancer-labs/sor'; +import { Pool } from '@/types'; export class LinearPoolSpotPrice implements SpotPriceConcern { - calcPoolSpotPrice( - tokenIn: string, - tokenOut: string, - pool: SubgraphPoolBase - ): string { - const poolClass = LinearPool.fromPool(pool); - const poolPairData = poolClass.parsePoolPairData(tokenIn, tokenOut); - return poolClass + calcPoolSpotPrice(tokenIn: string, tokenOut: string, pool: Pool): string { + const linearPool = LinearPool.fromPool(pool as SubgraphPoolBase); + const poolPairData = linearPool.parsePoolPairData(tokenIn, tokenOut); + return linearPool ._spotPriceAfterSwapExactTokenInForTokenOut(poolPairData, ZERO) .toString(); } diff --git a/balancer-js/src/modules/pools/pool-types/concerns/linear/spotPrice.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/linear/spotPrice.spec.ts new file mode 100644 index 000000000..a9ef47984 --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/linear/spotPrice.spec.ts @@ -0,0 +1,48 @@ +import dotenv from 'dotenv'; +import { expect } from 'chai'; +import pools_14717479 from '@/test/lib/pools_14717479.json'; +import { StaticPoolRepository } from '@/modules/data'; +import { PoolsProvider } from '@/modules/pools/provider'; +import { PoolModel, Pool } from '@/types'; +import { Network } from '@/.'; +import { setupPool } from '@/test/lib/utils'; +import { LinearPoolSpotPrice } from './spotPrice.concern'; +import { ADDRESSES } from '@/test/lib/constants'; + +dotenv.config(); + +const network = Network.MAINNET; +const rpcUrl = 'http://127.0.0.1:8545'; + +const spotPriceCalc = new LinearPoolSpotPrice(); +const linearId = + '0x9210f1204b5a24742eba12f710636d76240df3d00000000000000000000000fc'; + +describe('Linear pool spot price', () => { + let pool: PoolModel | undefined; + + // Setup chain + before(async function () { + const sdkConfig = { + network, + rpcUrl, + }; + // Using a static repository to make test consistent over time + const poolsProvider = new PoolsProvider( + sdkConfig, + new StaticPoolRepository(pools_14717479 as Pool[]) + ); + pool = await setupPool(poolsProvider, linearId); + }); + + context('calcPoolSpotPrice', () => { + it('should calculate spot price for pair', () => { + const spotPrice = spotPriceCalc.calcPoolSpotPrice( + ADDRESSES[network].USDC.address, + ADDRESSES[network].bbausdc.address, + pool as PoolModel + ); + expect(spotPrice).to.eq('1.008078200925769181'); + }); + }); +}); From 40fa72b7ee894a33728f2789493ace538d775612 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 31 Aug 2022 09:53:26 +0100 Subject: [PATCH 17/65] Update example and docs. --- balancer-js/README.md | 4 +-- balancer-js/examples/spotPrice.ts | 51 ++++++++++++++++++++++--------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/balancer-js/README.md b/balancer-js/README.md index be519a890..f582fc8c3 100644 --- a/balancer-js/README.md +++ b/balancer-js/README.md @@ -228,11 +228,10 @@ const pricing = new Pricing(sdkConfig); ### #getSpotPrice -Calculates Spot Price for a token pair - for specific pool if ID otherwise finds most liquid path and uses this as reference SP. +Calculates Spot Price for a token pair - finds most liquid path and uses this as reference SP. @param { string } tokenIn Token in address. @param { string } tokenOut Token out address. -@param { string } poolId Optional - if specified this pool will be used for SP calculation. @param { SubgraphPoolBase[] } pools Optional - Pool data. Will be fetched via dataProvider if not supplied. @returns { string } Spot price. @@ -240,7 +239,6 @@ Calculates Spot Price for a token pair - for specific pool if ID otherwise finds async getSpotPrice( tokenIn: string, tokenOut: string, - poolId = '', pools: SubgraphPoolBase[] = [] ): Promise ``` diff --git a/balancer-js/examples/spotPrice.ts b/balancer-js/examples/spotPrice.ts index 31621f04c..02f80306f 100644 --- a/balancer-js/examples/spotPrice.ts +++ b/balancer-js/examples/spotPrice.ts @@ -1,44 +1,67 @@ import dotenv from 'dotenv'; -import { BalancerSDK, Network, BalancerSdkConfig } from '../src/index'; +import { BalancerSDK, Network, BalancerSdkConfig, PoolModel, BalancerError, BalancerErrorCode } from '../src/index'; import { ADDRESSES } from '../src/test/lib/constants'; dotenv.config(); +const network = Network.MAINNET; +const config: BalancerSdkConfig = { + network, + rpcUrl: `https://mainnet.infura.io/v3/${process.env.INFURA}`, +}; + +const balancer = new BalancerSDK(config); /* -Example showing how to use SDK to get spot price for a pair. +Uses SDK to find spot price for pair in specific pool. */ -async function getSpotPrice() { - const network = Network.MAINNET; - const config: BalancerSdkConfig = { - network, - rpcUrl: `https://mainnet.infura.io/v3/${process.env.INFURA}`, - }; - - const balancer = new BalancerSDK(config); +async function getSpotPricePool() { const wethDaiPoolId = '0x0b09dea16768f0799065c475be02919503cb2a3500020000000000000000001a'; - // This will fetch pools information using data provider + const daiWethPool: PoolModel | undefined = await balancer.poolsProvider.find(wethDaiPoolId); + if (!daiWethPool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); + const spotPriceEthDai = await balancer.pricing.getSpotPrice( ADDRESSES[network].DAI.address, ADDRESSES[network].WETH.address, - wethDaiPoolId ); console.log(spotPriceEthDai.toString()); const balDaiPoolId = '0x4626d81b3a1711beb79f4cecff2413886d461677000200000000000000000011'; + const balDaiPool: PoolModel | undefined = await balancer.poolsProvider.find(balDaiPoolId); + if (!balDaiPool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); + + const spotPriceBalDai = await balDaiPool.calcSpotPrice( + ADDRESSES[network].DAI.address, + ADDRESSES[network].BAL.address, + ); + console.log(spotPriceBalDai.toString()); +} + + +/* +Uses SDK to find most liquid path for a pair and calculate spot price. +*/ +async function getSpotPriceMostLiquid() { + // This will fetch pools information using data provider + const spotPriceEthDai = await balancer.pricing.getSpotPrice( + ADDRESSES[network].DAI.address, + ADDRESSES[network].WETH.address, + ); + console.log(spotPriceEthDai.toString()); + // Reuses previously fetched pools data const pools = balancer.pricing.getPools(); const spotPriceBalDai = await balancer.pricing.getSpotPrice( ADDRESSES[network].DAI.address, ADDRESSES[network].BAL.address, - balDaiPoolId, pools ); console.log(spotPriceBalDai.toString()); } // yarn examples:run ./examples/spotPrice.ts -getSpotPrice(); +getSpotPricePool(); +getSpotPriceMostLiquid(); From 287d69261f4ab3234af4e361fe55047d3f6708c4 Mon Sep 17 00:00:00 2001 From: bronco Date: Wed, 31 Aug 2022 14:18:29 +0200 Subject: [PATCH 18/65] generated subgraph updates --- .../modules/subgraph/generated/balancer-subgraph-types.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/balancer-js/src/modules/subgraph/generated/balancer-subgraph-types.ts b/balancer-js/src/modules/subgraph/generated/balancer-subgraph-types.ts index b67e4a176..b6958f7d1 100644 --- a/balancer-js/src/modules/subgraph/generated/balancer-subgraph-types.ts +++ b/balancer-js/src/modules/subgraph/generated/balancer-subgraph-types.ts @@ -3550,7 +3550,7 @@ export type PoolsQueryVariables = Exact<{ }>; -export type PoolsQuery = { __typename?: 'Query', pool0: Array<{ __typename?: 'Pool', id: string, address: string, poolType?: string | null, symbol?: string | null, name?: string | null, swapFee: string, totalWeight?: string | null, totalSwapVolume: string, totalSwapFee: string, totalLiquidity: string, totalShares: string, swapsCount: string, holdersCount: string, tokensList: Array, amp?: string | null, expiryTime?: string | null, unitSeconds?: string | null, principalToken?: string | null, baseToken?: string | null, swapEnabled: boolean, wrappedIndex?: number | null, mainIndex?: number | null, lowerTarget?: string | null, upperTarget?: string | null, sqrtAlpha?: string | null, sqrtBeta?: string | null, root3Alpha?: string | null, tokens?: Array<{ __typename?: 'PoolToken', id: string, symbol: string, name: string, decimals: number, address: string, balance: string, managedBalance: string, weight?: string | null, priceRate: string }> | null }>, pool1000: Array<{ __typename?: 'Pool', id: string, address: string, poolType?: string | null, symbol?: string | null, name?: string | null, swapFee: string, totalWeight?: string | null, totalSwapVolume: string, totalSwapFee: string, totalLiquidity: string, totalShares: string, swapsCount: string, holdersCount: string, tokensList: Array, amp?: string | null, expiryTime?: string | null, unitSeconds?: string | null, principalToken?: string | null, baseToken?: string | null, swapEnabled: boolean, wrappedIndex?: number | null, mainIndex?: number | null, lowerTarget?: string | null, upperTarget?: string | null, sqrtAlpha?: string | null, sqrtBeta?: string | null, root3Alpha?: string | null, tokens?: Array<{ __typename?: 'PoolToken', id: string, symbol: string, name: string, decimals: number, address: string, balance: string, managedBalance: string, weight?: string | null, priceRate: string }> | null }> }; +export type PoolsQuery = { __typename?: 'Query', pool0: Array<{ __typename?: 'Pool', id: string, address: string, poolType?: string | null, symbol?: string | null, name?: string | null, swapFee: string, totalWeight?: string | null, totalSwapVolume: string, totalSwapFee: string, totalLiquidity: string, totalShares: string, swapsCount: string, holdersCount: string, tokensList: Array, amp?: string | null, expiryTime?: string | null, unitSeconds?: string | null, createTime: number, principalToken?: string | null, baseToken?: string | null, swapEnabled: boolean, wrappedIndex?: number | null, mainIndex?: number | null, lowerTarget?: string | null, upperTarget?: string | null, sqrtAlpha?: string | null, sqrtBeta?: string | null, root3Alpha?: string | null, tokens?: Array<{ __typename?: 'PoolToken', id: string, symbol: string, name: string, decimals: number, address: string, balance: string, managedBalance: string, weight?: string | null, priceRate: string }> | null }>, pool1000: Array<{ __typename?: 'Pool', id: string, address: string, poolType?: string | null, symbol?: string | null, name?: string | null, swapFee: string, totalWeight?: string | null, totalSwapVolume: string, totalSwapFee: string, totalLiquidity: string, totalShares: string, swapsCount: string, holdersCount: string, tokensList: Array, amp?: string | null, expiryTime?: string | null, unitSeconds?: string | null, createTime: number, principalToken?: string | null, baseToken?: string | null, swapEnabled: boolean, wrappedIndex?: number | null, mainIndex?: number | null, lowerTarget?: string | null, upperTarget?: string | null, sqrtAlpha?: string | null, sqrtBeta?: string | null, root3Alpha?: string | null, tokens?: Array<{ __typename?: 'PoolToken', id: string, symbol: string, name: string, decimals: number, address: string, balance: string, managedBalance: string, weight?: string | null, priceRate: string }> | null }> }; export type PoolQueryVariables = Exact<{ id: Scalars['ID']; @@ -3558,7 +3558,7 @@ export type PoolQueryVariables = Exact<{ }>; -export type PoolQuery = { __typename?: 'Query', pool?: { __typename?: 'Pool', id: string, address: string, poolType?: string | null, symbol?: string | null, name?: string | null, swapFee: string, totalWeight?: string | null, totalSwapVolume: string, totalSwapFee: string, totalLiquidity: string, totalShares: string, swapsCount: string, holdersCount: string, tokensList: Array, amp?: string | null, expiryTime?: string | null, unitSeconds?: string | null, principalToken?: string | null, baseToken?: string | null, swapEnabled: boolean, wrappedIndex?: number | null, mainIndex?: number | null, lowerTarget?: string | null, upperTarget?: string | null, sqrtAlpha?: string | null, sqrtBeta?: string | null, root3Alpha?: string | null, tokens?: Array<{ __typename?: 'PoolToken', id: string, symbol: string, name: string, decimals: number, address: string, balance: string, managedBalance: string, weight?: string | null, priceRate: string }> | null } | null }; +export type PoolQuery = { __typename?: 'Query', pool?: { __typename?: 'Pool', id: string, address: string, poolType?: string | null, symbol?: string | null, name?: string | null, swapFee: string, totalWeight?: string | null, totalSwapVolume: string, totalSwapFee: string, totalLiquidity: string, totalShares: string, swapsCount: string, holdersCount: string, tokensList: Array, amp?: string | null, expiryTime?: string | null, unitSeconds?: string | null, createTime: number, principalToken?: string | null, baseToken?: string | null, swapEnabled: boolean, wrappedIndex?: number | null, mainIndex?: number | null, lowerTarget?: string | null, upperTarget?: string | null, sqrtAlpha?: string | null, sqrtBeta?: string | null, root3Alpha?: string | null, tokens?: Array<{ __typename?: 'PoolToken', id: string, symbol: string, name: string, decimals: number, address: string, balance: string, managedBalance: string, weight?: string | null, priceRate: string }> | null } | null }; export type PoolsWithoutLinearQueryVariables = Exact<{ skip?: InputMaybe; @@ -3580,7 +3580,7 @@ export type PoolWithoutLinearQueryVariables = Exact<{ export type PoolWithoutLinearQuery = { __typename?: 'Query', pool?: { __typename?: 'Pool', id: string, address: string, poolType?: string | null, symbol?: string | null, name?: string | null, swapFee: string, totalWeight?: string | null, totalSwapVolume: string, totalSwapFee: string, totalLiquidity: string, totalShares: string, swapsCount: string, holdersCount: string, tokensList: Array, amp?: string | null, expiryTime?: string | null, unitSeconds?: string | null, principalToken?: string | null, baseToken?: string | null, swapEnabled: boolean, tokens?: Array<{ __typename?: 'PoolToken', id: string, symbol: string, name: string, decimals: number, address: string, balance: string, managedBalance: string, weight?: string | null, priceRate: string }> | null } | null }; -export type SubgraphPoolFragment = { __typename?: 'Pool', id: string, address: string, poolType?: string | null, symbol?: string | null, name?: string | null, swapFee: string, totalWeight?: string | null, totalSwapVolume: string, totalSwapFee: string, totalLiquidity: string, totalShares: string, swapsCount: string, holdersCount: string, tokensList: Array, amp?: string | null, expiryTime?: string | null, unitSeconds?: string | null, principalToken?: string | null, baseToken?: string | null, swapEnabled: boolean, wrappedIndex?: number | null, mainIndex?: number | null, lowerTarget?: string | null, upperTarget?: string | null, sqrtAlpha?: string | null, sqrtBeta?: string | null, root3Alpha?: string | null, tokens?: Array<{ __typename?: 'PoolToken', id: string, symbol: string, name: string, decimals: number, address: string, balance: string, managedBalance: string, weight?: string | null, priceRate: string }> | null }; +export type SubgraphPoolFragment = { __typename?: 'Pool', id: string, address: string, poolType?: string | null, symbol?: string | null, name?: string | null, swapFee: string, totalWeight?: string | null, totalSwapVolume: string, totalSwapFee: string, totalLiquidity: string, totalShares: string, swapsCount: string, holdersCount: string, tokensList: Array, amp?: string | null, expiryTime?: string | null, unitSeconds?: string | null, createTime: number, principalToken?: string | null, baseToken?: string | null, swapEnabled: boolean, wrappedIndex?: number | null, mainIndex?: number | null, lowerTarget?: string | null, upperTarget?: string | null, sqrtAlpha?: string | null, sqrtBeta?: string | null, root3Alpha?: string | null, tokens?: Array<{ __typename?: 'PoolToken', id: string, symbol: string, name: string, decimals: number, address: string, balance: string, managedBalance: string, weight?: string | null, priceRate: string }> | null }; export type SubgraphPoolWithoutLinearFragment = { __typename?: 'Pool', id: string, address: string, poolType?: string | null, symbol?: string | null, name?: string | null, swapFee: string, totalWeight?: string | null, totalSwapVolume: string, totalSwapFee: string, totalLiquidity: string, totalShares: string, swapsCount: string, holdersCount: string, tokensList: Array, amp?: string | null, expiryTime?: string | null, unitSeconds?: string | null, principalToken?: string | null, baseToken?: string | null, swapEnabled: boolean, tokens?: Array<{ __typename?: 'PoolToken', id: string, symbol: string, name: string, decimals: number, address: string, balance: string, managedBalance: string, weight?: string | null, priceRate: string }> | null }; @@ -3733,6 +3733,7 @@ export const SubgraphPoolFragmentDoc = gql` amp expiryTime unitSeconds + createTime principalToken baseToken swapEnabled From 1fc0df2df63ddb0ed52596bf65e6fdd7662675e1 Mon Sep 17 00:00:00 2001 From: bronco Date: Thu, 23 Jun 2022 11:02:51 +0200 Subject: [PATCH 19/65] rename providers to repositories * use repositories as raw data sources and providers and domain model sources * removed uninitialized (unused) --- balancer-js/src/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/balancer-js/src/types.ts b/balancer-js/src/types.ts index 8df0ce07d..cee501ac4 100644 --- a/balancer-js/src/types.ts +++ b/balancer-js/src/types.ts @@ -7,6 +7,8 @@ import { JoinPoolAttributes, } from './modules/pools/pool-types/concerns/types'; +export * from '@/modules/data/types'; + export type Address = string; export interface BalancerSdkConfig { From 3f200eef5810a8b8ec8280d31fceae72b31e24f9 Mon Sep 17 00:00:00 2001 From: bronco Date: Sun, 17 Jul 2022 20:05:41 +0200 Subject: [PATCH 20/65] update node to v18 --- .github/workflows/balancer-js.yaml | 14 +++++++------- balancer-js/.nvmrc | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 balancer-js/.nvmrc diff --git a/.github/workflows/balancer-js.yaml b/.github/workflows/balancer-js.yaml index 1005ebdec..e76c0d2a2 100644 --- a/.github/workflows/balancer-js.yaml +++ b/.github/workflows/balancer-js.yaml @@ -20,9 +20,9 @@ jobs: steps: - uses: actions/checkout@v2 - name: Install node - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 18 - name: Cache uses: actions/cache@v2 id: cache @@ -40,9 +40,9 @@ jobs: steps: - uses: actions/checkout@v2 - name: Install node - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 18 - name: Cache uses: actions/cache@v2 id: cache @@ -62,9 +62,9 @@ jobs: steps: - uses: actions/checkout@v2 - name: Install node - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 18 - name: Cache uses: actions/cache@v2 id: cache @@ -79,7 +79,7 @@ jobs: - name: Compile run: yarn build - name: Run node in background for integration tests - run: npx hardhat node --hostname 127.0.0.1 & + run: npx hardhat node --hostname 127.0.0.1 --fork ${{ secrets.ALCHEMY_URL }} & - name: Test run: yarn test diff --git a/balancer-js/.nvmrc b/balancer-js/.nvmrc new file mode 100644 index 000000000..7acf5b088 --- /dev/null +++ b/balancer-js/.nvmrc @@ -0,0 +1 @@ +v18.3.0 From 3f50b60db9e3fc4323107236454c0d7ada07f943 Mon Sep 17 00:00:00 2001 From: bronco Date: Wed, 31 Aug 2022 13:07:13 +0200 Subject: [PATCH 21/65] package.json with 2 spaces --- balancer-js/package.json | 204 ++++++++++++++++++----------------- balancer-js/rollup.config.ts | 88 ++++++++------- 2 files changed, 146 insertions(+), 146 deletions(-) diff --git a/balancer-js/package.json b/balancer-js/package.json index bfd22dda0..64e1af165 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -1,103 +1,105 @@ { - "name": "@balancer-labs/sdk", - "version": "0.1.21", - "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", - "repository": { - "type": "git", - "url": "https://github.com/balancer-labs/balancer-sdk", - "directory": "balancer-js" - }, - "bugs": { - "url": "https://github.com/balancer-labs/balancer-sdk/issues" - }, - "main": "dist/index.js", - "module": "dist/index.esm.js", - "browser": "dist/index.umd.js", - "typings": "dist/index.d.ts", - "files": [ - "dist/" - ], - "scripts": { - "build": "rimraf dist && rollup -c", - "dev": "rollup -c -w", - "test": "ts-mocha --paths --recursive -p tsconfig.testing.json 'src/**/*.spec.ts'", - "test:only": "ts-mocha --paths --recursive -p tsconfig.testing.json", - "lint": "eslint ./src --ext .ts --max-warnings 0", - "lint:fix": "eslint ./src --ext .ts --max-warnings 0 --fix", - "subgraph:generate": "graphql-codegen --config src/modules/subgraph/codegen.yml -r dotenv/config", - "examples:run": "TS_NODE_PROJECT='tsconfig.testing.json' ts-node -r tsconfig-paths/register", - "node": "npx hardhat node --fork $(grep ALCHEMY_URL .env | cut -d '=' -f2) --fork-block-number 14828550" - }, - "devDependencies": { - "@ethersproject/abi": "^5.4.0", - "@ethersproject/abstract-signer": "^5.4.0", - "@ethersproject/address": "^5.4.0", - "@ethersproject/bignumber": "^5.4.0", - "@ethersproject/bytes": "^5.4.0", - "@ethersproject/constants": "^5.4.0", - "@ethersproject/contracts": "^5.4.0", - "@ethersproject/providers": "^5.4.5", - "@ethersproject/solidity": "^5.6.1", - "@ethersproject/wallet": "^5.5.0", - "@graphql-codegen/add": "^3.1.0", - "@graphql-codegen/cli": "^2.3.0", - "@graphql-codegen/introspection": "^2.1.0", - "@graphql-codegen/schema-ast": "^2.4.0", - "@graphql-codegen/typescript": "2.4.0", - "@graphql-codegen/typescript-document-nodes": "^2.2.0", - "@graphql-codegen/typescript-graphql-request": "^4.3.0", - "@graphql-codegen/typescript-operations": "^2.2.0", - "@graphql-codegen/typescript-resolvers": "2.4.1", - "@nomiclabs/hardhat-ethers": "^2.0.5", - "@rollup/plugin-commonjs": "^21.0.1", - "@rollup/plugin-json": "^4.1.0", - "@rollup/plugin-node-resolve": "^13.0.0", - "@rollup/plugin-typescript": "^8.2.1", - "@typechain/ethers-v5": "^7.0.1", - "@types/chai": "^4.2.12", - "@types/lodash": "^4.14.177", - "@types/mocha": "^8.0.3", - "@types/node": "^15.12.4", - "@typescript-eslint/eslint-plugin": "^4.1.1", - "@typescript-eslint/parser": "^4.1.1", - "chai": "^4.2.0", - "dotenv": "^10.0.0", - "eslint": "^7.9.0", - "eslint-plugin-mocha-no-only": "^1.1.1", - "eslint-plugin-prettier": "^3.1.4", - "ethers": "^5.0.0", - "fishery": "^2.2.2", - "hardhat": "^2.9.3", - "mocha": "^8.2.1", - "prettier": "^2.1.2", - "rimraf": "^3.0.2", - "rollup": "^2.52.8", - "rollup-plugin-dts": "^3.0.2", - "tiny-invariant": "^1.1.0", - "ts-mocha": "^9.0.2", - "ts-node": "^10.4.0", - "tsconfig-paths": "^3.12.0", - "typechain": "^5.1.1", - "typescript": "^4.0.2" - }, - "dependencies": { - "@balancer-labs/sor": "^4.0.1-beta.3", - "@balancer-labs/typechain": "^1.0.0", - "axios": "^0.24.0", - "graphql": "^15.6.1", - "graphql-request": "^3.5.0", - "lodash": "^4.17.21" - }, - "peerDependencies": { - "@ethersproject/abi": "^5.4.0", - "@ethersproject/abstract-signer": "^5.4.0", - "@ethersproject/address": "^5.4.0", - "@ethersproject/bignumber": "^5.4.0", - "@ethersproject/bytes": "^5.4.0", - "@ethersproject/constants": "^5.4.0", - "@ethersproject/contracts": "^5.4.0", - "@ethersproject/providers": "^5.4.5" - } + "name": "@balancer-labs/sdk", + "version": "0.1.21", + "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", + "repository": { + "type": "git", + "url": "https://github.com/balancer-labs/balancer-sdk", + "directory": "balancer-js" + }, + "bugs": { + "url": "https://github.com/balancer-labs/balancer-sdk/issues" + }, + "main": "dist/index.js", + "module": "dist/index.esm.js", + "browser": "dist/index.umd.js", + "typings": "dist/index.d.ts", + "files": [ + "dist/" + ], + "scripts": { + "build": "rimraf dist && rollup -c", + "dev": "rollup -c -w", + "test": "ts-mocha --paths --recursive -p tsconfig.testing.json 'src/**/*.spec.ts' --timeout 20000", + "test:only": "ts-mocha --paths --recursive -p tsconfig.testing.json --timeout 20000", + "lint": "eslint ./src --ext .ts --max-warnings 0", + "lint:fix": "eslint ./src --ext .ts --max-warnings 0 --fix", + "subgraph:generate": "graphql-codegen --config src/modules/subgraph/codegen.yml -r dotenv/config", + "examples:run": "TS_NODE_PROJECT='tsconfig.testing.json' ts-node -r tsconfig-paths/register", + "node": "npx hardhat node --fork $(grep ALCHEMY_URL .env | cut -d '=' -f2 | tail -1)" + }, + "devDependencies": { + "@ethersproject/abi": "^5.4.0", + "@ethersproject/abstract-signer": "^5.4.0", + "@ethersproject/address": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/constants": "^5.4.0", + "@ethersproject/contracts": "^5.4.0", + "@ethersproject/providers": "^5.4.5", + "@ethersproject/solidity": "^5.6.1", + "@ethersproject/units": "^5.7.0", + "@ethersproject/wallet": "^5.5.0", + "@graphql-codegen/add": "^3.1.0", + "@graphql-codegen/cli": "^2.3.0", + "@graphql-codegen/introspection": "^2.1.0", + "@graphql-codegen/schema-ast": "^2.4.0", + "@graphql-codegen/typescript": "2.4.0", + "@graphql-codegen/typescript-document-nodes": "^2.2.0", + "@graphql-codegen/typescript-graphql-request": "^4.3.0", + "@graphql-codegen/typescript-operations": "^2.2.0", + "@graphql-codegen/typescript-resolvers": "2.4.1", + "@nomiclabs/hardhat-ethers": "^2.0.5", + "@rollup/plugin-commonjs": "^21.0.1", + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^13.0.0", + "@rollup/plugin-typescript": "^8.2.1", + "@typechain/ethers-v5": "^7.0.1", + "@types/chai": "^4.2.12", + "@types/lodash": "^4.14.177", + "@types/mocha": "^8.0.3", + "@types/node": "^15.12.4", + "@typescript-eslint/eslint-plugin": "^4.1.1", + "@typescript-eslint/parser": "^4.1.1", + "chai": "^4.2.0", + "dotenv": "^10.0.0", + "eslint": "^7.9.0", + "eslint-plugin-mocha-no-only": "^1.1.1", + "eslint-plugin-prettier": "^3.1.4", + "ethers": "^5.0.0", + "fishery": "^2.2.2", + "hardhat": "^2.9.3", + "mocha": "^8.2.1", + "mockdate": "^3.0.5", + "prettier": "^2.1.2", + "rimraf": "^3.0.2", + "rollup": "^2.52.8", + "rollup-plugin-dts": "^3.0.2", + "tiny-invariant": "^1.1.0", + "ts-mocha": "^9.0.2", + "ts-node": "^10.4.0", + "tsconfig-paths": "^3.12.0", + "typechain": "^5.1.1", + "typescript": "^4.0.2" + }, + "dependencies": { + "@balancer-labs/sor": "^4.0.1-beta.3", + "@balancer-labs/typechain": "^1.0.0", + "axios": "^0.24.0", + "graphql": "^15.6.1", + "graphql-request": "^3.5.0", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "@ethersproject/abi": "^5.4.0", + "@ethersproject/abstract-signer": "^5.4.0", + "@ethersproject/address": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/constants": "^5.4.0", + "@ethersproject/contracts": "^5.4.0", + "@ethersproject/providers": "^5.4.5" + } } diff --git a/balancer-js/rollup.config.ts b/balancer-js/rollup.config.ts index 2116c2526..aff9d7f52 100644 --- a/balancer-js/rollup.config.ts +++ b/balancer-js/rollup.config.ts @@ -11,49 +11,47 @@ const external = [ ]; export default [ - { - input: 'src/index.ts', - output: [ - { - name: 'balancer-js', - file: pkg.browser, - format: 'umd', - sourcemap: true, - globals: { - '@ethersproject/abi': 'abi', - '@ethersproject/constants': 'constants', - '@ethersproject/bignumber': 'bignumber', - '@ethersproject/address': 'address', - '@ethersproject/bytes': 'bytes', - '@ethersproject/abstract-signer': 'abstractSigner', - '@ethersproject/contracts': 'contracts', - '@balancer-labs/sor': 'sor', - '@ethersproject/providers': 'providers', - 'graphql-request': 'graphqlRequest', - graphql: 'graphql', - lodash: 'lodash', - axios: 'axios', - }, - }, - { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.module, format: 'es', sourcemap: true }, - ], - plugins: [ - nodeResolve(), - json(), - commonjs(), - typescript({ - exclude: ['node_modules', '**/*.spec.ts'], - }), - ], - external, - }, - { - input: 'src/index.ts', - output: [{ file: 'dist/index.d.ts', format: 'es' }], - plugins: [ - dts(), - typescript({ exclude: ['node_modules', '**/*.spec.ts'] }), - ], - }, + { + input: 'src/index.ts', + output: [ + { + name: 'balancer-js', + file: pkg.browser, + format: 'umd', + sourcemap: true, + globals: { + '@ethersproject/abi': 'abi', + '@ethersproject/constants': 'constants', + '@ethersproject/bignumber': 'bignumber', + '@ethersproject/address': 'address', + '@ethersproject/bytes': 'bytes', + '@ethersproject/abstract-signer': 'abstractSigner', + '@ethersproject/contracts': 'contracts', + '@balancer-labs/sor': 'sor', + '@balancer-labs/typechain': 'typechain', + '@ethersproject/providers': 'providers', + 'graphql-request': 'graphqlRequest', + graphql: 'graphql', + lodash: 'lodash', + axios: 'axios', + }, + }, + { file: pkg.main, format: 'cjs', sourcemap: true }, + { file: pkg.module, format: 'es', sourcemap: true }, + ], + plugins: [ + nodeResolve(), + json(), + commonjs(), + typescript({ + exclude: ['node_modules', '**/*.spec.ts'], + }), + ], + external, + }, + { + input: 'src/index.ts', + output: [{ file: 'dist/index.d.ts', format: 'es' }], + plugins: [dts(), typescript({ exclude: ['node_modules', '**/*.spec.ts'] })], + }, ]; From 78a6f979f90f5fe41e4c598805c88290ebe29e77 Mon Sep 17 00:00:00 2001 From: bronco Date: Sun, 17 Jul 2022 20:03:03 +0200 Subject: [PATCH 22/65] prettify examples/swap.ts --- balancer-js/examples/swapLocalFork.ts | 124 +++++++++++++------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/balancer-js/examples/swapLocalFork.ts b/balancer-js/examples/swapLocalFork.ts index d888aa736..c2001940a 100644 --- a/balancer-js/examples/swapLocalFork.ts +++ b/balancer-js/examples/swapLocalFork.ts @@ -36,68 +36,68 @@ async function swap() { provider ); - const mockPoolDataService = new MockPoolDataService(onchain); - - const balancer = new BalancerSDK({ - network, - rpcUrl, - sor: { - tokenPriceService: 'coingecko', - poolDataService: mockPoolDataService, - }, - }); - - const tokenOutContract = balancer.contracts.ERC20(tokenOut, provider); - - await balancer.swaps.fetchPools(); - - const swapInfo = await balancer.swaps.findRouteGivenIn({ - // tokenIn: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // weth - tokenIn: AddressZero, // eth - tokenOut, - amount: parseFixed('1', 18), - gasPrice: parseFixed('1', 9), - maxPools: 4, - }); - - const userAddress = wallet.address; - const deadline = BigNumber.from(`${Math.ceil(Date.now() / 1000) + 60}`); // 60 seconds from now - const maxSlippage = 50; // 50 bsp = 0.5% - - const transactionAttributes = balancer.swaps.buildSwap({ - userAddress, - swapInfo, - kind: 0, - deadline, - maxSlippage, - }); - - // Extract parameters required for sendTransaction - const { to, data, value } = transactionAttributes; - - // Execution with ethers.js - try { - const balanceBefore = await tokenOutContract.balanceOf(userAddress); - - await ( - await wallet.sendTransaction({ - to, - data, - value, - }) - ).wait(); - - // check delta - const balanceAfter = await tokenOutContract.balanceOf(userAddress); - console.log( - `Amount received: ${formatFixed( - balanceAfter.sub(balanceBefore), - 8 - )} Amount expected: ${formatFixed(swapInfo.returnAmount, 8)}` - ); - } catch (err) { - console.log(err); - } + const mockPoolDataService = new MockPoolDataService(onchain); + + const balancer = new BalancerSDK({ + network, + rpcUrl, + sor: { + tokenPriceService: 'coingecko', + poolDataService: mockPoolDataService, + }, + }); + + const tokenOutContract = balancer.contracts.ERC20(tokenOut, provider); + + await balancer.swaps.fetchPools(); + + const swapInfo = await balancer.swaps.findRouteGivenIn({ + // tokenIn: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // weth + tokenIn: AddressZero, // eth + tokenOut, + amount: parseFixed('1', 18), + gasPrice: parseFixed('1', 9), + maxPools: 4, + }); + + const userAddress = wallet.address; + const deadline = BigNumber.from(`${Math.ceil(Date.now() / 1000) + 60}`); // 60 seconds from now + const maxSlippage = 50; // 50 bsp = 0.5% + + const transactionAttributes = balancer.swaps.buildSwap({ + userAddress, + swapInfo, + kind: 0, + deadline, + maxSlippage, + }); + + // Extract parameters required for sendTransaction + const { to, data, value } = transactionAttributes; + + // Execution with ethers.js + try { + const balanceBefore = await tokenOutContract.balanceOf(userAddress); + + await ( + await wallet.sendTransaction({ + to, + data, + value, + }) + ).wait(); + + // check delta + const balanceAfter = await tokenOutContract.balanceOf(userAddress); + console.log( + `Amount received: ${formatFixed( + balanceAfter.sub(balanceBefore), + 8 + )} Amount expected: ${formatFixed(swapInfo.returnAmount, 8)}` + ); + } catch (err) { + console.log(err); + } } // yarn examples:run ./examples/swapLocalFork.ts From e954585b5a4ec442ed3f9a5f8c80cbc87d74c37d Mon Sep 17 00:00:00 2001 From: bronco Date: Thu, 23 Jun 2022 11:56:30 +0200 Subject: [PATCH 23/65] adding sdk factories --- balancer-js/src/test/factories/index.ts | 3 +- .../src/test/factories/named-tokens.ts | 19 +++++++++ balancer-js/src/test/factories/sdk.ts | 40 +++++++++++++++++++ balancer-js/src/test/factories/sor.ts | 13 +----- 4 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 balancer-js/src/test/factories/named-tokens.ts create mode 100644 balancer-js/src/test/factories/sdk.ts diff --git a/balancer-js/src/test/factories/index.ts b/balancer-js/src/test/factories/index.ts index cbf064da9..cdd965e6f 100644 --- a/balancer-js/src/test/factories/index.ts +++ b/balancer-js/src/test/factories/index.ts @@ -1,5 +1,6 @@ import * as sor from './sor'; +import * as sdk from './sdk'; -const factories = { ...sor }; +const factories = { ...sor, ...sdk }; export { factories }; diff --git a/balancer-js/src/test/factories/named-tokens.ts b/balancer-js/src/test/factories/named-tokens.ts new file mode 100644 index 000000000..febe2a2a2 --- /dev/null +++ b/balancer-js/src/test/factories/named-tokens.ts @@ -0,0 +1,19 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const namedTokens: Record = { + wstETH: { + address: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', + decimals: 18, + }, + wETH: { + address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + decimals: 18, + }, + wBTC: { + address: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', + decimals: 8, + }, + BADGER: { + address: '0x3472a5a71965499acd81997a54bba8d852c6e53d', + decimals: 18, + }, +}; diff --git a/balancer-js/src/test/factories/sdk.ts b/balancer-js/src/test/factories/sdk.ts new file mode 100644 index 000000000..7133ae0ca --- /dev/null +++ b/balancer-js/src/test/factories/sdk.ts @@ -0,0 +1,40 @@ +import { Pool, PoolType, PoolToken } from '../../types'; +import { Factory } from 'fishery'; +import { namedTokens } from './named-tokens'; + +const poolTokenFactory = Factory.define(({ transientParams }) => { + const { symbol } = transientParams; + const namedToken = namedTokens[symbol]; + + return { + ...namedToken, + balance: '1', + priceRate: '1', + weight: '0.5', + }; +}); + +const poolFactory = Factory.define(({ params, afterBuild }) => { + afterBuild((pool) => { + pool.tokensList = pool.tokens.map((t) => t.address); + }); + + const tokens = params.tokens || [ + poolTokenFactory.transient({ symbol: 'wETH' }).build(), + poolTokenFactory.transient({ symbol: 'wBTC' }).build(), + ]; + + return { + id: '0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e', + address: '0xa6f548df93de924d73be7d25dc02554c6bd66db5', + poolType: PoolType.Weighted, + swapFee: '0.001', + swapEnabled: true, + tokens, + tokensList: [], + totalWeight: '1', + totalShares: '1', + }; +}); + +export { poolFactory, poolTokenFactory }; diff --git a/balancer-js/src/test/factories/sor.ts b/balancer-js/src/test/factories/sor.ts index bc2555081..3161b68d7 100644 --- a/balancer-js/src/test/factories/sor.ts +++ b/balancer-js/src/test/factories/sor.ts @@ -6,6 +6,7 @@ import { SwapV2, } from '@balancer-labs/sor'; import { BigNumber } from '@ethersproject/bignumber'; +import { namedTokens } from './named-tokens'; const swapV2 = Factory.define(() => ({ poolId: '0xe2957c36816c1033e15dd3149ddf2508c3cfe79076ce4bde6cb3ecd34d4084b4', @@ -31,18 +32,6 @@ const swapInfo = Factory.define(() => ({ returnAmountConsideringFees: BigNumber.from('1000000000000000000'), })); -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const namedTokens: Record = { - wETH: { - address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - decimals: 18, - }, - wBTC: { - address: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', - decimals: 8, - }, -}; - const subgraphToken = Factory.define(({ transientParams }) => { const { symbol } = transientParams; const namedToken = namedTokens[symbol]; From 017cdc8bc1d1eb424ece6598193eb00392a4bf49 Mon Sep 17 00:00:00 2001 From: bronco Date: Fri, 1 Jul 2022 23:55:35 +0200 Subject: [PATCH 24/65] gauges subgraph --- balancer-js/src/lib/constants/config.ts | 14 + balancer-js/src/modules/data/types.ts | 7 + .../balancer-gauges/LiquidityGauges.graphql | 38 + .../{graphql => balancer-v2}/Pools.graphql | 1 + .../{graphql => balancer-v2}/Protocol.graphql | 0 .../{graphql => balancer-v2}/Tokens.graphql | 0 .../{graphql => balancer-v2}/Users.graphql | 0 balancer-js/src/modules/subgraph/codegen.yml | 18 +- .../generated/balancer-gauges.graphql | 1373 +++++++++++++++++ .../subgraph/generated/balancer-gauges.ts | 1343 ++++++++++++++++ balancer-js/src/modules/subgraph/subgraph.ts | 11 + balancer-js/src/types.ts | 1 + 12 files changed, 2805 insertions(+), 1 deletion(-) create mode 100644 balancer-js/src/modules/data/types.ts create mode 100644 balancer-js/src/modules/subgraph/balancer-gauges/LiquidityGauges.graphql rename balancer-js/src/modules/subgraph/{graphql => balancer-v2}/Pools.graphql (99%) rename balancer-js/src/modules/subgraph/{graphql => balancer-v2}/Protocol.graphql (100%) rename balancer-js/src/modules/subgraph/{graphql => balancer-v2}/Tokens.graphql (100%) rename balancer-js/src/modules/subgraph/{graphql => balancer-v2}/Users.graphql (100%) create mode 100644 balancer-js/src/modules/subgraph/generated/balancer-gauges.graphql create mode 100644 balancer-js/src/modules/subgraph/generated/balancer-gauges.ts diff --git a/balancer-js/src/lib/constants/config.ts b/balancer-js/src/lib/constants/config.ts index d7bdeff72..5d99726dc 100644 --- a/balancer-js/src/lib/constants/config.ts +++ b/balancer-js/src/lib/constants/config.ts @@ -26,6 +26,8 @@ export const BALANCER_NETWORK_CONFIG: Record = { urls: { subgraph: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2', + gaugesSubgraph: + 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', }, pools: { wETHwstETH: { @@ -48,6 +50,8 @@ export const BALANCER_NETWORK_CONFIG: Record = { urls: { subgraph: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-polygon-v2', + gaugesSubgraph: + 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', }, pools: {}, }, @@ -65,6 +69,8 @@ export const BALANCER_NETWORK_CONFIG: Record = { urls: { subgraph: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-arbitrum-v2', + gaugesSubgraph: + 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', }, pools: {}, }, @@ -82,6 +88,8 @@ export const BALANCER_NETWORK_CONFIG: Record = { urls: { subgraph: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-kovan-v2', + gaugesSubgraph: + 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', }, pools: {}, }, @@ -98,6 +106,8 @@ export const BALANCER_NETWORK_CONFIG: Record = { }, urls: { subgraph: '', + gaugesSubgraph: + 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', }, pools: {}, }, @@ -115,6 +125,8 @@ export const BALANCER_NETWORK_CONFIG: Record = { urls: { subgraph: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-rinkeby-v2', + gaugesSubgraph: + 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', }, pools: {}, }, @@ -132,6 +144,8 @@ export const BALANCER_NETWORK_CONFIG: Record = { urls: { subgraph: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-goerli-v2', + gaugesSubgraph: + 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', }, pools: {}, }, diff --git a/balancer-js/src/modules/data/types.ts b/balancer-js/src/modules/data/types.ts new file mode 100644 index 000000000..5641936c7 --- /dev/null +++ b/balancer-js/src/modules/data/types.ts @@ -0,0 +1,7 @@ +export { LiquidityGauge } from './liquidity-gauges/provider'; + +export interface Findable { + find: (id: string) => Promise; + findBy: (attribute: P, value: string) => Promise; + where?: (attributes: P[], values: string[]) => Promise; +} diff --git a/balancer-js/src/modules/subgraph/balancer-gauges/LiquidityGauges.graphql b/balancer-js/src/modules/subgraph/balancer-gauges/LiquidityGauges.graphql new file mode 100644 index 000000000..f0740ba26 --- /dev/null +++ b/balancer-js/src/modules/subgraph/balancer-gauges/LiquidityGauges.graphql @@ -0,0 +1,38 @@ +query LiquidityGauges( + $skip: Int + $first: Int + $orderBy: LiquidityGauge_orderBy + $orderDirection: OrderDirection + $where: LiquidityGauge_filter + $block: Block_height +) { + liquidityGauges( + skip: $skip + first: $first + orderBy: $orderBy + orderDirection: $orderDirection + where: $where + block: $block + ) { + ...SubgraphLiquidityGauge + } +} + +fragment SubgraphLiquidityGauge on LiquidityGauge { + id + symbol + poolAddress + poolId + streamer + factory { + id + numGauges + } + totalSupply + tokens { + id + symbol + decimals + totalDeposited + } +} diff --git a/balancer-js/src/modules/subgraph/graphql/Pools.graphql b/balancer-js/src/modules/subgraph/balancer-v2/Pools.graphql similarity index 99% rename from balancer-js/src/modules/subgraph/graphql/Pools.graphql rename to balancer-js/src/modules/subgraph/balancer-v2/Pools.graphql index 1e9096d1f..52b3e433c 100644 --- a/balancer-js/src/modules/subgraph/graphql/Pools.graphql +++ b/balancer-js/src/modules/subgraph/balancer-v2/Pools.graphql @@ -81,6 +81,7 @@ fragment SubgraphPool on Pool { amp expiryTime unitSeconds + createTime principalToken baseToken swapEnabled diff --git a/balancer-js/src/modules/subgraph/graphql/Protocol.graphql b/balancer-js/src/modules/subgraph/balancer-v2/Protocol.graphql similarity index 100% rename from balancer-js/src/modules/subgraph/graphql/Protocol.graphql rename to balancer-js/src/modules/subgraph/balancer-v2/Protocol.graphql diff --git a/balancer-js/src/modules/subgraph/graphql/Tokens.graphql b/balancer-js/src/modules/subgraph/balancer-v2/Tokens.graphql similarity index 100% rename from balancer-js/src/modules/subgraph/graphql/Tokens.graphql rename to balancer-js/src/modules/subgraph/balancer-v2/Tokens.graphql diff --git a/balancer-js/src/modules/subgraph/graphql/Users.graphql b/balancer-js/src/modules/subgraph/balancer-v2/Users.graphql similarity index 100% rename from balancer-js/src/modules/subgraph/graphql/Users.graphql rename to balancer-js/src/modules/subgraph/balancer-v2/Users.graphql diff --git a/balancer-js/src/modules/subgraph/codegen.yml b/balancer-js/src/modules/subgraph/codegen.yml index 08dd8481b..753bc2201 100644 --- a/balancer-js/src/modules/subgraph/codegen.yml +++ b/balancer-js/src/modules/subgraph/codegen.yml @@ -2,7 +2,7 @@ overwrite: true generates: src/modules/subgraph/generated/balancer-subgraph-types.ts: schema: ${BALANCER_SUBGRAPH_URL:https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2} - documents: 'src/modules/subgraph/graphql/**/*.graphql' + documents: 'src/modules/subgraph/balancer-v2/**/*.graphql' plugins: - typescript - typescript-operations @@ -16,6 +16,22 @@ generates: schema: ${BALANCER_SUBGRAPH_URL:https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2} plugins: - schema-ast + src/modules/subgraph/generated/balancer-gauges.ts: + schema: ${BALANCER_GAUGES_URL:https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges} + documents: 'src/modules/subgraph/balancer-gauges/**/*.graphql' + plugins: + - typescript + - typescript-operations + - typescript-graphql-request + config: + scalars: + BigInt: string + Bytes: string + BigDecimal: string + src/modules/subgraph/generated/balancer-gauges.graphql: + schema: ${BALANCER_GAUGES_URL:https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges} + plugins: + - schema-ast hooks: afterOneFileWrite: - eslint --fix diff --git a/balancer-js/src/modules/subgraph/generated/balancer-gauges.graphql b/balancer-js/src/modules/subgraph/generated/balancer-gauges.graphql new file mode 100644 index 000000000..a3baa39f3 --- /dev/null +++ b/balancer-js/src/modules/subgraph/generated/balancer-gauges.graphql @@ -0,0 +1,1373 @@ +""" +creates a virtual field on the entity that may be queried but cannot be set manually through the mappings API. +""" +directive @derivedFrom(field: String!) on FIELD_DEFINITION + +""" +Marks the GraphQL type as indexable entity. Each type that should be an entity is required to be annotated with this directive. +""" +directive @entity on OBJECT + +"""Defined a Subgraph ID for an object type""" +directive @subgraphId(id: String!) on OBJECT + +scalar BigDecimal + +scalar BigInt + +input BlockChangedFilter { + number_gte: Int! +} + +input Block_height { + hash: Bytes + number: Int + number_gte: Int +} + +scalar Bytes + +enum Chain { + Arbitrum + Optimism + Polygon +} + +type Gauge { + address: Bytes! + id: ID! + type: GaugeType! +} + +type GaugeFactory { + gauges(first: Int = 100, orderBy: LiquidityGauge_orderBy, orderDirection: OrderDirection, skip: Int = 0, where: LiquidityGauge_filter): [LiquidityGauge!] + id: ID! + numGauges: Int! +} + +input GaugeFactory_filter { + """Filter for the block changed event.""" + _change_block: BlockChangedFilter + gauges_: LiquidityGauge_filter + id: ID + id_gt: ID + id_gte: ID + id_in: [ID!] + id_lt: ID + id_lte: ID + id_not: ID + id_not_in: [ID!] + numGauges: Int + numGauges_gt: Int + numGauges_gte: Int + numGauges_in: [Int!] + numGauges_lt: Int + numGauges_lte: Int + numGauges_not: Int + numGauges_not_in: [Int!] +} + +enum GaugeFactory_orderBy { + gauges + id + numGauges +} + +type GaugeShare { + balance: BigDecimal! + gauge: LiquidityGauge! + id: ID! + user: User! +} + +input GaugeShare_filter { + """Filter for the block changed event.""" + _change_block: BlockChangedFilter + balance: BigDecimal + balance_gt: BigDecimal + balance_gte: BigDecimal + balance_in: [BigDecimal!] + balance_lt: BigDecimal + balance_lte: BigDecimal + balance_not: BigDecimal + balance_not_in: [BigDecimal!] + gauge: String + gauge_: LiquidityGauge_filter + gauge_contains: String + gauge_contains_nocase: String + gauge_ends_with: String + gauge_ends_with_nocase: String + gauge_gt: String + gauge_gte: String + gauge_in: [String!] + gauge_lt: String + gauge_lte: String + gauge_not: String + gauge_not_contains: String + gauge_not_contains_nocase: String + gauge_not_ends_with: String + gauge_not_ends_with_nocase: String + gauge_not_in: [String!] + gauge_not_starts_with: String + gauge_not_starts_with_nocase: String + gauge_starts_with: String + gauge_starts_with_nocase: String + id: ID + id_gt: ID + id_gte: ID + id_in: [ID!] + id_lt: ID + id_lte: ID + id_not: ID + id_not_in: [ID!] + user: String + user_: User_filter + user_contains: String + user_contains_nocase: String + user_ends_with: String + user_ends_with_nocase: String + user_gt: String + user_gte: String + user_in: [String!] + user_lt: String + user_lte: String + user_not: String + user_not_contains: String + user_not_contains_nocase: String + user_not_ends_with: String + user_not_ends_with_nocase: String + user_not_in: [String!] + user_not_starts_with: String + user_not_starts_with_nocase: String + user_starts_with: String + user_starts_with_nocase: String +} + +enum GaugeShare_orderBy { + balance + gauge + id + user +} + +type GaugeType { + id: ID! + name: String! +} + +input GaugeType_filter { + """Filter for the block changed event.""" + _change_block: BlockChangedFilter + id: ID + id_gt: ID + id_gte: ID + id_in: [ID!] + id_lt: ID + id_lte: ID + id_not: ID + id_not_in: [ID!] + name: String + name_contains: String + name_contains_nocase: String + name_ends_with: String + name_ends_with_nocase: String + name_gt: String + name_gte: String + name_in: [String!] + name_lt: String + name_lte: String + name_not: String + name_not_contains: String + name_not_contains_nocase: String + name_not_ends_with: String + name_not_ends_with_nocase: String + name_not_in: [String!] + name_not_starts_with: String + name_not_starts_with_nocase: String + name_starts_with: String + name_starts_with_nocase: String +} + +enum GaugeType_orderBy { + id + name +} + +type GaugeVote { + gauge: LiquidityGauge! + id: ID! + timestamp: BigInt + user: User! + weight: BigDecimal +} + +input GaugeVote_filter { + """Filter for the block changed event.""" + _change_block: BlockChangedFilter + gauge: String + gauge_: LiquidityGauge_filter + gauge_contains: String + gauge_contains_nocase: String + gauge_ends_with: String + gauge_ends_with_nocase: String + gauge_gt: String + gauge_gte: String + gauge_in: [String!] + gauge_lt: String + gauge_lte: String + gauge_not: String + gauge_not_contains: String + gauge_not_contains_nocase: String + gauge_not_ends_with: String + gauge_not_ends_with_nocase: String + gauge_not_in: [String!] + gauge_not_starts_with: String + gauge_not_starts_with_nocase: String + gauge_starts_with: String + gauge_starts_with_nocase: String + id: ID + id_gt: ID + id_gte: ID + id_in: [ID!] + id_lt: ID + id_lte: ID + id_not: ID + id_not_in: [ID!] + timestamp: BigInt + timestamp_gt: BigInt + timestamp_gte: BigInt + timestamp_in: [BigInt!] + timestamp_lt: BigInt + timestamp_lte: BigInt + timestamp_not: BigInt + timestamp_not_in: [BigInt!] + user: String + user_: User_filter + user_contains: String + user_contains_nocase: String + user_ends_with: String + user_ends_with_nocase: String + user_gt: String + user_gte: String + user_in: [String!] + user_lt: String + user_lte: String + user_not: String + user_not_contains: String + user_not_contains_nocase: String + user_not_ends_with: String + user_not_ends_with_nocase: String + user_not_in: [String!] + user_not_starts_with: String + user_not_starts_with_nocase: String + user_starts_with: String + user_starts_with_nocase: String + weight: BigDecimal + weight_gt: BigDecimal + weight_gte: BigDecimal + weight_in: [BigDecimal!] + weight_lt: BigDecimal + weight_lte: BigDecimal + weight_not: BigDecimal + weight_not_in: [BigDecimal!] +} + +enum GaugeVote_orderBy { + gauge + id + timestamp + user + weight +} + +input Gauge_filter { + """Filter for the block changed event.""" + _change_block: BlockChangedFilter + address: Bytes + address_contains: Bytes + address_in: [Bytes!] + address_not: Bytes + address_not_contains: Bytes + address_not_in: [Bytes!] + id: ID + id_gt: ID + id_gte: ID + id_in: [ID!] + id_lt: ID + id_lte: ID + id_not: ID + id_not_in: [ID!] + type: String + type_: GaugeType_filter + type_contains: String + type_contains_nocase: String + type_ends_with: String + type_ends_with_nocase: String + type_gt: String + type_gte: String + type_in: [String!] + type_lt: String + type_lte: String + type_not: String + type_not_contains: String + type_not_contains_nocase: String + type_not_ends_with: String + type_not_ends_with_nocase: String + type_not_in: [String!] + type_not_starts_with: String + type_not_starts_with_nocase: String + type_starts_with: String + type_starts_with_nocase: String +} + +enum Gauge_orderBy { + address + id + type +} + +type LiquidityGauge { + factory: GaugeFactory! + id: ID! + poolAddress: Bytes! + poolId: Bytes + shares(first: Int = 100, orderBy: GaugeShare_orderBy, orderDirection: OrderDirection, skip: Int = 0, where: GaugeShare_filter): [GaugeShare!] + streamer: Bytes + symbol: String! + tokens(first: Int = 100, orderBy: RewardToken_orderBy, orderDirection: OrderDirection, skip: Int = 0, where: RewardToken_filter): [RewardToken!] + totalSupply: BigDecimal! +} + +input LiquidityGauge_filter { + """Filter for the block changed event.""" + _change_block: BlockChangedFilter + factory: String + factory_: GaugeFactory_filter + factory_contains: String + factory_contains_nocase: String + factory_ends_with: String + factory_ends_with_nocase: String + factory_gt: String + factory_gte: String + factory_in: [String!] + factory_lt: String + factory_lte: String + factory_not: String + factory_not_contains: String + factory_not_contains_nocase: String + factory_not_ends_with: String + factory_not_ends_with_nocase: String + factory_not_in: [String!] + factory_not_starts_with: String + factory_not_starts_with_nocase: String + factory_starts_with: String + factory_starts_with_nocase: String + id: ID + id_gt: ID + id_gte: ID + id_in: [ID!] + id_lt: ID + id_lte: ID + id_not: ID + id_not_in: [ID!] + poolAddress: Bytes + poolAddress_contains: Bytes + poolAddress_in: [Bytes!] + poolAddress_not: Bytes + poolAddress_not_contains: Bytes + poolAddress_not_in: [Bytes!] + poolId: Bytes + poolId_contains: Bytes + poolId_in: [Bytes!] + poolId_not: Bytes + poolId_not_contains: Bytes + poolId_not_in: [Bytes!] + shares_: GaugeShare_filter + streamer: Bytes + streamer_contains: Bytes + streamer_in: [Bytes!] + streamer_not: Bytes + streamer_not_contains: Bytes + streamer_not_in: [Bytes!] + symbol: String + symbol_contains: String + symbol_contains_nocase: String + symbol_ends_with: String + symbol_ends_with_nocase: String + symbol_gt: String + symbol_gte: String + symbol_in: [String!] + symbol_lt: String + symbol_lte: String + symbol_not: String + symbol_not_contains: String + symbol_not_contains_nocase: String + symbol_not_ends_with: String + symbol_not_ends_with_nocase: String + symbol_not_in: [String!] + symbol_not_starts_with: String + symbol_not_starts_with_nocase: String + symbol_starts_with: String + symbol_starts_with_nocase: String + tokens_: RewardToken_filter + totalSupply: BigDecimal + totalSupply_gt: BigDecimal + totalSupply_gte: BigDecimal + totalSupply_in: [BigDecimal!] + totalSupply_lt: BigDecimal + totalSupply_lte: BigDecimal + totalSupply_not: BigDecimal + totalSupply_not_in: [BigDecimal!] +} + +enum LiquidityGauge_orderBy { + factory + id + poolAddress + poolId + shares + streamer + symbol + tokens + totalSupply +} + +"""Defines the order direction, either ascending or descending""" +enum OrderDirection { + asc + desc +} + +type Query { + """Access to subgraph metadata""" + _meta(block: Block_height): _Meta_ + gauge( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): Gauge + gaugeFactories( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: GaugeFactory_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: GaugeFactory_filter + ): [GaugeFactory!]! + gaugeFactory( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): GaugeFactory + gaugeShare( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): GaugeShare + gaugeShares( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: GaugeShare_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: GaugeShare_filter + ): [GaugeShare!]! + gaugeType( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): GaugeType + gaugeTypes( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: GaugeType_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: GaugeType_filter + ): [GaugeType!]! + gaugeVote( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): GaugeVote + gaugeVotes( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: GaugeVote_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: GaugeVote_filter + ): [GaugeVote!]! + gauges( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: Gauge_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: Gauge_filter + ): [Gauge!]! + liquidityGauge( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): LiquidityGauge + liquidityGauges( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: LiquidityGauge_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: LiquidityGauge_filter + ): [LiquidityGauge!]! + rewardToken( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): RewardToken + rewardTokens( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: RewardToken_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: RewardToken_filter + ): [RewardToken!]! + rootGauge( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): RootGauge + rootGauges( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: RootGauge_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: RootGauge_filter + ): [RootGauge!]! + user( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): User + users( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: User_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: User_filter + ): [User!]! + votingEscrow( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): VotingEscrow + votingEscrowLock( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): VotingEscrowLock + votingEscrowLocks( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: VotingEscrowLock_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: VotingEscrowLock_filter + ): [VotingEscrowLock!]! + votingEscrows( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: VotingEscrow_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: VotingEscrow_filter + ): [VotingEscrow!]! +} + +type RewardToken { + decimals: Int! + gauge: LiquidityGauge! + id: ID! + symbol: String! + totalDeposited: BigDecimal! +} + +input RewardToken_filter { + """Filter for the block changed event.""" + _change_block: BlockChangedFilter + decimals: Int + decimals_gt: Int + decimals_gte: Int + decimals_in: [Int!] + decimals_lt: Int + decimals_lte: Int + decimals_not: Int + decimals_not_in: [Int!] + gauge: String + gauge_: LiquidityGauge_filter + gauge_contains: String + gauge_contains_nocase: String + gauge_ends_with: String + gauge_ends_with_nocase: String + gauge_gt: String + gauge_gte: String + gauge_in: [String!] + gauge_lt: String + gauge_lte: String + gauge_not: String + gauge_not_contains: String + gauge_not_contains_nocase: String + gauge_not_ends_with: String + gauge_not_ends_with_nocase: String + gauge_not_in: [String!] + gauge_not_starts_with: String + gauge_not_starts_with_nocase: String + gauge_starts_with: String + gauge_starts_with_nocase: String + id: ID + id_gt: ID + id_gte: ID + id_in: [ID!] + id_lt: ID + id_lte: ID + id_not: ID + id_not_in: [ID!] + symbol: String + symbol_contains: String + symbol_contains_nocase: String + symbol_ends_with: String + symbol_ends_with_nocase: String + symbol_gt: String + symbol_gte: String + symbol_in: [String!] + symbol_lt: String + symbol_lte: String + symbol_not: String + symbol_not_contains: String + symbol_not_contains_nocase: String + symbol_not_ends_with: String + symbol_not_ends_with_nocase: String + symbol_not_in: [String!] + symbol_not_starts_with: String + symbol_not_starts_with_nocase: String + symbol_starts_with: String + symbol_starts_with_nocase: String + totalDeposited: BigDecimal + totalDeposited_gt: BigDecimal + totalDeposited_gte: BigDecimal + totalDeposited_in: [BigDecimal!] + totalDeposited_lt: BigDecimal + totalDeposited_lte: BigDecimal + totalDeposited_not: BigDecimal + totalDeposited_not_in: [BigDecimal!] +} + +enum RewardToken_orderBy { + decimals + gauge + id + symbol + totalDeposited +} + +type RootGauge { + chain: Chain! + id: ID! + recipient: Bytes! +} + +input RootGauge_filter { + """Filter for the block changed event.""" + _change_block: BlockChangedFilter + chain: Chain + chain_in: [Chain!] + chain_not: Chain + chain_not_in: [Chain!] + id: ID + id_gt: ID + id_gte: ID + id_in: [ID!] + id_lt: ID + id_lte: ID + id_not: ID + id_not_in: [ID!] + recipient: Bytes + recipient_contains: Bytes + recipient_in: [Bytes!] + recipient_not: Bytes + recipient_not_contains: Bytes + recipient_not_in: [Bytes!] +} + +enum RootGauge_orderBy { + chain + id + recipient +} + +type Subscription { + """Access to subgraph metadata""" + _meta(block: Block_height): _Meta_ + gauge( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): Gauge + gaugeFactories( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: GaugeFactory_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: GaugeFactory_filter + ): [GaugeFactory!]! + gaugeFactory( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): GaugeFactory + gaugeShare( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): GaugeShare + gaugeShares( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: GaugeShare_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: GaugeShare_filter + ): [GaugeShare!]! + gaugeType( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): GaugeType + gaugeTypes( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: GaugeType_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: GaugeType_filter + ): [GaugeType!]! + gaugeVote( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): GaugeVote + gaugeVotes( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: GaugeVote_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: GaugeVote_filter + ): [GaugeVote!]! + gauges( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: Gauge_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: Gauge_filter + ): [Gauge!]! + liquidityGauge( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): LiquidityGauge + liquidityGauges( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: LiquidityGauge_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: LiquidityGauge_filter + ): [LiquidityGauge!]! + rewardToken( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): RewardToken + rewardTokens( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: RewardToken_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: RewardToken_filter + ): [RewardToken!]! + rootGauge( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): RootGauge + rootGauges( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: RootGauge_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: RootGauge_filter + ): [RootGauge!]! + user( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): User + users( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: User_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: User_filter + ): [User!]! + votingEscrow( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): VotingEscrow + votingEscrowLock( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): VotingEscrowLock + votingEscrowLocks( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: VotingEscrowLock_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: VotingEscrowLock_filter + ): [VotingEscrowLock!]! + votingEscrows( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: VotingEscrow_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: VotingEscrow_filter + ): [VotingEscrow!]! +} + +type User { + gaugeShares(first: Int = 100, orderBy: GaugeShare_orderBy, orderDirection: OrderDirection, skip: Int = 0, where: GaugeShare_filter): [GaugeShare!] + gaugeVotes(first: Int = 100, orderBy: GaugeVote_orderBy, orderDirection: OrderDirection, skip: Int = 0, where: GaugeVote_filter): [GaugeVote!] + id: ID! + votingLocks(first: Int = 100, orderBy: VotingEscrowLock_orderBy, orderDirection: OrderDirection, skip: Int = 0, where: VotingEscrowLock_filter): [VotingEscrowLock!] +} + +input User_filter { + """Filter for the block changed event.""" + _change_block: BlockChangedFilter + gaugeShares_: GaugeShare_filter + gaugeVotes_: GaugeVote_filter + id: ID + id_gt: ID + id_gte: ID + id_in: [ID!] + id_lt: ID + id_lte: ID + id_not: ID + id_not_in: [ID!] + votingLocks_: VotingEscrowLock_filter +} + +enum User_orderBy { + gaugeShares + gaugeVotes + id + votingLocks +} + +type VotingEscrow { + id: ID! + locks(first: Int = 100, orderBy: VotingEscrowLock_orderBy, orderDirection: OrderDirection, skip: Int = 0, where: VotingEscrowLock_filter): [VotingEscrowLock!] + stakedSupply: BigDecimal! +} + +type VotingEscrowLock { + id: ID! + lockedBalance: BigDecimal! + unlockTime: BigInt + user: User! + votingEscrowID: VotingEscrow! +} + +input VotingEscrowLock_filter { + """Filter for the block changed event.""" + _change_block: BlockChangedFilter + id: ID + id_gt: ID + id_gte: ID + id_in: [ID!] + id_lt: ID + id_lte: ID + id_not: ID + id_not_in: [ID!] + lockedBalance: BigDecimal + lockedBalance_gt: BigDecimal + lockedBalance_gte: BigDecimal + lockedBalance_in: [BigDecimal!] + lockedBalance_lt: BigDecimal + lockedBalance_lte: BigDecimal + lockedBalance_not: BigDecimal + lockedBalance_not_in: [BigDecimal!] + unlockTime: BigInt + unlockTime_gt: BigInt + unlockTime_gte: BigInt + unlockTime_in: [BigInt!] + unlockTime_lt: BigInt + unlockTime_lte: BigInt + unlockTime_not: BigInt + unlockTime_not_in: [BigInt!] + user: String + user_: User_filter + user_contains: String + user_contains_nocase: String + user_ends_with: String + user_ends_with_nocase: String + user_gt: String + user_gte: String + user_in: [String!] + user_lt: String + user_lte: String + user_not: String + user_not_contains: String + user_not_contains_nocase: String + user_not_ends_with: String + user_not_ends_with_nocase: String + user_not_in: [String!] + user_not_starts_with: String + user_not_starts_with_nocase: String + user_starts_with: String + user_starts_with_nocase: String + votingEscrowID: String + votingEscrowID_: VotingEscrow_filter + votingEscrowID_contains: String + votingEscrowID_contains_nocase: String + votingEscrowID_ends_with: String + votingEscrowID_ends_with_nocase: String + votingEscrowID_gt: String + votingEscrowID_gte: String + votingEscrowID_in: [String!] + votingEscrowID_lt: String + votingEscrowID_lte: String + votingEscrowID_not: String + votingEscrowID_not_contains: String + votingEscrowID_not_contains_nocase: String + votingEscrowID_not_ends_with: String + votingEscrowID_not_ends_with_nocase: String + votingEscrowID_not_in: [String!] + votingEscrowID_not_starts_with: String + votingEscrowID_not_starts_with_nocase: String + votingEscrowID_starts_with: String + votingEscrowID_starts_with_nocase: String +} + +enum VotingEscrowLock_orderBy { + id + lockedBalance + unlockTime + user + votingEscrowID +} + +input VotingEscrow_filter { + """Filter for the block changed event.""" + _change_block: BlockChangedFilter + id: ID + id_gt: ID + id_gte: ID + id_in: [ID!] + id_lt: ID + id_lte: ID + id_not: ID + id_not_in: [ID!] + locks_: VotingEscrowLock_filter + stakedSupply: BigDecimal + stakedSupply_gt: BigDecimal + stakedSupply_gte: BigDecimal + stakedSupply_in: [BigDecimal!] + stakedSupply_lt: BigDecimal + stakedSupply_lte: BigDecimal + stakedSupply_not: BigDecimal + stakedSupply_not_in: [BigDecimal!] +} + +enum VotingEscrow_orderBy { + id + locks + stakedSupply +} + +type _Block_ { + """The hash of the block""" + hash: Bytes + + """The block number""" + number: Int! +} + +"""The type for the top-level _meta field""" +type _Meta_ { + """ + Information about a specific subgraph block. The hash of the block + will be null if the _meta field has a block constraint that asks for + a block number. It will be filled if the _meta field has no block constraint + and therefore asks for the latest block + + """ + block: _Block_! + + """The deployment ID""" + deployment: String! + + """If `true`, the subgraph encountered indexing errors at some past block""" + hasIndexingErrors: Boolean! +} + +enum _SubgraphErrorPolicy_ { + """Data will be returned even if the subgraph has indexing errors""" + allow + + """ + If the subgraph has indexing errors, data will be omitted. The default. + """ + deny +} diff --git a/balancer-js/src/modules/subgraph/generated/balancer-gauges.ts b/balancer-js/src/modules/subgraph/generated/balancer-gauges.ts new file mode 100644 index 000000000..b986c9ed6 --- /dev/null +++ b/balancer-js/src/modules/subgraph/generated/balancer-gauges.ts @@ -0,0 +1,1343 @@ +import { GraphQLClient } from 'graphql-request'; +import * as Dom from 'graphql-request/dist/types.dom'; +import gql from 'graphql-tag'; +export type Maybe = T | null; +export type InputMaybe = Maybe; +export type Exact = { [K in keyof T]: T[K] }; +export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; +export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: string; + String: string; + Boolean: boolean; + Int: number; + Float: number; + BigDecimal: string; + BigInt: string; + Bytes: string; +}; + +export type BlockChangedFilter = { + number_gte: Scalars['Int']; +}; + +export type Block_Height = { + hash?: InputMaybe; + number?: InputMaybe; + number_gte?: InputMaybe; +}; + +export enum Chain { + Arbitrum = 'Arbitrum', + Optimism = 'Optimism', + Polygon = 'Polygon' +} + +export type Gauge = { + __typename?: 'Gauge'; + address: Scalars['Bytes']; + id: Scalars['ID']; + type: GaugeType; +}; + +export type GaugeFactory = { + __typename?: 'GaugeFactory'; + gauges?: Maybe>; + id: Scalars['ID']; + numGauges: Scalars['Int']; +}; + + +export type GaugeFactoryGaugesArgs = { + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + where?: InputMaybe; +}; + +export type GaugeFactory_Filter = { + /** Filter for the block changed event. */ + _change_block?: InputMaybe; + gauges_?: InputMaybe; + id?: InputMaybe; + id_gt?: InputMaybe; + id_gte?: InputMaybe; + id_in?: InputMaybe>; + id_lt?: InputMaybe; + id_lte?: InputMaybe; + id_not?: InputMaybe; + id_not_in?: InputMaybe>; + numGauges?: InputMaybe; + numGauges_gt?: InputMaybe; + numGauges_gte?: InputMaybe; + numGauges_in?: InputMaybe>; + numGauges_lt?: InputMaybe; + numGauges_lte?: InputMaybe; + numGauges_not?: InputMaybe; + numGauges_not_in?: InputMaybe>; +}; + +export enum GaugeFactory_OrderBy { + Gauges = 'gauges', + Id = 'id', + NumGauges = 'numGauges' +} + +export type GaugeShare = { + __typename?: 'GaugeShare'; + balance: Scalars['BigDecimal']; + gauge: LiquidityGauge; + id: Scalars['ID']; + user: User; +}; + +export type GaugeShare_Filter = { + /** Filter for the block changed event. */ + _change_block?: InputMaybe; + balance?: InputMaybe; + balance_gt?: InputMaybe; + balance_gte?: InputMaybe; + balance_in?: InputMaybe>; + balance_lt?: InputMaybe; + balance_lte?: InputMaybe; + balance_not?: InputMaybe; + balance_not_in?: InputMaybe>; + gauge?: InputMaybe; + gauge_?: InputMaybe; + gauge_contains?: InputMaybe; + gauge_contains_nocase?: InputMaybe; + gauge_ends_with?: InputMaybe; + gauge_ends_with_nocase?: InputMaybe; + gauge_gt?: InputMaybe; + gauge_gte?: InputMaybe; + gauge_in?: InputMaybe>; + gauge_lt?: InputMaybe; + gauge_lte?: InputMaybe; + gauge_not?: InputMaybe; + gauge_not_contains?: InputMaybe; + gauge_not_contains_nocase?: InputMaybe; + gauge_not_ends_with?: InputMaybe; + gauge_not_ends_with_nocase?: InputMaybe; + gauge_not_in?: InputMaybe>; + gauge_not_starts_with?: InputMaybe; + gauge_not_starts_with_nocase?: InputMaybe; + gauge_starts_with?: InputMaybe; + gauge_starts_with_nocase?: InputMaybe; + id?: InputMaybe; + id_gt?: InputMaybe; + id_gte?: InputMaybe; + id_in?: InputMaybe>; + id_lt?: InputMaybe; + id_lte?: InputMaybe; + id_not?: InputMaybe; + id_not_in?: InputMaybe>; + user?: InputMaybe; + user_?: InputMaybe; + user_contains?: InputMaybe; + user_contains_nocase?: InputMaybe; + user_ends_with?: InputMaybe; + user_ends_with_nocase?: InputMaybe; + user_gt?: InputMaybe; + user_gte?: InputMaybe; + user_in?: InputMaybe>; + user_lt?: InputMaybe; + user_lte?: InputMaybe; + user_not?: InputMaybe; + user_not_contains?: InputMaybe; + user_not_contains_nocase?: InputMaybe; + user_not_ends_with?: InputMaybe; + user_not_ends_with_nocase?: InputMaybe; + user_not_in?: InputMaybe>; + user_not_starts_with?: InputMaybe; + user_not_starts_with_nocase?: InputMaybe; + user_starts_with?: InputMaybe; + user_starts_with_nocase?: InputMaybe; +}; + +export enum GaugeShare_OrderBy { + Balance = 'balance', + Gauge = 'gauge', + Id = 'id', + User = 'user' +} + +export type GaugeType = { + __typename?: 'GaugeType'; + id: Scalars['ID']; + name: Scalars['String']; +}; + +export type GaugeType_Filter = { + /** Filter for the block changed event. */ + _change_block?: InputMaybe; + id?: InputMaybe; + id_gt?: InputMaybe; + id_gte?: InputMaybe; + id_in?: InputMaybe>; + id_lt?: InputMaybe; + id_lte?: InputMaybe; + id_not?: InputMaybe; + id_not_in?: InputMaybe>; + name?: InputMaybe; + name_contains?: InputMaybe; + name_contains_nocase?: InputMaybe; + name_ends_with?: InputMaybe; + name_ends_with_nocase?: InputMaybe; + name_gt?: InputMaybe; + name_gte?: InputMaybe; + name_in?: InputMaybe>; + name_lt?: InputMaybe; + name_lte?: InputMaybe; + name_not?: InputMaybe; + name_not_contains?: InputMaybe; + name_not_contains_nocase?: InputMaybe; + name_not_ends_with?: InputMaybe; + name_not_ends_with_nocase?: InputMaybe; + name_not_in?: InputMaybe>; + name_not_starts_with?: InputMaybe; + name_not_starts_with_nocase?: InputMaybe; + name_starts_with?: InputMaybe; + name_starts_with_nocase?: InputMaybe; +}; + +export enum GaugeType_OrderBy { + Id = 'id', + Name = 'name' +} + +export type GaugeVote = { + __typename?: 'GaugeVote'; + gauge: LiquidityGauge; + id: Scalars['ID']; + timestamp?: Maybe; + user: User; + weight?: Maybe; +}; + +export type GaugeVote_Filter = { + /** Filter for the block changed event. */ + _change_block?: InputMaybe; + gauge?: InputMaybe; + gauge_?: InputMaybe; + gauge_contains?: InputMaybe; + gauge_contains_nocase?: InputMaybe; + gauge_ends_with?: InputMaybe; + gauge_ends_with_nocase?: InputMaybe; + gauge_gt?: InputMaybe; + gauge_gte?: InputMaybe; + gauge_in?: InputMaybe>; + gauge_lt?: InputMaybe; + gauge_lte?: InputMaybe; + gauge_not?: InputMaybe; + gauge_not_contains?: InputMaybe; + gauge_not_contains_nocase?: InputMaybe; + gauge_not_ends_with?: InputMaybe; + gauge_not_ends_with_nocase?: InputMaybe; + gauge_not_in?: InputMaybe>; + gauge_not_starts_with?: InputMaybe; + gauge_not_starts_with_nocase?: InputMaybe; + gauge_starts_with?: InputMaybe; + gauge_starts_with_nocase?: InputMaybe; + id?: InputMaybe; + id_gt?: InputMaybe; + id_gte?: InputMaybe; + id_in?: InputMaybe>; + id_lt?: InputMaybe; + id_lte?: InputMaybe; + id_not?: InputMaybe; + id_not_in?: InputMaybe>; + timestamp?: InputMaybe; + timestamp_gt?: InputMaybe; + timestamp_gte?: InputMaybe; + timestamp_in?: InputMaybe>; + timestamp_lt?: InputMaybe; + timestamp_lte?: InputMaybe; + timestamp_not?: InputMaybe; + timestamp_not_in?: InputMaybe>; + user?: InputMaybe; + user_?: InputMaybe; + user_contains?: InputMaybe; + user_contains_nocase?: InputMaybe; + user_ends_with?: InputMaybe; + user_ends_with_nocase?: InputMaybe; + user_gt?: InputMaybe; + user_gte?: InputMaybe; + user_in?: InputMaybe>; + user_lt?: InputMaybe; + user_lte?: InputMaybe; + user_not?: InputMaybe; + user_not_contains?: InputMaybe; + user_not_contains_nocase?: InputMaybe; + user_not_ends_with?: InputMaybe; + user_not_ends_with_nocase?: InputMaybe; + user_not_in?: InputMaybe>; + user_not_starts_with?: InputMaybe; + user_not_starts_with_nocase?: InputMaybe; + user_starts_with?: InputMaybe; + user_starts_with_nocase?: InputMaybe; + weight?: InputMaybe; + weight_gt?: InputMaybe; + weight_gte?: InputMaybe; + weight_in?: InputMaybe>; + weight_lt?: InputMaybe; + weight_lte?: InputMaybe; + weight_not?: InputMaybe; + weight_not_in?: InputMaybe>; +}; + +export enum GaugeVote_OrderBy { + Gauge = 'gauge', + Id = 'id', + Timestamp = 'timestamp', + User = 'user', + Weight = 'weight' +} + +export type Gauge_Filter = { + /** Filter for the block changed event. */ + _change_block?: InputMaybe; + address?: InputMaybe; + address_contains?: InputMaybe; + address_in?: InputMaybe>; + address_not?: InputMaybe; + address_not_contains?: InputMaybe; + address_not_in?: InputMaybe>; + id?: InputMaybe; + id_gt?: InputMaybe; + id_gte?: InputMaybe; + id_in?: InputMaybe>; + id_lt?: InputMaybe; + id_lte?: InputMaybe; + id_not?: InputMaybe; + id_not_in?: InputMaybe>; + type?: InputMaybe; + type_?: InputMaybe; + type_contains?: InputMaybe; + type_contains_nocase?: InputMaybe; + type_ends_with?: InputMaybe; + type_ends_with_nocase?: InputMaybe; + type_gt?: InputMaybe; + type_gte?: InputMaybe; + type_in?: InputMaybe>; + type_lt?: InputMaybe; + type_lte?: InputMaybe; + type_not?: InputMaybe; + type_not_contains?: InputMaybe; + type_not_contains_nocase?: InputMaybe; + type_not_ends_with?: InputMaybe; + type_not_ends_with_nocase?: InputMaybe; + type_not_in?: InputMaybe>; + type_not_starts_with?: InputMaybe; + type_not_starts_with_nocase?: InputMaybe; + type_starts_with?: InputMaybe; + type_starts_with_nocase?: InputMaybe; +}; + +export enum Gauge_OrderBy { + Address = 'address', + Id = 'id', + Type = 'type' +} + +export type LiquidityGauge = { + __typename?: 'LiquidityGauge'; + factory: GaugeFactory; + id: Scalars['ID']; + poolAddress: Scalars['Bytes']; + poolId?: Maybe; + shares?: Maybe>; + streamer?: Maybe; + symbol: Scalars['String']; + tokens?: Maybe>; + totalSupply: Scalars['BigDecimal']; +}; + + +export type LiquidityGaugeSharesArgs = { + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + where?: InputMaybe; +}; + + +export type LiquidityGaugeTokensArgs = { + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + where?: InputMaybe; +}; + +export type LiquidityGauge_Filter = { + /** Filter for the block changed event. */ + _change_block?: InputMaybe; + factory?: InputMaybe; + factory_?: InputMaybe; + factory_contains?: InputMaybe; + factory_contains_nocase?: InputMaybe; + factory_ends_with?: InputMaybe; + factory_ends_with_nocase?: InputMaybe; + factory_gt?: InputMaybe; + factory_gte?: InputMaybe; + factory_in?: InputMaybe>; + factory_lt?: InputMaybe; + factory_lte?: InputMaybe; + factory_not?: InputMaybe; + factory_not_contains?: InputMaybe; + factory_not_contains_nocase?: InputMaybe; + factory_not_ends_with?: InputMaybe; + factory_not_ends_with_nocase?: InputMaybe; + factory_not_in?: InputMaybe>; + factory_not_starts_with?: InputMaybe; + factory_not_starts_with_nocase?: InputMaybe; + factory_starts_with?: InputMaybe; + factory_starts_with_nocase?: InputMaybe; + id?: InputMaybe; + id_gt?: InputMaybe; + id_gte?: InputMaybe; + id_in?: InputMaybe>; + id_lt?: InputMaybe; + id_lte?: InputMaybe; + id_not?: InputMaybe; + id_not_in?: InputMaybe>; + poolAddress?: InputMaybe; + poolAddress_contains?: InputMaybe; + poolAddress_in?: InputMaybe>; + poolAddress_not?: InputMaybe; + poolAddress_not_contains?: InputMaybe; + poolAddress_not_in?: InputMaybe>; + poolId?: InputMaybe; + poolId_contains?: InputMaybe; + poolId_in?: InputMaybe>; + poolId_not?: InputMaybe; + poolId_not_contains?: InputMaybe; + poolId_not_in?: InputMaybe>; + shares_?: InputMaybe; + streamer?: InputMaybe; + streamer_contains?: InputMaybe; + streamer_in?: InputMaybe>; + streamer_not?: InputMaybe; + streamer_not_contains?: InputMaybe; + streamer_not_in?: InputMaybe>; + symbol?: InputMaybe; + symbol_contains?: InputMaybe; + symbol_contains_nocase?: InputMaybe; + symbol_ends_with?: InputMaybe; + symbol_ends_with_nocase?: InputMaybe; + symbol_gt?: InputMaybe; + symbol_gte?: InputMaybe; + symbol_in?: InputMaybe>; + symbol_lt?: InputMaybe; + symbol_lte?: InputMaybe; + symbol_not?: InputMaybe; + symbol_not_contains?: InputMaybe; + symbol_not_contains_nocase?: InputMaybe; + symbol_not_ends_with?: InputMaybe; + symbol_not_ends_with_nocase?: InputMaybe; + symbol_not_in?: InputMaybe>; + symbol_not_starts_with?: InputMaybe; + symbol_not_starts_with_nocase?: InputMaybe; + symbol_starts_with?: InputMaybe; + symbol_starts_with_nocase?: InputMaybe; + tokens_?: InputMaybe; + totalSupply?: InputMaybe; + totalSupply_gt?: InputMaybe; + totalSupply_gte?: InputMaybe; + totalSupply_in?: InputMaybe>; + totalSupply_lt?: InputMaybe; + totalSupply_lte?: InputMaybe; + totalSupply_not?: InputMaybe; + totalSupply_not_in?: InputMaybe>; +}; + +export enum LiquidityGauge_OrderBy { + Factory = 'factory', + Id = 'id', + PoolAddress = 'poolAddress', + PoolId = 'poolId', + Shares = 'shares', + Streamer = 'streamer', + Symbol = 'symbol', + Tokens = 'tokens', + TotalSupply = 'totalSupply' +} + +/** Defines the order direction, either ascending or descending */ +export enum OrderDirection { + Asc = 'asc', + Desc = 'desc' +} + +export type Query = { + __typename?: 'Query'; + /** Access to subgraph metadata */ + _meta?: Maybe<_Meta_>; + gauge?: Maybe; + gaugeFactories: Array; + gaugeFactory?: Maybe; + gaugeShare?: Maybe; + gaugeShares: Array; + gaugeType?: Maybe; + gaugeTypes: Array; + gaugeVote?: Maybe; + gaugeVotes: Array; + gauges: Array; + liquidityGauge?: Maybe; + liquidityGauges: Array; + rewardToken?: Maybe; + rewardTokens: Array; + rootGauge?: Maybe; + rootGauges: Array; + user?: Maybe; + users: Array; + votingEscrow?: Maybe; + votingEscrowLock?: Maybe; + votingEscrowLocks: Array; + votingEscrows: Array; +}; + + +export type Query_MetaArgs = { + block?: InputMaybe; +}; + + +export type QueryGaugeArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type QueryGaugeFactoriesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type QueryGaugeFactoryArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type QueryGaugeShareArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type QueryGaugeSharesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type QueryGaugeTypeArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type QueryGaugeTypesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type QueryGaugeVoteArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type QueryGaugeVotesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type QueryGaugesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type QueryLiquidityGaugeArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type QueryLiquidityGaugesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type QueryRewardTokenArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type QueryRewardTokensArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type QueryRootGaugeArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type QueryRootGaugesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type QueryUserArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type QueryUsersArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type QueryVotingEscrowArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type QueryVotingEscrowLockArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type QueryVotingEscrowLocksArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type QueryVotingEscrowsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type RewardToken = { + __typename?: 'RewardToken'; + decimals: Scalars['Int']; + gauge: LiquidityGauge; + id: Scalars['ID']; + symbol: Scalars['String']; + totalDeposited: Scalars['BigDecimal']; +}; + +export type RewardToken_Filter = { + /** Filter for the block changed event. */ + _change_block?: InputMaybe; + decimals?: InputMaybe; + decimals_gt?: InputMaybe; + decimals_gte?: InputMaybe; + decimals_in?: InputMaybe>; + decimals_lt?: InputMaybe; + decimals_lte?: InputMaybe; + decimals_not?: InputMaybe; + decimals_not_in?: InputMaybe>; + gauge?: InputMaybe; + gauge_?: InputMaybe; + gauge_contains?: InputMaybe; + gauge_contains_nocase?: InputMaybe; + gauge_ends_with?: InputMaybe; + gauge_ends_with_nocase?: InputMaybe; + gauge_gt?: InputMaybe; + gauge_gte?: InputMaybe; + gauge_in?: InputMaybe>; + gauge_lt?: InputMaybe; + gauge_lte?: InputMaybe; + gauge_not?: InputMaybe; + gauge_not_contains?: InputMaybe; + gauge_not_contains_nocase?: InputMaybe; + gauge_not_ends_with?: InputMaybe; + gauge_not_ends_with_nocase?: InputMaybe; + gauge_not_in?: InputMaybe>; + gauge_not_starts_with?: InputMaybe; + gauge_not_starts_with_nocase?: InputMaybe; + gauge_starts_with?: InputMaybe; + gauge_starts_with_nocase?: InputMaybe; + id?: InputMaybe; + id_gt?: InputMaybe; + id_gte?: InputMaybe; + id_in?: InputMaybe>; + id_lt?: InputMaybe; + id_lte?: InputMaybe; + id_not?: InputMaybe; + id_not_in?: InputMaybe>; + symbol?: InputMaybe; + symbol_contains?: InputMaybe; + symbol_contains_nocase?: InputMaybe; + symbol_ends_with?: InputMaybe; + symbol_ends_with_nocase?: InputMaybe; + symbol_gt?: InputMaybe; + symbol_gte?: InputMaybe; + symbol_in?: InputMaybe>; + symbol_lt?: InputMaybe; + symbol_lte?: InputMaybe; + symbol_not?: InputMaybe; + symbol_not_contains?: InputMaybe; + symbol_not_contains_nocase?: InputMaybe; + symbol_not_ends_with?: InputMaybe; + symbol_not_ends_with_nocase?: InputMaybe; + symbol_not_in?: InputMaybe>; + symbol_not_starts_with?: InputMaybe; + symbol_not_starts_with_nocase?: InputMaybe; + symbol_starts_with?: InputMaybe; + symbol_starts_with_nocase?: InputMaybe; + totalDeposited?: InputMaybe; + totalDeposited_gt?: InputMaybe; + totalDeposited_gte?: InputMaybe; + totalDeposited_in?: InputMaybe>; + totalDeposited_lt?: InputMaybe; + totalDeposited_lte?: InputMaybe; + totalDeposited_not?: InputMaybe; + totalDeposited_not_in?: InputMaybe>; +}; + +export enum RewardToken_OrderBy { + Decimals = 'decimals', + Gauge = 'gauge', + Id = 'id', + Symbol = 'symbol', + TotalDeposited = 'totalDeposited' +} + +export type RootGauge = { + __typename?: 'RootGauge'; + chain: Chain; + id: Scalars['ID']; + recipient: Scalars['Bytes']; +}; + +export type RootGauge_Filter = { + /** Filter for the block changed event. */ + _change_block?: InputMaybe; + chain?: InputMaybe; + chain_in?: InputMaybe>; + chain_not?: InputMaybe; + chain_not_in?: InputMaybe>; + id?: InputMaybe; + id_gt?: InputMaybe; + id_gte?: InputMaybe; + id_in?: InputMaybe>; + id_lt?: InputMaybe; + id_lte?: InputMaybe; + id_not?: InputMaybe; + id_not_in?: InputMaybe>; + recipient?: InputMaybe; + recipient_contains?: InputMaybe; + recipient_in?: InputMaybe>; + recipient_not?: InputMaybe; + recipient_not_contains?: InputMaybe; + recipient_not_in?: InputMaybe>; +}; + +export enum RootGauge_OrderBy { + Chain = 'chain', + Id = 'id', + Recipient = 'recipient' +} + +export type Subscription = { + __typename?: 'Subscription'; + /** Access to subgraph metadata */ + _meta?: Maybe<_Meta_>; + gauge?: Maybe; + gaugeFactories: Array; + gaugeFactory?: Maybe; + gaugeShare?: Maybe; + gaugeShares: Array; + gaugeType?: Maybe; + gaugeTypes: Array; + gaugeVote?: Maybe; + gaugeVotes: Array; + gauges: Array; + liquidityGauge?: Maybe; + liquidityGauges: Array; + rewardToken?: Maybe; + rewardTokens: Array; + rootGauge?: Maybe; + rootGauges: Array; + user?: Maybe; + users: Array; + votingEscrow?: Maybe; + votingEscrowLock?: Maybe; + votingEscrowLocks: Array; + votingEscrows: Array; +}; + + +export type Subscription_MetaArgs = { + block?: InputMaybe; +}; + + +export type SubscriptionGaugeArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type SubscriptionGaugeFactoriesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type SubscriptionGaugeFactoryArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type SubscriptionGaugeShareArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type SubscriptionGaugeSharesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type SubscriptionGaugeTypeArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type SubscriptionGaugeTypesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type SubscriptionGaugeVoteArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type SubscriptionGaugeVotesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type SubscriptionGaugesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type SubscriptionLiquidityGaugeArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type SubscriptionLiquidityGaugesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type SubscriptionRewardTokenArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type SubscriptionRewardTokensArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type SubscriptionRootGaugeArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type SubscriptionRootGaugesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type SubscriptionUserArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type SubscriptionUsersArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type SubscriptionVotingEscrowArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type SubscriptionVotingEscrowLockArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type SubscriptionVotingEscrowLocksArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + +export type SubscriptionVotingEscrowsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type User = { + __typename?: 'User'; + gaugeShares?: Maybe>; + gaugeVotes?: Maybe>; + id: Scalars['ID']; + votingLocks?: Maybe>; +}; + + +export type UserGaugeSharesArgs = { + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + where?: InputMaybe; +}; + + +export type UserGaugeVotesArgs = { + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + where?: InputMaybe; +}; + + +export type UserVotingLocksArgs = { + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + where?: InputMaybe; +}; + +export type User_Filter = { + /** Filter for the block changed event. */ + _change_block?: InputMaybe; + gaugeShares_?: InputMaybe; + gaugeVotes_?: InputMaybe; + id?: InputMaybe; + id_gt?: InputMaybe; + id_gte?: InputMaybe; + id_in?: InputMaybe>; + id_lt?: InputMaybe; + id_lte?: InputMaybe; + id_not?: InputMaybe; + id_not_in?: InputMaybe>; + votingLocks_?: InputMaybe; +}; + +export enum User_OrderBy { + GaugeShares = 'gaugeShares', + GaugeVotes = 'gaugeVotes', + Id = 'id', + VotingLocks = 'votingLocks' +} + +export type VotingEscrow = { + __typename?: 'VotingEscrow'; + id: Scalars['ID']; + locks?: Maybe>; + stakedSupply: Scalars['BigDecimal']; +}; + + +export type VotingEscrowLocksArgs = { + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + where?: InputMaybe; +}; + +export type VotingEscrowLock = { + __typename?: 'VotingEscrowLock'; + id: Scalars['ID']; + lockedBalance: Scalars['BigDecimal']; + unlockTime?: Maybe; + user: User; + votingEscrowID: VotingEscrow; +}; + +export type VotingEscrowLock_Filter = { + /** Filter for the block changed event. */ + _change_block?: InputMaybe; + id?: InputMaybe; + id_gt?: InputMaybe; + id_gte?: InputMaybe; + id_in?: InputMaybe>; + id_lt?: InputMaybe; + id_lte?: InputMaybe; + id_not?: InputMaybe; + id_not_in?: InputMaybe>; + lockedBalance?: InputMaybe; + lockedBalance_gt?: InputMaybe; + lockedBalance_gte?: InputMaybe; + lockedBalance_in?: InputMaybe>; + lockedBalance_lt?: InputMaybe; + lockedBalance_lte?: InputMaybe; + lockedBalance_not?: InputMaybe; + lockedBalance_not_in?: InputMaybe>; + unlockTime?: InputMaybe; + unlockTime_gt?: InputMaybe; + unlockTime_gte?: InputMaybe; + unlockTime_in?: InputMaybe>; + unlockTime_lt?: InputMaybe; + unlockTime_lte?: InputMaybe; + unlockTime_not?: InputMaybe; + unlockTime_not_in?: InputMaybe>; + user?: InputMaybe; + user_?: InputMaybe; + user_contains?: InputMaybe; + user_contains_nocase?: InputMaybe; + user_ends_with?: InputMaybe; + user_ends_with_nocase?: InputMaybe; + user_gt?: InputMaybe; + user_gte?: InputMaybe; + user_in?: InputMaybe>; + user_lt?: InputMaybe; + user_lte?: InputMaybe; + user_not?: InputMaybe; + user_not_contains?: InputMaybe; + user_not_contains_nocase?: InputMaybe; + user_not_ends_with?: InputMaybe; + user_not_ends_with_nocase?: InputMaybe; + user_not_in?: InputMaybe>; + user_not_starts_with?: InputMaybe; + user_not_starts_with_nocase?: InputMaybe; + user_starts_with?: InputMaybe; + user_starts_with_nocase?: InputMaybe; + votingEscrowID?: InputMaybe; + votingEscrowID_?: InputMaybe; + votingEscrowID_contains?: InputMaybe; + votingEscrowID_contains_nocase?: InputMaybe; + votingEscrowID_ends_with?: InputMaybe; + votingEscrowID_ends_with_nocase?: InputMaybe; + votingEscrowID_gt?: InputMaybe; + votingEscrowID_gte?: InputMaybe; + votingEscrowID_in?: InputMaybe>; + votingEscrowID_lt?: InputMaybe; + votingEscrowID_lte?: InputMaybe; + votingEscrowID_not?: InputMaybe; + votingEscrowID_not_contains?: InputMaybe; + votingEscrowID_not_contains_nocase?: InputMaybe; + votingEscrowID_not_ends_with?: InputMaybe; + votingEscrowID_not_ends_with_nocase?: InputMaybe; + votingEscrowID_not_in?: InputMaybe>; + votingEscrowID_not_starts_with?: InputMaybe; + votingEscrowID_not_starts_with_nocase?: InputMaybe; + votingEscrowID_starts_with?: InputMaybe; + votingEscrowID_starts_with_nocase?: InputMaybe; +}; + +export enum VotingEscrowLock_OrderBy { + Id = 'id', + LockedBalance = 'lockedBalance', + UnlockTime = 'unlockTime', + User = 'user', + VotingEscrowId = 'votingEscrowID' +} + +export type VotingEscrow_Filter = { + /** Filter for the block changed event. */ + _change_block?: InputMaybe; + id?: InputMaybe; + id_gt?: InputMaybe; + id_gte?: InputMaybe; + id_in?: InputMaybe>; + id_lt?: InputMaybe; + id_lte?: InputMaybe; + id_not?: InputMaybe; + id_not_in?: InputMaybe>; + locks_?: InputMaybe; + stakedSupply?: InputMaybe; + stakedSupply_gt?: InputMaybe; + stakedSupply_gte?: InputMaybe; + stakedSupply_in?: InputMaybe>; + stakedSupply_lt?: InputMaybe; + stakedSupply_lte?: InputMaybe; + stakedSupply_not?: InputMaybe; + stakedSupply_not_in?: InputMaybe>; +}; + +export enum VotingEscrow_OrderBy { + Id = 'id', + Locks = 'locks', + StakedSupply = 'stakedSupply' +} + +export type _Block_ = { + __typename?: '_Block_'; + /** The hash of the block */ + hash?: Maybe; + /** The block number */ + number: Scalars['Int']; +}; + +/** The type for the top-level _meta field */ +export type _Meta_ = { + __typename?: '_Meta_'; + /** + * Information about a specific subgraph block. The hash of the block + * will be null if the _meta field has a block constraint that asks for + * a block number. It will be filled if the _meta field has no block constraint + * and therefore asks for the latest block + * + */ + block: _Block_; + /** The deployment ID */ + deployment: Scalars['String']; + /** If `true`, the subgraph encountered indexing errors at some past block */ + hasIndexingErrors: Scalars['Boolean']; +}; + +export enum _SubgraphErrorPolicy_ { + /** Data will be returned even if the subgraph has indexing errors */ + Allow = 'allow', + /** If the subgraph has indexing errors, data will be omitted. The default. */ + Deny = 'deny' +} + +export type LiquidityGaugesQueryVariables = Exact<{ + skip?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + where?: InputMaybe; + block?: InputMaybe; +}>; + + +export type LiquidityGaugesQuery = { __typename?: 'Query', liquidityGauges: Array<{ __typename?: 'LiquidityGauge', id: string, symbol: string, poolAddress: string, poolId?: string | null, streamer?: string | null, totalSupply: string, factory: { __typename?: 'GaugeFactory', id: string, numGauges: number }, tokens?: Array<{ __typename?: 'RewardToken', id: string, symbol: string, decimals: number, totalDeposited: string }> | null }> }; + +export type SubgraphLiquidityGaugeFragment = { __typename?: 'LiquidityGauge', id: string, symbol: string, poolAddress: string, poolId?: string | null, streamer?: string | null, totalSupply: string, factory: { __typename?: 'GaugeFactory', id: string, numGauges: number }, tokens?: Array<{ __typename?: 'RewardToken', id: string, symbol: string, decimals: number, totalDeposited: string }> | null }; + +export const SubgraphLiquidityGaugeFragmentDoc = gql` + fragment SubgraphLiquidityGauge on LiquidityGauge { + id + symbol + poolAddress + poolId + streamer + factory { + id + numGauges + } + totalSupply + tokens { + id + symbol + decimals + totalDeposited + } +} + `; +export const LiquidityGaugesDocument = gql` + query LiquidityGauges($skip: Int, $first: Int, $orderBy: LiquidityGauge_orderBy, $orderDirection: OrderDirection, $where: LiquidityGauge_filter, $block: Block_height) { + liquidityGauges( + skip: $skip + first: $first + orderBy: $orderBy + orderDirection: $orderDirection + where: $where + block: $block + ) { + ...SubgraphLiquidityGauge + } +} + ${SubgraphLiquidityGaugeFragmentDoc}`; + +export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string) => Promise; + + +const defaultWrapper: SdkFunctionWrapper = (action, _operationName, _operationType) => action(); + +export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) { + return { + LiquidityGauges(variables?: LiquidityGaugesQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(LiquidityGaugesDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'LiquidityGauges', 'query'); + } + }; +} +export type Sdk = ReturnType; \ No newline at end of file diff --git a/balancer-js/src/modules/subgraph/subgraph.ts b/balancer-js/src/modules/subgraph/subgraph.ts index 87ebfda84..d77c660f8 100644 --- a/balancer-js/src/modules/subgraph/subgraph.ts +++ b/balancer-js/src/modules/subgraph/subgraph.ts @@ -1,12 +1,23 @@ import { GraphQLClient } from 'graphql-request'; import { getSdk, Sdk } from './generated/balancer-subgraph-types'; +import * as Gauges from './generated/balancer-gauges'; +import * as V2 from './generated/balancer-subgraph-types'; export * from './generated/balancer-subgraph-types'; export type SubgraphClient = Sdk; +export type GaugesClient = Gauges.Sdk; +export type SubgraphLiquidityGauge = Gauges.LiquidityGauge; +export type SubgraphPool = V2.SubgraphPoolFragment; export function createSubgraphClient(subgraphUrl: string): SubgraphClient { const client = new GraphQLClient(subgraphUrl); return getSdk(client); } + +export function createGaugesClient(url: string): GaugesClient { + const client = new GraphQLClient(url); + + return Gauges.getSdk(client); +} diff --git a/balancer-js/src/types.ts b/balancer-js/src/types.ts index cee501ac4..41e44b35f 100644 --- a/balancer-js/src/types.ts +++ b/balancer-js/src/types.ts @@ -52,6 +52,7 @@ export interface BalancerNetworkConfig { }; urls: { subgraph: string; + gaugesSubgraph: string; }; pools: { wETHwstETH?: PoolReference; From d572cca1559daa6a06448eb45122ad83d742d37e Mon Sep 17 00:00:00 2001 From: bronco Date: Fri, 8 Jul 2022 15:35:31 +0200 Subject: [PATCH 25/65] token prices data from coingecko --- balancer-js/src/modules/data/index.ts | 2 +- .../data/token-prices/coingecko.spec.ts | 57 ++ .../modules/data/token-prices/coingecko.ts | 104 ++ .../{token-price => token-prices}/index.ts | 1 + .../data/token-prices/initial-list.json | 907 ++++++++++++++++++ .../{token-price => token-prices}/static.ts | 8 + .../{token-price => token-prices}/types.ts | 0 7 files changed, 1078 insertions(+), 1 deletion(-) create mode 100644 balancer-js/src/modules/data/token-prices/coingecko.spec.ts create mode 100644 balancer-js/src/modules/data/token-prices/coingecko.ts rename balancer-js/src/modules/data/{token-price => token-prices}/index.ts (63%) create mode 100644 balancer-js/src/modules/data/token-prices/initial-list.json rename balancer-js/src/modules/data/{token-price => token-prices}/static.ts (91%) rename balancer-js/src/modules/data/{token-price => token-prices}/types.ts (100%) diff --git a/balancer-js/src/modules/data/index.ts b/balancer-js/src/modules/data/index.ts index dab3f9917..301c399eb 100644 --- a/balancer-js/src/modules/data/index.ts +++ b/balancer-js/src/modules/data/index.ts @@ -1,3 +1,3 @@ export * from './pool'; export * from './token'; -export * from './token-price'; +export * from './token-prices'; diff --git a/balancer-js/src/modules/data/token-prices/coingecko.spec.ts b/balancer-js/src/modules/data/token-prices/coingecko.spec.ts new file mode 100644 index 000000000..c57c7f0a6 --- /dev/null +++ b/balancer-js/src/modules/data/token-prices/coingecko.spec.ts @@ -0,0 +1,57 @@ +import { expect } from 'chai'; +import { CoingeckoPriceRepository } from './coingecko'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; + +const mockedResponse = { + '0x028171bca77440897b824ca71d1c56cac55b68a3': { + usd: 1.003, + eth: 0.00063646, + }, + '0x3ed3b47dd13ec9a98b44e6204a523e766b225811': { + usd: 0.995015, + eth: 0.00063114, + }, + '0xdac17f958d2ee523a2206206994597c13d831ec7': { + usd: 1.001, + eth: 0.00063693, + }, + '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56': {}, +}; + +const addresses = Object.keys(mockedResponse); + +const repository = new CoingeckoPriceRepository(addresses, 1); + +describe('coingecko repository', () => { + let mock: MockAdapter; + + before(() => { + mock = new MockAdapter(axios); + mock.onGet(new RegExp('https://api.coingecko.com/*')).reply( + () => + new Promise((resolve) => { + setTimeout(() => resolve([200, mockedResponse]), 10); + }) + ); + }); + + after(() => { + mock.restore(); + }); + + it('finds prices', async () => { + const [price1, price2, price3, price4, price5] = await Promise.all([ + repository.find(addresses[0]), + repository.find(addresses[1]), + repository.find(addresses[2]), + repository.find(addresses[3]), + repository.find(addresses[3]), + ]); + 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(price5?.usd).to.be.undefined; + }); +}); diff --git a/balancer-js/src/modules/data/token-prices/coingecko.ts b/balancer-js/src/modules/data/token-prices/coingecko.ts new file mode 100644 index 000000000..81b8e10c5 --- /dev/null +++ b/balancer-js/src/modules/data/token-prices/coingecko.ts @@ -0,0 +1,104 @@ +import { Price, Findable, TokenPrices } from '@/types'; +import { wrappedTokensMap as aaveWrappedMap } from '../token-yields/tokens/aave'; +import axios from 'axios'; + +/** + * Simple coingecko price source implementation. Configurable by network and token addresses. + */ +export class CoingeckoPriceRepository implements Findable { + prices: TokenPrices = {}; + fetching: { [address: string]: Promise } = {}; + urlBase: string; + baseTokenAddresses: string[]; + + constructor(tokenAddresses: string[], chainId = 1) { + this.baseTokenAddresses = tokenAddresses.map((a) => a.toLowerCase()); + this.urlBase = `https://api.coingecko.com/api/v3/simple/token_price/${this.platform( + chainId + )}?vs_currencies=usd,eth`; + } + + fetch(address: string): { [address: string]: Promise } { + console.time(`fetching coingecko ${address}`); + const addresses = this.addresses(address); + const prices = axios + .get(this.url(addresses)) + .then(({ data }) => { + addresses.forEach((address) => { + delete this.fetching[address]; + }); + this.prices = { + ...this.prices, + ...(Object.keys(data).length == 0 ? { [address]: {} } : data), + }; + return this.prices; + }) + .catch((error) => { + console.error(error); + return this.prices; + }); + console.timeEnd(`fetching coingecko ${address}`); + return Object.fromEntries(addresses.map((a) => [a, prices])); + } + + async find(address: string): Promise { + const lowercaseAddress = address.toLowerCase(); + const unwrapped = unwrapToken(lowercaseAddress); + if (Object.keys(this.fetching).includes(unwrapped)) { + await this.fetching[unwrapped]; + } else if (!Object.keys(this.prices).includes(unwrapped)) { + this.fetching = { + ...this.fetching, + ...this.fetch(unwrapped), + }; + await this.fetching[unwrapped]; + } + + return this.prices[unwrapped]; + } + + async findBy(attribute: string, value: string): Promise { + if (attribute != 'address') { + return undefined; + } + + return this.find(value); + } + + private platform(chainId: number): string { + switch (chainId) { + case 1: + case 42: + case 31337: + return 'ethereum'; + case 137: + return 'polygon-pos'; + case 42161: + return 'arbitrum-one'; + } + + return '2'; + } + + private url(addresses: string[]): string { + return `${this.urlBase}&contract_addresses=${addresses.join(',')}`; + } + + private addresses(address: string): string[] { + if (this.baseTokenAddresses.includes(address)) { + return this.baseTokenAddresses; + } else { + return [address]; + } + } +} + +const unwrapToken = (wrappedAddress: string) => { + const lowercase = wrappedAddress.toLocaleLowerCase(); + + if (Object.keys(aaveWrappedMap).includes(lowercase)) { + return aaveWrappedMap[lowercase as keyof typeof aaveWrappedMap].aToken; + } else { + return lowercase; + } +}; diff --git a/balancer-js/src/modules/data/token-price/index.ts b/balancer-js/src/modules/data/token-prices/index.ts similarity index 63% rename from balancer-js/src/modules/data/token-price/index.ts rename to balancer-js/src/modules/data/token-prices/index.ts index 8b48c0aed..a70204eb7 100644 --- a/balancer-js/src/modules/data/token-price/index.ts +++ b/balancer-js/src/modules/data/token-prices/index.ts @@ -1,2 +1,3 @@ export * from './types'; export * from './static'; +export * from './coingecko'; diff --git a/balancer-js/src/modules/data/token-prices/initial-list.json b/balancer-js/src/modules/data/token-prices/initial-list.json new file mode 100644 index 000000000..3a766bbb5 --- /dev/null +++ b/balancer-js/src/modules/data/token-prices/initial-list.json @@ -0,0 +1,907 @@ +[ + { + "chainId": 1, + "address": "0x8888801af4d980682e47f1a9036e589479e835c5", + "symbol": "mph" + }, + { + "chainId": 1, + "address": "0x27054b13b1b798b345b591a4d22e6562d47ea75a", + "symbol": "ast" + }, + { + "chainId": 1, + "address": "0x3301ee63fb29f863f2333bd4466acb46cd8323e6", + "symbol": "akita" + }, + { + "chainId": 1, + "address": "0x616e8bfa43f920657b3497dbf40d6b1a02d4608d", + "symbol": "aurabal" + }, + { + "chainId": 1, + "address": "0xc0c293ce456ff0ed870add98a0828dd4d2903dbf", + "symbol": "aura" + }, + { + "chainId": 1, + "address": "0x3472a5a71965499acd81997a54bba8d852c6e53d", + "symbol": "badger" + }, + { + "chainId": 1, + "address": "0xba100000625a3754423978a60c9317c58a424e3d", + "symbol": "bal" + }, + { + "chainId": 1, + "address": "0x804cdb9116a10bb78768d3252355a1b18067bf8f", + "symbol": "bb-a-dai" + }, + { + "chainId": 1, + "address": "0x9210f1204b5a24742eba12f710636d76240df3d0", + "symbol": "bb-a-usdc" + }, + { + "chainId": 1, + "address": "0x2bbf681cc4eb09218bee85ea2a5d3d13fa40fc0c", + "symbol": "bb-a-usdt" + }, + { + "chainId": 1, + "address": "0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb2", + "symbol": "bb-a-usd" + }, + { + "chainId": 1, + "address": "0x2d94aa3e47d9d5024503ca8491fce9a2fb4da198", + "symbol": "bank" + }, + { + "chainId": 1, + "address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef", + "symbol": "bat" + }, + { + "chainId": 1, + "address": "0xf17e65822b568b3903685a7c9f496cf7656cc6c2", + "symbol": "bico" + }, + { + "chainId": 1, + "address": "0x799ebfabe77a6e34311eeee9825190b9ece32824", + "symbol": "btrst" + }, + { + "chainId": 1, + "address": "0x514910771af9ca656af840dff83e8264ecf986ca", + "symbol": "link" + }, + { + "chainId": 1, + "address": "0x3506424f91fd33084466f402d5d97f05f8e3b4af", + "symbol": "chz" + }, + { + "chainId": 1, + "address": "0x41e5560054824ea6b0732e656e3ad64e20e94e45", + "symbol": "cvc" + }, + { + "chainId": 1, + "address": "0xc00e94cb662c3520282e6f5717214004a7f26888", + "symbol": "comp" + }, + { + "chainId": 1, + "address": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", + "symbol": "cow" + }, + { + "chainId": 1, + "address": "0xd533a949740bb3306d119cc777fa900ba034cd52", + "symbol": "crv" + }, + { + "chainId": 1, + "address": "0x6b175474e89094c44da98b954eedeac495271d0f", + "symbol": "dai" + }, + { + "chainId": 1, + "address": "0xf2051511b9b121394fa75b8f7d4e7424337af687", + "symbol": "haus" + }, + { + "chainId": 1, + "address": "0x888888435fde8e7d4c54cab67f206e4199454c60", + "symbol": "dfx" + }, + { + "chainId": 1, + "address": "0x798d1be841a82a273720ce31c822c61a67a601c3", + "symbol": "digg" + }, + { + "chainId": 1, + "address": "0xf629cbd94d3791c9250152bd8dfbdf380e2a3b9c", + "symbol": "enj" + }, + { + "chainId": 1, + "address": "0xc18360217d8f7ab5e7c516566761ea12ce7f9d72", + "symbol": "ens" + }, + { + "chainId": 1, + "address": "0x4e15361fd6b4bb609fa63c81a2be19d873717870", + "symbol": "ftm" + }, + { + "chainId": 1, + "address": "0x956f47f50a910163d8bf957cf5846d573e7f87ca", + "symbol": "fei" + }, + { + "chainId": 1, + "address": "0xed1480d12be41d92f36f5f7bdd88212e381a3677", + "symbol": "fdt" + }, + { + "chainId": 1, + "address": "0x586aa273f262909eef8fa02d90ab65f5015e0516", + "symbol": "fiat" + }, + { + "chainId": 1, + "address": "0xde30da39c46104798bb5aa3fe8b9e0e1f348163f", + "symbol": "gtc" + }, + { + "chainId": 1, + "address": "0x900db999074d9277c5da2a43f252d74366230da0", + "symbol": "giv" + }, + { + "chainId": 1, + "address": "0x6810e776880c02933d47db1b9fc05908e5386b96", + "symbol": "gno" + }, + { + "chainId": 1, + "address": "0xba485b556399123261a5f9c95d413b4f93107407", + "symbol": "graviaura" + }, + { + "chainId": 1, + "address": "0x3ec8798b81485a254928b70cda1cf0a2bb0b74d7", + "symbol": "gro" + }, + { + "chainId": 1, + "address": "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f", + "symbol": "snx" + }, + { + "chainId": 1, + "address": "0x5a98fcbea516cf06857215779fd812ca3bef1b32", + "symbol": "ldo" + }, + { + "chainId": 1, + "address": "0x6dea81c8171d0ba574754ef6f8b412f2ed88c54d", + "symbol": "lqty" + }, + { + "chainId": 1, + "address": "0x5f98805a4e8be255a32880fdec7f6728c6568ba0", + "symbol": "lusd" + }, + { + "chainId": 1, + "address": "0x965d79f1a1016b574a62986e13ca8ab04dfdd15c", + "symbol": "m2" + }, + { + "chainId": 1, + "address": "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2", + "symbol": "mkr" + }, + { + "chainId": 1, + "address": "0xd084944d3c05cd115c09d072b9f44ba3e0e45921", + "symbol": "fold" + }, + { + "chainId": 1, + "address": "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0", + "symbol": "matic" + }, + { + "chainId": 1, + "address": "0xa3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2", + "symbol": "mta" + }, + { + "chainId": 1, + "address": "0x4b13006980acb09645131b91d259eaa111eaf5ba", + "symbol": "myc" + }, + { + "chainId": 1, + "address": "0x333a4823466879eef910a04d473505da62142069", + "symbol": "nation" + }, + { + "chainId": 1, + "address": "0xcfeaead4947f0705a14ec42ac3d44129e1ef3ed5", + "symbol": "note" + }, + { + "chainId": 1, + "address": "0x967da4048cd07ab37855c090aaf366e4ce1b9f48", + "symbol": "ocean" + }, + { + "chainId": 1, + "address": "0x64aa3364f17a4d01c6f1751fd97c2bd3d7e7f1d5", + "symbol": "ohm" + }, + { + "chainId": 1, + "address": "0xab846fb6c81370327e784ae7cbb6d6a6af6ff4bf", + "symbol": "pal" + }, + { + "chainId": 1, + "address": "0xcafe001067cdef266afb7eb5a286dcfd277f3de5", + "symbol": "psp" + }, + { + "chainId": 1, + "address": "0x68037790a0229e9ce6eaa8a99ea92964106c4703", + "symbol": "par" + }, + { + "chainId": 1, + "address": "0x45804880de22913dafe09f4980848ece6ecbaf78", + "symbol": "paxg" + }, + { + "chainId": 1, + "address": "0x89ab32156e46f46d02ade3fecbe5fc4243b9aaed", + "symbol": "pnt" + }, + { + "chainId": 1, + "address": "0x9992ec3cf6a55b00978cddf2b27bc6882d88d1ec", + "symbol": "poly" + }, + { + "chainId": 1, + "address": "0x43d4a3cd90ddd2f8f4f693170c9c8098163502ad", + "symbol": "d2d" + }, + { + "chainId": 1, + "address": "0xeb4c2781e4eba804ce9a9803c67d0893436bb27d", + "symbol": "renbtc" + }, + { + "chainId": 1, + "address": "0x408e41876cccdc0f92210600ef50372656052a38", + "symbol": "ren" + }, + { + "chainId": 1, + "address": "0xfb5453340c03db5ade474b27e68b6a9c6b2823eb", + "symbol": "robot" + }, + { + "chainId": 1, + "address": "0xd33526068d116ce69f19a9ee46f0bd304f21a51f", + "symbol": "rpl" + }, + { + "chainId": 1, + "address": "0xae78736cd615f374d3085123a210448e74fc6393", + "symbol": "reth" + }, + { + "chainId": 1, + "address": "0xfe18be6b3bd88a2d2a7f928d00292e7a9963cfc6", + "symbol": "sbtc" + }, + { + "chainId": 1, + "address": "0x476c5e26a75bd202a9683ffd34359c0cc15be0ff", + "symbol": "srm" + }, + { + "chainId": 1, + "address": "0x35e78b3982e87ecfd5b3f3265b601c046cdbe232", + "symbol": "xai" + }, + { + "chainId": 1, + "address": "0x3affcca64c2a6f4e3b6bd9c64cd2c969efd1ecbe", + "symbol": "dsla" + }, + { + "chainId": 1, + "address": "0xf24d8651578a55b0c119b9910759a351a3458895", + "symbol": "sdbal" + }, + { + "chainId": 1, + "address": "0x11c1a6b3ed6bb362954b29d3183cfa97a0c806aa", + "symbol": "str" + }, + { + "chainId": 1, + "address": "0x8f693ca8d21b157107184d29d398a8d082b38b76", + "symbol": "data" + }, + { + "chainId": 1, + "address": "0x470ebf5f030ed85fc1ed4c2d36b9dd02e77cf1b7", + "symbol": "temple" + }, + { + "chainId": 1, + "address": "0xa36fdbbae3c9d55a1d67ee5821d53b50b63a1ab9", + "symbol": "temp" + }, + { + "chainId": 1, + "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "symbol": "usdt" + }, + { + "chainId": 1, + "address": "0x9c4a4204b79dd291d6b6571c5be8bbcd0622f050", + "symbol": "tcr" + }, + { + "chainId": 1, + "address": "0x226f7b842e0f0120b7e194d05432b3fd14773a9d", + "symbol": "unn" + }, + { + "chainId": 1, + "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "symbol": "uni" + }, + { + "chainId": 1, + "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "symbol": "usdc" + }, + { + "chainId": 1, + "address": "0x81f8f0bb1cb2a06649e51913a151f0e7ef6fa321", + "symbol": "vita" + }, + { + "chainId": 1, + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "symbol": "weth" + }, + { + "chainId": 1, + "address": "0xedb171c18ce90b633db442f2a6f72874093b49ef", + "symbol": "wampl" + }, + { + "chainId": 1, + "address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", + "symbol": "wbtc" + }, + { + "chainId": 1, + "address": "0xf203ca1769ca8e9e8fe1da9d147db68b6c919817", + "symbol": "wncg" + }, + { + "chainId": 1, + "address": "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", + "symbol": "wsteth" + }, + { + "chainId": 1, + "address": "0x79c71d3436f39ce382d0f58f1b011d88100b9d91", + "symbol": "xns" + }, + { + "chainId": 1, + "address": "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e", + "symbol": "yfi" + }, + { + "chainId": 1, + "address": "0xbcca60bb61934080951369a648fb03df4f96263c", + "symbol": "ausdc" + }, + { + "chainId": 1, + "address": "0x028171bca77440897b824ca71d1c56cac55b68a3", + "symbol": "adai" + }, + { + "chainId": 1, + "address": "0x3ed3b47dd13ec9a98b44e6204a523e766b225811", + "symbol": "ausdt" + }, + { + "chainId": 137, + "address": "0x9c2c5fd7b07e95ee044ddeba0e97a665f142394f", + "symbol": "1inch" + }, + { + "chainId": 137, + "address": "0xd6df932a45c0f255f85145f286ea0b292b21c90b", + "symbol": "aave" + }, + { + "chainId": 137, + "address": "0xc3fdbadc7c795ef1d6ba111e06ff8f16a20ea539", + "symbol": "addy" + }, + { + "chainId": 137, + "address": "0xf84bd51eab957c2e7b7d646a3427c5a50848281d", + "symbol": "agar" + }, + { + "chainId": 137, + "address": "0x033d942a6b495c4071083f4cde1f17e986fe856c", + "symbol": "aga" + }, + { + "chainId": 137, + "address": "0x0e9b89007eee9c958c0eda24ef70723c2c93dd58", + "symbol": "amaticc" + }, + { + "chainId": 137, + "address": "0x034b2090b579228482520c589dbd397c53fc51cc", + "symbol": "vision" + }, + { + "chainId": 137, + "address": "0x2c89bbc92bd86f8075d1decc58c7f4e0107f286b", + "symbol": "avax" + }, + { + "chainId": 137, + "address": "0x49690541e3f6e933a9aa3cffee6010a7bb5b72d7", + "symbol": "axiav3" + }, + { + "chainId": 137, + "address": "0x9a71012b13ca4d3d0cdc72a177df3ef03b0e76a3", + "symbol": "bal" + }, + { + "chainId": 137, + "address": "0xdb7cb471dd0b49b29cab4a1c14d070f27216a0ab", + "symbol": "bank" + }, + { + "chainId": 137, + "address": "0xfbdd194376de19a88118e84e279b977f165d01b8", + "symbol": "bifi" + }, + { + "chainId": 137, + "address": "0xd6ca869a4ec9ed2c7e618062cdc45306d8dbbc14", + "symbol": "btc2x-fli-p" + }, + { + "chainId": 137, + "address": "0x53e0bca35ec356bd5dddfebbd1fc0fd03fabad39", + "symbol": "link" + }, + { + "chainId": 137, + "address": "0x172370d5cd63279efa6d502dab29171933a610af", + "symbol": "crv" + }, + { + "chainId": 137, + "address": "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063", + "symbol": "dai" + }, + { + "chainId": 137, + "address": "0x1d607faa0a51518a7728580c238d912747e71f7a", + "symbol": "data" + }, + { + "chainId": 137, + "address": "0x85955046df4668e1dd369d2de9f3aeb98dd2a369", + "symbol": "dpi" + }, + { + "chainId": 137, + "address": "0xe7804d91dfcde7f776c90043e03eaa6df87e6395", + "symbol": "dfx" + }, + { + "chainId": 137, + "address": "0xf28164a485b0b2c90639e47b0f377b4a438a16b1", + "symbol": "dquick" + }, + { + "chainId": 137, + "address": "0x45c32fa6df82ead1e2ef74d17b76547eddfaff89", + "symbol": "frax" + }, + { + "chainId": 137, + "address": "0x50b728d8d964fd00c2d0aad81718b71311fef68a", + "symbol": "snx" + }, + { + "chainId": 137, + "address": "0x72928d5436ff65e57f72d5566dcd3baedc649a88", + "symbol": "hdao" + }, + { + "chainId": 137, + "address": "0x3ad707da309f3845cd602059901e39c4dcd66473", + "symbol": "eth2x-fli-p" + }, + { + "chainId": 137, + "address": "0x4f025829c4b13df652f38abd2ab901185ff1e609", + "symbol": "ieth-fli-p" + }, + { + "chainId": 137, + "address": "0x340f412860da7b7823df372a2b59ff78b7ae6abc", + "symbol": "imatic-fli-p" + }, + { + "chainId": 137, + "address": "0xf287d97b6345bad3d88856b26fb7c0ab3f2c7976", + "symbol": "matic2x-fli-p" + }, + { + "chainId": 137, + "address": "0x130ce4e4f76c2265f94a961d70618562de0bb8d2", + "symbol": "ibtc-fli-p" + }, + { + "chainId": 137, + "address": "0x596ebe76e2db4470966ea395b0d063ac6197a8c5", + "symbol": "jrt" + }, + { + "chainId": 137, + "address": "0x3a58a54c066fdc0f2d55fc9c89f0415c92ebf3c4", + "symbol": "stmatic" + }, + { + "chainId": 137, + "address": "0xf501dd45a1198c2e1b5aef5314a68b9006d842e0", + "symbol": "mta" + }, + { + "chainId": 137, + "address": "0xeaecc18198a475c921b24b8a6c1c1f0f5f3f7ea0", + "symbol": "seed" + }, + { + "chainId": 137, + "address": "0xfe712251173a2cd5f5be2b46bb528328ea3565e1", + "symbol": "mvi" + }, + { + "chainId": 137, + "address": "0xa3fa99a148fa48d14ed51d610c367c61876997f1", + "symbol": "mimatic" + }, + { + "chainId": 137, + "address": "0xa486c6bc102f409180ccb8a94ba045d39f8fc7cb", + "symbol": "nex" + }, + { + "chainId": 137, + "address": "0xe2aa7db6da1dae97c5f5c6914d285fbfcc32a128", + "symbol": "par" + }, + { + "chainId": 137, + "address": "0x580a84c73811e1839f75d86d75d88cca0c241ff4", + "symbol": "qi" + }, + { + "chainId": 137, + "address": "0x831753dd7087cac61ab5644b308642cc1c33dc13", + "symbol": "quick" + }, + { + "chainId": 137, + "address": "0xb5c064f955d8e7f38fe0460c556a72987494ee17", + "symbol": "quick" + }, + { + "chainId": 137, + "address": "0x00e5646f60ac6fb446f621d146b6e1886f002905", + "symbol": "rai" + }, + { + "chainId": 137, + "address": "0x431cd3c9ac9fc73644bf68bf5691f4b83f9e104f", + "symbol": "rbw" + }, + { + "chainId": 137, + "address": "0xdbf31df14b66535af65aac99c32e9ea844e14501", + "symbol": "renbtc" + }, + { + "chainId": 137, + "address": "0x501ace9c35e60f03a2af4d484f49f9b1efde9f40", + "symbol": "solace" + }, + { + "chainId": 137, + "address": "0xfa68fb4628dff1028cfec22b4162fccd0d45efb6", + "symbol": "maticx" + }, + { + "chainId": 137, + "address": "0x0b3f868e0be5597d5db7feb59e1cadbb0fdda50a", + "symbol": "sushi" + }, + { + "chainId": 137, + "address": "0xdf7837de1f2fa4631d716cf2502f8b230f1dcc32", + "symbol": "tel" + }, + { + "chainId": 137, + "address": "0xe6469ba6d2fd6130788e0ea9c0a0515900563b59", + "symbol": "ust" + }, + { + "chainId": 137, + "address": "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", + "symbol": "usdt" + }, + { + "chainId": 137, + "address": "0x5fe2b58c013d7601147dcdd68c143a77499f5531", + "symbol": "grt" + }, + { + "chainId": 137, + "address": "0xbbba073c31bf03b8acf7c28ef0738decf3695683", + "symbol": "sand" + }, + { + "chainId": 137, + "address": "0x2934b36ca9a4b31e633c5be670c8c8b28b6aa015", + "symbol": "thx" + }, + { + "chainId": 137, + "address": "0x2f800db0fdb5223b3c3f354886d907a671414a7f", + "symbol": "bct" + }, + { + "chainId": 137, + "address": "0x2e1ad108ff1d8c782fcbbb89aad783ac49586756", + "symbol": "tusd" + }, + { + "chainId": 137, + "address": "0x3809dcdd5dde24b37abe64a5a339784c3323c44f", + "symbol": "swap" + }, + { + "chainId": 137, + "address": "0x7fbc10850cae055b27039af31bd258430e714c62", + "symbol": "ubt" + }, + { + "chainId": 137, + "address": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "symbol": "usdc" + }, + { + "chainId": 137, + "address": "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619", + "symbol": "weth" + }, + { + "chainId": 137, + "address": "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270", + "symbol": "wmatic" + }, + { + "chainId": 137, + "address": "0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6", + "symbol": "wbtc" + }, + { + "chainId": 137, + "address": "0x24834bbec7e39ef42f4a75eaf8e5b6486d3f0e57", + "symbol": "lunc" + }, + { + "chainId": 137, + "address": "0xf153eff70dc0bf3b085134928daeea248d9b30d0", + "symbol": "xmark" + }, + { + "chainId": 42161, + "address": "0x9f20de1fc9b161b34089cbeae888168b44b03461", + "symbol": "arbis" + }, + { + "chainId": 42161, + "address": "0x040d1edc9569d4bab2d15287dc5a4f10f56a56b8", + "symbol": "bal" + }, + { + "chainId": 42161, + "address": "0x031d35296154279dc1984dcd93e392b1f946737b", + "symbol": "cap" + }, + { + "chainId": 42161, + "address": "0xf97f4df75117a78c1a5a0dbb814af92458539fb4", + "symbol": "link" + }, + { + "chainId": 42161, + "address": "0x354a6da3fcde098f8389cad84b0182725c6c91de", + "symbol": "comp" + }, + { + "chainId": 42161, + "address": "0xf4d48ce3ee1ac3651998971541badbb9a14d7234", + "symbol": "cream" + }, + { + "chainId": 42161, + "address": "0x11cdb42b0eb46d95f990bedd4695a6e3fa034978", + "symbol": "crv" + }, + { + "chainId": 42161, + "address": "0xda10009cbd5d07dd0cecc66161fc93d7c9000da1", + "symbol": "dai" + }, + { + "chainId": 42161, + "address": "0x8038f3c971414fd1fc220ba727f2d4a0fc98cb65", + "symbol": "dht" + }, + { + "chainId": 42161, + "address": "0xf0b5ceefc89684889e5f7e0a7775bd100fcd3709", + "symbol": "dusd" + }, + { + "chainId": 42161, + "address": "0x6c2c06790b3e3e3c38e12ee22f8183b37a13ee55", + "symbol": "dpx" + }, + { + "chainId": 42161, + "address": "0x32eb7902d4134bf98a28b963d26de779af92a212", + "symbol": "rdpx" + }, + { + "chainId": 42161, + "address": "0xc3ae0333f0f34aa734d5493276223d95b8f9cb37", + "symbol": "dxd" + }, + { + "chainId": 42161, + "address": "0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a", + "symbol": "gmx" + }, + { + "chainId": 42161, + "address": "0xa0b862f60edef4452f25b4160f177db44deb6cf1", + "symbol": "gno" + }, + { + "chainId": 42161, + "address": "0xb965029343d55189c25a7f3e0c9394dc0f5d41b1", + "symbol": "ndx" + }, + { + "chainId": 42161, + "address": "0x539bde0d7dbd336b79148aa742883198bbf60342", + "symbol": "magic" + }, + { + "chainId": 42161, + "address": "0x4e352cf164e64adcbad318c3a1e222e9eba4ce42", + "symbol": "mcb" + }, + { + "chainId": 42161, + "address": "0x3f56e0c36d275367b8c502090edf38289b3dea0d", + "symbol": "mimatic" + }, + { + "chainId": 42161, + "address": "0x965772e0e9c84b6f359c8597c891108dcf1c5b1a", + "symbol": "pickle" + }, + { + "chainId": 42161, + "address": "0x6694340fc020c5e6b96567843da2df01b2ce1eb6", + "symbol": "stg" + }, + { + "chainId": 42161, + "address": "0xd4d42f0b6def4ce0383636770ef773390d85c61a", + "symbol": "sushi" + }, + { + "chainId": 42161, + "address": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", + "symbol": "usdt" + }, + { + "chainId": 42161, + "address": "0x23a941036ae778ac51ab04cea08ed6e2fe103614", + "symbol": "grt" + }, + { + "chainId": 42161, + "address": "0xa72159fc390f0e3c6d415e658264c7c4051e9b87", + "symbol": "tcr" + }, + { + "chainId": 42161, + "address": "0x4d15a3a2286d883af0aa1b3f21367843fac63e07", + "symbol": "tusd" + }, + { + "chainId": 42161, + "address": "0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0", + "symbol": "uni" + }, + { + "chainId": 42161, + "address": "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8", + "symbol": "usdc" + }, + { + "chainId": 42161, + "address": "0xa684cd057951541187f288294a1e1c2646aa2d24", + "symbol": "vsta" + }, + { + "chainId": 42161, + "address": "0x64343594ab9b56e99087bfa6f2335db24c2d1f17", + "symbol": "vst" + }, + { + "chainId": 42161, + "address": "0x82af49447d8a07e3bd95bd0d56f35241523fbab1", + "symbol": "weth" + }, + { + "chainId": 42161, + "address": "0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f", + "symbol": "wbtc" + }, + { + "chainId": 42161, + "address": "0x82e3a8f066a6989666b031d916c43672085b1582", + "symbol": "yfi" + } +] diff --git a/balancer-js/src/modules/data/token-price/static.ts b/balancer-js/src/modules/data/token-prices/static.ts similarity index 91% rename from balancer-js/src/modules/data/token-price/static.ts rename to balancer-js/src/modules/data/token-prices/static.ts index d5601c642..221a3475d 100644 --- a/balancer-js/src/modules/data/token-price/static.ts +++ b/balancer-js/src/modules/data/token-prices/static.ts @@ -55,4 +55,12 @@ export class StaticTokenPriceProvider implements TokenPriceProvider { if (!price) return; return price; } + + async findBy(attribute: string, value: string): Promise { + if (attribute != 'address') { + return undefined; + } + + return this.find(value); + } } diff --git a/balancer-js/src/modules/data/token-price/types.ts b/balancer-js/src/modules/data/token-prices/types.ts similarity index 100% rename from balancer-js/src/modules/data/token-price/types.ts rename to balancer-js/src/modules/data/token-prices/types.ts From 5837fe96fedb144ca49ecd05663a357387150c3c Mon Sep 17 00:00:00 2001 From: bronco Date: Fri, 24 Jun 2022 16:21:10 +0200 Subject: [PATCH 26/65] BAL emissions data --- .../src/modules/data/bal/emissions.spec.ts | 67 +++++++++++++ balancer-js/src/modules/data/bal/emissions.ts | 95 +++++++++++++++++++ balancer-js/src/modules/data/index.ts | 1 + 3 files changed, 163 insertions(+) create mode 100644 balancer-js/src/modules/data/bal/emissions.spec.ts create mode 100644 balancer-js/src/modules/data/bal/emissions.ts diff --git a/balancer-js/src/modules/data/bal/emissions.spec.ts b/balancer-js/src/modules/data/bal/emissions.spec.ts new file mode 100644 index 000000000..efa07101b --- /dev/null +++ b/balancer-js/src/modules/data/bal/emissions.spec.ts @@ -0,0 +1,67 @@ +import { expect } from 'chai'; +import { + total, + between, + weekly, + START_EPOCH_TIME, + INITIAL_RATE, +} from './emissions'; + +describe('bal emissions', () => { + describe('per week', () => { + it('stay as initialised within first year', () => { + const currentEpoch = Date.parse('2022-04-01') / 1000; + const rate = weekly(currentEpoch); + expect(rate).to.eq(INITIAL_RATE); + }); + + it('decrease in subsequent years', () => { + const currentEpoch = Date.parse('2023-04-01') / 1000; + const rate = weekly(currentEpoch); + expect(rate).to.eq(INITIAL_RATE / 2 ** (1 / 4)); + }); + }); + + describe('within range', () => { + it('equals to INITIAL_RATE * 52 for the first year', () => { + const start = START_EPOCH_TIME; + const end = START_EPOCH_TIME + 365 * 86400; + const emissions = between(start, end); + expect(emissions).to.eq(total(0)); + }); + + it('equals to INITIAL_RATE * 10 for the first 10 weeks', () => { + const start = START_EPOCH_TIME; + const end = START_EPOCH_TIME + 10 * 7 * 86400; + const emissions = between(start, end); + expect(emissions).to.eq(INITIAL_RATE * 10); + }); + + it('equals to INITIAL_RATE * 10 for the last 10 weeks', () => { + const end = START_EPOCH_TIME + 365 * 86400; + const start = end - 10 * 7 * 86400; + const emissions = between(start, end); + expect(emissions).to.eq(INITIAL_RATE * 10); + }); + + it('equals to sum of total emissions for full years', () => { + const start = START_EPOCH_TIME; + const end = start + 3 * 365 * 86400; + // Ignore precision assuming it's for frontend only + const emissions = Math.round(between(start, end)); + const expected = Math.round(total(0) + total(1) + total(2)); + expect(emissions).to.eq(expected); + }); + + it('takes partial epochs into account', () => { + const start = START_EPOCH_TIME + 10 * 86400; // 355 days in first epoch + const end = START_EPOCH_TIME + 3 * 365 * 86400 - 100 * 86400; // 265 days in third epoch + // Ignore precision assuming it's for frontend only + const emissions = Math.round(between(start, end)); + const expected = Math.round( + total(0) * (355 / 365) + total(1) + total(2) * (265 / 365) + ); + expect(emissions).to.eq(expected); + }); + }); +}); diff --git a/balancer-js/src/modules/data/bal/emissions.ts b/balancer-js/src/modules/data/bal/emissions.ts new file mode 100644 index 000000000..e61bc7c82 --- /dev/null +++ b/balancer-js/src/modules/data/bal/emissions.ts @@ -0,0 +1,95 @@ +/** + * Weekly Bal emissions are fixed / year according to: + * https://docs.google.com/spreadsheets/d/1FY0gi596YWBOTeu_mrxhWcdF74SwKMNhmu0qJVgs0KI/edit#gid=0 + * + * Using regular numbers for simplicity assuming frontend use only. + * + * Calculation source + * https://github.com/balancer-labs/balancer-v2-monorepo/blob/master/pkg/liquidity-mining/contracts/BalancerTokenAdmin.sol + */ + +export const INITIAL_RATE = 145000; +export const START_EPOCH_TIME = 1648465251; +const RATE_REDUCTION_TIME = 365 * 86400; +const RATE_REDUCTION_COEFFICIENT = 2 ** (1 / 4); + +/** + * Weekly BAL emissions + * + * @param currentTimestamp used to get the epoch + * @returns BAL emitted in a week + */ +export const weekly = ( + currentTimestamp: number = Math.round(new Date().getTime() / 1000) +): number => { + const miningEpoch = Math.floor( + (currentTimestamp - START_EPOCH_TIME) / RATE_REDUCTION_TIME + ); + + const rate = INITIAL_RATE * RATE_REDUCTION_COEFFICIENT ** -miningEpoch; + + return rate; +}; + +/** + * Total BAL emitted in epoch (1 year) + * + * @param epoch starting from 0 for the first year of emissions + * @returns BAL emitted in epoch + */ +export const total = (epoch: number): number => { + const weeklyRate = INITIAL_RATE * RATE_REDUCTION_COEFFICIENT ** -epoch; + const dailyRate = weeklyRate / 7; + + return dailyRate * 365; +}; + +/** + * Total BAL emitted between two timestamps + * + * @param start starting timestamp + * @param end ending timestamp + * @returns BAL emitted in period + */ +export const between = (start: number, end: number): number => { + if (start < START_EPOCH_TIME) { + throw 'start timestamp before emission schedule deployment'; + } + if (end < start) { + throw 'cannot finish before starting'; + } + + let totalEmissions = 0; + + const startingEpoch = Math.floor( + (start - START_EPOCH_TIME) / RATE_REDUCTION_TIME + ); + const endingEpoch = Math.floor( + (end - START_EPOCH_TIME) / RATE_REDUCTION_TIME + ); + + for ( + let currentEpoch = startingEpoch; + currentEpoch <= endingEpoch; + currentEpoch++ + ) { + totalEmissions += total(currentEpoch); + } + + // Subtract what isn't emmited within the time range + const startingEpochEnd = + START_EPOCH_TIME + RATE_REDUCTION_TIME * (startingEpoch + 1); + const endingEpochStart = START_EPOCH_TIME + RATE_REDUCTION_TIME * endingEpoch; + + const secondsInStartingEpoch = startingEpochEnd - start; + const secondsInEndingEpoch = end - endingEpochStart; + + totalEmissions -= + (total(startingEpoch) * (RATE_REDUCTION_TIME - secondsInStartingEpoch)) / + RATE_REDUCTION_TIME; + totalEmissions -= + (total(endingEpoch) * (RATE_REDUCTION_TIME - secondsInEndingEpoch)) / + RATE_REDUCTION_TIME; + + return totalEmissions; +}; diff --git a/balancer-js/src/modules/data/index.ts b/balancer-js/src/modules/data/index.ts index 301c399eb..2d1f416a1 100644 --- a/balancer-js/src/modules/data/index.ts +++ b/balancer-js/src/modules/data/index.ts @@ -1,3 +1,4 @@ +export * as balEmissions from './bal/emissions'; export * from './pool'; export * from './token'; export * from './token-prices'; From 86502ac6f15960a35ad82258221253d7f77efb85 Mon Sep 17 00:00:00 2001 From: bronco Date: Fri, 1 Jul 2022 12:18:48 +0200 Subject: [PATCH 27/65] gauge controller data --- balancer-js/src/lib/constants/config.ts | 7 +++ .../data/gauge-controller/multicall.spec.ts | 41 +++++++++++++++++ .../data/gauge-controller/multicall.ts | 46 +++++++++++++++++++ balancer-js/src/modules/data/index.ts | 1 + balancer-js/src/types.ts | 1 + 5 files changed, 96 insertions(+) create mode 100644 balancer-js/src/modules/data/gauge-controller/multicall.spec.ts create mode 100644 balancer-js/src/modules/data/gauge-controller/multicall.ts diff --git a/balancer-js/src/lib/constants/config.ts b/balancer-js/src/lib/constants/config.ts index 5d99726dc..717399936 100644 --- a/balancer-js/src/lib/constants/config.ts +++ b/balancer-js/src/lib/constants/config.ts @@ -11,6 +11,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', multicall: '0xeefba1e63905ef1d7acba5a8513c70307c1ce441', lidoRelayer: '0xdcdbf71A870cc60C6F9B621E28a7D3Ffd6Dd4965', + gaugeController: '0xc128468b7ce63ea702c1f104d55a2566b13d3abd', }, tokens: { wrappedNativeAsset: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', @@ -42,6 +43,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { contracts: { vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', multicall: '0xa1B2b503959aedD81512C37e9dce48164ec6a94d', + gaugeController: '', }, tokens: { wrappedNativeAsset: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', @@ -61,6 +63,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { contracts: { vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', multicall: '0x269ff446d9892c9e19082564df3f5e8741e190a1', + gaugeController: '', }, tokens: { wrappedNativeAsset: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', @@ -80,6 +83,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { contracts: { vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', multicall: '0x2cc8688C5f75E365aaEEb4ea8D6a480405A48D2A', + gaugeController: '', }, tokens: { wrappedNativeAsset: '0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1', @@ -99,6 +103,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { contracts: { vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', multicall: '0x53c43764255c17bd724f74c4ef150724ac50a3ed', + gaugeController: '', }, tokens: { wrappedNativeAsset: '0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1', @@ -117,6 +122,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { contracts: { vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', multicall: '0x42ad527de7d4e9d9d011ac45b31d8551f8fe9821', + gaugeController: '', }, tokens: { wrappedNativeAsset: '0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1', @@ -136,6 +142,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { contracts: { vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', multicall: '0x77dCa2C955b15e9dE4dbBCf1246B4B85b651e50e', + gaugeController: '0xBB1CE49b16d55A1f2c6e88102f32144C7334B116', }, tokens: { wrappedNativeAsset: '0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1', diff --git a/balancer-js/src/modules/data/gauge-controller/multicall.spec.ts b/balancer-js/src/modules/data/gauge-controller/multicall.spec.ts new file mode 100644 index 000000000..d3b178b2a --- /dev/null +++ b/balancer-js/src/modules/data/gauge-controller/multicall.spec.ts @@ -0,0 +1,41 @@ +import { JsonRpcProvider } from '@ethersproject/providers'; +import { expect } from 'chai'; +import { GaugeControllerMulticallRepository } from './multicall'; + +describe('Gauge controller', () => { + const provider = new JsonRpcProvider('http://127.0.0.1:8545', 1); + const fetcher = new GaugeControllerMulticallRepository( + '0xeefba1e63905ef1d7acba5a8513c70307c1ce441', + '0xc128468b7ce63ea702c1f104d55a2566b13d3abd', + provider + ); + + it('is fetching relative weights for current period', async () => { + const weights = await fetcher.getRelativeWeights([ + '0x9f65d476dd77e24445a48b4fecdea81afaa63480', + '0xcb664132622f29943f67fa56ccfd1e24cc8b4995', + '0xaf50825b010ae4839ac444f6c12d44b96819739b', + ]); + + expect(Object.keys(weights).length).to.eq(3); + expect(weights['0x9f65d476dd77e24445a48b4fecdea81afaa63480']).to.satisfy( + (n: number) => n >= 0 + ); + }).timeout(20000); + + it('is fetching relative weights for next period', async () => { + const weights = await fetcher.getRelativeWeights( + [ + '0x9f65d476dd77e24445a48b4fecdea81afaa63480', + '0xcb664132622f29943f67fa56ccfd1e24cc8b4995', + '0xaf50825b010ae4839ac444f6c12d44b96819739b', + ], + Math.floor(Date.now() / 1000) + 7 * 24 * 3600 + ); + + expect(Object.keys(weights).length).to.eq(3); + expect(weights['0x9f65d476dd77e24445a48b4fecdea81afaa63480']).to.satisfy( + (n: number) => n >= 0 + ); + }).timeout(20000); +}); diff --git a/balancer-js/src/modules/data/gauge-controller/multicall.ts b/balancer-js/src/modules/data/gauge-controller/multicall.ts new file mode 100644 index 000000000..243d87918 --- /dev/null +++ b/balancer-js/src/modules/data/gauge-controller/multicall.ts @@ -0,0 +1,46 @@ +import { Interface } from '@ethersproject/abi'; +import { getAddress } from '@ethersproject/address'; +import { Contract } from '@ethersproject/contracts'; +import { Provider } from '@ethersproject/providers'; +import { formatUnits } from '@ethersproject/units'; +import { Multicall } from '@/modules/contracts/multicall'; + +const gaugeControllerInterface = new Interface([ + 'function gauge_relative_weight(address gauge, uint timestamp) view returns (uint)', +]); + +export class GaugeControllerMulticallRepository { + multicall: Contract; + + constructor( + multicallAddress: string, + private gaugeControllerAddress: string, + provider: Provider + ) { + this.multicall = Multicall(multicallAddress, provider); + } + + async getRelativeWeights( + gaugeAddresses: string[], + timestamp?: number + ): Promise<{ [gaugeAddress: string]: number }> { + const payload = gaugeAddresses.map((gaugeAddress) => [ + this.gaugeControllerAddress, + gaugeControllerInterface.encodeFunctionData('gauge_relative_weight', [ + getAddress(gaugeAddress), + timestamp || Math.floor(Date.now() / 1000), + ]), + ]); + const [, res] = await this.multicall.aggregate(payload); + + const weights = gaugeAddresses.reduce( + (p: { [key: string]: number }, a, i) => { + p[a] ||= parseFloat(formatUnits(res[i], 18)); + return p; + }, + {} + ); + + return weights; + } +} diff --git a/balancer-js/src/modules/data/index.ts b/balancer-js/src/modules/data/index.ts index 2d1f416a1..703b9e5b9 100644 --- a/balancer-js/src/modules/data/index.ts +++ b/balancer-js/src/modules/data/index.ts @@ -1,4 +1,5 @@ export * as balEmissions from './bal/emissions'; +export * from './gauge-controller/multicall'; export * from './pool'; export * from './token'; export * from './token-prices'; diff --git a/balancer-js/src/types.ts b/balancer-js/src/types.ts index 41e44b35f..3ad8be8e8 100644 --- a/balancer-js/src/types.ts +++ b/balancer-js/src/types.ts @@ -37,6 +37,7 @@ export interface ContractAddresses { vault: string; multicall: string; lidoRelayer?: string; + gaugeController: string; } export interface BalancerNetworkConfig { From f6f50954e027fb16c310b4dc192193a099bac850 Mon Sep 17 00:00:00 2001 From: bronco Date: Sat, 16 Jul 2022 11:58:54 +0200 Subject: [PATCH 28/65] liquidity gauges data --- balancer-js/src/modules/data/index.ts | 1 + .../modules/data/liquidity-gauges/index.ts | 3 + .../data/liquidity-gauges/multicall.spec.ts | 63 ++++++ .../data/liquidity-gauges/multicall.ts | 202 ++++++++++++++++++ .../modules/data/liquidity-gauges/provider.ts | 120 +++++++++++ .../modules/data/liquidity-gauges/subgraph.ts | 57 +++++ 6 files changed, 446 insertions(+) create mode 100644 balancer-js/src/modules/data/liquidity-gauges/index.ts create mode 100644 balancer-js/src/modules/data/liquidity-gauges/multicall.spec.ts create mode 100644 balancer-js/src/modules/data/liquidity-gauges/multicall.ts create mode 100644 balancer-js/src/modules/data/liquidity-gauges/provider.ts create mode 100644 balancer-js/src/modules/data/liquidity-gauges/subgraph.ts diff --git a/balancer-js/src/modules/data/index.ts b/balancer-js/src/modules/data/index.ts index 703b9e5b9..d442456ba 100644 --- a/balancer-js/src/modules/data/index.ts +++ b/balancer-js/src/modules/data/index.ts @@ -1,5 +1,6 @@ export * as balEmissions from './bal/emissions'; export * from './gauge-controller/multicall'; +export * from './liquidity-gauges'; export * from './pool'; export * from './token'; export * from './token-prices'; diff --git a/balancer-js/src/modules/data/liquidity-gauges/index.ts b/balancer-js/src/modules/data/liquidity-gauges/index.ts new file mode 100644 index 000000000..5e158bc3a --- /dev/null +++ b/balancer-js/src/modules/data/liquidity-gauges/index.ts @@ -0,0 +1,3 @@ +export * from './multicall'; +export * from './provider'; +export * from './subgraph'; diff --git a/balancer-js/src/modules/data/liquidity-gauges/multicall.spec.ts b/balancer-js/src/modules/data/liquidity-gauges/multicall.spec.ts new file mode 100644 index 000000000..154e00c65 --- /dev/null +++ b/balancer-js/src/modules/data/liquidity-gauges/multicall.spec.ts @@ -0,0 +1,63 @@ +import { JsonRpcProvider } from '@ethersproject/providers'; +import { expect } from 'chai'; +import { LiquidityGaugesMulticallRepository } from './multicall'; +import { LiquidityGaugesSubgraphRepository } from './subgraph'; + +describe('Liquidity gauge multicall', () => { + const provider = new JsonRpcProvider('http://127.0.0.1:8545', 1); + const fetcher = new LiquidityGaugesMulticallRepository( + '0xeefba1e63905ef1d7acba5a8513c70307c1ce441', + provider + ); + + it('is fetching total supplies', async () => { + const supplies = await fetcher.getTotalSupplies([ + '0x9f65d476dd77e24445a48b4fecdea81afaa63480', + '0xcb664132622f29943f67fa56ccfd1e24cc8b4995', + '0xaf50825b010ae4839ac444f6c12d44b96819739b', + ]); + + expect(Object.keys(supplies).length).to.eq(3); + expect(supplies['0x9f65d476dd77e24445a48b4fecdea81afaa63480']).to.satisfy( + (n: number) => n >= 0 + ); + }).timeout(60000); + + it('is fetching working supplies', async () => { + const supplies = await fetcher.getWorkingSupplies([ + '0x9f65d476dd77e24445a48b4fecdea81afaa63480', + '0xcb664132622f29943f67fa56ccfd1e24cc8b4995', + '0xaf50825b010ae4839ac444f6c12d44b96819739b', + ]); + + expect(Object.keys(supplies).length).to.eq(3); + expect(supplies['0x9f65d476dd77e24445a48b4fecdea81afaa63480']).to.satisfy( + (n: number) => n >= 0 + ); + }).timeout(60000); + + describe('with all gauges from subgraph', () => { + const gauges = new LiquidityGaugesSubgraphRepository( + 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges' + ); + + it('is fetching reward counts', async () => { + const list = await gauges.fetch(); + const gaugeAddresses = list.map((g) => g.id); + const rewardCounts = await fetcher.getRewardCounts(gaugeAddresses); + + expect(rewardCounts[Object.keys(rewardCounts)[0]]).to.be.gte(0); + }).timeout(60000); + + it('is fetching reward data', async () => { + const list = await gauges.fetch(); + const gaugeAddresses = list.map((g) => g.id); + const rewardData = await fetcher.getRewardData(gaugeAddresses); + const firstToken = rewardData[Object.keys(rewardData)[0]]; + + expect(firstToken[Object.keys(firstToken)[0]].token).to.eq( + '0x0000000000000000000000000000000000000000' + ); + }).timeout(60000); + }); +}); diff --git a/balancer-js/src/modules/data/liquidity-gauges/multicall.ts b/balancer-js/src/modules/data/liquidity-gauges/multicall.ts new file mode 100644 index 000000000..7e38b478e --- /dev/null +++ b/balancer-js/src/modules/data/liquidity-gauges/multicall.ts @@ -0,0 +1,202 @@ +import { Multicall } from '@/modules/contracts/multicall'; +import { Provider } from '@ethersproject/providers'; +import { Interface } from '@ethersproject/abi'; +import { Contract } from '@ethersproject/contracts'; +import { formatUnits } from '@ethersproject/units'; +import { BigNumber } from '@ethersproject/bignumber'; + +const liquidityGaugeV5Interface = new Interface([ + 'function totalSupply() view returns (uint)', + 'function working_supply() view returns (uint)', + 'function reward_count() view returns (uint)', + 'function reward_tokens(uint rewardIndex) view returns (address)', + 'function reward_data(address rewardToken) view returns (tuple(address token, address distributor, uint period_finish, uint rate, uint last_update, uint integral) data)', +]); + +export interface RewardData { + token: string; // Always 0x0 + distributor: string; + period_finish: BigNumber; + rate: BigNumber; // per second + last_update: BigNumber; + integral: BigNumber; // sum accrued to date +} + +/** + * A lot of code to get liquidity gauge state via RPC multicall. + * TODO: reseach helper contracts or extend subgraph + */ +export class LiquidityGaugesMulticallRepository { + multicall: Contract; + + constructor(multicallAddress: string, provider: Provider) { + this.multicall = Multicall(multicallAddress, provider); + } + + async getTotalSupplies( + gaugeAddresses: string[] + ): Promise<{ [gaugeAddress: string]: number }> { + const payload = gaugeAddresses.map((gaugeAddress) => [ + gaugeAddress, + liquidityGaugeV5Interface.encodeFunctionData('totalSupply', []), + ]); + const [, res] = await this.multicall.aggregate(payload); + // Handle 0x + const res0x = res.map((r: string) => (r == '0x' ? '0x0' : r)); + + const totalSupplies = gaugeAddresses.reduce( + (p: { [key: string]: number }, a, i) => { + p[a] ||= parseFloat(formatUnits(res0x[i], 18)); + return p; + }, + {} + ); + + return totalSupplies; + } + + async getWorkingSupplies( + gaugeAddresses: string[] + ): Promise<{ [gaugeAddress: string]: number }> { + const payload = gaugeAddresses.map((gaugeAddress) => [ + gaugeAddress, + liquidityGaugeV5Interface.encodeFunctionData('working_supply', []), + ]); + const [, res] = await this.multicall.aggregate(payload); + // Handle 0x + const res0x = res.map((r: string) => (r == '0x' ? '0x0' : r)); + + const workingSupplies = gaugeAddresses.reduce( + (p: { [key: string]: number }, a, i) => { + p[a] ||= parseFloat(formatUnits(res0x[i], 18)); + return p; + }, + {} + ); + + return workingSupplies; + } + + async getRewardCounts( + gaugeAddresses: string[] + ): Promise<{ [gaugeAddress: string]: number }> { + const payload = gaugeAddresses.map((gaugeAddress) => [ + gaugeAddress, + liquidityGaugeV5Interface.encodeFunctionData('reward_count', []), + ]); + const [, res] = await this.multicall.aggregate(payload); + // Handle 0x return values + const res0x = res.map((r: string) => (r == '0x' ? '0x0' : r)); + + const rewardCounts = gaugeAddresses.reduce( + (p: { [key: string]: number }, a, i) => { + p[a] ||= parseInt(res0x[i]); + return p; + }, + {} + ); + + return rewardCounts; + } + + async getRewardTokens( + gaugeAddresses: string[], + passingRewardCounts?: { [gaugeAddress: string]: number } + ): Promise<{ [gaugeAddress: string]: string[] }> { + const rewardCounts = + passingRewardCounts || (await this.getRewardCounts(gaugeAddresses)); + const gaugesWithRewards = gaugeAddresses.filter( + (gaugeAddress) => rewardCounts[gaugeAddress] > 0 + ); + const startIndexes = [0]; + const payload = gaugesWithRewards + .map((gaugeAddress, gaugeIndex) => { + const calls = []; + for (let i = 0; i < rewardCounts[gaugeAddress]; i++) { + calls.push([ + gaugeAddress, + liquidityGaugeV5Interface.encodeFunctionData('reward_tokens', [i]), + ]); + } + startIndexes[gaugeIndex + 1] = + startIndexes[gaugeIndex] + rewardCounts[gaugeAddress]; + return calls; + }) + .flat(); + const [, res] = await this.multicall.aggregate(payload); + + const rewardTokens = gaugesWithRewards.reduce( + (p: { [key: string]: string[] }, a, i) => { + const start = startIndexes[i]; + const end = startIndexes[i + 1]; + const tokens: string[] = []; + for (let i = start; i < end; i++) { + tokens.push( + liquidityGaugeV5Interface.decodeFunctionResult( + 'reward_tokens', + res[i] + )[0] + ); + } + p[a] ||= tokens; + return p; + }, + {} + ); + + return rewardTokens; + } + + async getRewardData( + gaugeAddresses: string[], + passingRewardTokens?: { [gaugeAddress: string]: string[] } + ): Promise<{ + [gaugeAddress: string]: { [rewardTokenAddress: string]: RewardData }; + }> { + const rewardTokens = + passingRewardTokens || (await this.getRewardTokens(gaugeAddresses)); + + const startIndexes = [0]; + const payload = Object.keys(rewardTokens) + .map((gaugeAddress, gaugeIndex) => { + const calls = []; + for (let i = 0; i < rewardTokens[gaugeAddress].length; i++) { + calls.push([ + gaugeAddress, + liquidityGaugeV5Interface.encodeFunctionData('reward_data', [ + rewardTokens[gaugeAddress][i], + ]), + ]); + } + startIndexes[gaugeIndex + 1] = + startIndexes[gaugeIndex] + rewardTokens[gaugeAddress].length; + return calls; + }) + .flat(); + const [, res] = (await this.multicall.aggregate(payload)) as [ + unknown, + string[] + ]; + const decoded = res.map( + (r) => liquidityGaugeV5Interface.decodeFunctionResult('reward_data', r)[0] + ); + + const rewardData = Object.keys(rewardTokens).reduce( + (p: { [key: string]: { [key: string]: RewardData } }, a, i) => { + const start = startIndexes[i]; + const data = rewardTokens[a].reduce( + (d: { [key: string]: RewardData }, t, x) => { + d[t] ||= decoded[start + x] as RewardData; + return d; + }, + {} + ); + p[a] ||= data; + return p; + }, + {} + ); + + return rewardData; + } +} diff --git a/balancer-js/src/modules/data/liquidity-gauges/provider.ts b/balancer-js/src/modules/data/liquidity-gauges/provider.ts new file mode 100644 index 000000000..a8d16347b --- /dev/null +++ b/balancer-js/src/modules/data/liquidity-gauges/provider.ts @@ -0,0 +1,120 @@ +import { GaugeControllerMulticallRepository } from '../gauge-controller/multicall'; +import { LiquidityGaugesMulticallRepository, RewardData } from './multicall'; +import { LiquidityGaugesSubgraphRepository } from './subgraph'; +import type { + Maybe, + SubgraphLiquidityGauge, +} from '@/modules/subgraph/subgraph'; +import type { Findable } from '../types'; +import type { Provider } from '@ethersproject/providers'; + +export interface LiquidityGauge { + id: string; + address: string; + name: string; + poolId?: Maybe; + poolAddress: string; + totalSupply: number; + workingSupply: number; + relativeWeight: number; + rewardTokens?: { [tokenAddress: string]: RewardData }; +} + +export class LiquidityGaugeSubgraphRPCProvider + implements Findable +{ + gaugeController: GaugeControllerMulticallRepository; + multicall: LiquidityGaugesMulticallRepository; + subgraph: LiquidityGaugesSubgraphRepository; + totalSupplies: { [gaugeAddress: string]: number } = {}; + workingSupplies: { [gaugeAddress: string]: number } = {}; + relativeWeights: { [gaugeAddress: string]: number } = {}; + rewardTokens: { + [gaugeAddress: string]: { [tokenAddress: string]: RewardData }; + } = {}; + + constructor( + subgraphUrl: string, + multicallAddress: string, + gaugeControllerAddress: string, + provider: Provider + ) { + this.gaugeController = new GaugeControllerMulticallRepository( + multicallAddress, + gaugeControllerAddress, + provider + ); + this.multicall = new LiquidityGaugesMulticallRepository( + multicallAddress, + provider + ); + this.subgraph = new LiquidityGaugesSubgraphRepository(subgraphUrl); + } + + async fetch(): Promise { + const gauges = await this.subgraph.fetch(); + const gaugeAddresses = gauges.map((g) => g.id); + this.totalSupplies = await this.multicall.getTotalSupplies(gaugeAddresses); + this.workingSupplies = await this.multicall.getWorkingSupplies( + gaugeAddresses + ); + this.rewardTokens = await this.multicall.getRewardData(gaugeAddresses); + this.relativeWeights = await this.gaugeController.getRelativeWeights( + gaugeAddresses + ); + } + + async find(id: string): Promise { + if (Object.keys(this.relativeWeights).length == 0) { + await this.fetch(); + } + + const gauge = await this.subgraph.find(id); + if (!gauge) { + return; + } + + return this.compose(gauge); + } + + async findBy( + attribute: string, + value: string + ): Promise { + if (Object.keys(this.relativeWeights).length == 0) { + await this.fetch(); + } + + let gauge: SubgraphLiquidityGauge | undefined; + if (attribute == 'id') { + return this.find(value); + } else if (attribute == 'address') { + return this.find(value); + } else if (attribute == 'poolId') { + gauge = await this.subgraph.findBy('poolId', value); + } else if (attribute == 'poolAddress') { + gauge = await this.subgraph.findBy('poolAddress', value); + } else { + throw `search by ${attribute} not implemented`; + } + if (!gauge) { + return undefined; + } + + return this.compose(gauge); + } + + private compose(subgraphGauge: SubgraphLiquidityGauge) { + return { + id: subgraphGauge.id, + address: subgraphGauge.id, + name: subgraphGauge.symbol, + poolId: subgraphGauge.poolId, + poolAddress: subgraphGauge.poolAddress, + totalSupply: this.totalSupplies[subgraphGauge.id], + workingSupply: this.workingSupplies[subgraphGauge.id], + relativeWeight: this.relativeWeights[subgraphGauge.id], + rewardTokens: this.rewardTokens[subgraphGauge.id], + }; + } +} diff --git a/balancer-js/src/modules/data/liquidity-gauges/subgraph.ts b/balancer-js/src/modules/data/liquidity-gauges/subgraph.ts new file mode 100644 index 000000000..bcbfc6153 --- /dev/null +++ b/balancer-js/src/modules/data/liquidity-gauges/subgraph.ts @@ -0,0 +1,57 @@ +import { Findable } from '../types'; +import { + createGaugesClient, + GaugesClient, + SubgraphLiquidityGauge, +} from '@/modules/subgraph/subgraph'; + +/** + * Access liquidity gauges indexed by subgraph. + * Because we have ~100 gauges to save on repeated http calls we cache all results as `gauges` on an instance. + * Balancer's subgraph URL: https://thegraph.com/hosted-service/subgraph/balancer-labs/balancer-gauges + */ +export class LiquidityGaugesSubgraphRepository + implements Findable +{ + private client: GaugesClient; + public gauges: SubgraphLiquidityGauge[] = []; + + constructor(url: string) { + this.client = createGaugesClient(url); + } + + async fetch(): Promise { + const queryResult = await this.client.LiquidityGauges(); + // TODO: optionally convert subgraph type to sdk internal type + this.gauges = queryResult.liquidityGauges as SubgraphLiquidityGauge[]; + + return this.gauges; + } + + async find(id: string): Promise { + if (this.gauges.length == 0) { + await this.fetch(); + } + + return this.gauges.find((gauge) => gauge.id == id); + } + + async findBy( + param: string, + value: string + ): Promise { + if (this.gauges.length == 0) { + await this.fetch(); + } + + if (param == 'id') { + return this.find(value); + } else if (param == 'poolId') { + return this.gauges.find((gauge) => gauge.poolId == value); + } else if (param == 'poolAddress') { + return this.gauges.find((gauge) => gauge.poolAddress == value); + } else { + throw `search by ${param} not implemented`; + } + } +} From bd899ac8f553ab1047344671263fc532a8dd6f36 Mon Sep 17 00:00:00 2001 From: bronco Date: Sat, 16 Jul 2022 11:59:17 +0200 Subject: [PATCH 29/65] fee distributor data --- balancer-js/src/lib/constants/config.ts | 6 + .../data/fee-distributor/repository.spec.ts | 39 +++++++ .../data/fee-distributor/repository.ts | 103 ++++++++++++++++++ balancer-js/src/modules/data/index.ts | 1 + balancer-js/src/types.ts | 1 + 5 files changed, 150 insertions(+) create mode 100644 balancer-js/src/modules/data/fee-distributor/repository.spec.ts create mode 100644 balancer-js/src/modules/data/fee-distributor/repository.ts diff --git a/balancer-js/src/lib/constants/config.ts b/balancer-js/src/lib/constants/config.ts index 717399936..330867487 100644 --- a/balancer-js/src/lib/constants/config.ts +++ b/balancer-js/src/lib/constants/config.ts @@ -12,6 +12,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { multicall: '0xeefba1e63905ef1d7acba5a8513c70307c1ce441', lidoRelayer: '0xdcdbf71A870cc60C6F9B621E28a7D3Ffd6Dd4965', gaugeController: '0xc128468b7ce63ea702c1f104d55a2566b13d3abd', + feeDistributor: '0xD3cf852898b21fc233251427c2DC93d3d604F3BB', }, tokens: { wrappedNativeAsset: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', @@ -44,6 +45,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', multicall: '0xa1B2b503959aedD81512C37e9dce48164ec6a94d', gaugeController: '', + feeDistributor: '', }, tokens: { wrappedNativeAsset: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', @@ -84,6 +86,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', multicall: '0x2cc8688C5f75E365aaEEb4ea8D6a480405A48D2A', gaugeController: '', + feeDistributor: '', }, tokens: { wrappedNativeAsset: '0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1', @@ -104,6 +107,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', multicall: '0x53c43764255c17bd724f74c4ef150724ac50a3ed', gaugeController: '', + feeDistributor: '', }, tokens: { wrappedNativeAsset: '0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1', @@ -123,6 +127,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', multicall: '0x42ad527de7d4e9d9d011ac45b31d8551f8fe9821', gaugeController: '', + feeDistributor: '', }, tokens: { wrappedNativeAsset: '0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1', @@ -143,6 +148,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', multicall: '0x77dCa2C955b15e9dE4dbBCf1246B4B85b651e50e', gaugeController: '0xBB1CE49b16d55A1f2c6e88102f32144C7334B116', + feeDistributor: '', }, tokens: { wrappedNativeAsset: '0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1', diff --git a/balancer-js/src/modules/data/fee-distributor/repository.spec.ts b/balancer-js/src/modules/data/fee-distributor/repository.spec.ts new file mode 100644 index 000000000..85c85c002 --- /dev/null +++ b/balancer-js/src/modules/data/fee-distributor/repository.spec.ts @@ -0,0 +1,39 @@ +import { JsonRpcProvider } from '@ethersproject/providers'; +import { expect } from 'chai'; +import { FeeDistributorRepository } from './repository'; + +describe('FeeDistributorRepository', () => { + const repo = new FeeDistributorRepository( + '', + '', + '', + '', + '', + new JsonRpcProvider('', 1) + ); + + describe('.getPreviousWeek', () => { + // Wednesday + const now = new Date('2022-07-01 11:11:11').getTime(); + const previousWeekTs = repo.getPreviousWeek(now); + const previousWeek = new Date(previousWeekTs * 1e3); + + it("goes back to last week's Thursday since last Thursday", () => { + const dayOfTheWeek = previousWeek.getUTCDay(); + expect(dayOfTheWeek).to.eq(4); + const day = previousWeek.getUTCDate(); + expect(day).to.eq(23); + const month = previousWeek.getUTCMonth(); // returns 0..11 + expect(month).to.eq(5); + }); + + it('goes back to midnight', () => { + const hour = previousWeek.getUTCHours(); + expect(hour).to.eq(0); + const minutes = previousWeek.getUTCMinutes(); + expect(minutes).to.eq(0); + const seconds = previousWeek.getUTCSeconds(); + expect(seconds).to.eq(0); + }); + }); +}); diff --git a/balancer-js/src/modules/data/fee-distributor/repository.ts b/balancer-js/src/modules/data/fee-distributor/repository.ts new file mode 100644 index 000000000..b1e60afda --- /dev/null +++ b/balancer-js/src/modules/data/fee-distributor/repository.ts @@ -0,0 +1,103 @@ +import { Interface } from '@ethersproject/abi'; +import { Provider } from '@ethersproject/providers'; +import { Contract } from '@ethersproject/contracts'; +import { getAddress } from '@ethersproject/address'; +import { formatUnits } from '@ethersproject/units'; +import { Multicall } from '@/modules/contracts/multicall'; + +export interface FeeDistributorData { + balAmount: number; + bbAUsdAmount: number; + veBalSupply: number; + bbAUsdPrice: number; + balAddress: string; +} + +export interface BaseFeeDistributor { + multicallData: (ts: number) => Promise; +} + +const feeDistributorInterface = new Interface([ + 'function getTokensDistributedInWeek(address token, uint timestamp) view returns (uint)', +]); + +const veBalInterface = new Interface([ + 'function totalSupply() view returns (uint)', +]); + +const bbAUsdInterface = new Interface([ + 'function getRate() view returns (uint)', +]); + +export class FeeDistributorRepository implements BaseFeeDistributor { + multicall: Contract; + data?: FeeDistributorData; + + constructor( + multicallAddress: string, + private feeDistributorAddress: string, + private balAddress: string, + private veBalAddress: string, + private bbAUsdAddress: string, + provider: Provider + ) { + this.multicall = Multicall(multicallAddress, provider); + } + + async fetch(timestamp: number): Promise { + const previousWeek = this.getPreviousWeek(timestamp); + const payload = [ + [ + this.feeDistributorAddress, + feeDistributorInterface.encodeFunctionData( + 'getTokensDistributedInWeek', + [getAddress(this.balAddress), previousWeek] + ), + ], + [ + this.feeDistributorAddress, + feeDistributorInterface.encodeFunctionData( + 'getTokensDistributedInWeek', + [getAddress(this.bbAUsdAddress), previousWeek] + ), + ], + [this.veBalAddress, veBalInterface.encodeFunctionData('totalSupply', [])], + [this.bbAUsdAddress, bbAUsdInterface.encodeFunctionData('getRate', [])], + ]; + const [, res] = await this.multicall.aggregate(payload); + + const data = { + balAmount: parseFloat(formatUnits(res[0], 18)), + bbAUsdAmount: parseFloat(formatUnits(res[1], 18)), + veBalSupply: parseFloat(formatUnits(res[2], 18)), + bbAUsdPrice: parseFloat(formatUnits(res[3], 18)), + balAddress: this.balAddress, + }; + + return data; + } + + async multicallData(timestamp: number): Promise { + if (!this.data) { + this.data = await this.fetch(timestamp); + } + + return this.data; + } + + getPreviousWeek(fromTimestamp: number): number { + const weeksToGoBack = 1; + const midnight = new Date(fromTimestamp); + midnight.setUTCHours(0); + midnight.setUTCMinutes(0); + midnight.setUTCSeconds(0); + midnight.setUTCMilliseconds(0); + + let daysSinceThursday = midnight.getUTCDay() - 4; + if (daysSinceThursday < 0) daysSinceThursday += 7; + + daysSinceThursday = daysSinceThursday + weeksToGoBack * 7; + + return Math.floor(midnight.getTime() / 1000) - daysSinceThursday * 86400; + } +} diff --git a/balancer-js/src/modules/data/index.ts b/balancer-js/src/modules/data/index.ts index d442456ba..7da2bf32b 100644 --- a/balancer-js/src/modules/data/index.ts +++ b/balancer-js/src/modules/data/index.ts @@ -4,3 +4,4 @@ export * from './liquidity-gauges'; export * from './pool'; export * from './token'; export * from './token-prices'; +export * from './fee-distributor/repository'; diff --git a/balancer-js/src/types.ts b/balancer-js/src/types.ts index 3ad8be8e8..3dc9b0d23 100644 --- a/balancer-js/src/types.ts +++ b/balancer-js/src/types.ts @@ -38,6 +38,7 @@ export interface ContractAddresses { multicall: string; lidoRelayer?: string; gaugeController: string; + feeDistributor: string; } export interface BalancerNetworkConfig { From a29e02d674d540f4508a9e817f83126f7ed24658 Mon Sep 17 00:00:00 2001 From: bronco Date: Fri, 8 Jul 2022 16:25:14 +0200 Subject: [PATCH 30/65] pools data --- balancer-js/src/modules/data/pool/static.ts | 16 +- balancer-js/src/modules/data/pool/subgraph.ts | 140 +++++++++++++----- balancer-js/src/modules/data/types.ts | 8 +- 3 files changed, 124 insertions(+), 40 deletions(-) diff --git a/balancer-js/src/modules/data/pool/static.ts b/balancer-js/src/modules/data/pool/static.ts index 3aa385a34..c74282270 100644 --- a/balancer-js/src/modules/data/pool/static.ts +++ b/balancer-js/src/modules/data/pool/static.ts @@ -1,7 +1,9 @@ -import { Pool } from '@/types'; -import { PoolAttribute, PoolRepository } from './types'; +import { Findable, Pool, Searchable } from '@/types'; +import { PoolAttribute } from './types'; -export class StaticPoolRepository implements PoolRepository { +export class StaticPoolRepository + implements Findable, Searchable +{ constructor(private pools: Pool[]) {} async find(id: string): Promise { @@ -18,4 +20,12 @@ export class StaticPoolRepository implements PoolRepository { return pool[attribute] === value; }); } + + async all(): Promise { + return this.pools; + } + + async where(filter: (pool: Pool) => boolean): Promise { + return (await this.all()).filter(filter); + } } diff --git a/balancer-js/src/modules/data/pool/subgraph.ts b/balancer-js/src/modules/data/pool/subgraph.ts index fb69797fe..6d427388a 100644 --- a/balancer-js/src/modules/data/pool/subgraph.ts +++ b/balancer-js/src/modules/data/pool/subgraph.ts @@ -1,49 +1,117 @@ +import { Findable, Searchable } from '../types'; +import { + createSubgraphClient, + SubgraphClient, + SubgraphPool, + Pool_OrderBy, + OrderDirection, +} from '@/modules/subgraph/subgraph'; +import { PoolAttribute } from './types'; import { Pool, PoolType } from '@/types'; -import { PoolAttribute, PoolRepository } from './types'; -import { PoolToken, SubgraphClient } from '@/modules/subgraph/subgraph'; -import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; -export class SubgraphPoolRepository implements PoolRepository { - constructor(private client: SubgraphClient) {} +/** + * Access pools using generated subgraph client. + * + * Balancer's subgraph URL: https://thegraph.com/hosted-service/subgraph/balancer-labs/balancer-v2 + */ +export class PoolsSubgraphRepository + implements Findable, Searchable +{ + private client: SubgraphClient; + public pools: SubgraphPool[] = []; + + /** + * Repository with optional lazy loaded blockHeight + * + * @param url subgraph URL + * @param blockHeight lazy loading blockHeigh resolver + */ + constructor( + url: string, + private blockHeight?: () => Promise + ) { + this.client = createSubgraphClient(url); + } + + async fetch(): Promise { + const { pool0, pool1000 } = await this.client.Pools({ + where: { swapEnabled: true, totalShares_gt: '0' }, + orderBy: Pool_OrderBy.TotalLiquidity, + orderDirection: OrderDirection.Desc, + block: this.blockHeight + ? { number: await this.blockHeight() } + : undefined, + }); + + // TODO: how to best convert subgraph type to sdk internal type? + this.pools = [...pool0, ...pool1000]; + + return this.pools; + } async find(id: string): Promise { - const { pool } = await this.client.Pool({ id }); - return this.mapPool(pool); + if (this.pools.length == 0) { + await this.fetch(); + } + + return this.findBy('id', id); + } + + async findBy(param: PoolAttribute, value: string): Promise { + if (this.pools.length == 0) { + await this.fetch(); + } + + const pool = this.pools.find((pool) => pool[param] == value); + if (pool) { + return this.mapType(pool); + } + return undefined; } - async findBy( - attribute: PoolAttribute, - value: string - ): Promise { - switch (attribute) { - case 'id': - return this.find(value); - case 'address': - // eslint-disable-next-line no-case-declarations - const { pool0 } = await this.client.Pools({ - where: { address: value }, - }); - return this.mapPool(pool0); - default: - return undefined; + async all(): Promise { + if (this.pools.length == 0) { + await this.fetch(); } + + return this.pools.map(this.mapType); + } + + async where(filter: (pool: Pool) => boolean): Promise { + if (this.pools.length == 0) { + await this.fetch(); + } + + return (await this.all()).filter(filter); } - // Helper methods - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private mapPool(pool: any): Pool | undefined { - if (!pool) return undefined; - const poolType = pool?.poolType as PoolType; - if (!poolType) - throw new BalancerError(BalancerErrorCode.UNSUPPORTED_POOL_TYPE); - const tokens = (pool?.tokens as PoolToken[]) || []; - if (tokens.length === 0) - throw new BalancerError(BalancerErrorCode.MISSING_TOKENS); + private mapType(subgraphPool: SubgraphPool): Pool { return { - ...pool, - poolType, - tokens, + id: subgraphPool.id, + name: subgraphPool.name || '', + address: subgraphPool.address, + poolType: subgraphPool.poolType as PoolType, + swapFee: subgraphPool.swapFee, + swapEnabled: subgraphPool.swapEnabled, + amp: subgraphPool.amp || undefined, + // owner: subgraphPool.owner, + // factory: subgraphPool.factory, + tokens: subgraphPool.tokens || [], + tokensList: subgraphPool.tokensList, + tokenAddresses: (subgraphPool.tokens || []).map((t) => t.address), + totalLiquidity: subgraphPool.totalLiquidity, + totalShares: subgraphPool.totalShares, + totalSwapFee: subgraphPool.totalSwapFee, + totalSwapVolume: subgraphPool.totalSwapVolume, + // onchain: subgraphPool.onchain, + createTime: subgraphPool.createTime, + // mainTokens: subgraphPool.mainTokens, + // wrappedTokens: subgraphPool.wrappedTokens, + // unwrappedTokens: subgraphPool.unwrappedTokens, + // isNew: subgraphPool.isNew, + // volumeSnapshot: subgraphPool.volumeSnapshot, + // feesSnapshot: subgraphPool.???, // Approximated last 24h fees + // boost: subgraphPool.boost, }; } } diff --git a/balancer-js/src/modules/data/types.ts b/balancer-js/src/modules/data/types.ts index 5641936c7..3e7ae1360 100644 --- a/balancer-js/src/modules/data/types.ts +++ b/balancer-js/src/modules/data/types.ts @@ -1,7 +1,13 @@ export { LiquidityGauge } from './liquidity-gauges/provider'; +export { PoolAttribute } from './pool/types'; +export { TokenAttribute } from './token/types'; export interface Findable { find: (id: string) => Promise; findBy: (attribute: P, value: string) => Promise; - where?: (attributes: P[], values: string[]) => Promise; +} + +export interface Searchable { + all: () => Promise; + where: (filters: (arg: T) => boolean) => Promise; } From cfeb2ce636a11172b04b1e5482e7a6e6867cd4b6 Mon Sep 17 00:00:00 2001 From: bronco Date: Wed, 31 Aug 2022 13:08:05 +0200 Subject: [PATCH 31/65] data for yield tokens --- balancer-js/examples/data/token-yields.ts | 17 + balancer-js/package.json | 1 + balancer-js/src/modules/data/index.ts | 1 + .../modules/data/token-yields/repository.ts | 53 ++ .../data/token-yields/tokens/aave.spec.ts | 40 + .../modules/data/token-yields/tokens/aave.ts | 117 +++ .../data/token-yields/tokens/lido.spec.ts | 28 + .../modules/data/token-yields/tokens/lido.ts | 35 + .../token-yields/tokens/overnight.spec.ts | 24 + .../data/token-yields/tokens/overnight.ts | 28 + balancer-js/yarn.lock | 745 +++++++++--------- 11 files changed, 726 insertions(+), 363 deletions(-) create mode 100644 balancer-js/examples/data/token-yields.ts create mode 100644 balancer-js/src/modules/data/token-yields/repository.ts create mode 100644 balancer-js/src/modules/data/token-yields/tokens/aave.spec.ts create mode 100644 balancer-js/src/modules/data/token-yields/tokens/aave.ts create mode 100644 balancer-js/src/modules/data/token-yields/tokens/lido.spec.ts create mode 100644 balancer-js/src/modules/data/token-yields/tokens/lido.ts create mode 100644 balancer-js/src/modules/data/token-yields/tokens/overnight.spec.ts create mode 100644 balancer-js/src/modules/data/token-yields/tokens/overnight.ts diff --git a/balancer-js/examples/data/token-yields.ts b/balancer-js/examples/data/token-yields.ts new file mode 100644 index 000000000..46f241bd4 --- /dev/null +++ b/balancer-js/examples/data/token-yields.ts @@ -0,0 +1,17 @@ +/** + * Display APRs for pool ids hardcoded under `const ids` + * Run command: yarn examples:run ./examples/data/token-yields.ts + */ +import { BalancerSDK } from '../../src/modules/sdk.module'; +import { yieldTokens } from '../../src/modules/data/token-yields/tokens/aave'; + +const sdk = new BalancerSDK({ network: 1, rpcUrl: '' }); +const { data } = sdk; + +const main = async () => { + const tokenYield = await data.tokenYields.find(yieldTokens.waDAI); + + console.log(yieldTokens.waDAI, tokenYield); +}; + +main(); diff --git a/balancer-js/package.json b/balancer-js/package.json index 64e1af165..0a1fb1d78 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -63,6 +63,7 @@ "@types/node": "^15.12.4", "@typescript-eslint/eslint-plugin": "^4.1.1", "@typescript-eslint/parser": "^4.1.1", + "axios-mock-adapter": "^1.21.2", "chai": "^4.2.0", "dotenv": "^10.0.0", "eslint": "^7.9.0", diff --git a/balancer-js/src/modules/data/index.ts b/balancer-js/src/modules/data/index.ts index 7da2bf32b..3b4ad3d36 100644 --- a/balancer-js/src/modules/data/index.ts +++ b/balancer-js/src/modules/data/index.ts @@ -5,3 +5,4 @@ export * from './pool'; export * from './token'; export * from './token-prices'; export * from './fee-distributor/repository'; +export * from './token-yields/repository'; diff --git a/balancer-js/src/modules/data/token-yields/repository.ts b/balancer-js/src/modules/data/token-yields/repository.ts new file mode 100644 index 000000000..02762684b --- /dev/null +++ b/balancer-js/src/modules/data/token-yields/repository.ts @@ -0,0 +1,53 @@ +import { lido, yieldTokens as lidoTokens } from './tokens/lido'; +import { aave, yieldTokens as aaveTokens } from './tokens/aave'; +import { overnight, yieldTokens as overnightTokens } from './tokens/overnight'; +import { Findable } from '../types'; + +/** + * Common interface for fetching APR from external sources + * + * @param address is optional, used when same source, eg: aave has multiple tokens and all of them can be fetched in one call. + */ +export interface AprFetcher { + (): Promise<{ [address: string]: number }>; +} + +const yieldSourceMap: { [address: string]: AprFetcher } = Object.fromEntries([ + ...Object.values(lidoTokens).map((k) => [k, lido]), + ...Object.values(aaveTokens).map((k) => [k, aave]), + ...Object.values(overnightTokens).map((k) => [k, overnight]), +]); + +export class TokenYieldsRepository implements Findable { + private yields: { [address: string]: number } = {}; + + constructor(private sources = yieldSourceMap) {} + + async fetch(address: string): Promise { + const tokenYields = await this.sources[address](); + this.yields = { + ...this.yields, + ...tokenYields, + }; + } + + async find(address: string): Promise { + const lowercase = address.toLocaleLowerCase(); + if ( + Object.keys(this.sources).includes(lowercase) && + !Object.keys(this.yields).includes(lowercase) + ) { + await this.fetch(address); + } + + return this.yields[lowercase]; + } + + async findBy(attribute: string, value: string): Promise { + if (attribute != 'address') { + return undefined; + } + + return this.find(value); + } +} diff --git a/balancer-js/src/modules/data/token-yields/tokens/aave.spec.ts b/balancer-js/src/modules/data/token-yields/tokens/aave.spec.ts new file mode 100644 index 000000000..74901ff31 --- /dev/null +++ b/balancer-js/src/modules/data/token-yields/tokens/aave.spec.ts @@ -0,0 +1,40 @@ +import { expect } from 'chai'; +import { aave, yieldTokens, wrappedTokensMap } from './aave'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; + +const mockedResponse = { + data: { + reserves: [ + { + underlyingAsset: wrappedTokensMap[yieldTokens.waUSDT].underlying, + liquidityRate: '16633720952291480781459657', + }, + ], + }, +}; + +describe('aave apr', () => { + let mock: MockAdapter; + + before(() => { + mock = new MockAdapter(axios); + mock + .onPost('https://api.thegraph.com/subgraphs/name/aave/protocol-v2') + .reply(() => [200, mockedResponse]); + }); + + after(() => { + mock.restore(); + }); + + it('is getting fetched', async () => { + const apr = (await aave())[yieldTokens.waUSDT]; + expect(apr).to.eq(166); + }); +}); + +// Mocking for fetch in case we migrate at some point: +// import fetchMock from 'fetch-mock'; +// fetchMock.post('https://api.thegraph.com/subgraphs/name/aave/protocol-v2', mockedResponse); +// fetchMock.reset(); diff --git a/balancer-js/src/modules/data/token-yields/tokens/aave.ts b/balancer-js/src/modules/data/token-yields/tokens/aave.ts new file mode 100644 index 000000000..7d5ed6def --- /dev/null +++ b/balancer-js/src/modules/data/token-yields/tokens/aave.ts @@ -0,0 +1,117 @@ +import { AprFetcher } from '../repository'; +import { BigNumber } from '@ethersproject/bignumber'; +import { formatUnits } from '@ethersproject/units'; +import axios from 'axios'; + +// can be fetched from subgraph +// aave-js: supplyAPR = graph.liquidityRate = core.getReserveCurrentLiquidityRate(_reserve) +// or directly from RPC: +// wrappedAaveToken.LENDING_POOL.getReserveCurrentLiquidityRate(mainTokenAddress) + +export const yieldTokens = { + waUSDT: '0xf8fd466f12e236f4c96f7cce6c79eadb819abf58', + waUSDC: '0xd093fa4fb80d09bb30817fdcd442d4d02ed3e5de', + waDAI: '0x02d60b84491589974263d922d9cc7a3152618ef6', +}; + +export const wrappedTokensMap = { + // USDT + [yieldTokens.waUSDT]: { + aToken: '0x3ed3b47dd13ec9a98b44e6204a523e766b225811', + underlying: '0xdac17f958d2ee523a2206206994597c13d831ec7', + }, + // USDC + [yieldTokens.waUSDC]: { + aToken: '0xbcca60bb61934080951369a648fb03df4f96263c', + underlying: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + }, + // DAI + [yieldTokens.waDAI]: { + aToken: '0x028171bca77440897b824ca71d1c56cac55b68a3', + underlying: '0x6b175474e89094c44da98b954eedeac495271d0f', + }, +}; + +const aTokens = Object.values(wrappedTokensMap).map((t) => t.aToken); +const underlyingAssets = Object.values(wrappedTokensMap).map( + (t) => t.underlying +); +const underlyingToWrapped = Object.fromEntries( + Object.keys(wrappedTokensMap).map((wrapped) => [ + wrappedTokensMap[wrapped as keyof typeof wrappedTokensMap].underlying, + wrapped, + ]) +); + +// Subgraph +// liquidityRate, depositors APR (in rays - 27 digits) +const endpoint = 'https://api.thegraph.com/subgraphs/name/aave/protocol-v2'; +const query = ` + query getReserves($aTokens: [String!], $underlyingAssets: [Bytes!]) { + reserves( + where: { + aToken_in: $aTokens + underlyingAsset_in: $underlyingAssets + isActive: true + } + ) { + underlyingAsset + liquidityRate + } + } +`; + +interface ReserveResponse { + data: { + reserves: [ + { + underlyingAsset: string; + liquidityRate: string; + } + ]; + }; +} + +/** + * Fetching and parsing aave APRs from a subgraph + * + * @returns APRs for aave tokens + */ +export const aave: AprFetcher = async () => { + try { + const graphqlQuery = { + operationName: 'getReserves', + query, + variables: { aTokens, underlyingAssets }, + }; + + const response = await axios.post(endpoint, graphqlQuery); + + const { + data: { reserves }, + } = response.data as ReserveResponse; + + const aprEntries = reserves.map((r) => [ + underlyingToWrapped[r.underlyingAsset], + // Note: our assumption is frontend usage, this service is not a good source where more accuracy is needed. + // Converting from aave ray number (27 digits) to bsp + // essentially same as here: + // https://github.com/aave/aave-utilities/blob/master/packages/math-utils/src/formatters/reserve/index.ts#L231 + Math.round( + parseFloat(formatUnits(BigNumber.from(r.liquidityRate), 27)) * 10000 + ), + ]); + + return Object.fromEntries(aprEntries); + } catch (error) { + console.log(error); + + return Object.fromEntries( + Object.keys(wrappedTokensMap).map((key) => [key, 0]) + ); + } +}; + +// TODO: RPC multicall +// always upto date +// const lendingPoolAddress = '0x7d2768de32b0b80b7a3454c06bdac94a69ddc7a9'; diff --git a/balancer-js/src/modules/data/token-yields/tokens/lido.spec.ts b/balancer-js/src/modules/data/token-yields/tokens/lido.spec.ts new file mode 100644 index 000000000..b1e4ad208 --- /dev/null +++ b/balancer-js/src/modules/data/token-yields/tokens/lido.spec.ts @@ -0,0 +1,28 @@ +import { expect } from 'chai'; +import { lido, yieldTokens } from './lido'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; + +const mockedResponse = { + data: { eth: '1', steth: '1' }, +}; + +describe('lido apr', () => { + let mock: MockAdapter; + + before(() => { + mock = new MockAdapter(axios); + mock + .onGet('https://stake.lido.fi/api/apr') + .reply(() => [200, mockedResponse]); + }); + + after(() => { + mock.restore(); + }); + + it('is getting fetched', async () => { + const apr = (await lido())[yieldTokens.stETH]; + expect(apr).to.eq(100); + }); +}); diff --git a/balancer-js/src/modules/data/token-yields/tokens/lido.ts b/balancer-js/src/modules/data/token-yields/tokens/lido.ts new file mode 100644 index 000000000..3cdc08d00 --- /dev/null +++ b/balancer-js/src/modules/data/token-yields/tokens/lido.ts @@ -0,0 +1,35 @@ +import { AprFetcher } from '../repository'; +import axios from 'axios'; + +export const yieldTokens = { + stETH: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', +}; + +interface LidoAPIResponse { + data: { + eth: string; + steth: string; + }; +} + +/** + * Lido APR fetching + * + * @returns lido APR for stETH + */ +export const lido: AprFetcher = async () => { + let apr = 0; + + try { + const response = await axios.get('https://stake.lido.fi/api/apr'); + const { data: aprs } = response.data as LidoAPIResponse; + + apr = Math.round(parseFloat(aprs.steth) * 100); + } catch (error) { + console.error('Failed to fetch stETH APR:', error); + } + + return { + [yieldTokens.stETH]: apr, + }; +}; diff --git a/balancer-js/src/modules/data/token-yields/tokens/overnight.spec.ts b/balancer-js/src/modules/data/token-yields/tokens/overnight.spec.ts new file mode 100644 index 000000000..502a71e50 --- /dev/null +++ b/balancer-js/src/modules/data/token-yields/tokens/overnight.spec.ts @@ -0,0 +1,24 @@ +import { expect } from 'chai'; +import { overnight, yieldTokens } from './overnight'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; + +describe('overnight apr', () => { + let mock: MockAdapter; + + before(() => { + mock = new MockAdapter(axios); + mock + .onGet('https://app.overnight.fi/api/balancer/week/apr') + .reply(() => [200, '0.01']); + }); + + after(() => { + mock.restore(); + }); + + it('is getting fetched', async () => { + const apr = (await overnight())[yieldTokens.usdcUSDplus]; + expect(apr).to.eq(1); + }); +}); diff --git a/balancer-js/src/modules/data/token-yields/tokens/overnight.ts b/balancer-js/src/modules/data/token-yields/tokens/overnight.ts new file mode 100644 index 000000000..f55431736 --- /dev/null +++ b/balancer-js/src/modules/data/token-yields/tokens/overnight.ts @@ -0,0 +1,28 @@ +import { AprFetcher } from '../repository'; +import axios from 'axios'; + +export const yieldTokens = { + usdcUSDplus: '0x1aafc31091d93c3ff003cff5d2d8f7ba2e728425', + usdcUSDplus2: '0x6933ec1ca55c06a894107860c92acdfd2dd8512f', +}; + +/** + * Overnight token APR fetching + * + * @returns cached APR for USD+ + */ +export const overnight: AprFetcher = async () => { + let bsp = 0; + try { + const { data: rate } = await axios.get( + 'https://app.overnight.fi/api/balancer/week/apr' + ); + bsp = Math.round((parseFloat(rate) * 10000) / 100); + } catch (error) { + console.error('Failed to fetch USD+ APR:', error); + } + + return Object.fromEntries( + Object.values(yieldTokens).map((address) => [address, bsp]) + ); +}; diff --git a/balancer-js/yarn.lock b/balancer-js/yarn.lock index a38fad423..b89b45507 100644 --- a/balancer-js/yarn.lock +++ b/balancer-js/yarn.lock @@ -596,346 +596,347 @@ merkle-patricia-tree "^4.2.4" rustbn.js "~0.2.0" -"@ethersproject/abi@5.6.4", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.0", "@ethersproject/abi@^5.6.3": - version "5.6.4" - resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.6.4.tgz#f6e01b6ed391a505932698ecc0d9e7a99ee60362" - integrity sha512-TTeZUlCeIHG6527/2goZA6gW5F8Emoc7MrZDC7hhP84aRGvW3TEdTnZR08Ls88YXM1m2SuK42Osw/jSi3uO8gg== - dependencies: - "@ethersproject/address" "^5.6.1" - "@ethersproject/bignumber" "^5.6.2" - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/constants" "^5.6.1" - "@ethersproject/hash" "^5.6.1" - "@ethersproject/keccak256" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - "@ethersproject/properties" "^5.6.0" - "@ethersproject/strings" "^5.6.1" - -"@ethersproject/abstract-provider@5.6.1", "@ethersproject/abstract-provider@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.6.1.tgz#02ddce150785caf0c77fe036a0ebfcee61878c59" - integrity sha512-BxlIgogYJtp1FS8Muvj8YfdClk3unZH0vRMVX791Z9INBNT/kuACZ9GzaY1Y4yFq+YSy6/w4gzj3HCRKrK9hsQ== - dependencies: - "@ethersproject/bignumber" "^5.6.2" - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - "@ethersproject/networks" "^5.6.3" - "@ethersproject/properties" "^5.6.0" - "@ethersproject/transactions" "^5.6.2" - "@ethersproject/web" "^5.6.1" - -"@ethersproject/abstract-signer@5.6.2", "@ethersproject/abstract-signer@^5.4.0", "@ethersproject/abstract-signer@^5.6.2": - version "5.6.2" - resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.6.2.tgz#491f07fc2cbd5da258f46ec539664713950b0b33" - integrity sha512-n1r6lttFBG0t2vNiI3HoWaS/KdOt8xyDjzlP2cuevlWLG6EX0OwcKLyG/Kp/cuwNxdy/ous+R/DEMdTUwWQIjQ== - dependencies: - "@ethersproject/abstract-provider" "^5.6.1" - "@ethersproject/bignumber" "^5.6.2" - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - "@ethersproject/properties" "^5.6.0" - -"@ethersproject/address@5.6.1", "@ethersproject/address@^5.4.0", "@ethersproject/address@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.6.1.tgz#ab57818d9aefee919c5721d28cd31fd95eff413d" - integrity sha512-uOgF0kS5MJv9ZvCz7x6T2EXJSzotiybApn4XlOgoTX0xdtyVIJ7pF+6cGPxiEq/dpBiTfMiw7Yc81JcwhSYA0Q== - dependencies: - "@ethersproject/bignumber" "^5.6.2" - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/keccak256" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - "@ethersproject/rlp" "^5.6.1" - -"@ethersproject/base64@5.6.1", "@ethersproject/base64@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.6.1.tgz#2c40d8a0310c9d1606c2c37ae3092634b41d87cb" - integrity sha512-qB76rjop6a0RIYYMiB4Eh/8n+Hxu2NIZm8S/Q7kNo5pmZfXhHGHmS4MinUainiBC54SCyRnwzL+KZjj8zbsSsw== - dependencies: - "@ethersproject/bytes" "^5.6.1" - -"@ethersproject/basex@5.6.1", "@ethersproject/basex@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.6.1.tgz#badbb2f1d4a6f52ce41c9064f01eab19cc4c5305" - integrity sha512-a52MkVz4vuBXR06nvflPMotld1FJWSj2QT0985v7P/emPZO00PucFAkbcmq2vpVU7Ts7umKiSI6SppiLykVWsA== - dependencies: - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/properties" "^5.6.0" - -"@ethersproject/bignumber@5.6.2", "@ethersproject/bignumber@^5.4.0", "@ethersproject/bignumber@^5.6.2": - version "5.6.2" - resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.6.2.tgz#72a0717d6163fab44c47bcc82e0c550ac0315d66" - integrity sha512-v7+EEUbhGqT3XJ9LMPsKvXYHFc8eHxTowFCG/HgJErmq4XHJ2WR7aeyICg3uTOAQ7Icn0GFHAohXEhxQHq4Ubw== - dependencies: - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/logger" "^5.6.0" +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.0", "@ethersproject/abi@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" + integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" + integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + +"@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.4.0", "@ethersproject/abstract-signer@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" + integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/address@5.7.0", "@ethersproject/address@^5.4.0", "@ethersproject/address@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" + integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + +"@ethersproject/base64@5.7.0", "@ethersproject/base64@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" + integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + +"@ethersproject/basex@5.7.0", "@ethersproject/basex@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.7.0.tgz#97034dc7e8938a8ca943ab20f8a5e492ece4020b" + integrity sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.4.0", "@ethersproject/bignumber@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" + integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" bn.js "^5.2.1" -"@ethersproject/bytes@5.6.1", "@ethersproject/bytes@^5.4.0", "@ethersproject/bytes@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.6.1.tgz#24f916e411f82a8a60412344bf4a813b917eefe7" - integrity sha512-NwQt7cKn5+ZE4uDn+X5RAXLp46E1chXoaMmrxAyA0rblpxz8t58lVkrHXoRIn0lz1joQElQ8410GqhTqMOwc6g== - dependencies: - "@ethersproject/logger" "^5.6.0" - -"@ethersproject/constants@5.6.1", "@ethersproject/constants@^5.4.0", "@ethersproject/constants@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.6.1.tgz#e2e974cac160dd101cf79fdf879d7d18e8cb1370" - integrity sha512-QSq9WVnZbxXYFftrjSjZDUshp6/eKp6qrtdBtUCm0QxCV5z1fG/w3kdlcsjMCQuQHUnAclKoK7XpXMezhRDOLg== - dependencies: - "@ethersproject/bignumber" "^5.6.2" - -"@ethersproject/contracts@5.6.2", "@ethersproject/contracts@^5.4.0": - version "5.6.2" - resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.6.2.tgz#20b52e69ebc1b74274ff8e3d4e508de971c287bc" - integrity sha512-hguUA57BIKi6WY0kHvZp6PwPlWF87MCeB4B7Z7AbUpTxfFXFdn/3b0GmjZPagIHS+3yhcBJDnuEfU4Xz+Ks/8g== - dependencies: - "@ethersproject/abi" "^5.6.3" - "@ethersproject/abstract-provider" "^5.6.1" - "@ethersproject/abstract-signer" "^5.6.2" - "@ethersproject/address" "^5.6.1" - "@ethersproject/bignumber" "^5.6.2" - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/constants" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - "@ethersproject/properties" "^5.6.0" - "@ethersproject/transactions" "^5.6.2" - -"@ethersproject/hash@5.6.1", "@ethersproject/hash@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.6.1.tgz#224572ea4de257f05b4abf8ae58b03a67e99b0f4" - integrity sha512-L1xAHurbaxG8VVul4ankNX5HgQ8PNCTrnVXEiFnE9xoRnaUcgfD12tZINtDinSllxPLCtGwguQxJ5E6keE84pA== - dependencies: - "@ethersproject/abstract-signer" "^5.6.2" - "@ethersproject/address" "^5.6.1" - "@ethersproject/bignumber" "^5.6.2" - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/keccak256" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - "@ethersproject/properties" "^5.6.0" - "@ethersproject/strings" "^5.6.1" - -"@ethersproject/hdnode@5.6.2", "@ethersproject/hdnode@^5.6.2": - version "5.6.2" - resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.6.2.tgz#26f3c83a3e8f1b7985c15d1db50dc2903418b2d2" - integrity sha512-tERxW8Ccf9CxW2db3WsN01Qao3wFeRsfYY9TCuhmG0xNpl2IO8wgXU3HtWIZ49gUWPggRy4Yg5axU0ACaEKf1Q== - dependencies: - "@ethersproject/abstract-signer" "^5.6.2" - "@ethersproject/basex" "^5.6.1" - "@ethersproject/bignumber" "^5.6.2" - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - "@ethersproject/pbkdf2" "^5.6.1" - "@ethersproject/properties" "^5.6.0" - "@ethersproject/sha2" "^5.6.1" - "@ethersproject/signing-key" "^5.6.2" - "@ethersproject/strings" "^5.6.1" - "@ethersproject/transactions" "^5.6.2" - "@ethersproject/wordlists" "^5.6.1" - -"@ethersproject/json-wallets@5.6.1", "@ethersproject/json-wallets@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.6.1.tgz#3f06ba555c9c0d7da46756a12ac53483fe18dd91" - integrity sha512-KfyJ6Zwz3kGeX25nLihPwZYlDqamO6pfGKNnVMWWfEVVp42lTfCZVXXy5Ie8IZTN0HKwAngpIPi7gk4IJzgmqQ== - dependencies: - "@ethersproject/abstract-signer" "^5.6.2" - "@ethersproject/address" "^5.6.1" - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/hdnode" "^5.6.2" - "@ethersproject/keccak256" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - "@ethersproject/pbkdf2" "^5.6.1" - "@ethersproject/properties" "^5.6.0" - "@ethersproject/random" "^5.6.1" - "@ethersproject/strings" "^5.6.1" - "@ethersproject/transactions" "^5.6.2" +"@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.4.0", "@ethersproject/bytes@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" + integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.4.0", "@ethersproject/constants@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" + integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + +"@ethersproject/contracts@5.7.0", "@ethersproject/contracts@^5.4.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" + integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + +"@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" + integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/hdnode@5.7.0", "@ethersproject/hdnode@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.7.0.tgz#e627ddc6b466bc77aebf1a6b9e47405ca5aef9cf" + integrity sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz#5e3355287b548c32b368d91014919ebebddd5360" + integrity sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" aes-js "3.0.0" scrypt-js "3.0.1" -"@ethersproject/keccak256@5.6.1", "@ethersproject/keccak256@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.6.1.tgz#b867167c9b50ba1b1a92bccdd4f2d6bd168a91cc" - integrity sha512-bB7DQHCTRDooZZdL3lk9wpL0+XuG3XLGHLh3cePnybsO3V0rdCAOQGpn/0R3aODmnTOOkCATJiD2hnL+5bwthA== +"@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== dependencies: - "@ethersproject/bytes" "^5.6.1" + "@ethersproject/bytes" "^5.7.0" js-sha3 "0.8.0" -"@ethersproject/logger@5.6.0", "@ethersproject/logger@^5.6.0": - version "5.6.0" - resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.6.0.tgz#d7db1bfcc22fd2e4ab574cba0bb6ad779a9a3e7a" - integrity sha512-BiBWllUROH9w+P21RzoxJKzqoqpkyM1pRnEKG69bulE9TSQD8SAIvTQqIMZmmCO8pUNkgLP1wndX1gKghSpBmg== - -"@ethersproject/networks@5.6.4", "@ethersproject/networks@^5.6.3": - version "5.6.4" - resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.6.4.tgz#51296d8fec59e9627554f5a8a9c7791248c8dc07" - integrity sha512-KShHeHPahHI2UlWdtDMn2lJETcbtaJge4k7XSjDR9h79QTd6yQJmv6Cp2ZA4JdqWnhszAOLSuJEd9C0PRw7hSQ== - dependencies: - "@ethersproject/logger" "^5.6.0" - -"@ethersproject/pbkdf2@5.6.1", "@ethersproject/pbkdf2@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.6.1.tgz#f462fe320b22c0d6b1d72a9920a3963b09eb82d1" - integrity sha512-k4gRQ+D93zDRPNUfmduNKq065uadC2YjMP/CqwwX5qG6R05f47boq6pLZtV/RnC4NZAYOPH1Cyo54q0c9sshRQ== - dependencies: - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/sha2" "^5.6.1" - -"@ethersproject/properties@5.6.0", "@ethersproject/properties@^5.6.0": - version "5.6.0" - resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.6.0.tgz#38904651713bc6bdd5bdd1b0a4287ecda920fa04" - integrity sha512-szoOkHskajKePTJSZ46uHUWWkbv7TzP2ypdEK6jGMqJaEt2sb0jCgfBo0gH0m2HBpRixMuJ6TBRaQCF7a9DoCg== - dependencies: - "@ethersproject/logger" "^5.6.0" - -"@ethersproject/providers@5.6.8", "@ethersproject/providers@^5.4.5": - version "5.6.8" - resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.6.8.tgz#22e6c57be215ba5545d3a46cf759d265bb4e879d" - integrity sha512-Wf+CseT/iOJjrGtAOf3ck9zS7AgPmr2fZ3N97r4+YXN3mBePTG2/bJ8DApl9mVwYL+RpYbNxMEkEp4mPGdwG/w== - dependencies: - "@ethersproject/abstract-provider" "^5.6.1" - "@ethersproject/abstract-signer" "^5.6.2" - "@ethersproject/address" "^5.6.1" - "@ethersproject/base64" "^5.6.1" - "@ethersproject/basex" "^5.6.1" - "@ethersproject/bignumber" "^5.6.2" - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/constants" "^5.6.1" - "@ethersproject/hash" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - "@ethersproject/networks" "^5.6.3" - "@ethersproject/properties" "^5.6.0" - "@ethersproject/random" "^5.6.1" - "@ethersproject/rlp" "^5.6.1" - "@ethersproject/sha2" "^5.6.1" - "@ethersproject/strings" "^5.6.1" - "@ethersproject/transactions" "^5.6.2" - "@ethersproject/web" "^5.6.1" +"@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" + integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== + +"@ethersproject/networks@5.7.0", "@ethersproject/networks@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.0.tgz#df72a392f1a63a57f87210515695a31a245845ad" + integrity sha512-MG6oHSQHd4ebvJrleEQQ4HhVu8Ichr0RDYEfHzsVAVjHNM+w36x9wp9r+hf1JstMXtseXDtkiVoARAG6M959AA== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/pbkdf2@5.7.0", "@ethersproject/pbkdf2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz#d2267d0a1f6e123f3771007338c47cccd83d3102" + integrity sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + +"@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" + integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/providers@5.7.0", "@ethersproject/providers@^5.4.5": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.0.tgz#a885cfc7650a64385e7b03ac86fe9c2d4a9c2c63" + integrity sha512-+TTrrINMzZ0aXtlwO/95uhAggKm4USLm1PbeCBR/3XZ7+Oey+3pMyddzZEyRhizHpy1HXV0FRWRMI1O3EGYibA== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" bech32 "1.1.4" ws "7.4.6" -"@ethersproject/random@5.6.1", "@ethersproject/random@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.6.1.tgz#66915943981bcd3e11bbd43733f5c3ba5a790255" - integrity sha512-/wtPNHwbmng+5yi3fkipA8YBT59DdkGRoC2vWk09Dci/q5DlgnMkhIycjHlavrvrjJBkFjO/ueLyT+aUDfc4lA== +"@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.7.0.tgz#af19dcbc2484aae078bb03656ec05df66253280c" + integrity sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ== dependencies: - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/logger" "^5.6.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" -"@ethersproject/rlp@5.6.1", "@ethersproject/rlp@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.6.1.tgz#df8311e6f9f24dcb03d59a2bac457a28a4fe2bd8" - integrity sha512-uYjmcZx+DKlFUk7a5/W9aQVaoEC7+1MOBgNtvNg13+RnuUwT4F0zTovC0tmay5SmRslb29V1B7Y5KCri46WhuQ== +"@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" + integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== dependencies: - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/logger" "^5.6.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" -"@ethersproject/sha2@5.6.1", "@ethersproject/sha2@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.6.1.tgz#211f14d3f5da5301c8972a8827770b6fd3e51656" - integrity sha512-5K2GyqcW7G4Yo3uenHegbXRPDgARpWUiXc6RiF7b6i/HXUoWlb7uCARh7BAHg7/qT/Q5ydofNwiZcim9qpjB6g== +"@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" + integrity sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw== dependencies: - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/logger" "^5.6.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" hash.js "1.1.7" -"@ethersproject/signing-key@5.6.2", "@ethersproject/signing-key@^5.6.2": - version "5.6.2" - resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.6.2.tgz#8a51b111e4d62e5a62aee1da1e088d12de0614a3" - integrity sha512-jVbu0RuP7EFpw82vHcL+GP35+KaNruVAZM90GxgQnGqB6crhBqW/ozBfFvdeImtmb4qPko0uxXjn8l9jpn0cwQ== +"@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" + integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== dependencies: - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - "@ethersproject/properties" "^5.6.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" bn.js "^5.2.1" elliptic "6.5.4" hash.js "1.1.7" -"@ethersproject/solidity@5.6.1", "@ethersproject/solidity@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.6.1.tgz#5845e71182c66d32e6ec5eefd041fca091a473e2" - integrity sha512-KWqVLkUUoLBfL1iwdzUVlkNqAUIFMpbbeH0rgCfKmJp0vFtY4AsaN91gHKo9ZZLkC4UOm3cI3BmMV4N53BOq4g== - dependencies: - "@ethersproject/bignumber" "^5.6.2" - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/keccak256" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - "@ethersproject/sha2" "^5.6.1" - "@ethersproject/strings" "^5.6.1" - -"@ethersproject/strings@5.6.1", "@ethersproject/strings@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.6.1.tgz#dbc1b7f901db822b5cafd4ebf01ca93c373f8952" - integrity sha512-2X1Lgk6Jyfg26MUnsHiT456U9ijxKUybz8IM1Vih+NJxYtXhmvKBcHOmvGqpFSVJ0nQ4ZCoIViR8XlRw1v/+Cw== - dependencies: - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/constants" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - -"@ethersproject/transactions@5.6.2", "@ethersproject/transactions@^5.6.2": - version "5.6.2" - resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.6.2.tgz#793a774c01ced9fe7073985bb95a4b4e57a6370b" - integrity sha512-BuV63IRPHmJvthNkkt9G70Ullx6AcM+SDc+a8Aw/8Yew6YwT51TcBKEp1P4oOQ/bP25I18JJr7rcFRgFtU9B2Q== - dependencies: - "@ethersproject/address" "^5.6.1" - "@ethersproject/bignumber" "^5.6.2" - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/constants" "^5.6.1" - "@ethersproject/keccak256" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - "@ethersproject/properties" "^5.6.0" - "@ethersproject/rlp" "^5.6.1" - "@ethersproject/signing-key" "^5.6.2" - -"@ethersproject/units@5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.6.1.tgz#ecc590d16d37c8f9ef4e89e2005bda7ddc6a4e6f" - integrity sha512-rEfSEvMQ7obcx3KWD5EWWx77gqv54K6BKiZzKxkQJqtpriVsICrktIQmKl8ReNToPeIYPnFHpXvKpi068YFZXw== - dependencies: - "@ethersproject/bignumber" "^5.6.2" - "@ethersproject/constants" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - -"@ethersproject/wallet@5.6.2", "@ethersproject/wallet@^5.5.0": - version "5.6.2" - resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.6.2.tgz#cd61429d1e934681e413f4bc847a5f2f87e3a03c" - integrity sha512-lrgh0FDQPuOnHcF80Q3gHYsSUODp6aJLAdDmDV0xKCN/T7D99ta1jGVhulg3PY8wiXEngD0DfM0I2XKXlrqJfg== - dependencies: - "@ethersproject/abstract-provider" "^5.6.1" - "@ethersproject/abstract-signer" "^5.6.2" - "@ethersproject/address" "^5.6.1" - "@ethersproject/bignumber" "^5.6.2" - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/hash" "^5.6.1" - "@ethersproject/hdnode" "^5.6.2" - "@ethersproject/json-wallets" "^5.6.1" - "@ethersproject/keccak256" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - "@ethersproject/properties" "^5.6.0" - "@ethersproject/random" "^5.6.1" - "@ethersproject/signing-key" "^5.6.2" - "@ethersproject/transactions" "^5.6.2" - "@ethersproject/wordlists" "^5.6.1" - -"@ethersproject/web@5.6.1", "@ethersproject/web@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.6.1.tgz#6e2bd3ebadd033e6fe57d072db2b69ad2c9bdf5d" - integrity sha512-/vSyzaQlNXkO1WV+RneYKqCJwualcUdx/Z3gseVovZP0wIlOFcCE1hkRhKBH8ImKbGQbMl9EAAyJFrJu7V0aqA== - dependencies: - "@ethersproject/base64" "^5.6.1" - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - "@ethersproject/properties" "^5.6.0" - "@ethersproject/strings" "^5.6.1" - -"@ethersproject/wordlists@5.6.1", "@ethersproject/wordlists@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.6.1.tgz#1e78e2740a8a21e9e99947e47979d72e130aeda1" - integrity sha512-wiPRgBpNbNwCQFoCr8bcWO8o5I810cqO6mkdtKfLKFlLxeCWcnzDi4Alu8iyNzlhYuS9npCwivMbRWF19dyblw== - dependencies: - "@ethersproject/bytes" "^5.6.1" - "@ethersproject/hash" "^5.6.1" - "@ethersproject/logger" "^5.6.0" - "@ethersproject/properties" "^5.6.0" - "@ethersproject/strings" "^5.6.1" +"@ethersproject/solidity@5.7.0", "@ethersproject/solidity@^5.6.1": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" + integrity sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" + integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" + integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + +"@ethersproject/units@5.7.0", "@ethersproject/units@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" + integrity sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/wallet@5.7.0", "@ethersproject/wallet@^5.5.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" + integrity sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/json-wallets" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/web@5.7.0", "@ethersproject/web@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.0.tgz#40850c05260edad8b54827923bbad23d96aac0bc" + integrity sha512-ApHcbbj+muRASVDSCl/tgxaH2LBkRMEYfLOLVa0COipx0+nlu0QKet7U2lEg0vdkh8XRSLf2nd1f1Uk9SrVSGA== + dependencies: + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/wordlists@5.7.0", "@ethersproject/wordlists@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.7.0.tgz#8fb2c07185d68c3e09eb3bfd6e779ba2774627f5" + integrity sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" "@graphql-codegen/add@^3.1.0": version "3.2.0" @@ -1450,9 +1451,9 @@ fastq "^1.6.0" "@nomiclabs/hardhat-ethers@^2.0.5": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.1.0.tgz#9b7dc94d669ad9dc286b94f6f2f1513118c7027b" - integrity sha512-vlW90etB3675QWG7tMrHaDoTa7ymMB7irM4DAQ98g8zJoe9YqEggeDnbO6v5b+BLth/ty4vN6Ko/kaqRN1krHw== + version "2.1.1" + resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.1.1.tgz#3f1d1ab49813d1bae4c035cc1adec224711e528b" + integrity sha512-Gg0IFkT/DW3vOpih4/kMjeZCLYqtfgECLeLXTs7ZDPzcK0cfoc5wKk4nq5n/izCUzdhidO/Utd6ptF9JrWwWVA== "@rollup/plugin-commonjs@^21.0.1": version "21.1.0" @@ -2087,6 +2088,14 @@ auto-bind@~4.0.0: resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-4.0.0.tgz#e3589fc6c2da8f7ca43ba9f84fa52a744fc997fb" integrity sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ== +axios-mock-adapter@^1.21.2: + version "1.21.2" + resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.21.2.tgz#87a48f80aa89bb1ab1ad630fa467975e30aa4721" + integrity sha512-jzyNxU3JzB2XVhplZboUcF0YDs7xuExzoRSHXPHr+UQajaGmcTqvkkUADgkVI2WkGlpZ1zZlMVdcTMU0ejV8zQ== + dependencies: + fast-deep-equal "^3.1.3" + is-buffer "^2.0.5" + axios@^0.24.0: version "0.24.0" resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" @@ -2621,9 +2630,9 @@ cookie@^0.4.1: integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== core-js-pure@^3.0.1: - version "3.24.0" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.24.0.tgz#10eeb90dbf0d670a6b22b081aecc7deb2faec7e1" - integrity sha512-uzMmW8cRh7uYw4JQtzqvGWRyC2T5+4zipQLQdi2FmiRqP83k3d6F3stv2iAlNhOs6cXN401FCD5TL0vvleuHgA== + version "3.25.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.25.0.tgz#f8d1f176ff29abbfeb610110de891d5ae5a361d4" + integrity sha512-IeHpLwk3uoci37yoI2Laty59+YqH9x5uR65/yiA0ARAJrTrN4YU0rmauLWfvqOuk77SlNJXj2rM6oT/dBD87+A== cosmiconfig-toml-loader@1.0.0: version "1.0.0" @@ -3135,40 +3144,40 @@ ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.4, ethereumjs-util@^7.1.5: rlp "^2.2.4" ethers@^5.0.0: - version "5.6.9" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.6.9.tgz#4e12f8dfcb67b88ae7a78a9519b384c23c576a4d" - integrity sha512-lMGC2zv9HC5EC+8r429WaWu3uWJUCgUCt8xxKCFqkrFuBDZXDYIdzDUECxzjf2BMF8IVBByY1EBoGSL3RTm8RA== - dependencies: - "@ethersproject/abi" "5.6.4" - "@ethersproject/abstract-provider" "5.6.1" - "@ethersproject/abstract-signer" "5.6.2" - "@ethersproject/address" "5.6.1" - "@ethersproject/base64" "5.6.1" - "@ethersproject/basex" "5.6.1" - "@ethersproject/bignumber" "5.6.2" - "@ethersproject/bytes" "5.6.1" - "@ethersproject/constants" "5.6.1" - "@ethersproject/contracts" "5.6.2" - "@ethersproject/hash" "5.6.1" - "@ethersproject/hdnode" "5.6.2" - "@ethersproject/json-wallets" "5.6.1" - "@ethersproject/keccak256" "5.6.1" - "@ethersproject/logger" "5.6.0" - "@ethersproject/networks" "5.6.4" - "@ethersproject/pbkdf2" "5.6.1" - "@ethersproject/properties" "5.6.0" - "@ethersproject/providers" "5.6.8" - "@ethersproject/random" "5.6.1" - "@ethersproject/rlp" "5.6.1" - "@ethersproject/sha2" "5.6.1" - "@ethersproject/signing-key" "5.6.2" - "@ethersproject/solidity" "5.6.1" - "@ethersproject/strings" "5.6.1" - "@ethersproject/transactions" "5.6.2" - "@ethersproject/units" "5.6.1" - "@ethersproject/wallet" "5.6.2" - "@ethersproject/web" "5.6.1" - "@ethersproject/wordlists" "5.6.1" + version "5.7.0" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.0.tgz#0055da174b9e076b242b8282638bc94e04b39835" + integrity sha512-5Xhzp2ZQRi0Em+0OkOcRHxPzCfoBfgtOQA+RUylSkuHbhTEaQklnYi2hsWbRgs3ztJsXVXd9VKBcO1ScWL8YfA== + dependencies: + "@ethersproject/abi" "5.7.0" + "@ethersproject/abstract-provider" "5.7.0" + "@ethersproject/abstract-signer" "5.7.0" + "@ethersproject/address" "5.7.0" + "@ethersproject/base64" "5.7.0" + "@ethersproject/basex" "5.7.0" + "@ethersproject/bignumber" "5.7.0" + "@ethersproject/bytes" "5.7.0" + "@ethersproject/constants" "5.7.0" + "@ethersproject/contracts" "5.7.0" + "@ethersproject/hash" "5.7.0" + "@ethersproject/hdnode" "5.7.0" + "@ethersproject/json-wallets" "5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/logger" "5.7.0" + "@ethersproject/networks" "5.7.0" + "@ethersproject/pbkdf2" "5.7.0" + "@ethersproject/properties" "5.7.0" + "@ethersproject/providers" "5.7.0" + "@ethersproject/random" "5.7.0" + "@ethersproject/rlp" "5.7.0" + "@ethersproject/sha2" "5.7.0" + "@ethersproject/signing-key" "5.7.0" + "@ethersproject/solidity" "5.7.0" + "@ethersproject/strings" "5.7.0" + "@ethersproject/transactions" "5.7.0" + "@ethersproject/units" "5.7.0" + "@ethersproject/wallet" "5.7.0" + "@ethersproject/web" "5.7.0" + "@ethersproject/wordlists" "5.7.0" ethjs-util@0.1.6, ethjs-util@^0.1.6: version "0.1.6" @@ -3581,9 +3590,9 @@ growl@1.10.5: integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== hardhat@^2.9.3: - version "2.10.1" - resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.10.1.tgz#37fdc0c96d6a5d16b322269db2ad8f9f115c4046" - integrity sha512-0FN9TyCtn7Lt25SB2ei2G7nA2rZjP+RN6MvFOm+zYwherxLZNo6RbD8nDz88eCbhRapevmXqOiL2nM8INKsjmA== + version "2.10.2" + resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.10.2.tgz#ff94ee4cb144a9c114641581ff5e4d7bea5f93a9" + integrity sha512-L/KvDDT/MA6332uAtYTqdcHoSABljw4pPjHQe5SHdIJ+xKfaSc6vDKw03CmrQ5Xup0gHs8XnVSBpZo1AbbIW7g== dependencies: "@ethereumjs/block" "^3.6.2" "@ethereumjs/blockchain" "^5.5.2" @@ -3870,6 +3879,11 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-buffer@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + is-builtin-module@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.1.0.tgz#6fdb24313b1c03b75f8b9711c0feb8c30b903b00" @@ -4640,6 +4654,11 @@ mocha@^8.2.1: yargs-parser "20.2.4" yargs-unparser "2.0.0" +mockdate@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb" + integrity sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -5833,9 +5852,9 @@ undici@5.5.1: integrity sha512-MEvryPLf18HvlCbLSzCW0U00IMftKGI5udnjrQbC5D4P0Hodwffhv+iGfWuJwg16Y/TK11ZFK8i+BPVW2z/eAw== undici@^5.4.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.8.0.tgz#dec9a8ccd90e5a1d81d43c0eab6503146d649a4f" - integrity sha512-1F7Vtcez5w/LwH2G2tGnFIihuWUlc58YidwLiCv+jR2Z50x0tNXpRRw7eOIJ+GvqCqIkg9SB7NWAJ/T9TLfv8Q== + version "5.10.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.10.0.tgz#dd9391087a90ccfbd007568db458674232ebf014" + integrity sha512-c8HsD3IbwmjjbLvoZuRI26TZic+TSEe8FPMLLOkN1AfYRhdjnKBU6yL+IwcSCbdZiX4e5t0lfMDLDCqj4Sq70g== universalify@^0.1.0: version "0.1.2" From 17274829f4ad13ef85f53f32b11c8083bff226b6 Mon Sep 17 00:00:00 2001 From: bronco Date: Mon, 15 Aug 2022 11:06:31 +0200 Subject: [PATCH 32/65] adding blockNumber data source --- balancer-js/src/lib/constants/config.ts | 8 +++ .../src/modules/data/block-number/index.ts | 61 +++++++++++++++++++ balancer-js/src/modules/data/index.ts | 1 + balancer-js/src/types.ts | 1 + 4 files changed, 71 insertions(+) create mode 100644 balancer-js/src/modules/data/block-number/index.ts diff --git a/balancer-js/src/lib/constants/config.ts b/balancer-js/src/lib/constants/config.ts index 330867487..2874b053b 100644 --- a/balancer-js/src/lib/constants/config.ts +++ b/balancer-js/src/lib/constants/config.ts @@ -30,6 +30,8 @@ export const BALANCER_NETWORK_CONFIG: Record = { 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2', gaugesSubgraph: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', + blockNumberSubgraph: + 'https://api.thegraph.com/subgraphs/name/blocklytics/ethereum-blocks', }, pools: { wETHwstETH: { @@ -56,6 +58,8 @@ export const BALANCER_NETWORK_CONFIG: Record = { 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-polygon-v2', gaugesSubgraph: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', + blockNumberSubgraph: + 'https://api.thegraph.com/subgraphs/name/ianlapham/polygon-blocks', }, pools: {}, }, @@ -76,6 +80,8 @@ export const BALANCER_NETWORK_CONFIG: Record = { 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-arbitrum-v2', gaugesSubgraph: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', + blockNumberSubgraph: + 'https://api.thegraph.com/subgraphs/name/ianlapham/arbitrum-one-blocks', }, pools: {}, }, @@ -159,6 +165,8 @@ export const BALANCER_NETWORK_CONFIG: Record = { 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-goerli-v2', gaugesSubgraph: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', + blockNumberSubgraph: + 'https://api.thegraph.com/subgraphs/name/blocklytics/goerli-blocks', }, pools: {}, }, diff --git a/balancer-js/src/modules/data/block-number/index.ts b/balancer-js/src/modules/data/block-number/index.ts new file mode 100644 index 000000000..cd6d5fdd3 --- /dev/null +++ b/balancer-js/src/modules/data/block-number/index.ts @@ -0,0 +1,61 @@ +import { Findable } from '../types'; +import axios from 'axios'; + +const query = (timestamp: string) => `{ + blocks(first: 1, orderBy: timestamp, orderDirection: asc, where: { timestamp_gt: ${timestamp} }) { + number + } +}`; + +interface BlockNumberResponse { + data: { + blocks: [ + { + number: string; + } + ]; + }; +} + +const fetchBlockByTime = async ( + endpoint: string, + timestamp: string +): Promise => { + console.time(`fetching blocks ${timestamp}`); + const payload = { + query: query(timestamp), + }; + + const response = await axios.post(endpoint, payload); + console.timeEnd(`fetching blocks ${timestamp}`); + + const { + data: { blocks }, + } = response.data as BlockNumberResponse; + + return parseInt(blocks[0].number); +}; + +export class BlockNumberRepository implements Findable { + blocks: { [ts: string]: Promise } = {}; + + constructor(private endpoint: string) {} + + async find(from: string): Promise { + if (from == 'dayAgo') { + const dayAgo = `${Math.floor(Date.now() / 1000) - 86400}`; + if (!this.blocks[dayAgo]) { + this.blocks = { + ...this.blocks, + [dayAgo]: fetchBlockByTime(this.endpoint, dayAgo), + }; + } + return this.blocks[dayAgo]; + } + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async findBy(attribute = '', value = ''): Promise { + return; + } +} diff --git a/balancer-js/src/modules/data/index.ts b/balancer-js/src/modules/data/index.ts index 3b4ad3d36..8f1c94ce0 100644 --- a/balancer-js/src/modules/data/index.ts +++ b/balancer-js/src/modules/data/index.ts @@ -6,3 +6,4 @@ export * from './token'; export * from './token-prices'; export * from './fee-distributor/repository'; export * from './token-yields/repository'; +export * from './block-number'; diff --git a/balancer-js/src/types.ts b/balancer-js/src/types.ts index 3dc9b0d23..6e4a52c27 100644 --- a/balancer-js/src/types.ts +++ b/balancer-js/src/types.ts @@ -55,6 +55,7 @@ export interface BalancerNetworkConfig { urls: { subgraph: string; gaugesSubgraph: string; + blockNumberSubgraph?: string; }; pools: { wETHwstETH?: PoolReference; From 9c7877c93f141c4978e881254f39ef1e2044b1b5 Mon Sep 17 00:00:00 2001 From: bronco Date: Wed, 31 Aug 2022 15:27:46 +0200 Subject: [PATCH 33/65] fee collector data --- .../modules/data/fee-collector/repository.ts | 51 +++++++++++++++++++ balancer-js/src/modules/data/index.ts | 1 + 2 files changed, 52 insertions(+) create mode 100644 balancer-js/src/modules/data/fee-collector/repository.ts diff --git a/balancer-js/src/modules/data/fee-collector/repository.ts b/balancer-js/src/modules/data/fee-collector/repository.ts new file mode 100644 index 000000000..e9e395d98 --- /dev/null +++ b/balancer-js/src/modules/data/fee-collector/repository.ts @@ -0,0 +1,51 @@ +import { Interface } from '@ethersproject/abi'; +import { Provider } from '@ethersproject/providers'; +import { Contract } from '@ethersproject/contracts'; +import { formatUnits } from '@ethersproject/units'; +import { Findable } from '../types'; + +const vaultInterface = new Interface([ + 'function getProtocolFeesCollector() view returns (address)', +]); + +const protocolFeesCollectorInterface = new Interface([ + 'function getSwapFeePercentage() view returns (uint)', +]); + +// Using singleton here, so subsequent calls will return the same promise +let swapFeePercentagePromise: Promise; + +export class FeeCollectorRepository implements Findable { + vault: Contract; + swapFeePercentage?: number; + + constructor(vaultAddress: string, private provider: Provider) { + this.vault = new Contract(vaultAddress, vaultInterface, this.provider); + } + + async fetch(): Promise { + const address = (await this.vault.getProtocolFeesCollector()) as string; + + const collector = new Contract( + address, + protocolFeesCollectorInterface, + this.provider + ); + const fees = (await collector.getSwapFeePercentage()) as string; + + return parseFloat(formatUnits(fees, 18)); + } + + async find(): Promise { + if (!swapFeePercentagePromise) { + swapFeePercentagePromise = this.fetch(); + } + this.swapFeePercentage = await swapFeePercentagePromise; + + return this.swapFeePercentage; + } + + async findBy(): Promise { + return this.find(); + } +} diff --git a/balancer-js/src/modules/data/index.ts b/balancer-js/src/modules/data/index.ts index 8f1c94ce0..194369689 100644 --- a/balancer-js/src/modules/data/index.ts +++ b/balancer-js/src/modules/data/index.ts @@ -5,5 +5,6 @@ export * from './pool'; export * from './token'; export * from './token-prices'; export * from './fee-distributor/repository'; +export * from './fee-collector/repository'; export * from './token-yields/repository'; export * from './block-number'; From 9a7a5e1108d146b27b49a4c5973dfcc7fdb2c369 Mon Sep 17 00:00:00 2001 From: bronco Date: Sat, 16 Jul 2022 11:33:20 +0200 Subject: [PATCH 34/65] token data provider type refactor --- balancer-js/src/modules/data/token/static.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/balancer-js/src/modules/data/token/static.ts b/balancer-js/src/modules/data/token/static.ts index fd81d2b69..25f28375d 100644 --- a/balancer-js/src/modules/data/token/static.ts +++ b/balancer-js/src/modules/data/token/static.ts @@ -1,7 +1,7 @@ -import { Token } from '@/types'; -import { TokenAttribute, TokenProvider } from './types'; +import type { Findable, Token } from '@/types'; +import type { TokenAttribute } from './types'; -export class StaticTokenProvider implements TokenProvider { +export class StaticTokenProvider implements Findable { constructor(private tokens: Token[]) {} async find(address: string): Promise { From cb06a97b1ba41d9060f09e981f04db385e5d44e9 Mon Sep 17 00:00:00 2001 From: bronco Date: Sun, 17 Jul 2022 18:13:01 +0200 Subject: [PATCH 35/65] data factories --- balancer-js/src/test/factories/data.ts | 70 +++++++++++++++++++++++++ balancer-js/src/test/factories/index.ts | 3 +- balancer-js/src/test/factories/sdk.ts | 2 + 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 balancer-js/src/test/factories/data.ts diff --git a/balancer-js/src/test/factories/data.ts b/balancer-js/src/test/factories/data.ts new file mode 100644 index 000000000..929bd45f3 --- /dev/null +++ b/balancer-js/src/test/factories/data.ts @@ -0,0 +1,70 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { + BalancerDataRepositories, + Findable, + LiquidityGauge, + Pool, + Price, + Searchable, + Token, +} from '@/types'; + +export const findable = ( + map: Map +): Findable & Searchable => ({ + find: (id: string) => Promise.resolve(map.get(id)), + findBy: (param: P, value: string) => Promise.resolve(map.get(value)), + all: () => Promise.resolve(Object.values(map)), + where: (filters: (arg: T) => boolean) => Promise.resolve(Object.values(map)), +}); + +export const stubbed = ( + value: unknown +): Findable & Searchable => ({ + find: (id: string) => Promise.resolve(value as T), + findBy: (param: P, _: string) => Promise.resolve(value as T), + all: () => Promise.resolve([value as T]), + where: (filters: (arg: T) => boolean) => Promise.resolve([value as T]), +}); + +interface IdentifiableArray { + id: number; +} + +export const array2map = (array: T[]): Map => { + const map = new Map(); + for (const item of array) { + map.set((item as unknown as IdentifiableArray).id, item); + } + return map; +}; + +export const repositores = ({ + pools = stubbed(undefined), + yesterdaysPools = stubbed(undefined), + tokenPrices = stubbed({ usd: '1' }), + tokenMeta = stubbed({ decimals: 18 }), + liquidityGauges = stubbed(undefined), + feeDistributor = { + multicallData: () => + Promise.resolve({ + balAmount: 1, + bbAUsdAmount: 1, + veBalSupply: 1, + bbAUsdPrice: 1, + balAddress: '', + }), + }, + feeCollector = stubbed(0), + tokenYields = stubbed(100), +}): BalancerDataRepositories => ({ + pools, + yesterdaysPools, + tokenPrices, + tokenMeta, + liquidityGauges, + feeDistributor, + feeCollector, + tokenYields, +}); diff --git a/balancer-js/src/test/factories/index.ts b/balancer-js/src/test/factories/index.ts index cdd965e6f..8e86224dc 100644 --- a/balancer-js/src/test/factories/index.ts +++ b/balancer-js/src/test/factories/index.ts @@ -1,6 +1,7 @@ import * as sor from './sor'; import * as sdk from './sdk'; +import * as data from './data'; -const factories = { ...sor, ...sdk }; +const factories = { ...sor, ...sdk, data }; export { factories }; diff --git a/balancer-js/src/test/factories/sdk.ts b/balancer-js/src/test/factories/sdk.ts index 7133ae0ca..138cd4d99 100644 --- a/balancer-js/src/test/factories/sdk.ts +++ b/balancer-js/src/test/factories/sdk.ts @@ -26,6 +26,7 @@ const poolFactory = Factory.define(({ params, afterBuild }) => { return { id: '0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e', + name: 'Test Pool', address: '0xa6f548df93de924d73be7d25dc02554c6bd66db5', poolType: PoolType.Weighted, swapFee: '0.001', @@ -34,6 +35,7 @@ const poolFactory = Factory.define(({ params, afterBuild }) => { tokensList: [], totalWeight: '1', totalShares: '1', + totalLiquidity: '0', }; }); From 7eb57b5c319b75a45dd5ba731718f7e2ea126b5a Mon Sep 17 00:00:00 2001 From: bronco Date: Sat, 16 Jul 2022 12:22:22 +0200 Subject: [PATCH 36/65] APRs --- balancer-js/package.json | 3 +- balancer-js/src/lib/constants/config.ts | 22 + .../modules/pools/apr/apr.integration.spec.ts | 128 ++++++ balancer-js/src/modules/pools/apr/apr.spec.ts | 197 +++++++++ balancer-js/src/modules/pools/apr/apr.ts | 401 ++++++++++++++++++ .../src/modules/pools/apr/protocol-revenue.ts | 30 ++ 6 files changed, 780 insertions(+), 1 deletion(-) create mode 100644 balancer-js/src/modules/pools/apr/apr.integration.spec.ts create mode 100644 balancer-js/src/modules/pools/apr/apr.spec.ts create mode 100644 balancer-js/src/modules/pools/apr/apr.ts create mode 100644 balancer-js/src/modules/pools/apr/protocol-revenue.ts diff --git a/balancer-js/package.json b/balancer-js/package.json index 0a1fb1d78..142148ccb 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -83,7 +83,8 @@ "ts-node": "^10.4.0", "tsconfig-paths": "^3.12.0", "typechain": "^5.1.1", - "typescript": "^4.0.2" + "typescript": "^4.0.2", + "mockdate": "^3.0.5" }, "dependencies": { "@balancer-labs/sor": "^4.0.1-beta.3", diff --git a/balancer-js/src/lib/constants/config.ts b/balancer-js/src/lib/constants/config.ts index 2874b053b..3ec0c2b3b 100644 --- a/balancer-js/src/lib/constants/config.ts +++ b/balancer-js/src/lib/constants/config.ts @@ -23,6 +23,9 @@ export const BALANCER_NETWORK_CONFIG: Record = { ], stETH: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', wstETH: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', + bal: '0xba100000625a3754423978a60c9317c58a424e3d', + veBal: '0xC128a9954e6c874eA3d62ce62B468bA073093F25', + bbaUsd: '0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb2', }, }, urls: { @@ -51,6 +54,9 @@ export const BALANCER_NETWORK_CONFIG: Record = { }, tokens: { wrappedNativeAsset: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', + bal: '', + veBal: '', + bbaUsd: '', }, }, urls: { @@ -70,9 +76,13 @@ export const BALANCER_NETWORK_CONFIG: Record = { vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', multicall: '0x269ff446d9892c9e19082564df3f5e8741e190a1', gaugeController: '', + feeDistributor: '', }, tokens: { wrappedNativeAsset: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', + bal: '', + veBal: '', + bbaUsd: '', }, }, urls: { @@ -96,6 +106,9 @@ export const BALANCER_NETWORK_CONFIG: Record = { }, tokens: { wrappedNativeAsset: '0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1', + bal: '', + veBal: '', + bbaUsd: '', }, }, urls: { @@ -117,6 +130,9 @@ export const BALANCER_NETWORK_CONFIG: Record = { }, tokens: { wrappedNativeAsset: '0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1', + bal: '', + veBal: '', + bbaUsd: '', }, }, urls: { @@ -137,6 +153,9 @@ export const BALANCER_NETWORK_CONFIG: Record = { }, tokens: { wrappedNativeAsset: '0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1', + bal: '', + veBal: '', + bbaUsd: '', }, }, urls: { @@ -158,6 +177,9 @@ export const BALANCER_NETWORK_CONFIG: Record = { }, tokens: { wrappedNativeAsset: '0xdFCeA9088c8A88A76FF74892C1457C17dfeef9C1', + bal: '', + veBal: '', + bbaUsd: '', }, }, urls: { diff --git a/balancer-js/src/modules/pools/apr/apr.integration.spec.ts b/balancer-js/src/modules/pools/apr/apr.integration.spec.ts new file mode 100644 index 000000000..a73df1075 --- /dev/null +++ b/balancer-js/src/modules/pools/apr/apr.integration.spec.ts @@ -0,0 +1,128 @@ +import dotenv from 'dotenv'; +import { expect } from 'chai'; +import MockDate from 'mockdate'; +import { BalancerSDK } from '@/modules/sdk.module'; +import { JsonRpcProvider } from '@ethersproject/providers'; + +dotenv.config(); + +const sdk = new BalancerSDK({ + network: 1, + rpcUrl: 'http://127.0.0.1:8545', +}); + +const { pools } = sdk; + +const ethStEth = + '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080'; + +const veBalId = + '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014'; + +const usdStable = + '0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe'; + +const btcEth = + '0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e'; + +const ethStEthCopy = + '0x851523a36690bf267bbfec389c823072d82921a90002000000000000000001ed'; + +const auraBALveBAL = + '0x3dd0843a028c86e0b760b1a76929d1c5ef93a2dd000200000000000000000249'; + +describe('happy case', () => { + // Time when veBal used to recieve procotol revenues + const now = new Date('2022-08-19 11:11:11').getTime(); + + before(async function () { + MockDate.set(now); + + const rpcProvider = sdk.rpcProvider as JsonRpcProvider; + + await rpcProvider.send('hardhat_reset', [ + { + forking: { + jsonRpcUrl: process.env.RPC_URL || process.env.ALCHEMY_URL, + blockNumber: 15370937, // 2022-08-19 11:11:11 + }, + }, + ]); + }); + + after(() => { + MockDate.reset(); + }); + + describe('duplicated pool with an empty gauge', () => { + it('has tokenAprs', async () => { + const pool = await pools.find(ethStEthCopy); + if (pool) { + const apr = await pools.apr(pool); + expect(apr && apr.tokenAprs).to.be.greaterThan(1); + } else { + throw 'no pool found'; + } + }).timeout(120000); + }); + + describe('pool with yield tokens', () => { + it('has tokenAprs', async () => { + const pool = await pools.find(ethStEth); + if (pool) { + const apr = await pools.apr(pool); + expect(apr && apr.tokenAprs).to.be.greaterThan(1); + } else { + throw 'no pool found'; + } + }).timeout(120000); + }); + + describe('phantom pool with linear pools', () => { + it('has tokenAprs', async () => { + const pool = await pools.find(usdStable); + if (pool) { + const apr = await pools.apr(pool); + expect(apr && apr.tokenAprs).to.be.greaterThan(1); + } else { + throw 'no pool found'; + } + }).timeout(120000); + }); + + describe('veBal pool', () => { + it('receives protocol revenues', async () => { + const pool = await pools.find(veBalId); + if (pool) { + const apr = await pools.apr(pool); + expect(apr && apr.protocolApr).to.be.greaterThan(1); + } else { + throw 'no pool found'; + } + }).timeout(120000); + }); + + describe('weighted pool with gauge', () => { + it('receives staking rewards', async () => { + const pool = await pools.find(btcEth); + if (pool) { + const apr = await pools.apr(pool); + expect(apr && apr.stakingApr.min).to.be.greaterThan(1); + } else { + throw 'no pool found'; + } + }).timeout(120000); + }); + + describe('auraBAL / veBAL pool', () => { + it('has tokenAprs', async () => { + const pool = await pools.find(auraBALveBAL); + if (pool) { + const apr = await pools.apr(pool); + expect(apr && apr.tokenAprs).to.be.greaterThan(1); + } else { + throw 'no pool found'; + } + }).timeout(120000); + }); +}); diff --git a/balancer-js/src/modules/pools/apr/apr.spec.ts b/balancer-js/src/modules/pools/apr/apr.spec.ts new file mode 100644 index 000000000..000573d56 --- /dev/null +++ b/balancer-js/src/modules/pools/apr/apr.spec.ts @@ -0,0 +1,197 @@ +import { BigNumber } from '@ethersproject/bignumber'; +import { expect } from 'chai'; +import MockDate from 'mockdate'; +import { PoolApr } from './apr'; +import { factories } from '@/test/factories'; +import * as emissions from '@/modules/data/bal/emissions'; +import type { LiquidityGauge, Pool } from '@/types'; + +const wETH = factories.poolTokenFactory + .transient({ symbol: 'wETH' }) + .build({ weight: '0.5', balance: '364' }); +const wstETH = factories.poolTokenFactory + .transient({ symbol: 'wstETH' }) + .build({ weight: '0.5', balance: '1' }); +const tokens = [wstETH, wETH]; + +const now = new Date(); + +const poolData = factories.poolFactory.build({ + address: 'pool', + totalSwapFee: '1', + totalShares: '365', + tokens, +}); + +const yesterdaysPool = { + ...poolData, + totalSwapFee: '0', +}; + +const poolsMap = new Map([[poolData.address, poolData]]); +const poolRepository = factories.data.findable(poolsMap); +const yesterdaysPoolsMap = new Map([[yesterdaysPool.id, yesterdaysPool]]); +const yesterdaysPoolRepository = + factories.data.findable(yesterdaysPoolsMap); +const repositories = factories.data.repositores({ + pools: poolRepository, + yesterdaysPools: yesterdaysPoolRepository, +}); + +// TODO: move to factory +const baseGauge = { + id: 'gauge', + name: 'gauge', + address: 'address', + poolAddress: '1', + totalSupply: 1, + workingSupply: 1, + relativeWeight: 1, +}; + +// TODO: move to factory +const baseRewardToken = { + token: '0x0', + distributor: '0x0', + period_finish: BigNumber.from(Math.round(+now / 1000 + 7 * 86400)), + rate: BigNumber.from('31709792000'), // 1 / 365 / 86400 scaled to 1e18 + integral: BigNumber.from('0'), + last_update: BigNumber.from('0'), +}; + +describe('pool apr', () => { + before(() => { + MockDate.set(now); + }); + + after(() => { + MockDate.reset(); + }); + + describe('.swapFees', () => { + // Notice that create time is set to 1 year ago. + // With totalLiquidity and totalSwapFees = 100, it's 100% apr + it('are 10000 bsp APR', async () => { + const apr = await new PoolApr( + repositories.pools, + repositories.tokenPrices, + repositories.tokenMeta, + repositories.tokenYields, + repositories.feeCollector, + repositories.yesterdaysPools, + repositories.liquidityGauges, + repositories.feeDistributor + ).swapFees(poolData); + expect(apr).to.eq(10000); + }); + }); + + describe('.tokenAprs', () => { + describe('lido token', () => { + // It will equal 1%, because rate is 2% but weight is 50% + it('are 100 bsp (1%)', async () => { + const apr = await new PoolApr( + repositories.pools, + repositories.tokenPrices, + repositories.tokenMeta, + repositories.tokenYields, + repositories.feeCollector, + repositories.yesterdaysPools, + repositories.liquidityGauges, + repositories.feeDistributor + ).tokenAprs(poolData); + expect(apr).to.eq(100); + }); + }); + + describe('nested pools', () => { + // Setting up pool with 100% total APR + const pool = factories.poolFactory.build({ + address: 'pool1', + totalSwapFee: '1', + tokens, + }); + // Notice weight of the bptToken. + // It is defining share in tokenAprs + const bptToken = factories.poolTokenFactory.build({ + address: 'pool1', + decimals: 18, + balance: '365', + weight: '0.5', + }); + const poolWithBpt = factories.poolFactory.build({ + address: 'poolWithBpt', + tokens: [{ ...wETH, balance: '1' }, bptToken], + }); + poolsMap.set(pool.address, pool); + poolsMap.set(poolWithBpt.address, poolWithBpt); + + it('are 5000 bsp (50%) half of pool1 APR', async () => { + const apr = await new PoolApr( + repositories.pools, + repositories.tokenPrices, + repositories.tokenMeta, + factories.data.stubbed(undefined), + factories.data.stubbed(0), + repositories.yesterdaysPools, + repositories.liquidityGauges, + repositories.feeDistributor + ).tokenAprs(poolWithBpt); + + expect(apr).to.eq(10000 / 2); + }); + }); + }); + + describe('.stakingApr', () => { + // Notice one token in gauge is worth 40% of it's value + it('has bal rewards as ~40% apr', async () => { + const now = Math.round(Date.now() / 1000); + const balEmissions = emissions.between(now, now + 365 * 86400); + + const gauge = { + ...baseGauge, + workingSupply: balEmissions, + }; + + const apr = await new PoolApr( + repositories.pools, + repositories.tokenPrices, + repositories.tokenMeta, + factories.data.stubbed(undefined), + repositories.feeCollector, + repositories.yesterdaysPools, + factories.data.stubbed(gauge), + repositories.feeDistributor + ).stakingApr(poolData); + + expect(apr).to.eq(4000); + }); + }); + + describe('.rewardAprs', () => { + it('has token rewards', async () => { + const rewardTokens = { + address1: baseRewardToken, + address2: baseRewardToken, + }; + const gauge = { + ...baseGauge, + rewardTokens, + }; + + const apr = await new PoolApr( + repositories.pools, + repositories.tokenPrices, + repositories.tokenMeta, + factories.data.stubbed(undefined), + repositories.feeCollector, + repositories.yesterdaysPools, + factories.data.stubbed(gauge), + repositories.feeDistributor + ).rewardAprs(poolData); + + expect(apr.total).to.eq(20000); + }); + }); +}); diff --git a/balancer-js/src/modules/pools/apr/apr.ts b/balancer-js/src/modules/pools/apr/apr.ts new file mode 100644 index 000000000..6b196990a --- /dev/null +++ b/balancer-js/src/modules/pools/apr/apr.ts @@ -0,0 +1,401 @@ +import { formatUnits } from '@ethersproject/units'; +import * as emissions from '@/modules/data/bal/emissions'; +import type { + Findable, + Pool, + PoolAttribute, + Price, + Token, + TokenAttribute, + LiquidityGauge, +} from '@/types'; +import { BaseFeeDistributor } from '@/modules/data'; +import { ProtocolRevenue } from './protocol-revenue'; +import { Liquidity } from '@/modules/liquidity/liquidity.module'; + +export interface AprBreakdown { + swapFees: number; + tokenAprs: number; + stakingApr: { + min: number; + max: number; + }; + rewardAprs: { + total: number; + breakdown: { [address: string]: number }; + }; + protocolApr: number; + min: number; + max: number; +} + +/** + * Calculates pool APR via summing up sources of APR: + * + * 1. Swap fees (pool level) data coming from subgraph + * 2. Yield bearing pool tokens, with data from external sources eg: http endpoints, subgraph, onchain + * * stETH + * * aave + * * usd+ + * map token: calculatorFn + * 3. Staking rewards based from veBal gauges + */ +export class PoolApr { + constructor( + private pools: Findable, + private tokenPrices: Findable, + private tokenMeta: Findable, + private tokenYields: Findable, + private feeCollector: Findable, + private yesterdaysPools?: Findable, + private liquidityGauges?: Findable, + private feeDistributor?: BaseFeeDistributor + ) {} + + /** + * Pool revenue via swap fees. + * Fees and liquidity are takes from subgraph as USD floats. + * + * @returns APR [bsp] from fees accumulated over last 24h + */ + async swapFees(pool: Pool): Promise { + // 365 * dailyFees * (1 - protocolFees) / totalLiquidity + const last24hFees = await this.last24hFees(pool); + const totalLiquidity = await this.totalLiquidity(pool); + // TODO: what to do when we are missing last24hFees or totalLiquidity? + // eg: stable phantom returns 0 + if (!last24hFees || !totalLiquidity) { + return 0; + } + const dailyFees = + last24hFees * (1 - (await this.protocolSwapFeePercentage())); + const feesDailyBsp = 10000 * (dailyFees / parseFloat(totalLiquidity)); + + return Math.round(365 * feesDailyBsp); + } + + /** + * Pool revenue from holding yield-bearing wrapped tokens. + * + * @param pool + * @returns APR [bsp] from tokens contained in the pool + */ + async tokenAprs(pool: Pool): Promise { + if (!pool.tokens) { + return 0; + } + + const totalLiquidity = await this.totalLiquidity(pool); + + // Filter out BPT: token with the same address as the pool + // TODO: move this to data layer + const bptFreeTokens = pool.tokens.filter((token) => { + return token.address !== pool.address; + }); + + // Get each token APRs + const aprs = bptFreeTokens.map(async (token) => { + let apr = 0; + const tokenYield = await this.tokenYields.find(token.address); + + if (tokenYield) { + apr = tokenYield; + } else { + // Handle subpool APRs with recursive call to get the subPool APR + const subPool = await this.pools.findBy('address', token.address); + + if (subPool) { + // INFO: Liquidity mining APR can't cascade to other pools + const subSwapFees = await this.swapFees(subPool); + const subtokenAprs = await this.tokenAprs(subPool); + apr = subSwapFees + subtokenAprs; + } + } + + return apr; + }); + + // Get token weights normalised by usd price + const weights = bptFreeTokens.map(async (token): Promise => { + if (token.weight) { + return parseFloat(token.weight); + } else { + let tokenPrice = + token.price?.usd || (await this.tokenPrices.find(token.address))?.usd; + if (!tokenPrice) { + const poolToken = await this.pools.findBy('address', token.address); + if (poolToken) { + tokenPrice = (await this.bptPrice(poolToken)).toString(); + } else { + throw `No price for ${token.address}`; + } + } + // using floats assuming frontend purposes with low precision needs + const tokenValue = parseFloat(token.balance) * parseFloat(tokenPrice); + return tokenValue / parseFloat(totalLiquidity); + } + }); + + // Normalise tokenAPRs according to weights + const weightedAprs = await Promise.all( + aprs.map(async (apr, idx) => { + const [a, w] = await Promise.all([apr, weights[idx]]); + return Math.round(a * w); + }) + ); + + // sum them up to get pool APRs + const apr = weightedAprs.reduce((sum, apr) => sum + apr, 0); + + return apr; + } + + /** + * Calculates staking rewards based on veBal gauges deployed with Curve Finance contracts. + * https://curve.readthedocs.io/dao-gauges.html + * + * Terminology: + * - LP token of a gauge is a BPT of a pool + * - Depositing into a gauge is called staking on the frontend + * - gauge totalSupply - BPT tokens deposited to a gauge + * - gauge workingSupply - effective BPT tokens participating in reward distribution. sum of 40% deposit + 60% boost from individual user's veBal + * - gauge relative weight - weight of this gauge in bal inflation distribution [0..1] scaled to 1e18 + * + * APR sources: + * - gauge BAL emissions = min: 40% of totalSupply, max: 40% of totalSupply + 60% of totalSupply * gauge LPs voting power + * https://github.com/balancer-labs/balancer-v2-monorepo/blob/master/pkg/liquidity-mining/contracts/gauges/ethereum/LiquidityGaugeV5.vy#L338 + * - gauge reward tokens: Admin or designated depositor has an option to deposit additional reward with a weekly accruing cadence. + * https://github.com/balancer-labs/balancer-v2-monorepo/blob/master/pkg/liquidity-mining/contracts/gauges/ethereum/LiquidityGaugeV5.vy#L641 + * rate: amount of token per second + * + * @param pool + * @param boost range between 1 and 2.5 + * @returns APR [bsp] from protocol rewards. + */ + async stakingApr(pool: Pool, boost = 1): Promise { + if (!this.liquidityGauges) { + return 0; + } + + // Data resolving + const gauge = await this.liquidityGauges.findBy('poolId', pool.id); + if (!gauge) { + return 0; + } + + const [balPrice, bptPriceUsd] = await Promise.all([ + this.tokenPrices.find('0xba100000625a3754423978a60c9317c58a424e3d'), // BAL + this.bptPrice(pool), + ]); + const balPriceUsd = parseFloat(balPrice?.usd || '0'); + + const now = Math.round(new Date().getTime() / 1000); + const totalBalEmissions = emissions.between(now, now + 365 * 86400); + const gaugeBalEmissions = totalBalEmissions * gauge.relativeWeight; + const gaugeBalEmissionsUsd = gaugeBalEmissions * balPriceUsd; + const gaugeSupply = (gauge.workingSupply + 0.4) / 0.4; // Only 40% of LP token staked accrue emissions, totalSupply = workingSupply * 2.5 + const gaugeSupplyUsd = gaugeSupply * bptPriceUsd; + const gaugeBalAprBps = Math.round( + (boost * 10000 * gaugeBalEmissionsUsd) / gaugeSupplyUsd + ); + + return gaugeBalAprBps; + } + + /** + * Some gauges are holding tokens distributed as rewards to LPs. + * + * @param pool + * @returns APR [bsp] from token rewards. + */ + async rewardAprs(pool: Pool): Promise { + if (!this.liquidityGauges) { + return { total: 0, breakdown: {} }; + } + + // Data resolving + const gauge = await this.liquidityGauges.findBy('poolId', pool.id); + if ( + !gauge || + !gauge.rewardTokens || + Object.keys(gauge.rewardTokens).length < 1 + ) { + return { total: 0, breakdown: {} }; + } + + const rewardTokenAddresses = Object.keys(gauge.rewardTokens); + + // Gets each tokens rate, extrapolate to a year and convert to USD + const rewards = rewardTokenAddresses.map(async (tAddress) => { + /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ + const data = gauge!.rewardTokens![tAddress]; + if (data.period_finish.toNumber() < Date.now() / 1000) { + return { + address: tAddress, + value: 0, + }; + } else { + const yearlyReward = data.rate.mul(86400).mul(365); + const price = await this.tokenPrices.find(tAddress); + if (price && price.usd) { + const meta = await this.tokenMeta.find(tAddress); + const decimals = meta?.decimals || 18; + const yearlyRewardUsd = + parseFloat(formatUnits(yearlyReward, decimals)) * + parseFloat(price.usd); + return { + address: tAddress, + value: yearlyRewardUsd, + }; + } else { + throw `No USD price for ${tAddress}`; + } + } + }); + + // Get the gauge totalSupplyUsd + const bptPriceUsd = await this.bptPrice(pool); + const totalSupplyUsd = gauge.totalSupply * bptPriceUsd; + + if (totalSupplyUsd == 0) { + return { total: 0, breakdown: {} }; + } + + const rewardTokensBreakdown: Record = {}; + + let total = 0; + for await (const reward of Object.values(rewards)) { + const rewardValue = reward.value / totalSupplyUsd; + total += rewardValue; + rewardTokensBreakdown[reward.address] = reward.value; + } + + return { + total: Math.round(10000 * total), + breakdown: rewardTokensBreakdown, + }; + } + + /** + * 80BAL-20WETH pool is accruing protocol revenue. + * + * @param pool + * @returns accrued protocol revenue as APR [bsp] + */ + async protocolApr(pool: Pool): Promise { + const veBalPoolId = + '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014'; + + if (pool.id != veBalPoolId || !this.feeDistributor) { + return 0; + } + + const revenue = new ProtocolRevenue(this.feeDistributor, this.tokenPrices); + + const { lastWeekBalRevenue, lastWeekBBAUsdRevenue, veBalSupply } = + await revenue.data(); + + const bptPrice = await this.bptPrice(pool); + if (!bptPrice) { + throw 'bptPrice for veBal pool missing'; + } + + const dailyRevenue = (lastWeekBalRevenue + lastWeekBBAUsdRevenue) / 7; + const apr = Math.round( + (10000 * (365 * dailyRevenue)) / (bptPrice * veBalSupply) + ); + + return apr; + } + + /** + * Composes all sources for total pool APR. + * + * @returns pool APR split [bsp] + */ + async apr(pool: Pool): Promise { + console.time(`APR for ${pool.id}`); + const [ + swapFees, + tokenAprs, + minStakingApr, + maxStakingApr, + rewardAprs, + protocolApr, + ] = await Promise.all([ + this.swapFees(pool), // pool snapshot for last 24h fees dependency + this.tokenAprs(pool), + this.stakingApr(pool), + this.stakingApr(pool, 2.5), + this.rewardAprs(pool), + this.protocolApr(pool), + ]); + console.timeEnd(`APR for ${pool.id}`); + + return { + swapFees, + tokenAprs, + stakingApr: { + min: minStakingApr, + max: maxStakingApr, + }, + rewardAprs, + protocolApr, + min: + swapFees + tokenAprs + rewardAprs.total + protocolApr + minStakingApr, + max: + swapFees + tokenAprs + rewardAprs.total + protocolApr + maxStakingApr, + }; + } + + // 🚨 this is adding 1 call to get yesterday's block height and 2nd call to fetch yesterday's pools data from subgraph + // TODO: find a better data source for that eg. add blocks to graph, replace with a database, or dune + private async last24hFees(pool: Pool): Promise { + let yesterdaysPool; + if (this.yesterdaysPools) { + yesterdaysPool = await this.yesterdaysPools.find(pool.id); + } + if (!pool.totalSwapFee) { + return 0; + } + if (!yesterdaysPool || !yesterdaysPool.totalSwapFee) { + return parseFloat(pool.totalSwapFee); + } + + return ( + parseFloat(pool.totalSwapFee) - parseFloat(yesterdaysPool.totalSwapFee) + ); + } + + /** + * Total Liquidity based on USD token prices taken from external price feed, eg: coingecko. + * + * @param pool + * @returns Pool liquidity in USD + */ + private async totalLiquidity(pool: Pool): Promise { + const liquidityService = new Liquidity(this.pools, this.tokenPrices); + const liquidity = await liquidityService.getLiquidity(pool); + + return liquidity; + } + + /** + * BPT price as pool totalLiquidity / pool total Shares + * Total Liquidity is calculated based on USD token prices taken from external price feed, eg: coingecko. + * + * @param pool + * @returns BPT price in USD + */ + private async bptPrice(pool: Pool) { + return ( + parseFloat(await this.totalLiquidity(pool)) / parseFloat(pool.totalShares) + ); + } + + private async protocolSwapFeePercentage() { + const fee = await this.feeCollector.find(''); + + return fee ? fee : 0; + } +} diff --git a/balancer-js/src/modules/pools/apr/protocol-revenue.ts b/balancer-js/src/modules/pools/apr/protocol-revenue.ts new file mode 100644 index 000000000..30a963894 --- /dev/null +++ b/balancer-js/src/modules/pools/apr/protocol-revenue.ts @@ -0,0 +1,30 @@ +import { Findable, Price } from '@/types'; +import { BaseFeeDistributor } from '@/modules/data'; + +export interface ProtocolRevenueData { + lastWeekBalRevenue: number; + lastWeekBBAUsdRevenue: number; + veBalSupply: number; +} + +export class ProtocolRevenue { + constructor( + private repository: BaseFeeDistributor, + private tokenPrices: Findable + ) {} + + async data(now = Date.now()): Promise { + const data = await this.repository.multicallData(now); + const balPrice = await this.tokenPrices.find(data.balAddress); + + if (!balPrice || !balPrice.usd) { + throw `No BAL USD price found`; + } + + return { + lastWeekBalRevenue: data.balAmount * parseFloat(balPrice.usd), + lastWeekBBAUsdRevenue: data.bbAUsdAmount * data.bbAUsdPrice, + veBalSupply: data.veBalSupply, + }; + } +} From 5431afad6ce173beef66df7486af9279965b4daa Mon Sep 17 00:00:00 2001 From: bronco Date: Wed, 31 Aug 2022 17:00:33 +0200 Subject: [PATCH 37/65] pools module --- balancer-js/src/index.ts | 2 +- balancer-js/src/modules/pools/index.ts | 164 ++++++++++++++++++ ...{pools.module.ts => pool-type-concerns.ts} | 7 +- balancer-js/src/modules/pools/provider.ts | 89 ---------- balancer-js/src/test/lib/utils.ts | 6 +- balancer-js/src/types.ts | 49 ++++-- 6 files changed, 213 insertions(+), 104 deletions(-) create mode 100644 balancer-js/src/modules/pools/index.ts rename balancer-js/src/modules/pools/{pools.module.ts => pool-type-concerns.ts} (89%) delete mode 100644 balancer-js/src/modules/pools/provider.ts diff --git a/balancer-js/src/index.ts b/balancer-js/src/index.ts index 8f0a1b489..2ac3ad315 100644 --- a/balancer-js/src/index.ts +++ b/balancer-js/src/index.ts @@ -12,7 +12,7 @@ export * from './modules/relayer/relayer.module'; export * from './modules/swaps/swaps.module'; export * from './modules/subgraph/subgraph.module'; export * from './modules/sor/sor.module'; -export * from './modules/pools/pools.module'; +export * from './modules/pools'; export * from './modules/data'; export * from './balancerErrors'; export { diff --git a/balancer-js/src/modules/pools/index.ts b/balancer-js/src/modules/pools/index.ts new file mode 100644 index 000000000..68c370bd4 --- /dev/null +++ b/balancer-js/src/modules/pools/index.ts @@ -0,0 +1,164 @@ +import type { + BalancerNetworkConfig, + BalancerDataRepositories, + Findable, + Searchable, + Pool, + PoolWithMethods, + AprBreakdown, + PoolAttribute, +} from '@/types'; +import { JoinPoolAttributes } from './pool-types/concerns/types'; +import { PoolTypeConcerns } from './pool-type-concerns'; +import { PoolApr } from './apr/apr'; +import { Liquidity } from '../liquidity/liquidity.module'; + +/** + * Controller / use-case layer for interacting with pools data. + */ +export class Pools implements Findable { + aprService; + liquidityService; + + constructor( + private networkConfig: BalancerNetworkConfig, + private repositories: BalancerDataRepositories + ) { + this.aprService = new PoolApr( + this.repositories.pools, + this.repositories.tokenPrices, + this.repositories.tokenMeta, + this.repositories.tokenYields, + this.repositories.feeCollector, + this.repositories.yesterdaysPools, + this.repositories.liquidityGauges, + this.repositories.feeDistributor + ); + this.liquidityService = new Liquidity( + repositories.pools, + repositories.tokenPrices + ); + } + + dataSource(): Findable & Searchable { + // TODO: Add API data repository to data and use liveModelProvider as fallback + return this.repositories.pools; + } + + /** + * Calculates APR on any pool data + * + * @param pool + * @returns + */ + async apr(pool: Pool): Promise { + return this.aprService.apr(pool); + } + + /** + * Calculates total liquidity of the pool + * + * @param pool + * @returns + */ + async liquidity(pool: Pool): Promise { + return this.liquidityService.getLiquidity(pool); + } + + static wrap( + pool: Pool, + networkConfig: BalancerNetworkConfig + ): PoolWithMethods { + const methods = PoolTypeConcerns.from(pool.poolType); + return { + ...pool, + buildJoin: ( + joiner: string, + tokensIn: string[], + amountsIn: string[], + slippage: string + ): JoinPoolAttributes => { + return methods.join.buildJoin({ + joiner, + pool, + tokensIn, + amountsIn, + slippage, + wrappedNativeAsset: networkConfig.addresses.tokens.wrappedNativeAsset, + }); + }, + calcPriceImpact: async (amountsIn: string[], minBPTOut: string) => + methods.priceImpactCalculator.calcPriceImpact( + pool, + amountsIn, + minBPTOut + ), + buildExitExactBPTIn: ( + exiter, + bptIn, + slippage, + shouldUnwrapNativeAsset = false, + singleTokenMaxOut + ) => + methods.exit.buildExitExactBPTIn({ + exiter, + pool, + bptIn, + slippage, + shouldUnwrapNativeAsset, + wrappedNativeAsset: networkConfig.addresses.tokens.wrappedNativeAsset, + singleTokenMaxOut, + }), + buildExitExactTokensOut: (exiter, tokensOut, amountsOut, slippage) => + methods.exit.buildExitExactTokensOut({ + exiter, + pool, + tokensOut, + amountsOut, + slippage, + wrappedNativeAsset: networkConfig.addresses.tokens.wrappedNativeAsset, + }), + // TODO: spotPrice fails, because it needs a subgraphType, + // either we refetch or it needs a type transformation from SDK internal to SOR (subgraph) + // spotPrice: async (tokenIn: string, tokenOut: string) => + // methods.spotPriceCalculator.calcPoolSpotPrice(tokenIn, tokenOut, data), + }; + } + + async find(id: string): Promise { + const data = await this.dataSource().find(id); + if (!data) return; + + return Pools.wrap(data, this.networkConfig); + } + + async findBy( + param: string, + value: string + ): Promise { + if (param == 'id') { + return this.find(value); + } else if (param == 'address') { + const data = await this.dataSource().findBy('address', value); + if (!data) return; + + return Pools.wrap(data, this.networkConfig); + } else { + throw `search by ${param} not implemented`; + } + } + + async all(): Promise { + const list = await this.dataSource().all(); + if (!list) return []; + + return list.map((data: Pool) => Pools.wrap(data, this.networkConfig)); + } + + async where(filter: (pool: Pool) => boolean): Promise { + const list = await this.dataSource().where(filter); + if (!list) return []; + + return list.map((data: Pool) => Pools.wrap(data, this.networkConfig)); + } +} diff --git a/balancer-js/src/modules/pools/pools.module.ts b/balancer-js/src/modules/pools/pool-type-concerns.ts similarity index 89% rename from balancer-js/src/modules/pools/pools.module.ts rename to balancer-js/src/modules/pools/pool-type-concerns.ts index 38f5f38bf..4406537ed 100644 --- a/balancer-js/src/modules/pools/pools.module.ts +++ b/balancer-js/src/modules/pools/pool-type-concerns.ts @@ -6,7 +6,12 @@ import { StablePhantom } from './pool-types/stablePhantom.module'; import { Linear } from './pool-types/linear.module'; import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; -export class Pools { +/** + * Wrapper around pool type specific methods. + * + * Returns a class instance of a type specific method handlers. + */ +export class PoolTypeConcerns { constructor( config: BalancerSdkConfig, public weighted = new Weighted(), diff --git a/balancer-js/src/modules/pools/provider.ts b/balancer-js/src/modules/pools/provider.ts deleted file mode 100644 index 99ab203aa..000000000 --- a/balancer-js/src/modules/pools/provider.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { BalancerSdkConfig, Pool, PoolModel } from '@/types'; -import { PoolRepository } from '@/modules/data'; -import { Pools as PoolMethods } from './pools.module'; -import { getNetworkConfig } from '../sdk.helpers'; - -/** - * Building pools from raw data injecting poolType specific methods - */ -export class PoolsProvider { - constructor( - private config: BalancerSdkConfig, - private repository: PoolRepository - ) {} - - static wrap(data: Pool, config: BalancerSdkConfig): PoolModel { - const methods = PoolMethods.from(data.poolType); - const networkConfig = getNetworkConfig(config); - return { - ...data, - liquidity: async () => methods.liquidity.calcTotal(data.tokens), - buildJoin: (joiner, tokensIn, amountsIn, slippage) => - methods.join.buildJoin({ - joiner, - pool: data, - tokensIn, - amountsIn, - slippage, - wrappedNativeAsset: networkConfig.addresses.tokens.wrappedNativeAsset, - }), - - calcPriceImpact: async (amountsIn: string[], minBPTOut: string) => - methods.priceImpactCalculator.calcPriceImpact( - data, - amountsIn, - minBPTOut - ), - - buildExitExactBPTIn: ( - exiter, - bptIn, - slippage, - shouldUnwrapNativeAsset = false, - singleTokenMaxOut - ) => - methods.exit.buildExitExactBPTIn({ - exiter, - pool: data, - bptIn, - slippage, - shouldUnwrapNativeAsset, - wrappedNativeAsset: networkConfig.addresses.tokens.wrappedNativeAsset, - singleTokenMaxOut, - }), - buildExitExactTokensOut: (exiter, tokensOut, amountsOut, slippage) => - methods.exit.buildExitExactTokensOut({ - exiter, - pool: data, - tokensOut, - amountsOut, - slippage, - wrappedNativeAsset: networkConfig.addresses.tokens.wrappedNativeAsset, - }), - // TODO: spotPrice fails, because it needs a subgraphType, - // either we refetch or it needs a type transformation from SDK internal to SOR (subgraph) - // spotPrice: async (tokenIn: string, tokenOut: string) => - // methods.spotPriceCalculator.calcPoolSpotPrice(tokenIn, tokenOut, data), - }; - } - - async find(id: string): Promise { - const data = await this.repository.find(id); - if (!data) return; - - return PoolsProvider.wrap(data, this.config); - } - - async findBy(param: string, value: string): Promise { - if (param == 'id') { - return this.find(value); - } else if (param == 'address') { - const data = await this.repository.findBy('address', value); - if (!data) return; - - return PoolsProvider.wrap(data, this.config); - } else { - throw `search by ${param} not implemented`; - } - } -} diff --git a/balancer-js/src/test/lib/utils.ts b/balancer-js/src/test/lib/utils.ts index d77614a7e..c991b203e 100644 --- a/balancer-js/src/test/lib/utils.ts +++ b/balancer-js/src/test/lib/utils.ts @@ -5,8 +5,8 @@ import { balancerVault } from '@/lib/constants/config'; import { hexlify, zeroPad } from '@ethersproject/bytes'; import { keccak256 } from '@ethersproject/solidity'; import { ERC20 } from '@/modules/contracts/ERC20'; -import { PoolsProvider } from '@/modules/pools/provider'; -import { PoolModel, BalancerError, BalancerErrorCode } from '@/.'; +import { Pools as PoolsProvider } from '@/modules/pools'; +import { PoolWithMethods, BalancerError, BalancerErrorCode } from '@/.'; /** * Setup local fork with approved token balance for a given account @@ -101,7 +101,7 @@ export const approveToken = async ( export const setupPool = async ( provider: PoolsProvider, poolId: string -): Promise => { +): Promise => { const pool = await provider.find(poolId); if (!pool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); return pool; diff --git a/balancer-js/src/types.ts b/balancer-js/src/types.ts index 6e4a52c27..00b53af0d 100644 --- a/balancer-js/src/types.ts +++ b/balancer-js/src/types.ts @@ -1,13 +1,23 @@ -import { BigNumberish } from '@ethersproject/bignumber'; -import { Network } from './lib/constants/network'; -import { Contract } from '@ethersproject/contracts'; -import { PoolDataService, TokenPriceService } from '@balancer-labs/sor'; -import { +import type { BigNumberish } from '@ethersproject/bignumber'; +import type { Network } from './lib/constants/network'; +import type { Contract } from '@ethersproject/contracts'; +import type { PoolDataService, TokenPriceService } from '@balancer-labs/sor'; +import type { ExitPoolAttributes, - JoinPoolAttributes, + JoinPoolAttributes } from './modules/pools/pool-types/concerns/types'; +import type { + Findable, + Searchable, + LiquidityGauge, + PoolAttribute, + TokenAttribute, +} from '@/modules/data/types'; +import type { BaseFeeDistributor } from './modules/data'; export * from '@/modules/data/types'; +import type { AprBreakdown } from '@/modules/pools/apr/apr'; +export { AprBreakdown }; export type Address = string; @@ -50,6 +60,9 @@ export interface BalancerNetworkConfig { lbpRaisingTokens?: string[]; stETH?: string; wstETH?: string; + bal: string; + veBal: string; + bbaUsd: string; }; }; urls: { @@ -62,6 +75,17 @@ export interface BalancerNetworkConfig { }; } +export interface BalancerDataRepositories { + pools: Findable & Searchable; + yesterdaysPools?: Findable & Searchable; + tokenPrices: Findable; + tokenMeta: Findable; + liquidityGauges: Findable; + feeDistributor: BaseFeeDistributor; + feeCollector: Findable; + tokenYields: Findable; +} + export type PoolReference = { id: string; address: string; @@ -182,6 +206,7 @@ export enum PoolType { export interface Pool { id: string; + name: string; address: string; poolType: PoolType; swapFee: string; @@ -190,7 +215,7 @@ export interface Pool { tokens: PoolToken[]; tokensList: string[]; tokenAddresses?: string[]; - totalLiquidity?: string; + totalLiquidity: string; totalShares: string; totalSwapFee?: string; totalSwapVolume?: string; @@ -206,16 +231,20 @@ export interface Pool { symbol?: string; swapEnabled: boolean; amp?: string; + apr?: AprBreakdown; + liquidity?: string; } -export interface PoolModel extends Pool { - liquidity: () => Promise; +/** + * Pool use-cases / controller layer + */ +export interface PoolWithMethods extends Pool { buildJoin: ( joiner: string, tokensIn: string[], amountsIn: string[], slippage: string - ) => JoinPoolAttributes; + ) => Promise; calcPriceImpact: (amountsIn: string[], minBPTOut: string) => Promise; buildExitExactBPTIn: ( exiter: string, From a746ab7a6d7c09a63817314a0f37c5fac79071ea Mon Sep 17 00:00:00 2001 From: bronco Date: Wed, 31 Aug 2022 16:55:11 +0200 Subject: [PATCH 38/65] data module --- balancer-js/src/modules/data/index.ts | 96 +++++++++++++++++++++++++++ balancer-js/src/types.ts | 20 +++--- 2 files changed, 106 insertions(+), 10 deletions(-) diff --git a/balancer-js/src/modules/data/index.ts b/balancer-js/src/modules/data/index.ts index 194369689..58b4a00b5 100644 --- a/balancer-js/src/modules/data/index.ts +++ b/balancer-js/src/modules/data/index.ts @@ -8,3 +8,99 @@ export * from './fee-distributor/repository'; export * from './fee-collector/repository'; export * from './token-yields/repository'; 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 { StaticTokenProvider } from './token/static'; +import { LiquidityGaugeSubgraphRPCProvider } from './liquidity-gauges/provider'; +import { FeeDistributorRepository } from './fee-distributor/repository'; +import { FeeCollectorRepository } from './fee-collector/repository'; +import { TokenYieldsRepository } from './token-yields/repository'; +import { Provider } from '@ethersproject/providers'; + +// initialCoingeckoList are used to get the initial token list for coingecko +// TODO: we might want to replace that with what frontend is using +import initialCoingeckoList from '@/modules/data/token-prices/initial-list.json'; + +export class Data implements BalancerDataRepositories { + pools; + yesterdaysPools; + tokenPrices; + tokenMeta; + liquidityGauges; + feeDistributor; + feeCollector; + tokenYields; + blockNumbers; + + constructor(networkConfig: BalancerNetworkConfig, provider: Provider) { + this.pools = new PoolsSubgraphRepository(networkConfig.urls.subgraph); + + // 🚨 yesterdaysPools is used to calculate swapFees accumulated over last 24 hours + // TODO: find a better data source for that, eg: maybe DUNE once API is available + if (networkConfig.urls.blockNumberSubgraph) { + this.blockNumbers = new BlockNumberRepository( + networkConfig.urls.blockNumberSubgraph + ); + + const blockDayAgo = async () => { + if (this.blockNumbers) { + return await this.blockNumbers.find('dayAgo'); + } + }; + + this.yesterdaysPools = new PoolsSubgraphRepository( + networkConfig.urls.subgraph, + blockDayAgo + ); + } + + const tokenAddresses = initialCoingeckoList + .filter((t) => t.chainId == networkConfig.chainId) + .map((t) => t.address); + + this.tokenPrices = new CoingeckoPriceRepository( + tokenAddresses, + networkConfig.chainId + ); + + this.tokenMeta = new StaticTokenProvider([]); + + if ( + networkConfig.urls.gaugesSubgraph && + networkConfig.addresses.contracts.gaugeController + ) { + this.liquidityGauges = new LiquidityGaugeSubgraphRPCProvider( + networkConfig.urls.gaugesSubgraph, + networkConfig.addresses.contracts.multicall, + networkConfig.addresses.contracts.gaugeController, + provider + ); + } + + if ( + networkConfig.addresses.contracts.feeDistributor && + networkConfig.addresses.tokens.bal && + networkConfig.addresses.tokens.veBal && + networkConfig.addresses.tokens.bbaUsd + ) { + this.feeDistributor = new FeeDistributorRepository( + networkConfig.addresses.contracts.multicall, + networkConfig.addresses.contracts.feeDistributor, + networkConfig.addresses.tokens.bal, + networkConfig.addresses.tokens.veBal, + networkConfig.addresses.tokens.bbaUsd, + provider + ); + } + + this.feeCollector = new FeeCollectorRepository( + networkConfig.addresses.contracts.vault, + provider + ); + + this.tokenYields = new TokenYieldsRepository(); + } +} diff --git a/balancer-js/src/types.ts b/balancer-js/src/types.ts index 00b53af0d..473288af1 100644 --- a/balancer-js/src/types.ts +++ b/balancer-js/src/types.ts @@ -4,7 +4,7 @@ import type { Contract } from '@ethersproject/contracts'; import type { PoolDataService, TokenPriceService } from '@balancer-labs/sor'; import type { ExitPoolAttributes, - JoinPoolAttributes + JoinPoolAttributes, } from './modules/pools/pool-types/concerns/types'; import type { Findable, @@ -47,8 +47,8 @@ export interface ContractAddresses { vault: string; multicall: string; lidoRelayer?: string; - gaugeController: string; - feeDistributor: string; + gaugeController?: string; + feeDistributor?: string; } export interface BalancerNetworkConfig { @@ -60,14 +60,14 @@ export interface BalancerNetworkConfig { lbpRaisingTokens?: string[]; stETH?: string; wstETH?: string; - bal: string; - veBal: string; - bbaUsd: string; + bal?: string; + veBal?: string; + bbaUsd?: string; }; }; urls: { subgraph: string; - gaugesSubgraph: string; + gaugesSubgraph?: string; blockNumberSubgraph?: string; }; pools: { @@ -80,8 +80,8 @@ export interface BalancerDataRepositories { yesterdaysPools?: Findable & Searchable; tokenPrices: Findable; tokenMeta: Findable; - liquidityGauges: Findable; - feeDistributor: BaseFeeDistributor; + liquidityGauges?: Findable; + feeDistributor?: BaseFeeDistributor; feeCollector: Findable; tokenYields: Findable; } @@ -244,7 +244,7 @@ export interface PoolWithMethods extends Pool { tokensIn: string[], amountsIn: string[], slippage: string - ) => Promise; + ) => JoinPoolAttributes; calcPriceImpact: (amountsIn: string[], minBPTOut: string) => Promise; buildExitExactBPTIn: ( exiter: string, From e186a0a8a66ecdb828af92c4999feb446bbd0609 Mon Sep 17 00:00:00 2001 From: bronco Date: Wed, 31 Aug 2022 17:00:10 +0200 Subject: [PATCH 39/65] sdk module updates --- balancer-js/src/modules/sdk.module.ts | 30 +++++++++++++++------------ 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/balancer-js/src/modules/sdk.module.ts b/balancer-js/src/modules/sdk.module.ts index 9d8830493..c5a11b003 100644 --- a/balancer-js/src/modules/sdk.module.ts +++ b/balancer-js/src/modules/sdk.module.ts @@ -4,50 +4,54 @@ import { Relayer } from './relayer/relayer.module'; import { Subgraph } from './subgraph/subgraph.module'; import { Sor } from './sor/sor.module'; import { getNetworkConfig } from './sdk.helpers'; -import { Pools } from './pools/pools.module'; import { Pricing } from './pricing/pricing.module'; import { ContractInstances, Contracts } from './contracts/contracts.module'; -import { PoolsProvider } from './pools/provider'; -import { SubgraphPoolRepository } from './data/pool/subgraph'; +import { Pools } from './pools'; +import { Data } from './data'; +import { Provider } from '@ethersproject/providers'; export interface BalancerSDKRoot { config: BalancerSdkConfig; sor: Sor; subgraph: Subgraph; pools: Pools; + data: Data; swaps: Swaps; relayer: Relayer; networkConfig: BalancerNetworkConfig; + rpcProvider: Provider; } export class BalancerSDK implements BalancerSDKRoot { readonly swaps: Swaps; readonly relayer: Relayer; readonly pricing: Pricing; + readonly pools: Pools; + readonly data: Data; balancerContracts: Contracts; + readonly networkConfig: BalancerNetworkConfig; constructor( public config: BalancerSdkConfig, public sor = new Sor(config), - public subgraph = new Subgraph(config), - public pools = new Pools(config), - public poolsProvider = new PoolsProvider( - config, - new SubgraphPoolRepository(subgraph.client) - ) + public subgraph = new Subgraph(config) ) { + this.networkConfig = getNetworkConfig(config); + + this.data = new Data(this.networkConfig, sor.provider); this.swaps = new Swaps(this.config); this.relayer = new Relayer(this.swaps); this.pricing = new Pricing(config, this.swaps); - const networkConfig = getNetworkConfig(config); + this.pools = new Pools(this.networkConfig, this.data); + this.balancerContracts = new Contracts( - networkConfig.addresses.contracts, + this.networkConfig.addresses.contracts, sor.provider ); } - get networkConfig(): BalancerNetworkConfig { - return getNetworkConfig(this.config); + get rpcProvider(): Provider { + return this.sor.provider; } /** From bf2f99208c57a668812d66ef95871c290edec555 Mon Sep 17 00:00:00 2001 From: bronco Date: Sun, 17 Jul 2022 20:02:33 +0200 Subject: [PATCH 40/65] updating existing modules with new names --- .../src/modules/liquidity/liquidity.module.ts | 8 ++++---- balancer-js/src/modules/pools/README.md | 12 ++---------- balancer-js/src/modules/pricing/pricing.module.ts | 8 ++++---- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/balancer-js/src/modules/liquidity/liquidity.module.ts b/balancer-js/src/modules/liquidity/liquidity.module.ts index ea2c7d8a0..0de1c98bb 100644 --- a/balancer-js/src/modules/liquidity/liquidity.module.ts +++ b/balancer-js/src/modules/liquidity/liquidity.module.ts @@ -1,7 +1,7 @@ import { BigNumber, formatFixed } from '@ethersproject/bignumber'; import { parseFixed } from '@/lib/utils/math'; import { Pool, PoolToken } from '@/types'; -import { Pools } from '@/modules/pools/pools.module'; +import { PoolTypeConcerns } from '@/modules/pools/pool-type-concerns'; import { PoolRepository } from '../data'; import { TokenPriceProvider } from '../data'; import { Zero } from '@ethersproject/constants'; @@ -79,9 +79,9 @@ export class Liquidity { }) ); - const tokenLiquidity = Pools.from(pool.poolType).liquidity.calcTotal( - tokenBalances - ); + const tokenLiquidity = PoolTypeConcerns.from( + pool.poolType + ).liquidity.calcTotal(tokenBalances); const totalLiquidity = formatFixed( BigNumber.from(totalSubPoolLiquidity).add( diff --git a/balancer-js/src/modules/pools/README.md b/balancer-js/src/modules/pools/README.md index fe484effe..58b21d264 100644 --- a/balancer-js/src/modules/pools/README.md +++ b/balancer-js/src/modules/pools/README.md @@ -12,13 +12,5 @@ import { BalancerSDK, Pools } from '@balancer/sdk'; // With full SDK const balancer = new BalancerSDK(...configParams); - -balancer.pools.weighted.liquidity.calcTotal(...); -balancer.pools.stable.liquidity.calcTotal(...); - -// or with pools module directly -const pools = new Pools(...configParams); - -pools.weighted.liquidity.calcTotal(...); -pools.stable.liquidity.calcTotal(...); -``` \ No newline at end of file +balancer.pools.find(poolId).liquidity(); +``` diff --git a/balancer-js/src/modules/pricing/pricing.module.ts b/balancer-js/src/modules/pricing/pricing.module.ts index 7fea92ef7..e55e9da89 100644 --- a/balancer-js/src/modules/pricing/pricing.module.ts +++ b/balancer-js/src/modules/pricing/pricing.module.ts @@ -7,11 +7,11 @@ import { getSpotPriceAfterSwapForPath, } from '@balancer-labs/sor'; import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; -import { Pools } from '@/modules/pools/pools.module'; +import { PoolTypeConcerns } from '@/modules/pools/pool-type-concerns'; export class Pricing { private readonly swaps: Swaps; - private pools: Pools; + private pools: PoolTypeConcerns; constructor(config: BalancerSdkConfig, swaps?: Swaps) { if (swaps) { @@ -19,7 +19,7 @@ export class Pricing { } else { this.swaps = new Swaps(config); } - this.pools = new Pools(config); + this.pools = new PoolTypeConcerns(config); } /** @@ -80,7 +80,7 @@ export class Pricing { ); if (!poolData) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); - const pool = Pools.from(poolData.poolType as PoolType); + const pool = PoolTypeConcerns.from(poolData.poolType as PoolType); return pool.spotPriceCalculator.calcPoolSpotPrice( tokenIn, tokenOut, From b8e4c0b0069647476a296ade5e16a83a3d5defa4 Mon Sep 17 00:00:00 2001 From: bronco Date: Mon, 18 Jul 2022 17:09:44 +0200 Subject: [PATCH 41/65] APR example --- balancer-js/examples/pools/aprs.ts | 39 +++++++++++++++++++++ balancer-js/examples/pools/mutable-apr.ts | 42 +++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 balancer-js/examples/pools/aprs.ts create mode 100644 balancer-js/examples/pools/mutable-apr.ts diff --git a/balancer-js/examples/pools/aprs.ts b/balancer-js/examples/pools/aprs.ts new file mode 100644 index 000000000..dbccfb22d --- /dev/null +++ b/balancer-js/examples/pools/aprs.ts @@ -0,0 +1,39 @@ +/** + * Display APRs for pool ids hardcoded under `const ids` + * Run command: yarn examples:run ./examples/pools/aprs.ts + */ +import dotenv from 'dotenv'; +import { BalancerSDK } from '../../src/modules/sdk.module'; + +dotenv.config(); + +const sdk = new BalancerSDK({ + network: 1, + rpcUrl: `${process.env.ALCHEMY_URL}`, +}); + +const { pools } = sdk; + +const main = async () => { + const list = ( + await pools.where( + (pool) => + pool.poolType != 'Element' && + pool.poolType != 'AaveLinear' && + pool.poolType != 'LiquidityBootstrapping' + ) + ) + .sort((a, b) => parseFloat(b.totalLiquidity) - parseFloat(a.totalLiquidity)) + .slice(0, 30); + + list.forEach(async (pool) => { + try { + const apr = await pools.apr(pool); + console.log(pool.id, apr); + } catch (e) { + console.log(e); + } + }); +}; + +main(); diff --git a/balancer-js/examples/pools/mutable-apr.ts b/balancer-js/examples/pools/mutable-apr.ts new file mode 100644 index 000000000..667863ecb --- /dev/null +++ b/balancer-js/examples/pools/mutable-apr.ts @@ -0,0 +1,42 @@ +/** + * Display APRs for pool ids hardcoded under `const ids` + * Run command: yarn examples:run ./examples/pools/aprs.ts + */ +import dotenv from 'dotenv'; +import { BalancerSDK } from '../../src/modules/sdk.module'; + +dotenv.config(); + +const sdk = new BalancerSDK({ + network: 1, + rpcUrl: `${process.env.ALCHEMY_URL}`, +}); + +const { pools } = sdk; + +const main = async () => { + const pool = await pools.find( + '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014' + ); + + if (pool) { + try { + console.log(pool.apr); + + // Mutate state + pool.tokens[0].balance = ( + parseFloat(pool.tokens[0].balance) * 2 + ).toString(); + + // Calculate new APR + const newApr = await pools.apr(pool); + console.log(newApr); + } catch (e) { + console.log(e); + } + } else { + return; + } +}; + +main(); From 09eb70656d120b28094601ca6db9e95a3e3e4064 Mon Sep 17 00:00:00 2001 From: Tim Robinson Date: Mon, 15 Aug 2022 18:26:27 +1000 Subject: [PATCH 42/65] liquidity module updates --- balancer-js/src/lib/utils/index.ts | 1 + balancer-js/src/lib/utils/math.ts | 9 +- balancer-js/src/lib/utils/tokens.ts | 12 ++ .../liquidity/liquidity.module.spec.ts | 27 ++- .../src/modules/liquidity/liquidity.module.ts | 47 ++--- .../concerns/linear/liquidity.concern.ts | 8 +- .../concerns/metaStable/liquidity.concern.ts | 2 +- .../concerns/stable/liquidity.concern.ts | 2 +- .../concerns/weighted/liquidity.concern.ts | 20 +- .../src/test/fixtures/liquidityPools.json | 196 ++++++++++++++++++ .../test/fixtures/liquidityTokenPrices.json | 34 --- .../src/test/fixtures/liquidityTokens.json | 97 +++++++++ 12 files changed, 368 insertions(+), 87 deletions(-) create mode 100644 balancer-js/src/lib/utils/tokens.ts delete mode 100644 balancer-js/src/test/fixtures/liquidityTokenPrices.json create mode 100644 balancer-js/src/test/fixtures/liquidityTokens.json diff --git a/balancer-js/src/lib/utils/index.ts b/balancer-js/src/lib/utils/index.ts index 91032e08d..63a5e0eaa 100644 --- a/balancer-js/src/lib/utils/index.ts +++ b/balancer-js/src/lib/utils/index.ts @@ -6,6 +6,7 @@ export * from './signatures'; export * from './assetHelpers'; export * from './aaveHelpers'; export * from './poolHelper'; +export * from './tokens'; export const isSameAddress = (address1: string, address2: string): boolean => getAddress(address1) === getAddress(address2); diff --git a/balancer-js/src/lib/utils/math.ts b/balancer-js/src/lib/utils/math.ts index 753835c98..33497896d 100644 --- a/balancer-js/src/lib/utils/math.ts +++ b/balancer-js/src/lib/utils/math.ts @@ -2,7 +2,7 @@ import { BigNumber, BigNumberish, parseFixed as _parseFixed, - formatFixed, + formatFixed as _formatFixed, } from '@ethersproject/bignumber'; export function parseFixed(value: string, decimals?: BigNumberish): BigNumber { @@ -16,10 +16,15 @@ export function parseFixed(value: string, decimals?: BigNumberish): BigNumber { return _parseFixed(parsedValue, decimals); } +export function formatFixed(value: BigNumber, decimals: BigNumberish): string { + const ethersFormat = _formatFixed(value, decimals); + return ethersFormat.replace(/(.0$)/, ''); +} + export function parseToBigInt18(value: string): bigint { return parseFixed(value, 18).toBigInt(); } export function formatFromBigInt18(value: bigint): string { - return formatFixed(BigNumber.from(value), 18); + return _formatFixed(BigNumber.from(value), 18); } diff --git a/balancer-js/src/lib/utils/tokens.ts b/balancer-js/src/lib/utils/tokens.ts new file mode 100644 index 000000000..f4840c977 --- /dev/null +++ b/balancer-js/src/lib/utils/tokens.ts @@ -0,0 +1,12 @@ +import { Token, TokenPrices } from '@/types'; + +export function tokensToTokenPrices(tokens: Token[]): TokenPrices { + const tokenPrices: TokenPrices = {}; + tokens.forEach((token) => { + if (token.price) { + tokenPrices[token.address] = token.price; + } + }); + + return tokenPrices; +} diff --git a/balancer-js/src/modules/liquidity/liquidity.module.spec.ts b/balancer-js/src/modules/liquidity/liquidity.module.spec.ts index 3670e45c9..ce157c5e0 100644 --- a/balancer-js/src/modules/liquidity/liquidity.module.spec.ts +++ b/balancer-js/src/modules/liquidity/liquidity.module.spec.ts @@ -3,9 +3,12 @@ import { Pool } from '@/types'; import { expect } from 'chai'; import { Liquidity } from './liquidity.module'; import pools from '@/test/fixtures/liquidityPools.json'; -import tokenPrices from '@/test/fixtures/liquidityTokenPrices.json'; +import tokens from '@/test/fixtures/liquidityTokens.json'; import { StaticTokenPriceProvider } from '../data'; import { formatFixed, parseFixed } from '@ethersproject/bignumber'; +import { tokensToTokenPrices } from '@/lib/utils'; + +const tokenPrices = tokensToTokenPrices(tokens); const tokenPriceProvider = new StaticTokenPriceProvider(tokenPrices); const poolProvider = new StaticPoolRepository(pools as Pool[]); @@ -30,28 +33,28 @@ describe('Liquidity Module', () => { const liquidity = await liquidityProvider.getLiquidity( findPool('0xa6f548df93de924d73be7d25dc02554c6bd66db5') ); - expect(liquidity).to.be.eq('640000.0'); + expect(liquidity).to.be.eq('640000'); }); it('Correct calculates liquidity of a 60/40 pool', async () => { const liquidity = await liquidityProvider.getLiquidity( findPool('0xc6a5032dc4bf638e15b4a66bc718ba7ba474ff73') ); - expect(liquidity).to.be.eq('10000.0'); + expect(liquidity).to.be.eq('10000'); }); it('Correctly calculates value of a 25/25/25/25 pool which is slightly imbalanced', async () => { const liquidity = await liquidityProvider.getLiquidity( findPool('0xd8833594420db3d6589c1098dbdd073f52419dba') ); - expect(liquidity).to.be.eq('127080.0'); + expect(liquidity).to.be.eq('127080'); }); it('Should return 0 liquidity with no errors when all prices are undefined', async () => { const liquidity = await liquidityProvider.getLiquidity( findPool('0x062f38735aac32320db5e2dbbeb07968351d7c72') ); - expect(liquidity).to.be.eq('0.0'); + expect(liquidity).to.be.eq('0'); }); it('Should approximate liquidity when some prices are unknown', async () => { @@ -67,6 +70,18 @@ describe('Liquidity Module', () => { .mul('2'); expect(liquidity).to.be.eq(formatFixed(expectedLiquidity, 18)); }); + + it('Should work with this Vita pool', async () => { + const pool = findPool('0xbaeec99c90e3420ec6c1e7a769d2a856d2898e4d'); + const liquidity = await liquidityProvider.getLiquidity(pool); + expect(liquidity).to.be.eq('666366.860307633662004'); + }); + + it('Should work with this NFT/Gaming index pool', async () => { + const pool = findPool('0x344e8f99a55da2ba6b4b5158df2143374e400df2'); + const liquidity = await liquidityProvider.getLiquidity(pool); + expect(liquidity).to.be.eq('116.303077211035488'); + }); }); context('Stable Pool calculations', () => { @@ -90,7 +105,7 @@ describe('Liquidity Module', () => { const liquidity = await liquidityProvider.getLiquidity( findPool('0x32296969ef14eb0c6d29669c550d4a0449130230') ); - expect(liquidity).to.be.eq('154558160.0'); + expect(liquidity).to.be.eq('154558160'); }); }); diff --git a/balancer-js/src/modules/liquidity/liquidity.module.ts b/balancer-js/src/modules/liquidity/liquidity.module.ts index 0de1c98bb..fa2fa4eae 100644 --- a/balancer-js/src/modules/liquidity/liquidity.module.ts +++ b/balancer-js/src/modules/liquidity/liquidity.module.ts @@ -1,13 +1,11 @@ -import { BigNumber, formatFixed } from '@ethersproject/bignumber'; -import { parseFixed } from '@/lib/utils/math'; import { Pool, PoolToken } from '@/types'; -import { PoolTypeConcerns } from '@/modules/pools/pool-type-concerns'; import { PoolRepository } from '../data'; import { TokenPriceProvider } from '../data'; -import { Zero } from '@ethersproject/constants'; +import { PoolTypeConcerns } from '../pools/pool-type-concerns'; +import { BigNumber } from '@ethersproject/bignumber'; +import { formatFixed, parseFixed } from '@/lib/utils/math'; -const SCALING_FACTOR = 36; -const TOKEN_WEIGHT_SCALING_FACTOR = 18; +const SCALE = 18; export interface PoolBPTValue { address: string; @@ -27,21 +25,17 @@ export class Liquidity { }); // For all tokens that are pools, recurse into them and fetch their liquidity - const subPoolLiquidity: (PoolBPTValue | undefined)[] = await Promise.all( + const subPoolLiquidity = await Promise.all( parsedTokens.map(async (token) => { const pool = await this.pools.findBy('address', token.address); if (!pool) return; - const liquidity = await this.getLiquidity(pool); - const scaledLiquidity = parseFixed(liquidity, SCALING_FACTOR * 2); - const totalBPT = parseFixed(pool.totalShares, SCALING_FACTOR); - const bptValue = scaledLiquidity.div(totalBPT); - - const bptInParentPool = parseFixed(token.balance, SCALING_FACTOR); - const liquidityInParentPool = formatFixed( - bptValue.mul(bptInParentPool), - SCALING_FACTOR - ).replace(/\.[0-9]+/, ''); // strip trailing decimals, we don't need them as we're already scaled up by 1e36 + const liquidity = parseFixed(await this.getLiquidity(pool), SCALE); + const totalBPT = parseFixed(pool.totalShares, SCALE); + const bptInParentPool = parseFixed(token.balance, SCALE); + const liquidityInParentPool = liquidity + .mul(bptInParentPool) + .div(totalBPT); return { address: pool.address, @@ -52,10 +46,10 @@ export class Liquidity { const totalSubPoolLiquidity = subPoolLiquidity.reduce( (totalLiquidity, subPool) => { - if (!subPool) return Zero; + if (!subPool) return BigNumber.from(0); return totalLiquidity.add(subPool.liquidity); }, - Zero + BigNumber.from(0) ); const nonPoolTokens = parsedTokens.filter((token) => { @@ -71,9 +65,7 @@ export class Liquidity { priceRate: token.priceRate, price: tokenPrice, balance: token.balance, - weight: token.weight - ? parseFixed(token.weight, TOKEN_WEIGHT_SCALING_FACTOR).toString() - : '0', + weight: token.weight, }; return poolToken; }) @@ -83,13 +75,10 @@ export class Liquidity { pool.poolType ).liquidity.calcTotal(tokenBalances); - const totalLiquidity = formatFixed( - BigNumber.from(totalSubPoolLiquidity).add( - parseFixed(tokenLiquidity, SCALING_FACTOR) - ), - SCALING_FACTOR - ); + const tl = parseFixed(tokenLiquidity, SCALE); + + const totalLiquidity = totalSubPoolLiquidity.add(tl); - return totalLiquidity; + return formatFixed(totalLiquidity, SCALE); } } diff --git a/balancer-js/src/modules/pools/pool-types/concerns/linear/liquidity.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/linear/liquidity.concern.ts index bb99204c6..4862b6861 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/linear/liquidity.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/linear/liquidity.concern.ts @@ -1,7 +1,6 @@ import { LiquidityConcern } from '../types'; import { PoolToken } from '@/types'; -import { formatFixed } from '@ethersproject/bignumber'; -import { parseFixed } from '@/lib/utils/math'; +import { parseFixed, formatFixed } from '@/lib/utils/math'; import { Zero } from '@ethersproject/constants'; const SCALING_FACTOR = 18; @@ -21,7 +20,7 @@ export class LinearPoolLiquidity implements LiquidityConcern { continue; } - const price = parseFixed(token.price.usd, SCALING_FACTOR); + const price = parseFixed(token.price.usd.toString(), SCALING_FACTOR); const balance = parseFixed(token.balance, SCALING_FACTOR); const value = balance.mul(price); @@ -55,7 +54,6 @@ export class LinearPoolLiquidity implements LiquidityConcern { } } - const totalLiquidity = formatFixed(sumValue, SCALING_FACTOR * 2).toString(); - return totalLiquidity; + return formatFixed(sumValue, SCALING_FACTOR * 2); } } diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/liquidity.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/liquidity.concern.ts index 6213a4203..d7304f358 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/liquidity.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/liquidity.concern.ts @@ -20,7 +20,7 @@ export class MetaStablePoolLiquidity implements LiquidityConcern { continue; } - const price = parseFixed(token.price.usd, SCALING_FACTOR); + const price = parseFixed(token.price.usd.toString(), SCALING_FACTOR); const balance = parseFixed(token.balance, SCALING_FACTOR); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/liquidity.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/liquidity.concern.ts index 8690c01ac..43fb05075 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/liquidity.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/liquidity.concern.ts @@ -19,7 +19,7 @@ export class StablePoolLiquidity implements LiquidityConcern { continue; } - const price = parseFixed(token.price.usd, SCALING_FACTOR); + const price = parseFixed(token.price.usd.toString(), SCALING_FACTOR); const balance = parseFixed(token.balance, SCALING_FACTOR); const value = balance.mul(price); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/weighted/liquidity.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/weighted/liquidity.concern.ts index b1005b757..466cbc6a9 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/weighted/liquidity.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/weighted/liquidity.concern.ts @@ -1,33 +1,35 @@ import { LiquidityConcern } from '../types'; import { PoolToken } from '@/types'; -import { BigNumber, formatFixed } from '@ethersproject/bignumber'; -import { parseFixed } from '@/lib/utils/math'; -import { Zero } from '@ethersproject/constants'; +import { BigNumber } from '@ethersproject/bignumber'; +import { parseFixed, formatFixed } from '@/lib/utils/math'; const SCALING_FACTOR = 18; export class WeightedPoolLiquidity implements LiquidityConcern { calcTotal(tokens: PoolToken[]): string { - let sumWeight = Zero; - let sumValue = Zero; + let sumWeight = BigNumber.from(0); + let sumValue = BigNumber.from(0); for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; if (!token.price?.usd) { continue; } - const price = parseFixed(token.price.usd, SCALING_FACTOR); + + const price = parseFixed(token.price.usd.toString(), SCALING_FACTOR); const balance = parseFixed(token.balance, SCALING_FACTOR); + const weight = parseFixed(token.weight || '0', SCALING_FACTOR); const value = balance.mul(price); sumValue = sumValue.add(value); - sumWeight = sumWeight.add(token.weight || '0'); + sumWeight = sumWeight.add(weight); } // Scale the known prices of x% of the pool to get value of 100% of the pool. const totalWeight = tokens.reduce( - (total: BigNumber, token) => total.add(token.weight || '0'), - Zero + (total: BigNumber, token) => + total.add(parseFixed(token.weight || '0', SCALING_FACTOR)), + BigNumber.from(0) ); if (sumWeight.gt(0)) { const liquidity = sumValue.mul(totalWeight).div(sumWeight); diff --git a/balancer-js/src/test/fixtures/liquidityPools.json b/balancer-js/src/test/fixtures/liquidityPools.json index aa13bf55d..167f7c5b9 100644 --- a/balancer-js/src/test/fixtures/liquidityPools.json +++ b/balancer-js/src/test/fixtures/liquidityPools.json @@ -522,5 +522,201 @@ ], "id": "0x996616bde0cb4974e571f17d31c844da2bd177f8000100000000000000000018", "swapFee": "0.01" + }, + { + "holdersCount": "5", + "lowerTarget": null, + "swapEnabled": true, + "name": "Balancer 50 VITA 50 WETH", + "id": "0xbaeec99c90e3420ec6c1e7a769d2a856d2898e4d00020000000000000000008a", + "totalSwapVolume": "2894704.530037270938008629720776634", + "totalShares": "13131.715529062384181831", + "tokensList": [ + "0x81f8f0bb1cb2a06649e51913a151f0e7ef6fa321", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + ], + "mainIndex": 0, + "symbol": "B-50VITA-50WETH", + "wrappedIndex": 0, + "snapshots": [ + { + "swapFees": "28947.04530037270938008629720776634", + "timestamp": 1660521600 + }, + { + "swapFees": "28928.07248262964911030900211200051", + "timestamp": 1660348800 + } + ], + "poolType": "Weighted", + "address": "0xbaeec99c90e3420ec6c1e7a769d2a856d2898e4d", + "createTime": 1631291082, + "lastUpdate": 1660547742027, + "totalWeight": "1", + "swapsCount": "419", + "totalSwapFee": "28947.04530037270938008629720776634", + "chainId": 1, + "totalLiquidity": "634448.98518992614430606016", + "graphData": { "totalLiquidity": "640698.368476791206945358004321879" }, + "swapFee": "0.01", + "upperTarget": null, + "tokens": [ + { + "symbol": "VITA", + "address": "0x81f8f0bb1cb2a06649e51913a151f0e7ef6fa321", + "priceRate": "1", + "balance": "278964.206419329154042175", + "decimals": 18, + "name": "VitaDAO Token", + "weight": "0.5", + "id": "0xbaeec99c90e3420ec6c1e7a769d2a856d2898e4d00020000000000000000008a-0x81f8f0bb1cb2a06649e51913a151f0e7ef6fa321", + "managedBalance": "0" + }, + { + "symbol": "WETH", + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "priceRate": "1", + "balance": "166.591715076908415501", + "decimals": 18, + "name": "Wrapped Ether", + "weight": "0.5", + "id": "0xbaeec99c90e3420ec6c1e7a769d2a856d2898e4d00020000000000000000008a-0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "managedBalance": "0" + } + ] + }, + { + "holdersCount": "3", + "lowerTarget": null, + "swapEnabled": true, + "name": "NFT/Gaming Index", + "id": "0x344e8f99a55da2ba6b4b5158df2143374e400df2000100000000000000000079", + "totalSwapVolume": "535332.2784457539488546921754750037", + "totalShares": "8.137638729432030372", + "tokensList": [ + "0x0ec9f76202a7061eb9b3a7d6b59d36215a7e37da", + "0x18aaa7115705e8be94bffebde57af9bfc265b998", + "0x25f8087ead173b73d6e8b84329989a8eea16cf73", + "0x3845badade8e6dff049820680d1f14bd3903a5d0", + "0x87d73e916d7057945c9bcd8cdd94e42a6f47f776", + "0xb6ca7399b4f9ca56fc27cbff44f4d2e4eef1fc81", + "0xbb0e17ef65f82ab018d8edd776e8dd940327b28b", + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + ], + "mainIndex": 0, + "symbol": "NFTG", + "wrappedIndex": 0, + "snapshots": [ + { + "swapFees": "5353.322784457539488546921754750037", + "timestamp": 1659484800 + }, + { + "swapFees": "5353.253430365446478494999759539674", + "timestamp": 1658880000 + } + ], + "poolType": "Weighted", + "address": "0x344e8f99a55da2ba6b4b5158df2143374e400df2", + "createTime": 1627482189, + "lastUpdate": 1660547742028, + "totalWeight": "1", + "swapsCount": "882", + "totalSwapFee": "5353.322784457539488546921754750037", + "chainId": 1, + "totalLiquidity": "60.450842422483721371046222", + "graphData": { "totalLiquidity": "107.0540296058752735129733507284743" }, + "swapFee": "0.01", + "upperTarget": null, + "tokens": [ + { + "symbol": "BPT", + "address": "0x0ec9f76202a7061eb9b3a7d6b59d36215a7e37da", + "priceRate": "1", + "balance": "9.339431454424532673", + "decimals": 18, + "name": "BlackPool Token", + "weight": "0.125", + "id": "0x344e8f99a55da2ba6b4b5158df2143374e400df2000100000000000000000079-0x0ec9f76202a7061eb9b3a7d6b59d36215a7e37da", + "managedBalance": "0" + }, + { + "symbol": "AUDIO", + "address": "0x18aaa7115705e8be94bffebde57af9bfc265b998", + "priceRate": "1", + "balance": "11.579054239992159476", + "decimals": 18, + "name": "Audius", + "weight": "0.125", + "id": "0x344e8f99a55da2ba6b4b5158df2143374e400df2000100000000000000000079-0x18aaa7115705e8be94bffebde57af9bfc265b998", + "managedBalance": "0" + }, + { + "symbol": "YGG", + "address": "0x25f8087ead173b73d6e8b84329989a8eea16cf73", + "priceRate": "1", + "balance": "4.945778411706075443", + "decimals": 18, + "name": "Yield Guild Games Token", + "weight": "0.125", + "id": "0x344e8f99a55da2ba6b4b5158df2143374e400df2000100000000000000000079-0x25f8087ead173b73d6e8b84329989a8eea16cf73", + "managedBalance": "0" + }, + { + "symbol": "SAND", + "address": "0x3845badade8e6dff049820680d1f14bd3903a5d0", + "priceRate": "1", + "balance": "8.57243620802115272", + "decimals": 18, + "name": "SAND", + "weight": "0.125", + "id": "0x344e8f99a55da2ba6b4b5158df2143374e400df2000100000000000000000079-0x3845badade8e6dff049820680d1f14bd3903a5d0", + "managedBalance": "0" + }, + { + "symbol": "NFTX", + "address": "0x87d73e916d7057945c9bcd8cdd94e42a6f47f776", + "priceRate": "1", + "balance": "0.123976658398697496", + "decimals": 18, + "name": "NFTX", + "weight": "0.125", + "id": "0x344e8f99a55da2ba6b4b5158df2143374e400df2000100000000000000000079-0x87d73e916d7057945c9bcd8cdd94e42a6f47f776", + "managedBalance": "0" + }, + { + "symbol": "MUSE", + "address": "0xb6ca7399b4f9ca56fc27cbff44f4d2e4eef1fc81", + "priceRate": "1", + "balance": "0.854224382331130788", + "decimals": 18, + "name": "Muse", + "weight": "0.125", + "id": "0x344e8f99a55da2ba6b4b5158df2143374e400df2000100000000000000000079-0xb6ca7399b4f9ca56fc27cbff44f4d2e4eef1fc81", + "managedBalance": "0" + }, + { + "symbol": "AXS", + "address": "0xbb0e17ef65f82ab018d8edd776e8dd940327b28b", + "priceRate": "1", + "balance": "0.441388331959998161", + "decimals": 18, + "name": "Axie Infinity Shard", + "weight": "0.125", + "id": "0x344e8f99a55da2ba6b4b5158df2143374e400df2000100000000000000000079-0xbb0e17ef65f82ab018d8edd776e8dd940327b28b", + "managedBalance": "0" + }, + { + "symbol": "WETH", + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "priceRate": "1", + "balance": "0.007268942325689718", + "decimals": 18, + "name": "Wrapped Ether", + "weight": "0.125", + "id": "0x344e8f99a55da2ba6b4b5158df2143374e400df2000100000000000000000079-0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "managedBalance": "0" + } + ] } ] diff --git a/balancer-js/src/test/fixtures/liquidityTokenPrices.json b/balancer-js/src/test/fixtures/liquidityTokenPrices.json deleted file mode 100644 index 32032fdff..000000000 --- a/balancer-js/src/test/fixtures/liquidityTokenPrices.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "0x6b175474e89094c44da98b954eedeac495271d0f": { - "usd": "1", - "eth": "0.0005" - }, - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { - "usd": "1.01", - "eth": "0.000505" - }, - "0xdac17f958d2ee523a2206206994597c13d831ec7": { - "usd": "0.99", - "eth": "0.000495" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "usd": "2000", - "eth": "1" - }, - "0x82af49447d8a07e3bd95bd0d56f35241523fbab1": { - "usd": "2000", - "eth": "1" - }, - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": { - "usd": "32000", - "eth": "16" - }, - "0xba100000625a3754423978a60c9317c58a424e3d": { - "usd": "16", - "eth": "0.008" - }, - "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0": { - "usd": "2140", - "eth": "1.07" - } -} \ No newline at end of file diff --git a/balancer-js/src/test/fixtures/liquidityTokens.json b/balancer-js/src/test/fixtures/liquidityTokens.json new file mode 100644 index 000000000..8c7916bd4 --- /dev/null +++ b/balancer-js/src/test/fixtures/liquidityTokens.json @@ -0,0 +1,97 @@ +[ + { + "chainId": 1, + "symbol": "BAL", + "decimals": 18, + "address": "0xba100000625a3754423978a60c9317c58a424e3d", + "price": { + "usd": "16", + "eth": "0.008" + }, + "lastUpdate": 1648702504302 + }, + { + "chainId": 1, + "symbol": "USDC", + "decimals": 6, + "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "price": { + "usd": "1.01", + "eth": "0.000505" + }, + "lastUpdate": 1648702623513 + }, + { + "chainId": 1, + "symbol": "DAI", + "decimals": 18, + "address": "0x6b175474e89094c44da98b954eedeac495271d0f", + "price": { + "usd": "1", + "eth": "0.0005" + }, + "lastUpdate": 1648702385646 + }, + { + "chainId": 1, + "symbol": "USDT", + "decimals": 6, + "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "price": { + "usd": "0.99", + "eth": "0.000495" + }, + "lastUpdate": 1648702503883 + }, + { + "chainId": 1, + "symbol": "WETH", + "decimals": 18, + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "price": { + "usd": "2000", + "eth": "1" + }, + "lastUpdate": 1648702384835 + }, + { + "chainId": 1, + "symbol": "WBTC", + "decimals": 8, + "address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", + "price": { + "usd": "32000", + "eth": "16" + } + }, + { + "chainId": 1, + "symbol": "wstETH", + "decimals": 18, + "address": "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", + "price": { + "usd": "2140", + "eth": "1.07" + } + }, + { + "chainId": 1, + "noPriceData": true, + "symbol": "bb-a-USD", + "decimals": 18, + "address": "0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb2", + "price": {}, + "lastUpdate": 1648700588917 + }, + { + "chainId": 42161, + "symbol": "WETH", + "decimals": 18, + "address": "0x82af49447d8a07e3bd95bd0d56f35241523fbab1", + "price": { + "usd": "2000", + "eth": "1" + }, + "lastUpdate": 1648702384835 + } +] \ No newline at end of file From 784dfd584541be5697299aeb82e888656e8b4de8 Mon Sep 17 00:00:00 2001 From: bronco Date: Wed, 31 Aug 2022 14:17:49 +0200 Subject: [PATCH 43/65] after rebase spec fixes --- .../contracts/contracts.module.spec.ts | 4 +- .../join.concern.integration.spec.ts | 72 +++++------ .../concerns/metaStable/join.concern.spec.ts | 48 ++++---- .../concerns/metaStable/priceImpact.spec.ts | 45 ++----- .../stable/join.concern.integration.spec.ts | 87 ++++++------- .../concerns/stable/join.concern.spec.ts | 34 +++--- .../concerns/stable/priceImpact.spec.ts | 32 +---- .../stablePhantom/priceImpact.spec.ts | 46 ++----- .../weighted/exit.concern.integration.spec.ts | 52 +++----- .../weighted/join.concern.integration.spec.ts | 114 ++++++++---------- .../concerns/weighted/join.concern.spec.ts | 41 +++---- .../concerns/weighted/priceImpact.spec.ts | 46 ++----- .../modules/relayer/relayer.module.spec.ts | 4 +- .../src/modules/sor/sor.module.spec.ts | 4 +- .../modules/subgraph/subgraph.module.spec.ts | 4 +- .../src/modules/swaps/swaps.module.spec.ts | 4 +- 16 files changed, 244 insertions(+), 393 deletions(-) diff --git a/balancer-js/src/modules/contracts/contracts.module.spec.ts b/balancer-js/src/modules/contracts/contracts.module.spec.ts index 3b2827856..94ad70672 100644 --- a/balancer-js/src/modules/contracts/contracts.module.spec.ts +++ b/balancer-js/src/modules/contracts/contracts.module.spec.ts @@ -26,7 +26,7 @@ describe('contracts module', () => { ); const wethAddress = await vaultContract.WETH(); expect(wethAddress).to.eq('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'); - }); + }).timeout(20000); it('instantiate via SDK', async () => { const balancer = new BalancerSDK(sdkConfig); @@ -36,6 +36,6 @@ describe('contracts module', () => { ); const wethAddress = await vaultContract.WETH(); expect(wethAddress).to.eq('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'); - }); + }).timeout(2e4); }); }); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/join.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/join.concern.integration.spec.ts index 7c7cb491a..d4a8ce8b6 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/join.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/join.concern.integration.spec.ts @@ -7,9 +7,6 @@ import { BalancerSDK, Network, Pool, - PoolModel, - PoolToken, - StaticPoolRepository, } from '@/.'; import hardhat from 'hardhat'; @@ -17,22 +14,20 @@ import { TransactionReceipt } from '@ethersproject/providers'; import { parseFixed, BigNumber } from '@ethersproject/bignumber'; import { ADDRESSES } from '@/test/lib/constants'; -import { forkSetup, setupPool, getBalances } from '@/test/lib/utils'; +import { forkSetup, getBalances } from '@/test/lib/utils'; import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { PoolsProvider } from '@/modules/pools/provider'; +import { Pools } from '../../../'; dotenv.config(); const { ALCHEMY_URL: jsonRpcUrl } = process.env; const { ethers } = hardhat; -let balancer: BalancerSDK; const rpcUrl = 'http://127.0.0.1:8545'; const network = Network.MAINNET; -const sdkConfig = { - network, - rpcUrl, -}; +const sdk = new BalancerSDK({ network, rpcUrl }); +const { networkConfig } = sdk; + const provider = new ethers.providers.JsonRpcProvider(rpcUrl, 1); const signer = provider.getSigner(); let signerAddress: string; @@ -44,10 +39,18 @@ const slots = [ADDRESSES[network].wSTETH.slot, ADDRESSES[network].WETH.slot]; const initialBalance = '100000'; const amountsInDiv = '10000'; // TODO: setting amountsInDiv to 1000 will fail test due to stable math convergence issue - check if that's expected from maths -let tokensIn: PoolToken[]; let amountsIn: string[]; // Test scenarios +const pool = pools_14717479.find( + (pool) => + pool.id == + '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080' // stETH_stable_pool_id +) as unknown as Pool; +const tokensIn = pool.tokens; + +const controller = Pools.wrap(pool, networkConfig); + describe('join execution', async () => { let transactionReceipt: TransactionReceipt; let bptBalanceBefore: BigNumber; @@ -55,31 +58,11 @@ describe('join execution', async () => { let bptBalanceAfter: BigNumber; let tokensBalanceBefore: BigNumber[]; let tokensBalanceAfter: BigNumber[]; - let pool: PoolModel; // Setup chain before(async function () { this.timeout(20000); - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - balancer = new BalancerSDK( - sdkConfig, - undefined, - undefined, - undefined, - poolsProvider - ); - const poolSetup = await setupPool( - poolsProvider, - '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080' // Balancer stETH Stable Pool - ); - if (!poolSetup) throw Error('Error setting up pool.'); - pool = poolSetup; - tokensIn = pool.tokens; - const balances = pool.tokens.map((token) => + const balances = tokensIn.map((token) => parseFixed(initialBalance, token.decimals).toString() ); await forkSetup( @@ -108,12 +91,13 @@ describe('join execution', async () => { const slippage = '1'; - const { to, data, minBPTOut } = pool.buildJoin( + const { to, data, minBPTOut } = controller.buildJoin( signerAddress, tokensIn.map((t) => t.address), amountsIn, slippage ); + const tx = { to, data }; bptMinBalanceIncrease = BigNumber.from(minBPTOut); @@ -131,7 +115,10 @@ describe('join execution', async () => { it('price impact calculation', async () => { const minBPTOut = bptMinBalanceIncrease.toString(); - const priceImpact = await pool.calcPriceImpact(amountsIn, minBPTOut); + const priceImpact = await controller.calcPriceImpact( + amountsIn, + minBPTOut + ); expect(priceImpact).to.eql('100000000010000'); }); @@ -164,13 +151,14 @@ describe('join execution', async () => { ); const slippage = '100'; - const { functionName, attributes, value, minBPTOut } = pool.buildJoin( - signerAddress, - tokensIn.map((t) => t.address), - amountsIn, - slippage - ); - const transactionResponse = await balancer.contracts.vault + const { functionName, attributes, value, minBPTOut } = + controller.buildJoin( + signerAddress, + tokensIn.map((t) => t.address), + amountsIn, + slippage + ); + const transactionResponse = await sdk.contracts.vault .connect(signer) [functionName](...Object.values(attributes), { value }); transactionReceipt = await transactionResponse.wait(); @@ -215,7 +203,7 @@ describe('join execution', async () => { const slippage = '10'; let errorMessage; try { - pool.buildJoin( + controller.buildJoin( signerAddress, tokensIn.map((t) => t.address), amountsIn, diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/join.concern.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/join.concern.spec.ts index c4711afd2..976ab6049 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/join.concern.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/join.concern.spec.ts @@ -1,14 +1,7 @@ import { expect } from 'chai'; -import { - BalancerError, - BalancerErrorCode, - BalancerSdkConfig, - Network, - StaticPoolRepository, - Pool, -} from '@/.'; -import { PoolsProvider } from '@/modules/pools/provider'; +import { Network, Pool } from '@/.'; import { parseFixed } from '@ethersproject/bignumber'; +import { MetaStablePoolJoin } from './join.concern'; import { ADDRESSES } from '@/test/lib/constants'; import pools_14717479 from '@/test/lib/pools_14717479.json'; @@ -16,32 +9,35 @@ import pools_14717479 from '@/test/lib/pools_14717479.json'; const stETH_stable_pool_id = '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080'; // Balancer stETH Stable Pool -describe('join module', () => { - const network = Network.MAINNET; - const sdkConfig: BalancerSdkConfig = { - network, - rpcUrl: '', - }; - describe('buildJoin', async () => { - const pools = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - const pool = await pools.find(stETH_stable_pool_id); - if (!pool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); +const wrappedNativeAsset = ADDRESSES[Network.MAINNET].WETH.address; +const pool = pools_14717479.find( + (pool) => pool.id == stETH_stable_pool_id +) as unknown as Pool; + +const concern = new MetaStablePoolJoin(); + +describe('joining metaStable pool', () => { + describe('.buildJoin', async () => { it('should return encoded params', async () => { - const account = '0x35f5a330fd2f8e521ebd259fa272ba8069590741'; + const joiner = '0x35f5a330fd2f8e521ebd259fa272ba8069590741'; const tokensIn = [ - ADDRESSES[network].wSTETH.address, - ADDRESSES[network].WETH.address, + ADDRESSES[Network.MAINNET].wSTETH.address, + ADDRESSES[Network.MAINNET].WETH.address, ]; const amountsIn = [ parseFixed('1', 18).toString(), parseFixed('1', 18).toString(), ]; const slippage = '100'; - const { data } = pool.buildJoin(account, tokensIn, amountsIn, slippage); + const { data } = concern.buildJoin({ + joiner, + pool, + tokensIn, + amountsIn, + slippage, + wrappedNativeAsset, + }); expect(data).to.equal( '0xb95cac2832296969ef14eb0c6d29669c550d4a044913023000020000000000000000008000000000000000000000000035f5a330fd2f8e521ebd259fa272ba806959074100000000000000000000000035f5a330fd2f8e521ebd259fa272ba80695907410000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000001bf892f9e875996800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a7640000' ); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/priceImpact.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/priceImpact.spec.ts index 766c237b1..3a98513e0 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/priceImpact.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/priceImpact.spec.ts @@ -1,47 +1,26 @@ -import dotenv from 'dotenv'; import { expect } from 'chai'; import { MetaStablePoolPriceImpact } from '@/modules/pools/pool-types/concerns/metaStable/priceImpact.concern'; import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { PoolsProvider } from '@/modules/pools/provider'; -import { StaticPoolRepository } from '@/modules/data'; -import { PoolModel, Pool } from '@/types'; -import { Network } from '@/.'; -import { setupPool } from '@/test/lib/utils'; - -dotenv.config(); - -const rpcUrl = 'http://127.0.0.1:8545'; +import { Pool } from '@/types'; const priceImpactCalc = new MetaStablePoolPriceImpact(); const wstETHwETH = '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080'; -describe('metastable pool price impact', () => { - let pool: PoolModel | undefined; +const pool = pools_14717479.find( + (pool) => pool.id == wstETHwETH +) as unknown as Pool; - // Setup chain - before(async function () { - this.timeout(20000); - const sdkConfig = { - network: Network.MAINNET, - rpcUrl, - }; - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - pool = await setupPool(poolsProvider, wstETHwETH); - }); +const tokenAmounts = [ + BigInt('629870162919981039400158'), + BigInt('615159929697'), +]; - const tokenAmounts = [ - BigInt('629870162919981039400158'), - BigInt('615159929697'), - ]; +describe('metastable pool price impact', () => { context('bpt zero price impact', () => { it('non-proportional case', () => { const bptZeroPriceImpact = priceImpactCalc.bptZeroPriceImpact( - pool as PoolModel, + pool, tokenAmounts ); expect(bptZeroPriceImpact.toString()).to.eq('662816325116386208862285'); @@ -56,7 +35,7 @@ describe('metastable pool price impact', () => { ]; const bptZeroPriceImpact = priceImpactCalc.bptZeroPriceImpact( - pool as PoolModel, + pool, proportionalTokenAmounts ); expect(bptZeroPriceImpact.toString()).to.eq('1696871032806568300470'); @@ -66,7 +45,7 @@ describe('metastable pool price impact', () => { context('price impact', () => { it('calculate price impact', () => { const priceImpact = priceImpactCalc.calcPriceImpact( - pool as PoolModel, + pool, tokenAmounts.map((amount) => amount.toString()), '660816325116386208862285' ); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/join.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/join.concern.integration.spec.ts index 1c9cc45c1..aeabe9c48 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/join.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/join.concern.integration.spec.ts @@ -7,9 +7,6 @@ import { BalancerSDK, Network, Pool, - PoolModel, - PoolToken, - StaticPoolRepository, } from '@/.'; import hardhat from 'hardhat'; @@ -17,22 +14,20 @@ import { TransactionReceipt } from '@ethersproject/providers'; import { parseFixed, BigNumber } from '@ethersproject/bignumber'; import { ADDRESSES } from '@/test/lib/constants'; -import { forkSetup, setupPool, getBalances } from '@/test/lib/utils'; +import { forkSetup, getBalances } from '@/test/lib/utils'; import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { PoolsProvider } from '@/modules/pools/provider'; +import { Pools } from '../../../'; dotenv.config(); const { ALCHEMY_URL: jsonRpcUrl } = process.env; const { ethers } = hardhat; -let balancer: BalancerSDK; -const network = Network.MAINNET; const rpcUrl = 'http://127.0.0.1:8545'; -const sdkConfig = { - network, - rpcUrl, -}; +const network = Network.MAINNET; +const sdk = new BalancerSDK({ network, rpcUrl }); +const { networkConfig } = sdk; + const provider = new ethers.providers.JsonRpcProvider(rpcUrl, 1); const signer = provider.getSigner(); let signerAddress: string; @@ -46,13 +41,20 @@ const slots = [ ]; const initialBalance = '100000'; -const amountsInDiv = '100000'; // TODO: setting amountsInDiv to 1000 will fail test due to stable math convergence issue - check if that's expected from maths -const slippage = '100'; +const amountsInDiv = '10000'; // TODO: setting amountsInDiv to 1000 will fail test due to stable math convergence issue - check if that's expected from maths -let tokensIn: PoolToken[]; let amountsIn: string[]; // Test scenarios +const pool = pools_14717479.find( + (pool) => + pool.id == + '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063' // Balancer USD Stable Pool - staBAL3 +) as unknown as Pool; +const tokensIn = pool.tokens; + +const controller = Pools.wrap(pool, networkConfig); + describe('join execution', async () => { let transactionReceipt: TransactionReceipt; let bptBalanceBefore: BigNumber; @@ -60,31 +62,11 @@ describe('join execution', async () => { let bptBalanceAfter: BigNumber; let tokensBalanceBefore: BigNumber[]; let tokensBalanceAfter: BigNumber[]; - let pool: PoolModel; // Setup chain before(async function () { this.timeout(20000); - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - balancer = new BalancerSDK( - sdkConfig, - undefined, - undefined, - undefined, - poolsProvider - ); - const poolSetup = await setupPool( - poolsProvider, - '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063' // Balancer USD Stable Pool - staBAL3 - ); - if (!poolSetup) throw Error('Error setting up pool.'); - pool = poolSetup; - tokensIn = pool.tokens; - const balances = pool.tokens.map((token) => + const balances = tokensIn.map((token) => parseFixed(initialBalance, token.decimals).toString() ); await forkSetup( @@ -111,18 +93,19 @@ describe('join execution', async () => { signerAddress ); - const { to, data, minBPTOut } = pool.buildJoin( + const slippage = '1'; + + const { to, data, minBPTOut } = controller.buildJoin( signerAddress, tokensIn.map((t) => t.address), amountsIn, slippage ); + const tx = { to, data }; bptMinBalanceIncrease = BigNumber.from(minBPTOut); - const transactionResponse = await signer.sendTransaction(tx); - - transactionReceipt = await transactionResponse.wait(); + transactionReceipt = await (await signer.sendTransaction(tx)).wait(); [bptBalanceAfter, ...tokensBalanceAfter] = await getBalances( [pool.address, ...pool.tokensList], signer, @@ -136,8 +119,11 @@ describe('join execution', async () => { it('price impact calculation', async () => { const minBPTOut = bptMinBalanceIncrease.toString(); - const priceImpact = await pool.calcPriceImpact(amountsIn, minBPTOut); - expect(priceImpact).to.eql('10000004864945495'); + const priceImpact = await controller.calcPriceImpact( + amountsIn, + minBPTOut + ); + expect(priceImpact).to.eql('100000444261607'); }); it('should increase BPT balance', async () => { @@ -168,13 +154,15 @@ describe('join execution', async () => { signerAddress ); - const { functionName, attributes, value, minBPTOut } = pool.buildJoin( - signerAddress, - tokensIn.map((t) => t.address), - amountsIn, - slippage - ); - const transactionResponse = await balancer.contracts.vault + const slippage = '100'; + const { functionName, attributes, value, minBPTOut } = + controller.buildJoin( + signerAddress, + tokensIn.map((t) => t.address), + amountsIn, + slippage + ); + const transactionResponse = await sdk.contracts.vault .connect(signer) [functionName](...Object.values(attributes), { value }); transactionReceipt = await transactionResponse.wait(); @@ -216,9 +204,10 @@ describe('join execution', async () => { }); it('should fail on number of input tokens', async () => { + const slippage = '10'; let errorMessage; try { - pool.buildJoin( + controller.buildJoin( signerAddress, tokensIn.map((t) => t.address), amountsIn, diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/join.concern.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/join.concern.spec.ts index 20bbd95b5..cb7a735b9 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/join.concern.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/join.concern.spec.ts @@ -1,6 +1,5 @@ import { expect } from 'chai'; -import { BalancerSdkConfig, Network, StaticPoolRepository, Pool } from '@/.'; -import { PoolsProvider } from '@/modules/pools/provider'; +import { StablePoolJoin } from './join.concern'; import pools_14717479 from '@/test/lib/pools_14717479.json'; import { parseFixed } from '@ethersproject/bignumber'; @@ -11,22 +10,18 @@ const dai_usdc_usdt_pool_id = const DAI_address = '0x6b175474e89094c44da98b954eedeac495271d0f'; // 18 const USDC_address = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; // 6 const USDT_address = '0xdac17f958d2ee523a2206206994597c13d831ec7'; // 6 +const wrappedNativeAsset = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; -describe('join module', () => { - const sdkConfig: BalancerSdkConfig = { - network: Network.MAINNET, - rpcUrl: '', - }; +const pool = pools_14717479.find( + (pool) => pool.id == dai_usdc_usdt_pool_id +) as unknown as Pool; - describe('buildJoin', async () => { - const pools = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - const pool = await pools.find(dai_usdc_usdt_pool_id); - if (!pool) throw new Error('Pool not found'); +const concern = new StablePoolJoin(); + +describe('joining stable pool', () => { + describe('.buildJoin', async () => { it('should return encoded params', async () => { - const account = '0x35f5a330fd2f8e521ebd259fa272ba8069590741'; + const joiner = '0x35f5a330fd2f8e521ebd259fa272ba8069590741'; const tokensIn = [DAI_address, USDC_address, USDT_address]; const amountsIn = [ parseFixed('100', 18).toString(), @@ -34,7 +29,14 @@ describe('join module', () => { parseFixed('100', 6).toString(), ]; const slippage = '100'; - const { data } = pool.buildJoin(account, tokensIn, amountsIn, slippage); + const { data } = concern.buildJoin({ + joiner, + pool, + tokensIn, + amountsIn, + slippage, + wrappedNativeAsset, + }); expect(data).to.equal( '0xb95cac2806df3b2bbb68adc8b0e302443692037ed9f91b4200000000000000000000006300000000000000000000000035f5a330fd2f8e521ebd259fa272ba806959074100000000000000000000000035f5a330fd2f8e521ebd259fa272ba80695907410000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000100116b8372224147e00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000005f5e100' diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/priceImpact.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/priceImpact.spec.ts index 1412f8dd2..4cb5246aa 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/priceImpact.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/priceImpact.spec.ts @@ -1,39 +1,17 @@ -import dotenv from 'dotenv'; import { expect } from 'chai'; import { StablePoolPriceImpact } from '@/modules/pools/pool-types/concerns/stable/priceImpact.concern'; import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { Pool, PoolModel } from '@/types'; -import { StaticPoolRepository } from '@/modules/data'; -import { PoolsProvider } from '@/modules/pools/provider'; -import { Network } from '@/.'; -import { setupPool } from '@/test/lib/utils'; - -dotenv.config(); - -const rpcUrl = 'http://127.0.0.1:8545'; +import { Pool } from '@/types'; const priceImpactCalc = new StablePoolPriceImpact(); const staBal3Id = '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063'; -describe('stable pool price impact', () => { - let pool: PoolModel; - - // Setup chain - before(async function () { - this.timeout(20000); - const sdkConfig = { - network: Network.MAINNET, - rpcUrl, - }; - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - pool = (await setupPool(poolsProvider, staBal3Id)) as PoolModel; - }); +const pool = pools_14717479.find( + (pool) => pool.id == staBal3Id +) as unknown as Pool; +describe('stable pool price impact', () => { context('bpt zero price impact', () => { it('proportional case', () => { const proportionalTokenAmounts = [ diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/priceImpact.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/priceImpact.spec.ts index b60316576..293ce673d 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/priceImpact.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/priceImpact.spec.ts @@ -1,47 +1,27 @@ -import dotenv from 'dotenv'; import { expect } from 'chai'; import { StablePhantomPriceImpact } from '@/modules/pools/pool-types/concerns/stablePhantom/priceImpact.concern'; - import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { StaticPoolRepository } from '@/modules/data'; -import { PoolsProvider } from '@/modules/pools/provider'; -import { PoolModel, Pool } from '@/types'; -import { Network } from '@/.'; -import { setupPool } from '@/test/lib/utils'; +import { Pool } from '@/types'; -dotenv.config(); -const rpcUrl = 'http://127.0.0.1:8545'; const priceImpactCalc = new StablePhantomPriceImpact(); const bbaUSDPoolId = '0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe'; -describe('phantomStable pool price impact', () => { - let pool: PoolModel; +const pool = pools_14717479.find( + (pool) => pool.id == bbaUSDPoolId +) as unknown as Pool; - // Setup chain - before(async function () { - this.timeout(20000); - const sdkConfig = { - network: Network.MAINNET, - rpcUrl, - }; - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - pool = await setupPool(poolsProvider, bbaUSDPoolId); - }); +const tokenAmounts = [ + BigInt('6298701629199810399876'), + BigInt('615159929697'), + BigInt('101515992969778'), +]; - const tokenAmounts = [ - BigInt('6298701629199810399876'), - BigInt('615159929697'), - BigInt('101515992969778'), - ]; +describe('phantomStable pool price impact', () => { context('bpt zero price impact', () => { it('non-proportional case', () => { const bptZeroPriceImpact = priceImpactCalc.bptZeroPriceImpact( - pool as PoolModel, + pool, tokenAmounts ); expect(bptZeroPriceImpact.toString()).to.eq('6310741387055771004078'); @@ -56,7 +36,7 @@ describe('phantomStable pool price impact', () => { ]; const bptZeroPriceImpact = priceImpactCalc.bptZeroPriceImpact( - pool as PoolModel, + pool, tokenAmounts ); expect(bptZeroPriceImpact.toString()).to.eq('2584652218704385059205928'); @@ -66,7 +46,7 @@ describe('phantomStable pool price impact', () => { context('price impact', () => { it('calculate price impact', () => { const priceImpact = priceImpactCalc.calcPriceImpact( - pool as PoolModel, + pool, tokenAmounts.map((amount) => amount.toString()), '6300741387055771004078' ); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/weighted/exit.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/weighted/exit.concern.integration.spec.ts index 47aa0255e..56ed1d6a5 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/weighted/exit.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/weighted/exit.concern.integration.spec.ts @@ -1,19 +1,12 @@ import dotenv from 'dotenv'; import { expect } from 'chai'; -import { - BalancerSDK, - Network, - Pool, - PoolModel, - PoolToken, - StaticPoolRepository, -} from '@/.'; +import { BalancerSDK, Network, Pool, PoolToken } from '@/.'; import hardhat from 'hardhat'; import { TransactionReceipt } from '@ethersproject/providers'; import { BigNumber, parseFixed } from '@ethersproject/bignumber'; -import { forkSetup, setupPool, getBalances } from '@/test/lib/utils'; -import { PoolsProvider } from '@/modules/pools/provider'; +import { forkSetup, getBalances } from '@/test/lib/utils'; +import { Pools } from '@/modules/pools'; import pools_14717479 from '@/test/lib/pools_14717479.json'; import { ExitPoolAttributes } from '../types'; @@ -24,9 +17,10 @@ dotenv.config(); const { ALCHEMY_URL: jsonRpcUrl } = process.env; const { ethers } = hardhat; -let balancer: BalancerSDK; const rpcUrl = 'http://127.0.0.1:8545'; const network = Network.MAINNET; +const sdk = new BalancerSDK({ network, rpcUrl }); +const { networkConfig } = sdk; const provider = new ethers.providers.JsonRpcProvider(rpcUrl, network); const signer = provider.getSigner(); @@ -41,6 +35,12 @@ const poolId = // '0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e'; // B_50WBTC_50WETH '0x96646936b91d6b9d7d0c47c496afbf3d6ec7b6f8000200000000000000000019'; // Balancer 50 USDC 50 WETH +const pool = pools_14717479.find( + (pool) => pool.id == poolId +) as unknown as Pool; + +const controller = Pools.wrap(pool, networkConfig); + let tokensOut: PoolToken[]; let amountsOut: string[]; let transactionReceipt: TransactionReceipt; @@ -52,30 +52,12 @@ let tokensBalanceAfter: BigNumber[]; let tokensMinBalanceIncrease: BigNumber[]; let transactionCost: BigNumber; let signerAddress: string; -let pool: PoolModel; describe('exit execution', async () => { // Setup chain before(async function () { this.timeout(20000); - const sdkConfig = { - network, - rpcUrl, - }; - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - balancer = new BalancerSDK( - sdkConfig, - undefined, - undefined, - undefined, - poolsProvider - ); - pool = await setupPool(poolsProvider, poolId); tokensOut = pool.tokens; await forkSetup( signer, @@ -123,7 +105,7 @@ describe('exit execution', async () => { tokensBalanceAfter = tokensBalanceAfter.map((balance, i) => { if ( pool.tokensList[i] === - balancer.networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase() + networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase() ) { return balance.add(transactionCost); } @@ -137,7 +119,7 @@ describe('exit execution', async () => { this.timeout(20000); const bptIn = parseFixed('10', 18).toString(); await testFlow( - pool.buildExitExactBPTIn(signerAddress, bptIn, slippage), + controller.buildExitExactBPTIn(signerAddress, bptIn, slippage), pool.tokensList ); }); @@ -173,7 +155,7 @@ describe('exit execution', async () => { ); await testFlow( - pool.buildExitExactTokensOut( + controller.buildExitExactTokensOut( signerAddress, tokensOut.map((t) => t.address), amountsOut, @@ -213,13 +195,13 @@ describe('exit execution', async () => { const exitTokens = pool.tokensList.map((token) => token === - balancer.networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase() + networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase() ? AddressZero : token ); await testFlow( - pool.buildExitExactTokensOut( + controller.buildExitExactTokensOut( signerAddress, exitTokens, amountsOut, @@ -256,7 +238,7 @@ describe('exit execution', async () => { try { const bptIn = parseFixed('10', 18).toString(); await testFlow( - pool.buildExitExactBPTIn( + controller.buildExitExactBPTIn( signerAddress, bptIn, slippage, diff --git a/balancer-js/src/modules/pools/pool-types/concerns/weighted/join.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/weighted/join.concern.integration.spec.ts index b5e4fcddb..e54748587 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/weighted/join.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/weighted/join.concern.integration.spec.ts @@ -7,9 +7,6 @@ import { BalancerSDK, Network, Pool, - PoolModel, - PoolToken, - StaticPoolRepository, } from '@/.'; import hardhat from 'hardhat'; @@ -17,17 +14,20 @@ import { TransactionReceipt } from '@ethersproject/providers'; import { parseFixed, BigNumber } from '@ethersproject/bignumber'; import { AddressZero } from '@ethersproject/constants'; -import { forkSetup, setupPool, getBalances } from '@/test/lib/utils'; +import { forkSetup, getBalances } from '@/test/lib/utils'; import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { PoolsProvider } from '@/modules/pools/provider'; +import { Pools } from '../../../'; dotenv.config(); const { ALCHEMY_URL: jsonRpcUrl } = process.env; const { ethers } = hardhat; -let balancer: BalancerSDK; const rpcUrl = 'http://127.0.0.1:8545'; +const network = Network.MAINNET; +const sdk = new BalancerSDK({ network, rpcUrl }); +const { networkConfig } = sdk; + const provider = new ethers.providers.JsonRpcProvider(rpcUrl, 1); const signer = provider.getSigner(); let signerAddress: string; @@ -41,49 +41,32 @@ const slots = [wBTC_SLOT, wETH_SLOT]; const initialBalance = '100000'; const amountsInDiv = '100000000'; -let tokensIn: PoolToken[]; let amountsIn: string[]; // Test scenarios -describe('debug join execution', async () => { +const pool = pools_14717479.find( + (pool) => + pool.id == + '0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e' // B_50WBTC_50WETH +) as unknown as Pool; +const tokensIn = pool.tokens; + +const controller = Pools.wrap(pool, networkConfig); + +describe('join execution', async () => { let transactionReceipt: TransactionReceipt; let bptBalanceBefore: BigNumber; let bptMinBalanceIncrease: BigNumber; let bptBalanceAfter: BigNumber; let tokensBalanceBefore: BigNumber[]; let tokensBalanceAfter: BigNumber[]; - let pool: PoolModel; // Setup chain before(async function () { this.timeout(20000); - const sdkConfig = { - network: Network.MAINNET, - rpcUrl, - }; - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - balancer = new BalancerSDK( - sdkConfig, - undefined, - undefined, - undefined, - poolsProvider - ); - const poolSetup = await setupPool( - poolsProvider, - '0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e' // B_50WBTC_50WETH + const balances = tokensIn.map((token) => + parseFixed(initialBalance, token.decimals).toString() ); - if (!poolSetup) throw Error('Error setting up pool.'); - pool = poolSetup; - tokensIn = pool.tokens; - const balances = [ - parseFixed(initialBalance, tokensIn[0].decimals).toString(), - parseFixed(initialBalance, tokensIn[1].decimals).toString(), - ]; await forkSetup( signer, tokensIn.map((t) => t.address), @@ -103,25 +86,26 @@ describe('debug join execution', async () => { ); [bptBalanceBefore, ...tokensBalanceBefore] = await getBalances( - [pool.address, ...tokensIn.map((t) => t.address)], + [pool.address, ...pool.tokensList], signer, signerAddress ); - const slippage = '100'; + const slippage = '1'; - const { to, data, minBPTOut } = pool.buildJoin( + const { to, data, minBPTOut } = controller.buildJoin( signerAddress, tokensIn.map((t) => t.address), amountsIn, slippage ); + const tx = { to, data }; bptMinBalanceIncrease = BigNumber.from(minBPTOut); transactionReceipt = await (await signer.sendTransaction(tx)).wait(); [bptBalanceAfter, ...tokensBalanceAfter] = await getBalances( - [pool.address, ...tokensIn.map((t) => t.address)], + [pool.address, ...pool.tokensList], signer, signerAddress ); @@ -133,14 +117,16 @@ describe('debug join execution', async () => { it('price impact calculation', async () => { const minBPTOut = bptMinBalanceIncrease.toString(); - const priceImpact = await pool.calcPriceImpact(amountsIn, minBPTOut); - expect(priceImpact).to.eql('10002035024956851'); + const priceImpact = await controller.calcPriceImpact( + amountsIn, + minBPTOut + ); + expect(priceImpact).to.eq('102055375201527'); }); it('should increase BPT balance', async () => { - expect( - bptBalanceAfter.sub(bptBalanceBefore).toNumber() - ).to.greaterThanOrEqual(bptMinBalanceIncrease.toNumber()); + expect(bptBalanceAfter.sub(bptBalanceBefore).gte(bptMinBalanceIncrease)) + .to.be.true; }); it('should decrease tokens balance', async () => { @@ -161,26 +147,27 @@ describe('debug join execution', async () => { ); [bptBalanceBefore, ...tokensBalanceBefore] = await getBalances( - [pool.address, ...tokensIn.map((t) => t.address)], + [pool.address, ...pool.tokensList], signer, signerAddress ); const slippage = '100'; - const { functionName, attributes, value, minBPTOut } = pool.buildJoin( - signerAddress, - tokensIn.map((t) => t.address), - amountsIn, - slippage - ); - const transactionResponse = await balancer.contracts.vault + const { functionName, attributes, value, minBPTOut } = + controller.buildJoin( + signerAddress, + tokensIn.map((t) => t.address), + amountsIn, + slippage + ); + const transactionResponse = await sdk.contracts.vault .connect(signer) [functionName](...Object.values(attributes), { value }); transactionReceipt = await transactionResponse.wait(); bptMinBalanceIncrease = BigNumber.from(minBPTOut); [bptBalanceAfter, ...tokensBalanceAfter] = await getBalances( - [pool.address, ...tokensIn.map((t) => t.address)], + [pool.address, ...pool.tokensList], signer, signerAddress ); @@ -191,9 +178,8 @@ describe('debug join execution', async () => { }); it('should increase BPT balance', async () => { - expect( - bptBalanceAfter.sub(bptBalanceBefore).toNumber() - ).to.greaterThanOrEqual(bptMinBalanceIncrease.toNumber()); + expect(bptBalanceAfter.sub(bptBalanceBefore).gte(bptMinBalanceIncrease)) + .to.be.true; }); it('should decrease tokens balance', async () => { @@ -215,10 +201,7 @@ describe('debug join execution', async () => { ); const tokensWithETH = tokensIn.map((t) => { - if ( - t.address === - balancer.networkConfig.addresses.tokens.wrappedNativeAsset - ) + if (t.address === networkConfig.addresses.tokens.wrappedNativeAsset) return AddressZero; return t.address; }); @@ -230,7 +213,7 @@ describe('debug join execution', async () => { ); const slippage = '100'; - const { to, data, value, minBPTOut } = pool.buildJoin( + const { to, data, value, minBPTOut } = controller.buildJoin( signerAddress, tokensWithETH, amountsIn, @@ -261,9 +244,8 @@ describe('debug join execution', async () => { }); it('should increase BPT balance', async () => { - expect( - bptBalanceAfter.sub(bptBalanceBefore).toNumber() - ).to.greaterThanOrEqual(bptMinBalanceIncrease.toNumber()); + expect(bptBalanceAfter.sub(bptBalanceBefore).gte(bptMinBalanceIncrease)) + .to.be.true; }); it('should decrease tokens balance', async () => { @@ -279,7 +261,7 @@ describe('debug join execution', async () => { before(async function () { this.timeout(20000); amountsIn = [ - parseFixed(tokensIn[1].balance, tokensIn[1].decimals) + parseFixed(tokensIn[0].balance, tokensIn[0].decimals) .div('100') .toString(), ]; @@ -289,7 +271,7 @@ describe('debug join execution', async () => { const slippage = '10'; let errorMessage; try { - pool.buildJoin( + controller.buildJoin( signerAddress, tokensIn.map((t) => t.address), amountsIn, diff --git a/balancer-js/src/modules/pools/pool-types/concerns/weighted/join.concern.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/weighted/join.concern.spec.ts index c5cf9402a..d7616f1ba 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/weighted/join.concern.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/weighted/join.concern.spec.ts @@ -1,13 +1,6 @@ import { expect } from 'chai'; -import { - BalancerError, - BalancerErrorCode, - BalancerSdkConfig, - Network, - StaticPoolRepository, - Pool, -} from '@/.'; -import { PoolsProvider } from '@/modules/pools/provider'; +import { Pool } from '@/.'; +import { WeightedPoolJoin } from './join.concern'; import pools_14717479 from '@/test/lib/pools_14717479.json'; @@ -17,25 +10,27 @@ const weth_usdc_pool_id = const USDC_address = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; const WETH_address = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; -describe('join module', () => { - const sdkConfig: BalancerSdkConfig = { - network: Network.MAINNET, - rpcUrl: '', - }; +const pool = pools_14717479.find( + (pool) => pool.id == weth_usdc_pool_id +) as unknown as Pool; - describe('buildJoin', async () => { - const pools = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - const pool = await pools.find(weth_usdc_pool_id); - if (!pool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); +const concern = new WeightedPoolJoin(); + +describe('joining weighted pool', () => { + describe('.buildJoin', async () => { it('should return encoded params', async () => { - const account = '0x35f5a330fd2f8e521ebd259fa272ba8069590741'; + const joiner = '0x35f5a330fd2f8e521ebd259fa272ba8069590741'; const tokensIn = [USDC_address, WETH_address]; const amountsIn = ['7249202509', '2479805746401150127']; const slippage = '100'; - const { data } = pool.buildJoin(account, tokensIn, amountsIn, slippage); + const { data } = concern.buildJoin({ + joiner, + pool, + tokensIn, + amountsIn, + slippage, + wrappedNativeAsset: WETH_address, + }); expect(data).to.equal( '0xb95cac2896646936b91d6b9d7d0c47c496afbf3d6ec7b6f800020000000000000000001900000000000000000000000035f5a330fd2f8e521ebd259fa272ba806959074100000000000000000000000035f5a330fd2f8e521ebd259fa272ba80695907410000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000001b0160d4d000000000000000000000000000000000000000000000000226a0a30123684af00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000d053b627d205d2629000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000001b0160d4d000000000000000000000000000000000000000000000000226a0a30123684af' diff --git a/balancer-js/src/modules/pools/pool-types/concerns/weighted/priceImpact.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/weighted/priceImpact.spec.ts index 94d97ec0a..6f10ea2c0 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/weighted/priceImpact.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/weighted/priceImpact.spec.ts @@ -1,16 +1,7 @@ -import dotenv from 'dotenv'; import { expect } from 'chai'; import { WeightedPoolPriceImpact } from '@/modules/pools/pool-types/concerns/weighted/priceImpact.concern'; import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { StaticPoolRepository } from '@/modules/data'; -import { PoolsProvider } from '@/modules/pools/provider'; -import { PoolModel, Pool } from '@/types'; -import { Network } from '@/.'; -import { setupPool } from '@/test/lib/utils'; - -dotenv.config(); - -const rpcUrl = 'http://127.0.0.1:8545'; +import { Pool } from '@/types'; const priceImpactCalc = new WeightedPoolPriceImpact(); const wethDaiId = @@ -18,26 +9,15 @@ const wethDaiId = const threeTokensPoolId = '0xb39362c3d5ac235fe588b0b83ed7ac87241039cb000100000000000000000195'; -describe('weighted pool price impact', () => { - let pool: PoolModel | undefined; - let threeTokensPool: PoolModel | undefined; +const pool = pools_14717479.find( + (pool) => pool.id == wethDaiId +) as unknown as Pool; - // Setup chain - before(async function () { - this.timeout(20000); - const sdkConfig = { - network: Network.MAINNET, - rpcUrl, - }; - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - pool = await setupPool(poolsProvider, wethDaiId); - threeTokensPool = await setupPool(poolsProvider, threeTokensPoolId); - }); +const threeTokensPool = pools_14717479.find( + (pool) => pool.id == threeTokensPoolId +) as unknown as Pool; +describe('weighted pool price impact', () => { context('bpt zero price impact', () => { it('two token pool', () => { const tokenAmounts = [ @@ -46,7 +26,7 @@ describe('weighted pool price impact', () => { ]; const bptZeroPriceImpact = priceImpactCalc.bptZeroPriceImpact( - pool as PoolModel, + pool, tokenAmounts ); expect(bptZeroPriceImpact.toString()).to.eq('2362847643421361281550'); @@ -56,7 +36,7 @@ describe('weighted pool price impact', () => { BigInt('125240456379058423162'), ]; const proportionalBptZeroPI = priceImpactCalc.bptZeroPriceImpact( - pool as PoolModel, + pool, proportionalTokenAmounts ); expect(proportionalBptZeroPI.toString()).to.eq('4931900186642428185328'); @@ -69,7 +49,7 @@ describe('weighted pool price impact', () => { ]; const bptZeroPriceImpact = priceImpactCalc.bptZeroPriceImpact( - threeTokensPool as PoolModel, + threeTokensPool, tokenAmounts ); expect(bptZeroPriceImpact.toString()).to.eq('876361770363362937782'); @@ -80,7 +60,7 @@ describe('weighted pool price impact', () => { BigInt('383499316375739080555'), ]; const proportionalBptZeroPI = priceImpactCalc.bptZeroPriceImpact( - threeTokensPool as PoolModel, + threeTokensPool, proportionalTokenAmounts ); expect(proportionalBptZeroPI.toString()).to.eq('279707470176761335097'); @@ -94,7 +74,7 @@ describe('weighted pool price impact', () => { '125240456379058423162', ]; const priceImpact = priceImpactCalc.calcPriceImpact( - pool as PoolModel, + pool, proportionalTokenAmounts, '4931900186642428185328' ); diff --git a/balancer-js/src/modules/relayer/relayer.module.spec.ts b/balancer-js/src/modules/relayer/relayer.module.spec.ts index 32be914ef..8608c4826 100644 --- a/balancer-js/src/modules/relayer/relayer.module.spec.ts +++ b/balancer-js/src/modules/relayer/relayer.module.spec.ts @@ -18,8 +18,8 @@ const sorConfig: BalancerSdkSorConfig = { }; const sdkConfig: BalancerSdkConfig = { - network: Network.KOVAN, - rpcUrl: `https://kovan.infura.io/v3/${process.env.INFURA}`, + network: Network.GOERLI, + rpcUrl: `https://goerli.infura.io/v3/${process.env.INFURA}`, sor: sorConfig, }; diff --git a/balancer-js/src/modules/sor/sor.module.spec.ts b/balancer-js/src/modules/sor/sor.module.spec.ts index db12521ae..df014e699 100644 --- a/balancer-js/src/modules/sor/sor.module.spec.ts +++ b/balancer-js/src/modules/sor/sor.module.spec.ts @@ -18,8 +18,8 @@ const sorConfig: BalancerSdkSorConfig = { }; const sdkConfig: BalancerSdkConfig = { - network: Network.KOVAN, - rpcUrl: `https://kovan.infura.io/v3/${process.env.INFURA}`, + network: Network.GOERLI, + rpcUrl: `https://goerli.infura.io/v3/${process.env.INFURA}`, sor: sorConfig, }; diff --git a/balancer-js/src/modules/subgraph/subgraph.module.spec.ts b/balancer-js/src/modules/subgraph/subgraph.module.spec.ts index ce46e485d..ffe4589d8 100644 --- a/balancer-js/src/modules/subgraph/subgraph.module.spec.ts +++ b/balancer-js/src/modules/subgraph/subgraph.module.spec.ts @@ -6,8 +6,8 @@ import { Subgraph } from './subgraph.module'; dotenv.config(); const sdkConfig: BalancerSdkConfig = { - network: Network.KOVAN, - rpcUrl: `https://kovan.infura.io/v3/${process.env.INFURA}`, + network: Network.GOERLI, + rpcUrl: `https://goerli.infura.io/v3/${process.env.INFURA}`, customSubgraphUrl: 'https://thegraph.com/custom-subgraph', }; diff --git a/balancer-js/src/modules/swaps/swaps.module.spec.ts b/balancer-js/src/modules/swaps/swaps.module.spec.ts index 798f990c0..0c80743c4 100644 --- a/balancer-js/src/modules/swaps/swaps.module.spec.ts +++ b/balancer-js/src/modules/swaps/swaps.module.spec.ts @@ -28,8 +28,8 @@ const sorConfig: BalancerSdkSorConfig = { }; const sdkConfig: BalancerSdkConfig = { - network: Network.KOVAN, - rpcUrl: `https://kovan.infura.io/v3/${process.env.INFURA}`, + network: Network.GOERLI, + rpcUrl: `https://goerli.infura.io/v3/${process.env.INFURA}`, sor: sorConfig, }; From b8e5809f93c5dbf6f061376f9e9e1c1f8a066810 Mon Sep 17 00:00:00 2001 From: bronco Date: Wed, 31 Aug 2022 14:18:11 +0200 Subject: [PATCH 44/65] examples updates --- balancer-js/examples/exitExactBPTIn.ts | 4 ++-- balancer-js/examples/exitExactTokensOut.ts | 10 +++++++--- balancer-js/examples/join.ts | 4 ++-- balancer-js/examples/priceImpact.ts | 21 ++++++++++++--------- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/balancer-js/examples/exitExactBPTIn.ts b/balancer-js/examples/exitExactBPTIn.ts index 2541123ea..49689c5ef 100644 --- a/balancer-js/examples/exitExactBPTIn.ts +++ b/balancer-js/examples/exitExactBPTIn.ts @@ -5,7 +5,7 @@ import { BalancerErrorCode, BalancerSDK, Network, - PoolModel, + PoolWithMethods, } from '../src/index'; import { parseFixed } from '@ethersproject/bignumber'; import { forkSetup, getBalances } from '../src/test/lib/utils'; @@ -40,7 +40,7 @@ async function exitExactBPTIn() { const balancer = new BalancerSDK(sdkConfig); // Use SDK to find pool info - const pool: PoolModel | undefined = await balancer.poolsProvider.find(poolId); + const pool: PoolWithMethods | undefined = await balancer.pools.find(poolId); if (!pool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); // Sets up local fork granting signer initial balances and token approvals diff --git a/balancer-js/examples/exitExactTokensOut.ts b/balancer-js/examples/exitExactTokensOut.ts index 6bfd9b48f..2ee3decd4 100644 --- a/balancer-js/examples/exitExactTokensOut.ts +++ b/balancer-js/examples/exitExactTokensOut.ts @@ -5,7 +5,7 @@ import { BalancerErrorCode, BalancerSDK, Network, - PoolModel, + PoolWithMethods, } from '../src/index'; import { forkSetup, getBalances } from '../src/test/lib/utils'; import { ADDRESSES } from '../src/test/lib/constants'; @@ -40,7 +40,7 @@ async function exitExactTokensOut() { const balancer = new BalancerSDK(sdkConfig); // Use SDK to find pool info - const pool: PoolModel | undefined = await balancer.poolsProvider.find(poolId); + const pool: PoolWithMethods | undefined = await balancer.pools.find(poolId); if (!pool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); const tokensOut = [ @@ -87,7 +87,11 @@ async function exitExactTokensOut() { console.log('Balances before exit: ', tokenBalancesBefore); console.log('Balances after exit: ', tokenBalancesAfter); console.log('Max BPT input: ', [maxBPTIn.toString()]); - console.log('Actual BPT input: ', [BigNumber.from(tokenBalancesBefore[0]).sub(BigNumber.from(tokenBalancesAfter[0])).toString()]); + console.log('Actual BPT input: ', [ + BigNumber.from(tokenBalancesBefore[0]) + .sub(BigNumber.from(tokenBalancesAfter[0])) + .toString(), + ]); } // yarn examples:run ./examples/exitExactTokensOut.ts diff --git a/balancer-js/examples/join.ts b/balancer-js/examples/join.ts index 40a7e699c..76d7afa6f 100644 --- a/balancer-js/examples/join.ts +++ b/balancer-js/examples/join.ts @@ -5,7 +5,7 @@ import { BalancerErrorCode, BalancerSDK, Network, - PoolModel, + PoolWithMethods, } from '../src/index'; import { forkSetup, getBalances } from '../src/test/lib/utils'; import { ADDRESSES } from '../src/test/lib/constants'; @@ -56,7 +56,7 @@ async function join() { ); // Use SDK to find pool info - const pool: PoolModel | undefined = await balancer.poolsProvider.find(poolId); + const pool: PoolWithMethods | undefined = await balancer.pools.find(poolId); if (!pool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); // Checking balances to confirm success diff --git a/balancer-js/examples/priceImpact.ts b/balancer-js/examples/priceImpact.ts index fad23cdbd..7ae1d9cf8 100644 --- a/balancer-js/examples/priceImpact.ts +++ b/balancer-js/examples/priceImpact.ts @@ -1,6 +1,10 @@ -import { JsonRpcProvider } from '@ethersproject/providers'; import dotenv from 'dotenv'; -import { BalancerSDK, Network, PoolModel, BalancerErrorCode, BalancerError } from '../src/index'; +import { + BalancerSDK, + Network, + BalancerErrorCode, + BalancerError, +} from '../src/index'; dotenv.config(); @@ -11,14 +15,12 @@ Example showing how to use SDK to get price impact for a join or exit operation. async function getPriceImpact() { const network = Network.MAINNET; const rpcUrl = 'http://127.0.0.1:8545'; - const provider = new JsonRpcProvider(rpcUrl, network); const WBTCWETHId = '0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e'; // 50/50 WBTC/WETH Pool const staBal3Id = '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063'; - const sdkConfig = { network, rpcUrl, @@ -26,23 +28,24 @@ async function getPriceImpact() { const balancer = new BalancerSDK(sdkConfig); // Use SDK to find pool info - const pool: PoolModel | undefined = await balancer.poolsProvider.find(WBTCWETHId); + const pool = await balancer.pools.find(WBTCWETHId); if (!pool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); - + const priceImpactWBTCWETH = await pool.calcPriceImpact( ['100000000000000000000', '100000000'], '99430576622436571714692' ); - console.log(priceImpactWBTCWETH); + console.log(priceImpactWBTCWETH); - const pool2: PoolModel | undefined = await balancer.poolsProvider.find(staBal3Id); + const pool2 = await balancer.pools.find(staBal3Id); + console.log(pool2); if (!pool2) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); const priceImpactStaBal3 = await pool2.calcPriceImpact( ['100000000000000000000', '100000000', '190000000'], '376972471880969684010' ); - console.log(priceImpactStaBal3); + console.log(priceImpactStaBal3); } // yarn examples:run ./examples/priceImpact.ts From 3a544440684de3caa174c201bd5b1940acdd3767 Mon Sep 17 00:00:00 2001 From: bronco Date: Wed, 31 Aug 2022 18:06:42 +0200 Subject: [PATCH 45/65] Update README.md --- balancer-js/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/balancer-js/README.md b/balancer-js/README.md index a51e02b69..9be270b06 100644 --- a/balancer-js/README.md +++ b/balancer-js/README.md @@ -276,7 +276,7 @@ Exposes Join functionality allowing user to join pools. ```js const balancer = new BalancerSDK(sdkConfig); -const pool = await balancer.poolsProvider.find(poolId); +const pool = await balancer.pools.find(poolId); const { to, functionName, attributes, data } = pool.buildJoin(params); ``` @@ -308,7 +308,7 @@ Exposes Exit functionality allowing user to exit pools. ```js const balancer = new BalancerSDK(sdkConfig); -const pool = await balancer.poolsProvider.find(poolId); +const pool = await balancer.pools.find(poolId); const { to, functionName, attributes, data } = pool.buildExitExactBPTIn(params); ``` From aca5c78e2622c76fa0cde06661dbda806cd90491 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 1 Sep 2022 09:44:31 +0100 Subject: [PATCH 46/65] Update Readme and examples. --- balancer-js/README.md | 21 +++++++++++++++++---- balancer-js/examples/spotPrice.ts | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/balancer-js/README.md b/balancer-js/README.md index f582fc8c3..9a7035f07 100644 --- a/balancer-js/README.md +++ b/balancer-js/README.md @@ -218,17 +218,30 @@ swaps.querySimpleFlashSwap(batchSwap: { [Example](./examples/querySimpleFlashSwap.ts) -## Pricing Module +## Pricing -Exposes Spot Price functionality allowing user to query spot price for token pair. +Spot Price functionality allowing user to query spot price for token pair. + +### calcSpotPrice + +Find Spot Price for pair in specific pool. ```js -const pricing = new Pricing(sdkConfig); +const balancer = new BalancerSDK(sdkConfig); +const pool = await balancer.poolsProvider.find(poolId); +const spotPrice = await balDaiPool.calcSpotPrice( + ADDRESSES[network].DAI.address, + ADDRESSES[network].BAL.address, + ); ``` ### #getSpotPrice -Calculates Spot Price for a token pair - finds most liquid path and uses this as reference SP. +Find Spot Price for a token pair - finds most liquid path and uses this as reference SP. + +```js +const pricing = new Pricing(sdkConfig); +``` @param { string } tokenIn Token in address. @param { string } tokenOut Token out address. diff --git a/balancer-js/examples/spotPrice.ts b/balancer-js/examples/spotPrice.ts index 02f80306f..f54eb2282 100644 --- a/balancer-js/examples/spotPrice.ts +++ b/balancer-js/examples/spotPrice.ts @@ -22,7 +22,7 @@ async function getSpotPricePool() { const daiWethPool: PoolModel | undefined = await balancer.poolsProvider.find(wethDaiPoolId); if (!daiWethPool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); - const spotPriceEthDai = await balancer.pricing.getSpotPrice( + const spotPriceEthDai = await daiWethPool.calcSpotPrice( ADDRESSES[network].DAI.address, ADDRESSES[network].WETH.address, ); From 2437e76700d8e6850340afc87d72de0d31186da8 Mon Sep 17 00:00:00 2001 From: bronco Date: Thu, 1 Sep 2022 12:13:47 +0200 Subject: [PATCH 47/65] Update package.json --- balancer-js/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/balancer-js/package.json b/balancer-js/package.json index 142148ccb..0a1fb1d78 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -83,8 +83,7 @@ "ts-node": "^10.4.0", "tsconfig-paths": "^3.12.0", "typechain": "^5.1.1", - "typescript": "^4.0.2", - "mockdate": "^3.0.5" + "typescript": "^4.0.2" }, "dependencies": { "@balancer-labs/sor": "^4.0.1-beta.3", From 43adc3406d050fa15bbbec995710c96d2b0b4a26 Mon Sep 17 00:00:00 2001 From: bronco Date: Thu, 1 Sep 2022 14:25:50 +0200 Subject: [PATCH 48/65] post merge fixes --- balancer-js/README.md | 4 +- balancer-js/examples/spotPrice.ts | 24 +++++++----- balancer-js/src/modules/data/pool/subgraph.ts | 1 + balancer-js/src/modules/pools/index.ts | 2 + .../concerns/linear/spotPrice.spec.ts | 37 ++++--------------- .../concerns/metaStable/spotPrice.spec.ts | 34 ++++------------- .../concerns/stable/spotPrice.spec.ts | 34 ++++------------- .../concerns/stablePhantom/spotPrice.spec.ts | 34 ++++------------- .../concerns/weighted/spotPrice.spec.ts | 34 ++++------------- 9 files changed, 56 insertions(+), 148 deletions(-) diff --git a/balancer-js/README.md b/balancer-js/README.md index 343ee2122..bf2b953e8 100644 --- a/balancer-js/README.md +++ b/balancer-js/README.md @@ -251,8 +251,8 @@ Find Spot Price for pair in specific pool. ```js const balancer = new BalancerSDK(sdkConfig); -const pool = await balancer.poolsProvider.find(poolId); -const spotPrice = await balDaiPool.calcSpotPrice( +const pool = await balancer.pools.find(poolId); +const spotPrice = await pool.calcSpotPrice( ADDRESSES[network].DAI.address, ADDRESSES[network].BAL.address, ); diff --git a/balancer-js/examples/spotPrice.ts b/balancer-js/examples/spotPrice.ts index f54eb2282..3aafa966a 100644 --- a/balancer-js/examples/spotPrice.ts +++ b/balancer-js/examples/spotPrice.ts @@ -1,5 +1,11 @@ import dotenv from 'dotenv'; -import { BalancerSDK, Network, BalancerSdkConfig, PoolModel, BalancerError, BalancerErrorCode } from '../src/index'; +import { + BalancerSDK, + Network, + BalancerSdkConfig, + BalancerError, + BalancerErrorCode, +} from '../src/index'; import { ADDRESSES } from '../src/test/lib/constants'; dotenv.config(); @@ -16,31 +22,31 @@ const balancer = new BalancerSDK(config); Uses SDK to find spot price for pair in specific pool. */ async function getSpotPricePool() { - const wethDaiPoolId = '0x0b09dea16768f0799065c475be02919503cb2a3500020000000000000000001a'; - const daiWethPool: PoolModel | undefined = await balancer.poolsProvider.find(wethDaiPoolId); - if (!daiWethPool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); + const daiWethPool = await balancer.pools.find(wethDaiPoolId); + if (!daiWethPool) + throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); const spotPriceEthDai = await daiWethPool.calcSpotPrice( ADDRESSES[network].DAI.address, - ADDRESSES[network].WETH.address, + ADDRESSES[network].WETH.address ); console.log(spotPriceEthDai.toString()); const balDaiPoolId = '0x4626d81b3a1711beb79f4cecff2413886d461677000200000000000000000011'; - const balDaiPool: PoolModel | undefined = await balancer.poolsProvider.find(balDaiPoolId); + + const balDaiPool = await balancer.pools.find(balDaiPoolId); if (!balDaiPool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); const spotPriceBalDai = await balDaiPool.calcSpotPrice( ADDRESSES[network].DAI.address, - ADDRESSES[network].BAL.address, + ADDRESSES[network].BAL.address ); console.log(spotPriceBalDai.toString()); } - /* Uses SDK to find most liquid path for a pair and calculate spot price. */ @@ -48,7 +54,7 @@ async function getSpotPriceMostLiquid() { // This will fetch pools information using data provider const spotPriceEthDai = await balancer.pricing.getSpotPrice( ADDRESSES[network].DAI.address, - ADDRESSES[network].WETH.address, + ADDRESSES[network].WETH.address ); console.log(spotPriceEthDai.toString()); diff --git a/balancer-js/src/modules/data/pool/subgraph.ts b/balancer-js/src/modules/data/pool/subgraph.ts index 6d427388a..6d42e178a 100644 --- a/balancer-js/src/modules/data/pool/subgraph.ts +++ b/balancer-js/src/modules/data/pool/subgraph.ts @@ -112,6 +112,7 @@ export class PoolsSubgraphRepository // volumeSnapshot: subgraphPool.volumeSnapshot, // feesSnapshot: subgraphPool.???, // Approximated last 24h fees // boost: subgraphPool.boost, + totalWeight: subgraphPool.totalWeight || '1', }; } } diff --git a/balancer-js/src/modules/pools/index.ts b/balancer-js/src/modules/pools/index.ts index 68c370bd4..2ce77f806 100644 --- a/balancer-js/src/modules/pools/index.ts +++ b/balancer-js/src/modules/pools/index.ts @@ -122,6 +122,8 @@ export class Pools implements Findable { // either we refetch or it needs a type transformation from SDK internal to SOR (subgraph) // spotPrice: async (tokenIn: string, tokenOut: string) => // methods.spotPriceCalculator.calcPoolSpotPrice(tokenIn, tokenOut, data), + calcSpotPrice: (tokenIn: string, tokenOut: string) => + methods.spotPriceCalculator.calcPoolSpotPrice(tokenIn, tokenOut, pool), }; } diff --git a/balancer-js/src/modules/pools/pool-types/concerns/linear/spotPrice.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/linear/spotPrice.spec.ts index a9ef47984..b1cecedfe 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/linear/spotPrice.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/linear/spotPrice.spec.ts @@ -1,46 +1,25 @@ -import dotenv from 'dotenv'; import { expect } from 'chai'; import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { StaticPoolRepository } from '@/modules/data'; -import { PoolsProvider } from '@/modules/pools/provider'; -import { PoolModel, Pool } from '@/types'; -import { Network } from '@/.'; -import { setupPool } from '@/test/lib/utils'; +import { Network, Pool } from '@/.'; import { LinearPoolSpotPrice } from './spotPrice.concern'; import { ADDRESSES } from '@/test/lib/constants'; -dotenv.config(); - -const network = Network.MAINNET; -const rpcUrl = 'http://127.0.0.1:8545'; - const spotPriceCalc = new LinearPoolSpotPrice(); -const linearId = +const network = Network.MAINNET; +const poolId = '0x9210f1204b5a24742eba12f710636d76240df3d00000000000000000000000fc'; -describe('Linear pool spot price', () => { - let pool: PoolModel | undefined; - - // Setup chain - before(async function () { - const sdkConfig = { - network, - rpcUrl, - }; - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - pool = await setupPool(poolsProvider, linearId); - }); +const pool = pools_14717479.find( + (pool) => pool.id == poolId +) as unknown as Pool; +describe('Linear pool spot price', () => { context('calcPoolSpotPrice', () => { it('should calculate spot price for pair', () => { const spotPrice = spotPriceCalc.calcPoolSpotPrice( ADDRESSES[network].USDC.address, ADDRESSES[network].bbausdc.address, - pool as PoolModel + pool ); expect(spotPrice).to.eq('1.008078200925769181'); }); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/spotPrice.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/spotPrice.spec.ts index 490651ddc..3595663bb 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/spotPrice.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/spotPrice.spec.ts @@ -1,46 +1,26 @@ -import dotenv from 'dotenv'; import { expect } from 'chai'; import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { StaticPoolRepository } from '@/modules/data'; -import { PoolsProvider } from '@/modules/pools/provider'; -import { PoolModel, Pool } from '@/types'; -import { Network } from '@/.'; -import { setupPool } from '@/test/lib/utils'; +import { Network, Pool } from '@/.'; import { MetaStablePoolSpotPrice } from './spotPrice.concern'; import { ADDRESSES } from '@/test/lib/constants'; -dotenv.config(); - const network = Network.MAINNET; -const rpcUrl = 'http://127.0.0.1:8545'; const spotPriceCalc = new MetaStablePoolSpotPrice(); -const metaStableId = +const poolId = '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080'; -describe('metaStable pool spot price', () => { - let pool: PoolModel | undefined; - - // Setup chain - before(async function () { - const sdkConfig = { - network, - rpcUrl, - }; - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - pool = await setupPool(poolsProvider, metaStableId); - }); +const pool = pools_14717479.find( + (pool) => pool.id == poolId +) as unknown as Pool; +describe('metaStable pool spot price', () => { context('calcPoolSpotPrice', () => { it('should calculate spot price for pair', () => { const spotPrice = spotPriceCalc.calcPoolSpotPrice( ADDRESSES[network].WETH.address, ADDRESSES[network].wSTETH.address, - pool as PoolModel + pool ); expect(spotPrice).to.eq('1.070497605163895290828158545877174735'); }); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/spotPrice.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/spotPrice.spec.ts index 6dfb9a6dd..aaaa65e16 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/spotPrice.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/spotPrice.spec.ts @@ -1,46 +1,26 @@ -import dotenv from 'dotenv'; import { expect } from 'chai'; import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { StaticPoolRepository } from '@/modules/data'; -import { PoolsProvider } from '@/modules/pools/provider'; -import { PoolModel, Pool } from '@/types'; -import { Network } from '@/.'; -import { setupPool } from '@/test/lib/utils'; +import { Network, Pool } from '@/.'; import { StablePoolSpotPrice } from './spotPrice.concern'; import { ADDRESSES } from '@/test/lib/constants'; -dotenv.config(); - const network = Network.MAINNET; -const rpcUrl = 'http://127.0.0.1:8545'; const spotPriceCalc = new StablePoolSpotPrice(); -const stable_pool_id = +const poolId = '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063'; -describe('stable pool spot price', () => { - let pool: PoolModel | undefined; - - // Setup chain - before(async function () { - const sdkConfig = { - network, - rpcUrl, - }; - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - pool = await setupPool(poolsProvider, stable_pool_id); - }); +const pool = pools_14717479.find( + (pool) => pool.id == poolId +) as unknown as Pool; +describe('stable pool spot price', () => { context('calcPoolSpotPrice', () => { it('should calculate spot price for pair', () => { const spotPrice = spotPriceCalc.calcPoolSpotPrice( ADDRESSES[network].DAI.address, ADDRESSES[network].USDC.address, - pool as PoolModel + pool ); expect(spotPrice).to.eq('1.000051911328148725'); }); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/spotPrice.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/spotPrice.spec.ts index 9de3d9b6a..6b7638ee7 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/spotPrice.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stablePhantom/spotPrice.spec.ts @@ -1,46 +1,26 @@ -import dotenv from 'dotenv'; import { expect } from 'chai'; import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { StaticPoolRepository } from '@/modules/data'; -import { PoolsProvider } from '@/modules/pools/provider'; -import { PoolModel, Pool } from '@/types'; -import { Network } from '@/.'; -import { setupPool } from '@/test/lib/utils'; +import { Network, Pool } from '@/.'; import { PhantomStablePoolSpotPrice } from './spotPrice.concern'; import { ADDRESSES } from '@/test/lib/constants'; -dotenv.config(); - const network = Network.MAINNET; -const rpcUrl = 'http://127.0.0.1:8545'; const spotPriceCalc = new PhantomStablePoolSpotPrice(); -const phantomStableId = +const poolId = '0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe'; -describe('phantomStable pool spot price', () => { - let pool: PoolModel | undefined; - - // Setup chain - before(async function () { - const sdkConfig = { - network, - rpcUrl, - }; - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - pool = await setupPool(poolsProvider, phantomStableId); - }); +const pool = pools_14717479.find( + (pool) => pool.id == poolId +) as unknown as Pool; +describe('phantomStable pool spot price', () => { context('calcPoolSpotPrice', () => { it('should calculate spot price for pair', () => { const spotPrice = spotPriceCalc.calcPoolSpotPrice( ADDRESSES[network].bbausd.address, ADDRESSES[network].bbausdc.address, - pool as PoolModel + pool ); expect(spotPrice).to.eq('0.997873677414938406552928560423740375'); }); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.spec.ts index 64c7ca4ce..c7a99bae3 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/weighted/spotPrice.spec.ts @@ -1,46 +1,26 @@ -import dotenv from 'dotenv'; import { expect } from 'chai'; import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { StaticPoolRepository } from '@/modules/data'; -import { PoolsProvider } from '@/modules/pools/provider'; -import { PoolModel, Pool } from '@/types'; -import { Network } from '@/.'; -import { setupPool } from '@/test/lib/utils'; +import { Network, Pool } from '@/.'; import { WeightedPoolSpotPrice } from './spotPrice.concern'; import { ADDRESSES } from '@/test/lib/constants'; -dotenv.config(); - const network = Network.MAINNET; -const rpcUrl = 'http://127.0.0.1:8545'; const spotPriceCalc = new WeightedPoolSpotPrice(); -const weth_bal_pool_id = +const poolId = '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014'; -describe('weighted pool spot price', () => { - let pool: PoolModel | undefined; - - // Setup chain - before(async function () { - const sdkConfig = { - network, - rpcUrl, - }; - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - pool = await setupPool(poolsProvider, weth_bal_pool_id); - }); +const pool = pools_14717479.find( + (pool) => pool.id == poolId +) as unknown as Pool; +describe('weighted pool spot price', () => { context('calcPoolSpotPrice', () => { it('should calculate spot price for pair', () => { const spotPrice = spotPriceCalc.calcPoolSpotPrice( ADDRESSES[network].WETH.address, ADDRESSES[network].BAL.address, - pool as PoolModel + pool ); expect(spotPrice).to.eq('0.004981212133448337'); From f0543e4dddca70003a315dbc81127661188a5656 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 1 Sep 2022 10:00:59 -0300 Subject: [PATCH 49/65] Move exit with ETH on stable pools from integration to unit test file --- .../exit.concern.integration.spec.ts | 24 ----- .../concerns/metaStable/exit.concern.spec.ts | 94 ++++++++---------- .../concerns/stable/exit.concern.spec.ts | 98 +++++++++---------- 3 files changed, 86 insertions(+), 130 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts index 4cd49eeb6..2c0af514b 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts @@ -287,28 +287,4 @@ describe('exit execution', async () => { }); }); }); - - context('exit with ETH - conflicting inputs', async () => { - it('should fail', async () => { - let errorMessage = ''; - try { - const bptIn = parseFixed('10', 18).toString(); - await testFlow( - pool.buildExitExactBPTIn( - signerAddress, - bptIn, - slippage, - false, - AddressZero - ), - pool.tokensList - ); - } catch (error) { - errorMessage = (error as Error).message; - } - expect(errorMessage).to.eql( - 'shouldUnwrapNativeAsset and singleTokenMaxOut should not have conflicting values' - ); - }); - }); }).timeout(20000); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.spec.ts index 846a186ba..015beb7d6 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.spec.ts @@ -1,77 +1,67 @@ import { expect } from 'chai'; +import { parseFixed } from '@ethersproject/bignumber'; +import { AddressZero } from '@ethersproject/constants'; + import { BalancerError, BalancerErrorCode, BalancerSdkConfig, Network, Pool, + PoolModel, StaticPoolRepository, } from '@/.'; import { PoolsProvider } from '@/modules/pools/provider'; - import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { parseFixed } from '@/lib/utils/math'; +import { MetaStablePoolExit } from './exit.concern'; +import { networkAddresses } from '@/lib/constants/config'; +const metaStablePoolExit = new MetaStablePoolExit(); const stETH_stable_pool_id = '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080'; // Balancer stETH Stable Pool -const exiter = '0x35f5a330FD2F8e521ebd259FA272bA8069590741'; -const slippage = '100'; // 100 bps +const network = Network.MAINNET; +const sdkConfig: BalancerSdkConfig = { + network, + rpcUrl: ``, +}; +const pools = new PoolsProvider( + sdkConfig, + new StaticPoolRepository(pools_14717479 as Pool[]) +); +const { tokens } = networkAddresses(network); describe('exit module', async () => { - const sdkConfig: BalancerSdkConfig = { - network: Network.MAINNET, - rpcUrl: ``, - }; - const pools = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - const pool = await pools.find(stETH_stable_pool_id); - if (!pool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); - - describe('buildExitExactBPTIn', () => { - const bptIn = parseFixed('100', 18).toString(); // bpt in EVM amounts - context('proportional amounts out', () => { - it('should return encoded params', async () => { - const { data } = pool.buildExitExactBPTIn(exiter, bptIn, slippage); + let pool: PoolModel; - expect(data).to.equal( - '0x8bdb391306df3b2bbb68adc8b0e302443692037ed9f91b4200000000000000000000006300000000000000000000000035f5a330fd2f8e521ebd259fa272ba806959074100000000000000000000000035f5a330fd2f8e521ebd259fa272ba80695907410000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000001cd70c57f08a1d48f0000000000000000000000000000000000000000000000000000000001ef82990000000000000000000000000000000000000000000000000000000002047879000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000056bc75e2d63100000' - ); - }); + before(async function () { + await pools.find(stETH_stable_pool_id).then((p) => { + if (!p) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); + pool = p; }); - context('single token max out', () => { - it('should return encoded params', async () => { - const { data } = pool.buildExitExactBPTIn( - exiter, - bptIn, - slippage, - pool.tokensList[0] - ); + }); - expect(data).to.equal( - '0x8bdb391306df3b2bbb68adc8b0e302443692037ed9f91b4200000000000000000000006300000000000000000000000035f5a330fd2f8e521ebd259fa272ba806959074100000000000000000000000035f5a330fd2f8e521ebd259fa272ba80695907410000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000003a432d1bf1a20356300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000000000000' + describe('buildExitExactBPTIn', () => { + context('exit with ETH', () => { + it('should fail due to conflicting inputs', () => { + let errorMessage = ''; + try { + metaStablePoolExit.buildExitExactBPTIn({ + exiter: '0x35f5a330FD2F8e521ebd259FA272bA8069590741', + pool, + bptIn: parseFixed('10', 18).toString(), + slippage: '100', // 100 bps + shouldUnwrapNativeAsset: false, + wrappedNativeAsset: tokens.wrappedNativeAsset, + singleTokenMaxOut: AddressZero, + }); + } catch (error) { + errorMessage = (error as Error).message; + } + expect(errorMessage).to.eql( + 'shouldUnwrapNativeAsset and singleTokenMaxOut should not have conflicting values' ); }); }); }); - describe('buildExitExactTokensOut', () => { - const tokensOut = pool.tokensList; - const amountsOut = pool.tokens.map((t) => - parseFixed(t.balance, t.decimals).div('100000').toString() - ); // EVM amounts) - it('should return encoded params', async () => { - const { data } = await pool.buildExitExactTokensOut( - exiter, - tokensOut, - amountsOut, - slippage - ); - - expect(data).to.equal( - '0x8bdb391306df3b2bbb68adc8b0e302443692037ed9f91b4200000000000000000000006300000000000000000000000035f5a330fd2f8e521ebd259fa272ba806959074100000000000000000000000035f5a330fd2f8e521ebd259fa272ba80695907410000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000022253457fb06e3bf280000000000000000000000000000000000000000000000000000000024aa9879000000000000000000000000000000000000000000000000000000002637a7d900000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000022253457fb06e3bf280000000000000000000000000000000000000000000000000000000024aa9879000000000000000000000000000000000000000000000000000000002637a7d9' - ); - }); - }); }); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.spec.ts index 9ac6a9332..5953ab8b6 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.spec.ts @@ -1,77 +1,67 @@ import { expect } from 'chai'; +import { parseFixed } from '@ethersproject/bignumber'; +import { AddressZero } from '@ethersproject/constants'; import { BalancerError, BalancerErrorCode, BalancerSdkConfig, Network, Pool, + PoolModel, StaticPoolRepository, } from '@/.'; import { PoolsProvider } from '@/modules/pools/provider'; import pools_14717479 from '@/test/lib/pools_14717479.json'; -import { parseFixed } from '@/lib/utils/math'; -const dai_usdc_usdt_pool_id = - '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063'; // Balancer USD Stable Pool - staBAL3 +import { StablePoolExit } from './exit.concern'; +import { networkAddresses } from '@/lib/constants/config'; -const exiter = '0x35f5a330FD2F8e521ebd259FA272bA8069590741'; -const slippage = '100'; // 100 bps +const stablePoolExit = new StablePoolExit(); +const network = Network.MAINNET; +const sdkConfig: BalancerSdkConfig = { + network, + rpcUrl: ``, +}; +const pools = new PoolsProvider( + sdkConfig, + new StaticPoolRepository(pools_14717479 as Pool[]) +); +const { tokens } = networkAddresses(network); describe('exit module', async () => { - const sdkConfig: BalancerSdkConfig = { - network: Network.MAINNET, - rpcUrl: ``, - }; - const pools = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - const pool = await pools.find(dai_usdc_usdt_pool_id); - if (!pool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); + let pool: PoolModel; - describe('buildExitExactBPTIn', () => { - const bptIn = parseFixed('100', 18).toString(); // bpt in EVM amounts - context('proportional amounts out', () => { - it('should return encoded params', async () => { - const { data } = pool.buildExitExactBPTIn(exiter, bptIn, slippage); - - expect(data).to.equal( - '0x8bdb391306df3b2bbb68adc8b0e302443692037ed9f91b4200000000000000000000006300000000000000000000000035f5a330fd2f8e521ebd259fa272ba806959074100000000000000000000000035f5a330fd2f8e521ebd259fa272ba80695907410000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000001cd70c57f08a1d48f0000000000000000000000000000000000000000000000000000000001ef82990000000000000000000000000000000000000000000000000000000002047879000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000056bc75e2d63100000' - ); + describe('buildExitExactBPTIn', async () => { + context('exit with ETH', async () => { + before(async function () { + // Note that currently there is no stable pool with WETH as underlying token, but for the purposes of this unit test any stable pool can be used + const stabal3 = + '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063'; // Balancer USD Stable Pool - staBAL3 + await pools.find(stabal3).then((p) => { + if (!p) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); + pool = p; + }); }); - }); - context('single token max out', () => { - it('should return encoded params', async () => { - const { data } = pool.buildExitExactBPTIn( - exiter, - bptIn, - slippage, - pool.tokensList[0] - ); - - expect(data).to.equal( - '0x8bdb391306df3b2bbb68adc8b0e302443692037ed9f91b4200000000000000000000006300000000000000000000000035f5a330fd2f8e521ebd259fa272ba806959074100000000000000000000000035f5a330fd2f8e521ebd259fa272ba80695907410000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000003a432d1bf1a20356300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000000000000' + it('should fail due to conflicting inputs', () => { + let errorMessage = ''; + try { + stablePoolExit.buildExitExactBPTIn({ + exiter: '0x35f5a330FD2F8e521ebd259FA272bA8069590741', + pool, + bptIn: parseFixed('10', 18).toString(), + slippage: '100', // 100 bps + shouldUnwrapNativeAsset: false, + wrappedNativeAsset: tokens.wrappedNativeAsset, + singleTokenMaxOut: AddressZero, + }); + } catch (error) { + errorMessage = (error as Error).message; + } + expect(errorMessage).to.eql( + 'shouldUnwrapNativeAsset and singleTokenMaxOut should not have conflicting values' ); }); }); }); - describe('buildExitExactTokensOut', () => { - const tokensOut = pool.tokensList; - const amountsOut = pool.tokens.map((t) => - parseFixed(t.balance, t.decimals).div('100000').toString() - ); // EVM amounts) - it('should return encoded params', async () => { - const { data } = await pool.buildExitExactTokensOut( - exiter, - tokensOut, - amountsOut, - slippage - ); - - expect(data).to.equal( - '0x8bdb391306df3b2bbb68adc8b0e302443692037ed9f91b4200000000000000000000006300000000000000000000000035f5a330fd2f8e521ebd259fa272ba806959074100000000000000000000000035f5a330fd2f8e521ebd259fa272ba80695907410000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000022253457fb06e3bf280000000000000000000000000000000000000000000000000000000024aa9879000000000000000000000000000000000000000000000000000000002637a7d900000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000022253457fb06e3bf280000000000000000000000000000000000000000000000000000000024aa9879000000000000000000000000000000000000000000000000000000002637a7d9' - ); - }); - }); }); From 897a6964a073e6af740b178aa921b59c879597fe Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 1 Sep 2022 10:03:28 -0300 Subject: [PATCH 50/65] Remove georgeroman dependency for stable math --- .../concerns/metaStable/exit.concern.ts | 55 +++++++------------ .../concerns/stable/exit.concern.ts | 52 ++++++------------ 2 files changed, 36 insertions(+), 71 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts index 279077602..aae0e6cf4 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts @@ -1,6 +1,5 @@ import { BigNumber, parseFixed } from '@ethersproject/bignumber'; -import OldBigNumber from 'bignumber.js'; -import * as SDK from '@georgeroman/balancer-v2-pools'; +import * as SOR from '@balancer-labs/sor'; import { ExitConcern, ExitExactBPTInParameters, @@ -83,7 +82,6 @@ export class MetaStablePoolExit implements ExitConcern { let minAmountsOut = Array(parsedTokens.length).fill('0'); let userData: string; - let value: BigNumber | undefined; if (singleTokenMaxOut) { // Exit pool with single token using exact bptIn @@ -91,13 +89,13 @@ export class MetaStablePoolExit implements ExitConcern { const singleTokenMaxOutIndex = sortedTokens.indexOf(singleTokenMaxOut); // Calculate amount out given BPT in - const scaledAmountOut = SDK.StableMath._calcTokenOutGivenExactBptIn( - new OldBigNumber(parsedAmp as string), - scaledBalances.map((b) => new OldBigNumber(b)), + const scaledAmountOut = SOR.StableMathBigInt._calcTokenOutGivenExactBptIn( + BigInt(parsedAmp as string), + scaledBalances.map((b) => BigInt(b)), singleTokenMaxOutIndex, - new OldBigNumber(bptIn), - new OldBigNumber(parsedTotalShares), - new OldBigNumber(parsedSwapFee) + BigInt(bptIn), + BigInt(parsedTotalShares), + BigInt(parsedSwapFee) ).toString(); // Reverse scaled amount out based on token price rate @@ -115,19 +113,16 @@ export class MetaStablePoolExit implements ExitConcern { bptIn, singleTokenMaxOutIndex ); - - if (shouldUnwrapNativeAsset) { - value = BigNumber.from(amountOut); - } } else { // Exit pool with all tokens proportinally // Calculate amount out given BPT in - const scaledAmountsOut = SDK.StableMath._calcTokensOutGivenExactBptIn( - scaledBalances.map((b) => new OldBigNumber(b)), - new OldBigNumber(bptIn), - new OldBigNumber(parsedTotalShares) - ).map((amount) => amount.toString()); + const scaledAmountsOut = + SOR.StableMathBigInt._calcTokensOutGivenExactBptIn( + scaledBalances.map((b) => BigInt(b)), + BigInt(bptIn), + BigInt(parsedTotalShares) + ).map((amount) => amount.toString()); // Reverse scaled amounts out based on token price rate const amountsOut = scaledAmountsOut.map((amount, i) => { @@ -147,13 +142,6 @@ export class MetaStablePoolExit implements ExitConcern { }); userData = StablePoolEncoder.exitExactBPTInForTokensOut(bptIn); - - if (shouldUnwrapNativeAsset) { - const amount = amountsOut.find( - (amount, i) => sortedTokens[i] == AddressZero - ); - value = amount ? BigNumber.from(amount) : undefined; - } } const to = balancerVault; @@ -184,7 +172,6 @@ export class MetaStablePoolExit implements ExitConcern { functionName, attributes, data, - value, minAmountsOut, maxBPTIn: bptIn, }; @@ -251,12 +238,12 @@ export class MetaStablePoolExit implements ExitConcern { }); // Calculate expected BPT in given tokens out - const bptIn = SDK.StableMath._calcBptInGivenExactTokensOut( - new OldBigNumber(parsedAmp as string), - scaledBalances.map((b) => new OldBigNumber(b)), - scaledAmounts.map((a) => new OldBigNumber(a)), - new OldBigNumber(parsedTotalShares), - new OldBigNumber(parsedSwapFee) + const bptIn = SOR.StableMathBigInt._calcBptInGivenExactTokensOut( + BigInt(parsedAmp as string), + scaledBalances.map((b) => BigInt(b)), + scaledAmounts.map((a) => BigInt(a)), + BigInt(parsedTotalShares), + BigInt(parsedSwapFee) ).toString(); // Apply slippage tolerance @@ -293,15 +280,11 @@ export class MetaStablePoolExit implements ExitConcern { attributes.exitPoolRequest, ]); - const amount = amountsOut.find((amount, i) => tokensOut[i] == AddressZero); // find native asset (e.g. ETH) amount - const value = amount ? BigNumber.from(amount) : undefined; - return { to, functionName, attributes, data, - value, minAmountsOut: sortedAmounts, maxBPTIn, }; diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts index f50c7256d..ad86b5237 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.ts @@ -1,7 +1,6 @@ import { BigNumber, parseFixed } from '@ethersproject/bignumber'; import { AddressZero } from '@ethersproject/constants'; -import OldBigNumber from 'bignumber.js'; -import * as SDK from '@georgeroman/balancer-v2-pools'; +import * as SOR from '@balancer-labs/sor'; import { ExitConcern, ExitExactBPTInParameters, @@ -70,7 +69,6 @@ export class StablePoolExit implements ExitConcern { let minAmountsOut = Array(parsedTokens.length).fill('0'); let userData: string; - let value: BigNumber | undefined; if (singleTokenMaxOut) { // Exit pool with single token using exact bptIn @@ -78,13 +76,13 @@ export class StablePoolExit implements ExitConcern { const singleTokenMaxOutIndex = parsedTokens.indexOf(singleTokenMaxOut); // Calculate amount out given BPT in - const amountOut = SDK.StableMath._calcTokenOutGivenExactBptIn( - new OldBigNumber(parsedAmp as string), - sortedBalances.map((b) => new OldBigNumber(b)), + const amountOut = SOR.StableMathBigInt._calcTokenOutGivenExactBptIn( + BigInt(parsedAmp as string), + sortedBalances.map((b) => BigInt(b)), singleTokenMaxOutIndex, - new OldBigNumber(bptIn), - new OldBigNumber(parsedTotalShares), - new OldBigNumber(parsedSwapFee) + BigInt(bptIn), + BigInt(parsedTotalShares), + BigInt(parsedSwapFee) ).toString(); // Apply slippage tolerance @@ -97,18 +95,14 @@ export class StablePoolExit implements ExitConcern { bptIn, singleTokenMaxOutIndex ); - - if (shouldUnwrapNativeAsset) { - value = BigNumber.from(amountOut); - } } else { // Exit pool with all tokens proportinally // Calculate amount out given BPT in - const amountsOut = SDK.StableMath._calcTokensOutGivenExactBptIn( - sortedBalances.map((b) => new OldBigNumber(b)), - new OldBigNumber(bptIn), - new OldBigNumber(parsedTotalShares) + const amountsOut = SOR.StableMathBigInt._calcTokensOutGivenExactBptIn( + sortedBalances.map((b) => BigInt(b)), + BigInt(bptIn), + BigInt(parsedTotalShares) ).map((amount) => amount.toString()); // Apply slippage tolerance @@ -121,13 +115,6 @@ export class StablePoolExit implements ExitConcern { }); userData = StablePoolEncoder.exitExactBPTInForTokensOut(bptIn); - - if (shouldUnwrapNativeAsset) { - const amount = amountsOut.find( - (amount, i) => sortedTokens[i] == AddressZero - ); - value = amount ? BigNumber.from(amount) : undefined; - } } const to = balancerVault; @@ -158,7 +145,6 @@ export class StablePoolExit implements ExitConcern { functionName, attributes, data, - value, minAmountsOut, maxBPTIn: bptIn, }; @@ -205,12 +191,12 @@ export class StablePoolExit implements ExitConcern { ) as [string[], string[]]; // Calculate expected BPT in given tokens out - const bptIn = SDK.StableMath._calcBptInGivenExactTokensOut( - new OldBigNumber(parsedAmp as string), - sortedBalances.map((b) => new OldBigNumber(b)), - sortedAmounts.map((a) => new OldBigNumber(a)), - new OldBigNumber(parsedTotalShares), - new OldBigNumber(parsedSwapFee) + const bptIn = SOR.StableMathBigInt._calcBptInGivenExactTokensOut( + BigInt(parsedAmp as string), + sortedBalances.map((b) => BigInt(b)), + sortedAmounts.map((a) => BigInt(a)), + BigInt(parsedTotalShares), + BigInt(parsedSwapFee) ).toString(); // Apply slippage tolerance @@ -247,15 +233,11 @@ export class StablePoolExit implements ExitConcern { attributes.exitPoolRequest, ]); - const amount = amountsOut.find((amount, i) => tokensOut[i] == AddressZero); // find native asset (e.g. ETH) amount - const value = amount ? BigNumber.from(amount) : undefined; - return { to, functionName, attributes, data, - value, minAmountsOut: sortedAmounts, maxBPTIn, }; From 0c847cc10801719e0ee2d82426ec426689100795 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 1 Sep 2022 10:03:55 -0300 Subject: [PATCH 51/65] Fix rounding issue for metaStable minAmountsOut --- .../pool-types/concerns/metaStable/exit.concern.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts index aae0e6cf4..c10860dfd 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.ts @@ -257,6 +257,12 @@ export class MetaStablePoolExit implements ExitConcern { maxBPTIn ); + // This is a hack to get around rounding issues for scaled amounts on MetaStable pools + // TODO: do this more elegantly + const minAmountsOut = sortedAmounts.map((a, i) => + a === scaledAmounts[i] ? a : BigNumber.from(a).sub(1).toString() + ); + const to = balancerVault; const functionName = 'exitPool'; const attributes: ExitPool = { @@ -265,7 +271,7 @@ export class MetaStablePoolExit implements ExitConcern { recipient: exiter, exitPoolRequest: { assets: sortedTokens, - minAmountsOut: sortedAmounts, + minAmountsOut, userData, toInternalBalance: false, }, @@ -285,7 +291,7 @@ export class MetaStablePoolExit implements ExitConcern { functionName, attributes, data, - minAmountsOut: sortedAmounts, + minAmountsOut, maxBPTIn, }; }; From be240eb8c8113a8c6147444c2bf30b98f37e5834 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 1 Sep 2022 10:04:37 -0300 Subject: [PATCH 52/65] Refactor exit stables integration tests --- .../exit.concern.integration.spec.ts | 196 ++++++++++------- .../stable/exit.concern.integration.spec.ts | 203 ++++++++++-------- .../pools/pool-types/concerns/types.ts | 4 +- 3 files changed, 231 insertions(+), 172 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts index 2c0af514b..f938e2c14 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts @@ -18,24 +18,26 @@ import { PoolsProvider } from '@/modules/pools/provider'; import pools_14717479 from '@/test/lib/pools_14717479.json'; import { ExitPoolAttributes } from '../types'; import { AddressZero } from '@ethersproject/constants'; +import { networkAddresses } from '@/lib/constants/config'; dotenv.config(); const { ALCHEMY_URL: jsonRpcUrl } = process.env; const { ethers } = hardhat; -let balancer: BalancerSDK; const rpcUrl = 'http://127.0.0.1:8545'; const network = Network.MAINNET; const provider = new ethers.providers.JsonRpcProvider(rpcUrl, network); const signer = provider.getSigner(); +const { tokens } = networkAddresses(network); + // Slots used to set the account balance for each token through hardhat_setStorageAt // Info fetched using npm package slot20 const BPT_SLOT = 0; -const initialBalance = '100000'; -const amountsOutDiv = '100000000'; -const slippage = '1000'; +const initialBalance = '10000000'; +const amountsOutDiv = (1e7).toString(); // FIXME: depending on this number, exitExactTokenOut (single token) throws Errors.STABLE_INVARIANT_DIDNT_CONVERGE +const slippage = '100'; const poolId = '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080'; // Balancer stETH Stable Pool @@ -66,17 +68,9 @@ describe('exit execution', async () => { sdkConfig, new StaticPoolRepository(pools_14717479 as Pool[]) ); - balancer = new BalancerSDK( - sdkConfig, - undefined, - undefined, - undefined, - poolsProvider - ); pool = await setupPool(poolsProvider, poolId); tokensOut = pool.tokens; await forkSetup( - balancer, signer, [pool.address], [BPT_SLOT], @@ -120,95 +114,134 @@ describe('exit execution', async () => { transactionReceipt.effectiveGasPrice ); tokensBalanceAfter = tokensBalanceAfter.map((balance, i) => { - if ( - pool.tokensList[i] === - balancer.networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase() - ) { + if (pool.tokensList[i] === tokens.wrappedNativeAsset.toLowerCase()) { return balance.add(transactionCost); } return balance; }); } }; - context('exitExactBPTIn', async () => { - before(async function () { - this.timeout(20000); - const bptIn = parseFixed('10', 18).toString(); - await testFlow( - pool.buildExitExactBPTIn(signerAddress, bptIn, slippage), - pool.tokensList - ); - }); + context('proportional amounts out', async () => { + before(async function () { + this.timeout(20000); + const bptIn = parseFixed('10', 18).toString(); + await testFlow( + pool.buildExitExactBPTIn(signerAddress, bptIn, slippage), + pool.tokensList + ); + }); - it('should work', async () => { - expect(transactionReceipt.status).to.eql(1); - }); + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); + }); - it('tokens balance should increase by at least minAmountsOut', async () => { - for (let i = 0; i < tokensBalanceAfter.length; i++) { - expect( - tokensBalanceAfter[i] - .sub(tokensBalanceBefore[i]) - .gte(tokensMinBalanceIncrease[i]) - ).to.be.true; - } - }); + it('tokens balance should increase by at least minAmountsOut', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .gte(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); - it('bpt balance should decrease by exact bptMaxBalanceDecrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)).to - .be.true; + it('bpt balance should decrease by exact bptMaxBalanceDecrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)) + .to.be.true; + }); }); - }); - - context('exitExactTokensOut', async () => { - context('all tokens out', async () => { + context('single token max out', async () => { before(async function () { - amountsOut = pool.tokens.map((t, i) => - parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() + this.timeout(20000); + const bptIn = parseFixed('10', 18).toString(); + await testFlow( + pool.buildExitExactBPTIn( + signerAddress, + bptIn, + slippage, + false, + pool.tokensList[0] + ), + pool.tokensList ); - - try { - await testFlow( - pool.buildExitExactTokensOut( - signerAddress, - tokensOut.map((t) => t.address), - amountsOut, - slippage - ), - tokensOut.map((t) => t.address) - ); - } catch (error) { - // remove try/catch after fixing issues - } }); it('should work', async () => { expect(transactionReceipt.status).to.eql(1); }); - it('tokens balance should increase by exact amountsOut', async () => { - expect('test failing due to stable math calculation issues').to.eql(''); - // for (let i = 0; i < tokensBalanceAfter.length; i++) { - // expect( - // tokensBalanceAfter[i] - // .sub(tokensBalanceBefore[i]) - // .eq(tokensMinBalanceIncrease[i]) - // ).to.be.true; - // } + it('tokens balance should increase by at least minAmountsOut', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .gte(tokensMinBalanceIncrease[i]) + ).to.be.true; + } }); - it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + it('bpt balance should decrease by exact bptMaxBalanceDecrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)) .to.be.true; }); }); + }); + context('exitExactTokensOut', async () => { + // FIXME: test scenario not working due to stable math issues with non-proportional inputs + // Frontend currently does not support exiting with more than one exact token out + + // context('all tokens out', async () => { + // before(async function () { + // amountsOut = pool.tokens.map((t, i) => + // parseFixed(t.balance, t.decimals) + // .div(amountsOutDiv) + // .mul(i + 1) + // .toString() + // ); + + // await testFlow( + // pool.buildExitExactTokensOut( + // signerAddress, + // tokensOut.map((t) => t.address), + // amountsOut, + // slippage + // ), + // tokensOut.map((t) => t.address) + // ); + // }); + + // it('should work', async () => { + // expect(transactionReceipt.status).to.eql(1); + // }); + + // it('tokens balance should increase by exact amountsOut', async () => { + // // expect('test failing due to stable math calculation issues').to.eql(''); + // for (let i = 0; i < tokensBalanceAfter.length; i++) { + // expect( + // tokensBalanceAfter[i] + // .sub(tokensBalanceBefore[i]) + // .eq(tokensMinBalanceIncrease[i]) + // ).to.be.true; + // } + // }); + + // it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { + // expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + // .to.be.true; + // }); + // }); context('single token out', async () => { before(async function () { - amountsOut = pool.tokens.map((t, i) => - parseFixed(t.balance, t.decimals).div(amountsOutDiv).mul(i).toString() - ); + amountsOut = pool.tokens.map((t, i) => { + if (i === 0) { + return parseFixed(t.balance, t.decimals) + .div(amountsOutDiv) + .toString(); + } + return '0'; + }); await testFlow( pool.buildExitExactTokensOut( @@ -244,17 +277,22 @@ describe('exit execution', async () => { context('exit with ETH', async () => { before(async function () { this.timeout(20000); - amountsOut = pool.tokens.map((t, i) => - parseFixed(t.balance, t.decimals).div(amountsOutDiv).mul(i).toString() - ); const exitTokens = pool.tokensList.map((token) => - token === - balancer.networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase() + token === tokens.wrappedNativeAsset.toLowerCase() ? AddressZero : token ); + amountsOut = exitTokens.map((t, i) => { + if (t === AddressZero) { + return parseFixed(pool.tokens[i].balance, pool.tokens[i].decimals) + .div(amountsOutDiv) + .toString(); + } + return '0'; + }); + await testFlow( pool.buildExitExactTokensOut( signerAddress, diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts index acf07fcb5..a017bdda3 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts @@ -1,13 +1,6 @@ import dotenv from 'dotenv'; import { expect } from 'chai'; -import { - BalancerSDK, - Network, - Pool, - PoolModel, - PoolToken, - StaticPoolRepository, -} from '@/.'; +import { Network, Pool, PoolModel, PoolToken, StaticPoolRepository } from '@/.'; import hardhat from 'hardhat'; import { TransactionReceipt } from '@ethersproject/providers'; @@ -17,42 +10,43 @@ import { PoolsProvider } from '@/modules/pools/provider'; import pools_14717479 from '@/test/lib/pools_14717479.json'; import { ExitPoolAttributes } from '../types'; -import { AddressZero } from '@ethersproject/constants'; +import { networkAddresses } from '@/lib/constants/config'; dotenv.config(); const { ALCHEMY_URL: jsonRpcUrl } = process.env; const { ethers } = hardhat; -let balancer: BalancerSDK; const rpcUrl = 'http://127.0.0.1:8545'; const network = Network.MAINNET; const provider = new ethers.providers.JsonRpcProvider(rpcUrl, network); const signer = provider.getSigner(); +const { tokens } = networkAddresses(network); + // Slots used to set the account balance for each token through hardhat_setStorageAt // Info fetched using npm package slot20 const BPT_SLOT = 0; const initialBalance = '100000'; -const amountsOutDiv = '100000000'; -const slippage = '1000'; +const amountsOutDiv = (1e7).toString(); // FIXME: depending on this number, exitExactTokenOut (single token) throws Errors.STABLE_INVARIANT_DIDNT_CONVERGE +const slippage = '100'; const poolId = '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063'; // Balancer USD Stable Pool - staBAL3 -let tokensOut: PoolToken[]; -let amountsOut: string[]; -let transactionReceipt: TransactionReceipt; -let bptBalanceBefore: BigNumber; -let bptBalanceAfter: BigNumber; -let bptMaxBalanceDecrease: BigNumber; -let tokensBalanceBefore: BigNumber[]; -let tokensBalanceAfter: BigNumber[]; -let tokensMinBalanceIncrease: BigNumber[]; -let transactionCost: BigNumber; -let signerAddress: string; -let pool: PoolModel; - describe('exit execution', async () => { + let tokensOut: PoolToken[]; + let amountsOut: string[]; + let transactionReceipt: TransactionReceipt; + let bptBalanceBefore: BigNumber; + let bptBalanceAfter: BigNumber; + let bptMaxBalanceDecrease: BigNumber; + let tokensBalanceBefore: BigNumber[]; + let tokensBalanceAfter: BigNumber[]; + let tokensMinBalanceIncrease: BigNumber[]; + let transactionCost: BigNumber; + let signerAddress: string; + let pool: PoolModel; + // Setup chain before(async function () { this.timeout(20000); @@ -66,17 +60,9 @@ describe('exit execution', async () => { sdkConfig, new StaticPoolRepository(pools_14717479 as Pool[]) ); - balancer = new BalancerSDK( - sdkConfig, - undefined, - undefined, - undefined, - poolsProvider - ); pool = await setupPool(poolsProvider, poolId); tokensOut = pool.tokens; await forkSetup( - balancer, signer, [pool.address], [BPT_SLOT], @@ -120,10 +106,7 @@ describe('exit execution', async () => { transactionReceipt.effectiveGasPrice ); tokensBalanceAfter = tokensBalanceAfter.map((balance, i) => { - if ( - pool.tokensList[i] === - balancer.networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase() - ) { + if (pool.tokensList[i] === tokens.wrappedNativeAsset.toLowerCase()) { return balance.add(transactionCost); } return balance; @@ -132,77 +115,115 @@ describe('exit execution', async () => { }; context('exitExactBPTIn', async () => { - before(async function () { - this.timeout(20000); - const bptIn = parseFixed('10', 18).toString(); - await testFlow( - pool.buildExitExactBPTIn(signerAddress, bptIn, slippage), - pool.tokensList - ); - }); + context('proportional amounts out', async () => { + before(async function () { + this.timeout(20000); + const bptIn = parseFixed('10', 18).toString(); + await testFlow( + pool.buildExitExactBPTIn(signerAddress, bptIn, slippage), + pool.tokensList + ); + }); - it('should work', async () => { - expect(transactionReceipt.status).to.eql(1); - }); + it('should work', async () => { + expect(transactionReceipt.status).to.eql(1); + }); - it('tokens balance should increase by at least minAmountsOut', async () => { - for (let i = 0; i < tokensBalanceAfter.length; i++) { - expect( - tokensBalanceAfter[i] - .sub(tokensBalanceBefore[i]) - .gte(tokensMinBalanceIncrease[i]) - ).to.be.true; - } - }); + it('tokens balance should increase by at least minAmountsOut', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .gte(tokensMinBalanceIncrease[i]) + ).to.be.true; + } + }); - it('bpt balance should decrease by exact bptMaxBalanceDecrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)).to - .be.true; + it('bpt balance should decrease by exact bptMaxBalanceDecrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)) + .to.be.true; + }); }); - }); - - context('exitExactTokensOut', async () => { - context('all tokens out', async () => { + context('single token max out', async () => { before(async function () { - amountsOut = pool.tokens.map((t, i) => - parseFixed(t.balance, t.decimals).div(amountsOutDiv).toString() + this.timeout(20000); + const bptIn = parseFixed('10', 18).toString(); + await testFlow( + pool.buildExitExactBPTIn( + signerAddress, + bptIn, + slippage, + false, + pool.tokensList[0] + ), + pool.tokensList ); - - try { - await testFlow( - pool.buildExitExactTokensOut( - signerAddress, - tokensOut.map((t) => t.address), - amountsOut, - slippage - ), - pool.tokensList - ); - } catch (error) { - // remove try/catch after fixing issues - } }); it('should work', async () => { expect(transactionReceipt.status).to.eql(1); }); - it('tokens balance should increase by exact amountsOut', async () => { - expect('test failing due to stable math calculation issues').to.eql(''); - // for (let i = 0; i < tokensBalanceAfter.length; i++) { - // expect( - // tokensBalanceAfter[i] - // .sub(tokensBalanceBefore[i]) - // .eq(tokensMinBalanceIncrease[i]) - // ).to.be.true; - // } + it('tokens balance should increase by at least minAmountsOut', async () => { + for (let i = 0; i < tokensBalanceAfter.length; i++) { + expect( + tokensBalanceAfter[i] + .sub(tokensBalanceBefore[i]) + .gte(tokensMinBalanceIncrease[i]) + ).to.be.true; + } }); - it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { - expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + it('bpt balance should decrease by exact bptMaxBalanceDecrease', async () => { + expect(bptBalanceBefore.sub(bptBalanceAfter).eq(bptMaxBalanceDecrease)) .to.be.true; }); }); + }); + + context('exitExactTokensOut', async () => { + // FIXME: test scenario not working due to stable math issues with non-proportional inputs + // Frontend currently does not support exiting with more than one exact token out + + // context('all tokens out', async () => { + // before(async function () { + // amountsOut = pool.tokens.map((t, i) => + // parseFixed(t.balance, t.decimals) + // .div(amountsOutDiv) + // .mul(i + 1) + // .toString() + // ); + + // await testFlow( + // pool.buildExitExactTokensOut( + // signerAddress, + // tokensOut.map((t) => t.address), + // amountsOut, + // slippage + // ), + // pool.tokensList + // ); + // }); + + // it('should work', async () => { + // expect(transactionReceipt.status).to.eql(1); + // }); + + // it('tokens balance should increase by exact amountsOut', async () => { + // for (let i = 0; i < tokensBalanceAfter.length; i++) { + // expect( + // tokensBalanceAfter[i] + // .sub(tokensBalanceBefore[i]) + // .eq(tokensMinBalanceIncrease[i]) + // ).to.be.true; + // } + // }); + + // it('bpt balance should decrease by max bptMaxBalanceDecrease', async () => { + // expect(bptBalanceBefore.sub(bptBalanceAfter).lte(bptMaxBalanceDecrease)) + // .to.be.true; + // }); + // }); context('single token out', async () => { before(async function () { diff --git a/balancer-js/src/modules/pools/pool-types/concerns/types.ts b/balancer-js/src/modules/pools/pool-types/concerns/types.ts index 1247bd9f4..b27fc627b 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/types.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/types.ts @@ -40,7 +40,7 @@ export interface ExitConcern { /** * Build exit pool transaction parameters with exact BPT in and minimum token amounts out based on slippage tolerance * @param exiter Account address exiting pool - * @param pool Subgraph pool object of pool being exited + * @param pool Pool being exited * @param bptIn BPT provided for exiting pool * @param slippage Maximum slippage tolerance in percentage. i.e. 0.05 = 5% * @param shouldUnwrapNativeAsset Indicates whether wrapped native asset should be unwrapped after exit. @@ -61,7 +61,7 @@ export interface ExitConcern { /** * Build exit pool transaction parameters with exact tokens out and maximum BPT in based on slippage tolerance * @param exiter Account address exiting pool - * @param pool Subgraph pool object of pool being exited + * @param pool Pool being exited * @param tokensOut Tokens provided for exiting pool * @param amountsOut Amounts provided for exiting pool * @param slippage Maximum slippage tolerance in percentage. i.e. 0.05 = 5% From f3d8555dda356156f6bf347b93418e0a49eda301 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 1 Sep 2022 10:11:23 -0300 Subject: [PATCH 53/65] Fix linter issue --- .../concerns/metaStable/exit.concern.integration.spec.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts index f938e2c14..27000e0cc 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts @@ -1,13 +1,6 @@ import dotenv from 'dotenv'; import { expect } from 'chai'; -import { - BalancerSDK, - Network, - Pool, - PoolModel, - PoolToken, - StaticPoolRepository, -} from '@/.'; +import { Network, Pool, PoolModel, PoolToken, StaticPoolRepository } from '@/.'; import hardhat from 'hardhat'; import { TransactionReceipt } from '@ethersproject/providers'; From cae2747b79887cf4660b291f8d2f20cd4252a2bd Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 2 Sep 2022 10:22:10 -0300 Subject: [PATCH 54/65] Post merge fixes --- balancer-js/src/modules/pools/index.ts | 8 +- .../exit.concern.integration.spec.ts | 82 +++++++---------- .../concerns/metaStable/exit.concern.spec.ts | 44 +++------ .../stable/exit.concern.integration.spec.ts | 50 +++++------ .../concerns/stable/exit.concern.spec.ts | 50 ++++------- balancer-js/src/modules/pools/provider.ts | 89 ------------------- 6 files changed, 86 insertions(+), 237 deletions(-) delete mode 100644 balancer-js/src/modules/pools/provider.ts diff --git a/balancer-js/src/modules/pools/index.ts b/balancer-js/src/modules/pools/index.ts index 2ce77f806..2826c7601 100644 --- a/balancer-js/src/modules/pools/index.ts +++ b/balancer-js/src/modules/pools/index.ts @@ -70,6 +70,8 @@ export class Pools implements Findable { networkConfig: BalancerNetworkConfig ): PoolWithMethods { const methods = PoolTypeConcerns.from(pool.poolType); + const wrappedNativeAsset = + networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase(); return { ...pool, buildJoin: ( @@ -84,7 +86,7 @@ export class Pools implements Findable { tokensIn, amountsIn, slippage, - wrappedNativeAsset: networkConfig.addresses.tokens.wrappedNativeAsset, + wrappedNativeAsset, }); }, calcPriceImpact: async (amountsIn: string[], minBPTOut: string) => @@ -106,7 +108,7 @@ export class Pools implements Findable { bptIn, slippage, shouldUnwrapNativeAsset, - wrappedNativeAsset: networkConfig.addresses.tokens.wrappedNativeAsset, + wrappedNativeAsset, singleTokenMaxOut, }), buildExitExactTokensOut: (exiter, tokensOut, amountsOut, slippage) => @@ -116,7 +118,7 @@ export class Pools implements Findable { tokensOut, amountsOut, slippage, - wrappedNativeAsset: networkConfig.addresses.tokens.wrappedNativeAsset, + wrappedNativeAsset, }), // TODO: spotPrice fails, because it needs a subgraphType, // either we refetch or it needs a type transformation from SDK internal to SOR (subgraph) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts index 27000e0cc..ffd4beb9d 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.integration.spec.ts @@ -1,17 +1,16 @@ import dotenv from 'dotenv'; import { expect } from 'chai'; -import { Network, Pool, PoolModel, PoolToken, StaticPoolRepository } from '@/.'; +import { BalancerSDK, Network, Pool } from '@/.'; import hardhat from 'hardhat'; import { TransactionReceipt } from '@ethersproject/providers'; import { BigNumber, parseFixed } from '@ethersproject/bignumber'; -import { forkSetup, setupPool, getBalances } from '@/test/lib/utils'; -import { PoolsProvider } from '@/modules/pools/provider'; +import { forkSetup, getBalances } from '@/test/lib/utils'; +import { Pools } from '@/modules/pools'; import pools_14717479 from '@/test/lib/pools_14717479.json'; import { ExitPoolAttributes } from '../types'; import { AddressZero } from '@ethersproject/constants'; -import { networkAddresses } from '@/lib/constants/config'; dotenv.config(); @@ -20,49 +19,42 @@ const { ethers } = hardhat; const rpcUrl = 'http://127.0.0.1:8545'; const network = Network.MAINNET; +const { networkConfig } = new BalancerSDK({ network, rpcUrl }); +const wrappedNativeAsset = + networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase(); + const provider = new ethers.providers.JsonRpcProvider(rpcUrl, network); const signer = provider.getSigner(); -const { tokens } = networkAddresses(network); - // Slots used to set the account balance for each token through hardhat_setStorageAt // Info fetched using npm package slot20 const BPT_SLOT = 0; const initialBalance = '10000000'; const amountsOutDiv = (1e7).toString(); // FIXME: depending on this number, exitExactTokenOut (single token) throws Errors.STABLE_INVARIANT_DIDNT_CONVERGE const slippage = '100'; -const poolId = - '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080'; // Balancer stETH Stable Pool - -let tokensOut: PoolToken[]; -let amountsOut: string[]; -let transactionReceipt: TransactionReceipt; -let bptBalanceBefore: BigNumber; -let bptBalanceAfter: BigNumber; -let bptMaxBalanceDecrease: BigNumber; -let tokensBalanceBefore: BigNumber[]; -let tokensBalanceAfter: BigNumber[]; -let tokensMinBalanceIncrease: BigNumber[]; -let transactionCost: BigNumber; -let signerAddress: string; -let pool: PoolModel; + +const pool = pools_14717479.find( + (pool) => + pool.id == + '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080' // Balancer stETH Stable Pool +) as unknown as Pool; +const tokensOut = pool.tokens; +const controller = Pools.wrap(pool, networkConfig); describe('exit execution', async () => { + let amountsOut: string[]; + let transactionReceipt: TransactionReceipt; + let bptBalanceBefore: BigNumber; + let bptBalanceAfter: BigNumber; + let bptMaxBalanceDecrease: BigNumber; + let tokensBalanceBefore: BigNumber[]; + let tokensBalanceAfter: BigNumber[]; + let tokensMinBalanceIncrease: BigNumber[]; + let transactionCost: BigNumber; + let signerAddress: string; + // Setup chain before(async function () { - this.timeout(20000); - - const sdkConfig = { - network, - rpcUrl, - }; - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - pool = await setupPool(poolsProvider, poolId); - tokensOut = pool.tokens; await forkSetup( signer, [pool.address], @@ -107,7 +99,7 @@ describe('exit execution', async () => { transactionReceipt.effectiveGasPrice ); tokensBalanceAfter = tokensBalanceAfter.map((balance, i) => { - if (pool.tokensList[i] === tokens.wrappedNativeAsset.toLowerCase()) { + if (pool.tokensList[i] === wrappedNativeAsset) { return balance.add(transactionCost); } return balance; @@ -117,10 +109,9 @@ describe('exit execution', async () => { context('exitExactBPTIn', async () => { context('proportional amounts out', async () => { before(async function () { - this.timeout(20000); const bptIn = parseFixed('10', 18).toString(); await testFlow( - pool.buildExitExactBPTIn(signerAddress, bptIn, slippage), + controller.buildExitExactBPTIn(signerAddress, bptIn, slippage), pool.tokensList ); }); @@ -146,10 +137,9 @@ describe('exit execution', async () => { }); context('single token max out', async () => { before(async function () { - this.timeout(20000); const bptIn = parseFixed('10', 18).toString(); await testFlow( - pool.buildExitExactBPTIn( + controller.buildExitExactBPTIn( signerAddress, bptIn, slippage, @@ -194,7 +184,7 @@ describe('exit execution', async () => { // ); // await testFlow( - // pool.buildExitExactTokensOut( + // controller.buildExitExactTokensOut( // signerAddress, // tokensOut.map((t) => t.address), // amountsOut, @@ -237,7 +227,7 @@ describe('exit execution', async () => { }); await testFlow( - pool.buildExitExactTokensOut( + controller.buildExitExactTokensOut( signerAddress, tokensOut.map((t) => t.address), amountsOut, @@ -269,12 +259,8 @@ describe('exit execution', async () => { context('exit with ETH', async () => { before(async function () { - this.timeout(20000); - const exitTokens = pool.tokensList.map((token) => - token === tokens.wrappedNativeAsset.toLowerCase() - ? AddressZero - : token + token === wrappedNativeAsset ? AddressZero : token ); amountsOut = exitTokens.map((t, i) => { @@ -287,7 +273,7 @@ describe('exit execution', async () => { }); await testFlow( - pool.buildExitExactTokensOut( + controller.buildExitExactTokensOut( signerAddress, exitTokens, amountsOut, @@ -318,4 +304,4 @@ describe('exit execution', async () => { }); }); }); -}).timeout(20000); +}); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.spec.ts index 015beb7d6..917c48152 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/metaStable/exit.concern.spec.ts @@ -2,45 +2,25 @@ import { expect } from 'chai'; import { parseFixed } from '@ethersproject/bignumber'; import { AddressZero } from '@ethersproject/constants'; -import { - BalancerError, - BalancerErrorCode, - BalancerSdkConfig, - Network, - Pool, - PoolModel, - StaticPoolRepository, -} from '@/.'; -import { PoolsProvider } from '@/modules/pools/provider'; +import { BalancerSDK, Network, Pool } from '@/.'; import pools_14717479 from '@/test/lib/pools_14717479.json'; import { MetaStablePoolExit } from './exit.concern'; -import { networkAddresses } from '@/lib/constants/config'; const metaStablePoolExit = new MetaStablePoolExit(); -const stETH_stable_pool_id = - '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080'; // Balancer stETH Stable Pool +const rpcUrl = ''; const network = Network.MAINNET; -const sdkConfig: BalancerSdkConfig = { - network, - rpcUrl: ``, -}; -const pools = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) -); -const { tokens } = networkAddresses(network); +const { networkConfig } = new BalancerSDK({ network, rpcUrl }); +const wrappedNativeAsset = + networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase(); -describe('exit module', async () => { - let pool: PoolModel; - - before(async function () { - await pools.find(stETH_stable_pool_id).then((p) => { - if (!p) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); - pool = p; - }); - }); +const pool = pools_14717479.find( + (pool) => + pool.id == + '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080' // Balancer stETH Stable Pool +) as unknown as Pool; +describe('exit module', () => { describe('buildExitExactBPTIn', () => { context('exit with ETH', () => { it('should fail due to conflicting inputs', () => { @@ -52,7 +32,7 @@ describe('exit module', async () => { bptIn: parseFixed('10', 18).toString(), slippage: '100', // 100 bps shouldUnwrapNativeAsset: false, - wrappedNativeAsset: tokens.wrappedNativeAsset, + wrappedNativeAsset, singleTokenMaxOut: AddressZero, }); } catch (error) { diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts index a017bdda3..9a6d6226b 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts @@ -1,16 +1,15 @@ import dotenv from 'dotenv'; import { expect } from 'chai'; -import { Network, Pool, PoolModel, PoolToken, StaticPoolRepository } from '@/.'; +import { BalancerSDK, Network, Pool, PoolToken } from '@/.'; import hardhat from 'hardhat'; import { TransactionReceipt } from '@ethersproject/providers'; import { BigNumber, parseFixed } from '@ethersproject/bignumber'; import { forkSetup, setupPool, getBalances } from '@/test/lib/utils'; -import { PoolsProvider } from '@/modules/pools/provider'; +import { Pools } from '@/modules/pools'; import pools_14717479 from '@/test/lib/pools_14717479.json'; import { ExitPoolAttributes } from '../types'; -import { networkAddresses } from '@/lib/constants/config'; dotenv.config(); @@ -19,22 +18,29 @@ const { ethers } = hardhat; const rpcUrl = 'http://127.0.0.1:8545'; const network = Network.MAINNET; +const { networkConfig } = new BalancerSDK({ network, rpcUrl }); +const wrappedNativeAsset = + networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase(); + const provider = new ethers.providers.JsonRpcProvider(rpcUrl, network); const signer = provider.getSigner(); -const { tokens } = networkAddresses(network); - // Slots used to set the account balance for each token through hardhat_setStorageAt // Info fetched using npm package slot20 const BPT_SLOT = 0; const initialBalance = '100000'; const amountsOutDiv = (1e7).toString(); // FIXME: depending on this number, exitExactTokenOut (single token) throws Errors.STABLE_INVARIANT_DIDNT_CONVERGE const slippage = '100'; -const poolId = - '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063'; // Balancer USD Stable Pool - staBAL3 + +const pool = pools_14717479.find( + (pool) => + pool.id == + '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063' // Balancer USD Stable Pool - staBAL3 +) as unknown as Pool; +const tokensOut = pool.tokens; +const controller = Pools.wrap(pool, networkConfig); describe('exit execution', async () => { - let tokensOut: PoolToken[]; let amountsOut: string[]; let transactionReceipt: TransactionReceipt; let bptBalanceBefore: BigNumber; @@ -45,23 +51,9 @@ describe('exit execution', async () => { let tokensMinBalanceIncrease: BigNumber[]; let transactionCost: BigNumber; let signerAddress: string; - let pool: PoolModel; // Setup chain before(async function () { - this.timeout(20000); - - const sdkConfig = { - network, - rpcUrl, - }; - // Using a static repository to make test consistent over time - const poolsProvider = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) - ); - pool = await setupPool(poolsProvider, poolId); - tokensOut = pool.tokens; await forkSetup( signer, [pool.address], @@ -106,7 +98,7 @@ describe('exit execution', async () => { transactionReceipt.effectiveGasPrice ); tokensBalanceAfter = tokensBalanceAfter.map((balance, i) => { - if (pool.tokensList[i] === tokens.wrappedNativeAsset.toLowerCase()) { + if (pool.tokensList[i] === wrappedNativeAsset) { return balance.add(transactionCost); } return balance; @@ -117,10 +109,9 @@ describe('exit execution', async () => { context('exitExactBPTIn', async () => { context('proportional amounts out', async () => { before(async function () { - this.timeout(20000); const bptIn = parseFixed('10', 18).toString(); await testFlow( - pool.buildExitExactBPTIn(signerAddress, bptIn, slippage), + controller.buildExitExactBPTIn(signerAddress, bptIn, slippage), pool.tokensList ); }); @@ -146,10 +137,9 @@ describe('exit execution', async () => { }); context('single token max out', async () => { before(async function () { - this.timeout(20000); const bptIn = parseFixed('10', 18).toString(); await testFlow( - pool.buildExitExactBPTIn( + controller.buildExitExactBPTIn( signerAddress, bptIn, slippage, @@ -195,7 +185,7 @@ describe('exit execution', async () => { // ); // await testFlow( - // pool.buildExitExactTokensOut( + // controller.buildExitExactTokensOut( // signerAddress, // tokensOut.map((t) => t.address), // amountsOut, @@ -237,7 +227,7 @@ describe('exit execution', async () => { }); await testFlow( - pool.buildExitExactTokensOut( + controller.buildExitExactTokensOut( signerAddress, tokensOut.map((t) => t.address), amountsOut, @@ -267,4 +257,4 @@ describe('exit execution', async () => { }); }); }); -}).timeout(20000); +}); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.spec.ts index 5953ab8b6..5da5a5089 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.spec.ts @@ -1,48 +1,28 @@ import { expect } from 'chai'; import { parseFixed } from '@ethersproject/bignumber'; import { AddressZero } from '@ethersproject/constants'; -import { - BalancerError, - BalancerErrorCode, - BalancerSdkConfig, - Network, - Pool, - PoolModel, - StaticPoolRepository, -} from '@/.'; -import { PoolsProvider } from '@/modules/pools/provider'; +import { BalancerSDK, Network, Pool } from '@/.'; import pools_14717479 from '@/test/lib/pools_14717479.json'; - import { StablePoolExit } from './exit.concern'; -import { networkAddresses } from '@/lib/constants/config'; const stablePoolExit = new StablePoolExit(); + +const rpcUrl = ''; const network = Network.MAINNET; -const sdkConfig: BalancerSdkConfig = { - network, - rpcUrl: ``, -}; -const pools = new PoolsProvider( - sdkConfig, - new StaticPoolRepository(pools_14717479 as Pool[]) -); -const { tokens } = networkAddresses(network); +const { networkConfig } = new BalancerSDK({ network, rpcUrl }); +const wrappedNativeAsset = + networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase(); -describe('exit module', async () => { - let pool: PoolModel; +const pool = pools_14717479.find( + (pool) => + pool.id == + '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063' // Balancer USD Stable Pool - staBAL3 +) as unknown as Pool; - describe('buildExitExactBPTIn', async () => { - context('exit with ETH', async () => { - before(async function () { - // Note that currently there is no stable pool with WETH as underlying token, but for the purposes of this unit test any stable pool can be used - const stabal3 = - '0x06df3b2bbb68adc8b0e302443692037ed9f91b42000000000000000000000063'; // Balancer USD Stable Pool - staBAL3 - await pools.find(stabal3).then((p) => { - if (!p) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); - pool = p; - }); - }); +describe('exit module', () => { + describe('buildExitExactBPTIn', () => { + context('exit with ETH', () => { it('should fail due to conflicting inputs', () => { let errorMessage = ''; try { @@ -52,7 +32,7 @@ describe('exit module', async () => { bptIn: parseFixed('10', 18).toString(), slippage: '100', // 100 bps shouldUnwrapNativeAsset: false, - wrappedNativeAsset: tokens.wrappedNativeAsset, + wrappedNativeAsset, singleTokenMaxOut: AddressZero, }); } catch (error) { diff --git a/balancer-js/src/modules/pools/provider.ts b/balancer-js/src/modules/pools/provider.ts deleted file mode 100644 index bb4f2064b..000000000 --- a/balancer-js/src/modules/pools/provider.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { BalancerSdkConfig, Pool, PoolModel } from '@/types'; -import { PoolRepository } from '@/modules/data'; -import { Pools as PoolMethods } from './pools.module'; -import { getNetworkConfig } from '../sdk.helpers'; - -/** - * Building pools from raw data injecting poolType specific methods - */ -export class PoolsProvider { - constructor( - private config: BalancerSdkConfig, - private repository: PoolRepository - ) {} - - static wrap(data: Pool, config: BalancerSdkConfig): PoolModel { - const methods = PoolMethods.from(data.poolType); - const networkConfig = getNetworkConfig(config); - const wrappedNativeAsset = - networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase(); - return { - ...data, - liquidity: async () => methods.liquidity.calcTotal(data.tokens), - buildJoin: (joiner, tokensIn, amountsIn, slippage) => - methods.join.buildJoin({ - joiner, - pool: data, - tokensIn, - amountsIn, - slippage, - wrappedNativeAsset, - }), - - calcPriceImpact: async (amountsIn: string[], minBPTOut: string) => - methods.priceImpactCalculator.calcPriceImpact( - data, - amountsIn, - minBPTOut - ), - - buildExitExactBPTIn: ( - exiter, - bptIn, - slippage, - shouldUnwrapNativeAsset = false, - singleTokenMaxOut - ) => - methods.exit.buildExitExactBPTIn({ - exiter, - pool: data, - bptIn, - slippage, - shouldUnwrapNativeAsset, - wrappedNativeAsset, - singleTokenMaxOut, - }), - buildExitExactTokensOut: (exiter, tokensOut, amountsOut, slippage) => - methods.exit.buildExitExactTokensOut({ - exiter, - pool: data, - tokensOut, - amountsOut, - slippage, - wrappedNativeAsset, - }), - calcSpotPrice: (tokenIn: string, tokenOut: string) => - methods.spotPriceCalculator.calcPoolSpotPrice(tokenIn, tokenOut, data), - }; - } - - async find(id: string): Promise { - const data = await this.repository.find(id); - if (!data) return; - - return PoolsProvider.wrap(data, this.config); - } - - async findBy(param: string, value: string): Promise { - if (param == 'id') { - return this.find(value); - } else if (param == 'address') { - const data = await this.repository.findBy('address', value); - if (!data) return; - - return PoolsProvider.wrap(data, this.config); - } else { - throw `search by ${param} not implemented`; - } - } -} From 8b0c9cdc541eb6b0b57b861702766ed68f0d6a7b Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 2 Sep 2022 10:24:44 -0300 Subject: [PATCH 55/65] Fix lint issues --- .../concerns/stable/exit.concern.integration.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts index 9a6d6226b..d202582ce 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/exit.concern.integration.spec.ts @@ -1,11 +1,11 @@ import dotenv from 'dotenv'; import { expect } from 'chai'; -import { BalancerSDK, Network, Pool, PoolToken } from '@/.'; +import { BalancerSDK, Network, Pool } from '@/.'; import hardhat from 'hardhat'; import { TransactionReceipt } from '@ethersproject/providers'; import { BigNumber, parseFixed } from '@ethersproject/bignumber'; -import { forkSetup, setupPool, getBalances } from '@/test/lib/utils'; +import { forkSetup, getBalances } from '@/test/lib/utils'; import { Pools } from '@/modules/pools'; import pools_14717479 from '@/test/lib/pools_14717479.json'; From decf6b72a72f4cf0089ecf027e4a48ed8e32ab8b Mon Sep 17 00:00:00 2001 From: bronco Date: Tue, 6 Sep 2022 10:28:27 +0200 Subject: [PATCH 56/65] fix: minimal APR shouldn't include protocol APR --- balancer-js/src/modules/pools/apr/apr.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/balancer-js/src/modules/pools/apr/apr.ts b/balancer-js/src/modules/pools/apr/apr.ts index 6b196990a..3462b2a76 100644 --- a/balancer-js/src/modules/pools/apr/apr.ts +++ b/balancer-js/src/modules/pools/apr/apr.ts @@ -341,8 +341,7 @@ export class PoolApr { }, rewardAprs, protocolApr, - min: - swapFees + tokenAprs + rewardAprs.total + protocolApr + minStakingApr, + min: swapFees + tokenAprs + rewardAprs.total + minStakingApr, max: swapFees + tokenAprs + rewardAprs.total + protocolApr + maxStakingApr, }; From 2544f62bd26b763e6eee3078a6be7d151070df59 Mon Sep 17 00:00:00 2001 From: bronco Date: Wed, 7 Sep 2022 21:33:04 +0200 Subject: [PATCH 57/65] fix: handle differences in L2 gauges --- balancer-js/examples/pools/aprs.polygon.ts | 31 +++++++++++ balancer-js/src/lib/constants/config.ts | 2 +- balancer-js/src/modules/data/index.ts | 14 ++--- .../data/liquidity-gauges/multicall.spec.ts | 1 + .../data/liquidity-gauges/multicall.ts | 54 +++++++++++++------ .../modules/data/liquidity-gauges/provider.ts | 25 +++++---- balancer-js/src/modules/data/pool/subgraph.ts | 6 ++- balancer-js/src/modules/pools/apr/apr.ts | 53 +++++++++--------- balancer-js/src/test/factories/sdk.ts | 1 + balancer-js/src/types.ts | 9 ++-- 10 files changed, 135 insertions(+), 61 deletions(-) create mode 100644 balancer-js/examples/pools/aprs.polygon.ts diff --git a/balancer-js/examples/pools/aprs.polygon.ts b/balancer-js/examples/pools/aprs.polygon.ts new file mode 100644 index 000000000..e75c8b1cb --- /dev/null +++ b/balancer-js/examples/pools/aprs.polygon.ts @@ -0,0 +1,31 @@ +/** + * Display APRs for pool ids hardcoded under `const ids` + * Run command: yarn examples:run ./examples/pools/aprs.ts + */ +import dotenv from 'dotenv'; +import { BalancerSDK } from '../../src/modules/sdk.module'; + +dotenv.config(); + +const sdk = new BalancerSDK({ + network: 137, + rpcUrl: `${process.env.ALCHEMY_URL?.replace( + 'eth-mainnet', + 'polygon-mainnet.g' + )}`, +}); + +const { pools } = sdk; + +const main = async () => { + const pool = await pools.find( + '0x0297e37f1873d2dab4487aa67cd56b58e2f27875000100000000000000000002' + ); + + if (pool) { + const apr = await pools.apr(pool); + console.log(pool.id, apr); + } +}; + +main(); diff --git a/balancer-js/src/lib/constants/config.ts b/balancer-js/src/lib/constants/config.ts index 3ec0c2b3b..c9e41624f 100644 --- a/balancer-js/src/lib/constants/config.ts +++ b/balancer-js/src/lib/constants/config.ts @@ -63,7 +63,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { subgraph: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-polygon-v2', gaugesSubgraph: - 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', + 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges-polygon', blockNumberSubgraph: 'https://api.thegraph.com/subgraphs/name/ianlapham/polygon-blocks', }, diff --git a/balancer-js/src/modules/data/index.ts b/balancer-js/src/modules/data/index.ts index 58b4a00b5..ba6583241 100644 --- a/balancer-js/src/modules/data/index.ts +++ b/balancer-js/src/modules/data/index.ts @@ -36,7 +36,10 @@ export class Data implements BalancerDataRepositories { blockNumbers; constructor(networkConfig: BalancerNetworkConfig, provider: Provider) { - this.pools = new PoolsSubgraphRepository(networkConfig.urls.subgraph); + this.pools = new PoolsSubgraphRepository( + networkConfig.urls.subgraph, + networkConfig.chainId + ); // 🚨 yesterdaysPools is used to calculate swapFees accumulated over last 24 hours // TODO: find a better data source for that, eg: maybe DUNE once API is available @@ -53,6 +56,7 @@ export class Data implements BalancerDataRepositories { this.yesterdaysPools = new PoolsSubgraphRepository( networkConfig.urls.subgraph, + networkConfig.chainId, blockDayAgo ); } @@ -68,14 +72,12 @@ export class Data implements BalancerDataRepositories { this.tokenMeta = new StaticTokenProvider([]); - if ( - networkConfig.urls.gaugesSubgraph && - networkConfig.addresses.contracts.gaugeController - ) { + if (networkConfig.urls.gaugesSubgraph) { this.liquidityGauges = new LiquidityGaugeSubgraphRPCProvider( networkConfig.urls.gaugesSubgraph, networkConfig.addresses.contracts.multicall, - networkConfig.addresses.contracts.gaugeController, + networkConfig.addresses.contracts.gaugeController || '', + networkConfig.chainId, provider ); } diff --git a/balancer-js/src/modules/data/liquidity-gauges/multicall.spec.ts b/balancer-js/src/modules/data/liquidity-gauges/multicall.spec.ts index 154e00c65..27dac183e 100644 --- a/balancer-js/src/modules/data/liquidity-gauges/multicall.spec.ts +++ b/balancer-js/src/modules/data/liquidity-gauges/multicall.spec.ts @@ -7,6 +7,7 @@ describe('Liquidity gauge multicall', () => { const provider = new JsonRpcProvider('http://127.0.0.1:8545', 1); const fetcher = new LiquidityGaugesMulticallRepository( '0xeefba1e63905ef1d7acba5a8513c70307c1ce441', + 1, provider ); diff --git a/balancer-js/src/modules/data/liquidity-gauges/multicall.ts b/balancer-js/src/modules/data/liquidity-gauges/multicall.ts index 7e38b478e..b44569636 100644 --- a/balancer-js/src/modules/data/liquidity-gauges/multicall.ts +++ b/balancer-js/src/modules/data/liquidity-gauges/multicall.ts @@ -4,6 +4,7 @@ import { Interface } from '@ethersproject/abi'; import { Contract } from '@ethersproject/contracts'; import { formatUnits } from '@ethersproject/units'; import { BigNumber } from '@ethersproject/bignumber'; +import type { Network } from '@/types'; const liquidityGaugeV5Interface = new Interface([ 'function totalSupply() view returns (uint)', @@ -29,7 +30,11 @@ export interface RewardData { export class LiquidityGaugesMulticallRepository { multicall: Contract; - constructor(multicallAddress: string, provider: Provider) { + constructor( + multicallAddress: string, + private chainId: Network, + provider: Provider + ) { this.multicall = Multicall(multicallAddress, provider); } @@ -58,9 +63,13 @@ export class LiquidityGaugesMulticallRepository { async getWorkingSupplies( gaugeAddresses: string[] ): Promise<{ [gaugeAddress: string]: number }> { + let functionName = 'working_supply'; + if (this.chainId != 1) { + functionName = 'totalSupply'; + } const payload = gaugeAddresses.map((gaugeAddress) => [ gaugeAddress, - liquidityGaugeV5Interface.encodeFunctionData('working_supply', []), + liquidityGaugeV5Interface.encodeFunctionData(functionName, []), ]); const [, res] = await this.multicall.aggregate(payload); // Handle 0x @@ -80,21 +89,32 @@ export class LiquidityGaugesMulticallRepository { async getRewardCounts( gaugeAddresses: string[] ): Promise<{ [gaugeAddress: string]: number }> { - const payload = gaugeAddresses.map((gaugeAddress) => [ - gaugeAddress, - liquidityGaugeV5Interface.encodeFunctionData('reward_count', []), - ]); - const [, res] = await this.multicall.aggregate(payload); - // Handle 0x return values - const res0x = res.map((r: string) => (r == '0x' ? '0x0' : r)); - - const rewardCounts = gaugeAddresses.reduce( - (p: { [key: string]: number }, a, i) => { - p[a] ||= parseInt(res0x[i]); - return p; - }, - {} - ); + let rewardCounts; + if (this.chainId == 1) { + const payload = gaugeAddresses.map((gaugeAddress) => [ + gaugeAddress, + liquidityGaugeV5Interface.encodeFunctionData('reward_count', []), + ]); + const [, res] = await this.multicall.aggregate(payload); + // Handle 0x return values + const res0x = res.map((r: string) => (r == '0x' ? '0x0' : r)); + + rewardCounts = gaugeAddresses.reduce( + (p: { [key: string]: number }, a, i) => { + p[a] ||= parseInt(res0x[i]); + return p; + }, + {} + ); + } else { + rewardCounts = gaugeAddresses.reduce( + (p: { [key: string]: number }, a) => { + p[a] ||= 1; + return p; + }, + {} + ); + } return rewardCounts; } diff --git a/balancer-js/src/modules/data/liquidity-gauges/provider.ts b/balancer-js/src/modules/data/liquidity-gauges/provider.ts index a8d16347b..2fa428e6c 100644 --- a/balancer-js/src/modules/data/liquidity-gauges/provider.ts +++ b/balancer-js/src/modules/data/liquidity-gauges/provider.ts @@ -7,6 +7,7 @@ import type { } from '@/modules/subgraph/subgraph'; import type { Findable } from '../types'; import type { Provider } from '@ethersproject/providers'; +import type { Network } from '@/types'; export interface LiquidityGauge { id: string; @@ -23,7 +24,7 @@ export interface LiquidityGauge { export class LiquidityGaugeSubgraphRPCProvider implements Findable { - gaugeController: GaugeControllerMulticallRepository; + gaugeController?: GaugeControllerMulticallRepository; multicall: LiquidityGaugesMulticallRepository; subgraph: LiquidityGaugesSubgraphRepository; totalSupplies: { [gaugeAddress: string]: number } = {}; @@ -37,15 +38,19 @@ export class LiquidityGaugeSubgraphRPCProvider subgraphUrl: string, multicallAddress: string, gaugeControllerAddress: string, + chainId: Network, provider: Provider ) { - this.gaugeController = new GaugeControllerMulticallRepository( - multicallAddress, - gaugeControllerAddress, - provider - ); + if (gaugeControllerAddress) { + this.gaugeController = new GaugeControllerMulticallRepository( + multicallAddress, + gaugeControllerAddress, + provider + ); + } this.multicall = new LiquidityGaugesMulticallRepository( multicallAddress, + chainId, provider ); this.subgraph = new LiquidityGaugesSubgraphRepository(subgraphUrl); @@ -59,9 +64,11 @@ export class LiquidityGaugeSubgraphRPCProvider gaugeAddresses ); this.rewardTokens = await this.multicall.getRewardData(gaugeAddresses); - this.relativeWeights = await this.gaugeController.getRelativeWeights( - gaugeAddresses - ); + if (this.gaugeController) { + this.relativeWeights = await this.gaugeController.getRelativeWeights( + gaugeAddresses + ); + } } async find(id: string): Promise { diff --git a/balancer-js/src/modules/data/pool/subgraph.ts b/balancer-js/src/modules/data/pool/subgraph.ts index 6d42e178a..51e719be3 100644 --- a/balancer-js/src/modules/data/pool/subgraph.ts +++ b/balancer-js/src/modules/data/pool/subgraph.ts @@ -8,6 +8,7 @@ import { } from '@/modules/subgraph/subgraph'; import { PoolAttribute } from './types'; import { Pool, PoolType } from '@/types'; +import { Network } from '@/lib/constants/network'; /** * Access pools using generated subgraph client. @@ -24,10 +25,12 @@ export class PoolsSubgraphRepository * Repository with optional lazy loaded blockHeight * * @param url subgraph URL + * @param chainId current network, needed for L2s logic * @param blockHeight lazy loading blockHeigh resolver */ constructor( url: string, + private chainId: Network, private blockHeight?: () => Promise ) { this.client = createSubgraphClient(url); @@ -74,7 +77,7 @@ export class PoolsSubgraphRepository await this.fetch(); } - return this.pools.map(this.mapType); + return this.pools.map(this.mapType.bind(this)); } async where(filter: (pool: Pool) => boolean): Promise { @@ -90,6 +93,7 @@ export class PoolsSubgraphRepository id: subgraphPool.id, name: subgraphPool.name || '', address: subgraphPool.address, + chainId: this.chainId, poolType: subgraphPool.poolType as PoolType, swapFee: subgraphPool.swapFee, swapEnabled: subgraphPool.swapEnabled, diff --git a/balancer-js/src/modules/pools/apr/apr.ts b/balancer-js/src/modules/pools/apr/apr.ts index 3462b2a76..3fc27c9d5 100644 --- a/balancer-js/src/modules/pools/apr/apr.ts +++ b/balancer-js/src/modules/pools/apr/apr.ts @@ -9,7 +9,7 @@ import type { TokenAttribute, LiquidityGauge, } from '@/types'; -import { BaseFeeDistributor } from '@/modules/data'; +import { BaseFeeDistributor, RewardData } from '@/modules/data'; import { ProtocolRevenue } from './protocol-revenue'; import { Liquidity } from '@/modules/liquidity/liquidity.module'; @@ -173,7 +173,8 @@ export class PoolApr { * @returns APR [bsp] from protocol rewards. */ async stakingApr(pool: Pool, boost = 1): Promise { - if (!this.liquidityGauges) { + // For L2s BAL is emmited as a reward token + if (!this.liquidityGauges || pool.chainId != 1) { return 0; } @@ -229,28 +230,7 @@ export class PoolApr { const rewards = rewardTokenAddresses.map(async (tAddress) => { /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ const data = gauge!.rewardTokens![tAddress]; - if (data.period_finish.toNumber() < Date.now() / 1000) { - return { - address: tAddress, - value: 0, - }; - } else { - const yearlyReward = data.rate.mul(86400).mul(365); - const price = await this.tokenPrices.find(tAddress); - if (price && price.usd) { - const meta = await this.tokenMeta.find(tAddress); - const decimals = meta?.decimals || 18; - const yearlyRewardUsd = - parseFloat(formatUnits(yearlyReward, decimals)) * - parseFloat(price.usd); - return { - address: tAddress, - value: yearlyRewardUsd, - }; - } else { - throw `No USD price for ${tAddress}`; - } - } + return this.rewardTokenApr(tAddress, data); }); // Get the gauge totalSupplyUsd @@ -397,4 +377,29 @@ export class PoolApr { return fee ? fee : 0; } + + private async rewardTokenApr(tokenAddress: string, rewardData: RewardData) { + if (rewardData.period_finish.toNumber() < Date.now() / 1000) { + return { + address: tokenAddress, + value: 0, + }; + } else { + const yearlyReward = rewardData.rate.mul(86400).mul(365); + const price = await this.tokenPrices.find(tokenAddress); + if (price && price.usd) { + const meta = await this.tokenMeta.find(tokenAddress); + const decimals = meta?.decimals || 18; + const yearlyRewardUsd = + parseFloat(formatUnits(yearlyReward, decimals)) * + parseFloat(price.usd); + return { + address: tokenAddress, + value: yearlyRewardUsd, + }; + } else { + throw `No USD price for ${tokenAddress}`; + } + } + } } diff --git a/balancer-js/src/test/factories/sdk.ts b/balancer-js/src/test/factories/sdk.ts index 138cd4d99..788518881 100644 --- a/balancer-js/src/test/factories/sdk.ts +++ b/balancer-js/src/test/factories/sdk.ts @@ -28,6 +28,7 @@ const poolFactory = Factory.define(({ params, afterBuild }) => { id: '0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e', name: 'Test Pool', address: '0xa6f548df93de924d73be7d25dc02554c6bd66db5', + chainId: 1, poolType: PoolType.Weighted, swapFee: '0.001', swapEnabled: true, diff --git a/balancer-js/src/types.ts b/balancer-js/src/types.ts index bbebae10a..35281359b 100644 --- a/balancer-js/src/types.ts +++ b/balancer-js/src/types.ts @@ -1,5 +1,5 @@ +import { Network } from './lib/constants/network'; import type { BigNumberish } from '@ethersproject/bignumber'; -import type { Network } from './lib/constants/network'; import type { Contract } from '@ethersproject/contracts'; import type { PoolDataService, TokenPriceService } from '@balancer-labs/sor'; import type { @@ -15,9 +15,9 @@ import type { } from '@/modules/data/types'; import type { BaseFeeDistributor } from './modules/data'; -export * from '@/modules/data/types'; import type { AprBreakdown } from '@/modules/pools/apr/apr'; -export { AprBreakdown }; +export * from '@/modules/data/types'; +export { Network, AprBreakdown }; export type Address = string; @@ -202,12 +202,15 @@ export enum PoolType { AaveLinear = 'AaveLinear', ERC4626Linear = 'ERC4626Linear', Element = 'Element', + Gyro2 = 'Gyro2', + Gyro3 = 'Gyro3', } export interface Pool { id: string; name: string; address: string; + chainId: number; poolType: PoolType; swapFee: string; owner?: string; From 03a2322c2a91862b47eff37a02e1ffd7543ffce5 Mon Sep 17 00:00:00 2001 From: bronco Date: Thu, 8 Sep 2022 14:08:31 +0200 Subject: [PATCH 58/65] perf: use totalSupply from gauges graph --- .../src/modules/data/liquidity-gauges/multicall.ts | 6 +----- .../src/modules/data/liquidity-gauges/provider.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/balancer-js/src/modules/data/liquidity-gauges/multicall.ts b/balancer-js/src/modules/data/liquidity-gauges/multicall.ts index b44569636..b3495b637 100644 --- a/balancer-js/src/modules/data/liquidity-gauges/multicall.ts +++ b/balancer-js/src/modules/data/liquidity-gauges/multicall.ts @@ -63,13 +63,9 @@ export class LiquidityGaugesMulticallRepository { async getWorkingSupplies( gaugeAddresses: string[] ): Promise<{ [gaugeAddress: string]: number }> { - let functionName = 'working_supply'; - if (this.chainId != 1) { - functionName = 'totalSupply'; - } const payload = gaugeAddresses.map((gaugeAddress) => [ gaugeAddress, - liquidityGaugeV5Interface.encodeFunctionData(functionName, []), + liquidityGaugeV5Interface.encodeFunctionData('working_supply', []), ]); const [, res] = await this.multicall.aggregate(payload); // Handle 0x diff --git a/balancer-js/src/modules/data/liquidity-gauges/provider.ts b/balancer-js/src/modules/data/liquidity-gauges/provider.ts index 2fa428e6c..cde794ef5 100644 --- a/balancer-js/src/modules/data/liquidity-gauges/provider.ts +++ b/balancer-js/src/modules/data/liquidity-gauges/provider.ts @@ -27,7 +27,6 @@ export class LiquidityGaugeSubgraphRPCProvider gaugeController?: GaugeControllerMulticallRepository; multicall: LiquidityGaugesMulticallRepository; subgraph: LiquidityGaugesSubgraphRepository; - totalSupplies: { [gaugeAddress: string]: number } = {}; workingSupplies: { [gaugeAddress: string]: number } = {}; relativeWeights: { [gaugeAddress: string]: number } = {}; rewardTokens: { @@ -38,7 +37,7 @@ export class LiquidityGaugeSubgraphRPCProvider subgraphUrl: string, multicallAddress: string, gaugeControllerAddress: string, - chainId: Network, + private chainId: Network, provider: Provider ) { if (gaugeControllerAddress) { @@ -59,11 +58,12 @@ export class LiquidityGaugeSubgraphRPCProvider async fetch(): Promise { const gauges = await this.subgraph.fetch(); const gaugeAddresses = gauges.map((g) => g.id); - this.totalSupplies = await this.multicall.getTotalSupplies(gaugeAddresses); - this.workingSupplies = await this.multicall.getWorkingSupplies( - gaugeAddresses - ); this.rewardTokens = await this.multicall.getRewardData(gaugeAddresses); + if (this.chainId == 1) { + this.workingSupplies = await this.multicall.getWorkingSupplies( + gaugeAddresses + ); + } if (this.gaugeController) { this.relativeWeights = await this.gaugeController.getRelativeWeights( gaugeAddresses @@ -118,7 +118,7 @@ export class LiquidityGaugeSubgraphRPCProvider name: subgraphGauge.symbol, poolId: subgraphGauge.poolId, poolAddress: subgraphGauge.poolAddress, - totalSupply: this.totalSupplies[subgraphGauge.id], + totalSupply: parseFloat(subgraphGauge.totalSupply), workingSupply: this.workingSupplies[subgraphGauge.id], relativeWeight: this.relativeWeights[subgraphGauge.id], rewardTokens: this.rewardTokens[subgraphGauge.id], From a4740f067e911bf56ebb5da419e7a54f1422e4be Mon Sep 17 00:00:00 2001 From: bronco Date: Thu, 8 Sep 2022 14:09:36 +0200 Subject: [PATCH 59/65] fix: query gauges only once --- .../modules/data/liquidity-gauges/provider.ts | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/balancer-js/src/modules/data/liquidity-gauges/provider.ts b/balancer-js/src/modules/data/liquidity-gauges/provider.ts index cde794ef5..30da2ea6c 100644 --- a/balancer-js/src/modules/data/liquidity-gauges/provider.ts +++ b/balancer-js/src/modules/data/liquidity-gauges/provider.ts @@ -32,6 +32,7 @@ export class LiquidityGaugeSubgraphRPCProvider rewardTokens: { [gaugeAddress: string]: { [tokenAddress: string]: RewardData }; } = {}; + gauges?: Promise; constructor( subgraphUrl: string, @@ -55,7 +56,8 @@ export class LiquidityGaugeSubgraphRPCProvider this.subgraph = new LiquidityGaugesSubgraphRepository(subgraphUrl); } - async fetch(): Promise { + async fetch(): Promise { + console.time('fetching liquidity gauges'); const gauges = await this.subgraph.fetch(); const gaugeAddresses = gauges.map((g) => g.id); this.rewardTokens = await this.multicall.getRewardData(gaugeAddresses); @@ -69,46 +71,40 @@ export class LiquidityGaugeSubgraphRPCProvider gaugeAddresses ); } + console.timeEnd('fetching liquidity gauges'); + return gauges.map(this.compose.bind(this)); } async find(id: string): Promise { - if (Object.keys(this.relativeWeights).length == 0) { - await this.fetch(); + if (!this.gauges) { + this.gauges = this.fetch(); } - const gauge = await this.subgraph.find(id); - if (!gauge) { - return; - } - - return this.compose(gauge); + return (await this.gauges).find((g) => g.id == id); } async findBy( attribute: string, value: string ): Promise { - if (Object.keys(this.relativeWeights).length == 0) { - await this.fetch(); + if (!this.gauges) { + this.gauges = this.fetch(); } - let gauge: SubgraphLiquidityGauge | undefined; + let gauge: LiquidityGauge | undefined; if (attribute == 'id') { return this.find(value); } else if (attribute == 'address') { return this.find(value); } else if (attribute == 'poolId') { - gauge = await this.subgraph.findBy('poolId', value); + gauge = (await this.gauges).find((g) => g.poolId == value); } else if (attribute == 'poolAddress') { - gauge = await this.subgraph.findBy('poolAddress', value); + gauge = (await this.gauges).find((g) => g.poolAddress == value); } else { throw `search by ${attribute} not implemented`; } - if (!gauge) { - return undefined; - } - return this.compose(gauge); + return gauge; } private compose(subgraphGauge: SubgraphLiquidityGauge) { From 394200ef31431e729a8141b1c281c895859291d2 Mon Sep 17 00:00:00 2001 From: bronco Date: Thu, 8 Sep 2022 14:13:46 +0200 Subject: [PATCH 60/65] refactor: use preferentialGauge in gauge queries --- .../modules/data/liquidity-gauges/subgraph.ts | 9 +- .../balancer-gauges/LiquidityGauges.graphql | 28 ++ .../generated/balancer-gauges.graphql | 443 +++++++++++++++++- .../subgraph/generated/balancer-gauges.ts | 422 ++++++++++++++++- .../balancer-subgraph-schema.graphql | 3 + .../generated/balancer-subgraph-types.ts | 2 + 6 files changed, 900 insertions(+), 7 deletions(-) diff --git a/balancer-js/src/modules/data/liquidity-gauges/subgraph.ts b/balancer-js/src/modules/data/liquidity-gauges/subgraph.ts index bcbfc6153..53e8a4e1f 100644 --- a/balancer-js/src/modules/data/liquidity-gauges/subgraph.ts +++ b/balancer-js/src/modules/data/liquidity-gauges/subgraph.ts @@ -21,9 +21,14 @@ export class LiquidityGaugesSubgraphRepository } async fetch(): Promise { - const queryResult = await this.client.LiquidityGauges(); + const queryResult = await this.client.Pools({ + where: { + preferentialGauge_not: null, + }, + }); + const qauges = queryResult.pools.map((pool) => pool.preferentialGauge); // TODO: optionally convert subgraph type to sdk internal type - this.gauges = queryResult.liquidityGauges as SubgraphLiquidityGauge[]; + this.gauges = qauges as SubgraphLiquidityGauge[]; return this.gauges; } diff --git a/balancer-js/src/modules/subgraph/balancer-gauges/LiquidityGauges.graphql b/balancer-js/src/modules/subgraph/balancer-gauges/LiquidityGauges.graphql index f0740ba26..7cf5a833c 100644 --- a/balancer-js/src/modules/subgraph/balancer-gauges/LiquidityGauges.graphql +++ b/balancer-js/src/modules/subgraph/balancer-gauges/LiquidityGauges.graphql @@ -36,3 +36,31 @@ fragment SubgraphLiquidityGauge on LiquidityGauge { totalDeposited } } + +query Pools( + $skip: Int + $first: Int + $orderBy: Pool_orderBy + $orderDirection: OrderDirection + $where: Pool_filter + $block: Block_height +) { + pools( + skip: $skip + first: $first + orderBy: $orderBy + orderDirection: $orderDirection + where: $where + block: $block + ) { + ...SubgraphPoolWithPreferentialGauge + } +} + +fragment SubgraphPoolWithPreferentialGauge on Pool { + id + poolId + preferentialGauge { + ...SubgraphLiquidityGauge + } +} diff --git a/balancer-js/src/modules/subgraph/generated/balancer-gauges.graphql b/balancer-js/src/modules/subgraph/generated/balancer-gauges.graphql index a3baa39f3..04a34ba61 100644 --- a/balancer-js/src/modules/subgraph/generated/balancer-gauges.graphql +++ b/balancer-js/src/modules/subgraph/generated/balancer-gauges.graphql @@ -34,14 +34,34 @@ enum Chain { } type Gauge { + """ Timestamp at which Balancer DAO added the gauge to GaugeController [seconds] + """ + addedTimestamp: Int! + + """ Address of the gauge """ address: Bytes! + + """ Equal to: - """ id: ID! + + """ Reference to LiquidityGauge """ + liquidityGauge: LiquidityGauge + + """ Reference to RootGauge """ + rootGauge: RootGauge + + """ Type of the gauge """ type: GaugeType! } type GaugeFactory { + """ List of gauges created through the factory """ gauges(first: Int = 100, orderBy: LiquidityGauge_orderBy, orderDirection: OrderDirection, skip: Int = 0, where: LiquidityGauge_filter): [LiquidityGauge!] + + """ Factory contract address """ id: ID! + + """ Number of gauges created through the factory """ numGauges: Int! } @@ -74,9 +94,16 @@ enum GaugeFactory_orderBy { } type GaugeShare { + """ User's balance of gauge deposit tokens """ balance: BigDecimal! + + """ Reference to LiquidityGauge entity """ gauge: LiquidityGauge! + + """ Equal to: - """ id: ID! + + """ Reference to User entity """ user: User! } @@ -151,7 +178,10 @@ enum GaugeShare_orderBy { } type GaugeType { + """ Type ID """ id: ID! + + """ Name of the type - empty string if call reverts """ name: String! } @@ -194,10 +224,19 @@ enum GaugeType_orderBy { } type GaugeVote { - gauge: LiquidityGauge! + """ Reference to Gauge entity """ + gauge: Gauge! + + """ Equal to: - """ id: ID! + + """ Timestamp at which user voted [seconds] """ timestamp: BigInt + + """ Reference to User entity """ user: User! + + """ Weight of veBAL power user has used to vote """ weight: BigDecimal } @@ -205,7 +244,7 @@ input GaugeVote_filter { """Filter for the block changed event.""" _change_block: BlockChangedFilter gauge: String - gauge_: LiquidityGauge_filter + gauge_: Gauge_filter gauge_contains: String gauge_contains_nocase: String gauge_ends_with: String @@ -283,6 +322,14 @@ enum GaugeVote_orderBy { input Gauge_filter { """Filter for the block changed event.""" _change_block: BlockChangedFilter + addedTimestamp: Int + addedTimestamp_gt: Int + addedTimestamp_gte: Int + addedTimestamp_in: [Int!] + addedTimestamp_lt: Int + addedTimestamp_lte: Int + addedTimestamp_not: Int + addedTimestamp_not_in: [Int!] address: Bytes address_contains: Bytes address_in: [Bytes!] @@ -297,6 +344,48 @@ input Gauge_filter { id_lte: ID id_not: ID id_not_in: [ID!] + liquidityGauge: String + liquidityGauge_: LiquidityGauge_filter + liquidityGauge_contains: String + liquidityGauge_contains_nocase: String + liquidityGauge_ends_with: String + liquidityGauge_ends_with_nocase: String + liquidityGauge_gt: String + liquidityGauge_gte: String + liquidityGauge_in: [String!] + liquidityGauge_lt: String + liquidityGauge_lte: String + liquidityGauge_not: String + liquidityGauge_not_contains: String + liquidityGauge_not_contains_nocase: String + liquidityGauge_not_ends_with: String + liquidityGauge_not_ends_with_nocase: String + liquidityGauge_not_in: [String!] + liquidityGauge_not_starts_with: String + liquidityGauge_not_starts_with_nocase: String + liquidityGauge_starts_with: String + liquidityGauge_starts_with_nocase: String + rootGauge: String + rootGauge_: RootGauge_filter + rootGauge_contains: String + rootGauge_contains_nocase: String + rootGauge_ends_with: String + rootGauge_ends_with_nocase: String + rootGauge_gt: String + rootGauge_gte: String + rootGauge_in: [String!] + rootGauge_lt: String + rootGauge_lte: String + rootGauge_not: String + rootGauge_not_contains: String + rootGauge_not_contains_nocase: String + rootGauge_not_ends_with: String + rootGauge_not_ends_with_nocase: String + rootGauge_not_in: [String!] + rootGauge_not_starts_with: String + rootGauge_not_starts_with_nocase: String + rootGauge_starts_with: String + rootGauge_starts_with_nocase: String type: String type_: GaugeType_filter type_contains: String @@ -321,20 +410,54 @@ input Gauge_filter { } enum Gauge_orderBy { + addedTimestamp address id + liquidityGauge + rootGauge type } type LiquidityGauge { + """ Factory contract address """ factory: GaugeFactory! + + """ Reference to Gauge entity - created when LiquidityGauge is added to GaugeController + """ + gauge: Gauge + + """ LiquidityGauge contract address """ id: ID! + + """ Whether Balancer DAO killed the gauge """ + isKilled: Boolean! + + """ Reference to Pool entity """ + pool: Pool! + + """ Address of the pool (lp_token of the gauge) """ poolAddress: Bytes! + + """ Pool ID if lp_token is a Balancer pool; null otherwise """ poolId: Bytes + + """ Relative weight cap of the gauge (0.01 = 1%) - V2 factories only """ + relativeWeightCap: BigDecimal + + """ List of user shares """ shares(first: Int = 100, orderBy: GaugeShare_orderBy, orderDirection: OrderDirection, skip: Int = 0, where: GaugeShare_filter): [GaugeShare!] + + """ Address of the contract that streams reward tokens to the gauge - ChildChainLiquidityGauge only + """ streamer: Bytes + + """ ERC20 token symbol """ symbol: String! + + """ List of reward tokens depositted in the gauge """ tokens(first: Int = 100, orderBy: RewardToken_orderBy, orderDirection: OrderDirection, skip: Int = 0, where: RewardToken_filter): [RewardToken!] + + """ Total of BPTs users have staked in the LiquidityGauge """ totalSupply: BigDecimal! } @@ -362,6 +485,27 @@ input LiquidityGauge_filter { factory_not_starts_with_nocase: String factory_starts_with: String factory_starts_with_nocase: String + gauge: String + gauge_: Gauge_filter + gauge_contains: String + gauge_contains_nocase: String + gauge_ends_with: String + gauge_ends_with_nocase: String + gauge_gt: String + gauge_gte: String + gauge_in: [String!] + gauge_lt: String + gauge_lte: String + gauge_not: String + gauge_not_contains: String + gauge_not_contains_nocase: String + gauge_not_ends_with: String + gauge_not_ends_with_nocase: String + gauge_not_in: [String!] + gauge_not_starts_with: String + gauge_not_starts_with_nocase: String + gauge_starts_with: String + gauge_starts_with_nocase: String id: ID id_gt: ID id_gte: ID @@ -370,6 +514,11 @@ input LiquidityGauge_filter { id_lte: ID id_not: ID id_not_in: [ID!] + isKilled: Boolean + isKilled_in: [Boolean!] + isKilled_not: Boolean + isKilled_not_in: [Boolean!] + pool: String poolAddress: Bytes poolAddress_contains: Bytes poolAddress_in: [Bytes!] @@ -382,6 +531,34 @@ input LiquidityGauge_filter { poolId_not: Bytes poolId_not_contains: Bytes poolId_not_in: [Bytes!] + pool_: Pool_filter + pool_contains: String + pool_contains_nocase: String + pool_ends_with: String + pool_ends_with_nocase: String + pool_gt: String + pool_gte: String + pool_in: [String!] + pool_lt: String + pool_lte: String + pool_not: String + pool_not_contains: String + pool_not_contains_nocase: String + pool_not_ends_with: String + pool_not_ends_with_nocase: String + pool_not_in: [String!] + pool_not_starts_with: String + pool_not_starts_with_nocase: String + pool_starts_with: String + pool_starts_with_nocase: String + relativeWeightCap: BigDecimal + relativeWeightCap_gt: BigDecimal + relativeWeightCap_gte: BigDecimal + relativeWeightCap_in: [BigDecimal!] + relativeWeightCap_lt: BigDecimal + relativeWeightCap_lte: BigDecimal + relativeWeightCap_not: BigDecimal + relativeWeightCap_not_in: [BigDecimal!] shares_: GaugeShare_filter streamer: Bytes streamer_contains: Bytes @@ -422,9 +599,13 @@ input LiquidityGauge_filter { enum LiquidityGauge_orderBy { factory + gauge id + isKilled + pool poolAddress poolId + relativeWeightCap shares streamer symbol @@ -438,6 +619,88 @@ enum OrderDirection { desc } +type Pool { + """ Address of the pool (lp_token of the gauge) """ + address: Bytes! + + """ List of gauges created for the pool """ + gauges(first: Int = 100, orderBy: LiquidityGauge_orderBy, orderDirection: OrderDirection, skip: Int = 0, where: LiquidityGauge_filter): [LiquidityGauge!] + + """ List of the pool's gauges addresses """ + gaugesList: [Bytes!]! + + """ Address of the pool (lp_token of the gauge) """ + id: ID! + + """ Pool ID if lp_token is a Balancer pool; null otherwise """ + poolId: Bytes + + """ Most recent, unkilled gauge in the GaugeController """ + preferentialGauge: LiquidityGauge +} + +input Pool_filter { + """Filter for the block changed event.""" + _change_block: BlockChangedFilter + address: Bytes + address_contains: Bytes + address_in: [Bytes!] + address_not: Bytes + address_not_contains: Bytes + address_not_in: [Bytes!] + gaugesList: [Bytes!] + gaugesList_contains: [Bytes!] + gaugesList_contains_nocase: [Bytes!] + gaugesList_not: [Bytes!] + gaugesList_not_contains: [Bytes!] + gaugesList_not_contains_nocase: [Bytes!] + gauges_: LiquidityGauge_filter + id: ID + id_gt: ID + id_gte: ID + id_in: [ID!] + id_lt: ID + id_lte: ID + id_not: ID + id_not_in: [ID!] + poolId: Bytes + poolId_contains: Bytes + poolId_in: [Bytes!] + poolId_not: Bytes + poolId_not_contains: Bytes + poolId_not_in: [Bytes!] + preferentialGauge: String + preferentialGauge_: LiquidityGauge_filter + preferentialGauge_contains: String + preferentialGauge_contains_nocase: String + preferentialGauge_ends_with: String + preferentialGauge_ends_with_nocase: String + preferentialGauge_gt: String + preferentialGauge_gte: String + preferentialGauge_in: [String!] + preferentialGauge_lt: String + preferentialGauge_lte: String + preferentialGauge_not: String + preferentialGauge_not_contains: String + preferentialGauge_not_contains_nocase: String + preferentialGauge_not_ends_with: String + preferentialGauge_not_ends_with_nocase: String + preferentialGauge_not_in: [String!] + preferentialGauge_not_starts_with: String + preferentialGauge_not_starts_with_nocase: String + preferentialGauge_starts_with: String + preferentialGauge_starts_with_nocase: String +} + +enum Pool_orderBy { + address + gauges + gaugesList + id + poolId + preferentialGauge +} + type Query { """Access to subgraph metadata""" _meta(block: Block_height): _Meta_ @@ -609,6 +872,34 @@ type Query { subgraphError: _SubgraphErrorPolicy_! = deny where: LiquidityGauge_filter ): [LiquidityGauge!]! + pool( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): Pool + pools( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: Pool_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: Pool_filter + ): [Pool!]! rewardToken( """ The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. @@ -752,10 +1043,19 @@ type Query { } type RewardToken { + """ ERC20 token decimals - zero if call to decimals() reverts """ decimals: Int! + + """ Reference to LiquidityGauge entity """ gauge: LiquidityGauge! + + """ Equal to: - """ id: ID! + + """ ERC20 token symbol - empty string if call to symbol() reverts """ symbol: String! + + """ Amount of reward tokens that has been deposited into the gauge """ totalDeposited: BigDecimal! } @@ -838,9 +1138,27 @@ enum RewardToken_orderBy { } type RootGauge { + """ Chain where emissions by this gauge will be bridged to """ chain: Chain! + + """ Factory contract address """ + factory: GaugeFactory! + + """ Reference to Gauge entity - created when LiquidityGauge is added to GaugeController + """ + gauge: Gauge + + """ RootGauge contract address""" id: ID! + + """ Whether Balancer DAO killed the gauge """ + isKilled: Boolean! + + """ Address where emissions by this gauge will be bridged to """ recipient: Bytes! + + """ Relative weight cap of the gauge (0.01 = 1%) - V2 factories only """ + relativeWeightCap: BigDecimal } input RootGauge_filter { @@ -850,6 +1168,48 @@ input RootGauge_filter { chain_in: [Chain!] chain_not: Chain chain_not_in: [Chain!] + factory: String + factory_: GaugeFactory_filter + factory_contains: String + factory_contains_nocase: String + factory_ends_with: String + factory_ends_with_nocase: String + factory_gt: String + factory_gte: String + factory_in: [String!] + factory_lt: String + factory_lte: String + factory_not: String + factory_not_contains: String + factory_not_contains_nocase: String + factory_not_ends_with: String + factory_not_ends_with_nocase: String + factory_not_in: [String!] + factory_not_starts_with: String + factory_not_starts_with_nocase: String + factory_starts_with: String + factory_starts_with_nocase: String + gauge: String + gauge_: Gauge_filter + gauge_contains: String + gauge_contains_nocase: String + gauge_ends_with: String + gauge_ends_with_nocase: String + gauge_gt: String + gauge_gte: String + gauge_in: [String!] + gauge_lt: String + gauge_lte: String + gauge_not: String + gauge_not_contains: String + gauge_not_contains_nocase: String + gauge_not_ends_with: String + gauge_not_ends_with_nocase: String + gauge_not_in: [String!] + gauge_not_starts_with: String + gauge_not_starts_with_nocase: String + gauge_starts_with: String + gauge_starts_with_nocase: String id: ID id_gt: ID id_gte: ID @@ -858,18 +1218,34 @@ input RootGauge_filter { id_lte: ID id_not: ID id_not_in: [ID!] + isKilled: Boolean + isKilled_in: [Boolean!] + isKilled_not: Boolean + isKilled_not_in: [Boolean!] recipient: Bytes recipient_contains: Bytes recipient_in: [Bytes!] recipient_not: Bytes recipient_not_contains: Bytes recipient_not_in: [Bytes!] + relativeWeightCap: BigDecimal + relativeWeightCap_gt: BigDecimal + relativeWeightCap_gte: BigDecimal + relativeWeightCap_in: [BigDecimal!] + relativeWeightCap_lt: BigDecimal + relativeWeightCap_lte: BigDecimal + relativeWeightCap_not: BigDecimal + relativeWeightCap_not_in: [BigDecimal!] } enum RootGauge_orderBy { chain + factory + gauge id + isKilled recipient + relativeWeightCap } type Subscription { @@ -1043,6 +1419,34 @@ type Subscription { subgraphError: _SubgraphErrorPolicy_! = deny where: LiquidityGauge_filter ): [LiquidityGauge!]! + pool( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + id: ID! + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): Pool + pools( + """ + The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. + """ + block: Block_height + first: Int = 100 + orderBy: Pool_orderBy + orderDirection: OrderDirection + skip: Int = 0 + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + where: Pool_filter + ): [Pool!]! rewardToken( """ The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the subgraph has progressed to or past the minimum block number. Defaults to the latest block when omitted. @@ -1186,9 +1590,16 @@ type Subscription { } type User { + """ List of gauge the user has shares """ gaugeShares(first: Int = 100, orderBy: GaugeShare_orderBy, orderDirection: OrderDirection, skip: Int = 0, where: GaugeShare_filter): [GaugeShare!] + + """ List of votes on gauges """ gaugeVotes(first: Int = 100, orderBy: GaugeVote_orderBy, orderDirection: OrderDirection, skip: Int = 0, where: GaugeVote_filter): [GaugeVote!] + + """ User address """ id: ID! + + """ List of locks the user created """ votingLocks(first: Int = 100, orderBy: VotingEscrowLock_orderBy, orderDirection: OrderDirection, skip: Int = 0, where: VotingEscrowLock_filter): [VotingEscrowLock!] } @@ -1216,16 +1627,32 @@ enum User_orderBy { } type VotingEscrow { + """ VotingEscrow contract address """ id: ID! + + """ List of veBAL locks created """ locks(first: Int = 100, orderBy: VotingEscrowLock_orderBy, orderDirection: OrderDirection, skip: Int = 0, where: VotingEscrowLock_filter): [VotingEscrowLock!] + + """ Amount of B-80BAL-20WETH BPT locked """ stakedSupply: BigDecimal! } type VotingEscrowLock { + """ Equal to: - """ id: ID! + + """ Amount of B-80BAL-20WETH BPT the user has locked """ lockedBalance: BigDecimal! + + """ Timestamp at which B-80BAL-20WETH BPT can be unlocked by user [seconds] + """ unlockTime: BigInt + updatedAt: Int! + + """ Reference to User entity """ user: User! + + """ Reference to VotingEscrow entity """ votingEscrowID: VotingEscrow! } @@ -1256,6 +1683,14 @@ input VotingEscrowLock_filter { unlockTime_lte: BigInt unlockTime_not: BigInt unlockTime_not_in: [BigInt!] + updatedAt: Int + updatedAt_gt: Int + updatedAt_gte: Int + updatedAt_in: [Int!] + updatedAt_lt: Int + updatedAt_lte: Int + updatedAt_not: Int + updatedAt_not_in: [Int!] user: String user_: User_filter user_contains: String @@ -1304,6 +1739,7 @@ enum VotingEscrowLock_orderBy { id lockedBalance unlockTime + updatedAt user votingEscrowID } @@ -1342,6 +1778,9 @@ type _Block_ { """The block number""" number: Int! + + """Timestamp of the block if available, format depends on the chain""" + timestamp: String } """The type for the top-level _meta field""" diff --git a/balancer-js/src/modules/subgraph/generated/balancer-gauges.ts b/balancer-js/src/modules/subgraph/generated/balancer-gauges.ts index b986c9ed6..c8d9ccac0 100644 --- a/balancer-js/src/modules/subgraph/generated/balancer-gauges.ts +++ b/balancer-js/src/modules/subgraph/generated/balancer-gauges.ts @@ -36,15 +36,27 @@ export enum Chain { export type Gauge = { __typename?: 'Gauge'; + /** Timestamp at which Balancer DAO added the gauge to GaugeController [seconds] */ + addedTimestamp: Scalars['Int']; + /** Address of the gauge */ address: Scalars['Bytes']; + /** Equal to: - */ id: Scalars['ID']; + /** Reference to LiquidityGauge */ + liquidityGauge?: Maybe; + /** Reference to RootGauge */ + rootGauge?: Maybe; + /** Type of the gauge */ type: GaugeType; }; export type GaugeFactory = { __typename?: 'GaugeFactory'; + /** List of gauges created through the factory */ gauges?: Maybe>; + /** Factory contract address */ id: Scalars['ID']; + /** Number of gauges created through the factory */ numGauges: Scalars['Int']; }; @@ -87,9 +99,13 @@ export enum GaugeFactory_OrderBy { export type GaugeShare = { __typename?: 'GaugeShare'; + /** User's balance of gauge deposit tokens */ balance: Scalars['BigDecimal']; + /** Reference to LiquidityGauge entity */ gauge: LiquidityGauge; + /** Equal to: - */ id: Scalars['ID']; + /** Reference to User entity */ user: User; }; @@ -165,7 +181,9 @@ export enum GaugeShare_OrderBy { export type GaugeType = { __typename?: 'GaugeType'; + /** Type ID */ id: Scalars['ID']; + /** Name of the type - empty string if call reverts */ name: Scalars['String']; }; @@ -209,10 +227,15 @@ export enum GaugeType_OrderBy { export type GaugeVote = { __typename?: 'GaugeVote'; - gauge: LiquidityGauge; + /** Reference to Gauge entity */ + gauge: Gauge; + /** Equal to: - */ id: Scalars['ID']; + /** Timestamp at which user voted [seconds] */ timestamp?: Maybe; + /** Reference to User entity */ user: User; + /** Weight of veBAL power user has used to vote */ weight?: Maybe; }; @@ -220,7 +243,7 @@ export type GaugeVote_Filter = { /** Filter for the block changed event. */ _change_block?: InputMaybe; gauge?: InputMaybe; - gauge_?: InputMaybe; + gauge_?: InputMaybe; gauge_contains?: InputMaybe; gauge_contains_nocase?: InputMaybe; gauge_ends_with?: InputMaybe; @@ -298,6 +321,14 @@ export enum GaugeVote_OrderBy { export type Gauge_Filter = { /** Filter for the block changed event. */ _change_block?: InputMaybe; + addedTimestamp?: InputMaybe; + addedTimestamp_gt?: InputMaybe; + addedTimestamp_gte?: InputMaybe; + addedTimestamp_in?: InputMaybe>; + addedTimestamp_lt?: InputMaybe; + addedTimestamp_lte?: InputMaybe; + addedTimestamp_not?: InputMaybe; + addedTimestamp_not_in?: InputMaybe>; address?: InputMaybe; address_contains?: InputMaybe; address_in?: InputMaybe>; @@ -312,6 +343,48 @@ export type Gauge_Filter = { id_lte?: InputMaybe; id_not?: InputMaybe; id_not_in?: InputMaybe>; + liquidityGauge?: InputMaybe; + liquidityGauge_?: InputMaybe; + liquidityGauge_contains?: InputMaybe; + liquidityGauge_contains_nocase?: InputMaybe; + liquidityGauge_ends_with?: InputMaybe; + liquidityGauge_ends_with_nocase?: InputMaybe; + liquidityGauge_gt?: InputMaybe; + liquidityGauge_gte?: InputMaybe; + liquidityGauge_in?: InputMaybe>; + liquidityGauge_lt?: InputMaybe; + liquidityGauge_lte?: InputMaybe; + liquidityGauge_not?: InputMaybe; + liquidityGauge_not_contains?: InputMaybe; + liquidityGauge_not_contains_nocase?: InputMaybe; + liquidityGauge_not_ends_with?: InputMaybe; + liquidityGauge_not_ends_with_nocase?: InputMaybe; + liquidityGauge_not_in?: InputMaybe>; + liquidityGauge_not_starts_with?: InputMaybe; + liquidityGauge_not_starts_with_nocase?: InputMaybe; + liquidityGauge_starts_with?: InputMaybe; + liquidityGauge_starts_with_nocase?: InputMaybe; + rootGauge?: InputMaybe; + rootGauge_?: InputMaybe; + rootGauge_contains?: InputMaybe; + rootGauge_contains_nocase?: InputMaybe; + rootGauge_ends_with?: InputMaybe; + rootGauge_ends_with_nocase?: InputMaybe; + rootGauge_gt?: InputMaybe; + rootGauge_gte?: InputMaybe; + rootGauge_in?: InputMaybe>; + rootGauge_lt?: InputMaybe; + rootGauge_lte?: InputMaybe; + rootGauge_not?: InputMaybe; + rootGauge_not_contains?: InputMaybe; + rootGauge_not_contains_nocase?: InputMaybe; + rootGauge_not_ends_with?: InputMaybe; + rootGauge_not_ends_with_nocase?: InputMaybe; + rootGauge_not_in?: InputMaybe>; + rootGauge_not_starts_with?: InputMaybe; + rootGauge_not_starts_with_nocase?: InputMaybe; + rootGauge_starts_with?: InputMaybe; + rootGauge_starts_with_nocase?: InputMaybe; type?: InputMaybe; type_?: InputMaybe; type_contains?: InputMaybe; @@ -336,21 +409,41 @@ export type Gauge_Filter = { }; export enum Gauge_OrderBy { + AddedTimestamp = 'addedTimestamp', Address = 'address', Id = 'id', + LiquidityGauge = 'liquidityGauge', + RootGauge = 'rootGauge', Type = 'type' } export type LiquidityGauge = { __typename?: 'LiquidityGauge'; + /** Factory contract address */ factory: GaugeFactory; + /** Reference to Gauge entity - created when LiquidityGauge is added to GaugeController */ + gauge?: Maybe; + /** LiquidityGauge contract address */ id: Scalars['ID']; + /** Whether Balancer DAO killed the gauge */ + isKilled: Scalars['Boolean']; + /** Reference to Pool entity */ + pool: Pool; + /** Address of the pool (lp_token of the gauge) */ poolAddress: Scalars['Bytes']; + /** Pool ID if lp_token is a Balancer pool; null otherwise */ poolId?: Maybe; + /** Relative weight cap of the gauge (0.01 = 1%) - V2 factories only */ + relativeWeightCap?: Maybe; + /** List of user shares */ shares?: Maybe>; + /** Address of the contract that streams reward tokens to the gauge - ChildChainLiquidityGauge only */ streamer?: Maybe; + /** ERC20 token symbol */ symbol: Scalars['String']; + /** List of reward tokens depositted in the gauge */ tokens?: Maybe>; + /** Total of BPTs users have staked in the LiquidityGauge */ totalSupply: Scalars['BigDecimal']; }; @@ -396,6 +489,27 @@ export type LiquidityGauge_Filter = { factory_not_starts_with_nocase?: InputMaybe; factory_starts_with?: InputMaybe; factory_starts_with_nocase?: InputMaybe; + gauge?: InputMaybe; + gauge_?: InputMaybe; + gauge_contains?: InputMaybe; + gauge_contains_nocase?: InputMaybe; + gauge_ends_with?: InputMaybe; + gauge_ends_with_nocase?: InputMaybe; + gauge_gt?: InputMaybe; + gauge_gte?: InputMaybe; + gauge_in?: InputMaybe>; + gauge_lt?: InputMaybe; + gauge_lte?: InputMaybe; + gauge_not?: InputMaybe; + gauge_not_contains?: InputMaybe; + gauge_not_contains_nocase?: InputMaybe; + gauge_not_ends_with?: InputMaybe; + gauge_not_ends_with_nocase?: InputMaybe; + gauge_not_in?: InputMaybe>; + gauge_not_starts_with?: InputMaybe; + gauge_not_starts_with_nocase?: InputMaybe; + gauge_starts_with?: InputMaybe; + gauge_starts_with_nocase?: InputMaybe; id?: InputMaybe; id_gt?: InputMaybe; id_gte?: InputMaybe; @@ -404,6 +518,11 @@ export type LiquidityGauge_Filter = { id_lte?: InputMaybe; id_not?: InputMaybe; id_not_in?: InputMaybe>; + isKilled?: InputMaybe; + isKilled_in?: InputMaybe>; + isKilled_not?: InputMaybe; + isKilled_not_in?: InputMaybe>; + pool?: InputMaybe; poolAddress?: InputMaybe; poolAddress_contains?: InputMaybe; poolAddress_in?: InputMaybe>; @@ -416,6 +535,34 @@ export type LiquidityGauge_Filter = { poolId_not?: InputMaybe; poolId_not_contains?: InputMaybe; poolId_not_in?: InputMaybe>; + pool_?: InputMaybe; + pool_contains?: InputMaybe; + pool_contains_nocase?: InputMaybe; + pool_ends_with?: InputMaybe; + pool_ends_with_nocase?: InputMaybe; + pool_gt?: InputMaybe; + pool_gte?: InputMaybe; + pool_in?: InputMaybe>; + pool_lt?: InputMaybe; + pool_lte?: InputMaybe; + pool_not?: InputMaybe; + pool_not_contains?: InputMaybe; + pool_not_contains_nocase?: InputMaybe; + pool_not_ends_with?: InputMaybe; + pool_not_ends_with_nocase?: InputMaybe; + pool_not_in?: InputMaybe>; + pool_not_starts_with?: InputMaybe; + pool_not_starts_with_nocase?: InputMaybe; + pool_starts_with?: InputMaybe; + pool_starts_with_nocase?: InputMaybe; + relativeWeightCap?: InputMaybe; + relativeWeightCap_gt?: InputMaybe; + relativeWeightCap_gte?: InputMaybe; + relativeWeightCap_in?: InputMaybe>; + relativeWeightCap_lt?: InputMaybe; + relativeWeightCap_lte?: InputMaybe; + relativeWeightCap_not?: InputMaybe; + relativeWeightCap_not_in?: InputMaybe>; shares_?: InputMaybe; streamer?: InputMaybe; streamer_contains?: InputMaybe; @@ -456,9 +603,13 @@ export type LiquidityGauge_Filter = { export enum LiquidityGauge_OrderBy { Factory = 'factory', + Gauge = 'gauge', Id = 'id', + IsKilled = 'isKilled', + Pool = 'pool', PoolAddress = 'poolAddress', PoolId = 'poolId', + RelativeWeightCap = 'relativeWeightCap', Shares = 'shares', Streamer = 'streamer', Symbol = 'symbol', @@ -472,6 +623,93 @@ export enum OrderDirection { Desc = 'desc' } +export type Pool = { + __typename?: 'Pool'; + /** Address of the pool (lp_token of the gauge) */ + address: Scalars['Bytes']; + /** List of gauges created for the pool */ + gauges?: Maybe>; + /** List of the pool's gauges addresses */ + gaugesList: Array; + /** Address of the pool (lp_token of the gauge) */ + id: Scalars['ID']; + /** Pool ID if lp_token is a Balancer pool; null otherwise */ + poolId?: Maybe; + /** Most recent, unkilled gauge in the GaugeController */ + preferentialGauge?: Maybe; +}; + + +export type PoolGaugesArgs = { + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + where?: InputMaybe; +}; + +export type Pool_Filter = { + /** Filter for the block changed event. */ + _change_block?: InputMaybe; + address?: InputMaybe; + address_contains?: InputMaybe; + address_in?: InputMaybe>; + address_not?: InputMaybe; + address_not_contains?: InputMaybe; + address_not_in?: InputMaybe>; + gaugesList?: InputMaybe>; + gaugesList_contains?: InputMaybe>; + gaugesList_contains_nocase?: InputMaybe>; + gaugesList_not?: InputMaybe>; + gaugesList_not_contains?: InputMaybe>; + gaugesList_not_contains_nocase?: InputMaybe>; + gauges_?: InputMaybe; + id?: InputMaybe; + id_gt?: InputMaybe; + id_gte?: InputMaybe; + id_in?: InputMaybe>; + id_lt?: InputMaybe; + id_lte?: InputMaybe; + id_not?: InputMaybe; + id_not_in?: InputMaybe>; + poolId?: InputMaybe; + poolId_contains?: InputMaybe; + poolId_in?: InputMaybe>; + poolId_not?: InputMaybe; + poolId_not_contains?: InputMaybe; + poolId_not_in?: InputMaybe>; + preferentialGauge?: InputMaybe; + preferentialGauge_?: InputMaybe; + preferentialGauge_contains?: InputMaybe; + preferentialGauge_contains_nocase?: InputMaybe; + preferentialGauge_ends_with?: InputMaybe; + preferentialGauge_ends_with_nocase?: InputMaybe; + preferentialGauge_gt?: InputMaybe; + preferentialGauge_gte?: InputMaybe; + preferentialGauge_in?: InputMaybe>; + preferentialGauge_lt?: InputMaybe; + preferentialGauge_lte?: InputMaybe; + preferentialGauge_not?: InputMaybe; + preferentialGauge_not_contains?: InputMaybe; + preferentialGauge_not_contains_nocase?: InputMaybe; + preferentialGauge_not_ends_with?: InputMaybe; + preferentialGauge_not_ends_with_nocase?: InputMaybe; + preferentialGauge_not_in?: InputMaybe>; + preferentialGauge_not_starts_with?: InputMaybe; + preferentialGauge_not_starts_with_nocase?: InputMaybe; + preferentialGauge_starts_with?: InputMaybe; + preferentialGauge_starts_with_nocase?: InputMaybe; +}; + +export enum Pool_OrderBy { + Address = 'address', + Gauges = 'gauges', + GaugesList = 'gaugesList', + Id = 'id', + PoolId = 'poolId', + PreferentialGauge = 'preferentialGauge' +} + export type Query = { __typename?: 'Query'; /** Access to subgraph metadata */ @@ -488,6 +726,8 @@ export type Query = { gauges: Array; liquidityGauge?: Maybe; liquidityGauges: Array; + pool?: Maybe; + pools: Array; rewardToken?: Maybe; rewardTokens: Array; rootGauge?: Maybe; @@ -614,6 +854,24 @@ export type QueryLiquidityGaugesArgs = { }; +export type QueryPoolArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type QueryPoolsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + export type QueryRewardTokenArgs = { block?: InputMaybe; id: Scalars['ID']; @@ -705,10 +963,15 @@ export type QueryVotingEscrowsArgs = { export type RewardToken = { __typename?: 'RewardToken'; + /** ERC20 token decimals - zero if call to decimals() reverts */ decimals: Scalars['Int']; + /** Reference to LiquidityGauge entity */ gauge: LiquidityGauge; + /** Equal to: - */ id: Scalars['ID']; + /** ERC20 token symbol - empty string if call to symbol() reverts */ symbol: Scalars['String']; + /** Amount of reward tokens that has been deposited into the gauge */ totalDeposited: Scalars['BigDecimal']; }; @@ -792,9 +1055,20 @@ export enum RewardToken_OrderBy { export type RootGauge = { __typename?: 'RootGauge'; + /** Chain where emissions by this gauge will be bridged to */ chain: Chain; + /** Factory contract address */ + factory: GaugeFactory; + /** Reference to Gauge entity - created when LiquidityGauge is added to GaugeController */ + gauge?: Maybe; + /** RootGauge contract address */ id: Scalars['ID']; + /** Whether Balancer DAO killed the gauge */ + isKilled: Scalars['Boolean']; + /** Address where emissions by this gauge will be bridged to */ recipient: Scalars['Bytes']; + /** Relative weight cap of the gauge (0.01 = 1%) - V2 factories only */ + relativeWeightCap?: Maybe; }; export type RootGauge_Filter = { @@ -804,6 +1078,48 @@ export type RootGauge_Filter = { chain_in?: InputMaybe>; chain_not?: InputMaybe; chain_not_in?: InputMaybe>; + factory?: InputMaybe; + factory_?: InputMaybe; + factory_contains?: InputMaybe; + factory_contains_nocase?: InputMaybe; + factory_ends_with?: InputMaybe; + factory_ends_with_nocase?: InputMaybe; + factory_gt?: InputMaybe; + factory_gte?: InputMaybe; + factory_in?: InputMaybe>; + factory_lt?: InputMaybe; + factory_lte?: InputMaybe; + factory_not?: InputMaybe; + factory_not_contains?: InputMaybe; + factory_not_contains_nocase?: InputMaybe; + factory_not_ends_with?: InputMaybe; + factory_not_ends_with_nocase?: InputMaybe; + factory_not_in?: InputMaybe>; + factory_not_starts_with?: InputMaybe; + factory_not_starts_with_nocase?: InputMaybe; + factory_starts_with?: InputMaybe; + factory_starts_with_nocase?: InputMaybe; + gauge?: InputMaybe; + gauge_?: InputMaybe; + gauge_contains?: InputMaybe; + gauge_contains_nocase?: InputMaybe; + gauge_ends_with?: InputMaybe; + gauge_ends_with_nocase?: InputMaybe; + gauge_gt?: InputMaybe; + gauge_gte?: InputMaybe; + gauge_in?: InputMaybe>; + gauge_lt?: InputMaybe; + gauge_lte?: InputMaybe; + gauge_not?: InputMaybe; + gauge_not_contains?: InputMaybe; + gauge_not_contains_nocase?: InputMaybe; + gauge_not_ends_with?: InputMaybe; + gauge_not_ends_with_nocase?: InputMaybe; + gauge_not_in?: InputMaybe>; + gauge_not_starts_with?: InputMaybe; + gauge_not_starts_with_nocase?: InputMaybe; + gauge_starts_with?: InputMaybe; + gauge_starts_with_nocase?: InputMaybe; id?: InputMaybe; id_gt?: InputMaybe; id_gte?: InputMaybe; @@ -812,18 +1128,34 @@ export type RootGauge_Filter = { id_lte?: InputMaybe; id_not?: InputMaybe; id_not_in?: InputMaybe>; + isKilled?: InputMaybe; + isKilled_in?: InputMaybe>; + isKilled_not?: InputMaybe; + isKilled_not_in?: InputMaybe>; recipient?: InputMaybe; recipient_contains?: InputMaybe; recipient_in?: InputMaybe>; recipient_not?: InputMaybe; recipient_not_contains?: InputMaybe; recipient_not_in?: InputMaybe>; + relativeWeightCap?: InputMaybe; + relativeWeightCap_gt?: InputMaybe; + relativeWeightCap_gte?: InputMaybe; + relativeWeightCap_in?: InputMaybe>; + relativeWeightCap_lt?: InputMaybe; + relativeWeightCap_lte?: InputMaybe; + relativeWeightCap_not?: InputMaybe; + relativeWeightCap_not_in?: InputMaybe>; }; export enum RootGauge_OrderBy { Chain = 'chain', + Factory = 'factory', + Gauge = 'gauge', Id = 'id', - Recipient = 'recipient' + IsKilled = 'isKilled', + Recipient = 'recipient', + RelativeWeightCap = 'relativeWeightCap' } export type Subscription = { @@ -842,6 +1174,8 @@ export type Subscription = { gauges: Array; liquidityGauge?: Maybe; liquidityGauges: Array; + pool?: Maybe; + pools: Array; rewardToken?: Maybe; rewardTokens: Array; rootGauge?: Maybe; @@ -968,6 +1302,24 @@ export type SubscriptionLiquidityGaugesArgs = { }; +export type SubscriptionPoolArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + + +export type SubscriptionPoolsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + + export type SubscriptionRewardTokenArgs = { block?: InputMaybe; id: Scalars['ID']; @@ -1059,9 +1411,13 @@ export type SubscriptionVotingEscrowsArgs = { export type User = { __typename?: 'User'; + /** List of gauge the user has shares */ gaugeShares?: Maybe>; + /** List of votes on gauges */ gaugeVotes?: Maybe>; + /** User address */ id: Scalars['ID']; + /** List of locks the user created */ votingLocks?: Maybe>; }; @@ -1117,8 +1473,11 @@ export enum User_OrderBy { export type VotingEscrow = { __typename?: 'VotingEscrow'; + /** VotingEscrow contract address */ id: Scalars['ID']; + /** List of veBAL locks created */ locks?: Maybe>; + /** Amount of B-80BAL-20WETH BPT locked */ stakedSupply: Scalars['BigDecimal']; }; @@ -1133,10 +1492,16 @@ export type VotingEscrowLocksArgs = { export type VotingEscrowLock = { __typename?: 'VotingEscrowLock'; + /** Equal to: - */ id: Scalars['ID']; + /** Amount of B-80BAL-20WETH BPT the user has locked */ lockedBalance: Scalars['BigDecimal']; + /** Timestamp at which B-80BAL-20WETH BPT can be unlocked by user [seconds] */ unlockTime?: Maybe; + updatedAt: Scalars['Int']; + /** Reference to User entity */ user: User; + /** Reference to VotingEscrow entity */ votingEscrowID: VotingEscrow; }; @@ -1167,6 +1532,14 @@ export type VotingEscrowLock_Filter = { unlockTime_lte?: InputMaybe; unlockTime_not?: InputMaybe; unlockTime_not_in?: InputMaybe>; + updatedAt?: InputMaybe; + updatedAt_gt?: InputMaybe; + updatedAt_gte?: InputMaybe; + updatedAt_in?: InputMaybe>; + updatedAt_lt?: InputMaybe; + updatedAt_lte?: InputMaybe; + updatedAt_not?: InputMaybe; + updatedAt_not_in?: InputMaybe>; user?: InputMaybe; user_?: InputMaybe; user_contains?: InputMaybe; @@ -1215,6 +1588,7 @@ export enum VotingEscrowLock_OrderBy { Id = 'id', LockedBalance = 'lockedBalance', UnlockTime = 'unlockTime', + UpdatedAt = 'updatedAt', User = 'user', VotingEscrowId = 'votingEscrowID' } @@ -1253,6 +1627,8 @@ export type _Block_ = { hash?: Maybe; /** The block number */ number: Scalars['Int']; + /** Timestamp of the block if available, format depends on the chain */ + timestamp?: Maybe; }; /** The type for the top-level _meta field */ @@ -1293,6 +1669,20 @@ export type LiquidityGaugesQuery = { __typename?: 'Query', liquidityGauges: Arra export type SubgraphLiquidityGaugeFragment = { __typename?: 'LiquidityGauge', id: string, symbol: string, poolAddress: string, poolId?: string | null, streamer?: string | null, totalSupply: string, factory: { __typename?: 'GaugeFactory', id: string, numGauges: number }, tokens?: Array<{ __typename?: 'RewardToken', id: string, symbol: string, decimals: number, totalDeposited: string }> | null }; +export type PoolsQueryVariables = Exact<{ + skip?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + where?: InputMaybe; + block?: InputMaybe; +}>; + + +export type PoolsQuery = { __typename?: 'Query', pools: Array<{ __typename?: 'Pool', id: string, poolId?: string | null, preferentialGauge?: { __typename?: 'LiquidityGauge', id: string, symbol: string, poolAddress: string, poolId?: string | null, streamer?: string | null, totalSupply: string, factory: { __typename?: 'GaugeFactory', id: string, numGauges: number }, tokens?: Array<{ __typename?: 'RewardToken', id: string, symbol: string, decimals: number, totalDeposited: string }> | null } | null }> }; + +export type SubgraphPoolWithPreferentialGaugeFragment = { __typename?: 'Pool', id: string, poolId?: string | null, preferentialGauge?: { __typename?: 'LiquidityGauge', id: string, symbol: string, poolAddress: string, poolId?: string | null, streamer?: string | null, totalSupply: string, factory: { __typename?: 'GaugeFactory', id: string, numGauges: number }, tokens?: Array<{ __typename?: 'RewardToken', id: string, symbol: string, decimals: number, totalDeposited: string }> | null } | null }; + export const SubgraphLiquidityGaugeFragmentDoc = gql` fragment SubgraphLiquidityGauge on LiquidityGauge { id @@ -1313,6 +1703,15 @@ export const SubgraphLiquidityGaugeFragmentDoc = gql` } } `; +export const SubgraphPoolWithPreferentialGaugeFragmentDoc = gql` + fragment SubgraphPoolWithPreferentialGauge on Pool { + id + poolId + preferentialGauge { + ...SubgraphLiquidityGauge + } +} + ${SubgraphLiquidityGaugeFragmentDoc}`; export const LiquidityGaugesDocument = gql` query LiquidityGauges($skip: Int, $first: Int, $orderBy: LiquidityGauge_orderBy, $orderDirection: OrderDirection, $where: LiquidityGauge_filter, $block: Block_height) { liquidityGauges( @@ -1327,6 +1726,20 @@ export const LiquidityGaugesDocument = gql` } } ${SubgraphLiquidityGaugeFragmentDoc}`; +export const PoolsDocument = gql` + query Pools($skip: Int, $first: Int, $orderBy: Pool_orderBy, $orderDirection: OrderDirection, $where: Pool_filter, $block: Block_height) { + pools( + skip: $skip + first: $first + orderBy: $orderBy + orderDirection: $orderDirection + where: $where + block: $block + ) { + ...SubgraphPoolWithPreferentialGauge + } +} + ${SubgraphPoolWithPreferentialGaugeFragmentDoc}`; export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string) => Promise; @@ -1337,6 +1750,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = return { LiquidityGauges(variables?: LiquidityGaugesQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { return withWrapper((wrappedRequestHeaders) => client.request(LiquidityGaugesDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'LiquidityGauges', 'query'); + }, + Pools(variables?: PoolsQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PoolsDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'Pools', 'query'); } }; } diff --git a/balancer-js/src/modules/subgraph/generated/balancer-subgraph-schema.graphql b/balancer-js/src/modules/subgraph/generated/balancer-subgraph-schema.graphql index cd4b914c7..b308e19fe 100644 --- a/balancer-js/src/modules/subgraph/generated/balancer-subgraph-schema.graphql +++ b/balancer-js/src/modules/subgraph/generated/balancer-subgraph-schema.graphql @@ -3708,6 +3708,9 @@ type _Block_ { """The block number""" number: Int! + + """Timestamp of the block if available, format depends on the chain""" + timestamp: String } """The type for the top-level _meta field""" diff --git a/balancer-js/src/modules/subgraph/generated/balancer-subgraph-types.ts b/balancer-js/src/modules/subgraph/generated/balancer-subgraph-types.ts index b6958f7d1..4a6ca0b09 100644 --- a/balancer-js/src/modules/subgraph/generated/balancer-subgraph-types.ts +++ b/balancer-js/src/modules/subgraph/generated/balancer-subgraph-types.ts @@ -3514,6 +3514,8 @@ export type _Block_ = { hash?: Maybe; /** The block number */ number: Scalars['Int']; + /** Timestamp of the block if available, format depends on the chain */ + timestamp?: Maybe; }; /** The type for the top-level _meta field */ From e4fbebbebd2d018d346340ec6396257c320193e4 Mon Sep 17 00:00:00 2001 From: bronco Date: Thu, 8 Sep 2022 14:14:40 +0200 Subject: [PATCH 61/65] perf: use tokens from gauge query --- .../src/modules/data/liquidity-gauges/provider.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/balancer-js/src/modules/data/liquidity-gauges/provider.ts b/balancer-js/src/modules/data/liquidity-gauges/provider.ts index 30da2ea6c..7fd1f796b 100644 --- a/balancer-js/src/modules/data/liquidity-gauges/provider.ts +++ b/balancer-js/src/modules/data/liquidity-gauges/provider.ts @@ -29,7 +29,7 @@ export class LiquidityGaugeSubgraphRPCProvider subgraph: LiquidityGaugesSubgraphRepository; workingSupplies: { [gaugeAddress: string]: number } = {}; relativeWeights: { [gaugeAddress: string]: number } = {}; - rewardTokens: { + rewardData: { [gaugeAddress: string]: { [tokenAddress: string]: RewardData }; } = {}; gauges?: Promise; @@ -60,7 +60,6 @@ export class LiquidityGaugeSubgraphRPCProvider console.time('fetching liquidity gauges'); const gauges = await this.subgraph.fetch(); const gaugeAddresses = gauges.map((g) => g.id); - this.rewardTokens = await this.multicall.getRewardData(gaugeAddresses); if (this.chainId == 1) { this.workingSupplies = await this.multicall.getWorkingSupplies( gaugeAddresses @@ -71,6 +70,15 @@ export class LiquidityGaugeSubgraphRPCProvider gaugeAddresses ); } + // TODO: Switch to getting rewards tokens from subgraph when indexer on polygon is fixed + // const rewardTokens = gauges.reduce((r: { [key: string]: string[] }, g) => { + // r[g.id] ||= g.tokens ? g.tokens.map((t) => t.id.split('-')[0]) : []; + // return r; + // }, {}); + this.rewardData = await this.multicall.getRewardData( + gaugeAddresses //, + // rewardTokens + ); console.timeEnd('fetching liquidity gauges'); return gauges.map(this.compose.bind(this)); } @@ -117,7 +125,7 @@ export class LiquidityGaugeSubgraphRPCProvider totalSupply: parseFloat(subgraphGauge.totalSupply), workingSupply: this.workingSupplies[subgraphGauge.id], relativeWeight: this.relativeWeights[subgraphGauge.id], - rewardTokens: this.rewardTokens[subgraphGauge.id], + rewardTokens: this.rewardData[subgraphGauge.id], }; } } From 83504b2c1bf2d135168a5ad070fb4c263dda3d56 Mon Sep 17 00:00:00 2001 From: bronco Date: Thu, 8 Sep 2022 15:57:26 +0200 Subject: [PATCH 62/65] refactor: call pools query only once --- balancer-js/src/modules/data/pool/subgraph.ts | 39 +++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/balancer-js/src/modules/data/pool/subgraph.ts b/balancer-js/src/modules/data/pool/subgraph.ts index 51e719be3..91205d33e 100644 --- a/balancer-js/src/modules/data/pool/subgraph.ts +++ b/balancer-js/src/modules/data/pool/subgraph.ts @@ -19,7 +19,7 @@ export class PoolsSubgraphRepository implements Findable, Searchable { private client: SubgraphClient; - public pools: SubgraphPool[] = []; + private pools?: Promise; /** * Repository with optional lazy loaded blockHeight @@ -36,7 +36,8 @@ export class PoolsSubgraphRepository this.client = createSubgraphClient(url); } - async fetch(): Promise { + async fetch(): Promise { + console.time('fetching pools'); const { pool0, pool1000 } = await this.client.Pools({ where: { swapEnabled: true, totalShares_gt: '0' }, orderBy: Pool_OrderBy.TotalLiquidity, @@ -45,47 +46,37 @@ export class PoolsSubgraphRepository ? { number: await this.blockHeight() } : undefined, }); + console.timeEnd('fetching pools'); // TODO: how to best convert subgraph type to sdk internal type? - this.pools = [...pool0, ...pool1000]; - - return this.pools; + return [...pool0, ...pool1000].map(this.mapType.bind(this)); } async find(id: string): Promise { - if (this.pools.length == 0) { - await this.fetch(); - } - - return this.findBy('id', id); + return await this.findBy('id', id); } async findBy(param: PoolAttribute, value: string): Promise { - if (this.pools.length == 0) { - await this.fetch(); + if (!this.pools) { + this.pools = this.fetch(); } - const pool = this.pools.find((pool) => pool[param] == value); - if (pool) { - return this.mapType(pool); - } - return undefined; + return (await this.pools).find((pool) => pool[param] == value); } async all(): Promise { - if (this.pools.length == 0) { - await this.fetch(); + if (!this.pools) { + this.pools = this.fetch(); } - - return this.pools.map(this.mapType.bind(this)); + return this.pools; } async where(filter: (pool: Pool) => boolean): Promise { - if (this.pools.length == 0) { - await this.fetch(); + if (!this.pools) { + this.pools = this.fetch(); } - return (await this.all()).filter(filter); + return (await this.pools).filter(filter); } private mapType(subgraphPool: SubgraphPool): Pool { From 5726a1244661498665968c8222d07ae0a41dafb3 Mon Sep 17 00:00:00 2001 From: bronco Date: Thu, 8 Sep 2022 16:54:59 +0200 Subject: [PATCH 63/65] fix: arbitrum gauges graph URL --- balancer-js/src/lib/constants/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/balancer-js/src/lib/constants/config.ts b/balancer-js/src/lib/constants/config.ts index c9e41624f..8e413b755 100644 --- a/balancer-js/src/lib/constants/config.ts +++ b/balancer-js/src/lib/constants/config.ts @@ -89,7 +89,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { subgraph: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-arbitrum-v2', gaugesSubgraph: - 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges', + 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges-arbitrum', blockNumberSubgraph: 'https://api.thegraph.com/subgraphs/name/ianlapham/arbitrum-one-blocks', }, From 1192c770e1483ba4669c2d2d5d8ae7a2b1999528 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 9 Sep 2022 11:15:51 +0100 Subject: [PATCH 64/65] Bump SOR to 4.0.1-beta.5. --- balancer-js/package.json | 2 +- balancer-js/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/balancer-js/package.json b/balancer-js/package.json index 0a1fb1d78..8a5257d2a 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -86,7 +86,7 @@ "typescript": "^4.0.2" }, "dependencies": { - "@balancer-labs/sor": "^4.0.1-beta.3", + "@balancer-labs/sor": "^4.0.1-beta.5", "@balancer-labs/typechain": "^1.0.0", "axios": "^0.24.0", "graphql": "^15.6.1", diff --git a/balancer-js/yarn.lock b/balancer-js/yarn.lock index b89b45507..14ff320d6 100644 --- a/balancer-js/yarn.lock +++ b/balancer-js/yarn.lock @@ -483,10 +483,10 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" -"@balancer-labs/sor@^4.0.1-beta.3": - version "4.0.1-beta.3" - resolved "https://registry.yarnpkg.com/@balancer-labs/sor/-/sor-4.0.1-beta.3.tgz#57a90783edb09510382b821e354d5d5b30860404" - integrity sha512-xyMXsK45thq9Rqf3Ou8aIPMrg9aOtTy20wXCV39cb6K9ewWXp54XQRb3qFa8N8s+Zy3hau+FpwRbO55/zi41jg== +"@balancer-labs/sor@^4.0.1-beta.5": + version "4.0.1-beta.5" + resolved "https://registry.yarnpkg.com/@balancer-labs/sor/-/sor-4.0.1-beta.5.tgz#a65efd0cbfbf9ce41694387f9c89e617f5c3d07a" + integrity sha512-HRww8+5MtV+cTNiAVgpeEBAHCFrsXApibkkKZxB9Lazv5imhM/f0/RYDie+I9S+p57m82njQOr1L0k5MSUSrPw== dependencies: isomorphic-fetch "^2.2.1" From 9467444ff4580dba271d4421a2fd9f6655b87e39 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 9 Sep 2022 11:16:31 +0100 Subject: [PATCH 65/65] Update Package to 0.1.22. --- balancer-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/balancer-js/package.json b/balancer-js/package.json index 8a5257d2a..231c7a9af 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -1,6 +1,6 @@ { "name": "@balancer-labs/sdk", - "version": "0.1.21", + "version": "0.1.22", "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",