From e7cba567afab0db103c082d316911dec0978a50f Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Mon, 29 May 2023 06:03:24 +0300 Subject: [PATCH 1/5] feat: gas optimizations --- .../contracts/interfaces/ILendPool.sol | 6 + contracts/mocks/benddao/MockLendPool.sol | 13 +- .../libraries/logic/PositionMoverLogic.sol | 183 ++++++++++++------ 3 files changed, 142 insertions(+), 60 deletions(-) diff --git a/contracts/dependencies/benddao/contracts/interfaces/ILendPool.sol b/contracts/dependencies/benddao/contracts/interfaces/ILendPool.sol index c532ba21c..36dad23e9 100644 --- a/contracts/dependencies/benddao/contracts/interfaces/ILendPool.sol +++ b/contracts/dependencies/benddao/contracts/interfaces/ILendPool.sol @@ -18,4 +18,10 @@ interface ILendPool { uint256 amount ) external returns (uint256, bool); + + function batchRepay( + address[] calldata nftAssets, + uint256[] calldata nftTokenIds, + uint256[] calldata amounts + ) external returns (uint256[] memory, bool[] memory); } diff --git a/contracts/mocks/benddao/MockLendPool.sol b/contracts/mocks/benddao/MockLendPool.sol index 0e98f2732..210c3f355 100644 --- a/contracts/mocks/benddao/MockLendPool.sol +++ b/contracts/mocks/benddao/MockLendPool.sol @@ -40,7 +40,7 @@ contract MockLendPool { address nftAsset, uint256 nftTokenId, uint256 amount - ) external returns (uint256, bool) { + ) public returns (uint256, bool) { BDaoDataTypes.LoanData storage loan = loans[loanMapping[nftAsset][nftTokenId]]; if (amount == loan.scaledAmount) { @@ -51,4 +51,15 @@ contract MockLendPool { IERC20(weth).transferFrom(msg.sender, address(this), amount); } + + function batchRepay( + address[] calldata nftAssets, + uint256[] calldata nftTokenIds, + uint256[] calldata amounts + ) external returns (uint256[] memory, bool[] memory) { + for (uint256 index = 0; index < nftAssets.length; index++) { + repay(nftAssets[index], nftTokenIds[index], amounts[index]); + } + } + } \ No newline at end of file diff --git a/contracts/protocol/libraries/logic/PositionMoverLogic.sol b/contracts/protocol/libraries/logic/PositionMoverLogic.sol index 421f408ee..97ff121b8 100644 --- a/contracts/protocol/libraries/logic/PositionMoverLogic.sol +++ b/contracts/protocol/libraries/logic/PositionMoverLogic.sol @@ -27,11 +27,10 @@ library PositionMoverLogic { using ReserveLogic for DataTypes.ReserveData; struct PositionMoverVars { - address weth; - address xTokenAddress; - address nftAsset; - uint256 tokenId; - uint256 borrowAmount; + address[] nftAssets; + uint256[] tokenIds; + uint256[] borrowAmounts; + uint256 totalBorrowAmount; } event PositionMoved(address asset, uint256 tokenId, address user); @@ -41,103 +40,169 @@ library PositionMoverLogic { IPoolAddressesProvider poolAddressProvider, ILendPoolLoan lendPoolLoan, ILendPool lendPool, - uint256[] calldata loandIds + uint256[] calldata loanIds ) external { - PositionMoverVars memory tmpVar; - - tmpVar.weth = poolAddressProvider.getWETH(); - DataTypes.ReserveData storage reserve = ps._reserves[tmpVar.weth]; - tmpVar.xTokenAddress = reserve.xTokenAddress; - - for (uint256 index = 0; index < loandIds.length; index++) { - ( - tmpVar.nftAsset, - tmpVar.tokenId, - tmpVar.borrowAmount - ) = _repayBendDAOPositionLoan( - lendPoolLoan, - lendPool, - tmpVar.weth, - tmpVar.xTokenAddress, - loandIds[index] - ); - - supplyNFTandBorrowWETH(ps, poolAddressProvider, tmpVar); + address weth = poolAddressProvider.getWETH(); + DataTypes.ReserveData storage reserve = ps._reserves[weth]; + address xTokenAddress = reserve.xTokenAddress; + + uint256 borrowAmount = _repayBendDAOPositionLoanAndSupply( + ps, + lendPoolLoan, + lendPool, + weth, + xTokenAddress, + loanIds + ); - emit PositionMoved(tmpVar.nftAsset, tmpVar.tokenId, msg.sender); - } + borrowWETH(ps, poolAddressProvider, weth, borrowAmount); } - function _repayBendDAOPositionLoan( + function _repayBendDAOPositionLoanAndSupply( + DataTypes.PoolStorage storage ps, ILendPoolLoan lendPoolLoan, ILendPool lendPool, address weth, address xTokenAddress, - uint256 loanId - ) - internal - returns ( - address nftAsset, - uint256 tokenId, - uint256 borrowAmount - ) - { - BDaoDataTypes.LoanData memory loanData = lendPoolLoan.getLoan(loanId); - - require( - loanData.state == BDaoDataTypes.LoanState.Active, - "Loan not active" - ); - require(loanData.borrower == msg.sender, Errors.NOT_THE_OWNER); + uint256[] calldata loanIds + ) internal returns (uint256 borrowAmount) { + BDaoDataTypes.LoanData memory loanData; + PositionMoverVars memory tmpVar; - (, borrowAmount) = lendPoolLoan.getLoanReserveBorrowAmount(loanId); + tmpVar.borrowAmounts = new uint256[](loanIds.length); + tmpVar.nftAssets = new address[](loanIds.length); + tmpVar.tokenIds = new uint256[](loanIds.length); + + for (uint256 index = 0; index < loanIds.length; index++) { + loanData = lendPoolLoan.getLoan(loanIds[index]); + + require( + loanData.state == BDaoDataTypes.LoanState.Active, + "Loan not active" + ); + require(loanData.borrower == msg.sender, Errors.NOT_THE_OWNER); + + (, tmpVar.borrowAmounts[index]) = lendPoolLoan + .getLoanReserveBorrowAmount(loanIds[index]); + + tmpVar.totalBorrowAmount += tmpVar.borrowAmounts[index]; + + emit PositionMoved( + loanData.nftAsset, + loanData.nftTokenId, + msg.sender + ); + } DataTypes.TimeLockParams memory timeLockParams; IPToken(xTokenAddress).transferUnderlyingTo( address(this), - borrowAmount, + tmpVar.totalBorrowAmount, timeLockParams ); - IERC20(weth).approve(address(lendPool), borrowAmount); + IERC20(weth).approve(address(lendPool), tmpVar.totalBorrowAmount); - lendPool.repay(loanData.nftAsset, loanData.nftTokenId, borrowAmount); + lendPool.batchRepay( + tmpVar.nftAssets, + tmpVar.tokenIds, + tmpVar.borrowAmounts + ); + + supplyAssets(ps, tmpVar); - (nftAsset, tokenId) = (loanData.nftAsset, loanData.nftTokenId); + borrowAmount = tmpVar.totalBorrowAmount; } - function supplyNFTandBorrowWETH( + function supplyAssets( DataTypes.PoolStorage storage ps, - IPoolAddressesProvider poolAddressProvider, PositionMoverVars memory tmpVar ) internal { DataTypes.ERC721SupplyParams[] - memory tokenData = new DataTypes.ERC721SupplyParams[](1); - tokenData[0] = DataTypes.ERC721SupplyParams({ - tokenId: tmpVar.tokenId, + memory tokensToSupply = new DataTypes.ERC721SupplyParams[]( + tmpVar.tokenIds.length + ); + + address currentSupplyAsset = tmpVar.nftAssets[0]; + uint256 supplySize = 1; + tokensToSupply[0] = DataTypes.ERC721SupplyParams({ + tokenId: tmpVar.tokenIds[0], useAsCollateral: true }); + for (uint256 index = 0; index < tmpVar.tokenIds.length; index++) { + if ( + index + 1 < tmpVar.tokenIds.length && + tmpVar.nftAssets[index] == tmpVar.nftAssets[index + 1] + ) { + tokensToSupply[supplySize] = DataTypes.ERC721SupplyParams({ + tokenId: tmpVar.tokenIds[index + 1], + useAsCollateral: true + }); + supplySize++; + } else { + reduceArrayAndSupply( + ps, + currentSupplyAsset, + tokensToSupply, + supplySize + ); + if (index + 1 < tmpVar.tokenIds.length) { + currentSupplyAsset = tmpVar.nftAssets[index + 1]; + tokensToSupply = tokensToSupply = new DataTypes.ERC721SupplyParams[]( + tmpVar.tokenIds.length + ); + tokensToSupply[0] = DataTypes.ERC721SupplyParams({ + tokenId: tmpVar.tokenIds[index + 1], + useAsCollateral: true + }); + supplySize = 1; + } + } + } + } + + function reduceArrayAndSupply( + DataTypes.PoolStorage storage ps, + address asset, + DataTypes.ERC721SupplyParams[] memory tokensToSupply, + uint256 subArraySize + ) internal { + subArraySize = tokensToSupply.length - subArraySize; + if (subArraySize > 0 && tokensToSupply.length - subArraySize > 0) { + assembly { + mstore(tokensToSupply, sub(mload(tokensToSupply), subArraySize)) + } + } + + // supply the current asset and tokens SupplyLogic.executeSupplyERC721( ps._reserves, ps._usersConfig[msg.sender], DataTypes.ExecuteSupplyERC721Params({ - asset: tmpVar.nftAsset, - tokenData: tokenData, + asset: asset, + tokenData: tokensToSupply, onBehalfOf: msg.sender, payer: msg.sender, referralCode: 0x0 }) ); + } + function borrowWETH( + DataTypes.PoolStorage storage ps, + IPoolAddressesProvider poolAddressProvider, + address weth, + uint256 borrowAmount + ) internal { BorrowLogic.executeBorrow( ps._reserves, ps._reservesList, ps._usersConfig[msg.sender], DataTypes.ExecuteBorrowParams({ - asset: tmpVar.weth, + asset: weth, user: msg.sender, onBehalfOf: msg.sender, - amount: tmpVar.borrowAmount, + amount: borrowAmount, referralCode: 0x0, releaseUnderlying: false, reservesCount: ps._reservesCount, From c75d7807f8c7728cd0d407d57eaef610c141c261 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 30 May 2023 01:44:20 +0300 Subject: [PATCH 2/5] chore: fixes minor issue and adds tests --- .../libraries/logic/PositionMoverLogic.sol | 3 +++ test/_pool_position_mover.spec.ts | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/contracts/protocol/libraries/logic/PositionMoverLogic.sol b/contracts/protocol/libraries/logic/PositionMoverLogic.sol index 97ff121b8..1bbd02943 100644 --- a/contracts/protocol/libraries/logic/PositionMoverLogic.sol +++ b/contracts/protocol/libraries/logic/PositionMoverLogic.sol @@ -87,6 +87,8 @@ library PositionMoverLogic { tmpVar.totalBorrowAmount += tmpVar.borrowAmounts[index]; + tmpVar.nftAssets[index] = loanData.nftAsset; + tmpVar.tokenIds[index] = loanData.nftTokenId; emit PositionMoved( loanData.nftAsset, loanData.nftTokenId, @@ -146,6 +148,7 @@ library PositionMoverLogic { tokensToSupply, supplySize ); + if (index + 1 < tmpVar.tokenIds.length) { currentSupplyAsset = tmpVar.nftAssets[index + 1]; tokensToSupply = tokensToSupply = new DataTypes.ERC721SupplyParams[]( diff --git a/test/_pool_position_mover.spec.ts b/test/_pool_position_mover.spec.ts index bcb621459..76263f668 100644 --- a/test/_pool_position_mover.spec.ts +++ b/test/_pool_position_mover.spec.ts @@ -23,24 +23,34 @@ describe("Pool: rescue tokens", () => { const { users: [user1, user2], bayc, + mayc, pool, } = testEnv; bendDaoLendPool = await getMockBendDaoLendPool(); await mintAndValidate(bayc, "5", user1); + await mintAndValidate(mayc, "5", user1); await bayc .connect(user1.signer) .transferFrom(user1.address, user2.address, 2); + await waitForTx( await bayc.connect(user1.signer).setApprovalForAll(pool.address, true) ); - + await waitForTx( + await mayc.connect(user1.signer).setApprovalForAll(pool.address, true) + ); await waitForTx( await bayc .connect(user1.signer) .setApprovalForAll(bendDaoLendPool.address, true) ); + await waitForTx( + await mayc + .connect(user1.signer) + .setApprovalForAll(bendDaoLendPool.address, true) + ); await waitForTx( await bayc.connect(user2.signer).setApprovalForAll(pool.address, true) ); @@ -183,6 +193,7 @@ describe("Pool: rescue tokens", () => { users: [user1], pool, bayc, + mayc, variableDebtWeth, nBAYC, } = testEnv; @@ -195,12 +206,14 @@ describe("Pool: rescue tokens", () => { 2 ); + await bendDaoLendPool.setLoan(5, mayc.address, 1, user1.address, "20", 2); + await expect( - await pool.connect(user1.signer).movePositionFromBendDAO([3, 4]) + await pool.connect(user1.signer).movePositionFromBendDAO([3, 4, 5]) ); await expect(await variableDebtWeth.balanceOf(user1.address)).to.be.eq( - "600000" + "600020" ); await expect(await nBAYC.balanceOf(user1.address)).to.be.eq(3); }); From 56c22658e873348005199ff90b80f9ad1ee3ba5b Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 30 May 2023 03:05:48 +0300 Subject: [PATCH 3/5] chore: remove redundant operations --- .../libraries/logic/PositionMoverLogic.sol | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/contracts/protocol/libraries/logic/PositionMoverLogic.sol b/contracts/protocol/libraries/logic/PositionMoverLogic.sol index 1bbd02943..09eaf00ff 100644 --- a/contracts/protocol/libraries/logic/PositionMoverLogic.sol +++ b/contracts/protocol/libraries/logic/PositionMoverLogic.sol @@ -119,9 +119,10 @@ library PositionMoverLogic { DataTypes.PoolStorage storage ps, PositionMoverVars memory tmpVar ) internal { + uint256 tokenIdsLength = tmpVar.tokenIds.length; DataTypes.ERC721SupplyParams[] memory tokensToSupply = new DataTypes.ERC721SupplyParams[]( - tmpVar.tokenIds.length + tokenIdsLength ); address currentSupplyAsset = tmpVar.nftAssets[0]; @@ -130,14 +131,16 @@ library PositionMoverLogic { tokenId: tmpVar.tokenIds[0], useAsCollateral: true }); + uint256 nextIndex; - for (uint256 index = 0; index < tmpVar.tokenIds.length; index++) { + for (uint256 index = 0; index < tokenIdsLength; index++) { + nextIndex = index + 1; if ( - index + 1 < tmpVar.tokenIds.length && - tmpVar.nftAssets[index] == tmpVar.nftAssets[index + 1] + nextIndex < tokenIdsLength && + tmpVar.nftAssets[index] == tmpVar.nftAssets[nextIndex] ) { tokensToSupply[supplySize] = DataTypes.ERC721SupplyParams({ - tokenId: tmpVar.tokenIds[index + 1], + tokenId: tmpVar.tokenIds[nextIndex], useAsCollateral: true }); supplySize++; @@ -149,13 +152,13 @@ library PositionMoverLogic { supplySize ); - if (index + 1 < tmpVar.tokenIds.length) { - currentSupplyAsset = tmpVar.nftAssets[index + 1]; + if (nextIndex < tokenIdsLength) { + currentSupplyAsset = tmpVar.nftAssets[nextIndex]; tokensToSupply = tokensToSupply = new DataTypes.ERC721SupplyParams[]( - tmpVar.tokenIds.length + tokenIdsLength ); tokensToSupply[0] = DataTypes.ERC721SupplyParams({ - tokenId: tmpVar.tokenIds[index + 1], + tokenId: tmpVar.tokenIds[nextIndex], useAsCollateral: true }); supplySize = 1; @@ -170,10 +173,11 @@ library PositionMoverLogic { DataTypes.ERC721SupplyParams[] memory tokensToSupply, uint256 subArraySize ) internal { - subArraySize = tokensToSupply.length - subArraySize; - if (subArraySize > 0 && tokensToSupply.length - subArraySize > 0) { + uint256 itemsToTrim = tokensToSupply.length - subArraySize; + + if (itemsToTrim != 0 && subArraySize != 0) { assembly { - mstore(tokensToSupply, sub(mload(tokensToSupply), subArraySize)) + mstore(tokensToSupply, sub(mload(tokensToSupply), itemsToTrim)) } } From c1d87b6211c091600671d1648c7a116f8fb0a6b7 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Wed, 31 May 2023 02:28:51 +0300 Subject: [PATCH 4/5] chore: adds comments and minor changes --- .../libraries/logic/PositionMoverLogic.sol | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/contracts/protocol/libraries/logic/PositionMoverLogic.sol b/contracts/protocol/libraries/logic/PositionMoverLogic.sol index 09eaf00ff..0108f2242 100644 --- a/contracts/protocol/libraries/logic/PositionMoverLogic.sol +++ b/contracts/protocol/libraries/logic/PositionMoverLogic.sol @@ -132,7 +132,18 @@ library PositionMoverLogic { useAsCollateral: true }); uint256 nextIndex; - + /** + The following logic divides the array of tokenIds into sub-arrays based on the asset in a greedy logic. + Then uses the sub-arrays as an inout to the supply logic to reduce the number of supplies. + Example1: + input: [BAYCToken1, BAYCToken2, MAYCToken1, MAYCToken1, BAKCToken1] + output: [BAYCToken1, BAYCToken2] [MAYCToken1, MAYCToken1] [BAKCToken1] (3 supply calls) + + Example2: + input: [BAYCToken1, MAYCToken1, BAYCToken2, MAYCToken1, BAKCToken1] + output: [BAYCToken1] [MAYCToken1] [BAYCToken2] [MAYCToken1] [BAKCToken1] (5 supply calls) + Note: To optimi + */ for (uint256 index = 0; index < tokenIdsLength; index++) { nextIndex = index + 1; if ( @@ -154,7 +165,7 @@ library PositionMoverLogic { if (nextIndex < tokenIdsLength) { currentSupplyAsset = tmpVar.nftAssets[nextIndex]; - tokensToSupply = tokensToSupply = new DataTypes.ERC721SupplyParams[]( + tokensToSupply = new DataTypes.ERC721SupplyParams[]( tokenIdsLength ); tokensToSupply[0] = DataTypes.ERC721SupplyParams({ @@ -173,11 +184,9 @@ library PositionMoverLogic { DataTypes.ERC721SupplyParams[] memory tokensToSupply, uint256 subArraySize ) internal { - uint256 itemsToTrim = tokensToSupply.length - subArraySize; - - if (itemsToTrim != 0 && subArraySize != 0) { + if (tokensToSupply.length - subArraySize != 0 && subArraySize != 0) { assembly { - mstore(tokensToSupply, sub(mload(tokensToSupply), itemsToTrim)) + mstore(tokensToSupply, subArraySize) } } From 76c94d1eac63c42f99643e60c71f512df55227d1 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Mon, 26 Jun 2023 02:54:25 +0200 Subject: [PATCH 5/5] chore: test permissions --- test/_pool_position_mover.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/_pool_position_mover.spec.ts b/test/_pool_position_mover.spec.ts index 76263f668..2e7513a88 100644 --- a/test/_pool_position_mover.spec.ts +++ b/test/_pool_position_mover.spec.ts @@ -215,6 +215,7 @@ describe("Pool: rescue tokens", () => { await expect(await variableDebtWeth.balanceOf(user1.address)).to.be.eq( "600020" ); + await expect(await nBAYC.balanceOf(user1.address)).to.be.eq(3); }); });