update
This commit is contained in:
@ -20,6 +20,9 @@ function safeState(env e, uint256 pId) returns uint8 {
|
||||
return lastReverted ? UNSET() : result;
|
||||
}
|
||||
|
||||
definition proposalCreated(uint256 pId) returns bool =
|
||||
proposalSnapshot(pId) > 0 && proposalDeadline(pId) > 0 && proposalProposer(pId) != 0;
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Filters │
|
||||
|
||||
@ -1,80 +1,11 @@
|
||||
import "methods/IGovernor.spec"
|
||||
import "Governor.helpers.spec"
|
||||
import "GovernorInvariants.spec"
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Definitions │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
|
||||
definition proposalCreated(uint256 pId) returns bool =
|
||||
proposalSnapshot(pId) > 0 && proposalDeadline(pId) > 0 && proposalProposer(pId) != 0;
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: Votes start and end are either initialized (non zero) or uninitialized (zero) simultaneously │
|
||||
│ │
|
||||
│ This invariant assumes that the block number cannot be 0 at any stage of the contract cycle │
|
||||
│ This is very safe assumption as usually the 0 block is genesis block which is uploaded with data │
|
||||
│ by the developers and will not be valid to raise proposals (at the current way that block chain is functioning) │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant proposalStateConsistency(uint256 pId)
|
||||
(proposalProposer(pId) != 0 <=> proposalSnapshot(pId) != 0) && (proposalProposer(pId) != 0 <=> proposalDeadline(pId) != 0)
|
||||
{
|
||||
preserved with (env e) {
|
||||
require clock(e) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: cancel => created │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant canceledImplyCreated(uint pId)
|
||||
isCanceled(pId) => proposalCreated(pId)
|
||||
{
|
||||
preserved with (env e) {
|
||||
requireInvariant proposalStateConsistency(pId);
|
||||
require clock(e) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: executed => created │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant executedImplyCreated(uint pId)
|
||||
isExecuted(pId) => proposalCreated(pId)
|
||||
{
|
||||
preserved with (env e) {
|
||||
requireInvariant proposalStateConsistency(pId);
|
||||
require clock(e) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: Votes start before it ends │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant voteStartBeforeVoteEnd(uint256 pId)
|
||||
proposalSnapshot(pId) <= proposalDeadline(pId)
|
||||
{
|
||||
preserved {
|
||||
requireInvariant proposalStateConsistency(pId);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: A proposal cannot be both executed and canceled simultaneously │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant noBothExecutedAndCanceled(uint256 pId)
|
||||
!isExecuted(pId) || !isCanceled(pId)
|
||||
use invariant proposalStateConsistency
|
||||
use invariant canceledImplyCreated
|
||||
use invariant executedImplyCreated
|
||||
use invariant noBothExecutedAndCanceled
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
@ -143,7 +74,7 @@ rule executionOnlyIfQuoromReachedAndVoteSucceeded(uint256 pId, env e, method f,
|
||||
|
||||
f(e, args);
|
||||
|
||||
assert isExecuted(pId) => (quorumReachedBefore && voteSucceededBefore), "quorum was changed";
|
||||
assert isExecuted(pId) => (quorumReachedBefore && voteSucceededBefore), "quorum not met or vote not succesfull";
|
||||
}
|
||||
|
||||
/*
|
||||
@ -216,21 +147,24 @@ rule allFunctionsRevertIfCanceled(uint256 pId, env e, method f, calldataarg args
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule stateOnlyAfterFunc(uint256 pId, env e, method f) {
|
||||
bool createdBefore = proposalCreated(pId);
|
||||
bool createdBefore = proposalCreated(pId);
|
||||
bool executedBefore = isExecuted(pId);
|
||||
bool canceledBefore = isCanceled(pId);
|
||||
|
||||
helperFunctionsWithRevert(e, f, pId);
|
||||
|
||||
assert (proposalCreated(pId) != createdBefore)
|
||||
=> (createdBefore == false && f.selector == propose(address[], uint256[], bytes[], string).selector),
|
||||
"proposalCreated only changes in the propose method";
|
||||
assert (proposalCreated(pId) != createdBefore) => (
|
||||
createdBefore == false &&
|
||||
f.selector == propose(address[], uint256[], bytes[], string).selector
|
||||
), "proposalCreated only changes in the propose method";
|
||||
|
||||
assert (isExecuted(pId) != executedBefore)
|
||||
=> (executedBefore == false && f.selector == execute(address[], uint256[], bytes[], bytes32).selector),
|
||||
"isExecuted only changes in the execute method";
|
||||
assert (isExecuted(pId) != executedBefore) => (
|
||||
executedBefore == false &&
|
||||
f.selector == execute(address[], uint256[], bytes[], bytes32).selector
|
||||
), "isExecuted only changes in the execute method";
|
||||
|
||||
assert (isCanceled(pId) != canceledBefore)
|
||||
=> (canceledBefore == false && f.selector == cancel(address[], uint256[], bytes[], bytes32).selector),
|
||||
"isCanceled only changes in the cancel method";
|
||||
assert (isCanceled(pId) != canceledBefore) => (
|
||||
canceledBefore == false &&
|
||||
f.selector == cancel(address[], uint256[], bytes[], bytes32).selector
|
||||
), "isCanceled only changes in the cancel method";
|
||||
}
|
||||
@ -95,3 +95,79 @@ rule castVote(uint256 pId, env e, method f)
|
||||
assert getForVotes(otherId) != otherForVotesBefore => (otherId == pId);
|
||||
assert getAbstainVotes(otherId) != otherAbstainVotesBefore => (otherId == pId);
|
||||
}
|
||||
|
||||
|
||||
rule queue(uint256 pId, env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
uint256 otherId;
|
||||
|
||||
uint8 stateBefore = state(e, pId);
|
||||
uint8 otherStateBefore = state(e, otherId);
|
||||
|
||||
address[] targets; uint256[] values; bytes[] calldatas; string reason;
|
||||
require pId == queue@withrevert(e, targets, values, calldatas, reason);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> stateBefore == SUCCEEDED();
|
||||
|
||||
// effect
|
||||
assert success => (
|
||||
state(e, pId) == QUEUED()
|
||||
);
|
||||
|
||||
// no side-effect
|
||||
assert state(e, otherId) != otherStateBefore => otherId == pId;
|
||||
}
|
||||
|
||||
rule execute(uint256 pId, env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
uint256 otherId;
|
||||
|
||||
uint8 stateBefore = state(e, pId);
|
||||
uint8 otherStateBefore = state(e, otherId);
|
||||
|
||||
address[] targets; uint256[] values; bytes[] calldatas; string reason;
|
||||
require pId == execute@withrevert(e, targets, values, calldatas, reason);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness: can't check full equivalence because of execution call reverts
|
||||
assert success => (stateBefore == SUCCEEDED() || stateBefore == QUEUED());
|
||||
|
||||
// effect
|
||||
assert success => (
|
||||
state(e, pId) == EXECUTED()
|
||||
);
|
||||
|
||||
// no side-effect
|
||||
assert state(e, otherId) != otherStateBefore => otherId == pId;
|
||||
}
|
||||
|
||||
rule cancel(uint256 pId, env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
uint256 otherId;
|
||||
|
||||
uint8 stateBefore = state(e, pId);
|
||||
uint8 otherStateBefore = state(e, otherId);
|
||||
|
||||
address[] targets; uint256[] values; bytes[] calldatas; string reason;
|
||||
require pId == cancel@withrevert(e, targets, values, calldatas, reason);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> (
|
||||
stateBefore == PENDING() &&
|
||||
e.msg.sender == proposalProposer(pId)
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => (
|
||||
state(e, pId) == CANCELED()
|
||||
);
|
||||
|
||||
// no side-effect
|
||||
assert state(e, otherId) != otherStateBefore => otherId == pId;
|
||||
}
|
||||
@ -8,32 +8,70 @@ import "Governor.helpers.spec"
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule clockMode(env e) {
|
||||
require e.block.number < max_uint48() && e.block.timestamp < max_uint48();
|
||||
|
||||
assert clock(e) == e.block.number || clock(e) == e.block.timestamp;
|
||||
assert clock(e) == token_clock(e);
|
||||
|
||||
/// This causes a failure in the prover
|
||||
// assert CLOCK_MODE(e) == token_CLOCK_MODE(e);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: Proposal is UNSET iff the proposer, the snapshot and the deadline are unset. │
|
||||
│ Invariant: Votes start and end are either initialized (non zero) or uninitialized (zero) simultaneously │
|
||||
│ │
|
||||
│ This invariant assumes that the block number cannot be 0 at any stage of the contract cycle │
|
||||
│ This is very safe assumption as usually the 0 block is genesis block which is uploaded with data │
|
||||
│ by the developers and will not be valid to raise proposals (at the current way that block chain is functioning) │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant createdConsistency(env e, uint256 pId)
|
||||
safeState(e, pId) == UNSET() <=> proposalProposer(pId) == 0 &&
|
||||
safeState(e, pId) == UNSET() <=> proposalSnapshot(pId) == 0 &&
|
||||
safeState(e, pId) == UNSET() <=> proposalDeadline(pId) == 0 &&
|
||||
safeState(e, pId) == UNSET() => !isExecuted(pId) &&
|
||||
safeState(e, pId) == UNSET() => !isCanceled(pId)
|
||||
invariant proposalStateConsistency(uint256 pId)
|
||||
(proposalProposer(pId) == 0 <=> proposalSnapshot(pId) == 0) &&
|
||||
(proposalProposer(pId) == 0 <=> proposalDeadline(pId) == 0)
|
||||
{
|
||||
preserved {
|
||||
preserved with (env e) {
|
||||
require clock(e) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
invariant createdConsistencyWeak(uint256 pId)
|
||||
proposalProposer(pId) == 0 <=> proposalSnapshot(pId) == 0 &&
|
||||
proposalProposer(pId) == 0 <=> proposalDeadline(pId) == 0
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: cancel => created │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant canceledImplyCreated(uint pId)
|
||||
isCanceled(pId) => proposalCreated(pId)
|
||||
{
|
||||
preserved with (env e) {
|
||||
requireInvariant proposalStateConsistency(pId);
|
||||
require clock(e) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: executed => created │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant executedImplyCreated(uint pId)
|
||||
isExecuted(pId) => proposalCreated(pId)
|
||||
{
|
||||
preserved with (env e) {
|
||||
requireInvariant proposalStateConsistency(pId);
|
||||
require clock(e) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: The state UNSET() correctly catched uninitialized proposal. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant proposalStateConsistencyUnset(env e, uint256 pId)
|
||||
proposalCreated(pId) <=> safeState(e, pId) == UNSET()
|
||||
{
|
||||
preserved {
|
||||
require clock(e) > 0;
|
||||
}
|
||||
}
|
||||
@ -47,7 +85,7 @@ invariant voteStartBeforeVoteEnd(uint256 pId)
|
||||
proposalSnapshot(pId) <= proposalDeadline(pId)
|
||||
{
|
||||
preserved {
|
||||
requireInvariant createdConsistencyWeak(pId);
|
||||
requireInvariant proposalStateConsistency(pId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,21 +99,8 @@ invariant noBothExecutedAndCanceled(uint256 pId)
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: Once a proposal is created, voteStart, voteEnd and proposer are immutable │
|
||||
│ Invariant: the "governance call" dequeue is empty │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule immutableFieldsAfterProposalCreation(uint256 pId, env e, method f, calldataarg arg)
|
||||
filtered { f -> !skip(f) }
|
||||
{
|
||||
require state(e, pId) != UNSET();
|
||||
|
||||
uint256 voteStart = proposalSnapshot(pId);
|
||||
uint256 voteEnd = proposalDeadline(pId);
|
||||
address proposer = proposalProposer(pId);
|
||||
|
||||
f(e, arg);
|
||||
|
||||
assert voteStart == proposalSnapshot(pId), "Start date was changed";
|
||||
assert voteEnd == proposalDeadline(pId), "End date was changed";
|
||||
assert proposer == proposalProposer(pId), "Proposer was changed";
|
||||
}
|
||||
invariant governanceCallLength()
|
||||
governanceCallLength() == 0
|
||||
@ -1,6 +1,9 @@
|
||||
import "helpers.spec"
|
||||
import "methods/IGovernor.spec"
|
||||
import "Governor.helpers.spec"
|
||||
import "GovernorInvariants.spec"
|
||||
|
||||
use invariant proposalStateConsistency
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
@ -78,3 +81,45 @@ rule stateTransitionWait(uint256 pId, env e1, env e2) {
|
||||
stateBefore == EXECUTED() => false
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: State corresponds to the vote timming and results │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule stateFollowsVoteTimmingAndResult(uint256 pId, env e) {
|
||||
require clock(e) > 0; // Sanity
|
||||
requireInvariant proposalStateConsistency(pId);
|
||||
|
||||
uint8 currentState = state(e, pId);
|
||||
uint48 currentClock = clock(e);
|
||||
|
||||
// Pending = before vote starts
|
||||
assert currentState == PENDING() => (
|
||||
proposalSnapshot(pId) >= currentClock
|
||||
);
|
||||
|
||||
// Active = after vote starts & before vote ends
|
||||
assert currentState == ACTIVE() => (
|
||||
proposalSnapshot(pId) < currentClock &&
|
||||
proposalDeadline(pId) >= currentClock
|
||||
);
|
||||
|
||||
// Succeeded = after vote end, with vote successfull and quorum reached
|
||||
assert currentState == SUCCEEDED() => (
|
||||
proposalDeadline(pId) < currentClock &&
|
||||
(
|
||||
quorumReached(pId) &&
|
||||
voteSucceeded(pId)
|
||||
)
|
||||
);
|
||||
|
||||
// Succeeded = after vote end, with vote not successfull or quorum not reached
|
||||
assert currentState == DEFEATED() => (
|
||||
proposalDeadline(pId) < currentClock &&
|
||||
(
|
||||
!quorumReached(pId) ||
|
||||
!voteSucceeded(pId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -1 +1,3 @@
|
||||
definition nonpayable(env e) returns bool = e.msg.value == 0;
|
||||
|
||||
definition max_uint48() returns uint48 = 0xffffffffffff;
|
||||
@ -39,6 +39,7 @@ methods {
|
||||
voteSucceeded(uint256) returns bool envfree
|
||||
isExecuted(uint256) returns bool envfree
|
||||
isCanceled(uint256) returns bool envfree
|
||||
governanceCallLength() returns uint256 envfree
|
||||
getAgainstVotes(uint256) returns uint256 envfree
|
||||
getForVotes(uint256) returns uint256 envfree
|
||||
getAbstainVotes(uint256) returns uint256 envfree
|
||||
|
||||
Reference in New Issue
Block a user