forked from morpho-org/morpho-blue
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Health.spec
134 lines (108 loc) · 5.44 KB
/
Health.spec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// SPDX-License-Identifier: GPL-2.0-or-later
methods {
function extSloads(bytes32[]) external returns bytes32[] => NONDET DELETE;
function totalBorrowShares(MorphoHarness.Id) external returns uint256 envfree;
function lastUpdate(MorphoHarness.Id) external returns uint256 envfree;
function borrowShares(MorphoHarness.Id, address) external returns uint256 envfree;
function collateral(MorphoHarness.Id, address) external returns uint256 envfree;
function isAuthorized(address, address user) external returns bool envfree;
function lastUpdate(MorphoHarness.Id) external returns uint256 envfree;
function libId(MorphoHarness.MarketParams) external returns MorphoHarness.Id envfree;
function isHealthy(MorphoHarness.MarketParams, address user) external returns bool envfree;
function _.price() external => mockPrice() expect uint256;
function MathLib.mulDivDown(uint256 a, uint256 b, uint256 c) internal returns uint256 => summaryMulDivDown(a,b,c);
function MathLib.mulDivUp(uint256 a, uint256 b, uint256 c) internal returns uint256 => summaryMulDivUp(a,b,c);
function UtilsLib.min(uint256 a, uint256 b) internal returns uint256 => summaryMin(a,b);
}
persistent ghost uint256 lastPrice;
persistent ghost bool priceChanged;
function mockPrice() returns uint256 {
uint256 updatedPrice;
if (updatedPrice != lastPrice) {
priceChanged = true;
lastPrice = updatedPrice;
}
return updatedPrice;
}
function summaryMulDivUp(uint256 x, uint256 y, uint256 d) returns uint256 {
// Safe require because the reference implementation would revert.
require d != 0;
return require_uint256((x * y + (d - 1)) / d);
}
function summaryMulDivDown(uint256 x, uint256 y, uint256 d) returns uint256 {
// Safe require because the reference implementation would revert.
require d != 0;
return require_uint256((x * y) / d);
}
function summaryMin(uint256 a, uint256 b) returns uint256 {
return a < b ? a : b;
}
// Check that without accruing interest, no interaction can put an healthy account into an unhealthy one.
// This rule times out for liquidate, repay and borrow.
rule stayHealthy(env e, method f, calldataarg data)
filtered {
f -> !f.isView &&
f.selector != sig:liquidate(MorphoHarness.MarketParams, address, uint256, uint256, bytes).selector &&
f.selector != sig:repay(MorphoHarness.MarketParams, uint256, uint256, address, bytes).selector &&
f.selector != sig:borrow(MorphoHarness.MarketParams, uint256, uint256, address, address).selector
}
{
MorphoHarness.MarketParams marketParams;
MorphoHarness.Id id = libId(marketParams);
address user;
// Assume that the position is healthy before the interaction.
require isHealthy(marketParams, user);
// Safe require because of the invariants onlyEnabledLltv and lltvSmallerThanWad in ConsistentState.spec.
require marketParams.lltv < 10^18;
// Assumption to ensure that no interest is accumulated.
require lastUpdate(id) == e.block.timestamp;
priceChanged = false;
f(e, data);
// Safe require because of the invariant sumBorrowSharesCorrect.
require borrowShares(id, user) <= totalBorrowShares(id);
bool stillHealthy = isHealthy(marketParams, user);
assert !priceChanged => stillHealthy;
}
// Check that users cannot lose collateral by unauthorized parties except in case of an unhealthy position.
rule healthyUserCannotLoseCollateral(env e, method f, calldataarg data)
filtered { f -> !f.isView }
{
MorphoHarness.MarketParams marketParams;
MorphoHarness.Id id = libId(marketParams);
address user;
// Assume that the e.msg.sender is not authorized.
require !isAuthorized(user, e.msg.sender);
require user != e.msg.sender;
// Assumption to ensure that no interest is accumulated.
require lastUpdate(id) == e.block.timestamp;
// Assume that the user is healthy.
require isHealthy(marketParams, user);
mathint collateralBefore = collateral(id, user);
priceChanged = false;
f(e, data);
mathint collateralAfter = collateral(id, user);
assert !priceChanged => collateralAfter >= collateralBefore;
}
// Check that users without collateral also have no debt.
// This invariant ensures that bad debt realization cannot be bypassed.
invariant alwaysCollateralized(MorphoHarness.Id id, address borrower)
borrowShares(id, borrower) != 0 => collateral(id, borrower) != 0;
// Checks that passing a seized amount input to liquidate leads to repaid shares S and repaid amount A such that liquidating instead with shares S also repays the amount A.
rule liquidateEquivalentInputDebtAndInputCollateral(env e, MorphoHarness.MarketParams marketParams, address borrower, uint256 seizedAssets, bytes data) {
MorphoHarness.Id id = libId(marketParams);
// Assume no interest accrual to ease the verification.
require lastUpdate(id) == e.block.timestamp;
storage init = lastStorage;
uint256 sharesBefore = borrowShares(id, borrower);
uint256 repaidAssets1;
_, repaidAssets1 = liquidate(e, marketParams, borrower, seizedAssets, 0, data);
require !priceChanged;
// Omit the bad debt realization case.
require collateral(id, borrower) != 0;
uint256 sharesAfter = borrowShares(id, borrower);
uint256 repaidShares1 = assert_uint256(sharesBefore - sharesAfter);
uint256 repaidAssets2;
_, repaidAssets2 = liquidate(e, marketParams, borrower, 0, repaidShares1, data) at init;
require !priceChanged;
assert repaidAssets1 == repaidAssets2;
}