-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathMErc20DelegateFixer.spec
More file actions
233 lines (177 loc) · 8.13 KB
/
Copy pathMErc20DelegateFixer.spec
File metadata and controls
233 lines (177 loc) · 8.13 KB
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
using MockERC20 as token;
using MockMErc20DelegateFixer as fixer;
using MockComptroller as comptroller;
using JumpRateModel as jrm;
methods {
function badDebt() external returns (uint256) envfree;
function fixUser(address liquidator, address user) external;
function borrowIndex() external returns (uint256) envfree;
function totalBorrows() external returns (uint256) envfree;
function totalReserves() external returns (uint256) envfree;
function token.balanceOf(address) external returns (uint256) envfree;
function balanceOf(address) external returns (uint256) envfree;
function repayBadDebtWithCash(uint256) external;
function accrueInterest() external returns (uint256);
function exchangeRateCurrent() external returns (uint256);
function borrowBalanceCurrent(address account) external returns (uint256);
function repayBadDebtWithReserves() external envfree;
function getUserBorrowSnapshot(address user) external returns (uint256, uint256) envfree;
function getUserBorrowInterestIndex(address user) external returns (uint256) envfree;
function getInitialExchangeRateMantissa() external returns (uint256) envfree;
function fixer.sweepToken(address) external => CONSTANT;
function fixer.liquidateBorrow(address borrower, uint256 repayAmount, address) external returns (uint256) => CONSTANT;
function fixer.seize(address liquidator, address borrower, uint256 seizeTokens) external returns (uint) => CONSTANT;
function accrueInterest() external returns uint256 => CONSTANT;
/// summarize these calls to prevent prover havoc
function _.isComptroller() external => DISPATCHER(true);
function _.isInterestRateModel() external => DISPATCHER(true);
function _.admin() external => DISPATCHER(true);
function _.borrowIndex() external => DISPATCHER(true);
function _._acceptImplementation() external => CONSTANT;
function comptroller._ external => NONDET;
function jrm._ external => NONDET;
}
ghost uint256 borrowIndex {
init_state axiom borrowIndex == 0;
}
ghost uint256 totalBorrows {
init_state axiom totalBorrows == 0;
}
ghost uint256 initialExchangeRateMantissa {
init_state axiom initialExchangeRateMantissa == 0;
}
hook Sstore borrowIndex uint256 newBorrowIndex (uint256 oldBorrowIndex) STORAGE {
borrowIndex = newBorrowIndex;
}
hook Sstore totalBorrows uint256 newTotalBorrows (uint256 oldTotalBorrows) STORAGE {
totalBorrows = newTotalBorrows;
}
hook Sstore initialExchangeRateMantissa uint256 newInitialExchangeRateMantissa (uint256 oldInitialExchangeRateMantissa) STORAGE {
initialExchangeRateMantissa = newInitialExchangeRateMantissa;
}
function one() returns uint256 {
return 1000000000000000000;
}
function uintMax() returns uint256 {
return 2 ^ 256 - 1;
}
/// market initialization check
invariant ghostInitialExchangeRateMantissaMirrorsStorage()
initialExchangeRateMantissa == getInitialExchangeRateMantissa();
invariant ghostBorrowIndexMirrorsStorage()
borrowIndex == borrowIndex();
invariant ghostTotalBorrowsMirrorsStorage()
totalBorrows == totalBorrows();
invariant initialBorrowIndexGteOne()
borrowIndex() != 0 => borrowIndex() >= one();
invariant exchangeRateGteOne(env e)
borrowIndex() >= one() => exchangeRateCurrent(e) >= one() {
preserved {
requireInvariant initialBorrowIndexGteOne();
}
}
invariant userBorrowIndexLteExchangeRateCurrent(env e, address user)
getUserBorrowInterestIndex(user) != 0 =>
(getUserBorrowInterestIndex(user) <= exchangeRateCurrent(e) && getUserBorrowInterestIndex(user) >= one()) {
preserved {
requireInvariant ghostBorrowIndexMirrorsStorage();
}
}
rule fixUserIncreasesBadDebt(env e) {
address user;
address liquidator;
uint256 principle;
uint256 interestIndex;
principle, interestIndex = getUserBorrowSnapshot(user);
uint256 badDebt = badDebt();
uint256 liquidatorBalance = balanceOf(liquidator);
uint256 userBalance = balanceOf(user);
uint256 borrowBalance = borrowBalanceCurrent(e, user);
fixUser(e, liquidator, user);
uint256 badDebtAfter = badDebt();
assert badDebtAfter >= badDebt, "bad debt decreased from fixing a user";
assert balanceOf(user) == 0, "user balance not zero";
assert (liquidatorBalance + userBalance == to_mathint(balanceOf(liquidator))), "liquidator balance not increased by user balance";
assert borrowBalanceCurrent(e, user) == 0, "user borrow balance not zero";
assert interestIndex >= one() => (badDebt + borrowBalance == to_mathint(badDebtAfter)), "bad debt not increased by user borrow amt";
}
rule fixingUserDoesNotChangeSharePrice(env e) {
address user;
address liquidator;
uint256 startingSharePrice = exchangeRateCurrent(e);
fixUser(e, liquidator, user);
assert exchangeRateCurrent(e) == startingSharePrice, "share price should not change fixing user";
}
rule repayBadDebtDecreasesBadDebt(env e, uint256 repayAmount) {
require e.msg.sender != fixer;
uint256 badDebt = badDebt();
uint256 userBalance = token.balanceOf(e.msg.sender);
uint256 mTokenBalance = token.balanceOf(fixer);
uint256 startingSharePrice = exchangeRateCurrent(e);
repayBadDebtWithCash(e, repayAmount);
uint256 badDebtAfter = badDebt();
assert repayAmount != 0 => badDebtAfter < badDebt, "bad debt did not decrease from repaying";
assert badDebtAfter <= badDebt, "bad debt increased from repaying";
assert to_mathint(token.balanceOf(fixer)) == mTokenBalance + repayAmount, "underlying balance did not increase";
assert badDebt - repayAmount == to_mathint(badDebt()), "bad debt not decreased by repay amt";
assert exchangeRateCurrent(e) == startingSharePrice, "share price should not change repaying bad debt";
}
rule badDebtRules(method f, env e, calldataarg args)
filtered {
f ->
f.selector == sig:fixUser(address,address).selector ||
f.selector == sig:repayBadDebtWithCash(uint256).selector ||
f.selector == sig:repayBadDebtWithReserves().selector
} {
require e.msg.sender != fixer;
uint256 startingSharePrice = exchangeRateCurrent(e);
uint256 startingBadDebt = badDebt();
uint256 startingCash = token.balanceOf(fixer);
f(e, args);
uint256 endingBadDebt = badDebt();
assert startingCash + startingBadDebt <= to_mathint(uintMax()) =>
exchangeRateCurrent(e) == startingSharePrice,
"share price should not change repaying bad debt";
assert (endingBadDebt > startingBadDebt) =>
(f.selector == sig:fixUser(address,address).selector),
"bad debt should only increase when fixing users";
assert (startingBadDebt >= endingBadDebt) =>
(f.selector == sig:repayBadDebtWithCash(uint256).selector ||
f.selector == sig:repayBadDebtWithReserves().selector),
"bad debt should only increase when fixing users";
}
rule cannotChangeBadDebt(method f, env e, calldataarg args)
filtered {
f ->
!f.isView &&
f.selector != sig:fixUser(address,address).selector &&
f.selector != sig:repayBadDebtWithCash(uint256).selector &&
f.selector != sig:repayBadDebtWithReserves().selector
} {
require e.msg.sender != fixer;
uint256 startingBadDebt = badDebt();
f(e, args);
uint256 endingBadDebt = badDebt();
assert endingBadDebt == startingBadDebt,
"bad debt should not change";
}
rule repayBadDebtWithReserves(env e) {
uint256 startingReserves = totalReserves();
uint256 startingBadDebt = badDebt();
repayBadDebtWithReserves();
uint256 endingReserves = totalReserves();
uint256 endingBadDebt = badDebt();
assert (startingReserves >= startingBadDebt) =>
(endingBadDebt == 0),
"bad debt not fully paid off";
assert (startingReserves < startingBadDebt) =>
(to_mathint(endingBadDebt) == startingBadDebt - startingReserves),
"bad debt not paid off by reserve amount";
}
rule repayBadDebtWithReservesDoesNotChangeSharePrice(env e) {
require e.msg.sender != fixer;
uint256 startingSharePrice = exchangeRateCurrent(e);
repayBadDebtWithReserves();
uint256 endingSharePrice = exchangeRateCurrent(e);
assert endingSharePrice == startingSharePrice, "share price should remain unchanged";
}