-
Notifications
You must be signed in to change notification settings - Fork 1
/
Example.spec
182 lines (152 loc) · 7.47 KB
/
Example.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
using ERC20 as asset;
//Method block defining envfree method (and summaries)
methods {
function totalSupply() external returns uint256 envfree;
function balanceOf(address) external returns uint256 envfree;
function allowance(address, address) external returns uint256 envfree;
function totalAssets() external returns uint256 envfree;
function asset.totalSupply() external returns uint256 envfree;
function asset.balanceOf(address) external returns uint256 envfree;
function asset.decimals() external returns uint8 envfree;
}
////////////////////////////////////////////////////////////////
// //
// Simple Example Rules and Invariants //
// //
////////////////////////////////////////////////////////////////
//Basic rule stating mint must increase totalAssets.
rule mintMustIncreaseTotalAssets(uint256 shares, address user){
//State basic assumption that will be verified and hold.
safeAssumptions();
uint256 totalAssetsBefore = totalAssets();
env e;
mint(e, shares, user);
uint256 totalAssetsAfter = totalAssets();
assert totalAssetsAfter >= totalAssetsBefore, "Total assets must increase when mint is called.";
}
//More complex rule stating a property on the relationship between totalSupply and totalAssets.
rule assetAndShareMonotonicity(env e, method f, uint256 amount, address receiver, address owner){
safeAssumptions();
uint256 totalAssetsBefore = totalAssets();
uint256 totalSupplyBefore = totalSupply();
//The assert only holds if the msg.sender isn't ERC4626 itself.
require(e.msg.sender != currentContract);
if(f.selector == sig:mint(uint,address).selector){
mint(e, amount, receiver);
} else if(f.selector == sig:withdraw(uint,address,address).selector){
//The rule only holds if the receiver isn't ERC4626 itself!
require(receiver != currentContract);
withdraw(e, amount, receiver, owner);
} else if(f.selector == sig:deposit(uint,address).selector){
deposit(e, amount, receiver);
} else if(f.selector == sig:redeem(uint,address,address).selector){
//Commented out this necessary requirement on purpose. The rule fails.
//require(receiver != currentContract);
redeem(e, amount, receiver, owner);
} else {
calldataarg args;
f(e,args);
}
uint256 totalAssetsAfter = totalAssets();
uint256 totalSupplyAfter = totalSupply();
assert totalSupplyBefore <= totalSupplyAfter <=> totalAssetsBefore <= totalAssetsAfter , "Monotonicity doesn't hold.";
}
/*An example of an invariant:
* An invariant is a property that holds before and after all method executions of a contract under verification.
*
* Invariant: When totalAssets of the underlying ERC20 vault (assets) is zero, totalSupply must be zero.
*/
invariant totalAssetsZeroImpliesTotalSupplyZero()
totalAssets() == 0 => totalSupply() == 0
{
preserved {
requireInvariant sumOfBalancesEqualsTotalSupplyERC4626;
requireInvariant sumOfBalancesEqualsTotalSupplyERC20;
}
}
//Invariant: When totalSupply is larger than zero, the underlying's ERC20 vault (asset) also must have a non-zero total balance.
invariant totalSupplyMatch()
totalSupply() > 0 => asset.totalSupply() > 0
{
preserved {
requireInvariant sumOfBalancesEqualsTotalSupplyERC20;
}
}
/*Safe assumptions below: These are invariants for the underyling ERC20 as well as ERC4626.
* The safeAssumptions use CVL features such as invariants, hooks and ghost.
*/
function safeAssumptions(){
requireInvariant sumOfBalancesEqualsTotalSupplyERC4626;
requireInvariant sumOfBalancesEqualsTotalSupplyERC20;
requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20;
requireInvariant singleUserBalanceSmallerThanTotalSupplyERC4626;
}
////////////////////////////////////////////////////////////////
// //
// CVL Invariants //
// //
////////////////////////////////////////////////////////////////
invariant sumOfBalancesEqualsTotalSupplyERC4626()
sumOfBalancesERC4626 == to_mathint(totalSupply());
invariant sumOfBalancesEqualsTotalSupplyERC20()
sumOfBalancesERC20 == to_mathint(asset.totalSupply());
invariant singleUserBalanceSmallerThanTotalSupplyERC20()
userBalanceERC20 <= sumOfBalancesERC20;
invariant singleUserBalanceSmallerThanTotalSupplyERC4626()
userBalanceERC4626 <= sumOfBalancesERC4626;
////////////////////////////////////////////////////////////////
// //
// Ghost Variables //
// //
////////////////////////////////////////////////////////////////
//A ghost is a state variable of CVL that can be modified, for instance, in hooks.
//Ghost variable tracking the total sum of all balances of ERC4626
ghost mathint sumOfBalancesERC4626 {
init_state axiom sumOfBalancesERC4626 == 0;
}
ghost mathint sumOfBalancesERC20 {
init_state axiom sumOfBalancesERC20 == 0;
}
ghost mathint userBalanceERC20 {
init_state axiom userBalanceERC20 == 0;
}
//Ghost variable tracking a "ghost" mapping of the balances of all addresses
ghost mapping(address => uint256) balanceOfMirroredERC4626 {
init_state axiom forall address a. (balanceOfMirroredERC4626[a] == 0);
}
ghost mapping(address => uint256) balanceOfMirroredERC20 {
init_state axiom forall address a. (balanceOfMirroredERC20[a] == 0);
}
ghost mathint userBalanceERC4626 {
init_state axiom userBalanceERC4626 == 0;
}
////////////////////////////////////////////////////////////////
// //
// CVL Hooks //
// //
////////////////////////////////////////////////////////////////
//Hooks intercept EVM instructions and allows adding custom logic.
//A store hook that triggers on every store to the storage variable `balanceOf` of ERC4626 (currentContract).
hook Sstore currentContract.balanceOf[KEY address user] uint256 newValue (uint256 oldValue) STORAGE {
//Updating the ghost variable sumOfBalances
sumOfBalancesERC4626 = sumOfBalancesERC4626 + newValue - oldValue;
//Also updating a second ghost variable.
userBalanceERC4626 = newValue;
balanceOfMirroredERC4626[user] = newValue;
}
//A store hook that triggers on every store to the storage variable `balanceOf` of ERC20.
hook Sstore asset.balanceOf[KEY address user] uint256 newValue (uint256 oldValue) STORAGE {
sumOfBalancesERC20 = sumOfBalancesERC20 + newValue - oldValue;
userBalanceERC20 = newValue;
balanceOfMirroredERC20[user] = newValue;
}
//A load hook that triggers on every load from the storage variable `balanceOf` of ERC20.
hook Sload uint256 value currentContract.balanceOf[KEY address user] STORAGE {
require to_mathint(value) <= sumOfBalancesERC4626;
require value == balanceOfMirroredERC4626[user];
}
//A load hook that triggers on every load from the storage variable `balanceOf` of ERC20.
hook Sload uint256 value asset.balanceOf[KEY address user] STORAGE {
require to_mathint(value) <= sumOfBalancesERC20;
require value == balanceOfMirroredERC20[user];
}