Skip to content

Commit

Permalink
✨ installments: add receiver
Browse files Browse the repository at this point in the history
  • Loading branch information
itofarina committed Oct 4, 2024
1 parent 4edd9e4 commit bddac8a
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 55 deletions.
22 changes: 12 additions & 10 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
60 changes: 29 additions & 31 deletions contracts/periphery/InstallmentsRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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();
}

Expand All @@ -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
_;
}
}
Expand Down
50 changes: 36 additions & 14 deletions test/InstallmentsRouter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand All @@ -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++) {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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);

Expand Down Expand Up @@ -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");
}

Expand All @@ -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 { }
}

0 comments on commit bddac8a

Please sign in to comment.