From d354ed96daf142f01863d7843d377d7f4826748e Mon Sep 17 00:00:00 2001 From: Jean-Grimal <83286814+Jean-Grimal@users.noreply.github.com> Date: Fri, 7 Feb 2025 16:21:57 +0100 Subject: [PATCH 1/8] feat: new reallocation algorithm --- packages/bundler-sdk-viem/src/operations.ts | 27 +++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/bundler-sdk-viem/src/operations.ts b/packages/bundler-sdk-viem/src/operations.ts index afabca4c..08c28425 100644 --- a/packages/bundler-sdk-viem/src/operations.ts +++ b/packages/bundler-sdk-viem/src/operations.ts @@ -326,18 +326,41 @@ export const populateSubBundle = ( ) { // Liquidity is insufficient: trigger a public reallocation and try to have a resulting utilization as low as possible, above the target. // Solve: newTotalBorrowAssets / (newTotalSupplyAssets + reallocatedAssets) = supplyTargetUtilization - // Worst case is: there is not enough withdrawals available to fill reallocatedAssets, so utilization is above supplyTargetUtilization. + // We first try to find public reallocations that respect every markets targets. + // If this is not enough, the first market to be pushed above target is the supply market. Then we fully withdraw from every market. let requiredAssets = supplyTargetUtilization === 0n ? MathLib.MAX_UINT_160 : MathLib.wDivDown(newTotalBorrowAssets, supplyTargetUtilization) - newTotalSupplyAssets; - const { withdrawals } = data.getMarketPublicReallocations( + let { withdrawals } = data.getMarketPublicReallocations( market.id, publicAllocatorOptions, ); + const asssetsWithdrawn = withdrawals.reduce( + (total, { assets }) => total + assets, + 0n, + ); + + if ( + MarketUtils.getUtilization({ + totalSupplyAssets: newTotalSupplyAssets + asssetsWithdrawn, + totalBorrowAssets: newTotalBorrowAssets, + }) > MathLib.WAD + ) { + // If the target friendly reallocation is not enough, we fully withdraw from every market. + + requiredAssets = + MathLib.wDivDown(newTotalBorrowAssets, MathLib.WAD) - + newTotalSupplyAssets; + + withdrawals = data.getMarketPublicReallocations(market.id, { + defaultMaxWithdrawalUtilization: MathLib.WAD, + }).withdrawals; + } + for (const { vault, ...withdrawal } of withdrawals) { const vaultReallocations = (reallocations[vault] ??= []); From 73db6c76d2332f8aafe30dca18729f9194ff7f11 Mon Sep 17 00:00:00 2001 From: Jean-Grimal <83286814+Jean-Grimal@users.noreply.github.com> Date: Fri, 7 Feb 2025 17:11:37 +0100 Subject: [PATCH 2/8] Update packages/bundler-sdk-viem/src/operations.ts Co-authored-by: Romain Milon Signed-off-by: Jean-Grimal <83286814+Jean-Grimal@users.noreply.github.com> --- packages/bundler-sdk-viem/src/operations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bundler-sdk-viem/src/operations.ts b/packages/bundler-sdk-viem/src/operations.ts index 08c28425..efc92a05 100644 --- a/packages/bundler-sdk-viem/src/operations.ts +++ b/packages/bundler-sdk-viem/src/operations.ts @@ -350,7 +350,7 @@ export const populateSubBundle = ( totalBorrowAssets: newTotalBorrowAssets, }) > MathLib.WAD ) { - // If the target friendly reallocation is not enough, we fully withdraw from every market. + // If the "friendly" reallocations are not enough, we fully withdraw from every market. requiredAssets = MathLib.wDivDown(newTotalBorrowAssets, MathLib.WAD) - From 85aacfa32d234025a7dd6cf2b87be3129ced61fc Mon Sep 17 00:00:00 2001 From: Jean-Grimal <83286814+Jean-Grimal@users.noreply.github.com> Date: Fri, 7 Feb 2025 17:13:33 +0100 Subject: [PATCH 3/8] Update packages/bundler-sdk-viem/src/operations.ts Co-authored-by: Romain Milon Signed-off-by: Jean-Grimal <83286814+Jean-Grimal@users.noreply.github.com> --- packages/bundler-sdk-viem/src/operations.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/bundler-sdk-viem/src/operations.ts b/packages/bundler-sdk-viem/src/operations.ts index efc92a05..23fe686d 100644 --- a/packages/bundler-sdk-viem/src/operations.ts +++ b/packages/bundler-sdk-viem/src/operations.ts @@ -356,9 +356,11 @@ export const populateSubBundle = ( MathLib.wDivDown(newTotalBorrowAssets, MathLib.WAD) - newTotalSupplyAssets; - withdrawals = data.getMarketPublicReallocations(market.id, { + ({ withdrawals } = data.getMarketPublicReallocations(market.id, { + ...publicAllocatorOptions, defaultMaxWithdrawalUtilization: MathLib.WAD, - }).withdrawals; + maxWithdrawalUtilization: {}, + })); } for (const { vault, ...withdrawal } of withdrawals) { From 80655150c85214605d8a426b958534e1934576e2 Mon Sep 17 00:00:00 2001 From: Jean-Grimal <83286814+Jean-Grimal@users.noreply.github.com> Date: Fri, 7 Feb 2025 17:21:31 +0100 Subject: [PATCH 4/8] fix: apply suggestions --- packages/bundler-sdk-viem/src/operations.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/bundler-sdk-viem/src/operations.ts b/packages/bundler-sdk-viem/src/operations.ts index 23fe686d..c81bfbc0 100644 --- a/packages/bundler-sdk-viem/src/operations.ts +++ b/packages/bundler-sdk-viem/src/operations.ts @@ -339,22 +339,15 @@ export const populateSubBundle = ( publicAllocatorOptions, ); - const asssetsWithdrawn = withdrawals.reduce( + const reallocatedAssets = withdrawals.reduce( (total, { assets }) => total + assets, 0n, ); - if ( - MarketUtils.getUtilization({ - totalSupplyAssets: newTotalSupplyAssets + asssetsWithdrawn, - totalBorrowAssets: newTotalBorrowAssets, - }) > MathLib.WAD - ) { + if (newTotalSupplyAssets + reallocatedAssets < newTotalBorrowAssets) { // If the "friendly" reallocations are not enough, we fully withdraw from every market. - requiredAssets = - MathLib.wDivDown(newTotalBorrowAssets, MathLib.WAD) - - newTotalSupplyAssets; + requiredAssets = newTotalBorrowAssets - newTotalSupplyAssets; ({ withdrawals } = data.getMarketPublicReallocations(market.id, { ...publicAllocatorOptions, From a5e03f1c2172a557884988a4efdb45c4effb08f6 Mon Sep 17 00:00:00 2001 From: Jean-Grimal <83286814+Jean-Grimal@users.noreply.github.com> Date: Tue, 11 Feb 2025 17:53:29 +0100 Subject: [PATCH 5/8] fix: apply suggestions --- packages/bundler-sdk-viem/src/operations.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/bundler-sdk-viem/src/operations.ts b/packages/bundler-sdk-viem/src/operations.ts index c81bfbc0..475223c9 100644 --- a/packages/bundler-sdk-viem/src/operations.ts +++ b/packages/bundler-sdk-viem/src/operations.ts @@ -334,17 +334,13 @@ export const populateSubBundle = ( : MathLib.wDivDown(newTotalBorrowAssets, supplyTargetUtilization) - newTotalSupplyAssets; - let { withdrawals } = data.getMarketPublicReallocations( - market.id, - publicAllocatorOptions, - ); - - const reallocatedAssets = withdrawals.reduce( - (total, { assets }) => total + assets, - 0n, - ); + let { withdrawals, data: simulationState } = + data.getMarketPublicReallocations(market.id, publicAllocatorOptions); - if (newTotalSupplyAssets + reallocatedAssets < newTotalBorrowAssets) { + if ( + simulationState.getMarket(market.id).totalBorrowAssets > + simulationState.getMarket(market.id).totalSupplyAssets + ) { // If the "friendly" reallocations are not enough, we fully withdraw from every market. requiredAssets = newTotalBorrowAssets - newTotalSupplyAssets; From d771213e986787e121f3808a453fbe0274b7afb4 Mon Sep 17 00:00:00 2001 From: Jean-Grimal <83286814+Jean-Grimal@users.noreply.github.com> Date: Wed, 12 Feb 2025 16:12:46 +0100 Subject: [PATCH 6/8] fix: full reallocation condition and add tests --- packages/bundler-sdk-viem/src/operations.ts | 9 +- .../test/populateBundle.test.ts | 759 ++++++++++++++++++ 2 files changed, 765 insertions(+), 3 deletions(-) diff --git a/packages/bundler-sdk-viem/src/operations.ts b/packages/bundler-sdk-viem/src/operations.ts index 475223c9..00c91334 100644 --- a/packages/bundler-sdk-viem/src/operations.ts +++ b/packages/bundler-sdk-viem/src/operations.ts @@ -334,12 +334,15 @@ export const populateSubBundle = ( : MathLib.wDivDown(newTotalBorrowAssets, supplyTargetUtilization) - newTotalSupplyAssets; - let { withdrawals, data: simulationState } = + let { withdrawals, data: simulationStatePostFriendlyReallocation } = data.getMarketPublicReallocations(market.id, publicAllocatorOptions); + const marketPostFriendlyReallocation = + simulationStatePostFriendlyReallocation.getMarket(market.id); + if ( - simulationState.getMarket(market.id).totalBorrowAssets > - simulationState.getMarket(market.id).totalSupplyAssets + marketPostFriendlyReallocation.totalBorrowAssets + borrowedAssets > + marketPostFriendlyReallocation.totalSupplyAssets - withdrawnAssets ) { // If the "friendly" reallocations are not enough, we fully withdraw from every market. diff --git a/packages/bundler-sdk-viem/test/populateBundle.test.ts b/packages/bundler-sdk-viem/test/populateBundle.test.ts index ff8e7343..bfbe35eb 100644 --- a/packages/bundler-sdk-viem/test/populateBundle.test.ts +++ b/packages/bundler-sdk-viem/test/populateBundle.test.ts @@ -5047,6 +5047,765 @@ describe("populateBundle", () => { ); // we normally didn't experienced any slippage }, ); + + test[ChainId.EthMainnet]( + "should borrow USDC without shared liquidity", + async ({ client, config }) => { + const steakUsdcOwner = await client.readContract({ + address: steakUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: steakUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: steakUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + steakUsdc.address, + [ + { + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("10000", 6), + maxOut: 0n, + }, + }, + { + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("20000", 6), // Less than bbUsdc but more than maxIn. + }, + }, + ], + ], + }); + + const bbUsdcOwner = await client.readContract({ + address: bbUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: bbUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: bbUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + bbUsdc.address, + [ + { + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("1000000", 6), + maxOut: 0n, + }, + }, + { + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("1000000", 6), + }, + }, + ], + ], + }); + + const collateralAssets = parseEther("50000"); + const depositAssets = parseEther("50"); + await client.deal({ erc20: wstEth, amount: collateralAssets }); + await client.deal({ erc20: wNative, amount: depositAssets }); + + const { id } = usdc_wstEth; + + const block = await client.getBlock(); + + const { result } = await renderHook(config, () => + useSimulationState({ + marketIds: [ + eth_idle.id, + eth_rEth.id, + eth_sDai.id, + eth_wbtc.id, + eth_wstEth.id, + eth_wstEth_2.id, + id, + usdc_idle.id, + usdc_wbtc.id, + usdc_wbIB01.id, + ], + users: [ + client.account.address, + donator.address, + bundler, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + tokens: [ + NATIVE_ADDRESS, + wNative, + usdc, + stEth, + wstEth, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + vaults: [steakUsdc.address, bbEth.address, bbUsdc.address], + block, + }), + ); + + await waitFor( + () => expect(result.current.isFetchingAny).toBeFalsy(), + { timeout: 30_000 }, + ); + + const data = result.current.data!; + + const target = parseEther("0.92"); + + const borrowed = MathLib.wMulDown( + data.getMarket(id).totalSupplyAssets, + target - data.getMarket(id).utilization, + ); + + const { operations } = await setupBundle( + client, + data, + [ + { + type: "Blue_SupplyCollateral", + sender: client.account.address, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, + }, + }, + { + type: "Blue_Borrow", + sender: client.account.address, + address: morpho, + args: { + id, + assets: borrowed, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ], + { + publicAllocatorOptions: { + enabled: true, + reallocatableVaults: [bbUsdc.address], + maxWithdrawalUtilization: { + [usdc_wbtc.id]: parseEther("0.95"), + }, + supplyTargetUtilization: { + [id]: target, + }, + }, + }, + ); + + expect(operations).toStrictEqual([ + { + type: "Erc20_Permit", + sender: client.account.address, + address: wstEth, + args: { + amount: collateralAssets, + spender: bundler, + nonce: 0n, + }, + }, + { + type: "Erc20_Transfer", + sender: bundler, + address: wstEth, + args: { + amount: collateralAssets, + from: client.account.address, + to: bundler, + }, + }, + { + type: "Blue_SupplyCollateral", + sender: bundler, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, + }, + }, + { + type: "Blue_SetAuthorization", + sender: bundler, + address: morpho, + args: { + owner: client.account.address, + isBundlerAuthorized: true, + }, + }, + { + type: "Blue_Borrow", + sender: bundler, + address: morpho, + args: { + id, + assets: borrowed, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ]); + }, + ); + + test[ChainId.EthMainnet]( + "should borrow USDC with shared liquidity and friendly reallocation", + async ({ client, config }) => { + const steakUsdcOwner = await client.readContract({ + address: steakUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: steakUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: steakUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + steakUsdc.address, + [ + { + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("10000", 6), + maxOut: 0n, + }, + }, + { + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("20000", 6), // Less than bbUsdc but more than maxIn. + }, + }, + ], + ], + }); + + const bbUsdcOwner = await client.readContract({ + address: bbUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: bbUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: bbUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + bbUsdc.address, + [ + { + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("1000000", 6), + maxOut: 0n, + }, + }, + { + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("1000000", 6), + }, + }, + ], + ], + }); + + const collateralAssets = parseEther("50000"); + const depositAssets = parseEther("50"); + await client.deal({ erc20: wstEth, amount: collateralAssets }); + await client.deal({ erc20: wNative, amount: depositAssets }); + + const { id } = usdc_wstEth; + + const block = await client.getBlock(); + + const { result } = await renderHook(config, () => + useSimulationState({ + marketIds: [ + eth_idle.id, + eth_rEth.id, + eth_sDai.id, + eth_wbtc.id, + eth_wstEth.id, + eth_wstEth_2.id, + id, + usdc_idle.id, + usdc_wbtc.id, + usdc_wbIB01.id, + ], + users: [ + client.account.address, + donator.address, + bundler, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + tokens: [ + NATIVE_ADDRESS, + wNative, + usdc, + stEth, + wstEth, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + vaults: [steakUsdc.address, bbEth.address, bbUsdc.address], + block, + }), + ); + + await waitFor( + () => expect(result.current.isFetchingAny).toBeFalsy(), + { timeout: 30_000 }, + ); + + const data = result.current.data!; + + const target = parseEther("0.92"); + + const amountForWbtcUsdctToReachTarget = + MathLib.wDivDown( + data.getMarket(usdc_wbtc.id).totalBorrowAssets, + data.getMarket(usdc_wbtc.id).utilization, + ) - + MathLib.wDivDown( + data.getMarket(usdc_wbtc.id).totalBorrowAssets, + target, + ) - + 1n; // -1n because of the rounding on withdrawals + + const amountForWstEthUsdcToReach100Utilization = MathLib.wMulDown( + data.getMarket(id).totalSupplyAssets, + MathLib.WAD - data.getMarket(id).utilization, + ); + + const maxFriendlyReallocationAmount = + amountForWbtcUsdctToReachTarget + + amountForWstEthUsdcToReach100Utilization; + + const { operations } = await setupBundle( + client, + data, + [ + { + type: "Blue_SupplyCollateral", + sender: client.account.address, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, + }, + }, + { + type: "Blue_Borrow", + sender: client.account.address, + address: morpho, + args: { + id, + assets: maxFriendlyReallocationAmount, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ], + { + publicAllocatorOptions: { + enabled: true, + reallocatableVaults: [bbUsdc.address], + maxWithdrawalUtilization: { + [usdc_wbtc.id]: target, + }, + supplyTargetUtilization: { + [id]: target, + }, + }, + }, + ); + + expect(operations).toStrictEqual([ + { + type: "Erc20_Permit", + sender: client.account.address, + address: wstEth, + args: { + amount: collateralAssets, + spender: bundler, + nonce: 0n, + }, + }, + { + type: "Erc20_Transfer", + sender: bundler, + address: wstEth, + args: { + amount: collateralAssets, + from: client.account.address, + to: bundler, + }, + }, + { + type: "Blue_SupplyCollateral", + sender: bundler, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, + }, + }, + { + type: "Blue_SetAuthorization", + sender: bundler, + address: morpho, + args: { + owner: client.account.address, + isBundlerAuthorized: true, + }, + }, + { + type: "MetaMorpho_PublicReallocate", + sender: bundler, + address: bbUsdc.address, + args: { + withdrawals: [ + { + id: usdc_wbtc.id, + assets: amountForWbtcUsdctToReachTarget, + }, + ], + supplyMarketId: id, + }, + }, + { + type: "Blue_Borrow", + sender: bundler, + address: morpho, + args: { + id, + assets: maxFriendlyReallocationAmount, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ]); + }, + ); + + test[ChainId.EthMainnet]( + "should borrow USDC with shared liquidity and full reallocation", + async ({ client, config }) => { + const steakUsdcOwner = await client.readContract({ + address: steakUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: steakUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: steakUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + steakUsdc.address, + [ + { + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("10000", 6), + maxOut: 0n, + }, + }, + { + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("20000", 6), // Less than bbUsdc but more than maxIn. + }, + }, + ], + ], + }); + + const bbUsdcOwner = await client.readContract({ + address: bbUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: bbUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: bbUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + bbUsdc.address, + [ + { + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("1000000", 6), + maxOut: 0n, + }, + }, + { + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("1000000", 6), + }, + }, + ], + ], + }); + + const collateralAssets = parseEther("50000"); + const depositAssets = parseEther("50"); + await client.deal({ erc20: wstEth, amount: collateralAssets }); + await client.deal({ erc20: wNative, amount: depositAssets }); + + const { id } = usdc_wstEth; + + const block = await client.getBlock(); + + const { result } = await renderHook(config, () => + useSimulationState({ + marketIds: [ + eth_idle.id, + eth_rEth.id, + eth_sDai.id, + eth_wbtc.id, + eth_wstEth.id, + eth_wstEth_2.id, + id, + usdc_idle.id, + usdc_wbtc.id, + usdc_wbIB01.id, + ], + users: [ + client.account.address, + donator.address, + bundler, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + tokens: [ + NATIVE_ADDRESS, + wNative, + usdc, + stEth, + wstEth, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + vaults: [steakUsdc.address, bbEth.address, bbUsdc.address], + block, + }), + ); + + await waitFor( + () => expect(result.current.isFetchingAny).toBeFalsy(), + { timeout: 30_000 }, + ); + + const data = result.current.data!; + + const target = parseEther("0.92"); + + const amountForWbtcUsdctToReachTarget = + MathLib.wDivDown( + data.getMarket(usdc_wbtc.id).totalBorrowAssets, + data.getMarket(usdc_wbtc.id).utilization, + ) - + MathLib.wDivDown( + data.getMarket(usdc_wbtc.id).totalBorrowAssets, + target, + ); + + const additionnalReallocationAmount = parseUnits("10000", 6); + + const amountForWstEthUsdcToReach100Utilization = MathLib.wMulDown( + data.getMarket(id).totalSupplyAssets, + MathLib.WAD - data.getMarket(id).utilization, + ); + + const borrowed = + amountForWbtcUsdctToReachTarget + + amountForWstEthUsdcToReach100Utilization + + additionnalReallocationAmount; + + const withdrawnAssets = + amountForWbtcUsdctToReachTarget + additionnalReallocationAmount; + + const { operations } = await setupBundle( + client, + data, + [ + { + type: "Blue_SupplyCollateral", + sender: client.account.address, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, + }, + }, + { + type: "Blue_Borrow", + sender: client.account.address, + address: morpho, + args: { + id, + assets: borrowed, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ], + { + publicAllocatorOptions: { + enabled: true, + reallocatableVaults: [bbUsdc.address], + maxWithdrawalUtilization: { + [usdc_wbtc.id]: target, + }, + supplyTargetUtilization: { + [id]: target, + }, + }, + }, + ); + + expect(operations).toStrictEqual([ + { + type: "Erc20_Permit", + sender: client.account.address, + address: wstEth, + args: { + amount: collateralAssets, + spender: bundler, + nonce: 0n, + }, + }, + { + type: "Erc20_Transfer", + sender: bundler, + address: wstEth, + args: { + amount: collateralAssets, + from: client.account.address, + to: bundler, + }, + }, + { + type: "Blue_SupplyCollateral", + sender: bundler, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, + }, + }, + { + type: "Blue_SetAuthorization", + sender: bundler, + address: morpho, + args: { + owner: client.account.address, + isBundlerAuthorized: true, + }, + }, + { + type: "MetaMorpho_PublicReallocate", + sender: bundler, + address: bbUsdc.address, + args: { + withdrawals: [ + { + id: usdc_wbtc.id, + assets: withdrawnAssets, + }, + ], + supplyMarketId: id, + }, + }, + { + type: "Blue_Borrow", + sender: bundler, + address: morpho, + args: { + id, + assets: borrowed, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ]); + }, + ); }); describe("base", () => { From cb076c0e1a7d7c01a3c770ed50a136fbe4928352 Mon Sep 17 00:00:00 2001 From: Jean-Grimal <83286814+Jean-Grimal@users.noreply.github.com> Date: Thu, 13 Feb 2025 10:52:04 +0100 Subject: [PATCH 7/8] test: seperate files --- .../test/populateBundle.test.ts | 759 ----------------- .../test/sharedLiquidity.test.ts | 803 ++++++++++++++++++ 2 files changed, 803 insertions(+), 759 deletions(-) create mode 100644 packages/bundler-sdk-viem/test/sharedLiquidity.test.ts diff --git a/packages/bundler-sdk-viem/test/populateBundle.test.ts b/packages/bundler-sdk-viem/test/populateBundle.test.ts index bfbe35eb..ff8e7343 100644 --- a/packages/bundler-sdk-viem/test/populateBundle.test.ts +++ b/packages/bundler-sdk-viem/test/populateBundle.test.ts @@ -5047,765 +5047,6 @@ describe("populateBundle", () => { ); // we normally didn't experienced any slippage }, ); - - test[ChainId.EthMainnet]( - "should borrow USDC without shared liquidity", - async ({ client, config }) => { - const steakUsdcOwner = await client.readContract({ - address: steakUsdc.address, - abi: metaMorphoAbi, - functionName: "owner", - }); - - await client.setBalance({ - address: steakUsdcOwner, - value: parseEther("1000"), - }); - await client.writeContract({ - account: steakUsdcOwner, - address: publicAllocator, - abi: publicAllocatorAbi, - functionName: "setFlowCaps", - args: [ - steakUsdc.address, - [ - { - id: usdc_wstEth.id, - caps: { - maxIn: parseUnits("10000", 6), - maxOut: 0n, - }, - }, - { - id: usdc_wbtc.id, - caps: { - maxIn: 0n, - maxOut: parseUnits("20000", 6), // Less than bbUsdc but more than maxIn. - }, - }, - ], - ], - }); - - const bbUsdcOwner = await client.readContract({ - address: bbUsdc.address, - abi: metaMorphoAbi, - functionName: "owner", - }); - - await client.setBalance({ - address: bbUsdcOwner, - value: parseEther("1000"), - }); - await client.writeContract({ - account: bbUsdcOwner, - address: publicAllocator, - abi: publicAllocatorAbi, - functionName: "setFlowCaps", - args: [ - bbUsdc.address, - [ - { - id: usdc_wstEth.id, - caps: { - maxIn: parseUnits("1000000", 6), - maxOut: 0n, - }, - }, - { - id: usdc_wbtc.id, - caps: { - maxIn: 0n, - maxOut: parseUnits("1000000", 6), - }, - }, - ], - ], - }); - - const collateralAssets = parseEther("50000"); - const depositAssets = parseEther("50"); - await client.deal({ erc20: wstEth, amount: collateralAssets }); - await client.deal({ erc20: wNative, amount: depositAssets }); - - const { id } = usdc_wstEth; - - const block = await client.getBlock(); - - const { result } = await renderHook(config, () => - useSimulationState({ - marketIds: [ - eth_idle.id, - eth_rEth.id, - eth_sDai.id, - eth_wbtc.id, - eth_wstEth.id, - eth_wstEth_2.id, - id, - usdc_idle.id, - usdc_wbtc.id, - usdc_wbIB01.id, - ], - users: [ - client.account.address, - donator.address, - bundler, - steakUsdc.address, - bbEth.address, - bbUsdc.address, - ], - tokens: [ - NATIVE_ADDRESS, - wNative, - usdc, - stEth, - wstEth, - steakUsdc.address, - bbEth.address, - bbUsdc.address, - ], - vaults: [steakUsdc.address, bbEth.address, bbUsdc.address], - block, - }), - ); - - await waitFor( - () => expect(result.current.isFetchingAny).toBeFalsy(), - { timeout: 30_000 }, - ); - - const data = result.current.data!; - - const target = parseEther("0.92"); - - const borrowed = MathLib.wMulDown( - data.getMarket(id).totalSupplyAssets, - target - data.getMarket(id).utilization, - ); - - const { operations } = await setupBundle( - client, - data, - [ - { - type: "Blue_SupplyCollateral", - sender: client.account.address, - address: morpho, - args: { - id, - assets: collateralAssets, - onBehalf: client.account.address, - }, - }, - { - type: "Blue_Borrow", - sender: client.account.address, - address: morpho, - args: { - id, - assets: borrowed, - onBehalf: client.account.address, - receiver: client.account.address, - slippage: DEFAULT_SLIPPAGE_TOLERANCE, - }, - }, - ], - { - publicAllocatorOptions: { - enabled: true, - reallocatableVaults: [bbUsdc.address], - maxWithdrawalUtilization: { - [usdc_wbtc.id]: parseEther("0.95"), - }, - supplyTargetUtilization: { - [id]: target, - }, - }, - }, - ); - - expect(operations).toStrictEqual([ - { - type: "Erc20_Permit", - sender: client.account.address, - address: wstEth, - args: { - amount: collateralAssets, - spender: bundler, - nonce: 0n, - }, - }, - { - type: "Erc20_Transfer", - sender: bundler, - address: wstEth, - args: { - amount: collateralAssets, - from: client.account.address, - to: bundler, - }, - }, - { - type: "Blue_SupplyCollateral", - sender: bundler, - address: morpho, - args: { - id, - assets: collateralAssets, - onBehalf: client.account.address, - }, - }, - { - type: "Blue_SetAuthorization", - sender: bundler, - address: morpho, - args: { - owner: client.account.address, - isBundlerAuthorized: true, - }, - }, - { - type: "Blue_Borrow", - sender: bundler, - address: morpho, - args: { - id, - assets: borrowed, - onBehalf: client.account.address, - receiver: client.account.address, - slippage: DEFAULT_SLIPPAGE_TOLERANCE, - }, - }, - ]); - }, - ); - - test[ChainId.EthMainnet]( - "should borrow USDC with shared liquidity and friendly reallocation", - async ({ client, config }) => { - const steakUsdcOwner = await client.readContract({ - address: steakUsdc.address, - abi: metaMorphoAbi, - functionName: "owner", - }); - - await client.setBalance({ - address: steakUsdcOwner, - value: parseEther("1000"), - }); - await client.writeContract({ - account: steakUsdcOwner, - address: publicAllocator, - abi: publicAllocatorAbi, - functionName: "setFlowCaps", - args: [ - steakUsdc.address, - [ - { - id: usdc_wstEth.id, - caps: { - maxIn: parseUnits("10000", 6), - maxOut: 0n, - }, - }, - { - id: usdc_wbtc.id, - caps: { - maxIn: 0n, - maxOut: parseUnits("20000", 6), // Less than bbUsdc but more than maxIn. - }, - }, - ], - ], - }); - - const bbUsdcOwner = await client.readContract({ - address: bbUsdc.address, - abi: metaMorphoAbi, - functionName: "owner", - }); - - await client.setBalance({ - address: bbUsdcOwner, - value: parseEther("1000"), - }); - await client.writeContract({ - account: bbUsdcOwner, - address: publicAllocator, - abi: publicAllocatorAbi, - functionName: "setFlowCaps", - args: [ - bbUsdc.address, - [ - { - id: usdc_wstEth.id, - caps: { - maxIn: parseUnits("1000000", 6), - maxOut: 0n, - }, - }, - { - id: usdc_wbtc.id, - caps: { - maxIn: 0n, - maxOut: parseUnits("1000000", 6), - }, - }, - ], - ], - }); - - const collateralAssets = parseEther("50000"); - const depositAssets = parseEther("50"); - await client.deal({ erc20: wstEth, amount: collateralAssets }); - await client.deal({ erc20: wNative, amount: depositAssets }); - - const { id } = usdc_wstEth; - - const block = await client.getBlock(); - - const { result } = await renderHook(config, () => - useSimulationState({ - marketIds: [ - eth_idle.id, - eth_rEth.id, - eth_sDai.id, - eth_wbtc.id, - eth_wstEth.id, - eth_wstEth_2.id, - id, - usdc_idle.id, - usdc_wbtc.id, - usdc_wbIB01.id, - ], - users: [ - client.account.address, - donator.address, - bundler, - steakUsdc.address, - bbEth.address, - bbUsdc.address, - ], - tokens: [ - NATIVE_ADDRESS, - wNative, - usdc, - stEth, - wstEth, - steakUsdc.address, - bbEth.address, - bbUsdc.address, - ], - vaults: [steakUsdc.address, bbEth.address, bbUsdc.address], - block, - }), - ); - - await waitFor( - () => expect(result.current.isFetchingAny).toBeFalsy(), - { timeout: 30_000 }, - ); - - const data = result.current.data!; - - const target = parseEther("0.92"); - - const amountForWbtcUsdctToReachTarget = - MathLib.wDivDown( - data.getMarket(usdc_wbtc.id).totalBorrowAssets, - data.getMarket(usdc_wbtc.id).utilization, - ) - - MathLib.wDivDown( - data.getMarket(usdc_wbtc.id).totalBorrowAssets, - target, - ) - - 1n; // -1n because of the rounding on withdrawals - - const amountForWstEthUsdcToReach100Utilization = MathLib.wMulDown( - data.getMarket(id).totalSupplyAssets, - MathLib.WAD - data.getMarket(id).utilization, - ); - - const maxFriendlyReallocationAmount = - amountForWbtcUsdctToReachTarget + - amountForWstEthUsdcToReach100Utilization; - - const { operations } = await setupBundle( - client, - data, - [ - { - type: "Blue_SupplyCollateral", - sender: client.account.address, - address: morpho, - args: { - id, - assets: collateralAssets, - onBehalf: client.account.address, - }, - }, - { - type: "Blue_Borrow", - sender: client.account.address, - address: morpho, - args: { - id, - assets: maxFriendlyReallocationAmount, - onBehalf: client.account.address, - receiver: client.account.address, - slippage: DEFAULT_SLIPPAGE_TOLERANCE, - }, - }, - ], - { - publicAllocatorOptions: { - enabled: true, - reallocatableVaults: [bbUsdc.address], - maxWithdrawalUtilization: { - [usdc_wbtc.id]: target, - }, - supplyTargetUtilization: { - [id]: target, - }, - }, - }, - ); - - expect(operations).toStrictEqual([ - { - type: "Erc20_Permit", - sender: client.account.address, - address: wstEth, - args: { - amount: collateralAssets, - spender: bundler, - nonce: 0n, - }, - }, - { - type: "Erc20_Transfer", - sender: bundler, - address: wstEth, - args: { - amount: collateralAssets, - from: client.account.address, - to: bundler, - }, - }, - { - type: "Blue_SupplyCollateral", - sender: bundler, - address: morpho, - args: { - id, - assets: collateralAssets, - onBehalf: client.account.address, - }, - }, - { - type: "Blue_SetAuthorization", - sender: bundler, - address: morpho, - args: { - owner: client.account.address, - isBundlerAuthorized: true, - }, - }, - { - type: "MetaMorpho_PublicReallocate", - sender: bundler, - address: bbUsdc.address, - args: { - withdrawals: [ - { - id: usdc_wbtc.id, - assets: amountForWbtcUsdctToReachTarget, - }, - ], - supplyMarketId: id, - }, - }, - { - type: "Blue_Borrow", - sender: bundler, - address: morpho, - args: { - id, - assets: maxFriendlyReallocationAmount, - onBehalf: client.account.address, - receiver: client.account.address, - slippage: DEFAULT_SLIPPAGE_TOLERANCE, - }, - }, - ]); - }, - ); - - test[ChainId.EthMainnet]( - "should borrow USDC with shared liquidity and full reallocation", - async ({ client, config }) => { - const steakUsdcOwner = await client.readContract({ - address: steakUsdc.address, - abi: metaMorphoAbi, - functionName: "owner", - }); - - await client.setBalance({ - address: steakUsdcOwner, - value: parseEther("1000"), - }); - await client.writeContract({ - account: steakUsdcOwner, - address: publicAllocator, - abi: publicAllocatorAbi, - functionName: "setFlowCaps", - args: [ - steakUsdc.address, - [ - { - id: usdc_wstEth.id, - caps: { - maxIn: parseUnits("10000", 6), - maxOut: 0n, - }, - }, - { - id: usdc_wbtc.id, - caps: { - maxIn: 0n, - maxOut: parseUnits("20000", 6), // Less than bbUsdc but more than maxIn. - }, - }, - ], - ], - }); - - const bbUsdcOwner = await client.readContract({ - address: bbUsdc.address, - abi: metaMorphoAbi, - functionName: "owner", - }); - - await client.setBalance({ - address: bbUsdcOwner, - value: parseEther("1000"), - }); - await client.writeContract({ - account: bbUsdcOwner, - address: publicAllocator, - abi: publicAllocatorAbi, - functionName: "setFlowCaps", - args: [ - bbUsdc.address, - [ - { - id: usdc_wstEth.id, - caps: { - maxIn: parseUnits("1000000", 6), - maxOut: 0n, - }, - }, - { - id: usdc_wbtc.id, - caps: { - maxIn: 0n, - maxOut: parseUnits("1000000", 6), - }, - }, - ], - ], - }); - - const collateralAssets = parseEther("50000"); - const depositAssets = parseEther("50"); - await client.deal({ erc20: wstEth, amount: collateralAssets }); - await client.deal({ erc20: wNative, amount: depositAssets }); - - const { id } = usdc_wstEth; - - const block = await client.getBlock(); - - const { result } = await renderHook(config, () => - useSimulationState({ - marketIds: [ - eth_idle.id, - eth_rEth.id, - eth_sDai.id, - eth_wbtc.id, - eth_wstEth.id, - eth_wstEth_2.id, - id, - usdc_idle.id, - usdc_wbtc.id, - usdc_wbIB01.id, - ], - users: [ - client.account.address, - donator.address, - bundler, - steakUsdc.address, - bbEth.address, - bbUsdc.address, - ], - tokens: [ - NATIVE_ADDRESS, - wNative, - usdc, - stEth, - wstEth, - steakUsdc.address, - bbEth.address, - bbUsdc.address, - ], - vaults: [steakUsdc.address, bbEth.address, bbUsdc.address], - block, - }), - ); - - await waitFor( - () => expect(result.current.isFetchingAny).toBeFalsy(), - { timeout: 30_000 }, - ); - - const data = result.current.data!; - - const target = parseEther("0.92"); - - const amountForWbtcUsdctToReachTarget = - MathLib.wDivDown( - data.getMarket(usdc_wbtc.id).totalBorrowAssets, - data.getMarket(usdc_wbtc.id).utilization, - ) - - MathLib.wDivDown( - data.getMarket(usdc_wbtc.id).totalBorrowAssets, - target, - ); - - const additionnalReallocationAmount = parseUnits("10000", 6); - - const amountForWstEthUsdcToReach100Utilization = MathLib.wMulDown( - data.getMarket(id).totalSupplyAssets, - MathLib.WAD - data.getMarket(id).utilization, - ); - - const borrowed = - amountForWbtcUsdctToReachTarget + - amountForWstEthUsdcToReach100Utilization + - additionnalReallocationAmount; - - const withdrawnAssets = - amountForWbtcUsdctToReachTarget + additionnalReallocationAmount; - - const { operations } = await setupBundle( - client, - data, - [ - { - type: "Blue_SupplyCollateral", - sender: client.account.address, - address: morpho, - args: { - id, - assets: collateralAssets, - onBehalf: client.account.address, - }, - }, - { - type: "Blue_Borrow", - sender: client.account.address, - address: morpho, - args: { - id, - assets: borrowed, - onBehalf: client.account.address, - receiver: client.account.address, - slippage: DEFAULT_SLIPPAGE_TOLERANCE, - }, - }, - ], - { - publicAllocatorOptions: { - enabled: true, - reallocatableVaults: [bbUsdc.address], - maxWithdrawalUtilization: { - [usdc_wbtc.id]: target, - }, - supplyTargetUtilization: { - [id]: target, - }, - }, - }, - ); - - expect(operations).toStrictEqual([ - { - type: "Erc20_Permit", - sender: client.account.address, - address: wstEth, - args: { - amount: collateralAssets, - spender: bundler, - nonce: 0n, - }, - }, - { - type: "Erc20_Transfer", - sender: bundler, - address: wstEth, - args: { - amount: collateralAssets, - from: client.account.address, - to: bundler, - }, - }, - { - type: "Blue_SupplyCollateral", - sender: bundler, - address: morpho, - args: { - id, - assets: collateralAssets, - onBehalf: client.account.address, - }, - }, - { - type: "Blue_SetAuthorization", - sender: bundler, - address: morpho, - args: { - owner: client.account.address, - isBundlerAuthorized: true, - }, - }, - { - type: "MetaMorpho_PublicReallocate", - sender: bundler, - address: bbUsdc.address, - args: { - withdrawals: [ - { - id: usdc_wbtc.id, - assets: withdrawnAssets, - }, - ], - supplyMarketId: id, - }, - }, - { - type: "Blue_Borrow", - sender: bundler, - address: morpho, - args: { - id, - assets: borrowed, - onBehalf: client.account.address, - receiver: client.account.address, - slippage: DEFAULT_SLIPPAGE_TOLERANCE, - }, - }, - ]); - }, - ); }); describe("base", () => { diff --git a/packages/bundler-sdk-viem/test/sharedLiquidity.test.ts b/packages/bundler-sdk-viem/test/sharedLiquidity.test.ts new file mode 100644 index 00000000..559c62f1 --- /dev/null +++ b/packages/bundler-sdk-viem/test/sharedLiquidity.test.ts @@ -0,0 +1,803 @@ +import { + ChainId, + DEFAULT_SLIPPAGE_TOLERANCE, + MathLib, + NATIVE_ADDRESS, + addresses, +} from "@morpho-org/blue-sdk"; +import { metaMorphoAbi, publicAllocatorAbi } from "@morpho-org/blue-sdk-viem"; +import { markets, vaults } from "@morpho-org/morpho-test"; +import { useSimulationState } from "@morpho-org/simulation-sdk-wagmi"; +import { renderHook, waitFor } from "@morpho-org/test-wagmi"; +import { configure } from "@testing-library/dom"; +import dotenv from "dotenv"; +import { parseEther, parseUnits } from "viem"; +import { describe, expect } from "vitest"; +import { donator, setupBundle } from "./helpers.js"; +import { test } from "./setup.js"; + +dotenv.config(); + +configure({ asyncUtilTimeout: 10_000 }); + +describe("populateBundle", () => { + describe("with signatures", () => { + describe("ethereum", () => { + const { morpho, bundler, publicAllocator, wNative, wstEth, stEth, usdc } = + addresses[ChainId.EthMainnet]; + const { + eth_idle, + eth_wstEth_2, + eth_rEth, + eth_sDai, + eth_wbtc, + usdc_wstEth, + usdc_idle, + usdc_wbtc, + usdc_wbIB01, + } = markets[ChainId.EthMainnet]; + const { eth_wstEth } = markets[ChainId.EthMainnet]; + + const { steakUsdc, bbUsdc, bbEth } = vaults[ChainId.EthMainnet]; + + test[ChainId.EthMainnet]( + "should borrow USDC without shared liquidity", + async ({ client, config }) => { + const steakUsdcOwner = await client.readContract({ + address: steakUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: steakUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: steakUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + steakUsdc.address, + [ + { + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("10000", 6), + maxOut: 0n, + }, + }, + { + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("20000", 6), // Less than bbUsdc but more than maxIn. + }, + }, + ], + ], + }); + + const bbUsdcOwner = await client.readContract({ + address: bbUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: bbUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: bbUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + bbUsdc.address, + [ + { + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("1000000", 6), + maxOut: 0n, + }, + }, + { + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("1000000", 6), + }, + }, + ], + ], + }); + + const collateralAssets = parseEther("50000"); + const depositAssets = parseEther("50"); + await client.deal({ erc20: wstEth, amount: collateralAssets }); + await client.deal({ erc20: wNative, amount: depositAssets }); + + const { id } = usdc_wstEth; + + const block = await client.getBlock(); + + const { result } = await renderHook(config, () => + useSimulationState({ + marketIds: [ + eth_idle.id, + eth_rEth.id, + eth_sDai.id, + eth_wbtc.id, + eth_wstEth.id, + eth_wstEth_2.id, + id, + usdc_idle.id, + usdc_wbtc.id, + usdc_wbIB01.id, + ], + users: [ + client.account.address, + donator.address, + bundler, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + tokens: [ + NATIVE_ADDRESS, + wNative, + usdc, + stEth, + wstEth, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + vaults: [steakUsdc.address, bbEth.address, bbUsdc.address], + block, + }), + ); + + await waitFor( + () => expect(result.current.isFetchingAny).toBeFalsy(), + { timeout: 30_000 }, + ); + + const data = result.current.data!; + + const target = parseEther("0.92"); + + const borrowed = MathLib.wMulDown( + data.getMarket(id).totalSupplyAssets, + target - data.getMarket(id).utilization, + ); + + const { operations } = await setupBundle( + client, + data, + [ + { + type: "Blue_SupplyCollateral", + sender: client.account.address, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, + }, + }, + { + type: "Blue_Borrow", + sender: client.account.address, + address: morpho, + args: { + id, + assets: borrowed, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ], + { + publicAllocatorOptions: { + enabled: true, + reallocatableVaults: [bbUsdc.address], + maxWithdrawalUtilization: { + [usdc_wbtc.id]: parseEther("0.95"), + }, + supplyTargetUtilization: { + [id]: target, + }, + }, + }, + ); + + expect(operations).toStrictEqual([ + { + type: "Erc20_Permit", + sender: client.account.address, + address: wstEth, + args: { + amount: collateralAssets, + spender: bundler, + nonce: 0n, + }, + }, + { + type: "Erc20_Transfer", + sender: bundler, + address: wstEth, + args: { + amount: collateralAssets, + from: client.account.address, + to: bundler, + }, + }, + { + type: "Blue_SupplyCollateral", + sender: bundler, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, + }, + }, + { + type: "Blue_SetAuthorization", + sender: bundler, + address: morpho, + args: { + owner: client.account.address, + isBundlerAuthorized: true, + }, + }, + { + type: "Blue_Borrow", + sender: bundler, + address: morpho, + args: { + id, + assets: borrowed, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ]); + }, + ); + + test[ChainId.EthMainnet]( + "should borrow USDC with shared liquidity and friendly reallocation", + async ({ client, config }) => { + const steakUsdcOwner = await client.readContract({ + address: steakUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: steakUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: steakUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + steakUsdc.address, + [ + { + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("10000", 6), + maxOut: 0n, + }, + }, + { + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("20000", 6), // Less than bbUsdc but more than maxIn. + }, + }, + ], + ], + }); + + const bbUsdcOwner = await client.readContract({ + address: bbUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: bbUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: bbUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + bbUsdc.address, + [ + { + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("1000000", 6), + maxOut: 0n, + }, + }, + { + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("1000000", 6), + }, + }, + ], + ], + }); + + const collateralAssets = parseEther("50000"); + const depositAssets = parseEther("50"); + await client.deal({ erc20: wstEth, amount: collateralAssets }); + await client.deal({ erc20: wNative, amount: depositAssets }); + + const { id } = usdc_wstEth; + + const block = await client.getBlock(); + + const { result } = await renderHook(config, () => + useSimulationState({ + marketIds: [ + eth_idle.id, + eth_rEth.id, + eth_sDai.id, + eth_wbtc.id, + eth_wstEth.id, + eth_wstEth_2.id, + id, + usdc_idle.id, + usdc_wbtc.id, + usdc_wbIB01.id, + ], + users: [ + client.account.address, + donator.address, + bundler, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + tokens: [ + NATIVE_ADDRESS, + wNative, + usdc, + stEth, + wstEth, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + vaults: [steakUsdc.address, bbEth.address, bbUsdc.address], + block, + }), + ); + + await waitFor( + () => expect(result.current.isFetchingAny).toBeFalsy(), + { timeout: 30_000 }, + ); + + const data = result.current.data!; + + const target = parseEther("0.92"); + + const amountForWbtcUsdctToReachTarget = + MathLib.wDivDown( + data.getMarket(usdc_wbtc.id).totalBorrowAssets, + data.getMarket(usdc_wbtc.id).utilization, + ) - + MathLib.wDivDown( + data.getMarket(usdc_wbtc.id).totalBorrowAssets, + target, + ) - + 1n; // -1n because of the rounding on withdrawals + + const amountForWstEthUsdcToReach100Utilization = MathLib.wMulDown( + data.getMarket(id).totalSupplyAssets, + MathLib.WAD - data.getMarket(id).utilization, + ); + + const maxFriendlyReallocationAmount = + amountForWbtcUsdctToReachTarget + + amountForWstEthUsdcToReach100Utilization; + + const { operations } = await setupBundle( + client, + data, + [ + { + type: "Blue_SupplyCollateral", + sender: client.account.address, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, + }, + }, + { + type: "Blue_Borrow", + sender: client.account.address, + address: morpho, + args: { + id, + assets: maxFriendlyReallocationAmount, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ], + { + publicAllocatorOptions: { + enabled: true, + reallocatableVaults: [bbUsdc.address], + maxWithdrawalUtilization: { + [usdc_wbtc.id]: target, + }, + supplyTargetUtilization: { + [id]: target, + }, + }, + }, + ); + + expect(operations).toStrictEqual([ + { + type: "Erc20_Permit", + sender: client.account.address, + address: wstEth, + args: { + amount: collateralAssets, + spender: bundler, + nonce: 0n, + }, + }, + { + type: "Erc20_Transfer", + sender: bundler, + address: wstEth, + args: { + amount: collateralAssets, + from: client.account.address, + to: bundler, + }, + }, + { + type: "Blue_SupplyCollateral", + sender: bundler, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, + }, + }, + { + type: "Blue_SetAuthorization", + sender: bundler, + address: morpho, + args: { + owner: client.account.address, + isBundlerAuthorized: true, + }, + }, + { + type: "MetaMorpho_PublicReallocate", + sender: bundler, + address: bbUsdc.address, + args: { + withdrawals: [ + { + id: usdc_wbtc.id, + assets: amountForWbtcUsdctToReachTarget, + }, + ], + supplyMarketId: id, + }, + }, + { + type: "Blue_Borrow", + sender: bundler, + address: morpho, + args: { + id, + assets: maxFriendlyReallocationAmount, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ]); + }, + ); + + test[ChainId.EthMainnet]( + "should borrow USDC with shared liquidity and full reallocation", + async ({ client, config }) => { + const steakUsdcOwner = await client.readContract({ + address: steakUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: steakUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: steakUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + steakUsdc.address, + [ + { + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("10000", 6), + maxOut: 0n, + }, + }, + { + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("20000", 6), // Less than bbUsdc but more than maxIn. + }, + }, + ], + ], + }); + + const bbUsdcOwner = await client.readContract({ + address: bbUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: bbUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: bbUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + bbUsdc.address, + [ + { + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("1000000", 6), + maxOut: 0n, + }, + }, + { + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("1000000", 6), + }, + }, + ], + ], + }); + + const collateralAssets = parseEther("50000"); + const depositAssets = parseEther("50"); + await client.deal({ erc20: wstEth, amount: collateralAssets }); + await client.deal({ erc20: wNative, amount: depositAssets }); + + const { id } = usdc_wstEth; + + const block = await client.getBlock(); + + const { result } = await renderHook(config, () => + useSimulationState({ + marketIds: [ + eth_idle.id, + eth_rEth.id, + eth_sDai.id, + eth_wbtc.id, + eth_wstEth.id, + eth_wstEth_2.id, + id, + usdc_idle.id, + usdc_wbtc.id, + usdc_wbIB01.id, + ], + users: [ + client.account.address, + donator.address, + bundler, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + tokens: [ + NATIVE_ADDRESS, + wNative, + usdc, + stEth, + wstEth, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + vaults: [steakUsdc.address, bbEth.address, bbUsdc.address], + block, + }), + ); + + await waitFor( + () => expect(result.current.isFetchingAny).toBeFalsy(), + { timeout: 30_000 }, + ); + + const data = result.current.data!; + + const target = parseEther("0.92"); + + const amountForWbtcUsdctToReachTarget = + MathLib.wDivDown( + data.getMarket(usdc_wbtc.id).totalBorrowAssets, + data.getMarket(usdc_wbtc.id).utilization, + ) - + MathLib.wDivDown( + data.getMarket(usdc_wbtc.id).totalBorrowAssets, + target, + ); + + const additionnalReallocationAmount = parseUnits("10000", 6); + + const amountForWstEthUsdcToReach100Utilization = MathLib.wMulDown( + data.getMarket(id).totalSupplyAssets, + MathLib.WAD - data.getMarket(id).utilization, + ); + + const borrowed = + amountForWbtcUsdctToReachTarget + + amountForWstEthUsdcToReach100Utilization + + additionnalReallocationAmount; + + const withdrawnAssets = + amountForWbtcUsdctToReachTarget + additionnalReallocationAmount; + + const { operations } = await setupBundle( + client, + data, + [ + { + type: "Blue_SupplyCollateral", + sender: client.account.address, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, + }, + }, + { + type: "Blue_Borrow", + sender: client.account.address, + address: morpho, + args: { + id, + assets: borrowed, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ], + { + publicAllocatorOptions: { + enabled: true, + reallocatableVaults: [bbUsdc.address], + maxWithdrawalUtilization: { + [usdc_wbtc.id]: target, + }, + supplyTargetUtilization: { + [id]: target, + }, + }, + }, + ); + + expect(operations).toStrictEqual([ + { + type: "Erc20_Permit", + sender: client.account.address, + address: wstEth, + args: { + amount: collateralAssets, + spender: bundler, + nonce: 0n, + }, + }, + { + type: "Erc20_Transfer", + sender: bundler, + address: wstEth, + args: { + amount: collateralAssets, + from: client.account.address, + to: bundler, + }, + }, + { + type: "Blue_SupplyCollateral", + sender: bundler, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, + }, + }, + { + type: "Blue_SetAuthorization", + sender: bundler, + address: morpho, + args: { + owner: client.account.address, + isBundlerAuthorized: true, + }, + }, + { + type: "MetaMorpho_PublicReallocate", + sender: bundler, + address: bbUsdc.address, + args: { + withdrawals: [ + { + id: usdc_wbtc.id, + assets: withdrawnAssets, + }, + ], + supplyMarketId: id, + }, + }, + { + type: "Blue_Borrow", + sender: bundler, + address: morpho, + args: { + id, + assets: borrowed, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ]); + }, + ); + }); + }); +}); From 348d28698dfc082fccdeca1976bee90d4589586e Mon Sep 17 00:00:00 2001 From: Jean-Grimal <83286814+Jean-Grimal@users.noreply.github.com> Date: Thu, 13 Feb 2025 10:59:37 +0100 Subject: [PATCH 8/8] fix: apply suggestions --- .../test/sharedLiquidity.test.ts | 1446 ++++++++--------- 1 file changed, 718 insertions(+), 728 deletions(-) diff --git a/packages/bundler-sdk-viem/test/sharedLiquidity.test.ts b/packages/bundler-sdk-viem/test/sharedLiquidity.test.ts index 559c62f1..457dd941 100644 --- a/packages/bundler-sdk-viem/test/sharedLiquidity.test.ts +++ b/packages/bundler-sdk-viem/test/sharedLiquidity.test.ts @@ -10,794 +10,784 @@ import { markets, vaults } from "@morpho-org/morpho-test"; import { useSimulationState } from "@morpho-org/simulation-sdk-wagmi"; import { renderHook, waitFor } from "@morpho-org/test-wagmi"; import { configure } from "@testing-library/dom"; -import dotenv from "dotenv"; import { parseEther, parseUnits } from "viem"; import { describe, expect } from "vitest"; import { donator, setupBundle } from "./helpers.js"; import { test } from "./setup.js"; -dotenv.config(); - configure({ asyncUtilTimeout: 10_000 }); -describe("populateBundle", () => { - describe("with signatures", () => { - describe("ethereum", () => { - const { morpho, bundler, publicAllocator, wNative, wstEth, stEth, usdc } = - addresses[ChainId.EthMainnet]; - const { - eth_idle, - eth_wstEth_2, - eth_rEth, - eth_sDai, - eth_wbtc, - usdc_wstEth, - usdc_idle, - usdc_wbtc, - usdc_wbIB01, - } = markets[ChainId.EthMainnet]; - const { eth_wstEth } = markets[ChainId.EthMainnet]; - - const { steakUsdc, bbUsdc, bbEth } = vaults[ChainId.EthMainnet]; - - test[ChainId.EthMainnet]( - "should borrow USDC without shared liquidity", - async ({ client, config }) => { - const steakUsdcOwner = await client.readContract({ - address: steakUsdc.address, - abi: metaMorphoAbi, - functionName: "owner", - }); - - await client.setBalance({ - address: steakUsdcOwner, - value: parseEther("1000"), - }); - await client.writeContract({ - account: steakUsdcOwner, - address: publicAllocator, - abi: publicAllocatorAbi, - functionName: "setFlowCaps", - args: [ - steakUsdc.address, - [ - { - id: usdc_wstEth.id, - caps: { - maxIn: parseUnits("10000", 6), - maxOut: 0n, - }, - }, - { - id: usdc_wbtc.id, - caps: { - maxIn: 0n, - maxOut: parseUnits("20000", 6), // Less than bbUsdc but more than maxIn. - }, - }, - ], - ], - }); - - const bbUsdcOwner = await client.readContract({ - address: bbUsdc.address, - abi: metaMorphoAbi, - functionName: "owner", - }); - - await client.setBalance({ - address: bbUsdcOwner, - value: parseEther("1000"), - }); - await client.writeContract({ - account: bbUsdcOwner, - address: publicAllocator, - abi: publicAllocatorAbi, - functionName: "setFlowCaps", - args: [ - bbUsdc.address, - [ - { - id: usdc_wstEth.id, - caps: { - maxIn: parseUnits("1000000", 6), - maxOut: 0n, - }, - }, - { - id: usdc_wbtc.id, - caps: { - maxIn: 0n, - maxOut: parseUnits("1000000", 6), - }, - }, - ], - ], - }); - - const collateralAssets = parseEther("50000"); - const depositAssets = parseEther("50"); - await client.deal({ erc20: wstEth, amount: collateralAssets }); - await client.deal({ erc20: wNative, amount: depositAssets }); - - const { id } = usdc_wstEth; - - const block = await client.getBlock(); - - const { result } = await renderHook(config, () => - useSimulationState({ - marketIds: [ - eth_idle.id, - eth_rEth.id, - eth_sDai.id, - eth_wbtc.id, - eth_wstEth.id, - eth_wstEth_2.id, - id, - usdc_idle.id, - usdc_wbtc.id, - usdc_wbIB01.id, - ], - users: [ - client.account.address, - donator.address, - bundler, - steakUsdc.address, - bbEth.address, - bbUsdc.address, - ], - tokens: [ - NATIVE_ADDRESS, - wNative, - usdc, - stEth, - wstEth, - steakUsdc.address, - bbEth.address, - bbUsdc.address, - ], - vaults: [steakUsdc.address, bbEth.address, bbUsdc.address], - block, - }), - ); - - await waitFor( - () => expect(result.current.isFetchingAny).toBeFalsy(), - { timeout: 30_000 }, - ); - - const data = result.current.data!; - - const target = parseEther("0.92"); - - const borrowed = MathLib.wMulDown( - data.getMarket(id).totalSupplyAssets, - target - data.getMarket(id).utilization, - ); - - const { operations } = await setupBundle( - client, - data, - [ - { - type: "Blue_SupplyCollateral", - sender: client.account.address, - address: morpho, - args: { - id, - assets: collateralAssets, - onBehalf: client.account.address, - }, - }, - { - type: "Blue_Borrow", - sender: client.account.address, - address: morpho, - args: { - id, - assets: borrowed, - onBehalf: client.account.address, - receiver: client.account.address, - slippage: DEFAULT_SLIPPAGE_TOLERANCE, - }, - }, - ], +describe("sharedLiquidity", () => { + const { morpho, bundler, publicAllocator, wNative, wstEth, stEth, usdc } = + addresses[ChainId.EthMainnet]; + const { + eth_idle, + eth_wstEth_2, + eth_rEth, + eth_sDai, + eth_wbtc, + usdc_wstEth, + usdc_idle, + usdc_wbtc, + usdc_wbIB01, + } = markets[ChainId.EthMainnet]; + const { eth_wstEth } = markets[ChainId.EthMainnet]; + + const { steakUsdc, bbUsdc, bbEth } = vaults[ChainId.EthMainnet]; + + test[ChainId.EthMainnet]( + "should borrow USDC without shared liquidity", + async ({ client, config }) => { + const steakUsdcOwner = await client.readContract({ + address: steakUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: steakUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: steakUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + steakUsdc.address, + [ { - publicAllocatorOptions: { - enabled: true, - reallocatableVaults: [bbUsdc.address], - maxWithdrawalUtilization: { - [usdc_wbtc.id]: parseEther("0.95"), - }, - supplyTargetUtilization: { - [id]: target, - }, + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("10000", 6), + maxOut: 0n, }, }, - ); - - expect(operations).toStrictEqual([ { - type: "Erc20_Permit", - sender: client.account.address, - address: wstEth, - args: { - amount: collateralAssets, - spender: bundler, - nonce: 0n, + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("20000", 6), // Less than bbUsdc but more than maxIn. }, }, + ], + ], + }); + + const bbUsdcOwner = await client.readContract({ + address: bbUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: bbUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: bbUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + bbUsdc.address, + [ { - type: "Erc20_Transfer", - sender: bundler, - address: wstEth, - args: { - amount: collateralAssets, - from: client.account.address, - to: bundler, + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("1000000", 6), + maxOut: 0n, }, }, { - type: "Blue_SupplyCollateral", - sender: bundler, - address: morpho, - args: { - id, - assets: collateralAssets, - onBehalf: client.account.address, + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("1000000", 6), }, }, - { - type: "Blue_SetAuthorization", - sender: bundler, - address: morpho, - args: { - owner: client.account.address, - isBundlerAuthorized: true, - }, + ], + ], + }); + + const collateralAssets = parseEther("50000"); + const depositAssets = parseEther("50"); + await client.deal({ erc20: wstEth, amount: collateralAssets }); + await client.deal({ erc20: wNative, amount: depositAssets }); + + const { id } = usdc_wstEth; + + const block = await client.getBlock(); + + const { result } = await renderHook(config, () => + useSimulationState({ + marketIds: [ + eth_idle.id, + eth_rEth.id, + eth_sDai.id, + eth_wbtc.id, + eth_wstEth.id, + eth_wstEth_2.id, + id, + usdc_idle.id, + usdc_wbtc.id, + usdc_wbIB01.id, + ], + users: [ + client.account.address, + donator.address, + bundler, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + tokens: [ + NATIVE_ADDRESS, + wNative, + usdc, + stEth, + wstEth, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + vaults: [steakUsdc.address, bbEth.address, bbUsdc.address], + block, + }), + ); + + await waitFor(() => expect(result.current.isFetchingAny).toBeFalsy(), { + timeout: 30_000, + }); + + const data = result.current.data!; + + const target = parseEther("0.92"); + + const borrowed = MathLib.wMulDown( + data.getMarket(id).totalSupplyAssets, + target - data.getMarket(id).utilization, + ); + + const { operations } = await setupBundle( + client, + data, + [ + { + type: "Blue_SupplyCollateral", + sender: client.account.address, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, }, - { - type: "Blue_Borrow", - sender: bundler, - address: morpho, - args: { - id, - assets: borrowed, - onBehalf: client.account.address, - receiver: client.account.address, - slippage: DEFAULT_SLIPPAGE_TOLERANCE, - }, + }, + { + type: "Blue_Borrow", + sender: client.account.address, + address: morpho, + args: { + id, + assets: borrowed, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ], + { + publicAllocatorOptions: { + enabled: true, + reallocatableVaults: [bbUsdc.address], + maxWithdrawalUtilization: { + [usdc_wbtc.id]: parseEther("0.95"), + }, + supplyTargetUtilization: { + [id]: target, }, - ]); + }, }, ); - test[ChainId.EthMainnet]( - "should borrow USDC with shared liquidity and friendly reallocation", - async ({ client, config }) => { - const steakUsdcOwner = await client.readContract({ - address: steakUsdc.address, - abi: metaMorphoAbi, - functionName: "owner", - }); - - await client.setBalance({ - address: steakUsdcOwner, - value: parseEther("1000"), - }); - await client.writeContract({ - account: steakUsdcOwner, - address: publicAllocator, - abi: publicAllocatorAbi, - functionName: "setFlowCaps", - args: [ - steakUsdc.address, - [ - { - id: usdc_wstEth.id, - caps: { - maxIn: parseUnits("10000", 6), - maxOut: 0n, - }, - }, - { - id: usdc_wbtc.id, - caps: { - maxIn: 0n, - maxOut: parseUnits("20000", 6), // Less than bbUsdc but more than maxIn. - }, - }, - ], - ], - }); - - const bbUsdcOwner = await client.readContract({ - address: bbUsdc.address, - abi: metaMorphoAbi, - functionName: "owner", - }); - - await client.setBalance({ - address: bbUsdcOwner, - value: parseEther("1000"), - }); - await client.writeContract({ - account: bbUsdcOwner, - address: publicAllocator, - abi: publicAllocatorAbi, - functionName: "setFlowCaps", - args: [ - bbUsdc.address, - [ - { - id: usdc_wstEth.id, - caps: { - maxIn: parseUnits("1000000", 6), - maxOut: 0n, - }, - }, - { - id: usdc_wbtc.id, - caps: { - maxIn: 0n, - maxOut: parseUnits("1000000", 6), - }, - }, - ], - ], - }); - - const collateralAssets = parseEther("50000"); - const depositAssets = parseEther("50"); - await client.deal({ erc20: wstEth, amount: collateralAssets }); - await client.deal({ erc20: wNative, amount: depositAssets }); - - const { id } = usdc_wstEth; - - const block = await client.getBlock(); - - const { result } = await renderHook(config, () => - useSimulationState({ - marketIds: [ - eth_idle.id, - eth_rEth.id, - eth_sDai.id, - eth_wbtc.id, - eth_wstEth.id, - eth_wstEth_2.id, - id, - usdc_idle.id, - usdc_wbtc.id, - usdc_wbIB01.id, - ], - users: [ - client.account.address, - donator.address, - bundler, - steakUsdc.address, - bbEth.address, - bbUsdc.address, - ], - tokens: [ - NATIVE_ADDRESS, - wNative, - usdc, - stEth, - wstEth, - steakUsdc.address, - bbEth.address, - bbUsdc.address, - ], - vaults: [steakUsdc.address, bbEth.address, bbUsdc.address], - block, - }), - ); - - await waitFor( - () => expect(result.current.isFetchingAny).toBeFalsy(), - { timeout: 30_000 }, - ); - - const data = result.current.data!; - - const target = parseEther("0.92"); - - const amountForWbtcUsdctToReachTarget = - MathLib.wDivDown( - data.getMarket(usdc_wbtc.id).totalBorrowAssets, - data.getMarket(usdc_wbtc.id).utilization, - ) - - MathLib.wDivDown( - data.getMarket(usdc_wbtc.id).totalBorrowAssets, - target, - ) - - 1n; // -1n because of the rounding on withdrawals - - const amountForWstEthUsdcToReach100Utilization = MathLib.wMulDown( - data.getMarket(id).totalSupplyAssets, - MathLib.WAD - data.getMarket(id).utilization, - ); - - const maxFriendlyReallocationAmount = - amountForWbtcUsdctToReachTarget + - amountForWstEthUsdcToReach100Utilization; - - const { operations } = await setupBundle( - client, - data, - [ - { - type: "Blue_SupplyCollateral", - sender: client.account.address, - address: morpho, - args: { - id, - assets: collateralAssets, - onBehalf: client.account.address, - }, - }, - { - type: "Blue_Borrow", - sender: client.account.address, - address: morpho, - args: { - id, - assets: maxFriendlyReallocationAmount, - onBehalf: client.account.address, - receiver: client.account.address, - slippage: DEFAULT_SLIPPAGE_TOLERANCE, - }, - }, - ], + expect(operations).toStrictEqual([ + { + type: "Erc20_Permit", + sender: client.account.address, + address: wstEth, + args: { + amount: collateralAssets, + spender: bundler, + nonce: 0n, + }, + }, + { + type: "Erc20_Transfer", + sender: bundler, + address: wstEth, + args: { + amount: collateralAssets, + from: client.account.address, + to: bundler, + }, + }, + { + type: "Blue_SupplyCollateral", + sender: bundler, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, + }, + }, + { + type: "Blue_SetAuthorization", + sender: bundler, + address: morpho, + args: { + owner: client.account.address, + isBundlerAuthorized: true, + }, + }, + { + type: "Blue_Borrow", + sender: bundler, + address: morpho, + args: { + id, + assets: borrowed, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ]); + }, + ); + + test[ChainId.EthMainnet]( + "should borrow USDC with shared liquidity and friendly reallocation", + async ({ client, config }) => { + const steakUsdcOwner = await client.readContract({ + address: steakUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: steakUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: steakUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + steakUsdc.address, + [ { - publicAllocatorOptions: { - enabled: true, - reallocatableVaults: [bbUsdc.address], - maxWithdrawalUtilization: { - [usdc_wbtc.id]: target, - }, - supplyTargetUtilization: { - [id]: target, - }, + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("10000", 6), + maxOut: 0n, }, }, - ); - - expect(operations).toStrictEqual([ { - type: "Erc20_Permit", - sender: client.account.address, - address: wstEth, - args: { - amount: collateralAssets, - spender: bundler, - nonce: 0n, + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("20000", 6), // Less than bbUsdc but more than maxIn. }, }, + ], + ], + }); + + const bbUsdcOwner = await client.readContract({ + address: bbUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: bbUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: bbUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + bbUsdc.address, + [ { - type: "Erc20_Transfer", - sender: bundler, - address: wstEth, - args: { - amount: collateralAssets, - from: client.account.address, - to: bundler, + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("1000000", 6), + maxOut: 0n, }, }, { - type: "Blue_SupplyCollateral", - sender: bundler, - address: morpho, - args: { - id, - assets: collateralAssets, - onBehalf: client.account.address, + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("1000000", 6), }, }, - { - type: "Blue_SetAuthorization", - sender: bundler, - address: morpho, - args: { - owner: client.account.address, - isBundlerAuthorized: true, - }, + ], + ], + }); + + const collateralAssets = parseEther("50000"); + const depositAssets = parseEther("50"); + await client.deal({ erc20: wstEth, amount: collateralAssets }); + await client.deal({ erc20: wNative, amount: depositAssets }); + + const { id } = usdc_wstEth; + + const block = await client.getBlock(); + + const { result } = await renderHook(config, () => + useSimulationState({ + marketIds: [ + eth_idle.id, + eth_rEth.id, + eth_sDai.id, + eth_wbtc.id, + eth_wstEth.id, + eth_wstEth_2.id, + id, + usdc_idle.id, + usdc_wbtc.id, + usdc_wbIB01.id, + ], + users: [ + client.account.address, + donator.address, + bundler, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + tokens: [ + NATIVE_ADDRESS, + wNative, + usdc, + stEth, + wstEth, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + vaults: [steakUsdc.address, bbEth.address, bbUsdc.address], + block, + }), + ); + + await waitFor(() => expect(result.current.isFetchingAny).toBeFalsy(), { + timeout: 30_000, + }); + + const data = result.current.data!; + + const target = parseEther("0.92"); + + const amountForWbtcUsdctToReachTarget = + MathLib.wDivDown( + data.getMarket(usdc_wbtc.id).totalBorrowAssets, + data.getMarket(usdc_wbtc.id).utilization, + ) - + MathLib.wDivDown( + data.getMarket(usdc_wbtc.id).totalBorrowAssets, + target, + ) - + 1n; // -1n because of the rounding on withdrawals + + const amountForWstEthUsdcToReach100Utilization = MathLib.wMulDown( + data.getMarket(id).totalSupplyAssets, + MathLib.WAD - data.getMarket(id).utilization, + ); + + const maxFriendlyReallocationAmount = + amountForWbtcUsdctToReachTarget + + amountForWstEthUsdcToReach100Utilization; + + const { operations } = await setupBundle( + client, + data, + [ + { + type: "Blue_SupplyCollateral", + sender: client.account.address, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, }, - { - type: "MetaMorpho_PublicReallocate", - sender: bundler, - address: bbUsdc.address, - args: { - withdrawals: [ - { - id: usdc_wbtc.id, - assets: amountForWbtcUsdctToReachTarget, - }, - ], - supplyMarketId: id, - }, + }, + { + type: "Blue_Borrow", + sender: client.account.address, + address: morpho, + args: { + id, + assets: maxFriendlyReallocationAmount, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, }, - { - type: "Blue_Borrow", - sender: bundler, - address: morpho, - args: { - id, - assets: maxFriendlyReallocationAmount, - onBehalf: client.account.address, - receiver: client.account.address, - slippage: DEFAULT_SLIPPAGE_TOLERANCE, - }, + }, + ], + { + publicAllocatorOptions: { + enabled: true, + reallocatableVaults: [bbUsdc.address], + maxWithdrawalUtilization: { + [usdc_wbtc.id]: target, + }, + supplyTargetUtilization: { + [id]: target, }, - ]); + }, }, ); - test[ChainId.EthMainnet]( - "should borrow USDC with shared liquidity and full reallocation", - async ({ client, config }) => { - const steakUsdcOwner = await client.readContract({ - address: steakUsdc.address, - abi: metaMorphoAbi, - functionName: "owner", - }); - - await client.setBalance({ - address: steakUsdcOwner, - value: parseEther("1000"), - }); - await client.writeContract({ - account: steakUsdcOwner, - address: publicAllocator, - abi: publicAllocatorAbi, - functionName: "setFlowCaps", - args: [ - steakUsdc.address, - [ - { - id: usdc_wstEth.id, - caps: { - maxIn: parseUnits("10000", 6), - maxOut: 0n, - }, - }, - { - id: usdc_wbtc.id, - caps: { - maxIn: 0n, - maxOut: parseUnits("20000", 6), // Less than bbUsdc but more than maxIn. - }, - }, - ], - ], - }); - - const bbUsdcOwner = await client.readContract({ - address: bbUsdc.address, - abi: metaMorphoAbi, - functionName: "owner", - }); - - await client.setBalance({ - address: bbUsdcOwner, - value: parseEther("1000"), - }); - await client.writeContract({ - account: bbUsdcOwner, - address: publicAllocator, - abi: publicAllocatorAbi, - functionName: "setFlowCaps", - args: [ - bbUsdc.address, - [ - { - id: usdc_wstEth.id, - caps: { - maxIn: parseUnits("1000000", 6), - maxOut: 0n, - }, - }, - { - id: usdc_wbtc.id, - caps: { - maxIn: 0n, - maxOut: parseUnits("1000000", 6), - }, - }, - ], - ], - }); - - const collateralAssets = parseEther("50000"); - const depositAssets = parseEther("50"); - await client.deal({ erc20: wstEth, amount: collateralAssets }); - await client.deal({ erc20: wNative, amount: depositAssets }); - - const { id } = usdc_wstEth; - - const block = await client.getBlock(); - - const { result } = await renderHook(config, () => - useSimulationState({ - marketIds: [ - eth_idle.id, - eth_rEth.id, - eth_sDai.id, - eth_wbtc.id, - eth_wstEth.id, - eth_wstEth_2.id, - id, - usdc_idle.id, - usdc_wbtc.id, - usdc_wbIB01.id, - ], - users: [ - client.account.address, - donator.address, - bundler, - steakUsdc.address, - bbEth.address, - bbUsdc.address, - ], - tokens: [ - NATIVE_ADDRESS, - wNative, - usdc, - stEth, - wstEth, - steakUsdc.address, - bbEth.address, - bbUsdc.address, - ], - vaults: [steakUsdc.address, bbEth.address, bbUsdc.address], - block, - }), - ); - - await waitFor( - () => expect(result.current.isFetchingAny).toBeFalsy(), - { timeout: 30_000 }, - ); - - const data = result.current.data!; - - const target = parseEther("0.92"); - - const amountForWbtcUsdctToReachTarget = - MathLib.wDivDown( - data.getMarket(usdc_wbtc.id).totalBorrowAssets, - data.getMarket(usdc_wbtc.id).utilization, - ) - - MathLib.wDivDown( - data.getMarket(usdc_wbtc.id).totalBorrowAssets, - target, - ); - - const additionnalReallocationAmount = parseUnits("10000", 6); - - const amountForWstEthUsdcToReach100Utilization = MathLib.wMulDown( - data.getMarket(id).totalSupplyAssets, - MathLib.WAD - data.getMarket(id).utilization, - ); - - const borrowed = - amountForWbtcUsdctToReachTarget + - amountForWstEthUsdcToReach100Utilization + - additionnalReallocationAmount; - - const withdrawnAssets = - amountForWbtcUsdctToReachTarget + additionnalReallocationAmount; - - const { operations } = await setupBundle( - client, - data, - [ - { - type: "Blue_SupplyCollateral", - sender: client.account.address, - address: morpho, - args: { - id, - assets: collateralAssets, - onBehalf: client.account.address, - }, - }, + expect(operations).toStrictEqual([ + { + type: "Erc20_Permit", + sender: client.account.address, + address: wstEth, + args: { + amount: collateralAssets, + spender: bundler, + nonce: 0n, + }, + }, + { + type: "Erc20_Transfer", + sender: bundler, + address: wstEth, + args: { + amount: collateralAssets, + from: client.account.address, + to: bundler, + }, + }, + { + type: "Blue_SupplyCollateral", + sender: bundler, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, + }, + }, + { + type: "Blue_SetAuthorization", + sender: bundler, + address: morpho, + args: { + owner: client.account.address, + isBundlerAuthorized: true, + }, + }, + { + type: "MetaMorpho_PublicReallocate", + sender: bundler, + address: bbUsdc.address, + args: { + withdrawals: [ { - type: "Blue_Borrow", - sender: client.account.address, - address: morpho, - args: { - id, - assets: borrowed, - onBehalf: client.account.address, - receiver: client.account.address, - slippage: DEFAULT_SLIPPAGE_TOLERANCE, - }, + id: usdc_wbtc.id, + assets: amountForWbtcUsdctToReachTarget, }, ], + supplyMarketId: id, + }, + }, + { + type: "Blue_Borrow", + sender: bundler, + address: morpho, + args: { + id, + assets: maxFriendlyReallocationAmount, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ]); + }, + ); + + test[ChainId.EthMainnet]( + "should borrow USDC with shared liquidity and full reallocation", + async ({ client, config }) => { + const steakUsdcOwner = await client.readContract({ + address: steakUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: steakUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: steakUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + steakUsdc.address, + [ { - publicAllocatorOptions: { - enabled: true, - reallocatableVaults: [bbUsdc.address], - maxWithdrawalUtilization: { - [usdc_wbtc.id]: target, - }, - supplyTargetUtilization: { - [id]: target, - }, + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("10000", 6), + maxOut: 0n, }, }, - ); - - expect(operations).toStrictEqual([ { - type: "Erc20_Permit", - sender: client.account.address, - address: wstEth, - args: { - amount: collateralAssets, - spender: bundler, - nonce: 0n, + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("20000", 6), // Less than bbUsdc but more than maxIn. }, }, + ], + ], + }); + + const bbUsdcOwner = await client.readContract({ + address: bbUsdc.address, + abi: metaMorphoAbi, + functionName: "owner", + }); + + await client.setBalance({ + address: bbUsdcOwner, + value: parseEther("1000"), + }); + await client.writeContract({ + account: bbUsdcOwner, + address: publicAllocator, + abi: publicAllocatorAbi, + functionName: "setFlowCaps", + args: [ + bbUsdc.address, + [ { - type: "Erc20_Transfer", - sender: bundler, - address: wstEth, - args: { - amount: collateralAssets, - from: client.account.address, - to: bundler, + id: usdc_wstEth.id, + caps: { + maxIn: parseUnits("1000000", 6), + maxOut: 0n, }, }, { - type: "Blue_SupplyCollateral", - sender: bundler, - address: morpho, - args: { - id, - assets: collateralAssets, - onBehalf: client.account.address, + id: usdc_wbtc.id, + caps: { + maxIn: 0n, + maxOut: parseUnits("1000000", 6), }, }, - { - type: "Blue_SetAuthorization", - sender: bundler, - address: morpho, - args: { - owner: client.account.address, - isBundlerAuthorized: true, - }, + ], + ], + }); + + const collateralAssets = parseEther("50000"); + const depositAssets = parseEther("50"); + await client.deal({ erc20: wstEth, amount: collateralAssets }); + await client.deal({ erc20: wNative, amount: depositAssets }); + + const { id } = usdc_wstEth; + + const block = await client.getBlock(); + + const { result } = await renderHook(config, () => + useSimulationState({ + marketIds: [ + eth_idle.id, + eth_rEth.id, + eth_sDai.id, + eth_wbtc.id, + eth_wstEth.id, + eth_wstEth_2.id, + id, + usdc_idle.id, + usdc_wbtc.id, + usdc_wbIB01.id, + ], + users: [ + client.account.address, + donator.address, + bundler, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + tokens: [ + NATIVE_ADDRESS, + wNative, + usdc, + stEth, + wstEth, + steakUsdc.address, + bbEth.address, + bbUsdc.address, + ], + vaults: [steakUsdc.address, bbEth.address, bbUsdc.address], + block, + }), + ); + + await waitFor(() => expect(result.current.isFetchingAny).toBeFalsy(), { + timeout: 30_000, + }); + + const data = result.current.data!; + + const target = parseEther("0.92"); + + const amountForWbtcUsdctToReachTarget = + MathLib.wDivDown( + data.getMarket(usdc_wbtc.id).totalBorrowAssets, + data.getMarket(usdc_wbtc.id).utilization, + ) - + MathLib.wDivDown( + data.getMarket(usdc_wbtc.id).totalBorrowAssets, + target, + ); + + const additionnalReallocationAmount = parseUnits("10000", 6); + + const amountForWstEthUsdcToReach100Utilization = MathLib.wMulDown( + data.getMarket(id).totalSupplyAssets, + MathLib.WAD - data.getMarket(id).utilization, + ); + + const borrowed = + amountForWbtcUsdctToReachTarget + + amountForWstEthUsdcToReach100Utilization + + additionnalReallocationAmount; + + const withdrawnAssets = + amountForWbtcUsdctToReachTarget + additionnalReallocationAmount; + + const { operations } = await setupBundle( + client, + data, + [ + { + type: "Blue_SupplyCollateral", + sender: client.account.address, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, }, - { - type: "MetaMorpho_PublicReallocate", - sender: bundler, - address: bbUsdc.address, - args: { - withdrawals: [ - { - id: usdc_wbtc.id, - assets: withdrawnAssets, - }, - ], - supplyMarketId: id, - }, + }, + { + type: "Blue_Borrow", + sender: client.account.address, + address: morpho, + args: { + id, + assets: borrowed, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, }, - { - type: "Blue_Borrow", - sender: bundler, - address: morpho, - args: { - id, - assets: borrowed, - onBehalf: client.account.address, - receiver: client.account.address, - slippage: DEFAULT_SLIPPAGE_TOLERANCE, - }, + }, + ], + { + publicAllocatorOptions: { + enabled: true, + reallocatableVaults: [bbUsdc.address], + maxWithdrawalUtilization: { + [usdc_wbtc.id]: target, + }, + supplyTargetUtilization: { + [id]: target, }, - ]); + }, }, ); - }); - }); + + expect(operations).toStrictEqual([ + { + type: "Erc20_Permit", + sender: client.account.address, + address: wstEth, + args: { + amount: collateralAssets, + spender: bundler, + nonce: 0n, + }, + }, + { + type: "Erc20_Transfer", + sender: bundler, + address: wstEth, + args: { + amount: collateralAssets, + from: client.account.address, + to: bundler, + }, + }, + { + type: "Blue_SupplyCollateral", + sender: bundler, + address: morpho, + args: { + id, + assets: collateralAssets, + onBehalf: client.account.address, + }, + }, + { + type: "Blue_SetAuthorization", + sender: bundler, + address: morpho, + args: { + owner: client.account.address, + isBundlerAuthorized: true, + }, + }, + { + type: "MetaMorpho_PublicReallocate", + sender: bundler, + address: bbUsdc.address, + args: { + withdrawals: [ + { + id: usdc_wbtc.id, + assets: withdrawnAssets, + }, + ], + supplyMarketId: id, + }, + }, + { + type: "Blue_Borrow", + sender: bundler, + address: morpho, + args: { + id, + assets: borrowed, + onBehalf: client.account.address, + receiver: client.account.address, + slippage: DEFAULT_SLIPPAGE_TOLERANCE, + }, + }, + ]); + }, + ); });