Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

repay/withdraw shares input #194

Merged
merged 34 commits into from
Aug 5, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e1d53c6
feat: repay/withdraw all
MerlinEgalite Jul 12, 2023
d5036b7
test: re-add empty market tests
MerlinEgalite Jul 15, 2023
8264939
Merge branch 'refactor/morpho-utils' of github.com:morpho-labs/blue i…
Rubilmax Jul 19, 2023
24cdfb4
fix(repay/withdraw): edge case
Rubilmax Jul 19, 2023
ea73dd3
Update src/Blue.sol
Rubilmax Jul 19, 2023
ede9cee
Update src/Blue.sol
Rubilmax Jul 19, 2023
2e471cc
Merge pull request #149 from morpho-labs/refactor/repay-withdraw-all-…
Rubilmax Jul 20, 2023
2b8c672
Merge branch 'main' of github.com:morpho-labs/blue into feat/repay-wi…
Rubilmax Jul 21, 2023
1b98702
Merge branch 'main' of github.com:morpho-labs/blue into feat/repay-wi…
Rubilmax Jul 26, 2023
a202085
feat: repay/withdraw with shares
MathisGD Jul 28, 2023
0b37029
Merge branch 'main' of github.com:morpho-labs/blue into feat/repay-wi…
Rubilmax Aug 1, 2023
783983a
test(blue): update repay/withdraw all tests
Rubilmax Aug 1, 2023
0814a37
Merge branch 'main' into feat/repay-withdraw-max-alternative
MathisGD Aug 1, 2023
c72e36f
Merge branch 'feat/repay-withdraw-all' into feat/repay-withdraw-max-a…
MathisGD Aug 1, 2023
7ad349f
fix: Market in memory
MathisGD Aug 1, 2023
e40c970
fix: Market in memory
MathisGD Aug 1, 2023
f7e0442
test: improve testing of withdraw/repay
MathisGD Aug 3, 2023
ea9bc73
test: repair repay/withdraw amount tests
MathisGD Aug 3, 2023
aec3ddf
test: fix testWithdrawShares
MathisGD Aug 3, 2023
884aedf
test: withdraw/repay exact amount
MathisGD Aug 4, 2023
fc1fa9e
feat(shares-math): add util to get shares from amount
Rubilmax Aug 4, 2023
4deb905
fix(shares-math): implement Mathis' formula
Rubilmax Aug 4, 2023
7679fee
test: use dichotomy to withdraw/repay exact amount
MathisGD Aug 4, 2023
42dca9a
refactor(libraries): add blueLib
Rubilmax Aug 4, 2023
fd93b63
Merge branch 'main' of github.com:morpho-labs/blue into feat/repay-wi…
Rubilmax Aug 4, 2023
85f75e1
Merge branch 'feat/repay-withdraw-all' of github.com:morpho-labs/blue…
Rubilmax Aug 4, 2023
6c3f418
Merge branch 'main' of github.com:morpho-labs/blue into feat/repay-wi…
Rubilmax Aug 4, 2023
eb3e2ba
Merge branch 'feat/repay-withdraw-all' of github.com:morpho-labs/blue…
Rubilmax Aug 4, 2023
46e7c48
Merge branch 'feat/repay-withdraw-max-alternative' of github.com:morp…
Rubilmax Aug 4, 2023
e29346f
refactor(shares-math): more precise upper bound
Rubilmax Aug 4, 2023
2233661
refactor(shares-math): rename utils
Rubilmax Aug 4, 2023
c49c6f7
Merge pull request #221 from morpho-labs/test/repay-withdraw-shares-util
MathisGD Aug 4, 2023
9be7701
chore: blue lib visibility issue
MathisGD Aug 4, 2023
713c487
test: fix compilation issue
MathisGD Aug 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/Blue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -151,18 +151,18 @@ contract Blue is IFlashLender {
market.borrowableAsset.safeTransferFrom(msg.sender, address(this), amount);
}

