forked from OpenZeppelin/openzeppelin-contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Certora's Governance verification rules (OpenZeppelin#2997)
Co-authored-by: Shelly Grossman <shelly@certora.com> Co-authored-by: Aleksander Kryukov <58052996+RedLikeRosesss@users.noreply.github.com> Co-authored-by: Michael M <91594326+MichaelMorami@users.noreply.github.com> Co-authored-by: Aleksander Kryukov <firealexkryukov@gmail.com>
- Loading branch information
1 parent
a0a8bbb
commit 915ca18
Showing
18 changed files
with
1,310 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,3 +57,8 @@ allFiredEvents | |
# hardhat | ||
cache | ||
artifacts | ||
|
||
# Certora | ||
.certora* | ||
.last_confs | ||
certora_* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
Oops, something went wrong.