Compare commits
72 Commits
master
...
fv/Governo
| Author | SHA1 | Date | |
|---|---|---|---|
| aaa5a5abab | |||
| e9f23aba2d | |||
| 6d539e6c31 | |||
| 2e1d0b3756 | |||
| e9779f8ef2 | |||
| c664f5f2c1 | |||
| 3431624598 | |||
| 9a33b0d2a2 | |||
| a97d3f5ce9 | |||
| df88ea34d0 | |||
| fd5f309d86 | |||
| e856ebbbb5 | |||
| e83fdf0828 | |||
| 7c37ea0ff6 | |||
| 2a6ccebfb7 | |||
| 3d9ef789cc | |||
| 75d6f5a42c | |||
| f21451facc | |||
| 6af1f18eb5 | |||
| 0daafdb01e | |||
| c8457ba975 | |||
| e928466183 | |||
| 8339187625 | |||
| 5af9167030 | |||
| e072521fcb | |||
| 5e71c01bcc | |||
| 42580f2891 | |||
| 8d029476af | |||
| 7c62ed2e8a | |||
| ecec8a7353 | |||
| 67a00ccaea | |||
| a64bb8801c | |||
| 1744132d37 | |||
| 5770dfbe36 | |||
| ddaf4bccf2 | |||
| 5ef4d207a6 | |||
| 3f79e2610c | |||
| 607268bd97 | |||
| dbb4a29dc9 | |||
| a355bf0de2 | |||
| 06baea7fa8 | |||
| 7512b8e171 | |||
| dd6a9ee240 | |||
| 74f613f5cc | |||
| d0b259546f | |||
| 82bbdb2c64 | |||
| 89ceb34f0d | |||
| 96553597fa | |||
| dfafd79692 | |||
| 50a13d52b9 | |||
| 4ea73a8c05 | |||
| 0874adbd1f | |||
| 198c4b7728 | |||
| 0d4df8972e | |||
| d7884251aa | |||
| d2b5d154d6 | |||
| 397f4cdfe2 | |||
| 728e2c8899 | |||
| e1120b9137 | |||
| 704e265c41 | |||
| e35daecf95 | |||
| b320e1ec4c | |||
| c33e7bd340 | |||
| 4b11b4d3a6 | |||
| f71bc6899f | |||
| 318cfd501b | |||
| f44e26fa7b | |||
| ec82e2f6fd | |||
| 9f39697a44 | |||
| f35c824435 | |||
| 5421355e57 | |||
| 1f5982b5e3 |
24
certora/diff/governance_Governor.sol.patch
Normal file
24
certora/diff/governance_Governor.sol.patch
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
--- governance/Governor.sol 2023-05-03 09:17:45.566699712 +0200
|
||||||
|
+++ governance/Governor.sol 2023-05-04 15:18:42.667741565 +0200
|
||||||
|
@@ -224,6 +224,21 @@
|
||||||
|
return _proposals[proposalId].proposer;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // FV
|
||||||
|
+ function _isExecuted(uint256 proposalId) internal view returns (bool) {
|
||||||
|
+ return _proposals[proposalId].executed;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // FV
|
||||||
|
+ function _isCanceled(uint256 proposalId) internal view returns (bool) {
|
||||||
|
+ return _proposals[proposalId].canceled;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // FV
|
||||||
|
+ function _governanceCallLength() internal view returns (uint256) {
|
||||||
|
+ return _governanceCall.length();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* @dev Amount of votes already cast passes the threshold limit.
|
||||||
|
*/
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
--- governance/extensions/GovernorPreventLateQuorum.sol 2023-05-03 09:17:45.566699712 +0200
|
||||||
|
+++ governance/extensions/GovernorPreventLateQuorum.sol 2023-05-04 15:18:42.657742113 +0200
|
||||||
|
@@ -82,6 +82,11 @@
|
||||||
|
return _voteExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // FV
|
||||||
|
+ function _getExtendedDeadline(uint256 proposalId) internal view returns (uint64) {
|
||||||
|
+ return _extendedDeadlines[proposalId];
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* @dev Changes the {lateQuorumVoteExtension}. This operation can only be performed by the governance executor,
|
||||||
|
* generally through a governance proposal.
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
--- governance/extensions/GovernorTimelockControl.sol 2023-05-04 11:44:55.587737817 +0200
|
||||||
|
+++ governance/extensions/GovernorTimelockControl.sol 2023-05-04 15:18:42.661075263 +0200
|
||||||
|
@@ -24,7 +24,7 @@
|
||||||
|
* _Available since v4.3._
|
||||||
|
*/
|
||||||
|
abstract contract GovernorTimelockControl is IGovernorTimelock, Governor {
|
||||||
|
- TimelockController private _timelock;
|
||||||
|
+ TimelockController public _timelock; // FV: public for link
|
||||||
|
mapping(uint256 => bytes32) private _timelockIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
@@ -84,6 +84,11 @@
|
||||||
|
return eta == 1 ? 0 : eta; // _DONE_TIMESTAMP (1) should be replaced with a 0 value
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // FV
|
||||||
|
+ function _proposalQueueId(uint256 proposalId) internal view returns (bytes32) {
|
||||||
|
+ return _timelockIds[proposalId];
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* @dev Function to queue a proposal to the timelock.
|
||||||
|
*/
|
||||||
|
@@ -163,4 +168,9 @@
|
||||||
|
emit TimelockChange(address(_timelock), address(newTimelock));
|
||||||
|
_timelock = newTimelock;
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ // FV
|
||||||
|
+ function timelockId(uint256 proposalId) public view returns (bytes32) {
|
||||||
|
+ return _timelockIds[proposalId];
|
||||||
|
+ }
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
--- governance/extensions/GovernorVotesQuorumFraction.sol 2023-05-03 09:17:45.570033048 +0200
|
||||||
|
+++ governance/extensions/GovernorVotesQuorumFraction.sol 2023-05-04 15:18:42.664408414 +0200
|
||||||
|
@@ -61,6 +61,11 @@
|
||||||
|
return _quorumNumeratorHistory.upperLookupRecent(SafeCast.toUint32(timepoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // FV
|
||||||
|
+ function quorumNumeratorLength() public view returns (uint256) {
|
||||||
|
+ return _quorumNumeratorHistory._checkpoints.length;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* @dev Returns the quorum denominator. Defaults to 100, but may be overridden.
|
||||||
|
*/
|
||||||
@ -1,5 +1,5 @@
|
|||||||
--- token/ERC721/ERC721.sol 2023-03-07 10:48:47.736822221 +0100
|
--- token/ERC721/ERC721.sol 2023-04-27 22:16:54.864065073 +0200
|
||||||
+++ token/ERC721/ERC721.sol 2023-03-09 19:49:39.669338673 +0100
|
+++ token/ERC721/ERC721.sol 2023-05-04 15:18:42.671074716 +0200
|
||||||
@@ -199,6 +199,11 @@
|
@@ -199,6 +199,11 @@
|
||||||
return _owners[tokenId];
|
return _owners[tokenId];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
pragma solidity ^0.8.0;
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
import "../patched/access/AccessControl.sol";
|
import "../patched/access/AccessControl.sol";
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
pragma solidity ^0.8.0;
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
import "../patched/token/ERC20/ERC20.sol";
|
import "../patched/token/ERC20/ERC20.sol";
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
pragma solidity ^0.8.0;
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
import "../patched/token/ERC20/extensions/ERC20Permit.sol";
|
import "../patched/token/ERC20/extensions/ERC20Permit.sol";
|
||||||
|
|||||||
29
certora/harnesses/ERC20VotesBlocknumberHarness.sol
Normal file
29
certora/harnesses/ERC20VotesBlocknumberHarness.sol
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import "../patched/token/ERC20/extensions/ERC20Votes.sol";
|
||||||
|
|
||||||
|
contract ERC20VotesBlocknumberHarness is ERC20Votes {
|
||||||
|
constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {}
|
||||||
|
|
||||||
|
function mint(address account, uint256 amount) external {
|
||||||
|
_mint(account, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function burn(address account, uint256 amount) external {
|
||||||
|
_burn(account, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// inspection
|
||||||
|
function ckptFromBlock(address account, uint32 pos) public view returns (uint32) {
|
||||||
|
return checkpoints(account, pos).fromBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ckptVotes(address account, uint32 pos) public view returns (uint224) {
|
||||||
|
return checkpoints(account, pos).votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function maxSupply() public view returns (uint224) {
|
||||||
|
return _maxSupply();
|
||||||
|
}
|
||||||
|
}
|
||||||
39
certora/harnesses/ERC20VotesTimestampHarness.sol
Normal file
39
certora/harnesses/ERC20VotesTimestampHarness.sol
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import "../patched/token/ERC20/extensions/ERC20Votes.sol";
|
||||||
|
|
||||||
|
contract ERC20VotesTimestampHarness is ERC20Votes {
|
||||||
|
constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {}
|
||||||
|
|
||||||
|
function mint(address account, uint256 amount) external {
|
||||||
|
_mint(account, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function burn(address account, uint256 amount) external {
|
||||||
|
_burn(account, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// inspection
|
||||||
|
function ckptFromBlock(address account, uint32 pos) public view returns (uint32) {
|
||||||
|
return checkpoints(account, pos).fromBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ckptVotes(address account, uint32 pos) public view returns (uint224) {
|
||||||
|
return checkpoints(account, pos).votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function maxSupply() public view returns (uint224) {
|
||||||
|
return _maxSupply();
|
||||||
|
}
|
||||||
|
|
||||||
|
// clock
|
||||||
|
function clock() public view override returns (uint48) {
|
||||||
|
return uint48(block.timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// solhint-disable-next-line func-name-mixedcase
|
||||||
|
function CLOCK_MODE() public view virtual override returns (string memory) {
|
||||||
|
return "mode=timestamp";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
pragma solidity ^0.8.0;
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
import "../patched/token/ERC20/extensions/ERC20Wrapper.sol";
|
import "../patched/token/ERC20/extensions/ERC20Wrapper.sol";
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
import "../patched/interfaces/IERC3156FlashBorrower.sol";
|
import "../patched/interfaces/IERC3156FlashBorrower.sol";
|
||||||
|
|
||||||
pragma solidity ^0.8.0;
|
|
||||||
|
|
||||||
contract ERC3156FlashBorrowerHarness is IERC3156FlashBorrower {
|
contract ERC3156FlashBorrowerHarness is IERC3156FlashBorrower {
|
||||||
bytes32 somethingToReturn;
|
bytes32 somethingToReturn;
|
||||||
|
|
||||||
|
|||||||
153
certora/harnesses/GovernorHarness.sol
Normal file
153
certora/harnesses/GovernorHarness.sol
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.2;
|
||||||
|
|
||||||
|
import "../patched/governance/Governor.sol";
|
||||||
|
import "../patched/governance/extensions/GovernorCountingSimple.sol";
|
||||||
|
import "../patched/governance/extensions/GovernorTimelockControl.sol";
|
||||||
|
import "../patched/governance/extensions/GovernorVotes.sol";
|
||||||
|
import "../patched/governance/extensions/GovernorVotesQuorumFraction.sol";
|
||||||
|
import "../patched/token/ERC20/extensions/ERC20Votes.sol";
|
||||||
|
|
||||||
|
contract GovernorHarness is
|
||||||
|
Governor,
|
||||||
|
GovernorCountingSimple,
|
||||||
|
GovernorTimelockControl,
|
||||||
|
GovernorVotes,
|
||||||
|
GovernorVotesQuorumFraction
|
||||||
|
{
|
||||||
|
constructor(IVotes _token, TimelockController _timelock, uint256 _quorumNumeratorValue)
|
||||||
|
Governor("Harness")
|
||||||
|
GovernorTimelockControl(_timelock)
|
||||||
|
GovernorVotes(_token)
|
||||||
|
GovernorVotesQuorumFraction(_quorumNumeratorValue)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// Harness from Votes
|
||||||
|
function token_getPastTotalSupply(uint256 blockNumber) public view returns(uint256) {
|
||||||
|
return token.getPastTotalSupply(blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
function token_getPastVotes(address account, uint256 blockNumber) public view returns(uint256) {
|
||||||
|
return token.getPastVotes(account, blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
function token_clock() public view returns (uint48) {
|
||||||
|
return token.clock();
|
||||||
|
}
|
||||||
|
|
||||||
|
function token_CLOCK_MODE() public view returns (string memory) {
|
||||||
|
return token.CLOCK_MODE();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Harness from Governor
|
||||||
|
function hashProposal(address[] memory targets,uint256[] memory values,bytes[] memory calldatas,string memory description) public pure returns (uint256) {
|
||||||
|
return hashProposal(targets, values, calldatas, keccak256(bytes(description)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExecutor() public view returns (address) {
|
||||||
|
return _executor();
|
||||||
|
}
|
||||||
|
|
||||||
|
function quorumReached(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _quorumReached(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function voteSucceeded(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _voteSucceeded(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isExecuted(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _isExecuted(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCanceled(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _isCanceled(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isQueued(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _proposalQueueId(proposalId) != bytes32(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function governanceCallLength() public view returns (uint256) {
|
||||||
|
return _governanceCallLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Harness from GovernorCountingSimple
|
||||||
|
function getAgainstVotes(uint256 proposalId) public view returns (uint256) {
|
||||||
|
(uint256 againstVotes,,) = proposalVotes(proposalId);
|
||||||
|
return againstVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getForVotes(uint256 proposalId) public view returns (uint256) {
|
||||||
|
(,uint256 forVotes,) = proposalVotes(proposalId);
|
||||||
|
return forVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAbstainVotes(uint256 proposalId) public view returns (uint256) {
|
||||||
|
(,,uint256 abstainVotes) = proposalVotes(proposalId);
|
||||||
|
return abstainVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following functions are overrides required by Solidity added by OZ Wizard.
|
||||||
|
function votingDelay() public pure override returns (uint256) {
|
||||||
|
return 1; // 1 block
|
||||||
|
}
|
||||||
|
|
||||||
|
function votingPeriod() public pure override returns (uint256) {
|
||||||
|
return 45818; // 1 week
|
||||||
|
}
|
||||||
|
|
||||||
|
function quorum(uint256 blockNumber)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
override(IGovernor, GovernorVotesQuorumFraction)
|
||||||
|
returns (uint256)
|
||||||
|
{
|
||||||
|
return super.quorum(blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) {
|
||||||
|
return super.state(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function propose(
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
string memory description
|
||||||
|
) public override(Governor, IGovernor) returns (uint256) {
|
||||||
|
return super.propose(targets, values, calldatas, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _execute(
|
||||||
|
uint256 proposalId,
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
bytes32 descriptionHash
|
||||||
|
) internal override(Governor, GovernorTimelockControl) {
|
||||||
|
super._execute(proposalId, targets, values, calldatas, descriptionHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _cancel(
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
bytes32 descriptionHash
|
||||||
|
) internal override(Governor, GovernorTimelockControl) returns (uint256) {
|
||||||
|
return super._cancel(targets, values, calldatas, descriptionHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) {
|
||||||
|
return super._executor();
|
||||||
|
}
|
||||||
|
|
||||||
|
function supportsInterface(bytes4 interfaceId)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
override(Governor, GovernorTimelockControl)
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
return super.supportsInterface(interfaceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
176
certora/harnesses/GovernorPreventLateHarness.sol
Normal file
176
certora/harnesses/GovernorPreventLateHarness.sol
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.2;
|
||||||
|
|
||||||
|
import "../patched/governance/Governor.sol";
|
||||||
|
import "../patched/governance/extensions/GovernorCountingSimple.sol";
|
||||||
|
import "../patched/governance/extensions/GovernorPreventLateQuorum.sol";
|
||||||
|
import "../patched/governance/extensions/GovernorTimelockControl.sol";
|
||||||
|
import "../patched/governance/extensions/GovernorVotes.sol";
|
||||||
|
import "../patched/governance/extensions/GovernorVotesQuorumFraction.sol";
|
||||||
|
import "../patched/token/ERC20/extensions/ERC20Votes.sol";
|
||||||
|
|
||||||
|
contract GovernorPreventLateHarness is
|
||||||
|
Governor,
|
||||||
|
GovernorCountingSimple,
|
||||||
|
GovernorPreventLateQuorum,
|
||||||
|
GovernorTimelockControl,
|
||||||
|
GovernorVotes,
|
||||||
|
GovernorVotesQuorumFraction
|
||||||
|
{
|
||||||
|
constructor(IVotes _token, TimelockController _timelock, uint256 _quorumNumeratorValue, uint64 _initialVoteExtension)
|
||||||
|
Governor("Harness")
|
||||||
|
GovernorPreventLateQuorum(_initialVoteExtension)
|
||||||
|
GovernorTimelockControl(_timelock)
|
||||||
|
GovernorVotes(_token)
|
||||||
|
GovernorVotesQuorumFraction(_quorumNumeratorValue)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// Harness from Votes
|
||||||
|
function token_getPastTotalSupply(uint256 blockNumber) public view returns(uint256) {
|
||||||
|
return token.getPastTotalSupply(blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
function token_getPastVotes(address account, uint256 blockNumber) public view returns(uint256) {
|
||||||
|
return token.getPastVotes(account, blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
function token_clock() public view returns (uint48) {
|
||||||
|
return token.clock();
|
||||||
|
}
|
||||||
|
|
||||||
|
function token_CLOCK_MODE() public view returns (string memory) {
|
||||||
|
return token.CLOCK_MODE();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Harness from Governor
|
||||||
|
function hashProposal(address[] memory targets,uint256[] memory values,bytes[] memory calldatas,string memory description) public pure returns (uint256) {
|
||||||
|
return hashProposal(targets, values, calldatas, keccak256(bytes(description)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExecutor() public view returns (address) {
|
||||||
|
return _executor();
|
||||||
|
}
|
||||||
|
|
||||||
|
function quorumReached(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _quorumReached(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function voteSucceeded(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _voteSucceeded(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isExecuted(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _isExecuted(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCanceled(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _isCanceled(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isQueued(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _proposalQueueId(proposalId) != bytes32(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function governanceCallLength() public view returns (uint256) {
|
||||||
|
return _governanceCallLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Harness from GovernorPreventLateQuorum
|
||||||
|
function getExtendedDeadline(uint256 proposalId) public view returns (uint64) {
|
||||||
|
return _getExtendedDeadline(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Harness from GovernorCountingSimple
|
||||||
|
function getAgainstVotes(uint256 proposalId) public view returns (uint256) {
|
||||||
|
(uint256 againstVotes,,) = proposalVotes(proposalId);
|
||||||
|
return againstVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getForVotes(uint256 proposalId) public view returns (uint256) {
|
||||||
|
(,uint256 forVotes,) = proposalVotes(proposalId);
|
||||||
|
return forVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAbstainVotes(uint256 proposalId) public view returns (uint256) {
|
||||||
|
(,,uint256 abstainVotes) = proposalVotes(proposalId);
|
||||||
|
return abstainVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following functions are overrides required by Solidity added by OZ Wizard.
|
||||||
|
function votingDelay() public pure override returns (uint256) {
|
||||||
|
return 1; // 1 block
|
||||||
|
}
|
||||||
|
|
||||||
|
function votingPeriod() public pure override returns (uint256) {
|
||||||
|
return 45818; // 1 week
|
||||||
|
}
|
||||||
|
|
||||||
|
function quorum(uint256 blockNumber)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
override(IGovernor, GovernorVotesQuorumFraction)
|
||||||
|
returns (uint256)
|
||||||
|
{
|
||||||
|
return super.quorum(blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) {
|
||||||
|
return super.state(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function proposalDeadline(uint256 proposalId) public view override(IGovernor, Governor, GovernorPreventLateQuorum) returns (uint256) {
|
||||||
|
return super.proposalDeadline(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function propose(
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
string memory description
|
||||||
|
) public override(Governor, IGovernor) returns (uint256) {
|
||||||
|
return super.propose(targets, values, calldatas, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _execute(
|
||||||
|
uint256 proposalId,
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
bytes32 descriptionHash
|
||||||
|
) internal override(Governor, GovernorTimelockControl) {
|
||||||
|
super._execute(proposalId, targets, values, calldatas, descriptionHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _cancel(
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
bytes32 descriptionHash
|
||||||
|
) internal override(Governor, GovernorTimelockControl) returns (uint256) {
|
||||||
|
return super._cancel(targets, values, calldatas, descriptionHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _castVote(
|
||||||
|
uint256 proposalId,
|
||||||
|
address account,
|
||||||
|
uint8 support,
|
||||||
|
string memory reason,
|
||||||
|
bytes memory params
|
||||||
|
) internal override(Governor, GovernorPreventLateQuorum) returns (uint256) {
|
||||||
|
return super._castVote(proposalId, account, support, reason, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) {
|
||||||
|
return super._executor();
|
||||||
|
}
|
||||||
|
|
||||||
|
function supportsInterface(bytes4 interfaceId)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
virtual
|
||||||
|
override(Governor, GovernorTimelockControl)
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
return super.supportsInterface(interfaceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
pragma solidity ^0.8.0;
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
import "../patched/access/Ownable2Step.sol";
|
import "../patched/access/Ownable2Step.sol";
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
pragma solidity ^0.8.0;
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
import "../patched/access/Ownable.sol";
|
import "../patched/access/Ownable.sol";
|
||||||
|
|||||||
@ -21,7 +21,7 @@ const argv = require('yargs')
|
|||||||
spec: {
|
spec: {
|
||||||
alias: 's',
|
alias: 's',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: __dirname + '/specs.json',
|
default: __dirname + '/specs.js',
|
||||||
},
|
},
|
||||||
parallel: {
|
parallel: {
|
||||||
alias: 'p',
|
alias: 'p',
|
||||||
@ -59,12 +59,17 @@ if (process.exitCode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const { spec, contract, files, options = [] } of specs) {
|
for (const { spec, contract, files, options = [] } of specs) {
|
||||||
limit(runCertora, spec, contract, files, [...options.flatMap(opt => opt.split(' ')), ...argv.options]);
|
limit(runCertora, spec, contract, files, [...options, ...argv.options]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run certora, aggregate the output and print it at the end
|
// Run certora, aggregate the output and print it at the end
|
||||||
async function runCertora(spec, contract, files, options = []) {
|
async function runCertora(spec, contract, files, options = []) {
|
||||||
const args = [...files, '--verify', `${contract}:certora/specs/${spec}.spec`, ...options];
|
const args = [
|
||||||
|
...files,
|
||||||
|
'--verify',
|
||||||
|
`${contract}:certora/specs/${spec}.spec`,
|
||||||
|
...options.flatMap(opt => opt.split(' ')),
|
||||||
|
];
|
||||||
const child = proc.spawn('certoraRun', args);
|
const child = proc.spawn('certoraRun', args);
|
||||||
|
|
||||||
const stream = new PassThrough();
|
const stream = new PassThrough();
|
||||||
|
|||||||
129
certora/specs.js
Normal file
129
certora/specs.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
const product = (...arrays) => arrays.reduce((a, b) => a.flatMap(ai => b.map(bi => [ai, bi].flat())));
|
||||||
|
|
||||||
|
module.exports = [].concat(
|
||||||
|
// AccessControl
|
||||||
|
{
|
||||||
|
spec: 'AccessControl',
|
||||||
|
contract: 'AccessControlHarness',
|
||||||
|
files: ['certora/harnesses/AccessControlHarness.sol'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spec: 'AccessControlDefaultAdminRules',
|
||||||
|
contract: 'AccessControlDefaultAdminRulesHarness',
|
||||||
|
files: ['certora/harnesses/AccessControlDefaultAdminRulesHarness.sol'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spec: 'Ownable',
|
||||||
|
contract: 'OwnableHarness',
|
||||||
|
files: ['certora/harnesses/OwnableHarness.sol'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spec: 'Ownable2Step',
|
||||||
|
contract: 'Ownable2StepHarness',
|
||||||
|
files: ['certora/harnesses/Ownable2StepHarness.sol'],
|
||||||
|
},
|
||||||
|
// Tokens
|
||||||
|
{
|
||||||
|
spec: 'ERC20',
|
||||||
|
contract: 'ERC20PermitHarness',
|
||||||
|
files: ['certora/harnesses/ERC20PermitHarness.sol'],
|
||||||
|
options: ['--optimistic_loop'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spec: 'ERC20FlashMint',
|
||||||
|
contract: 'ERC20FlashMintHarness',
|
||||||
|
files: ['certora/harnesses/ERC20FlashMintHarness.sol', 'certora/harnesses/ERC3156FlashBorrowerHarness.sol'],
|
||||||
|
options: ['--optimistic_loop'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spec: 'ERC20Wrapper',
|
||||||
|
contract: 'ERC20WrapperHarness',
|
||||||
|
files: ['certora/harnesses/ERC20PermitHarness.sol', 'certora/harnesses/ERC20WrapperHarness.sol'],
|
||||||
|
options: ['--link ERC20WrapperHarness:_underlying=ERC20PermitHarness', '--optimistic_loop'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spec: 'ERC721',
|
||||||
|
contract: 'ERC721Harness',
|
||||||
|
files: ['certora/harnesses/ERC721Harness.sol', 'certora/harnesses/ERC721ReceiverHarness.sol'],
|
||||||
|
options: ['--optimistic_loop'],
|
||||||
|
},
|
||||||
|
// Security
|
||||||
|
{
|
||||||
|
spec: 'Pausable',
|
||||||
|
contract: 'PausableHarness',
|
||||||
|
files: ['certora/harnesses/PausableHarness.sol'],
|
||||||
|
},
|
||||||
|
// Proxy
|
||||||
|
{
|
||||||
|
spec: 'Initializable',
|
||||||
|
contract: 'InitializableHarness',
|
||||||
|
files: ['certora/harnesses/InitializableHarness.sol'],
|
||||||
|
},
|
||||||
|
// Structures
|
||||||
|
{
|
||||||
|
spec: 'DoubleEndedQueue',
|
||||||
|
contract: 'DoubleEndedQueueHarness',
|
||||||
|
files: ['certora/harnesses/DoubleEndedQueueHarness.sol'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spec: 'EnumerableSet',
|
||||||
|
contract: 'EnumerableSetHarness',
|
||||||
|
files: ['certora/harnesses/EnumerableSetHarness.sol'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spec: 'EnumerableMap',
|
||||||
|
contract: 'EnumerableMapHarness',
|
||||||
|
files: ['certora/harnesses/EnumerableMapHarness.sol'],
|
||||||
|
},
|
||||||
|
// Governance
|
||||||
|
{
|
||||||
|
spec: 'TimelockController',
|
||||||
|
contract: 'TimelockControllerHarness',
|
||||||
|
files: ['certora/harnesses/TimelockControllerHarness.sol'],
|
||||||
|
options: ['--optimistic_hashing', '--optimistic_loop'],
|
||||||
|
},
|
||||||
|
// Governor
|
||||||
|
product(
|
||||||
|
[
|
||||||
|
...product(['GovernorHarness'], ['GovernorInvariants', 'GovernorBaseRules', 'GovernorChanges', 'GovernorStates']),
|
||||||
|
...product(['GovernorPreventLateHarness'], ['GovernorPreventLateQuorum']),
|
||||||
|
],
|
||||||
|
['ERC20VotesBlocknumberHarness', 'ERC20VotesTimestampHarness'],
|
||||||
|
).map(([contract, spec, token]) => ({
|
||||||
|
spec,
|
||||||
|
contract,
|
||||||
|
files: [
|
||||||
|
`certora/harnesses/${contract}.sol`,
|
||||||
|
`certora/harnesses/${token}.sol`,
|
||||||
|
`certora/harnesses/TimelockControllerHarness.sol`,
|
||||||
|
],
|
||||||
|
options: [
|
||||||
|
`--link ${contract}:token=${token}`,
|
||||||
|
`--link ${contract}:_timelock=TimelockControllerHarness`,
|
||||||
|
'--optimistic_hashing',
|
||||||
|
'--optimistic_loop',
|
||||||
|
],
|
||||||
|
})),
|
||||||
|
product(
|
||||||
|
['GovernorHarness'],
|
||||||
|
['GovernorFunctions'],
|
||||||
|
['ERC20VotesBlocknumberHarness', 'ERC20VotesTimestampHarness'],
|
||||||
|
['castVote', 'execute'], // 'propose', 'queue', 'cancel' // these rules timeout/fail
|
||||||
|
).map(([contract, spec, token, fn]) => ({
|
||||||
|
spec,
|
||||||
|
contract,
|
||||||
|
files: [
|
||||||
|
`certora/harnesses/${contract}.sol`,
|
||||||
|
`certora/harnesses/${token}.sol`,
|
||||||
|
`certora/harnesses/TimelockControllerHarness.sol`,
|
||||||
|
],
|
||||||
|
options: [
|
||||||
|
`--link ${contract}:token=${token}`,
|
||||||
|
`--link ${contract}:_timelock=TimelockControllerHarness`,
|
||||||
|
'--optimistic_hashing',
|
||||||
|
'--optimistic_loop',
|
||||||
|
'--rules',
|
||||||
|
...['liveness', 'effect', 'sideeffect'].map(kind => `${fn}_${kind}`),
|
||||||
|
],
|
||||||
|
})),
|
||||||
|
);
|
||||||
@ -1,86 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"spec": "Pausable",
|
|
||||||
"contract": "PausableHarness",
|
|
||||||
"files": ["certora/harnesses/PausableHarness.sol"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"spec": "AccessControl",
|
|
||||||
"contract": "AccessControlHarness",
|
|
||||||
"files": ["certora/harnesses/AccessControlHarness.sol"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"spec": "AccessControlDefaultAdminRules",
|
|
||||||
"contract": "AccessControlDefaultAdminRulesHarness",
|
|
||||||
"files": ["certora/harnesses/AccessControlDefaultAdminRulesHarness.sol"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"spec": "DoubleEndedQueue",
|
|
||||||
"contract": "DoubleEndedQueueHarness",
|
|
||||||
"files": ["certora/harnesses/DoubleEndedQueueHarness.sol"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"spec": "Ownable",
|
|
||||||
"contract": "OwnableHarness",
|
|
||||||
"files": ["certora/harnesses/OwnableHarness.sol"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"spec": "Ownable2Step",
|
|
||||||
"contract": "Ownable2StepHarness",
|
|
||||||
"files": ["certora/harnesses/Ownable2StepHarness.sol"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"spec": "ERC20",
|
|
||||||
"contract": "ERC20PermitHarness",
|
|
||||||
"files": ["certora/harnesses/ERC20PermitHarness.sol"],
|
|
||||||
"options": ["--optimistic_loop"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"spec": "ERC20FlashMint",
|
|
||||||
"contract": "ERC20FlashMintHarness",
|
|
||||||
"files": [
|
|
||||||
"certora/harnesses/ERC20FlashMintHarness.sol",
|
|
||||||
"certora/harnesses/ERC3156FlashBorrowerHarness.sol"
|
|
||||||
],
|
|
||||||
"options": ["--optimistic_loop"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"spec": "ERC20Wrapper",
|
|
||||||
"contract": "ERC20WrapperHarness",
|
|
||||||
"files": [
|
|
||||||
"certora/harnesses/ERC20PermitHarness.sol",
|
|
||||||
"certora/harnesses/ERC20WrapperHarness.sol"
|
|
||||||
],
|
|
||||||
"options": [
|
|
||||||
"--link ERC20WrapperHarness:_underlying=ERC20PermitHarness",
|
|
||||||
"--optimistic_loop"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"spec": "ERC721",
|
|
||||||
"contract": "ERC721Harness",
|
|
||||||
"files": ["certora/harnesses/ERC721Harness.sol", "certora/harnesses/ERC721ReceiverHarness.sol"],
|
|
||||||
"options": ["--optimistic_loop"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"spec": "Initializable",
|
|
||||||
"contract": "InitializableHarness",
|
|
||||||
"files": ["certora/harnesses/InitializableHarness.sol"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"spec": "EnumerableSet",
|
|
||||||
"contract": "EnumerableSetHarness",
|
|
||||||
"files": ["certora/harnesses/EnumerableSetHarness.sol"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"spec": "EnumerableMap",
|
|
||||||
"contract": "EnumerableMapHarness",
|
|
||||||
"files": ["certora/harnesses/EnumerableMapHarness.sol"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"spec": "TimelockController",
|
|
||||||
"contract": "TimelockControllerHarness",
|
|
||||||
"files": ["certora/harnesses/TimelockControllerHarness.sol"],
|
|
||||||
"options": ["--optimistic_hashing", "--optimistic_loop"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
202
certora/specs/GovernorBaseRules.spec
Normal file
202
certora/specs/GovernorBaseRules.spec
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
import "helpers/helpers.spec"
|
||||||
|
import "helpers/Governor.helpers.spec"
|
||||||
|
import "GovernorInvariants.spec"
|
||||||
|
|
||||||
|
use invariant proposalStateConsistency
|
||||||
|
use invariant canceledImplyCreated
|
||||||
|
use invariant executedImplyCreated
|
||||||
|
use invariant noBothExecutedAndCanceled
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: No double proposition │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule noDoublePropose(uint256 pId, env e) {
|
||||||
|
require proposalCreated(pId);
|
||||||
|
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; string reason;
|
||||||
|
uint256 result = propose(e, targets, values, calldatas, reason);
|
||||||
|
|
||||||
|
assert result != pId, "double proposal";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: Once a proposal is created, voteStart, voteEnd and proposer are immutable │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule immutableFieldsAfterProposalCreation(uint256 pId, env e, method f, calldataarg args)
|
||||||
|
filtered { f -> !assumedSafe(f) }
|
||||||
|
{
|
||||||
|
require proposalCreated(pId);
|
||||||
|
|
||||||
|
uint256 voteStart = proposalSnapshot(pId);
|
||||||
|
uint256 voteEnd = proposalDeadline(pId);
|
||||||
|
address proposer = proposalProposer(pId);
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert voteStart == proposalSnapshot(pId), "Start date was changed";
|
||||||
|
assert voteEnd == proposalDeadline(pId), "End date was changed";
|
||||||
|
assert proposer == proposalProposer(pId), "Proposer was changed";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: A user cannot vote twice │
|
||||||
|
│ │
|
||||||
|
│ 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)
|
||||||
|
filtered { f -> voting(f) }
|
||||||
|
{
|
||||||
|
address voter;
|
||||||
|
uint8 support;
|
||||||
|
|
||||||
|
bool votedCheck = hasVoted(pId, voter);
|
||||||
|
|
||||||
|
helperVoteWithRevert(e, f, pId, voter, support);
|
||||||
|
|
||||||
|
assert votedCheck => lastReverted, "double voting occurred";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: Voting against a proposal does not count towards quorum. │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 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 -> !assumedSafe(f) }
|
||||||
|
{
|
||||||
|
require !isExecuted(pId);
|
||||||
|
|
||||||
|
bool quorumReachedBefore = quorumReached(pId);
|
||||||
|
bool voteSucceededBefore = voteSucceeded(pId);
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert isExecuted(pId) => (quorumReachedBefore && voteSucceededBefore), "quorum not met or vote not successful";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: Voting cannot start at a block number prior to proposal’s creation block number │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule noStartBeforeCreation(uint256 pId, env e, method f, calldataarg args)
|
||||||
|
filtered { f -> !assumedSafe(f) }
|
||||||
|
{
|
||||||
|
require !proposalCreated(pId);
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert proposalCreated(pId) => proposalSnapshot(pId) >= clock(e), "starts before proposal";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: A proposal cannot be executed before it ends │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule noExecuteBeforeDeadline(uint256 pId, env e, method f, calldataarg args)
|
||||||
|
filtered { f -> !assumedSafe(f) }
|
||||||
|
{
|
||||||
|
require !isExecuted(pId);
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert isExecuted(pId) => proposalDeadline(pId) <= clock(e), "executed before deadline";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Invariant: The quorum numerator is always less than or equal to the quorum denominator │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
invariant quorumRatioLessThanOne()
|
||||||
|
quorumNumerator() <= quorumDenominator()
|
||||||
|
filtered { f -> !assumedSafe(f) }
|
||||||
|
{
|
||||||
|
preserved {
|
||||||
|
require quorumNumeratorLength() < max_uint256;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 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, 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 -> operateOnProposal(f) }
|
||||||
|
{
|
||||||
|
require isExecuted(pId);
|
||||||
|
requireInvariant noBothExecutedAndCanceled(pId);
|
||||||
|
requireInvariant executedImplyCreated(pId);
|
||||||
|
|
||||||
|
helperFunctionsWithRevert(e, f, pId);
|
||||||
|
|
||||||
|
assert lastReverted, "Function was not reverted";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 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. 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 -> operateOnProposal(f) }
|
||||||
|
{
|
||||||
|
require isCanceled(pId);
|
||||||
|
requireInvariant noBothExecutedAndCanceled(pId);
|
||||||
|
requireInvariant canceledImplyCreated(pId);
|
||||||
|
|
||||||
|
helperFunctionsWithRevert(e, f, pId);
|
||||||
|
|
||||||
|
assert lastReverted, "Function was not reverted";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: Update operation are restricted to executor │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule privilegedUpdate(env e, method f, calldataarg args)
|
||||||
|
filtered { f -> !assumedSafe(f) }
|
||||||
|
{
|
||||||
|
address executorBefore = getExecutor();
|
||||||
|
uint256 quorumNumeratorBefore = quorumNumerator();
|
||||||
|
address timelockBefore = timelock();
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert quorumNumerator() != quorumNumeratorBefore => e.msg.sender == executorBefore;
|
||||||
|
assert timelock() != timelockBefore => e.msg.sender == executorBefore;
|
||||||
|
}
|
||||||
56
certora/specs/GovernorChanges.spec
Normal file
56
certora/specs/GovernorChanges.spec
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import "helpers/helpers.spec"
|
||||||
|
import "helpers/Governor.helpers.spec"
|
||||||
|
import "GovernorInvariants.spec"
|
||||||
|
|
||||||
|
use invariant proposalStateConsistency
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: Proposal can be switched state only by specific functions │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule changes(uint256 pId, env e, method f, calldataarg args)
|
||||||
|
filtered { f -> !assumedSafe(f) }
|
||||||
|
{
|
||||||
|
require clockSanity(e);
|
||||||
|
requireInvariant proposalStateConsistency(pId);
|
||||||
|
|
||||||
|
address user;
|
||||||
|
|
||||||
|
bool existBefore = proposalCreated(pId);
|
||||||
|
bool isExecutedBefore = isExecuted(pId);
|
||||||
|
bool isCanceledBefore = isCanceled(pId);
|
||||||
|
bool isQueuedBefore = isQueued(pId);
|
||||||
|
bool hasVotedBefore = hasVoted(pId, user);
|
||||||
|
uint256 votesAgainstBefore = getAgainstVotes(pId);
|
||||||
|
uint256 votesForBefore = getForVotes(pId);
|
||||||
|
uint256 votesAbstainBefore = getAbstainVotes(pId);
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
bool existAfter = proposalCreated(pId);
|
||||||
|
bool isExecutedAfter = isExecuted(pId);
|
||||||
|
bool isCanceledAfter = isCanceled(pId);
|
||||||
|
bool isQueuedAfter = isQueued(pId);
|
||||||
|
bool hasVotedAfter = hasVoted(pId, user);
|
||||||
|
uint256 votesAgainstAfter = getAgainstVotes(pId);
|
||||||
|
uint256 votesForAfter = getForVotes(pId);
|
||||||
|
uint256 votesAbstainAfter = getAbstainVotes(pId);
|
||||||
|
|
||||||
|
// propose, execute, cancel
|
||||||
|
assert existAfter != existBefore => (!existBefore && f.selector == propose(address[],uint256[],bytes[],string).selector);
|
||||||
|
assert isExecutedAfter != isExecutedBefore => (!isExecutedBefore && f.selector == execute(address[],uint256[],bytes[],bytes32).selector);
|
||||||
|
assert isCanceledAfter != isCanceledBefore => (!isCanceledBefore && f.selector == cancel(address[],uint256[],bytes[],bytes32).selector);
|
||||||
|
|
||||||
|
// queue is cleared on cancel
|
||||||
|
assert isQueuedAfter != isQueuedBefore => (
|
||||||
|
(!isQueuedBefore && f.selector == queue(address[],uint256[],bytes[],bytes32).selector) ||
|
||||||
|
(isQueuedBefore && f.selector == cancel(address[],uint256[],bytes[],bytes32).selector)
|
||||||
|
);
|
||||||
|
|
||||||
|
// votes
|
||||||
|
assert hasVotedAfter != hasVotedBefore => (!hasVotedBefore && votingAll(f));
|
||||||
|
assert votesAgainstAfter != votesAgainstBefore => (votesAgainstAfter > votesAgainstBefore && votingAll(f));
|
||||||
|
assert votesForAfter != votesForBefore => (votesForAfter > votesForBefore && votingAll(f));
|
||||||
|
assert votesAbstainAfter != votesAbstainBefore => (votesAbstainAfter > votesAbstainBefore && votingAll(f));
|
||||||
|
}
|
||||||
276
certora/specs/GovernorFunctions.spec
Normal file
276
certora/specs/GovernorFunctions.spec
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
import "helpers/helpers.spec"
|
||||||
|
import "helpers/Governor.helpers.spec"
|
||||||
|
import "GovernorInvariants.spec"
|
||||||
|
|
||||||
|
use invariant proposalStateConsistency
|
||||||
|
use invariant queuedImplyDeadlineOver
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: propose effect and liveness. Includes "no double proposition" │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule propose_liveness(uint256 pId, env e) {
|
||||||
|
require nonpayable(e);
|
||||||
|
require clockSanity(e);
|
||||||
|
|
||||||
|
uint8 stateBefore = state(e, pId);
|
||||||
|
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; string descr;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descr);
|
||||||
|
|
||||||
|
propose@withrevert(e, targets, values, calldatas, descr);
|
||||||
|
|
||||||
|
// liveness & double proposal
|
||||||
|
assert !lastReverted <=> (
|
||||||
|
stateBefore == UNSET() &&
|
||||||
|
validProposal(targets, values, calldatas)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
rule propose_effect(uint256 pId, env e) {
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; string descr;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descr);
|
||||||
|
|
||||||
|
propose(e, targets, values, calldatas, descr);
|
||||||
|
|
||||||
|
// effect
|
||||||
|
assert state(e, pId) == PENDING();
|
||||||
|
assert proposalProposer(pId) == e.msg.sender;
|
||||||
|
assert proposalSnapshot(pId) == clock(e) + votingDelay();
|
||||||
|
assert proposalDeadline(pId) == clock(e) + votingDelay() + votingPeriod();
|
||||||
|
}
|
||||||
|
|
||||||
|
rule propose_sideeffect(uint256 pId, env e, uint256 otherId) {
|
||||||
|
uint8 otherStateBefore = state(e, otherId);
|
||||||
|
uint256 otherVoteStart = proposalSnapshot(otherId);
|
||||||
|
uint256 otherVoteEnd = proposalDeadline(otherId);
|
||||||
|
address otherProposer = proposalProposer(otherId);
|
||||||
|
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; string descr;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descr);
|
||||||
|
|
||||||
|
propose(e, targets, values, calldatas, descr);
|
||||||
|
|
||||||
|
// no side-effect
|
||||||
|
assert state(e, otherId) != otherStateBefore => otherId == pId;
|
||||||
|
assert proposalSnapshot(otherId) == otherVoteStart;
|
||||||
|
assert proposalDeadline(otherId) == otherVoteEnd;
|
||||||
|
assert proposalProposer(otherId) == otherProposer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: votes effect and liveness. Includes "A user cannot vote twice" │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule castVote_liveness(uint256 pId, env e, method f)
|
||||||
|
filtered { f -> voting(f) }
|
||||||
|
{
|
||||||
|
require nonpayable(e);
|
||||||
|
require clockSanity(e);
|
||||||
|
|
||||||
|
uint8 support;
|
||||||
|
address voter;
|
||||||
|
|
||||||
|
uint8 stateBefore = state(e, pId);
|
||||||
|
bool hasVotedBefore = hasVoted(pId, voter);
|
||||||
|
uint256 voterWeight = token_getPastVotes(voter, proposalSnapshot(pId));
|
||||||
|
|
||||||
|
// voting weight overflow check
|
||||||
|
require getAgainstVotes(pId) + voterWeight <= max_uint256;
|
||||||
|
require getForVotes(pId) + voterWeight <= max_uint256;
|
||||||
|
require getAbstainVotes(pId) + voterWeight <= max_uint256;
|
||||||
|
|
||||||
|
helperVoteWithRevert(e, f, pId, voter, support);
|
||||||
|
|
||||||
|
assert !lastReverted <=> (
|
||||||
|
stateBefore == ACTIVE() &&
|
||||||
|
!hasVotedBefore &&
|
||||||
|
(support == 0 || support == 1 || support == 2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
rule castVote_effect(uint256 pId, env e, method f)
|
||||||
|
filtered { f -> voting(f) }
|
||||||
|
{
|
||||||
|
uint8 support;
|
||||||
|
address voter;
|
||||||
|
|
||||||
|
uint256 againstVotesBefore = getAgainstVotes(pId);
|
||||||
|
uint256 forVotesBefore = getForVotes(pId);
|
||||||
|
uint256 abstainVotesBefore = getAbstainVotes(pId);
|
||||||
|
uint256 voterWeight = token_getPastVotes(voter, proposalSnapshot(pId));
|
||||||
|
|
||||||
|
uint256 weight = helperVoteWithRevert(e, f, pId, voter, support);
|
||||||
|
require !lastReverted;
|
||||||
|
|
||||||
|
assert state(e, pId) == ACTIVE();
|
||||||
|
assert voterWeight == weight;
|
||||||
|
assert getAgainstVotes(pId) == againstVotesBefore + (support == 0 ? weight : 0);
|
||||||
|
assert getForVotes(pId) == forVotesBefore + (support == 1 ? weight : 0);
|
||||||
|
assert getAbstainVotes(pId) == abstainVotesBefore + (support == 2 ? weight : 0);
|
||||||
|
assert hasVoted(pId, voter);
|
||||||
|
}
|
||||||
|
|
||||||
|
rule castVote_sideeffect(uint256 pId, env e, method f)
|
||||||
|
filtered { f -> voting(f) }
|
||||||
|
{
|
||||||
|
uint8 support;
|
||||||
|
address voter;
|
||||||
|
address otherVoter;
|
||||||
|
uint256 otherId;
|
||||||
|
|
||||||
|
bool otherVotedBefore = hasVoted(otherId, otherVoter);
|
||||||
|
uint256 otherAgainstVotesBefore = getAgainstVotes(otherId);
|
||||||
|
uint256 otherForVotesBefore = getForVotes(otherId);
|
||||||
|
uint256 otherAbstainVotesBefore = getAbstainVotes(otherId);
|
||||||
|
|
||||||
|
helperVoteWithRevert(e, f, pId, voter, support);
|
||||||
|
require !lastReverted;
|
||||||
|
|
||||||
|
// no side-effect
|
||||||
|
assert hasVoted(otherId, otherVoter) != otherVotedBefore => (otherId == pId && otherVoter == voter);
|
||||||
|
assert getAgainstVotes(otherId) != otherAgainstVotesBefore => (otherId == pId);
|
||||||
|
assert getForVotes(otherId) != otherForVotesBefore => (otherId == pId);
|
||||||
|
assert getAbstainVotes(otherId) != otherAbstainVotesBefore => (otherId == pId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: queue effect and liveness. │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule queue_liveness(uint256 pId, env e) {
|
||||||
|
require nonpayable(e);
|
||||||
|
require clockSanity(e);
|
||||||
|
|
||||||
|
uint8 stateBefore = state(e, pId);
|
||||||
|
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
queue@withrevert(e, targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
// liveness
|
||||||
|
assert !lastReverted <=> stateBefore == SUCCEEDED();
|
||||||
|
}
|
||||||
|
|
||||||
|
rule queue_effect(uint256 pId, env e) {
|
||||||
|
uint8 stateBefore = state(e, pId);
|
||||||
|
bool queuedBefore = isQueued(pId);
|
||||||
|
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
queue(e, targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
assert state(e, pId) == QUEUED();
|
||||||
|
assert isQueued(pId);
|
||||||
|
assert !queuedBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
rule queue_sideeffect(uint256 pId, env e, uint256 otherId) {
|
||||||
|
uint8 otherStateBefore = state(e, otherId);
|
||||||
|
bool otherQueuedBefore = isQueued(otherId);
|
||||||
|
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
queue(e, targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
// no side-effect
|
||||||
|
assert state(e, otherId) != otherStateBefore => otherId == pId;
|
||||||
|
assert isQueued(otherId) != otherQueuedBefore => otherId == pId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: execute effect and liveness. │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule execute_liveness(uint256 pId, env e) {
|
||||||
|
require nonpayable(e);
|
||||||
|
require clockSanity(e);
|
||||||
|
|
||||||
|
uint8 stateBefore = state(e, pId);
|
||||||
|
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
execute@withrevert(e, targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
// liveness: can't check full equivalence because of execution call reverts
|
||||||
|
assert !lastReverted => (stateBefore == SUCCEEDED() || stateBefore == QUEUED());
|
||||||
|
}
|
||||||
|
|
||||||
|
rule execute_effect(uint256 pId, env e) {
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
execute(e, targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
// effect
|
||||||
|
assert state(e, pId) == EXECUTED();
|
||||||
|
}
|
||||||
|
|
||||||
|
rule execute_sideeffect(uint256 pId, env e, uint256 otherId) {
|
||||||
|
uint8 otherStateBefore = state(e, otherId);
|
||||||
|
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
execute(e, targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
// no side-effect
|
||||||
|
assert state(e, otherId) != otherStateBefore => otherId == pId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: cancel (public) effect and liveness. │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule cancel_liveness(uint256 pId, env e) {
|
||||||
|
require nonpayable(e);
|
||||||
|
require clockSanity(e);
|
||||||
|
requireInvariant queuedImplyDeadlineOver(e, pId);
|
||||||
|
|
||||||
|
uint8 stateBefore = state(e, pId);
|
||||||
|
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
cancel@withrevert(e, targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
// liveness
|
||||||
|
assert !lastReverted <=> (
|
||||||
|
stateBefore == PENDING() &&
|
||||||
|
e.msg.sender == proposalProposer(pId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
rule cancel_effect(uint256 pId, env e) {
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
cancel(e, targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
// effect
|
||||||
|
assert state(e, pId) == CANCELED();
|
||||||
|
assert !isQueued(pId); // cancel resets timelockId
|
||||||
|
}
|
||||||
|
|
||||||
|
rule cancel_sideeffect(uint256 pId, env e, uint256 otherId) {
|
||||||
|
uint8 otherStateBefore = state(e, otherId);
|
||||||
|
bool otherQueuedBefore = isQueued(otherId);
|
||||||
|
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
cancel(e, targets, values, calldatas, descrHash);
|
||||||
|
|
||||||
|
// no side-effect
|
||||||
|
assert state(e, otherId) != otherStateBefore => otherId == pId;
|
||||||
|
assert isQueued(otherId) != otherQueuedBefore => otherId == pId;
|
||||||
|
}
|
||||||
153
certora/specs/GovernorInvariants.spec
Normal file
153
certora/specs/GovernorInvariants.spec
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import "helpers/helpers.spec"
|
||||||
|
import "helpers/Governor.helpers.spec"
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Invariant: clock is consistent between the goernor and the token │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule clockMode(env e) {
|
||||||
|
require clockSanity(e);
|
||||||
|
|
||||||
|
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: 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 clockSanity(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Invariant: votes recorded => created │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
invariant votesImplyCreated(uint256 pId)
|
||||||
|
(
|
||||||
|
getAgainstVotes(pId) > 0 ||
|
||||||
|
getForVotes(pId) > 0 ||
|
||||||
|
getAbstainVotes(pId) > 0
|
||||||
|
) => proposalCreated(pId)
|
||||||
|
{
|
||||||
|
preserved with (env e) {
|
||||||
|
require clockSanity(e);
|
||||||
|
requireInvariant proposalStateConsistency(pId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Invariant: cancel => created │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
invariant canceledImplyCreated(uint pId)
|
||||||
|
isCanceled(pId) => proposalCreated(pId)
|
||||||
|
{
|
||||||
|
preserved with (env e) {
|
||||||
|
require clockSanity(e);
|
||||||
|
requireInvariant proposalStateConsistency(pId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Invariant: executed => created │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
invariant executedImplyCreated(uint pId)
|
||||||
|
isExecuted(pId) => proposalCreated(pId)
|
||||||
|
{
|
||||||
|
preserved with (env e) {
|
||||||
|
require clockSanity(e);
|
||||||
|
requireInvariant proposalStateConsistency(pId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Invariant: queued => created │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
invariant queuedImplyCreated(uint pId)
|
||||||
|
isQueued(pId) => proposalCreated(pId)
|
||||||
|
{
|
||||||
|
preserved with (env e) {
|
||||||
|
require clockSanity(e);
|
||||||
|
requireInvariant proposalStateConsistency(pId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Invariant: timings │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
invariant votesImplySnapshotPassed(env e, uint256 pId)
|
||||||
|
(
|
||||||
|
getAgainstVotes(pId) > 0 ||
|
||||||
|
getForVotes(pId) > 0 ||
|
||||||
|
getAbstainVotes(pId) > 0
|
||||||
|
) => proposalSnapshot(pId) < clock(e)
|
||||||
|
{
|
||||||
|
preserved with (env e2) {
|
||||||
|
// In this invariant, `env e` is representing the present. And `clock(e)` the current timestamp.
|
||||||
|
// It should hold for any transitions in the pasts
|
||||||
|
require clock(e2) <= clock(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invariant queuedImplyDeadlineOver(env e, uint pId)
|
||||||
|
isQueued(pId) => proposalDeadline(pId) < clock(e)
|
||||||
|
{
|
||||||
|
preserved {
|
||||||
|
require clockSanity(e);
|
||||||
|
requireInvariant proposalStateConsistency(pId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 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)
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Invariant: the "governance call" dequeue is empty │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
invariant governanceCallLength()
|
||||||
|
governanceCallLength() == 0
|
||||||
68
certora/specs/GovernorPreventLateQuorum.spec
Normal file
68
certora/specs/GovernorPreventLateQuorum.spec
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import "helpers/helpers.spec"
|
||||||
|
import "helpers/Governor.helpers.spec"
|
||||||
|
import "GovernorInvariants.spec"
|
||||||
|
|
||||||
|
methods {
|
||||||
|
lateQuorumVoteExtension() returns uint64 envfree
|
||||||
|
getExtendedDeadline(uint256) returns uint64 envfree
|
||||||
|
}
|
||||||
|
|
||||||
|
use invariant proposalStateConsistency
|
||||||
|
use invariant votesImplySnapshotPassed
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ This is not (easily) provable as an invariant because the prover think `_totalSupplyCheckpoints` can arbitrarily │
|
||||||
|
│ change, which causes the quorum() to change. Not sure how to fix that. │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
// invariant deadlineExtendedEquivQuorumReached(uint256 pId)
|
||||||
|
// getExtendedDeadline(pId) > 0 <=> (quorumReached(pId) && !isCanceled(pId))
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: │
|
||||||
|
│ * Deadline can never be reduced │
|
||||||
|
│ * If deadline increases then we are in `deadlineExtended` state and `castVote` was called. │
|
||||||
|
│ * A proposal's deadline can't change in `deadlineExtended` state. │
|
||||||
|
│ * A proposal's deadline can't be unextended. │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule deadlineChangeToPreventLateQuorum(uint256 pId, env e, method f, calldataarg args)
|
||||||
|
filtered { f -> !assumedSafe(f) }
|
||||||
|
{
|
||||||
|
require clockSanity(e);
|
||||||
|
requireInvariant proposalStateConsistency(pId);
|
||||||
|
requireInvariant votesImplySnapshotPassed(e, pId);
|
||||||
|
|
||||||
|
uint256 deadlineBefore = proposalDeadline(pId);
|
||||||
|
bool deadlineExtendedBefore = getExtendedDeadline(pId) > 0;
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
uint256 deadlineAfter = proposalDeadline(pId);
|
||||||
|
bool deadlineExtendedAfter = getExtendedDeadline(pId) > 0;
|
||||||
|
|
||||||
|
// deadline can never be reduced
|
||||||
|
assert deadlineBefore <= deadlineAfter;
|
||||||
|
|
||||||
|
// deadline can only be extended in proposal or on cast vote
|
||||||
|
assert deadlineAfter != deadlineBefore => (
|
||||||
|
(
|
||||||
|
!deadlineExtendedBefore &&
|
||||||
|
!deadlineExtendedAfter &&
|
||||||
|
f.selector == propose(address[], uint256[], bytes[], string).selector
|
||||||
|
) || (
|
||||||
|
!deadlineExtendedBefore &&
|
||||||
|
deadlineExtendedAfter &&
|
||||||
|
deadlineAfter == clock(e) + lateQuorumVoteExtension() &&
|
||||||
|
votingAll(f)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// a deadline can only be extended once
|
||||||
|
assert deadlineExtendedBefore => deadlineBefore == deadlineAfter;
|
||||||
|
|
||||||
|
// a deadline cannot be un-extended
|
||||||
|
assert deadlineExtendedBefore => deadlineExtendedAfter;
|
||||||
|
}
|
||||||
238
certora/specs/GovernorStates.spec
Normal file
238
certora/specs/GovernorStates.spec
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
import "helpers/helpers.spec"
|
||||||
|
import "helpers/Governor.helpers.spec"
|
||||||
|
import "GovernorInvariants.spec"
|
||||||
|
|
||||||
|
use invariant proposalStateConsistency
|
||||||
|
use invariant votesImplySnapshotPassed
|
||||||
|
|
||||||
|
definition assumedSafeOrDuplicate(method f) returns bool =
|
||||||
|
assumedSafe(f)
|
||||||
|
|| f.selector == castVoteWithReason(uint256,uint8,string).selector
|
||||||
|
|| f.selector == castVoteWithReasonAndParams(uint256,uint8,string,bytes).selector
|
||||||
|
|| f.selector == castVoteBySig(uint256,uint8,uint8,bytes32,bytes32).selector
|
||||||
|
|| f.selector == castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32).selector;
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: state returns one of the value in the enumeration │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule stateDomain(env e, uint256 pId) {
|
||||||
|
uint8 result = safeState(e, pId);
|
||||||
|
assert (
|
||||||
|
result == UNSET() ||
|
||||||
|
result == PENDING() ||
|
||||||
|
result == ACTIVE() ||
|
||||||
|
result == CANCELED() ||
|
||||||
|
result == DEFEATED() ||
|
||||||
|
result == SUCCEEDED() ||
|
||||||
|
result == QUEUED() ||
|
||||||
|
result == EXECUTED()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ [DISABLED] Rule: State transitions caused by function calls │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
/// Previous version that results in the prover timing out.
|
||||||
|
// rule stateTransitionFn(uint256 pId, env e, method f, calldataarg args)
|
||||||
|
// filtered { f -> !assumedSafeOrDuplicate(f) }
|
||||||
|
// {
|
||||||
|
// require clockSanity(e);
|
||||||
|
// require quorumNumeratorLength() < max_uint256; // sanity
|
||||||
|
//
|
||||||
|
// uint8 stateBefore = safeState(e, pId);
|
||||||
|
// f(e, args);
|
||||||
|
// uint8 stateAfter = safeState(e, pId);
|
||||||
|
//
|
||||||
|
// assert (stateBefore != stateAfter) => (
|
||||||
|
// (stateBefore == UNSET() && stateAfter == PENDING() && f.selector == propose(address[],uint256[],bytes[],string).selector ) ||
|
||||||
|
// (stateBefore == PENDING() && stateAfter == CANCELED() && f.selector == cancel(address[],uint256[],bytes[],bytes32).selector ) ||
|
||||||
|
// (stateBefore == SUCCEEDED() && stateAfter == QUEUED() && f.selector == queue(address[],uint256[],bytes[],bytes32).selector ) ||
|
||||||
|
// (stateBefore == SUCCEEDED() && stateAfter == EXECUTED() && f.selector == execute(address[],uint256[],bytes[],bytes32).selector) ||
|
||||||
|
// (stateBefore == QUEUED() && stateAfter == EXECUTED() && f.selector == execute(address[],uint256[],bytes[],bytes32).selector)
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// This version also causes a lot of timeouts so we comment it out of now
|
||||||
|
// function stateTransitionFnHelper(method f, uint8 s) returns uint8 {
|
||||||
|
// uint256 pId; env e; calldataarg args;
|
||||||
|
//
|
||||||
|
// require clockSanity(e);
|
||||||
|
// require quorumNumeratorLength() < max_uint256; // sanity
|
||||||
|
//
|
||||||
|
// require safeState(e, pId) == s; // constrain state before
|
||||||
|
// f(e, args);
|
||||||
|
// require safeState(e, pId) != s; // constrain state after
|
||||||
|
//
|
||||||
|
// return safeState(e, pId);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// rule stateTransitionFn_UNSET(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
|
||||||
|
// uint8 stateAfter = stateTransitionFnHelper(f, UNSET());
|
||||||
|
// assert stateAfter == PENDING() && f.selector == propose(address[],uint256[],bytes[],string).selector;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// rule stateTransitionFn_PENDING(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
|
||||||
|
// uint8 stateAfter = stateTransitionFnHelper(f, PENDING());
|
||||||
|
// assert stateAfter == CANCELED() && f.selector == cancel(address[],uint256[],bytes[],bytes32).selector;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// rule stateTransitionFn_ACTIVE(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
|
||||||
|
// uint8 stateAfter = stateTransitionFnHelper(f, ACTIVE());
|
||||||
|
// assert false;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// rule stateTransitionFn_CANCELED(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
|
||||||
|
// uint8 stateAfter = stateTransitionFnHelper(f, CANCELED());
|
||||||
|
// assert false;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// rule stateTransitionFn_DEFEATED(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
|
||||||
|
// uint8 stateAfter = stateTransitionFnHelper(f, DEFEATED());
|
||||||
|
// assert false;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// rule stateTransitionFn_SUCCEEDED(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
|
||||||
|
// uint8 stateAfter = stateTransitionFnHelper(f, SUCCEEDED());
|
||||||
|
// assert (
|
||||||
|
// (stateAfter == QUEUED() && f.selector == queue(address[],uint256[],bytes[],bytes32).selector) ||
|
||||||
|
// (stateAfter == EXECUTED() && f.selector == execute(address[],uint256[],bytes[],bytes32).selector)
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// rule stateTransitionFn_QUEUED(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
|
||||||
|
// uint8 stateAfter = stateTransitionFnHelper(f, QUEUED());
|
||||||
|
// assert stateAfter == EXECUTED() && f.selector == execute(address[],uint256[],bytes[],bytes32).selector;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// rule stateTransitionFn_EXECUTED(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
|
||||||
|
// uint8 stateAfter = stateTransitionFnHelper(f, EXECUTED());
|
||||||
|
// assert false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: State transitions caused by time passing │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
// The timelockId can be set in states QUEUED, EXECUTED and CANCELED. However, checking the full scope of this results
|
||||||
|
// in a timeout. This is a weaker version that is still useful. Ideally we would check `safeState`.
|
||||||
|
invariant noTimelockBeforeEndOfVote(env e, uint256 pId)
|
||||||
|
state(e, pId) == ACTIVE() => timelockId(pId) == 0
|
||||||
|
|
||||||
|
rule stateTransitionWait(uint256 pId, env e1, env e2) {
|
||||||
|
require clockSanity(e1);
|
||||||
|
require clockSanity(e2);
|
||||||
|
require clock(e2) > clock(e1);
|
||||||
|
|
||||||
|
// Force the state to be consistent with e1 (before). We want the storage related to `pId` to match what is
|
||||||
|
// possible before the time passes. We don't want the state transition include elements that cannot have happened
|
||||||
|
// before e1. This ensure that the e1 → e2 state transition is purelly a consequence of time passing.
|
||||||
|
requireInvariant votesImplySnapshotPassed(e1, pId);
|
||||||
|
requireInvariant noTimelockBeforeEndOfVote(e1, pId);
|
||||||
|
|
||||||
|
uint8 stateBefore = state(e1, pId); // Ideally we would use "safeState(e1, pId)"
|
||||||
|
uint8 stateAfter = state(e2, pId); // Ideally we would use "safeState(e2, pId)"
|
||||||
|
|
||||||
|
assert (stateBefore != stateAfter) => (
|
||||||
|
(stateBefore == PENDING() && stateAfter == ACTIVE() ) ||
|
||||||
|
(stateBefore == PENDING() && stateAfter == DEFEATED() ) ||
|
||||||
|
(stateBefore == ACTIVE() && stateAfter == SUCCEEDED()) ||
|
||||||
|
(stateBefore == ACTIVE() && stateAfter == DEFEATED() )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: State corresponds to the vote timing and results │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule stateIsConsistentWithVotes(uint256 pId, env e) {
|
||||||
|
require clockSanity(e);
|
||||||
|
requireInvariant proposalStateConsistency(pId);
|
||||||
|
|
||||||
|
uint48 currentClock = clock(e);
|
||||||
|
uint8 currentState = safeState(e, pId);
|
||||||
|
uint256 snapshot = proposalSnapshot(pId);
|
||||||
|
uint256 deadline = proposalDeadline(pId);
|
||||||
|
bool quorumSuccess = quorumReached(pId);
|
||||||
|
bool voteSuccess = voteSucceeded(pId);
|
||||||
|
|
||||||
|
// Pending: before vote starts
|
||||||
|
assert currentState == PENDING() => (
|
||||||
|
snapshot >= currentClock
|
||||||
|
);
|
||||||
|
|
||||||
|
// Active: after vote starts & before vote ends
|
||||||
|
assert currentState == ACTIVE() => (
|
||||||
|
snapshot < currentClock &&
|
||||||
|
deadline >= currentClock
|
||||||
|
);
|
||||||
|
|
||||||
|
// Succeeded: after vote end, with vote successful and quorum reached
|
||||||
|
assert currentState == SUCCEEDED() => (
|
||||||
|
deadline < currentClock &&
|
||||||
|
(
|
||||||
|
quorumSuccess &&
|
||||||
|
voteSuccess
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Defeated: after vote end, with vote not successful or quorum not reached
|
||||||
|
assert currentState == DEFEATED() => (
|
||||||
|
deadline < currentClock &&
|
||||||
|
(
|
||||||
|
!quorumSuccess ||
|
||||||
|
!voteSuccess
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ [NEED WORK] Rule: `updateQuorumNumerator` cannot cause quorumReached to change. │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
//// This would be nice, but its way to slow to run because "quorumReached" is a FV nightmare
|
||||||
|
//// Also, for it to work we need to prove that the checkpoints have (strictly) increasing keys.
|
||||||
|
// rule onlyVoteCanChangeQuorumReached(uint256 pId, env e, method f, calldataarg args)
|
||||||
|
// filtered { f -> !assumedSafeOrDuplicate(f) }
|
||||||
|
// {
|
||||||
|
// require clockSanity(e);
|
||||||
|
// require clock(e) > proposalSnapshot(pId); // vote has started
|
||||||
|
// require quorumNumeratorLength() < max_uint256; // sanity
|
||||||
|
//
|
||||||
|
// bool quorumReachedBefore = quorumReached(pId);
|
||||||
|
//
|
||||||
|
// uint256 snapshot = proposalSnapshot(pId);
|
||||||
|
// uint256 totalSupply = token_getPastTotalSupply(snapshot);
|
||||||
|
//
|
||||||
|
// f(e, args);
|
||||||
|
//
|
||||||
|
// // Needed because the prover doesn't understand the checkpoint properties of the voting token.
|
||||||
|
// require clock(e) > snapshot => token_getPastTotalSupply(snapshot) == totalSupply;
|
||||||
|
//
|
||||||
|
// assert quorumReached(pId) != quorumReachedBefore => (
|
||||||
|
// !quorumReachedBefore &&
|
||||||
|
// votingAll(f)
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
//// To prove that, we need to prove that the checkpoints have (strictly) increasing keys.
|
||||||
|
//// otherwise it gives us counter example where the checkpoint history has keys:
|
||||||
|
//// [ 12,12,13,13,12] and the lookup obviously fail to get the correct value
|
||||||
|
// rule quorumUpdateDoesntAffectPastProposals(uint256 pId, env e) {
|
||||||
|
// require clockSanity(e);
|
||||||
|
// require clock(e) > proposalSnapshot(pId); // vote has started
|
||||||
|
// require quorumNumeratorLength() < max_uint256; // sanity
|
||||||
|
//
|
||||||
|
// bool quorumReachedBefore = quorumReached(pId);
|
||||||
|
//
|
||||||
|
// uint256 newQuorumNumerator;
|
||||||
|
// updateQuorumNumerator(e, newQuorumNumerator);
|
||||||
|
//
|
||||||
|
// assert quorumReached(pId) == quorumReachedBefore;
|
||||||
|
// }
|
||||||
@ -1,29 +1,6 @@
|
|||||||
import "helpers/helpers.spec"
|
import "helpers/helpers.spec"
|
||||||
import "methods/IAccessControl.spec"
|
import "methods/IAccessControl.spec"
|
||||||
|
import "methods/ITimelockController.spec"
|
||||||
methods {
|
|
||||||
TIMELOCK_ADMIN_ROLE() returns (bytes32) envfree
|
|
||||||
PROPOSER_ROLE() returns (bytes32) envfree
|
|
||||||
EXECUTOR_ROLE() returns (bytes32) envfree
|
|
||||||
CANCELLER_ROLE() returns (bytes32) envfree
|
|
||||||
isOperation(bytes32) returns (bool) envfree
|
|
||||||
isOperationPending(bytes32) returns (bool) envfree
|
|
||||||
isOperationReady(bytes32) returns (bool)
|
|
||||||
isOperationDone(bytes32) returns (bool) envfree
|
|
||||||
getTimestamp(bytes32) returns (uint256) envfree
|
|
||||||
getMinDelay() returns (uint256) envfree
|
|
||||||
|
|
||||||
hashOperation(address, uint256, bytes, bytes32, bytes32) returns(bytes32) envfree
|
|
||||||
hashOperationBatch(address[], uint256[], bytes[], bytes32, bytes32) returns(bytes32) envfree
|
|
||||||
|
|
||||||
schedule(address, uint256, bytes, bytes32, bytes32, uint256)
|
|
||||||
scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256)
|
|
||||||
execute(address, uint256, bytes, bytes32, bytes32)
|
|
||||||
executeBatch(address[], uint256[], bytes[], bytes32, bytes32)
|
|
||||||
cancel(bytes32)
|
|
||||||
|
|
||||||
updateDelay(uint256)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
|||||||
186
certora/specs/helpers/Governor.helpers.spec
Normal file
186
certora/specs/helpers/Governor.helpers.spec
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import "helpers.spec"
|
||||||
|
import "../methods/IGovernor.spec"
|
||||||
|
import "../methods/ITimelockController.spec"
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Sanity │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
function clockSanity(env e) returns bool {
|
||||||
|
return e.block.number < max_uint48()
|
||||||
|
&& e.block.timestamp < max_uint48()
|
||||||
|
&& clock(e) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validProposal(address[] targets, uint256[] values, bytes[] calldatas) returns bool {
|
||||||
|
return targets.length > 0
|
||||||
|
&& targets.length == values.length
|
||||||
|
&& targets.length == calldatas.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanityString(string s) returns bool {
|
||||||
|
return s.length < 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanityBytes(bytes b) returns bool {
|
||||||
|
return b.length < 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ States │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
definition UNSET() returns uint8 = 255;
|
||||||
|
definition PENDING() returns uint8 = 0;
|
||||||
|
definition ACTIVE() returns uint8 = 1;
|
||||||
|
definition CANCELED() returns uint8 = 2;
|
||||||
|
definition DEFEATED() returns uint8 = 3;
|
||||||
|
definition SUCCEEDED() returns uint8 = 4;
|
||||||
|
definition QUEUED() returns uint8 = 5;
|
||||||
|
definition EXPIRED() returns uint8 = 6;
|
||||||
|
definition EXECUTED() returns uint8 = 7;
|
||||||
|
|
||||||
|
// This helper is an alternative to state(e, pId) that will return UNSET() instead of reverting when then proposal
|
||||||
|
// does not exist (not created yet)
|
||||||
|
function safeState(env e, uint256 pId) returns uint8 {
|
||||||
|
return proposalCreated(pId) ? state(e, pId): UNSET();
|
||||||
|
}
|
||||||
|
|
||||||
|
definition proposalCreated(uint256 pId) returns bool =
|
||||||
|
proposalSnapshot(pId) > 0 && proposalDeadline(pId) > 0 && proposalProposer(pId) != 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Filters │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
definition assumedSafe(method f) returns bool =
|
||||||
|
f.isView ||
|
||||||
|
f.isFallback ||
|
||||||
|
f.selector == relay(address,uint256,bytes).selector ||
|
||||||
|
f.selector == onERC721Received(address,address,uint256,bytes).selector ||
|
||||||
|
f.selector == onERC1155Received(address,address,uint256,uint256,bytes).selector ||
|
||||||
|
f.selector == onERC1155BatchReceived(address,address,uint256[],uint256[],bytes).selector;
|
||||||
|
|
||||||
|
// These function are covered by helperFunctionsWithRevert
|
||||||
|
definition operateOnProposal(method f) returns bool =
|
||||||
|
f.selector == propose(address[],uint256[],bytes[],string).selector ||
|
||||||
|
f.selector == queue(address[],uint256[],bytes[],bytes32).selector ||
|
||||||
|
f.selector == execute(address[],uint256[],bytes[],bytes32).selector ||
|
||||||
|
f.selector == cancel(address[],uint256[],bytes[],bytes32).selector ||
|
||||||
|
f.selector == castVote(uint256,uint8).selector ||
|
||||||
|
f.selector == castVoteWithReason(uint256,uint8,string).selector ||
|
||||||
|
f.selector == castVoteWithReasonAndParams(uint256,uint8,string,bytes).selector ||
|
||||||
|
f.selector == castVoteBySig(uint256,uint8,uint8,bytes32,bytes32).selector ||
|
||||||
|
f.selector == castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32).selector;
|
||||||
|
|
||||||
|
// These function are covered by helperVoteWithRevert
|
||||||
|
definition voting(method f) returns bool =
|
||||||
|
f.selector == castVote(uint256,uint8).selector ||
|
||||||
|
f.selector == castVoteWithReason(uint256,uint8,string).selector ||
|
||||||
|
f.selector == castVoteWithReasonAndParams(uint256,uint8,string,bytes).selector;
|
||||||
|
|
||||||
|
definition votingBySig(method f) returns bool =
|
||||||
|
f.selector == castVoteBySig(uint256,uint8,uint8,bytes32,bytes32).selector ||
|
||||||
|
f.selector == castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32).selector;
|
||||||
|
|
||||||
|
definition votingAll(method f) returns bool =
|
||||||
|
voting(f) || votingBySig(f);
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Helper functions │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
function helperVoteWithRevert(env e, method f, uint256 pId, address voter, uint8 support) returns uint256 {
|
||||||
|
if (f.selector == castVote(uint256,uint8).selector)
|
||||||
|
{
|
||||||
|
require e.msg.sender == voter;
|
||||||
|
return castVote@withrevert(e, pId, support);
|
||||||
|
}
|
||||||
|
else if (f.selector == castVoteWithReason(uint256,uint8,string).selector)
|
||||||
|
{
|
||||||
|
string reason;
|
||||||
|
require e.msg.sender == voter && sanityString(reason);
|
||||||
|
return castVoteWithReason@withrevert(e, pId, support, reason);
|
||||||
|
}
|
||||||
|
else if (f.selector == castVoteWithReasonAndParams(uint256,uint8,string,bytes).selector)
|
||||||
|
{
|
||||||
|
string reason; bytes params;
|
||||||
|
require e.msg.sender == voter && sanityString(reason) && sanityBytes(params);
|
||||||
|
return castVoteWithReasonAndParams@withrevert(e, pId, support, reason, params);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Governor function that operates on a given proposalId may or may not include the proposalId in the arguments. This
|
||||||
|
// helper restricts the call to method `f` in a way that it's operating on a specific proposal.
|
||||||
|
//
|
||||||
|
// This can be used to say "consider any function call that operates on proposal `pId`" or "consider a propose call
|
||||||
|
// that corresponds to a given pId".
|
||||||
|
//
|
||||||
|
// This is for example used when proving that not 2 proposals can be proposed with the same id: Once the proposal is
|
||||||
|
// proposed a first time, we want to prove that "any propose call that corresponds to the same id should revert".
|
||||||
|
function helperFunctionsWithRevert(env e, method f, uint256 pId) {
|
||||||
|
if (f.selector == propose(address[],uint256[],bytes[],string).selector)
|
||||||
|
{
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; string descr;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descr);
|
||||||
|
propose@withrevert(e, targets, values, calldatas, descr);
|
||||||
|
}
|
||||||
|
else if (f.selector == queue(address[],uint256[],bytes[],bytes32).selector)
|
||||||
|
{
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descrHash);
|
||||||
|
queue@withrevert(e, targets, values, calldatas, descrHash);
|
||||||
|
}
|
||||||
|
else if (f.selector == execute(address[],uint256[],bytes[],bytes32).selector)
|
||||||
|
{
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descrHash);
|
||||||
|
execute@withrevert(e, targets, values, calldatas, descrHash);
|
||||||
|
}
|
||||||
|
else if (f.selector == cancel(address[],uint256[],bytes[],bytes32).selector)
|
||||||
|
{
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
|
||||||
|
require pId == hashProposal(targets, values, calldatas, descrHash);
|
||||||
|
cancel@withrevert(e, targets, values, calldatas, descrHash);
|
||||||
|
}
|
||||||
|
else if (f.selector == castVote(uint256,uint8).selector)
|
||||||
|
{
|
||||||
|
uint8 support;
|
||||||
|
castVote@withrevert(e, pId, support);
|
||||||
|
}
|
||||||
|
else if (f.selector == castVoteWithReason(uint256,uint8,string).selector)
|
||||||
|
{
|
||||||
|
uint8 support; string reason;
|
||||||
|
castVoteWithReason@withrevert(e, pId, support, reason);
|
||||||
|
}
|
||||||
|
else if (f.selector == castVoteWithReasonAndParams(uint256,uint8,string,bytes).selector)
|
||||||
|
{
|
||||||
|
uint8 support; string reason; bytes params;
|
||||||
|
castVoteWithReasonAndParams@withrevert(e, pId, support, reason, params);
|
||||||
|
}
|
||||||
|
else if (f.selector == castVoteBySig(uint256,uint8,uint8,bytes32,bytes32).selector)
|
||||||
|
{
|
||||||
|
uint8 support; uint8 v; bytes32 r; bytes32 s;
|
||||||
|
castVoteBySig@withrevert(e, pId, support, v, r, s);
|
||||||
|
}
|
||||||
|
else if (f.selector == castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32).selector)
|
||||||
|
{
|
||||||
|
uint8 support; string reason; bytes params; uint8 v; bytes32 r; bytes32 s;
|
||||||
|
castVoteWithReasonAndParamsBySig@withrevert(e, pId, support, reason, params, v, r, s);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
certora/specs/methods/IGovernor.spec
Normal file
56
certora/specs/methods/IGovernor.spec
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// includes some non standard (from extension) and harness functions
|
||||||
|
methods {
|
||||||
|
name() returns string envfree
|
||||||
|
version() returns string envfree
|
||||||
|
token() returns address envfree
|
||||||
|
timelock() returns address envfree
|
||||||
|
clock() returns uint48
|
||||||
|
CLOCK_MODE() returns string
|
||||||
|
COUNTING_MODE() returns string envfree
|
||||||
|
hashProposal(address[],uint256[],bytes[],bytes32) returns uint256 envfree
|
||||||
|
state(uint256) returns uint8
|
||||||
|
proposalThreshold() returns uint256 envfree
|
||||||
|
proposalSnapshot(uint256) returns uint256 envfree
|
||||||
|
proposalDeadline(uint256) returns uint256 envfree
|
||||||
|
votingDelay() returns uint256 envfree
|
||||||
|
votingPeriod() returns uint256 envfree
|
||||||
|
quorum(uint256) returns uint256 envfree
|
||||||
|
getVotes(address,uint256) returns uint256 envfree
|
||||||
|
getVotesWithParams(address,uint256,bytes) returns uint256 envfree
|
||||||
|
hasVoted(uint256,address) returns bool envfree
|
||||||
|
quorumNumerator() returns uint256 envfree
|
||||||
|
quorumNumerator(uint256) returns uint256 envfree
|
||||||
|
quorumDenominator() returns uint256 envfree
|
||||||
|
|
||||||
|
propose(address[],uint256[],bytes[],string) returns uint256
|
||||||
|
execute(address[],uint256[],bytes[],bytes32) returns uint256
|
||||||
|
queue(address[], uint256[], bytes[], bytes32) returns uint256
|
||||||
|
cancel(address[],uint256[],bytes[],bytes32) returns uint256
|
||||||
|
castVote(uint256,uint8) returns uint256
|
||||||
|
castVoteWithReason(uint256,uint8,string) returns uint256
|
||||||
|
castVoteWithReasonAndParams(uint256,uint8,string,bytes) returns uint256
|
||||||
|
castVoteBySig(uint256,uint8,uint8,bytes32,bytes32) returns uint256
|
||||||
|
castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32) returns uint256
|
||||||
|
updateQuorumNumerator(uint256)
|
||||||
|
updateTimelock(address)
|
||||||
|
|
||||||
|
// harness
|
||||||
|
token_getPastTotalSupply(uint256) returns uint256 envfree
|
||||||
|
token_getPastVotes(address,uint256) returns uint256 envfree
|
||||||
|
token_clock() returns uint48
|
||||||
|
token_CLOCK_MODE() returns string
|
||||||
|
hashProposal(address[],uint256[],bytes[],string) returns uint256 envfree
|
||||||
|
getExecutor() returns address envfree
|
||||||
|
proposalProposer(uint256) returns address envfree
|
||||||
|
quorumReached(uint256) returns bool envfree
|
||||||
|
voteSucceeded(uint256) returns bool envfree
|
||||||
|
isExecuted(uint256) returns bool envfree
|
||||||
|
isCanceled(uint256) returns bool envfree
|
||||||
|
isQueued(uint256) returns bool envfree
|
||||||
|
governanceCallLength() returns uint256 envfree
|
||||||
|
getAgainstVotes(uint256) returns uint256 envfree
|
||||||
|
getForVotes(uint256) returns uint256 envfree
|
||||||
|
getAbstainVotes(uint256) returns uint256 envfree
|
||||||
|
quorumNumeratorLength() returns uint256 envfree
|
||||||
|
timelockId(uint256) returns bytes32 envfree
|
||||||
|
}
|
||||||
22
certora/specs/methods/ITimelockController.spec
Normal file
22
certora/specs/methods/ITimelockController.spec
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
methods {
|
||||||
|
TIMELOCK_ADMIN_ROLE() returns (bytes32) envfree => DISPATCHER(true)
|
||||||
|
PROPOSER_ROLE() returns (bytes32) envfree => DISPATCHER(true)
|
||||||
|
EXECUTOR_ROLE() returns (bytes32) envfree => DISPATCHER(true)
|
||||||
|
CANCELLER_ROLE() returns (bytes32) envfree => DISPATCHER(true)
|
||||||
|
isOperation(bytes32) returns (bool) envfree => DISPATCHER(true)
|
||||||
|
isOperationPending(bytes32) returns (bool) envfree => DISPATCHER(true)
|
||||||
|
isOperationReady(bytes32) returns (bool) => DISPATCHER(true)
|
||||||
|
isOperationDone(bytes32) returns (bool) envfree => DISPATCHER(true)
|
||||||
|
getTimestamp(bytes32) returns (uint256) envfree => DISPATCHER(true)
|
||||||
|
getMinDelay() returns (uint256) envfree => DISPATCHER(true)
|
||||||
|
|
||||||
|
hashOperation(address, uint256, bytes, bytes32, bytes32) returns(bytes32) envfree => DISPATCHER(true)
|
||||||
|
hashOperationBatch(address[], uint256[], bytes[], bytes32, bytes32) returns(bytes32) envfree => DISPATCHER(true)
|
||||||
|
|
||||||
|
schedule(address, uint256, bytes, bytes32, bytes32, uint256) => DISPATCHER(true)
|
||||||
|
scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256) => DISPATCHER(true)
|
||||||
|
execute(address, uint256, bytes, bytes32, bytes32) => DISPATCHER(true)
|
||||||
|
executeBatch(address[], uint256[], bytes[], bytes32, bytes32) => DISPATCHER(true)
|
||||||
|
cancel(bytes32) => DISPATCHER(true)
|
||||||
|
updateDelay(uint256) => DISPATCHER(true)
|
||||||
|
}
|
||||||
@ -1 +1 @@
|
|||||||
certora-cli==3.6.4
|
certora-cli==3.6.8
|
||||||
|
|||||||
Reference in New Issue
Block a user