commit
1c3b17826e
@ -1,4 +1,4 @@ |
||||
contact_links: |
||||
- name: Support request |
||||
- name: Questions & Support Requests |
||||
url: https://forum.openzeppelin.com/c/support/contracts/18 |
||||
about: Ask the community in the Community Forum |
||||
about: Ask in the OpenZeppelin Forum |
||||
|
@ -0,0 +1,25 @@ |
||||
name: Build Docs |
||||
|
||||
on: |
||||
push: |
||||
branches: [release-v*] |
||||
|
||||
jobs: |
||||
build: |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/checkout@v2 |
||||
- uses: actions/setup-node@v3 |
||||
with: |
||||
node-version: 12.x |
||||
- uses: actions/cache@v2 |
||||
id: cache |
||||
with: |
||||
path: '**/node_modules' |
||||
key: npm-v2-${{ hashFiles('**/package-lock.json') }} |
||||
restore-keys: npm-v2- |
||||
- run: npm ci |
||||
if: steps.cache.outputs.cache-hit != 'true' |
||||
- run: bash scripts/git-user-config.sh |
||||
- run: node scripts/update-docs-branch.js |
||||
- run: git push --all origin |
@ -0,0 +1,24 @@ |
||||
default: help |
||||
|
||||
PATCH = applyHarness.patch
|
||||
CONTRACTS_DIR = ../contracts
|
||||
MUNGED_DIR = munged
|
||||
|
||||
help: |
||||
@echo "usage:"
|
||||
@echo " make clean: remove all generated files (those ignored by git)"
|
||||
@echo " make $(MUNGED_DIR): create $(MUNGED_DIR) directory by applying the patch file to $(CONTRACTS_DIR)"
|
||||
@echo " make record: record a new patch file capturing the differences between $(CONTRACTS_DIR) and $(MUNGED_DIR)"
|
||||
|
||||
munged: $(wildcard $(CONTRACTS_DIR)/*.sol) $(PATCH) |
||||
rm -rf $@
|
||||
cp -r $(CONTRACTS_DIR) $@
|
||||
patch -p0 -d $@ < $(PATCH)
|
||||
|
||||
record: |
||||
diff -ruN $(CONTRACTS_DIR) $(MUNGED_DIR) | sed 's+../contracts/++g' | sed 's+munged/++g' > $(PATCH)
|
||||
|
||||
clean: |
||||
git clean -fdX
|
||||
touch $(PATCH)
|
||||
|
@ -0,0 +1,56 @@ |
||||
# Running the certora verification tool |
||||
|
||||
These instructions detail the process for running CVT on the OpenZeppelin (Wizard/Governor) contracts. |
||||
|
||||
Documentation for CVT and the specification language are available |
||||
[here](https://certora.atlassian.net/wiki/spaces/CPD/overview) |
||||
|
||||
## Running the verification |
||||
|
||||
The scripts in the `certora/scripts` directory are used to submit verification |
||||
jobs to the Certora verification service. After the job is complete, the results will be available on |
||||
[the Certora portal](https://vaas-stg.certora.com/). |
||||
|
||||
These scripts should be run from the root directory; for example by running |
||||
|
||||
``` |
||||
sh certora/scripts/verifyAll.sh <meaningful comment> |
||||
``` |
||||
|
||||
The most important of these is `verifyAll.sh`, which checks |
||||
all of the harnessed contracts (`certora/harness/Wizard*.sol`) against all of |
||||
the specifications (`certora/spec/*.spec`). |
||||
|
||||
The other scripts run a subset of the specifications or the contracts. You can |
||||
verify different contracts or specifications by changing the `--verify` option, |
||||
and you can run a single rule or method with the `--rule` or `--method` option. |
||||
|
||||
For example, to verify the `WizardFirstPriority` contract against the |
||||
`GovernorCountingSimple` specification, you could change the `--verify` line of |
||||
the `WizardControlFirstPriortity.sh` script to: |
||||
|
||||
``` |
||||
--verify WizardFirstPriority:certora/specs/GovernorCountingSimple.spec \ |
||||
``` |
||||
|
||||
## Adapting to changes in the contracts |
||||
|
||||
Some of our rules require the code to be simplified in various ways. Our |
||||
primary tool for performing these simplifications is to run verification on a |
||||
contract that extends the original contracts and overrides some of the methods. |
||||
These "harness" contracts can be found in the `certora/harness` directory. |
||||
|
||||
This pattern does require some modifications to the original code: some methods |
||||
need to be made virtual or public, for example. These changes are handled by |
||||
applying a patch to the code before verification. |
||||
|
||||
When one of the `verify` scripts is executed, it first applies the patch file |
||||
`certora/applyHarness.patch` to the `contracts` directory, placing the output |
||||
in the `certora/munged` directory. We then verify the contracts in the |
||||
`certora/munged` directory. |
||||
|
||||
If the original contracts change, it is possible to create a conflict with the |
||||
patch. In this case, the verify scripts will report an error message and output |
||||
rejected changes in the `munged` directory. After merging the changes, run |
||||
`make record` in the `certora` directory; this will regenerate the patch file, |
||||
which can then be checked into git. |
@ -0,0 +1,101 @@ |
||||
diff -ruN .gitignore .gitignore
|
||||
--- .gitignore 1969-12-31 19:00:00.000000000 -0500
|
||||
+++ .gitignore 2021-12-09 14:46:33.923637220 -0500
|
||||
@@ -0,0 +1,2 @@
|
||||
+*
|
||||
+!.gitignore
|
||||
diff -ruN governance/compatibility/GovernorCompatibilityBravo.sol governance/compatibility/GovernorCompatibilityBravo.sol
|
||||
--- governance/compatibility/GovernorCompatibilityBravo.sol 2021-12-03 15:24:56.523654357 -0500
|
||||
+++ governance/compatibility/GovernorCompatibilityBravo.sol 2021-12-09 14:46:33.923637220 -0500
|
||||
@@ -245,7 +245,7 @@
|
||||
/**
|
||||
* @dev See {Governor-_quorumReached}. In this module, only forVotes count toward the quorum.
|
||||
*/
|
||||
- function _quorumReached(uint256 proposalId) internal view virtual override returns (bool) {
|
||||
+ function _quorumReached(uint256 proposalId) public view virtual override returns (bool) { // HARNESS: changed to public from internal
|
||||
ProposalDetails storage details = _proposalDetails[proposalId];
|
||||
return quorum(proposalSnapshot(proposalId)) <= details.forVotes;
|
||||
}
|
||||
@@ -253,7 +253,7 @@
|
||||
/**
|
||||
* @dev See {Governor-_voteSucceeded}. In this module, the forVotes must be scritly over the againstVotes.
|
||||
*/
|
||||
- function _voteSucceeded(uint256 proposalId) internal view virtual override returns (bool) {
|
||||
+ function _voteSucceeded(uint256 proposalId) public view virtual override returns (bool) { // HARNESS: changed to public from internal
|
||||
ProposalDetails storage details = _proposalDetails[proposalId];
|
||||
return details.forVotes > details.againstVotes;
|
||||
}
|
||||
diff -ruN governance/extensions/GovernorCountingSimple.sol governance/extensions/GovernorCountingSimple.sol
|
||||
--- governance/extensions/GovernorCountingSimple.sol 2021-12-03 15:24:56.523654357 -0500
|
||||
+++ governance/extensions/GovernorCountingSimple.sol 2021-12-09 14:46:33.923637220 -0500
|
||||
@@ -64,7 +64,7 @@
|
||||
/**
|
||||
* @dev See {Governor-_quorumReached}.
|
||||
*/
|
||||
- function _quorumReached(uint256 proposalId) internal view virtual override returns (bool) {
|
||||
+ function _quorumReached(uint256 proposalId) public view virtual override returns (bool) {
|
||||
ProposalVote storage proposalvote = _proposalVotes[proposalId];
|
||||
|
||||
return quorum(proposalSnapshot(proposalId)) <= proposalvote.forVotes + proposalvote.abstainVotes;
|
||||
@@ -73,7 +73,7 @@
|
||||
/**
|
||||
* @dev See {Governor-_voteSucceeded}. In this module, the forVotes must be strictly over the againstVotes.
|
||||
*/
|
||||
- function _voteSucceeded(uint256 proposalId) internal view virtual override returns (bool) {
|
||||
+ function _voteSucceeded(uint256 proposalId) public view virtual override returns (bool) {
|
||||
ProposalVote storage proposalvote = _proposalVotes[proposalId];
|
||||
|
||||
return proposalvote.forVotes > proposalvote.againstVotes;
|
||||
diff -ruN governance/extensions/GovernorTimelockControl.sol governance/extensions/GovernorTimelockControl.sol
|
||||
--- governance/extensions/GovernorTimelockControl.sol 2021-12-03 15:24:56.523654357 -0500
|
||||
+++ governance/extensions/GovernorTimelockControl.sol 2021-12-09 14:46:33.923637220 -0500
|
||||
@@ -111,7 +111,7 @@
|
||||
bytes[] memory calldatas,
|
||||
bytes32 descriptionHash
|
||||
) internal virtual override {
|
||||
- _timelock.executeBatch{value: msg.value}(targets, values, calldatas, 0, descriptionHash);
|
||||
+ _timelock.executeBatch{value: msg.value}(targets, values, calldatas, 0, descriptionHash);
|
||||
}
|
||||
|
||||
/**
|
||||
diff -ruN governance/Governor.sol governance/Governor.sol
|
||||
--- governance/Governor.sol 2021-12-03 15:24:56.523654357 -0500
|
||||
+++ governance/Governor.sol 2021-12-09 14:46:56.411503587 -0500
|
||||
@@ -38,8 +38,8 @@
|
||||
|
||||
string private _name;
|
||||
|
||||
- mapping(uint256 => ProposalCore) private _proposals;
|
||||
-
|
||||
+ mapping(uint256 => ProposalCore) public _proposals;
|
||||
+
|
||||
/**
|
||||
* @dev Restrict access to governor executing address. Some module might override the _executor function to make
|
||||
* sure this modifier is consistant with the execution model.
|
||||
@@ -167,12 +167,12 @@
|
||||
/**
|
||||
* @dev Amount of votes already cast passes the threshold limit.
|
||||
*/
|
||||
- function _quorumReached(uint256 proposalId) internal view virtual returns (bool);
|
||||
+ function _quorumReached(uint256 proposalId) public view virtual returns (bool); // HARNESS: changed to public from internal
|
||||
|
||||
/**
|
||||
* @dev Is the proposal successful or not.
|
||||
*/
|
||||
- function _voteSucceeded(uint256 proposalId) internal view virtual returns (bool);
|
||||
+ function _voteSucceeded(uint256 proposalId) public view virtual returns (bool); // HARNESS: changed to public from internal
|
||||
|
||||
/**
|
||||
* @dev Register a vote with a given support and voting weight.
|
||||
diff -ruN token/ERC20/extensions/ERC20Votes.sol token/ERC20/extensions/ERC20Votes.sol
|
||||
--- token/ERC20/extensions/ERC20Votes.sol 2021-12-03 15:24:56.527654330 -0500
|
||||
+++ token/ERC20/extensions/ERC20Votes.sol 2021-12-09 14:46:33.927637196 -0500
|
||||
@@ -84,7 +84,7 @@
|
||||
*
|
||||
* - `blockNumber` must have been already mined
|
||||
*/
|
||||
- function getPastVotes(address account, uint256 blockNumber) public view returns (uint256) {
|
||||
+ function getPastVotes(address account, uint256 blockNumber) public view virtual returns (uint256) {
|
||||
require(blockNumber < block.number, "ERC20Votes: block not yet mined");
|
||||
return _checkpointsLookup(_checkpoints[account], blockNumber);
|
||||
}
|
@ -0,0 +1,28 @@ |
||||
import "../munged/token/ERC20/extensions/ERC20Votes.sol"; |
||||
|
||||
contract ERC20VotesHarness is ERC20Votes { |
||||
constructor(string memory name, string memory symbol) ERC20Permit(name) ERC20(name, symbol) {} |
||||
|
||||
mapping(address => mapping(uint256 => uint256)) public _getPastVotes; |
||||
|
||||
function _afterTokenTransfer( |
||||
address from, |
||||
address to, |
||||
uint256 amount |
||||
) internal virtual override { |
||||
super._afterTokenTransfer(from, to, amount); |
||||
_getPastVotes[from][block.number] -= amount; |
||||
_getPastVotes[to][block.number] += amount; |
||||
} |
||||
|
||||
/** |
||||
* @dev Change delegation for `delegator` to `delegatee`. |
||||
* |
||||
* Emits events {DelegateChanged} and {DelegateVotesChanged}. |
||||
*/ |
||||
function _delegate(address delegator, address delegatee) internal virtual override{ |
||||
super._delegate(delegator, delegatee); |
||||
_getPastVotes[delegator][block.number] -= balanceOf(delegator); |
||||
_getPastVotes[delegatee][block.number] += balanceOf(delegator); |
||||
} |
||||
} |
@ -0,0 +1,150 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
pragma solidity ^0.8.2; |
||||
|
||||
import "../munged/governance/Governor.sol"; |
||||
import "../munged/governance/extensions/GovernorCountingSimple.sol"; |
||||
import "../munged/governance/extensions/GovernorVotes.sol"; |
||||
import "../munged/governance/extensions/GovernorVotesQuorumFraction.sol"; |
||||
import "../munged/governance/extensions/GovernorTimelockControl.sol"; |
||||
import "../munged/governance/extensions/GovernorProposalThreshold.sol"; |
||||
|
||||
/* |
||||
Wizard options: |
||||
ProposalThreshhold = 10 |
||||
ERC20Votes |
||||
TimelockController |
||||
*/ |
||||
|
||||
contract WizardControlFirstPriority is Governor, GovernorProposalThreshold, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl { |
||||
constructor(ERC20Votes _token, TimelockController _timelock, string memory name, uint256 quorumFraction) |
||||
Governor(name) |
||||
GovernorVotes(_token) |
||||
GovernorVotesQuorumFraction(quorumFraction) |
||||
GovernorTimelockControl(_timelock) |
||||
{} |
||||
|
||||
//HARNESS |
||||
|
||||
function isExecuted(uint256 proposalId) public view returns (bool) { |
||||
return _proposals[proposalId].executed; |
||||
} |
||||
|
||||
function isCanceled(uint256 proposalId) public view returns (bool) { |
||||
return _proposals[proposalId].canceled; |
||||
} |
||||
|
||||
uint256 _votingDelay; |
||||
|
||||
uint256 _votingPeriod; |
||||
|
||||
uint256 _proposalThreshold; |
||||
|
||||
mapping(uint256 => uint256) public ghost_sum_vote_power_by_id; |
||||
|
||||
function _castVote( |
||||
uint256 proposalId, |
||||
address account, |
||||
uint8 support, |
||||
string memory reason |
||||
) internal override virtual returns (uint256) { |
||||
|
||||
uint256 deltaWeight = super._castVote(proposalId, account, support, reason); //HARNESS |
||||
ghost_sum_vote_power_by_id[proposalId] += deltaWeight; |
||||
|
||||
return deltaWeight; |
||||
} |
||||
|
||||
function snapshot(uint256 proposalId) public view returns (uint64) { |
||||
return _proposals[proposalId].voteStart._deadline; |
||||
} |
||||
|
||||
|
||||
function getExecutor() public view returns (address){ |
||||
return _executor(); |
||||
} |
||||
|
||||
// original code, harnessed |
||||
|
||||
function votingDelay() public view override returns (uint256) { // HARNESS: pure -> view |
||||
return _votingDelay; // HARNESS: parametric |
||||
} |
||||
|
||||
function votingPeriod() public view override returns (uint256) { // HARNESS: pure -> view |
||||
return _votingPeriod; // HARNESS: parametric |
||||
} |
||||
|
||||
function proposalThreshold() public view override returns (uint256) { // HARNESS: pure -> view |
||||
return _proposalThreshold; // HARNESS: parametric |
||||
} |
||||
|
||||
// original code, not harnessed |
||||
// The following functions are overrides required by Solidity. |
||||
|
||||
function quorum(uint256 blockNumber) |
||||
public |
||||
view |
||||
override(IGovernor, GovernorVotesQuorumFraction) |
||||
returns (uint256) |
||||
{ |
||||
return super.quorum(blockNumber); |
||||
} |
||||
|
||||
function getVotes(address account, uint256 blockNumber) |
||||
public |
||||
view |
||||
override(IGovernor, GovernorVotes) |
||||
returns (uint256) |
||||
{ |
||||
return super.getVotes(account, 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, GovernorProposalThreshold, 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); |
||||
} |
||||
} |
@ -0,0 +1,141 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
pragma solidity ^0.8.2; |
||||
|
||||
import "../munged/governance/Governor.sol"; |
||||
import "../munged/governance/extensions/GovernorCountingSimple.sol"; |
||||
import "../munged/governance/extensions/GovernorVotes.sol"; |
||||
import "../munged/governance/extensions/GovernorVotesQuorumFraction.sol"; |
||||
import "../munged/governance/extensions/GovernorTimelockCompound.sol"; |
||||
|
||||
/* |
||||
Wizard options: |
||||
ERC20Votes |
||||
TimelockCompound |
||||
*/ |
||||
|
||||
contract WizardFirstTry is Governor, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockCompound { |
||||
constructor(ERC20Votes _token, ICompoundTimelock _timelock, string memory name, uint256 quorumFraction) |
||||
Governor(name) |
||||
GovernorVotes(_token) |
||||
GovernorVotesQuorumFraction(quorumFraction) |
||||
GovernorTimelockCompound(_timelock) |
||||
{} |
||||
|
||||
//HARNESS |
||||
|
||||
function isExecuted(uint256 proposalId) public view returns (bool) { |
||||
return _proposals[proposalId].executed; |
||||
} |
||||
|
||||
function isCanceled(uint256 proposalId) public view returns (bool) { |
||||
return _proposals[proposalId].canceled; |
||||
} |
||||
|
||||
function snapshot(uint256 proposalId) public view returns (uint64) { |
||||
return _proposals[proposalId].voteStart._deadline; |
||||
} |
||||
|
||||
function getExecutor() public view returns (address){ |
||||
return _executor(); |
||||
} |
||||
|
||||
uint256 _votingDelay; |
||||
|
||||
uint256 _votingPeriod; |
||||
|
||||
mapping(uint256 => uint256) public ghost_sum_vote_power_by_id; |
||||
|
||||
function _castVote( |
||||
uint256 proposalId, |
||||
address account, |
||||
uint8 support, |
||||
string memory reason |
||||
) internal override virtual returns (uint256) { |
||||
|
||||
uint256 deltaWeight = super._castVote(proposalId, account, support, reason); //HARNESS |
||||
ghost_sum_vote_power_by_id[proposalId] += deltaWeight; |
||||
|
||||
return deltaWeight; |
||||
} |
||||
|
||||
// original code, harnessed |
||||
|
||||
function votingDelay() public view override virtual returns (uint256) { // HARNESS: pure -> view |
||||
return _votingDelay; // HARNESS: parametric |
||||
} |
||||
|
||||
function votingPeriod() public view override virtual returns (uint256) { // HARNESS: pure -> view |
||||
return _votingPeriod; // HARNESS: parametric |
||||
} |
||||
|
||||
// original code, not harnessed |
||||
// The following functions are overrides required by Solidity. |
||||
|
||||
function quorum(uint256 blockNumber) |
||||
public |
||||
view |
||||
override(IGovernor, GovernorVotesQuorumFraction) |
||||
returns (uint256) |
||||
{ |
||||
return super.quorum(blockNumber); |
||||
} |
||||
|
||||
function getVotes(address account, uint256 blockNumber) |
||||
public |
||||
view |
||||
override(IGovernor, GovernorVotes) |
||||
returns (uint256) |
||||
{ |
||||
return super.getVotes(account, blockNumber); |
||||
} |
||||
|
||||
function state(uint256 proposalId) |
||||
public |
||||
view |
||||
override(Governor, GovernorTimelockCompound) |
||||
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, GovernorTimelockCompound) |
||||
{ |
||||
super._execute(proposalId, targets, values, calldatas, descriptionHash); |
||||
} |
||||
|
||||
function _cancel(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash) |
||||
internal |
||||
override(Governor, GovernorTimelockCompound) |
||||
returns (uint256) |
||||
{ |
||||
return super._cancel(targets, values, calldatas, descriptionHash); |
||||
} |
||||
|
||||
function _executor() |
||||
internal |
||||
view |
||||
override(Governor, GovernorTimelockCompound) |
||||
returns (address) |
||||
{ |
||||
return super._executor(); |
||||
} |
||||
|
||||
function supportsInterface(bytes4 interfaceId) |
||||
public |
||||
view |
||||
override(Governor, GovernorTimelockCompound) |
||||
returns (bool) |
||||
{ |
||||
return super.supportsInterface(interfaceId); |
||||
} |
||||
} |
@ -0,0 +1,2 @@ |
||||
* |
||||
!.gitignore |
@ -0,0 +1,10 @@ |
||||
make -C certora munged |
||||
|
||||
certoraRun certora/harnesses/ERC20VotesHarness.sol certora/harnesses/GovernorHarness.sol \ |
||||
--verify GovernorHarness:certora/specs/GovernorBase.spec \ |
||||
--solc solc8.0 \ |
||||
--staging shelly/forSasha \ |
||||
--optimistic_loop \ |
||||
--settings -copyLoopUnroll=4 \ |
||||
--rule voteStartBeforeVoteEnd \ |
||||
--msg "$1" |
@ -0,0 +1,10 @@ |
||||
make -C certora munged |
||||
|
||||
certoraRun certora/harnesses/ERC20VotesHarness.sol certora/harnesses/GovernorBasicHarness.sol \ |
||||
--verify GovernorBasicHarness:certora/specs/GovernorCountingSimple.spec \ |
||||
--solc solc8.2 \ |
||||
--staging shelly/forSasha \ |
||||
--optimistic_loop \ |
||||
--settings -copyLoopUnroll=4 \ |
||||
--rule hasVotedCorrelation \ |
||||
--msg "$1" |
@ -0,0 +1,12 @@ |
||||
make -C certora munged |
||||
|
||||
certoraRun certora/harnesses/ERC20VotesHarness.sol certora/harnesses/WizardControlFirstPriority.sol \ |
||||
--link WizardControlFirstPriority:token=ERC20VotesHarness \ |
||||
--verify WizardControlFirstPriority:certora/specs/GovernorBase.spec \ |
||||
--solc solc8.2 \ |
||||
--disableLocalTypeChecking \ |
||||
--staging shelly/forSasha \ |
||||
--optimistic_loop \ |
||||
--settings -copyLoopUnroll=4 \ |
||||
--rule canVoteDuringVotingPeriod \ |
||||
--msg "$1" |
@ -0,0 +1,10 @@ |
||||
make -C certora munged |
||||
|
||||
certoraRun certora/harnesses/ERC20VotesHarness.sol certora/harnesses/WizardFirstTry.sol \ |
||||
--verify WizardFirstTry:certora/specs/GovernorBase.spec \ |
||||
--solc solc8.2 \ |
||||
--staging shelly/forSasha \ |
||||
--optimistic_loop \ |
||||
--disableLocalTypeChecking \ |
||||
--settings -copyLoopUnroll=4 \ |
||||
--msg "$1" |
@ -0,0 +1,14 @@ |
||||
make -C certora munged |
||||
|
||||
for f in certora/harnesses/Wizard*.sol |
||||
do |
||||
echo "Processing $f" |
||||
file=$(basename $f) |
||||
echo ${file%.*} |
||||
certoraRun certora/harnesses/$file \ |
||||
--verify ${file%.*}:certora/specs/sanity.spec "$@" \ |
||||
--solc solc8.2 --staging shelly/forSasha \ |
||||
--optimistic_loop \ |
||||
--msg "checking sanity on ${file%.*}" |
||||
--settings -copyLoopUnroll=4 |
||||
done |
@ -0,0 +1,39 @@ |
||||
#!/bin/bash |
||||
|
||||
make -C certora munged |
||||
|
||||
for contract in certora/harnesses/Wizard*.sol; |
||||
do |
||||
for spec in certora/specs/*.spec; |
||||
do |
||||
contractFile=$(basename $contract) |
||||
specFile=$(basename $spec) |
||||
if [[ "${specFile%.*}" != "RulesInProgress" ]]; |
||||
then |
||||
echo "Processing ${contractFile%.*} with $specFile" |
||||
if [[ "${contractFile%.*}" = *"WizardControl"* ]]; |
||||
then |
||||
certoraRun certora/harnesses/ERC20VotesHarness.sol certora/harnesses/$contractFile \ |
||||
--link ${contractFile%.*}:token=ERC20VotesHarness \ |
||||
--verify ${contractFile%.*}:certora/specs/$specFile "$@" \ |
||||
--solc solc8.2 \ |
||||
--staging shelly/forSasha \ |
||||
--disableLocalTypeChecking \ |
||||
--optimistic_loop \ |
||||
--settings -copyLoopUnroll=4 \ |
||||
--send_only \ |
||||
--msg "checking $specFile on ${contractFile%.*}" |
||||
else |
||||
certoraRun certora/harnesses/ERC20VotesHarness.sol certora/harnesses/$contractFile \ |
||||
--verify ${contractFile%.*}:certora/specs/$specFile "$@" \ |
||||
--solc solc8.2 \ |
||||
--staging shelly/forSasha \ |
||||
--disableLocalTypeChecking \ |
||||
--optimistic_loop \ |
||||
--settings -copyLoopUnroll=4 \ |
||||
--send_only \ |
||||
--msg "checking $specFile on ${contractFile%.*}" |
||||
fi |
||||
fi |
||||
done |
||||
done |
@ -0,0 +1,334 @@ |
||||
////////////////////////////////////////////////////////////////////////////// |
||||
///////////////////// Governor.sol base definitions ////////////////////////// |
||||
////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
using ERC20VotesHarness as erc20votes |
||||
|
||||
methods { |
||||
proposalSnapshot(uint256) returns uint256 envfree // matches proposalVoteStart |
||||
proposalDeadline(uint256) returns uint256 envfree // matches proposalVoteEnd |
||||
hashProposal(address[],uint256[],bytes[],bytes32) returns uint256 envfree |
||||
isExecuted(uint256) returns bool envfree |
||||
isCanceled(uint256) returns bool envfree |
||||
execute(address[], uint256[], bytes[], bytes32) returns uint256 |
||||
hasVoted(uint256, address) returns bool |
||||
castVote(uint256, uint8) returns uint256 |
||||
updateQuorumNumerator(uint256) |
||||
queue(address[], uint256[], bytes[], bytes32) returns uint256 |
||||
|
||||
// internal functions made public in harness: |
||||
_quorumReached(uint256) returns bool |
||||
_voteSucceeded(uint256) returns bool envfree |
||||
|
||||
// function summarization |
||||
proposalThreshold() returns uint256 envfree |
||||
|
||||
getVotes(address, uint256) returns uint256 => DISPATCHER(true) |
||||
|
||||
getPastTotalSupply(uint256 t) returns uint256 => PER_CALLEE_CONSTANT |
||||
getPastVotes(address a, uint256 t) returns uint256 => PER_CALLEE_CONSTANT |
||||
|
||||
//scheduleBatch(address[],uint256[],bytes[],bytes32,bytes32,uint256) => DISPATCHER(true) |
||||
//executeBatch(address[], uint256[], bytes[], bytes32, bytes32) => DISPATCHER(true) |
||||
} |
||||
|
||||
////////////////////////////////////////////////////////////////////////////// |
||||
//////////////////////////////// Definitions ///////////////////////////////// |
||||
////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
|
||||
// proposal was created - relation proved in noStartBeforeCreation |
||||
definition proposalCreated(uint256 pId) returns bool = proposalSnapshot(pId) > 0; |
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////// |
||||
///////////////////////////// Helper Functions /////////////////////////////// |
||||
////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
function helperFunctionsWithRevert(uint256 proposalId, method f, env e) { |
||||
address[] targets; uint256[] values; bytes[] calldatas; string reason; bytes32 descriptionHash; |
||||
uint8 support; uint8 v; bytes32 r; bytes32 s; |
||||
if (f.selector == propose(address[], uint256[], bytes[], string).selector) { |
||||
uint256 result = propose@withrevert(e, targets, values, calldatas, reason); |
||||
require(result == proposalId); |
||||
} else if (f.selector == execute(address[], uint256[], bytes[], bytes32).selector) { |
||||
uint256 result = execute@withrevert(e, targets, values, calldatas, descriptionHash); |
||||
require(result == proposalId); |
||||
} else if (f.selector == castVote(uint256, uint8).selector) { |
||||
castVote@withrevert(e, proposalId, support); |
||||
} else if (f.selector == castVoteWithReason(uint256, uint8, string).selector) { |
||||
castVoteWithReason@withrevert(e, proposalId, support, reason); |
||||
} else if (f.selector == castVoteBySig(uint256, uint8,uint8, bytes32, bytes32).selector) { |
||||
castVoteBySig@withrevert(e, proposalId, support, v, r, s); |
||||
} else if (f.selector == queue(address[], uint256[], bytes[], bytes32).selector) { |
||||
queue@withrevert(e, targets, values, calldatas, descriptionHash); |
||||
} else { |
||||
calldataarg args; |
||||
f@withrevert(e, args); |
||||
} |
||||
} |
||||
|
||||
/* |
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||||
///////////////////////////////////////////////////// State Diagram ////////////////////////////////////////////////////////// |
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||||
// // |
||||
// castVote(s)() // |
||||
// ------------- propose() ---------------------- time pass --------------- time passes ----------- // |
||||
// | No Proposal | --------> | Before Start (Delay) | --------> | Voting Period | ----------------------> | execute() | // |
||||
// ------------- ---------------------- --------------- -> Executed/Canceled ----------- // |
||||
// ------------------------------------------------------------|---------------|-------------------------|--------------> // |
||||
// t start end timelock // |
||||
// // |
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||||
*/ |
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////// |
||||
///////////////////////////////// Global Valid States ///////////////////////////////// |
||||
/////////////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
|
||||
/* |
||||
* Start and end date 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) |
||||
*/ |
||||
// To use env with general preserved block disable type checking [--disableLocalTypeChecking] |
||||
invariant startAndEndDatesNonZero(uint256 pId) |
||||
proposalSnapshot(pId) != 0 <=> proposalDeadline(pId) != 0 |
||||
{ preserved with (env e){ |
||||
require e.block.number > 0; |
||||
}} |
||||
|
||||
|
||||
/* |
||||
* If a proposal is canceled it must have a start and an end date |
||||
*/ |
||||
// To use env with general preserved block disable type checking [--disableLocalTypeChecking] |
||||
invariant canceledImplyStartAndEndDateNonZero(uint pId) |
||||
isCanceled(pId) => proposalSnapshot(pId) != 0 |
||||
{preserved with (env e){ |
||||
require e.block.number > 0; |
||||
}} |
||||
|
||||
|
||||
/* |
||||
* If a proposal is executed it must have a start and an end date |
||||
*/ |
||||
// To use env with general preserved block disable type checking [--disableLocalTypeChecking] |
||||
invariant executedImplyStartAndEndDateNonZero(uint pId) |
||||
isExecuted(pId) => proposalSnapshot(pId) != 0 |
||||
{ preserved with (env e){ |
||||
requireInvariant startAndEndDatesNonZero(pId); |
||||
require e.block.number > 0; |
||||
}} |
||||
|
||||
|
||||
/* |
||||
* A proposal starting block number must be less or equal than the proposal end date |
||||
*/ |
||||
invariant voteStartBeforeVoteEnd(uint256 pId) |
||||
// from < to <= because snapshot and deadline can be the same block number if delays are set to 0 |
||||
// This is possible before the integration of GovernorSettings.sol to the system. |
||||
// After integration of GovernorSettings.sol the invariant expression should be changed from <= to < |
||||
(proposalSnapshot(pId) > 0 => proposalSnapshot(pId) <= proposalDeadline(pId)) |
||||
// (proposalSnapshot(pId) > 0 => proposalSnapshot(pId) <= proposalDeadline(pId)) |
||||
{ preserved { |
||||
requireInvariant startAndEndDatesNonZero(pId); |
||||
}} |
||||
|
||||
|
||||
/* |
||||
* A proposal cannot be both executed and canceled simultaneously. |
||||
*/ |
||||
invariant noBothExecutedAndCanceled(uint256 pId) |
||||
!isExecuted(pId) || !isCanceled(pId) |
||||
|
||||
|
||||
/* |
||||
* A proposal could be executed only if quorum was reached and vote succeeded |
||||
*/ |
||||
rule executionOnlyIfQuoromReachedAndVoteSucceeded(uint256 pId, env e, method f){ |
||||
bool isExecutedBefore = isExecuted(pId); |
||||
bool quorumReachedBefore = _quorumReached(e, pId); |
||||
bool voteSucceededBefore = _voteSucceeded(pId); |
||||
|
||||
calldataarg args; |
||||
f(e, args); |
||||
|
||||
bool isExecutedAfter = isExecuted(pId); |
||||
assert (!isExecutedBefore && isExecutedAfter) => (quorumReachedBefore && voteSucceededBefore), "quorum was changed"; |
||||
} |
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////// |
||||
////////////////////////////////// In-State Rules ///////////////////////////////////// |
||||
/////////////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
//========================================== |
||||
//------------- Voting Period -------------- |
||||
//========================================== |
||||
|
||||
/* |
||||
* A user cannot vote twice |
||||
*/ |
||||
// Checked for castVote only. all 3 castVote functions call _castVote, so the completness of the verification is counted on |
||||
// the fact that the 3 functions themselves makes no chages, but rather call an internal function to execute. |
||||
// That means that we do not check those 3 functions directly, however for castVote & castVoteWithReason it is quite trivial |
||||
// to understand why this is ok. For castVoteBySig we basically assume that the signature referendum is correct without checking it. |
||||
// We could check each function seperately and pass the rule, but that would have uglyfied the code with no concrete |
||||
// benefit, as it is evident that nothing is happening in the first 2 functions (calling a view function), and we do not desire to check the signature verification. |
||||
rule doubleVoting(uint256 pId, uint8 sup, method f) { |
||||
env e; |
||||
address user = e.msg.sender; |
||||
bool votedCheck = hasVoted(e, pId, user); |
||||
|
||||
castVote@withrevert(e, pId, sup); |
||||
|
||||
assert votedCheck => lastReverted, "double voting accured"; |
||||
} |
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////// |
||||
//////////////////////////// State Transitions Rules ////////////////////////////////// |
||||
/////////////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
//=========================================== |
||||
//-------- Propose() --> End of Time -------- |
||||
//=========================================== |
||||
|
||||
|
||||
/* |
||||
* Once a proposal is created, voteStart and voteEnd are immutable |
||||
*/ |
||||
rule immutableFieldsAfterProposalCreation(uint256 pId, method f) { |
||||
uint256 _voteStart = proposalSnapshot(pId); |
||||
uint256 _voteEnd = proposalDeadline(pId); |
||||
|
||||
require proposalCreated(pId); // startDate > 0 |
||||
|
||||
env e; calldataarg arg; |
||||
f(e, arg); |
||||
|
||||
uint256 voteStart_ = proposalSnapshot(pId); |
||||
uint256 voteEnd_ = proposalDeadline(pId); |
||||
assert _voteStart == voteStart_, "Start date was changed"; |
||||
assert _voteEnd == voteEnd_, "End date was changed"; |
||||
} |
||||
|
||||
|
||||
/* |
||||
* Voting cannot start at a block number prior to proposal’s creation block number |
||||
*/ |
||||
rule noStartBeforeCreation(uint256 pId) { |
||||
uint256 previousStart = proposalSnapshot(pId); |
||||
// This line makes sure that we see only cases where start date is changed from 0, i.e. creation of proposal |
||||
// We proved in immutableFieldsAfterProposalCreation that once dates set for proposal, it cannot be changed |
||||
require !proposalCreated(pId); // previousStart == 0; |
||||
|
||||
env e; calldataarg args; |
||||
propose(e, args); |
||||
|
||||
uint256 newStart = proposalSnapshot(pId); |
||||
// if created, start is after current block number (creation block) |
||||
assert(newStart != previousStart => newStart >= e.block.number); |
||||
} |
||||
|
||||
|
||||
//============================================ |
||||
//--- End of Voting Period --> End of Time --- |
||||
//============================================ |
||||
|
||||
|
||||
/* |
||||
* A proposal can neither be executed nor canceled before it ends |
||||
*/ |
||||
// By induction it cannot be executed nor canceled before it starts, due to voteStartBeforeVoteEnd |
||||
rule noExecuteOrCancelBeforeDeadline(uint256 pId, method f){ |
||||
require !isExecuted(pId) && !isCanceled(pId); |
||||
|
||||
env e; calldataarg args; |
||||
f(e, args); |
||||
|
||||
assert e.block.number < proposalDeadline(pId) => (!isExecuted(pId) && !isCanceled(pId)), "executed/cancelled before deadline"; |
||||
} |
||||
|
||||
//////////////////////////////////////////////////////////////////////////////// |
||||
////////////////////// Integrity Of Functions (Unit Tests) ///////////////////// |
||||
//////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////// |
||||
////////////////////////////// High Level Rules //////////////////////////////// |
||||
//////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////// |
||||
///////////////////////////// Not Categorized Yet ////////////////////////////// |
||||
//////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
|
||||
/* |
||||
* All proposal specific (non-view) functions should revert if proposal is executed |
||||
*/ |
||||
// In this rule we show that if a function is executed, i.e. execute() was called on the proposal ID, |
||||
// non of the proposal specific functions can make changes again. In executedOnlyAfterExecuteFunc |
||||
// we connected the executed attribute to the execute() function, showing that only execute() can |
||||
// change it, and that it will always change it. |
||||
rule allFunctionsRevertIfExecuted(method f) filtered { f -> |
||||
!f.isView && !f.isFallback |
||||
&& f.selector != updateTimelock(address).selector |
||||
&& f.selector != updateQuorumNumerator(uint256).selector |
||||
&& f.selector != queue(address[],uint256[],bytes[],bytes32).selector |
||||
&& f.selector != relay(address,uint256,bytes).selector |
||||
&& f.selector != 0xb9a61961 // __acceptAdmin() |
||||
} { |
||||
env e; calldataarg args; |
||||
uint256 pId; |
||||
require(isExecuted(pId)); |
||||
requireInvariant noBothExecutedAndCanceled(pId); |
||||
requireInvariant executedImplyStartAndEndDateNonZero(pId); |
||||
|
||||
helperFunctionsWithRevert(pId, f, e); |
||||
|
||||
assert(lastReverted, "Function was not reverted"); |
||||
} |
||||
|
||||
/* |
||||
* All proposal specific (non-view) functions should revert if proposal is canceled |
||||
*/ |
||||
rule allFunctionsRevertIfCanceled(method f) filtered { |
||||
f -> !f.isView && !f.isFallback |
||||
&& f.selector != updateTimelock(address).selector |
||||
&& f.selector != updateQuorumNumerator(uint256).selector |
||||
&& f.selector != queue(address[],uint256[],bytes[],bytes32).selector |
||||
&& f.selector != relay(address,uint256,bytes).selector |
||||
&& f.selector != 0xb9a61961 // __acceptAdmin() |
||||
} { |
||||
env e; calldataarg args; |
||||
uint256 pId; |
||||
require(isCanceled(pId)); |
||||
requireInvariant noBothExecutedAndCanceled(pId); |
||||
requireInvariant canceledImplyStartAndEndDateNonZero(pId); |
||||
|
||||
helperFunctionsWithRevert(pId, f, e); |
||||
|
||||
assert(lastReverted, "Function was not reverted"); |
||||
} |
||||
|
||||
/* |
||||
* Proposal can be switched to executed only via execute() function |
||||
*/ |
||||
rule executedOnlyAfterExecuteFunc(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash, method f) { |
||||
env e; calldataarg args; |
||||
uint256 pId; |
||||
bool executedBefore = isExecuted(pId); |
||||
require(!executedBefore); |
||||
|
||||
helperFunctionsWithRevert(pId, f, e); |
||||
|
||||
bool executedAfter = isExecuted(pId); |
||||
assert(executedAfter != executedBefore => f.selector == execute(address[], uint256[], bytes[], bytes32).selector, "isExecuted only changes in the execute method"); |
||||
} |
||||
|
@ -0,0 +1,221 @@ |
||||
import "GovernorBase.spec" |
||||
|
||||
using ERC20VotesHarness as erc20votes |
||||
|
||||
methods { |
||||
ghost_sum_vote_power_by_id(uint256) returns uint256 envfree |
||||
|
||||
quorum(uint256) returns uint256 |
||||
proposalVotes(uint256) returns (uint256, uint256, uint256) envfree |
||||
|
||||
quorumNumerator() returns uint256 |
||||
_executor() returns address |
||||
|
||||
erc20votes._getPastVotes(address, uint256) returns uint256 |
||||
|
||||
getExecutor() returns address |
||||
|
||||
timelock() returns address |
||||
} |
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////// |
||||
///////////////////////////////// GHOSTS ///////////////////////////////////// |
||||
////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
|
||||
//////////// ghosts to keep track of votes counting //////////// |
||||
|
||||
/* |
||||
* the sum of voting power of those who voted |
||||
*/ |
||||
ghost sum_all_votes_power() returns uint256 { |
||||
init_state axiom sum_all_votes_power() == 0; |
||||
} |
||||
|
||||
hook Sstore ghost_sum_vote_power_by_id [KEY uint256 pId] uint256 current_power(uint256 old_power) STORAGE { |
||||
havoc sum_all_votes_power assuming sum_all_votes_power@new() == sum_all_votes_power@old() - old_power + current_power; |
||||
} |
||||
|
||||
/* |
||||
* sum of all votes casted per proposal |
||||
*/ |
||||
ghost tracked_weight(uint256) returns uint256 { |
||||
init_state axiom forall uint256 p. tracked_weight(p) == 0; |
||||
} |
||||
|
||||
/* |
||||
* sum of all votes casted |
||||
*/ |
||||
ghost sum_tracked_weight() returns uint256 { |
||||
init_state axiom sum_tracked_weight() == 0; |
||||
} |
||||
|
||||
/* |
||||
* getter for _proposalVotes.againstVotes |
||||
*/ |
||||
ghost votesAgainst() returns uint256 { |
||||
init_state axiom votesAgainst() == 0; |
||||
} |
||||
|
||||
/* |
||||
* getter for _proposalVotes.forVotes |
||||
*/ |
||||
ghost votesFor() returns uint256 { |
||||
init_state axiom votesFor() == 0; |
||||
} |
||||
|
||||
/* |
||||
* getter for _proposalVotes.abstainVotes |
||||
*/ |
||||
ghost votesAbstain() returns uint256 { |
||||
init_state axiom votesAbstain() == 0; |
||||
} |
||||
|
||||
hook Sstore _proposalVotes [KEY uint256 pId].againstVotes uint256 votes(uint256 old_votes) STORAGE { |
||||
havoc tracked_weight assuming forall uint256 p.(p == pId => tracked_weight@new(p) == tracked_weight@old(p) - old_votes + votes) && |
||||
(p != pId => tracked_weight@new(p) == tracked_weight@old(p)); |
||||
havoc sum_tracked_weight assuming sum_tracked_weight@new() == sum_tracked_weight@old() - old_votes + votes; |
||||
havoc votesAgainst assuming votesAgainst@new() == votesAgainst@old() - old_votes + votes; |
||||
} |
||||
|
||||
hook Sstore _proposalVotes [KEY uint256 pId].forVotes uint256 votes(uint256 old_votes) STORAGE { |
||||
havoc tracked_weight assuming forall uint256 p.(p == pId => tracked_weight@new(p) == tracked_weight@old(p) - old_votes + votes) && |
||||
(p != pId => tracked_weight@new(p) == tracked_weight@old(p)); |
||||
havoc sum_tracked_weight assuming sum_tracked_weight@new() == sum_tracked_weight@old() - old_votes + votes; |
||||
havoc votesFor assuming votesFor@new() == votesFor@old() - old_votes + votes; |
||||
} |
||||
|
||||
hook Sstore _proposalVotes [KEY uint256 pId].abstainVotes uint256 votes(uint256 old_votes) STORAGE { |
||||
havoc tracked_weight assuming forall uint256 p.(p == pId => tracked_weight@new(p) == tracked_weight@old(p) - old_votes + votes) && |
||||
(p != pId => tracked_weight@new(p) == tracked_weight@old(p)); |
||||
havoc sum_tracked_weight assuming sum_tracked_weight@new() == sum_tracked_weight@old() - old_votes + votes; |
||||
havoc votesAbstain assuming votesAbstain@new() == votesAbstain@old() - old_votes + votes; |
||||
} |
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////// |
||||
////////////////////////////// INVARIANTS //////////////////////////////////// |
||||
////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
|
||||
/* |
||||
* sum of all votes casted is equal to the sum of voting power of those who voted, per each proposal |
||||
*/ |
||||
invariant SumOfVotesCastEqualSumOfPowerOfVotedPerProposal(uint256 pId) |
||||
tracked_weight(pId) == ghost_sum_vote_power_by_id(pId) |
||||
|
||||
|
||||
/* |
||||
* sum of all votes casted is equal to the sum of voting power of those who voted |
||||
*/ |
||||
invariant SumOfVotesCastEqualSumOfPowerOfVoted() |
||||
sum_tracked_weight() == sum_all_votes_power() |
||||
|
||||
|
||||
/* |
||||
* sum of all votes casted is greater or equal to the sum of voting power of those who voted at a specific proposal |
||||
*/ |
||||
invariant OneIsNotMoreThanAll(uint256 pId) |
||||
sum_all_votes_power() >= tracked_weight(pId) |
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////// |
||||
///////////////////////////////// RULES ////////////////////////////////////// |
||||
////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
|
||||
/* |
||||
* Only sender's voting status can be changed by execution of any cast vote function |
||||
*/ |
||||
// Checked for castVote only. all 3 castVote functions call _castVote, so the completness of the verification is counted on |
||||
// the fact that the 3 functions themselves makes no chages, but rather call an internal function to execute. |
||||
// That means that we do not check those 3 functions directly, however for castVote & castVoteWithReason it is quite trivial |
||||
// to understand why this is ok. For castVoteBySig we basically assume that the signature referendum is correct without checking it. |
||||
// We could check each function seperately and pass the rule, but that would have uglyfied the code with no concrete |
||||
// benefit, as it is evident that nothing is happening in the first 2 functions (calling a view function), and we do not desire to check the signature verification. |
||||
rule noVoteForSomeoneElse(uint256 pId, uint8 sup, method f) { |
||||
env e; calldataarg args; |
||||
|
||||
address voter = e.msg.sender; |
||||
address user; |
||||
|
||||
bool hasVotedBefore_User = hasVoted(e, pId, user); |
||||
|
||||
castVote@withrevert(e, pId, sup); |
||||
require(!lastReverted); |
||||
|
||||
bool hasVotedAfter_User = hasVoted(e, pId, user); |
||||
|
||||
assert user != voter => hasVotedBefore_User == hasVotedAfter_User; |
||||
} |
||||
|
||||
|
||||
/* |
||||
* Total voting tally is monotonically non-decreasing in every operation |
||||
*/ |
||||
rule votingWeightMonotonicity(method f){ |
||||
uint256 votingWeightBefore = sum_tracked_weight(); |
||||
|
||||
env e; |
||||
calldataarg args; |
||||
f(e, args); |
||||
|
||||
uint256 votingWeightAfter = sum_tracked_weight(); |
||||
|
||||
assert votingWeightBefore <= votingWeightAfter, "Voting weight was decreased somehow"; |
||||
} |
||||
|
||||
|
||||
/* |
||||
* A change in hasVoted must be correlated with an non-decreasing of the vote supports (nondecrease because user can vote with weight 0) |
||||
*/ |
||||
rule hasVotedCorrelation(uint256 pId, method f, env e, uint256 bn) { |
||||
address acc = e.msg.sender; |
||||
|
||||
uint256 againstBefore = votesAgainst(); |
||||
uint256 forBefore = votesFor(); |
||||
uint256 abstainBefore = votesAbstain(); |
||||
|
||||
bool hasVotedBefore = hasVoted(e, pId, acc); |
||||
|
||||
helperFunctionsWithRevert(pId, f, e); |
||||
require(!lastReverted); |
||||
|
||||
uint256 againstAfter = votesAgainst(); |
||||
uint256 forAfter = votesFor(); |
||||
uint256 abstainAfter = votesAbstain(); |
||||
|
||||
bool hasVotedAfter = hasVoted(e, pId, acc); |
||||
|
||||
assert (!hasVotedBefore && hasVotedAfter) => againstBefore <= againstAfter || forBefore <= forAfter || abstainBefore <= abstainAfter, "no correlation"; |
||||
} |
||||
|
||||
|
||||
/* |
||||
* Only privileged users can execute privileged operations, e.g. change _quorumNumerator or _timelock |
||||
*/ |
||||
rule privilegedOnlyNumerator(method f, uint256 newQuorumNumerator){ |
||||
env e; |
||||
calldataarg arg; |
||||
uint256 quorumNumBefore = quorumNumerator(e); |
||||
|
||||
f(e, arg); |
||||
|
||||
uint256 quorumNumAfter = quorumNumerator(e); |
||||
address executorCheck = getExecutor(e); |
||||
|
||||
assert quorumNumBefore != quorumNumAfter => e.msg.sender == executorCheck, "non priveleged user changed quorum numerator"; |
||||
} |
||||
|
||||
rule privilegedOnlyTimelock(method f, uint256 newQuorumNumerator){ |
||||
env e; |
||||
calldataarg arg; |
||||
uint256 timelockBefore = timelock(e); |
||||
|
||||
f(e, arg); |
||||
|
||||
uint256 timelockAfter = timelock(e); |
||||
|
||||
assert timelockBefore != timelockAfter => e.msg.sender == timelockBefore, "non priveleged user changed timelock"; |
||||
} |
@ -0,0 +1,139 @@ |
||||
////////////////////////////////////////////////////////////////////////////// |
||||
////////////// THIS SPEC IS A RESERVE FOR NOT IN PROGRESS ////////////// |
||||
////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
import "GovernorBase.spec" |
||||
|
||||
using ERC20VotesHarness as erc20votes |
||||
|
||||
methods { |
||||
ghost_sum_vote_power_by_id(uint256) returns uint256 envfree |
||||
|
||||
quorum(uint256) returns uint256 |
||||
proposalVotes(uint256) returns (uint256, uint256, uint256) envfree |
||||
|
||||
quorumNumerator() returns uint256 |
||||
_executor() returns address |
||||
|
||||
erc20votes._getPastVotes(address, uint256) returns uint256 |
||||
|
||||
getExecutor() returns address |
||||
|
||||
timelock() returns address |
||||
} |
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////// |
||||
///////////////////////////////// GHOSTS ///////////////////////////////////// |
||||
////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
|
||||
//////////// ghosts to keep track of votes counting //////////// |
||||
|
||||
/* |
||||
* the sum of voting power of those who voted |
||||
*/ |
||||
ghost sum_all_votes_power() returns uint256 { |
||||
init_state axiom sum_all_votes_power() == 0; |
||||
} |
||||
|
||||
hook Sstore ghost_sum_vote_power_by_id [KEY uint256 pId] uint256 current_power(uint256 old_power) STORAGE { |
||||
havoc sum_all_votes_power assuming sum_all_votes_power@new() == sum_all_votes_power@old() - old_power + current_power; |
||||
} |
||||
|
||||
/* |
||||
* sum of all votes casted per proposal |
||||
*/ |
||||
ghost tracked_weight(uint256) returns uint256 { |
||||
init_state axiom forall uint256 p. tracked_weight(p) == 0; |
||||
} |
||||
|
||||
/* |
||||
* sum of all votes casted |
||||
*/ |
||||
ghost sum_tracked_weight() returns uint256 { |
||||
init_state axiom sum_tracked_weight() == 0; |
||||
} |
||||
|
||||
/* |
||||
* getter for _proposalVotes.againstVotes |
||||
*/ |
||||
ghost votesAgainst() returns uint256 { |
||||
init_state axiom votesAgainst() == 0; |
||||
} |
||||
|
||||
/* |
||||
* getter for _proposalVotes.forVotes |
||||
*/ |
||||
ghost votesFor() returns uint256 { |
||||
init_state axiom votesFor() == 0; |
||||
} |
||||
|
||||
/* |
||||
* getter for _proposalVotes.abstainVotes |
||||
*/ |
||||
ghost votesAbstain() returns uint256 { |
||||
init_state axiom votesAbstain() == 0; |
||||
} |
||||
|
||||
hook Sstore _proposalVotes [KEY uint256 pId].againstVotes uint256 votes(uint256 old_votes) STORAGE { |
||||
havoc tracked_weight assuming forall uint256 p.(p == pId => tracked_weight@new(p) == tracked_weight@old(p) - old_votes + votes) && |
||||
(p != pId => tracked_weight@new(p) == tracked_weight@old(p)); |
||||
havoc sum_tracked_weight assuming sum_tracked_weight@new() == sum_tracked_weight@old() - old_votes + votes; |
||||
havoc votesAgainst assuming votesAgainst@new() == votesAgainst@old() - old_votes + votes; |
||||
} |
||||
|
||||
hook Sstore _proposalVotes [KEY uint256 pId].forVotes uint256 votes(uint256 old_votes) STORAGE { |
||||
havoc tracked_weight assuming forall uint256 p.(p == pId => tracked_weight@new(p) == tracked_weight@old(p) - old_votes + votes) && |
||||
(p != pId => tracked_weight@new(p) == tracked_weight@old(p)); |
||||
havoc sum_tracked_weight assuming sum_tracked_weight@new() == sum_tracked_weight@old() - old_votes + votes; |
||||
havoc votesFor assuming votesFor@new() == votesFor@old() - old_votes + votes; |
||||
} |
||||
|
||||
hook Sstore _proposalVotes [KEY uint256 pId].abstainVotes uint256 votes(uint256 old_votes) STORAGE { |
||||
havoc tracked_weight assuming forall uint256 p.(p == pId => tracked_weight@new(p) == tracked_weight@old(p) - old_votes + votes) && |
||||
(p != pId => tracked_weight@new(p) == tracked_weight@old(p)); |
||||
havoc sum_tracked_weight assuming sum_tracked_weight@new() == sum_tracked_weight@old() - old_votes + votes; |
||||
havoc votesAbstain assuming votesAbstain@new() == votesAbstain@old() - old_votes + votes; |
||||
} |
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////// |
||||
////////////////////////////// INVARIANTS //////////////////////////////////// |
||||
////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////// |
||||
///////////////////////////////// RULES ////////////////////////////////////// |
||||
////////////////////////////////////////////////////////////////////////////// |
||||
|
||||
|
||||
//NOT FINISHED |
||||
/* |
||||
* the sum of voting power of those who voted is less or equal to the maximum possible votes, per each proposal |
||||
*/ |
||||
rule possibleTotalVotes(uint256 pId, uint8 sup, env e, method f) { |
||||
|
||||
// add requireinvariant for all i, j. i = i - 1 && i < j => checkpointlookup[i] < checkpointlookup[j]; |
||||
require tracked_weight(pId) <= erc20votes.getPastTotalSupply(e, proposalSnapshot(pId)); |
||||
|
||||
uint256 againstB; |
||||
uint256 forB; |
||||
uint256 absatinB; |
||||
againstB, forB, absatinB = proposalVotes(pId); |
||||
|
||||
calldataarg args; |
||||
//f(e, args); |
||||
|
||||
castVote(e, pId, sup); |
||||
|
||||
uint256 against; |
||||
uint256 for; |
||||
uint256 absatin; |
||||
against, for, absatin = proposalVotes(pId); |
||||
|
||||
uint256 ps = proposalSnapshot(pId); |
||||
|
||||
assert tracked_weight(pId) <= erc20votes.getPastTotalSupply(e, proposalSnapshot(pId)), "bla bla bla"; |
||||
} |
@ -0,0 +1,14 @@ |
||||
/* |
||||
This rule looks for a non-reverting execution path to each method, including those overridden in the harness. |
||||
A method has such an execution path if it violates this rule. |
||||
How it works: |
||||
- If there is a non-reverting execution path, we reach the false assertion, and the sanity fails. |
||||
- If all execution paths are reverting, we never call the assertion, and the method will pass this rule vacuously. |
||||
*/ |
||||
|
||||
rule sanity(method f) { |
||||
env e; |
||||
calldataarg arg; |
||||
f(e, arg); |
||||
assert false; |
||||
} |
@ -0,0 +1,61 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
// OpenZeppelin Contracts (last updated v4.5.0) (governance/utils/IVotes.sol) |
||||
pragma solidity ^0.8.0; |
||||
|
||||
/** |
||||
* @dev Common interface for {ERC20Votes}, {ERC721Votes}, and other {Votes}-enabled contracts. |
||||
* |
||||
* _Available since v4.5._ |
||||
*/ |
||||
interface IVotes { |
||||
/** |
||||
* @dev Emitted when an account changes their delegate. |
||||
*/ |
||||
event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); |
||||
|
||||
/** |
||||
* @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of votes. |
||||
*/ |
||||
event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance); |
||||
|
||||
/** |
||||
* @dev Returns the current amount of votes that `account` has. |
||||
*/ |
||||
function getVotes(address account) external view returns (uint256); |
||||
|
||||
/** |
||||
* @dev Returns the amount of votes that `account` had at the end of a past block (`blockNumber`). |
||||
*/ |
||||
function getPastVotes(address account, uint256 blockNumber) external view returns (uint256); |
||||
|
||||
/** |
||||
* @dev Returns the total supply of votes available at the end of a past block (`blockNumber`). |
||||
* |
||||
* NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. |
||||
* Votes that have not been delegated are still part of total supply, even though they would not participate in a |
||||
* vote. |
||||
*/ |
||||
function getPastTotalSupply(uint256 blockNumber) external view returns (uint256); |
||||
|
||||
/** |
||||
* @dev Returns the delegate that `account` has chosen. |
||||
*/ |
||||
function delegates(address account) external view returns (address); |
||||
|
||||
/** |
||||
* @dev Delegates votes from the sender to `delegatee`. |
||||
*/ |
||||
function delegate(address delegatee) external; |
||||
|
||||
/** |
||||
* @dev Delegates votes from signer to `delegatee`. |
||||
*/ |
||||
function delegateBySig( |
||||
address delegatee, |
||||
uint256 nonce, |
||||
uint256 expiry, |
||||
uint8 v, |
||||
bytes32 r, |
||||
bytes32 s |
||||
) external; |
||||
} |
@ -0,0 +1,211 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
// OpenZeppelin Contracts (last updated v4.5.0) (governance/utils/Votes.sol) |
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "../../utils/Context.sol"; |
||||
import "../../utils/Counters.sol"; |
||||
import "../../utils/Checkpoints.sol"; |
||||
import "../../utils/cryptography/draft-EIP712.sol"; |
||||
import "./IVotes.sol"; |
||||
|
||||
/** |
||||
* @dev This is a base abstract contract that tracks voting units, which are a measure of voting power that can be |
||||
* transferred, and provides a system of vote delegation, where an account can delegate its voting units to a sort of |
||||
* "representative" that will pool delegated voting units from different accounts and can then use it to vote in |
||||
* decisions. In fact, voting units _must_ be delegated in order to count as actual votes, and an account has to |
||||
* delegate those votes to itself if it wishes to participate in decisions and does not have a trusted representative. |
||||
* |
||||
* This contract is often combined with a token contract such that voting units correspond to token units. For an |
||||
* example, see {ERC721Votes}. |
||||
* |
||||
* The full history of delegate votes is tracked on-chain so that governance protocols can consider votes as distributed |
||||
* at a particular block number to protect against flash loans and double voting. The opt-in delegate system makes the |
||||
* cost of this history tracking optional. |
||||
* |
||||
* When using this module the derived contract must implement {_getVotingUnits} (for example, make it return |
||||
* {ERC721-balanceOf}), and can use {_transferVotingUnits} to track a change in the distribution of those units (in the |
||||
* previous example, it would be included in {ERC721-_beforeTokenTransfer}). |
||||
* |
||||
* _Available since v4.5._ |
||||
*/ |
||||
abstract contract Votes is IVotes, Context, EIP712 { |
||||
using Checkpoints for Checkpoints.History; |
||||
using Counters for Counters.Counter; |
||||
|
||||
bytes32 private constant _DELEGATION_TYPEHASH = |
||||
keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); |
||||
|
||||
mapping(address => address) private _delegation; |
||||
mapping(address => Checkpoints.History) private _delegateCheckpoints; |
||||
Checkpoints.History private _totalCheckpoints; |
||||
|
||||
mapping(address => Counters.Counter) private _nonces; |
||||
|
||||
/** |
||||
* @dev Returns the current amount of votes that `account` has. |
||||
*/ |
||||
function getVotes(address account) public view virtual override returns (uint256) { |
||||
return _delegateCheckpoints[account].latest(); |
||||
} |
||||
|
||||
/** |
||||
* @dev Returns the amount of votes that `account` had at the end of a past block (`blockNumber`). |
||||
* |
||||
* Requirements: |
||||
* |
||||
* - `blockNumber` must have been already mined |
||||
*/ |
||||
function getPastVotes(address account, uint256 blockNumber) public view virtual override returns (uint256) { |
||||
return _delegateCheckpoints[account].getAtBlock(blockNumber); |
||||
} |
||||
|
||||
/** |
||||
* @dev Returns the total supply of votes available at the end of a past block (`blockNumber`). |
||||
* |
||||
* NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. |
||||
* Votes that have not been delegated are still part of total supply, even though they would not participate in a |
||||
* vote. |
||||
* |
||||
* Requirements: |
||||
* |
||||
* - `blockNumber` must have been already mined |
||||
*/ |
||||
function getPastTotalSupply(uint256 blockNumber) public view virtual override returns (uint256) { |
||||
require(blockNumber < block.number, "Votes: block not yet mined"); |
||||
return _totalCheckpoints.getAtBlock(blockNumber); |
||||
} |
||||
|
||||
/** |
||||
* @dev Returns the current total supply of votes. |
||||
*/ |
||||
function _getTotalSupply() internal view virtual returns (uint256) { |
||||
return _totalCheckpoints.latest(); |
||||
} |
||||
|
||||
/** |
||||
* @dev Returns the delegate that `account` has chosen. |
||||
*/ |
||||
function delegates(address account) public view virtual override returns (address) { |
||||
return _delegation[account]; |
||||
} |
||||
|
||||
/** |
||||
* @dev Delegates votes from the sender to `delegatee`. |
||||
*/ |
||||
function delegate(address delegatee) public virtual override { |
||||
address account = _msgSender(); |
||||
_delegate(account, delegatee); |
||||
} |
||||
|
||||
/** |
||||
* @dev Delegates votes from signer to `delegatee`. |
||||
*/ |
||||
function delegateBySig( |
||||
address delegatee, |
||||
uint256 nonce, |
||||
uint256 expiry, |
||||
uint8 v, |
||||
bytes32 r, |
||||
bytes32 s |
||||
) public virtual override { |
||||
require(block.timestamp <= expiry, "Votes: signature expired"); |
||||
address signer = ECDSA.recover( |
||||
_hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry))), |
||||
v, |
||||
r, |
||||
s |
||||
); |
||||
require(nonce == _useNonce(signer), "Votes: invalid nonce"); |
||||
_delegate(signer, delegatee); |
||||
} |
||||
|
||||
/** |
||||
* @dev Delegate all of `account`'s voting units to `delegatee`. |
||||
* |
||||
* Emits events {DelegateChanged} and {DelegateVotesChanged}. |
||||
*/ |
||||
function _delegate(address account, address delegatee) internal virtual { |
||||
address oldDelegate = delegates(account); |
||||
_delegation[account] = delegatee; |
||||
|
||||
emit DelegateChanged(account, oldDelegate, delegatee); |
||||
_moveDelegateVotes(oldDelegate, delegatee, _getVotingUnits(account)); |
||||
} |
||||
|
||||
/** |
||||
* @dev Transfers, mints, or burns voting units. To register a mint, `from` should be zero. To register a burn, `to` |
||||
* should be zero. Total supply of voting units will be adjusted with mints and burns. |
||||
*/ |
||||
function _transferVotingUnits( |
||||
address from, |
||||
address to, |
||||
uint256 amount |
||||
) internal virtual { |
||||
if (from == address(0)) { |
||||
_totalCheckpoints.push(_add, amount); |
||||
} |
||||
if (to == address(0)) { |
||||
_totalCheckpoints.push(_subtract, amount); |
||||
} |
||||
_moveDelegateVotes(delegates(from), delegates(to), amount); |
||||
} |
||||
|
||||
/** |
||||
* @dev Moves delegated votes from one delegate to another. |
||||
*/ |
||||
function _moveDelegateVotes( |
||||
address from, |
||||
address to, |
||||
uint256 amount |
||||
) private { |
||||
if (from != to && amount > 0) { |
||||
if (from != address(0)) { |
||||
(uint256 oldValue, uint256 newValue) = _delegateCheckpoints[from].push(_subtract, amount); |
||||
emit DelegateVotesChanged(from, oldValue, newValue); |
||||
} |
||||
if (to != address(0)) { |
||||
(uint256 oldValue, uint256 newValue) = _delegateCheckpoints[to].push(_add, amount); |
||||
emit DelegateVotesChanged(to, oldValue, newValue); |
||||
} |
||||
} |
||||
} |
||||
|
||||
function _add(uint256 a, uint256 b) private pure returns (uint256) { |
||||
return a + b; |
||||
} |
||||
|
||||
function _subtract(uint256 a, uint256 b) private pure returns (uint256) { |
||||
return a - b; |
||||
} |
||||
|
||||
/** |
||||
* @dev Consumes a nonce. |
||||
* |
||||
* Returns the current value and increments nonce. |
||||
*/ |
||||
function _useNonce(address owner) internal virtual returns (uint256 current) { |
||||
Counters.Counter storage nonce = _nonces[owner]; |
||||
current = nonce.current(); |
||||
nonce.increment(); |
||||
} |
||||
|
||||
/** |
||||
* @dev Returns an address nonce. |
||||
*/ |
||||
function nonces(address owner) public view virtual returns (uint256) { |
||||
return _nonces[owner].current(); |
||||
} |
||||
|
||||
/** |
||||
* @dev Returns the contract's {EIP712} domain separator. |
||||
*/ |
||||
// solhint-disable-next-line func-name-mixedcase |
||||
function DOMAIN_SEPARATOR() external view returns (bytes32) { |
||||
return _domainSeparatorV4(); |
||||
} |
||||
|
||||
/** |
||||
* @dev Must return the voting units held by an account. |
||||
*/ |
||||
function _getVotingUnits(address) internal virtual returns (uint256); |
||||
} |
@ -0,0 +1,20 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
// OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol) |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
/** |
||||
* @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified |
||||
* proxy whose upgrades are fully controlled by the current implementation. |
||||
*/ |
||||
interface IERC1822Proxiable { |
||||
/** |
||||
* @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation |
||||
* address. |
||||
* |
||||
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks |
||||
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this |
||||
* function revert if invoked through a proxy. |
||||
*/ |
||||
function proxiableUUID() external view returns (bytes32); |
||||
} |
@ -0,0 +1,11 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "../utils/Base64.sol"; |
||||
|
||||
contract Base64Mock { |
||||
function encode(bytes memory value) external pure returns (string memory) { |
||||
return Base64.encode(value); |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "../utils/Checkpoints.sol"; |
||||
|
||||
contract CheckpointsImpl { |
||||
using Checkpoints for Checkpoints.History; |
||||
|
||||
Checkpoints.History private _totalCheckpoints; |
||||
|
||||
function latest() public view returns (uint256) { |
||||
return _totalCheckpoints.latest(); |
||||
} |
||||
|
||||
function getAtBlock(uint256 blockNumber) public view returns (uint256) { |
||||
return _totalCheckpoints.getAtBlock(blockNumber); |
||||
} |
||||
|
||||
function push(uint256 value) public returns (uint256, uint256) { |
||||
return _totalCheckpoints.push(value); |
||||
} |
||||
} |
@ -0,0 +1,58 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "../utils/structs/DoubleEndedQueue.sol"; |
||||
|
||||
// Bytes32Deque |
||||
contract Bytes32DequeMock { |
||||
using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; |
||||
|
||||
event OperationResult(bytes32 value); |
||||
|
||||
DoubleEndedQueue.Bytes32Deque private _vector; |
||||
|
||||
function pushBack(bytes32 value) public { |
||||
_vector.pushBack(value); |
||||
} |
||||
|
||||
function pushFront(bytes32 value) public { |
||||
_vector.pushFront(value); |
||||
} |
||||
|
||||
function popFront() public returns (bytes32) { |
||||
bytes32 value = _vector.popFront(); |
||||
emit OperationResult(value); |
||||
return value; |
||||
} |
||||
|
||||
function popBack() public returns (bytes32) { |
||||
bytes32 value = _vector.popBack(); |
||||
emit OperationResult(value); |
||||
return value; |
||||
} |
||||
|
||||
function front() public view returns (bytes32) { |
||||
return _vector.front(); |
||||
} |
||||
|
||||
function back() public view returns (bytes32) { |
||||
return _vector.back(); |
||||
} |
||||
|
||||
function at(uint256 i) public view returns (bytes32) { |
||||
return _vector.at(i); |
||||
} |
||||
|
||||
function clear() public { |
||||
_vector.clear(); |
||||
} |
||||
|
||||
function length() public view returns (uint256) { |
||||
return _vector.length(); |
||||
} |
||||
|
||||
function empty() public view returns (bool) { |
||||
return _vector.empty(); |
||||
} |
||||
} |
@ -0,0 +1,33 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "../token/ERC721/extensions/ERC721Royalty.sol"; |
||||
|
||||
contract ERC721RoyaltyMock is ERC721Royalty { |
||||
constructor(string memory name, string memory symbol) ERC721(name, symbol) {} |
||||
|
||||
function setTokenRoyalty( |
||||
uint256 tokenId, |
||||
address recipient, |
||||
uint96 fraction |
||||
) public { |
||||
_setTokenRoyalty(tokenId, recipient, fraction); |
||||
} |
||||
|
||||
function setDefaultRoyalty(address recipient, uint96 fraction) public { |
||||
_setDefaultRoyalty(recipient, fraction); |
||||
} |
||||
|
||||
function mint(address to, uint256 tokenId) public { |
||||
_mint(to, tokenId); |
||||
} |
||||
|
||||
function burn(uint256 tokenId) public { |
||||
_burn(tokenId); |
||||
} |
||||
|
||||
function deleteDefaultRoyalty() public { |
||||
_deleteDefaultRoyalty(); |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "../token/ERC721/extensions/draft-ERC721Votes.sol"; |
||||
|
||||
contract ERC721VotesMock is ERC721Votes { |
||||
constructor(string memory name, string memory symbol) ERC721(name, symbol) EIP712(name, "1") {} |
||||
|
||||
function getTotalSupply() public view returns (uint256) { |
||||
return _getTotalSupply(); |
||||
} |
||||
|
||||
function mint(address account, uint256 tokenId) public { |
||||
_mint(account, tokenId); |
||||
} |
||||
|
||||
function burn(uint256 tokenId) public { |
||||
_burn(tokenId); |
||||
} |
||||
|
||||
function getChainId() external view returns (uint256) { |
||||
return block.chainid; |
||||
} |
||||
} |
@ -0,0 +1,41 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "../governance/extensions/GovernorCountingSimple.sol"; |
||||
import "../governance/extensions/GovernorVotes.sol"; |
||||
|
||||
contract GovernorVoteMocks is GovernorVotes, GovernorCountingSimple { |
||||
constructor(string memory name_, IVotes token_) Governor(name_) GovernorVotes(token_) {} |
||||
|
||||
function quorum(uint256) public pure override returns (uint256) { |
||||
return 0; |
||||
} |
||||
|
||||
function votingDelay() public pure override returns (uint256) { |
||||
return 4; |
||||
} |
||||
|
||||
function votingPeriod() public pure override returns (uint256) { |
||||
return 16; |
||||
} |
||||
|
||||
function cancel( |
||||
address[] memory targets, |
||||
uint256[] memory values, |
||||
bytes[] memory calldatas, |
||||
bytes32 salt |
||||
) public returns (uint256 proposalId) { |
||||
return _cancel(targets, values, calldatas, salt); |
||||
} |
||||
|
||||
function getVotes(address account, uint256 blockNumber) |
||||
public |
||||
view |
||||
virtual |
||||
override(IGovernor, GovernorVotes) |
||||
returns (uint256) |
||||
{ |
||||
return super.getVotes(account, blockNumber); |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "../utils/math/SignedMath.sol"; |
||||
|
||||
contract SignedMathMock { |
||||
function max(int256 a, int256 b) public pure returns (int256) { |
||||
return SignedMath.max(a, b); |
||||
} |
||||
|
||||
function min(int256 a, int256 b) public pure returns (int256) { |
||||
return SignedMath.min(a, b); |
||||
} |
||||
|
||||
function average(int256 a, int256 b) public pure returns (int256) { |
||||
return SignedMath.average(a, b); |
||||
} |
||||
|
||||
function abs(int256 n) public pure returns (uint256) { |
||||
return SignedMath.abs(n); |
||||
} |
||||
} |
@ -0,0 +1,58 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "./UUPSUpgradeableMock.sol"; |
||||
|
||||
// This contract implements the pre-4.5 UUPS upgrade function with a rollback test. |
||||
// It's used to test that newer UUPS contracts are considered valid upgrades by older UUPS contracts. |
||||
contract UUPSUpgradeableLegacyMock is UUPSUpgradeableMock { |
||||
// Inlined from ERC1967Upgrade |
||||
bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; |
||||
|
||||
// ERC1967Upgrade._setImplementation is private so we reproduce it here. |
||||
// An extra underscore prevents a name clash error. |
||||
function __setImplementation(address newImplementation) private { |
||||
require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); |
||||
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; |
||||
} |
||||
|
||||
function _upgradeToAndCallSecureLegacyV1( |
||||
address newImplementation, |
||||
bytes memory data, |
||||
bool forceCall |
||||
) internal { |
||||
address oldImplementation = _getImplementation(); |
||||
|
||||
// Initial upgrade and setup call |
||||
__setImplementation(newImplementation); |
||||
if (data.length > 0 || forceCall) { |
||||
Address.functionDelegateCall(newImplementation, data); |
||||
} |
||||
|
||||
// Perform rollback test if not already in progress |
||||
StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT); |
||||
if (!rollbackTesting.value) { |
||||
// Trigger rollback using upgradeTo from the new implementation |
||||
rollbackTesting.value = true; |
||||
Address.functionDelegateCall( |
||||
newImplementation, |
||||
abi.encodeWithSignature("upgradeTo(address)", oldImplementation) |
||||
); |
||||
rollbackTesting.value = false; |
||||
// Check rollback was effective |
||||
require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades"); |
||||
// Finally reset to the new implementation and log the upgrade |
||||
_upgradeTo(newImplementation); |
||||
} |
||||
} |
||||
|
||||
// hooking into the old mechanism |
||||
function upgradeTo(address newImplementation) external virtual override { |
||||
_upgradeToAndCallSecureLegacyV1(newImplementation, bytes(""), false); |
||||
} |
||||
|
||||
function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual override { |
||||
_upgradeToAndCallSecureLegacyV1(newImplementation, data, false); |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "../governance/utils/Votes.sol"; |
||||
|
||||
contract VotesMock is Votes { |
||||
mapping(address => uint256) private _balances; |
||||
mapping(uint256 => address) private _owners; |
||||
|
||||
constructor(string memory name) EIP712(name, "1") {} |
||||
|
||||
function getTotalSupply() public view returns (uint256) { |
||||
return _getTotalSupply(); |
||||
} |
||||
|
||||
function delegate(address account, address newDelegation) public { |
||||
return _delegate(account, newDelegation); |
||||
} |
||||
|
||||
function _getVotingUnits(address account) internal virtual override returns (uint256) { |
||||
return _balances[account]; |
||||
} |
||||
|
||||
function mint(address account, uint256 voteId) external { |
||||
_balances[account] += 1; |
||||
_owners[voteId] = account; |
||||
_transferVotingUnits(address(0), account, 1); |
||||
} |
||||
|
||||
function burn(uint256 voteId) external { |
||||
address owner = _owners[voteId]; |
||||
_balances[owner] -= 1; |
||||
_transferVotingUnits(owner, address(0), 1); |
||||
} |
||||
|
||||
function getChainId() external view returns (uint256) { |
||||
return block.chainid; |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue