-
Notifications
You must be signed in to change notification settings - Fork 60
/
ConsistentState.spec
287 lines (225 loc) · 11.7 KB
/
ConsistentState.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
// SPDX-License-Identifier: GPL-2.0-or-later
using Util as Util;
methods {
function extSloads(bytes32[]) external returns bytes32[] => NONDET DELETE;
function supplyShares(MorphoHarness.Id, address) external returns uint256 envfree;
function borrowShares(MorphoHarness.Id, address) external returns uint256 envfree;
function collateral(MorphoHarness.Id, address) external returns uint256 envfree;
function totalSupplyAssets(MorphoHarness.Id) external returns uint256 envfree;
function totalSupplyShares(MorphoHarness.Id) external returns uint256 envfree;
function totalBorrowAssets(MorphoHarness.Id) external returns uint256 envfree;
function totalBorrowShares(MorphoHarness.Id) external returns uint256 envfree;
function fee(MorphoHarness.Id) external returns uint256 envfree;
function lastUpdate(MorphoHarness.Id) external returns uint256 envfree;
function isIrmEnabled(address) external returns bool envfree;
function isLltvEnabled(uint256) external returns bool envfree;
function isAuthorized(address, address) external returns bool envfree;
function idToMarketParams_(MorphoHarness.Id) external returns MorphoHarness.MarketParams envfree;
function Util.maxFee() external returns uint256 envfree;
function Util.wad() external returns uint256 envfree;
function Util.libId(MorphoHarness.MarketParams) external returns MorphoHarness.Id envfree;
function _.borrowRate(MorphoHarness.MarketParams, MorphoHarness.Market) external => HAVOC_ECF;
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(MorphoHarness.Id => mathint) sumSupplyShares {
init_state axiom (forall MorphoHarness.Id id. sumSupplyShares[id] == 0);
}
persistent ghost mapping(MorphoHarness.Id => mathint) sumBorrowShares {
init_state axiom (forall MorphoHarness.Id id. sumBorrowShares[id] == 0);
}
persistent ghost mapping(MorphoHarness.Id => mathint) sumCollateral {
init_state axiom (forall MorphoHarness.Id id. sumCollateral[id] == 0);
}
persistent ghost mapping(address => mathint) balance {
init_state axiom (forall address token. balance[token] == 0);
}
persistent ghost mapping(address => mathint) idleAmount {
init_state axiom (forall address token. idleAmount[token] == 0);
}
hook Sstore position[KEY MorphoHarness.Id id][KEY address owner].supplyShares uint256 newShares (uint256 oldShares) {
sumSupplyShares[id] = sumSupplyShares[id] - oldShares + newShares;
}
hook Sstore position[KEY MorphoHarness.Id id][KEY address owner].borrowShares uint128 newShares (uint128 oldShares) {
sumBorrowShares[id] = sumBorrowShares[id] - oldShares + newShares;
}
hook Sstore position[KEY MorphoHarness.Id id][KEY address owner].collateral uint128 newAmount (uint128 oldAmount) {
sumCollateral[id] = sumCollateral[id] - oldAmount + newAmount;
idleAmount[idToMarketParams_(id).collateralToken] = idleAmount[idToMarketParams_(id).collateralToken] - oldAmount + newAmount;
}
hook Sstore market[KEY MorphoHarness.Id id].totalSupplyAssets uint128 newAmount (uint128 oldAmount) {
idleAmount[idToMarketParams_(id).loanToken] = idleAmount[idToMarketParams_(id).loanToken] - oldAmount + newAmount;
}
hook Sstore market[KEY MorphoHarness.Id id].totalBorrowAssets uint128 newAmount (uint128 oldAmount) {
idleAmount[idToMarketParams_(id).loanToken] = idleAmount[idToMarketParams_(id).loanToken] + oldAmount - newAmount;
}
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);
}
}
definition isCreated(MorphoHarness.Id id) returns bool =
lastUpdate(id) != 0;
// Check that the fee is always lower than the max fee constant.
invariant feeInRange(MorphoHarness.Id id)
fee(id) <= Util.maxFee();
// Check that the accounting of totalSupplyShares is correct.
invariant sumSupplySharesCorrect(MorphoHarness.Id id)
to_mathint(totalSupplyShares(id)) == sumSupplyShares[id];
// Check that the accounting of totalBorrowShares is correct.
invariant sumBorrowSharesCorrect(MorphoHarness.Id id)
to_mathint(totalBorrowShares(id)) == sumBorrowShares[id];
// Check that a market only allows borrows up to the total supply.
// This invariant shows that markets are independent, tokens from one market cannot be taken by interacting with another market.
invariant borrowLessThanSupply(MorphoHarness.Id id)
totalBorrowAssets(id) <= totalSupplyAssets(id);
// Check correctness of applying idToMarketParams() to an identifier.
invariant hashOfMarketParamsOf(MorphoHarness.Id id)
isCreated(id) =>
Util.libId(idToMarketParams_(id)) == id;
// Check correctness of applying id() to a market params.
// This invariant is useful in the following rule, to link an id back to a market.
invariant marketParamsOfHashOf(MorphoHarness.MarketParams marketParams)
isCreated(Util.libId(marketParams)) =>
idToMarketParams_(Util.libId(marketParams)) == marketParams;
// Check that the idle amount on the singleton is greater to the sum amount, that is the sum over all the markets of the total supply plus the total collateral minus the total borrow.
invariant idleAmountLessThanBalance(address token)
idleAmount[token] <= balance[token]
{
// Safe requires on the sender because the contract cannot call the function itself.
preserved supply(MorphoHarness.MarketParams marketParams, uint256 assets, uint256 shares, address onBehalf, bytes data) with (env e) {
requireInvariant marketParamsOfHashOf(marketParams);
require e.msg.sender != currentContract;
}
preserved withdraw(MorphoHarness.MarketParams marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) with (env e) {
requireInvariant marketParamsOfHashOf(marketParams);
require e.msg.sender != currentContract;
}
preserved borrow(MorphoHarness.MarketParams marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) with (env e) {
requireInvariant marketParamsOfHashOf(marketParams);
require e.msg.sender != currentContract;
}
preserved repay(MorphoHarness.MarketParams marketParams, uint256 assets, uint256 shares, address onBehalf, bytes data) with (env e) {
requireInvariant marketParamsOfHashOf(marketParams);
require e.msg.sender != currentContract;
}
preserved supplyCollateral(MorphoHarness.MarketParams marketParams, uint256 assets, address onBehalf, bytes data) with (env e) {
requireInvariant marketParamsOfHashOf(marketParams);
require e.msg.sender != currentContract;
}
preserved withdrawCollateral(MorphoHarness.MarketParams marketParams, uint256 assets, address onBehalf, address receiver) with (env e) {
requireInvariant marketParamsOfHashOf(marketParams);
require e.msg.sender != currentContract;
}
preserved liquidate(MorphoHarness.MarketParams marketParams, address _b, uint256 shares, uint256 receiver, bytes data) with (env e) {
requireInvariant marketParamsOfHashOf(marketParams);
require e.msg.sender != currentContract;
}
}
// Check that a market can only exist if its LLTV is enabled.
invariant onlyEnabledLltv(MorphoHarness.MarketParams marketParams)
isCreated(Util.libId(marketParams)) => isLltvEnabled(marketParams.lltv);
invariant lltvSmallerThanWad(uint256 lltv)
isLltvEnabled(lltv) => lltv < Util.wad();
// Check that a market can only exist if its IRM is enabled.
invariant onlyEnabledIrm(MorphoHarness.MarketParams marketParams)
isCreated(Util.libId(marketParams)) => isIrmEnabled(marketParams.irm);
// Check the pseudo-injectivity of the hashing function id().
rule libIdUnique() {
MorphoHarness.MarketParams marketParams1;
MorphoHarness.MarketParams marketParams2;
// Assume that arguments are the same.
require Util.libId(marketParams1) == Util.libId(marketParams2);
assert marketParams1 == marketParams2;
}
// Check that only the user is able to change who is authorized to manage his position.
rule onlyUserCanAuthorizeWithoutSig(env e, method f, calldataarg data)
filtered {
f -> !f.isView && f.selector != sig:setAuthorizationWithSig(MorphoHarness.Authorization memory, MorphoHarness.Signature calldata).selector
}
{
address user;
address someone;
// Assume that it is another user that is interacting with Morpho.
require user != e.msg.sender;
bool authorizedBefore = isAuthorized(user, someone);
f(e, data);
bool authorizedAfter = isAuthorized(user, someone);
assert authorizedAfter == authorizedBefore;
}
// Check that only authorized users are able to decrease supply shares of a position.
rule userCannotLoseSupplyShares(env e, method f, calldataarg data)
filtered { f -> !f.isView }
{
MorphoHarness.Id id;
address user;
// Assume that the e.msg.sender is not authorized.
require !isAuthorized(user, e.msg.sender);
require user != e.msg.sender;
mathint sharesBefore = supplyShares(id, user);
f(e, data);
mathint sharesAfter = supplyShares(id, user);
assert sharesAfter >= sharesBefore;
}
// Check that only authorized users are able to increase the borrow shares of a position.
rule userCannotGainBorrowShares(env e, method f, calldataarg args)
filtered { f -> !f.isView }
{
MorphoHarness.Id id;
address user;
// Assume that the e.msg.sender is not authorized.
require !isAuthorized(user, e.msg.sender);
require user != e.msg.sender;
mathint sharesBefore = borrowShares(id, user);
f(e, args);
mathint sharesAfter = borrowShares(id, user);
assert sharesAfter <= sharesBefore;
}
// Check that users cannot lose collateral by unauthorized parties except in case of a liquidation.
rule userCannotLoseCollateralExceptLiquidate(env e, method f, calldataarg args)
filtered {
f -> !f.isView &&
f.selector != sig:liquidate(MorphoHarness.MarketParams, address, uint256, uint256, bytes).selector
}
{
MorphoHarness.Id id;
address user;
// Assume that the e.msg.sender is not authorized.
require !isAuthorized(user, e.msg.sender);
require user != e.msg.sender;
mathint collateralBefore = collateral(id, user);
f(e, args);
mathint collateralAfter = collateral(id, user);
assert collateralAfter >= collateralBefore;
}
// Check that users cannot lose collateral by unauthorized parties if they have no outstanding debt.
rule userWithoutBorrowCannotLoseCollateral(env e, method f, calldataarg args)
filtered { f -> !f.isView }
{
MorphoHarness.Id id;
address user;
// Assume that the e.msg.sender is not authorized.
require !isAuthorized(user, e.msg.sender);
require user != e.msg.sender;
// Assume that the user has no outstanding debt.
require borrowShares(id, user) == 0;
mathint collateralBefore = collateral(id, user);
f(e, args);
mathint collateralAfter = collateral(id, user);
assert collateralAfter >= collateralBefore;
}
// Invariant checking that the last updated time is never greater than the current time.
rule noTimeTravel(method f, env e, calldataarg args)
filtered { f -> !f.isView }
{
MorphoHarness.Id id;
// Assume the property before the interaction.
require lastUpdate(id) <= e.block.timestamp;
f(e, args);
assert lastUpdate(id) <= e.block.timestamp;
}