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

Feat: invariant tests #231

Merged
merged 50 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
7ed77a4
shares invariant tests
Jean-Grimal Aug 7, 2023
2f214c7
add total amounts and shares ratio invariants tests
Jean-Grimal Aug 7, 2023
ac78914
Merge branch 'feat/tests' of github.com:morpho-labs/blue into feat/in…
Jean-Grimal Aug 8, 2023
9c4479b
fix: senders addresses
Jean-Grimal Aug 8, 2023
0b90b84
fix: naming
Jean-Grimal Aug 8, 2023
d9334bc
feat: merge with feat/tests
Jean-Grimal Aug 8, 2023
f5ccc76
fix: conflicts
Jean-Grimal Aug 8, 2023
98e8342
Delete TestInvariant.sol
Jean-Grimal Aug 8, 2023
b70d82d
Merge branch 'feat/tests' of github.com:morpho-labs/morpho-blue into …
Jean-Grimal Aug 9, 2023
2a721df
feat: add two markets invariant tests
Jean-Grimal Aug 9, 2023
385c54e
refactor: invariant tests
Jean-Grimal Aug 9, 2023
3125c7c
fix: no more reverts on invariant tests
Jean-Grimal Aug 9, 2023
3f655e6
feat: add single position invariant tests
Jean-Grimal Aug 10, 2023
25ee54d
feat: add changing price tests
Jean-Grimal Aug 10, 2023
69a3455
fix: reduce the number of reverts
Jean-Grimal Aug 10, 2023
5e7ad1f
Merge branch 'feat/tests' of github.com:morpho-labs/morpho-blue into …
Jean-Grimal Aug 11, 2023
7c1a2fa
refactor: single oracle and amount+shares inputs
Jean-Grimal Aug 11, 2023
3c3241b
refactor: morpho naming
Jean-Grimal Aug 11, 2023
10ea6f5
fix: reduce invariant runs
Jean-Grimal Aug 11, 2023
a301ad5
Merge branch 'feat/tests' of github.com:morpho-labs/morpho-blue into …
Jean-Grimal Aug 14, 2023
152a607
fix: tests after merges
Jean-Grimal Aug 14, 2023
569b190
Merge branch 'feat/tests' of github.com:morpho-labs/morpho-blue into …
Jean-Grimal Aug 14, 2023
c3ded54
fix: reverts because of too low collateral price
Jean-Grimal Aug 14, 2023
7361a55
Merge branch 'feat/tests' of github.com:morpho-labs/morpho-blue into …
Jean-Grimal Aug 14, 2023
cc09e57
fix: accrue interests with accrueInterests function
Jean-Grimal Aug 14, 2023
8cbc592
Merge branch 'feat/tests' of github.com:morpho-labs/morpho-blue into …
Jean-Grimal Aug 14, 2023
e5783a9
refactor: naming of mock ERC20
Jean-Grimal Aug 14, 2023
d3b4263
fix: forge fmt
Jean-Grimal Aug 14, 2023
91eb40a
fix: merge
Jean-Grimal Aug 16, 2023
7ffbfb3
Merge branch 'main' of github.com:morpho-labs/morpho-blue into feat/i…
Jean-Grimal Aug 16, 2023
6fae418
fix: sync with main
Jean-Grimal Aug 16, 2023
118638a
Merge branch 'main' of github.com:morpho-labs/morpho-blue into feat/i…
Jean-Grimal Aug 17, 2023
1564f1f
fix: warp bug
Jean-Grimal Aug 17, 2023
3cfdea6
fix: lint
Jean-Grimal Aug 17, 2023
e22f0a7
Merge branch 'main' of github.com:morpho-labs/morpho-blue into feat/i…
Jean-Grimal Aug 18, 2023
78ba3e4
fix: apply suggestions
Jean-Grimal Aug 18, 2023
cb39d16
fix: lint
Jean-Grimal Aug 18, 2023
f5daa57
Merge branch 'main' of github.com:morpho-labs/morpho-blue into feat/i…
Jean-Grimal Aug 18, 2023
9c89396
Merge branch 'main' of github.com:morpho-labs/morpho-blue into feat/i…
Jean-Grimal Aug 18, 2023
bf2d1a0
Merge branch 'main' of github.com:morpho-labs/morpho-blue into feat/i…
Jean-Grimal Aug 19, 2023
12eff77
fix: conflicts
Jean-Grimal Aug 19, 2023
b919184
Merge branch 'main' of github.com:morpho-labs/morpho-blue into feat/i…
Jean-Grimal Aug 20, 2023
17b8df5
Merge branch 'main' of github.com:morpho-labs/morpho-blue into feat/i…
Jean-Grimal Aug 21, 2023
d0fd553
Merge branch 'main' of github.com:morpho-labs/morpho-blue into feat/i…
Jean-Grimal Aug 21, 2023
b45366c
fix: apply suggestions
Jean-Grimal Aug 22, 2023
ad337a4
fix: conflicts
Jean-Grimal Aug 22, 2023
d92f616
Merge branch 'main' of github.com:morpho-labs/morpho-blue into feat/i…
Jean-Grimal Aug 22, 2023
6dd4d7d
fix: conflicts
Jean-Grimal Aug 22, 2023
0152b92
fix: lint
Jean-Grimal Aug 22, 2023
dd89809
Merge branch 'main' of github.com:morpho-labs/morpho-blue into feat/i…
Jean-Grimal Aug 22, 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
2 changes: 1 addition & 1 deletion foundry.toml
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ optimizer_runs = 4294967295
[fmt]
wrap_comments = true