function withdraw(Market memory market, uint256 amount, address onBehalf, address receiver) external {
function withdraw(Market calldata market, uint256 shares, address onBehalf, address receiver) external {
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
Id id = market.id();
require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED);
require(amount != 0, Errors.ZERO_AMOUNT);
require(shares != 0, Errors.ZERO_AMOUNT);
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
require(_isSenderAuthorized(onBehalf), Errors.UNAUTHORIZED);

_accrueInterests(market, id);

uint256 shares = amount.toSharesUp(totalSupply[id], totalSupplyShares[id]);
uint256 amount = shares.toAssetsDown(totalSupply[id], totalSupplyShares[id]);

supplyShare[id][onBehalf] -= shares;
totalSupplyShares[id] -= shares;

totalSupply[id] -= amount;

require(totalBorrow[id] <= totalSupply[id], Errors.INSUFFICIENT_LIQUIDITY);
Expand Down Expand Up @@ -192,17 +192,17 @@ contract Blue is IFlashLender {
market.borrowableAsset.safeTransfer(receiver, amount);
}

function repay(Market memory market, uint256 amount, address onBehalf, bytes calldata data) external {
function repay(Market calldata market, uint256 shares, address onBehalf, bytes calldata data) external {
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
Id id = market.id();
require(shares != 0, Errors.ZERO_AMOUNT);
require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED);
require(amount != 0, Errors.ZERO_AMOUNT);

_accrueInterests(market, id);

uint256 shares = amount.toSharesDown(totalBorrow[id], totalBorrowShares[id]);
uint256 amount = shares.toAssetsUp(totalBorrow[id], totalBorrowShares[id]);

borrowShare[id][onBehalf] -= shares;
totalBorrowShares[id] -= shares;

totalBorrow[id] -= amount;

if (data.length > 0) IBlueRepayCallback(msg.sender).onBlueRepay(amount, data);
Expand Down
113 changes: 80 additions & 33 deletions test/forge/Blue.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "forge-std/console.sol";
import {SigUtils} from "./helpers/SigUtils.sol";

import "src/Blue.sol";
import {SharesMath} from "src/libraries/SharesMath.sol";
import {
IBlueLiquidateCallback,
IBlueRepayCallback,
Expand Down Expand Up @@ -339,12 +340,11 @@ contract BlueTest is
assertEq(borrowableAsset.balanceOf(address(blue)), amountLent - amountBorrowed, "blue balance");
}

function testWithdraw(uint256 amountLent, uint256 amountWithdrawn, uint256 amountBorrowed, address receiver)
function testWithdraw(uint256 amountLent, uint256 sharesWithdrawn, uint256 amountBorrowed, address receiver)
public
{
vm.assume(receiver != address(blue));
amountLent = bound(amountLent, 1, 2 ** 64);
amountWithdrawn = bound(amountWithdrawn, 1, 2 ** 64);
amountBorrowed = bound(amountBorrowed, 1, 2 ** 64);
vm.assume(amountLent >= amountBorrowed);

Expand All @@ -354,30 +354,45 @@ contract BlueTest is
vm.prank(BORROWER);
blue.borrow(market, amountBorrowed, BORROWER, BORROWER);

uint256 supplyShareBefore = blue.supplyShare(id, address(this));
sharesWithdrawn = bound(sharesWithdrawn, 1, 2 ** 64);
uint256 amountWithdrawn =
SharesMath.toAssetsDown(sharesWithdrawn, blue.totalSupply(id), blue.totalSupplyShares(id));

if (amountWithdrawn > amountLent - amountBorrowed) {
if (amountWithdrawn > amountLent) {
if (sharesWithdrawn > blue.supplyShare(id, address(this))) {
vm.expectRevert();
} else {
vm.expectRevert(bytes(Errors.INSUFFICIENT_LIQUIDITY));
}
blue.withdraw(market, amountWithdrawn, address(this), receiver);
blue.withdraw(market, sharesWithdrawn, address(this), receiver);
return;
}

blue.withdraw(market, amountWithdrawn, address(this), receiver);
blue.withdraw(market, sharesWithdrawn, address(this), receiver);

assertApproxEqAbs(
blue.supplyShare(id, address(this)),
(amountLent - amountWithdrawn) * SharesMath.VIRTUAL_SHARES,
100,
"supply share"
);
assertEq(blue.supplyShare(id, address(this)), supplyShareBefore - sharesWithdrawn, "supply share");
assertEq(borrowableAsset.balanceOf(receiver), amountWithdrawn, "receiver balance");
assertEq(
borrowableAsset.balanceOf(address(blue)), amountLent - amountBorrowed - amountWithdrawn, "blue balance"
);
}

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), hex"");
blue.withdraw(market, blue.supplyShare(id, address(this)), address(this), receiver);

assertEq(blue.supplyShare(id, address(this)), 0, "supply share");
assertEq(borrowableAsset.balanceOf(receiver), amountLent, "receiver balance");
assertEq(borrowableAsset.balanceOf(address(blue)), 0, "blue balance");
}

