|
|
|
@ -27,7 +27,7 @@ rule noDoublePropose(uint256 pId, env e) { |
|
|
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
|
|
|
|
*/ |
|
|
|
|
rule immutableFieldsAfterProposalCreation(uint256 pId, env e, method f, calldataarg args) |
|
|
|
|
filtered { f -> !skip(f) } |
|
|
|
|
filtered { f -> !assumedSafe(f) } |
|
|
|
|
{ |
|
|
|
|
require proposalCreated(pId); |
|
|
|
|
|
|
|
|
@ -46,13 +46,9 @@ rule immutableFieldsAfterProposalCreation(uint256 pId, env e, method f, calldata |
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
|
|
|
|
│ Rule: A user cannot vote twice │ |
|
|
|
|
│ │ |
|
|
|
|
│ Checked for castVote only. all 3 castVote functions call _castVote, so the completeness of the verification is │ |
|
|
|
|
│ counted on the fact that the 3 functions themselves makes no changes, but rather call an internal function to │ |
|
|
|
|
│ execute. That means that we do not check those 3 functions directly, however for castVote & castVoteWithReason it │ |
|
|
|
|
│ is quite trivial to understand why this is ok. For castVoteBySig we basically assume that the signature referendum │ |
|
|
|
|
│ is correct without checking it. We could check each function separately and pass the rule, but that would have │ |
|
|
|
|
│ uglyfied the code with no concrete benefit, as it is evident that nothing is happening in the first 2 functions │ |
|
|
|
|
│ (calling a view function), and we do not desire to check the signature verification. │ |
|
|
|
|
│ This rule is checked for castVote, castVoteWithReason and castVoteWithReasonAndParams. For the signature variants │ |
|
|
|
|
│ (castVoteBySig and castVoteWithReasonAndParamsBySig) we basically assume that the signature referendum is correct │ |
|
|
|
|
│ without checking it. │ |
|
|
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
|
|
|
|
*/ |
|
|
|
|
rule noDoubleVoting(uint256 pId, env e, method f) |
|
|
|
@ -76,31 +72,20 @@ rule noDoubleVoting(uint256 pId, env e, method f) |
|
|
|
|
rule againstVotesDontCountTowardsQuorum(uint256 pId, env e) |
|
|
|
|
{ |
|
|
|
|
bool quorumReachedBefore = quorumReached(pId); |
|
|
|
|
|
|
|
|
|
// Ideally we would use `helperVoteWithRevert` here, but it causes timeout. Consider changing it if/when the prover improves. |
|
|
|
|
castVote(e, pId, 0); |
|
|
|
|
|
|
|
|
|
assert quorumReached(pId) == quorumReachedBefore, "quorum must not be reached with an against vote"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// This version is more exhaustive, but to slow because "quorumReached" is a FV nightmare |
|
|
|
|
// rule againstVotesDontCountTowardsQuorum(uint256 pId, env e, method f) |
|
|
|
|
// filtered { f -> voting(f) } |
|
|
|
|
// { |
|
|
|
|
// address voter; |
|
|
|
|
// |
|
|
|
|
// bool quorumReachedBefore = quorumReached(pId); |
|
|
|
|
// |
|
|
|
|
// helperVoteWithRevert(e, f, pId, voter, 0); // support 0 = against |
|
|
|
|
// |
|
|
|
|
// assert quorumReached(pId) == quorumReachedBefore, "quorum must not be reached with an against vote"; |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
|
|
|
|
│ Rule: A proposal could be executed only if quorum was reached and vote succeeded │ |
|
|
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
|
|
|
|
*/ |
|
|
|
|
rule executionOnlyIfQuoromReachedAndVoteSucceeded(uint256 pId, env e, method f, calldataarg args) |
|
|
|
|
filtered { f -> !skip(f) } |
|
|
|
|
filtered { f -> !assumedSafe(f) } |
|
|
|
|
{ |
|
|
|
|
require !isExecuted(pId); |
|
|
|
|
|
|
|
|
@ -118,7 +103,7 @@ rule executionOnlyIfQuoromReachedAndVoteSucceeded(uint256 pId, env e, method f, |
|
|
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
|
|
|
|
*/ |
|
|
|
|
rule noStartBeforeCreation(uint256 pId, env e, method f, calldataarg args) |
|
|
|
|
filtered { f -> !skip(f) } |
|
|
|
|
filtered { f -> !assumedSafe(f) } |
|
|
|
|
{ |
|
|
|
|
require !proposalCreated(pId); |
|
|
|
|
|
|
|
|
@ -133,7 +118,7 @@ rule noStartBeforeCreation(uint256 pId, env e, method f, calldataarg args) |
|
|
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
|
|
|
|
*/ |
|
|
|
|
rule noExecuteBeforeDeadline(uint256 pId, env e, method f, calldataarg args) |
|
|
|
|
filtered { f -> !skip(f) } |
|
|
|
|
filtered { f -> !assumedSafe(f) } |
|
|
|
|
{ |
|
|
|
|
require !isExecuted(pId); |
|
|
|
|
|
|
|
|
@ -149,7 +134,7 @@ rule noExecuteBeforeDeadline(uint256 pId, env e, method f, calldataarg args) |
|
|
|
|
*/ |
|
|
|
|
invariant quorumRatioLessThanOne() |
|
|
|
|
quorumNumerator() <= quorumDenominator() |
|
|
|
|
filtered { f -> !skip(f) } |
|
|
|
|
filtered { f -> !assumedSafe(f) } |
|
|
|
|
{ |
|
|
|
|
preserved { |
|
|
|
|
require quorumNumeratorLength() < max_uint256; |
|
|
|
@ -160,16 +145,14 @@ invariant quorumRatioLessThanOne() |
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ |
|
|
|
|
│ Rule: All proposal specific (non-view) functions should revert if proposal is executed │ |
|
|
|
|
│ │ |
|
|
|
|
│ In this rule we show that if a function is executed, i.e. execute() was called on the proposal ID, non of the │ |
|
|
|
|
│ proposal specific functions can make changes again. In executedOnlyAfterExecuteFunc we connected the executed │ |
|
|
|
|
│ attribute to the execute() function, showing that only execute() can change it, and that it will always change it. │ |
|
|
|
|
│ In this rule we show that if a function is executed, i.e. execute() was called on the proposal ID, none of the │ |
|
|
|
|
│ proposal specific functions can make changes again. Note that we prove that only the `execute()` function can set | |
|
|
|
|
| isExecuted() to true in in `GorvernorChanges.spec`. | |
|
|
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
|
|
|
|
*/ |
|
|
|
|
rule allFunctionsRevertIfExecuted(uint256 pId, env e, method f, calldataarg args) filtered { f -> |
|
|
|
|
!skip(f) && |
|
|
|
|
f.selector != updateQuorumNumerator(uint256).selector && |
|
|
|
|
f.selector != updateTimelock(address).selector |
|
|
|
|
} { |
|
|
|
|
rule allFunctionsRevertIfExecuted(uint256 pId, env e, method f, calldataarg args) |
|
|
|
|
filtered { f -> operateOnProposal(f) } |
|
|
|
|
{ |
|
|
|
|
require isExecuted(pId); |
|
|
|
|
requireInvariant noBothExecutedAndCanceled(pId); |
|
|
|
|
requireInvariant executedImplyCreated(pId); |
|
|
|
@ -184,15 +167,13 @@ rule allFunctionsRevertIfExecuted(uint256 pId, env e, method f, calldataarg args |
|
|
|
|
│ Rule: All proposal specific (non-view) functions should revert if proposal is canceled │ |
|
|
|
|
│ │ |
|
|
|
|
│ In this rule we show that if a function is executed, i.e. execute() was called on the proposal ID, non of the │ |
|
|
|
|
│ proposal specific functions can make changes again. In executedOnlyAfterExecuteFunc we connected the executed │ |
|
|
|
|
│ attribute to the execute() function, showing that only execute() can change it, and that it will always change it. │ |
|
|
|
|
│ proposal specific functions can make changes again. Note that we prove that only the `execute()` function can set | |
|
|
|
|
| isExecuted() to true in in `GorvernorChanges.spec`. | |
|
|
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
|
|
|
|
*/ |
|
|
|
|
rule allFunctionsRevertIfCanceled(uint256 pId, env e, method f, calldataarg args) filtered { f -> |
|
|
|
|
!skip(f) && |
|
|
|
|
f.selector != updateQuorumNumerator(uint256).selector && |
|
|
|
|
f.selector != updateTimelock(address).selector |
|
|
|
|
} { |
|
|
|
|
rule allFunctionsRevertIfCanceled(uint256 pId, env e, method f, calldataarg args) |
|
|
|
|
filtered { f -> operateOnProposal(f) } |
|
|
|
|
{ |
|
|
|
|
require isCanceled(pId); |
|
|
|
|
requireInvariant noBothExecutedAndCanceled(pId); |
|
|
|
|
requireInvariant canceledImplyCreated(pId); |
|
|
|
@ -208,7 +189,7 @@ rule allFunctionsRevertIfCanceled(uint256 pId, env e, method f, calldataarg args |
|
|
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ |
|
|
|
|
*/ |
|
|
|
|
rule privilegedUpdate(env e, method f, calldataarg args) |
|
|
|
|
filtered { f -> !skip(f) } |
|
|
|
|
filtered { f -> !assumedSafe(f) } |
|
|
|
|
{ |
|
|
|
|
address executorBefore = getExecutor(); |
|
|
|
|
uint256 quorumNumeratorBefore = quorumNumerator(); |
|
|
|
|