forked from morpho-org/morpho-blue
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ExchangeRate.spec
211 lines (158 loc) · 9 KB
/
ExchangeRate.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
// SPDX-License-Identifier: GPL-2.0-or-later
methods {
function extSloads(bytes32[]) external returns bytes32[] => NONDET DELETE;
function collateral(MorphoHarness.Id, address) external returns uint256 envfree;
function virtualTotalSupplyAssets(MorphoHarness.Id) external returns uint256 envfree;
function virtualTotalSupplyShares(MorphoHarness.Id) external returns uint256 envfree;
function virtualTotalBorrowAssets(MorphoHarness.Id) external returns uint256 envfree;
function virtualTotalBorrowShares(MorphoHarness.Id) external returns uint256 envfree;
function fee(MorphoHarness.Id) external returns uint256 envfree;
function lastUpdate(MorphoHarness.Id) external returns uint256 envfree;
function maxFee() external returns uint256 envfree;
function libId(MorphoHarness.MarketParams) external returns MorphoHarness.Id envfree;
function UtilsLib.min(uint256 x, uint256 y) internal returns uint256 => summaryMin(x, y);
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 MathLib.wTaylorCompounded(uint256, uint256) internal returns uint256 => NONDET;
function _.borrowRate(MorphoHarness.MarketParams, MorphoHarness.Market) external => HAVOC_ECF;
}
invariant feeInRange(MorphoHarness.Id id)
fee(id) <= maxFee();
function summaryMin(uint256 x, uint256 y) returns uint256 {
return x < y ? x : y;
}
// This is a simple overapproximative summary, stating that it rounds in the right direction.
function summaryMulDivUp(uint256 x, uint256 y, uint256 d) returns uint256 {
uint256 result;
// Safe require that is checked by the specification in LibSummary.spec.
require result * d >= x * y;
return result;
}
// This is a simple overapproximative summary, stating that it rounds in the right direction.
function summaryMulDivDown(uint256 x, uint256 y, uint256 d) returns uint256 {
uint256 result;
// Safe require that is checked by the specification in LibSummary.spec.
require result * d <= x * y;
return result;
}
// Check that accrueInterest increases the value of supply shares.
rule accrueInterestIncreasesSupplyExchangeRate(env e, MorphoHarness.MarketParams marketParams) {
MorphoHarness.Id id;
requireInvariant feeInRange(id);
mathint assetsBefore = virtualTotalSupplyAssets(id);
mathint sharesBefore = virtualTotalSupplyShares(id);
// The check is done for every market, not just for id.
accrueInterest(e, marketParams);
mathint assetsAfter = virtualTotalSupplyAssets(id);
mathint sharesAfter = virtualTotalSupplyShares(id);
// Check that the exchange rate increases: assetsBefore/sharesBefore <= assetsAfter/sharesAfter.
assert assetsBefore * sharesAfter <= assetsAfter * sharesBefore;
}
// Check that accrueInterest increases the value of borrow shares.
rule accrueInterestIncreasesBorrowExchangeRate(env e, MorphoHarness.MarketParams marketParams) {
MorphoHarness.Id id;
requireInvariant feeInRange(id);
mathint assetsBefore = virtualTotalBorrowAssets(id);
mathint sharesBefore = virtualTotalBorrowShares(id);
// The check is done for every marketParams, not just for id.
accrueInterest(e, marketParams);
mathint assetsAfter = virtualTotalBorrowAssets(id);
mathint sharesAfter = virtualTotalBorrowShares(id);
// Check that the exchange rate increases: assetsBefore/sharesBefore <= assetsAfter/sharesAfter.
assert assetsBefore * sharesAfter <= assetsAfter * sharesBefore;
}
// Check that except when not accruing interest and except for liquidate, every function increases the value of supply shares.
rule onlyLiquidateCanDecreaseSupplyExchangeRate(env e, method f, calldataarg args)
filtered {
f -> !f.isView && f.selector != sig:liquidate(MorphoHarness.MarketParams, address, uint256, uint256, bytes).selector
}
{
MorphoHarness.Id id;
requireInvariant feeInRange(id);
mathint assetsBefore = virtualTotalSupplyAssets(id);
mathint sharesBefore = virtualTotalSupplyShares(id);
// Interest is checked separately by the rules above.
// Here we assume interest has already been accumulated for this block.
require lastUpdate(id) == e.block.timestamp;
f(e,args);
mathint assetsAfter = virtualTotalSupplyAssets(id);
mathint sharesAfter = virtualTotalSupplyShares(id);
// Check that the exchange rate increases: assetsBefore/sharesBefore <= assetsAfter/sharesAfter
assert assetsBefore * sharesAfter <= assetsAfter * sharesBefore;
}
// Check that when not realizing bad debt in liquidate, the value of supply shares increases.
rule liquidateWithoutBadDebtRealizationIncreasesSupplyExchangeRate(env e, MorphoHarness.MarketParams marketParams, address borrower, uint256 seizedAssets, uint256 repaidShares, bytes data)
{
MorphoHarness.Id id;
requireInvariant feeInRange(id);
mathint assetsBefore = virtualTotalSupplyAssets(id);
mathint sharesBefore = virtualTotalSupplyShares(id);
// Interest is checked separately by the rules above.
// Here we assume interest has already been accumulated for this block.
require lastUpdate(id) == e.block.timestamp;
liquidate(e, marketParams, borrower, seizedAssets, repaidShares, data);
mathint assetsAfter = virtualTotalSupplyAssets(id);
mathint sharesAfter = virtualTotalSupplyShares(id);
// Trick to ensure that no bad debt realization happened.
require collateral(id, borrower) != 0;
// Check that the exchange rate increases: assetsBefore/sharesBefore <= assetsAfter/sharesAfter
assert assetsBefore * sharesAfter <= assetsAfter * sharesBefore;
}
// Check that except when not accruing interest, every function is decreasing the value of borrow shares.
// The repay function is checked separately, see below.
// The liquidate function is checked separately, see below.
rule onlyAccrueInterestCanIncreaseBorrowExchangeRate(env e, method f, calldataarg args)
filtered {
f -> !f.isView &&
f.selector != sig:repay(MorphoHarness.MarketParams, uint256, uint256, address, bytes).selector &&
f.selector != sig:liquidate(MorphoHarness.MarketParams, address, uint256, uint256, bytes).selector
}
{
MorphoHarness.Id id;
requireInvariant feeInRange(id);
// Interest would increase borrow exchange rate, so we need to assume that no time passes.
require lastUpdate(id) == e.block.timestamp;
mathint assetsBefore = virtualTotalBorrowAssets(id);
mathint sharesBefore = virtualTotalBorrowShares(id);
f(e,args);
mathint assetsAfter = virtualTotalBorrowAssets(id);
mathint sharesAfter = virtualTotalBorrowShares(id);
// Check that the exchange rate decreases: assetsBefore/sharesBefore >= assetsAfter/sharesAfter
assert assetsBefore * sharesAfter >= assetsAfter * sharesBefore;
}
// Check that when not accruing interest, repay is decreasing the value of borrow shares.
// Check the case where it would not repay more than the total assets.
// The other case requires exact math (ie not over-approximating mulDivUp and mulDivDown), so it is checked separately in ExactMath.spec.
rule repayDecreasesBorrowExchangeRate(env e, MorphoHarness.MarketParams marketParams, uint256 assets, uint256 shares, address onBehalf, bytes data)
{
MorphoHarness.Id id = libId(marketParams);
requireInvariant feeInRange(id);
mathint assetsBefore = virtualTotalBorrowAssets(id);
mathint sharesBefore = virtualTotalBorrowShares(id);
// Interest would increase borrow exchange rate, so we need to assume that no time passes.
require lastUpdate(id) == e.block.timestamp;
mathint repaidAssets;
repaidAssets, _ = repay(e, marketParams, assets, shares, onBehalf, data);
// Check the case where it would not repay more than the total assets.
require repaidAssets < assetsBefore;
mathint assetsAfter = virtualTotalBorrowAssets(id);
mathint sharesAfter = virtualTotalBorrowShares(id);
assert assetsAfter == assetsBefore - repaidAssets;
// Check that the exchange rate decreases: assetsBefore/sharesBefore >= assetsAfter/sharesAfter
assert assetsBefore * sharesAfter >= assetsAfter * sharesBefore;
}
rule liquidateDecreasesBorrowExchangeRate(env e, MorphoHarness.MarketParams marketParams, address borrower, uint256 seizedAssets, uint256 repaidShares, bytes data)
{
require data.length == 0;
MorphoHarness.Id id = libId(marketParams);
mathint assetsBefore = virtualTotalBorrowAssets(id);
mathint sharesBefore = virtualTotalBorrowShares(id);
// Interest would increase borrow exchange rate, so we need to assume that no time passes.
require lastUpdate(id) == e.block.timestamp;
liquidate(e, marketParams, borrower, seizedAssets, repaidShares, data);
mathint assetsAfter = virtualTotalBorrowAssets(id);
mathint sharesAfter = virtualTotalBorrowShares(id);
require assetsAfter != 1;
// Check that the exchange rate decreases: assetsBefore/sharesBefore >= assetsAfter/sharesAfter
assert assetsBefore * sharesAfter >= assetsAfter * sharesBefore;
}