mirror of
https://github.com/luxfi/markets.git
synced 2026-10-30 16:19:55 +00:00
203 lines
7.6 KiB
Solidity
203 lines
7.6 KiB
Solidity
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
pragma solidity ^0.8.0;
|
|
|
|
import "../../lib/forge-std/src/Test.sol";
|
|
import {SymTest} from "../../lib/halmos-cheatcodes/src/SymTest.sol";
|
|
|
|
import {IMorpho} from "../../src/interfaces/IMorpho.sol";
|
|
import {IrmMock} from "../../src/mocks/IrmMock.sol";
|
|
import {ERC20Mock} from "../../src/mocks/ERC20Mock.sol";
|
|
import {OracleMock} from "../../src/mocks/OracleMock.sol";
|
|
import {FlashBorrowerMock} from "../../src/mocks/FlashBorrowerMock.sol";
|
|
|
|
import "../../src/Morpho.sol";
|
|
import "../../src/libraries/ConstantsLib.sol";
|
|
import {MorphoLib} from "../../src/libraries/periphery/MorphoLib.sol";
|
|
|
|
/// @custom:halmos --solver-timeout-assertion 0
|
|
contract HalmosTest is SymTest, Test {
|
|
using MorphoLib for IMorpho;
|
|
using MarketParamsLib for MarketParams;
|
|
|
|
address internal owner;
|
|
|
|
IMorpho internal morpho;
|
|
ERC20Mock internal loanToken;
|
|
ERC20Mock internal collateralToken;
|
|
OracleMock internal oracle;
|
|
IrmMock internal irm;
|
|
uint256 internal lltv;
|
|
|
|
MarketParams internal marketParams;
|
|
|
|
ERC20Mock internal otherToken;
|
|
FlashBorrowerMock internal flashBorrower;
|
|
|
|
function setUp() public virtual {
|
|
owner = svm.createAddress("owner");
|
|
morpho = IMorpho(address(new Morpho(owner)));
|
|
|
|
loanToken = new ERC20Mock();
|
|
collateralToken = new ERC20Mock();
|
|
oracle = new OracleMock();
|
|
oracle.setPrice(ORACLE_PRICE_SCALE);
|
|
irm = new IrmMock();
|
|
lltv = svm.createUint256("lltv");
|
|
|
|
marketParams = MarketParams({
|
|
loanToken: address(loanToken),
|
|
collateralToken: address(collateralToken),
|
|
oracle: address(oracle),
|
|
irm: address(irm),
|
|
lltv: lltv
|
|
});
|
|
vm.startPrank(owner);
|
|
morpho.enableIrm(address(irm));
|
|
morpho.enableLltv(lltv);
|
|
morpho.createMarket(marketParams);
|
|
vm.stopPrank();
|
|
|
|
// for flashLoan
|
|
otherToken = new ERC20Mock();
|
|
flashBorrower = new FlashBorrowerMock(morpho);
|
|
|
|
// Enable symbolic storage
|
|
svm.enableSymbolicStorage(address(this));
|
|
svm.enableSymbolicStorage(address(morpho));
|
|
svm.enableSymbolicStorage(address(loanToken));
|
|
svm.enableSymbolicStorage(address(collateralToken));
|
|
svm.enableSymbolicStorage(address(oracle));
|
|
svm.enableSymbolicStorage(address(irm));
|
|
svm.enableSymbolicStorage(address(otherToken));
|
|
svm.enableSymbolicStorage(address(flashBorrower));
|
|
|
|
// Set symbolic block number and timestamp
|
|
vm.roll(svm.createUint(64, "block.number"));
|
|
vm.warp(svm.createUint(64, "block.timestamp"));
|
|
}
|
|
|
|
// Call Morpho, assuming interacting with only the defined market for performance reasons.
|
|
function _callMorpho(bytes4 selector, address caller) internal {
|
|
vm.assume(selector != morpho.extSloads.selector);
|
|
vm.assume(selector != morpho.createMarket.selector);
|
|
|
|
bytes memory emptyData = hex"";
|
|
uint256 assets = svm.createUint256("assets");
|
|
uint256 shares = svm.createUint256("shares");
|
|
address onBehalf = svm.createAddress("onBehalf");
|
|
address receiver = svm.createAddress("receiver");
|
|
|
|
bytes memory args;
|
|
|
|
if (selector == morpho.supply.selector || selector == morpho.repay.selector) {
|
|
args = abi.encode(marketParams, assets, shares, onBehalf, emptyData);
|
|
} else if (selector == morpho.withdraw.selector || selector == morpho.borrow.selector) {
|
|
args = abi.encode(marketParams, assets, shares, onBehalf, receiver);
|
|
} else if (selector == morpho.supplyCollateral.selector) {
|
|
args = abi.encode(marketParams, assets, onBehalf, emptyData);
|
|
} else if (selector == morpho.withdrawCollateral.selector) {
|
|
args = abi.encode(marketParams, assets, onBehalf, receiver);
|
|
} else if (selector == morpho.liquidate.selector) {
|
|
address borrower = svm.createAddress("borrower");
|
|
args = abi.encode(marketParams, borrower, assets, shares, emptyData);
|
|
} else if (selector == morpho.flashLoan.selector) {
|
|
address token = svm.createAddress("token");
|
|
bytes memory _data = svm.createBytes(1024, "_data");
|
|
args = abi.encode(token, assets, _data);
|
|
} else if (selector == morpho.accrueInterest.selector) {
|
|
args = abi.encode(marketParams);
|
|
} else if (selector == morpho.setFee.selector) {
|
|
uint256 newFee = svm.createUint256("newFee");
|
|
args = abi.encode(marketParams, newFee);
|
|
} else {
|
|
args = svm.createBytes(1024, "data");
|
|
}
|
|
|
|
vm.prank(caller);
|
|
(bool success,) = address(morpho).call(abi.encodePacked(selector, args));
|
|
vm.assume(success);
|
|
}
|
|
|
|
// Check that the fee is always smaller than the max fee.
|
|
function check_feeInRange(bytes4 selector, address caller, Id id) public {
|
|
vm.assume(morpho.fee(id) <= MAX_FEE);
|
|
|
|
_callMorpho(selector, caller);
|
|
|
|
assert(morpho.fee(id) <= MAX_FEE);
|
|
}
|
|
|
|
// Check that there is always less borrow than supply on the market.
|
|
function check_borrowLessThanSupply(bytes4 selector, address caller, Id id) public {
|
|
vm.assume(morpho.totalBorrowAssets(id) <= morpho.totalSupplyAssets(id));
|
|
|
|
_callMorpho(selector, caller);
|
|
|
|
assert(morpho.totalBorrowAssets(id) <= morpho.totalSupplyAssets(id));
|
|
}
|
|
|
|
// Check that the market cannot be "destroyed".
|
|
function check_lastUpdateNonZero(bytes4 selector, address caller, Id id) public {
|
|
vm.assume(morpho.lastUpdate(id) != 0);
|
|
|
|
_callMorpho(selector, caller);
|
|
|
|
assert(morpho.lastUpdate(id) != 0);
|
|
}
|
|
|
|
// Check that the lastUpdate can only increase.
|
|
function check_lastUpdateCannotDecrease(bytes4 selector, address caller, Id id) public {
|
|
uint256 lastUpdateBefore = morpho.lastUpdate(id);
|
|
|
|
_callMorpho(selector, caller);
|
|
|
|
uint256 lastUpdateAfter = morpho.lastUpdate(id);
|
|
assert(lastUpdateAfter >= lastUpdateBefore);
|
|
}
|
|
|
|
// Check that enabled LLTVs are necessarily less than 1.
|
|
function check_lltvSmallerThanWad(bytes4 selector, address caller, uint256 _lltv) public {
|
|
vm.assume(!morpho.isLltvEnabled(_lltv) || _lltv < 1e18);
|
|
|
|
_callMorpho(selector, caller);
|
|
|
|
assert(!morpho.isLltvEnabled(_lltv) || _lltv < 1e18);
|
|
}
|
|
|
|
// Check that LLTVs can't be disabled.
|
|
function check_lltvCannotBeDisabled(bytes4 selector, address caller) public {
|
|
_callMorpho(selector, caller);
|
|
|
|
assert(morpho.isLltvEnabled(lltv));
|
|
}
|
|
|
|
// Check that IRMs can't be disabled.
|
|
// Note: IRM is not symbolic, that is not ideal.
|
|
function check_irmCannotBeDisabled(bytes4 selector, address caller) public {
|
|
_callMorpho(selector, caller);
|
|
|
|
assert(morpho.isIrmEnabled(address(irm)));
|
|
}
|
|
|
|
// Check that the nonce of users cannot decrease.
|
|
function check_nonceCannotDecrease(bytes4 selector, address caller, address user) public {
|
|
uint256 nonceBefore = morpho.nonce(user);
|
|
|
|
_callMorpho(selector, caller);
|
|
|
|
uint256 nonceAfter = morpho.nonce(user);
|
|
assert(nonceAfter == nonceBefore || nonceAfter == nonceBefore + 1);
|
|
}
|
|
|
|
// Check that idToMarketParams cannot change.
|
|
// Note: ok because createMarket is never called by _callMorpho.
|
|
function check_idToMarketParamsForCreatedMarketCannotChange(bytes4 selector, address caller, Id id) public {
|
|
MarketParams memory itmpBefore = morpho.idToMarketParams(id);
|
|
|
|
_callMorpho(selector, caller);
|
|
|
|
MarketParams memory itmpAfter = morpho.idToMarketParams(id);
|
|
assert(Id.unwrap(itmpBefore.id()) == Id.unwrap(itmpAfter.id()));
|
|
}
|
|
}
|