|
@@ -1,22 +1,8 @@
|
|
-//////////////////////////////////////////////////////////////////////////////
|
|
|
|
-///////////////////// Governor.sol base definitions //////////////////////////
|
|
|
|
-//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
+import "GovernorCountingSimple.spec"
|
|
|
|
|
|
using ERC721VotesHarness as erc721votes
|
|
using ERC721VotesHarness as erc721votes
|
|
-using ERC20VotesHarness as erc20votes
|
|
|
|
|
|
|
|
methods {
|
|
methods {
|
|
- proposalSnapshot(uint256) returns uint256 envfree // matches proposalVoteStart
|
|
|
|
- proposalDeadline(uint256) returns uint256 envfree // matches proposalVoteEnd
|
|
|
|
- hashProposal(address[],uint256[],bytes[],bytes32) returns uint256 envfree
|
|
|
|
- isExecuted(uint256) returns bool envfree
|
|
|
|
- isCanceled(uint256) returns bool envfree
|
|
|
|
- execute(address[], uint256[], bytes[], bytes32) returns uint256
|
|
|
|
- hasVoted(uint256, address) returns bool
|
|
|
|
- castVote(uint256, uint8) returns uint256
|
|
|
|
- updateQuorumNumerator(uint256)
|
|
|
|
- queue(address[], uint256[], bytes[], bytes32) returns uint256
|
|
|
|
- quorumNumerator() returns uint256 envfree
|
|
|
|
quorumDenominator() returns uint256 envfree
|
|
quorumDenominator() returns uint256 envfree
|
|
votingPeriod() returns uint256 envfree
|
|
votingPeriod() returns uint256 envfree
|
|
lateQuorumVoteExtension() returns uint64 envfree
|
|
lateQuorumVoteExtension() returns uint64 envfree
|
|
@@ -24,20 +10,12 @@ methods {
|
|
|
|
|
|
// harness
|
|
// harness
|
|
getExtendedDeadlineIsUnset(uint256) returns bool envfree
|
|
getExtendedDeadlineIsUnset(uint256) returns bool envfree
|
|
|
|
+ getExtendedDeadlineIsStarted(uint256) returns bool envfree
|
|
getExtendedDeadline(uint256) returns uint64 envfree
|
|
getExtendedDeadline(uint256) returns uint64 envfree
|
|
- quorumReached(uint256) returns bool envfree
|
|
|
|
- voteSucceeded(uint256) returns bool envfree
|
|
|
|
- quorum(uint256) returns uint256
|
|
|
|
- latestCastVoteCall() returns uint256 envfree // more robust check than f.selector == _castVote(...).selector
|
|
|
|
-
|
|
|
|
- // function summarization
|
|
|
|
- proposalThreshold() returns uint256 envfree
|
|
|
|
-
|
|
|
|
- // erc20votes dispatch
|
|
|
|
- getVotes(address, uint256) returns uint256 => DISPATCHER(true)
|
|
|
|
- // erc721votes/Votes dispatch
|
|
|
|
- getPastTotalSupply(uint256) returns uint256 => DISPATCHER(true)
|
|
|
|
- getPastVotes(address, uint256) returns uint256 => DISPATCHER(true)
|
|
|
|
|
|
+
|
|
|
|
+ // more robust check than f.selector == _castVote(...).selector
|
|
|
|
+ latestCastVoteCall() returns uint256 envfree
|
|
|
|
+
|
|
// timelock dispatch
|
|
// timelock dispatch
|
|
getMinDelay() returns uint256 => DISPATCHER(true)
|
|
getMinDelay() returns uint256 => DISPATCHER(true)
|
|
|
|
|
|
@@ -46,34 +24,72 @@ methods {
|
|
scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256) => CONSTANT
|
|
scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256) => CONSTANT
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+
|
|
|
|
+//////////////////////////////////////////////////////////////////////////////
|
|
|
|
+///////////////////////////// Helper Functions ///////////////////////////////
|
|
|
|
+//////////////////////////////////////////////////////////////////////////////
|
|
|
|
+
|
|
|
|
+function helperFunctionsWithRevertOnlyCastVote(uint256 proposalId, method f, env e) {
|
|
|
|
+ string reason; uint8 support; uint8 v; bytes32 r; bytes32 s; bytes params;
|
|
|
|
+ if (f.selector == castVote(uint256, uint8).selector) {
|
|
|
|
+ castVote@withrevert(e, proposalId, support);
|
|
|
|
+ } else if (f.selector == castVoteWithReason(uint256, uint8, string).selector) {
|
|
|
|
+ castVoteWithReason@withrevert(e, proposalId, support, reason);
|
|
|
|
+ } else if (f.selector == castVoteBySig(uint256, uint8,uint8, bytes32, bytes32).selector) {
|
|
|
|
+ castVoteBySig@withrevert(e, proposalId, support, v, r, s);
|
|
|
|
+ } else if (f.selector == castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32).selector) {
|
|
|
|
+ castVoteWithReasonAndParamsBySig@withrevert(e, proposalId, support, reason, params, v, r, s);
|
|
|
|
+ } else if (f.selector == castVoteWithReasonAndParams(uint256,uint8,string,bytes).selector) {
|
|
|
|
+ castVoteWithReasonAndParams@withrevert(e, proposalId, support, reason, params);
|
|
|
|
+ } else {
|
|
|
|
+ calldataarg args;
|
|
|
|
+ f@withrevert(e, args);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////// Definitions /////////////////////////////////
|
|
//////////////////////////////// Definitions /////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
-// where can invariants help?
|
|
|
|
-// can I replace definitions with invariants?
|
|
|
|
|
|
+// proposal deadline can be extended (but isn't)
|
|
|
|
+definition deadlineExtendable(env e, uint256 pId) returns bool =
|
|
|
|
+ getExtendedDeadlineIsUnset(pId)
|
|
|
|
+ && !quorumReached(e, pId);
|
|
|
|
+
|
|
|
|
+// proposal deadline has been extended
|
|
|
|
+definition deadlineExtended(env e, uint256 pId) returns bool =
|
|
|
|
+ getExtendedDeadlineIsStarted(pId)
|
|
|
|
+ && quorumReached(e, pId);
|
|
|
|
+
|
|
|
|
|
|
-// create definition for extended
|
|
|
|
-definition deadlineCanBeExtended(uint256 pId) returns bool =
|
|
|
|
- getExtendedDeadlineIsUnset(pId) &&
|
|
|
|
- !quorumReached(pId);
|
|
|
|
|
|
+//////////////////////////////////////////////////////////////////////////////
|
|
|
|
+///////////////////////////////// Invariants /////////////////////////////////
|
|
|
|
+//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
-definition deadlineHasBeenExtended(uint256 pId) returns bool =
|
|
|
|
- !getExtendedDeadlineIsUnset(pId) &&
|
|
|
|
- quorumReached(pId);
|
|
|
|
|
|
+/*
|
|
|
|
+ * I1: A propsal must be in state deadlineExtendable or deadlineExtended.
|
|
|
|
+ * --INVARIANT PASSING // fails for updateQuorumNumerator
|
|
|
|
+ * --ADVANCED SANITY PASSING // can't sanity test failing rules, not sure how it works for invariants
|
|
|
|
+ */
|
|
|
|
+invariant proposalInOneState(env e, uint256 pId)
|
|
|
|
+ deadlineExtendable(e, pId) || deadlineExtended(e, pId)
|
|
|
|
+ { preserved { require proposalCreated(pId); } }
|
|
|
|
|
|
-definition proposalCreated(uint256 pId) returns bool = proposalSnapshot(pId) > 0;
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////// Rules /////////////////////////////////////
|
|
////////////////////////////////// Rules /////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
-// RULE deadline can only be extended only once PASSING but vacuous
|
|
|
|
- // 1. if deadline changes then we have state transition to deadlineHasBeenExtended RULE PASSING; ADV SANITY PASSING
|
|
|
|
-rule deadlineChangeEffects(method f)
|
|
|
|
- filtered {
|
|
|
|
- f -> !f.isView
|
|
|
|
- } {
|
|
|
|
|
|
+///////////////////////////// first set of rules /////////////////////////////
|
|
|
|
+
|
|
|
|
+// R1 and R2 are assumed in R3, so we prove them first
|
|
|
|
+/*
|
|
|
|
+ * R1: If deadline increases then we are in deadlineExtended state and castVote was called.
|
|
|
|
+ * RULE PASSING
|
|
|
|
+ * ADVANCED SANITY PASSING
|
|
|
|
+ */
|
|
|
|
+rule deadlineChangeEffects(method f) filtered {f -> !f.isView} {
|
|
env e; calldataarg args; uint256 pId;
|
|
env e; calldataarg args; uint256 pId;
|
|
|
|
|
|
require (proposalCreated(pId));
|
|
require (proposalCreated(pId));
|
|
@@ -82,10 +98,15 @@ rule deadlineChangeEffects(method f)
|
|
f(e, args);
|
|
f(e, args);
|
|
uint256 deadlineAfter = proposalDeadline(pId);
|
|
uint256 deadlineAfter = proposalDeadline(pId);
|
|
|
|
|
|
- assert(deadlineAfter > deadlineBefore => latestCastVoteCall() == e.block.number && deadlineHasBeenExtended(pId));
|
|
|
|
|
|
+ assert(deadlineAfter > deadlineBefore => latestCastVoteCall() == e.block.number && deadlineExtended(e, pId));
|
|
}
|
|
}
|
|
|
|
|
|
- // 2. cant unextend RULE PASSING*; ADV SANITY PASSING
|
|
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * R2: A proposal can't leave deadlineExtended state.
|
|
|
|
+ * RULE PASSING*
|
|
|
|
+ * ADVANCED SANITY PASSING
|
|
|
|
+ */
|
|
rule deadlineCantBeUnextended(method f)
|
|
rule deadlineCantBeUnextended(method f)
|
|
filtered {
|
|
filtered {
|
|
f -> !f.isView
|
|
f -> !f.isView
|
|
@@ -93,23 +114,24 @@ rule deadlineCantBeUnextended(method f)
|
|
} {
|
|
} {
|
|
env e; calldataarg args; uint256 pId;
|
|
env e; calldataarg args; uint256 pId;
|
|
|
|
|
|
- require(deadlineHasBeenExtended(pId));
|
|
|
|
|
|
+ require(deadlineExtended(e, pId));
|
|
require(proposalCreated(pId));
|
|
require(proposalCreated(pId));
|
|
|
|
|
|
f(e, args);
|
|
f(e, args);
|
|
|
|
|
|
- assert(deadlineHasBeenExtended(pId));
|
|
|
|
|
|
+ assert(deadlineExtended(e, pId));
|
|
}
|
|
}
|
|
|
|
|
|
- // 3. extended => can't change deadline RULE PASSING; ADV SANITY PASSING
|
|
|
|
- //@note if deadline changed, then it wasnt extended and castvote was called
|
|
|
|
-rule canExtendDeadlineOnce(method f)
|
|
|
|
- filtered {
|
|
|
|
- f -> !f.isView
|
|
|
|
- } {
|
|
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * R3: A proposal's deadline can't change in deadlineExtended state.
|
|
|
|
+ * RULE PASSING*
|
|
|
|
+ * ADVANCED SANITY PASSING
|
|
|
|
+ */
|
|
|
|
+rule canExtendDeadlineOnce(method f) filtered {f -> !f.isView} {
|
|
env e; calldataarg args; uint256 pId;
|
|
env e; calldataarg args; uint256 pId;
|
|
|
|
|
|
- require(deadlineHasBeenExtended(pId));
|
|
|
|
|
|
+ require(deadlineExtended(e, pId));
|
|
require(proposalCreated(pId));
|
|
require(proposalCreated(pId));
|
|
|
|
|
|
uint256 deadlineBefore = proposalDeadline(pId);
|
|
uint256 deadlineBefore = proposalDeadline(pId);
|
|
@@ -118,3 +140,145 @@ rule canExtendDeadlineOnce(method f)
|
|
|
|
|
|
assert(deadlineBefore == deadlineAfter, "deadline can not be extended twice");
|
|
assert(deadlineBefore == deadlineAfter, "deadline can not be extended twice");
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+//////////////////////////// second set of rules ////////////////////////////
|
|
|
|
+
|
|
|
|
+// HIGH LEVEL RULE R6: deadline can only extended if quorum reached w/ <= timeOfExtension left to vote
|
|
|
|
+// I1, R4 and R5 are assumed in R6 so we prove them first
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * R4: A change in hasVoted must be correlated with an increasing of the vote supports, i.e. casting a vote increases the total number of votes.
|
|
|
|
+ * RULE PASSING
|
|
|
|
+ * ADVANCED SANITY PASSING
|
|
|
|
+ */
|
|
|
|
+rule hasVotedCorrelationNonzero(uint256 pId, method f, env e) filtered {f -> !f.isView} {
|
|
|
|
+ address acc = e.msg.sender;
|
|
|
|
+
|
|
|
|
+ require(getVotes(e, acc, proposalSnapshot(pId)) > 0); // assuming voter has non-zero voting power
|
|
|
|
+
|
|
|
|
+ uint256 againstBefore = votesAgainst();
|
|
|
|
+ uint256 forBefore = votesFor();
|
|
|
|
+ uint256 abstainBefore = votesAbstain();
|
|
|
|
+
|
|
|
|
+ bool hasVotedBefore = hasVoted(e, pId, acc);
|
|
|
|
+
|
|
|
|
+ helperFunctionsWithRevertOnlyCastVote(pId, f, e);
|
|
|
|
+
|
|
|
|
+ uint256 againstAfter = votesAgainst();
|
|
|
|
+ uint256 forAfter = votesFor();
|
|
|
|
+ uint256 abstainAfter = votesAbstain();
|
|
|
|
+
|
|
|
|
+ bool hasVotedAfter = hasVoted(e, pId, acc);
|
|
|
|
+
|
|
|
|
+ // want all vote categories to not decrease and at least one category to increase
|
|
|
|
+ assert
|
|
|
|
+ (!hasVotedBefore && hasVotedAfter) =>
|
|
|
|
+ (againstBefore <= againstAfter && forBefore <= forAfter && abstainBefore <= abstainAfter),
|
|
|
|
+ "no correlation: some category decreased"; // currently vacous but keeping for CI tests
|
|
|
|
+ assert
|
|
|
|
+ (!hasVotedBefore && hasVotedAfter) =>
|
|
|
|
+ (againstBefore < againstAfter || forBefore < forAfter || abstainBefore < abstainAfter),
|
|
|
|
+ "no correlation: no category increased";
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * R5: An against vote does not make a proposal reach quorum.
|
|
|
|
+ * RULE PASSING
|
|
|
|
+ * --ADVANCED SANITY PASSING vacuous but keeping
|
|
|
|
+ */
|
|
|
|
+rule againstVotesDontCount(method f) filtered {f -> !f.isView} {
|
|
|
|
+ env e; calldataarg args; uint256 pId;
|
|
|
|
+ address acc = e.msg.sender;
|
|
|
|
+
|
|
|
|
+ bool quorumBefore = quorumReached(e, pId);
|
|
|
|
+ uint256 againstBefore = votesAgainst();
|
|
|
|
+
|
|
|
|
+ f(e, args);
|
|
|
|
+
|
|
|
|
+ bool quorumAfter = quorumReached(e, pId);
|
|
|
|
+ uint256 againstAfter = votesAgainst();
|
|
|
|
+
|
|
|
|
+ assert (againstBefore < againstAfter) => quorumBefore == quorumAfter, "quorum reached with against vote";
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * R6: Deadline can only be extended from a `deadlineExtendible` state with quorum being reached with <= `lateQuorumVoteExtension` time left to vote
|
|
|
|
+ * RULE PASSING
|
|
|
|
+ * ADVANCED SANITY PASSING
|
|
|
|
+ */
|
|
|
|
+rule deadlineExtenededIfQuorumReached(method f) filtered {f -> !f.isView} {
|
|
|
|
+ env e; calldataarg args; uint256 pId;
|
|
|
|
+
|
|
|
|
+ // need invariant that proves that a propsal must be in state deadlineExtendable or deadlineExtended
|
|
|
|
+ require(deadlineExtended(e, pId) || deadlineExtendable(e, pId));
|
|
|
|
+ require(proposalCreated(pId));
|
|
|
|
+
|
|
|
|
+ bool wasDeadlineExtendable = deadlineExtendable(e, pId);
|
|
|
|
+ uint64 extension = lateQuorumVoteExtension();
|
|
|
|
+ uint256 deadlineBefore = proposalDeadline(pId);
|
|
|
|
+ f(e, args);
|
|
|
|
+ uint256 deadlineAfter = proposalDeadline(pId);
|
|
|
|
+
|
|
|
|
+ assert(deadlineAfter > deadlineBefore => wasDeadlineExtendable, "deadline was not extendable");
|
|
|
|
+ assert(deadlineAfter > deadlineBefore => deadlineBefore - e.block.number <= extension, "deadline extension should not be used");
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * R7: `extendedDeadlineField` is set iff `_castVote` is called and quroum is reached.
|
|
|
|
+ * RULE PASSING
|
|
|
|
+ * ADVANCED SANITY PASSING
|
|
|
|
+ */
|
|
|
|
+rule extendedDeadlineValueSetIfQuorumReached(method f) filtered {f -> !f.isView} {
|
|
|
|
+ env e; calldataarg args; uint256 pId;
|
|
|
|
+ require(deadlineExtended(e, pId) || deadlineExtendable(e, pId));
|
|
|
|
+
|
|
|
|
+ bool extendedBefore = deadlineExtended(e, pId);
|
|
|
|
+ f(e, args);
|
|
|
|
+ bool extendedAfter = deadlineExtended(e, pId);
|
|
|
|
+ uint256 extDeadline = getExtendedDeadline(pId);
|
|
|
|
+
|
|
|
|
+ assert(
|
|
|
|
+ !extendedBefore && extendedAfter
|
|
|
|
+ => extDeadline == e.block.number + lateQuorumVoteExtension(),
|
|
|
|
+ "extended deadline was not set"
|
|
|
|
+ );
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+* R8: If the deadline for a proposal has not been reached, users can still vote.
|
|
|
|
+* --RULE PASSING
|
|
|
|
+* --ADVANCED SANITY PASSING
|
|
|
|
+*/
|
|
|
|
+rule canVote(method f) filtered {f -> !f.isView} {
|
|
|
|
+ env e; calldataarg args; uint256 pId;
|
|
|
|
+ address acc = e.msg.sender;
|
|
|
|
+ uint256 deadline = proposalDeadline(pId);
|
|
|
|
+ bool votedBefore = hasVoted(e, pId, acc);
|
|
|
|
+
|
|
|
|
+ require(proposalCreated(pId));
|
|
|
|
+ require(deadline >= e.block.number);
|
|
|
|
+ // last error?
|
|
|
|
+ helperFunctionsWithRevertOnlyCastVote(pId, f, e);
|
|
|
|
+ bool votedAfter = hasVoted(e, pId, acc);
|
|
|
|
+
|
|
|
|
+ assert !votedBefore && votedAfter => deadline >= e.block.number;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * R9: Deadline can never be reduced.
|
|
|
|
+ * RULE PASSING
|
|
|
|
+ * ADVANCED SANITY PASSING
|
|
|
|
+ */
|
|
|
|
+rule deadlineNeverReduced(method f) filtered {f -> !f.isView} {
|
|
|
|
+ env e; calldataarg args; uint256 pId;
|
|
|
|
+ require(proposalCreated(pId));
|
|
|
|
+
|
|
|
|
+ uint256 deadlineBefore = proposalDeadline(pId);
|
|
|
|
+ f(e, args);
|
|
|
|
+ uint256 deadlineAfter = proposalDeadline(pId);
|
|
|
|
+
|
|
|
|
+ assert(deadlineAfter >= deadlineBefore);
|
|
|
|
+}
|
|
|
|
+
|