Skip to content

Commit

Permalink
Merge pull request #498 from morpho-labs/refactor/tests
Browse files Browse the repository at this point in the history
refactor(test): fix various issues
  • Loading branch information
MathisGD authored Sep 27, 2023
2 parents 5de3552 + 278710b commit 11e69bb
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 60 deletions.
4 changes: 2 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ via-ir = true
optimizer_runs = 4294967295

[profile.default.invariant]
runs = 4
depth = 512
runs = 16
depth = 256
fail_on_revert = true

[profile.default.fmt]
Expand Down
32 changes: 8 additions & 24 deletions test/forge/BaseTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,28 @@ import {OracleMock} from "src/mocks/OracleMock.sol";
import "src/Morpho.sol";
import {Math} from "./helpers/Math.sol";
import {SigUtils} from "./helpers/SigUtils.sol";
import {ArrayLib} from "./helpers/ArrayLib.sol";
import {MorphoLib} from "src/libraries/periphery/MorphoLib.sol";
import {MorphoBalancesLib} from "src/libraries/periphery/MorphoBalancesLib.sol";

contract BaseTest is Test {
using Math for uint256;
using MathLib for uint256;
using SharesMathLib for uint256;
using ArrayLib for address[];
using MorphoLib for IMorpho;
using MorphoBalancesLib for IMorpho;
using MarketParamsLib for MarketParams;

uint256 internal constant BLOCK_TIME = 12;
uint256 internal constant BLOCK_TIME = 1;
uint256 internal constant HIGH_COLLATERAL_AMOUNT = 1e35;
uint256 internal constant MIN_TEST_AMOUNT = 100;
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 DEFAULT_TEST_LLTV = 0.8 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 Down Expand Up @@ -106,7 +109,7 @@ contract BaseTest is Test {
morpho.setAuthorization(BORROWER, true);
vm.stopPrank();

_setLltv(0.8 ether);
_setLltv(DEFAULT_TEST_LLTV);
}

function _setLltv(uint256 lltv) internal {
Expand All @@ -118,8 +121,7 @@ contract BaseTest is Test {
if (morpho.lastUpdate(marketParams.id()) == 0) morpho.createMarket(marketParams);
vm.stopPrank();

vm.roll(block.number + 1);
vm.warp(block.timestamp + 1 days);
_forward(1);
}

function _addrFromHashedString(string memory name) internal returns (address addr) {
Expand All @@ -135,7 +137,7 @@ contract BaseTest is Test {

/// @dev Bounds the fuzzing input to a realistic number of blocks.
function _boundBlocks(uint256 blocks) internal view returns (uint256) {
return bound(blocks, 1, type(uint24).max);
return bound(blocks, 1, type(uint32).max);
}

/// @dev Bounds the fuzzing input to a non-zero address.
Expand Down Expand Up @@ -403,26 +405,8 @@ contract BaseTest is Test {
return candidates[seed % candidates.length];
}

function _removeAll(address[] memory inputs, address removed) internal pure returns (address[] memory result) {
result = new address[](inputs.length);

uint256 nbAddresses;
for (uint256 i; i < inputs.length; ++i) {
address input = inputs[i];

if (input != removed) {
result[nbAddresses] = input;
++nbAddresses;
}
}

assembly {
mstore(result, nbAddresses)
}
}

function _randomNonZero(address[] memory users, uint256 seed) internal pure returns (address) {
users = _removeAll(users, address(0));
users = users.removeAll(address(0));

return _randomCandidate(users, seed);
}
Expand Down
22 changes: 22 additions & 0 deletions test/forge/helpers/ArrayLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

library ArrayLib {
function removeAll(address[] memory inputs, address removed) internal pure returns (address[] memory result) {
result = new address[](inputs.length);

uint256 nbAddresses;
for (uint256 i; i < inputs.length; ++i) {
address input = inputs[i];

if (input != removed) {
result[nbAddresses] = input;
++nbAddresses;
}
}

assembly {
mstore(result, nbAddresses)
}
}
}
36 changes: 14 additions & 22 deletions test/forge/integration/AccrueInterestIntegrationTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,14 @@ contract AccrueInterestIntegrationTest is BaseTest {
assertEq(morpho.supplyShares(id, FEE_RECIPIENT), 0, "feeRecipient's supply shares");
}

function testAccrueInterestNoBorrow(uint256 amountSupplied, uint256 timeElapsed) public {
function testAccrueInterestNoBorrow(uint256 amountSupplied, uint256 blocks) public {
amountSupplied = bound(amountSupplied, 2, MAX_TEST_AMOUNT);
timeElapsed = uint32(bound(timeElapsed, 1, type(uint32).max));
blocks = _boundBlocks(blocks);

loanToken.setBalance(address(this), amountSupplied);
morpho.supply(marketParams, amountSupplied, 0, address(this), hex"");

// New block.
vm.roll(block.number + 1);
vm.warp(block.timestamp + timeElapsed);
_forward(blocks);

uint256 totalBorrowBeforeAccrued = morpho.totalBorrowAssets(id);
uint256 totalSupplyBeforeAccrued = morpho.totalSupplyAssets(id);
Expand All @@ -60,12 +58,12 @@ contract AccrueInterestIntegrationTest is BaseTest {
assertEq(morpho.lastUpdate(id), block.timestamp, "last update");
}

function testAccrueInterestNoFee(uint256 amountSupplied, uint256 amountBorrowed, uint256 timeElapsed) public {
function testAccrueInterestNoFee(uint256 amountSupplied, uint256 amountBorrowed, uint256 blocks) public {
uint256 collateralPrice = oracle.price();
uint256 amountCollateral;
(amountCollateral, amountBorrowed,) = _boundHealthyPosition(amountCollateral, amountBorrowed, collateralPrice);
amountSupplied = bound(amountSupplied, amountBorrowed, MAX_TEST_AMOUNT);
timeElapsed = uint32(bound(timeElapsed, 1, type(uint32).max));
blocks = _boundBlocks(blocks);

loanToken.setBalance(address(this), amountSupplied);
loanToken.setBalance(address(this), amountSupplied);
Expand All @@ -79,15 +77,14 @@ contract AccrueInterestIntegrationTest is BaseTest {
morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
vm.stopPrank();

// New block.
vm.roll(block.number + 1);
vm.warp(block.timestamp + timeElapsed);
_forward(blocks);

uint256 borrowRate = (morpho.totalBorrowAssets(id).wDivDown(morpho.totalSupplyAssets(id))) / 365 days;
uint256 totalBorrowBeforeAccrued = morpho.totalBorrowAssets(id);
uint256 totalSupplyBeforeAccrued = morpho.totalSupplyAssets(id);
uint256 totalSupplySharesBeforeAccrued = morpho.totalSupplyShares(id);
uint256 expectedAccruedInterest = totalBorrowBeforeAccrued.wMulDown(borrowRate.wTaylorCompounded(timeElapsed));
uint256 expectedAccruedInterest =
totalBorrowBeforeAccrued.wMulDown(borrowRate.wTaylorCompounded(blocks * BLOCK_TIME));

collateralToken.setBalance(address(this), 1);
morpho.supplyCollateral(marketParams, 1, address(this), hex"");
Expand All @@ -113,19 +110,16 @@ contract AccrueInterestIntegrationTest is BaseTest {
uint256 feeShares;
}

function testAccrueInterestWithFees(
uint256 amountSupplied,
uint256 amountBorrowed,
uint256 timeElapsed,
uint256 fee
) public {
function testAccrueInterestWithFees(uint256 amountSupplied, uint256 amountBorrowed, uint256 blocks, uint256 fee)
public
{
AccrueInterestWithFeesTestParams memory params;

uint256 collateralPrice = oracle.price();
uint256 amountCollateral;
(amountCollateral, amountBorrowed,) = _boundHealthyPosition(amountCollateral, amountBorrowed, collateralPrice);
amountSupplied = bound(amountSupplied, amountBorrowed, MAX_TEST_AMOUNT);
timeElapsed = uint32(bound(timeElapsed, 1, 1e8));
blocks = _boundBlocks(blocks);
fee = bound(fee, 1, MAX_FEE);

// Set fee parameters.
Expand All @@ -143,16 +137,14 @@ contract AccrueInterestIntegrationTest is BaseTest {
morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
vm.stopPrank();

// New block.
vm.roll(block.number + 1);
vm.warp(block.timestamp + timeElapsed);
_forward(blocks);

params.borrowRate = (morpho.totalBorrowAssets(id).wDivDown(morpho.totalSupplyAssets(id))) / 365 days;
params.totalBorrowBeforeAccrued = morpho.totalBorrowAssets(id);
params.totalSupplyBeforeAccrued = morpho.totalSupplyAssets(id);
params.totalSupplySharesBeforeAccrued = morpho.totalSupplyShares(id);
params.expectedAccruedInterest =
params.totalBorrowBeforeAccrued.wMulDown(params.borrowRate.wTaylorCompounded(timeElapsed));
params.totalBorrowBeforeAccrued.wMulDown(params.borrowRate.wTaylorCompounded(blocks * BLOCK_TIME));
params.feeAmount = params.expectedAccruedInterest.wMulDown(fee);
params.feeShares = params.feeAmount.toSharesDown(
params.totalSupplyBeforeAccrued + params.expectedAccruedInterest - params.feeAmount,
Expand Down
7 changes: 3 additions & 4 deletions test/forge/integration/AuthorizationIntegrationTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ contract AuthorizationIntegrationTest is BaseTest {
function testSetAuthorizationWithSignatureDeadlineOutdated(
Authorization memory authorization,
uint256 privateKey,
uint256 elapsed
uint256 blocks
) public {
elapsed = bound(elapsed, 1, type(uint32).max);
blocks = _boundBlocks(blocks);
authorization.deadline = block.timestamp;

// Private key must be less than the secp256k1 curve order.
Expand All @@ -33,8 +33,7 @@ contract AuthorizationIntegrationTest is BaseTest {
bytes32 digest = SigUtils.getTypedDataHash(morpho.DOMAIN_SEPARATOR(), authorization);
(sig.v, sig.r, sig.s) = vm.sign(privateKey, digest);

vm.roll(block.number + 1);
vm.warp(block.timestamp + elapsed);
_forward(blocks);

vm.expectRevert(bytes(ErrorsLib.SIGNATURE_EXPIRED));
morpho.setAuthorizationWithSig(authorization, sig);
Expand Down
18 changes: 15 additions & 3 deletions test/forge/invariant/MorphoInvariantTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ contract MorphoInvariantTest is InvariantTest {
collateralToken: address(collateralToken),
oracle: address(oracle),
irm: address(irm),
lltv: 0.8 ether / i
lltv: MAX_TEST_LLTV / i
});

vm.startPrank(OWNER);
Expand Down Expand Up @@ -375,11 +375,23 @@ contract MorphoInvariantTest is InvariantTest {
}

function invariantTotalSupplyGeTotalBorrow() public {
assertGe(morpho.totalSupplyAssets(id), morpho.totalBorrowAssets(id));
for (uint256 i; i < allMarketParams.length; ++i) {
MarketParams memory _marketParams = allMarketParams[i];
Id _id = _marketParams.id();

assertGe(morpho.totalSupplyAssets(_id), morpho.totalBorrowAssets(_id));
}
}

function invariantMorphoBalance() public {
assertGe(loanToken.balanceOf(address(morpho)), morpho.totalSupplyAssets(id) - morpho.totalBorrowAssets(id));
for (uint256 i; i < allMarketParams.length; ++i) {
MarketParams memory _marketParams = allMarketParams[i];
Id _id = _marketParams.id();

assertGe(
loanToken.balanceOf(address(morpho)) + morpho.totalBorrowAssets(_id), morpho.totalSupplyAssets(_id)
);
}
}

function invariantBadDebt() public {
Expand Down
8 changes: 3 additions & 5 deletions test/forge/libraries/periphery/MorphoBalancesLibTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,12 @@ contract MorphoBalancesLibTest is BaseTest {
assertEq(expectedBorrowBalance, actualBorrowBalance);
}

function _generatePendingInterest(uint256 amountSupplied, uint256 amountBorrowed, uint256 timeElapsed, uint256 fee)
function _generatePendingInterest(uint256 amountSupplied, uint256 amountBorrowed, uint256 blocks, uint256 fee)
internal
{
amountSupplied = bound(amountSupplied, 0, MAX_TEST_AMOUNT);
amountBorrowed = bound(amountBorrowed, 0, amountSupplied);
timeElapsed = uint32(bound(timeElapsed, 0, 1e8));
blocks = _boundBlocks(blocks);
fee = bound(fee, 0, MAX_FEE);

// Set fee parameters.
Expand Down Expand Up @@ -135,8 +135,6 @@ contract MorphoBalancesLibTest is BaseTest {
}
}

// New block.
vm.roll(block.number + 1);
vm.warp(block.timestamp + timeElapsed);
_forward(blocks);
}
}

0 comments on commit 11e69bb

Please sign in to comment.