function testCollateralRequirements(
uint256 amountCollateral,
uint256 amountBorrowed,
Expand Down Expand Up @@ -412,56 +427,70 @@ contract BlueTest is
}
}

function testRepay(uint256 amountLent, uint256 amountBorrowed, uint256 amountRepaid) public {
function testRepay(uint256 amountLent, uint256 amountBorrowed, uint256 sharesRepaid) public {
amountLent = bound(amountLent, 1, 2 ** 64);
amountBorrowed = bound(amountBorrowed, 1, amountLent);
amountRepaid = bound(amountRepaid, 1, amountBorrowed);

borrowableAsset.setBalance(address(this), amountLent);
blue.supply(market, amountLent, address(this), hex"");

vm.startPrank(BORROWER);
vm.prank(BORROWER);
blue.borrow(market, amountBorrowed, BORROWER, BORROWER);
blue.repay(market, amountRepaid, BORROWER, hex"");
vm.stopPrank();

assertApproxEqAbs(
blue.borrowShare(id, BORROWER),
(amountBorrowed - amountRepaid) * SharesMath.VIRTUAL_SHARES,
100,
"borrow share"
);
uint256 borrowShareBefore = blue.borrowShare(id, BORROWER);
sharesRepaid = bound(sharesRepaid, 1, borrowShareBefore);
uint256 amountRepaid = SharesMath.toAssetsUp(sharesRepaid, blue.totalBorrow(id), blue.totalBorrowShares(id));
vm.prank(BORROWER);
blue.repay(market, sharesRepaid, BORROWER, hex"");

assertEq(blue.borrowShare(id, BORROWER), borrowShareBefore - sharesRepaid, "borrow share");
assertEq(borrowableAsset.balanceOf(BORROWER), amountBorrowed - amountRepaid, "BORROWER balance");
assertEq(borrowableAsset.balanceOf(address(blue)), amountLent - amountBorrowed + amountRepaid, "blue balance");
}

function testRepayOnBehalf(uint256 amountLent, uint256 amountBorrowed, uint256 amountRepaid, address onBehalf)
function testRepayOnBehalf(uint256 amountLent, uint256 amountBorrowed, uint256 sharesRepaid, address onBehalf)
public
{
vm.assume(onBehalf != address(blue));
vm.assume(onBehalf != address(this));
amountLent = bound(amountLent, 1, 2 ** 64);
amountBorrowed = bound(amountBorrowed, 1, amountLent);
amountRepaid = bound(amountRepaid, 1, amountBorrowed);

borrowableAsset.setBalance(address(this), amountLent + amountRepaid);
borrowableAsset.setBalance(address(this), amountLent);
blue.supply(market, amountLent, address(this), hex"");

vm.prank(onBehalf);
blue.borrow(market, amountBorrowed, onBehalf, onBehalf);

blue.repay(market, amountRepaid, onBehalf, hex"");
uint256 borrowShareBefore = blue.borrowShare(id, onBehalf);
sharesRepaid = bound(sharesRepaid, 1, borrowShareBefore);
uint256 amountRepaid = SharesMath.toAssetsUp(sharesRepaid, blue.totalBorrow(id), blue.totalBorrowShares(id));
borrowableAsset.setBalance(address(this), amountRepaid);

assertApproxEqAbs(
blue.borrowShare(id, onBehalf),
(amountBorrowed - amountRepaid) * SharesMath.VIRTUAL_SHARES,
100,
"borrow share"
);
blue.repay(market, sharesRepaid, onBehalf, hex"");

assertEq(blue.borrowShare(id, onBehalf), borrowShareBefore - sharesRepaid, "borrow share");
assertEq(borrowableAsset.balanceOf(onBehalf), amountBorrowed, "onBehalf balance");
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, address(this), hex"");

vm.startPrank(BORROWER);
blue.borrow(market, amountBorrowed, BORROWER, BORROWER);
blue.repay(market, blue.borrowShare(id, BORROWER), BORROWER, hex"");
vm.stopPrank();

assertEq(blue.borrowShare(id, BORROWER), 0, "borrow share");
assertEq(borrowableAsset.balanceOf(BORROWER), 0, "receiver balance");
assertEq(borrowableAsset.balanceOf(address(blue)), amountLent, "blue balance");
}

