From e8a059494be502e7c1604a362bc522e38b635987 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 13 Apr 2023 11:05:10 -0300 Subject: [PATCH 01/86] Add unwrap action to generalisedExit --- .../exits.module.integration-mainnet.spec.ts | 221 +++++++----------- balancer-js/src/modules/exits/exits.module.ts | 122 +++++++++- balancer-js/src/modules/graph/graph.ts | 100 ++++++-- balancer-js/src/modules/joins/joins.module.ts | 60 ++++- .../src/modules/relayer/relayer.module.ts | 28 +++ balancer-js/src/modules/relayer/types.ts | 18 ++ 6 files changed, 379 insertions(+), 170 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index fa6dfc9cb..2c7343a05 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -2,14 +2,24 @@ import dotenv from 'dotenv'; import { expect } from 'chai'; -import { BalancerSDK, GraphQLQuery, GraphQLArgs, Network } from '@/.'; +import { + BalancerSDK, + GraphQLQuery, + GraphQLArgs, + Network, + truncateAddresses, + subSlippage, +} from '@/.'; import { BigNumber, parseFixed } from '@ethersproject/bignumber'; import { JsonRpcProvider } from '@ethersproject/providers'; import { Contracts } from '@/modules/contracts/contracts.module'; -import { accuracy, forkSetup, getBalances } from '@/test/lib/utils'; +import { + accuracy, + forkSetup, + sendTransactionGetBalances, +} from '@/test/lib/utils'; import { ADDRESSES } from '@/test/lib/constants'; import { Relayer } from '@/modules/relayer/relayer.module'; -import { JsonRpcSigner } from '@ethersproject/providers'; import { SimulationType } from '../simulation/simulation.module'; /** @@ -24,7 +34,8 @@ import { SimulationType } from '../simulation/simulation.module'; dotenv.config(); -const TEST_BBEUSD = true; +const TEST_BBEUSD = false; +const TEST_BBAUSD2 = true; /* * Testing on MAINNET @@ -32,9 +43,7 @@ const TEST_BBEUSD = true; * - Uncomment section below: */ const network = Network.MAINNET; -const blockNumber = 16685902; -const customSubgraphUrl = - 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2'; +const blockNumber = 17026902; const { ALCHEMY_URL: jsonRpcUrl } = process.env; const rpcUrl = 'http://127.0.0.1:8545'; @@ -75,65 +84,46 @@ const subgraphQuery: GraphQLQuery = { args: subgraphArgs, attrs: {} }; const sdk = new BalancerSDK({ network, rpcUrl, - customSubgraphUrl, tenderly: tenderlyConfig, subgraphQuery, }); const { pools } = sdk; const provider = new JsonRpcProvider(rpcUrl, network); -const signer = provider.getSigner(1); // signer 0 at this blockNumber has DAI balance and tests were failling +const signer = provider.getSigner(); const { contracts, contractAddresses } = new Contracts( network as number, provider ); const relayer = contractAddresses.relayer; -interface Test { - signer: JsonRpcSigner; - description: string; - pool: { - id: string; - address: string; - }; - amount: string; - authorisation: string | undefined; - simulationType?: SimulationType; -} - -const runTests = async (tests: Test[]) => { - for (let i = 0; i < tests.length; i++) { - const test = tests[i]; - it(test.description, async () => { - const signerAddress = await test.signer.getAddress(); - const authorisation = await Relayer.signRelayerApproval( - relayer, - signerAddress, - test.signer, - contracts.vault - ); - await testFlow( - test.signer, - signerAddress, - test.pool, - test.amount, - authorisation, - test.simulationType - ); - }).timeout(120000); - } -}; - const testFlow = async ( - signer: JsonRpcSigner, - signerAddress: string, - pool: { id: string; address: string }, + pool: { id: string; address: string; slot: number }, amount: string, - authorisation: string | undefined, - simulationType = SimulationType.VaultModel + simulationType = SimulationType.Tenderly ) => { - const gasLimit = 8e6; const slippage = '10'; // 10 bps = 0.1% + const tokens = [pool.address]; + const slots = [pool.slot]; + const balances = [amount]; + + await forkSetup( + signer, + tokens, + slots, + balances, + jsonRpcUrl as string, + blockNumber + ); + + const signerAddress = await signer.getAddress(); + const authorisation = await Relayer.signRelayerApproval( + relayer, + signerAddress, + signer, + contracts.vault + ); + const { to, encodedCall, tokensOut, expectedAmountsOut, minAmountsOut } = await pools.generalisedExit( pool.id, @@ -145,112 +135,59 @@ const testFlow = async ( authorisation ); - const [bptBalanceBefore, ...tokensOutBalanceBefore] = await getBalances( - [pool.address, ...tokensOut], - signer, - signerAddress - ); - - const response = await signer.sendTransaction({ - to, - data: encodedCall, - gasLimit, - }); - - const receipt = await response.wait(); - console.log('Gas used', receipt.gasUsed.toString()); + const { transactionReceipt, balanceDeltas, gasUsed } = + await sendTransactionGetBalances( + tokensOut, + signer, + signerAddress, + to, + encodedCall + ); - const [bptBalanceAfter, ...tokensOutBalanceAfter] = await getBalances( - [pool.address, ...tokensOut], - signer, - signerAddress - ); + console.log('Gas used', gasUsed.toString()); console.table({ - tokensOut: tokensOut.map((t) => `${t.slice(0, 6)}...${t.slice(38, 42)}`), + tokensOut: truncateAddresses(tokensOut), minOut: minAmountsOut, expectedOut: expectedAmountsOut, - balanceAfter: tokensOutBalanceAfter.map((b) => b.toString()), - balanceBefore: tokensOutBalanceBefore.map((b) => b.toString()), + balanceDeltas: balanceDeltas.map((b) => b.toString()), }); - expect(receipt.status).to.eql(1); - minAmountsOut.forEach((minAmountOut) => { - expect(BigNumber.from(minAmountOut).gte('0')).to.be.true; - }); - expectedAmountsOut.forEach((expectedAmountOut, i) => { - expect( - BigNumber.from(expectedAmountOut).gte(BigNumber.from(minAmountsOut[i])) - ).to.be.true; - }); - expect(bptBalanceAfter.eq(bptBalanceBefore.sub(amount))).to.be.true; - tokensOutBalanceBefore.forEach((b) => expect(b.eq(0)).to.be.true); - tokensOutBalanceAfter.forEach((balanceAfter, i) => { + expect(transactionReceipt.status).to.eq(1); + balanceDeltas.forEach((b, i) => { const minOut = BigNumber.from(minAmountsOut[i]); - expect(balanceAfter.gte(minOut)).to.be.true; - const expectedOut = BigNumber.from(expectedAmountsOut[i]); - expect(accuracy(balanceAfter, expectedOut)).to.be.closeTo(1, 1e-2); // inaccuracy should not be over to 1% + expect(b.gte(minOut)).to.be.true; + expect(accuracy(b, BigNumber.from(expectedAmountsOut[i]))).to.be.closeTo( + 1, + 1e-2 + ); // inaccuracy should be less than 1% }); + const expectedMins = expectedAmountsOut.map((a) => + subSlippage(BigNumber.from(a), BigNumber.from(slippage)).toString() + ); + expect(expectedMins).to.deep.eq(minAmountsOut); }; -// Skipping Euler specific tests while eTokens transactions are paused -describe.skip('generalised exit execution', async () => { - /* - bbeusd: ComposableStable, bbeusdt/bbeusdc/bbedai - bbeusdt: Linear, eUsdt/usdt - bbeusdc: Linear, eUsdc/usdc - bbedai: Linear, eDai/dai - */ +describe('generalised exit execution', async function () { + this.timeout(120000); // Sets timeout for all tests within this scope to 2 minutes + context('bbeusd', async () => { if (!TEST_BBEUSD) return true; - let authorisation: string | undefined; - beforeEach(async () => { - const tokens = [addresses.bbeusd.address]; - const slots = [addresses.bbeusd.slot]; - const balances = [parseFixed('2', addresses.bbeusd.decimals).toString()]; - await forkSetup( - signer, - tokens, - slots, - balances, - jsonRpcUrl as string, - blockNumber - ); + const pool = addresses.bbeusd; + const amount = parseFixed('2', pool.decimals).toString(); + + it('should exit pool correctly', async () => { + await testFlow(pool, amount); }); + }); + + context('bbausd2', async () => { + if (!TEST_BBAUSD2) return true; + const pool = addresses.bbausd2; + const amount = parseFixed('0.2', pool.decimals).toString(); - await runTests([ - { - signer, - description: 'exit pool', - pool: { - id: addresses.bbeusd.id, - address: addresses.bbeusd.address, - }, - amount: parseFixed('2', addresses.bbeusd.decimals).toString(), - authorisation: authorisation, - simulationType: SimulationType.Tenderly, - }, - { - signer, - description: 'exit pool', - pool: { - id: addresses.bbeusd.id, - address: addresses.bbeusd.address, - }, - amount: parseFixed('2', addresses.bbeusd.decimals).toString(), - authorisation: authorisation, - simulationType: SimulationType.Static, - }, - { - signer, - description: 'exit pool', - pool: { - id: addresses.bbeusd.id, - address: addresses.bbeusd.address, - }, - amount: parseFixed('2', addresses.bbeusd.decimals).toString(), - authorisation: authorisation, - }, - ]); + it('should exit pool correctly', async () => { + await testFlow(pool, amount); + }); }); }); diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index 9977c19b7..275b99278 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -10,7 +10,10 @@ import { AssetHelpers, subSlippage } from '@/lib/utils'; import { PoolGraph, Node } from '@/modules/graph/graph'; import { Join } from '@/modules/joins/joins.module'; import { calcPriceImpact } from '@/modules/pricing/priceImpact'; -import { Relayer } from '@/modules/relayer/relayer.module'; +import { + EncodeUnwrapAaveStaticTokenInput, + Relayer, +} from '@/modules/relayer/relayer.module'; import { Simulation, SimulationType, @@ -53,7 +56,8 @@ export class Exit { slippage: string, signer: JsonRpcSigner, simulationType: SimulationType, - authorisation?: string + authorisation?: string, + unwrapTokens = false ): Promise<{ to: string; encodedCall: string; @@ -73,7 +77,11 @@ export class Exit { */ // Create nodes and order by breadth first - const orderedNodes = await this.poolGraph.getGraphNodes(false, poolId); + const orderedNodes = await this.poolGraph.getGraphNodes( + false, + poolId, + unwrapTokens + ); const isProportional = PoolGraph.isProportionalPools(orderedNodes); console.log(`isProportional`, isProportional); @@ -136,6 +144,21 @@ export class Exit { slippage ); + // TODO: If expectedAmountsOut are greater than mainTokens balance, exit by unwrapping wrappedTokens + const insufficientMainTokenBalance = false; + if (unwrapTokens === false && insufficientMainTokenBalance) { + return this.exitPool( + poolId, + amountBptIn, + userAddress, + slippage, + signer, + simulationType, + authorisation, + true + ); + } + // Create calls with minimum expected amount out for each exit path const { encodedCall, deltas } = await this.createCalls( exitPaths, @@ -185,7 +208,11 @@ export class Exit { amountBptIn: string ): Promise { // Create nodes for each pool/token interaction and order by breadth first - const orderedNodesForJoin = await poolGraph.getGraphNodes(true, poolId); + const orderedNodesForJoin = await poolGraph.getGraphNodes( + true, + poolId, + false + ); const joinPaths = Join.getJoinPaths( orderedNodesForJoin, tokensOut, @@ -497,6 +524,20 @@ export class Exit { } switch (node.exitAction) { + case 'unwrap': { + const { encodedCall, assets, amounts } = this.createUnwrap( + node, + exitChild as Node, + i, + minAmountOut, + sender, + recipient + ); + // modelRequests.push(modelRequest); // TODO: add model request for unwrap to work with vault model + calls.push(encodedCall); + this.updateDeltas(deltas, assets, amounts); + break; + } case 'batchSwap': { const { modelRequest, encodedCall, assets, amounts } = this.createSwap( @@ -565,6 +606,55 @@ export class Exit { return { multiRequests, calls, outputIndexes, deltas }; } + private createUnwrap = ( + node: Node, + exitChild: Node, + exitPathIndex: number, + minAmountOut: string, + sender: string, + recipient: string + ): { + encodedCall: string; + assets: string[]; + amounts: string[]; + } => { + const amount = Relayer.toChainedReference( + this.getOutputRef(exitPathIndex, node.index) + ).toString(); + const outputReference = Relayer.toChainedReference( + this.getOutputRef(exitPathIndex, exitChild.index) + ); + + // console.log( + // `${node.type} ${node.address} prop: ${formatFixed( + // node.proportionOfParent, + // 18 + // )} + // ${node.exitAction}( + // inputAmt: ${amount}, + // inputToken: ${node.address}, + // pool: ${node.id}, + // outputToken: ${exitChild.address}, + // outputRef: ${this.getOutputRef(exitPathIndex, exitChild.index)}, + // sender: ${sender}, + // recipient: ${recipient} + // )` + // ); + + const call: EncodeUnwrapAaveStaticTokenInput = { + staticToken: node.address, + sender, + recipient, + amount, + toUnderlying: true, + outputReference, + }; + const encodedCall = Relayer.encodeUnwrapAaveStaticToken(call); + const assets = [exitChild.address]; + const amounts = [Zero.sub(minAmountOut).toString()]; // needs to be negative because it's handled by the vault model as an amount going out of the vault + return { encodedCall, assets, amounts }; + }; + private createSwap( node: Node, exitChild: Node, @@ -601,11 +691,14 @@ export class Exit { userData: '0x', }; + const fromInternalBalance = this.receivesFromInternal(node); + const toInternalBalance = this.receivesFromInternal(exitChild); + const funds: FundManagement = { sender, recipient, - fromInternalBalance: this.receivesFromInternal(node), - toInternalBalance: this.receivesFromInternal(exitChild), + fromInternalBalance, + toInternalBalance, }; const outputReference = Relayer.toChainedReference( @@ -624,7 +717,9 @@ export class Exit { // outputToken: ${exitChild.address}, // outputRef: ${this.getOutputRef(exitPathIndex, exitChild.index)}, // sender: ${sender}, - // recipient: ${recipient} + // recipient: ${recipient}, + // fromInternalBalance: ${fromInternalBalance}, + // toInternalBalance: ${toInternalBalance} // )` // ); @@ -736,6 +831,8 @@ export class Exit { }, ]; + const toInternalBalance = this.receivesFromInternal(exitChild); + // console.log( // `${node.type} ${node.address} prop: ${formatFixed( // node.proportionOfParent, @@ -750,7 +847,8 @@ export class Exit { // minAmountOut: ${minAmountOut}, // outputRef: ${this.getOutputRef(exitPathIndex, exitChild.index)}, // sender: ${sender}, - // recipient: ${recipient} + // recipient: ${recipient}, + // toInternalBalance: ${toInternalBalance} // )` // ); @@ -764,7 +862,7 @@ export class Exit { assets: sortedTokens, minAmountsOut: sortedAmounts, userData, - toInternalBalance: this.receivesFromInternal(exitChild), + toInternalBalance, }); const encodedCall = Relayer.encodeExitPool(call); const modelRequest = VaultModel.mapExitPoolRequest(call); @@ -917,6 +1015,10 @@ export class Exit { // others should always receive from internal balance private receivesFromInternal = (node: Node): boolean => { if (!node.parent) return false; - return node.exitAction !== 'output' && node.exitAction !== 'exitPool'; + return ( + node.exitAction !== 'output' && + node.exitAction !== 'unwrap' && + node.exitAction !== 'exitPool' + ); }; } diff --git a/balancer-js/src/modules/graph/graph.ts b/balancer-js/src/modules/graph/graph.ts index def10ab68..25432e5c5 100644 --- a/balancer-js/src/modules/graph/graph.ts +++ b/balancer-js/src/modules/graph/graph.ts @@ -26,7 +26,7 @@ export interface Node { decimals: number; } -type JoinAction = 'input' | 'batchSwap' | 'joinPool'; +type JoinAction = 'input' | 'batchSwap' | 'wrap' | 'joinPool'; const joinActions = new Map(); supportedPoolTypes.forEach((type) => { if (type.includes('Linear') && supportedPoolTypes.includes(type)) @@ -41,7 +41,7 @@ joinActions.set(PoolType.StablePhantom, 'batchSwap'); joinActions.set(PoolType.Weighted, 'joinPool'); joinActions.set(PoolType.ComposableStable, 'joinPool'); -type ExitAction = 'output' | 'batchSwap' | 'exitPool' | 'exitPoolProportional'; +type ExitAction = 'output' | 'batchSwap' | 'unwrap' | 'exitPool'; const exitActions = new Map(); supportedPoolTypes.forEach((type) => { if (type.includes('Linear') && supportedPoolTypes.includes(type)) @@ -59,7 +59,10 @@ exitActions.set(PoolType.ComposableStable, 'exitPool'); export class PoolGraph { constructor(private pools: Findable) {} - async buildGraphFromRootPool(poolId: string): Promise { + async buildGraphFromRootPool( + poolId: string, + wrapMainTokens: boolean + ): Promise { const rootPool = await this.pools.find(poolId); if (!rootPool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); const nodeIndex = 0; @@ -67,7 +70,8 @@ export class PoolGraph { rootPool.address, nodeIndex, undefined, - WeiPerEther + WeiPerEther, + wrapMainTokens ); return rootNode[0]; } @@ -89,7 +93,8 @@ export class PoolGraph { address: string, nodeIndex: number, parent: Node | undefined, - proportionOfParent: BigNumber + proportionOfParent: BigNumber, + wrapMainTokens: boolean ): Promise<[Node, number]> { const pool = await this.pools.findBy('address', address); @@ -166,7 +171,8 @@ export class PoolGraph { [poolNode, nodeIndex] = this.createLinearNodeChildren( poolNode, nodeIndex, - pool + pool, + wrapMainTokens ); } else { const { balancesEvm } = parsePoolInfo(pool); @@ -190,7 +196,8 @@ export class PoolGraph { pool.tokens[i].address, nodeIndex, poolNode, - finalProportion + finalProportion, + wrapMainTokens ); nodeIndex = childNode[1]; if (childNode[0]) poolNode.children.push(childNode[0]); @@ -216,25 +223,82 @@ export class PoolGraph { createLinearNodeChildren( linearPoolNode: Node, nodeIndex: number, - linearPool: Pool + linearPool: Pool, + wrapMainTokens: boolean ): [Node, number] { // Main token if (linearPool.mainIndex === undefined) throw new Error('Issue With Linear Pool'); + if (wrapMainTokens) { + // Linear pool will be joined via wrapped token. This will be the child node. + const wrappedNodeInfo = this.createWrappedTokenNode( + linearPool, + nodeIndex, + linearPoolNode, + linearPoolNode.proportionOfParent + ); + linearPoolNode.children.push(wrappedNodeInfo[0]); + return [linearPoolNode, wrappedNodeInfo[1]]; + } else { + const mainTokenDecimals = + linearPool.tokens[linearPool.mainIndex].decimals ?? 18; + + const nodeInfo = PoolGraph.createInputTokenNode( + nodeIndex, + linearPool.tokensList[linearPool.mainIndex], + mainTokenDecimals, + linearPoolNode, + linearPoolNode.proportionOfParent + ); + linearPoolNode.children.push(nodeInfo[0]); + nodeIndex = nodeInfo[1]; + return [linearPoolNode, nodeIndex]; + } + } + + createWrappedTokenNode( + linearPool: Pool, + nodeIndex: number, + parent: Node | undefined, + proportionOfParent: BigNumber + ): [Node, number] { + if ( + linearPool.wrappedIndex === undefined || + linearPool.mainIndex === undefined + ) + throw new Error('Issue With Linear Pool'); + + const wrappedTokenNode: Node = { + type: 'WrappedToken', + address: linearPool.tokensList[linearPool.wrappedIndex], + id: 'N/A', + children: [], + marked: false, + joinAction: 'wrap', + exitAction: 'unwrap', + isProportionalExit: false, + index: nodeIndex.toString(), + parent, + proportionOfParent, + isLeaf: false, + spotPrices: {}, + decimals: 18, + }; + nodeIndex++; const mainTokenDecimals = linearPool.tokens[linearPool.mainIndex].decimals ?? 18; - const nodeInfo = PoolGraph.createInputTokenNode( + const inputNode = PoolGraph.createInputTokenNode( nodeIndex, linearPool.tokensList[linearPool.mainIndex], mainTokenDecimals, - linearPoolNode, - linearPoolNode.proportionOfParent + wrappedTokenNode, + proportionOfParent ); - linearPoolNode.children.push(nodeInfo[0]); - nodeIndex = nodeInfo[1]; - return [linearPoolNode, nodeIndex]; + wrappedTokenNode.children = [inputNode[0]]; + nodeIndex = inputNode[1]; + return [wrappedTokenNode, nodeIndex]; } static createInputTokenNode( @@ -302,11 +366,15 @@ export class PoolGraph { } // Get full graph from root pool and return ordered nodes - getGraphNodes = async (isJoin: boolean, poolId: string): Promise => { + getGraphNodes = async ( + isJoin: boolean, + poolId: string, + wrapMainTokens: boolean + ): Promise => { const rootPool = await this.pools.find(poolId); if (!rootPool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); - const rootNode = await this.buildGraphFromRootPool(poolId); + const rootNode = await this.buildGraphFromRootPool(poolId, wrapMainTokens); if (rootNode.id !== poolId) throw new Error('Error creating graph nodes'); diff --git a/balancer-js/src/modules/joins/joins.module.ts b/balancer-js/src/modules/joins/joins.module.ts index 1c4e6d01a..9bb3a6802 100644 --- a/balancer-js/src/modules/joins/joins.module.ts +++ b/balancer-js/src/modules/joins/joins.module.ts @@ -3,7 +3,11 @@ import { BigNumber, parseFixed } from '@ethersproject/bignumber'; import { AddressZero, WeiPerEther, Zero } from '@ethersproject/constants'; import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; -import { EncodeJoinPoolInput, Relayer } from '@/modules/relayer/relayer.module'; +import { + EncodeJoinPoolInput, + EncodeWrapAaveDynamicTokenInput, + Relayer, +} from '@/modules/relayer/relayer.module'; import { FundManagement, SingleSwap, @@ -67,7 +71,11 @@ export class Join { throw new BalancerError(BalancerErrorCode.INPUT_LENGTH_MISMATCH); // Create nodes for each pool/token interaction and order by breadth first - const orderedNodes = await this.poolGraph.getGraphNodes(true, poolId); + const orderedNodes = await this.poolGraph.getGraphNodes( + true, + poolId, + false + ); const joinPaths = Join.getJoinPaths(orderedNodes, tokensIn, amountsIn); @@ -556,6 +564,18 @@ export class Join { isLastChainedCall && minAmountsOut ? minAmountsOut[j] : '0'; switch (node.joinAction) { + case 'wrap': + { + const { encodedCall } = this.createWrap( + node, + j, + sender, + recipient + ); + // modelRequests.push(modelRequest); // TODO: add model request when available + encodedCalls.push(encodedCall); + } + break; case 'batchSwap': { const { modelRequest, encodedCall, assets, amounts } = @@ -665,6 +685,42 @@ export class Join { return node; }; + private createWrap = ( + node: Node, + joinPathIndex: number, + sender: string, + recipient: string + ): { encodedCall: string } => { + // Throws error based on the assumption that aaveWrap apply only to input tokens from leaf nodes + if (node.children.length !== 1) + throw new Error('aaveWrap nodes should always have a single child node'); + + const childNode = node.children[0]; + + const staticToken = node.address; + const amount = childNode.index; + const call: EncodeWrapAaveDynamicTokenInput = { + staticToken, + sender, + recipient, + amount, + fromUnderlying: true, + outputReference: this.getOutputRefValue(joinPathIndex, node).value, + }; + const encodedCall = Relayer.encodeWrapAaveDynamicToken(call); + + // console.log( + // `${node.type} ${node.address} prop: ${node.proportionOfParent.toString()} + // ${node.joinAction} ( + // staticToken: ${staticToken}, + // input: ${amount}, + // outputRef: ${node.index.toString()} + // )` + // ); + + return { encodedCall }; + }; + private createSwap = ( node: Node, joinPathIndex: number, diff --git a/balancer-js/src/modules/relayer/relayer.module.ts b/balancer-js/src/modules/relayer/relayer.module.ts index c41a15118..909081147 100644 --- a/balancer-js/src/modules/relayer/relayer.module.ts +++ b/balancer-js/src/modules/relayer/relayer.module.ts @@ -7,6 +7,8 @@ import { EncodeBatchSwapInput, EncodeExitPoolInput, EncodeJoinPoolInput, + EncodeUnwrapAaveStaticTokenInput, + EncodeWrapAaveDynamicTokenInput, ExitPoolData, JoinPoolData, } from './types'; @@ -118,6 +120,32 @@ export class Relayer { ]); } + static encodeWrapAaveDynamicToken( + params: EncodeWrapAaveDynamicTokenInput + ): string { + return relayerLibrary.encodeFunctionData('wrapAaveDynamicToken', [ + params.staticToken, + params.sender, + params.recipient, + params.amount, + params.fromUnderlying, + params.outputReference, + ]); + } + + static encodeUnwrapAaveStaticToken( + params: EncodeUnwrapAaveStaticTokenInput + ): string { + return relayerLibrary.encodeFunctionData('unwrapAaveStaticToken', [ + params.staticToken, + params.sender, + params.recipient, + params.amount, + params.toUnderlying, + params.outputReference, + ]); + } + static encodePeekChainedReferenceValue(reference: BigNumberish): string { return relayerLibrary.encodeFunctionData('peekChainedReferenceValue', [ reference, diff --git a/balancer-js/src/modules/relayer/types.ts b/balancer-js/src/modules/relayer/types.ts index 48613b6d7..74e000849 100644 --- a/balancer-js/src/modules/relayer/types.ts +++ b/balancer-js/src/modules/relayer/types.ts @@ -45,5 +45,23 @@ export interface EncodeJoinPoolInput { outputReference: string; } +export interface EncodeWrapAaveDynamicTokenInput { + staticToken: string; + sender: string; + recipient: string; + amount: BigNumberish; + fromUnderlying: boolean; + outputReference: BigNumberish; +} + +export interface EncodeUnwrapAaveStaticTokenInput { + staticToken: string; + sender: string; + recipient: string; + amount: BigNumberish; + toUnderlying: boolean; + outputReference: BigNumberish; +} + export type ExitPoolData = ExitPoolRequest & EncodeExitPoolInput; export type JoinPoolData = JoinPoolRequest & EncodeJoinPoolInput; From 8a72b443dbcdadf4889ced97e4dcb21de39efce4 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 13 Apr 2023 16:56:10 -0300 Subject: [PATCH 02/86] Add unwrap action to vault model --- .../exits.module.integration-mainnet.spec.ts | 4 +- balancer-js/src/modules/exits/exits.module.ts | 31 ++++++--- balancer-js/src/modules/pools/index.ts | 3 +- .../modules/vaultModel/poolModel/poolModel.ts | 10 +++ .../modules/vaultModel/poolModel/unwrap.ts | 57 +++++++++++++++++ .../modules/vaultModel/vaultModel.module.ts | 64 +++++++++++++------ 6 files changed, 138 insertions(+), 31 deletions(-) create mode 100644 balancer-js/src/modules/vaultModel/poolModel/unwrap.ts diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index 2c7343a05..3e3edd594 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -43,7 +43,7 @@ const TEST_BBAUSD2 = true; * - Uncomment section below: */ const network = Network.MAINNET; -const blockNumber = 17026902; +const blockNumber = 17040540; const { ALCHEMY_URL: jsonRpcUrl } = process.env; const rpcUrl = 'http://127.0.0.1:8545'; @@ -99,7 +99,7 @@ const relayer = contractAddresses.relayer; const testFlow = async ( pool: { id: string; address: string; slot: number }, amount: string, - simulationType = SimulationType.Tenderly + simulationType = SimulationType.VaultModel ) => { const slippage = '10'; // 10 bps = 0.1% diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index 275b99278..a3a59d852 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -26,6 +26,7 @@ import { } from '@/modules/swaps/types'; import { ExitPoolRequest as ExitPoolModelRequest } from '@/modules/vaultModel/poolModel/exit'; import { SwapRequest } from '@/modules/vaultModel/poolModel/swap'; +import { UnwrapRequest } from '@/modules/vaultModel/poolModel/unwrap'; import { Requests, VaultModel } from '@/modules/vaultModel/vaultModel.module'; import { ComposableStablePoolEncoder } from '@/pool-composable-stable'; import { StablePoolEncoder } from '@/pool-stable'; @@ -66,6 +67,8 @@ export class Exit { minAmountsOut: string[]; priceImpact: string; }> { + console.log('unwrapTokens', unwrapTokens); + /* Overall exit flow description: - Create calls with 0 expected min amount for each token out @@ -525,15 +528,16 @@ export class Exit { switch (node.exitAction) { case 'unwrap': { - const { encodedCall, assets, amounts } = this.createUnwrap( - node, - exitChild as Node, - i, - minAmountOut, - sender, - recipient - ); - // modelRequests.push(modelRequest); // TODO: add model request for unwrap to work with vault model + const { modelRequest, encodedCall, assets, amounts } = + this.createUnwrap( + node, + exitChild as Node, + i, + minAmountOut, + sender, + recipient + ); + modelRequests.push(modelRequest); calls.push(encodedCall); this.updateDeltas(deltas, assets, amounts); break; @@ -614,6 +618,7 @@ export class Exit { sender: string, recipient: string ): { + modelRequest: UnwrapRequest; encodedCall: string; assets: string[]; amounts: string[]; @@ -650,9 +655,15 @@ export class Exit { outputReference, }; const encodedCall = Relayer.encodeUnwrapAaveStaticToken(call); + + const modelRequest = VaultModel.mapUnwrapRequest( + call, + node.parent?.id as string // linear pool id + ); + const assets = [exitChild.address]; const amounts = [Zero.sub(minAmountOut).toString()]; // needs to be negative because it's handled by the vault model as an amount going out of the vault - return { encodedCall, assets, amounts }; + return { modelRequest, encodedCall, assets, amounts }; }; private createSwap( diff --git a/balancer-js/src/modules/pools/index.ts b/balancer-js/src/modules/pools/index.ts index 33a4b6872..4bc34fc05 100644 --- a/balancer-js/src/modules/pools/index.ts +++ b/balancer-js/src/modules/pools/index.ts @@ -383,7 +383,8 @@ export class Pools implements Findable { slippage, signer, simulationType, - authorisation + authorisation, + false ); } diff --git a/balancer-js/src/modules/vaultModel/poolModel/poolModel.ts b/balancer-js/src/modules/vaultModel/poolModel/poolModel.ts index 764e49b65..df9d0cd5d 100644 --- a/balancer-js/src/modules/vaultModel/poolModel/poolModel.ts +++ b/balancer-js/src/modules/vaultModel/poolModel/poolModel.ts @@ -3,16 +3,19 @@ import { RelayerModel } from '../relayer'; import { JoinModel, JoinPoolRequest } from './join'; import { ExitModel, ExitPoolRequest } from './exit'; import { SwapModel, BatchSwapRequest, SwapRequest } from './swap'; +import { UnwrapModel, UnwrapRequest } from './unwrap'; export class PoolModel { joinModel: JoinModel; exitModel: ExitModel; swapModel: SwapModel; + unwrapModel: UnwrapModel; constructor(private relayerModel: RelayerModel) { this.joinModel = new JoinModel(relayerModel); this.exitModel = new ExitModel(relayerModel); this.swapModel = new SwapModel(relayerModel); + this.unwrapModel = new UnwrapModel(relayerModel); } async doJoin( @@ -42,4 +45,11 @@ export class PoolModel { ): Promise { return this.swapModel.doSingleSwap(swapRequest, pools); } + + async doUnwrap( + unwrapRequest: UnwrapRequest, + pools: PoolDictionary + ): Promise<[string[], string[]]> { + return this.unwrapModel.doUnwrap(unwrapRequest, pools); + } } diff --git a/balancer-js/src/modules/vaultModel/poolModel/unwrap.ts b/balancer-js/src/modules/vaultModel/poolModel/unwrap.ts new file mode 100644 index 000000000..dc56271c5 --- /dev/null +++ b/balancer-js/src/modules/vaultModel/poolModel/unwrap.ts @@ -0,0 +1,57 @@ +import { LinearPool } from '@balancer-labs/sor'; +import { parseFixed } from '@ethersproject/bignumber'; + +import { EncodeUnwrapAaveStaticTokenInput } from '@/modules/relayer/types'; + +import { PoolDictionary } from '../poolSource'; +import { RelayerModel } from '../relayer'; +import { ActionType } from '../vaultModel.module'; +import { WeiPerEther, Zero } from '@ethersproject/constants'; +import { SolidityMaths } from '@/lib/utils/solidityMaths'; + +export interface UnwrapRequest + extends Pick { + poolId: string; + actionType: ActionType.Unwrap; +} + +export class UnwrapModel { + constructor(private relayerModel: RelayerModel) {} + + /** + * Perform the specified exit type. + * @param exitPoolRequest + * @returns tokens out + */ + async doUnwrap( + unwrapRequest: UnwrapRequest, + pools: PoolDictionary + ): Promise<[string[], string[]]> { + const pool = pools[unwrapRequest.poolId] as LinearPool; + const wrappedToken = pool.tokens[pool.wrappedIndex]; + const underlyingToken = pool.tokens[pool.mainIndex]; + + const amountIn = this.relayerModel.doChainedRefReplacement( + unwrapRequest.amount.toString() + ); + + // must be negative because is leaving the vault + const amountOut = SolidityMaths.divDownFixed( + SolidityMaths.mulDownFixed( + BigInt(amountIn), + parseFixed(wrappedToken.priceRate, 18).toBigInt() + ), + WeiPerEther.toBigInt() + ).toString(); + + // Save chained references + this.relayerModel.setChainedReferenceValue( + unwrapRequest.outputReference.toString(), + amountOut + ); + + const tokens = [wrappedToken.address, underlyingToken.address]; + const deltas = [amountIn, Zero.sub(amountOut).toString()]; + return [tokens, deltas]; + } +} diff --git a/balancer-js/src/modules/vaultModel/vaultModel.module.ts b/balancer-js/src/modules/vaultModel/vaultModel.module.ts index 1deefc81b..b786138ab 100644 --- a/balancer-js/src/modules/vaultModel/vaultModel.module.ts +++ b/balancer-js/src/modules/vaultModel/vaultModel.module.ts @@ -6,12 +6,14 @@ import { PoolModel } from './poolModel/poolModel'; import { JoinPoolRequest } from './poolModel/join'; import { ExitPoolRequest } from './poolModel/exit'; import { BatchSwapRequest, SwapRequest } from './poolModel/swap'; +import { UnwrapRequest } from './poolModel/unwrap'; import { RelayerModel } from './relayer'; import { PoolsSource } from './poolSource'; import { EncodeBatchSwapInput, EncodeJoinPoolInput, EncodeExitPoolInput, + EncodeUnwrapAaveStaticTokenInput, } from '../relayer/types'; import { Swap } from '../swaps/types'; @@ -20,13 +22,15 @@ export enum ActionType { Join, Exit, Swap, + Unwrap, } export type Requests = | BatchSwapRequest | JoinPoolRequest | ExitPoolRequest - | SwapRequest; + | SwapRequest + | UnwrapRequest; /** * Controller / use-case layer for interacting with pools data. @@ -59,24 +63,35 @@ export class VaultModel { const pools = await this.poolsSource.poolsDictionary(refresh); const deltas: Record = {}; for (const call of rawCalls) { - if (call.actionType === ActionType.Join) { - const [tokens, amounts] = await poolModel.doJoin(call, pools); - // const [tokens, amounts] = await this.doJoinPool(call); - this.updateDeltas(deltas, tokens, amounts); - } else if (call.actionType === ActionType.Exit) { - const [tokens, amounts] = await poolModel.doExit(call, pools); - this.updateDeltas(deltas, tokens, amounts); - } else if (call.actionType === ActionType.BatchSwap) { - const swapDeltas = await poolModel.doBatchSwap(call, pools); - this.updateDeltas(deltas, call.assets, swapDeltas); - } else { - const swapDeltas = await poolModel.doSingleSwap(call, pools); - this.updateDeltas( - deltas, - [call.request.assetOut, call.request.assetIn], - swapDeltas - ); + let tokens: string[] = []; + let amounts: string[] = []; + switch (call.actionType) { + case ActionType.Join: { + [tokens, amounts] = await poolModel.doJoin(call, pools); + break; + } + case ActionType.Exit: { + [tokens, amounts] = await poolModel.doExit(call, pools); + break; + } + case ActionType.BatchSwap: { + tokens = call.assets; + amounts = await poolModel.doBatchSwap(call, pools); + break; + } + case ActionType.Swap: { + tokens = [call.request.assetOut, call.request.assetIn]; + amounts = await poolModel.doSingleSwap(call, pools); + break; + } + case ActionType.Unwrap: { + [tokens, amounts] = await poolModel.doUnwrap(call, pools); + break; + } + default: + break; } + this.updateDeltas(deltas, tokens, amounts); } return deltas; } @@ -122,4 +137,17 @@ export class VaultModel { }; return exitPoolRequest; } + + static mapUnwrapRequest( + call: EncodeUnwrapAaveStaticTokenInput, + poolId: string + ): UnwrapRequest { + const unwrapRequest: UnwrapRequest = { + actionType: ActionType.Unwrap, + poolId, + amount: call.amount, + outputReference: call.outputReference, + }; + return unwrapRequest; + } } From bf1c432c2bb1a8fd58d0a7ef6b609ac5c0cc4476 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 14 Apr 2023 16:38:20 -0300 Subject: [PATCH 03/86] Replace bbausd2 test by wstETH_rETH_sfrxETH --- .../exits/exits.module.integration-mainnet.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index 3e3edd594..2de72850e 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -35,7 +35,7 @@ import { SimulationType } from '../simulation/simulation.module'; dotenv.config(); const TEST_BBEUSD = false; -const TEST_BBAUSD2 = true; +const TEST_ETH_STABLE = true; /* * Testing on MAINNET @@ -181,9 +181,9 @@ describe('generalised exit execution', async function () { }); }); - context('bbausd2', async () => { - if (!TEST_BBAUSD2) return true; - const pool = addresses.bbausd2; + context('wstETH_rETH_sfrxETH', async () => { + if (!TEST_ETH_STABLE) return true; + const pool = addresses.wstETH_rETH_sfrxETH; const amount = parseFixed('0.2', pool.decimals).toString(); it('should exit pool correctly', async () => { From 745b64bf0c3360feb1174b32014c6b22cfac202f Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 20 Apr 2023 12:13:04 +0200 Subject: [PATCH 04/86] Expose exit by unwrapping tokens as input to generalisedExit --- .../exits.module.integration-mainnet.spec.ts | 27 ++++++++++++++++--- balancer-js/src/modules/exits/exits.module.ts | 15 ----------- balancer-js/src/modules/pools/index.ts | 6 +++-- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index 2de72850e..90ab1bc75 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -99,8 +99,9 @@ const relayer = contractAddresses.relayer; const testFlow = async ( pool: { id: string; address: string; slot: number }, amount: string, + unwrapTokens = false, simulationType = SimulationType.VaultModel -) => { +): Promise => { const slippage = '10'; // 10 bps = 0.1% const tokens = [pool.address]; @@ -132,7 +133,8 @@ const testFlow = async ( slippage, signer, simulationType, - authorisation + authorisation, + unwrapTokens ); const { transactionReceipt, balanceDeltas, gasUsed } = @@ -166,6 +168,7 @@ const testFlow = async ( subSlippage(BigNumber.from(a), BigNumber.from(slippage)).toString() ); expect(expectedMins).to.deep.eq(minAmountsOut); + return expectedAmountsOut; }; describe('generalised exit execution', async function () { @@ -185,9 +188,25 @@ describe('generalised exit execution', async function () { if (!TEST_ETH_STABLE) return true; const pool = addresses.wstETH_rETH_sfrxETH; const amount = parseFixed('0.2', pool.decimals).toString(); + let mainTokensAmountsOut: string[]; + let unwrappingTokensAmountsOut: string[]; - it('should exit pool correctly', async () => { - await testFlow(pool, amount); + context('exit by unwrapping tokens', async () => { + it('should exit pool correctly', async () => { + unwrappingTokensAmountsOut = await testFlow(pool, amount, true); + }); + }); + + context('exit to main tokens directly', async () => { + it('should exit pool correctly', async () => { + mainTokensAmountsOut = await testFlow(pool, amount); + }); + }); + + context('exit by unwrapping vs exit to main tokens', async () => { + it('should return the same amount out', async () => { + expect(mainTokensAmountsOut).to.deep.eq(unwrappingTokensAmountsOut); + }); }); }); }); diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index a3a59d852..6fb13ee21 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -147,21 +147,6 @@ export class Exit { slippage ); - // TODO: If expectedAmountsOut are greater than mainTokens balance, exit by unwrapping wrappedTokens - const insufficientMainTokenBalance = false; - if (unwrapTokens === false && insufficientMainTokenBalance) { - return this.exitPool( - poolId, - amountBptIn, - userAddress, - slippage, - signer, - simulationType, - authorisation, - true - ); - } - // Create calls with minimum expected amount out for each exit path const { encodedCall, deltas } = await this.createCalls( exitPaths, diff --git a/balancer-js/src/modules/pools/index.ts b/balancer-js/src/modules/pools/index.ts index 4bc34fc05..da2b38791 100644 --- a/balancer-js/src/modules/pools/index.ts +++ b/balancer-js/src/modules/pools/index.ts @@ -358,6 +358,7 @@ export class Pools implements Findable { * @param signer JsonRpcSigner that will sign the staticCall transaction if Static simulation chosen * @param simulationType Simulation type (VaultModel, Tenderly or Static) * @param authorisation Optional auhtorisation call to be added to the chained transaction + * @param unwrapTokens true = exit by unwrapping wrapped tokens, false = exit to main tokens direclty * @returns transaction data ready to be sent to the network along with tokens, min and expected amounts out. */ async generalisedExit( @@ -367,7 +368,8 @@ export class Pools implements Findable { slippage: string, signer: JsonRpcSigner, simulationType: SimulationType, - authorisation?: string + authorisation?: string, + unwrapTokens = false ): Promise<{ to: string; encodedCall: string; @@ -384,7 +386,7 @@ export class Pools implements Findable { signer, simulationType, authorisation, - false + unwrapTokens ); } From 9d25c0a4e516b853295edd40895c2657029accf4 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 4 May 2023 15:46:43 +0100 Subject: [PATCH 05/86] Update SOR to 4.1.1-beta.9. --- 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 64ba6e8d8..b24467b42 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -84,7 +84,7 @@ "typescript": "^4.0.2" }, "dependencies": { - "@balancer-labs/sor": "^4.1.1-beta.8", + "@balancer-labs/sor": "^4.1.1-beta.9", "@ethersproject/abi": "^5.4.0", "@ethersproject/abstract-signer": "^5.4.0", "@ethersproject/address": "^5.4.0", diff --git a/balancer-js/yarn.lock b/balancer-js/yarn.lock index 823763377..70c7b70d0 100644 --- a/balancer-js/yarn.lock +++ b/balancer-js/yarn.lock @@ -507,10 +507,10 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@balancer-labs/sor@^4.1.1-beta.8": - version "4.1.1-beta.8" - resolved "https://registry.yarnpkg.com/@balancer-labs/sor/-/sor-4.1.1-beta.8.tgz#75b2f40d8083499a1552900c0e2489f6e0a16452" - integrity sha512-By0k3apw/8MLu7UJAsV1xj3kxp9WON8cZI3REFKfOl4xvJq+yB1qR7BHbtR0p7bYZSwUucdiPMevSAlcAXNAsA== +"@balancer-labs/sor@^4.1.1-beta.9": + version "4.1.1-beta.9" + resolved "https://registry.yarnpkg.com/@balancer-labs/sor/-/sor-4.1.1-beta.9.tgz#68ea312f57d43595a0156642b56e7713f87cf4bb" + integrity sha512-jwqEkjOgc9qTnuQGne/ZnG+ynx4ca4qEIgRClJVH2tCCf+pXL7jVPWv/v3I+7dJs2aCyet4uIsXwU/vi2jK+/Q== dependencies: isomorphic-fetch "^2.2.1" From cf3bddda7e8cb40f7854b0a718e4500a7f8b5bc9 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 4 May 2023 16:06:55 +0100 Subject: [PATCH 06/86] Add GyroE V2 support for swaps. --- balancer-js/examples/swapSor.ts | 8 +- balancer-js/src/lib/abi/gyroEV2.json | 1968 +++++++++++++++++ .../src/modules/sor/pool-data/onChainData.ts | 23 + balancer-js/src/test/lib/constants.ts | 10 + balancer-js/src/types.ts | 1 + 5 files changed, 2006 insertions(+), 4 deletions(-) create mode 100644 balancer-js/src/lib/abi/gyroEV2.json diff --git a/balancer-js/examples/swapSor.ts b/balancer-js/examples/swapSor.ts index 64a96b49d..33be6192f 100644 --- a/balancer-js/examples/swapSor.ts +++ b/balancer-js/examples/swapSor.ts @@ -121,12 +121,12 @@ async function getAndProcessSwaps( } async function swapExample() { - const network = Network.GOERLI; + const network = Network.POLYGON; const rpcUrl = PROVIDER_URLS[network]; - const tokenIn = ADDRESSES[network].DAI.address; - const tokenOut = ADDRESSES[network].USDT.address; + const tokenIn = ADDRESSES[network].stMATIC.address; + const tokenOut = ADDRESSES[network].MATIC.address; const swapType = SwapTypes.SwapExactIn; - const amount = parseFixed('200', 18); + const amount = parseFixed('20', 18); // Currently Relayer only suitable for ExactIn and non-eth swaps const canUseJoinExitPaths = canUseJoinExit(swapType, tokenIn!, tokenOut!); diff --git a/balancer-js/src/lib/abi/gyroEV2.json b/balancer-js/src/lib/abi/gyroEV2.json new file mode 100644 index 000000000..f7175fc92 --- /dev/null +++ b/balancer-js/src/lib/abi/gyroEV2.json @@ -0,0 +1,1968 @@ +[ + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "contract IVault", + "name": "vault", + "type": "address" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "contract IERC20", + "name": "token0", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pauseWindowDuration", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bufferPeriodDuration", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "oracleEnabled", + "type": "bool" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "internalType": "struct ExtensibleWeightedPool2Tokens.NewPoolParams", + "name": "baseParams", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int256", + "name": "alpha", + "type": "int256" + }, + { + "internalType": "int256", + "name": "beta", + "type": "int256" + }, + { + "internalType": "int256", + "name": "c", + "type": "int256" + }, + { + "internalType": "int256", + "name": "s", + "type": "int256" + }, + { + "internalType": "int256", + "name": "lambda", + "type": "int256" + } + ], + "internalType": "struct GyroECLPMath.Params", + "name": "eclpParams", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "int256", + "name": "x", + "type": "int256" + }, + { + "internalType": "int256", + "name": "y", + "type": "int256" + } + ], + "internalType": "struct GyroECLPMath.Vector2", + "name": "tauAlpha", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int256", + "name": "x", + "type": "int256" + }, + { + "internalType": "int256", + "name": "y", + "type": "int256" + } + ], + "internalType": "struct GyroECLPMath.Vector2", + "name": "tauBeta", + "type": "tuple" + }, + { + "internalType": "int256", + "name": "u", + "type": "int256" + }, + { + "internalType": "int256", + "name": "v", + "type": "int256" + }, + { + "internalType": "int256", + "name": "w", + "type": "int256" + }, + { + "internalType": "int256", + "name": "z", + "type": "int256" + }, + { + "internalType": "int256", + "name": "dSq", + "type": "int256" + } + ], + "internalType": "struct GyroECLPMath.DerivedParams", + "name": "derivedEclpParams", + "type": "tuple" + }, + { + "internalType": "address", + "name": "rateProvider0", + "type": "address" + }, + { + "internalType": "address", + "name": "rateProvider1", + "type": "address" + }, + { + "internalType": "address", + "name": "capManager", + "type": "address" + }, + { + "components": [ + { + "internalType": "bool", + "name": "capEnabled", + "type": "bool" + }, + { + "internalType": "uint120", + "name": "perAddressCap", + "type": "uint120" + }, + { + "internalType": "uint128", + "name": "globalCap", + "type": "uint128" + } + ], + "internalType": "struct ICappedLiquidity.CapParams", + "name": "capParams", + "type": "tuple" + }, + { + "internalType": "address", + "name": "pauseManager", + "type": "address" + } + ], + "internalType": "struct GyroECLPPool.GyroParams", + "name": "params", + "type": "tuple" + }, + { + "internalType": "address", + "name": "configAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "capManager", + "type": "address" + } + ], + "name": "CapManagerUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "capEnabled", + "type": "bool" + }, + { + "internalType": "uint120", + "name": "perAddressCap", + "type": "uint120" + }, + { + "internalType": "uint128", + "name": "globalCap", + "type": "uint128" + } + ], + "indexed": false, + "internalType": "struct ICappedLiquidity.CapParams", + "name": "params", + "type": "tuple" + } + ], + "name": "CapParamsUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "derivedParamsValidated", + "type": "bool" + } + ], + "name": "ECLPDerivedParamsValidated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "paramsValidated", + "type": "bool" + } + ], + "name": "ECLPParamsValidated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "invariantAfterJoin", + "type": "uint256" + } + ], + "name": "InvariantAterInitializeJoin", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldInvariant", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newInvariant", + "type": "uint256" + } + ], + "name": "InvariantOldAndNew", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "OracleEnabledChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oracleUpdatedIndex", + "type": "uint256" + } + ], + "name": "OracleIndexUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldPauseManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newPauseManager", + "type": "address" + } + ], + "name": "PauseManagerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "PausedLocally", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "PausedStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + } + ], + "name": "SwapFeePercentageChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "components": [ + { + "internalType": "int256", + "name": "x", + "type": "int256" + }, + { + "internalType": "int256", + "name": "y", + "type": "int256" + } + ], + "indexed": false, + "internalType": "struct GyroECLPMath.Vector2", + "name": "invariant", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SwapParams", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "UnpausedLocally", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_dSq", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_paramsAlpha", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_paramsBeta", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_paramsC", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_paramsLambda", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_paramsS", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_tauAlphaX", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_tauAlphaY", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_tauBetaX", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_tauBetaY", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_u", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_v", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_w", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_z", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "capManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "capParams", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "capEnabled", + "type": "bool" + }, + { + "internalType": "uint120", + "name": "perAddressCap", + "type": "uint120" + }, + { + "internalType": "uint128", + "name": "globalCap", + "type": "uint128" + } + ], + "internalType": "struct ICappedLiquidity.CapParams", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_pauseManager", + "type": "address" + } + ], + "name": "changePauseManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "startIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endIndex", + "type": "uint256" + } + ], + "name": "dirtyUninitializedOracleSamples", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "enableOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "selector", + "type": "bytes4" + } + ], + "name": "getActionId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAuthorizer", + "outputs": [ + { + "internalType": "contract IAuthorizer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getECLPParams", + "outputs": [ + { + "components": [ + { + "internalType": "int256", + "name": "alpha", + "type": "int256" + }, + { + "internalType": "int256", + "name": "beta", + "type": "int256" + }, + { + "internalType": "int256", + "name": "c", + "type": "int256" + }, + { + "internalType": "int256", + "name": "s", + "type": "int256" + }, + { + "internalType": "int256", + "name": "lambda", + "type": "int256" + } + ], + "internalType": "struct GyroECLPMath.Params", + "name": "params", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "int256", + "name": "x", + "type": "int256" + }, + { + "internalType": "int256", + "name": "y", + "type": "int256" + } + ], + "internalType": "struct GyroECLPMath.Vector2", + "name": "tauAlpha", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int256", + "name": "x", + "type": "int256" + }, + { + "internalType": "int256", + "name": "y", + "type": "int256" + } + ], + "internalType": "struct GyroECLPMath.Vector2", + "name": "tauBeta", + "type": "tuple" + }, + { + "internalType": "int256", + "name": "u", + "type": "int256" + }, + { + "internalType": "int256", + "name": "v", + "type": "int256" + }, + { + "internalType": "int256", + "name": "w", + "type": "int256" + }, + { + "internalType": "int256", + "name": "z", + "type": "int256" + }, + { + "internalType": "int256", + "name": "dSq", + "type": "int256" + } + ], + "internalType": "struct GyroECLPMath.DerivedParams", + "name": "d", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInvariant", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLargestSafeQueryWindow", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getLastInvariant", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum IPriceOracle.Variable", + "name": "variable", + "type": "uint8" + } + ], + "name": "getLatest", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMiscData", + "outputs": [ + { + "internalType": "int256", + "name": "logInvariant", + "type": "int256" + }, + { + "internalType": "int256", + "name": "logTotalSupply", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "oracleSampleCreationTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "oracleIndex", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "oracleEnabled", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNormalizedWeights", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "enum IPriceOracle.Variable", + "name": "variable", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "ago", + "type": "uint256" + } + ], + "internalType": "struct IPriceOracle.OracleAccumulatorQuery[]", + "name": "queries", + "type": "tuple[]" + } + ], + "name": "getPastAccumulators", + "outputs": [ + { + "internalType": "int256[]", + "name": "results", + "type": "int256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPausedState", + "outputs": [ + { + "internalType": "bool", + "name": "paused", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "pauseWindowEndTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bufferPeriodEndTime", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPoolId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "spotPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getSample", + "outputs": [ + { + "internalType": "int256", + "name": "logPairPrice", + "type": "int256" + }, + { + "internalType": "int256", + "name": "accLogPairPrice", + "type": "int256" + }, + { + "internalType": "int256", + "name": "logBptPrice", + "type": "int256" + }, + { + "internalType": "int256", + "name": "accLogBptPrice", + "type": "int256" + }, + { + "internalType": "int256", + "name": "logInvariant", + "type": "int256" + }, + { + "internalType": "int256", + "name": "accLogInvariant", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSwapFeePercentage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "enum IPriceOracle.Variable", + "name": "variable", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "secs", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "ago", + "type": "uint256" + } + ], + "internalType": "struct IPriceOracle.OracleAverageQuery[]", + "name": "queries", + "type": "tuple[]" + } + ], + "name": "getTimeWeightedAverage", + "outputs": [ + { + "internalType": "uint256[]", + "name": "results", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTokenRates", + "outputs": [ + { + "internalType": "uint256", + "name": "rate0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rate1", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTotalSamples", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getVault", + "outputs": [ + { + "internalType": "contract IVault", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gyroConfig", + "outputs": [ + { + "internalType": "contract IGyroConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "protocolSwapFeePercentage", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "onExitPool", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "protocolSwapFeePercentage", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "onJoinPool", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "dueProtocolFeeAmounts", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "enum IVault.SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IPoolSwapStructs.SwapRequest", + "name": "request", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "balanceTokenIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "balanceTokenOut", + "type": "uint256" + } + ], + "name": "onSwap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pauseManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "protocolSwapFeePercentage", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "queryExit", + "outputs": [ + { + "internalType": "uint256", + "name": "bptIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "protocolSwapFeePercentage", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "queryJoin", + "outputs": [ + { + "internalType": "uint256", + "name": "bptOut", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rateProvider0", + "outputs": [ + { + "internalType": "contract IRateProvider", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rateProvider1", + "outputs": [ + { + "internalType": "contract IRateProvider", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_capManager", + "type": "address" + } + ], + "name": "setCapManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "capEnabled", + "type": "bool" + }, + { + "internalType": "uint120", + "name": "perAddressCap", + "type": "uint120" + }, + { + "internalType": "uint128", + "name": "globalCap", + "type": "uint128" + } + ], + "internalType": "struct ICappedLiquidity.CapParams", + "name": "params", + "type": "tuple" + } + ], + "name": "setCapParams", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "setPaused", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + } + ], + "name": "setSwapFeePercentage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/balancer-js/src/modules/sor/pool-data/onChainData.ts b/balancer-js/src/modules/sor/pool-data/onChainData.ts index 94bcea1da..23018ac4d 100644 --- a/balancer-js/src/modules/sor/pool-data/onChainData.ts +++ b/balancer-js/src/modules/sor/pool-data/onChainData.ts @@ -14,6 +14,7 @@ import { StablePool__factory, StaticATokenRateProvider__factory, WeightedPool__factory, + GyroEV2__factory, } from '@/contracts'; import { JsonFragment } from '@ethersproject/abi'; @@ -41,6 +42,7 @@ export async function getOnChainBalances< ...(ConvergentCurvePool__factory.abi as readonly JsonFragment[]), ...(LinearPool__factory.abi as readonly JsonFragment[]), ...(ComposableStablePool__factory.abi as readonly JsonFragment[]), + ...(GyroEV2__factory.abi as readonly JsonFragment[]), ].map((row) => [row.name, row]) ) ); @@ -139,6 +141,9 @@ export async function getOnChainBalances< 'getSwapFeePercentage' ); } + if (pool.poolType.toString() === 'GyroE' && pool.poolTypeVersion === 2) { + multiPool.call(`${pool.id}.tokenRates`, pool.address, 'getTokenRates'); + } }); let pools = {} as Record< @@ -156,6 +161,7 @@ export async function getOnChainBalances< virtualSupply?: string; rate?: string; actualSupply?: string; + tokenRates?: string[]; } >; @@ -174,6 +180,7 @@ export async function getOnChainBalances< virtualSupply?: string; rate?: string; actualSupply?: string; + tokenRates?: string[]; } >; } catch (err) { @@ -191,6 +198,7 @@ export async function getOnChainBalances< totalSupply, virtualSupply, actualSupply, + tokenRates, } = onchainData; if ( @@ -274,6 +282,21 @@ export async function getOnChainBalances< subgraphPools[index].totalShares = formatFixed(totalSupply, 18); } + if ( + subgraphPools[index].poolType === 'GyroE' && + subgraphPools[index].poolTypeVersion == 2 + ) { + if (!Array.isArray(tokenRates) || tokenRates.length !== 2) { + console.error( + `GyroEV2 pool with missing or invalid tokenRates: ${poolId}` + ); + return; + } + subgraphPools[index].tokenRates = tokenRates.map((rate) => + formatFixed(rate, 18) + ); + } + onChainPools.push(subgraphPools[index]); } catch (err) { throw new Error(`Issue with pool onchain data: ${err}`); diff --git a/balancer-js/src/test/lib/constants.ts b/balancer-js/src/test/lib/constants.ts index 4d9ed1320..e9abe907c 100644 --- a/balancer-js/src/test/lib/constants.ts +++ b/balancer-js/src/test/lib/constants.ts @@ -545,6 +545,16 @@ export const ADDRESSES = { decimals: 6, symbol: 'XSGD', }, + WMATIC: { + address: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', + decimals: 18, + symbol: 'WMATIC', + }, + stMATIC: { + address: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + decimals: 18, + symbol: 'stMATIC', + }, }, [Network.ARBITRUM]: { WETH: { diff --git a/balancer-js/src/types.ts b/balancer-js/src/types.ts index 6d7944df9..a0382c19a 100644 --- a/balancer-js/src/types.ts +++ b/balancer-js/src/types.ts @@ -330,6 +330,7 @@ export interface Pool { lastJoinExitInvariant?: string; isInRecoveryMode?: boolean; isPaused?: boolean; + tokenRates?: string[]; } export interface PriceRateProvider { From a6253e9db9e0c287a326380483d6ccf74f58493e Mon Sep 17 00:00:00 2001 From: johngrantuk <4797222+johngrantuk@users.noreply.github.com> Date: Thu, 4 May 2023 21:34:45 +0000 Subject: [PATCH 07/86] chore: version bump v1.0.6-beta.0 --- 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 b24467b42..e4834ece3 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -1,6 +1,6 @@ { "name": "@balancer-labs/sdk", - "version": "1.0.5", + "version": "1.0.6-beta.0", "description": "JavaScript SDK for interacting with the Balancer Protocol V2", "license": "GPL-3.0-only", "homepage": "https://github.com/balancer-labs/balancer-sdk#readme", From 3b13806335511fe4be47014e3b720d8b19c86617 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Tue, 9 May 2023 10:20:10 -0300 Subject: [PATCH 08/86] Fix failing migration integration tests on polygon due to subgraph pruning older blocks --- .../liquidity-managment/migrations.integrations.spec.ts | 4 ++-- .../composableStable/join.concern.integration.spec.ts | 2 +- .../concerns/fx/liquidity.concern.integration.spec.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/balancer-js/src/modules/liquidity-managment/migrations.integrations.spec.ts b/balancer-js/src/modules/liquidity-managment/migrations.integrations.spec.ts index bcce554a4..37df2b2dc 100644 --- a/balancer-js/src/modules/liquidity-managment/migrations.integrations.spec.ts +++ b/balancer-js/src/modules/liquidity-managment/migrations.integrations.spec.ts @@ -263,7 +263,7 @@ describe('Migrations', function () { ); beforeEach(async () => { - await reset('https://rpc.ankr.com/polygon', provider, 41098000); + await reset('https://rpc.ankr.com/polygon', provider, 42462957); signer = await impersonateAccount(address, provider); // approve relayer @@ -274,7 +274,7 @@ describe('Migrations', function () { context('ComposableStable to ComposableStable', () => { before(() => { - address = '0x92a0b2c089733bef43ac367d2ce7783526aea590'; + address = '0xe80a6a7b4fdadf0aa59f3f669a8d394d1d4da86b'; }); it('should build a migration using exit / join', async () => { diff --git a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts index ee3fee103..c08877fd0 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts @@ -25,7 +25,7 @@ const { ALCHEMY_URL_POLYGON: jsonRpcUrl } = process.env; const rpcUrl = 'http://127.0.0.1:8137'; const provider = new ethers.providers.JsonRpcProvider(rpcUrl, network); const signer = provider.getSigner(); -const blockNumber = 41400000; +const blockNumber = 42462957; const testPoolId = '0x02d2e2d7a89d6c5cb3681cfcb6f7dac02a55eda400000000000000000000088f'; diff --git a/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts index 8446a0bb4..b128612d1 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts @@ -21,7 +21,7 @@ const signer = provider.getSigner(); const testPoolId = '0x726e324c29a1e49309672b244bdc4ff62a270407000200000000000000000702'; let pool: PoolWithMethods; -const blockNumber = 41400000; +const blockNumber = 42462957; describe('FX Pool - Calculate Liquidity', () => { const sdkConfig = { From 639a217baa9716c406d3249772449f9c3a3d1b13 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Tue, 9 May 2023 14:37:30 -0300 Subject: [PATCH 09/86] Update tests so they are able to catch when unwrapping wasn't done --- .../exits.module.integration-mainnet.spec.ts | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index 90ab1bc75..f52913cc0 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -43,7 +43,7 @@ const TEST_ETH_STABLE = true; * - Uncomment section below: */ const network = Network.MAINNET; -const blockNumber = 17040540; +const blockNumber = 17223300; const { ALCHEMY_URL: jsonRpcUrl } = process.env; const rpcUrl = 'http://127.0.0.1:8545'; @@ -100,8 +100,11 @@ const testFlow = async ( pool: { id: string; address: string; slot: number }, amount: string, unwrapTokens = false, - simulationType = SimulationType.VaultModel -): Promise => { + simulationType = SimulationType.Tenderly +): Promise<{ + expectedAmountsOut: string[]; + gasUsed: BigNumber; +}> => { const slippage = '10'; // 10 bps = 0.1% const tokens = [pool.address]; @@ -168,7 +171,7 @@ const testFlow = async ( subSlippage(BigNumber.from(a), BigNumber.from(slippage)).toString() ); expect(expectedMins).to.deep.eq(minAmountsOut); - return expectedAmountsOut; + return { expectedAmountsOut, gasUsed }; }; describe('generalised exit execution', async function () { @@ -184,22 +187,32 @@ describe('generalised exit execution', async function () { }); }); - context('wstETH_rETH_sfrxETH', async () => { + context('bbausd3', async () => { if (!TEST_ETH_STABLE) return true; - const pool = addresses.wstETH_rETH_sfrxETH; + const pool = addresses.bbausd3; const amount = parseFixed('0.2', pool.decimals).toString(); - let mainTokensAmountsOut: string[]; let unwrappingTokensAmountsOut: string[]; + let unwrappingTokensGasUsed: BigNumber; + let mainTokensAmountsOut: string[]; + let mainTokensGasUsed: BigNumber; context('exit by unwrapping tokens', async () => { it('should exit pool correctly', async () => { - unwrappingTokensAmountsOut = await testFlow(pool, amount, true); + const { expectedAmountsOut, gasUsed } = await testFlow( + pool, + amount, + true + ); + unwrappingTokensAmountsOut = expectedAmountsOut; + unwrappingTokensGasUsed = gasUsed; }); }); context('exit to main tokens directly', async () => { it('should exit pool correctly', async () => { - mainTokensAmountsOut = await testFlow(pool, amount); + const { expectedAmountsOut, gasUsed } = await testFlow(pool, amount); + mainTokensAmountsOut = expectedAmountsOut; + mainTokensGasUsed = gasUsed; }); }); @@ -207,6 +220,9 @@ describe('generalised exit execution', async function () { it('should return the same amount out', async () => { expect(mainTokensAmountsOut).to.deep.eq(unwrappingTokensAmountsOut); }); + it('should spend more gas when unwrapping tokens', async () => { + expect(unwrappingTokensGasUsed.gt(mainTokensGasUsed)).to.be.true; + }); }); }); }); From 3bee6b9456b7a61ce31e8f62817139e2ed9e3469 Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem de Liz Date: Tue, 9 May 2023 17:54:59 -0300 Subject: [PATCH 10/86] Adding liquidity functionality to GyroE pools; --- .../src/modules/pools/pool-type-concerns.ts | 24 ++++--- .../concerns/gyro-e/exit.concern.ts | 41 +++++++++++ .../concerns/gyro-e/join.concern.ts | 26 +++++++ .../liquidity.concern.integration.spec.ts | 71 +++++++++++++++++++ .../concerns/gyro-e/liquidity.concern.ts | 54 ++++++++++++++ .../concerns/gyro-e/priceImpact.concern.ts | 19 +++++ .../concerns/gyro-e/spotPrice.concern.ts | 9 +++ .../modules/pools/pool-types/gyro-e.module.ts | 23 ++++++ 8 files changed, 257 insertions(+), 10 deletions(-) create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/gyro-e/exit.concern.ts create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/gyro-e/join.concern.ts create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.ts create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/gyro-e/priceImpact.concern.ts create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/gyro-e/spotPrice.concern.ts create mode 100644 balancer-js/src/modules/pools/pool-types/gyro-e.module.ts diff --git a/balancer-js/src/modules/pools/pool-type-concerns.ts b/balancer-js/src/modules/pools/pool-type-concerns.ts index 774c798bd..f22ec8e93 100644 --- a/balancer-js/src/modules/pools/pool-type-concerns.ts +++ b/balancer-js/src/modules/pools/pool-type-concerns.ts @@ -8,6 +8,7 @@ import { Linear } from './pool-types/linear.module'; import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; import { isLinearish } from '@/lib/utils'; import { FX } from '@/modules/pools/pool-types/fx.module'; +import { GyroE } from '@/modules/pools/pool-types/gyro-e.module'; /** * Wrapper around pool type specific methods. @@ -36,25 +37,28 @@ export class PoolTypeConcerns { | Linear { // Calculate spot price using pool type switch (poolType) { - case 'Weighted': - case 'Investment': - case 'LiquidityBootstrapping': { - return new Weighted(); - } - case 'Stable': { - return new Stable(); - } case 'ComposableStable': { return new ComposableStable(); } + case 'FX': { + return new FX(); + } + case 'GyroE': { + return new GyroE(); + } case 'MetaStable': { return new MetaStable(); } + case 'Stable': { + return new Stable(); + } case 'StablePhantom': { return new StablePhantom(); } - case 'FX': { - return new FX(); + case 'Investment': + case 'LiquidityBootstrapping': + case 'Weighted': { + return new Weighted(); } default: { // Handles all Linear pool types diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/exit.concern.ts new file mode 100644 index 000000000..e9870a978 --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/exit.concern.ts @@ -0,0 +1,41 @@ +import { + ExitConcern, + ExitExactBPTInAttributes, + ExitExactBPTInParameters, + ExitExactTokensOutAttributes, + ExitExactTokensOutParameters, +} from '@/modules/pools/pool-types/concerns/types'; + +export class GyroEExitConcern implements ExitConcern { + buildExitExactTokensOut({ + exiter, + pool, + tokensOut, + amountsOut, + slippage, + wrappedNativeAsset, + }: ExitExactTokensOutParameters): ExitExactTokensOutAttributes { + console.log( + exiter, + pool, + tokensOut, + amountsOut, + slippage, + wrappedNativeAsset + ); + throw new Error('Not implemented'); + } + + buildRecoveryExit({ + exiter, + pool, + bptIn, + slippage, + }: Pick< + ExitExactBPTInParameters, + 'exiter' | 'pool' | 'bptIn' | 'slippage' + >): ExitExactBPTInAttributes { + console.log(exiter, pool, bptIn, slippage); + throw new Error('Not implemented'); + } +} diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/join.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/join.concern.ts new file mode 100644 index 000000000..63076fe4e --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/join.concern.ts @@ -0,0 +1,26 @@ +import { + JoinConcern, + JoinPoolAttributes, + JoinPoolParameters, +} from '@/modules/pools/pool-types/concerns/types'; + +export class GyroEJoinConcern implements JoinConcern { + buildJoin({ + joiner, + pool, + tokensIn, + amountsIn, + slippage, + wrappedNativeAsset, + }: JoinPoolParameters): JoinPoolAttributes { + console.log( + joiner, + pool, + tokensIn, + amountsIn, + slippage, + wrappedNativeAsset + ); + throw new Error('Not implemented'); + } +} diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts new file mode 100644 index 000000000..d7cbc1edf --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts @@ -0,0 +1,71 @@ +// yarn test:only ./src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts +import dotenv from 'dotenv'; +import { Network, PoolWithMethods } from '@/types'; +import { forkSetup, TestPoolHelper } from '@/test/lib/utils'; +import { ethers } from 'hardhat'; +import { BalancerSDK } from '@/modules/sdk.module'; +import { FXPool__factory, GyroEPool__factory } from '@/contracts'; +import { Contract } from '@ethersproject/contracts'; +import { expect } from 'chai'; +import { formatFixed, parseFixed } from '@ethersproject/bignumber'; +import { SolidityMaths } from '@/lib/utils/solidityMaths'; + +dotenv.config(); + +const network = Network.POLYGON; +const { ALCHEMY_URL_POLYGON: rpcUrlArchive } = process.env; +const rpcUrlLocal = 'http://127.0.0.1:8137'; + +const provider = new ethers.providers.JsonRpcProvider(rpcUrlLocal, network); +const signer = provider.getSigner(); +const testPoolId = + '0x97469e6236bd467cd147065f77752b00efadce8a0002000000000000000008c0'; +let pool: PoolWithMethods; +const blockNumber = 42505555; + +describe('GyroE Pool - Calculate Liquidity', () => { + const sdkConfig = { + network, + rpcUrl: rpcUrlLocal, + }; + const balancer = new BalancerSDK(sdkConfig); + before(async () => { + const testPool = new TestPoolHelper( + testPoolId, + network, + rpcUrlLocal, + blockNumber + ); + // Gets initial pool info from Subgraph + pool = await testPool.getPool(); + + // Setup forked network, set initial token balances and allowances + await forkSetup(signer, [], [], [], rpcUrlArchive as string, undefined); + + // Update pool info with onchain state from fork block no + pool = await testPool.getPool(); + }); + it('calculating liquidity', async () => { + const liquidity = await balancer.pools.liquidity(pool); + const liquidityFromContract = parseFixed( + parseFloat(pool.totalLiquidity).toFixed(18).toString(), + 18 + ).toBigInt(); + const liquidityBigInt = parseFixed(liquidity, 18).toBigInt(); + // expecting 5% of margin error + console.log( + formatFixed( + SolidityMaths.divDownFixed(liquidityBigInt, liquidityFromContract), + 18 + ).toString() + ); + expect( + parseFloat( + formatFixed( + SolidityMaths.divDownFixed(liquidityBigInt, liquidityFromContract), + 18 + ).toString() + ) + ).to.be.closeTo(1, 0.05); + }); +}); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.ts new file mode 100644 index 000000000..06d9edb2a --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.ts @@ -0,0 +1,54 @@ +import { LiquidityConcern } from '../types'; +import { PoolToken } from '@/types'; +import { formatFixed } from '@ethersproject/bignumber'; +import { parseFixed } from '@/lib/utils/math'; +import { SolidityMaths } from '@/lib/utils/solidityMaths'; + +const SCALING_FACTOR = 18; + +export class GyroELiquidityConcern implements LiquidityConcern { + calcTotal(tokens: PoolToken[]): string { + let sumBalance = BigInt(0); + let sumValue = BigInt(0); + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + + // if a token's price is unknown, ignore it + // it will be computed at the next step + if (!token.price?.usd) { + continue; + } + + const price = parseFixed( + token.price.usd.toString(), + SCALING_FACTOR + ).toBigInt(); + const balance = parseFixed(token.balance, SCALING_FACTOR).toBigInt(); + + const value = SolidityMaths.mulDownFixed(balance, price); + sumValue = SolidityMaths.add(sumValue, value); + sumBalance = SolidityMaths.add(sumBalance, balance); + } + // if at least the partial value of the pool is known + // then compute the rest of the value of tokens with unknown prices + if (sumBalance > BigInt(0)) { + const avgPrice = SolidityMaths.divDownFixed(sumValue, sumBalance); + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + + if (token.price?.usd) { + continue; + } + + const balance = parseFixed(token.balance, SCALING_FACTOR).toBigInt(); + + const value = SolidityMaths.mulDownFixed(balance, avgPrice); + sumValue = SolidityMaths.add(sumValue, value); + sumBalance = SolidityMaths.add(sumBalance, balance); + } + } + return formatFixed(sumValue.toString(), SCALING_FACTOR).toString(); + } +} diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/priceImpact.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/priceImpact.concern.ts new file mode 100644 index 000000000..8d32e005c --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/priceImpact.concern.ts @@ -0,0 +1,19 @@ +import { PriceImpactConcern } from '@/modules/pools/pool-types/concerns/types'; +import { Pool } from '@/types'; + +export class GyroEPriceImpactConcern implements PriceImpactConcern { + bptZeroPriceImpact(pool: Pool, tokenAmounts: bigint[]): bigint { + console.log(pool, tokenAmounts); + throw new Error('Not implemented'); + } + + calcPriceImpact( + pool: Pool, + tokenAmounts: bigint[], + bptAmount: bigint, + isJoin: boolean + ): string { + console.log(pool, tokenAmounts, bptAmount, isJoin); + throw new Error('Not implemented'); + } +} diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/spotPrice.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/spotPrice.concern.ts new file mode 100644 index 000000000..d5b910d3b --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/spotPrice.concern.ts @@ -0,0 +1,9 @@ +import { SpotPriceConcern } from '@/modules/pools/pool-types/concerns/types'; +import { Pool } from '@/types'; + +export class GyroESpotPriceConcern implements SpotPriceConcern { + calcPoolSpotPrice(tokenIn: string, tokenOut: string, pool: Pool): string { + console.log(tokenIn, tokenOut, pool); + throw new Error('Not implemented'); + } +} diff --git a/balancer-js/src/modules/pools/pool-types/gyro-e.module.ts b/balancer-js/src/modules/pools/pool-types/gyro-e.module.ts new file mode 100644 index 000000000..0476a262e --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/gyro-e.module.ts @@ -0,0 +1,23 @@ +import { PoolType } from './pool-type.interface'; +import { + ExitConcern, + JoinConcern, + LiquidityConcern, + PriceImpactConcern, + SpotPriceConcern, +} from '@/modules/pools/pool-types/concerns/types'; +import { GyroEExitConcern } from '@/modules/pools/pool-types/concerns/gyro-e/exit.concern'; +import { GyroELiquidityConcern } from '@/modules/pools/pool-types/concerns/gyro-e/liquidity.concern'; +import { GyroESpotPriceConcern } from '@/modules/pools/pool-types/concerns/gyro-e/spotPrice.concern'; +import { GyroEPriceImpactConcern } from '@/modules/pools/pool-types/concerns/gyro-e/priceImpact.concern'; +import { GyroEJoinConcern } from '@/modules/pools/pool-types/concerns/gyro-e/join.concern'; + +export class GyroE implements PoolType { + constructor( + public exit: ExitConcern = new GyroEExitConcern(), + public liquidity: LiquidityConcern = new GyroELiquidityConcern(), + public spotPriceCalculator: SpotPriceConcern = new GyroESpotPriceConcern(), + public priceImpactCalculator: PriceImpactConcern = new GyroEPriceImpactConcern(), + public join: JoinConcern = new GyroEJoinConcern() + ) {} +} From 984cc5a44bd1d5daff16268391c6fa211138f145 Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem de Liz Date: Tue, 9 May 2023 17:55:53 -0300 Subject: [PATCH 11/86] Removing unnecessary logs; --- .../concerns/fx/liquidity.concern.integration.spec.ts | 6 ------ .../concerns/gyro-e/liquidity.concern.integration.spec.ts | 6 ------ 2 files changed, 12 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts index 8446a0bb4..b8bad9128 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts @@ -54,12 +54,6 @@ describe('FX Pool - Calculate Liquidity', () => { ).total_.toBigInt(); const liquidityBigInt = parseFixed(liquidity, 18).toBigInt(); // expecting 5% of margin error - console.log( - formatFixed( - SolidityMaths.divDownFixed(liquidityBigInt, liquidityFromContract), - 18 - ).toString() - ); expect( parseFloat( formatFixed( diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts index d7cbc1edf..e011940bf 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts @@ -53,12 +53,6 @@ describe('GyroE Pool - Calculate Liquidity', () => { ).toBigInt(); const liquidityBigInt = parseFixed(liquidity, 18).toBigInt(); // expecting 5% of margin error - console.log( - formatFixed( - SolidityMaths.divDownFixed(liquidityBigInt, liquidityFromContract), - 18 - ).toString() - ); expect( parseFloat( formatFixed( From aca045f509b7bec25026a6abe862f8f821df1174 Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem de Liz Date: Tue, 9 May 2023 18:02:18 -0300 Subject: [PATCH 12/86] Removing unused imports; --- .../concerns/gyro-e/liquidity.concern.integration.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts index e011940bf..92d82e508 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts @@ -4,8 +4,6 @@ import { Network, PoolWithMethods } from '@/types'; import { forkSetup, TestPoolHelper } from '@/test/lib/utils'; import { ethers } from 'hardhat'; import { BalancerSDK } from '@/modules/sdk.module'; -import { FXPool__factory, GyroEPool__factory } from '@/contracts'; -import { Contract } from '@ethersproject/contracts'; import { expect } from 'chai'; import { formatFixed, parseFixed } from '@ethersproject/bignumber'; import { SolidityMaths } from '@/lib/utils/solidityMaths'; From 3eb4a1c61750a189c6c7f649065855158abb0d8b Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Wed, 10 May 2023 10:57:15 -0300 Subject: [PATCH 13/86] Update unwrap method to ERC4626 to conform with bbausd3 integration tests --- .../exits/exits.module.integration-mainnet.spec.ts | 7 +++++-- balancer-js/src/modules/exits/exits.module.ts | 9 ++++----- balancer-js/src/modules/relayer/relayer.module.ts | 11 +++++++++++ balancer-js/src/modules/relayer/types.ts | 8 ++++++++ .../src/modules/vaultModel/vaultModel.module.ts | 4 ++-- 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index f52913cc0..ba41d368b 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -100,7 +100,7 @@ const testFlow = async ( pool: { id: string; address: string; slot: number }, amount: string, unwrapTokens = false, - simulationType = SimulationType.Tenderly + simulationType = SimulationType.VaultModel ): Promise<{ expectedAmountsOut: string[]; gasUsed: BigNumber; @@ -218,7 +218,10 @@ describe('generalised exit execution', async function () { context('exit by unwrapping vs exit to main tokens', async () => { it('should return the same amount out', async () => { - expect(mainTokensAmountsOut).to.deep.eq(unwrappingTokensAmountsOut); + mainTokensAmountsOut.forEach((amount, i) => { + const unwrappedAmount = BigNumber.from(unwrappingTokensAmountsOut[i]); + expect(unwrappedAmount.sub(amount).toNumber()).to.be.closeTo(0, 1); + }); }); it('should spend more gas when unwrapping tokens', async () => { expect(unwrappingTokensGasUsed.gt(mainTokensGasUsed)).to.be.true; diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index b3427ee6c..666dc54b2 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -11,7 +11,7 @@ import { PoolGraph, Node } from '@/modules/graph/graph'; import { Join } from '@/modules/joins/joins.module'; import { calcPriceImpact } from '@/modules/pricing/priceImpact'; import { - EncodeUnwrapAaveStaticTokenInput, + EncodeUnwrapERC4626Input, Relayer, } from '@/modules/relayer/relayer.module'; import { @@ -631,15 +631,14 @@ export class Exit { // )` // ); - const call: EncodeUnwrapAaveStaticTokenInput = { - staticToken: node.address, + const call: EncodeUnwrapERC4626Input = { + wrappedToken: node.address, sender, recipient, amount, - toUnderlying: true, outputReference, }; - const encodedCall = Relayer.encodeUnwrapAaveStaticToken(call); + const encodedCall = Relayer.encodeUnwrapERC4626(call); const modelRequest = VaultModel.mapUnwrapRequest( call, diff --git a/balancer-js/src/modules/relayer/relayer.module.ts b/balancer-js/src/modules/relayer/relayer.module.ts index 909081147..4be5a47c1 100644 --- a/balancer-js/src/modules/relayer/relayer.module.ts +++ b/balancer-js/src/modules/relayer/relayer.module.ts @@ -8,6 +8,7 @@ import { EncodeExitPoolInput, EncodeJoinPoolInput, EncodeUnwrapAaveStaticTokenInput, + EncodeUnwrapERC4626Input, EncodeWrapAaveDynamicTokenInput, ExitPoolData, JoinPoolData, @@ -146,6 +147,16 @@ export class Relayer { ]); } + static encodeUnwrapERC4626(params: EncodeUnwrapERC4626Input): string { + return relayerLibrary.encodeFunctionData('unwrapERC4626', [ + params.wrappedToken, + params.sender, + params.recipient, + params.amount, + params.outputReference, + ]); + } + static encodePeekChainedReferenceValue(reference: BigNumberish): string { return relayerLibrary.encodeFunctionData('peekChainedReferenceValue', [ reference, diff --git a/balancer-js/src/modules/relayer/types.ts b/balancer-js/src/modules/relayer/types.ts index 74e000849..474b708f6 100644 --- a/balancer-js/src/modules/relayer/types.ts +++ b/balancer-js/src/modules/relayer/types.ts @@ -63,5 +63,13 @@ export interface EncodeUnwrapAaveStaticTokenInput { outputReference: BigNumberish; } +export interface EncodeUnwrapERC4626Input { + wrappedToken: string; + sender: string; + recipient: string; + amount: BigNumberish; + outputReference: BigNumberish; +} + export type ExitPoolData = ExitPoolRequest & EncodeExitPoolInput; export type JoinPoolData = JoinPoolRequest & EncodeJoinPoolInput; diff --git a/balancer-js/src/modules/vaultModel/vaultModel.module.ts b/balancer-js/src/modules/vaultModel/vaultModel.module.ts index b786138ab..da11775ef 100644 --- a/balancer-js/src/modules/vaultModel/vaultModel.module.ts +++ b/balancer-js/src/modules/vaultModel/vaultModel.module.ts @@ -13,7 +13,7 @@ import { EncodeBatchSwapInput, EncodeJoinPoolInput, EncodeExitPoolInput, - EncodeUnwrapAaveStaticTokenInput, + EncodeUnwrapERC4626Input, } from '../relayer/types'; import { Swap } from '../swaps/types'; @@ -139,7 +139,7 @@ export class VaultModel { } static mapUnwrapRequest( - call: EncodeUnwrapAaveStaticTokenInput, + call: EncodeUnwrapERC4626Input, poolId: string ): UnwrapRequest { const unwrapRequest: UnwrapRequest = { From b0967e46d07914fe5397eada14358f2d3b864948 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Wed, 10 May 2023 11:48:04 -0300 Subject: [PATCH 14/86] Remove unused bbeusd integration test --- .../exits/exits.module.integration-mainnet.spec.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index ba41d368b..e432becbe 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -34,7 +34,6 @@ import { SimulationType } from '../simulation/simulation.module'; dotenv.config(); -const TEST_BBEUSD = false; const TEST_ETH_STABLE = true; /* @@ -177,16 +176,6 @@ const testFlow = async ( describe('generalised exit execution', async function () { this.timeout(120000); // Sets timeout for all tests within this scope to 2 minutes - context('bbeusd', async () => { - if (!TEST_BBEUSD) return true; - const pool = addresses.bbeusd; - const amount = parseFixed('2', pool.decimals).toString(); - - it('should exit pool correctly', async () => { - await testFlow(pool, amount); - }); - }); - context('bbausd3', async () => { if (!TEST_ETH_STABLE) return true; const pool = addresses.bbausd3; From 7eb67e210d7a524a4752e102eec31aa115a0c6c3 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Wed, 10 May 2023 12:02:03 -0300 Subject: [PATCH 15/86] Add wrapped token cases back to graph unit tests --- .../src/modules/graph/graph.module.spec.ts | 293 +++++++++++++----- 1 file changed, 214 insertions(+), 79 deletions(-) diff --git a/balancer-js/src/modules/graph/graph.module.spec.ts b/balancer-js/src/modules/graph/graph.module.spec.ts index 4745c9d26..e911b2e61 100644 --- a/balancer-js/src/modules/graph/graph.module.spec.ts +++ b/balancer-js/src/modules/graph/graph.module.spec.ts @@ -71,8 +71,8 @@ function checkLinearNode( 'N/A', wrappedTokens[poolIndex].address, 'WrappedToken', - 'wrapAaveDynamicToken', - 'unwrapAaveStaticToken', + 'wrap', + 'unwrap', 1, (expectedOutPutReference + 1).toString(), linearPools[poolIndex].proportionOfParent @@ -247,27 +247,32 @@ describe('Graph', () => { linearInfo.linearPools as unknown as SdkPool[] ); poolsGraph = new PoolGraph(poolProvider); - rootNode = await poolsGraph.buildGraphFromRootPool( - linearInfo.linearPools[0].id - ); - }); - it('should build single linearPool graph', async () => { - checkLinearNode( - rootNode, - 0, - linearInfo.linearPools, - linearInfo.wrappedTokens, - linearInfo.mainTokens, - 0, - false - ); }); + context('using non-wrapped tokens', () => { + before(async () => { + rootNode = await poolsGraph.buildGraphFromRootPool( + linearInfo.linearPools[0].id, + false + ); + }); + it('should build single linearPool graph', async () => { + checkLinearNode( + rootNode, + 0, + linearInfo.linearPools, + linearInfo.wrappedTokens, + linearInfo.mainTokens, + 0, + false + ); + }); - it('should sort in breadth first order', async () => { - const orderedNodes = PoolGraph.orderByBfs(rootNode).reverse(); - expect(orderedNodes.length).to.eq(2); - expect(orderedNodes[0].type).to.eq('Input'); - expect(orderedNodes[1].type).to.eq('AaveLinear'); + it('should sort in breadth first order', async () => { + const orderedNodes = PoolGraph.orderByBfs(rootNode).reverse(); + expect(orderedNodes.length).to.eq(2); + expect(orderedNodes[0].type).to.eq('Input'); + expect(orderedNodes[1].type).to.eq('AaveLinear'); + }); }); }); @@ -318,40 +323,83 @@ describe('Graph', () => { pools as unknown as SdkPool[] ); poolsGraph = new PoolGraph(poolProvider); - boostedNode = await poolsGraph.buildGraphFromRootPool(boostedPool.id); }); it('should throw when pool doesnt exist', async () => { let errorMessage = ''; try { - await poolsGraph.buildGraphFromRootPool('thisisntapool'); + await poolsGraph.buildGraphFromRootPool('thisisntapool', true); } catch (error) { errorMessage = (error as Error).message; } expect(errorMessage).to.eq('balancer pool does not exist'); }); - it('should build boostedPool graph', async () => { - checkBoosted( - boostedNode, - boostedPoolInfo.rootPool, - boostedPoolInfo, - 0, - '1', - false - ); + context('using wrapped tokens', () => { + before(async () => { + boostedNode = await poolsGraph.buildGraphFromRootPool( + boostedPool.id, + true + ); + }); + + it('should build boostedPool graph', async () => { + checkBoosted( + boostedNode, + boostedPoolInfo.rootPool, + boostedPoolInfo, + 0, + '1', + true + ); + }); + + it('should sort in breadth first order', async () => { + const orderedNodes = PoolGraph.orderByBfs(boostedNode).reverse(); + expect(orderedNodes.length).to.eq(10); + expect(orderedNodes[0].type).to.eq('Input'); + expect(orderedNodes[1].type).to.eq('Input'); + expect(orderedNodes[2].type).to.eq('Input'); + expect(orderedNodes[3].type).to.eq('WrappedToken'); + expect(orderedNodes[4].type).to.eq('WrappedToken'); + expect(orderedNodes[5].type).to.eq('WrappedToken'); + expect(orderedNodes[6].type).to.eq('AaveLinear'); + expect(orderedNodes[7].type).to.eq('AaveLinear'); + expect(orderedNodes[8].type).to.eq('AaveLinear'); + expect(orderedNodes[9].type).to.eq('ComposableStable'); + }); }); - it('should sort in breadth first order', async () => { - const orderedNodes = PoolGraph.orderByBfs(boostedNode).reverse(); - expect(orderedNodes.length).to.eq(7); - expect(orderedNodes[0].type).to.eq('Input'); - expect(orderedNodes[1].type).to.eq('Input'); - expect(orderedNodes[2].type).to.eq('Input'); - expect(orderedNodes[3].type).to.eq('AaveLinear'); - expect(orderedNodes[4].type).to.eq('AaveLinear'); - expect(orderedNodes[5].type).to.eq('AaveLinear'); - expect(orderedNodes[6].type).to.eq('ComposableStable'); + context('using non-wrapped tokens', () => { + before(async () => { + boostedNode = await poolsGraph.buildGraphFromRootPool( + boostedPool.id, + false + ); + }); + + it('should build boostedPool graph', async () => { + checkBoosted( + boostedNode, + boostedPoolInfo.rootPool, + boostedPoolInfo, + 0, + '1', + false + ); + }); + + it('should sort in breadth first order', async () => { + const orderedNodes = PoolGraph.orderByBfs(boostedNode).reverse(); + expect(orderedNodes.length).to.eq(7); + expect(orderedNodes[0].type).to.eq('Input'); + expect(orderedNodes[1].type).to.eq('Input'); + expect(orderedNodes[2].type).to.eq('Input'); + expect(orderedNodes[3].type).to.eq('AaveLinear'); + expect(orderedNodes[4].type).to.eq('AaveLinear'); + expect(orderedNodes[5].type).to.eq('AaveLinear'); + expect(orderedNodes[6].type).to.eq('ComposableStable'); + }); }); }); @@ -429,26 +477,66 @@ describe('Graph', () => { pools as unknown as SdkPool[] ); poolsGraph = new PoolGraph(poolProvider); - boostedNode = await poolsGraph.buildGraphFromRootPool(rootPool.id); }); - it('should build boostedPool graph', async () => { - checkBoostedMeta(boostedNode, boostedMetaInfo, false); + context('using wrapped tokens', () => { + before(async () => { + boostedNode = await poolsGraph.buildGraphFromRootPool( + rootPool.id, + true + ); + }); + + it('should build boostedPool graph', async () => { + checkBoostedMeta(boostedNode, boostedMetaInfo, true); + }); + + it('should sort in breadth first order', async () => { + const orderedNodes = PoolGraph.orderByBfs(boostedNode).reverse(); + expect(orderedNodes.length).to.eq(14); + expect(orderedNodes[0].type).to.eq('Input'); + expect(orderedNodes[1].type).to.eq('Input'); + expect(orderedNodes[2].type).to.eq('Input'); + expect(orderedNodes[3].type).to.eq('Input'); + expect(orderedNodes[4].type).to.eq('WrappedToken'); + expect(orderedNodes[5].type).to.eq('WrappedToken'); + expect(orderedNodes[6].type).to.eq('WrappedToken'); + expect(orderedNodes[7].type).to.eq('WrappedToken'); + expect(orderedNodes[8].type).to.eq('AaveLinear'); + expect(orderedNodes[9].type).to.eq('AaveLinear'); + expect(orderedNodes[10].type).to.eq('AaveLinear'); + expect(orderedNodes[11].type).to.eq('AaveLinear'); + expect(orderedNodes[12].type).to.eq('ComposableStable'); + expect(orderedNodes[13].type).to.eq('ComposableStable'); + }); }); - it('should sort in breadth first order', async () => { - const orderedNodes = PoolGraph.orderByBfs(boostedNode).reverse(); - expect(orderedNodes.length).to.eq(10); - expect(orderedNodes[0].type).to.eq('Input'); - expect(orderedNodes[1].type).to.eq('Input'); - expect(orderedNodes[2].type).to.eq('Input'); - expect(orderedNodes[3].type).to.eq('Input'); - expect(orderedNodes[4].type).to.eq('AaveLinear'); - expect(orderedNodes[5].type).to.eq('AaveLinear'); - expect(orderedNodes[6].type).to.eq('AaveLinear'); - expect(orderedNodes[7].type).to.eq('AaveLinear'); - expect(orderedNodes[8].type).to.eq('ComposableStable'); - expect(orderedNodes[9].type).to.eq('ComposableStable'); + context('using non-wrapped tokens', () => { + before(async () => { + boostedNode = await poolsGraph.buildGraphFromRootPool( + rootPool.id, + false + ); + }); + + it('should build boostedPool graph', async () => { + checkBoostedMeta(boostedNode, boostedMetaInfo, false); + }); + + it('should sort in breadth first order', async () => { + const orderedNodes = PoolGraph.orderByBfs(boostedNode).reverse(); + expect(orderedNodes.length).to.eq(10); + expect(orderedNodes[0].type).to.eq('Input'); + expect(orderedNodes[1].type).to.eq('Input'); + expect(orderedNodes[2].type).to.eq('Input'); + expect(orderedNodes[3].type).to.eq('Input'); + expect(orderedNodes[4].type).to.eq('AaveLinear'); + expect(orderedNodes[5].type).to.eq('AaveLinear'); + expect(orderedNodes[6].type).to.eq('AaveLinear'); + expect(orderedNodes[7].type).to.eq('AaveLinear'); + expect(orderedNodes[8].type).to.eq('ComposableStable'); + expect(orderedNodes[9].type).to.eq('ComposableStable'); + }); }); }); @@ -545,31 +633,78 @@ describe('Graph', () => { pools as unknown as SdkPool[] ); poolsGraph = new PoolGraph(poolProvider); - boostedNode = await poolsGraph.buildGraphFromRootPool(boostedPool.id); }); - it('should build boostedPool graph', async () => { - checkBoostedMetaBig(boostedNode, boostedMetaBigInfo, false); + context('using wrapped tokens', () => { + before(async () => { + boostedNode = await poolsGraph.buildGraphFromRootPool( + boostedPool.id, + true + ); + }); + + it('should build boostedPool graph', async () => { + checkBoostedMetaBig(boostedNode, boostedMetaBigInfo, true); + }); + + it('should sort in breadth first order', async () => { + const orderedNodes = PoolGraph.orderByBfs(boostedNode).reverse(); + expect(orderedNodes.length).to.eq(21); + expect(orderedNodes[0].type).to.eq('Input'); + expect(orderedNodes[1].type).to.eq('Input'); + expect(orderedNodes[2].type).to.eq('Input'); + expect(orderedNodes[3].type).to.eq('Input'); + expect(orderedNodes[4].type).to.eq('Input'); + expect(orderedNodes[5].type).to.eq('Input'); + expect(orderedNodes[6].type).to.eq('WrappedToken'); + expect(orderedNodes[7].type).to.eq('WrappedToken'); + expect(orderedNodes[8].type).to.eq('WrappedToken'); + expect(orderedNodes[9].type).to.eq('WrappedToken'); + expect(orderedNodes[10].type).to.eq('WrappedToken'); + expect(orderedNodes[11].type).to.eq('WrappedToken'); + expect(orderedNodes[12].type).to.eq('AaveLinear'); + expect(orderedNodes[13].type).to.eq('AaveLinear'); + expect(orderedNodes[14].type).to.eq('AaveLinear'); + expect(orderedNodes[15].type).to.eq('AaveLinear'); + expect(orderedNodes[16].type).to.eq('AaveLinear'); + expect(orderedNodes[17].type).to.eq('AaveLinear'); + expect(orderedNodes[18].type).to.eq('ComposableStable'); + expect(orderedNodes[19].type).to.eq('ComposableStable'); + expect(orderedNodes[20].type).to.eq('ComposableStable'); + }); }); - it('should sort in breadth first order', async () => { - const orderedNodes = PoolGraph.orderByBfs(boostedNode).reverse(); - expect(orderedNodes.length).to.eq(15); - expect(orderedNodes[0].type).to.eq('Input'); - expect(orderedNodes[1].type).to.eq('Input'); - expect(orderedNodes[2].type).to.eq('Input'); - expect(orderedNodes[3].type).to.eq('Input'); - expect(orderedNodes[4].type).to.eq('Input'); - expect(orderedNodes[5].type).to.eq('Input'); - expect(orderedNodes[6].type).to.eq('AaveLinear'); - expect(orderedNodes[7].type).to.eq('AaveLinear'); - expect(orderedNodes[8].type).to.eq('AaveLinear'); - expect(orderedNodes[9].type).to.eq('AaveLinear'); - expect(orderedNodes[10].type).to.eq('AaveLinear'); - expect(orderedNodes[11].type).to.eq('AaveLinear'); - expect(orderedNodes[12].type).to.eq('ComposableStable'); - expect(orderedNodes[13].type).to.eq('ComposableStable'); - expect(orderedNodes[14].type).to.eq('ComposableStable'); + context('using non-wrapped tokens', () => { + before(async () => { + boostedNode = await poolsGraph.buildGraphFromRootPool( + boostedPool.id, + false + ); + }); + + it('should build boostedPool graph', async () => { + checkBoostedMetaBig(boostedNode, boostedMetaBigInfo, false); + }); + + it('should sort in breadth first order', async () => { + const orderedNodes = PoolGraph.orderByBfs(boostedNode).reverse(); + expect(orderedNodes.length).to.eq(15); + expect(orderedNodes[0].type).to.eq('Input'); + expect(orderedNodes[1].type).to.eq('Input'); + expect(orderedNodes[2].type).to.eq('Input'); + expect(orderedNodes[3].type).to.eq('Input'); + expect(orderedNodes[4].type).to.eq('Input'); + expect(orderedNodes[5].type).to.eq('Input'); + expect(orderedNodes[6].type).to.eq('AaveLinear'); + expect(orderedNodes[7].type).to.eq('AaveLinear'); + expect(orderedNodes[8].type).to.eq('AaveLinear'); + expect(orderedNodes[9].type).to.eq('AaveLinear'); + expect(orderedNodes[10].type).to.eq('AaveLinear'); + expect(orderedNodes[11].type).to.eq('AaveLinear'); + expect(orderedNodes[12].type).to.eq('ComposableStable'); + expect(orderedNodes[13].type).to.eq('ComposableStable'); + expect(orderedNodes[14].type).to.eq('ComposableStable'); + }); }); }); }); From 6896c288e8acc652f8a0a1759619f759978db2a2 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Wed, 10 May 2023 12:06:42 -0300 Subject: [PATCH 16/86] Remove wrap action handling from generalised join --- balancer-js/src/modules/joins/joins.module.ts | 57 +------------------ 1 file changed, 2 insertions(+), 55 deletions(-) diff --git a/balancer-js/src/modules/joins/joins.module.ts b/balancer-js/src/modules/joins/joins.module.ts index 9bb3a6802..d0babbffb 100644 --- a/balancer-js/src/modules/joins/joins.module.ts +++ b/balancer-js/src/modules/joins/joins.module.ts @@ -3,11 +3,7 @@ import { BigNumber, parseFixed } from '@ethersproject/bignumber'; import { AddressZero, WeiPerEther, Zero } from '@ethersproject/constants'; import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; -import { - EncodeJoinPoolInput, - EncodeWrapAaveDynamicTokenInput, - Relayer, -} from '@/modules/relayer/relayer.module'; +import { EncodeJoinPoolInput, Relayer } from '@/modules/relayer/relayer.module'; import { FundManagement, SingleSwap, @@ -537,7 +533,6 @@ export class Join { // Sender's rule // 1. If any child node is an input node, tokens are coming from the user - // 2. Wrapped tokens have to come from user (Relayer has no approval for wrapped tokens) const hasChildInput = node.children .filter((c) => this.shouldBeConsidered(c)) .some((c) => c.joinAction === 'input'); @@ -564,18 +559,6 @@ export class Join { isLastChainedCall && minAmountsOut ? minAmountsOut[j] : '0'; switch (node.joinAction) { - case 'wrap': - { - const { encodedCall } = this.createWrap( - node, - j, - sender, - recipient - ); - // modelRequests.push(modelRequest); // TODO: add model request when available - encodedCalls.push(encodedCall); - } - break; case 'batchSwap': { const { modelRequest, encodedCall, assets, amounts } = @@ -685,42 +668,6 @@ export class Join { return node; }; - private createWrap = ( - node: Node, - joinPathIndex: number, - sender: string, - recipient: string - ): { encodedCall: string } => { - // Throws error based on the assumption that aaveWrap apply only to input tokens from leaf nodes - if (node.children.length !== 1) - throw new Error('aaveWrap nodes should always have a single child node'); - - const childNode = node.children[0]; - - const staticToken = node.address; - const amount = childNode.index; - const call: EncodeWrapAaveDynamicTokenInput = { - staticToken, - sender, - recipient, - amount, - fromUnderlying: true, - outputReference: this.getOutputRefValue(joinPathIndex, node).value, - }; - const encodedCall = Relayer.encodeWrapAaveDynamicToken(call); - - // console.log( - // `${node.type} ${node.address} prop: ${node.proportionOfParent.toString()} - // ${node.joinAction} ( - // staticToken: ${staticToken}, - // input: ${amount}, - // outputRef: ${node.index.toString()} - // )` - // ); - - return { encodedCall }; - }; - private createSwap = ( node: Node, joinPathIndex: number, @@ -733,7 +680,7 @@ export class Join { assets: string[]; amounts: string[]; } => { - // We only need swaps for main/wrapped > linearBpt so shouldn't be more than token > token + // We only need swaps for main > linearBpt so shouldn't be more than token > token if (node.children.length !== 1) throw new Error('Unsupported swap'); const tokenIn = node.children[0].address; const amountIn = this.getOutputRefValue(joinPathIndex, node.children[0]); From 0f37dbf11b2cfa1fae1425e7394ca9f84b9a687f Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Wed, 10 May 2023 12:11:39 -0300 Subject: [PATCH 17/86] Update comments/docs on doUnwrap method --- balancer-js/src/modules/vaultModel/poolModel/unwrap.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/balancer-js/src/modules/vaultModel/poolModel/unwrap.ts b/balancer-js/src/modules/vaultModel/poolModel/unwrap.ts index dc56271c5..e13dfb93f 100644 --- a/balancer-js/src/modules/vaultModel/poolModel/unwrap.ts +++ b/balancer-js/src/modules/vaultModel/poolModel/unwrap.ts @@ -19,9 +19,10 @@ export class UnwrapModel { constructor(private relayerModel: RelayerModel) {} /** - * Perform the specified exit type. - * @param exitPoolRequest - * @returns tokens out + * Perform the specified unwrap type. + * @param unwrapRequest + * @param pools + * @returns tokens out and their respective deltas */ async doUnwrap( unwrapRequest: UnwrapRequest, From 802a595204e061234bf6f022542126c60f55bc1c Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Wed, 10 May 2023 16:17:48 -0300 Subject: [PATCH 18/86] Add logic to decide when to exit by unwrapping tokens to generalisedExit --- .../exits.module.integration-mainnet.spec.ts | 29 +++++++++++------ balancer-js/src/modules/exits/exits.module.ts | 32 +++++++++++++++++-- balancer-js/src/modules/graph/graph.ts | 27 +++++++++++----- balancer-js/src/modules/pools/index.ts | 6 ++-- 4 files changed, 70 insertions(+), 24 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index e432becbe..1a6f7876a 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -98,7 +98,6 @@ const relayer = contractAddresses.relayer; const testFlow = async ( pool: { id: string; address: string; slot: number }, amount: string, - unwrapTokens = false, simulationType = SimulationType.VaultModel ): Promise<{ expectedAmountsOut: string[]; @@ -135,8 +134,7 @@ const testFlow = async ( slippage, signer, simulationType, - authorisation, - unwrapTokens + authorisation ); const { transactionReceipt, balanceDeltas, gasUsed } = @@ -179,18 +177,22 @@ describe('generalised exit execution', async function () { context('bbausd3', async () => { if (!TEST_ETH_STABLE) return true; const pool = addresses.bbausd3; - const amount = parseFixed('0.2', pool.decimals).toString(); let unwrappingTokensAmountsOut: string[]; let unwrappingTokensGasUsed: BigNumber; let mainTokensAmountsOut: string[]; let mainTokensGasUsed: BigNumber; + const amountRatio = 10; + // Amount greater than the underlying main token balance, which will cause the exit to be unwrapped + const unwrapExitAmount = parseFixed('6000000', pool.decimals); + // Amount smaller than the underlying main token balance, which will cause the exit to be done directly + const mainExitAmount = unwrapExitAmount.div(amountRatio); + context('exit by unwrapping tokens', async () => { it('should exit pool correctly', async () => { const { expectedAmountsOut, gasUsed } = await testFlow( pool, - amount, - true + unwrapExitAmount.toString() ); unwrappingTokensAmountsOut = expectedAmountsOut; unwrappingTokensGasUsed = gasUsed; @@ -199,17 +201,24 @@ describe('generalised exit execution', async function () { context('exit to main tokens directly', async () => { it('should exit pool correctly', async () => { - const { expectedAmountsOut, gasUsed } = await testFlow(pool, amount); + const { expectedAmountsOut, gasUsed } = await testFlow( + pool, + mainExitAmount.toString() + ); mainTokensAmountsOut = expectedAmountsOut; mainTokensGasUsed = gasUsed; }); }); context('exit by unwrapping vs exit to main tokens', async () => { - it('should return the same amount out', async () => { + it('should return similar amounts (proportional to the input)', async () => { mainTokensAmountsOut.forEach((amount, i) => { - const unwrappedAmount = BigNumber.from(unwrappingTokensAmountsOut[i]); - expect(unwrappedAmount.sub(amount).toNumber()).to.be.closeTo(0, 1); + const unwrappedAmount = BigNumber.from( + unwrappingTokensAmountsOut[i] + ).div(amountRatio); + expect( + accuracy(unwrappedAmount, BigNumber.from(amount)) + ).to.be.closeTo(1, 1e-4); // inaccuracy should not be over 1 bps }); }); it('should spend more gas when unwrapping tokens', async () => { diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index 666dc54b2..e2c760c31 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -67,8 +67,6 @@ export class Exit { minAmountsOut: string[]; priceImpact: string; }> { - console.log('unwrapTokens', unwrapTokens); - /* Overall exit flow description: - Create calls with 0 expected min amount for each token out @@ -94,6 +92,7 @@ export class Exit { let tokensOut: string[] = []; const outputNodes = orderedNodes.filter((n) => n.exitAction === 'output'); + const outputBalances = outputNodes.map((n) => n.balance); tokensOutByExitPath = outputNodes.map((n) => n.address.toLowerCase()); tokensOut = [...new Set(tokensOutByExitPath)].sort(); @@ -134,6 +133,35 @@ export class Exit { simulationType ); + const hasSufficientBalance = outputBalances.every((balance, i) => + BigNumber.from(balance).gt(expectedAmountsOutByExitPath[i]) + ); + + if (!hasSufficientBalance) { + if (unwrapTokens) { + /** + * This case might happen when a whale tries to exit with an amount that + * is at the same time larger than both main and wrapped token balances + */ + throw new Error( + 'Insufficient pool balance to perform generalised exit - try exitting with smaller amounts' + ); + } else { + return this.exitPool( + poolId, + amountBptIn, + userAddress, + slippage, + signer, + simulationType, + authorisation, + true + ); + } + } + + console.log('unwrapTokens', unwrapTokens); + const expectedAmountsOutByTokenOut = this.amountsOutByTokenOut( tokensOut, tokensOutByExitPath, diff --git a/balancer-js/src/modules/graph/graph.ts b/balancer-js/src/modules/graph/graph.ts index 25432e5c5..d33a5a747 100644 --- a/balancer-js/src/modules/graph/graph.ts +++ b/balancer-js/src/modules/graph/graph.ts @@ -24,6 +24,7 @@ export interface Node { isLeaf: boolean; spotPrices: SpotPrices; decimals: number; + balance: string; } type JoinAction = 'input' | 'batchSwap' | 'wrap' | 'joinPool'; @@ -104,21 +105,21 @@ export class PoolGraph { throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); } else { // If pool not found by address, but it has parent, assume it's a leaf token and add a leafTokenNode - // TODO: maybe it's a safety issue? Can we be safer? const parentPool = (await this.pools.findBy( 'address', parent.address )) as Pool; - const leafTokenDecimals = - parentPool.tokens[parentPool.tokensList.indexOf(address)].decimals ?? - 18; + const tokenIndex = parentPool.tokensList.indexOf(address); + const leafTokenDecimals = parentPool.tokens[tokenIndex].decimals ?? 18; + const { balancesEvm } = parsePoolInfo(parentPool); const nodeInfo = PoolGraph.createInputTokenNode( nodeIndex, address, leafTokenDecimals, parent, - proportionOfParent + proportionOfParent, + balancesEvm[tokenIndex].toString() ); return nodeInfo; } @@ -164,6 +165,7 @@ export class PoolGraph { isLeaf: false, spotPrices, decimals, + balance: pool.totalShares, }; this.updateNodeIfProportionalExit(pool, poolNode); nodeIndex++; @@ -229,6 +231,7 @@ export class PoolGraph { // Main token if (linearPool.mainIndex === undefined) throw new Error('Issue With Linear Pool'); + if (wrapMainTokens) { // Linear pool will be joined via wrapped token. This will be the child node. const wrappedNodeInfo = this.createWrappedTokenNode( @@ -240,6 +243,7 @@ export class PoolGraph { linearPoolNode.children.push(wrappedNodeInfo[0]); return [linearPoolNode, wrappedNodeInfo[1]]; } else { + const { balancesEvm } = parsePoolInfo(linearPool); const mainTokenDecimals = linearPool.tokens[linearPool.mainIndex].decimals ?? 18; @@ -248,7 +252,8 @@ export class PoolGraph { linearPool.tokensList[linearPool.mainIndex], mainTokenDecimals, linearPoolNode, - linearPoolNode.proportionOfParent + linearPoolNode.proportionOfParent, + balancesEvm[linearPool.mainIndex].toString() ); linearPoolNode.children.push(nodeInfo[0]); nodeIndex = nodeInfo[1]; @@ -268,6 +273,8 @@ export class PoolGraph { ) throw new Error('Issue With Linear Pool'); + const { balancesEvm, upScaledBalances } = parsePoolInfo(linearPool); + const wrappedTokenNode: Node = { type: 'WrappedToken', address: linearPool.tokensList[linearPool.wrappedIndex], @@ -283,6 +290,7 @@ export class PoolGraph { isLeaf: false, spotPrices: {}, decimals: 18, + balance: balancesEvm[linearPool.wrappedIndex].toString(), }; nodeIndex++; @@ -294,7 +302,8 @@ export class PoolGraph { linearPool.tokensList[linearPool.mainIndex], mainTokenDecimals, wrappedTokenNode, - proportionOfParent + proportionOfParent, + upScaledBalances[linearPool.wrappedIndex].toString() // takes price rate into account ); wrappedTokenNode.children = [inputNode[0]]; nodeIndex = inputNode[1]; @@ -306,7 +315,8 @@ export class PoolGraph { address: string, decimals: number, parent: Node | undefined, - proportionOfParent: BigNumber + proportionOfParent: BigNumber, + balance: string ): [Node, number] { return [ { @@ -324,6 +334,7 @@ export class PoolGraph { isLeaf: true, spotPrices: {}, decimals, + balance, }, nodeIndex + 1, ]; diff --git a/balancer-js/src/modules/pools/index.ts b/balancer-js/src/modules/pools/index.ts index da2b38791..4bc34fc05 100644 --- a/balancer-js/src/modules/pools/index.ts +++ b/balancer-js/src/modules/pools/index.ts @@ -358,7 +358,6 @@ export class Pools implements Findable { * @param signer JsonRpcSigner that will sign the staticCall transaction if Static simulation chosen * @param simulationType Simulation type (VaultModel, Tenderly or Static) * @param authorisation Optional auhtorisation call to be added to the chained transaction - * @param unwrapTokens true = exit by unwrapping wrapped tokens, false = exit to main tokens direclty * @returns transaction data ready to be sent to the network along with tokens, min and expected amounts out. */ async generalisedExit( @@ -368,8 +367,7 @@ export class Pools implements Findable { slippage: string, signer: JsonRpcSigner, simulationType: SimulationType, - authorisation?: string, - unwrapTokens = false + authorisation?: string ): Promise<{ to: string; encodedCall: string; @@ -386,7 +384,7 @@ export class Pools implements Findable { signer, simulationType, authorisation, - unwrapTokens + false ); } From 383629cdf32776e70309e377e29ef4e431108104 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Wed, 10 May 2023 16:26:28 -0300 Subject: [PATCH 19/86] Fix build issue --- balancer-js/src/modules/joins/joins.module.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/balancer-js/src/modules/joins/joins.module.ts b/balancer-js/src/modules/joins/joins.module.ts index d0babbffb..261296944 100644 --- a/balancer-js/src/modules/joins/joins.module.ts +++ b/balancer-js/src/modules/joins/joins.module.ts @@ -233,7 +233,8 @@ export class Join { nonLeafInputNode.address, nonLeafInputNode.decimals, nonLeafInputNode.parent, - WeiPerEther + WeiPerEther, + nonLeafInputNode.balance ); // Update index to be actual amount in inputTokenNode.index = proportionalNonLeafAmountIn; From c712df5d073d3b38227b0f35cb3238acfb43022e Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem de Liz Date: Wed, 10 May 2023 17:47:24 -0300 Subject: [PATCH 20/86] Adding Gyro2 and Gyro3 to the scope of Gyro concern Adding tests; Fixing problem with blocknumber from old tests; --- .../src/modules/pools/pool-type-concerns.ts | 8 +- .../join.concern.integration.spec.ts | 2 +- .../fx/liquidity.concern.integration.spec.ts | 2 +- .../liquidity.concern.integration.spec.ts | 63 -------- .../concerns/{gyro-e => gyro}/exit.concern.ts | 2 +- .../concerns/{gyro-e => gyro}/join.concern.ts | 2 +- .../liquidity.concern.integration.spec.ts | 141 ++++++++++++++++++ .../{gyro-e => gyro}/liquidity.concern.ts | 2 +- .../{gyro-e => gyro}/priceImpact.concern.ts | 2 +- .../{gyro-e => gyro}/spotPrice.concern.ts | 2 +- .../modules/pools/pool-types/gyro-e.module.ts | 23 --- .../modules/pools/pool-types/gyro.module.ts | 23 +++ 12 files changed, 176 insertions(+), 96 deletions(-) delete mode 100644 balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts rename balancer-js/src/modules/pools/pool-types/concerns/{gyro-e => gyro}/exit.concern.ts (93%) rename balancer-js/src/modules/pools/pool-types/concerns/{gyro-e => gyro}/join.concern.ts (88%) create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/gyro/liquidity.concern.integration.spec.ts rename balancer-js/src/modules/pools/pool-types/concerns/{gyro-e => gyro}/liquidity.concern.ts (96%) rename balancer-js/src/modules/pools/pool-types/concerns/{gyro-e => gyro}/priceImpact.concern.ts (87%) rename balancer-js/src/modules/pools/pool-types/concerns/{gyro-e => gyro}/spotPrice.concern.ts (80%) delete mode 100644 balancer-js/src/modules/pools/pool-types/gyro-e.module.ts create mode 100644 balancer-js/src/modules/pools/pool-types/gyro.module.ts diff --git a/balancer-js/src/modules/pools/pool-type-concerns.ts b/balancer-js/src/modules/pools/pool-type-concerns.ts index f22ec8e93..5e6d739bd 100644 --- a/balancer-js/src/modules/pools/pool-type-concerns.ts +++ b/balancer-js/src/modules/pools/pool-type-concerns.ts @@ -8,7 +8,7 @@ import { Linear } from './pool-types/linear.module'; import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; import { isLinearish } from '@/lib/utils'; import { FX } from '@/modules/pools/pool-types/fx.module'; -import { GyroE } from '@/modules/pools/pool-types/gyro-e.module'; +import { Gyro } from '@/modules/pools/pool-types/gyro.module'; /** * Wrapper around pool type specific methods. @@ -43,8 +43,10 @@ export class PoolTypeConcerns { case 'FX': { return new FX(); } - case 'GyroE': { - return new GyroE(); + case 'GyroE': + case 'Gyro2': + case 'Gyro3': { + return new Gyro(); } case 'MetaStable': { return new MetaStable(); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts index ee3fee103..21e75eabe 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts @@ -25,7 +25,7 @@ const { ALCHEMY_URL_POLYGON: jsonRpcUrl } = process.env; const rpcUrl = 'http://127.0.0.1:8137'; const provider = new ethers.providers.JsonRpcProvider(rpcUrl, network); const signer = provider.getSigner(); -const blockNumber = 41400000; +const blockNumber = 42505555; const testPoolId = '0x02d2e2d7a89d6c5cb3681cfcb6f7dac02a55eda400000000000000000000088f'; diff --git a/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts index b8bad9128..b3f8026d8 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts @@ -21,7 +21,7 @@ const signer = provider.getSigner(); const testPoolId = '0x726e324c29a1e49309672b244bdc4ff62a270407000200000000000000000702'; let pool: PoolWithMethods; -const blockNumber = 41400000; +const blockNumber = 42505555; describe('FX Pool - Calculate Liquidity', () => { const sdkConfig = { diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts deleted file mode 100644 index 92d82e508..000000000 --- a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.integration.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -// yarn test:only ./src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts -import dotenv from 'dotenv'; -import { Network, PoolWithMethods } from '@/types'; -import { forkSetup, TestPoolHelper } from '@/test/lib/utils'; -import { ethers } from 'hardhat'; -import { BalancerSDK } from '@/modules/sdk.module'; -import { expect } from 'chai'; -import { formatFixed, parseFixed } from '@ethersproject/bignumber'; -import { SolidityMaths } from '@/lib/utils/solidityMaths'; - -dotenv.config(); - -const network = Network.POLYGON; -const { ALCHEMY_URL_POLYGON: rpcUrlArchive } = process.env; -const rpcUrlLocal = 'http://127.0.0.1:8137'; - -const provider = new ethers.providers.JsonRpcProvider(rpcUrlLocal, network); -const signer = provider.getSigner(); -const testPoolId = - '0x97469e6236bd467cd147065f77752b00efadce8a0002000000000000000008c0'; -let pool: PoolWithMethods; -const blockNumber = 42505555; - -describe('GyroE Pool - Calculate Liquidity', () => { - const sdkConfig = { - network, - rpcUrl: rpcUrlLocal, - }; - const balancer = new BalancerSDK(sdkConfig); - before(async () => { - const testPool = new TestPoolHelper( - testPoolId, - network, - rpcUrlLocal, - blockNumber - ); - // Gets initial pool info from Subgraph - pool = await testPool.getPool(); - - // Setup forked network, set initial token balances and allowances - await forkSetup(signer, [], [], [], rpcUrlArchive as string, undefined); - - // Update pool info with onchain state from fork block no - pool = await testPool.getPool(); - }); - it('calculating liquidity', async () => { - const liquidity = await balancer.pools.liquidity(pool); - const liquidityFromContract = parseFixed( - parseFloat(pool.totalLiquidity).toFixed(18).toString(), - 18 - ).toBigInt(); - const liquidityBigInt = parseFixed(liquidity, 18).toBigInt(); - // expecting 5% of margin error - expect( - parseFloat( - formatFixed( - SolidityMaths.divDownFixed(liquidityBigInt, liquidityFromContract), - 18 - ).toString() - ) - ).to.be.closeTo(1, 0.05); - }); -}); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro/exit.concern.ts similarity index 93% rename from balancer-js/src/modules/pools/pool-types/concerns/gyro-e/exit.concern.ts rename to balancer-js/src/modules/pools/pool-types/concerns/gyro/exit.concern.ts index e9870a978..098c23f12 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/exit.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/gyro/exit.concern.ts @@ -6,7 +6,7 @@ import { ExitExactTokensOutParameters, } from '@/modules/pools/pool-types/concerns/types'; -export class GyroEExitConcern implements ExitConcern { +export class GyroExitConcern implements ExitConcern { buildExitExactTokensOut({ exiter, pool, diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/join.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro/join.concern.ts similarity index 88% rename from balancer-js/src/modules/pools/pool-types/concerns/gyro-e/join.concern.ts rename to balancer-js/src/modules/pools/pool-types/concerns/gyro/join.concern.ts index 63076fe4e..1c4f528d9 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/join.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/gyro/join.concern.ts @@ -4,7 +4,7 @@ import { JoinPoolParameters, } from '@/modules/pools/pool-types/concerns/types'; -export class GyroEJoinConcern implements JoinConcern { +export class GyroJoinConcern implements JoinConcern { buildJoin({ joiner, pool, diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro/liquidity.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro/liquidity.concern.integration.spec.ts new file mode 100644 index 000000000..5660d1979 --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/gyro/liquidity.concern.integration.spec.ts @@ -0,0 +1,141 @@ +// yarn test:only ./src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts +import dotenv from 'dotenv'; +import { Network, PoolWithMethods } from '@/types'; +import { forkSetup, TestPoolHelper } from '@/test/lib/utils'; +import { ethers } from 'hardhat'; +import { BalancerSDK } from '@/modules/sdk.module'; +import { expect } from 'chai'; +import { formatFixed, parseFixed } from '@ethersproject/bignumber'; +import { SolidityMaths } from '@/lib/utils/solidityMaths'; + +dotenv.config(); + +const network = Network.POLYGON; +const { ALCHEMY_URL_POLYGON: rpcUrlArchive } = process.env; +const rpcUrlLocal = 'http://127.0.0.1:8137'; + +const provider = new ethers.providers.JsonRpcProvider(rpcUrlLocal, network); +const signer = provider.getSigner(); +const blockNumber = 42505555; + +describe('Gyro Pools - Calculate Liquidity', () => { + const sdkConfig = { + network, + rpcUrl: rpcUrlLocal, + }; + const balancer = new BalancerSDK(sdkConfig); + context('GyroE Pools', () => { + const testPoolId = + '0x97469e6236bd467cd147065f77752b00efadce8a0002000000000000000008c0'; + let pool: PoolWithMethods; + before(async () => { + const testPool = new TestPoolHelper( + testPoolId, + network, + rpcUrlLocal, + blockNumber + ); + // Gets initial pool info from Subgraph + pool = await testPool.getPool(); + + // Setup forked network, set initial token balances and allowances + await forkSetup(signer, [], [], [], rpcUrlArchive as string, undefined); + + // Update pool info with onchain state from fork block no + pool = await testPool.getPool(); + }); + it('calculating liquidity', async () => { + const liquidity = await balancer.pools.liquidity(pool); + const liquidityFromContract = parseFixed( + parseFloat(pool.totalLiquidity).toFixed(18).toString(), + 18 + ).toBigInt(); + const liquidityBigInt = parseFixed(liquidity, 18).toBigInt(); + // expecting 5% of margin error + expect( + parseFloat( + formatFixed( + SolidityMaths.divDownFixed(liquidityBigInt, liquidityFromContract), + 18 + ).toString() + ) + ).to.be.closeTo(1, 0.05); + }); + }); + context('Gyro V2 Pools', () => { + const testPoolId = + '0xdac42eeb17758daa38caf9a3540c808247527ae3000200000000000000000a2b'; + let pool: PoolWithMethods; + before(async () => { + const testPool = new TestPoolHelper( + testPoolId, + network, + rpcUrlLocal, + blockNumber + ); + // Gets initial pool info from Subgraph + pool = await testPool.getPool(); + + // Setup forked network, set initial token balances and allowances + await forkSetup(signer, [], [], [], rpcUrlArchive as string, undefined); + + // Update pool info with onchain state from fork block no + pool = await testPool.getPool(); + }); + it('calculating liquidity', async () => { + const liquidity = await balancer.pools.liquidity(pool); + const liquidityFromContract = parseFixed( + parseFloat(pool.totalLiquidity).toFixed(18).toString(), + 18 + ).toBigInt(); + const liquidityBigInt = parseFixed(liquidity, 18).toBigInt(); + // expecting 5% of margin error + expect( + parseFloat( + formatFixed( + SolidityMaths.divDownFixed(liquidityBigInt, liquidityFromContract), + 18 + ).toString() + ) + ).to.be.closeTo(1, 0.05); + }); + }); + context('Gyro V3 Pools', () => { + const testPoolId = + '0x17f1ef81707811ea15d9ee7c741179bbe2a63887000100000000000000000799'; + let pool: PoolWithMethods; + before(async () => { + const testPool = new TestPoolHelper( + testPoolId, + network, + rpcUrlLocal, + blockNumber + ); + // Gets initial pool info from Subgraph + pool = await testPool.getPool(); + + // Setup forked network, set initial token balances and allowances + await forkSetup(signer, [], [], [], rpcUrlArchive as string, undefined); + + // Update pool info with onchain state from fork block no + pool = await testPool.getPool(); + }); + it('calculating liquidity', async () => { + const liquidity = await balancer.pools.liquidity(pool); + const liquidityFromContract = parseFixed( + parseFloat(pool.totalLiquidity).toFixed(18).toString(), + 18 + ).toBigInt(); + const liquidityBigInt = parseFixed(liquidity, 18).toBigInt(); + // expecting 5% of margin error + expect( + parseFloat( + formatFixed( + SolidityMaths.divDownFixed(liquidityBigInt, liquidityFromContract), + 18 + ).toString() + ) + ).to.be.closeTo(1, 0.05); + }); + }); +}); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro/liquidity.concern.ts similarity index 96% rename from balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.ts rename to balancer-js/src/modules/pools/pool-types/concerns/gyro/liquidity.concern.ts index 06d9edb2a..782b07d42 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/liquidity.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/gyro/liquidity.concern.ts @@ -6,7 +6,7 @@ import { SolidityMaths } from '@/lib/utils/solidityMaths'; const SCALING_FACTOR = 18; -export class GyroELiquidityConcern implements LiquidityConcern { +export class GyroLiquidityConcern implements LiquidityConcern { calcTotal(tokens: PoolToken[]): string { let sumBalance = BigInt(0); let sumValue = BigInt(0); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/priceImpact.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro/priceImpact.concern.ts similarity index 87% rename from balancer-js/src/modules/pools/pool-types/concerns/gyro-e/priceImpact.concern.ts rename to balancer-js/src/modules/pools/pool-types/concerns/gyro/priceImpact.concern.ts index 8d32e005c..cf892b581 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/priceImpact.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/gyro/priceImpact.concern.ts @@ -1,7 +1,7 @@ import { PriceImpactConcern } from '@/modules/pools/pool-types/concerns/types'; import { Pool } from '@/types'; -export class GyroEPriceImpactConcern implements PriceImpactConcern { +export class GyroPriceImpactConcern implements PriceImpactConcern { bptZeroPriceImpact(pool: Pool, tokenAmounts: bigint[]): bigint { console.log(pool, tokenAmounts); throw new Error('Not implemented'); diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/spotPrice.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro/spotPrice.concern.ts similarity index 80% rename from balancer-js/src/modules/pools/pool-types/concerns/gyro-e/spotPrice.concern.ts rename to balancer-js/src/modules/pools/pool-types/concerns/gyro/spotPrice.concern.ts index d5b910d3b..e8c2724a5 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/gyro-e/spotPrice.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/gyro/spotPrice.concern.ts @@ -1,7 +1,7 @@ import { SpotPriceConcern } from '@/modules/pools/pool-types/concerns/types'; import { Pool } from '@/types'; -export class GyroESpotPriceConcern implements SpotPriceConcern { +export class GyroSpotPriceConcern implements SpotPriceConcern { calcPoolSpotPrice(tokenIn: string, tokenOut: string, pool: Pool): string { console.log(tokenIn, tokenOut, pool); throw new Error('Not implemented'); diff --git a/balancer-js/src/modules/pools/pool-types/gyro-e.module.ts b/balancer-js/src/modules/pools/pool-types/gyro-e.module.ts deleted file mode 100644 index 0476a262e..000000000 --- a/balancer-js/src/modules/pools/pool-types/gyro-e.module.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { PoolType } from './pool-type.interface'; -import { - ExitConcern, - JoinConcern, - LiquidityConcern, - PriceImpactConcern, - SpotPriceConcern, -} from '@/modules/pools/pool-types/concerns/types'; -import { GyroEExitConcern } from '@/modules/pools/pool-types/concerns/gyro-e/exit.concern'; -import { GyroELiquidityConcern } from '@/modules/pools/pool-types/concerns/gyro-e/liquidity.concern'; -import { GyroESpotPriceConcern } from '@/modules/pools/pool-types/concerns/gyro-e/spotPrice.concern'; -import { GyroEPriceImpactConcern } from '@/modules/pools/pool-types/concerns/gyro-e/priceImpact.concern'; -import { GyroEJoinConcern } from '@/modules/pools/pool-types/concerns/gyro-e/join.concern'; - -export class GyroE implements PoolType { - constructor( - public exit: ExitConcern = new GyroEExitConcern(), - public liquidity: LiquidityConcern = new GyroELiquidityConcern(), - public spotPriceCalculator: SpotPriceConcern = new GyroESpotPriceConcern(), - public priceImpactCalculator: PriceImpactConcern = new GyroEPriceImpactConcern(), - public join: JoinConcern = new GyroEJoinConcern() - ) {} -} diff --git a/balancer-js/src/modules/pools/pool-types/gyro.module.ts b/balancer-js/src/modules/pools/pool-types/gyro.module.ts new file mode 100644 index 000000000..0ac1a4ec0 --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/gyro.module.ts @@ -0,0 +1,23 @@ +import { PoolType } from './pool-type.interface'; +import { + ExitConcern, + JoinConcern, + LiquidityConcern, + PriceImpactConcern, + SpotPriceConcern, +} from '@/modules/pools/pool-types/concerns/types'; +import { GyroExitConcern } from '@/modules/pools/pool-types/concerns/gyro/exit.concern'; +import { GyroLiquidityConcern } from '@/modules/pools/pool-types/concerns/gyro/liquidity.concern'; +import { GyroSpotPriceConcern } from '@/modules/pools/pool-types/concerns/gyro/spotPrice.concern'; +import { GyroPriceImpactConcern } from '@/modules/pools/pool-types/concerns/gyro/priceImpact.concern'; +import { GyroJoinConcern } from '@/modules/pools/pool-types/concerns/gyro/join.concern'; + +export class Gyro implements PoolType { + constructor( + public exit: ExitConcern = new GyroExitConcern(), + public liquidity: LiquidityConcern = new GyroLiquidityConcern(), + public spotPriceCalculator: SpotPriceConcern = new GyroSpotPriceConcern(), + public priceImpactCalculator: PriceImpactConcern = new GyroPriceImpactConcern(), + public join: JoinConcern = new GyroJoinConcern() + ) {} +} From c1b7e8f657994a00502376deafadfa49085e9d5b Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem de Liz Date: Wed, 10 May 2023 21:44:26 -0300 Subject: [PATCH 21/86] Altering blocknumber to pass in the tests; --- .../concerns/composableStable/join.concern.integration.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts index 21e75eabe..c08877fd0 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts @@ -25,7 +25,7 @@ const { ALCHEMY_URL_POLYGON: jsonRpcUrl } = process.env; const rpcUrl = 'http://127.0.0.1:8137'; const provider = new ethers.providers.JsonRpcProvider(rpcUrl, network); const signer = provider.getSigner(); -const blockNumber = 42505555; +const blockNumber = 42462957; const testPoolId = '0x02d2e2d7a89d6c5cb3681cfcb6f7dac02a55eda400000000000000000000088f'; From eb51aba1572c4f7b4acc7a4c5c9618f9eb531785 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 11 May 2023 10:55:15 +0100 Subject: [PATCH 22/86] Initial debug setup. --- balancer-js/examples/exitGeneralised.ts | 22 ++++++------------- balancer-js/src/modules/exits/exits.module.ts | 16 +++++++++++++- balancer-js/src/test/lib/constants.ts | 19 ++++++++++++++++ 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/balancer-js/examples/exitGeneralised.ts b/balancer-js/examples/exitGeneralised.ts index 718a93c8c..f7e6f67a9 100644 --- a/balancer-js/examples/exitGeneralised.ts +++ b/balancer-js/examples/exitGeneralised.ts @@ -27,24 +27,15 @@ import { SimulationType } from '../src/modules/simulation/simulation.module'; dotenv.config(); -// const network = Network.GOERLI; -// const jsonRpcUrl = process.env.ALCHEMY_URL_GOERLI; -// const blockNumber = 7890980; -// const rpcUrl = 'http://127.0.0.1:8000'; -// const customSubgraphUrl = -// 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-goerli-v2-beta'; - -const network = Network.MAINNET; -const jsonRpcUrl = process.env.ALCHEMY_URL; -const blockNumber = 16940624; -const rpcUrl = 'http://127.0.0.1:8545'; -const customSubgraphUrl = - 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2'; +const network = Network.ARBITRUM; +const jsonRpcUrl = process.env.ALCHEMY_URL_ARBITRUM; +const blockNumber = 89431060; +const rpcUrl = 'http://127.0.0.1:8161'; const addresses = ADDRESSES[network]; // bb-a-usd -const testPool = addresses.bbausd2; +const testPool = addresses.bbUSD_PLUS; // Amount of testPool BPT that will be used to exit const amount = parseFixed('2', testPool.decimals).toString(); @@ -117,7 +108,6 @@ const exit = async () => { const balancer = new BalancerSDK({ network, rpcUrl, - customSubgraphUrl, subgraphQuery, }); @@ -135,6 +125,8 @@ const exit = async () => { // User reviews expectedAmountOut console.log(' -- Simulating using Vault Model -- '); + console.log(signerAddress); + console.table({ tokensOut: truncateAddresses([testPool.address, ...tokensOut]), expectedAmountsOut: ['0', ...expectedAmountsOut], diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index 6f94a24d3..16e1aacf8 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -386,6 +386,10 @@ export class Exit { 'multicall', [calls] ); + // console.log(`---------------------------`); + // console.log(JSON.stringify(calls)); + // console.log(`++++++++++++++++++++++++++---------------------------`); + return { multiRequests, @@ -498,6 +502,7 @@ export class Exit { switch (node.exitAction) { case 'batchSwap': { + console.log('batchSwap'); const { modelRequest, encodedCall, assets, amounts } = this.createSwap( node, @@ -513,6 +518,7 @@ export class Exit { break; } case 'exitPool': { + console.log('exitPool'); let exit; if (isProportional) { exit = this.createExitPoolProportional( @@ -543,6 +549,7 @@ export class Exit { break; } case 'output': + console.log('output'); if (isPeek) { calls.push( Relayer.encodePeekChainedReferenceValue( @@ -552,6 +559,7 @@ export class Exit { ) ) ); + console.log(calls[calls.length - 1].toString()); outputIndexes.push(calls.length - 1); } break; @@ -637,6 +645,9 @@ export class Exit { outputReference, }; + console.log(call); + console.log(call.outputReference?.toString()); + const encodedCall = Relayer.encodeSwap(call); const modelRequest = VaultModel.mapSwapRequest(call); @@ -766,6 +777,7 @@ export class Exit { userData, toInternalBalance: this.receivesFromInternal(exitChild), }); + console.log(call); const encodedCall = Relayer.encodeExitPool(call); const modelRequest = VaultModel.mapExitPoolRequest(call); @@ -884,8 +896,10 @@ export class Exit { assets: sortedTokens, minAmountsOut: sortedAmounts, userData, - toInternalBalance: false, + toInternalBalance: true, // this.receivesFromInternal(exitChild), }); + console.log(call); + call.outputReferences.forEach(op => console.log(op.index, op.key.toString())); const encodedCall = Relayer.encodeExitPool(call); const modelRequest = VaultModel.mapExitPoolRequest(call); diff --git a/balancer-js/src/test/lib/constants.ts b/balancer-js/src/test/lib/constants.ts index e9abe907c..af6ef3887 100644 --- a/balancer-js/src/test/lib/constants.ts +++ b/balancer-js/src/test/lib/constants.ts @@ -635,6 +635,25 @@ export const ADDRESSES = { symbol: 'bb-rf-dai', slot: 52, }, + bbUSD_PLUS: { + id: '0x519cce718fcd11ac09194cff4517f12d263be067000000000000000000000382', + address: '0x519cCe718FCD11AC09194CFf4517F12D263BE067', + decimals: 18, + symbol: 'bbUSD_PLUS', + slot: 52, + }, + bbDAI_PLUS: { + address: '0x117a3d474976274b37b7b94af5dcade5c90c6e85', + decimals: 18, + symbol: 'bbDAI_PLUS', + slot: 52, + }, + bbUSDC_PLUS: { + address: '0x284eb68520c8fa83361c1a3a5910aec7f873c18b', + decimals: 18, + symbol: 'bbUSDC_PLUS', + slot: 52, + }, }, [Network.GNOSIS]: { WETH: { From c7cc61e76084e2b541826fcbd750a50c510bfd94 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 11 May 2023 12:08:00 +0100 Subject: [PATCH 23/86] Remove debug statements and use correct slot for tests. --- balancer-js/examples/exitGeneralised.ts | 2 +- balancer-js/src/modules/exits/exits.module.ts | 16 +--------------- balancer-js/src/test/lib/constants.ts | 2 +- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/balancer-js/examples/exitGeneralised.ts b/balancer-js/examples/exitGeneralised.ts index f7e6f67a9..d1211d8d2 100644 --- a/balancer-js/examples/exitGeneralised.ts +++ b/balancer-js/examples/exitGeneralised.ts @@ -38,7 +38,7 @@ const addresses = ADDRESSES[network]; const testPool = addresses.bbUSD_PLUS; // Amount of testPool BPT that will be used to exit -const amount = parseFixed('2', testPool.decimals).toString(); +const amount = parseFixed('1000', testPool.decimals).toString(); // Setup local fork with correct balances/approval to exit bb-a-usd2 pool const setUp = async (provider: JsonRpcProvider) => { diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index 16e1aacf8..6f94a24d3 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -386,10 +386,6 @@ export class Exit { 'multicall', [calls] ); - // console.log(`---------------------------`); - // console.log(JSON.stringify(calls)); - // console.log(`++++++++++++++++++++++++++---------------------------`); - return { multiRequests, @@ -502,7 +498,6 @@ export class Exit { switch (node.exitAction) { case 'batchSwap': { - console.log('batchSwap'); const { modelRequest, encodedCall, assets, amounts } = this.createSwap( node, @@ -518,7 +513,6 @@ export class Exit { break; } case 'exitPool': { - console.log('exitPool'); let exit; if (isProportional) { exit = this.createExitPoolProportional( @@ -549,7 +543,6 @@ export class Exit { break; } case 'output': - console.log('output'); if (isPeek) { calls.push( Relayer.encodePeekChainedReferenceValue( @@ -559,7 +552,6 @@ export class Exit { ) ) ); - console.log(calls[calls.length - 1].toString()); outputIndexes.push(calls.length - 1); } break; @@ -645,9 +637,6 @@ export class Exit { outputReference, }; - console.log(call); - console.log(call.outputReference?.toString()); - const encodedCall = Relayer.encodeSwap(call); const modelRequest = VaultModel.mapSwapRequest(call); @@ -777,7 +766,6 @@ export class Exit { userData, toInternalBalance: this.receivesFromInternal(exitChild), }); - console.log(call); const encodedCall = Relayer.encodeExitPool(call); const modelRequest = VaultModel.mapExitPoolRequest(call); @@ -896,10 +884,8 @@ export class Exit { assets: sortedTokens, minAmountsOut: sortedAmounts, userData, - toInternalBalance: true, // this.receivesFromInternal(exitChild), + toInternalBalance: false, }); - console.log(call); - call.outputReferences.forEach(op => console.log(op.index, op.key.toString())); const encodedCall = Relayer.encodeExitPool(call); const modelRequest = VaultModel.mapExitPoolRequest(call); diff --git a/balancer-js/src/test/lib/constants.ts b/balancer-js/src/test/lib/constants.ts index af6ef3887..bd2695889 100644 --- a/balancer-js/src/test/lib/constants.ts +++ b/balancer-js/src/test/lib/constants.ts @@ -640,7 +640,7 @@ export const ADDRESSES = { address: '0x519cCe718FCD11AC09194CFf4517F12D263BE067', decimals: 18, symbol: 'bbUSD_PLUS', - slot: 52, + slot: 0, }, bbDAI_PLUS: { address: '0x117a3d474976274b37b7b94af5dcade5c90c6e85', From 0ddfa051978ef842503e1b2a2cf53a6ea2dc4919 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 11 May 2023 12:08:17 +0100 Subject: [PATCH 24/86] Remove gasLimit in simulation. --- balancer-js/src/modules/simulation/simulation.module.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/balancer-js/src/modules/simulation/simulation.module.ts b/balancer-js/src/modules/simulation/simulation.module.ts index 016a21db9..dd1d639d5 100644 --- a/balancer-js/src/modules/simulation/simulation.module.ts +++ b/balancer-js/src/modules/simulation/simulation.module.ts @@ -118,11 +118,9 @@ export class Simulation { break; } case SimulationType.Static: { - const gasLimit = 8e6; const staticResult = await signer.call({ to, data: encodedCall, - gasLimit, }); amountsOut.push(...this.decodeResult(staticResult, outputIndexes)); break; From 8104c87152bb201c2a6563411df1108beecf0107 Mon Sep 17 00:00:00 2001 From: johngrantuk <4797222+johngrantuk@users.noreply.github.com> Date: Thu, 11 May 2023 11:29:38 +0000 Subject: [PATCH 25/86] chore: version bump v1.0.6-beta.1 --- 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 e4834ece3..c80105ec7 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -1,6 +1,6 @@ { "name": "@balancer-labs/sdk", - "version": "1.0.6-beta.0", + "version": "1.0.6-beta.1", "description": "JavaScript SDK for interacting with the Balancer Protocol V2", "license": "GPL-3.0-only", "homepage": "https://github.com/balancer-labs/balancer-sdk#readme", From 301c45f4212d9bfd3779a29b15f2b8e56584921e Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 11 May 2023 15:15:28 -0300 Subject: [PATCH 26/86] Fix unwrapped token scaling --- .../exits.module.integration-mainnet.spec.ts | 4 ++-- balancer-js/src/modules/graph/graph.ts | 21 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index 1a6f7876a..430a5e1cc 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -34,7 +34,7 @@ import { SimulationType } from '../simulation/simulation.module'; dotenv.config(); -const TEST_ETH_STABLE = true; +const TEST_BBAUSD3 = true; /* * Testing on MAINNET @@ -175,7 +175,7 @@ describe('generalised exit execution', async function () { this.timeout(120000); // Sets timeout for all tests within this scope to 2 minutes context('bbausd3', async () => { - if (!TEST_ETH_STABLE) return true; + if (!TEST_BBAUSD3) return true; const pool = addresses.bbausd3; let unwrappingTokensAmountsOut: string[]; let unwrappingTokensGasUsed: BigNumber; diff --git a/balancer-js/src/modules/graph/graph.ts b/balancer-js/src/modules/graph/graph.ts index d33a5a747..52bc5e1e8 100644 --- a/balancer-js/src/modules/graph/graph.ts +++ b/balancer-js/src/modules/graph/graph.ts @@ -1,8 +1,11 @@ +import { BigNumber, parseFixed } from '@ethersproject/bignumber'; +import { Zero, WeiPerEther } from '@ethersproject/constants'; + import { BalancerError, BalancerErrorCode } from '@/balancerErrors'; import { isSameAddress, parsePoolInfo } from '@/lib/utils'; +import { _downscaleDown } from '@/lib/utils/solidityMaths'; import { Pool, PoolAttribute, PoolType } from '@/types'; -import { Zero, WeiPerEther } from '@ethersproject/constants'; -import { BigNumber, parseFixed } from '@ethersproject/bignumber'; + import { Findable } from '../data/types'; import { PoolTypeConcerns } from '../pools/pool-type-concerns'; @@ -273,7 +276,8 @@ export class PoolGraph { ) throw new Error('Issue With Linear Pool'); - const { balancesEvm, upScaledBalances } = parsePoolInfo(linearPool); + const { balancesEvm, upScaledBalances, scalingFactorsRaw } = + parsePoolInfo(linearPool); const wrappedTokenNode: Node = { type: 'WrappedToken', @@ -297,13 +301,22 @@ export class PoolGraph { const mainTokenDecimals = linearPool.tokens[linearPool.mainIndex].decimals ?? 18; + /** + * - upscaledBalances takes price rate into account, which is equivalent to unwrapping tokens + * - downscaling with scalingFactorsRaw will downscale the unwrapped balance to the main token decimals + */ + const unwrappedBalance = _downscaleDown( + upScaledBalances[linearPool.wrappedIndex], + scalingFactorsRaw[linearPool.mainIndex] + ).toString(); + const inputNode = PoolGraph.createInputTokenNode( nodeIndex, linearPool.tokensList[linearPool.mainIndex], mainTokenDecimals, wrappedTokenNode, proportionOfParent, - upScaledBalances[linearPool.wrappedIndex].toString() // takes price rate into account + unwrappedBalance ); wrappedTokenNode.children = [inputNode[0]]; nodeIndex = inputNode[1]; From be478a56f035c5410fab6de07c050f4250f0bb7c Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 11 May 2023 16:23:28 -0300 Subject: [PATCH 27/86] Update generalisedExit integration tests to replicate frontend flow --- .../exits.module.integration-mainnet.spec.ts | 32 ++++++--- .../exits/exits.module.integration.spec.ts | 68 ++++++++----------- 2 files changed, 52 insertions(+), 48 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index 430a5e1cc..9d1734b36 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -97,8 +97,7 @@ const relayer = contractAddresses.relayer; const testFlow = async ( pool: { id: string; address: string; slot: number }, - amount: string, - simulationType = SimulationType.VaultModel + amount: string ): Promise<{ expectedAmountsOut: string[]; gasUsed: BigNumber; @@ -119,6 +118,23 @@ const testFlow = async ( ); const signerAddress = await signer.getAddress(); + + const query = await pools.generalisedExit( + pool.id, + amount, + signerAddress, + slippage, + signer, + SimulationType.VaultModel + ); + + // User reviews expectedAmountOut + console.log(' -- Simulating using Vault Model -- '); + console.table({ + tokensOut: truncateAddresses([pool.address, ...query.tokensOut]), + expectedAmountsOut: ['0', ...query.expectedAmountsOut], + }); + const authorisation = await Relayer.signRelayerApproval( relayer, signerAddress, @@ -133,7 +149,7 @@ const testFlow = async ( signerAddress, slippage, signer, - simulationType, + SimulationType.Static, authorisation ); @@ -146,14 +162,14 @@ const testFlow = async ( encodedCall ); - console.log('Gas used', gasUsed.toString()); - + console.log(' -- Simulating using Static Call -- '); console.table({ - tokensOut: truncateAddresses(tokensOut), - minOut: minAmountsOut, - expectedOut: expectedAmountsOut, + tokensOut: truncateAddresses([pool.address, ...tokensOut]), + minAmountsOut: ['0', ...minAmountsOut], + expectedAmountsOut: ['0', ...expectedAmountsOut], balanceDeltas: balanceDeltas.map((b) => b.toString()), }); + console.log('Gas used', gasUsed.toString()); expect(transactionReceipt.status).to.eq(1); balanceDeltas.forEach((b, i) => { diff --git a/balancer-js/src/modules/exits/exits.module.integration.spec.ts b/balancer-js/src/modules/exits/exits.module.integration.spec.ts index 79b65a0eb..47feee993 100644 --- a/balancer-js/src/modules/exits/exits.module.integration.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration.spec.ts @@ -117,8 +117,7 @@ const relayer = contractAddresses.relayer; const testFlow = async ( pool: { id: string; address: string; slot: number }, - amount: string, - simulationType = SimulationType.VaultModel + amount: string ) => { const slippage = '10'; // 10 bps = 0.1% @@ -136,6 +135,23 @@ const testFlow = async ( ); const signerAddress = await signer.getAddress(); + + const query = await pools.generalisedExit( + pool.id, + amount, + signerAddress, + slippage, + signer, + SimulationType.VaultModel + ); + + // User reviews expectedAmountOut + console.log(' -- Simulating using Vault Model -- '); + console.table({ + tokensOut: truncateAddresses([pool.address, ...query.tokensOut]), + expectedAmountsOut: ['0', ...query.expectedAmountsOut], + }); + const authorisation = await Relayer.signRelayerApproval( relayer, signerAddress, @@ -150,7 +166,7 @@ const testFlow = async ( signerAddress, slippage, signer, - simulationType, + SimulationType.Static, authorisation ); @@ -163,14 +179,14 @@ const testFlow = async ( encodedCall ); - console.log('Gas used', gasUsed.toString()); - + console.log(' -- Simulating using Static Call -- '); console.table({ - tokensOut: truncateAddresses(tokensOut), - minOut: minAmountsOut, - expectedOut: expectedAmountsOut, + tokensOut: truncateAddresses([pool.address, ...tokensOut]), + minAmountsOut: ['0', ...minAmountsOut], + expectedAmountsOut: ['0', ...expectedAmountsOut], balanceDeltas: balanceDeltas.map((b) => b.toString()), }); + console.log('Gas used', gasUsed.toString()); expect(transactionReceipt.status).to.eq(1); balanceDeltas.forEach((b, i) => { @@ -247,22 +263,8 @@ describe('generalised exit execution', async function () { const pool = addresses.boostedMetaBig1; const amount = parseFixed('0.05', pool.decimals).toString(); - context('using simulation type VaultModel', async () => { - it('should exit pool correctly', async () => { - await testFlow(pool, amount, SimulationType.VaultModel); - }); - }); - - context('using simulation type Tenderly', async () => { - it('should exit pool correctly', async () => { - await testFlow(pool, amount, SimulationType.Tenderly); - }); - }); - - context('using simulation type Satic', async () => { - it('should exit pool correctly', async () => { - await testFlow(pool, amount, SimulationType.Static); - }); + it('should exit pool correctly', async () => { + await testFlow(pool, amount); }); }); @@ -341,22 +343,8 @@ describe('generalised exit execution', async function () { const pool = addresses.boostedWeightedMetaGeneral1; const amount = parseFixed('0.05', pool.decimals).toString(); - context('using simulation type VaultModel', async () => { - it('should exit pool correctly', async () => { - await testFlow(pool, amount, SimulationType.VaultModel); - }); - }); - - context('using simulation type Tenderly', async () => { - it('should exit pool correctly', async () => { - await testFlow(pool, amount, SimulationType.Tenderly); - }); - }); - - context('using simulation type Satic', async () => { - it('should exit pool correctly', async () => { - await testFlow(pool, amount, SimulationType.Static); - }); + it('should exit pool correctly', async () => { + await testFlow(pool, amount); }); }); }); From 0dec9e420e4b1d0d35add743b92819bf45d48792 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 11 May 2023 16:46:56 -0300 Subject: [PATCH 28/86] Add support to exit by unwrapping Euler, Gearbox, Reaper, etc.. --- .../src/lib/abi/BatchRelayerLibrary.json | 485 +++++++++++++++++- balancer-js/src/modules/exits/exits.module.ts | 81 ++- .../src/modules/relayer/relayer.module.ts | 55 ++ balancer-js/src/modules/relayer/types.ts | 31 ++ .../modules/vaultModel/vaultModel.module.ts | 10 +- 5 files changed, 629 insertions(+), 33 deletions(-) diff --git a/balancer-js/src/lib/abi/BatchRelayerLibrary.json b/balancer-js/src/lib/abi/BatchRelayerLibrary.json index 3c3b2bc33..0e6c72416 100644 --- a/balancer-js/src/lib/abi/BatchRelayerLibrary.json +++ b/balancer-js/src/lib/abi/BatchRelayerLibrary.json @@ -35,7 +35,7 @@ ], "name": "approveVault", "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "payable", "type": "function" }, { @@ -143,13 +143,7 @@ } ], "name": "batchSwap", - "outputs": [ - { - "internalType": "int256[]", - "name": "", - "type": "int256[]" - } - ], + "outputs": [], "stateMutability": "payable", "type": "function" }, @@ -662,13 +656,7 @@ } ], "name": "swap", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], + "outputs": [], "stateMutability": "payable", "type": "function" }, @@ -710,6 +698,39 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract ICToken", + "name": "wrappedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "outputReference", + "type": "uint256" + } + ], + "name": "unwrapCompoundV2", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { "inputs": [ { @@ -743,6 +764,171 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract IEulerToken", + "name": "wrappedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "outputReference", + "type": "uint256" + } + ], + "name": "unwrapEuler", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IGearboxDieselToken", + "name": "wrappedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "dieselAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "outputReference", + "type": "uint256" + } + ], + "name": "unwrapGearbox", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IReaperTokenVault", + "name": "vaultToken", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "outputReference", + "type": "uint256" + } + ], + "name": "unwrapReaperVaultToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IShareToken", + "name": "wrappedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "outputReference", + "type": "uint256" + } + ], + "name": "unwrapShareToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ITetuSmartVault", + "name": "wrappedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "outputReference", + "type": "uint256" + } + ], + "name": "unwrapTetu", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { "inputs": [ { @@ -804,6 +990,39 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract IYearnTokenVault", + "name": "wrappedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "outputReference", + "type": "uint256" + } + ], + "name": "unwrapYearn", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { "inputs": [ { @@ -933,6 +1152,39 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract ICToken", + "name": "wrappedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "outputReference", + "type": "uint256" + } + ], + "name": "wrapCompoundV2", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { "inputs": [ { @@ -966,6 +1218,143 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract IEulerToken", + "name": "wrappedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "eulerProtocol", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "outputReference", + "type": "uint256" + } + ], + "name": "wrapEuler", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IGearboxDieselToken", + "name": "wrappedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "mainAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "outputReference", + "type": "uint256" + } + ], + "name": "wrapGearbox", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IReaperTokenVault", + "name": "vaultToken", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "outputReference", + "type": "uint256" + } + ], + "name": "wrapReaperVaultToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IShareToken", + "name": "wrappedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "outputReference", + "type": "uint256" + } + ], + "name": "wrapShareToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { "inputs": [ { @@ -994,6 +1383,39 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract ITetuSmartVault", + "name": "wrappedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "outputReference", + "type": "uint256" + } + ], + "name": "wrapTetu", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { "inputs": [ { @@ -1026,5 +1448,38 @@ "outputs": [], "stateMutability": "payable", "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IYearnTokenVault", + "name": "wrappedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "outputReference", + "type": "uint256" + } + ], + "name": "wrapYearn", + "outputs": [], + "stateMutability": "payable", + "type": "function" } ] diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index e2c760c31..fe6d0d19f 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -10,10 +10,7 @@ import { AssetHelpers, subSlippage } from '@/lib/utils'; import { PoolGraph, Node } from '@/modules/graph/graph'; import { Join } from '@/modules/joins/joins.module'; import { calcPriceImpact } from '@/modules/pricing/priceImpact'; -import { - EncodeUnwrapERC4626Input, - Relayer, -} from '@/modules/relayer/relayer.module'; +import { Relayer } from '@/modules/relayer/relayer.module'; import { Simulation, SimulationType, @@ -659,17 +656,75 @@ export class Exit { // )` // ); - const call: EncodeUnwrapERC4626Input = { - wrappedToken: node.address, - sender, - recipient, - amount, - outputReference, - }; - const encodedCall = Relayer.encodeUnwrapERC4626(call); + const linearPoolType = node.parent?.type as string; + console.log('linear type: ', linearPoolType); + + let encodedCall = ''; + + switch (linearPoolType) { + case 'ERC4626Linear': + { + encodedCall = Relayer.encodeUnwrapERC4626({ + wrappedToken: node.address, + sender, + recipient, + amount, + outputReference, + }); + } + break; + case 'AaveLinear': + { + encodedCall = Relayer.encodeUnwrapAaveStaticToken({ + staticToken: node.address, + sender, + recipient, + amount, + toUnderlying: true, + outputReference, + }); + } + break; + case 'EulerLinear': + { + encodedCall = Relayer.encodeUnwrapEuler({ + wrappedToken: node.address, + sender, + recipient, + amount, + outputReference, + }); + } + break; + case 'GearboxLinear': + { + encodedCall = Relayer.encodeUnwrapGearbox({ + wrappedToken: node.address, + sender, + recipient, + dieselAmount: amount, + outputReference, + }); + } + break; + case 'ReaperLinear': + { + encodedCall = Relayer.encodeUnwrapReaper({ + vaultToken: node.address, + sender, + recipient, + amount, + outputReference, + }); + } + break; + default: + throw new Error('Unsupported linear pool type'); + } const modelRequest = VaultModel.mapUnwrapRequest( - call, + amount, + outputReference, node.parent?.id as string // linear pool id ); diff --git a/balancer-js/src/modules/relayer/relayer.module.ts b/balancer-js/src/modules/relayer/relayer.module.ts index 4be5a47c1..e1d86ac7b 100644 --- a/balancer-js/src/modules/relayer/relayer.module.ts +++ b/balancer-js/src/modules/relayer/relayer.module.ts @@ -9,6 +9,10 @@ import { EncodeJoinPoolInput, EncodeUnwrapAaveStaticTokenInput, EncodeUnwrapERC4626Input, + EncodeUnwrapGearboxInput, + EncodeUnwrapReaperInput, + EncodeUnwrapUnbuttonTokenInput, + EncodeUnwrapWstETHInput, EncodeWrapAaveDynamicTokenInput, ExitPoolData, JoinPoolData, @@ -157,6 +161,57 @@ export class Relayer { ]); } + static encodeUnwrapEuler(params: EncodeUnwrapERC4626Input): string { + return relayerLibrary.encodeFunctionData('unwrapEuler', [ + params.wrappedToken, + params.sender, + params.recipient, + params.amount, + params.outputReference, + ]); + } + + static encodeUnwrapGearbox(params: EncodeUnwrapGearboxInput): string { + return relayerLibrary.encodeFunctionData('unwrapGearbox', [ + params.wrappedToken, + params.sender, + params.recipient, + params.dieselAmount, + params.outputReference, + ]); + } + + static encodeUnwrapReaper(params: EncodeUnwrapReaperInput): string { + return relayerLibrary.encodeFunctionData('unwrapReaperVaultToken', [ + params.vaultToken, + params.sender, + params.recipient, + params.amount, + params.outputReference, + ]); + } + + static encodeUnwrapUnbuttonToken( + params: EncodeUnwrapUnbuttonTokenInput + ): string { + return relayerLibrary.encodeFunctionData('unwrapUnbuttonToken', [ + params.wrapperToken, + params.sender, + params.recipient, + params.amount, + params.outputReference, + ]); + } + + static encodeUnwrapWstETH(params: EncodeUnwrapWstETHInput): string { + return relayerLibrary.encodeFunctionData('unwrapWstETH', [ + params.sender, + params.recipient, + params.amount, + params.outputReference, + ]); + } + static encodePeekChainedReferenceValue(reference: BigNumberish): string { return relayerLibrary.encodeFunctionData('peekChainedReferenceValue', [ reference, diff --git a/balancer-js/src/modules/relayer/types.ts b/balancer-js/src/modules/relayer/types.ts index 474b708f6..b90acfc69 100644 --- a/balancer-js/src/modules/relayer/types.ts +++ b/balancer-js/src/modules/relayer/types.ts @@ -71,5 +71,36 @@ export interface EncodeUnwrapERC4626Input { outputReference: BigNumberish; } +export interface EncodeUnwrapGearboxInput { + wrappedToken: string; + sender: string; + recipient: string; + dieselAmount: BigNumberish; + outputReference: BigNumberish; +} + +export interface EncodeUnwrapReaperInput { + vaultToken: string; + sender: string; + recipient: string; + amount: BigNumberish; + outputReference: BigNumberish; +} + +export interface EncodeUnwrapUnbuttonTokenInput { + wrapperToken: string; + sender: string; + recipient: string; + amount: BigNumberish; + outputReference: BigNumberish; +} + +export interface EncodeUnwrapWstETHInput { + sender: string; + recipient: string; + amount: BigNumberish; + outputReference: BigNumberish; +} + export type ExitPoolData = ExitPoolRequest & EncodeExitPoolInput; export type JoinPoolData = JoinPoolRequest & EncodeJoinPoolInput; diff --git a/balancer-js/src/modules/vaultModel/vaultModel.module.ts b/balancer-js/src/modules/vaultModel/vaultModel.module.ts index da11775ef..9903ed1b5 100644 --- a/balancer-js/src/modules/vaultModel/vaultModel.module.ts +++ b/balancer-js/src/modules/vaultModel/vaultModel.module.ts @@ -1,4 +1,4 @@ -import { BigNumber } from '@ethersproject/bignumber'; +import { BigNumber, BigNumberish } from '@ethersproject/bignumber'; import { Zero } from '@ethersproject/constants'; import { PoolDataService } from '@balancer-labs/sor'; @@ -13,7 +13,6 @@ import { EncodeBatchSwapInput, EncodeJoinPoolInput, EncodeExitPoolInput, - EncodeUnwrapERC4626Input, } from '../relayer/types'; import { Swap } from '../swaps/types'; @@ -139,14 +138,15 @@ export class VaultModel { } static mapUnwrapRequest( - call: EncodeUnwrapERC4626Input, + amount: BigNumberish, + outputReference: BigNumberish, poolId: string ): UnwrapRequest { const unwrapRequest: UnwrapRequest = { actionType: ActionType.Unwrap, poolId, - amount: call.amount, - outputReference: call.outputReference, + amount, + outputReference, }; return unwrapRequest; } From 61d14964856738e03130e14767a2af9dfd2531f8 Mon Sep 17 00:00:00 2001 From: johngrantuk <4797222+johngrantuk@users.noreply.github.com> Date: Fri, 12 May 2023 08:28:11 +0000 Subject: [PATCH 29/86] chore: version bump v1.0.6-beta.2 --- 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 c80105ec7..4f7ba18f3 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -1,6 +1,6 @@ { "name": "@balancer-labs/sdk", - "version": "1.0.6-beta.1", + "version": "1.0.6-beta.2", "description": "JavaScript SDK for interacting with the Balancer Protocol V2", "license": "GPL-3.0-only", "homepage": "https://github.com/balancer-labs/balancer-sdk#readme", From a723e6c6b4883e0700a7e4e6047cbc0cd7901d89 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 12 May 2023 09:55:22 +0100 Subject: [PATCH 30/86] Add some comments to help debug. --- balancer-js/src/modules/exits/exits.module.ts | 1 + balancer-js/src/modules/pools/index.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index e2c760c31..a9ac6538a 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -147,6 +147,7 @@ export class Exit { 'Insufficient pool balance to perform generalised exit - try exitting with smaller amounts' ); } else { + // If there is not sufficient main token balance to exit we must exit using unwrap method return this.exitPool( poolId, amountBptIn, diff --git a/balancer-js/src/modules/pools/index.ts b/balancer-js/src/modules/pools/index.ts index 4bc34fc05..431a6635e 100644 --- a/balancer-js/src/modules/pools/index.ts +++ b/balancer-js/src/modules/pools/index.ts @@ -384,7 +384,7 @@ export class Pools implements Findable { signer, simulationType, authorisation, - false + false // This is initially false as the function will auto switch to unwrap method if needed ); } From e57228588c1dda3ad7c084740fe4f8caf3585cb5 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 12 May 2023 10:52:35 +0100 Subject: [PATCH 31/86] Added easier debugging. --- balancer-js/src/modules/exits/exits.module.ts | 95 ++++--------------- 1 file changed, 20 insertions(+), 75 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index a9ac6538a..0cccf2677 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -36,6 +36,13 @@ import { BalancerNetworkConfig, ExitPoolRequest, PoolType } from '@/types'; const balancerRelayerInterface = BalancerRelayer__factory.createInterface(); +// Quickly switch useful debug logs on/off +const DEBUG = false; + +function debugLog(log: string) { + if (DEBUG) console.log(log); +} + export class Exit { private wrappedNativeAsset: string; private relayer: string; @@ -67,6 +74,9 @@ export class Exit { minAmountsOut: string[]; priceImpact: string; }> { + debugLog( + `\n--- exitPool(): unwrapTokens, ${unwrapTokens}, simulationType: ${simulationType}` + ); /* Overall exit flow description: - Create calls with 0 expected min amount for each token out @@ -161,8 +171,6 @@ export class Exit { } } - console.log('unwrapTokens', unwrapTokens); - const expectedAmountsOutByTokenOut = this.amountsOutByTokenOut( tokensOut, tokensOutByExitPath, @@ -176,6 +184,7 @@ export class Exit { slippage ); + debugLog(`------------ Updating limits...`); // Create calls with minimum expected amount out for each exit path const { encodedCall, deltas } = await this.createCalls( exitPaths, @@ -643,23 +652,6 @@ export class Exit { const outputReference = Relayer.toChainedReference( this.getOutputRef(exitPathIndex, exitChild.index) ); - - // console.log( - // `${node.type} ${node.address} prop: ${formatFixed( - // node.proportionOfParent, - // 18 - // )} - // ${node.exitAction}( - // inputAmt: ${amount}, - // inputToken: ${node.address}, - // pool: ${node.id}, - // outputToken: ${exitChild.address}, - // outputRef: ${this.getOutputRef(exitPathIndex, exitChild.index)}, - // sender: ${sender}, - // recipient: ${recipient} - // )` - // ); - const call: EncodeUnwrapERC4626Input = { wrappedToken: node.address, sender, @@ -667,6 +659,8 @@ export class Exit { amount, outputReference, }; + debugLog('\nUwrap:'); + debugLog(JSON.stringify(call)); const encodedCall = Relayer.encodeUnwrapERC4626(call); const modelRequest = VaultModel.mapUnwrapRequest( @@ -729,24 +723,6 @@ export class Exit { this.getOutputRef(exitPathIndex, exitChild.index) ); - // console.log( - // `${node.type} ${node.address} prop: ${formatFixed( - // node.proportionOfParent, - // 18 - // )} - // ${node.exitAction}( - // inputAmt: ${amountIn}, - // inputToken: ${node.address}, - // pool: ${node.id}, - // outputToken: ${exitChild.address}, - // outputRef: ${this.getOutputRef(exitPathIndex, exitChild.index)}, - // sender: ${sender}, - // recipient: ${recipient}, - // fromInternalBalance: ${fromInternalBalance}, - // toInternalBalance: ${toInternalBalance} - // )` - // ); - const call: Swap = { request, funds, @@ -755,6 +731,8 @@ export class Exit { value: '0', // TODO: check if swap with ETH is possible in this case and handle it outputReference, }; + debugLog('\nSwap:'); + debugLog(JSON.stringify(call)); const encodedCall = Relayer.encodeSwap(call); @@ -857,25 +835,6 @@ export class Exit { const toInternalBalance = this.receivesFromInternal(exitChild); - // console.log( - // `${node.type} ${node.address} prop: ${formatFixed( - // node.proportionOfParent, - // 18 - // )} - // ${node.exitAction}( - // poolId: ${node.id}, - // tokensOut: ${sortedTokens}, - // tokenOut: ${sortedTokens[sortedTokens.indexOf(tokenOut)].toString()}, - // amountOut: ${sortedAmounts[sortedTokens.indexOf(tokenOut)].toString()}, - // amountIn: ${amountIn}, - // minAmountOut: ${minAmountOut}, - // outputRef: ${this.getOutputRef(exitPathIndex, exitChild.index)}, - // sender: ${sender}, - // recipient: ${recipient}, - // toInternalBalance: ${toInternalBalance} - // )` - // ); - const call = Relayer.formatExitPoolInput({ poolId: node.id, poolKind: 0, @@ -888,6 +847,9 @@ export class Exit { userData, toInternalBalance, }); + debugLog('\nExit:'); + debugLog(JSON.stringify(call)); + const encodedCall = Relayer.encodeExitPool(call); const modelRequest = VaultModel.mapExitPoolRequest(call); @@ -969,25 +931,6 @@ export class Exit { key: Relayer.toChainedReference(this.getOutputRef(0, child.index)), }; }); - - // console.log( - // `${node.type} ${node.address} prop: ${formatFixed( - // node.proportionOfParent, - // 18 - // )} - // ${node.exitAction}( - // poolId: ${node.id}, - // tokensOut: ${sortedTokens}, - // tokenOut: ${sortedTokens[sortedTokens.indexOf(tokenOut)].toString()}, - // amountOut: ${sortedAmounts[sortedTokens.indexOf(tokenOut)].toString()}, - // amountIn: ${amountIn}, - // minAmountOut: ${minAmountOut}, - // outputRef: ${this.getOutputRef(exitPathIndex, exitChild.index)}, - // sender: ${sender}, - // recipient: ${recipient} - // )` - // ); - // We have to use correct pool type based off following from Relayer: // enum PoolKind { WEIGHTED, LEGACY_STABLE, COMPOSABLE_STABLE, COMPOSABLE_STABLE_V2 } // (note only Weighted and COMPOSABLE_STABLE_V2 will support proportional exits) @@ -1008,6 +951,8 @@ export class Exit { userData, toInternalBalance: false, }); + debugLog('\nExitProportional:'); + debugLog(JSON.stringify(call)); const encodedCall = Relayer.encodeExitPool(call); const modelRequest = VaultModel.mapExitPoolRequest(call); From 0b10104e5dd3a3088f9afedf8de19960ec396aaa Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 12 May 2023 12:36:53 +0100 Subject: [PATCH 32/86] Updated flow and test. --- .../exits.module.integration-mainnet.spec.ts | 56 +++--- balancer-js/src/modules/exits/exits.module.ts | 172 +++++++++++++----- balancer-js/src/modules/pools/index.ts | 29 ++- 3 files changed, 185 insertions(+), 72 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index 9d1734b36..997269762 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -9,6 +9,7 @@ import { Network, truncateAddresses, subSlippage, + removeItem, } from '@/.'; import { BigNumber, parseFixed } from '@ethersproject/bignumber'; import { JsonRpcProvider } from '@ethersproject/providers'; @@ -97,7 +98,8 @@ const relayer = contractAddresses.relayer; const testFlow = async ( pool: { id: string; address: string; slot: number }, - amount: string + exitAmount: string, + expectUnwrap: boolean ): Promise<{ expectedAmountsOut: string[]; gasUsed: BigNumber; @@ -106,7 +108,7 @@ const testFlow = async ( const tokens = [pool.address]; const slots = [pool.slot]; - const balances = [amount]; + const balances = [exitAmount]; await forkSetup( signer, @@ -119,22 +121,18 @@ const testFlow = async ( const signerAddress = await signer.getAddress(); - const query = await pools.generalisedExit( + // Replicating UI user flow: + + // 1. Gets exitInfo + // - this helps user to decide if they will approve relayer, etc by returning estimated amounts out/pi. + // - also returns tokensOut and whether or not unwrap should be used + const exitInfo = await pools.getExitInfo( pool.id, - amount, + exitAmount, signerAddress, - slippage, - signer, - SimulationType.VaultModel + signer ); - // User reviews expectedAmountOut - console.log(' -- Simulating using Vault Model -- '); - console.table({ - tokensOut: truncateAddresses([pool.address, ...query.tokensOut]), - expectedAmountsOut: ['0', ...query.expectedAmountsOut], - }); - const authorisation = await Relayer.signRelayerApproval( relayer, signerAddress, @@ -142,37 +140,45 @@ const testFlow = async ( contracts.vault ); + // 2. Get call data and expected/min amounts out + // - Uses a Static/Tenderly call to simulate tx then applies slippage const { to, encodedCall, tokensOut, expectedAmountsOut, minAmountsOut } = await pools.generalisedExit( pool.id, - amount, + exitAmount, signerAddress, slippage, signer, SimulationType.Static, + exitInfo.needsUnwrap, authorisation ); + // 3. Sends tx const { transactionReceipt, balanceDeltas, gasUsed } = await sendTransactionGetBalances( - tokensOut, + [pool.address, ...tokensOut], signer, signerAddress, to, encodedCall ); - console.log(' -- Simulating using Static Call -- '); + const tokensOutDeltas = removeItem(balanceDeltas, 0); console.table({ - tokensOut: truncateAddresses([pool.address, ...tokensOut]), - minAmountsOut: ['0', ...minAmountsOut], - expectedAmountsOut: ['0', ...expectedAmountsOut], - balanceDeltas: balanceDeltas.map((b) => b.toString()), + tokensOut: truncateAddresses(tokensOut), + estimateAmountsOut: exitInfo.estimatedAmountsOut, + minAmountsOut: minAmountsOut, + expectedAmountsOut: expectedAmountsOut, + balanceDeltas: tokensOutDeltas.map((b) => b.toString()), }); console.log('Gas used', gasUsed.toString()); + console.log(`Should unwrap: `, exitInfo.needsUnwrap); expect(transactionReceipt.status).to.eq(1); - balanceDeltas.forEach((b, i) => { + expect(balanceDeltas[0].toString()).to.eq(exitAmount.toString()); + expect(exitInfo.needsUnwrap).to.eq(expectUnwrap); + tokensOutDeltas.forEach((b, i) => { const minOut = BigNumber.from(minAmountsOut[i]); expect(b.gte(minOut)).to.be.true; expect(accuracy(b, BigNumber.from(expectedAmountsOut[i]))).to.be.closeTo( @@ -208,7 +214,8 @@ describe('generalised exit execution', async function () { it('should exit pool correctly', async () => { const { expectedAmountsOut, gasUsed } = await testFlow( pool, - unwrapExitAmount.toString() + unwrapExitAmount.toString(), + true ); unwrappingTokensAmountsOut = expectedAmountsOut; unwrappingTokensGasUsed = gasUsed; @@ -219,7 +226,8 @@ describe('generalised exit execution', async function () { it('should exit pool correctly', async () => { const { expectedAmountsOut, gasUsed } = await testFlow( pool, - mainExitAmount.toString() + mainExitAmount.toString(), + false ); mainTokensAmountsOut = expectedAmountsOut; mainTokensGasUsed = gasUsed; diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index 0cccf2677..a7a192230 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -57,45 +57,31 @@ export class Exit { this.relayer = contracts.relayer; } - async exitPool( + private async getExit( poolId: string, amountBptIn: string, userAddress: string, - slippage: string, signer: JsonRpcSigner, + doUnwrap: boolean, simulationType: SimulationType, - authorisation?: string, - unwrapTokens = false + authorisation?: string ): Promise<{ - to: string; - encodedCall: string; + unwrap: boolean; tokensOut: string[]; + exitPaths: Node[][]; + isProportional: boolean; expectedAmountsOut: string[]; - minAmountsOut: string[]; - priceImpact: string; + expectedAmountsOutByExitPath: string[]; }> { - debugLog( - `\n--- exitPool(): unwrapTokens, ${unwrapTokens}, simulationType: ${simulationType}` - ); - /* - Overall exit flow description: - - Create calls with 0 expected min amount for each token out - - static call (or V4 special call) to get actual amounts for each token out - - Apply slippage to amountsOut - - Recreate calls with minAmounts === actualAmountsWithSlippage - - Return minAmoutsOut, UI would use this to display to user - - Return updatedCalls, UI would use this to execute tx - */ - - // Create nodes and order by breadth first + // Create nodes and order by breadth first - initially trys with no unwrapping const orderedNodes = await this.poolGraph.getGraphNodes( false, poolId, - unwrapTokens + doUnwrap ); const isProportional = PoolGraph.isProportionalPools(orderedNodes); - console.log(`isProportional`, isProportional); + debugLog(`isProportional, ${isProportional}`); let exitPaths: Node[][] = []; let tokensOutByExitPath: string[] = []; @@ -148,7 +134,7 @@ export class Exit { ); if (!hasSufficientBalance) { - if (unwrapTokens) { + if (doUnwrap) /** * This case might happen when a whale tries to exit with an amount that * is at the same time larger than both main and wrapped token balances @@ -156,40 +142,136 @@ export class Exit { throw new Error( 'Insufficient pool balance to perform generalised exit - try exitting with smaller amounts' ); - } else { - // If there is not sufficient main token balance to exit we must exit using unwrap method - return this.exitPool( + else + return await this.getExit( poolId, amountBptIn, userAddress, - slippage, signer, + true, simulationType, - authorisation, - true + authorisation ); - } + } else { + const expectedAmountsOut = this.amountsOutByTokenOut( + tokensOut, + tokensOutByExitPath, + expectedAmountsOutByExitPath + ); + return { + unwrap: doUnwrap, + tokensOut, + exitPaths, + isProportional, + expectedAmountsOut, + expectedAmountsOutByExitPath, + }; } + } - const expectedAmountsOutByTokenOut = this.amountsOutByTokenOut( - tokensOut, - tokensOutByExitPath, - expectedAmountsOutByExitPath + async getExitInfo( + poolId: string, + amountBptIn: string, + userAddress: string, + signer: JsonRpcSigner, + authorisation?: string + ): Promise<{ + tokensOut: string[]; + estimatedAmountsOut: string[]; + priceImpact: string; + needsUnwrap: boolean; + }> { + debugLog(`\n--- getExitInfo()`); + /* + Overall exit flow description: + - Create calls with 0 expected min amount for each token out + - static call (or V4 special call) to get actual amounts for each token out + - Apply slippage to amountsOut + - Recreate calls with minAmounts === actualAmountsWithSlippage + - Return minAmoutsOut, UI would use this to display to user + - Return updatedCalls, UI would use this to execute tx + */ + const exit = await this.getExit( + poolId, + amountBptIn, + userAddress, + signer, + false, + SimulationType.VaultModel, + authorisation + ); + + const priceImpact = await this.calculatePriceImpact( + poolId, + this.poolGraph, + exit.tokensOut, + exit.expectedAmountsOut, + amountBptIn + ); + + return { + tokensOut: exit.tokensOut, + estimatedAmountsOut: exit.expectedAmountsOut, + priceImpact, + needsUnwrap: exit.unwrap, + }; + } + + async buildExitCall( + poolId: string, + amountBptIn: string, + userAddress: string, + slippage: string, + signer: JsonRpcSigner, + simulationType: SimulationType, // TODO - Narrow this to only static/tenderly as options + unwrapTokens: boolean, + authorisation?: string + ): Promise<{ + to: string; + encodedCall: string; + tokensOut: string[]; + expectedAmountsOut: string[]; + minAmountsOut: string[]; + priceImpact: string; + }> { + debugLog( + `\n--- exitPool(): unwrapTokens, ${unwrapTokens}, simulationType: ${simulationType}` + ); + /* + Overall exit flow description: + - Create calls with 0 expected min amount for each token out + - static call (or V4 special call) to get actual amounts for each token out + - Apply slippage to amountsOut + - Recreate calls with minAmounts === actualAmountsWithSlippage + - Return minAmoutsOut, UI would use this to display to user + - Return updatedCalls, UI would use this to execute tx + */ + if (simulationType === SimulationType.VaultModel) + throw new Error(`Cant use VaultModel as simulation type in build call`); + + const exit = await this.getExit( + poolId, + amountBptIn, + userAddress, + signer, + unwrapTokens, + simulationType, + authorisation ); const { minAmountsOutByExitPath, minAmountsOutByTokenOut } = this.minAmountsOut( - expectedAmountsOutByExitPath, - expectedAmountsOutByTokenOut, + exit.expectedAmountsOutByExitPath, + exit.expectedAmountsOut, slippage ); debugLog(`------------ Updating limits...`); // Create calls with minimum expected amount out for each exit path const { encodedCall, deltas } = await this.createCalls( - exitPaths, + exit.exitPaths, userAddress, - isProportional, + exit.isProportional, minAmountsOutByExitPath, authorisation ); @@ -198,23 +280,23 @@ export class Exit { poolId, deltas, amountBptIn, - tokensOut, + exit.tokensOut, minAmountsOutByTokenOut ); const priceImpact = await this.calculatePriceImpact( poolId, this.poolGraph, - tokensOut, - expectedAmountsOutByTokenOut, + exit.tokensOut, + exit.expectedAmountsOut, amountBptIn ); return { to: this.relayer, encodedCall, - tokensOut, - expectedAmountsOut: expectedAmountsOutByTokenOut, + tokensOut: exit.tokensOut, + expectedAmountsOut: exit.expectedAmountsOut, minAmountsOut: minAmountsOutByTokenOut, priceImpact, }; diff --git a/balancer-js/src/modules/pools/index.ts b/balancer-js/src/modules/pools/index.ts index 431a6635e..c74697f25 100644 --- a/balancer-js/src/modules/pools/index.ts +++ b/balancer-js/src/modules/pools/index.ts @@ -357,6 +357,7 @@ export class Pools implements Findable { * @param slippage Maximum slippage tolerance in bps i.e. 50 = 0.5%. * @param signer JsonRpcSigner that will sign the staticCall transaction if Static simulation chosen * @param simulationType Simulation type (VaultModel, Tenderly or Static) + * @param unwrapTokens Determines if wrapped tokens should be unwrapped * @param authorisation Optional auhtorisation call to be added to the chained transaction * @returns transaction data ready to be sent to the network along with tokens, min and expected amounts out. */ @@ -367,6 +368,7 @@ export class Pools implements Findable { slippage: string, signer: JsonRpcSigner, simulationType: SimulationType, + unwrapTokens: boolean, authorisation?: string ): Promise<{ to: string; @@ -376,15 +378,36 @@ export class Pools implements Findable { minAmountsOut: string[]; priceImpact: string; }> { - return this.exitService.exitPool( + return this.exitService.buildExitCall( poolId, amount, userAddress, slippage, signer, simulationType, - authorisation, - false // This is initially false as the function will auto switch to unwrap method if needed + unwrapTokens, + authorisation + ); + } + + async getExitInfo( + poolId: string, + amountBptIn: string, + userAddress: string, + signer: JsonRpcSigner, + authorisation?: string + ): Promise<{ + tokensOut: string[]; + estimatedAmountsOut: string[]; + priceImpact: string; + needsUnwrap: boolean; + }> { + return this.exitService.getExitInfo( + poolId, + amountBptIn, + userAddress, + signer, + authorisation ); } From 70a285e5cc9596a342b6150fae6692e7926a431d Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 12 May 2023 12:59:10 +0100 Subject: [PATCH 33/86] Update other tests. --- .../modules/exits/exits.module.integration.spec.ts | 11 +++++------ .../exitsProportional.module.integration.spec.ts | 3 ++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration.spec.ts b/balancer-js/src/modules/exits/exits.module.integration.spec.ts index 47feee993..8b39e9885 100644 --- a/balancer-js/src/modules/exits/exits.module.integration.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration.spec.ts @@ -136,20 +136,18 @@ const testFlow = async ( const signerAddress = await signer.getAddress(); - const query = await pools.generalisedExit( + const exitInfo = await pools.getExitInfo( pool.id, amount, signerAddress, - slippage, - signer, - SimulationType.VaultModel + signer ); // User reviews expectedAmountOut console.log(' -- Simulating using Vault Model -- '); console.table({ - tokensOut: truncateAddresses([pool.address, ...query.tokensOut]), - expectedAmountsOut: ['0', ...query.expectedAmountsOut], + tokensOut: truncateAddresses([pool.address, ...exitInfo.tokensOut]), + expectedAmountsOut: ['0', ...exitInfo.estimatedAmountsOut], }); const authorisation = await Relayer.signRelayerApproval( @@ -167,6 +165,7 @@ const testFlow = async ( slippage, signer, SimulationType.Static, + exitInfo.needsUnwrap, authorisation ); diff --git a/balancer-js/src/modules/exits/exitsProportional.module.integration.spec.ts b/balancer-js/src/modules/exits/exitsProportional.module.integration.spec.ts index 5c417fb52..ec753865e 100644 --- a/balancer-js/src/modules/exits/exitsProportional.module.integration.spec.ts +++ b/balancer-js/src/modules/exits/exitsProportional.module.integration.spec.ts @@ -105,7 +105,7 @@ const testFlow = async ( pool: { id: string; address: string }, amount: string, authorisation: string | undefined, - simulationType = SimulationType.VaultModel + simulationType = SimulationType.Static ) => { const gasLimit = 8e6; const slippage = '10'; // 10 bps = 0.1% @@ -118,6 +118,7 @@ const testFlow = async ( slippage, signer, simulationType, + false, authorisation ); From 2f56a7059d50341da4619657352d41efb3961a4c Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 12 May 2023 13:14:20 +0100 Subject: [PATCH 34/86] Fix example. --- balancer-js/examples/exitGeneralised.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/balancer-js/examples/exitGeneralised.ts b/balancer-js/examples/exitGeneralised.ts index 718a93c8c..4c1b26c60 100644 --- a/balancer-js/examples/exitGeneralised.ts +++ b/balancer-js/examples/exitGeneralised.ts @@ -122,22 +122,19 @@ const exit = async () => { }); // Use SDK to create exit transaction - const { expectedAmountsOut, tokensOut } = - await balancer.pools.generalisedExit( + const { estimatedAmountsOut, tokensOut, needsUnwrap } = + await balancer.pools.getExitInfo( testPool.id, amount, signerAddress, - slippage, - signer, - SimulationType.VaultModel, - undefined + signer ); // User reviews expectedAmountOut - console.log(' -- Simulating using Vault Model -- '); + console.log(' -- getExitInfo() -- '); console.table({ tokensOut: truncateAddresses([testPool.address, ...tokensOut]), - expectedAmountsOut: ['0', ...expectedAmountsOut], + estimatedAmountsOut: ['0', ...estimatedAmountsOut], }); // User approves relayer @@ -160,6 +157,7 @@ const exit = async () => { slippage, signer, SimulationType.Static, + needsUnwrap, relayerAuth ); From 6f10a2ccdf28aa992a4fa6c414b2284a5493c3a2 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 12 May 2023 10:27:39 -0300 Subject: [PATCH 35/86] Reorder methods so public are on top and private below --- balancer-js/src/modules/exits/exits.module.ts | 224 +++++++++--------- 1 file changed, 112 insertions(+), 112 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index a7a192230..b6ab236bf 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -57,118 +57,6 @@ export class Exit { this.relayer = contracts.relayer; } - private async getExit( - poolId: string, - amountBptIn: string, - userAddress: string, - signer: JsonRpcSigner, - doUnwrap: boolean, - simulationType: SimulationType, - authorisation?: string - ): Promise<{ - unwrap: boolean; - tokensOut: string[]; - exitPaths: Node[][]; - isProportional: boolean; - expectedAmountsOut: string[]; - expectedAmountsOutByExitPath: string[]; - }> { - // Create nodes and order by breadth first - initially trys with no unwrapping - const orderedNodes = await this.poolGraph.getGraphNodes( - false, - poolId, - doUnwrap - ); - - const isProportional = PoolGraph.isProportionalPools(orderedNodes); - debugLog(`isProportional, ${isProportional}`); - - let exitPaths: Node[][] = []; - let tokensOutByExitPath: string[] = []; - let tokensOut: string[] = []; - - const outputNodes = orderedNodes.filter((n) => n.exitAction === 'output'); - const outputBalances = outputNodes.map((n) => n.balance); - tokensOutByExitPath = outputNodes.map((n) => n.address.toLowerCase()); - - tokensOut = [...new Set(tokensOutByExitPath)].sort(); - - if (isProportional) { - // All proportional will have single path from root node, exiting proportionally by ref all the way to leafs - const path = orderedNodes.map((node, i) => { - // First node should exit with full BPT amount in - if (i === 0) node.index = amountBptIn; - return node; - }); - exitPaths[0] = path; - } else { - // Create exit paths for each output node and splits amount in proportionally between them - exitPaths = this.getExitPaths(outputNodes, amountBptIn); - } - - // Create calls with minimum expected amount out for each exit path - const { - multiRequests, - encodedCall: queryData, - outputIndexes, - } = await this.createCalls( - exitPaths, - userAddress, - isProportional, - undefined, - authorisation - ); - - const expectedAmountsOutByExitPath = await this.amountsOutByExitPath( - userAddress, - multiRequests, - queryData, - orderedNodes[0].address, - outputIndexes, - signer, - simulationType - ); - - const hasSufficientBalance = outputBalances.every((balance, i) => - BigNumber.from(balance).gt(expectedAmountsOutByExitPath[i]) - ); - - if (!hasSufficientBalance) { - if (doUnwrap) - /** - * This case might happen when a whale tries to exit with an amount that - * is at the same time larger than both main and wrapped token balances - */ - throw new Error( - 'Insufficient pool balance to perform generalised exit - try exitting with smaller amounts' - ); - else - return await this.getExit( - poolId, - amountBptIn, - userAddress, - signer, - true, - simulationType, - authorisation - ); - } else { - const expectedAmountsOut = this.amountsOutByTokenOut( - tokensOut, - tokensOutByExitPath, - expectedAmountsOutByExitPath - ); - return { - unwrap: doUnwrap, - tokensOut, - exitPaths, - isProportional, - expectedAmountsOut, - expectedAmountsOutByExitPath, - }; - } - } - async getExitInfo( poolId: string, amountBptIn: string, @@ -302,6 +190,118 @@ export class Exit { }; } + private async getExit( + poolId: string, + amountBptIn: string, + userAddress: string, + signer: JsonRpcSigner, + doUnwrap: boolean, + simulationType: SimulationType, + authorisation?: string + ): Promise<{ + unwrap: boolean; + tokensOut: string[]; + exitPaths: Node[][]; + isProportional: boolean; + expectedAmountsOut: string[]; + expectedAmountsOutByExitPath: string[]; + }> { + // Create nodes and order by breadth first - initially trys with no unwrapping + const orderedNodes = await this.poolGraph.getGraphNodes( + false, + poolId, + doUnwrap + ); + + const isProportional = PoolGraph.isProportionalPools(orderedNodes); + debugLog(`isProportional, ${isProportional}`); + + let exitPaths: Node[][] = []; + let tokensOutByExitPath: string[] = []; + let tokensOut: string[] = []; + + const outputNodes = orderedNodes.filter((n) => n.exitAction === 'output'); + const outputBalances = outputNodes.map((n) => n.balance); + tokensOutByExitPath = outputNodes.map((n) => n.address.toLowerCase()); + + tokensOut = [...new Set(tokensOutByExitPath)].sort(); + + if (isProportional) { + // All proportional will have single path from root node, exiting proportionally by ref all the way to leafs + const path = orderedNodes.map((node, i) => { + // First node should exit with full BPT amount in + if (i === 0) node.index = amountBptIn; + return node; + }); + exitPaths[0] = path; + } else { + // Create exit paths for each output node and splits amount in proportionally between them + exitPaths = this.getExitPaths(outputNodes, amountBptIn); + } + + // Create calls with minimum expected amount out for each exit path + const { + multiRequests, + encodedCall: queryData, + outputIndexes, + } = await this.createCalls( + exitPaths, + userAddress, + isProportional, + undefined, + authorisation + ); + + const expectedAmountsOutByExitPath = await this.amountsOutByExitPath( + userAddress, + multiRequests, + queryData, + orderedNodes[0].address, + outputIndexes, + signer, + simulationType + ); + + const hasSufficientBalance = outputBalances.every((balance, i) => + BigNumber.from(balance).gt(expectedAmountsOutByExitPath[i]) + ); + + if (!hasSufficientBalance) { + if (doUnwrap) + /** + * This case might happen when a whale tries to exit with an amount that + * is at the same time larger than both main and wrapped token balances + */ + throw new Error( + 'Insufficient pool balance to perform generalised exit - try exitting with smaller amounts' + ); + else + return await this.getExit( + poolId, + amountBptIn, + userAddress, + signer, + true, + simulationType, + authorisation + ); + } else { + const expectedAmountsOut = this.amountsOutByTokenOut( + tokensOut, + tokensOutByExitPath, + expectedAmountsOutByExitPath + ); + return { + unwrap: doUnwrap, + tokensOut, + exitPaths, + isProportional, + expectedAmountsOut, + expectedAmountsOutByExitPath, + }; + } + } + /* (From Fernando) 1. Given a bpt amount in find the expect token amounts out (proportionally) From 42095e4ccafa3966092a0a55a0c0b4e1c3ee89df Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 12 May 2023 10:36:44 -0300 Subject: [PATCH 36/86] Remove unnecessary authorisation parameter from getExitInfo --- balancer-js/src/modules/exits/exits.module.ts | 6 ++---- balancer-js/src/modules/pools/index.ts | 15 +++++++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index b6ab236bf..cf41f570f 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -61,8 +61,7 @@ export class Exit { poolId: string, amountBptIn: string, userAddress: string, - signer: JsonRpcSigner, - authorisation?: string + signer: JsonRpcSigner ): Promise<{ tokensOut: string[]; estimatedAmountsOut: string[]; @@ -85,8 +84,7 @@ export class Exit { userAddress, signer, false, - SimulationType.VaultModel, - authorisation + SimulationType.VaultModel ); const priceImpact = await this.calculatePriceImpact( diff --git a/balancer-js/src/modules/pools/index.ts b/balancer-js/src/modules/pools/index.ts index c74697f25..b8a3819d8 100644 --- a/balancer-js/src/modules/pools/index.ts +++ b/balancer-js/src/modules/pools/index.ts @@ -390,12 +390,20 @@ export class Pools implements Findable { ); } + /** + * Gets info required to build generalised exit transaction + * + * @param poolId Pool id + * @param amountBptIn BPT amount in EVM scale + * @param userAddress User address + * @param signer JsonRpcSigner that will sign the staticCall transaction if Static simulation chosen + * @returns info required to build a generalised exit transaction including whether tokens need to be unwrapped + */ async getExitInfo( poolId: string, amountBptIn: string, userAddress: string, - signer: JsonRpcSigner, - authorisation?: string + signer: JsonRpcSigner ): Promise<{ tokensOut: string[]; estimatedAmountsOut: string[]; @@ -406,8 +414,7 @@ export class Pools implements Findable { poolId, amountBptIn, userAddress, - signer, - authorisation + signer ); } From 3d0ada3028c95e8ea492301962b27f4abb0d270c Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 12 May 2023 10:38:00 -0300 Subject: [PATCH 37/86] Refactor simulationType parameter on buildExitCall to accept only Tenderly and Static types --- balancer-js/src/modules/exits/exits.module.ts | 4 +--- balancer-js/src/modules/pools/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index cf41f570f..890ed329a 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -109,7 +109,7 @@ export class Exit { userAddress: string, slippage: string, signer: JsonRpcSigner, - simulationType: SimulationType, // TODO - Narrow this to only static/tenderly as options + simulationType: SimulationType.Static | SimulationType.Tenderly, unwrapTokens: boolean, authorisation?: string ): Promise<{ @@ -132,8 +132,6 @@ export class Exit { - Return minAmoutsOut, UI would use this to display to user - Return updatedCalls, UI would use this to execute tx */ - if (simulationType === SimulationType.VaultModel) - throw new Error(`Cant use VaultModel as simulation type in build call`); const exit = await this.getExit( poolId, diff --git a/balancer-js/src/modules/pools/index.ts b/balancer-js/src/modules/pools/index.ts index b8a3819d8..90a0dc054 100644 --- a/balancer-js/src/modules/pools/index.ts +++ b/balancer-js/src/modules/pools/index.ts @@ -356,7 +356,7 @@ export class Pools implements Findable { * @param userAddress User address * @param slippage Maximum slippage tolerance in bps i.e. 50 = 0.5%. * @param signer JsonRpcSigner that will sign the staticCall transaction if Static simulation chosen - * @param simulationType Simulation type (VaultModel, Tenderly or Static) + * @param simulationType Simulation type (Tenderly or Static) - VaultModel should not be used to build exit transaction * @param unwrapTokens Determines if wrapped tokens should be unwrapped * @param authorisation Optional auhtorisation call to be added to the chained transaction * @returns transaction data ready to be sent to the network along with tokens, min and expected amounts out. @@ -367,7 +367,7 @@ export class Pools implements Findable { userAddress: string, slippage: string, signer: JsonRpcSigner, - simulationType: SimulationType, + simulationType: SimulationType.Static | SimulationType.Tenderly, unwrapTokens: boolean, authorisation?: string ): Promise<{ From d518754bb09ae0a494dd3895fec31edc07382862 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 12 May 2023 10:44:25 -0300 Subject: [PATCH 38/86] Extract priceImpact calculation to inside getExit --- balancer-js/src/modules/exits/exits.module.ts | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index 890ed329a..4df558d7c 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -87,18 +87,10 @@ export class Exit { SimulationType.VaultModel ); - const priceImpact = await this.calculatePriceImpact( - poolId, - this.poolGraph, - exit.tokensOut, - exit.expectedAmountsOut, - amountBptIn - ); - return { tokensOut: exit.tokensOut, estimatedAmountsOut: exit.expectedAmountsOut, - priceImpact, + priceImpact: exit.priceImpact, needsUnwrap: exit.unwrap, }; } @@ -168,21 +160,13 @@ export class Exit { minAmountsOutByTokenOut ); - const priceImpact = await this.calculatePriceImpact( - poolId, - this.poolGraph, - exit.tokensOut, - exit.expectedAmountsOut, - amountBptIn - ); - return { to: this.relayer, encodedCall, tokensOut: exit.tokensOut, expectedAmountsOut: exit.expectedAmountsOut, minAmountsOut: minAmountsOutByTokenOut, - priceImpact, + priceImpact: exit.priceImpact, }; } @@ -201,6 +185,7 @@ export class Exit { isProportional: boolean; expectedAmountsOut: string[]; expectedAmountsOutByExitPath: string[]; + priceImpact: string; }> { // Create nodes and order by breadth first - initially trys with no unwrapping const orderedNodes = await this.poolGraph.getGraphNodes( @@ -287,6 +272,15 @@ export class Exit { tokensOutByExitPath, expectedAmountsOutByExitPath ); + + const priceImpact = await this.calculatePriceImpact( + poolId, + this.poolGraph, + tokensOut, + expectedAmountsOut, + amountBptIn + ); + return { unwrap: doUnwrap, tokensOut, @@ -294,6 +288,7 @@ export class Exit { isProportional, expectedAmountsOut, expectedAmountsOutByExitPath, + priceImpact, }; } } From 7e6039755b0d622ea48f8e7bbaef182eacc7f160 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 12 May 2023 15:53:43 +0100 Subject: [PATCH 39/86] Initial refactor of testFlow in to helper. --- .../exits.module.integration-mainnet.spec.ts | 208 ++--------------- balancer-js/src/modules/exits/exits.module.ts | 32 +-- balancer-js/src/modules/exits/testHelper.ts | 214 ++++++++++++++++++ balancer-js/src/modules/pools/index.ts | 18 +- balancer-js/src/test/lib/utils.ts | 14 +- 5 files changed, 263 insertions(+), 223 deletions(-) create mode 100644 balancer-js/src/modules/exits/testHelper.ts diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index 997269762..6728267ea 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -1,208 +1,32 @@ // yarn test:only ./src/modules/exits/exits.module.integration-mainnet.spec.ts import dotenv from 'dotenv'; import { expect } from 'chai'; - -import { - BalancerSDK, - GraphQLQuery, - GraphQLArgs, - Network, - truncateAddresses, - subSlippage, - removeItem, -} from '@/.'; +import { Network } from '@/.'; import { BigNumber, parseFixed } from '@ethersproject/bignumber'; -import { JsonRpcProvider } from '@ethersproject/providers'; -import { Contracts } from '@/modules/contracts/contracts.module'; -import { - accuracy, - forkSetup, - sendTransactionGetBalances, -} from '@/test/lib/utils'; +import { accuracy } from '@/test/lib/utils'; import { ADDRESSES } from '@/test/lib/constants'; -import { Relayer } from '@/modules/relayer/relayer.module'; -import { SimulationType } from '../simulation/simulation.module'; - -/** - * -- Integration tests for generalisedExit -- - * - * It compares results from local fork transactions with simulated results from - * the Simulation module, which can be of 3 different types: - * 1. Tenderly: uses Tenderly Simulation API (third party service) - * 2. VaultModel: uses TS math, which may be less accurate (min. 99% accuracy) - * 3. Static: uses staticCall, which is 100% accurate but requires vault approval - */ +import { testFlow } from './testHelper'; dotenv.config(); const TEST_BBAUSD3 = true; -/* - * Testing on MAINNET - * - Run node on terminal: yarn run node - * - Uncomment section below: - */ -const network = Network.MAINNET; -const blockNumber = 17223300; -const { ALCHEMY_URL: jsonRpcUrl } = process.env; -const rpcUrl = 'http://127.0.0.1:8545'; - -const addresses = ADDRESSES[network]; - -// Set tenderly config blockNumber and use default values for other parameters -const tenderlyConfig = { - blockNumber, -}; - -/** - * Example of subgraph query that allows filtering pools. - * Might be useful to reduce the response time by limiting the amount of pool - * data that will be queried by the SDK. Specially when on chain data is being - * fetched as well. - */ -const poolAddresses = Object.values(addresses).map( - (address) => address.address -); -const subgraphArgs: GraphQLArgs = { - where: { - swapEnabled: { - eq: true, - }, - totalShares: { - gt: 0.000000000001, - }, - address: { - in: poolAddresses, - }, - }, - orderBy: 'totalLiquidity', - orderDirection: 'desc', - block: { number: blockNumber }, -}; -const subgraphQuery: GraphQLQuery = { args: subgraphArgs, attrs: {} }; - -const sdk = new BalancerSDK({ - network, - rpcUrl, - tenderly: tenderlyConfig, - subgraphQuery, -}); -const { pools } = sdk; -const provider = new JsonRpcProvider(rpcUrl, network); -const signer = provider.getSigner(); -const { contracts, contractAddresses } = new Contracts( - network as number, - provider -); -const relayer = contractAddresses.relayer; - -const testFlow = async ( - pool: { id: string; address: string; slot: number }, - exitAmount: string, - expectUnwrap: boolean -): Promise<{ - expectedAmountsOut: string[]; - gasUsed: BigNumber; -}> => { - const slippage = '10'; // 10 bps = 0.1% - - const tokens = [pool.address]; - const slots = [pool.slot]; - const balances = [exitAmount]; - - await forkSetup( - signer, - tokens, - slots, - balances, - jsonRpcUrl as string, - blockNumber - ); - - const signerAddress = await signer.getAddress(); - - // Replicating UI user flow: - - // 1. Gets exitInfo - // - this helps user to decide if they will approve relayer, etc by returning estimated amounts out/pi. - // - also returns tokensOut and whether or not unwrap should be used - const exitInfo = await pools.getExitInfo( - pool.id, - exitAmount, - signerAddress, - signer - ); - - const authorisation = await Relayer.signRelayerApproval( - relayer, - signerAddress, - signer, - contracts.vault - ); - - // 2. Get call data and expected/min amounts out - // - Uses a Static/Tenderly call to simulate tx then applies slippage - const { to, encodedCall, tokensOut, expectedAmountsOut, minAmountsOut } = - await pools.generalisedExit( - pool.id, - exitAmount, - signerAddress, - slippage, - signer, - SimulationType.Static, - exitInfo.needsUnwrap, - authorisation - ); - - // 3. Sends tx - const { transactionReceipt, balanceDeltas, gasUsed } = - await sendTransactionGetBalances( - [pool.address, ...tokensOut], - signer, - signerAddress, - to, - encodedCall - ); - - const tokensOutDeltas = removeItem(balanceDeltas, 0); - console.table({ - tokensOut: truncateAddresses(tokensOut), - estimateAmountsOut: exitInfo.estimatedAmountsOut, - minAmountsOut: minAmountsOut, - expectedAmountsOut: expectedAmountsOut, - balanceDeltas: tokensOutDeltas.map((b) => b.toString()), - }); - console.log('Gas used', gasUsed.toString()); - console.log(`Should unwrap: `, exitInfo.needsUnwrap); - - expect(transactionReceipt.status).to.eq(1); - expect(balanceDeltas[0].toString()).to.eq(exitAmount.toString()); - expect(exitInfo.needsUnwrap).to.eq(expectUnwrap); - tokensOutDeltas.forEach((b, i) => { - const minOut = BigNumber.from(minAmountsOut[i]); - expect(b.gte(minOut)).to.be.true; - expect(accuracy(b, BigNumber.from(expectedAmountsOut[i]))).to.be.closeTo( - 1, - 1e-2 - ); // inaccuracy should be less than 1% - }); - const expectedMins = expectedAmountsOut.map((a) => - subSlippage(BigNumber.from(a), BigNumber.from(slippage)).toString() - ); - expect(expectedMins).to.deep.eq(minAmountsOut); - return { expectedAmountsOut, gasUsed }; -}; - describe('generalised exit execution', async function () { this.timeout(120000); // Sets timeout for all tests within this scope to 2 minutes context('bbausd3', async () => { if (!TEST_BBAUSD3) return true; - const pool = addresses.bbausd3; + const network = Network.MAINNET; + const blockNo = 17223300; + const pool = ADDRESSES[network].bbausd3; + const slippage = '10'; // 10 bps = 0.1% let unwrappingTokensAmountsOut: string[]; let unwrappingTokensGasUsed: BigNumber; let mainTokensAmountsOut: string[]; let mainTokensGasUsed: BigNumber; + const poolAddresses = Object.values(ADDRESSES[network]).map( + (address) => address.address + ); const amountRatio = 10; // Amount greater than the underlying main token balance, which will cause the exit to be unwrapped @@ -214,8 +38,12 @@ describe('generalised exit execution', async function () { it('should exit pool correctly', async () => { const { expectedAmountsOut, gasUsed } = await testFlow( pool, + slippage, unwrapExitAmount.toString(), - true + true, + network, + blockNo, + poolAddresses ); unwrappingTokensAmountsOut = expectedAmountsOut; unwrappingTokensGasUsed = gasUsed; @@ -226,8 +54,12 @@ describe('generalised exit execution', async function () { it('should exit pool correctly', async () => { const { expectedAmountsOut, gasUsed } = await testFlow( pool, + slippage, mainExitAmount.toString(), - false + false, + network, + blockNo, + poolAddresses ); mainTokensAmountsOut = expectedAmountsOut; mainTokensGasUsed = gasUsed; diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index a7a192230..fedc16e88 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -36,6 +36,22 @@ import { BalancerNetworkConfig, ExitPoolRequest, PoolType } from '@/types'; const balancerRelayerInterface = BalancerRelayer__factory.createInterface(); +export interface GeneralisedExitOutput { + to: string; + encodedCall: string; + tokensOut: string[]; + expectedAmountsOut: string[]; + minAmountsOut: string[]; + priceImpact: string; +} + +export interface ExitInfo { + tokensOut: string[]; + estimatedAmountsOut: string[]; + priceImpact: string; + needsUnwrap: boolean; +} + // Quickly switch useful debug logs on/off const DEBUG = false; @@ -175,12 +191,7 @@ export class Exit { userAddress: string, signer: JsonRpcSigner, authorisation?: string - ): Promise<{ - tokensOut: string[]; - estimatedAmountsOut: string[]; - priceImpact: string; - needsUnwrap: boolean; - }> { + ): Promise { debugLog(`\n--- getExitInfo()`); /* Overall exit flow description: @@ -226,14 +237,7 @@ export class Exit { simulationType: SimulationType, // TODO - Narrow this to only static/tenderly as options unwrapTokens: boolean, authorisation?: string - ): Promise<{ - to: string; - encodedCall: string; - tokensOut: string[]; - expectedAmountsOut: string[]; - minAmountsOut: string[]; - priceImpact: string; - }> { + ): Promise { debugLog( `\n--- exitPool(): unwrapTokens, ${unwrapTokens}, simulationType: ${simulationType}` ); diff --git a/balancer-js/src/modules/exits/testHelper.ts b/balancer-js/src/modules/exits/testHelper.ts new file mode 100644 index 000000000..95b120373 --- /dev/null +++ b/balancer-js/src/modules/exits/testHelper.ts @@ -0,0 +1,214 @@ +import { expect } from 'chai'; +import { + BalancerSDK, + GraphQLQuery, + GraphQLArgs, + Network, + truncateAddresses, + subSlippage, + removeItem, +} from '@/.'; +import { BigNumber } from '@ethersproject/bignumber'; +import { JsonRpcProvider, JsonRpcSigner } from '@ethersproject/providers'; +import { + TxResult, + accuracy, + forkSetup, + sendTransactionGetBalances, +} from '@/test/lib/utils'; +import { Relayer } from '@/modules/relayer/relayer.module'; +import { SimulationType } from '../simulation/simulation.module'; +import { GeneralisedExitOutput, ExitInfo } from '../exits/exits.module'; + +const RPC_URLS: Record = { + [Network.MAINNET]: `http://127.0.0.1:8545`, + [Network.GOERLI]: `http://127.0.0.1:8000`, + [Network.POLYGON]: `http://127.0.0.1:8137`, + [Network.ARBITRUM]: `http://127.0.0.1:8161`, +}; + +const FORK_NODES: Record = { + [Network.MAINNET]: `${process.env.ALCHEMY_URL}`, + [Network.GOERLI]: `${process.env.ALCHEMY_URL_GOERLI}`, + [Network.POLYGON]: `${process.env.ALCHEMY_URL_POLYGON}`, + [Network.ARBITRUM]: `${process.env.ALCHEMY_URL_ARBITRUM}`, +}; + +interface Pool { + id: string; + address: string; + slot: number; +} + +export const testFlow = async ( + pool: Pool, + slippage: string, + exitAmount: string, + expectUnwrap: boolean, + network: Network, + blockNumber: number, + poolAddressesToConsider: string[] +): Promise<{ + expectedAmountsOut: string[]; + gasUsed: BigNumber; +}> => { + const { sdk, signer } = await setUpForkAndSdk( + network, + blockNumber, + poolAddressesToConsider, + [pool.address], + [pool.slot], + [exitAmount] + ); + // Follows similar flow to a front end implementation + const { exitOutput, txResult, exitInfo } = await userFlow( + pool, + sdk, + signer, + exitAmount, + slippage + ); + const tokensOutDeltas = removeItem(txResult.balanceDeltas, 0); + console.table({ + tokensOut: truncateAddresses(exitOutput.tokensOut), + estimateAmountsOut: exitInfo.estimatedAmountsOut, + minAmountsOut: exitOutput.minAmountsOut, + expectedAmountsOut: exitOutput.expectedAmountsOut, + balanceDeltas: tokensOutDeltas.map((b) => b.toString()), + }); + console.log('Gas used', txResult.gasUsed.toString()); + console.log(`Should unwrap: `, exitInfo.needsUnwrap); + + expect(txResult.balanceDeltas[0].toString()).to.eq(exitAmount.toString()); + expect(exitInfo.needsUnwrap).to.eq(expectUnwrap); + tokensOutDeltas.forEach((b, i) => { + const minOut = BigNumber.from(exitOutput.minAmountsOut[i]); + expect(b.gte(minOut)).to.be.true; + expect( + accuracy(b, BigNumber.from(exitOutput.expectedAmountsOut[i])) + ).to.be.closeTo(1, 1e-2); // inaccuracy should be less than 1% + }); + const expectedMins = exitOutput.expectedAmountsOut.map((a) => + subSlippage(BigNumber.from(a), BigNumber.from(slippage)).toString() + ); + expect(expectedMins).to.deep.eq(exitOutput.minAmountsOut); + return { + expectedAmountsOut: exitOutput.expectedAmountsOut, + gasUsed: txResult.gasUsed, + }; +}; + +async function userFlow( + pool: Pool, + sdk: BalancerSDK, + signer: JsonRpcSigner, + exitAmount: string, + slippage: string +): Promise<{ + exitOutput: GeneralisedExitOutput; + exitInfo: ExitInfo; + txResult: TxResult; +}> { + const signerAddress = await signer.getAddress(); + // Replicating UI user flow: + // 1. Gets exitInfo + // - this helps user to decide if they will approve relayer, etc by returning estimated amounts out/pi. + // - also returns tokensOut and whether or not unwrap should be used + const exitInfo = await sdk.pools.getExitInfo( + pool.id, + exitAmount, + signerAddress, + signer + ); + const authorisation = await Relayer.signRelayerApproval( + sdk.contracts.relayer.address, + signerAddress, + signer, + sdk.contracts.vault + ); + // 2. Get call data and expected/min amounts out + // - Uses a Static/Tenderly call to simulate tx then applies slippage + const output = await sdk.pools.generalisedExit( + pool.id, + exitAmount, + signerAddress, + slippage, + signer, + SimulationType.Static, + exitInfo.needsUnwrap, + authorisation + ); + // 3. Sends tx + const txResult = await sendTransactionGetBalances( + [pool.address, ...output.tokensOut], + signer, + signerAddress, + output.to, + output.encodedCall + ); + return { + exitOutput: output, + txResult, + exitInfo, + }; +} + +async function setUpForkAndSdk( + network: Network, + blockNumber: number, + pools: string[], + tokens: string[], + slots: number[], + balances: string[] +): Promise<{ + sdk: BalancerSDK; + signer: JsonRpcSigner; +}> { + // Set tenderly config blockNumber and use default values for other parameters + const tenderlyConfig = { + blockNumber, + }; + + // Only queries minimal set of addresses + const subgraphQuery = createSubgraphQuery(pools, blockNumber); + + const sdk = new BalancerSDK({ + network, + rpcUrl: RPC_URLS[network], + tenderly: tenderlyConfig, + subgraphQuery, + }); + const provider = new JsonRpcProvider(RPC_URLS[network], network); + const signer = provider.getSigner(); + + await forkSetup( + signer, + tokens, + slots, + balances, + FORK_NODES[network], + blockNumber + ); + return { sdk, signer }; +} + +function createSubgraphQuery(pools: string[], blockNo: number): GraphQLQuery { + const subgraphArgs: GraphQLArgs = { + where: { + swapEnabled: { + eq: true, + }, + totalShares: { + gt: 0.000000000001, + }, + address: { + in: pools, + }, + }, + orderBy: 'totalLiquidity', + orderDirection: 'desc', + block: { number: blockNo }, + }; + const subgraphQuery: GraphQLQuery = { args: subgraphArgs, attrs: {} }; + return subgraphQuery; +} diff --git a/balancer-js/src/modules/pools/index.ts b/balancer-js/src/modules/pools/index.ts index c74697f25..a49fb95c7 100644 --- a/balancer-js/src/modules/pools/index.ts +++ b/balancer-js/src/modules/pools/index.ts @@ -14,7 +14,7 @@ import { PoolTypeConcerns } from './pool-type-concerns'; import { PoolApr } from './apr/apr'; import { Liquidity } from '../liquidity/liquidity.module'; import { Join } from '../joins/joins.module'; -import { Exit } from '../exits/exits.module'; +import { Exit, GeneralisedExitOutput, ExitInfo } from '../exits/exits.module'; import { PoolVolume } from './volume/volume'; import { PoolFees } from './fees/fees'; import { Simulation, SimulationType } from '../simulation/simulation.module'; @@ -370,14 +370,7 @@ export class Pools implements Findable { simulationType: SimulationType, unwrapTokens: boolean, authorisation?: string - ): Promise<{ - to: string; - encodedCall: string; - tokensOut: string[]; - expectedAmountsOut: string[]; - minAmountsOut: string[]; - priceImpact: string; - }> { + ): Promise { return this.exitService.buildExitCall( poolId, amount, @@ -396,12 +389,7 @@ export class Pools implements Findable { userAddress: string, signer: JsonRpcSigner, authorisation?: string - ): Promise<{ - tokensOut: string[]; - estimatedAmountsOut: string[]; - priceImpact: string; - needsUnwrap: boolean; - }> { + ): Promise { return this.exitService.getExitInfo( poolId, amountBptIn, diff --git a/balancer-js/src/test/lib/utils.ts b/balancer-js/src/test/lib/utils.ts index 6dce020ce..51ed5955c 100644 --- a/balancer-js/src/test/lib/utils.ts +++ b/balancer-js/src/test/lib/utils.ts @@ -40,6 +40,13 @@ import polygonPools from '../fixtures/pools-polygon.json'; import { PoolsJsonRepository } from './pools-json-repository'; import { Contracts } from '@/modules/contracts/contracts.module'; +export interface TxResult { + transactionReceipt: TransactionReceipt; + balanceDeltas: BigNumber[]; + internalBalanceDeltas: BigNumber[]; + gasUsed: BigNumber; +} + const jsonPools = { [Network.MAINNET]: mainnetPools, [Network.POLYGON]: polygonPools, @@ -378,12 +385,7 @@ export async function sendTransactionGetBalances( to: string, data: string, value?: BigNumberish -): Promise<{ - transactionReceipt: TransactionReceipt; - balanceDeltas: BigNumber[]; - internalBalanceDeltas: BigNumber[]; - gasUsed: BigNumber; -}> { +): Promise { const balanceBefore = await getBalances( tokensForBalanceCheck, signer, From 0494d6c81420198fc001ea6faf3e15df8a77ba7f Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 12 May 2023 11:56:47 -0300 Subject: [PATCH 40/86] Extract generalisedExit integration test flow to helper file --- .../exits.module.integration-mainnet.spec.ts | 139 +++--------- .../exits/exits.module.integration.spec.ts | 203 ++++++------------ balancer-js/src/test/lib/exitHelper.ts | 103 ++++++++- 3 files changed, 192 insertions(+), 253 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index 997269762..b6853e9c9 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -7,21 +7,13 @@ import { GraphQLQuery, GraphQLArgs, Network, - truncateAddresses, - subSlippage, - removeItem, + SimulationType, } from '@/.'; import { BigNumber, parseFixed } from '@ethersproject/bignumber'; import { JsonRpcProvider } from '@ethersproject/providers'; -import { Contracts } from '@/modules/contracts/contracts.module'; -import { - accuracy, - forkSetup, - sendTransactionGetBalances, -} from '@/test/lib/utils'; +import { accuracy, forkSetup } from '@/test/lib/utils'; import { ADDRESSES } from '@/test/lib/constants'; -import { Relayer } from '@/modules/relayer/relayer.module'; -import { SimulationType } from '../simulation/simulation.module'; +import { testGeneralisedExit } from '@/test/lib/exitHelper'; /** * -- Integration tests for generalisedExit -- @@ -90,108 +82,8 @@ const sdk = new BalancerSDK({ const { pools } = sdk; const provider = new JsonRpcProvider(rpcUrl, network); const signer = provider.getSigner(); -const { contracts, contractAddresses } = new Contracts( - network as number, - provider -); -const relayer = contractAddresses.relayer; - -const testFlow = async ( - pool: { id: string; address: string; slot: number }, - exitAmount: string, - expectUnwrap: boolean -): Promise<{ - expectedAmountsOut: string[]; - gasUsed: BigNumber; -}> => { - const slippage = '10'; // 10 bps = 0.1% - - const tokens = [pool.address]; - const slots = [pool.slot]; - const balances = [exitAmount]; - - await forkSetup( - signer, - tokens, - slots, - balances, - jsonRpcUrl as string, - blockNumber - ); - - const signerAddress = await signer.getAddress(); - - // Replicating UI user flow: - - // 1. Gets exitInfo - // - this helps user to decide if they will approve relayer, etc by returning estimated amounts out/pi. - // - also returns tokensOut and whether or not unwrap should be used - const exitInfo = await pools.getExitInfo( - pool.id, - exitAmount, - signerAddress, - signer - ); - - const authorisation = await Relayer.signRelayerApproval( - relayer, - signerAddress, - signer, - contracts.vault - ); - - // 2. Get call data and expected/min amounts out - // - Uses a Static/Tenderly call to simulate tx then applies slippage - const { to, encodedCall, tokensOut, expectedAmountsOut, minAmountsOut } = - await pools.generalisedExit( - pool.id, - exitAmount, - signerAddress, - slippage, - signer, - SimulationType.Static, - exitInfo.needsUnwrap, - authorisation - ); - - // 3. Sends tx - const { transactionReceipt, balanceDeltas, gasUsed } = - await sendTransactionGetBalances( - [pool.address, ...tokensOut], - signer, - signerAddress, - to, - encodedCall - ); - - const tokensOutDeltas = removeItem(balanceDeltas, 0); - console.table({ - tokensOut: truncateAddresses(tokensOut), - estimateAmountsOut: exitInfo.estimatedAmountsOut, - minAmountsOut: minAmountsOut, - expectedAmountsOut: expectedAmountsOut, - balanceDeltas: tokensOutDeltas.map((b) => b.toString()), - }); - console.log('Gas used', gasUsed.toString()); - console.log(`Should unwrap: `, exitInfo.needsUnwrap); - - expect(transactionReceipt.status).to.eq(1); - expect(balanceDeltas[0].toString()).to.eq(exitAmount.toString()); - expect(exitInfo.needsUnwrap).to.eq(expectUnwrap); - tokensOutDeltas.forEach((b, i) => { - const minOut = BigNumber.from(minAmountsOut[i]); - expect(b.gte(minOut)).to.be.true; - expect(accuracy(b, BigNumber.from(expectedAmountsOut[i]))).to.be.closeTo( - 1, - 1e-2 - ); // inaccuracy should be less than 1% - }); - const expectedMins = expectedAmountsOut.map((a) => - subSlippage(BigNumber.from(a), BigNumber.from(slippage)).toString() - ); - expect(expectedMins).to.deep.eq(minAmountsOut); - return { expectedAmountsOut, gasUsed }; -}; + +const simulationType = SimulationType.Static; describe('generalised exit execution', async function () { this.timeout(120000); // Sets timeout for all tests within this scope to 2 minutes @@ -210,11 +102,25 @@ describe('generalised exit execution', async function () { // Amount smaller than the underlying main token balance, which will cause the exit to be done directly const mainExitAmount = unwrapExitAmount.div(amountRatio); + beforeEach(async () => { + await forkSetup( + signer, + [pool.address], + [pool.slot], + [unwrapExitAmount.toString()], + jsonRpcUrl as string, + blockNumber + ); + }); + context('exit by unwrapping tokens', async () => { it('should exit pool correctly', async () => { - const { expectedAmountsOut, gasUsed } = await testFlow( + const { expectedAmountsOut, gasUsed } = await testGeneralisedExit( pool, + pools, + signer, unwrapExitAmount.toString(), + simulationType, true ); unwrappingTokensAmountsOut = expectedAmountsOut; @@ -224,9 +130,12 @@ describe('generalised exit execution', async function () { context('exit to main tokens directly', async () => { it('should exit pool correctly', async () => { - const { expectedAmountsOut, gasUsed } = await testFlow( + const { expectedAmountsOut, gasUsed } = await testGeneralisedExit( pool, + pools, + signer, mainExitAmount.toString(), + simulationType, false ); mainTokensAmountsOut = expectedAmountsOut; diff --git a/balancer-js/src/modules/exits/exits.module.integration.spec.ts b/balancer-js/src/modules/exits/exits.module.integration.spec.ts index 8b39e9885..0f0937f39 100644 --- a/balancer-js/src/modules/exits/exits.module.integration.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration.spec.ts @@ -1,26 +1,13 @@ // yarn test:only ./src/modules/exits/exits.module.integration.spec.ts import dotenv from 'dotenv'; -import { expect } from 'chai'; -import { BigNumber, parseFixed } from '@ethersproject/bignumber'; +import { parseFixed } from '@ethersproject/bignumber'; import { JsonRpcProvider } from '@ethersproject/providers'; -import { - BalancerSDK, - GraphQLQuery, - GraphQLArgs, - Network, - truncateAddresses, -} from '@/.'; -import { subSlippage } from '@/lib/utils/slippageHelper'; -import { Relayer } from '@/modules/relayer/relayer.module'; +import { BalancerSDK, GraphQLQuery, GraphQLArgs, Network } from '@/.'; import { SimulationType } from '@/modules/simulation/simulation.module'; -import { Contracts } from '@/modules/contracts/contracts.module'; -import { - accuracy, - forkSetup, - sendTransactionGetBalances, -} from '@/test/lib/utils'; +import { forkSetup } from '@/test/lib/utils'; import { ADDRESSES } from '@/test/lib/constants'; +import { testGeneralisedExit } from '@/test/lib/exitHelper'; /** * -- Integration tests for generalisedExit -- @@ -109,101 +96,24 @@ const sdk = new BalancerSDK({ const { pools } = sdk; const provider = new JsonRpcProvider(rpcUrl, network); const signer = provider.getSigner(); -const { contracts, contractAddresses } = new Contracts( - network as number, - provider -); -const relayer = contractAddresses.relayer; - -const testFlow = async ( - pool: { id: string; address: string; slot: number }, - amount: string -) => { - const slippage = '10'; // 10 bps = 0.1% - - const tokens = [pool.address]; - const slots = [pool.slot]; - const balances = [amount]; - - await forkSetup( - signer, - tokens, - slots, - balances, - jsonRpcUrl as string, - blockNumber - ); - - const signerAddress = await signer.getAddress(); - - const exitInfo = await pools.getExitInfo( - pool.id, - amount, - signerAddress, - signer - ); - - // User reviews expectedAmountOut - console.log(' -- Simulating using Vault Model -- '); - console.table({ - tokensOut: truncateAddresses([pool.address, ...exitInfo.tokensOut]), - expectedAmountsOut: ['0', ...exitInfo.estimatedAmountsOut], - }); - const authorisation = await Relayer.signRelayerApproval( - relayer, - signerAddress, - signer, - contracts.vault - ); - - const { to, encodedCall, tokensOut, expectedAmountsOut, minAmountsOut } = - await pools.generalisedExit( - pool.id, - amount, - signerAddress, - slippage, - signer, - SimulationType.Static, - exitInfo.needsUnwrap, - authorisation - ); +const simulationType = SimulationType.Static; - const { transactionReceipt, balanceDeltas, gasUsed } = - await sendTransactionGetBalances( - tokensOut, +describe('generalised exit execution', async function () { + this.timeout(120000); // Sets timeout for all tests within this scope to 2 minutes + let pool = { id: '', address: '', decimals: 0, slot: 0 }; + let amount = ''; + + beforeEach(async () => { + await forkSetup( signer, - signerAddress, - to, - encodedCall + [pool.address], + [pool.slot], + [amount], + jsonRpcUrl as string, + blockNumber ); - - console.log(' -- Simulating using Static Call -- '); - console.table({ - tokensOut: truncateAddresses([pool.address, ...tokensOut]), - minAmountsOut: ['0', ...minAmountsOut], - expectedAmountsOut: ['0', ...expectedAmountsOut], - balanceDeltas: balanceDeltas.map((b) => b.toString()), }); - console.log('Gas used', gasUsed.toString()); - - expect(transactionReceipt.status).to.eq(1); - balanceDeltas.forEach((b, i) => { - const minOut = BigNumber.from(minAmountsOut[i]); - expect(b.gte(minOut)).to.be.true; - expect(accuracy(b, BigNumber.from(expectedAmountsOut[i]))).to.be.closeTo( - 1, - 1e-2 - ); // inaccuracy should be less than 1% - }); - const expectedMins = expectedAmountsOut.map((a) => - subSlippage(BigNumber.from(a), BigNumber.from(slippage)).toString() - ); - expect(expectedMins).to.deep.eq(minAmountsOut); -}; - -describe('generalised exit execution', async function () { - this.timeout(120000); // Sets timeout for all tests within this scope to 2 minutes /* bbamaiweth: ComposableStable, baMai/baWeth @@ -212,11 +122,14 @@ describe('generalised exit execution', async function () { */ context('boosted', async () => { if (!TEST_BOOSTED) return true; - const pool = addresses.bbamaiweth; - const amount = parseFixed('0.02', pool.decimals).toString(); + + before(() => { + pool = addresses.bbamaiweth; + amount = parseFixed('0.02', pool.decimals).toString(); + }); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testGeneralisedExit(pool, pools, signer, amount, simulationType); }); }); @@ -227,11 +140,13 @@ describe('generalised exit execution', async function () { */ context('boostedMeta', async () => { if (!TEST_BOOSTED_META) return true; - const pool = addresses.boostedMeta1; - const amount = parseFixed('0.05', pool.decimals).toString(); + before(() => { + pool = addresses.boostedMeta1; + amount = parseFixed('0.05', pool.decimals).toString(); + }); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testGeneralisedExit(pool, pools, signer, amount, simulationType); }); }); @@ -242,11 +157,13 @@ describe('generalised exit execution', async function () { */ context('boostedMetaAlt', async () => { if (!TEST_BOOSTED_META_ALT) return true; - const pool = addresses.boostedMetaAlt1; - const amount = parseFixed('0.05', pool.decimals).toString(); + before(() => { + pool = addresses.boostedMetaAlt1; + amount = parseFixed('0.05', pool.decimals).toString(); + }); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testGeneralisedExit(pool, pools, signer, amount, simulationType); }); }); @@ -259,11 +176,14 @@ describe('generalised exit execution', async function () { */ context('boostedMetaBig', async () => { if (!TEST_BOOSTED_META_BIG) return true; - const pool = addresses.boostedMetaBig1; - const amount = parseFixed('0.05', pool.decimals).toString(); + + before(() => { + pool = addresses.boostedMetaBig1; + amount = parseFixed('0.05', pool.decimals).toString(); + }); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testGeneralisedExit(pool, pools, signer, amount, simulationType); }); }); @@ -274,11 +194,14 @@ describe('generalised exit execution', async function () { */ context('boostedWeightedSimple', async () => { if (!TEST_BOOSTED_WEIGHTED_SIMPLE) return true; - const pool = addresses.boostedWeightedSimple1; - const amount = parseFixed('0.05', pool.decimals).toString(); + + before(() => { + pool = addresses.boostedWeightedSimple1; + amount = parseFixed('0.05', pool.decimals).toString(); + }); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testGeneralisedExit(pool, pools, signer, amount, simulationType); }); }); @@ -291,11 +214,14 @@ describe('generalised exit execution', async function () { */ context('boostedWeightedGeneral', async () => { if (!TEST_BOOSTED_WEIGHTED_GENERAL) return true; - const pool = addresses.boostedMeta1; - const amount = parseFixed('0.05', pool.decimals).toString(); + + before(() => { + pool = addresses.boostedMeta1; + amount = parseFixed('0.05', pool.decimals).toString(); + }); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testGeneralisedExit(pool, pools, signer, amount, simulationType); }); }); @@ -307,11 +233,14 @@ describe('generalised exit execution', async function () { */ context('boostedWeightedMeta', async () => { if (!TEST_BOOSTED_WEIGHTED_META) return true; - const pool = addresses.boostedWeightedMeta1; - const amount = parseFixed('0.05', pool.decimals).toString(); + + before(() => { + pool = addresses.boostedWeightedMeta1; + amount = parseFixed('0.05', pool.decimals).toString(); + }); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testGeneralisedExit(pool, pools, signer, amount, simulationType); }); }); @@ -322,11 +251,14 @@ describe('generalised exit execution', async function () { */ context('boostedWeightedMetaAlt', async () => { if (!TEST_BOOSTED_WEIGHTED_META_ALT) return true; - const pool = addresses.boostedWeightedMetaAlt1; - const amount = parseFixed('0.01', pool.decimals).toString(); + + before(() => { + pool = addresses.boostedWeightedMetaAlt1; + amount = parseFixed('0.01', pool.decimals).toString(); + }); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testGeneralisedExit(pool, pools, signer, amount, simulationType); }); }); @@ -339,11 +271,14 @@ describe('generalised exit execution', async function () { */ context('boostedWeightedMetaGeneral', async () => { if (!TEST_BOOSTED_WEIGHTED_META_GENERAL) return true; - const pool = addresses.boostedWeightedMetaGeneral1; - const amount = parseFixed('0.05', pool.decimals).toString(); + + before(() => { + pool = addresses.boostedWeightedMetaGeneral1; + amount = parseFixed('0.05', pool.decimals).toString(); + }); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testGeneralisedExit(pool, pools, signer, amount, simulationType); }); }); }); diff --git a/balancer-js/src/test/lib/exitHelper.ts b/balancer-js/src/test/lib/exitHelper.ts index 6951a9311..b32ee1742 100644 --- a/balancer-js/src/test/lib/exitHelper.ts +++ b/balancer-js/src/test/lib/exitHelper.ts @@ -1,11 +1,15 @@ -import { PoolWithMethods } from '@/types'; import { JsonRpcSigner } from '@ethersproject/providers'; import { BigNumber } from '@ethersproject/bignumber'; -import { expect } from 'chai'; import { formatFixed } from '@ethersproject/bignumber'; -import { addSlippage, subSlippage } from '@/lib/utils/slippageHelper'; +import { expect } from 'chai'; + +import { truncateAddresses, removeItem, SimulationType } from '@/.'; +import { insert, addSlippage, subSlippage } from '@/lib/utils'; +import { Contracts } from '@/modules/contracts/contracts.module'; +import { Pools } from '@/modules/pools'; +import { Relayer } from '@/modules/relayer/relayer.module'; import { accuracy, sendTransactionGetBalances } from '@/test/lib/utils'; -import { insert } from '@/lib/utils'; +import { PoolWithMethods } from '@/types'; export const testExactBptIn = async ( pool: PoolWithMethods, @@ -151,3 +155,94 @@ export const testRecoveryExit = async ( ); expect(priceImpactFloat).to.be.closeTo(0, 0.01); // exiting proportionally should have price impact near zero }; + +export const testGeneralisedExit = async ( + pool: { id: string; address: string; slot: number }, + pools: Pools, + signer: JsonRpcSigner, + exitAmount: string, + simulationType: SimulationType.Static | SimulationType.Tenderly, + expectUnwrap = false +): Promise<{ + expectedAmountsOut: string[]; + gasUsed: BigNumber; +}> => { + const slippage = '10'; // 10 bps = 0.1% + + const signerAddress = await signer.getAddress(); + const { contracts, contractAddresses } = new Contracts( + signer.provider.network.chainId, + signer.provider + ); + + // Replicating UI user flow: + + // 1. Gets exitInfo + // - this helps user to decide if they will approve relayer, etc by returning estimated amounts out/pi. + // - also returns tokensOut and whether or not unwrap should be used + const exitInfo = await pools.getExitInfo( + pool.id, + exitAmount, + signerAddress, + signer + ); + + const authorisation = await Relayer.signRelayerApproval( + contractAddresses.relayer, + signerAddress, + signer, + contracts.vault + ); + + // 2. Get call data and expected/min amounts out + // - Uses a Static/Tenderly call to simulate tx then applies slippage + const { to, encodedCall, tokensOut, expectedAmountsOut, minAmountsOut } = + await pools.generalisedExit( + pool.id, + exitAmount, + signerAddress, + slippage, + signer, + simulationType, + exitInfo.needsUnwrap, + authorisation + ); + + // 3. Sends tx + const { transactionReceipt, balanceDeltas, gasUsed } = + await sendTransactionGetBalances( + [pool.address, ...tokensOut], + signer, + signerAddress, + to, + encodedCall + ); + + const tokensOutDeltas = removeItem(balanceDeltas, 0); + console.table({ + tokensOut: truncateAddresses(tokensOut), + estimateAmountsOut: exitInfo.estimatedAmountsOut, + minAmountsOut: minAmountsOut, + expectedAmountsOut: expectedAmountsOut, + balanceDeltas: tokensOutDeltas.map((b) => b.toString()), + }); + console.log('Gas used', gasUsed.toString()); + console.log(`Should unwrap: `, exitInfo.needsUnwrap); + + expect(transactionReceipt.status).to.eq(1); + expect(balanceDeltas[0].toString()).to.eq(exitAmount.toString()); + expect(exitInfo.needsUnwrap).to.eq(expectUnwrap); + tokensOutDeltas.forEach((b, i) => { + const minOut = BigNumber.from(minAmountsOut[i]); + expect(b.gte(minOut)).to.be.true; + expect(accuracy(b, BigNumber.from(expectedAmountsOut[i]))).to.be.closeTo( + 1, + 1e-2 + ); // inaccuracy should be less than 1% + }); + const expectedMins = expectedAmountsOut.map((a) => + subSlippage(BigNumber.from(a), BigNumber.from(slippage)).toString() + ); + expect(expectedMins).to.deep.eq(minAmountsOut); + return { expectedAmountsOut, gasUsed }; +}; From 97d1dfb7fa192a366c86aa922b4a725e2fceb15b Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 12 May 2023 16:04:51 +0100 Subject: [PATCH 41/86] Change to use testFlow helper. --- .../exits/exits.module.integration.spec.ts | 279 ++++++------------ balancer-js/src/modules/exits/testHelper.ts | 1 + 2 files changed, 88 insertions(+), 192 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration.spec.ts b/balancer-js/src/modules/exits/exits.module.integration.spec.ts index 8b39e9885..4963e0e66 100644 --- a/balancer-js/src/modules/exits/exits.module.integration.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration.spec.ts @@ -1,36 +1,9 @@ // yarn test:only ./src/modules/exits/exits.module.integration.spec.ts import dotenv from 'dotenv'; -import { expect } from 'chai'; -import { BigNumber, parseFixed } from '@ethersproject/bignumber'; -import { JsonRpcProvider } from '@ethersproject/providers'; - -import { - BalancerSDK, - GraphQLQuery, - GraphQLArgs, - Network, - truncateAddresses, -} from '@/.'; -import { subSlippage } from '@/lib/utils/slippageHelper'; -import { Relayer } from '@/modules/relayer/relayer.module'; -import { SimulationType } from '@/modules/simulation/simulation.module'; -import { Contracts } from '@/modules/contracts/contracts.module'; -import { - accuracy, - forkSetup, - sendTransactionGetBalances, -} from '@/test/lib/utils'; +import { parseFixed } from '@ethersproject/bignumber'; +import { Network } from '@/.'; import { ADDRESSES } from '@/test/lib/constants'; - -/** - * -- Integration tests for generalisedExit -- - * - * It compares results from local fork transactions with simulated results from - * the Simulation module, which can be of 3 different types: - * 1. Tenderly: uses Tenderly Simulation API (third party service) - * 2. VaultModel: uses TS math, which may be less accurate (min. 99% accuracy) - * 3. Static: uses staticCall, which is 100% accurate but requires vault approval - */ +import { testFlow } from './testHelper'; dotenv.config(); @@ -44,163 +17,13 @@ const TEST_BOOSTED_WEIGHTED_META = true; const TEST_BOOSTED_WEIGHTED_META_ALT = true; const TEST_BOOSTED_WEIGHTED_META_GENERAL = true; -/* - * Testing on GOERLI - * - Run node on terminal: yarn run node:goerli - * - Uncomment section below: - */ const network = Network.GOERLI; const blockNumber = 8744170; -const { ALCHEMY_URL_GOERLI: jsonRpcUrl } = process.env; -const rpcUrl = 'http://127.0.0.1:8000'; - -/* - * Testing on MAINNET - * - Run node on terminal: yarn run node - * - Uncomment section below: - */ -// const network = Network.MAINNET; -// const blockNumber = 15519886; -// const customSubgraphUrl = -// 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2-beta'; -// const { ALCHEMY_URL: jsonRpcUrl } = process.env; -// const rpcUrl = 'http://127.0.0.1:8545'; - -const addresses = ADDRESSES[network]; - -// Set tenderly config blockNumber and use default values for other parameters -const tenderlyConfig = { - blockNumber, -}; - -/** - * Example of subgraph query that allows filtering pools. - * Might be useful to reduce the response time by limiting the amount of pool - * data that will be queried by the SDK. Specially when on chain data is being - * fetched as well. - */ -const poolAddresses = Object.values(addresses).map( +const slippage = '10'; // 10 bps = 0.1% +const poolAddresses = Object.values(ADDRESSES[network]).map( (address) => address.address ); -const subgraphArgs: GraphQLArgs = { - where: { - swapEnabled: { - eq: true, - }, - totalShares: { - gt: 0.000000000001, - }, - address: { - in: poolAddresses, - }, - }, - orderBy: 'totalLiquidity', - orderDirection: 'desc', - block: { number: blockNumber }, -}; -const subgraphQuery: GraphQLQuery = { args: subgraphArgs, attrs: {} }; - -const sdk = new BalancerSDK({ - network, - rpcUrl, - tenderly: tenderlyConfig, - subgraphQuery, -}); -const { pools } = sdk; -const provider = new JsonRpcProvider(rpcUrl, network); -const signer = provider.getSigner(); -const { contracts, contractAddresses } = new Contracts( - network as number, - provider -); -const relayer = contractAddresses.relayer; - -const testFlow = async ( - pool: { id: string; address: string; slot: number }, - amount: string -) => { - const slippage = '10'; // 10 bps = 0.1% - - const tokens = [pool.address]; - const slots = [pool.slot]; - const balances = [amount]; - - await forkSetup( - signer, - tokens, - slots, - balances, - jsonRpcUrl as string, - blockNumber - ); - - const signerAddress = await signer.getAddress(); - - const exitInfo = await pools.getExitInfo( - pool.id, - amount, - signerAddress, - signer - ); - - // User reviews expectedAmountOut - console.log(' -- Simulating using Vault Model -- '); - console.table({ - tokensOut: truncateAddresses([pool.address, ...exitInfo.tokensOut]), - expectedAmountsOut: ['0', ...exitInfo.estimatedAmountsOut], - }); - - const authorisation = await Relayer.signRelayerApproval( - relayer, - signerAddress, - signer, - contracts.vault - ); - - const { to, encodedCall, tokensOut, expectedAmountsOut, minAmountsOut } = - await pools.generalisedExit( - pool.id, - amount, - signerAddress, - slippage, - signer, - SimulationType.Static, - exitInfo.needsUnwrap, - authorisation - ); - - const { transactionReceipt, balanceDeltas, gasUsed } = - await sendTransactionGetBalances( - tokensOut, - signer, - signerAddress, - to, - encodedCall - ); - - console.log(' -- Simulating using Static Call -- '); - console.table({ - tokensOut: truncateAddresses([pool.address, ...tokensOut]), - minAmountsOut: ['0', ...minAmountsOut], - expectedAmountsOut: ['0', ...expectedAmountsOut], - balanceDeltas: balanceDeltas.map((b) => b.toString()), - }); - console.log('Gas used', gasUsed.toString()); - - expect(transactionReceipt.status).to.eq(1); - balanceDeltas.forEach((b, i) => { - const minOut = BigNumber.from(minAmountsOut[i]); - expect(b.gte(minOut)).to.be.true; - expect(accuracy(b, BigNumber.from(expectedAmountsOut[i]))).to.be.closeTo( - 1, - 1e-2 - ); // inaccuracy should be less than 1% - }); - const expectedMins = expectedAmountsOut.map((a) => - subSlippage(BigNumber.from(a), BigNumber.from(slippage)).toString() - ); - expect(expectedMins).to.deep.eq(minAmountsOut); -}; +const addresses = ADDRESSES[network]; describe('generalised exit execution', async function () { this.timeout(120000); // Sets timeout for all tests within this scope to 2 minutes @@ -216,7 +39,15 @@ describe('generalised exit execution', async function () { const amount = parseFixed('0.02', pool.decimals).toString(); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testFlow( + pool, + slippage, + amount, + false, + network, + blockNumber, + poolAddresses + ); }); }); @@ -231,7 +62,15 @@ describe('generalised exit execution', async function () { const amount = parseFixed('0.05', pool.decimals).toString(); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testFlow( + pool, + slippage, + amount, + false, + network, + blockNumber, + poolAddresses + ); }); }); @@ -246,7 +85,15 @@ describe('generalised exit execution', async function () { const amount = parseFixed('0.05', pool.decimals).toString(); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testFlow( + pool, + slippage, + amount, + false, + network, + blockNumber, + poolAddresses + ); }); }); @@ -263,7 +110,15 @@ describe('generalised exit execution', async function () { const amount = parseFixed('0.05', pool.decimals).toString(); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testFlow( + pool, + slippage, + amount, + false, + network, + blockNumber, + poolAddresses + ); }); }); @@ -278,7 +133,15 @@ describe('generalised exit execution', async function () { const amount = parseFixed('0.05', pool.decimals).toString(); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testFlow( + pool, + slippage, + amount, + false, + network, + blockNumber, + poolAddresses + ); }); }); @@ -295,7 +158,15 @@ describe('generalised exit execution', async function () { const amount = parseFixed('0.05', pool.decimals).toString(); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testFlow( + pool, + slippage, + amount, + false, + network, + blockNumber, + poolAddresses + ); }); }); @@ -311,7 +182,15 @@ describe('generalised exit execution', async function () { const amount = parseFixed('0.05', pool.decimals).toString(); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testFlow( + pool, + slippage, + amount, + false, + network, + blockNumber, + poolAddresses + ); }); }); @@ -326,7 +205,15 @@ describe('generalised exit execution', async function () { const amount = parseFixed('0.01', pool.decimals).toString(); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testFlow( + pool, + slippage, + amount, + false, + network, + blockNumber, + poolAddresses + ); }); }); @@ -343,7 +230,15 @@ describe('generalised exit execution', async function () { const amount = parseFixed('0.05', pool.decimals).toString(); it('should exit pool correctly', async () => { - await testFlow(pool, amount); + await testFlow( + pool, + slippage, + amount, + false, + network, + blockNumber, + poolAddresses + ); }); }); }); diff --git a/balancer-js/src/modules/exits/testHelper.ts b/balancer-js/src/modules/exits/testHelper.ts index 95b120373..03d0e7315 100644 --- a/balancer-js/src/modules/exits/testHelper.ts +++ b/balancer-js/src/modules/exits/testHelper.ts @@ -79,6 +79,7 @@ export const testFlow = async ( console.log('Gas used', txResult.gasUsed.toString()); console.log(`Should unwrap: `, exitInfo.needsUnwrap); + expect(txResult.transactionReceipt.status).to.eq(1); expect(txResult.balanceDeltas[0].toString()).to.eq(exitAmount.toString()); expect(exitInfo.needsUnwrap).to.eq(expectUnwrap); tokensOutDeltas.forEach((b, i) => { From ad72a7f4e22cc782212990fbe2e74b8390a7d718 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 12 May 2023 16:20:09 +0100 Subject: [PATCH 42/86] Change to use testFlow helper. --- ...itsProportional.module.integration.spec.ts | 208 ++---------------- balancer-js/src/modules/exits/testHelper.ts | 2 +- 2 files changed, 14 insertions(+), 196 deletions(-) diff --git a/balancer-js/src/modules/exits/exitsProportional.module.integration.spec.ts b/balancer-js/src/modules/exits/exitsProportional.module.integration.spec.ts index ec753865e..7ba23edea 100644 --- a/balancer-js/src/modules/exits/exitsProportional.module.integration.spec.ts +++ b/balancer-js/src/modules/exits/exitsProportional.module.integration.spec.ts @@ -1,265 +1,83 @@ // yarn test:only ./src/modules/exits/exitsProportional.module.integration.spec.ts import dotenv from 'dotenv'; -import { expect } from 'chai'; - -import { BigNumber, parseFixed } from '@ethersproject/bignumber'; -import { JsonRpcProvider, JsonRpcSigner } from '@ethersproject/providers'; - -import { BalancerSDK, GraphQLQuery, GraphQLArgs, Network } from '@/.'; -import { Relayer } from '@/modules/relayer/relayer.module'; -import { accuracy, forkSetup, getBalances } from '@/test/lib/utils'; +import { parseFixed } from '@ethersproject/bignumber'; +import { Network } from '@/.'; import { ADDRESSES } from '@/test/lib/constants'; -import { SimulationType } from '../simulation/simulation.module'; +import { testFlow, Pool } from './testHelper'; dotenv.config(); const network = Network.MAINNET; const blockNumber = 17116836; -const { ALCHEMY_URL: jsonRpcUrl } = process.env; -const rpcUrl = 'http://127.0.0.1:8545'; - +const slippage = '10'; // 10 bps = 0.1% const addresses = ADDRESSES[network]; - -// Set tenderly config blockNumber and use default values for other parameters -const tenderlyConfig = { - blockNumber, -}; - -/** - * Example of subgraph query that allows filtering pools. - * Might be useful to reduce the response time by limiting the amount of pool - * data that will be queried by the SDK. Specially when on chain data is being - * fetched as well. - */ const poolAddresses = Object.values(addresses).map( (address) => address.address ); -const subgraphArgs: GraphQLArgs = { - where: { - swapEnabled: { - eq: true, - }, - totalShares: { - gt: 0.000000000001, - }, - address: { - in: poolAddresses, - }, - }, - orderBy: 'totalLiquidity', - orderDirection: 'desc', - block: { number: blockNumber }, -}; -const subgraphQuery: GraphQLQuery = { args: subgraphArgs, attrs: {} }; - -const sdk = new BalancerSDK({ - network, - rpcUrl, - tenderly: tenderlyConfig, - subgraphQuery, -}); -const { pools, balancerContracts } = sdk; -const provider = new JsonRpcProvider(rpcUrl, network); -const signer = provider.getSigner(1); -const { contracts, contractAddresses } = balancerContracts; -const relayerAddress = contractAddresses.relayer as string; interface Test { - signer: JsonRpcSigner; description: string; - pool: { - id: string; - address: string; - }; + pool: Pool; amount: string; - authorisation: string | undefined; - simulationType?: SimulationType; } const runTests = async (tests: Test[]) => { for (let i = 0; i < tests.length; i++) { const test = tests[i]; it(test.description, async () => { - const signerAddress = await test.signer.getAddress(); - const authorisation = await Relayer.signRelayerApproval( - relayerAddress, - signerAddress, - test.signer, - contracts.vault - ); await testFlow( - test.signer, - signerAddress, test.pool, + slippage, test.amount, - authorisation, - test.simulationType + false, + network, + blockNumber, + poolAddresses ); }).timeout(120000); } }; -const testFlow = async ( - signer: JsonRpcSigner, - signerAddress: string, - pool: { id: string; address: string }, - amount: string, - authorisation: string | undefined, - simulationType = SimulationType.Static -) => { - const gasLimit = 8e6; - const slippage = '10'; // 10 bps = 0.1% - - const { to, encodedCall, tokensOut, expectedAmountsOut, minAmountsOut } = - await pools.generalisedExit( - pool.id, - amount, - signerAddress, - slippage, - signer, - simulationType, - false, - authorisation - ); - - const [bptBalanceBefore, ...tokensOutBalanceBefore] = await getBalances( - [pool.address, ...tokensOut], - signer, - signerAddress - ); - - const response = await signer.sendTransaction({ - to, - data: encodedCall, - gasLimit, - }); - - const receipt = await response.wait(); - console.log('Gas used', receipt.gasUsed.toString()); - - const [bptBalanceAfter, ...tokensOutBalanceAfter] = await getBalances( - [pool.address, ...tokensOut], - signer, - signerAddress - ); - - console.table({ - tokensOut: tokensOut.map((t) => `${t.slice(0, 6)}...${t.slice(38, 42)}`), - minOut: minAmountsOut, - expectedOut: expectedAmountsOut, - balanceAfter: tokensOutBalanceAfter.map((b) => b.toString()), - }); - - expect(receipt.status).to.eql(1); - minAmountsOut.forEach((minAmountOut) => { - expect(BigNumber.from(minAmountOut).gte('0')).to.be.true; - }); - expectedAmountsOut.forEach((expectedAmountOut, i) => { - expect( - BigNumber.from(expectedAmountOut).gte(BigNumber.from(minAmountsOut[i])) - ).to.be.true; - }); - expect(bptBalanceAfter.eq(bptBalanceBefore.sub(amount))).to.be.true; - tokensOutBalanceBefore.forEach((b) => expect(b.eq(0)).to.be.true); - tokensOutBalanceAfter.forEach((balanceAfter, i) => { - const minOut = BigNumber.from(minAmountsOut[i]); - expect(balanceAfter.gte(minOut)).to.be.true; - const expectedOut = BigNumber.from(expectedAmountsOut[i]); - expect(accuracy(balanceAfter, expectedOut)).to.be.closeTo(1, 1e-2); // inaccuracy should not be over to 1% - }); -}; - describe('generalised exit execution', async () => { context('composable stable pool - non-boosted', async () => { - let authorisation: string | undefined; const testPool = addresses.wstETH_rETH_sfrxETH; - - beforeEach(async () => { - const tokens = [testPool.address]; - const slots = [testPool.slot]; - const balances = [parseFixed('0.02', testPool.decimals).toString()]; - await forkSetup( - signer, - tokens, - slots, - balances, - jsonRpcUrl as string, - blockNumber - ); - }); - await runTests([ { - signer, description: 'exit pool', pool: { id: testPool.id, address: testPool.address, + slot: testPool.slot, }, amount: parseFixed('0.01', testPool.decimals).toString(), - authorisation, }, ]); }); context('composable stable pool - boosted', async () => { - let authorisation: string | undefined; const testPool = addresses.bbgusd; - - beforeEach(async () => { - const tokens = [testPool.address]; - const slots = [testPool.slot]; - const balances = [parseFixed('0.02', testPool.decimals).toString()]; - await forkSetup( - signer, - tokens, - slots, - balances, - jsonRpcUrl as string, - blockNumber - ); - }); - await runTests([ { - signer, description: 'exit pool', pool: { id: testPool.id, address: testPool.address, + slot: testPool.slot, }, amount: parseFixed('0.01', testPool.decimals).toString(), - authorisation, }, ]); }); context('weighted with boosted', async () => { - let authorisation: string | undefined; const testPool = addresses.STG_BBAUSD; - - beforeEach(async () => { - const tokens = [testPool.address]; - const slots = [testPool.slot]; - const balances = [parseFixed('25.111', testPool.decimals).toString()]; - await forkSetup( - signer, - tokens, - slots, - balances, - jsonRpcUrl as string, - blockNumber - ); - }); - await runTests([ { - signer, description: 'exit pool', pool: { id: testPool.id, address: testPool.address, + slot: testPool.slot, }, amount: parseFixed('25.111', testPool.decimals).toString(), - authorisation, }, ]); }); diff --git a/balancer-js/src/modules/exits/testHelper.ts b/balancer-js/src/modules/exits/testHelper.ts index 03d0e7315..6b46ce2c2 100644 --- a/balancer-js/src/modules/exits/testHelper.ts +++ b/balancer-js/src/modules/exits/testHelper.ts @@ -34,7 +34,7 @@ const FORK_NODES: Record = { [Network.ARBITRUM]: `${process.env.ALCHEMY_URL_ARBITRUM}`, }; -interface Pool { +export interface Pool { id: string; address: string; slot: number; From a1fcfbc65bf2b6425e60ce2f78c85f178d6a7555 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 12 May 2023 16:22:29 -0300 Subject: [PATCH 43/86] Add limits to swaps when exiting by unwrapping tokens --- balancer-js/src/modules/exits/exits.module.ts | 36 ++++++++++++------- balancer-js/src/modules/graph/graph.ts | 6 +++- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index 0e005c909..cd8c933b5 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -211,7 +211,7 @@ export class Exit { ); const isProportional = PoolGraph.isProportionalPools(orderedNodes); - debugLog(`isProportional, ${isProportional}`); + debugLog(`\nisProportional = ${isProportional}`); let exitPaths: Node[][] = []; let tokensOutByExitPath: string[] = []; @@ -606,15 +606,18 @@ export class Exit { const exitChildren = node.children.filter((child) => exitPath.map((n) => n.index).includes(child.index) ); - const hasOutputChild = exitChildren.some( - (c) => c.exitAction === 'output' + // An action that has either outputs or unwraps as child actions is the last action where we're able to set limits on expected output amounts + const isLastActionWithLimits = exitChildren.some( + (c) => c.exitAction === 'output' || c.exitAction === 'unwrap' ); // Last calls will use minAmountsOut to protect user. Middle calls can safely have 0 minimum as tx will revert if last fails. let minAmountOut = '0'; const minAmountsOutProportional = Array(node.children.length).fill('0'); - if (minAmountsOut && hasOutputChild) { + if (minAmountsOut && isLastActionWithLimits) { if (isProportional) { + // Proportional exits have a minAmountOut for each output node within a single exit path + /** * minAmountsOut is related to the whole multicall transaction, while * minAmountsOutProportional is related only to the current node/transaction @@ -623,18 +626,27 @@ export class Exit { * TODO: extract to a function so it's easier to understand */ node.children.forEach((child, i) => { - if (child.exitAction === 'output') { - minAmountsOutProportional[i] = - minAmountsOut[outputNodes.indexOf(child)]; + let outputChildIndex: number; + if (child.exitAction === 'unwrap') { + outputChildIndex = outputNodes.indexOf(child.children[0]); + minAmountOut = WeiPerEther.mul(minAmountsOut[outputChildIndex]) + .div(child.priceRate) + .toString(); + } else if (child.exitAction === 'output') { + outputChildIndex = outputNodes.indexOf(child); + minAmountOut = minAmountsOut[outputChildIndex]; } + minAmountsOutProportional[i] = minAmountOut; }); - - // Proportional exits have a minAmountOut for each output node within a single exit path - minAmountOut = - minAmountsOut[outputNodes.indexOf(exitChild as Node)]; } else { // Non-proportional exits have a minAmountOut for each exit path - minAmountOut = minAmountsOut[i]; + if (exitChild?.exitAction === 'unwrap') { + minAmountOut = WeiPerEther.mul(minAmountsOut[i]) + .div(exitChild.priceRate) + .toString(); + } else { + minAmountOut = minAmountsOut[i]; + } } } diff --git a/balancer-js/src/modules/graph/graph.ts b/balancer-js/src/modules/graph/graph.ts index 52bc5e1e8..6a3566a72 100644 --- a/balancer-js/src/modules/graph/graph.ts +++ b/balancer-js/src/modules/graph/graph.ts @@ -28,6 +28,7 @@ export interface Node { spotPrices: SpotPrices; decimals: number; balance: string; + priceRate: string; } type JoinAction = 'input' | 'batchSwap' | 'wrap' | 'joinPool'; @@ -169,6 +170,7 @@ export class PoolGraph { spotPrices, decimals, balance: pool.totalShares, + priceRate: WeiPerEther.toString(), }; this.updateNodeIfProportionalExit(pool, poolNode); nodeIndex++; @@ -276,7 +278,7 @@ export class PoolGraph { ) throw new Error('Issue With Linear Pool'); - const { balancesEvm, upScaledBalances, scalingFactorsRaw } = + const { balancesEvm, upScaledBalances, scalingFactorsRaw, priceRates } = parsePoolInfo(linearPool); const wrappedTokenNode: Node = { @@ -295,6 +297,7 @@ export class PoolGraph { spotPrices: {}, decimals: 18, balance: balancesEvm[linearPool.wrappedIndex].toString(), + priceRate: priceRates[linearPool.wrappedIndex].toString(), }; nodeIndex++; @@ -348,6 +351,7 @@ export class PoolGraph { spotPrices: {}, decimals, balance, + priceRate: WeiPerEther.toString(), }, nodeIndex + 1, ]; From 3cb94bde349334f7339f7b6330604711b6fd291b Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 12 May 2023 17:23:12 -0300 Subject: [PATCH 44/86] Refactors encode unwrap to avoid code repetition --- balancer-js/src/modules/exits/exits.module.ts | 72 ++---------- .../src/modules/relayer/relayer.module.ts | 108 +++++++++--------- balancer-js/src/modules/relayer/types.ts | 26 +---- 3 files changed, 68 insertions(+), 138 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index fb979cefe..c12afb136 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -754,68 +754,16 @@ export class Exit { const linearPoolType = node.parent?.type as string; console.log('linear type: ', linearPoolType); - let encodedCall = ''; - - switch (linearPoolType) { - case 'ERC4626Linear': - { - encodedCall = Relayer.encodeUnwrapERC4626({ - wrappedToken: node.address, - sender, - recipient, - amount, - outputReference, - }); - } - break; - case 'AaveLinear': - { - encodedCall = Relayer.encodeUnwrapAaveStaticToken({ - staticToken: node.address, - sender, - recipient, - amount, - toUnderlying: true, - outputReference, - }); - } - break; - case 'EulerLinear': - { - encodedCall = Relayer.encodeUnwrapEuler({ - wrappedToken: node.address, - sender, - recipient, - amount, - outputReference, - }); - } - break; - case 'GearboxLinear': - { - encodedCall = Relayer.encodeUnwrapGearbox({ - wrappedToken: node.address, - sender, - recipient, - dieselAmount: amount, - outputReference, - }); - } - break; - case 'ReaperLinear': - { - encodedCall = Relayer.encodeUnwrapReaper({ - vaultToken: node.address, - sender, - recipient, - amount, - outputReference, - }); - } - break; - default: - throw new Error('Unsupported linear pool type'); - } + const encodedCall = Relayer.encodeUnwrap( + { + wrappedToken: node.address, + sender, + recipient, + amount, + outputReference, + }, + linearPoolType + ); // debugLog('\nUwrap:'); // debugLog(JSON.stringify(call)); diff --git a/balancer-js/src/modules/relayer/relayer.module.ts b/balancer-js/src/modules/relayer/relayer.module.ts index e1d86ac7b..10ade310f 100644 --- a/balancer-js/src/modules/relayer/relayer.module.ts +++ b/balancer-js/src/modules/relayer/relayer.module.ts @@ -8,20 +8,18 @@ import { EncodeExitPoolInput, EncodeJoinPoolInput, EncodeUnwrapAaveStaticTokenInput, - EncodeUnwrapERC4626Input, - EncodeUnwrapGearboxInput, - EncodeUnwrapReaperInput, - EncodeUnwrapUnbuttonTokenInput, + EncodeUnwrapInput, EncodeUnwrapWstETHInput, EncodeWrapAaveDynamicTokenInput, ExitPoolData, JoinPoolData, } from './types'; -import { ExitPoolRequest, JoinPoolRequest } from '@/types'; +import { ExitPoolRequest, JoinPoolRequest, PoolType } from '@/types'; import { Swap } from '../swaps/types'; import { RelayerAuthorization } from '@/lib/utils'; import FundManagementStruct = IVault.FundManagementStruct; import SingleSwapStruct = IVault.SingleSwapStruct; +import { isLinearish } from '@/lib/utils'; export * from './types'; @@ -151,39 +149,8 @@ export class Relayer { ]); } - static encodeUnwrapERC4626(params: EncodeUnwrapERC4626Input): string { - return relayerLibrary.encodeFunctionData('unwrapERC4626', [ - params.wrappedToken, - params.sender, - params.recipient, - params.amount, - params.outputReference, - ]); - } - - static encodeUnwrapEuler(params: EncodeUnwrapERC4626Input): string { - return relayerLibrary.encodeFunctionData('unwrapEuler', [ - params.wrappedToken, - params.sender, - params.recipient, - params.amount, - params.outputReference, - ]); - } - - static encodeUnwrapGearbox(params: EncodeUnwrapGearboxInput): string { - return relayerLibrary.encodeFunctionData('unwrapGearbox', [ - params.wrappedToken, - params.sender, - params.recipient, - params.dieselAmount, - params.outputReference, - ]); - } - - static encodeUnwrapReaper(params: EncodeUnwrapReaperInput): string { - return relayerLibrary.encodeFunctionData('unwrapReaperVaultToken', [ - params.vaultToken, + static encodeUnwrapWstETH(params: EncodeUnwrapWstETHInput): string { + return relayerLibrary.encodeFunctionData('unwrapWstETH', [ params.sender, params.recipient, params.amount, @@ -191,20 +158,59 @@ export class Relayer { ]); } - static encodeUnwrapUnbuttonToken( - params: EncodeUnwrapUnbuttonTokenInput + static encodeUnwrap( + params: EncodeUnwrapInput, + linearPoolType: string ): string { - return relayerLibrary.encodeFunctionData('unwrapUnbuttonToken', [ - params.wrapperToken, - params.sender, - params.recipient, - params.amount, - params.outputReference, - ]); - } - - static encodeUnwrapWstETH(params: EncodeUnwrapWstETHInput): string { - return relayerLibrary.encodeFunctionData('unwrapWstETH', [ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let unwrapType: any; + + switch (linearPoolType) { + case 'AaveLinear': + return this.encodeUnwrapAaveStaticToken({ + staticToken: params.wrappedToken, + sender: params.sender, + recipient: params.recipient, + amount: params.amount, + toUnderlying: true, + outputReference: params.outputReference, + }); + case 'Linear': + unwrapType = 'unwrapCompoundV2'; + break; + case 'EulerLinear': + unwrapType = 'unwrapEuler'; + break; + case 'ERC4626Linear': + unwrapType = 'unwrapERC4626'; + break; + case 'BeefyLinear': + unwrapType = 'unwrapBeefy'; + break; + case 'GearboxLinear': + unwrapType = 'unwrapGearbox'; + break; + case 'MidasLinear': + unwrapType = 'unwrapMidas'; + break; + case 'ReaperLinear': + unwrapType = 'unwrapReaperVaultToken'; + break; + case 'SiloLinear': + unwrapType = 'unwrapSilo'; + break; + case 'TetuLinear': + unwrapType = 'unwrapTetu'; + break; + case 'YearnLinear': + unwrapType = 'unwrapYearn'; + break; + default: + throw new Error('Unsupported linear pool type'); + } + + return relayerLibrary.encodeFunctionData(unwrapType, [ + params.wrappedToken, params.sender, params.recipient, params.amount, diff --git a/balancer-js/src/modules/relayer/types.ts b/balancer-js/src/modules/relayer/types.ts index b90acfc69..9dc57c46a 100644 --- a/balancer-js/src/modules/relayer/types.ts +++ b/balancer-js/src/modules/relayer/types.ts @@ -63,7 +63,7 @@ export interface EncodeUnwrapAaveStaticTokenInput { outputReference: BigNumberish; } -export interface EncodeUnwrapERC4626Input { +export interface EncodeUnwrapInput { wrappedToken: string; sender: string; recipient: string; @@ -71,30 +71,6 @@ export interface EncodeUnwrapERC4626Input { outputReference: BigNumberish; } -export interface EncodeUnwrapGearboxInput { - wrappedToken: string; - sender: string; - recipient: string; - dieselAmount: BigNumberish; - outputReference: BigNumberish; -} - -export interface EncodeUnwrapReaperInput { - vaultToken: string; - sender: string; - recipient: string; - amount: BigNumberish; - outputReference: BigNumberish; -} - -export interface EncodeUnwrapUnbuttonTokenInput { - wrapperToken: string; - sender: string; - recipient: string; - amount: BigNumberish; - outputReference: BigNumberish; -} - export interface EncodeUnwrapWstETHInput { sender: string; recipient: string; From 2c04ffe443eec7d43c836af7a73d869dd1e6fd20 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 12 May 2023 17:38:45 -0300 Subject: [PATCH 45/86] Fix minAmountOut calc issue --- balancer-js/src/modules/exits/exits.module.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index cd8c933b5..8747e44a7 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -635,6 +635,8 @@ export class Exit { } else if (child.exitAction === 'output') { outputChildIndex = outputNodes.indexOf(child); minAmountOut = minAmountsOut[outputChildIndex]; + } else { + minAmountOut = '0'; // clears minAmountOut if it's not an output or unwrap } minAmountsOutProportional[i] = minAmountOut; }); From a96dba69320130332dd1585b82ffc165445e7693 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Fri, 12 May 2023 17:55:07 -0300 Subject: [PATCH 46/86] Compare linear pool types supported by the SDK and the BatchRelayer --- .../src/modules/relayer/relayer.module.ts | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/balancer-js/src/modules/relayer/relayer.module.ts b/balancer-js/src/modules/relayer/relayer.module.ts index 10ade310f..1a8016343 100644 --- a/balancer-js/src/modules/relayer/relayer.module.ts +++ b/balancer-js/src/modules/relayer/relayer.module.ts @@ -165,6 +165,15 @@ export class Relayer { // eslint-disable-next-line @typescript-eslint/no-explicit-any let unwrapType: any; + /** + * Other unwrap types available on BatchRelayerLibrary that does not seem to + * have a respective Linear pool type in the SDK: + * - unwrapCompoundV2 + * - unwrapShareToken + * - unwrapUnbuttonToken + * - unwrapWstETH + */ + switch (linearPoolType) { case 'AaveLinear': return this.encodeUnwrapAaveStaticToken({ @@ -175,38 +184,32 @@ export class Relayer { toUnderlying: true, outputReference: params.outputReference, }); - case 'Linear': - unwrapType = 'unwrapCompoundV2'; - break; - case 'EulerLinear': - unwrapType = 'unwrapEuler'; - break; case 'ERC4626Linear': unwrapType = 'unwrapERC4626'; break; - case 'BeefyLinear': - unwrapType = 'unwrapBeefy'; + case 'EulerLinear': + unwrapType = 'unwrapEuler'; break; case 'GearboxLinear': unwrapType = 'unwrapGearbox'; break; - case 'MidasLinear': - unwrapType = 'unwrapMidas'; - break; case 'ReaperLinear': unwrapType = 'unwrapReaperVaultToken'; break; - case 'SiloLinear': - unwrapType = 'unwrapSilo'; - break; case 'TetuLinear': unwrapType = 'unwrapTetu'; break; case 'YearnLinear': unwrapType = 'unwrapYearn'; break; + case 'Linear': + case 'BeefyLinear': + case 'MidasLinear': + case 'SiloLinear': default: - throw new Error('Unsupported linear pool type'); + throw new Error( + 'Unwrapping not supported for this pool type: ' + linearPoolType + ); } return relayerLibrary.encodeFunctionData(unwrapType, [ From e4c643aa4f53c1ddae78d7ac03cf1e8195c3b259 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 15 May 2023 10:06:00 +0100 Subject: [PATCH 47/86] Fix lint. --- balancer-js/src/modules/relayer/relayer.module.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/balancer-js/src/modules/relayer/relayer.module.ts b/balancer-js/src/modules/relayer/relayer.module.ts index 1a8016343..8cc5a5760 100644 --- a/balancer-js/src/modules/relayer/relayer.module.ts +++ b/balancer-js/src/modules/relayer/relayer.module.ts @@ -14,12 +14,11 @@ import { ExitPoolData, JoinPoolData, } from './types'; -import { ExitPoolRequest, JoinPoolRequest, PoolType } from '@/types'; +import { ExitPoolRequest, JoinPoolRequest } from '@/types'; import { Swap } from '../swaps/types'; import { RelayerAuthorization } from '@/lib/utils'; import FundManagementStruct = IVault.FundManagementStruct; import SingleSwapStruct = IVault.SingleSwapStruct; -import { isLinearish } from '@/lib/utils'; export * from './types'; From dbb2fa0fd1d11ae7d30d396cf535ab3bd1ad37b1 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 15 May 2023 11:19:30 +0100 Subject: [PATCH 48/86] Update example with easier network switching. --- balancer-js/examples/exitGeneralised.ts | 27 +++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/balancer-js/examples/exitGeneralised.ts b/balancer-js/examples/exitGeneralised.ts index 7014ad69a..f88c27888 100644 --- a/balancer-js/examples/exitGeneralised.ts +++ b/balancer-js/examples/exitGeneralised.ts @@ -27,18 +27,29 @@ import { SimulationType } from '../src/modules/simulation/simulation.module'; dotenv.config(); -const network = Network.ARBITRUM; -const jsonRpcUrl = process.env.ALCHEMY_URL_ARBITRUM; -const blockNumber = 89431060; -const rpcUrl = 'http://127.0.0.1:8161'; +const RPC_URLS: Record = { + [Network.MAINNET]: `http://127.0.0.1:8545`, + [Network.GOERLI]: `http://127.0.0.1:8000`, + [Network.POLYGON]: `http://127.0.0.1:8137`, + [Network.ARBITRUM]: `http://127.0.0.1:8161`, +}; -const addresses = ADDRESSES[network]; +const FORK_NODES: Record = { + [Network.MAINNET]: `${process.env.ALCHEMY_URL}`, + [Network.GOERLI]: `${process.env.ALCHEMY_URL_GOERLI}`, + [Network.POLYGON]: `${process.env.ALCHEMY_URL_POLYGON}`, + [Network.ARBITRUM]: `${process.env.ALCHEMY_URL_ARBITRUM}`, +}; +const network = Network.MAINNET; +const blockNumber = 17263241; +const addresses = ADDRESSES[network]; +const jsonRpcUrl = FORK_NODES[network]; +const rpcUrl = RPC_URLS[network]; // bb-a-usd -const testPool = addresses.bbUSD_PLUS; - +const testPool = addresses.bbgusd; // Amount of testPool BPT that will be used to exit -const amount = parseFixed('1000', testPool.decimals).toString(); +const amount = parseFixed('1000000', testPool.decimals).toString(); // Setup local fork with correct balances/approval to exit bb-a-usd2 pool const setUp = async (provider: JsonRpcProvider) => { From d0ad210ffc693e96ac382c75eb57e26aec57d122 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 15 May 2023 11:20:15 +0100 Subject: [PATCH 49/86] Add ERC4626 APE pools to constants for testing/examples. --- balancer-js/src/test/lib/constants.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/balancer-js/src/test/lib/constants.ts b/balancer-js/src/test/lib/constants.ts index bd2695889..1685f3d9e 100644 --- a/balancer-js/src/test/lib/constants.ts +++ b/balancer-js/src/test/lib/constants.ts @@ -323,6 +323,20 @@ export const ADDRESSES = { symbol: 'STG', slot: 0, }, + wstEthBoostedApe: { + id: '0x959216bb492b2efa72b15b7aacea5b5c984c3cca000200000000000000000472', + address: '0x959216BB492B2efa72b15B7AAcEa5B5C984c3ccA', + decimals: 18, + symbol: '50wstETH-50stk-APE', + slot: 0, + }, + bbtape: { + id: '0x126e7643235ec0ab9c103c507642dc3f4ca23c66000000000000000000000468', + address: '0x126e7643235ec0ab9c103c507642dC3F4cA23C66', + decimals: 18, + symbol: 'bb-t-stkAPE', + slot: 0, + }, }, [Network.KOVAN]: { // Visit https://balancer-faucet.on.fleek.co/#/faucet for test tokens From 2a7aadd3aba228f55b97f863a3e89725da09906d Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 15 May 2023 11:20:57 +0100 Subject: [PATCH 50/86] Add Gear box integration test (currently failing). --- .../exits.module.integration-mainnet.spec.ts | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index 6728267ea..8c4b6f540 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -14,7 +14,7 @@ const TEST_BBAUSD3 = true; describe('generalised exit execution', async function () { this.timeout(120000); // Sets timeout for all tests within this scope to 2 minutes - context('bbausd3', async () => { + context('ERC4626 - bbausd3', async () => { if (!TEST_BBAUSD3) return true; const network = Network.MAINNET; const blockNo = 17223300; @@ -66,6 +66,73 @@ describe('generalised exit execution', async function () { }); }); + context('exit by unwrapping vs exit to main tokens', async () => { + it('should return similar amounts (proportional to the input)', async () => { + mainTokensAmountsOut.forEach((amount, i) => { + const unwrappedAmount = BigNumber.from( + unwrappingTokensAmountsOut[i] + ).div(amountRatio); + expect( + accuracy(unwrappedAmount, BigNumber.from(amount)) + ).to.be.closeTo(1, 1e-4); // inaccuracy should not be over 1 bps + }); + }); + it('should spend more gas when unwrapping tokens', async () => { + expect(unwrappingTokensGasUsed.gt(mainTokensGasUsed)).to.be.true; + }); + }); + }); + context('GearboxLinear - bbgusd', async () => { + const network = Network.MAINNET; + const blockNo = 17263241; + const pool = ADDRESSES[network].bbgusd; + const slippage = '10'; // 10 bps = 0.1% + let unwrappingTokensAmountsOut: string[]; + let unwrappingTokensGasUsed: BigNumber; + let mainTokensAmountsOut: string[]; + let mainTokensGasUsed: BigNumber; + const poolAddresses = Object.values(ADDRESSES[network]).map( + (address) => address.address + ); + + const amountRatio = 100000; + // Amount greater than the underlying main token balance, which will cause the exit to be unwrapped + const unwrapExitAmount = parseFixed('1000000', pool.decimals); + // Amount smaller than the underlying main token balance, which will cause the exit to be done directly + const mainExitAmount = unwrapExitAmount.div(amountRatio); + + context('exit by unwrapping tokens', async () => { + it('should exit pool correctly', async () => { + const { expectedAmountsOut, gasUsed } = await testFlow( + pool, + slippage, + unwrapExitAmount.toString(), + true, + network, + blockNo, + poolAddresses + ); + unwrappingTokensAmountsOut = expectedAmountsOut; + unwrappingTokensGasUsed = gasUsed; + }); + }); + + context('exit to main tokens directly', async () => { + it('should exit pool correctly', async () => { + const { expectedAmountsOut, gasUsed } = await testFlow( + pool, + slippage, + mainExitAmount.toString(), + false, + network, + blockNo, + poolAddresses + ); + mainTokensAmountsOut = expectedAmountsOut; + mainTokensGasUsed = gasUsed; + }); + }); + context('exit by unwrapping vs exit to main tokens', async () => { it('should return similar amounts (proportional to the input)', async () => { mainTokensAmountsOut.forEach((amount, i) => { From 500baacc24b5c2a92f3bf539e3ff95e646b32dc0 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 15 May 2023 11:31:32 +0100 Subject: [PATCH 51/86] Update test description to make it easier to follow errors. --- .../exits/exits.module.integration-mainnet.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index 8c4b6f540..95c280276 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -35,7 +35,7 @@ describe('generalised exit execution', async function () { const mainExitAmount = unwrapExitAmount.div(amountRatio); context('exit by unwrapping tokens', async () => { - it('should exit pool correctly', async () => { + it('should exit via unwrapping', async () => { const { expectedAmountsOut, gasUsed } = await testFlow( pool, slippage, @@ -51,7 +51,7 @@ describe('generalised exit execution', async function () { }); context('exit to main tokens directly', async () => { - it('should exit pool correctly', async () => { + it('should exit to main tokens directly', async () => { const { expectedAmountsOut, gasUsed } = await testFlow( pool, slippage, @@ -102,7 +102,7 @@ describe('generalised exit execution', async function () { const mainExitAmount = unwrapExitAmount.div(amountRatio); context('exit by unwrapping tokens', async () => { - it('should exit pool correctly', async () => { + it('should exit via unwrapping', async () => { const { expectedAmountsOut, gasUsed } = await testFlow( pool, slippage, @@ -118,7 +118,7 @@ describe('generalised exit execution', async function () { }); context('exit to main tokens directly', async () => { - it('should exit pool correctly', async () => { + it('should exit to main tokens directly', async () => { const { expectedAmountsOut, gasUsed } = await testFlow( pool, slippage, From b2be48645316d034dbb128e8c6dc1a5a46779884 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 15 May 2023 11:47:44 +0100 Subject: [PATCH 52/86] Change console log to debug. --- balancer-js/src/modules/exits/exits.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index c5ff74f66..72f4a3d2b 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -754,7 +754,7 @@ export class Exit { ); const linearPoolType = node.parent?.type as string; - console.log('linear type: ', linearPoolType); + debugLog(`linear type: , ${linearPoolType}`); const encodedCall = Relayer.encodeUnwrap( { From 6f4b745836288ee8e0f86b0262f8d5817503711f Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 15 May 2023 11:56:30 +0100 Subject: [PATCH 53/86] Readd debug log. --- balancer-js/src/modules/exits/exits.module.ts | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index 72f4a3d2b..712246120 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -10,7 +10,7 @@ import { AssetHelpers, subSlippage } from '@/lib/utils'; import { PoolGraph, Node } from '@/modules/graph/graph'; import { Join } from '@/modules/joins/joins.module'; import { calcPriceImpact } from '@/modules/pricing/priceImpact'; -import { Relayer } from '@/modules/relayer/relayer.module'; +import { EncodeUnwrapInput, Relayer } from '@/modules/relayer/relayer.module'; import { Simulation, SimulationType, @@ -754,21 +754,20 @@ export class Exit { ); const linearPoolType = node.parent?.type as string; - debugLog(`linear type: , ${linearPoolType}`); - const encodedCall = Relayer.encodeUnwrap( - { - wrappedToken: node.address, - sender, - recipient, - amount, - outputReference, - }, - linearPoolType - ); + const call: EncodeUnwrapInput = { + wrappedToken: node.address, + sender, + recipient, + amount, + outputReference, + }; - // debugLog('\nUwrap:'); - // debugLog(JSON.stringify(call)); + const encodedCall = Relayer.encodeUnwrap(call, linearPoolType); + + debugLog(`linear type: , ${linearPoolType}`); + debugLog('\nUwrap:'); + debugLog(JSON.stringify(call)); const modelRequest = VaultModel.mapUnwrapRequest( amount, From 20772321448418f022bb7d140904b216e48ca6b3 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Mon, 15 May 2023 15:35:09 -0300 Subject: [PATCH 54/86] Refactor generalisedExit interface to prevent adding a breaking change for SDK clients --- balancer-js/README.md | 8 +++++++- balancer-js/examples/exitGeneralised.ts | 4 ++-- balancer-js/src/modules/exits/exits.module.ts | 4 ++-- balancer-js/src/modules/exits/testHelper.ts | 4 ++-- balancer-js/src/modules/pools/index.ts | 10 +++++----- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/balancer-js/README.md b/balancer-js/README.md index 9f0a62af9..8fb4e4611 100644 --- a/balancer-js/README.md +++ b/balancer-js/README.md @@ -593,6 +593,7 @@ Can exit with CS0_BPT proportionally to: DAI, USDC, USDT and FRAX * @param signer JsonRpcSigner that will sign the staticCall transaction if Static simulation chosen * @param simulationType Simulation type (VaultModel, Tenderly or Static) * @param authorisation Optional auhtorisation call to be added to the chained transaction + * @param unwrapTokens Determines if wrapped tokens should be unwrapped. Default = false * @returns transaction data ready to be sent to the network along with tokens, min and expected amounts out. */ async generalisedExit( @@ -602,7 +603,8 @@ async generalisedExit( slippage: string, signer: JsonRpcSigner, simulationType: SimulationType, - authorisation?: string + authorisation?: string, + unwrapTokens = false ): Promise<{ to: string; encodedCall: string; @@ -616,6 +618,7 @@ async generalisedExit( [Example](./examples/exitGeneralised.ts) # Factory + ### Creating Pools ### WeightedPool @@ -685,6 +688,7 @@ create({ data: BytesLike; } ``` + [Example](./examples/pools/composable-stable/create-and-init-join.ts) ### Linear Pool @@ -717,7 +721,9 @@ Builds a transaction to create a linear pool. data: BytesLike; } ``` + [Example](./examples/pools/linear/create.ts) + ## RelayerService Relayers are (user opt-in, audited) contracts that can make calls to the vault (with the transaction “sender” being any arbitrary address) and use the sender’s ERC20 vault allowance, internal balance or BPTs on their behalf. diff --git a/balancer-js/examples/exitGeneralised.ts b/balancer-js/examples/exitGeneralised.ts index f88c27888..6a8e43096 100644 --- a/balancer-js/examples/exitGeneralised.ts +++ b/balancer-js/examples/exitGeneralised.ts @@ -158,8 +158,8 @@ const exit = async () => { slippage, signer, SimulationType.Static, - needsUnwrap, - relayerAuth + relayerAuth, + needsUnwrap ); // Submit transaction and check balance deltas to confirm success diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index 712246120..df9e83785 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -115,8 +115,8 @@ export class Exit { slippage: string, signer: JsonRpcSigner, simulationType: SimulationType.Static | SimulationType.Tenderly, - unwrapTokens: boolean, - authorisation?: string + authorisation?: string, + unwrapTokens = false ): Promise<{ to: string; encodedCall: string; diff --git a/balancer-js/src/modules/exits/testHelper.ts b/balancer-js/src/modules/exits/testHelper.ts index 6b46ce2c2..5f9ad1efd 100644 --- a/balancer-js/src/modules/exits/testHelper.ts +++ b/balancer-js/src/modules/exits/testHelper.ts @@ -136,8 +136,8 @@ async function userFlow( slippage, signer, SimulationType.Static, - exitInfo.needsUnwrap, - authorisation + authorisation, + exitInfo.needsUnwrap ); // 3. Sends tx const txResult = await sendTransactionGetBalances( diff --git a/balancer-js/src/modules/pools/index.ts b/balancer-js/src/modules/pools/index.ts index 5157e9093..632efb2a1 100644 --- a/balancer-js/src/modules/pools/index.ts +++ b/balancer-js/src/modules/pools/index.ts @@ -357,8 +357,8 @@ export class Pools implements Findable { * @param slippage Maximum slippage tolerance in bps i.e. 50 = 0.5%. * @param signer JsonRpcSigner that will sign the staticCall transaction if Static simulation chosen * @param simulationType Simulation type (Tenderly or Static) - VaultModel should not be used to build exit transaction - * @param unwrapTokens Determines if wrapped tokens should be unwrapped * @param authorisation Optional auhtorisation call to be added to the chained transaction + * @param unwrapTokens Determines if wrapped tokens should be unwrapped. Default = false * @returns transaction data ready to be sent to the network along with tokens, min and expected amounts out. */ async generalisedExit( @@ -368,8 +368,8 @@ export class Pools implements Findable { slippage: string, signer: JsonRpcSigner, simulationType: SimulationType.Static | SimulationType.Tenderly, - unwrapTokens: boolean, - authorisation?: string + authorisation?: string, + unwrapTokens = false ): Promise { return this.exitService.buildExitCall( poolId, @@ -378,8 +378,8 @@ export class Pools implements Findable { slippage, signer, simulationType, - unwrapTokens, - authorisation + authorisation, + unwrapTokens ); } From 315a2b05f0007b90f8ba68613f8fcdd73d943284 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Mon, 15 May 2023 16:13:18 -0300 Subject: [PATCH 55/86] Add support for unwrapping tokens from Beefy, Midas and Silo linear pools --- balancer-js/src/modules/relayer/relayer.module.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/balancer-js/src/modules/relayer/relayer.module.ts b/balancer-js/src/modules/relayer/relayer.module.ts index 8cc5a5760..8550adaf8 100644 --- a/balancer-js/src/modules/relayer/relayer.module.ts +++ b/balancer-js/src/modules/relayer/relayer.module.ts @@ -167,8 +167,6 @@ export class Relayer { /** * Other unwrap types available on BatchRelayerLibrary that does not seem to * have a respective Linear pool type in the SDK: - * - unwrapCompoundV2 - * - unwrapShareToken * - unwrapUnbuttonToken * - unwrapWstETH */ @@ -183,6 +181,7 @@ export class Relayer { toUnderlying: true, outputReference: params.outputReference, }); + case 'BeefyLinear': case 'ERC4626Linear': unwrapType = 'unwrapERC4626'; break; @@ -201,10 +200,12 @@ export class Relayer { case 'YearnLinear': unwrapType = 'unwrapYearn'; break; - case 'Linear': - case 'BeefyLinear': case 'MidasLinear': + unwrapType = 'unwrapCompoundV2'; + break; case 'SiloLinear': + unwrapType = 'unwrapShareToken'; + break; default: throw new Error( 'Unwrapping not supported for this pool type: ' + linearPoolType From 986106875b73cab0dda9653bcfb02d87f4dc8ac9 Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem de Liz Date: Mon, 15 May 2023 19:38:03 -0300 Subject: [PATCH 56/86] Adding Sepolia Network Missing multicall; --- balancer-js/src/lib/constants/config.ts | 34 ++++++++++++++++++++++++ balancer-js/src/lib/constants/network.ts | 1 + 2 files changed, 35 insertions(+) diff --git a/balancer-js/src/lib/constants/config.ts b/balancer-js/src/lib/constants/config.ts index d58189ac9..ff39415fd 100644 --- a/balancer-js/src/lib/constants/config.ts +++ b/balancer-js/src/lib/constants/config.ts @@ -398,6 +398,40 @@ export const BALANCER_NETWORK_CONFIG: Record = { }, ], }, + [Network.SEPOLIA]: { + chainId: Network.SEPOLIA, //11155111 + addresses: { + contracts: { + aaveLinearPoolFactory: '0xdf9b5b00ef9bca66e9902bd813db14e4343be025', + balancerHelpers: '0xdae7e32adc5d490a43ccba1f0c736033f2b4efca', + balancerMinterAddress: '0x1783cd84b3d01854a96b4ed5843753c2ccbd574a', + composableStablePoolFactory: + '0xa3fd20e29358c056b727657e83dfd139abbc9924', + erc4626LinearPoolFactory: '0x59562f93c447656f6e4799fc1fc7c3d977c3324f', + feeDistributor: '0xa6971317fb06c76ef731601c64433a4846fca707', + gaugeController: '0x577e5993b9cc480f07f98b5ebd055604bd9071c4', + gearboxLinearPoolFactory: '0x8df317a729fcaa260306d7de28888932cb579b88', + multicall: '', + protocolFeePercentagesProvider: + '0xf7d5dce55e6d47852f054697bab6a1b48a00ddbd', + relayer: '0x6d5342d716c13d9a3f072a2b11498624ade27f90', + vault: '0xba12222222228d8ba445958a75a0704d566bf2c8', + weightedPoolFactory: '0x7920bfa1b2041911b354747ca7a6cdd2dfc50cfd', + yearnLinearPoolFactory: '0xacf05be5134d64d150d153818f8c67ee36996650', + }, + tokens: { + bal: '0xb19382073c7a0addbb56ac6af1808fa49e377b75', + wrappedNativeAsset: '0x7b79995e5f793a07bc00c21412e50ecae098e7f9', + }, + }, + urls: { + subgraph: + 'https://api.studio.thegraph.com/proxy/24660/balancer-sepolia-v2/v0.0.1', + }, + pools: {}, + poolsToIgnore: [], + sorConnectingTokens: [], + }, }; export const networkAddresses = ( diff --git a/balancer-js/src/lib/constants/network.ts b/balancer-js/src/lib/constants/network.ts index e920fb4ec..6865ec944 100644 --- a/balancer-js/src/lib/constants/network.ts +++ b/balancer-js/src/lib/constants/network.ts @@ -10,4 +10,5 @@ export enum Network { POLYGON = 137, ARBITRUM = 42161, FANTOM = 250, + SEPOLIA = 11155111, } From ac7351ab22a836954b49fb3204566a7faf2814a2 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 16 May 2023 10:28:48 +0100 Subject: [PATCH 57/86] Add multicall from: https://github.com/makerdao/multicall/pull/48. --- balancer-js/src/lib/constants/config.ts | 2 +- balancer-js/src/test/lib/constants.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/balancer-js/src/lib/constants/config.ts b/balancer-js/src/lib/constants/config.ts index ff39415fd..24ed89632 100644 --- a/balancer-js/src/lib/constants/config.ts +++ b/balancer-js/src/lib/constants/config.ts @@ -411,7 +411,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { feeDistributor: '0xa6971317fb06c76ef731601c64433a4846fca707', gaugeController: '0x577e5993b9cc480f07f98b5ebd055604bd9071c4', gearboxLinearPoolFactory: '0x8df317a729fcaa260306d7de28888932cb579b88', - multicall: '', + multicall: '0x25eef291876194aefad0d60dff89e268b90754bb', protocolFeePercentagesProvider: '0xf7d5dce55e6d47852f054697bab6a1b48a00ddbd', relayer: '0x6d5342d716c13d9a3f072a2b11498624ade27f90', diff --git a/balancer-js/src/test/lib/constants.ts b/balancer-js/src/test/lib/constants.ts index bd2695889..97cb4745f 100644 --- a/balancer-js/src/test/lib/constants.ts +++ b/balancer-js/src/test/lib/constants.ts @@ -10,6 +10,7 @@ export const PROVIDER_URLS = { [Network.KOVAN]: `https://kovan.infura.io/v3/${process.env.INFURA}`, [Network.POLYGON]: `https://polygon-mainnet.infura.io/v3/${process.env.INFURA}`, [Network.ARBITRUM]: `https://arbitrum-mainnet.infura.io/v3/${process.env.INFURA}`, + [Network.SEPOLIA]: `https://sepolia.infura.io/v3/${process.env.INFURA}`, }; export const ADDRESSES = { @@ -884,4 +885,16 @@ export const ADDRESSES = { slot: 0, }, }, + [Network.SEPOLIA]: { + WETH: { + address: '0x7b79995e5f793a07bc00c21412e50ecae098e7f9', + decimals: 18, + symbol: 'WETH', + }, + BAL: { + address: '0xb19382073c7a0addbb56ac6af1808fa49e377b75', + decimals: 18, + symbol: 'BAL', + }, + }, }; From fb4d9de174620599120c14aab79435d2bfa252bd Mon Sep 17 00:00:00 2001 From: johngrantuk <4797222+johngrantuk@users.noreply.github.com> Date: Tue, 16 May 2023 09:57:07 +0000 Subject: [PATCH 58/86] chore: version bump v1.0.6-beta.3 --- 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 4f7ba18f3..675f77f4c 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -1,6 +1,6 @@ { "name": "@balancer-labs/sdk", - "version": "1.0.6-beta.2", + "version": "1.0.6-beta.3", "description": "JavaScript SDK for interacting with the Balancer Protocol V2", "license": "GPL-3.0-only", "homepage": "https://github.com/balancer-labs/balancer-sdk#readme", From 0a9cda4e42a6b5ba1c2a7c13cb71040d3450fc8e Mon Sep 17 00:00:00 2001 From: johngrantuk <4797222+johngrantuk@users.noreply.github.com> Date: Tue, 16 May 2023 14:22:09 +0000 Subject: [PATCH 59/86] chore: version bump v1.0.6-beta.4 --- 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 675f77f4c..efd1d703e 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -1,6 +1,6 @@ { "name": "@balancer-labs/sdk", - "version": "1.0.6-beta.3", + "version": "1.0.6-beta.4", "description": "JavaScript SDK for interacting with the Balancer Protocol V2", "license": "GPL-3.0-only", "homepage": "https://github.com/balancer-labs/balancer-sdk#readme", From f8617ac8a185f793171ebec286ab7ac23149c117 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Tue, 16 May 2023 17:24:48 -0300 Subject: [PATCH 60/86] Add failing test to validate that solution works as expected --- .../exits.module.integration-mainnet.spec.ts | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index 95c280276..211ceb737 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -82,6 +82,7 @@ describe('generalised exit execution', async function () { }); }); }); + context('GearboxLinear - bbgusd', async () => { const network = Network.MAINNET; const blockNo = 17263241; @@ -149,4 +150,72 @@ describe('generalised exit execution', async function () { }); }); }); + + context('AaveLinear - bbausd', async () => { + const network = Network.MAINNET; + const blockNo = 17273179; + const pool = ADDRESSES[network].bbausd2; + const slippage = '10'; // 10 bps = 0.1% + let unwrappingTokensAmountsOut: string[]; + let unwrappingTokensGasUsed: BigNumber; + let mainTokensAmountsOut: string[]; + let mainTokensGasUsed: BigNumber; + const poolAddresses = Object.values(ADDRESSES[network]).map( + (address) => address.address + ); + + const amountRatio = 1000; + // Amount greater than the underlying main token balance, which will cause the exit to be unwrapped + const unwrapExitAmount = parseFixed('3000000', pool.decimals); + // Amount smaller than the underlying main token balance, which will cause the exit to be done directly + const mainExitAmount = unwrapExitAmount.div(amountRatio); + + context('exit by unwrapping tokens', async () => { + it('should exit via unwrapping', async () => { + const { expectedAmountsOut, gasUsed } = await testFlow( + pool, + slippage, + unwrapExitAmount.toString(), + true, + network, + blockNo, + poolAddresses + ); + unwrappingTokensAmountsOut = expectedAmountsOut; + unwrappingTokensGasUsed = gasUsed; + }); + }); + + context('exit to main tokens directly', async () => { + it('should exit to main tokens directly', async () => { + const { expectedAmountsOut, gasUsed } = await testFlow( + pool, + slippage, + mainExitAmount.toString(), + false, + network, + blockNo, + poolAddresses + ); + mainTokensAmountsOut = expectedAmountsOut; + mainTokensGasUsed = gasUsed; + }); + }); + + context('exit by unwrapping vs exit to main tokens', async () => { + it('should return similar amounts (proportional to the input)', async () => { + mainTokensAmountsOut.forEach((amount, i) => { + const unwrappedAmount = BigNumber.from( + unwrappingTokensAmountsOut[i] + ).div(amountRatio); + expect( + accuracy(unwrappedAmount, BigNumber.from(amount)) + ).to.be.closeTo(1, 1e-4); // inaccuracy should not be over 1 bps + }); + }); + it('should spend more gas when unwrapping tokens', async () => { + expect(unwrappingTokensGasUsed.gt(mainTokensGasUsed)).to.be.true; + }); + }); + }); }); From 42257f0b841dd3a9363568ff2a61149b5d3efbfb Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Tue, 16 May 2023 18:20:44 -0300 Subject: [PATCH 61/86] Update generalisedExit to exit by selectively unwrapping required tokens --- balancer-js/examples/exitGeneralised.ts | 5 +- .../exits.module.integration-mainnet.spec.ts | 16 ++-- balancer-js/src/modules/exits/exits.module.ts | 79 +++++++++---------- balancer-js/src/modules/exits/testHelper.ts | 8 +- balancer-js/src/modules/graph/graph.ts | 22 +++--- balancer-js/src/modules/pools/index.ts | 6 +- 6 files changed, 70 insertions(+), 66 deletions(-) diff --git a/balancer-js/examples/exitGeneralised.ts b/balancer-js/examples/exitGeneralised.ts index 6a8e43096..709651f73 100644 --- a/balancer-js/examples/exitGeneralised.ts +++ b/balancer-js/examples/exitGeneralised.ts @@ -123,7 +123,7 @@ const exit = async () => { }); // Use SDK to create exit transaction - const { estimatedAmountsOut, tokensOut, needsUnwrap } = + const { estimatedAmountsOut, tokensOut, tokensToUnwrap } = await balancer.pools.getExitInfo( testPool.id, amount, @@ -136,6 +136,7 @@ const exit = async () => { console.table({ tokensOut: truncateAddresses([testPool.address, ...tokensOut]), estimatedAmountsOut: ['0', ...estimatedAmountsOut], + tokensToUnwrap, }); // User approves relayer @@ -159,7 +160,7 @@ const exit = async () => { signer, SimulationType.Static, relayerAuth, - needsUnwrap + tokensToUnwrap ); // Submit transaction and check balance deltas to confirm success diff --git a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts index 211ceb737..2ea09858b 100644 --- a/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration-mainnet.spec.ts @@ -40,7 +40,7 @@ describe('generalised exit execution', async function () { pool, slippage, unwrapExitAmount.toString(), - true, + [ADDRESSES[network].USDC.address], network, blockNo, poolAddresses @@ -56,7 +56,7 @@ describe('generalised exit execution', async function () { pool, slippage, mainExitAmount.toString(), - false, + [], network, blockNo, poolAddresses @@ -108,7 +108,7 @@ describe('generalised exit execution', async function () { pool, slippage, unwrapExitAmount.toString(), - true, + [ADDRESSES[network].DAI.address, ADDRESSES[network].USDC.address], network, blockNo, poolAddresses @@ -124,7 +124,7 @@ describe('generalised exit execution', async function () { pool, slippage, mainExitAmount.toString(), - false, + [], network, blockNo, poolAddresses @@ -153,7 +153,7 @@ describe('generalised exit execution', async function () { context('AaveLinear - bbausd', async () => { const network = Network.MAINNET; - const blockNo = 17273179; + const blockNo = 17263241; const pool = ADDRESSES[network].bbausd2; const slippage = '10'; // 10 bps = 0.1% let unwrappingTokensAmountsOut: string[]; @@ -176,7 +176,7 @@ describe('generalised exit execution', async function () { pool, slippage, unwrapExitAmount.toString(), - true, + [ADDRESSES[network].DAI.address], network, blockNo, poolAddresses @@ -192,7 +192,7 @@ describe('generalised exit execution', async function () { pool, slippage, mainExitAmount.toString(), - false, + [], network, blockNo, poolAddresses @@ -210,7 +210,7 @@ describe('generalised exit execution', async function () { ).div(amountRatio); expect( accuracy(unwrappedAmount, BigNumber.from(amount)) - ).to.be.closeTo(1, 1e-4); // inaccuracy should not be over 1 bps + ).to.be.closeTo(1, 1e-3); // inaccuracy should not be over 10 bps }); }); it('should spend more gas when unwrapping tokens', async () => { diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index df9e83785..41e932c65 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -46,7 +46,7 @@ export interface ExitInfo { tokensOut: string[]; estimatedAmountsOut: string[]; priceImpact: string; - needsUnwrap: boolean; + tokensToUnwrap: string[]; } // Quickly switch useful debug logs on/off @@ -79,7 +79,7 @@ export class Exit { tokensOut: string[]; estimatedAmountsOut: string[]; priceImpact: string; - needsUnwrap: boolean; + tokensToUnwrap: string[]; }> { debugLog(`\n--- getExitInfo()`); /* @@ -96,7 +96,7 @@ export class Exit { amountBptIn, userAddress, signer, - false, + [], SimulationType.VaultModel ); @@ -104,7 +104,7 @@ export class Exit { tokensOut: exit.tokensOut, estimatedAmountsOut: exit.expectedAmountsOut, priceImpact: exit.priceImpact, - needsUnwrap: exit.unwrap, + tokensToUnwrap: exit.tokensToUnwrap, }; } @@ -116,7 +116,7 @@ export class Exit { signer: JsonRpcSigner, simulationType: SimulationType.Static | SimulationType.Tenderly, authorisation?: string, - unwrapTokens = false + tokensToUnwrap?: string[] ): Promise<{ to: string; encodedCall: string; @@ -126,7 +126,7 @@ export class Exit { priceImpact: string; }> { debugLog( - `\n--- exitPool(): unwrapTokens, ${unwrapTokens}, simulationType: ${simulationType}` + `\n--- exitPool(): simulationType: ${simulationType} - tokensToUnwrap: ${tokensToUnwrap}` ); /* Overall exit flow description: @@ -143,7 +143,7 @@ export class Exit { amountBptIn, userAddress, signer, - unwrapTokens, + tokensToUnwrap ?? [], simulationType, authorisation ); @@ -188,11 +188,11 @@ export class Exit { amountBptIn: string, userAddress: string, signer: JsonRpcSigner, - doUnwrap: boolean, + tokensToUnwrap: string[], simulationType: SimulationType, authorisation?: string ): Promise<{ - unwrap: boolean; + tokensToUnwrap: string[]; tokensOut: string[]; exitPaths: Node[][]; isProportional: boolean; @@ -204,7 +204,7 @@ export class Exit { const orderedNodes = await this.poolGraph.getGraphNodes( false, poolId, - doUnwrap + tokensToUnwrap ); const isProportional = PoolGraph.isProportionalPools(orderedNodes); @@ -215,7 +215,6 @@ export class Exit { let tokensOut: string[] = []; const outputNodes = orderedNodes.filter((n) => n.exitAction === 'output'); - const outputBalances = outputNodes.map((n) => n.balance); tokensOutByExitPath = outputNodes.map((n) => n.address.toLowerCase()); tokensOut = [...new Set(tokensOutByExitPath)].sort(); @@ -256,29 +255,33 @@ export class Exit { simulationType ); - const hasSufficientBalance = outputBalances.every((balance, i) => - BigNumber.from(balance).gt(expectedAmountsOutByExitPath[i]) - ); - - if (!hasSufficientBalance) { - if (doUnwrap) - /** - * This case might happen when a whale tries to exit with an amount that - * is at the same time larger than both main and wrapped token balances - */ - throw new Error( - 'Insufficient pool balance to perform generalised exit - try exitting with smaller amounts' - ); - else - return await this.getExit( - poolId, - amountBptIn, - userAddress, - signer, - true, - simulationType, - authorisation - ); + const tokensWithInsufficientBalance = outputNodes + .filter((outputNode, i) => + BigNumber.from(expectedAmountsOutByExitPath[i]).gt(outputNode.balance) + ) + .map((node) => node.address.toLowerCase()); + + if ( + tokensToUnwrap.some((t) => + tokensWithInsufficientBalance.includes(t.toLowerCase()) + ) + ) { + /** + * This means there is not enough balance to exit to main or wrapped tokens only + */ + throw new Error( + 'Insufficient pool balance to perform generalised exit - try exitting with smaller amounts' + ); + } else if (tokensWithInsufficientBalance.length > 0) { + return await this.getExit( + poolId, + amountBptIn, + userAddress, + signer, + [...new Set(tokensWithInsufficientBalance)].sort(), + simulationType, + authorisation + ); } else { const expectedAmountsOut = this.amountsOutByTokenOut( tokensOut, @@ -295,7 +298,7 @@ export class Exit { ); return { - unwrap: doUnwrap, + tokensToUnwrap, tokensOut, exitPaths, isProportional, @@ -320,11 +323,7 @@ export class Exit { amountBptIn: string ): Promise { // Create nodes for each pool/token interaction and order by breadth first - const orderedNodesForJoin = await poolGraph.getGraphNodes( - true, - poolId, - false - ); + const orderedNodesForJoin = await poolGraph.getGraphNodes(true, poolId, []); const joinPaths = Join.getJoinPaths( orderedNodesForJoin, tokensOut, diff --git a/balancer-js/src/modules/exits/testHelper.ts b/balancer-js/src/modules/exits/testHelper.ts index 5f9ad1efd..272b56d9d 100644 --- a/balancer-js/src/modules/exits/testHelper.ts +++ b/balancer-js/src/modules/exits/testHelper.ts @@ -44,7 +44,7 @@ export const testFlow = async ( pool: Pool, slippage: string, exitAmount: string, - expectUnwrap: boolean, + expectToUnwrap: string[], network: Network, blockNumber: number, poolAddressesToConsider: string[] @@ -77,11 +77,11 @@ export const testFlow = async ( balanceDeltas: tokensOutDeltas.map((b) => b.toString()), }); console.log('Gas used', txResult.gasUsed.toString()); - console.log(`Should unwrap: `, exitInfo.needsUnwrap); + console.log(`Tokens to unwrap: `, exitInfo.tokensToUnwrap); expect(txResult.transactionReceipt.status).to.eq(1); expect(txResult.balanceDeltas[0].toString()).to.eq(exitAmount.toString()); - expect(exitInfo.needsUnwrap).to.eq(expectUnwrap); + expect(exitInfo.tokensToUnwrap).to.deep.eq(expectToUnwrap); tokensOutDeltas.forEach((b, i) => { const minOut = BigNumber.from(exitOutput.minAmountsOut[i]); expect(b.gte(minOut)).to.be.true; @@ -137,7 +137,7 @@ async function userFlow( signer, SimulationType.Static, authorisation, - exitInfo.needsUnwrap + exitInfo.tokensToUnwrap ); // 3. Sends tx const txResult = await sendTransactionGetBalances( diff --git a/balancer-js/src/modules/graph/graph.ts b/balancer-js/src/modules/graph/graph.ts index 6a3566a72..24e8b88a4 100644 --- a/balancer-js/src/modules/graph/graph.ts +++ b/balancer-js/src/modules/graph/graph.ts @@ -66,7 +66,7 @@ export class PoolGraph { async buildGraphFromRootPool( poolId: string, - wrapMainTokens: boolean + tokensToUnwrap: string[] ): Promise { const rootPool = await this.pools.find(poolId); if (!rootPool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); @@ -76,7 +76,7 @@ export class PoolGraph { nodeIndex, undefined, WeiPerEther, - wrapMainTokens + tokensToUnwrap ); return rootNode[0]; } @@ -99,7 +99,7 @@ export class PoolGraph { nodeIndex: number, parent: Node | undefined, proportionOfParent: BigNumber, - wrapMainTokens: boolean + tokensToUnwrap: string[] ): Promise<[Node, number]> { const pool = await this.pools.findBy('address', address); @@ -179,7 +179,7 @@ export class PoolGraph { poolNode, nodeIndex, pool, - wrapMainTokens + tokensToUnwrap ); } else { const { balancesEvm } = parsePoolInfo(pool); @@ -204,7 +204,7 @@ export class PoolGraph { nodeIndex, poolNode, finalProportion, - wrapMainTokens + tokensToUnwrap ); nodeIndex = childNode[1]; if (childNode[0]) poolNode.children.push(childNode[0]); @@ -231,13 +231,17 @@ export class PoolGraph { linearPoolNode: Node, nodeIndex: number, linearPool: Pool, - wrapMainTokens: boolean + tokensToUnwrap: string[] ): [Node, number] { // Main token if (linearPool.mainIndex === undefined) throw new Error('Issue With Linear Pool'); - if (wrapMainTokens) { + if ( + tokensToUnwrap + .map((t) => t.toLowerCase()) + .includes(linearPool.tokensList[linearPool.mainIndex].toLowerCase()) + ) { // Linear pool will be joined via wrapped token. This will be the child node. const wrappedNodeInfo = this.createWrappedTokenNode( linearPool, @@ -397,12 +401,12 @@ export class PoolGraph { getGraphNodes = async ( isJoin: boolean, poolId: string, - wrapMainTokens: boolean + tokensToUnwrap: string[] ): Promise => { const rootPool = await this.pools.find(poolId); if (!rootPool) throw new BalancerError(BalancerErrorCode.POOL_DOESNT_EXIST); - const rootNode = await this.buildGraphFromRootPool(poolId, wrapMainTokens); + const rootNode = await this.buildGraphFromRootPool(poolId, tokensToUnwrap); if (rootNode.id !== poolId) throw new Error('Error creating graph nodes'); diff --git a/balancer-js/src/modules/pools/index.ts b/balancer-js/src/modules/pools/index.ts index 632efb2a1..22d152a24 100644 --- a/balancer-js/src/modules/pools/index.ts +++ b/balancer-js/src/modules/pools/index.ts @@ -358,7 +358,7 @@ export class Pools implements Findable { * @param signer JsonRpcSigner that will sign the staticCall transaction if Static simulation chosen * @param simulationType Simulation type (Tenderly or Static) - VaultModel should not be used to build exit transaction * @param authorisation Optional auhtorisation call to be added to the chained transaction - * @param unwrapTokens Determines if wrapped tokens should be unwrapped. Default = false + * @param tokensToUnwrap List all tokens that requires exit by unwrapping - info provided by getExitInfo * @returns transaction data ready to be sent to the network along with tokens, min and expected amounts out. */ async generalisedExit( @@ -369,7 +369,7 @@ export class Pools implements Findable { signer: JsonRpcSigner, simulationType: SimulationType.Static | SimulationType.Tenderly, authorisation?: string, - unwrapTokens = false + tokensToUnwrap?: string[] ): Promise { return this.exitService.buildExitCall( poolId, @@ -379,7 +379,7 @@ export class Pools implements Findable { signer, simulationType, authorisation, - unwrapTokens + tokensToUnwrap ); } From c631ad5f13ae9e10c5842019d8257ae1d4a29020 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Tue, 16 May 2023 18:32:54 -0300 Subject: [PATCH 62/86] Updates remaining tests to conform with new helper method --- .../exits/exits.module.integration.spec.ts | 18 +++++++++--------- ...xitsProportional.module.integration.spec.ts | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/balancer-js/src/modules/exits/exits.module.integration.spec.ts b/balancer-js/src/modules/exits/exits.module.integration.spec.ts index 4963e0e66..bd35a25c9 100644 --- a/balancer-js/src/modules/exits/exits.module.integration.spec.ts +++ b/balancer-js/src/modules/exits/exits.module.integration.spec.ts @@ -43,7 +43,7 @@ describe('generalised exit execution', async function () { pool, slippage, amount, - false, + [], network, blockNumber, poolAddresses @@ -66,7 +66,7 @@ describe('generalised exit execution', async function () { pool, slippage, amount, - false, + [], network, blockNumber, poolAddresses @@ -89,7 +89,7 @@ describe('generalised exit execution', async function () { pool, slippage, amount, - false, + [], network, blockNumber, poolAddresses @@ -114,7 +114,7 @@ describe('generalised exit execution', async function () { pool, slippage, amount, - false, + [], network, blockNumber, poolAddresses @@ -137,7 +137,7 @@ describe('generalised exit execution', async function () { pool, slippage, amount, - false, + [], network, blockNumber, poolAddresses @@ -162,7 +162,7 @@ describe('generalised exit execution', async function () { pool, slippage, amount, - false, + [], network, blockNumber, poolAddresses @@ -186,7 +186,7 @@ describe('generalised exit execution', async function () { pool, slippage, amount, - false, + [], network, blockNumber, poolAddresses @@ -209,7 +209,7 @@ describe('generalised exit execution', async function () { pool, slippage, amount, - false, + [], network, blockNumber, poolAddresses @@ -234,7 +234,7 @@ describe('generalised exit execution', async function () { pool, slippage, amount, - false, + [], network, blockNumber, poolAddresses diff --git a/balancer-js/src/modules/exits/exitsProportional.module.integration.spec.ts b/balancer-js/src/modules/exits/exitsProportional.module.integration.spec.ts index 7ba23edea..408d83bd7 100644 --- a/balancer-js/src/modules/exits/exitsProportional.module.integration.spec.ts +++ b/balancer-js/src/modules/exits/exitsProportional.module.integration.spec.ts @@ -29,7 +29,7 @@ const runTests = async (tests: Test[]) => { test.pool, slippage, test.amount, - false, + [], network, blockNumber, poolAddresses From ea1dd4f392ba6b5930b151ea2dcf09f17fe1ec5f Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Tue, 16 May 2023 18:39:13 -0300 Subject: [PATCH 63/86] Fix minor issues --- balancer-js/examples/exitGeneralised.ts | 6 +++--- balancer-js/src/modules/joins/joins.module.ts | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/balancer-js/examples/exitGeneralised.ts b/balancer-js/examples/exitGeneralised.ts index 709651f73..c112d8cdf 100644 --- a/balancer-js/examples/exitGeneralised.ts +++ b/balancer-js/examples/exitGeneralised.ts @@ -17,10 +17,10 @@ import { SimulationType } from '../src/modules/simulation/simulation.module'; // Expected frontend (FE) flow: // 1. User selects BPT amount to exit a pool -// 2. FE calls exitGeneralised with simulation type VaultModel -// 3. SDK calculates expectedAmountsOut that is at least 99% accurate +// 2. FE calls exitInfo +// 3. SDK returns expectedAmountsOut that is at least 99% accurate and indicates which tokens should be unwrapped (tokensToUnwrap) // 4. User agrees expectedAmountsOut and approves relayer -// 5. With approvals in place, FE calls exitGeneralised with simulation type Static +// 5. With approvals in place, FE calls exitGeneralised with simulation type Static and tokensToUnwrap // 6. SDK calculates expectedAmountsOut that is 100% accurate // 7. SDK returns exitGeneralised transaction data with proper minAmountsOut limits in place // 8. User is now able to submit a safe transaction to the blockchain diff --git a/balancer-js/src/modules/joins/joins.module.ts b/balancer-js/src/modules/joins/joins.module.ts index 261296944..d97c81f07 100644 --- a/balancer-js/src/modules/joins/joins.module.ts +++ b/balancer-js/src/modules/joins/joins.module.ts @@ -67,11 +67,7 @@ export class Join { throw new BalancerError(BalancerErrorCode.INPUT_LENGTH_MISMATCH); // Create nodes for each pool/token interaction and order by breadth first - const orderedNodes = await this.poolGraph.getGraphNodes( - true, - poolId, - false - ); + const orderedNodes = await this.poolGraph.getGraphNodes(true, poolId, []); const joinPaths = Join.getJoinPaths(orderedNodes, tokensIn, amountsIn); From cee3c3818fcda4ad1a62d5030ed43a4ab6ba3e56 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Tue, 16 May 2023 22:23:44 -0300 Subject: [PATCH 64/86] Update unit tests to conform with tokensToUnwrap --- .../src/modules/graph/graph.module.spec.ts | 81 ++++++++++++------- 1 file changed, 51 insertions(+), 30 deletions(-) diff --git a/balancer-js/src/modules/graph/graph.module.spec.ts b/balancer-js/src/modules/graph/graph.module.spec.ts index e911b2e61..c8cdf0654 100644 --- a/balancer-js/src/modules/graph/graph.module.spec.ts +++ b/balancer-js/src/modules/graph/graph.module.spec.ts @@ -16,6 +16,7 @@ import { LinearInfo, } from '@/test/factories/pools'; import { Pool as SdkPool } from '@/types'; +import { formatAddress } from '@/test/lib/utils'; function checkNode( node: Node, @@ -52,7 +53,7 @@ function checkLinearNode( wrappedTokens: SubgraphToken[], mainTokens: SubgraphToken[], expectedOutPutReference: number, - wrapMainTokens: boolean + tokensToUnwrap: string[] ): void { checkNode( linearNode, @@ -65,7 +66,11 @@ function checkLinearNode( expectedOutPutReference.toString(), linearPools[poolIndex].proportionOfParent ); - if (wrapMainTokens) { + const mainToken = + linearPools[poolIndex].tokensList[ + linearPools[poolIndex].mainIndex as number + ]; + if (tokensToUnwrap.includes(mainToken)) { checkNode( linearNode.children[0], 'N/A', @@ -112,7 +117,7 @@ function checkBoosted( boostedPoolInfo: BoostedInfo, boostedIndex: number, expectedProportionOfParent: string, - wrapMainTokens: boolean + tokensToUnwrap: string[] ): void { checkNode( boostedNode, @@ -127,7 +132,7 @@ function checkBoosted( ); boostedNode.children.forEach((linearNode, i) => { let linearInputRef; - if (wrapMainTokens) linearInputRef = boostedIndex + 1 + i * 3; + if (tokensToUnwrap.length > 0) linearInputRef = boostedIndex + 1 + i * 3; else linearInputRef = boostedIndex + 1 + i * 2; checkLinearNode( linearNode, @@ -136,7 +141,7 @@ function checkBoosted( boostedPoolInfo.wrappedTokens, boostedPoolInfo.mainTokens, linearInputRef, - wrapMainTokens + tokensToUnwrap ); }); } @@ -147,7 +152,7 @@ Checks a boostedMeta, a phantomStable with one Linear and one boosted. function checkBoostedMeta( rootNode: Node, boostedMetaInfo: BoostedMetaInfo, - wrapMainTokens: boolean + tokensToUnwrap: string[] ): void { // Check parent node checkNode( @@ -168,10 +173,10 @@ function checkBoostedMeta( boostedMetaInfo.childBoostedInfo, 1, boostedMetaInfo.childBoostedInfo.proportion, - wrapMainTokens + tokensToUnwrap ); let expectedOutputReference = 11; - if (!wrapMainTokens) expectedOutputReference = 8; + if (tokensToUnwrap.length === 0) expectedOutputReference = 8; // Check child Linear node checkLinearNode( rootNode.children[1], @@ -180,7 +185,7 @@ function checkBoostedMeta( boostedMetaInfo.childLinearInfo.wrappedTokens, boostedMetaInfo.childLinearInfo.mainTokens, expectedOutputReference, - wrapMainTokens + tokensToUnwrap ); } @@ -190,7 +195,7 @@ Checks a boostedBig, a phantomStable with two Boosted. function checkBoostedMetaBig( rootNode: Node, boostedMetaBigInfo: BoostedMetaBigInfo, - wrapMainTokens: boolean + tokensToUnwrap: string[] ): void { // Check parent node checkNode( @@ -212,9 +217,9 @@ function checkBoostedMetaBig( boostedMetaBigInfo.childPoolsInfo[i], numberOfNodes, boostedMetaBigInfo.childPoolsInfo[i].proportion, - wrapMainTokens + tokensToUnwrap ); - if (wrapMainTokens) + if (tokensToUnwrap.length > 0) numberOfNodes = boostedMetaBigInfo.childPoolsInfo[i].linearPools.length * 3 + 2; else @@ -252,7 +257,7 @@ describe('Graph', () => { before(async () => { rootNode = await poolsGraph.buildGraphFromRootPool( linearInfo.linearPools[0].id, - false + [] ); }); it('should build single linearPool graph', async () => { @@ -263,7 +268,7 @@ describe('Graph', () => { linearInfo.wrappedTokens, linearInfo.mainTokens, 0, - false + [] ); }); @@ -328,7 +333,7 @@ describe('Graph', () => { it('should throw when pool doesnt exist', async () => { let errorMessage = ''; try { - await poolsGraph.buildGraphFromRootPool('thisisntapool', true); + await poolsGraph.buildGraphFromRootPool('thisisntapool', []); } catch (error) { errorMessage = (error as Error).message; } @@ -336,10 +341,16 @@ describe('Graph', () => { }); context('using wrapped tokens', () => { + let tokensToUnwrap: string[]; before(async () => { + tokensToUnwrap = [ + '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC + '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT + ]; boostedNode = await poolsGraph.buildGraphFromRootPool( boostedPool.id, - true + tokensToUnwrap ); }); @@ -350,7 +361,7 @@ describe('Graph', () => { boostedPoolInfo, 0, '1', - true + tokensToUnwrap ); }); @@ -374,7 +385,7 @@ describe('Graph', () => { before(async () => { boostedNode = await poolsGraph.buildGraphFromRootPool( boostedPool.id, - false + [] ); }); @@ -385,7 +396,7 @@ describe('Graph', () => { boostedPoolInfo, 0, '1', - false + [] ); }); @@ -480,15 +491,22 @@ describe('Graph', () => { }); context('using wrapped tokens', () => { + let tokensToUnwrap: string[]; before(async () => { + tokensToUnwrap = [ + '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC + '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT + formatAddress(`address_STABLE`), + ]; boostedNode = await poolsGraph.buildGraphFromRootPool( rootPool.id, - true + tokensToUnwrap ); }); it('should build boostedPool graph', async () => { - checkBoostedMeta(boostedNode, boostedMetaInfo, true); + checkBoostedMeta(boostedNode, boostedMetaInfo, tokensToUnwrap); }); it('should sort in breadth first order', async () => { @@ -513,14 +531,11 @@ describe('Graph', () => { context('using non-wrapped tokens', () => { before(async () => { - boostedNode = await poolsGraph.buildGraphFromRootPool( - rootPool.id, - false - ); + boostedNode = await poolsGraph.buildGraphFromRootPool(rootPool.id, []); }); it('should build boostedPool graph', async () => { - checkBoostedMeta(boostedNode, boostedMetaInfo, false); + checkBoostedMeta(boostedNode, boostedMetaInfo, []); }); it('should sort in breadth first order', async () => { @@ -636,15 +651,21 @@ describe('Graph', () => { }); context('using wrapped tokens', () => { + let tokensToUnwrap: string[]; before(async () => { + tokensToUnwrap = [ + '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC + '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT + ]; boostedNode = await poolsGraph.buildGraphFromRootPool( boostedPool.id, - true + tokensToUnwrap ); }); it('should build boostedPool graph', async () => { - checkBoostedMetaBig(boostedNode, boostedMetaBigInfo, true); + checkBoostedMetaBig(boostedNode, boostedMetaBigInfo, tokensToUnwrap); }); it('should sort in breadth first order', async () => { @@ -678,12 +699,12 @@ describe('Graph', () => { before(async () => { boostedNode = await poolsGraph.buildGraphFromRootPool( boostedPool.id, - false + [] ); }); it('should build boostedPool graph', async () => { - checkBoostedMetaBig(boostedNode, boostedMetaBigInfo, false); + checkBoostedMetaBig(boostedNode, boostedMetaBigInfo, []); }); it('should sort in breadth first order', async () => { From 7caa811c41a9adec4451021b08e199802e0fa2e0 Mon Sep 17 00:00:00 2001 From: Tim Robinson Date: Wed, 17 May 2023 18:22:51 +1000 Subject: [PATCH 65/86] Add zkEVM support --- balancer-js/src/lib/constants/config.ts | 31 ++++++++++++++++++++++++ balancer-js/src/lib/constants/network.ts | 1 + 2 files changed, 32 insertions(+) diff --git a/balancer-js/src/lib/constants/config.ts b/balancer-js/src/lib/constants/config.ts index 24ed89632..3ba459160 100644 --- a/balancer-js/src/lib/constants/config.ts +++ b/balancer-js/src/lib/constants/config.ts @@ -432,6 +432,37 @@ export const BALANCER_NETWORK_CONFIG: Record = { poolsToIgnore: [], sorConnectingTokens: [], }, + [Network.ZKEVM]: { + chainId: Network.ZKEVM, //1101 + addresses: { + contracts: { + aaveLinearPoolFactory: '0x4b7b369989e613ff2C65768B7Cf930cC927F901E', + balancerHelpers: '0x8E9aa87E45e92bad84D5F8DD1bff34Fb92637dE9', + balancerMinterAddress: '0x475D18169BE8a89357A9ee3Ab00ca386d20fA229', + composableStablePoolFactory: '0x8eA89804145c007e7D226001A96955ad53836087', + erc4626LinearPoolFactory: '0x6B1Da720Be2D11d95177ccFc40A917c2688f396c', + feeDistributor: '', + gaugeController: '', + gearboxLinearPoolFactory: '0x687b8C9b41E01Be8B591725fac5d5f52D0564d79', + multicall: '0xca11bde05977b3631167028862be2a173976ca11', + protocolFeePercentagesProvider: '0x1802953277FD955f9a254B80Aa0582f193cF1d77', + relayer: '0x4678731DC41142A902a114aC5B2F77b63f4a259D', + vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', + weightedPoolFactory: '0x03F3Fb107e74F2EAC9358862E91ad3c692712054', + yearnLinearPoolFactory: '', + }, + tokens: { + bal: '0x120eF59b80774F02211563834d8E3b72cb1649d6', + wrappedNativeAsset: '0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9', + }, + }, + urls: { + subgraph: 'https://api.studio.thegraph.com/query/24660/balancer-polygon-zkevm-v2/v0.0.1', + }, + pools: {}, + poolsToIgnore: [], + sorConnectingTokens: [], + }, }; export const networkAddresses = ( diff --git a/balancer-js/src/lib/constants/network.ts b/balancer-js/src/lib/constants/network.ts index 6865ec944..b4876a0e1 100644 --- a/balancer-js/src/lib/constants/network.ts +++ b/balancer-js/src/lib/constants/network.ts @@ -8,6 +8,7 @@ export enum Network { KOVAN = 42, GNOSIS = 100, POLYGON = 137, + ZKEVM = 1101, ARBITRUM = 42161, FANTOM = 250, SEPOLIA = 11155111, From c1776eadbeb630e1a98531c054eafa998eabc0ff Mon Sep 17 00:00:00 2001 From: Tim Robinson Date: Wed, 17 May 2023 18:27:19 +1000 Subject: [PATCH 66/86] Fix lint issues --- balancer-js/src/lib/constants/config.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/balancer-js/src/lib/constants/config.ts b/balancer-js/src/lib/constants/config.ts index 3ba459160..d63a6d84d 100644 --- a/balancer-js/src/lib/constants/config.ts +++ b/balancer-js/src/lib/constants/config.ts @@ -439,13 +439,15 @@ export const BALANCER_NETWORK_CONFIG: Record = { aaveLinearPoolFactory: '0x4b7b369989e613ff2C65768B7Cf930cC927F901E', balancerHelpers: '0x8E9aa87E45e92bad84D5F8DD1bff34Fb92637dE9', balancerMinterAddress: '0x475D18169BE8a89357A9ee3Ab00ca386d20fA229', - composableStablePoolFactory: '0x8eA89804145c007e7D226001A96955ad53836087', + composableStablePoolFactory: + '0x8eA89804145c007e7D226001A96955ad53836087', erc4626LinearPoolFactory: '0x6B1Da720Be2D11d95177ccFc40A917c2688f396c', feeDistributor: '', gaugeController: '', gearboxLinearPoolFactory: '0x687b8C9b41E01Be8B591725fac5d5f52D0564d79', multicall: '0xca11bde05977b3631167028862be2a173976ca11', - protocolFeePercentagesProvider: '0x1802953277FD955f9a254B80Aa0582f193cF1d77', + protocolFeePercentagesProvider: + '0x1802953277FD955f9a254B80Aa0582f193cF1d77', relayer: '0x4678731DC41142A902a114aC5B2F77b63f4a259D', vault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', weightedPoolFactory: '0x03F3Fb107e74F2EAC9358862E91ad3c692712054', @@ -457,7 +459,8 @@ export const BALANCER_NETWORK_CONFIG: Record = { }, }, urls: { - subgraph: 'https://api.studio.thegraph.com/query/24660/balancer-polygon-zkevm-v2/v0.0.1', + subgraph: + 'https://api.studio.thegraph.com/query/24660/balancer-polygon-zkevm-v2/v0.0.1', }, pools: {}, poolsToIgnore: [], From 94f01a6156b8df6b49c3b79ec04425caa1679a3c Mon Sep 17 00:00:00 2001 From: Tim Robinson Date: Wed, 17 May 2023 20:37:31 +1000 Subject: [PATCH 67/86] Update zkEVM subgraph URL --- balancer-js/src/lib/constants/config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/balancer-js/src/lib/constants/config.ts b/balancer-js/src/lib/constants/config.ts index d63a6d84d..3e5f9a166 100644 --- a/balancer-js/src/lib/constants/config.ts +++ b/balancer-js/src/lib/constants/config.ts @@ -445,7 +445,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { feeDistributor: '', gaugeController: '', gearboxLinearPoolFactory: '0x687b8C9b41E01Be8B591725fac5d5f52D0564d79', - multicall: '0xca11bde05977b3631167028862be2a173976ca11', + multicall: '0xcA11bde05977b3631167028862bE2a173976CA11', protocolFeePercentagesProvider: '0x1802953277FD955f9a254B80Aa0582f193cF1d77', relayer: '0x4678731DC41142A902a114aC5B2F77b63f4a259D', @@ -460,7 +460,7 @@ export const BALANCER_NETWORK_CONFIG: Record = { }, urls: { subgraph: - 'https://api.studio.thegraph.com/query/24660/balancer-polygon-zkevm-v2/v0.0.1', + 'https://api.studio.thegraph.com/query/24660/balancer-polygon-zkevm-v2/v0.0.2', }, pools: {}, poolsToIgnore: [], From 634e7a205653bf7d08243b5df674ebdb9354dbcb Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 17 May 2023 13:35:26 +0100 Subject: [PATCH 68/86] Update doc in example. --- balancer-js/examples/exitGeneralised.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/balancer-js/examples/exitGeneralised.ts b/balancer-js/examples/exitGeneralised.ts index c112d8cdf..1fd1f3f61 100644 --- a/balancer-js/examples/exitGeneralised.ts +++ b/balancer-js/examples/exitGeneralised.ts @@ -18,11 +18,11 @@ import { SimulationType } from '../src/modules/simulation/simulation.module'; // Expected frontend (FE) flow: // 1. User selects BPT amount to exit a pool // 2. FE calls exitInfo -// 3. SDK returns expectedAmountsOut that is at least 99% accurate and indicates which tokens should be unwrapped (tokensToUnwrap) -// 4. User agrees expectedAmountsOut and approves relayer +// 3. SDK returns estimatedAmountsOut that is at least 99% accurate and indicates which tokens should be unwrapped (tokensToUnwrap) +// 4. User agrees estimatedAmountsOut and approves relayer // 5. With approvals in place, FE calls exitGeneralised with simulation type Static and tokensToUnwrap // 6. SDK calculates expectedAmountsOut that is 100% accurate -// 7. SDK returns exitGeneralised transaction data with proper minAmountsOut limits in place +// 7. SDK returns exitGeneralised transaction data with proper minAmountsOut limits in place (calculated using user defined slippage) // 8. User is now able to submit a safe transaction to the blockchain dotenv.config(); From 715197a2381c00cf30f31813e87389db05badb3b Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 17 May 2023 14:16:07 +0100 Subject: [PATCH 69/86] Update example. --- balancer-js/examples/exitGeneralised.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/balancer-js/examples/exitGeneralised.ts b/balancer-js/examples/exitGeneralised.ts index 1fd1f3f61..b0dcce471 100644 --- a/balancer-js/examples/exitGeneralised.ts +++ b/balancer-js/examples/exitGeneralised.ts @@ -8,6 +8,7 @@ import { GraphQLArgs, Network, truncateAddresses, + removeItem, } from '../src/index'; import { forkSetup, sendTransactionGetBalances } from '../src/test/lib/utils'; import { ADDRESSES } from '../src/test/lib/constants'; @@ -133,10 +134,11 @@ const exit = async () => { // User reviews expectedAmountOut console.log(' -- getExitInfo() -- '); + console.log(tokensToUnwrap.toString()); console.table({ - tokensOut: truncateAddresses([testPool.address, ...tokensOut]), - estimatedAmountsOut: ['0', ...estimatedAmountsOut], - tokensToUnwrap, + tokensOut: truncateAddresses(tokensOut), + estimatedAmountsOut: estimatedAmountsOut, + unwrap: tokensOut.map((t) => tokensToUnwrap.includes(t)), }); // User approves relayer @@ -174,11 +176,12 @@ const exit = async () => { console.log(' -- Simulating using Static Call -- '); console.log('Price impact: ', formatFixed(query.priceImpact, 18)); + console.log(`Amount Pool Token In: ${balanceDeltas[0].toString()}`); console.table({ - tokensOut: truncateAddresses([testPool.address, ...query.tokensOut]), - minAmountsOut: ['0', ...query.minAmountsOut], - expectedAmountsOut: ['0', ...query.expectedAmountsOut], - balanceDeltas: balanceDeltas.map((b) => b.toString()), + tokensOut: truncateAddresses(query.tokensOut), + minAmountsOut: query.minAmountsOut, + expectedAmountsOut: query.expectedAmountsOut, + balanceDeltas: removeItem(balanceDeltas, 0).map((b) => b.toString()), }); }; From e8f832501b226467e71c9d98eae445883e68b00f Mon Sep 17 00:00:00 2001 From: johngrantuk <4797222+johngrantuk@users.noreply.github.com> Date: Wed, 17 May 2023 13:29:44 +0000 Subject: [PATCH 70/86] chore: version bump v1.0.6-beta.5 --- 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 efd1d703e..b06c7b654 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -1,6 +1,6 @@ { "name": "@balancer-labs/sdk", - "version": "1.0.6-beta.4", + "version": "1.0.6-beta.5", "description": "JavaScript SDK for interacting with the Balancer Protocol V2", "license": "GPL-3.0-only", "homepage": "https://github.com/balancer-labs/balancer-sdk#readme", From 8088e9d13d91423c6a72f8da72609f267a109ae4 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Wed, 17 May 2023 14:32:12 -0300 Subject: [PATCH 71/86] Refactor exit composable stable V3 --- .../exitV2.concern.integration.spec.ts | 2 +- .../exitV3.concern.integration.spec.ts | 164 +++++------------- balancer-js/src/test/lib/mainnetPools.ts | 10 ++ 3 files changed, 50 insertions(+), 126 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/exitV2.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/exitV2.concern.integration.spec.ts index c9c2808e3..dfa67fa2a 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/exitV2.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/exitV2.concern.integration.spec.ts @@ -30,7 +30,7 @@ const blockNumber = 40818844; let pool: PoolWithMethods; describe('ComposableStableV2 Exits', () => { - // We have to rest the fork between each test as pool value changes after tx is submitted + // We have to reset the fork between each test as pool value changes after tx is submitted beforeEach(async () => { // Setup forked network, set initial token balances and allowances await forkSetup( diff --git a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/exitV3.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/exitV3.concern.integration.spec.ts index 31b035a13..928b4ea0d 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/exitV3.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/exitV3.concern.integration.spec.ts @@ -1,110 +1,64 @@ // yarn test:only ./src/modules/pools/pool-types/concerns/composableStable/exitV3.concern.integration.spec.ts import dotenv from 'dotenv'; -import { expect } from 'chai'; -import { ethers } from 'hardhat'; -import { BigNumber, parseFixed } from '@ethersproject/bignumber'; -import { insert, Network, PoolWithMethods, removeItem } from '@/.'; -import { subSlippage, addSlippage } from '@/lib/utils/slippageHelper'; +import { parseFixed } from '@ethersproject/bignumber'; +import { JsonRpcProvider } from '@ethersproject/providers'; import { - forkSetup, - TestPoolHelper, - sendTransactionGetBalances, -} from '@/test/lib/utils'; + BALANCER_NETWORK_CONFIG, + getPoolAddress, + Network, + Pools, + PoolWithMethods, + removeItem, +} from '@/.'; +import { forkSetup, getPoolFromFile, updateFromChain } from '@/test/lib/utils'; +import { testExactBptIn, testExactTokensOut } from '@/test/lib/exitHelper'; dotenv.config(); const network = Network.MAINNET; const { ALCHEMY_URL: jsonRpcUrl } = process.env; const rpcUrl = 'http://127.0.0.1:8545'; -const provider = new ethers.providers.JsonRpcProvider(rpcUrl, network); +const provider = new JsonRpcProvider(rpcUrl, network); const signer = provider.getSigner(); const blockNumber = 16649181; // wstETH-rETH-sfrxETH-BPT const testPoolId = '0x5aee1e99fe86960377de9f88689616916d5dcabe000000000000000000000467'; - -let signerAddress: string; let pool: PoolWithMethods; -// TODO Add these tests back once Protocol Fees are handled - check V1 and V2 tests to be used as reference -describe.skip('ComposableStableV3 Exits', () => { - // We have to rest the fork between each test as pool value changes after tx is submitted +describe('ComposableStableV3 Exits', () => { beforeEach(async () => { - signerAddress = await signer.getAddress(); - - const testPool = new TestPoolHelper( - testPoolId, - network, - rpcUrl, - blockNumber - ); - - // Gets initial pool info from Subgraph - pool = await testPool.getPool(); - // Setup forked network, set initial token balances and allowances await forkSetup( signer, - pool.tokensList, - Array(pool.tokensList.length).fill(0), - Array(pool.tokensList.length).fill(parseFixed('10', 18).toString()), + [getPoolAddress(testPoolId)], + [0], + [parseFixed('10000', 18).toString()], jsonRpcUrl as string, blockNumber ); + let testPool = await getPoolFromFile(testPoolId, network); // Updatate pool info with onchain state from fork block no - pool = await testPool.getPool(); - }); + testPool = await updateFromChain(testPool, network, provider); + pool = Pools.wrap(testPool, BALANCER_NETWORK_CONFIG[network]); + }); context('exitExactBPTIn', async () => { it('single token max out', async () => { const bptIn = parseFixed('0.1', 18).toString(); - const slippage = '10'; - const { to, data, minAmountsOut, expectedAmountsOut } = - pool.buildExitExactBPTIn( - signerAddress, - bptIn, - slippage, - false, - pool.tokensList[1] - ); - const { transactionReceipt, balanceDeltas } = - await sendTransactionGetBalances( - pool.tokensList, - signer, - signerAddress, - to, - data - ); - expect(transactionReceipt.status).to.eq(1); - const expectedDeltas = insert(expectedAmountsOut, pool.bptIndex, bptIn); - expect(expectedDeltas).to.deep.eq(balanceDeltas.map((a) => a.toString())); - const expectedMins = expectedAmountsOut.map((a) => - subSlippage(BigNumber.from(a), BigNumber.from(slippage)).toString() - ); - expect(expectedMins).to.deep.eq(minAmountsOut); + const tokenOut = pool.tokensList[0]; + await testExactBptIn(pool, signer, bptIn, tokenOut); + }); + it('single token max out, token out after BPT index', async () => { + const bptIn = parseFixed('0.1', 18).toString(); + const tokenOut = pool.tokensList[2]; + await testExactBptIn(pool, signer, bptIn, tokenOut); }); it('proportional exit', async () => { - const bptIn = parseFixed('0.01', 18).toString(); - const slippage = '10'; - const { to, data, minAmountsOut, expectedAmountsOut } = - pool.buildExitExactBPTIn(signerAddress, bptIn, slippage, false); - const { transactionReceipt, balanceDeltas } = - await sendTransactionGetBalances( - pool.tokensList, - signer, - signerAddress, - to, - data - ); - expect(transactionReceipt.status).to.eq(1); - const expectedDeltas = insert(expectedAmountsOut, pool.bptIndex, bptIn); - expect(expectedDeltas).to.deep.eq(balanceDeltas.map((a) => a.toString())); - const expectedMins = expectedAmountsOut.map((a) => - subSlippage(BigNumber.from(a), BigNumber.from(slippage)).toString() - ); - expect(expectedMins).to.deep.eq(minAmountsOut); + const bptIn = parseFixed('0.1', 18).toString(); + await testExactBptIn(pool, signer, bptIn); }); }); @@ -112,61 +66,21 @@ describe.skip('ComposableStableV3 Exits', () => { it('all tokens with value', async () => { const tokensOut = removeItem(pool.tokensList, pool.bptIndex); const amountsOut = tokensOut.map((_, i) => - parseFixed((i * 1).toString(), 18).toString() + parseFixed(((i + 1) * 0.1).toString(), 18).toString() ); - const slippage = '7'; - const { to, data, maxBPTIn, expectedBPTIn } = - pool.buildExitExactTokensOut( - signerAddress, - tokensOut, - amountsOut, - slippage - ); - const { transactionReceipt, balanceDeltas } = - await sendTransactionGetBalances( - pool.tokensList, - signer, - signerAddress, - to, - data - ); - expect(transactionReceipt.status).to.eq(1); - const expectedDeltas = insert(amountsOut, pool.bptIndex, expectedBPTIn); - expect(expectedDeltas).to.deep.eq(balanceDeltas.map((a) => a.toString())); - const expectedMaxBpt = addSlippage( - BigNumber.from(expectedBPTIn), - BigNumber.from(slippage) - ).toString(); - expect(expectedMaxBpt).to.deep.eq(maxBPTIn); + await testExactTokensOut(pool, signer, tokensOut, amountsOut); }); it('single token with value', async () => { const tokensOut = removeItem(pool.tokensList, pool.bptIndex); const amountsOut = Array(tokensOut.length).fill('0'); - amountsOut[0] = parseFixed('2', 18).toString(); - const slippage = '7'; - const { to, data, maxBPTIn, expectedBPTIn } = - pool.buildExitExactTokensOut( - signerAddress, - tokensOut, - amountsOut, - slippage - ); - const { transactionReceipt, balanceDeltas } = - await sendTransactionGetBalances( - pool.tokensList, - signer, - signerAddress, - to, - data - ); - expect(transactionReceipt.status).to.eq(1); - const expectedDeltas = insert(amountsOut, pool.bptIndex, expectedBPTIn); - expect(expectedDeltas).to.deep.eq(balanceDeltas.map((a) => a.toString())); - const expectedMaxBpt = addSlippage( - BigNumber.from(expectedBPTIn), - BigNumber.from(slippage) - ).toString(); - expect(expectedMaxBpt).to.deep.eq(maxBPTIn); + amountsOut[0] = parseFixed('0.1', 18).toString(); + await testExactTokensOut(pool, signer, tokensOut, amountsOut); + }); + it('single token with value, token out after BPT index', async () => { + const tokensOut = removeItem(pool.tokensList, pool.bptIndex); + const amountsOut = Array(tokensOut.length).fill('0'); + amountsOut[2] = parseFixed('0.1', 18).toString(); + await testExactTokensOut(pool, signer, tokensOut, amountsOut); }); }); }); diff --git a/balancer-js/src/test/lib/mainnetPools.ts b/balancer-js/src/test/lib/mainnetPools.ts index e12c443a2..fe067c7d5 100644 --- a/balancer-js/src/test/lib/mainnetPools.ts +++ b/balancer-js/src/test/lib/mainnetPools.ts @@ -61,6 +61,16 @@ export const B_50auraBAL_50wstETH = factories.subgraphPoolBase.build({ ], }); +export const wstETH_rETH_sfrxETH = factories.subgraphPoolBase.build({ + id: '0x5aee1e99fe86960377de9f88689616916d5dcabe000000000000000000000467', + address: '0x5aee1e99fe86960377de9f88689616916d5dcabe'.toLowerCase(), + tokens: [ + factories.subgraphToken.transient({ symbol: 'wstETH' }).build(), + factories.subgraphToken.transient({ symbol: 'rETH' }).build(), + factories.subgraphToken.transient({ symbol: 'sfrxETH' }).build(), + ], +}); + export const getForkedPools = async ( provider: JsonRpcProvider, pools: SubgraphPoolBase[] = [B_50WBTC_50WETH] From 621a017b83bd66739704e8bb7ccf5b7e70798105 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Wed, 17 May 2023 14:35:46 -0300 Subject: [PATCH 72/86] Add integration test to make sure exiting composable stable V4 works as expected --- .../exitV4.concern.integration.spec.ts | 86 +++++++++++++++ .../src/test/fixtures/pools-mainnet.json | 102 +++++++++++++++++- balancer-js/src/test/lib/mainnetPools.ts | 9 ++ 3 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 balancer-js/src/modules/pools/pool-types/concerns/composableStable/exitV4.concern.integration.spec.ts diff --git a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/exitV4.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/exitV4.concern.integration.spec.ts new file mode 100644 index 000000000..cf561fe17 --- /dev/null +++ b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/exitV4.concern.integration.spec.ts @@ -0,0 +1,86 @@ +// yarn test:only ./src/modules/pools/pool-types/concerns/composableStable/exitV4.concern.integration.spec.ts +import dotenv from 'dotenv'; +import { parseFixed } from '@ethersproject/bignumber'; +import { JsonRpcProvider } from '@ethersproject/providers'; +import { + BALANCER_NETWORK_CONFIG, + getPoolAddress, + Network, + Pools, + PoolWithMethods, + removeItem, +} from '@/.'; +import { forkSetup, getPoolFromFile, updateFromChain } from '@/test/lib/utils'; +import { testExactBptIn, testExactTokensOut } from '@/test/lib/exitHelper'; + +dotenv.config(); + +const network = Network.MAINNET; +const { ALCHEMY_URL: jsonRpcUrl } = process.env; +const rpcUrl = 'http://127.0.0.1:8545'; +const provider = new JsonRpcProvider(rpcUrl, network); +const signer = provider.getSigner(); +const blockNumber = 17280000; + +// wstETH-rETH-sfrxETH-BPT +const testPoolId = + '0xec3626fee40ef95e7c0cbb1d495c8b67b34d398300000000000000000000053d'; +let pool: PoolWithMethods; + +describe('ComposableStableV4 Exits', () => { + beforeEach(async () => { + // Setup forked network, set initial token balances and allowances + await forkSetup( + signer, + [getPoolAddress(testPoolId)], + [0], + [parseFixed('10000', 18).toString()], + jsonRpcUrl as string, + blockNumber + ); + + let testPool = await getPoolFromFile(testPoolId, network); + // Updatate pool info with onchain state from fork block no + testPool = await updateFromChain(testPool, network, provider); + + pool = Pools.wrap(testPool, BALANCER_NETWORK_CONFIG[network]); + }); + context('exitExactBPTIn', async () => { + it('single token max out', async () => { + const bptIn = parseFixed('0.1', 18).toString(); + const tokenOut = pool.tokensList[0]; + await testExactBptIn(pool, signer, bptIn, tokenOut); + }); + it('single token max out, token out after BPT index', async () => { + const bptIn = parseFixed('0.1', 18).toString(); + const tokenOut = pool.tokensList[2]; + await testExactBptIn(pool, signer, bptIn, tokenOut); + }); + it('proportional exit', async () => { + const bptIn = parseFixed('0.1', 18).toString(); + await testExactBptIn(pool, signer, bptIn); + }); + }); + + context('exitExactTokensOut', async () => { + it('all tokens with value', async () => { + const tokensOut = removeItem(pool.tokensList, pool.bptIndex); + const amountsOut = tokensOut.map((_, i) => + parseFixed(((i + 1) * 0.1).toString(), 18).toString() + ); + await testExactTokensOut(pool, signer, tokensOut, amountsOut); + }); + it('single token with value', async () => { + const tokensOut = removeItem(pool.tokensList, pool.bptIndex); + const amountsOut = Array(tokensOut.length).fill('0'); + amountsOut[0] = parseFixed('0.1', 18).toString(); + await testExactTokensOut(pool, signer, tokensOut, amountsOut); + }); + it('single token with value, token out after BPT index', async () => { + const tokensOut = removeItem(pool.tokensList, pool.bptIndex); + const amountsOut = Array(tokensOut.length).fill('0'); + amountsOut[1] = parseFixed('0.1', 18).toString(); + await testExactTokensOut(pool, signer, tokensOut, amountsOut); + }); + }); +}); diff --git a/balancer-js/src/test/fixtures/pools-mainnet.json b/balancer-js/src/test/fixtures/pools-mainnet.json index 89fa0dc5d..4c5f636ac 100644 --- a/balancer-js/src/test/fixtures/pools-mainnet.json +++ b/balancer-js/src/test/fixtures/pools-mainnet.json @@ -8058,7 +8058,107 @@ "strategyType": 1, "swapsCount": "723", "holdersCount": "4" + }, + { + "id": "0xec3626fee40ef95e7c0cbb1d495c8b67b34d398300000000000000000000053d", + "name": "Balancer UZD/bb-a-USD stableswap", + "symbol": "B-S-UZD-BB-A-USD", + "address": "0xec3626fee40ef95e7c0cbb1d495c8b67b34d3983", + "poolType": "ComposableStable", + "poolTypeVersion": 4, + "swapFee": "0.0004", + "swapEnabled": true, + "protocolYieldFeeCache": "0.5", + "protocolSwapFeeCache": "0.5", + "amp": "500", + "owner": "0xba1ba1ba1ba1ba1ba1ba1ba1ba1ba1ba1ba1ba1b", + "factory": "0xfada0f4547ab2de89d1304a668c39b3e09aa7c76", + "tokensList": [ + "0xb40b6608b2743e691c9b54ddbdee7bf03cd79f1c", + "0xec3626fee40ef95e7c0cbb1d495c8b67b34d3983", + "0xfebb0bbf162e64fb9d0dfe186e517d84c395f016" + ], + "tokens": [ + { + "id": "0xec3626fee40ef95e7c0cbb1d495c8b67b34d398300000000000000000000053d-0xb40b6608b2743e691c9b54ddbdee7bf03cd79f1c", + "symbol": "UZD", + "name": "UZD Zunami Stable", + "decimals": 18, + "address": "0xb40b6608b2743e691c9b54ddbdee7bf03cd79f1c", + "balance": "5000", + "managedBalance": "0", + "weight": null, + "priceRate": "1", + "token": { + "latestUSDPrice": null, + "pool": null + }, + "isExemptFromYieldProtocolFee": false + }, + { + "id": "0xec3626fee40ef95e7c0cbb1d495c8b67b34d398300000000000000000000053d-0xec3626fee40ef95e7c0cbb1d495c8b67b34d3983", + "symbol": "B-S-UZD-BB-A-USD", + "name": "Balancer UZD/bb-a-USD stableswap", + "decimals": 18, + "address": "0xec3626fee40ef95e7c0cbb1d495c8b67b34d3983", + "balance": "2596148429257413.218796159909145654", + "managedBalance": "0", + "weight": null, + "priceRate": "1", + "token": { + "latestUSDPrice": "0.4999798622425397962243462434197151", + "pool": { + "id": "0xec3626fee40ef95e7c0cbb1d495c8b67b34d398300000000000000000000053d", + "address": "0xec3626fee40ef95e7c0cbb1d495c8b67b34d3983", + "totalShares": "10000.595469088255464394" + } + }, + "isExemptFromYieldProtocolFee": false + }, + { + "id": "0xec3626fee40ef95e7c0cbb1d495c8b67b34d398300000000000000000000053d-0xfebb0bbf162e64fb9d0dfe186e517d84c395f016", + "symbol": "bb-a-USD", + "name": "Balancer Aave v3 Boosted StablePool", + "decimals": 18, + "address": "0xfebb0bbf162e64fb9d0dfe186e517d84c395f016", + "balance": "5000", + "managedBalance": "0", + "weight": null, + "priceRate": "1.000119093824728186", + "token": { + "latestUSDPrice": "1.000034802450382120947007057396189", + "pool": { + "id": "0xfebb0bbf162e64fb9d0dfe186e517d84c395f016000000000000000000000502", + "address": "0xfebb0bbf162e64fb9d0dfe186e517d84c395f016", + "totalShares": "21811046.145758434854302198" + } + }, + "isExemptFromYieldProtocolFee": true + } + ], + "totalLiquidity": "5000.09634497811362010324384540818", + "totalShares": "10000.595469088255464394", + "totalSwapFee": "0", + "totalSwapVolume": "0", + "priceRateProviders": [ + { + "address": "0xfebb0bbf162e64fb9d0dfe186e517d84c395f016", + "token": { + "address": "0xfebb0bbf162e64fb9d0dfe186e517d84c395f016" + } + } + ], + "createTime": 1683442295, + "mainIndex": null, + "wrappedIndex": null, + "totalWeight": "0", + "lowerTarget": null, + "upperTarget": null, + "isInRecoveryMode": null, + "strategyType": 0, + "swapsCount": "0", + "holdersCount": "3" } ] } -} \ No newline at end of file +} diff --git a/balancer-js/src/test/lib/mainnetPools.ts b/balancer-js/src/test/lib/mainnetPools.ts index fe067c7d5..34d88cfc9 100644 --- a/balancer-js/src/test/lib/mainnetPools.ts +++ b/balancer-js/src/test/lib/mainnetPools.ts @@ -71,6 +71,15 @@ export const wstETH_rETH_sfrxETH = factories.subgraphPoolBase.build({ ], }); +export const UZD_bbausd3 = factories.subgraphPoolBase.build({ + id: '0xec3626fee40ef95e7c0cbb1d495c8b67b34d398300000000000000000000053d', + address: '0xec3626fee40ef95e7c0cbb1d495c8b67b34d3983'.toLowerCase(), + tokens: [ + factories.subgraphToken.transient({ symbol: 'UZD' }).build(), + factories.subgraphToken.transient({ symbol: 'bb-a-USD' }).build(), + ], +}); + export const getForkedPools = async ( provider: JsonRpcProvider, pools: SubgraphPoolBase[] = [B_50WBTC_50WETH] From 4e1df6b23394975a2e968386dc7cd51a429925bc Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Wed, 17 May 2023 16:27:56 -0300 Subject: [PATCH 73/86] Move RPC_URL and FORK_NODE to test utils so they can be reused --- balancer-js/src/modules/exits/testHelper.ts | 16 ++-------------- balancer-js/src/test/lib/utils.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/balancer-js/src/modules/exits/testHelper.ts b/balancer-js/src/modules/exits/testHelper.ts index 272b56d9d..bbc3fc63c 100644 --- a/balancer-js/src/modules/exits/testHelper.ts +++ b/balancer-js/src/modules/exits/testHelper.ts @@ -15,25 +15,13 @@ import { accuracy, forkSetup, sendTransactionGetBalances, + FORK_NODES, + RPC_URLS, } from '@/test/lib/utils'; import { Relayer } from '@/modules/relayer/relayer.module'; import { SimulationType } from '../simulation/simulation.module'; import { GeneralisedExitOutput, ExitInfo } from '../exits/exits.module'; -const RPC_URLS: Record = { - [Network.MAINNET]: `http://127.0.0.1:8545`, - [Network.GOERLI]: `http://127.0.0.1:8000`, - [Network.POLYGON]: `http://127.0.0.1:8137`, - [Network.ARBITRUM]: `http://127.0.0.1:8161`, -}; - -const FORK_NODES: Record = { - [Network.MAINNET]: `${process.env.ALCHEMY_URL}`, - [Network.GOERLI]: `${process.env.ALCHEMY_URL_GOERLI}`, - [Network.POLYGON]: `${process.env.ALCHEMY_URL_POLYGON}`, - [Network.ARBITRUM]: `${process.env.ALCHEMY_URL_ARBITRUM}`, -}; - export interface Pool { id: string; address: string; diff --git a/balancer-js/src/test/lib/utils.ts b/balancer-js/src/test/lib/utils.ts index 51ed5955c..47bb6b80b 100644 --- a/balancer-js/src/test/lib/utils.ts +++ b/balancer-js/src/test/lib/utils.ts @@ -1,3 +1,5 @@ +import dotenv from 'dotenv'; + import { BigNumber, BigNumberish, formatFixed } from '@ethersproject/bignumber'; import { hexlify, zeroPad } from '@ethersproject/bytes'; import { AddressZero, MaxUint256, WeiPerEther } from '@ethersproject/constants'; @@ -40,6 +42,8 @@ import polygonPools from '../fixtures/pools-polygon.json'; import { PoolsJsonRepository } from './pools-json-repository'; import { Contracts } from '@/modules/contracts/contracts.module'; +dotenv.config(); + export interface TxResult { transactionReceipt: TransactionReceipt; balanceDeltas: BigNumber[]; @@ -52,6 +56,20 @@ const jsonPools = { [Network.POLYGON]: polygonPools, }; +export const RPC_URLS: Record = { + [Network.MAINNET]: `http://127.0.0.1:8545`, + [Network.GOERLI]: `http://127.0.0.1:8000`, + [Network.POLYGON]: `http://127.0.0.1:8137`, + [Network.ARBITRUM]: `http://127.0.0.1:8161`, +}; + +export const FORK_NODES: Record = { + [Network.MAINNET]: `${process.env.ALCHEMY_URL}`, + [Network.GOERLI]: `${process.env.ALCHEMY_URL_GOERLI}`, + [Network.POLYGON]: `${process.env.ALCHEMY_URL_POLYGON}`, + [Network.ARBITRUM]: `${process.env.ALCHEMY_URL_ARBITRUM}`, +}; + /** * Setup local fork with approved token balance for a given account * From d161915c65990a87256c7bc336239c8116255ccd Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Wed, 17 May 2023 16:28:55 -0300 Subject: [PATCH 74/86] Add failing test so we can verify that joining composable stable V4 does not currently work --- .../join.concern.integration.spec.ts | 127 +++++++++++++----- balancer-js/src/test/lib/utils.ts | 4 +- 2 files changed, 98 insertions(+), 33 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts index c08877fd0..e92d1de8d 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts @@ -1,7 +1,6 @@ // yarn test:only ./src/modules/pools/pool-types/concerns/composableStable/join.concern.integration.spec.ts -import dotenv from 'dotenv'; -import { ethers } from 'hardhat'; import { parseFixed } from '@ethersproject/bignumber'; +import { JsonRpcProvider, JsonRpcSigner } from '@ethersproject/providers'; import { removeItem, @@ -10,7 +9,12 @@ import { replace, BALANCER_NETWORK_CONFIG, } from '@/.'; -import { forkSetup, TestPoolHelper } from '@/test/lib/utils'; +import { + FORK_NODES, + forkSetup, + RPC_URLS, + TestPoolHelper, +} from '@/test/lib/utils'; import { testExactTokensIn, testAttributes, @@ -18,36 +22,41 @@ import { } from '@/test/lib/joinHelper'; import { AddressZero } from '@ethersproject/constants'; -dotenv.config(); - -const network = Network.POLYGON; -const { ALCHEMY_URL_POLYGON: jsonRpcUrl } = process.env; -const rpcUrl = 'http://127.0.0.1:8137'; -const provider = new ethers.providers.JsonRpcProvider(rpcUrl, network); -const signer = provider.getSigner(); -const blockNumber = 42462957; -const testPoolId = - '0x02d2e2d7a89d6c5cb3681cfcb6f7dac02a55eda400000000000000000000088f'; - describe('ComposableStable Pool - Join Functions', async () => { let signerAddress: string; let pool: PoolWithMethods; let testPoolHelper: TestPoolHelper; - before(async () => { - signerAddress = await signer.getAddress(); - - testPoolHelper = new TestPoolHelper( - testPoolId, - network, - rpcUrl, - blockNumber - ); - - // Gets initial pool info from Subgraph - pool = await testPoolHelper.getPool(); - }); + let network: Network; + let jsonRpcUrl: string; + let rpcUrl: string; + let provider: JsonRpcProvider; + let signer: JsonRpcSigner; + let blockNumber: number; + let testPoolId: string; + + context('Integration Tests - Join V1', async () => { + before(async () => { + network = Network.POLYGON; + rpcUrl = RPC_URLS[network]; + provider = new JsonRpcProvider(rpcUrl, network); + signer = provider.getSigner(); + signerAddress = await signer.getAddress(); + jsonRpcUrl = FORK_NODES[network]; + blockNumber = 42462957; + testPoolId = + '0x02d2e2d7a89d6c5cb3681cfcb6f7dac02a55eda400000000000000000000088f'; + + testPoolHelper = new TestPoolHelper( + testPoolId, + network, + rpcUrl, + blockNumber + ); + + // Gets initial pool info from Subgraph + pool = await testPoolHelper.getPool(); + }); - context('Integration Tests', async () => { // We have to rest the fork between each test as pool value changes after tx is submitted beforeEach(async () => { // Setup forked network, set initial token balances and allowances @@ -56,7 +65,7 @@ describe('ComposableStable Pool - Join Functions', async () => { pool.tokensList, [0, 3, 0], Array(pool.tokensList.length).fill(parseFixed('100000', 18).toString()), - jsonRpcUrl as string, + jsonRpcUrl, blockNumber // holds the same state as the static repository ); @@ -95,11 +104,67 @@ describe('ComposableStable Pool - Join Functions', async () => { }); }); + context('Integration Tests - Join V4', async () => { + beforeEach(async () => { + network = Network.MAINNET; + rpcUrl = RPC_URLS[network]; + provider = new JsonRpcProvider(rpcUrl, network); + signer = provider.getSigner(); + signerAddress = await signer.getAddress(); + jsonRpcUrl = FORK_NODES[network]; + blockNumber = 17280000; + testPoolId = + '0xd61e198e139369a40818fe05f5d5e6e045cd6eaf000000000000000000000540'; + + testPoolHelper = new TestPoolHelper( + testPoolId, + network, + rpcUrl, + blockNumber + ); + + // Gets initial pool info from Subgraph + pool = await testPoolHelper.getPool(); + }); + + // We have to rest the fork between each test as pool value changes after tx is submitted + beforeEach(async () => { + // Setup forked network, set initial token balances and allowances + await forkSetup( + signer, + pool.tokensList, + [0, 5, 0], + Array(pool.tokensList.length).fill(parseFixed('10', 18).toString()), + jsonRpcUrl, + blockNumber, // holds the same state as the static repository + [false, true, false] + ); + + // Updatate pool info with onchain state from fork block no + pool = await testPoolHelper.getPool(); + }); + + it('should join - all tokens have value', async () => { + const tokensIn = removeItem(pool.tokensList, pool.bptIndex); + const amountsIn = tokensIn.map((_, i) => + parseFixed(((i + 1) * 0.1).toString(), 18).toString() + ); + await testExactTokensIn(pool, signer, signerAddress, tokensIn, amountsIn); + }); + + it('should join - single token has value', async () => { + const tokensIn = removeItem(pool.tokensList, pool.bptIndex); + const amountsIn = Array(tokensIn.length).fill('0'); + amountsIn[0] = parseFixed('0.202', 18).toString(); + await testExactTokensIn(pool, signer, signerAddress, tokensIn, amountsIn); + }); + }); + context('Unit Tests', () => { it('should return correct attributes for joining', () => { const tokensIn = removeItem(pool.tokensList, pool.bptIndex); const amountsIn = tokensIn.map((_, i) => - parseFixed(((i + 1) * 100).toString(), 18).toString() + parseFixed(((i + 1) * 0.1).toString(), 18).toString() ); testAttributes(pool, testPoolId, signerAddress, tokensIn, amountsIn); }); @@ -107,7 +172,7 @@ describe('ComposableStable Pool - Join Functions', async () => { it('should automatically sort tokens/amounts in correct order', () => { const tokensIn = removeItem(pool.tokensList, pool.bptIndex); const amountsIn = tokensIn.map((_, i) => - parseFixed(((i + 1) * 100).toString(), 18).toString() + parseFixed(((i + 1) * 0.1).toString(), 18).toString() ); testSortingInputs(pool, signerAddress, tokensIn, amountsIn); }); diff --git a/balancer-js/src/test/lib/utils.ts b/balancer-js/src/test/lib/utils.ts index 47bb6b80b..dfd17709b 100644 --- a/balancer-js/src/test/lib/utils.ts +++ b/balancer-js/src/test/lib/utils.ts @@ -87,7 +87,7 @@ export const forkSetup = async ( balances: string[], jsonRpcUrl: string, blockNumber?: number, - isVyperMapping = false + isVyperMapping: boolean[] = Array(tokens.length).fill(false) ): Promise => { await signer.provider.send('hardhat_reset', [ { @@ -110,7 +110,7 @@ export const forkSetup = async ( tokens[i], slots[i], balances[i], - isVyperMapping + isVyperMapping[i] ); // Approve appropriate allowances so that vault contract can move tokens From 2ffca1a5034836055f50bb3177118b63f236c621 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Wed, 17 May 2023 16:29:15 -0300 Subject: [PATCH 75/86] Fix exiting composable stable v4 so test passes --- .../pool-types/concerns/composableStable/join.concern.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.ts index 4b49ca39b..a438d06b1 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/join.concern.ts @@ -106,11 +106,12 @@ export class ComposableStablePoolJoin implements JoinConcern { * V1: Does not have proportional exits. * V2: Reintroduced proportional exits. Has vulnerability. * V3: Fixed vulnerability. Functionally the same as V2. + * V4: Update to use new create method with new salt parameter */ - if (pool.poolTypeVersion < 4) + if (pool.poolTypeVersion <= 4) return this.sortV1(wrappedNativeAsset, tokensIn, amountsIn, pool); // Not release yet and needs tests to confirm - // else if (values.pool.poolTypeVersion === 4) + // else if (values.pool.poolTypeVersion === 5) // sortedValues = this.sortV4( // values.tokensIn, // values.amountsIn, From 3dcbb265ef0f38d1df284a100e4a2c1d5e490f62 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 18 May 2023 12:25:22 -0300 Subject: [PATCH 76/86] Fix false negative check when decimals equals zero --- .../pool-types/concerns/composableStable/exit.concern.ts | 4 ++-- .../modules/pools/pool-types/concerns/linear/exit.concern.ts | 2 +- .../modules/pools/pool-types/concerns/stable/exit.concern.ts | 4 ++-- .../modules/pools/pool-types/concerns/stable/join.concern.ts | 2 +- .../pools/pool-types/concerns/weighted/exit.concern.ts | 4 ++-- .../pools/pool-types/concerns/weighted/join.concern.ts | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/exit.concern.ts index c7e9d1b52..b73a1eba4 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/exit.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/exit.concern.ts @@ -304,7 +304,7 @@ export class ComposableStablePoolExit implements ExitConcern { ); // Check if there's any relevant stable pool info missing - if (pool.tokens.some((token) => !token.decimals)) + if (pool.tokens.some((token) => token.decimals === undefined)) throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); if (!pool.amp) throw new BalancerError(BalancerErrorCode.MISSING_AMP); }; @@ -346,7 +346,7 @@ export class ComposableStablePoolExit implements ExitConcern { } // Check if there's any relevant stable pool info missing - if (pool.tokens.some((token) => !token.decimals)) + if (pool.tokens.some((token) => token.decimals === undefined)) throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); if (!pool.amp) throw new BalancerError(BalancerErrorCode.MISSING_AMP); }; diff --git a/balancer-js/src/modules/pools/pool-types/concerns/linear/exit.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/linear/exit.concern.ts index fab93215f..6b0946a6c 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/linear/exit.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/linear/exit.concern.ts @@ -144,7 +144,7 @@ export class LinearPoolExit implements ExitConcern { } // Check if there's any relevant stable pool info missing - if (pool.tokens.some((token) => !token.decimals)) + if (pool.tokens.some((token) => token.decimals === undefined)) throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); }; 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 bfb54ca3a..7681b0eda 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 @@ -278,7 +278,7 @@ export class StablePoolExit implements ExitConcern { ); // Check if there's any relevant stable pool info missing - if (pool.tokens.some((token) => !token.decimals)) + if (pool.tokens.some((token) => token.decimals === undefined)) throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); if (!pool.amp) throw new BalancerError(BalancerErrorCode.MISSING_AMP); }; @@ -300,7 +300,7 @@ 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)) + if (pool.tokens.some((token) => token.decimals === undefined)) throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); if (!pool.amp) throw new BalancerError(BalancerErrorCode.MISSING_AMP); }; diff --git a/balancer-js/src/modules/pools/pool-types/concerns/stable/join.concern.ts b/balancer-js/src/modules/pools/pool-types/concerns/stable/join.concern.ts index fa930b2ad..1d7848ce7 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/stable/join.concern.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/stable/join.concern.ts @@ -98,7 +98,7 @@ export class StablePoolJoin implements JoinConcern { } // Check if there's any relevant stable pool info missing - if (pool.tokens.some((token) => !token.decimals)) + if (pool.tokens.some((token) => token.decimals === undefined)) throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); if (!pool.amp) throw new BalancerError(BalancerErrorCode.MISSING_AMP); }; 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 62cae623e..8d76b66c2 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 @@ -278,7 +278,7 @@ export class WeightedPoolExit implements ExitConcern { ); // Check if there's any relevant weighted pool info missing - if (pool.tokens.some((token) => !token.decimals)) + if (pool.tokens.some((token) => token.decimals === undefined)) throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); }; /** @@ -299,7 +299,7 @@ export class WeightedPoolExit implements ExitConcern { throw new BalancerError(BalancerErrorCode.INPUT_LENGTH_MISMATCH); } // Check if there's any important weighted pool info missing - if (pool.tokens.some((token) => !token.decimals)) + if (pool.tokens.some((token) => token.decimals === undefined)) throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); }; sortValuesExitExactBptIn = ({ 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 a521f2778..0c9346789 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 @@ -91,7 +91,7 @@ export class WeightedPoolJoin implements JoinConcern { } // Check if there's any relevant weighted pool info missing - if (pool.tokens.some((token) => !token.decimals)) + if (pool.tokens.some((token) => token.decimals === undefined)) throw new BalancerError(BalancerErrorCode.MISSING_DECIMALS); if (pool.tokens.some((token) => !token.weight)) throw new BalancerError(BalancerErrorCode.MISSING_WEIGHT); From 58f04fe5e38950d8f9863bac87f9f1d6d55985f6 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 18 May 2023 12:25:54 -0300 Subject: [PATCH 77/86] Add script to spin up gnosis local node --- balancer-js/hardhat.config.gnosis.ts | 12 ++++++++++++ balancer-js/package.json | 1 + 2 files changed, 13 insertions(+) create mode 100644 balancer-js/hardhat.config.gnosis.ts diff --git a/balancer-js/hardhat.config.gnosis.ts b/balancer-js/hardhat.config.gnosis.ts new file mode 100644 index 000000000..6858bde0a --- /dev/null +++ b/balancer-js/hardhat.config.gnosis.ts @@ -0,0 +1,12 @@ +import '@nomiclabs/hardhat-ethers'; + +/** + * @type import('hardhat/config').HardhatUserConfig + */ +export default { + networks: { + hardhat: { + chainId: 100, + }, + }, +}; diff --git a/balancer-js/package.json b/balancer-js/package.json index b06c7b654..c62fca48f 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -32,6 +32,7 @@ "node:goerli": "npx hardhat --tsconfig tsconfig.testing.json --config hardhat.config.goerli.ts node --fork $(. ./.env && echo $ALCHEMY_URL_GOERLI) --port 8000", "node:polygon": "npx hardhat --tsconfig tsconfig.testing.json --config hardhat.config.polygon.ts node --fork $(. ./.env && echo $ALCHEMY_URL_POLYGON) --port 8137", "node:arbitrum": "npx hardhat --tsconfig tsconfig.testing.json --config hardhat.config.arbitrum.ts node --fork $(. ./.env && echo $ALCHEMY_URL_ARBITRUM) --port 8161", + "node:gnosis": "npx hardhat --tsconfig tsconfig.testing.json --config hardhat.config.gnosis.ts node --fork $(. ./.env && echo $RPC_URL_GNOSIS) --port 8100", "typechain:generate": "npx typechain --target ethers-v5 --out-dir src/contracts './src/lib/abi/*.json'" }, "devDependencies": { From 44847755a6887da00a631e5dac6b9645a274f999 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 18 May 2023 12:27:30 -0300 Subject: [PATCH 78/86] Add some tokens to gnosis constants file --- balancer-js/src/test/lib/constants.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/balancer-js/src/test/lib/constants.ts b/balancer-js/src/test/lib/constants.ts index 326950f65..9fa4ceb2a 100644 --- a/balancer-js/src/test/lib/constants.ts +++ b/balancer-js/src/test/lib/constants.ts @@ -689,13 +689,25 @@ export const ADDRESSES = { WXDAI: { address: '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d', decimals: 18, - symbol: 'DAI', + symbol: 'WXDAI', + slot: 3, }, USDT: { address: '0x4ECaBa5870353805a9F068101A40E0f32ed605C6', decimals: 6, symbol: 'USDT', }, + WXDAI_MPS: { + id: '0x4bcf6b48906fa0f68bea1fc255869a41241d4851000200000000000000000021', + address: '0x4bcf6b48906fa0f68bea1fc255869a41241d4851', + decimals: 18, + symbol: 'WXDAI_MPS', + }, + MPS: { + address: '0xfa57aa7beed63d03aaf85ffd1753f5f6242588fb', + decimals: 0, + symbol: 'MPS', + }, }, [Network.GOERLI]: { USDC_old: { From 781287b5e7e0c525e7c639fa2cda0fd5fc5e2187 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 18 May 2023 12:42:47 -0300 Subject: [PATCH 79/86] Fix lint issue --- balancer-js/examples/join.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/balancer-js/examples/join.ts b/balancer-js/examples/join.ts index 9c2c3cfc4..2b21bb883 100644 --- a/balancer-js/examples/join.ts +++ b/balancer-js/examples/join.ts @@ -86,6 +86,7 @@ async function join() { console.log('Balances before exit: ', tokenBalancesBefore); console.log('Balances after exit: ', tokenBalancesAfter); console.log('Min BPT expected after exit: ', [minBPTOut.toString()]); + console.log('Price impact: ', priceImpact.toString()); } // yarn examples:run ./examples/join.ts From 4e0ef4ef42f2569526adbc4d50abefa13005b02f Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Thu, 18 May 2023 15:16:27 -0300 Subject: [PATCH 80/86] Fix toInternalBalance on createExitPoolProportional --- balancer-js/src/modules/exits/exits.module.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/balancer-js/src/modules/exits/exits.module.ts b/balancer-js/src/modules/exits/exits.module.ts index 41e932c65..207c56202 100644 --- a/balancer-js/src/modules/exits/exits.module.ts +++ b/balancer-js/src/modules/exits/exits.module.ts @@ -1045,6 +1045,10 @@ export class Exit { kind = 3; } + const allChildrenReceiveFromInternal = node.children.every((child) => + this.receivesFromInternal(child) + ); + const call = Relayer.formatExitPoolInput({ poolId: node.id, poolKind: kind, @@ -1055,7 +1059,7 @@ export class Exit { assets: sortedTokens, minAmountsOut: sortedAmounts, userData, - toInternalBalance: false, + toInternalBalance: allChildrenReceiveFromInternal, }); debugLog('\nExitProportional:'); debugLog(JSON.stringify(call)); From 545688864e92648218ba29c3a7510fb543d31b08 Mon Sep 17 00:00:00 2001 From: johngrantuk <4797222+johngrantuk@users.noreply.github.com> Date: Thu, 18 May 2023 20:34:15 +0000 Subject: [PATCH 81/86] chore: version bump v1.0.6-beta.6 --- 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 c62fca48f..19843b830 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -1,6 +1,6 @@ { "name": "@balancer-labs/sdk", - "version": "1.0.6-beta.5", + "version": "1.0.6-beta.6", "description": "JavaScript SDK for interacting with the Balancer Protocol V2", "license": "GPL-3.0-only", "homepage": "https://github.com/balancer-labs/balancer-sdk#readme", From 1d9bb1b6285065a4be2ccbc4d5df33cc75d40c74 Mon Sep 17 00:00:00 2001 From: johngrantuk <4797222+johngrantuk@users.noreply.github.com> Date: Fri, 19 May 2023 04:06:07 +0000 Subject: [PATCH 82/86] chore: version bump v1.0.6-beta.7 --- 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 19843b830..296a0edad 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -1,6 +1,6 @@ { "name": "@balancer-labs/sdk", - "version": "1.0.6-beta.6", + "version": "1.0.6-beta.7", "description": "JavaScript SDK for interacting with the Balancer Protocol V2", "license": "GPL-3.0-only", "homepage": "https://github.com/balancer-labs/balancer-sdk#readme", From 229a2396a5e2ae08066178377d4ae9efd09c0468 Mon Sep 17 00:00:00 2001 From: johngrantuk <4797222+johngrantuk@users.noreply.github.com> Date: Fri, 19 May 2023 13:28:26 +0000 Subject: [PATCH 83/86] chore: version bump v1.0.6-beta.8 --- 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 296a0edad..52c4caac5 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -1,6 +1,6 @@ { "name": "@balancer-labs/sdk", - "version": "1.0.6-beta.7", + "version": "1.0.6-beta.8", "description": "JavaScript SDK for interacting with the Balancer Protocol V2", "license": "GPL-3.0-only", "homepage": "https://github.com/balancer-labs/balancer-sdk#readme", From 5b781c63403bee05c31d4848f8d09169a9fae746 Mon Sep 17 00:00:00 2001 From: johngrantuk <4797222+johngrantuk@users.noreply.github.com> Date: Fri, 19 May 2023 13:32:00 +0000 Subject: [PATCH 84/86] chore: version bump v1.0.6-beta.9 --- 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 52c4caac5..f8b5e56e0 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -1,6 +1,6 @@ { "name": "@balancer-labs/sdk", - "version": "1.0.6-beta.8", + "version": "1.0.6-beta.9", "description": "JavaScript SDK for interacting with the Balancer Protocol V2", "license": "GPL-3.0-only", "homepage": "https://github.com/balancer-labs/balancer-sdk#readme", From 0e1243f1e0f4d8af4ac88a4be3b38c9f0ee8e46b Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 22 May 2023 09:47:16 +0100 Subject: [PATCH 85/86] Update to version 1.1.0. --- 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 f8b5e56e0..d61e9311d 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -1,6 +1,6 @@ { "name": "@balancer-labs/sdk", - "version": "1.0.6-beta.9", + "version": "1.1.0", "description": "JavaScript SDK for interacting with the Balancer Protocol V2", "license": "GPL-3.0-only", "homepage": "https://github.com/balancer-labs/balancer-sdk#readme", From e131c9da78cdac5ac4c28a9145422ecd96ef5e76 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 22 May 2023 15:53:33 +0100 Subject: [PATCH 86/86] Update tests. --- .../fx/liquidity.concern.integration.spec.ts | 2 +- .../liquidity.concern.integration.spec.ts | 4 +-- .../modules/pools/pools.integration.spec.ts | 33 ++++++++++++++++--- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts index b3f8026d8..84d7b91ca 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts @@ -21,7 +21,7 @@ const signer = provider.getSigner(); const testPoolId = '0x726e324c29a1e49309672b244bdc4ff62a270407000200000000000000000702'; let pool: PoolWithMethods; -const blockNumber = 42505555; +const blockNumber = 43015527; describe('FX Pool - Calculate Liquidity', () => { const sdkConfig = { diff --git a/balancer-js/src/modules/pools/pool-types/concerns/gyro/liquidity.concern.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/gyro/liquidity.concern.integration.spec.ts index 5660d1979..b74ea53b5 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/gyro/liquidity.concern.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/gyro/liquidity.concern.integration.spec.ts @@ -1,4 +1,4 @@ -// yarn test:only ./src/modules/pools/pool-types/concerns/fx/liquidity.concern.integration.spec.ts +// yarn test:only ./src/modules/pools/pool-types/concerns/gyro/liquidity.concern.integration.spec.ts import dotenv from 'dotenv'; import { Network, PoolWithMethods } from '@/types'; import { forkSetup, TestPoolHelper } from '@/test/lib/utils'; @@ -16,7 +16,7 @@ const rpcUrlLocal = 'http://127.0.0.1:8137'; const provider = new ethers.providers.JsonRpcProvider(rpcUrlLocal, network); const signer = provider.getSigner(); -const blockNumber = 42505555; +const blockNumber = 43015527; describe('Gyro Pools - Calculate Liquidity', () => { const sdkConfig = { diff --git a/balancer-js/src/modules/pools/pools.integration.spec.ts b/balancer-js/src/modules/pools/pools.integration.spec.ts index 8820f7d5a..cc89c7c56 100644 --- a/balancer-js/src/modules/pools/pools.integration.spec.ts +++ b/balancer-js/src/modules/pools/pools.integration.spec.ts @@ -1,5 +1,13 @@ import { expect } from 'chai'; -import { BalancerSDK, Network, Pool, PoolWithMethods, Pools } from '@/.'; +import { + BalancerSDK, + Network, + Pool, + PoolWithMethods, + Pools, + GraphQLQuery, + GraphQLArgs, +} from '@/.'; import { AddressZero, Zero } from '@ethersproject/constants'; import { bn } from '@/lib/utils'; import { poolFactory } from '@/test/factories/sdk'; @@ -7,12 +15,27 @@ import { BALANCER_NETWORK_CONFIG } from '@/lib/constants/config'; const rpcUrl = 'http://127.0.0.1:8545'; const network = Network.MAINNET; -const sdk = new BalancerSDK({ network, rpcUrl }); -const { pools, contracts } = sdk; -const { balancerHelpers } = contracts; - const ethStEth = '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080'; +const subgraphArgs: GraphQLArgs = { + where: { + swapEnabled: { + eq: true, + }, + totalShares: { + gt: 0.000000000001, + }, + address: { + in: [ethStEth], + }, + }, + orderBy: 'totalLiquidity', + orderDirection: 'desc', +}; +const subgraphQuery: GraphQLQuery = { args: subgraphArgs, attrs: {} }; +const sdk = new BalancerSDK({ network, rpcUrl, subgraphQuery }); +const { pools, contracts } = sdk; +const { balancerHelpers } = contracts; describe('pools module', () => { describe('methods', () => {