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

test: add lltv shifting and authorization double spend tests #449

Merged
merged 4 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
41 changes: 27 additions & 14 deletions test/forge/BaseTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ contract BaseTest is Test {
uint256 internal constant MAX_TEST_AMOUNT = 1e28;
uint256 internal constant MIN_TEST_SHARES = MIN_TEST_AMOUNT * SharesMathLib.VIRTUAL_SHARES;
uint256 internal constant MAX_TEST_SHARES = MAX_TEST_AMOUNT * SharesMathLib.VIRTUAL_SHARES;
uint256 internal constant MIN_TEST_LLTV = 0.01 ether;
uint256 internal constant MAX_TEST_LLTV = 0.99 ether;
uint256 internal constant MIN_COLLATERAL_PRICE = 1e10;
uint256 internal constant MAX_COLLATERAL_PRICE = 1e40;
uint256 internal constant MAX_COLLATERAL_ASSETS = type(uint128).max;
Expand All @@ -36,15 +38,14 @@ contract BaseTest is Test {
address internal LIQUIDATOR = _addrFromHashedString("Morpho Liquidator");
address internal OWNER = _addrFromHashedString("Morpho Owner");

uint256 internal constant LLTV = 0.8 ether;

Morpho internal morpho;
ERC20 internal borrowableToken;
ERC20 internal collateralToken;
Oracle internal oracle;
Irm internal irm;
MarketParams internal marketParams;
Id internal id;
uint256 internal lltv;

function setUp() public virtual {
vm.label(OWNER, "Owner");
Expand Down Expand Up @@ -74,14 +75,8 @@ contract BaseTest is Test {
irm = new Irm();
vm.label(address(irm), "IRM");

marketParams =
MarketParams(address(borrowableToken), address(collateralToken), address(oracle), address(irm), LLTV);
id = marketParams.id();

vm.startPrank(OWNER);
morpho.enableIrm(address(irm));
morpho.enableLltv(LLTV);
morpho.createMarket(marketParams);
vm.stopPrank();

borrowableToken.approve(address(morpho), type(uint256).max);
Expand All @@ -108,6 +103,20 @@ contract BaseTest is Test {
morpho.setAuthorization(BORROWER, true);
vm.stopPrank();

_setLltv(0.8 ether);
}

function _setLltv(uint256 _lltv) internal {
lltv = _lltv;
marketParams =
MarketParams(address(borrowableToken), address(collateralToken), address(oracle), address(irm), _lltv);
id = marketParams.id();

vm.startPrank(OWNER);
if (!morpho.isLltvEnabled(_lltv)) morpho.enableLltv(_lltv);
if (morpho.lastUpdate(marketParams.id()) == 0) morpho.createMarket(marketParams);
vm.stopPrank();

vm.roll(block.number + 1);
vm.warp(block.timestamp + 1 days);
}
Expand Down Expand Up @@ -170,17 +179,21 @@ contract BaseTest is Test {
return (amountCollateral, amountBorrowed, priceCollateral);
}

function _boundValidLltv(uint256 lltv) internal view returns (uint256) {
return bound(lltv, 0, WAD - 1);
function _boundTestLltv(uint256 _lltv) internal view returns (uint256) {
return bound(_lltv, MIN_TEST_LLTV, MAX_TEST_LLTV);
}

function _boundValidLltv(uint256 _lltv) internal view returns (uint256) {
return bound(_lltv, 0, WAD - 1);
}

function _boundInvalidLltv(uint256 lltv) internal view returns (uint256) {
return bound(lltv, WAD, type(uint256).max);
function _boundInvalidLltv(uint256 _lltv) internal view returns (uint256) {
return bound(_lltv, WAD, type(uint256).max);
}

function _liquidationIncentive(uint256 lltv) internal pure returns (uint256) {
function _liquidationIncentive(uint256 _lltv) internal pure returns (uint256) {
return
UtilsLib.min(MAX_LIQUIDATION_INCENTIVE_FACTOR, WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - lltv)));
UtilsLib.min(MAX_LIQUIDATION_INCENTIVE_FACTOR, WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - _lltv)));
}

function _accrueInterest(MarketParams memory market) internal {
Expand Down
18 changes: 18 additions & 0 deletions test/forge/integration/AuthorizationIntegrationTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,22 @@ contract AuthorizationIntegrationTest is BaseTest {
assertEq(morpho.isAuthorized(authorization.authorizer, authorization.authorized), authorization.isAuthorized);
assertEq(morpho.nonce(authorization.authorizer), 1);
}

function testAuthorizationFailsWithReusedSig(Authorization memory authorization, uint256 privateKey) public {
authorization.deadline = bound(authorization.deadline, block.timestamp + 1, type(uint256).max);

// Private key must be less than the secp256k1 curve order.
privateKey = bound(privateKey, 1, type(uint32).max);
authorization.nonce = 0;
authorization.authorizer = vm.addr(privateKey);

Signature memory sig;
bytes32 digest = SigUtils.getTypedDataHash(morpho.DOMAIN_SEPARATOR(), authorization);
(sig.v, sig.r, sig.s) = vm.sign(privateKey, digest);

morpho.setAuthorizationWithSig(authorization, sig);

vm.expectRevert(bytes(ErrorsLib.INVALID_NONCE));
morpho.setAuthorizationWithSig(authorization, sig);
}
}
12 changes: 6 additions & 6 deletions test/forge/integration/CreateMarketIntegrationTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ contract CreateMarketIntegrationTest is BaseTest {
using MarketParamsLib for MarketParams;

function testCreateMarketWithNotEnabledIrmAndNotEnabledLltv(MarketParams memory marketParamsFuzz) public {
vm.assume(marketParamsFuzz.irm != address(irm) && marketParamsFuzz.lltv != LLTV);
vm.assume(marketParamsFuzz.irm != address(irm) && marketParamsFuzz.lltv != lltv);

vm.prank(OWNER);
vm.expectRevert(bytes(ErrorsLib.IRM_NOT_ENABLED));
Expand All @@ -21,15 +21,15 @@ contract CreateMarketIntegrationTest is BaseTest {
marketParamsFuzz.lltv = _boundValidLltv(marketParamsFuzz.lltv);

vm.startPrank(OWNER);
if (marketParamsFuzz.lltv != LLTV) morpho.enableLltv(marketParamsFuzz.lltv);
if (marketParamsFuzz.lltv != lltv) morpho.enableLltv(marketParamsFuzz.lltv);

vm.expectRevert(bytes(ErrorsLib.IRM_NOT_ENABLED));
morpho.createMarket(marketParamsFuzz);
vm.stopPrank();
}

function testCreateMarketWithEnabledIrmAndNotEnabledLltv(MarketParams memory marketParamsFuzz) public {
vm.assume(marketParamsFuzz.lltv != LLTV);
vm.assume(marketParamsFuzz.lltv != lltv);

vm.startPrank(OWNER);
if (marketParamsFuzz.irm != marketParams.irm) morpho.enableIrm(marketParamsFuzz.irm);
Expand All @@ -45,7 +45,7 @@ contract CreateMarketIntegrationTest is BaseTest {

vm.startPrank(OWNER);
if (marketParamsFuzz.irm != marketParams.irm) morpho.enableIrm(marketParamsFuzz.irm);
if (marketParamsFuzz.lltv != LLTV) morpho.enableLltv(marketParamsFuzz.lltv);
if (marketParamsFuzz.lltv != lltv) morpho.enableLltv(marketParamsFuzz.lltv);

vm.expectEmit(true, true, true, true, address(morpho));
emit EventsLib.CreateMarket(marketParamsFuzz.id(), marketParamsFuzz);
Expand All @@ -65,7 +65,7 @@ contract CreateMarketIntegrationTest is BaseTest {

vm.startPrank(OWNER);
if (marketParamsFuzz.irm != marketParams.irm) morpho.enableIrm(marketParamsFuzz.irm);
if (marketParamsFuzz.lltv != LLTV) morpho.enableLltv(marketParamsFuzz.lltv);
if (marketParamsFuzz.lltv != lltv) morpho.enableLltv(marketParamsFuzz.lltv);
morpho.createMarket(marketParamsFuzz);

vm.expectRevert(bytes(ErrorsLib.MARKET_ALREADY_CREATED));
Expand All @@ -79,7 +79,7 @@ contract CreateMarketIntegrationTest is BaseTest {

vm.startPrank(OWNER);
if (marketParamsFuzz.irm != marketParams.irm) morpho.enableIrm(marketParamsFuzz.irm);
if (marketParamsFuzz.lltv != LLTV) morpho.enableLltv(marketParamsFuzz.lltv);
if (marketParamsFuzz.lltv != lltv) morpho.enableLltv(marketParamsFuzz.lltv);

morpho.createMarket(marketParamsFuzz);
vm.stopPrank();
Expand Down
27 changes: 19 additions & 8 deletions test/forge/integration/LiquidateIntegrationTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ contract LiquidateIntegrationTest is BaseTest {
using MorphoLib for Morpho;
using SharesMathLib for uint256;

function testLiquidateNotCreatedMarket(MarketParams memory marketParamsFuzz) public {
function testLiquidateNotCreatedMarket(MarketParams memory marketParamsFuzz, uint256 lltv) public {
_setLltv(_boundTestLltv(lltv));
vm.assume(neq(marketParamsFuzz, marketParams));

vm.expectRevert(bytes(ErrorsLib.MARKET_NOT_CREATED));
morpho.liquidate(marketParamsFuzz, address(this), 1, 0, hex"");
}

function testLiquidateZeroAmount() public {
function testLiquidateZeroAmount(uint256 lltv) public {
_setLltv(_boundTestLltv(lltv));
vm.prank(BORROWER);

vm.expectRevert(bytes(ErrorsLib.INCONSISTENT_INPUT));
Expand All @@ -37,12 +39,14 @@ contract LiquidateIntegrationTest is BaseTest {
uint256 amountSupplied,
uint256 amountBorrowed,
uint256 amountSeized,
uint256 priceCollateral
uint256 priceCollateral,
uint256 lltv
) public {
_setLltv(_boundTestLltv(lltv));
(amountCollateral, amountBorrowed, priceCollateral) =
_boundHealthyPosition(amountCollateral, amountBorrowed, priceCollateral);

amountSupplied = bound(amountSupplied, amountBorrowed, MAX_TEST_AMOUNT);
amountSupplied = bound(amountSupplied, amountBorrowed, amountBorrowed + MAX_TEST_AMOUNT);
_supply(amountSupplied);

amountSeized = bound(amountSeized, 1, amountCollateral);
Expand All @@ -67,18 +71,21 @@ contract LiquidateIntegrationTest is BaseTest {
uint256 amountSupplied,
uint256 amountBorrowed,
uint256 amountSeized,
uint256 priceCollateral
uint256 priceCollateral,
uint256 lltv
) public {
_setLltv(_boundTestLltv(lltv));
(amountCollateral, amountBorrowed, priceCollateral) =
_boundUnhealthyPosition(amountCollateral, amountBorrowed, priceCollateral);

vm.assume(amountCollateral > 1);

amountSupplied = bound(amountSupplied, amountBorrowed, MAX_TEST_AMOUNT);
amountSupplied = bound(amountSupplied, amountBorrowed, amountBorrowed + MAX_TEST_AMOUNT);
_supply(amountSupplied);

uint256 incentive = _liquidationIncentive(marketParams.lltv);
uint256 maxSeized = amountBorrowed.wMulDown(incentive).mulDivDown(ORACLE_PRICE_SCALE, priceCollateral);
vm.assume(maxSeized != 0);
amountSeized = bound(amountSeized, 1, min(maxSeized, amountCollateral - 1));
uint256 expectedRepaid = amountSeized.mulDivUp(priceCollateral, ORACLE_PRICE_SCALE).wDivUp(incentive);

Expand Down Expand Up @@ -129,8 +136,10 @@ contract LiquidateIntegrationTest is BaseTest {
uint256 amountSupplied,
uint256 amountBorrowed,
uint256 sharesRepaid,
uint256 priceCollateral
uint256 priceCollateral,
uint256 lltv
) public {
_setLltv(_boundTestLltv(lltv));
(amountCollateral, amountBorrowed, priceCollateral) =
_boundUnhealthyPosition(amountCollateral, amountBorrowed, priceCollateral);

Expand Down Expand Up @@ -204,8 +213,10 @@ contract LiquidateIntegrationTest is BaseTest {
uint256 amountCollateral,
uint256 amountSupplied,
uint256 amountBorrowed,
uint256 priceCollateral
uint256 priceCollateral,
uint256 lltv
) public {
_setLltv(_boundTestLltv(lltv));
LiquidateBadDebtTestParams memory params;

(amountCollateral, amountBorrowed, priceCollateral) =
Expand Down
6 changes: 3 additions & 3 deletions test/forge/integration/OnlyOwnerIntegrationTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ contract OnlyOwnerIntegrationTest is BaseTest {

function testEnableLltvWhenNotOwner(address addressFuzz, uint256 lltvFuzz) public {
vm.assume(addressFuzz != OWNER);
vm.assume(lltvFuzz != LLTV);
vm.assume(lltvFuzz != lltv);

vm.prank(addressFuzz);
vm.expectRevert(bytes(ErrorsLib.NOT_OWNER));
Expand All @@ -75,7 +75,7 @@ contract OnlyOwnerIntegrationTest is BaseTest {
function testEnableLltvAlreadySet() public {
vm.prank(OWNER);
vm.expectRevert(bytes(ErrorsLib.ALREADY_SET));
morpho.enableLltv(LLTV);
morpho.enableLltv(lltv);
}

function testEnableTooHighLltv(uint256 lltvFuzz) public {
Expand All @@ -88,7 +88,7 @@ contract OnlyOwnerIntegrationTest is BaseTest {

function testEnableLltv(uint256 lltvFuzz) public {
lltvFuzz = _boundValidLltv(lltvFuzz);
vm.assume(lltvFuzz != LLTV);
vm.assume(lltvFuzz != lltv);

vm.prank(OWNER);
vm.expectEmit(true, true, true, true, address(morpho));
Expand Down
4 changes: 2 additions & 2 deletions test/forge/invariant/TwoMarketsInvariantTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ contract TwoMarketsInvariantTest is InvariantTest {
vm.label(address(irm2), "IRM2");

marketParams2 =
MarketParams(address(borrowableToken), address(collateralToken), address(oracle), address(irm2), LLTV + 1);
MarketParams(address(borrowableToken), address(collateralToken), address(oracle), address(irm2), lltv + 1);
id2 = marketParams2.id();

vm.startPrank(OWNER);
morpho.enableIrm(address(irm2));
morpho.enableLltv(LLTV + 1);
morpho.enableLltv(lltv + 1);
morpho.createMarket(marketParams2);
vm.stopPrank();

Expand Down
4 changes: 2 additions & 2 deletions test/forge/libraries/periphery/MorphoBalancesLibTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,13 @@ contract MorphoBalancesLibTest is BaseTest {
if (amountBorrowed > 0) {
uint256 collateralPrice = IOracle(marketParams.oracle).price();
collateralToken.setBalance(
BORROWER, amountBorrowed.wDivUp(LLTV).mulDivUp(ORACLE_PRICE_SCALE, collateralPrice)
BORROWER, amountBorrowed.wDivUp(lltv).mulDivUp(ORACLE_PRICE_SCALE, collateralPrice)
);

vm.startPrank(BORROWER);
morpho.supplyCollateral(
marketParams,
amountBorrowed.wDivUp(LLTV).mulDivUp(ORACLE_PRICE_SCALE, collateralPrice),
amountBorrowed.wDivUp(lltv).mulDivUp(ORACLE_PRICE_SCALE, collateralPrice),
BORROWER,
hex""
);
Expand Down
4 changes: 2 additions & 2 deletions test/forge/libraries/periphery/MorphoLibTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ contract MorphoLibTest is BaseTest {
morpho.supply(marketParams, amountSupplied, 0, address(this), hex"");

uint256 collateralPrice = IOracle(marketParams.oracle).price();
collateralToken.setBalance(BORROWER, amountBorrowed.wDivUp(LLTV).mulDivUp(ORACLE_PRICE_SCALE, collateralPrice));
collateralToken.setBalance(BORROWER, amountBorrowed.wDivUp(lltv).mulDivUp(ORACLE_PRICE_SCALE, collateralPrice));

vm.startPrank(BORROWER);
morpho.supplyCollateral(
marketParams, amountBorrowed.wDivUp(LLTV).mulDivUp(ORACLE_PRICE_SCALE, collateralPrice), BORROWER, hex""
marketParams, amountBorrowed.wDivUp(lltv).mulDivUp(ORACLE_PRICE_SCALE, collateralPrice), BORROWER, hex""
);
morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
vm.stopPrank();
Expand Down
8 changes: 4 additions & 4 deletions test/forge/libraries/periphery/MorphoStorageLibTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ contract MorphoStorageLibTest is BaseTest {
morpho.supply(marketParams, amountSupplied, 0, address(this), hex"");

uint256 collateralPrice = IOracle(marketParams.oracle).price();
collateralToken.setBalance(BORROWER, amountBorrowed.wDivUp(LLTV).mulDivUp(ORACLE_PRICE_SCALE, collateralPrice));
collateralToken.setBalance(BORROWER, amountBorrowed.wDivUp(lltv).mulDivUp(ORACLE_PRICE_SCALE, collateralPrice));

vm.startPrank(BORROWER);
morpho.supplyCollateral(
marketParams, amountBorrowed.wDivUp(LLTV).mulDivUp(ORACLE_PRICE_SCALE, collateralPrice), BORROWER, hex""
marketParams, amountBorrowed.wDivUp(lltv).mulDivUp(ORACLE_PRICE_SCALE, collateralPrice), BORROWER, hex""
);
morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
vm.stopPrank();
Expand Down Expand Up @@ -63,7 +63,7 @@ contract MorphoStorageLibTest is BaseTest {
slots[5] = MorphoStorageLib.marketTotalBorrowAssetsAndSharesSlot(id);
slots[6] = MorphoStorageLib.marketLastUpdateAndFeeSlot(id);
slots[7] = MorphoStorageLib.isIrmEnabledSlot(address(irm));
slots[8] = MorphoStorageLib.isLltvEnabledSlot(LLTV);
slots[8] = MorphoStorageLib.isLltvEnabledSlot(lltv);
slots[9] = MorphoStorageLib.isAuthorizedSlot(authorizer, BORROWER);
slots[10] = MorphoStorageLib.nonceSlot(authorizer);
slots[11] = MorphoStorageLib.idToBorrowableTokenSlot(id);
Expand All @@ -86,7 +86,7 @@ contract MorphoStorageLibTest is BaseTest {
assertEq(uint128(uint256(values[6])), morpho.lastUpdate(id));
assertEq(uint256(values[6] >> 128), morpho.fee(id));
assertEq(abi.decode(abi.encode(values[7]), (bool)), morpho.isIrmEnabled(address(irm)));
assertEq(abi.decode(abi.encode(values[8]), (bool)), morpho.isLltvEnabled(LLTV));
assertEq(abi.decode(abi.encode(values[8]), (bool)), morpho.isLltvEnabled(lltv));
assertEq(abi.decode(abi.encode(values[9]), (bool)), morpho.isAuthorized(authorizer, BORROWER));
assertEq(uint256(values[10]), morpho.nonce(authorizer));

Expand Down