This commit is contained in:
Hadrien Croubois
2023-03-10 20:30:25 +01:00
parent f44e26fa7b
commit 318cfd501b
13 changed files with 243 additions and 119 deletions

View File

@ -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

View File

@ -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";
}

View File

@ -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;
}

View File

@ -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

View File

@ -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)
)
);
}

View File

@ -1 +1,3 @@
definition nonpayable(env e) returns bool = e.msg.value == 0;
definition max_uint48() returns uint48 = 0xffffffffffff;

View File

@ -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