# See more config options https://github.com/foundry-rs/foundry/tree/master/crates/config
# See more config options https://github.com/foundry-rs/foundry/tree/master/crates/config
8 changes: 4 additions & 4 deletions test/forge/BaseTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ contract BaseTest is Test {
MarketParams internal marketParams;
Id internal id;

function setUp() public {
function setUp() public virtual {
vm.label(OWNER, "Owner");
vm.label(SUPPLIER, "Supplier");
vm.label(BORROWER, "Borrower");
Expand Down Expand Up @@ -183,10 +183,10 @@ contract BaseTest is Test {
UtilsLib.min(MAX_LIQUIDATION_INCENTIVE_FACTOR, WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - lltv)));
}

function _accrueInterest() internal {
function _accrueInterest(MarketParams memory market) internal {
collateralToken.setBalance(address(this), 1);
morpho.supplyCollateral(marketParams, 1, address(this), hex"");
morpho.withdrawCollateral(marketParams, 1, address(this), address(10));
morpho.supplyCollateral(market, 1, address(this), hex"");
morpho.withdrawCollateral(market, 1, address(this), address(10));
}

function neq(MarketParams memory a, MarketParams memory b) internal pure returns (bool) {
Expand Down
214 changes: 214 additions & 0 deletions test/forge/InvariantBase.sol
Rubilmax marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "test/forge/BaseTest.sol";

contract InvariantBaseTest is BaseTest {
using MathLib for uint256;
using MorphoLib for Morpho;
using SharesMathLib for uint256;

uint256 blockNumber;
uint256 timestamp;

bytes4[] internal selectors;

address[] internal addressArray;

function setUp() public virtual override {
super.setUp();

targetContract(address(this));
}

function _targetDefaultSenders() internal {
targetSender(_addrFromHashedString("Morpho address1"));
targetSender(_addrFromHashedString("Morpho address2"));
targetSender(_addrFromHashedString("Morpho address3"));
targetSender(_addrFromHashedString("Morpho address4"));
targetSender(_addrFromHashedString("Morpho address5"));
targetSender(_addrFromHashedString("Morpho address6"));
targetSender(_addrFromHashedString("Morpho address7"));
targetSender(_addrFromHashedString("Morpho address8"));
}

function _weightSelector(bytes4 selector, uint256 weight) internal {
for (uint256 i; i < weight; ++i) {
selectors.push(selector);
}
}

function _approveSendersTransfers(address[] memory senders) internal {
for (uint256 i; i < senders.length; ++i) {
vm.startPrank(senders[i]);
borrowableToken.approve(address(morpho), type(uint256).max);
collateralToken.approve(address(morpho), type(uint256).max);
vm.stopPrank();
}
}

function _supplyHighAmountOfCollateralForAllSenders(address[] memory senders, MarketParams memory marketParams)
internal
{
for (uint256 i; i < senders.length; ++i) {
collateralToken.setBalance(senders[i], 1e30);
vm.prank(senders[i]);
morpho.supplyCollateral(marketParams, 1e30, senders[i], hex"");
}
}

/// @dev Apparently permanently setting block number and timestamp with cheatcodes in this function doesn't work,
/// they get reset to the ones defined in the set up function after each function call.
/// The solution we choose is to store these in storage, and set them with roll and warp cheatcodes with the
/// setCorrectBlock function at the the begenning of each function.
/// The purpose of this function is to increment these variables to simulate a new block.
function newBlock(uint256 elapsed) public {
Jean-Grimal marked this conversation as resolved.
Show resolved Hide resolved
elapsed = bound(elapsed, 10, 1 days);

blockNumber += 1;
timestamp += elapsed;
}

modifier setCorrectBlock() {
vm.roll(blockNumber);
vm.warp(timestamp);
_;
}

function _randomSenderToWithdrawOnBehalf(address[] memory addresses, address seed, address sender)
internal
returns (address randomSenderToWithdrawOnBehalf)
{
for (uint256 i; i < addresses.length; ++i) {
if (morpho.supplyShares(id, addresses[i]) != 0) {
addressArray.push(addresses[i]);
}
}
if (addressArray.length == 0) return address(0);

randomSenderToWithdrawOnBehalf = addressArray[uint256(uint160(seed)) % addressArray.length];
MathisGD marked this conversation as resolved.
Show resolved Hide resolved

vm.prank(randomSenderToWithdrawOnBehalf);
morpho.setAuthorization(sender, true);

delete addressArray;
}

function _randomSenderToBorrowOnBehalf(address[] memory addresses, address seed, address sender)
internal
returns (address randomSenderToBorrowOnBehalf)
{
for (uint256 i; i < addresses.length; ++i) {
if (morpho.collateral(id, addresses[i]) != 0 && isHealthy(id, addresses[i])) {
addressArray.push(addresses[i]);
}
}
if (addressArray.length == 0) return address(0);

randomSenderToBorrowOnBehalf = addressArray[uint256(uint160(seed)) % addressArray.length];

vm.prank(randomSenderToBorrowOnBehalf);
morpho.setAuthorization(sender, true);

delete addressArray;
}

function _randomSenderToRepayOnBehalf(address[] memory addresses, address seed)
internal
returns (address randomSenderToRepayOnBehalf)
{
for (uint256 i; i < addresses.length; ++i) {
if (morpho.borrowShares(id, addresses[i]) != 0) {
addressArray.push(addresses[i]);
}
}
if (addressArray.length == 0) return address(0);

randomSenderToRepayOnBehalf = addressArray[uint256(uint160(seed)) % addressArray.length];

delete addressArray;
}

function _randomSenderToWithdrawCollateralOnBehalf(address[] memory addresses, address seed, address sender)
internal
returns (address randomSenderToWithdrawCollateralOnBehalf)
{
for (uint256 i; i < addresses.length; ++i) {
if (morpho.collateral(id, addresses[i]) != 0 && isHealthy(id, addresses[i])) {
addressArray.push(addresses[i]);
}
}
if (addressArray.length == 0) return address(0);

randomSenderToWithdrawCollateralOnBehalf = addressArray[uint256(uint160(seed)) % addressArray.length];

vm.prank(randomSenderToWithdrawCollateralOnBehalf);
morpho.setAuthorization(sender, true);

delete addressArray;
}

function _randomSenderToLiquidate(address[] memory addresses, address seed)
internal
returns (address randomSenderToLiquidate)
{
for (uint256 i; i < addresses.length; ++i) {
if (morpho.borrowShares(id, addresses[i]) != 0 && !isHealthy(id, addresses[i])) {
addressArray.push(addresses[i]);
}
}
if (addressArray.length == 0) return address(0);

randomSenderToLiquidate = addressArray[uint256(uint160(seed)) % addressArray.length];

delete addressArray;
}

function sumUsersSupplyShares(address[] memory addresses) internal view returns (uint256 sum) {
Jean-Grimal marked this conversation as resolved.
Show resolved Hide resolved
for (uint256 i; i < addresses.length; ++i) {
sum += morpho.supplyShares(id, addresses[i]);
}
sum += morpho.supplyShares(id, morpho.feeRecipient());
}

function sumUsersBorrowShares(address[] memory addresses) internal view returns (uint256 sum) {
for (uint256 i; i < addresses.length; ++i) {
sum += morpho.borrowShares(id, addresses[i]);
}
}

function sumUsersSuppliedAmounts(address[] memory addresses) internal view returns (uint256 sum) {
for (uint256 i; i < addresses.length; ++i) {
sum += morpho.supplyShares(id, addresses[i]).toAssetsDown(
morpho.totalSupplyAssets(id), morpho.totalSupplyShares(id)
);
}
sum += morpho.supplyShares(id, morpho.feeRecipient()).toAssetsDown(
morpho.totalSupplyAssets(id), morpho.totalSupplyShares(id)
);
}

function sumUsersBorrowedAmounts(address[] memory addresses) internal view returns (uint256 sum) {
for (uint256 i; i < addresses.length; ++i) {
sum += morpho.borrowShares(id, addresses[i]).toAssetsUp(
morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id)
);
}
}

function isHealthy(Id id, address user) public view returns (bool) {
uint256 collateralPrice = IOracle(marketParams.oracle).price();

uint256 borrowed =
morpho.borrowShares(id, user).toAssetsUp(morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id));
uint256 maxBorrow =
morpho.collateral(id, user).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(marketParams.lltv);

return maxBorrow >= borrowed;
}

function _liquidationIncentiveFactor(uint256 lltv) internal pure returns (uint256) {
return
UtilsLib.min(MAX_LIQUIDATION_INCENTIVE_FACTOR, WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - lltv)));
}
}
4 changes: 2 additions & 2 deletions test/forge/integration/TestIntegrationAccrueInterest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ contract IntegrationAccrueInterestTest is BaseTest {
uint256 totalSupplyBeforeAccrued = morpho.totalSupplyAssets(id);
uint256 totalSupplySharesBeforeAccrued = morpho.totalSupplyShares(id);

_accrueInterest();
_accrueInterest(marketParams);

assertEq(morpho.totalBorrowAssets(id), totalBorrowBeforeAccrued, "total borrow");
assertEq(morpho.totalSupplyAssets(id), totalSupplyBeforeAccrued, "total supply");
Expand All @@ -59,7 +59,7 @@ contract IntegrationAccrueInterestTest is BaseTest {
uint256 totalSupplyBeforeAccrued = morpho.totalSupplyAssets(id);
uint256 totalSupplySharesBeforeAccrued = morpho.totalSupplyShares(id);

_accrueInterest();
_accrueInterest(marketParams);

assertEq(morpho.totalBorrowAssets(id), totalBorrowBeforeAccrued, "total borrow");
assertEq(morpho.totalSupplyAssets(id), totalSupplyBeforeAccrued, "total supply");
Expand Down
Loading