function testSupplyCollateralOnBehalf(uint256 amount, address onBehalf) public {
vm.assume(onBehalf != address(blue));
amount = bound(amount, 1, 2 ** 64);
Expand Down Expand Up @@ -495,6 +524,19 @@ contract BlueTest is
assertEq(collateralAsset.balanceOf(address(blue)), amountDeposited - amountWithdrawn, "blue balance");
}

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), hex"");
blue.withdrawCollateral(market, blue.collateral(id, address(this)), address(this), receiver);

assertEq(blue.collateral(id, address(this)), 0, "this collateral");
assertEq(collateralAsset.balanceOf(receiver), amountDeposited, "receiver balance");
assertEq(collateralAsset.balanceOf(address(blue)), 0, "blue balance");
}

function testLiquidate(uint256 amountLent) public {
borrowableOracle.setPrice(1e18);
amountLent = bound(amountLent, 1000, 2 ** 64);
Expand Down Expand Up @@ -830,7 +872,12 @@ contract BlueTest is
);
assertGt(blue.borrowShare(market.id(), address(this)), 0, "no borrow");

blue.repay(market, toBorrow, address(this), abi.encode(this.testFlashActions.selector, abi.encode(amount)));
blue.repay(
market,
blue.borrowShare(id, address(this)),
address(this),
abi.encode(this.testFlashActions.selector, abi.encode(amount))
);
assertEq(blue.collateral(market.id(), address(this)), 0, "no withdraw collateral");
}

Expand Down
15 changes: 9 additions & 6 deletions test/hardhat/Blue.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,25 +129,28 @@ describe("Blue", () => {

const user = signers[i];

let amount = BigNumber.WAD.mul(1 + Math.floor(random() * 100));

let amount = BigNumber.WAD.mul(1 + Math.floor(random() * 100));
makcandrov marked this conversation as resolved.
Show resolved Hide resolved
if (random() < 2 / 3) {
const totalSupply = await blue.totalSupply(id);
const totalSupplyShares = await blue.totalSupplyShares(id);
Promise.all([
blue.connect(user).supply(market, amount, user.address, []),
blue.connect(user).withdraw(market, amount.div(2), user.address, user.address),
blue.connect(user).withdraw(market, amount.mul(totalSupplyShares.add(BigNumber.WAD)).div(totalSupply.add(1)).div(2), user.address, user.address),
]);
} else {
const totalSupply = await blue.totalSupply(id);
const totalBorrow = await blue.totalBorrow(id);
const totalBorrowShares = await blue.totalBorrowShares(id);
const liquidity = BigNumber.from(totalSupply).sub(BigNumber.from(totalBorrow));

amount = BigNumber.min(amount, BigNumber.from(liquidity).div(2));

if (amount > BigNumber.from(0)) {
Promise.all([
blue.connect(user).supplyCollateral(market, amount, user.address, []),
blue.connect(user).borrow(market, amount.div(2), user.address, user.address),
blue.connect(user).repay(market, amount.div(4), user.address, []),
blue.connect(user).repay(market, amount.mul(totalBorrowShares.add(BigNumber.WAD)).div(totalBorrow.add(1)).div(4), user.address, []),
blue.connect(user).withdrawCollateral(market, amount.div(8), user.address, user.address),
]);
}
Expand Down