From e1d53c675786602142f06aa282747fc820ed55e6 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Wed, 12 Jul 2023 20:05:47 +0200 Subject: [PATCH 1/6] feat: repay/withdraw all --- src/Blue.sol | 7 ++++++ test/forge/Blue.t.sol | 56 +++++++++++++++++++++++++++++++++---------- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/Blue.sol b/src/Blue.sol index 17ad531bb..16550ec41 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -121,6 +121,9 @@ contract Blue { function withdraw(Market calldata market, uint256 amount) external { Id id = market.toId(); require(lastUpdate[id] != 0, "unknown market"); + if (amount == type(uint256).max) { + amount = supplyShare[id][msg.sender].toAssetsDown(totalSupply[id], totalSupplyShares[id]); + } require(amount != 0, "zero amount"); accrueInterests(market, id); @@ -160,6 +163,9 @@ contract Blue { function repay(Market calldata market, uint256 amount) external { Id id = market.toId(); require(lastUpdate[id] != 0, "unknown market"); + if (amount == type(uint256).max) { + amount = borrowShare[id][msg.sender].toAssetsUp(totalBorrow[id], totalBorrowShares[id]); + } require(amount != 0, "zero amount"); accrueInterests(market, id); @@ -191,6 +197,7 @@ contract Blue { function withdrawCollateral(Market calldata market, uint256 amount) external { Id id = market.toId(); require(lastUpdate[id] != 0, "unknown market"); + if (amount == type(uint256).max) amount = collateral[id][msg.sender]; require(amount != 0, "zero amount"); accrueInterests(market, id); diff --git a/test/forge/Blue.t.sol b/test/forge/Blue.t.sol index a710ea17f..4c4cb882a 100644 --- a/test/forge/Blue.t.sol +++ b/test/forge/Blue.t.sol @@ -275,6 +275,20 @@ contract BlueTest is Test { ); } + function testWithdrawAll(uint256 amountLent, uint256 amountBorrowed) public { + amountLent = bound(amountLent, 1, 2 ** 64); + amountBorrowed = bound(amountBorrowed, 1, 2 ** 64); + vm.assume(amountLent >= amountBorrowed); + + borrowableAsset.setBalance(address(this), amountLent); + blue.supply(market, amountLent); + blue.withdraw(market, type(uint256).max); + + assertEq(blue.supplyShare(id, address(this)), 0, "supply share"); + assertEq(borrowableAsset.balanceOf(address(this)), amountLent, "this balance"); + assertEq(borrowableAsset.balanceOf(address(blue)), 0, "blue balance"); + } + function testCollateralRequirements( uint256 amountCollateral, uint256 amountBorrowed, @@ -332,6 +346,23 @@ contract BlueTest is Test { assertEq(borrowableAsset.balanceOf(address(blue)), amountLent - amountBorrowed + amountRepaid, "blue balance"); } + function testRepayAll(uint256 amountLent, uint256 amountBorrowed) public { + amountLent = bound(amountLent, 1, 2 ** 64); + amountBorrowed = bound(amountBorrowed, 1, amountLent); + + borrowableAsset.setBalance(address(this), amountLent); + blue.supply(market, amountLent); + + vm.startPrank(BORROWER); + blue.borrow(market, amountBorrowed); + blue.repay(market, type(uint256).max); + vm.stopPrank(); + + assertEq(blue.borrowShare(id, BORROWER), 0, "borrow share"); + assertEq(borrowableAsset.balanceOf(BORROWER), 0, "BORROWER balance"); + assertEq(borrowableAsset.balanceOf(address(blue)), amountLent, "blue balance"); + } + function testSupplyCollateral(uint256 amount) public { amount = bound(amount, 1, 2 ** 64); @@ -363,6 +394,18 @@ contract BlueTest is Test { assertEq(collateralAsset.balanceOf(address(blue)), amountDeposited - amountWithdrawn, "blue balance"); } + function testWithdrawCollateralAll(uint256 amountDeposited) public { + amountDeposited = bound(amountDeposited, 1, 2 ** 64); + + collateralAsset.setBalance(address(this), amountDeposited); + blue.supplyCollateral(market, amountDeposited); + blue.withdrawCollateral(market, type(uint256).max); + + assertEq(blue.collateral(id, address(this)), 0, "this collateral"); + assertEq(collateralAsset.balanceOf(address(this)), amountDeposited, "this balance"); + assertEq(collateralAsset.balanceOf(address(blue)), 0, "blue balance"); + } + function testLiquidate(uint256 amountLent) public { borrowableOracle.setPrice(1e18); amountLent = bound(amountLent, 1000, 2 ** 64); @@ -521,19 +564,6 @@ contract BlueTest is Test { vm.expectRevert("zero amount"); blue.liquidate(market, address(0), 0); } - - function testEmptyMarket(uint256 amount) public { - amount = bound(amount, 1, type(uint256).max / SharesMath.VIRTUAL_SHARES); - - vm.expectRevert(stdError.arithmeticError); - blue.withdraw(market, amount); - - vm.expectRevert(stdError.arithmeticError); - blue.repay(market, amount); - - vm.expectRevert(stdError.arithmeticError); - blue.withdrawCollateral(market, amount); - } } function neq(Market memory a, Market memory b) pure returns (bool) { From d5036b71a49aed283faaa295ceae2c3248ca0f7a Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Sat, 15 Jul 2023 17:49:21 +0200 Subject: [PATCH 2/6] test: re-add empty market tests --- test/forge/Blue.t.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/forge/Blue.t.sol b/test/forge/Blue.t.sol index 4c4cb882a..32b2ffde5 100644 --- a/test/forge/Blue.t.sol +++ b/test/forge/Blue.t.sol @@ -564,6 +564,19 @@ contract BlueTest is Test { vm.expectRevert("zero amount"); blue.liquidate(market, address(0), 0); } + + function testEmptyMarket(uint256 amount) public { + amount = bound(amount, 1, type(uint256).max / SharesMath.VIRTUAL_SHARES); + + vm.expectRevert(stdError.arithmeticError); + blue.withdraw(market, amount); + + vm.expectRevert(stdError.arithmeticError); + blue.repay(market, amount); + + vm.expectRevert(stdError.arithmeticError); + blue.withdrawCollateral(market, amount); + } } function neq(Market memory a, Market memory b) pure returns (bool) { From 24cdfb49bc2dfb62fd5b7d0ce12d09311b8769c1 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 19 Jul 2023 11:54:41 +0200 Subject: [PATCH 3/6] fix(repay/withdraw): edge case --- src/Blue.sol | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/Blue.sol b/src/Blue.sol index 79faa011b..9a30bea4a 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -121,18 +121,22 @@ contract Blue { function withdraw(Market calldata market, uint256 amount, address onBehalf) external { Id id = market.id(); require(lastUpdate[id] != 0, "unknown market"); - if (amount == type(uint256).max) { - amount = supplyShare[id][msg.sender].toAssetsDown(totalSupply[id], totalSupplyShares[id]); - } - require(amount != 0, "zero amount"); require(isSenderOrIsApproved(onBehalf), "not approved"); accrueInterests(market, id); - uint256 shares = amount.toSharesUp(totalSupply[id], totalSupplyShares[id]); + uint256 shares; + if (amount == type(uint256).max) { + amount = supplyShare[id][onBehalf].toAssetsDown(totalSupply[id], totalSupplyShares[id]); + shares = supplyShare[id][onBehalf]; + } else { + shares = amount.toSharesUp(totalSupply[id], totalSupplyShares[id]); + } + + require(amount != 0, "zero amount"); + supplyShare[id][onBehalf] -= shares; totalSupplyShares[id] -= shares; - totalSupply[id] -= amount; require(totalBorrow[id] <= totalSupply[id], "not enough liquidity"); @@ -165,17 +169,21 @@ contract Blue { function repay(Market calldata market, uint256 amount, address onBehalf) external { Id id = market.id(); require(lastUpdate[id] != 0, "unknown market"); + + accrueInterests(market, id); + + uint256 shares; if (amount == type(uint256).max) { - amount = borrowShare[id][msg.sender].toAssetsUp(totalBorrow[id], totalBorrowShares[id]); + amount = supplyShare[id][onBehalf].toAssetsUp(totalSupply[id], totalSupplyShares[id]); + shares = supplyShare[id][onBehalf]; + } else { + shares = amount.toSharesDown(totalSupply[id], totalSupplyShares[id]); } - require(amount != 0, "zero amount"); - accrueInterests(market, id); + require(amount != 0, "zero amount"); - uint256 shares = amount.toSharesDown(totalBorrow[id], totalBorrowShares[id]); borrowShare[id][onBehalf] -= shares; totalBorrowShares[id] -= shares; - totalBorrow[id] -= amount; market.borrowableAsset.safeTransferFrom(msg.sender, address(this), amount); From ea73dd38ce6bea152cc26aa1b027abff6eecadfc Mon Sep 17 00:00:00 2001 From: Romain Milon Date: Wed, 19 Jul 2023 14:08:14 +0200 Subject: [PATCH 4/6] Update src/Blue.sol Co-authored-by: Jean-Grimal <83286814+Jean-Grimal@users.noreply.github.com> Signed-off-by: Romain Milon --- src/Blue.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Blue.sol b/src/Blue.sol index 9a30bea4a..3f5a23c2e 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -174,8 +174,8 @@ contract Blue { uint256 shares; if (amount == type(uint256).max) { - amount = supplyShare[id][onBehalf].toAssetsUp(totalSupply[id], totalSupplyShares[id]); - shares = supplyShare[id][onBehalf]; + amount = borrowShare[id][onBehalf].toAssetsUp(totalBorrow[id], totalBorrowShares[id]); + shares = borrowShare[id][onBehalf]; } else { shares = amount.toSharesDown(totalSupply[id], totalSupplyShares[id]); } From ede9cee8f4b417b47a548c935d7b91f54b6ec2e2 Mon Sep 17 00:00:00 2001 From: Romain Milon Date: Wed, 19 Jul 2023 14:10:03 +0200 Subject: [PATCH 5/6] Update src/Blue.sol Signed-off-by: Romain Milon --- src/Blue.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blue.sol b/src/Blue.sol index 3f5a23c2e..42f6b5752 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -177,7 +177,7 @@ contract Blue { amount = borrowShare[id][onBehalf].toAssetsUp(totalBorrow[id], totalBorrowShares[id]); shares = borrowShare[id][onBehalf]; } else { - shares = amount.toSharesDown(totalSupply[id], totalSupplyShares[id]); + shares = amount.toSharesDown(totalBorrow[id], totalBorrowShares[id]); } require(amount != 0, "zero amount"); From 783983abd6ce176ff9cc99073303bfd25934f7a0 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Tue, 1 Aug 2023 11:30:06 +0200 Subject: [PATCH 6/6] test(blue): update repay/withdraw all tests --- test/forge/Blue.t.sol | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/test/forge/Blue.t.sol b/test/forge/Blue.t.sol index 14573aee1..2917a2e92 100644 --- a/test/forge/Blue.t.sol +++ b/test/forge/Blue.t.sol @@ -378,17 +378,18 @@ contract BlueTest is ); } - function testWithdrawAll(uint256 amountLent, uint256 amountBorrowed) public { + function testWithdrawAll(uint256 amountLent, uint256 amountBorrowed, address receiver) public { + vm.assume(receiver != address(blue)); amountLent = bound(amountLent, 1, 2 ** 64); amountBorrowed = bound(amountBorrowed, 1, 2 ** 64); vm.assume(amountLent >= amountBorrowed); borrowableAsset.setBalance(address(this), amountLent); - blue.supply(market, amountLent, address(this)); - blue.withdraw(market, type(uint256).max, address(this)); + blue.supply(market, amountLent, address(this), hex""); + blue.withdraw(market, type(uint256).max, address(this), receiver); assertEq(blue.supplyShare(id, address(this)), 0, "supply share"); - assertEq(borrowableAsset.balanceOf(address(this)), amountLent, "this balance"); + assertEq(borrowableAsset.balanceOf(receiver), amountLent, "receiver balance"); assertEq(borrowableAsset.balanceOf(address(blue)), 0, "blue balance"); } @@ -481,15 +482,15 @@ contract BlueTest is amountBorrowed = bound(amountBorrowed, 1, amountLent); borrowableAsset.setBalance(address(this), amountLent); - blue.supply(market, amountLent, address(this)); + blue.supply(market, amountLent, address(this), hex""); vm.startPrank(BORROWER); - blue.borrow(market, amountBorrowed, BORROWER); - blue.repay(market, type(uint256).max, BORROWER); + blue.borrow(market, amountBorrowed, BORROWER, BORROWER); + blue.repay(market, type(uint256).max, BORROWER, hex""); vm.stopPrank(); assertEq(blue.borrowShare(id, BORROWER), 0, "borrow share"); - assertEq(borrowableAsset.balanceOf(BORROWER), 0, "BORROWER balance"); + assertEq(borrowableAsset.balanceOf(BORROWER), 0, "receiver balance"); assertEq(borrowableAsset.balanceOf(address(blue)), amountLent, "blue balance"); } @@ -526,15 +527,16 @@ contract BlueTest is assertEq(collateralAsset.balanceOf(address(blue)), amountDeposited - amountWithdrawn, "blue balance"); } - function testWithdrawCollateralAll(uint256 amountDeposited) public { + function testWithdrawCollateralAll(uint256 amountDeposited, address receiver) public { + vm.assume(receiver != address(blue)); amountDeposited = bound(amountDeposited, 1, 2 ** 64); collateralAsset.setBalance(address(this), amountDeposited); - blue.supplyCollateral(market, amountDeposited, address(this)); - blue.withdrawCollateral(market, type(uint256).max, address(this)); + blue.supplyCollateral(market, amountDeposited, address(this), hex""); + blue.withdrawCollateral(market, type(uint256).max, address(this), receiver); assertEq(blue.collateral(id, address(this)), 0, "this collateral"); - assertEq(collateralAsset.balanceOf(address(this)), amountDeposited, "this balance"); + assertEq(collateralAsset.balanceOf(receiver), amountDeposited, "receiver balance"); assertEq(collateralAsset.balanceOf(address(blue)), 0, "blue balance"); }