-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathSafe.spec
More file actions
155 lines (124 loc) · 6.19 KB
/
Safe.spec
File metadata and controls
155 lines (124 loc) · 6.19 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
methods {
//
function getThreshold() external returns (uint256) envfree;
function disableModule(address,address) external;
function nonce() external returns (uint256) envfree;
// harnessed
function getModule(address) external returns (address) envfree;
function getSafeGuard() external returns (address) envfree;
// optional
function execTransactionFromModuleReturnData(address,uint256,bytes,SafeHarness.Operation) external returns (bool, bytes memory);
function execTransactionFromModule(address,uint256,bytes,SafeHarness.Operation) external returns (bool);
function execTransaction(address,uint256,bytes,SafeHarness.Operation,uint256,uint256,uint256,address,address,bytes) external returns (bool);
}
definition noHavoc(method f) returns bool =
f.selector != sig:execTransactionFromModuleReturnData(address,uint256,bytes,SafeHarness.Operation).selector
&& f.selector != sig:execTransactionFromModule(address,uint256,bytes,SafeHarness.Operation).selector
&& f.selector != sig:execTransaction(address,uint256,bytes,SafeHarness.Operation,uint256,uint256,uint256,address,address,bytes).selector;
definition reachableOnly(method f) returns bool =
f.selector != sig:setup(address[],uint256,address,bytes,address,address,uint256,address).selector
&& f.selector != sig:simulateAndRevert(address,bytes).selector;
definition MAX_UINT256() returns uint256 = 0xffffffffffffffffffffffffffffffff;
/// Nonce must never decrease
rule nonceMonotonicity(method f) filtered {
f -> reachableOnly(f) &&
f.selector != sig:getStorageAt(uint256,uint256).selector
} {
uint256 nonceBefore = nonce();
// The nonce may overflow, but since it's increased only by 1 with each transaction, it is not realistically possible to overflow it.
require nonceBefore < MAX_UINT256();
calldataarg args; env e;
f(e, args);
uint256 nonceAfter = nonce();
assert nonceAfter != nonceBefore =>
to_mathint(nonceAfter) == nonceBefore + 1 && f.selector == sig:execTransaction(address,uint256,bytes,SafeHarness.Operation,uint256,uint256,uint256,address,address,bytes).selector;
}
/// The sentinel must never point to the zero address.
/// @notice It should either point to itself or some nonzero value
invariant liveSentinel()
getModule(1) != 0
filtered { f -> noHavoc(f) && reachableOnly(f) }
{ preserved {
requireInvariant noDeadEnds(getModule(1), 1);
}}
/// Threshold must always be nonzero.
invariant nonzeroThreshold()
getThreshold() > 0
filtered { f -> noHavoc(f) && reachableOnly(f) }
/// Two different modules must not point to the same module/
invariant uniquePrevs(address prev1, address prev2)
prev1 != prev2 && getModule(prev1) != 0 => getModule(prev1) != getModule(prev2)
filtered { f -> noHavoc(f) && reachableOnly(f) }
{
preserved {
requireInvariant noDeadEnds(getModule(prev1), prev1);
requireInvariant noDeadEnds(getModule(prev2), prev2);
requireInvariant uniquePrevs(prev1, 1);
requireInvariant uniquePrevs(prev2, 1);
requireInvariant uniquePrevs(prev1, getModule(prev2));
requireInvariant uniquePrevs(prev2, getModule(prev1));
}
}
/// A module that points to the zero address must not have another module pointing to it.
invariant noDeadEnds(address dead, address lost)
dead != 0 && getModule(dead) == 0 => getModule(lost) != dead
filtered { f -> noHavoc(f) && reachableOnly(f) }
{
preserved {
requireInvariant liveSentinel();
requireInvariant noDeadEnds(getModule(1), 1);
}
preserved disableModule(address prevModule, address module) with (env e) {
requireInvariant uniquePrevs(prevModule, lost);
requireInvariant uniquePrevs(prevModule, dead);
requireInvariant noDeadEnds(dead, module);
requireInvariant noDeadEnds(module, dead);
}
}
// The singleton is a private variable, so we need to use a ghost variable to track it.
ghost address ghostSingletonAddress {
init_state axiom ghostSingletonAddress == 0;
}
hook Sstore SafeHarness.(slot 0) address newSingletonAddress STORAGE {
ghostSingletonAddress = newSingletonAddress;
}
// This is EIP-1967's singleton storage slot:
// 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
// converted to decimal because certora doesn't seem to support hex yet.
hook Sstore SafeHarness.(slot 24440054405305269366569402256811496959409073762505157381672968839269610695612) address newSingletonAddress STORAGE {
ghostSingletonAddress = newSingletonAddress;
}
invariant sigletonAddressNeverChanges()
ghostSingletonAddress == 0
filtered { f -> reachableOnly(f) && f.selector != sig:getStorageAt(uint256,uint256).selector }
ghost address fallbackHandlerAddress {
init_state axiom fallbackHandlerAddress == 0;
}
// This is Safe's fallback handler storage slot:
// 0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5
// converted to decimal because certora doesn't seem to support hex yet.
hook Sstore SafeHarness.(slot 49122629484629529244014240937346711770925847994644146912111677022347558721749) address newFallbackHandlerAddress STORAGE {
fallbackHandlerAddress = newFallbackHandlerAddress;
}
rule fallbackHandlerAddressChange(method f) filtered {
f -> f.selector != sig:simulateAndRevert(address,bytes).selector &&
f.selector != sig:getStorageAt(uint256,uint256).selector
} {
address fbHandlerBefore = fallbackHandlerAddress;
calldataarg args; env e;
f(e, args);
address fbHandlerAfter = fallbackHandlerAddress;
assert fbHandlerBefore != fbHandlerAfter =>
f.selector == sig:setup(address[],uint256,address,bytes,address,address,uint256,address).selector || f.selector == sig:setFallbackHandler(address).selector;
}
rule guardAddressChange(method f) filtered {
f -> f.selector != sig:simulateAndRevert(address,bytes).selector &&
f.selector != sig:getStorageAt(uint256,uint256).selector
} {
address guardBefore = getSafeGuard();
calldataarg args; env e;
f(e, args);
address guardAfter = getSafeGuard();
assert guardBefore != guardAfter =>
f.selector == sig:setGuard(address).selector;
}