mirror of
https://github.com/luxfi/markets.git
synced 2026-10-30 16:19:55 +00:00
393 lines
18 KiB
RPMSpec
393 lines
18 KiB
RPMSpec
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using Util as Util;
|
|
|
|
methods {
|
|
function extSloads(bytes32[]) external returns bytes32[] => NONDET DELETE;
|
|
function supplyShares(MorphoInternalAccess.Id, address) external returns uint256 envfree;
|
|
function borrowShares(MorphoInternalAccess.Id, address) external returns uint256 envfree;
|
|
function collateral(MorphoInternalAccess.Id, address) external returns uint256 envfree;
|
|
function totalSupplyAssets(MorphoInternalAccess.Id) external returns uint256 envfree;
|
|
function totalSupplyShares(MorphoInternalAccess.Id) external returns uint256 envfree;
|
|
function totalBorrowAssets(MorphoInternalAccess.Id) external returns uint256 envfree;
|
|
function totalBorrowShares(MorphoInternalAccess.Id) external returns uint256 envfree;
|
|
function fee(MorphoInternalAccess.Id) external returns uint256 envfree;
|
|
function lastUpdate(MorphoInternalAccess.Id) external returns uint256 envfree;
|
|
function nonce(address) external returns uint256 envfree;
|
|
function isAuthorized(address, address) external returns bool envfree;
|
|
|
|
function Util.libId(MorphoInternalAccess.MarketParams) external returns MorphoInternalAccess.Id envfree;
|
|
function Util.refId(MorphoInternalAccess.MarketParams) external returns MorphoInternalAccess.Id envfree;
|
|
|
|
function _._accrueInterest(MorphoInternalAccess.MarketParams memory marketParams, MorphoInternalAccess.Id id) internal with (env e) => summaryAccrueInterest(e, marketParams, id) expect void;
|
|
|
|
function MarketParamsLib.id(MorphoInternalAccess.MarketParams memory marketParams) internal returns MorphoInternalAccess.Id => summaryId(marketParams);
|
|
function SafeTransferLib.safeTransfer(address token, address to, uint256 value) internal => summarySafeTransferFrom(token, currentContract, to, value);
|
|
function SafeTransferLib.safeTransferFrom(address token, address from, address to, uint256 value) internal => summarySafeTransferFrom(token, from, to, value);
|
|
}
|
|
|
|
persistent ghost mapping(address => mathint) balance {
|
|
init_state axiom (forall address token. balance[token] == 0);
|
|
}
|
|
|
|
function summaryId(MorphoInternalAccess.MarketParams marketParams) returns MorphoInternalAccess.Id {
|
|
return Util.refId(marketParams);
|
|
}
|
|
|
|
function summarySafeTransferFrom(address token, address from, address to, uint256 amount) {
|
|
if (from == currentContract) {
|
|
// Safe require because the reference implementation would revert.
|
|
balance[token] = require_uint256(balance[token] - amount);
|
|
}
|
|
if (to == currentContract) {
|
|
// Safe require because the reference implementation would revert.
|
|
balance[token] = require_uint256(balance[token] + amount);
|
|
}
|
|
}
|
|
|
|
function min(mathint a, mathint b) returns mathint {
|
|
return a < b ? a : b;
|
|
}
|
|
|
|
// Assume no fee.
|
|
// Summarize the accrue interest to avoid having to deal with reverts with absurdly high borrow rates.
|
|
function summaryAccrueInterest(env e, MorphoInternalAccess.MarketParams marketParams, MorphoInternalAccess.Id id) {
|
|
// Safe require because timestamps cannot realistically be that large.
|
|
require e.block.timestamp < 2^128;
|
|
if (e.block.timestamp != lastUpdate(id) && totalBorrowAssets(id) != 0) {
|
|
uint128 interest;
|
|
uint256 supply = totalSupplyAssets(id);
|
|
// Safe require because tokens should have bounded supply.
|
|
require interest + supply < 2^128;
|
|
uint256 borrow = totalBorrowAssets(id);
|
|
// Safe require because it is verified in borrowLessThanSupply.
|
|
require borrow <= supply;
|
|
increaseInterest(e, id, interest);
|
|
}
|
|
|
|
update(e, id, e.block.timestamp);
|
|
}
|
|
|
|
definition isCreated(MorphoInternalAccess.Id id) returns bool =
|
|
lastUpdate(id) != 0;
|
|
|
|
// Check that tokens and shares are properly accounted following a supply.
|
|
rule supplyChangesTokensAndShares(env e, MorphoInternalAccess.MarketParams marketParams, uint256 assets, uint256 shares, address onBehalf, bytes data) {
|
|
MorphoInternalAccess.Id id = Util.libId(marketParams);
|
|
|
|
// Safe require because Morpho cannot call such functions by itself.
|
|
require currentContract != e.msg.sender;
|
|
// Assumption to ensure that no interest is accumulated.
|
|
require lastUpdate(id) == e.block.timestamp;
|
|
|
|
mathint sharesBefore = supplyShares(id, onBehalf);
|
|
mathint balanceBefore = balance[marketParams.loanToken];
|
|
mathint liquidityBefore = totalSupplyAssets(id) - totalBorrowAssets(id);
|
|
|
|
uint256 suppliedAssets;
|
|
uint256 suppliedShares;
|
|
suppliedAssets, suppliedShares = supply(e, marketParams, assets, shares, onBehalf, data);
|
|
|
|
mathint sharesAfter = supplyShares(id, onBehalf);
|
|
mathint balanceAfter = balance[marketParams.loanToken];
|
|
mathint liquidityAfter = totalSupplyAssets(id) - totalBorrowAssets(id);
|
|
|
|
assert assets != 0 => suppliedAssets == assets;
|
|
assert shares != 0 => suppliedShares == shares;
|
|
assert sharesAfter == sharesBefore + suppliedShares;
|
|
assert balanceAfter == balanceBefore + suppliedAssets;
|
|
assert liquidityAfter == liquidityBefore + suppliedAssets;
|
|
}
|
|
|
|
// Check that you can supply non-zero tokens by passing shares.
|
|
rule canSupplyByPassingShares(env e, MorphoInternalAccess.MarketParams marketParams, uint256 shares, address onBehalf, bytes data) {
|
|
uint256 suppliedAssets;
|
|
suppliedAssets, _ = supply(e, marketParams, 0, shares, onBehalf, data);
|
|
|
|
satisfy suppliedAssets != 0;
|
|
}
|
|
|
|
// Check that tokens and shares are properly accounted following a withdraw.
|
|
rule withdrawChangesTokensAndShares(env e, MorphoInternalAccess.MarketParams marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) {
|
|
MorphoInternalAccess.Id id = Util.libId(marketParams);
|
|
|
|
// Assume that Morpho is not the receiver.
|
|
require currentContract != receiver;
|
|
// Assumption to ensure that no interest is accumulated.
|
|
require lastUpdate(id) == e.block.timestamp;
|
|
|
|
mathint sharesBefore = supplyShares(id, onBehalf);
|
|
mathint balanceBefore = balance[marketParams.loanToken];
|
|
mathint liquidityBefore = totalSupplyAssets(id) - totalBorrowAssets(id);
|
|
|
|
uint256 withdrawnAssets;
|
|
uint256 withdrawnShares;
|
|
withdrawnAssets, withdrawnShares = withdraw(e, marketParams, assets, shares, onBehalf, receiver);
|
|
|
|
mathint sharesAfter = supplyShares(id, onBehalf);
|
|
mathint balanceAfter = balance[marketParams.loanToken];
|
|
mathint liquidityAfter = totalSupplyAssets(id) - totalBorrowAssets(id);
|
|
|
|
assert assets != 0 => withdrawnAssets == assets;
|
|
assert shares != 0 => withdrawnShares == shares;
|
|
assert sharesAfter == sharesBefore - withdrawnShares;
|
|
assert balanceAfter == balanceBefore - withdrawnAssets;
|
|
assert liquidityAfter == liquidityBefore - withdrawnAssets;
|
|
}
|
|
|
|
// Check that you can withdraw non-zero tokens by passing shares.
|
|
rule canWithdrawByPassingShares(env e, MorphoInternalAccess.MarketParams marketParams, uint256 shares, address onBehalf, address receiver) {
|
|
uint256 withdrawnAssets;
|
|
withdrawnAssets, _ = withdraw(e, marketParams, 0, shares, onBehalf, receiver);
|
|
|
|
satisfy withdrawnAssets != 0;
|
|
}
|
|
|
|
// Check that tokens and shares are properly accounted following a borrow.
|
|
rule borrowChangesTokensAndShares(env e, MorphoInternalAccess.MarketParams marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) {
|
|
MorphoInternalAccess.Id id = Util.libId(marketParams);
|
|
|
|
// Assume that Morpho is not the receiver.
|
|
require currentContract != receiver;
|
|
// Assumption to ensure that no interest is accumulated.
|
|
require lastUpdate(id) == e.block.timestamp;
|
|
|
|
mathint sharesBefore = borrowShares(id, onBehalf);
|
|
mathint balanceBefore = balance[marketParams.loanToken];
|
|
mathint liquidityBefore = totalSupplyAssets(id) - totalBorrowAssets(id);
|
|
|
|
uint256 borrowedAssets;
|
|
uint256 borrowedShares;
|
|
borrowedAssets, borrowedShares = borrow(e, marketParams, assets, shares, onBehalf, receiver);
|
|
|
|
mathint sharesAfter = borrowShares(id, onBehalf);
|
|
mathint balanceAfter = balance[marketParams.loanToken];
|
|
mathint liquidityAfter = totalSupplyAssets(id) - totalBorrowAssets(id);
|
|
|
|
assert assets != 0 => borrowedAssets == assets;
|
|
assert shares != 0 => borrowedShares == shares;
|
|
assert sharesAfter == sharesBefore + borrowedShares;
|
|
assert balanceAfter == balanceBefore - borrowedAssets;
|
|
assert liquidityAfter == liquidityBefore - borrowedAssets;
|
|
}
|
|
|
|
// Check that you can borrow non-zero tokens by passing shares.
|
|
rule canBorrowByPassingShares(env e, MorphoInternalAccess.MarketParams marketParams, uint256 shares, address onBehalf, address receiver) {
|
|
uint256 borrowedAssets;
|
|
borrowedAssets, _ = borrow(e, marketParams, 0, shares, onBehalf, receiver);
|
|
|
|
satisfy borrowedAssets != 0;
|
|
}
|
|
|
|
// Check that tokens and shares are properly accounted following a repay.
|
|
rule repayChangesTokensAndShares(env e, MorphoInternalAccess.MarketParams marketParams, uint256 assets, uint256 shares, address onBehalf, bytes data) {
|
|
MorphoInternalAccess.Id id = Util.libId(marketParams);
|
|
|
|
// Safe require because Morpho cannot call such functions by itself.
|
|
require currentContract != e.msg.sender;
|
|
// Assumption to ensure that no interest is accumulated.
|
|
require lastUpdate(id) == e.block.timestamp;
|
|
|
|
mathint sharesBefore = borrowShares(id, onBehalf);
|
|
mathint balanceBefore = balance[marketParams.loanToken];
|
|
mathint liquidityBefore = totalSupplyAssets(id) - totalBorrowAssets(id);
|
|
|
|
mathint borrowAssetsBefore = totalBorrowAssets(id);
|
|
|
|
uint256 repaidAssets;
|
|
uint256 repaidShares;
|
|
repaidAssets, repaidShares = repay(e, marketParams, assets, shares, onBehalf, data);
|
|
|
|
mathint sharesAfter = borrowShares(id, onBehalf);
|
|
mathint balanceAfter = balance[marketParams.loanToken];
|
|
mathint liquidityAfter = totalSupplyAssets(id) - totalBorrowAssets(id);
|
|
|
|
assert assets != 0 => repaidAssets == assets;
|
|
assert shares != 0 => repaidShares == shares;
|
|
assert sharesAfter == sharesBefore - repaidShares;
|
|
assert balanceAfter == balanceBefore + repaidAssets;
|
|
// Taking the min to handle the zeroFloorSub in the code.
|
|
assert liquidityAfter == liquidityBefore + min(repaidAssets, borrowAssetsBefore);
|
|
}
|
|
|
|
// Check that you can repay non-zero tokens by passing shares.
|
|
rule canRepayByPassingShares(env e, MorphoInternalAccess.MarketParams marketParams, uint256 shares, address onBehalf, bytes data) {
|
|
uint256 repaidAssets;
|
|
repaidAssets, _ = repay(e, marketParams, 0, shares, onBehalf, data);
|
|
|
|
satisfy repaidAssets != 0;
|
|
}
|
|
|
|
// Check that tokens and balances are properly accounted following a supplyCollateral.
|
|
rule supplyCollateralChangesTokensAndBalance(env e, MorphoInternalAccess.MarketParams marketParams, uint256 assets, address onBehalf, bytes data) {
|
|
MorphoInternalAccess.Id id = Util.libId(marketParams);
|
|
|
|
// Safe require because Morpho cannot call such functions by itself.
|
|
require currentContract != e.msg.sender;
|
|
|
|
mathint collateralBefore = collateral(id, onBehalf);
|
|
mathint balanceBefore = balance[marketParams.collateralToken];
|
|
|
|
supplyCollateral(e, marketParams, assets, onBehalf, data);
|
|
|
|
mathint collateralAfter = collateral(id, onBehalf);
|
|
mathint balanceAfter = balance[marketParams.collateralToken];
|
|
|
|
assert collateralAfter == collateralBefore + assets;
|
|
assert balanceAfter == balanceBefore + assets;
|
|
}
|
|
|
|
// Check that tokens and balances are properly accounted following a withdrawCollateral.
|
|
rule withdrawCollateralChangesTokensAndBalance(env e, MorphoInternalAccess.MarketParams marketParams, uint256 assets, address onBehalf, address receiver) {
|
|
MorphoInternalAccess.Id id = Util.libId(marketParams);
|
|
|
|
// Assume that Morpho is not the receiver.
|
|
require currentContract != receiver;
|
|
// Assumption to ensure that no interest is accumulated.
|
|
require lastUpdate(id) == e.block.timestamp;
|
|
|
|
mathint collateralBefore = collateral(id, onBehalf);
|
|
mathint balanceBefore = balance[marketParams.collateralToken];
|
|
|
|
withdrawCollateral(e, marketParams, assets, onBehalf, receiver);
|
|
|
|
mathint collateralAfter = collateral(id, onBehalf);
|
|
mathint balanceAfter = balance[marketParams.collateralToken];
|
|
|
|
assert collateralAfter == collateralBefore - assets;
|
|
assert balanceAfter == balanceBefore - assets;
|
|
}
|
|
|
|
// Check that tokens are properly accounted following a liquidate.
|
|
rule liquidateChangesTokens(env e, MorphoInternalAccess.MarketParams marketParams, address borrower, uint256 seized, uint256 repaidShares, bytes data) {
|
|
MorphoInternalAccess.Id id = Util.libId(marketParams);
|
|
|
|
// Safe require because Morpho cannot call such functions by itself.
|
|
require currentContract != e.msg.sender;
|
|
// Assumption to simplify the balance specification in the rest of this rule.
|
|
require marketParams.loanToken != marketParams.collateralToken;
|
|
// Assumption to ensure that no interest is accumulated.
|
|
require lastUpdate(id) == e.block.timestamp;
|
|
|
|
mathint collateralBefore = collateral(id, borrower);
|
|
mathint balanceLoanBefore = balance[marketParams.loanToken];
|
|
mathint balanceCollateralBefore = balance[marketParams.collateralToken];
|
|
mathint liquidityBefore = totalSupplyAssets(id) - totalBorrowAssets(id);
|
|
|
|
mathint borrowLoanAssetsBefore = totalBorrowAssets(id);
|
|
|
|
uint256 seizedAssets;
|
|
uint256 repaidAssets;
|
|
seizedAssets, repaidAssets = liquidate(e, marketParams, borrower, seized, repaidShares, data);
|
|
|
|
mathint collateralAfter = collateral(id, borrower);
|
|
mathint balanceLoanAfter = balance[marketParams.loanToken];
|
|
mathint balanceCollateralAfter = balance[marketParams.collateralToken];
|
|
mathint liquidityAfter = totalSupplyAssets(id) - totalBorrowAssets(id);
|
|
|
|
assert seized != 0 => seizedAssets == seized;
|
|
assert collateralBefore > to_mathint(seizedAssets) => collateralAfter == collateralBefore - seizedAssets;
|
|
assert balanceLoanAfter == balanceLoanBefore + repaidAssets;
|
|
assert balanceCollateralAfter == balanceCollateralBefore - seizedAssets;
|
|
// Taking the min to handle the zeroFloorSub in the code.
|
|
assert liquidityAfter == liquidityBefore + min(repaidAssets, borrowLoanAssetsBefore);
|
|
}
|
|
|
|
// Check that you can liquidate non-zero tokens by passing shares.
|
|
rule canLiquidateByPassingShares(env e, MorphoInternalAccess.MarketParams marketParams, address borrower, uint256 repaidShares, bytes data) {
|
|
uint256 seizedAssets;
|
|
uint256 repaidAssets;
|
|
seizedAssets, repaidAssets = liquidate(e, marketParams, borrower, 0, repaidShares, data);
|
|
|
|
satisfy seizedAssets != 0 && repaidAssets != 0;
|
|
}
|
|
|
|
// Check that nonce and authorization are properly updated with calling setAuthorizationWithSig.
|
|
rule setAuthorizationWithSigChangesNonceAndAuthorizes(env e, MorphoInternalAccess.Authorization authorization, MorphoInternalAccess.Signature signature) {
|
|
mathint nonceBefore = nonce(authorization.authorizer);
|
|
|
|
setAuthorizationWithSig(e, authorization, signature);
|
|
|
|
mathint nonceAfter = nonce(authorization.authorizer);
|
|
|
|
assert nonceAfter == nonceBefore + 1;
|
|
assert isAuthorized(authorization.authorizer, authorization.authorized) == authorization.isAuthorized;
|
|
}
|
|
|
|
// Check that one can always repay the debt in full.
|
|
rule canRepayAll(env e, MorphoInternalAccess.MarketParams marketParams, uint256 shares, bytes data) {
|
|
MorphoInternalAccess.Id id = Util.libId(marketParams);
|
|
|
|
// Assume no callback, which still allows to repay all.
|
|
require data.length == 0;
|
|
|
|
// Assume a full repay.
|
|
require shares == borrowShares(id, e.msg.sender);
|
|
// Omit sanity checks.
|
|
require isCreated(id);
|
|
require e.msg.sender != 0;
|
|
require e.msg.value == 0;
|
|
require shares > 0;
|
|
// Safe require because of the noTimeTravel rule.
|
|
require lastUpdate(id) <= e.block.timestamp;
|
|
// Safe require because of the sumBorrowSharesCorrect invariant.
|
|
require shares <= totalBorrowShares(id);
|
|
|
|
// Accrue interest first to ensure that the accrued interest is reasonable (next require).
|
|
// Safe because of the AccrueInterest.repayAccruesInterest rule
|
|
summaryAccrueInterest(e, marketParams, id);
|
|
|
|
// Assume that the invariant about tokens total supply is respected.
|
|
require totalBorrowAssets(id) < 10^35;
|
|
|
|
repay@withrevert(e, marketParams, 0, shares, e.msg.sender, data);
|
|
|
|
assert !lastReverted;
|
|
}
|
|
|
|
// Check the one can always withdraw all, under the condition that there are no outstanding debt on the market.
|
|
rule canWithdrawAll(env e, MorphoInternalAccess.MarketParams marketParams, uint256 shares, address receiver) {
|
|
MorphoInternalAccess.Id id = Util.libId(marketParams);
|
|
|
|
// Assume a full withdraw.
|
|
require shares == supplyShares(id, e.msg.sender);
|
|
// Omit sanity checks.
|
|
require isCreated(id);
|
|
require e.msg.sender != 0;
|
|
require receiver != 0;
|
|
require e.msg.value == 0;
|
|
require shares > 0;
|
|
// Assume no outstanding debt on the market.
|
|
require totalBorrowAssets(id) == 0;
|
|
// Safe require because of the noTimeTravel rule.
|
|
require lastUpdate(id) <= e.block.timestamp;
|
|
// Safe require because of the sumSupplySharesCorrect invariant.
|
|
require shares <= totalSupplyShares(id);
|
|
|
|
withdraw@withrevert(e, marketParams, 0, shares, e.msg.sender, receiver);
|
|
|
|
assert !lastReverted;
|
|
}
|
|
|
|
// Check that a user can always withdraw all, under the condition that this user does not have an outstanding debt.
|
|
// Combined with the canRepayAll rule, this ensures that a borrower can always fully exit a market.
|
|
rule canWithdrawCollateralAll(env e, MorphoInternalAccess.MarketParams marketParams, uint256 assets, address receiver) {
|
|
MorphoInternalAccess.Id id = Util.libId(marketParams);
|
|
|
|
// Ensure a full withdrawCollateral.
|
|
require assets == collateral(id, e.msg.sender);
|
|
// Omit sanity checks.
|
|
require isCreated(id);
|
|
require receiver != 0;
|
|
require e.msg.value == 0;
|
|
require assets > 0;
|
|
// Safe require because of the noTimeTravel rule.
|
|
require lastUpdate(id) <= e.block.timestamp;
|
|
// Assume that the user does not have an outstanding debt.
|
|
require borrowShares(id, e.msg.sender) == 0;
|
|
|
|
withdrawCollateral@withrevert(e, marketParams, assets, e.msg.sender, receiver);
|
|
|
|
assert !lastReverted;
|
|
}
|