From bddac8a2ed3410fb770a212af55b7fdb19dae99c Mon Sep 17 00:00:00 2001 From: itofarina Date: Thu, 3 Oct 2024 17:40:34 -0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20installments:=20add=20receiver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 22 ++++---- contracts/periphery/InstallmentsRouter.sol | 60 +++++++++++----------- test/InstallmentsRouter.t.sol | 50 +++++++++++++----- 3 files changed, 77 insertions(+), 55 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 6ff33933..645b4619 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -127,16 +127,18 @@ EscrowedEXATest:testWithdrawFromUnknownStream() (gas: 1111321) EscrowedEXATest:testWithdrawMaxFromMultipleStreams() (gas: 1553619) EscrowedEXATest:testWithdrawMaxShouldGiveReserveBackWhenDepleted() (gas: 546979) EscrowedEXATest:testWithdrawMaxWithInvalidSender() (gas: 499105) -InstallmentsRouterTest:testAmountsLength() (gas: 36244) -InstallmentsRouterTest:testBorrowETHWithPermit() (gas: 1086693) -InstallmentsRouterTest:testBorrowRouter() (gas: 638449) -InstallmentsRouterTest:testBorrowUnwrappedETH() (gas: 722180) -InstallmentsRouterTest:testBorrowWithPermit() (gas: 1214775) -InstallmentsRouterTest:testFakeMarket() (gas: 47406) -InstallmentsRouterTest:testInsufficientMaxRepay() (gas: 630150) -InstallmentsRouterTest:testMaxRepay() (gas: 630153) -InstallmentsRouterTest:testMissingMarketWETH() (gas: 808074) -InstallmentsRouterTest:testMoreBorrowsThanMaxPools() (gas: 633994) +InstallmentsRouterTest:testAmountsLength() (gas: 36784) +InstallmentsRouterTest:testBorrowETHToAnotherReceiver() (gas: 755446) +InstallmentsRouterTest:testBorrowETHWithPermit() (gas: 1087336) +InstallmentsRouterTest:testBorrowRouter() (gas: 638991) +InstallmentsRouterTest:testBorrowToAnotherReceiver() (gas: 658388) +InstallmentsRouterTest:testBorrowUnwrappedETH() (gas: 722649) +InstallmentsRouterTest:testBorrowWithPermit() (gas: 1214797) +InstallmentsRouterTest:testFakeMarket() (gas: 47946) +InstallmentsRouterTest:testInsufficientMaxRepay() (gas: 630668) +InstallmentsRouterTest:testMaxRepay() (gas: 630695) +InstallmentsRouterTest:testMissingMarketWETH() (gas: 818663) +InstallmentsRouterTest:testMoreBorrowsThanMaxPools() (gas: 634534) InterestRateModelTest:testFixedBorrowRate() (gas: 2088868) InterestRateModelTest:testFixedRateRevertAlreadyMatured() (gas: 2083135) InterestRateModelTest:testFixedRateRevertUtilizationExceeded() (gas: 2090203) diff --git a/contracts/periphery/InstallmentsRouter.sol b/contracts/periphery/InstallmentsRouter.sol index 18256760..52204b47 100644 --- a/contracts/periphery/InstallmentsRouter.sol +++ b/contracts/periphery/InstallmentsRouter.sol @@ -39,22 +39,17 @@ contract InstallmentsRouter { /// @param amounts The amounts to borrow in each maturity. /// @param maxRepay The maximum amount of assets to repay. /// @return assetsOwed The amount of assets owed for each maturity. - function borrow( - Market market, - uint256 firstMaturity, - uint256[] calldata amounts, - uint256 maxRepay - ) external returns (uint256[] memory assetsOwed) { - return borrow(market, firstMaturity, amounts, maxRepay, msg.sender); + function borrow(Market market, uint256 firstMaturity, uint256[] calldata amounts, uint256 maxRepay, address receiver) + external + returns (uint256[] memory assetsOwed) + { + return _borrow(market, firstMaturity, amounts, maxRepay, receiver); } - function borrow( - Market market, - uint256 firstMaturity, - uint256[] calldata amounts, - uint256 maxRepay, - address receiver - ) internal returns (uint256[] memory assetsOwed) { + function _borrow(Market market, uint256 firstMaturity, uint256[] calldata amounts, uint256 maxRepay, address receiver) + internal + returns (uint256[] memory assetsOwed) + { assert(amounts.length > 1); checkMarket(market); @@ -77,7 +72,7 @@ contract InstallmentsRouter { uint256 maxRepay, Permit calldata marketPermit ) external permit(market, marketPermit) returns (uint256[] memory assetsOwed) { - return borrow(market, firstMaturity, amounts, maxRepay, msg.sender); + return _borrow(market, firstMaturity, amounts, maxRepay, msg.sender); } /// @notice Borrows WETH from the WETH Market in the subsequent maturities, @@ -86,16 +81,17 @@ contract InstallmentsRouter { /// @param amounts The amounts to borrow in each maturity. /// @param maxRepay The maximum amount of assets to repay. /// @return assetsOwed The amount of assets owed for each maturity. - function borrowETH( - uint256 maturity, - uint256[] calldata amounts, - uint256 maxRepay - ) external returns (uint256[] memory assetsOwed) { - assetsOwed = borrow(marketWETH, maturity, amounts, maxRepay, address(this)); + function borrowETH(uint256 maturity, uint256[] calldata amounts, uint256 maxRepay, address receiver) + external + returns (uint256[] memory assetsOwed) + { + assetsOwed = _borrow(marketWETH, maturity, amounts, maxRepay, address(this)); uint256 totalAmount = 0; - for (uint256 i = 0; i < amounts.length; ++i) totalAmount += amounts[i]; + for (uint256 i = 0; i < amounts.length; ++i) { + totalAmount += amounts[i]; + } weth.withdraw(totalAmount); - msg.sender.safeTransferETH(totalAmount); + receiver.safeTransferETH(totalAmount); } /// @notice Borrows WETH from the WETH Market in the subsequent maturities using permit, @@ -109,19 +105,22 @@ contract InstallmentsRouter { uint256 maturity, uint256[] calldata amounts, uint256 maxRepay, - Permit calldata marketPermit + Permit calldata marketPermit, + address receiver ) external permit(marketWETH, marketPermit) returns (uint256[] memory assetsOwed) { - assetsOwed = borrow(marketWETH, maturity, amounts, maxRepay, address(this)); + assetsOwed = _borrow(marketWETH, maturity, amounts, maxRepay, address(this)); uint256 totalAmount = 0; - for (uint256 i = 0; i < amounts.length; ++i) totalAmount += amounts[i]; + for (uint256 i = 0; i < amounts.length; ++i) { + totalAmount += amounts[i]; + } weth.withdraw(totalAmount); - msg.sender.safeTransferETH(totalAmount); + receiver.safeTransferETH(totalAmount); } /// @notice Reverts if the Market is not listed by the Auditor. /// @param market The Market to check. function checkMarket(Market market) internal view { - (, , , bool listed, ) = auditor.markets(market); + (,,, bool listed,) = auditor.markets(market); if (!listed) revert MarketNotListed(); } @@ -133,9 +132,8 @@ contract InstallmentsRouter { modifier permit(Market market, Permit calldata p) { // If the permit fails, the account may have already approved. This prevents DoS attacks. - try - IERC20PermitUpgradeable(address(market)).permit(msg.sender, address(this), p.value, p.deadline, p.v, p.r, p.s) - {} catch {} // solhint-disable-line no-empty-blocks + try IERC20PermitUpgradeable(address(market)).permit(msg.sender, address(this), p.value, p.deadline, p.v, p.r, p.s) { + } catch { } // solhint-disable-line no-empty-blocks _; } } diff --git a/test/InstallmentsRouter.t.sol b/test/InstallmentsRouter.t.sol index 12b2c355..04485db6 100644 --- a/test/InstallmentsRouter.t.sol +++ b/test/InstallmentsRouter.t.sol @@ -97,7 +97,7 @@ contract InstallmentsRouterTest is Test { amounts[0] = 10_000e6; amounts[1] = 10_000e6; amounts[2] = 10_000e6; - router.borrow(market, maturity, amounts, type(uint256).max); + router.borrow(market, maturity, amounts, type(uint256).max, address(this)); uint256 finalBalance = usdc.balanceOf(address(this)); assertEq(initialBalance + 30_000e6, finalBalance, "borrowed amounts are not correct"); } @@ -109,7 +109,7 @@ contract InstallmentsRouterTest is Test { amounts[1] = 10_000e6; amounts[2] = 10_000e6; uint256 maxRepay = 31_000e6; - uint256[] memory assetsOwed = router.borrow(market, maturity, amounts, maxRepay); + uint256[] memory assetsOwed = router.borrow(market, maturity, amounts, maxRepay, address(this)); uint256 totalOwed; for (uint256 i = 0; i < assetsOwed.length; i++) { @@ -125,7 +125,7 @@ contract InstallmentsRouterTest is Test { amounts[1] = 10_000e6; Market fake = Market(address(0)); vm.expectRevert(abi.encodeWithSelector(MarketNotListed.selector)); - router.borrow(fake, maturity, amounts, type(uint256).max); + router.borrow(fake, maturity, amounts, type(uint256).max, address(this)); } function testInsufficientMaxRepay() external { @@ -137,7 +137,7 @@ contract InstallmentsRouterTest is Test { uint256 maxRepay = 29_000e6; vm.expectRevert(Disagreement.selector); - router.borrow(market, maturity, amounts, maxRepay); + router.borrow(market, maturity, amounts, maxRepay, address(this)); } function testMoreBorrowsThanMaxPools() external { @@ -148,10 +148,8 @@ contract InstallmentsRouterTest is Test { amounts[2] = 10_000e6; amounts[3] = 10_000e6; - vm.expectRevert( - abi.encodeWithSelector(UnmatchedPoolState.selector, FixedLib.State.NOT_READY, FixedLib.State.VALID) - ); - router.borrow(market, maturity, amounts, type(uint256).max); + vm.expectRevert(abi.encodeWithSelector(UnmatchedPoolState.selector, FixedLib.State.NOT_READY, FixedLib.State.VALID)); + router.borrow(market, maturity, amounts, type(uint256).max, address(this)); } function testBorrowUnwrappedETH() external { @@ -162,13 +160,11 @@ contract InstallmentsRouterTest is Test { amounts[2] = 10_000e18; uint256 maxRepay = 32_000e18; uint256 balanceBefore = address(this).balance; - router.borrowETH(maturity, amounts, maxRepay); + router.borrowETH(maturity, amounts, maxRepay, address(this)); uint256 balanceAfter = address(this).balance; assertEq(balanceAfter, balanceBefore + 30_000e18, "borrow != expected"); } - receive() external payable {} - function testBorrowWithPermit() external { deal(address(weth), bob, 100_000e18); @@ -251,7 +247,7 @@ contract InstallmentsRouterTest is Test { uint256 balanceBefore = bob.balance; vm.prank(bob); - router.borrowETH(FixedLib.INTERVAL, amounts, maxRepay, Permit(maxRepay, block.timestamp, v, r, s)); + router.borrowETH(FixedLib.INTERVAL, amounts, maxRepay, Permit(maxRepay, block.timestamp, v, r, s), bob); assertEq(bob.balance, balanceBefore + 30_000e18, "borrow != expected"); } @@ -261,12 +257,38 @@ contract InstallmentsRouterTest is Test { amounts[0] = 10_000e6; vm.expectRevert(stdError.assertionError); - router.borrow(market, maturity, amounts, type(uint256).max); + router.borrow(market, maturity, amounts, type(uint256).max, address(this)); } function testMissingMarketWETH() external { router = new InstallmentsRouter(auditor, Market(address(0))); vm.expectRevert(abi.encodeWithSelector(MarketNotListed.selector)); - router.borrowETH(FixedLib.INTERVAL, new uint256[](3), type(uint256).max); + router.borrowETH(FixedLib.INTERVAL, new uint256[](3), type(uint256).max, address(this)); + } + + function testBorrowToAnotherReceiver() external { + uint256 maturity = FixedLib.INTERVAL; + uint256[] memory amounts = new uint256[](3); + amounts[0] = 10_000e6; + amounts[1] = 10_000e6; + amounts[2] = 10_000e6; + uint256 maxRepay = 32_000e6; + assertEq(usdc.balanceOf(bob), 0); + router.borrow(market, maturity, amounts, maxRepay, bob); + assertEq(usdc.balanceOf(bob), 30_000e6, "borrow != expected"); } + + function testBorrowETHToAnotherReceiver() external { + uint256 maturity = FixedLib.INTERVAL; + uint256[] memory amounts = new uint256[](3); + amounts[0] = 10_000e18; + amounts[1] = 10_000e18; + amounts[2] = 10_000e18; + uint256 maxRepay = 32_000e18; + assertEq(bob.balance, 0); + router.borrowETH(maturity, amounts, maxRepay, bob); + assertEq(bob.balance, 30_000e18, "borrow != expected"); + } + + receive() external payable { } }