Compare commits
344 Commits
v5.0.2
...
formal-ver
| Author | SHA1 | Date | |
|---|---|---|---|
| 37991642dc | |||
| 1038b7f2c7 | |||
| 7dc201fce9 | |||
| bf73fb4013 | |||
| 6c4ffe783f | |||
| f21f86c3c1 | |||
| 2e7bca424a | |||
| d4e9d8d54d | |||
| baf71582b9 | |||
| ecebc5688d | |||
| 3c58e4e3d3 | |||
| 4242fdace4 | |||
| 14b40eddcb | |||
| c8c8ca39d7 | |||
| 150edce57b | |||
| 9f2d511d20 | |||
| b50d9980be | |||
| 423a808748 | |||
| 5c859049e6 | |||
| cad49af31e | |||
| e0fa84a6b7 | |||
| e04f7ded94 | |||
| 2c5194f3f1 | |||
| 07ac1030e7 | |||
| a73d7ab57b | |||
| 89962af163 | |||
| c7a544d568 | |||
| 634c37becf | |||
| d986bbc8e0 | |||
| fed8d5c856 | |||
| 597202d904 | |||
| 8f6a03204e | |||
| d916e2edf4 | |||
| 3075181276 | |||
| 8b1042371a | |||
| 38d21cab86 | |||
| bfbf4d8398 | |||
| 49333bcc24 | |||
| 3252e54f2b | |||
| cc7837fb46 | |||
| 9dd0eb5e5c | |||
| 9ce8455ae0 | |||
| dfa0505c40 | |||
| e7f770591b | |||
| b9c7899e6c | |||
| 0f20d57771 | |||
| 797ef26bb6 | |||
| 9aa745e4fe | |||
| d7c3cc758b | |||
| 3c0d926908 | |||
| 28367f8989 | |||
| 61817e055f | |||
| 94f9f7ac58 | |||
| b2ec06aaf2 | |||
| 0fa6aad5e5 | |||
| 0c2453ba17 | |||
| 0d1e919742 | |||
| 75ce9ef8e9 | |||
| 56c355ea8b | |||
| e9f53ebc02 | |||
| 3aa0a015d1 | |||
| d98d9c03f3 | |||
| 2627753bfe | |||
| 4820ed4ea8 | |||
| 4a3b0bb875 | |||
| 3e6045155e | |||
| b90f4d285e | |||
| 1701b0c7fd | |||
| 154a151518 | |||
| 0b99b54327 | |||
| 9708bc0397 | |||
| 8ec6b0f56f | |||
| 962a5023e6 | |||
| 6820ff8b9c | |||
| 65ab8e9ac4 | |||
| 9bbc7b7eb3 | |||
| e4492aed8a | |||
| 9c45c52c4b | |||
| ca034ab3df | |||
| 75a3602ba6 | |||
| 7946806fb3 | |||
| 84b371f92c | |||
| cccd90ec83 | |||
| ee2f0ecb68 | |||
| 6a4fc6acb8 | |||
| 1aa8141b14 | |||
| 866042d6fc | |||
| 234b843c36 | |||
| a373d25b01 | |||
| f4b2aff79e | |||
| 371818f792 | |||
| 3ccaf4f6d1 | |||
| ffa3daa5d9 | |||
| 990fd18c21 | |||
| 5a7cc50974 | |||
| e3341255b2 | |||
| 69d9ebfcdf | |||
| be18334b69 | |||
| 5516589b88 | |||
| 6363deaedd | |||
| b10a2b8cd3 | |||
| bdb49654c5 | |||
| bab9528dc1 | |||
| 93928e3e19 | |||
| f3f26e3ff3 | |||
| 3eb67081f3 | |||
| d02c2ccab3 | |||
| 27fa53bba9 | |||
| b90d195c6c | |||
| a6863a059c | |||
| 669a22e0ff | |||
| cab8e489b2 | |||
| 0119a187c1 | |||
| 15e847c835 | |||
| 8e283704c3 | |||
| 38495a5026 | |||
| f74e316422 | |||
| 2a73da9f67 | |||
| 018c58219f | |||
| bd3427d5ff | |||
| 10f5d8d942 | |||
| b2cdcc38d4 | |||
| ca0d3363b8 | |||
| 78263e2a9a | |||
| 4dc0ff9fe3 | |||
| 1dd3b7a307 | |||
| 657a051062 | |||
| da1cda69bf | |||
| 428197be69 | |||
| 5e69b54af1 | |||
| 04382cd1d3 | |||
| d01f3ba925 | |||
| 94eba74016 | |||
| 8ec6785cb8 | |||
| 0321f38054 | |||
| 36327ce8c5 | |||
| 39f29ec3fd | |||
| 7a2b502b9c | |||
| 46cb74f3cf | |||
| fa89068f2b | |||
| 793b88efd8 | |||
| c45f34adc8 | |||
| 6add1e7718 | |||
| 0deaee1217 | |||
| 2fc3a5d4b8 | |||
| da0fdc1aa0 | |||
| aafb14461b | |||
| 70cbfffc74 | |||
| 4a3cddc529 | |||
| cca337f5ae | |||
| 02de598056 | |||
| 8fc90f6779 | |||
| f15308f763 | |||
| f242abbf93 | |||
| 135e21f35d | |||
| 6662d0556f | |||
| d1454932b2 | |||
| cab9b09b7b | |||
| 741e9a8b6d | |||
| 66c72f2b5d | |||
| 163a76f436 | |||
| 75417fbf9f | |||
| ec8f03ee96 | |||
| 140df5b7ce | |||
| da674eced1 | |||
| 479118fcd1 | |||
| 8c86b250bc | |||
| a0b58c3071 | |||
| fe7d42dedd | |||
| 44fba3e2eb | |||
| b2b72e7783 | |||
| 22827223c0 | |||
| 033f08972f | |||
| ec4e77397f | |||
| 50cf82823e | |||
| 53b6ed80bb | |||
| a982bee235 | |||
| 92f07bae1b | |||
| 4c74b2951d | |||
| 3f1ee39910 | |||
| 4b9500cf25 | |||
| a35ad6dfc3 | |||
| 140f019155 | |||
| 6895946f41 | |||
| 5153c462d5 | |||
| 8318470cca | |||
| 89f9878ba2 | |||
| 6c5d33ba22 | |||
| 8d9ab176d7 | |||
| 62d60a5890 | |||
| 7caa9bbb2c | |||
| 2be84e627b | |||
| 1900c86c99 | |||
| 56e4ae9f4a | |||
| 3cb87abec1 | |||
| b3845e43d8 | |||
| 61fa061ecf | |||
| ef8013ef79 | |||
| 7ab95baab8 | |||
| 99864fd2da | |||
| 1c3b17826e | |||
| 97b2e1b12a | |||
| 2304dd7bb1 | |||
| 44cedd5ea2 | |||
| 7ffbf6a3c8 | |||
| b5980a569c | |||
| 7912b1af7d | |||
| 2a75aa19bd | |||
| d95c3eeee1 | |||
| 22de642692 | |||
| 6bd525fd67 | |||
| 8c0684ad13 | |||
| 3c150953ed | |||
| d64869545d | |||
| 5888bee853 | |||
| ec5d501791 | |||
| 760edf9b87 | |||
| 380b87dc0c | |||
| f3087407c6 | |||
| 749738f2aa | |||
| 7d0eeab6f7 | |||
| dae72a7e1b | |||
| 96c6120609 | |||
| de594921cc | |||
| f40c48a83d | |||
| 43e37f0184 | |||
| 1d25a22201 | |||
| 73080c79d0 | |||
| 37725a0f2c | |||
| b3dd1e0386 | |||
| 108be781a4 | |||
| 0894724496 | |||
| 9344f697f9 | |||
| 4c3ad9c95a | |||
| 38e42f92c2 | |||
| c38babecd9 | |||
| e01b285780 | |||
| 1b4fb6c758 | |||
| 95321a3516 | |||
| ff8e17ec2f | |||
| 37fe8c292a | |||
| cd703a5ee0 | |||
| 167f175f3a | |||
| a14abd0276 | |||
| 0fbf745efe | |||
| 92f5f0dfbb | |||
| 0cbb98b92c | |||
| 9f2a672240 | |||
| 0ecb5fce78 | |||
| 65af47d90d | |||
| f7049de567 | |||
| 61b011869c | |||
| a33b9b2bb0 | |||
| 44113d58f5 | |||
| eb27bdd282 | |||
| daad23b3a7 | |||
| 1da0a4ae7d | |||
| 5833f52879 | |||
| a16eaebb25 | |||
| d297280617 | |||
| eee306acda | |||
| c0a257fa0c | |||
| c6365ef868 | |||
| 54fa59f879 | |||
| 2baa9bd801 | |||
| a858ed7a2a | |||
| 921c668a59 | |||
| 0598a3ac43 | |||
| 0d724ca892 | |||
| b948e70258 | |||
| 5267eaac81 | |||
| 9b4634bebe | |||
| f8a54d2ae2 | |||
| 92744a195a | |||
| bc9bbc2431 | |||
| 16e101bba9 | |||
| 861fab8589 | |||
| 8ed7f965bb | |||
| 53d4006806 | |||
| 2761ec0b66 | |||
| d5c6520e4d | |||
| 5ea1cc7a8a | |||
| c50cb000dd | |||
| d4b9e9ab80 | |||
| 85b65befd5 | |||
| 77efd53f0c | |||
| 364da56ab4 | |||
| 9298482163 | |||
| 91f8919876 | |||
| 6323c9a73d | |||
| c08a73a6ca | |||
| 547e7a8308 | |||
| 6307b3bb64 | |||
| 32ab301c9d | |||
| 788d4672d7 | |||
| 1c35a7dad0 | |||
| 2d33674870 | |||
| e810379262 | |||
| d6036f9291 | |||
| ea6baf2220 | |||
| a0cb8cd446 | |||
| 8494fe20bc | |||
| 4a0077d685 | |||
| 9a194f24b8 | |||
| 4337957a6b | |||
| 2ecba5326b | |||
| 2a0532dacc | |||
| f2c352366a | |||
| b52832ca7f | |||
| eb87bb4822 | |||
| 52924aaec0 | |||
| 85855b8cc7 | |||
| 6ac85d8d15 | |||
| 96df9799c3 | |||
| 07d637980c | |||
| 44a8fed410 | |||
| 34cb4bdc9c | |||
| c819e0b063 | |||
| 37a4975544 | |||
| 751277a1ab | |||
| ad7993d7d5 | |||
| 7a5bd86ef4 | |||
| ac729e0ecf | |||
| 0ebc0d5844 | |||
| b133fee376 | |||
| f08ee568b9 | |||
| bfa1dd3756 | |||
| f7cc2548f3 | |||
| a2960e22b9 | |||
| 21b84349d4 | |||
| d6e79f4366 | |||
| c00d951e06 | |||
| 6876df00ae | |||
| e888ea4ccb | |||
| 69f87ad916 | |||
| a710435535 | |||
| 72d4e9c29c | |||
| cac49bfc2e | |||
| 6776cc6ee4 | |||
| 22030f2fd3 | |||
| 4c1d5e01c6 | |||
| fdc4b0cf23 | |||
| f239fa56dd | |||
| 2c08f85744 |
55
.github/workflows/formal-verifiation.yml
vendored
Normal file
55
.github/workflows/formal-verifiation.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
name: Formal verification
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- release-v*
|
||||||
|
- formal-verification
|
||||||
|
pull_request: {}
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
list-scripts:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- id: set-matrix
|
||||||
|
run: echo ::set-output name=matrix::$(ls certora/scripts/{,**}/*.sh | grep -v '\WnoCI\W' | jq -Rsc 'split("\n")[:-1]')
|
||||||
|
|
||||||
|
verify:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: list-scripts
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
cache: 'pip'
|
||||||
|
- name: Install java
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: '11'
|
||||||
|
java-package: 'jre'
|
||||||
|
- name: Install certora
|
||||||
|
run: pip install certora-cli
|
||||||
|
- name: Install solc
|
||||||
|
run: |
|
||||||
|
wget https://github.com/ethereum/solidity/releases/download/v0.8.19/solc-static-linux
|
||||||
|
sudo mv solc-static-linux /usr/local/bin/solc
|
||||||
|
chmod +x /usr/local/bin/solc
|
||||||
|
- name: Verify rule ${{ matrix.params }}
|
||||||
|
run: |
|
||||||
|
touch certora/applyHarness.patch
|
||||||
|
make -C certora munged
|
||||||
|
bash ${{ matrix.params }}
|
||||||
|
env:
|
||||||
|
CERTORAKEY: ${{ secrets.CERTORAKEY }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
max-parallel: 4
|
||||||
|
matrix:
|
||||||
|
params: ${{ fromJson(needs.list-scripts.outputs.matrix) }}
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -68,3 +68,4 @@ contracts-exposed
|
|||||||
.certora*
|
.certora*
|
||||||
.last_confs
|
.last_confs
|
||||||
certora_*
|
certora_*
|
||||||
|
resource_errors.json
|
||||||
|
|||||||
BIN
audit/2020-10-certora.pdf
Normal file
BIN
audit/2020-10-certora.pdf
Normal file
Binary file not shown.
1
certora/.gitignore
vendored
Normal file
1
certora/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
munged
|
||||||
@ -16,9 +16,10 @@ munged: $(wildcard $(CONTRACTS_DIR)/*.sol) $(PATCH)
|
|||||||
patch -p0 -d $@ < $(PATCH)
|
patch -p0 -d $@ < $(PATCH)
|
||||||
|
|
||||||
record:
|
record:
|
||||||
diff -ruN $(CONTRACTS_DIR) $(MUNGED_DIR) | sed 's+../contracts/++g' | sed 's+munged/++g' > $(PATCH)
|
diff -druN $(CONTRACTS_DIR) $(MUNGED_DIR) | sed 's+../contracts/++g' | sed 's+munged/++g' > $(PATCH)
|
||||||
|
|
||||||
|
refresh: munged record
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
git clean -fdX
|
git clean -fdX
|
||||||
touch $(PATCH)
|
touch $(PATCH)
|
||||||
|
|
||||||
|
|||||||
@ -1,101 +1,408 @@
|
|||||||
diff -ruN .gitignore .gitignore
|
diff -druN governance/extensions/GovernorCountingSimple.sol governance/extensions/GovernorCountingSimple.sol
|
||||||
--- .gitignore 1969-12-31 19:00:00.000000000 -0500
|
--- governance/extensions/GovernorCountingSimple.sol 2023-02-27 10:59:32.652558153 +0100
|
||||||
+++ .gitignore 2021-12-09 14:46:33.923637220 -0500
|
+++ governance/extensions/GovernorCountingSimple.sol 2023-02-28 16:49:10.417726143 +0100
|
||||||
@@ -0,0 +1,2 @@
|
@@ -27,7 +27,7 @@
|
||||||
+*
|
mapping(address => bool) hasVoted;
|
||||||
+!.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
- mapping(uint256 => ProposalVote) private _proposalVotes;
|
||||||
diff -ruN governance/Governor.sol governance/Governor.sol
|
+ mapping(uint256 => ProposalVote) internal _proposalVotes; // HARNESS: private -> internal
|
||||||
--- 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 @@
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev See {IGovernor-COUNTING_MODE}.
|
||||||
|
diff -druN governance/extensions/GovernorPreventLateQuorum.sol governance/extensions/GovernorPreventLateQuorum.sol
|
||||||
|
--- governance/extensions/GovernorPreventLateQuorum.sol 2023-02-27 10:59:32.652558153 +0100
|
||||||
|
+++ governance/extensions/GovernorPreventLateQuorum.sol 2023-02-28 16:49:10.417726143 +0100
|
||||||
|
@@ -20,10 +20,10 @@
|
||||||
|
abstract contract GovernorPreventLateQuorum is Governor {
|
||||||
|
using SafeCast for uint256;
|
||||||
|
|
||||||
|
- uint64 private _voteExtension;
|
||||||
|
+ uint64 internal _voteExtension; // HARNESS: private -> internal
|
||||||
|
|
||||||
|
/// @custom:oz-retyped-from mapping(uint256 => Timers.BlockNumber)
|
||||||
|
- mapping(uint256 => uint64) private _extendedDeadlines;
|
||||||
|
+ mapping(uint256 => uint64) internal _extendedDeadlines; // HARNESS: private -> internal
|
||||||
|
|
||||||
|
/// @dev Emitted when a proposal deadline is pushed back due to reaching quorum late in its voting period.
|
||||||
|
event ProposalExtended(uint256 indexed proposalId, uint64 extendedDeadline);
|
||||||
|
diff -druN governance/extensions/GovernorVotesQuorumFraction.sol governance/extensions/GovernorVotesQuorumFraction.sol
|
||||||
|
--- governance/extensions/GovernorVotesQuorumFraction.sol 2023-02-27 10:59:32.655891529 +0100
|
||||||
|
+++ governance/extensions/GovernorVotesQuorumFraction.sol 2023-02-28 16:49:10.417726143 +0100
|
||||||
|
@@ -17,10 +17,10 @@
|
||||||
|
using SafeCast for *;
|
||||||
|
using Checkpoints for Checkpoints.Trace224;
|
||||||
|
|
||||||
|
- uint256 private _quorumNumerator; // DEPRECATED in favor of _quorumNumeratorHistory
|
||||||
|
+ uint256 internal _quorumNumerator; // DEPRECATED // MUNGED private => internal
|
||||||
|
|
||||||
|
/// @custom:oz-retyped-from Checkpoints.History
|
||||||
|
- Checkpoints.Trace224 private _quorumNumeratorHistory;
|
||||||
|
+ Checkpoints.Trace224 internal _quorumNumeratorHistory; // MUNGED private => internal
|
||||||
|
|
||||||
|
event QuorumNumeratorUpdated(uint256 oldQuorumNumerator, uint256 newQuorumNumerator);
|
||||||
|
|
||||||
|
diff -druN governance/Governor.sol governance/Governor.sol
|
||||||
|
--- governance/Governor.sol 2023-02-27 10:59:32.652558153 +0100
|
||||||
|
+++ governance/Governor.sol 2023-02-28 16:49:10.421059488 +0100
|
||||||
|
@@ -51,7 +51,7 @@
|
||||||
string private _name;
|
string private _name;
|
||||||
|
|
||||||
|
/// @custom:oz-retyped-from mapping(uint256 => Governor.ProposalCore)
|
||||||
- mapping(uint256 => ProposalCore) private _proposals;
|
- mapping(uint256 => ProposalCore) private _proposals;
|
||||||
-
|
+ mapping(uint256 => ProposalCore) internal _proposals; // HARNESS: private -> internal
|
||||||
+ mapping(uint256 => ProposalCore) public _proposals;
|
|
||||||
+
|
// This queue keeps track of the governor operating on itself. Calls to functions protected by the
|
||||||
/**
|
// {onlyGovernance} modifier needs to be whitelisted in this queue. Whitelisting is set in {_beforeExecute},
|
||||||
* @dev Restrict access to governor executing address. Some module might override the _executor function to make
|
diff -druN governance/TimelockController.sol governance/TimelockController.sol
|
||||||
* sure this modifier is consistent with the execution model.
|
--- governance/TimelockController.sol 2023-02-27 10:59:32.652558153 +0100
|
||||||
@@ -167,12 +167,12 @@
|
+++ governance/TimelockController.sol 2023-02-28 16:49:10.421059488 +0100
|
||||||
/**
|
@@ -28,10 +28,10 @@
|
||||||
* @dev Amount of votes already cast passes the threshold limit.
|
bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE");
|
||||||
*/
|
bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE");
|
||||||
- function _quorumReached(uint256 proposalId) internal view virtual returns (bool);
|
bytes32 public constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE");
|
||||||
+ function _quorumReached(uint256 proposalId) public view virtual returns (bool); // HARNESS: changed to public from internal
|
- uint256 internal constant _DONE_TIMESTAMP = uint256(1);
|
||||||
|
+ uint256 public constant _DONE_TIMESTAMP = uint256(1); // HARNESS: internal -> public
|
||||||
|
|
||||||
|
mapping(bytes32 => uint256) private _timestamps;
|
||||||
|
- uint256 private _minDelay;
|
||||||
|
+ uint256 public _minDelay; // HARNESS: private -> public
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Is the proposal successful or not.
|
* @dev Emitted when a call is scheduled as part of operation `id`.
|
||||||
|
diff -druN governance/utils/Votes.sol governance/utils/Votes.sol
|
||||||
|
--- governance/utils/Votes.sol 2023-02-27 10:59:32.655891529 +0100
|
||||||
|
+++ governance/utils/Votes.sol 2023-02-28 16:49:10.421059488 +0100
|
||||||
|
@@ -35,7 +35,25 @@
|
||||||
|
bytes32 private constant _DELEGATION_TYPEHASH =
|
||||||
|
keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
|
||||||
|
|
||||||
|
- mapping(address => address) private _delegation;
|
||||||
|
+ // HARNESS : Hooks cannot access any information from Checkpoints yet, so I am also updating votes and fromBlock in this struct
|
||||||
|
+ struct Ckpt {
|
||||||
|
+ uint32 fromBlock;
|
||||||
|
+ uint224 votes;
|
||||||
|
+ }
|
||||||
|
+ mapping(address => Ckpt) public _checkpoints;
|
||||||
|
+
|
||||||
|
+ // HARNESSED getters
|
||||||
|
+ function numCheckpoints(address account) public view returns (uint32) {
|
||||||
|
+ return SafeCast.toUint32(_delegateCheckpoints[account]._checkpoints.length);
|
||||||
|
+ }
|
||||||
|
+ function ckptFromBlock(address account, uint32 pos) public view returns (uint32) {
|
||||||
|
+ return _delegateCheckpoints[account]._checkpoints[pos]._key;
|
||||||
|
+ }
|
||||||
|
+ function ckptVotes(address account, uint32 pos) public view returns (uint224) {
|
||||||
|
+ return _delegateCheckpoints[account]._checkpoints[pos]._value;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ mapping(address => address) public _delegation; // HARNESS: private -> public
|
||||||
|
|
||||||
|
/// @custom:oz-retyped-from mapping(address => Checkpoints.History)
|
||||||
|
mapping(address => Checkpoints.Trace224) private _delegateCheckpoints;
|
||||||
|
@@ -240,5 +258,5 @@
|
||||||
|
/**
|
||||||
|
* @dev Must return the voting units held by an account.
|
||||||
*/
|
*/
|
||||||
- function _voteSucceeded(uint256 proposalId) internal view virtual returns (bool);
|
- function _getVotingUnits(address) internal view virtual returns (uint256);
|
||||||
+ function _voteSucceeded(uint256 proposalId) public view virtual returns (bool); // HARNESS: changed to public from internal
|
+ function _getVotingUnits(address) public view virtual returns (uint256); // HARNESS: internal -> public
|
||||||
|
}
|
||||||
|
diff -druN proxy/utils/Initializable.sol proxy/utils/Initializable.sol
|
||||||
|
--- proxy/utils/Initializable.sol 2023-02-27 10:59:32.655891529 +0100
|
||||||
|
+++ proxy/utils/Initializable.sol 2023-02-28 16:49:10.421059488 +0100
|
||||||
|
@@ -60,12 +60,12 @@
|
||||||
|
* @dev Indicates that the contract has been initialized.
|
||||||
|
* @custom:oz-retyped-from bool
|
||||||
|
*/
|
||||||
|
- uint8 private _initialized;
|
||||||
|
+ uint8 internal _initialized; // HARNESS: private -> internal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Register a vote with a given support and voting weight.
|
* @dev Indicates that the contract is in the process of being initialized.
|
||||||
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
|
- bool private _initializing;
|
||||||
+++ token/ERC20/extensions/ERC20Votes.sol 2021-12-09 14:46:33.927637196 -0500
|
+ bool internal _initializing; // HARNESS: private -> internal
|
||||||
@@ -84,7 +84,7 @@
|
|
||||||
|
/**
|
||||||
|
* @dev Triggered when the contract has been initialized or reinitialized.
|
||||||
|
diff -druN token/ERC1155/ERC1155.sol token/ERC1155/ERC1155.sol
|
||||||
|
--- token/ERC1155/ERC1155.sol 2023-02-27 10:59:32.655891529 +0100
|
||||||
|
+++ token/ERC1155/ERC1155.sol 2023-02-28 16:49:10.421059488 +0100
|
||||||
|
@@ -21,7 +21,7 @@
|
||||||
|
using Address for address;
|
||||||
|
|
||||||
|
// Mapping from token ID to account balances
|
||||||
|
- mapping(uint256 => mapping(address => uint256)) private _balances;
|
||||||
|
+ mapping(uint256 => mapping(address => uint256)) internal _balances; // HARNESS: private -> internal
|
||||||
|
|
||||||
|
// Mapping from account to operator approvals
|
||||||
|
mapping(address => mapping(address => bool)) private _operatorApprovals;
|
||||||
|
@@ -451,7 +451,7 @@
|
||||||
|
uint256 id,
|
||||||
|
uint256 amount,
|
||||||
|
bytes memory data
|
||||||
|
- ) private {
|
||||||
|
+ ) public { // HARNESS: private -> public
|
||||||
|
if (to.isContract()) {
|
||||||
|
try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
|
||||||
|
if (response != IERC1155Receiver.onERC1155Received.selector) {
|
||||||
|
@@ -472,7 +472,7 @@
|
||||||
|
uint256[] memory ids,
|
||||||
|
uint256[] memory amounts,
|
||||||
|
bytes memory data
|
||||||
|
- ) private {
|
||||||
|
+ ) public { // HARNESS: private -> public
|
||||||
|
if (to.isContract()) {
|
||||||
|
try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
|
||||||
|
bytes4 response
|
||||||
|
diff -druN token/ERC20/ERC20.sol token/ERC20/ERC20.sol
|
||||||
|
--- token/ERC20/ERC20.sol 2023-02-27 10:59:32.655891529 +0100
|
||||||
|
+++ token/ERC20/ERC20.sol 2023-02-28 16:49:10.421059488 +0100
|
||||||
|
@@ -248,7 +248,7 @@
|
||||||
*
|
*
|
||||||
* - `blockNumber` must have been already mined
|
* - `account` cannot be the zero address.
|
||||||
*/
|
*/
|
||||||
- function getPastVotes(address account, uint256 blockNumber) public view returns (uint256) {
|
- function _mint(address account, uint256 amount) internal virtual {
|
||||||
+ function getPastVotes(address account, uint256 blockNumber) public view virtual returns (uint256) {
|
+ function _mint(address account, uint256 amount) public virtual { // HARNESS: internal -> public
|
||||||
require(blockNumber < block.number, "ERC20Votes: block not yet mined");
|
require(account != address(0), "ERC20: mint to the zero address");
|
||||||
return _checkpointsLookup(_checkpoints[account], blockNumber);
|
|
||||||
|
_beforeTokenTransfer(address(0), account, amount);
|
||||||
|
@@ -274,7 +274,7 @@
|
||||||
|
* - `account` cannot be the zero address.
|
||||||
|
* - `account` must have at least `amount` tokens.
|
||||||
|
*/
|
||||||
|
- function _burn(address account, uint256 amount) internal virtual {
|
||||||
|
+ function _burn(address account, uint256 amount) public virtual { // HARNESS: internal -> public
|
||||||
|
require(account != address(0), "ERC20: burn from the zero address");
|
||||||
|
|
||||||
|
_beforeTokenTransfer(account, address(0), amount);
|
||||||
|
diff -druN token/ERC20/extensions/ERC20Capped.sol token/ERC20/extensions/ERC20Capped.sol
|
||||||
|
--- token/ERC20/extensions/ERC20Capped.sol 2023-02-22 15:43:36.624717708 +0100
|
||||||
|
+++ token/ERC20/extensions/ERC20Capped.sol 2023-02-28 16:49:10.421059488 +0100
|
||||||
|
@@ -30,7 +30,7 @@
|
||||||
|
/**
|
||||||
|
* @dev See {ERC20-_mint}.
|
||||||
|
*/
|
||||||
|
- function _mint(address account, uint256 amount) internal virtual override {
|
||||||
|
+ function _mint(address account, uint256 amount) public virtual override { // HARNESS: internal -> public
|
||||||
|
require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded");
|
||||||
|
super._mint(account, amount);
|
||||||
}
|
}
|
||||||
|
diff -druN token/ERC20/extensions/ERC20FlashMint.sol token/ERC20/extensions/ERC20FlashMint.sol
|
||||||
|
--- token/ERC20/extensions/ERC20FlashMint.sol 2023-02-27 10:59:32.655891529 +0100
|
||||||
|
+++ token/ERC20/extensions/ERC20FlashMint.sol 2023-02-28 16:49:10.421059488 +0100
|
||||||
|
@@ -53,9 +53,11 @@
|
||||||
|
// silence warning about unused variable without the addition of bytecode.
|
||||||
|
token;
|
||||||
|
amount;
|
||||||
|
- return 0;
|
||||||
|
+ return fee; // HARNESS: made "return" nonzero
|
||||||
|
}
|
||||||
|
|
||||||
|
+ uint256 public fee; // HARNESS: added it to simulate random fee amount
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* @dev Returns the receiver address of the flash fee. By default this
|
||||||
|
* implementation returns the address(0) which means the fee amount will be burnt.
|
||||||
|
diff -druN token/ERC20/extensions/ERC20Votes.sol token/ERC20/extensions/ERC20Votes.sol
|
||||||
|
--- token/ERC20/extensions/ERC20Votes.sol 2023-02-27 10:59:32.655891529 +0100
|
||||||
|
+++ token/ERC20/extensions/ERC20Votes.sol 2023-02-28 16:49:10.421059488 +0100
|
||||||
|
@@ -33,8 +33,8 @@
|
||||||
|
bytes32 private constant _DELEGATION_TYPEHASH =
|
||||||
|
keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
|
||||||
|
|
||||||
|
- mapping(address => address) private _delegates;
|
||||||
|
- mapping(address => Checkpoint[]) private _checkpoints;
|
||||||
|
+ mapping(address => address) public _delegates; // HARNESS: private -> public
|
||||||
|
+ mapping(address => Checkpoint[]) public _checkpoints; // HARNESS: private -> public
|
||||||
|
Checkpoint[] private _totalSupplyCheckpoints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
@@ -186,27 +186,27 @@
|
||||||
|
/**
|
||||||
|
* @dev Maximum token supply. Defaults to `type(uint224).max` (2^224^ - 1).
|
||||||
|
*/
|
||||||
|
- function _maxSupply() internal view virtual returns (uint224) {
|
||||||
|
+ function _maxSupply() public view virtual returns (uint224) { // HARNESS: internal -> public
|
||||||
|
return type(uint224).max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Snapshots the totalSupply after it has been increased.
|
||||||
|
*/
|
||||||
|
- function _mint(address account, uint256 amount) internal virtual override {
|
||||||
|
+ function _mint(address account, uint256 amount) public virtual override { // HARNESS: internal -> public
|
||||||
|
super._mint(account, amount);
|
||||||
|
require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes");
|
||||||
|
|
||||||
|
- _writeCheckpoint(_totalSupplyCheckpoints, _add, amount);
|
||||||
|
+ _writeCheckpointAdd(_totalSupplyCheckpoints, amount); // HARNESS: new version without pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Snapshots the totalSupply after it has been decreased.
|
||||||
|
*/
|
||||||
|
- function _burn(address account, uint256 amount) internal virtual override {
|
||||||
|
+ function _burn(address account, uint256 amount) public virtual override { // HARNESS: internal -> public (to comply with the ERC20 harness)
|
||||||
|
super._burn(account, amount);
|
||||||
|
|
||||||
|
- _writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount);
|
||||||
|
+ _writeCheckpointSub(_totalSupplyCheckpoints, amount); // HARNESS: new version without pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@@ -225,7 +225,7 @@
|
||||||
|
*
|
||||||
|
* Emits events {IVotes-DelegateChanged} and {IVotes-DelegateVotesChanged}.
|
||||||
|
*/
|
||||||
|
- function _delegate(address delegator, address delegatee) internal virtual {
|
||||||
|
+ function _delegate(address delegator, address delegatee) public virtual { // HARNESS: internal -> public
|
||||||
|
address currentDelegate = delegates(delegator);
|
||||||
|
uint256 delegatorBalance = balanceOf(delegator);
|
||||||
|
_delegates[delegator] = delegatee;
|
||||||
|
@@ -238,35 +238,60 @@
|
||||||
|
function _moveVotingPower(address src, address dst, uint256 amount) private {
|
||||||
|
if (src != dst && amount > 0) {
|
||||||
|
if (src != address(0)) {
|
||||||
|
- (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[src], _subtract, amount);
|
||||||
|
+ (uint256 oldWeight, uint256 newWeight) = _writeCheckpointSub(_checkpoints[src], amount); // HARNESS: new version without pointer
|
||||||
|
emit DelegateVotesChanged(src, oldWeight, newWeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dst != address(0)) {
|
||||||
|
- (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[dst], _add, amount);
|
||||||
|
+ (uint256 oldWeight, uint256 newWeight) = _writeCheckpointAdd(_checkpoints[dst], amount); // HARNESS: new version without pointer
|
||||||
|
emit DelegateVotesChanged(dst, oldWeight, newWeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- function _writeCheckpoint(
|
||||||
|
- Checkpoint[] storage ckpts,
|
||||||
|
- function(uint256, uint256) view returns (uint256) op,
|
||||||
|
- uint256 delta
|
||||||
|
- ) private returns (uint256 oldWeight, uint256 newWeight) {
|
||||||
|
+ // HARNESS: split _writeCheckpoint() to two functions as a workaround for function pointers that cannot be managed by the tool
|
||||||
|
+ // function _writeCheckpoint(
|
||||||
|
+ // Checkpoint[] storage ckpts,
|
||||||
|
+ // function(uint256, uint256) view returns (uint256) op,
|
||||||
|
+ // uint256 delta
|
||||||
|
+ // ) private returns (uint256 oldWeight, uint256 newWeight) {
|
||||||
|
+ // uint256 pos = ckpts.length;
|
||||||
|
+
|
||||||
|
+ // unchecked {
|
||||||
|
+ // Checkpoint memory oldCkpt = pos == 0 ? Checkpoint(0, 0) : _unsafeAccess(ckpts, pos - 1);
|
||||||
|
+
|
||||||
|
+ // oldWeight = oldCkpt.votes;
|
||||||
|
+ // newWeight = op(oldWeight, delta);
|
||||||
|
+
|
||||||
|
+ // if (pos > 0 && oldCkpt.fromBlock == clock()) {
|
||||||
|
+ // _unsafeAccess(ckpts, pos - 1).votes = SafeCast.toUint224(newWeight);
|
||||||
|
+ // } else {
|
||||||
|
+ // ckpts.push(Checkpoint({fromBlock: SafeCast.toUint32(clock()), votes: SafeCast.toUint224(newWeight)}));
|
||||||
|
+ // }
|
||||||
|
+ // }
|
||||||
|
+ // }
|
||||||
|
+
|
||||||
|
+ function _writeCheckpointAdd(Checkpoint[] storage ckpts, uint256 delta) private returns (uint256 oldWeight, uint256 newWeight) {
|
||||||
|
uint256 pos = ckpts.length;
|
||||||
|
+ oldWeight = pos == 0 ? 0 : ckpts[pos - 1].votes;
|
||||||
|
+ newWeight = _add(oldWeight, delta);
|
||||||
|
|
||||||
|
- unchecked {
|
||||||
|
- Checkpoint memory oldCkpt = pos == 0 ? Checkpoint(0, 0) : _unsafeAccess(ckpts, pos - 1);
|
||||||
|
+ if (pos > 0 && ckpts[pos - 1].fromBlock == clock()) {
|
||||||
|
+ ckpts[pos - 1].votes = SafeCast.toUint224(newWeight);
|
||||||
|
+ } else {
|
||||||
|
+ ckpts.push(Checkpoint({fromBlock: SafeCast.toUint32(clock()), votes: SafeCast.toUint224(newWeight)}));
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- oldWeight = oldCkpt.votes;
|
||||||
|
- newWeight = op(oldWeight, delta);
|
||||||
|
+ function _writeCheckpointSub(Checkpoint[] storage ckpts, uint256 delta) private returns (uint256 oldWeight, uint256 newWeight) {
|
||||||
|
+ uint256 pos = ckpts.length;
|
||||||
|
+ oldWeight = pos == 0 ? 0 : ckpts[pos - 1].votes;
|
||||||
|
+ newWeight = _subtract(oldWeight, delta);
|
||||||
|
|
||||||
|
- if (pos > 0 && oldCkpt.fromBlock == clock()) {
|
||||||
|
- _unsafeAccess(ckpts, pos - 1).votes = SafeCast.toUint224(newWeight);
|
||||||
|
- } else {
|
||||||
|
- ckpts.push(Checkpoint({fromBlock: SafeCast.toUint32(clock()), votes: SafeCast.toUint224(newWeight)}));
|
||||||
|
- }
|
||||||
|
+ if (pos > 0 && ckpts[pos - 1].fromBlock == clock()) {
|
||||||
|
+ ckpts[pos - 1].votes = SafeCast.toUint224(newWeight);
|
||||||
|
+ } else {
|
||||||
|
+ ckpts.push(Checkpoint({fromBlock: SafeCast.toUint32(clock()), votes: SafeCast.toUint224(newWeight)}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diff -druN token/ERC20/extensions/ERC20Wrapper.sol token/ERC20/extensions/ERC20Wrapper.sol
|
||||||
|
--- token/ERC20/extensions/ERC20Wrapper.sol 2023-02-27 10:59:32.655891529 +0100
|
||||||
|
+++ token/ERC20/extensions/ERC20Wrapper.sol 2023-02-28 16:49:10.421059488 +0100
|
||||||
|
@@ -62,7 +62,7 @@
|
||||||
|
* @dev Mint wrapped token to cover any underlyingTokens that would have been transferred by mistake. Internal
|
||||||
|
* function that can be exposed with access control if desired.
|
||||||
|
*/
|
||||||
|
- function _recover(address account) internal virtual returns (uint256) {
|
||||||
|
+ function _recover(address account) public virtual returns (uint256) { // HARNESS: internal -> public
|
||||||
|
uint256 value = _underlying.balanceOf(address(this)) - totalSupply();
|
||||||
|
_mint(account, value);
|
||||||
|
return value;
|
||||||
|
diff -druN token/ERC721/extensions/ERC721Votes.sol token/ERC721/extensions/ERC721Votes.sol
|
||||||
|
--- token/ERC721/extensions/ERC721Votes.sol 2023-02-27 10:59:32.655891529 +0100
|
||||||
|
+++ token/ERC721/extensions/ERC721Votes.sol 2023-02-28 16:49:10.421059488 +0100
|
||||||
|
@@ -35,7 +35,7 @@
|
||||||
|
/**
|
||||||
|
* @dev Returns the balance of `account`.
|
||||||
|
*/
|
||||||
|
- function _getVotingUnits(address account) internal view virtual override returns (uint256) {
|
||||||
|
+ function _getVotingUnits(address account) public view virtual override returns (uint256) { // HARNESS: internal -> public
|
||||||
|
return balanceOf(account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff -druN utils/Address.sol utils/Address.sol
|
||||||
|
--- utils/Address.sol 2023-02-27 10:59:32.659224903 +0100
|
||||||
|
+++ utils/Address.sol 2023-02-28 16:49:10.421059488 +0100
|
||||||
|
@@ -197,7 +197,7 @@
|
||||||
|
bool success,
|
||||||
|
bytes memory returndata,
|
||||||
|
string memory errorMessage
|
||||||
|
- ) internal view returns (bytes memory) {
|
||||||
|
+ ) internal view returns (bytes memory val) { // MUNGED undeterministic return causes error for Prover
|
||||||
|
if (success) {
|
||||||
|
if (returndata.length == 0) {
|
||||||
|
// only check isContract if the call was successful and the return data is empty
|
||||||
|
@@ -220,7 +220,7 @@
|
||||||
|
bool success,
|
||||||
|
bytes memory returndata,
|
||||||
|
string memory errorMessage
|
||||||
|
- ) internal pure returns (bytes memory) {
|
||||||
|
+ ) internal pure returns (bytes memory val) { // MUNGED undeterministic return causes error for Prover
|
||||||
|
if (success) {
|
||||||
|
return returndata;
|
||||||
|
} else {
|
||||||
|
diff -druN utils/Checkpoints.sol utils/Checkpoints.sol
|
||||||
|
--- utils/Checkpoints.sol 2023-02-27 10:59:32.659224903 +0100
|
||||||
|
+++ utils/Checkpoints.sol 2023-02-28 16:49:10.424392833 +0100
|
||||||
|
@@ -84,13 +84,13 @@
|
||||||
|
*
|
||||||
|
* Returns previous value and new value.
|
||||||
|
*/
|
||||||
|
- function push(
|
||||||
|
- History storage self,
|
||||||
|
- function(uint256, uint256) view returns (uint256) op,
|
||||||
|
- uint256 delta
|
||||||
|
- ) internal returns (uint256, uint256) {
|
||||||
|
- return push(self, op(latest(self), delta));
|
||||||
|
- }
|
||||||
|
+ // function push(
|
||||||
|
+ // History storage self,
|
||||||
|
+ // function(uint256, uint256) view returns (uint256) op,
|
||||||
|
+ // uint256 delta
|
||||||
|
+ // ) internal returns (uint256, uint256) {
|
||||||
|
+ // return push(self, op(latest(self), delta));
|
||||||
|
+ // }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints.
|
||||||
|
|||||||
8
certora/harnesses/AccessControlHarness.sol
Normal file
8
certora/harnesses/AccessControlHarness.sol
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControl.sol)
|
||||||
|
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import "../munged/access/AccessControl.sol";
|
||||||
|
|
||||||
|
contract AccessControlHarness is AccessControl {}
|
||||||
5
certora/harnesses/ERC1155/ERC1155BurnableHarness.sol
Normal file
5
certora/harnesses/ERC1155/ERC1155BurnableHarness.sol
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import "../../munged/token/ERC1155/extensions/ERC1155Burnable.sol";
|
||||||
|
|
||||||
|
contract ERC1155BurnableHarness is ERC1155Burnable {
|
||||||
|
constructor(string memory uri_) ERC1155(uri_) {}
|
||||||
|
}
|
||||||
41
certora/harnesses/ERC1155/ERC1155Harness.sol
Normal file
41
certora/harnesses/ERC1155/ERC1155Harness.sol
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.2;
|
||||||
|
import "../../munged/token/ERC1155/ERC1155.sol";
|
||||||
|
|
||||||
|
contract ERC1155Harness is ERC1155 {
|
||||||
|
constructor(string memory uri_) ERC1155(uri_) {}
|
||||||
|
|
||||||
|
function burn(
|
||||||
|
address from,
|
||||||
|
uint256 id,
|
||||||
|
uint256 amount
|
||||||
|
) public virtual {
|
||||||
|
_burn(from, id, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function burnBatch(
|
||||||
|
address from,
|
||||||
|
uint256[] memory ids,
|
||||||
|
uint256[] memory amounts
|
||||||
|
) public virtual {
|
||||||
|
_burnBatch(from, ids, amounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mint(
|
||||||
|
address to,
|
||||||
|
uint256 id,
|
||||||
|
uint256 amount,
|
||||||
|
bytes memory data
|
||||||
|
) public virtual {
|
||||||
|
_mint(to, id, amount, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mintBatch(
|
||||||
|
address to,
|
||||||
|
uint256[] memory ids,
|
||||||
|
uint256[] memory amounts,
|
||||||
|
bytes memory data
|
||||||
|
) public virtual {
|
||||||
|
_mintBatch(to, ids, amounts, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
certora/harnesses/ERC1155/ERC1155PausableHarness.sol
Normal file
17
certora/harnesses/ERC1155/ERC1155PausableHarness.sol
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import "../../munged/token/ERC1155/extensions/ERC1155Pausable.sol";
|
||||||
|
|
||||||
|
contract ERC1155PausableHarness is ERC1155Pausable {
|
||||||
|
constructor(string memory uri_) ERC1155(uri_) {}
|
||||||
|
|
||||||
|
function pause() public {
|
||||||
|
_pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
function unpause() public {
|
||||||
|
_unpause();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onlyWhenPausedMethod() public whenPaused {}
|
||||||
|
|
||||||
|
function onlyWhenNotPausedMethod() public whenNotPaused {}
|
||||||
|
}
|
||||||
65
certora/harnesses/ERC1155/ERC1155SupplyHarness.sol
Normal file
65
certora/harnesses/ERC1155/ERC1155SupplyHarness.sol
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import "../../munged/token/ERC1155/extensions/ERC1155Supply.sol";
|
||||||
|
|
||||||
|
contract ERC1155SupplyHarness is ERC1155Supply {
|
||||||
|
address public owner;
|
||||||
|
|
||||||
|
constructor(string memory uri_) ERC1155(uri_) {
|
||||||
|
owner = msg.sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
// workaround for problem caused by `exists` being a CVL keyword
|
||||||
|
function exists_wrapper(uint256 id) public view virtual returns (bool) {
|
||||||
|
return exists(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// These rules were not implemented in the base but there are changes in supply
|
||||||
|
// that are affected by the internal contracts so we implemented them. We assume
|
||||||
|
// only the owner can call any of these functions to be able to test them but also
|
||||||
|
// limit false positives.
|
||||||
|
|
||||||
|
modifier onlyOwner() {
|
||||||
|
require(msg.sender == owner);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function burn(
|
||||||
|
address from,
|
||||||
|
uint256 id,
|
||||||
|
uint256 amount
|
||||||
|
) public virtual onlyOwner {
|
||||||
|
_burn(from, id, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function burnBatch(
|
||||||
|
address from,
|
||||||
|
uint256[] memory ids,
|
||||||
|
uint256[] memory amounts
|
||||||
|
) public virtual onlyOwner {
|
||||||
|
_burnBatch(from, ids, amounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mint(
|
||||||
|
address to,
|
||||||
|
uint256 id,
|
||||||
|
uint256 amount,
|
||||||
|
bytes memory data
|
||||||
|
) public virtual onlyOwner {
|
||||||
|
_mint(to, id, amount, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mintBatch(
|
||||||
|
address to,
|
||||||
|
uint256[] memory ids,
|
||||||
|
uint256[] memory amounts,
|
||||||
|
bytes memory data
|
||||||
|
) public virtual onlyOwner {
|
||||||
|
_mintBatch(to, ids, amounts, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In order to check the invariant that zero address never holds any tokens, we need to remove the require
|
||||||
|
// from this function.
|
||||||
|
function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
|
||||||
|
// require(account != address(0), "ERC1155: address zero is not a valid owner");
|
||||||
|
return _balances[id][account];
|
||||||
|
}
|
||||||
|
}
|
||||||
5
certora/harnesses/ERC20FlashMintHarness.sol
Normal file
5
certora/harnesses/ERC20FlashMintHarness.sol
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import "../munged/token/ERC20/extensions/ERC20FlashMint.sol";
|
||||||
|
|
||||||
|
contract ERC20FlashMintHarness is ERC20FlashMint {
|
||||||
|
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
|
||||||
|
}
|
||||||
5
certora/harnesses/ERC20PermitHarness.sol
Normal file
5
certora/harnesses/ERC20PermitHarness.sol
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import "../munged/token/ERC20/extensions/draft-ERC20Permit.sol";
|
||||||
|
|
||||||
|
contract ERC20PermitHarness is ERC20, ERC20Permit {
|
||||||
|
constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {}
|
||||||
|
}
|
||||||
@ -1,28 +1,26 @@
|
|||||||
import "../munged/token/ERC20/extensions/ERC20Votes.sol";
|
import "../munged/token/ERC20/extensions/ERC20Votes.sol";
|
||||||
|
|
||||||
contract ERC20VotesHarness is ERC20Votes {
|
contract ERC20VotesHarness is ERC20Votes {
|
||||||
constructor(string memory name, string memory symbol) ERC20Permit(name) ERC20(name, symbol) {}
|
constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {}
|
||||||
|
|
||||||
mapping(address => mapping(uint256 => uint256)) public _getPastVotes;
|
function ckptFromBlock(address account, uint32 pos) public view returns (uint32) {
|
||||||
|
return _checkpoints[account][pos].fromBlock;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function ckptVotes(address account, uint32 pos) public view returns (uint224) {
|
||||||
* @dev Change delegation for `delegator` to `delegatee`.
|
return _checkpoints[account][pos].fromBlock;
|
||||||
*
|
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unsafeNumCheckpoints(address account) public view returns (uint256) {
|
||||||
|
return _checkpoints[account].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function delegateBySig(
|
||||||
|
address delegatee,
|
||||||
|
uint256 nonce,
|
||||||
|
uint256 expiry,
|
||||||
|
uint8 v,
|
||||||
|
bytes32 r,
|
||||||
|
bytes32 s
|
||||||
|
) public virtual override {}
|
||||||
}
|
}
|
||||||
|
|||||||
17
certora/harnesses/ERC20WrapperHarness.sol
Normal file
17
certora/harnesses/ERC20WrapperHarness.sol
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import "../munged/token/ERC20/extensions/ERC20Wrapper.sol";
|
||||||
|
|
||||||
|
contract ERC20WrapperHarness is ERC20Wrapper {
|
||||||
|
constructor(
|
||||||
|
IERC20 _underlying,
|
||||||
|
string memory _name,
|
||||||
|
string memory _symbol
|
||||||
|
) ERC20(_name, _symbol) ERC20Wrapper(_underlying) {}
|
||||||
|
|
||||||
|
function underlyingTotalSupply() public view returns (uint256) {
|
||||||
|
return underlying().totalSupply();
|
||||||
|
}
|
||||||
|
|
||||||
|
function underlyingBalanceOf(address account) public view returns (uint256) {
|
||||||
|
return underlying().balanceOf(account);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
certora/harnesses/ERC721VotesHarness.sol
Normal file
26
certora/harnesses/ERC721VotesHarness.sol
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import "../munged/token/ERC721/extensions/draft-ERC721Votes.sol";
|
||||||
|
|
||||||
|
contract ERC721VotesHarness is ERC721Votes {
|
||||||
|
constructor(string memory name, string memory symbol) ERC721(name, symbol) EIP712(name, symbol) {}
|
||||||
|
|
||||||
|
function delegateBySig(
|
||||||
|
address delegatee,
|
||||||
|
uint256 nonce,
|
||||||
|
uint256 expiry,
|
||||||
|
uint8 v,
|
||||||
|
bytes32 r,
|
||||||
|
bytes32 s
|
||||||
|
) public virtual override {
|
||||||
|
assert(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mint(address account, uint256 tokenID) public {
|
||||||
|
_mint(account, tokenID);
|
||||||
|
}
|
||||||
|
|
||||||
|
function burn(uint256 tokenID) public {
|
||||||
|
_burn(tokenID);
|
||||||
|
}
|
||||||
|
}
|
||||||
206
certora/harnesses/GovernorFullHarness.sol
Normal file
206
certora/harnesses/GovernorFullHarness.sol
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.2;
|
||||||
|
|
||||||
|
import "../munged/governance/Governor.sol";
|
||||||
|
import "../munged/governance/extensions/GovernorCountingSimple.sol";
|
||||||
|
import "../munged/governance/extensions/GovernorPreventLateQuorum.sol";
|
||||||
|
import "../munged/governance/extensions/GovernorTimelockControl.sol";
|
||||||
|
import "../munged/governance/extensions/GovernorVotes.sol";
|
||||||
|
import "../munged/governance/extensions/GovernorVotesQuorumFraction.sol";
|
||||||
|
import "../munged/token/ERC20/extensions/ERC20Votes.sol";
|
||||||
|
|
||||||
|
contract GovernorFullHarness is
|
||||||
|
Governor,
|
||||||
|
GovernorCountingSimple,
|
||||||
|
GovernorTimelockControl,
|
||||||
|
GovernorPreventLateQuorum,
|
||||||
|
GovernorVotes,
|
||||||
|
GovernorVotesQuorumFraction
|
||||||
|
{
|
||||||
|
using Checkpoints for Checkpoints.Trace224;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
IVotes _token,
|
||||||
|
TimelockController _timelock,
|
||||||
|
uint64 initialVoteExtension,
|
||||||
|
uint256 quorumNumeratorValue
|
||||||
|
)
|
||||||
|
Governor("Harness")
|
||||||
|
GovernorPreventLateQuorum(initialVoteExtension)
|
||||||
|
GovernorTimelockControl(_timelock)
|
||||||
|
GovernorVotes(_token)
|
||||||
|
GovernorVotesQuorumFraction(quorumNumeratorValue)
|
||||||
|
{}
|
||||||
|
|
||||||
|
mapping(uint256 => uint256) public ghost_sum_vote_power_by_id;
|
||||||
|
|
||||||
|
// Harness from Votes //
|
||||||
|
|
||||||
|
function getPastTotalSupply(uint256 blockNumber) public view returns(uint256) {
|
||||||
|
return token.getPastTotalSupply(blockNumber);
|
||||||
|
}
|
||||||
|
// Harness from GovernorVotesQuorumFraction //
|
||||||
|
|
||||||
|
function getQuorumNumeratorLength() public view returns(uint256) {
|
||||||
|
return _quorumNumeratorHistory._checkpoints.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getQuorumNumeratorLatest() public view returns(uint256) {
|
||||||
|
return _quorumNumeratorHistory.latest();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDeprecatedQuorumNumerator() public view returns(uint256) {
|
||||||
|
return _quorumNumerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Harness from GovernorPreventLateQuorum //
|
||||||
|
|
||||||
|
function getVoteExtension() public view returns (uint64) {
|
||||||
|
return _voteExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExtendedDeadline(uint256 proposalId) public view returns (uint64) {
|
||||||
|
return _extendedDeadlines[proposalId];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Harness from GovernorCountingSimple //
|
||||||
|
|
||||||
|
function getAgainstVotes(uint256 proposalId) public view returns (uint256) {
|
||||||
|
ProposalVote storage proposalvote = _proposalVotes[proposalId];
|
||||||
|
return proposalvote.againstVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAbstainVotes(uint256 proposalId) public view returns (uint256) {
|
||||||
|
ProposalVote storage proposalvote = _proposalVotes[proposalId];
|
||||||
|
return proposalvote.abstainVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getForVotes(uint256 proposalId) public view returns (uint256) {
|
||||||
|
ProposalVote storage proposalvote = _proposalVotes[proposalId];
|
||||||
|
return proposalvote.forVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function quorumReached(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _quorumReached(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function voteSucceeded(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _voteSucceeded(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Harness from Governor //
|
||||||
|
|
||||||
|
function getExecutor() public view returns (address) {
|
||||||
|
return _executor();
|
||||||
|
}
|
||||||
|
|
||||||
|
function proposalProposer(uint256 proposalId) public view returns (address) {
|
||||||
|
return _proposalProposer(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isExecuted(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _proposals[proposalId].executed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCanceled(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _proposals[proposalId].canceled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following functions are overrides required by Solidity added by Certora. //
|
||||||
|
|
||||||
|
function proposalDeadline(uint256 proposalId)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
virtual
|
||||||
|
override(Governor, GovernorPreventLateQuorum, IGovernor)
|
||||||
|
returns (uint256)
|
||||||
|
{
|
||||||
|
return super.proposalDeadline(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _castVote(
|
||||||
|
uint256 proposalId,
|
||||||
|
address account,
|
||||||
|
uint8 support,
|
||||||
|
string memory reason,
|
||||||
|
bytes memory params
|
||||||
|
) internal virtual override(Governor, GovernorPreventLateQuorum) returns (uint256) {
|
||||||
|
// added to run GovernorCountingSimple.spec
|
||||||
|
uint256 deltaWeight = super._castVote(proposalId, account, support, reason, params);
|
||||||
|
ghost_sum_vote_power_by_id[proposalId] += deltaWeight;
|
||||||
|
|
||||||
|
return deltaWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lateQuorumVoteExtension() public view virtual override returns (uint64) {
|
||||||
|
return super.lateQuorumVoteExtension();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLateQuorumVoteExtension(uint64 newVoteExtension) public virtual override onlyGovernance {
|
||||||
|
super.setLateQuorumVoteExtension(newVoteExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following functions are overrides required by Solidity added by OZ Wizard. //
|
||||||
|
|
||||||
|
function votingDelay() public pure override returns (uint256) {
|
||||||
|
return 1; // 1 block
|
||||||
|
}
|
||||||
|
|
||||||
|
function votingPeriod() public pure override returns (uint256) {
|
||||||
|
return 45818; // 1 week
|
||||||
|
}
|
||||||
|
|
||||||
|
function quorum(uint256 blockNumber)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
override(IGovernor, GovernorVotesQuorumFraction)
|
||||||
|
returns (uint256)
|
||||||
|
{
|
||||||
|
return super.quorum(blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) {
|
||||||
|
return super.state(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function propose(
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
string memory description
|
||||||
|
) public override(Governor, IGovernor) returns (uint256) {
|
||||||
|
return super.propose(targets, values, calldatas, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _execute(
|
||||||
|
uint256 proposalId,
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
bytes32 descriptionHash
|
||||||
|
) internal override(Governor, GovernorTimelockControl) {
|
||||||
|
super._execute(proposalId, targets, values, calldatas, descriptionHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _cancel(
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
bytes32 descriptionHash
|
||||||
|
) internal override(Governor, GovernorTimelockControl) returns (uint256) {
|
||||||
|
return super._cancel(targets, values, calldatas, descriptionHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) {
|
||||||
|
return super._executor();
|
||||||
|
}
|
||||||
|
|
||||||
|
function supportsInterface(bytes4 interfaceId)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
override(Governor, GovernorTimelockControl)
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
return super.supportsInterface(interfaceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
154
certora/harnesses/GovernorHarness.sol
Normal file
154
certora/harnesses/GovernorHarness.sol
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.2;
|
||||||
|
|
||||||
|
import "../munged/governance/Governor.sol";
|
||||||
|
import "../munged/governance/extensions/GovernorCountingSimple.sol";
|
||||||
|
import "../munged/governance/extensions/GovernorTimelockControl.sol";
|
||||||
|
import "../munged/governance/extensions/GovernorVotes.sol";
|
||||||
|
import "../munged/governance/extensions/GovernorVotesQuorumFraction.sol";
|
||||||
|
import "../munged/token/ERC20/extensions/ERC20Votes.sol";
|
||||||
|
|
||||||
|
contract GovernorHarness is
|
||||||
|
Governor,
|
||||||
|
GovernorCountingSimple,
|
||||||
|
GovernorVotes,
|
||||||
|
GovernorVotesQuorumFraction,
|
||||||
|
GovernorTimelockControl
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
IVotes _token,
|
||||||
|
TimelockController _timelock,
|
||||||
|
uint64 initialVoteExtension,
|
||||||
|
uint256 quorumNumeratorValue
|
||||||
|
)
|
||||||
|
Governor("Harness")
|
||||||
|
GovernorVotes(_token)
|
||||||
|
GovernorVotesQuorumFraction(quorumNumeratorValue)
|
||||||
|
GovernorTimelockControl(_timelock)
|
||||||
|
{}
|
||||||
|
|
||||||
|
mapping(uint256 => uint256) public ghost_sum_vote_power_by_id;
|
||||||
|
|
||||||
|
// Harness from GovernorCountingSimple //
|
||||||
|
|
||||||
|
function getAgainstVotes(uint256 proposalId) public view returns (uint256) {
|
||||||
|
ProposalVote storage proposalvote = _proposalVotes[proposalId];
|
||||||
|
return proposalvote.againstVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAbstainVotes(uint256 proposalId) public view returns (uint256) {
|
||||||
|
ProposalVote storage proposalvote = _proposalVotes[proposalId];
|
||||||
|
return proposalvote.abstainVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getForVotes(uint256 proposalId) public view returns (uint256) {
|
||||||
|
ProposalVote storage proposalvote = _proposalVotes[proposalId];
|
||||||
|
return proposalvote.forVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function quorumReached(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _quorumReached(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function voteSucceeded(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _voteSucceeded(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Harness from Governor //
|
||||||
|
|
||||||
|
function getExecutor() public view returns (address) {
|
||||||
|
return _executor();
|
||||||
|
}
|
||||||
|
|
||||||
|
function proposalProposer(uint256 proposalId) public view returns (address) {
|
||||||
|
return _proposalProposer(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isExecuted(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _proposals[proposalId].executed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCanceled(uint256 proposalId) public view returns (bool) {
|
||||||
|
return _proposals[proposalId].canceled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following functions are overrides required by Solidity added by Certora. //
|
||||||
|
|
||||||
|
function _castVote(
|
||||||
|
uint256 proposalId,
|
||||||
|
address account,
|
||||||
|
uint8 support,
|
||||||
|
string memory reason,
|
||||||
|
bytes memory params
|
||||||
|
) internal virtual override returns (uint256) {
|
||||||
|
// added to run GovernorCountingSimple.spec
|
||||||
|
uint256 deltaWeight = super._castVote(proposalId, account, support, reason, params);
|
||||||
|
ghost_sum_vote_power_by_id[proposalId] += deltaWeight;
|
||||||
|
|
||||||
|
return deltaWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following functions are overrides required by Solidity added by OZ Wizard. //
|
||||||
|
|
||||||
|
function votingDelay() public pure override returns (uint256) {
|
||||||
|
return 1; // 1 block
|
||||||
|
}
|
||||||
|
|
||||||
|
function votingPeriod() public pure override returns (uint256) {
|
||||||
|
return 45818; // 1 week
|
||||||
|
}
|
||||||
|
|
||||||
|
function quorum(uint256 blockNumber)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
override(IGovernor, GovernorVotesQuorumFraction)
|
||||||
|
returns (uint256)
|
||||||
|
{
|
||||||
|
return super.quorum(blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) {
|
||||||
|
return super.state(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function propose(
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
string memory description
|
||||||
|
) public override(Governor, IGovernor) returns (uint256) {
|
||||||
|
return super.propose(targets, values, calldatas, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _execute(
|
||||||
|
uint256 proposalId,
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
bytes32 descriptionHash
|
||||||
|
) internal override(Governor, GovernorTimelockControl) {
|
||||||
|
super._execute(proposalId, targets, values, calldatas, descriptionHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _cancel(
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
bytes32 descriptionHash
|
||||||
|
) internal override(Governor, GovernorTimelockControl) returns (uint256) {
|
||||||
|
return super._cancel(targets, values, calldatas, descriptionHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) {
|
||||||
|
return super._executor();
|
||||||
|
}
|
||||||
|
|
||||||
|
function supportsInterface(bytes4 interfaceId)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
override(Governor, GovernorTimelockControl)
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
return super.supportsInterface(interfaceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
certora/harnesses/IERC3156FlashBorrowerHarness.sol
Normal file
20
certora/harnesses/IERC3156FlashBorrowerHarness.sol
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC3156FlashBorrower.sol)
|
||||||
|
|
||||||
|
import "../munged/interfaces/IERC3156FlashBorrower.sol";
|
||||||
|
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
contract IERC3156FlashBorrowerHarness is IERC3156FlashBorrower {
|
||||||
|
bytes32 somethingToReturn;
|
||||||
|
|
||||||
|
function onFlashLoan(
|
||||||
|
address initiator,
|
||||||
|
address token,
|
||||||
|
uint256 amount,
|
||||||
|
uint256 fee,
|
||||||
|
bytes calldata data
|
||||||
|
) external override returns (bytes32) {
|
||||||
|
return somethingToReturn;
|
||||||
|
}
|
||||||
|
}
|
||||||
80
certora/harnesses/InitializableBasicHarness.sol
Normal file
80
certora/harnesses/InitializableBasicHarness.sol
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.2;
|
||||||
|
|
||||||
|
import "../munged/proxy/utils/Initializable.sol";
|
||||||
|
|
||||||
|
contract InitializableBasicHarness is Initializable {
|
||||||
|
uint256 public val;
|
||||||
|
uint256 public a;
|
||||||
|
uint256 public b;
|
||||||
|
|
||||||
|
modifier version1() {
|
||||||
|
require(_initialized == 1);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier versionN(uint8 n) {
|
||||||
|
require(_initialized == n);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initialize(
|
||||||
|
uint256 _val,
|
||||||
|
uint256 _a,
|
||||||
|
uint256 _b
|
||||||
|
) public initializer {
|
||||||
|
a = _a;
|
||||||
|
b = _b;
|
||||||
|
val = _val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reinitialize(
|
||||||
|
uint256 _val,
|
||||||
|
uint256 _a,
|
||||||
|
uint256 _b,
|
||||||
|
uint8 n
|
||||||
|
) public reinitializer(n) {
|
||||||
|
a = _a;
|
||||||
|
b = _b;
|
||||||
|
val = _val;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Versioned return functions for testing
|
||||||
|
|
||||||
|
function returnsV1() public view version1 returns (uint256) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function returnsVN(uint8 n) public view versionN(n) returns (uint256) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function returnsAV1() public view version1 returns (uint256) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
function returnsAVN(uint8 n) public view versionN(n) returns (uint256) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
function returnsBV1() public view version1 returns (uint256) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function returnsBVN(uint8 n) public view versionN(n) returns (uint256) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Harness //
|
||||||
|
function initialized() public view returns (uint8) {
|
||||||
|
return _initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializing() public view returns (bool) {
|
||||||
|
return _initializing;
|
||||||
|
}
|
||||||
|
|
||||||
|
function thisIsContract() public view returns (bool) {
|
||||||
|
return !Address.isContract(address(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
92
certora/harnesses/InitializableComplexHarness.sol
Normal file
92
certora/harnesses/InitializableComplexHarness.sol
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.2;
|
||||||
|
|
||||||
|
import "../munged/proxy/utils/Initializable.sol";
|
||||||
|
|
||||||
|
contract InitializableA is Initializable {
|
||||||
|
uint256 public a;
|
||||||
|
|
||||||
|
modifier version1() {
|
||||||
|
require(_initialized == 1);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier versionN(uint8 n) {
|
||||||
|
require(_initialized == n);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function __InitializableA_init(uint256 _a) internal onlyInitializing {
|
||||||
|
a = _a;
|
||||||
|
}
|
||||||
|
|
||||||
|
function returnsAV1() public view version1 returns (uint256) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
function returnsAVN(uint8 n) public view versionN(n) returns (uint256) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract InitializableB is Initializable, InitializableA {
|
||||||
|
uint256 public b;
|
||||||
|
|
||||||
|
function __InitializableB_init(uint256 _b) internal onlyInitializing {
|
||||||
|
b = _b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function returnsBV1() public view version1 returns (uint256) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function returnsBVN(uint8 n) public view versionN(n) returns (uint256) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract InitializableComplexHarness is Initializable, InitializableB {
|
||||||
|
uint256 public val;
|
||||||
|
|
||||||
|
function initialize(
|
||||||
|
uint256 _val,
|
||||||
|
uint256 _a,
|
||||||
|
uint256 _b
|
||||||
|
) public initializer {
|
||||||
|
val = _val;
|
||||||
|
__InitializableA_init(_a);
|
||||||
|
__InitializableB_init(_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reinitialize(
|
||||||
|
uint256 _val,
|
||||||
|
uint256 _a,
|
||||||
|
uint256 _b,
|
||||||
|
uint8 n
|
||||||
|
) public reinitializer(n) {
|
||||||
|
val = _val;
|
||||||
|
__InitializableA_init(_a);
|
||||||
|
__InitializableB_init(_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function returnsV1() public view version1 returns (uint256) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function returnsVN(uint8 n) public view versionN(n) returns (uint256) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Harness //
|
||||||
|
function initialized() public view returns (uint8) {
|
||||||
|
return _initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializing() public view returns (bool) {
|
||||||
|
return _initializing;
|
||||||
|
}
|
||||||
|
|
||||||
|
function thisIsContract() public view returns (bool) {
|
||||||
|
return !Address.isContract(address(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
12
certora/harnesses/TimelockControllerHarness.sol
Normal file
12
certora/harnesses/TimelockControllerHarness.sol
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import "../munged/governance/TimelockController.sol";
|
||||||
|
|
||||||
|
contract TimelockControllerHarness is TimelockController {
|
||||||
|
constructor(
|
||||||
|
uint256 minDelay,
|
||||||
|
address[] memory proposers,
|
||||||
|
address[] memory executors,
|
||||||
|
address admin
|
||||||
|
) TimelockController(minDelay, proposers, executors, admin) {}
|
||||||
|
}
|
||||||
@ -7,16 +7,29 @@ import "../munged/governance/extensions/GovernorVotes.sol";
|
|||||||
import "../munged/governance/extensions/GovernorVotesQuorumFraction.sol";
|
import "../munged/governance/extensions/GovernorVotesQuorumFraction.sol";
|
||||||
import "../munged/governance/extensions/GovernorTimelockControl.sol";
|
import "../munged/governance/extensions/GovernorTimelockControl.sol";
|
||||||
import "../munged/governance/extensions/GovernorProposalThreshold.sol";
|
import "../munged/governance/extensions/GovernorProposalThreshold.sol";
|
||||||
|
import "../munged/token/ERC20/extensions/ERC20Votes.sol";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Wizard options:
|
Wizard options:
|
||||||
ProposalThreshhold = 10
|
ProposalThreshhold = 10
|
||||||
ERC20Votes
|
ERC20Votes
|
||||||
TimelockController
|
TimelockController
|
||||||
*/
|
*/
|
||||||
|
|
||||||
contract WizardControlFirstPriority is Governor, GovernorProposalThreshold, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl {
|
contract WizardControlFirstPriority is
|
||||||
constructor(ERC20Votes _token, TimelockController _timelock, string memory name, uint256 quorumFraction)
|
Governor,
|
||||||
|
GovernorProposalThreshold,
|
||||||
|
GovernorCountingSimple,
|
||||||
|
GovernorVotes,
|
||||||
|
GovernorVotesQuorumFraction,
|
||||||
|
GovernorTimelockControl
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
ERC20Votes _token,
|
||||||
|
TimelockController _timelock,
|
||||||
|
string memory name,
|
||||||
|
uint256 quorumFraction
|
||||||
|
)
|
||||||
Governor(name)
|
Governor(name)
|
||||||
GovernorVotes(_token)
|
GovernorVotes(_token)
|
||||||
GovernorVotesQuorumFraction(quorumFraction)
|
GovernorVotesQuorumFraction(quorumFraction)
|
||||||
@ -28,7 +41,7 @@ contract WizardControlFirstPriority is Governor, GovernorProposalThreshold, Gove
|
|||||||
function isExecuted(uint256 proposalId) public view returns (bool) {
|
function isExecuted(uint256 proposalId) public view returns (bool) {
|
||||||
return _proposals[proposalId].executed;
|
return _proposals[proposalId].executed;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCanceled(uint256 proposalId) public view returns (bool) {
|
function isCanceled(uint256 proposalId) public view returns (bool) {
|
||||||
return _proposals[proposalId].canceled;
|
return _proposals[proposalId].canceled;
|
||||||
}
|
}
|
||||||
@ -46,35 +59,36 @@ contract WizardControlFirstPriority is Governor, GovernorProposalThreshold, Gove
|
|||||||
address account,
|
address account,
|
||||||
uint8 support,
|
uint8 support,
|
||||||
string memory reason
|
string memory reason
|
||||||
) internal override virtual returns (uint256) {
|
) internal virtual override returns (uint256) {
|
||||||
|
uint256 deltaWeight = super._castVote(proposalId, account, support, reason); //HARNESS
|
||||||
uint256 deltaWeight = super._castVote(proposalId, account, support, reason); //HARNESS
|
|
||||||
ghost_sum_vote_power_by_id[proposalId] += deltaWeight;
|
ghost_sum_vote_power_by_id[proposalId] += deltaWeight;
|
||||||
|
|
||||||
return deltaWeight;
|
return deltaWeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
function snapshot(uint256 proposalId) public view returns (uint64) {
|
function snapshot(uint256 proposalId) public view returns (uint64) {
|
||||||
return _proposals[proposalId].voteStart._deadline;
|
return _proposals[proposalId].voteStart._deadline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getExecutor() public view returns (address) {
|
||||||
function getExecutor() public view returns (address){
|
|
||||||
return _executor();
|
return _executor();
|
||||||
}
|
}
|
||||||
|
|
||||||
// original code, harnessed
|
// original code, harnessed
|
||||||
|
|
||||||
function votingDelay() public view override returns (uint256) { // HARNESS: pure -> view
|
function votingDelay() public view override returns (uint256) {
|
||||||
return _votingDelay; // HARNESS: parametric
|
// HARNESS: pure -> view
|
||||||
|
return _votingDelay; // HARNESS: parametric
|
||||||
}
|
}
|
||||||
|
|
||||||
function votingPeriod() public view override returns (uint256) { // HARNESS: pure -> view
|
function votingPeriod() public view override returns (uint256) {
|
||||||
return _votingPeriod; // HARNESS: parametric
|
// HARNESS: pure -> view
|
||||||
|
return _votingPeriod; // HARNESS: parametric
|
||||||
}
|
}
|
||||||
|
|
||||||
function proposalThreshold() public view override returns (uint256) { // HARNESS: pure -> view
|
function proposalThreshold() public view override returns (uint256) {
|
||||||
return _proposalThreshold; // HARNESS: parametric
|
// HARNESS: pure -> view
|
||||||
|
return _proposalThreshold; // HARNESS: parametric
|
||||||
}
|
}
|
||||||
|
|
||||||
// original code, not harnessed
|
// original code, not harnessed
|
||||||
@ -92,50 +106,45 @@ contract WizardControlFirstPriority is Governor, GovernorProposalThreshold, Gove
|
|||||||
function getVotes(address account, uint256 blockNumber)
|
function getVotes(address account, uint256 blockNumber)
|
||||||
public
|
public
|
||||||
view
|
view
|
||||||
override(IGovernor, GovernorVotes)
|
override(IGovernor, Governor)
|
||||||
returns (uint256)
|
returns (uint256)
|
||||||
{
|
{
|
||||||
return super.getVotes(account, blockNumber);
|
return super.getVotes(account, blockNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
function state(uint256 proposalId)
|
function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) {
|
||||||
public
|
|
||||||
view
|
|
||||||
override(Governor, GovernorTimelockControl)
|
|
||||||
returns (ProposalState)
|
|
||||||
{
|
|
||||||
return super.state(proposalId);
|
return super.state(proposalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function propose(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, string memory description)
|
function propose(
|
||||||
public
|
address[] memory targets,
|
||||||
override(Governor, GovernorProposalThreshold, IGovernor)
|
uint256[] memory values,
|
||||||
returns (uint256)
|
bytes[] memory calldatas,
|
||||||
{
|
string memory description
|
||||||
|
) public override(Governor, GovernorProposalThreshold, IGovernor) returns (uint256) {
|
||||||
return super.propose(targets, values, calldatas, description);
|
return super.propose(targets, values, calldatas, description);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _execute(uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash)
|
function _execute(
|
||||||
internal
|
uint256 proposalId,
|
||||||
override(Governor, GovernorTimelockControl)
|
address[] memory targets,
|
||||||
{
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
bytes32 descriptionHash
|
||||||
|
) internal override(Governor, GovernorTimelockControl) {
|
||||||
super._execute(proposalId, targets, values, calldatas, descriptionHash);
|
super._execute(proposalId, targets, values, calldatas, descriptionHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _cancel(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash)
|
function _cancel(
|
||||||
internal
|
address[] memory targets,
|
||||||
override(Governor, GovernorTimelockControl)
|
uint256[] memory values,
|
||||||
returns (uint256)
|
bytes[] memory calldatas,
|
||||||
{
|
bytes32 descriptionHash
|
||||||
|
) internal override(Governor, GovernorTimelockControl) returns (uint256) {
|
||||||
return super._cancel(targets, values, calldatas, descriptionHash);
|
return super._cancel(targets, values, calldatas, descriptionHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _executor()
|
function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) {
|
||||||
internal
|
|
||||||
view
|
|
||||||
override(Governor, GovernorTimelockControl)
|
|
||||||
returns (address)
|
|
||||||
{
|
|
||||||
return super._executor();
|
return super._executor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,15 +6,27 @@ import "../munged/governance/extensions/GovernorCountingSimple.sol";
|
|||||||
import "../munged/governance/extensions/GovernorVotes.sol";
|
import "../munged/governance/extensions/GovernorVotes.sol";
|
||||||
import "../munged/governance/extensions/GovernorVotesQuorumFraction.sol";
|
import "../munged/governance/extensions/GovernorVotesQuorumFraction.sol";
|
||||||
import "../munged/governance/extensions/GovernorTimelockCompound.sol";
|
import "../munged/governance/extensions/GovernorTimelockCompound.sol";
|
||||||
|
import "../munged/token/ERC20/extensions/ERC20Votes.sol";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Wizard options:
|
Wizard options:
|
||||||
ERC20Votes
|
ERC20Votes
|
||||||
TimelockCompound
|
TimelockCompound
|
||||||
*/
|
*/
|
||||||
|
|
||||||
contract WizardFirstTry is Governor, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockCompound {
|
contract WizardFirstTry is
|
||||||
constructor(ERC20Votes _token, ICompoundTimelock _timelock, string memory name, uint256 quorumFraction)
|
Governor,
|
||||||
|
GovernorCountingSimple,
|
||||||
|
GovernorVotes,
|
||||||
|
GovernorVotesQuorumFraction,
|
||||||
|
GovernorTimelockCompound
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
ERC20Votes _token,
|
||||||
|
ICompoundTimelock _timelock,
|
||||||
|
string memory name,
|
||||||
|
uint256 quorumFraction
|
||||||
|
)
|
||||||
Governor(name)
|
Governor(name)
|
||||||
GovernorVotes(_token)
|
GovernorVotes(_token)
|
||||||
GovernorVotesQuorumFraction(quorumFraction)
|
GovernorVotesQuorumFraction(quorumFraction)
|
||||||
@ -26,7 +38,7 @@ contract WizardFirstTry is Governor, GovernorCountingSimple, GovernorVotes, Gove
|
|||||||
function isExecuted(uint256 proposalId) public view returns (bool) {
|
function isExecuted(uint256 proposalId) public view returns (bool) {
|
||||||
return _proposals[proposalId].executed;
|
return _proposals[proposalId].executed;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCanceled(uint256 proposalId) public view returns (bool) {
|
function isCanceled(uint256 proposalId) public view returns (bool) {
|
||||||
return _proposals[proposalId].canceled;
|
return _proposals[proposalId].canceled;
|
||||||
}
|
}
|
||||||
@ -35,7 +47,7 @@ contract WizardFirstTry is Governor, GovernorCountingSimple, GovernorVotes, Gove
|
|||||||
return _proposals[proposalId].voteStart._deadline;
|
return _proposals[proposalId].voteStart._deadline;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExecutor() public view returns (address){
|
function getExecutor() public view returns (address) {
|
||||||
return _executor();
|
return _executor();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,22 +62,23 @@ contract WizardFirstTry is Governor, GovernorCountingSimple, GovernorVotes, Gove
|
|||||||
address account,
|
address account,
|
||||||
uint8 support,
|
uint8 support,
|
||||||
string memory reason
|
string memory reason
|
||||||
) internal override virtual returns (uint256) {
|
) internal virtual override returns (uint256) {
|
||||||
|
uint256 deltaWeight = super._castVote(proposalId, account, support, reason); //HARNESS
|
||||||
uint256 deltaWeight = super._castVote(proposalId, account, support, reason); //HARNESS
|
|
||||||
ghost_sum_vote_power_by_id[proposalId] += deltaWeight;
|
ghost_sum_vote_power_by_id[proposalId] += deltaWeight;
|
||||||
|
|
||||||
return deltaWeight;
|
return deltaWeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// original code, harnessed
|
// original code, harnessed
|
||||||
|
|
||||||
function votingDelay() public view override virtual returns (uint256) { // HARNESS: pure -> view
|
function votingDelay() public view virtual override returns (uint256) {
|
||||||
return _votingDelay; // HARNESS: parametric
|
// HARNESS: pure -> view
|
||||||
|
return _votingDelay; // HARNESS: parametric
|
||||||
}
|
}
|
||||||
|
|
||||||
function votingPeriod() public view override virtual returns (uint256) { // HARNESS: pure -> view
|
function votingPeriod() public view virtual override returns (uint256) {
|
||||||
return _votingPeriod; // HARNESS: parametric
|
// HARNESS: pure -> view
|
||||||
|
return _votingPeriod; // HARNESS: parametric
|
||||||
}
|
}
|
||||||
|
|
||||||
// original code, not harnessed
|
// original code, not harnessed
|
||||||
@ -83,7 +96,7 @@ contract WizardFirstTry is Governor, GovernorCountingSimple, GovernorVotes, Gove
|
|||||||
function getVotes(address account, uint256 blockNumber)
|
function getVotes(address account, uint256 blockNumber)
|
||||||
public
|
public
|
||||||
view
|
view
|
||||||
override(IGovernor, GovernorVotes)
|
override(IGovernor, Governor)
|
||||||
returns (uint256)
|
returns (uint256)
|
||||||
{
|
{
|
||||||
return super.getVotes(account, blockNumber);
|
return super.getVotes(account, blockNumber);
|
||||||
@ -98,35 +111,35 @@ contract WizardFirstTry is Governor, GovernorCountingSimple, GovernorVotes, Gove
|
|||||||
return super.state(proposalId);
|
return super.state(proposalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function propose(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, string memory description)
|
function propose(
|
||||||
public
|
address[] memory targets,
|
||||||
override(Governor, IGovernor)
|
uint256[] memory values,
|
||||||
returns (uint256)
|
bytes[] memory calldatas,
|
||||||
{
|
string memory description
|
||||||
|
) public override(Governor, IGovernor) returns (uint256) {
|
||||||
return super.propose(targets, values, calldatas, description);
|
return super.propose(targets, values, calldatas, description);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _execute(uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash)
|
function _execute(
|
||||||
internal
|
uint256 proposalId,
|
||||||
override(Governor, GovernorTimelockCompound)
|
address[] memory targets,
|
||||||
{
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
bytes32 descriptionHash
|
||||||
|
) internal override(Governor, GovernorTimelockCompound) {
|
||||||
super._execute(proposalId, targets, values, calldatas, descriptionHash);
|
super._execute(proposalId, targets, values, calldatas, descriptionHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _cancel(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash)
|
function _cancel(
|
||||||
internal
|
address[] memory targets,
|
||||||
override(Governor, GovernorTimelockCompound)
|
uint256[] memory values,
|
||||||
returns (uint256)
|
bytes[] memory calldatas,
|
||||||
{
|
bytes32 descriptionHash
|
||||||
|
) internal override(Governor, GovernorTimelockCompound) returns (uint256) {
|
||||||
return super._cancel(targets, values, calldatas, descriptionHash);
|
return super._cancel(targets, values, calldatas, descriptionHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _executor()
|
function _executor() internal view override(Governor, GovernorTimelockCompound) returns (address) {
|
||||||
internal
|
|
||||||
view
|
|
||||||
override(Governor, GovernorTimelockCompound)
|
|
||||||
returns (address)
|
|
||||||
{
|
|
||||||
return super._executor();
|
return super._executor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
certora/munged/.gitignore
vendored
2
certora/munged/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
*
|
|
||||||
!.gitignore
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
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"
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
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"
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
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"
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
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"
|
|
||||||
13
certora/scripts/noCI/Round1/WizardControlFirstPriority.sh
Normal file
13
certora/scripts/noCI/Round1/WizardControlFirstPriority.sh
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC20VotesHarness.sol certora/harnesses/WizardControlFirstPriority.sol \
|
||||||
|
--verify WizardControlFirstPriority:certora/specs/GovernorBase.spec \
|
||||||
|
--link WizardControlFirstPriority:token=ERC20VotesHarness \
|
||||||
|
--solc solc \
|
||||||
|
--optimistic_loop \
|
||||||
|
--disableLocalTypeChecking \
|
||||||
|
--settings -copyLoopUnroll=4 \
|
||||||
|
$@
|
||||||
13
certora/scripts/noCI/Round1/WizardFirstTry.sh
Normal file
13
certora/scripts/noCI/Round1/WizardFirstTry.sh
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC20VotesHarness.sol certora/harnesses/WizardFirstTry.sol \
|
||||||
|
--verify WizardFirstTry:certora/specs/GovernorBase.spec \
|
||||||
|
--link WizardFirstTry:token=ERC20VotesHarness \
|
||||||
|
--solc solc \
|
||||||
|
--optimistic_loop \
|
||||||
|
--disableLocalTypeChecking \
|
||||||
|
--settings -copyLoopUnroll=4 \
|
||||||
|
$@
|
||||||
37
certora/scripts/noCI/Round1/verifyGovernor.sh
Normal file
37
certora/scripts/noCI/Round1/verifyGovernor.sh
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
for contract in certora/harnesses/Wizard*.sol;
|
||||||
|
do
|
||||||
|
for spec in certora/specs/Governor*.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 solc \
|
||||||
|
--optimistic_loop \
|
||||||
|
--disableLocalTypeChecking \
|
||||||
|
--settings -copyLoopUnroll=4 \
|
||||||
|
--msg "checking $specFile on ${contractFile%.*}"
|
||||||
|
else
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC20VotesHarness.sol certora/harnesses/$contractFile \
|
||||||
|
--verify ${contractFile%.*}:certora/specs/$specFile "$@" \
|
||||||
|
--solc solc \
|
||||||
|
--optimistic_loop \
|
||||||
|
--disableLocalTypeChecking \
|
||||||
|
--settings -copyLoopUnroll=4 \
|
||||||
|
--msg "checking $specFile on ${contractFile%.*}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
34
certora/scripts/noCI/sanity/sanity.sh
Normal file
34
certora/scripts/noCI/sanity/sanity.sh
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
# 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 solc \
|
||||||
|
# --optimistic_loop \
|
||||||
|
# --settings -copyLoopUnroll=4 \
|
||||||
|
# --msg "checking sanity on ${file%.*}"
|
||||||
|
# done
|
||||||
|
|
||||||
|
# TimelockController
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/TimelockControllerHarness.sol \
|
||||||
|
--verify TimelockControllerHarness:certora/specs/sanity.spec \
|
||||||
|
--solc solc \
|
||||||
|
--optimistic_loop \
|
||||||
|
--msg "sanity and keccak check"
|
||||||
|
|
||||||
|
# Votes
|
||||||
|
# certoraRun \
|
||||||
|
# certora/harnesses/VotesHarness.sol \
|
||||||
|
# --verify VotesHarness:certora/specs/sanity.spec \
|
||||||
|
# --solc solc \
|
||||||
|
# --optimistic_loop \
|
||||||
|
# --settings -strictDecompiler=false,-assumeUnwindCond \
|
||||||
|
# --msg "sanityVotes"
|
||||||
16
certora/scripts/noCI/sanity/sanityGovernor.sh
Normal file
16
certora/scripts/noCI/sanity/sanityGovernor.sh
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
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 solc \
|
||||||
|
--optimistic_loop \
|
||||||
|
--settings -copyLoopUnroll=4 \
|
||||||
|
--msg "checking sanity on ${file%.*}"
|
||||||
|
done
|
||||||
16
certora/scripts/noCI/sanity/sanityTokens.sh
Normal file
16
certora/scripts/noCI/sanity/sanityTokens.sh
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
for f in certora/harnesses/ERC20{Votes,Permit,Wrapper}Harness.sol
|
||||||
|
do
|
||||||
|
echo "Processing $f"
|
||||||
|
file=$(basename $f)
|
||||||
|
echo ${file%.*}
|
||||||
|
certoraRun certora/harnesses/$file \
|
||||||
|
--verify ${file%.*}:certora/specs/sanity.spec "$@" \
|
||||||
|
--solc solc \
|
||||||
|
--optimistic_loop \
|
||||||
|
--settings -copyLoopUnroll=4,-strictDecompiler=false \
|
||||||
|
--msg "checking sanity on ${file%.*}"
|
||||||
|
done
|
||||||
9
certora/scripts/passes/verifyAccessControl.sh
Normal file
9
certora/scripts/passes/verifyAccessControl.sh
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/AccessControlHarness.sol \
|
||||||
|
--verify AccessControlHarness:certora/specs/AccessControl.spec \
|
||||||
|
--optimistic_loop \
|
||||||
|
$@
|
||||||
10
certora/scripts/passes/verifyERC1155.sh
Normal file
10
certora/scripts/passes/verifyERC1155.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC1155/ERC1155Harness.sol \
|
||||||
|
--verify ERC1155Harness:certora/specs/ERC1155.spec \
|
||||||
|
--optimistic_loop \
|
||||||
|
--loop_iter 3 \
|
||||||
|
$@
|
||||||
10
certora/scripts/passes/verifyERC1155Burnable.sh
Normal file
10
certora/scripts/passes/verifyERC1155Burnable.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC1155/ERC1155BurnableHarness.sol \
|
||||||
|
--verify ERC1155BurnableHarness:certora/specs/ERC1155Burnable.spec \
|
||||||
|
--optimistic_loop \
|
||||||
|
--loop_iter 3 \
|
||||||
|
$@
|
||||||
10
certora/scripts/passes/verifyERC1155Pausable.sh
Normal file
10
certora/scripts/passes/verifyERC1155Pausable.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC1155/ERC1155PausableHarness.sol \
|
||||||
|
--verify ERC1155PausableHarness:certora/specs/ERC1155Pausable.spec \
|
||||||
|
--optimistic_loop \
|
||||||
|
--loop_iter 3 \
|
||||||
|
$@
|
||||||
10
certora/scripts/passes/verifyERC1155Supply.sh
Normal file
10
certora/scripts/passes/verifyERC1155Supply.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC1155/ERC1155SupplyHarness.sol \
|
||||||
|
--verify ERC1155SupplyHarness:certora/specs/ERC1155Supply.spec \
|
||||||
|
--optimistic_loop \
|
||||||
|
--loop_iter 3 \
|
||||||
|
$@
|
||||||
10
certora/scripts/passes/verifyERC20FlashMint.sh
Normal file
10
certora/scripts/passes/verifyERC20FlashMint.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC20FlashMintHarness.sol \
|
||||||
|
certora/harnesses/IERC3156FlashBorrowerHarness.sol \
|
||||||
|
--verify ERC20FlashMintHarness:certora/specs/ERC20FlashMint.spec \
|
||||||
|
--optimistic_loop \
|
||||||
|
$@
|
||||||
10
certora/scripts/passes/verifyERC20Votes.sh
Normal file
10
certora/scripts/passes/verifyERC20Votes.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC20VotesHarness.sol \
|
||||||
|
--verify ERC20VotesHarness:certora/specs/ERC20Votes.spec \
|
||||||
|
--optimistic_loop \
|
||||||
|
--settings -copyLoopUnroll=4 \
|
||||||
|
$@
|
||||||
9
certora/scripts/passes/verifyERC20Wrapper.sh
Normal file
9
certora/scripts/passes/verifyERC20Wrapper.sh
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC20WrapperHarness.sol certora/harnesses/ERC20PermitHarness.sol \
|
||||||
|
--verify ERC20WrapperHarness:certora/specs/ERC20Wrapper.spec \
|
||||||
|
--optimistic_loop \
|
||||||
|
$@
|
||||||
11
certora/scripts/passes/verifyERC721Votes.sh
Normal file
11
certora/scripts/passes/verifyERC721Votes.sh
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC721VotesHarness.sol certora/munged/utils/Checkpoints.sol \
|
||||||
|
--verify ERC721VotesHarness:certora/specs/ERC721Votes.spec \
|
||||||
|
--optimistic_loop \
|
||||||
|
--disableLocalTypeChecking \
|
||||||
|
--settings -copyLoopUnroll=4 \
|
||||||
|
$@
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC20VotesHarness.sol certora/harnesses/GovernorFullHarness.sol certora/munged/governance/TimelockController.sol \
|
||||||
|
--verify GovernorFullHarness:certora/specs/GovernorPreventLateQuorum.spec \
|
||||||
|
--link GovernorFullHarness:token=ERC20VotesHarness \
|
||||||
|
--optimistic_loop \
|
||||||
|
--optimistic_hashing \
|
||||||
|
--rule deadlineCantBeUnextended \
|
||||||
|
--loop_iter 1 \
|
||||||
|
$@
|
||||||
14
certora/scripts/passes/verifyGPLQ_proposalInOneState.sh
Normal file
14
certora/scripts/passes/verifyGPLQ_proposalInOneState.sh
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC20VotesHarness.sol certora/harnesses/GovernorFullHarness.sol certora/munged/governance/TimelockController.sol \
|
||||||
|
--verify GovernorFullHarness:certora/specs/GovernorPreventLateQuorum.spec \
|
||||||
|
--link GovernorFullHarness:token=ERC20VotesHarness \
|
||||||
|
--optimistic_loop \
|
||||||
|
--optimistic_hashing \
|
||||||
|
--rule proposalInOneState \
|
||||||
|
--loop_iter 1 \
|
||||||
|
--settings -t=1000 \
|
||||||
|
$@
|
||||||
13
certora/scripts/passes/verifyGPLQ_quorumReachedEffect.sh
Normal file
13
certora/scripts/passes/verifyGPLQ_quorumReachedEffect.sh
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC20VotesHarness.sol certora/harnesses/GovernorFullHarness.sol certora/munged/governance/TimelockController.sol \
|
||||||
|
--verify GovernorFullHarness:certora/specs/GovernorPreventLateQuorum.spec \
|
||||||
|
--link GovernorFullHarness:token=ERC20VotesHarness \
|
||||||
|
--optimistic_loop \
|
||||||
|
--optimistic_hashing \
|
||||||
|
--rule quorumReachedEffect \
|
||||||
|
--loop_iter 1 \
|
||||||
|
$@
|
||||||
16
certora/scripts/passes/verifyGovernor.sh
Normal file
16
certora/scripts/passes/verifyGovernor.sh
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
# Changed: GovernorHarness missing
|
||||||
|
#
|
||||||
|
# Note: rule `immutableFieldsAfterProposalCreation` fails with
|
||||||
|
# GovernorFullHarness because of late quorum changing the vote's end.
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC20VotesHarness.sol certora/harnesses/GovernorHarness.sol \
|
||||||
|
--verify GovernorHarness:certora/specs/GovernorBase.spec \
|
||||||
|
--link GovernorHarness:token=ERC20VotesHarness \
|
||||||
|
--optimistic_loop \
|
||||||
|
--optimistic_hashing \
|
||||||
|
--settings -copyLoopUnroll=4 \
|
||||||
|
$@
|
||||||
13
certora/scripts/passes/verifyGovernorCountingSimple.sh
Normal file
13
certora/scripts/passes/verifyGovernorCountingSimple.sh
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
# Changed: GovernorBasicHarness missing
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC20VotesHarness.sol certora/harnesses/GovernorHarness.sol \
|
||||||
|
--verify GovernorHarness:certora/specs/GovernorCountingSimple.spec \
|
||||||
|
--link GovernorHarness:token=ERC20VotesHarness \
|
||||||
|
--optimistic_loop \
|
||||||
|
--optimistic_hashing \
|
||||||
|
--settings -copyLoopUnroll=4 \
|
||||||
|
$@
|
||||||
11
certora/scripts/passes/verifyGovernorPreventLateQuorum.sh
Normal file
11
certora/scripts/passes/verifyGovernorPreventLateQuorum.sh
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC20VotesHarness.sol certora/harnesses/GovernorFullHarness.sol certora/munged/governance/TimelockController.sol \
|
||||||
|
--verify GovernorFullHarness:certora/specs/GovernorPreventLateQuorum.spec \
|
||||||
|
--link GovernorFullHarness:token=ERC20VotesHarness \
|
||||||
|
--optimistic_loop \
|
||||||
|
--optimistic_hashing \
|
||||||
|
$@
|
||||||
10
certora/scripts/passes/verifyInitializable.sh
Normal file
10
certora/scripts/passes/verifyInitializable.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/InitializableComplexHarness.sol \
|
||||||
|
--verify InitializableComplexHarness:certora/specs/Initializable.spec \
|
||||||
|
--optimistic_loop \
|
||||||
|
--loop_iter 3 \
|
||||||
|
$@
|
||||||
11
certora/scripts/passes/verifyTimelock.sh
Normal file
11
certora/scripts/passes/verifyTimelock.sh
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/TimelockControllerHarness.sol certora/harnesses/AccessControlHarness.sol \
|
||||||
|
--verify TimelockControllerHarness:certora/specs/TimelockController.spec \
|
||||||
|
--optimistic_loop \
|
||||||
|
--loop_iter 3 \
|
||||||
|
--optimistic_hashing \
|
||||||
|
$@
|
||||||
@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
#!/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
|
|
||||||
10
certora/scripts/verifyERC20.sh
Normal file
10
certora/scripts/verifyERC20.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
certoraRun \
|
||||||
|
certora/harnesses/ERC20PermitHarness.sol \
|
||||||
|
--verify ERC20PermitHarness:certora/specs/ERC20.spec \
|
||||||
|
--solc solc \
|
||||||
|
--optimistic_loop \
|
||||||
|
$@
|
||||||
133
certora/specs/AccessControl.spec
Normal file
133
certora/specs/AccessControl.spec
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
methods {
|
||||||
|
hasRole(bytes32, address) returns(bool) envfree
|
||||||
|
getRoleAdmin(bytes32) returns(bytes32) envfree
|
||||||
|
|
||||||
|
grantRole(bytes32, address)
|
||||||
|
revokeRole(bytes32, address)
|
||||||
|
renounceRole(bytes32, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: only grantRole can grant a role │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule onlyGrantCanGrant(env e, bytes32 role, address account) {
|
||||||
|
require !hasRole(role, account);
|
||||||
|
|
||||||
|
method f; calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert hasRole(role, account) => f.selector == grantRole(bytes32, address).selector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: only revokeRole and renounceRole can grant a role │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule onlyRevokeAndRenounceCanRevoke(env e, bytes32 role, address account) {
|
||||||
|
require hasRole(role, account);
|
||||||
|
|
||||||
|
method f; calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert !hasRole(role, account) => (f.selector == revokeRole(bytes32, address).selector || f.selector == renounceRole(bytes32, address).selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: only an account with admin role can call grantRole │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule onlyAdminCanGrant(env e, bytes32 role, address account) {
|
||||||
|
bool isAdmin = hasRole(getRoleAdmin(role), e.msg.sender);
|
||||||
|
|
||||||
|
grantRole@withrevert(e, role, account);
|
||||||
|
|
||||||
|
assert !lastReverted => isAdmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: only an account with admin role can call revokeRole │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule onlyAdminCanRevoke(env e, bytes32 role, address account) {
|
||||||
|
bool isAdmin = hasRole(getRoleAdmin(role), e.msg.sender);
|
||||||
|
|
||||||
|
revokeRole@withrevert(e, role, account);
|
||||||
|
|
||||||
|
assert !lastReverted => isAdmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: only the affected account can revoke │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule onlyUserCanRenounce(env e, bytes32 role, address account) {
|
||||||
|
renounceRole@withrevert(e, role, account);
|
||||||
|
assert !lastReverted => account == e.msg.sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: grantRole only affects the specified user/role combo │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule grantRoleEffect(env e) {
|
||||||
|
bytes32 role1; bytes32 role2;
|
||||||
|
address account1; address account2;
|
||||||
|
|
||||||
|
bool hasRoleBefore = hasRole(role1, account1);
|
||||||
|
grantRole(e, role2, account2);
|
||||||
|
bool hasRoleAfter = hasRole(role1, account1);
|
||||||
|
|
||||||
|
assert (
|
||||||
|
hasRoleBefore != hasRoleAfter
|
||||||
|
) => (
|
||||||
|
hasRoleAfter == true && role1 == role2 && account1 == account2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: revokeRole only affects the specified user/role combo │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule revokeRoleEffect(env e) {
|
||||||
|
bytes32 role1; bytes32 role2;
|
||||||
|
address account1; address account2;
|
||||||
|
|
||||||
|
bool hasRoleBefore = hasRole(role1, account1);
|
||||||
|
revokeRole(e, role2, account2);
|
||||||
|
bool hasRoleAfter = hasRole(role1, account1);
|
||||||
|
|
||||||
|
assert (
|
||||||
|
hasRoleBefore != hasRoleAfter
|
||||||
|
) => (
|
||||||
|
hasRoleAfter == false && role1 == role2 && account1 == account2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: renounceRole only affects the specified user/role combo │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule renounceRoleEffect(env e) {
|
||||||
|
bytes32 role1; bytes32 role2;
|
||||||
|
address account1; address account2;
|
||||||
|
|
||||||
|
bool hasRoleBefore = hasRole(role1, account1);
|
||||||
|
renounceRole(e, role2, account2);
|
||||||
|
bool hasRoleAfter = hasRole(role1, account1);
|
||||||
|
|
||||||
|
assert (
|
||||||
|
hasRoleBefore != hasRoleAfter
|
||||||
|
) => (
|
||||||
|
hasRoleAfter == false && role1 == role2 && account1 == account2
|
||||||
|
);
|
||||||
|
}
|
||||||
877
certora/specs/ERC1155.spec
Normal file
877
certora/specs/ERC1155.spec
Normal file
@ -0,0 +1,877 @@
|
|||||||
|
methods {
|
||||||
|
isApprovedForAll(address, address) returns(bool) envfree
|
||||||
|
balanceOf(address, uint256) returns(uint256) envfree
|
||||||
|
balanceOfBatch(address[], uint256[]) returns(uint256[]) envfree
|
||||||
|
_doSafeBatchTransferAcceptanceCheck(address, address, address, uint256[], uint256[], bytes) envfree
|
||||||
|
_doSafeTransferAcceptanceCheck(address, address, address, uint256, uint256, bytes) envfree
|
||||||
|
|
||||||
|
setApprovalForAll(address, bool)
|
||||||
|
safeTransferFrom(address, address, uint256, uint256, bytes)
|
||||||
|
safeBatchTransferFrom(address, address, uint256[], uint256[], bytes)
|
||||||
|
mint(address, uint256, uint256, bytes)
|
||||||
|
mintBatch(address, uint256[], uint256[], bytes)
|
||||||
|
burn(address, uint256, uint256)
|
||||||
|
burnBatch(address, uint256[], uint256[])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// Approval (4/4)
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Function $f, which is not setApprovalForAll, should not change approval
|
||||||
|
rule unexpectedAllowanceChange(method f, env e) filtered { f -> f.selector != setApprovalForAll(address, bool).selector } {
|
||||||
|
address account; address operator;
|
||||||
|
bool approveBefore = isApprovedForAll(account, operator);
|
||||||
|
|
||||||
|
calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
bool approveAfter = isApprovedForAll(account, operator);
|
||||||
|
|
||||||
|
assert approveBefore == approveAfter, "You couldn't get king's approval this way!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// approval can be changed only by owner
|
||||||
|
rule onlyOwnerCanApprove(env e){
|
||||||
|
address owner; address operator; bool approved;
|
||||||
|
|
||||||
|
bool aprovalBefore = isApprovedForAll(owner, operator);
|
||||||
|
|
||||||
|
setApprovalForAll(e, operator, approved);
|
||||||
|
|
||||||
|
bool aprovalAfter = isApprovedForAll(owner, operator);
|
||||||
|
|
||||||
|
assert aprovalBefore != aprovalAfter => owner == e.msg.sender, "There should be only one owner";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Check that isApprovedForAll() revertes in planned scenarios and no more.
|
||||||
|
rule approvalRevertCases(env e){
|
||||||
|
address account; address operator;
|
||||||
|
isApprovedForAll@withrevert(account, operator);
|
||||||
|
assert !lastReverted, "Houston, we have a problem";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// setApproval changes only one approval
|
||||||
|
rule onlyOneAllowanceChange(method f, env e) {
|
||||||
|
address owner; address operator; address user;
|
||||||
|
bool approved;
|
||||||
|
|
||||||
|
bool userApproveBefore = isApprovedForAll(owner, user);
|
||||||
|
|
||||||
|
setApprovalForAll(e, operator, approved);
|
||||||
|
|
||||||
|
bool userApproveAfter = isApprovedForAll(owner, user);
|
||||||
|
|
||||||
|
assert userApproveBefore != userApproveAfter => (e.msg.sender == owner && operator == user), "Imposter!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// Balance (3/3)
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Function $f, which is not one of transfers, mints and burns, should not change balanceOf of a user
|
||||||
|
rule unexpectedBalanceChange(method f, env e)
|
||||||
|
filtered { f -> f.selector != safeTransferFrom(address, address, uint256, uint256, bytes).selector
|
||||||
|
&& f.selector != safeBatchTransferFrom(address, address, uint256[], uint256[], bytes).selector
|
||||||
|
&& f.selector != mint(address, uint256, uint256, bytes).selector
|
||||||
|
&& f.selector != mintBatch(address, uint256[], uint256[], bytes).selector
|
||||||
|
&& f.selector != burn(address, uint256, uint256).selector
|
||||||
|
&& f.selector != burnBatch(address, uint256[], uint256[]).selector } {
|
||||||
|
address from; uint256 id;
|
||||||
|
uint256 balanceBefore = balanceOf(from, id);
|
||||||
|
|
||||||
|
calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
uint256 balanceAfter = balanceOf(from, id);
|
||||||
|
|
||||||
|
assert balanceBefore == balanceAfter, "How you dare to take my money?";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Check that `balanceOf()` revertes in planned scenarios and no more (only if `account` is 0)
|
||||||
|
rule balanceOfRevertCases(env e){
|
||||||
|
address account; uint256 id;
|
||||||
|
balanceOf@withrevert(account, id);
|
||||||
|
assert lastReverted => account == 0, "Houston, we have a problem";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Check that `balanceOfBatch()` revertes in planned scenarios and no more (only if at least one of `account`s is 0)
|
||||||
|
rule balanceOfBatchRevertCases(env e){
|
||||||
|
address[] accounts; uint256[] ids;
|
||||||
|
address account1; address account2; address account3;
|
||||||
|
uint256 id1; uint256 id2; uint256 id3;
|
||||||
|
|
||||||
|
require accounts.length == 3;
|
||||||
|
require ids.length == 3;
|
||||||
|
|
||||||
|
require accounts[0] == account1; require accounts[1] == account2; require accounts[2] == account3;
|
||||||
|
|
||||||
|
balanceOfBatch@withrevert(accounts, ids);
|
||||||
|
assert lastReverted => (account1 == 0 || account2 == 0 || account3 == 0), "Houston, we have a problem";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// Transfer (13/13)
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// transfer additivity
|
||||||
|
rule transferAdditivity(env e){
|
||||||
|
address from; address to; uint256 id; bytes data;
|
||||||
|
uint256 amount; uint256 amount1; uint256 amount2;
|
||||||
|
require amount == amount1 + amount2;
|
||||||
|
|
||||||
|
storage initialStorage = lastStorage;
|
||||||
|
|
||||||
|
safeTransferFrom(e, from, to, id, amount, data);
|
||||||
|
|
||||||
|
uint256 balanceAfterSingleTransaction = balanceOf(to, id);
|
||||||
|
|
||||||
|
safeTransferFrom(e, from, to, id, amount1, data) at initialStorage;
|
||||||
|
safeTransferFrom(e, from, to, id, amount2, data);
|
||||||
|
|
||||||
|
uint256 balanceAfterDoubleTransaction = balanceOf(to, id);
|
||||||
|
|
||||||
|
assert balanceAfterSingleTransaction == balanceAfterDoubleTransaction, "Not additive";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// safeTransferFrom updates `from` and `to` balances
|
||||||
|
rule transferCorrectness(env e){
|
||||||
|
address from; address to; uint256 id; uint256 amount; bytes data;
|
||||||
|
|
||||||
|
require to != from;
|
||||||
|
|
||||||
|
uint256 fromBalanceBefore = balanceOf(from, id);
|
||||||
|
uint256 toBalanceBefore = balanceOf(to, id);
|
||||||
|
|
||||||
|
safeTransferFrom(e, from, to, id, amount, data);
|
||||||
|
|
||||||
|
uint256 fromBalanceAfter = balanceOf(from, id);
|
||||||
|
uint256 toBalanceAfter = balanceOf(to, id);
|
||||||
|
|
||||||
|
assert fromBalanceBefore == fromBalanceAfter + amount, "Something wet wrong";
|
||||||
|
assert toBalanceBefore == toBalanceAfter - amount, "Something wet wrong";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// safeBatchTransferFrom updates `from` and `to` balances)
|
||||||
|
rule transferBatchCorrectness(env e){
|
||||||
|
address from; address to; uint256[] ids; uint256[] amounts; bytes data;
|
||||||
|
uint256 idToCheck1; uint256 amountToCheck1;
|
||||||
|
uint256 idToCheck2; uint256 amountToCheck2;
|
||||||
|
uint256 idToCheck3; uint256 amountToCheck3;
|
||||||
|
|
||||||
|
require to != from;
|
||||||
|
require idToCheck1 != idToCheck2 && idToCheck3 != idToCheck2 && idToCheck1 != idToCheck3;
|
||||||
|
|
||||||
|
require ids.length == 3;
|
||||||
|
require amounts.length == 3;
|
||||||
|
require ids[0] == idToCheck1; require amounts[0] == amountToCheck1;
|
||||||
|
require ids[1] == idToCheck2; require amounts[1] == amountToCheck2;
|
||||||
|
require ids[2] == idToCheck3; require amounts[2] == amountToCheck3;
|
||||||
|
|
||||||
|
uint256 fromBalanceBefore1 = balanceOf(from, idToCheck1);
|
||||||
|
uint256 fromBalanceBefore2 = balanceOf(from, idToCheck2);
|
||||||
|
uint256 fromBalanceBefore3 = balanceOf(from, idToCheck3);
|
||||||
|
|
||||||
|
uint256 toBalanceBefore1 = balanceOf(to, idToCheck1);
|
||||||
|
uint256 toBalanceBefore2 = balanceOf(to, idToCheck2);
|
||||||
|
uint256 toBalanceBefore3 = balanceOf(to, idToCheck3);
|
||||||
|
|
||||||
|
safeBatchTransferFrom(e, from, to, ids, amounts, data);
|
||||||
|
|
||||||
|
uint256 fromBalanceAfter1 = balanceOf(from, idToCheck1);
|
||||||
|
uint256 fromBalanceAfter2 = balanceOf(from, idToCheck2);
|
||||||
|
uint256 fromBalanceAfter3 = balanceOf(from, idToCheck3);
|
||||||
|
|
||||||
|
uint256 toBalanceAfter1 = balanceOf(to, idToCheck1);
|
||||||
|
uint256 toBalanceAfter2 = balanceOf(to, idToCheck2);
|
||||||
|
uint256 toBalanceAfter3 = balanceOf(to, idToCheck3);
|
||||||
|
|
||||||
|
assert (fromBalanceBefore1 == fromBalanceAfter1 + amountToCheck1)
|
||||||
|
&& (fromBalanceBefore2 == fromBalanceAfter2 + amountToCheck2)
|
||||||
|
&& (fromBalanceBefore3 == fromBalanceAfter3 + amountToCheck3), "Something wet wrong";
|
||||||
|
assert (toBalanceBefore1 == toBalanceAfter1 - amountToCheck1)
|
||||||
|
&& (toBalanceBefore2 == toBalanceAfter2 - amountToCheck2)
|
||||||
|
&& (toBalanceBefore3 == toBalanceAfter3 - amountToCheck3), "Something wet wrong";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// cannot transfer more than `from` balance (safeTransferFrom version)
|
||||||
|
rule cannotTransferMoreSingle(env e){
|
||||||
|
address from; address to; uint256 id; uint256 amount; bytes data;
|
||||||
|
uint256 balanceBefore = balanceOf(from, id);
|
||||||
|
|
||||||
|
safeTransferFrom@withrevert(e, from, to, id, amount, data);
|
||||||
|
|
||||||
|
assert amount > balanceBefore => lastReverted, "Achtung! Scammer!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// cannot transfer more than allowed (safeBatchTransferFrom version)
|
||||||
|
rule cannotTransferMoreBatch(env e){
|
||||||
|
address from; address to; uint256[] ids; uint256[] amounts; bytes data;
|
||||||
|
uint256 idToCheck1; uint256 amountToCheck1;
|
||||||
|
uint256 idToCheck2; uint256 amountToCheck2;
|
||||||
|
uint256 idToCheck3; uint256 amountToCheck3;
|
||||||
|
|
||||||
|
uint256 balanceBefore1 = balanceOf(from, idToCheck1);
|
||||||
|
uint256 balanceBefore2 = balanceOf(from, idToCheck2);
|
||||||
|
uint256 balanceBefore3 = balanceOf(from, idToCheck3);
|
||||||
|
|
||||||
|
require ids.length == 3;
|
||||||
|
require amounts.length == 3;
|
||||||
|
require ids[0] == idToCheck1; require amounts[0] == amountToCheck1;
|
||||||
|
require ids[1] == idToCheck2; require amounts[1] == amountToCheck2;
|
||||||
|
require ids[2] == idToCheck3; require amounts[2] == amountToCheck3;
|
||||||
|
|
||||||
|
safeBatchTransferFrom@withrevert(e, from, to, ids, amounts, data);
|
||||||
|
|
||||||
|
assert (amountToCheck1 > balanceBefore1 || amountToCheck2 > balanceBefore2 || amountToCheck3 > balanceBefore3) => lastReverted, "Achtung! Scammer!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Sender calling safeTransferFrom should only reduce 'from' balance and not other's if sending amount is greater than 0
|
||||||
|
rule transferBalanceReduceEffect(env e){
|
||||||
|
address from; address to; address other;
|
||||||
|
uint256 id; uint256 amount;
|
||||||
|
bytes data;
|
||||||
|
|
||||||
|
require other != to;
|
||||||
|
|
||||||
|
uint256 otherBalanceBefore = balanceOf(other, id);
|
||||||
|
|
||||||
|
safeTransferFrom(e, from, to, id, amount, data);
|
||||||
|
|
||||||
|
uint256 otherBalanceAfter = balanceOf(other, id);
|
||||||
|
|
||||||
|
assert from != other => otherBalanceBefore == otherBalanceAfter, "Don't touch my money!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Sender calling safeTransferFrom should only increase 'to' balance and not other's if sending amount is greater than 0
|
||||||
|
rule transferBalanceIncreaseEffect(env e){
|
||||||
|
address from; address to; address other;
|
||||||
|
uint256 id; uint256 amount;
|
||||||
|
bytes data;
|
||||||
|
|
||||||
|
require from != other;
|
||||||
|
|
||||||
|
uint256 otherBalanceBefore = balanceOf(other, id);
|
||||||
|
|
||||||
|
safeTransferFrom(e, from, to, id, amount, data);
|
||||||
|
|
||||||
|
uint256 otherBalanceAfter = balanceOf(other, id);
|
||||||
|
|
||||||
|
assert other != to => otherBalanceBefore == otherBalanceAfter, "Don't touch my money!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Sender calling safeTransferFrom should only reduce 'from' balance and not other's if sending amount is greater than 0
|
||||||
|
rule transferBatchBalanceFromEffect(env e){
|
||||||
|
address from; address to; address other;
|
||||||
|
uint256[] ids; uint256[] amounts;
|
||||||
|
uint256 id1; uint256 amount1; uint256 id2; uint256 amount2; uint256 id3; uint256 amount3;
|
||||||
|
bytes data;
|
||||||
|
|
||||||
|
require other != to;
|
||||||
|
|
||||||
|
uint256 otherBalanceBefore1 = balanceOf(other, id1);
|
||||||
|
uint256 otherBalanceBefore2 = balanceOf(other, id2);
|
||||||
|
uint256 otherBalanceBefore3 = balanceOf(other, id3);
|
||||||
|
|
||||||
|
safeBatchTransferFrom(e, from, to, ids, amounts, data);
|
||||||
|
|
||||||
|
uint256 otherBalanceAfter1 = balanceOf(other, id1);
|
||||||
|
uint256 otherBalanceAfter2 = balanceOf(other, id2);
|
||||||
|
uint256 otherBalanceAfter3 = balanceOf(other, id3);
|
||||||
|
|
||||||
|
assert from != other => (otherBalanceBefore1 == otherBalanceAfter1
|
||||||
|
&& otherBalanceBefore2 == otherBalanceAfter2
|
||||||
|
&& otherBalanceBefore3 == otherBalanceAfter3), "Don't touch my money!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Sender calling safeBatchTransferFrom should only increase 'to' balance and not other's if sending amount is greater than 0
|
||||||
|
rule transferBatchBalanceToEffect(env e){
|
||||||
|
address from; address to; address other;
|
||||||
|
uint256[] ids; uint256[] amounts;
|
||||||
|
uint256 id1; uint256 amount1; uint256 id2; uint256 amount2; uint256 id3; uint256 amount3;
|
||||||
|
bytes data;
|
||||||
|
|
||||||
|
require other != from;
|
||||||
|
|
||||||
|
uint256 otherBalanceBefore1 = balanceOf(other, id1);
|
||||||
|
uint256 otherBalanceBefore2 = balanceOf(other, id2);
|
||||||
|
uint256 otherBalanceBefore3 = balanceOf(other, id3);
|
||||||
|
|
||||||
|
safeBatchTransferFrom(e, from, to, ids, amounts, data);
|
||||||
|
|
||||||
|
uint256 otherBalanceAfter1 = balanceOf(other, id1);
|
||||||
|
uint256 otherBalanceAfter2 = balanceOf(other, id2);
|
||||||
|
uint256 otherBalanceAfter3 = balanceOf(other, id3);
|
||||||
|
|
||||||
|
assert other != to => (otherBalanceBefore1 == otherBalanceAfter1
|
||||||
|
&& otherBalanceBefore2 == otherBalanceAfter2
|
||||||
|
&& otherBalanceBefore3 == otherBalanceAfter3), "Don't touch my money!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// cannot transfer without approval (safeTransferFrom version)
|
||||||
|
rule noTransferForNotApproved(env e) {
|
||||||
|
address from; address operator;
|
||||||
|
address to; uint256 id; uint256 amount; bytes data;
|
||||||
|
|
||||||
|
require from != e.msg.sender;
|
||||||
|
|
||||||
|
bool approve = isApprovedForAll(from, e.msg.sender);
|
||||||
|
|
||||||
|
safeTransferFrom@withrevert(e, from, to, id, amount, data);
|
||||||
|
|
||||||
|
assert !approve => lastReverted, "You don't have king's approval!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// cannot transfer without approval (safeBatchTransferFrom version)
|
||||||
|
rule noTransferBatchForNotApproved(env e) {
|
||||||
|
address from; address operator; address to;
|
||||||
|
bytes data;
|
||||||
|
uint256[] ids; uint256[] amounts;
|
||||||
|
|
||||||
|
require from != e.msg.sender;
|
||||||
|
|
||||||
|
bool approve = isApprovedForAll(from, e.msg.sender);
|
||||||
|
|
||||||
|
safeBatchTransferFrom@withrevert(e, from, to, ids, amounts, data);
|
||||||
|
|
||||||
|
assert !approve => lastReverted, "You don't have king's approval!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// safeTransferFrom doesn't affect any approval
|
||||||
|
rule noTransferEffectOnApproval(env e){
|
||||||
|
address from; address to;
|
||||||
|
address owner; address operator;
|
||||||
|
uint256 id; uint256 amount;
|
||||||
|
bytes data;
|
||||||
|
|
||||||
|
bool approveBefore = isApprovedForAll(owner, operator);
|
||||||
|
|
||||||
|
safeTransferFrom(e, from, to, id, amount, data);
|
||||||
|
|
||||||
|
bool approveAfter = isApprovedForAll(owner, operator);
|
||||||
|
|
||||||
|
assert approveBefore == approveAfter, "Something was effected";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// safeTransferFrom doesn't affect any approval
|
||||||
|
rule noTransferBatchEffectOnApproval(env e){
|
||||||
|
address from; address to;
|
||||||
|
address owner; address operator;
|
||||||
|
uint256[] ids; uint256[] amounts;
|
||||||
|
bytes data;
|
||||||
|
|
||||||
|
bool approveBefore = isApprovedForAll(owner, operator);
|
||||||
|
|
||||||
|
safeBatchTransferFrom(e, from, to, ids, amounts, data);
|
||||||
|
|
||||||
|
bool approveAfter = isApprovedForAll(owner, operator);
|
||||||
|
|
||||||
|
assert approveBefore == approveAfter, "Something was effected";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// Mint (7/9)
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Additivity of mint: mint(a); mint(b) has same effect as mint(a+b)
|
||||||
|
rule mintAdditivity(env e){
|
||||||
|
address to; uint256 id; uint256 amount; uint256 amount1; uint256 amount2; bytes data;
|
||||||
|
require amount == amount1 + amount2;
|
||||||
|
|
||||||
|
storage initialStorage = lastStorage;
|
||||||
|
|
||||||
|
mint(e, to, id, amount, data);
|
||||||
|
|
||||||
|
uint256 balanceAfterSingleTransaction = balanceOf(to, id);
|
||||||
|
|
||||||
|
mint(e, to, id, amount1, data) at initialStorage;
|
||||||
|
mint(e, to, id, amount2, data);
|
||||||
|
|
||||||
|
uint256 balanceAfterDoubleTransaction = balanceOf(to, id);
|
||||||
|
|
||||||
|
assert balanceAfterSingleTransaction == balanceAfterDoubleTransaction, "Not additive";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Check that `mint()` revertes in planned scenario(s) (only if `to` is 0)
|
||||||
|
rule mintRevertCases(env e){
|
||||||
|
address to; uint256 id; uint256 amount; bytes data;
|
||||||
|
|
||||||
|
mint@withrevert(e, to, id, amount, data);
|
||||||
|
|
||||||
|
assert to == 0 => lastReverted, "Should revert";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Check that `mintBatch()` revertes in planned scenario(s) (only if `to` is 0 or arrays have different length)
|
||||||
|
rule mintBatchRevertCases(env e){
|
||||||
|
address to; uint256[] ids; uint256[] amounts; bytes data;
|
||||||
|
|
||||||
|
require ids.length < 1000000000;
|
||||||
|
require amounts.length < 1000000000;
|
||||||
|
|
||||||
|
mintBatch@withrevert(e, to, ids, amounts, data);
|
||||||
|
|
||||||
|
assert (ids.length != amounts.length || to == 0) => lastReverted, "Should revert";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Check that mint updates `to` balance correctly
|
||||||
|
rule mintCorrectWork(env e){
|
||||||
|
address to; uint256 id; uint256 amount; bytes data;
|
||||||
|
|
||||||
|
uint256 otherBalanceBefore = balanceOf(to, id);
|
||||||
|
|
||||||
|
mint(e, to, id, amount, data);
|
||||||
|
|
||||||
|
uint256 otherBalanceAfter = balanceOf(to, id);
|
||||||
|
|
||||||
|
assert otherBalanceBefore == otherBalanceAfter - amount, "Something is wrong";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// check that mintBatch updates `bootcamp participantsfrom` balance correctly
|
||||||
|
rule mintBatchCorrectWork(env e){
|
||||||
|
address to;
|
||||||
|
uint256 id1; uint256 id2; uint256 id3;
|
||||||
|
uint256 amount1; uint256 amount2; uint256 amount3;
|
||||||
|
uint256[] ids; uint256[] amounts;
|
||||||
|
bytes data;
|
||||||
|
|
||||||
|
require ids.length == 3;
|
||||||
|
require amounts.length == 3;
|
||||||
|
|
||||||
|
require id1 != id2 && id2 != id3 && id3 != id1;
|
||||||
|
require ids[0] == id1; require ids[1] == id2; require ids[2] == id3;
|
||||||
|
require amounts[0] == amount1; require amounts[1] == amount2; require amounts[2] == amount3;
|
||||||
|
|
||||||
|
uint256 otherBalanceBefore1 = balanceOf(to, id1);
|
||||||
|
uint256 otherBalanceBefore2 = balanceOf(to, id2);
|
||||||
|
uint256 otherBalanceBefore3 = balanceOf(to, id3);
|
||||||
|
|
||||||
|
mintBatch(e, to, ids, amounts, data);
|
||||||
|
|
||||||
|
uint256 otherBalanceAfter1 = balanceOf(to, id1);
|
||||||
|
uint256 otherBalanceAfter2 = balanceOf(to, id2);
|
||||||
|
uint256 otherBalanceAfter3 = balanceOf(to, id3);
|
||||||
|
|
||||||
|
assert otherBalanceBefore1 == otherBalanceAfter1 - amount1
|
||||||
|
&& otherBalanceBefore2 == otherBalanceAfter2 - amount2
|
||||||
|
&& otherBalanceBefore3 == otherBalanceAfter3 - amount3
|
||||||
|
, "Something is wrong";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// The user cannot mint more than max_uint256
|
||||||
|
rule cantMintMoreSingle(env e){
|
||||||
|
address to; uint256 id; uint256 amount; bytes data;
|
||||||
|
|
||||||
|
require to_mathint(balanceOf(to, id) + amount) > max_uint256;
|
||||||
|
|
||||||
|
mint@withrevert(e, to, id, amount, data);
|
||||||
|
|
||||||
|
assert lastReverted, "Don't be too greedy!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// the user cannot mint more than max_uint256 (batch version)
|
||||||
|
rule cantMintMoreBatch(env e){
|
||||||
|
address to; bytes data;
|
||||||
|
uint256 id1; uint256 id2; uint256 id3;
|
||||||
|
uint256 amount1; uint256 amount2; uint256 amount3;
|
||||||
|
uint256[] ids; uint256[] amounts;
|
||||||
|
|
||||||
|
require ids.length == 3;
|
||||||
|
require amounts.length == 3;
|
||||||
|
|
||||||
|
require ids[0] == id1; require ids[1] == id2; require ids[2] == id3;
|
||||||
|
require amounts[0] == amount1; require amounts[1] == amount2; require amounts[2] == amount3;
|
||||||
|
|
||||||
|
require to_mathint(balanceOf(to, id1) + amount1) > max_uint256
|
||||||
|
|| to_mathint(balanceOf(to, id2) + amount2) > max_uint256
|
||||||
|
|| to_mathint(balanceOf(to, id3) + amount3) > max_uint256;
|
||||||
|
|
||||||
|
mintBatch@withrevert(e, to, ids, amounts, data);
|
||||||
|
|
||||||
|
assert lastReverted, "Don't be too greedy!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// `mint()` changes only `to` balance
|
||||||
|
rule cantMintOtherBalances(env e){
|
||||||
|
address to; uint256 id; uint256 amount; bytes data;
|
||||||
|
address other;
|
||||||
|
|
||||||
|
uint256 otherBalanceBefore = balanceOf(other, id);
|
||||||
|
|
||||||
|
mint(e, to, id, amount, data);
|
||||||
|
|
||||||
|
uint256 otherBalanceAfter = balanceOf(other, id);
|
||||||
|
|
||||||
|
assert other != to => otherBalanceBefore == otherBalanceAfter, "I like to see your money disappearing";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// mintBatch changes only `to` balance
|
||||||
|
rule cantMintBatchOtherBalances(env e){
|
||||||
|
address to;
|
||||||
|
uint256 id1; uint256 id2; uint256 id3;
|
||||||
|
uint256[] ids; uint256[] amounts;
|
||||||
|
address other;
|
||||||
|
bytes data;
|
||||||
|
|
||||||
|
uint256 otherBalanceBefore1 = balanceOf(other, id1);
|
||||||
|
uint256 otherBalanceBefore2 = balanceOf(other, id2);
|
||||||
|
uint256 otherBalanceBefore3 = balanceOf(other, id3);
|
||||||
|
|
||||||
|
mintBatch(e, to, ids, amounts, data);
|
||||||
|
|
||||||
|
uint256 otherBalanceAfter1 = balanceOf(other, id1);
|
||||||
|
uint256 otherBalanceAfter2 = balanceOf(other, id2);
|
||||||
|
uint256 otherBalanceAfter3 = balanceOf(other, id3);
|
||||||
|
|
||||||
|
assert other != to => (otherBalanceBefore1 == otherBalanceAfter1
|
||||||
|
&& otherBalanceBefore2 == otherBalanceAfter2
|
||||||
|
&& otherBalanceBefore3 == otherBalanceAfter3)
|
||||||
|
, "I like to see your money disappearing";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// Burn (9/9)
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Additivity of burn: burn(a); burn(b) has same effect as burn(a+b)
|
||||||
|
rule burnAdditivity(env e){
|
||||||
|
address from; uint256 id; uint256 amount; uint256 amount1; uint256 amount2;
|
||||||
|
require amount == amount1 + amount2;
|
||||||
|
|
||||||
|
storage initialStorage = lastStorage;
|
||||||
|
|
||||||
|
burn(e, from, id, amount);
|
||||||
|
|
||||||
|
uint256 balanceAfterSingleTransaction = balanceOf(from, id);
|
||||||
|
|
||||||
|
burn(e, from, id, amount1) at initialStorage;
|
||||||
|
burn(e, from, id, amount2);
|
||||||
|
|
||||||
|
uint256 balanceAfterDoubleTransaction = balanceOf(from, id);
|
||||||
|
|
||||||
|
assert balanceAfterSingleTransaction == balanceAfterDoubleTransaction, "Not additive";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Check that `burn()` revertes in planned scenario(s) (if `from` is 0)
|
||||||
|
rule burnRevertCases(env e){
|
||||||
|
address from; uint256 id; uint256 amount;
|
||||||
|
|
||||||
|
burn@withrevert(e, from, id, amount);
|
||||||
|
|
||||||
|
assert from == 0 => lastReverted, "Should revert";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Check that `balanceOf()` revertes in planned scenario(s) (if `from` is 0 or arrays have different length)
|
||||||
|
rule burnBatchRevertCases(env e){
|
||||||
|
address from; uint256[] ids; uint256[] amounts;
|
||||||
|
|
||||||
|
require ids.length < 1000000000;
|
||||||
|
require amounts.length < 1000000000;
|
||||||
|
|
||||||
|
burnBatch@withrevert(e, from, ids, amounts);
|
||||||
|
|
||||||
|
assert (from == 0 || ids.length != amounts.length) => lastReverted, "Should revert";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// check that burn updates `from` balance correctly
|
||||||
|
rule burnCorrectWork(env e){
|
||||||
|
address from; uint256 id; uint256 amount;
|
||||||
|
|
||||||
|
uint256 otherBalanceBefore = balanceOf(from, id);
|
||||||
|
|
||||||
|
burn(e, from, id, amount);
|
||||||
|
|
||||||
|
uint256 otherBalanceAfter = balanceOf(from, id);
|
||||||
|
|
||||||
|
assert otherBalanceBefore == otherBalanceAfter + amount, "Something is wrong";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// check that burnBatch updates `from` balance correctly
|
||||||
|
rule burnBatchCorrectWork(env e){
|
||||||
|
address from;
|
||||||
|
uint256 id1; uint256 id2; uint256 id3;
|
||||||
|
uint256 amount1; uint256 amount2; uint256 amount3;
|
||||||
|
uint256[] ids; uint256[] amounts;
|
||||||
|
|
||||||
|
require ids.length == 3;
|
||||||
|
|
||||||
|
require id1 != id2 && id2 != id3 && id3 != id1;
|
||||||
|
require ids[0] == id1; require ids[1] == id2; require ids[2] == id3;
|
||||||
|
require amounts[0] == amount1; require amounts[1] == amount2; require amounts[2] == amount3;
|
||||||
|
|
||||||
|
uint256 otherBalanceBefore1 = balanceOf(from, id1);
|
||||||
|
uint256 otherBalanceBefore2 = balanceOf(from, id2);
|
||||||
|
uint256 otherBalanceBefore3 = balanceOf(from, id3);
|
||||||
|
|
||||||
|
burnBatch(e, from, ids, amounts);
|
||||||
|
|
||||||
|
uint256 otherBalanceAfter1 = balanceOf(from, id1);
|
||||||
|
uint256 otherBalanceAfter2 = balanceOf(from, id2);
|
||||||
|
uint256 otherBalanceAfter3 = balanceOf(from, id3);
|
||||||
|
|
||||||
|
assert otherBalanceBefore1 == otherBalanceAfter1 + amount1
|
||||||
|
&& otherBalanceBefore2 == otherBalanceAfter2 + amount2
|
||||||
|
&& otherBalanceBefore3 == otherBalanceAfter3 + amount3
|
||||||
|
, "Something is wrong";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// the user cannot burn more than they have
|
||||||
|
rule cantBurnMoreSingle(env e){
|
||||||
|
address from; uint256 id; uint256 amount;
|
||||||
|
|
||||||
|
require to_mathint(balanceOf(from, id) - amount) < 0;
|
||||||
|
|
||||||
|
burn@withrevert(e, from, id, amount);
|
||||||
|
|
||||||
|
assert lastReverted, "Don't be too greedy!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// the user cannot burn more than they have (batch version)
|
||||||
|
rule cantBurnMoreBatch(env e){
|
||||||
|
address from;
|
||||||
|
uint256 id1; uint256 id2; uint256 id3;
|
||||||
|
uint256 amount1; uint256 amount2; uint256 amount3;
|
||||||
|
uint256[] ids; uint256[] amounts;
|
||||||
|
|
||||||
|
require ids.length == 3;
|
||||||
|
|
||||||
|
require ids[0] == id1; require ids[1] == id2; require ids[2] == id3;
|
||||||
|
require amounts[0] == amount1; require amounts[1] == amount2; require amounts[2] == amount3;
|
||||||
|
|
||||||
|
require to_mathint(balanceOf(from, id1) - amount1) < 0
|
||||||
|
|| to_mathint(balanceOf(from, id2) - amount2) < 0
|
||||||
|
|| to_mathint(balanceOf(from, id3) - amount3) < 0 ;
|
||||||
|
|
||||||
|
burnBatch@withrevert(e, from, ids, amounts);
|
||||||
|
|
||||||
|
assert lastReverted, "Don't be too greedy!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// burn changes only `from` balance
|
||||||
|
rule cantBurnOtherBalances(env e){
|
||||||
|
address from; uint256 id; uint256 amount;
|
||||||
|
address other;
|
||||||
|
|
||||||
|
uint256 otherBalanceBefore = balanceOf(other, id);
|
||||||
|
|
||||||
|
burn(e, from, id, amount);
|
||||||
|
|
||||||
|
uint256 otherBalanceAfter = balanceOf(other, id);
|
||||||
|
|
||||||
|
assert other != from => otherBalanceBefore == otherBalanceAfter, "I like to see your money disappearing";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// burnBatch changes only `from` balance
|
||||||
|
rule cantBurnBatchOtherBalances(env e){
|
||||||
|
address from;
|
||||||
|
uint256 id1; uint256 id2; uint256 id3;
|
||||||
|
uint256 amount1; uint256 amount2; uint256 amount3;
|
||||||
|
uint256[] ids; uint256[] amounts;
|
||||||
|
address other;
|
||||||
|
|
||||||
|
uint256 otherBalanceBefore1 = balanceOf(other, id1);
|
||||||
|
uint256 otherBalanceBefore2 = balanceOf(other, id2);
|
||||||
|
uint256 otherBalanceBefore3 = balanceOf(other, id3);
|
||||||
|
|
||||||
|
burnBatch(e, from, ids, amounts);
|
||||||
|
|
||||||
|
uint256 otherBalanceAfter1 = balanceOf(other, id1);
|
||||||
|
uint256 otherBalanceAfter2 = balanceOf(other, id2);
|
||||||
|
uint256 otherBalanceAfter3 = balanceOf(other, id3);
|
||||||
|
|
||||||
|
assert other != from => (otherBalanceBefore1 == otherBalanceAfter1
|
||||||
|
&& otherBalanceBefore2 == otherBalanceAfter2
|
||||||
|
&& otherBalanceBefore3 == otherBalanceAfter3)
|
||||||
|
, "I like to see your money disappearing";
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// The rules below were added to this base ERC1155 spec as part of a later
|
||||||
|
// project with OpenZeppelin covering various ERC1155 extensions.
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// The result of transferring a single token must be equivalent whether done
|
||||||
|
/// via safeTransferFrom or safeBatchTransferFrom.
|
||||||
|
rule singleTokenSafeTransferFromSafeBatchTransferFromEquivalence {
|
||||||
|
storage beforeTransfer = lastStorage;
|
||||||
|
env e;
|
||||||
|
|
||||||
|
address holder; address recipient;
|
||||||
|
uint256 token; uint256 transferAmount; bytes data;
|
||||||
|
uint256[] tokens; uint256[] transferAmounts;
|
||||||
|
|
||||||
|
mathint holderStartingBalance = balanceOf(holder, token);
|
||||||
|
mathint recipientStartingBalance = balanceOf(recipient, token);
|
||||||
|
|
||||||
|
require tokens.length == 1; require transferAmounts.length == 1;
|
||||||
|
require tokens[0] == token; require transferAmounts[0] == transferAmount;
|
||||||
|
|
||||||
|
// transferring via safeTransferFrom
|
||||||
|
safeTransferFrom(e, holder, recipient, token, transferAmount, data) at beforeTransfer;
|
||||||
|
mathint holderSafeTransferFromBalanceChange = holderStartingBalance - balanceOf(holder, token);
|
||||||
|
mathint recipientSafeTransferFromBalanceChange = balanceOf(recipient, token) - recipientStartingBalance;
|
||||||
|
|
||||||
|
// transferring via safeBatchTransferFrom
|
||||||
|
safeBatchTransferFrom(e, holder, recipient, tokens, transferAmounts, data) at beforeTransfer;
|
||||||
|
mathint holderSafeBatchTransferFromBalanceChange = holderStartingBalance - balanceOf(holder, token);
|
||||||
|
mathint recipientSafeBatchTransferFromBalanceChange = balanceOf(recipient, token) - recipientStartingBalance;
|
||||||
|
|
||||||
|
assert holderSafeTransferFromBalanceChange == holderSafeBatchTransferFromBalanceChange
|
||||||
|
&& recipientSafeTransferFromBalanceChange == recipientSafeBatchTransferFromBalanceChange,
|
||||||
|
"Transferring a single token via safeTransferFrom or safeBatchTransferFrom must be equivalent";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The results of transferring multiple tokens must be equivalent whether done
|
||||||
|
/// separately via safeTransferFrom or together via safeBatchTransferFrom.
|
||||||
|
rule multipleTokenSafeTransferFromSafeBatchTransferFromEquivalence {
|
||||||
|
storage beforeTransfers = lastStorage;
|
||||||
|
env e;
|
||||||
|
|
||||||
|
address holder; address recipient; bytes data;
|
||||||
|
uint256 tokenA; uint256 tokenB; uint256 tokenC;
|
||||||
|
uint256 transferAmountA; uint256 transferAmountB; uint256 transferAmountC;
|
||||||
|
uint256[] tokens; uint256[] transferAmounts;
|
||||||
|
|
||||||
|
mathint holderStartingBalanceA = balanceOf(holder, tokenA);
|
||||||
|
mathint holderStartingBalanceB = balanceOf(holder, tokenB);
|
||||||
|
mathint holderStartingBalanceC = balanceOf(holder, tokenC);
|
||||||
|
mathint recipientStartingBalanceA = balanceOf(recipient, tokenA);
|
||||||
|
mathint recipientStartingBalanceB = balanceOf(recipient, tokenB);
|
||||||
|
mathint recipientStartingBalanceC = balanceOf(recipient, tokenC);
|
||||||
|
|
||||||
|
require tokens.length == 3; require transferAmounts.length == 3;
|
||||||
|
require tokens[0] == tokenA; require transferAmounts[0] == transferAmountA;
|
||||||
|
require tokens[1] == tokenB; require transferAmounts[1] == transferAmountB;
|
||||||
|
require tokens[2] == tokenC; require transferAmounts[2] == transferAmountC;
|
||||||
|
|
||||||
|
// transferring via safeTransferFrom
|
||||||
|
safeTransferFrom(e, holder, recipient, tokenA, transferAmountA, data) at beforeTransfers;
|
||||||
|
safeTransferFrom(e, holder, recipient, tokenB, transferAmountB, data);
|
||||||
|
safeTransferFrom(e, holder, recipient, tokenC, transferAmountC, data);
|
||||||
|
mathint holderSafeTransferFromBalanceChangeA = holderStartingBalanceA - balanceOf(holder, tokenA);
|
||||||
|
mathint holderSafeTransferFromBalanceChangeB = holderStartingBalanceB - balanceOf(holder, tokenB);
|
||||||
|
mathint holderSafeTransferFromBalanceChangeC = holderStartingBalanceC - balanceOf(holder, tokenC);
|
||||||
|
mathint recipientSafeTransferFromBalanceChangeA = balanceOf(recipient, tokenA) - recipientStartingBalanceA;
|
||||||
|
mathint recipientSafeTransferFromBalanceChangeB = balanceOf(recipient, tokenB) - recipientStartingBalanceB;
|
||||||
|
mathint recipientSafeTransferFromBalanceChangeC = balanceOf(recipient, tokenC) - recipientStartingBalanceC;
|
||||||
|
|
||||||
|
// transferring via safeBatchTransferFrom
|
||||||
|
safeBatchTransferFrom(e, holder, recipient, tokens, transferAmounts, data) at beforeTransfers;
|
||||||
|
mathint holderSafeBatchTransferFromBalanceChangeA = holderStartingBalanceA - balanceOf(holder, tokenA);
|
||||||
|
mathint holderSafeBatchTransferFromBalanceChangeB = holderStartingBalanceB - balanceOf(holder, tokenB);
|
||||||
|
mathint holderSafeBatchTransferFromBalanceChangeC = holderStartingBalanceC - balanceOf(holder, tokenC);
|
||||||
|
mathint recipientSafeBatchTransferFromBalanceChangeA = balanceOf(recipient, tokenA) - recipientStartingBalanceA;
|
||||||
|
mathint recipientSafeBatchTransferFromBalanceChangeB = balanceOf(recipient, tokenB) - recipientStartingBalanceB;
|
||||||
|
mathint recipientSafeBatchTransferFromBalanceChangeC = balanceOf(recipient, tokenC) - recipientStartingBalanceC;
|
||||||
|
|
||||||
|
assert holderSafeTransferFromBalanceChangeA == holderSafeBatchTransferFromBalanceChangeA
|
||||||
|
&& holderSafeTransferFromBalanceChangeB == holderSafeBatchTransferFromBalanceChangeB
|
||||||
|
&& holderSafeTransferFromBalanceChangeC == holderSafeBatchTransferFromBalanceChangeC
|
||||||
|
&& recipientSafeTransferFromBalanceChangeA == recipientSafeBatchTransferFromBalanceChangeA
|
||||||
|
&& recipientSafeTransferFromBalanceChangeB == recipientSafeBatchTransferFromBalanceChangeB
|
||||||
|
&& recipientSafeTransferFromBalanceChangeC == recipientSafeBatchTransferFromBalanceChangeC,
|
||||||
|
"Transferring multiple tokens via safeTransferFrom or safeBatchTransferFrom must be equivalent";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If transfer methods do not revert, the input arrays must be the same length.
|
||||||
|
rule transfersHaveSameLengthInputArrays {
|
||||||
|
env e;
|
||||||
|
|
||||||
|
address recipient; bytes data;
|
||||||
|
uint256[] tokens; uint256[] transferAmounts;
|
||||||
|
uint max_int = 0xffffffffffffffffffffffffffffffff;
|
||||||
|
|
||||||
|
require tokens.length >= 0 && tokens.length <= max_int;
|
||||||
|
require transferAmounts.length >= 0 && transferAmounts.length <= max_int;
|
||||||
|
|
||||||
|
safeBatchTransferFrom(e, _, recipient, tokens, transferAmounts, data);
|
||||||
|
|
||||||
|
uint256 tokensLength = tokens.length;
|
||||||
|
uint256 transferAmountsLength = transferAmounts.length;
|
||||||
|
|
||||||
|
assert tokens.length == transferAmounts.length,
|
||||||
|
"If transfer methods do not revert, the input arrays must be the same length";
|
||||||
|
}
|
||||||
187
certora/specs/ERC1155Burnable.spec
Normal file
187
certora/specs/ERC1155Burnable.spec
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
//// ## Verification of `ERC1155Burnable`
|
||||||
|
////
|
||||||
|
//// `ERC1155Burnable` extends the `ERC1155` functionality by wrapping the internal
|
||||||
|
//// methods `_burn` and `_burnBatch` in the public methods `burn` and `burnBatch`,
|
||||||
|
//// adding a requirement that the caller of either method be the account holding
|
||||||
|
//// the tokens or approved to act on that account's behalf.
|
||||||
|
////
|
||||||
|
//// ### Assumptions and Simplifications
|
||||||
|
////
|
||||||
|
//// - No changes made using the harness
|
||||||
|
////
|
||||||
|
//// ### Properties
|
||||||
|
|
||||||
|
methods {
|
||||||
|
balanceOf(address, uint256) returns uint256 envfree
|
||||||
|
isApprovedForAll(address,address) returns bool envfree
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If a method call reduces account balances, the caller must be either the
|
||||||
|
/// holder of the account or approved to act on the holder's behalf.
|
||||||
|
rule onlyHolderOrApprovedCanReduceBalance(method f)
|
||||||
|
{
|
||||||
|
address holder; uint256 token; uint256 amount;
|
||||||
|
uint256 balanceBefore = balanceOf(holder, token);
|
||||||
|
|
||||||
|
env e; calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
uint256 balanceAfter = balanceOf(holder, token);
|
||||||
|
|
||||||
|
assert balanceAfter < balanceBefore => e.msg.sender == holder || isApprovedForAll(holder, e.msg.sender),
|
||||||
|
"An account balance may only be reduced by the holder or a holder-approved agent";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Burning a larger amount of a token must reduce that token's balance more
|
||||||
|
/// than burning a smaller amount.
|
||||||
|
/// n.b. This rule holds for `burnBatch` as well due to rules establishing
|
||||||
|
/// appropriate equivalence between `burn` and `burnBatch` methods.
|
||||||
|
rule burnAmountProportionalToBalanceReduction {
|
||||||
|
storage beforeBurn = lastStorage;
|
||||||
|
env e;
|
||||||
|
|
||||||
|
address holder; uint256 token;
|
||||||
|
mathint startingBalance = balanceOf(holder, token);
|
||||||
|
uint256 smallBurn; uint256 largeBurn;
|
||||||
|
require smallBurn < largeBurn;
|
||||||
|
|
||||||
|
// smaller burn amount
|
||||||
|
burn(e, holder, token, smallBurn) at beforeBurn;
|
||||||
|
mathint smallBurnBalanceChange = startingBalance - balanceOf(holder, token);
|
||||||
|
|
||||||
|
// larger burn amount
|
||||||
|
burn(e, holder, token, largeBurn) at beforeBurn;
|
||||||
|
mathint largeBurnBalanceChange = startingBalance - balanceOf(holder, token);
|
||||||
|
|
||||||
|
assert smallBurnBalanceChange < largeBurnBalanceChange,
|
||||||
|
"A larger burn must lead to a larger decrease in balance";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Two sequential burns must be equivalent to a single burn of the sum of their
|
||||||
|
/// amounts.
|
||||||
|
/// This rule holds for `burnBatch` as well due to rules establishing
|
||||||
|
/// appropriate equivalence between `burn` and `burnBatch` methods.
|
||||||
|
rule sequentialBurnsEquivalentToSingleBurnOfSum {
|
||||||
|
storage beforeBurns = lastStorage;
|
||||||
|
env e;
|
||||||
|
|
||||||
|
address holder; uint256 token;
|
||||||
|
mathint startingBalance = balanceOf(holder, token);
|
||||||
|
uint256 firstBurn; uint256 secondBurn; uint256 sumBurn;
|
||||||
|
require sumBurn == firstBurn + secondBurn;
|
||||||
|
|
||||||
|
// sequential burns
|
||||||
|
burn(e, holder, token, firstBurn) at beforeBurns;
|
||||||
|
burn(e, holder, token, secondBurn);
|
||||||
|
mathint sequentialBurnsBalanceChange = startingBalance - balanceOf(holder, token);
|
||||||
|
|
||||||
|
// burn of sum of sequential burns
|
||||||
|
burn(e, holder, token, sumBurn) at beforeBurns;
|
||||||
|
mathint sumBurnBalanceChange = startingBalance - balanceOf(holder, token);
|
||||||
|
|
||||||
|
assert sequentialBurnsBalanceChange == sumBurnBalanceChange,
|
||||||
|
"Sequential burns must be equivalent to a burn of their sum";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The result of burning a single token must be equivalent whether done via
|
||||||
|
/// burn or burnBatch.
|
||||||
|
/// @title Single token `burn` / `burnBatch` equivalence
|
||||||
|
rule singleTokenBurnBurnBatchEquivalence {
|
||||||
|
storage beforeBurn = lastStorage;
|
||||||
|
env e;
|
||||||
|
|
||||||
|
address holder;
|
||||||
|
uint256 token; uint256 burnAmount;
|
||||||
|
uint256[] tokens; uint256[] burnAmounts;
|
||||||
|
|
||||||
|
mathint startingBalance = balanceOf(holder, token);
|
||||||
|
|
||||||
|
require tokens.length == 1; require burnAmounts.length == 1;
|
||||||
|
require tokens[0] == token; require burnAmounts[0] == burnAmount;
|
||||||
|
|
||||||
|
// burning via burn
|
||||||
|
burn(e, holder, token, burnAmount) at beforeBurn;
|
||||||
|
mathint burnBalanceChange = startingBalance - balanceOf(holder, token);
|
||||||
|
|
||||||
|
// burning via burnBatch
|
||||||
|
burnBatch(e, holder, tokens, burnAmounts) at beforeBurn;
|
||||||
|
mathint burnBatchBalanceChange = startingBalance - balanceOf(holder, token);
|
||||||
|
|
||||||
|
assert burnBalanceChange == burnBatchBalanceChange,
|
||||||
|
"Burning a single token via burn or burnBatch must be equivalent";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The results of burning multiple tokens must be equivalent whether done
|
||||||
|
/// separately via `burn` or together via `burnBatch`.
|
||||||
|
/// @title Multiple token `burn` / `burnBatch` equivalence
|
||||||
|
rule multipleTokenBurnBurnBatchEquivalence {
|
||||||
|
storage beforeBurns = lastStorage;
|
||||||
|
env e;
|
||||||
|
|
||||||
|
address holder;
|
||||||
|
uint256 tokenA; uint256 tokenB; uint256 tokenC;
|
||||||
|
uint256 burnAmountA; uint256 burnAmountB; uint256 burnAmountC;
|
||||||
|
uint256[] tokens; uint256[] burnAmounts;
|
||||||
|
|
||||||
|
mathint startingBalanceA = balanceOf(holder, tokenA);
|
||||||
|
mathint startingBalanceB = balanceOf(holder, tokenB);
|
||||||
|
mathint startingBalanceC = balanceOf(holder, tokenC);
|
||||||
|
|
||||||
|
require tokens.length == 3; require burnAmounts.length == 3;
|
||||||
|
require tokens[0] == tokenA; require burnAmounts[0] == burnAmountA;
|
||||||
|
require tokens[1] == tokenB; require burnAmounts[1] == burnAmountB;
|
||||||
|
require tokens[2] == tokenC; require burnAmounts[2] == burnAmountC;
|
||||||
|
|
||||||
|
// burning via burn
|
||||||
|
burn(e, holder, tokenA, burnAmountA) at beforeBurns;
|
||||||
|
burn(e, holder, tokenB, burnAmountB);
|
||||||
|
burn(e, holder, tokenC, burnAmountC);
|
||||||
|
mathint burnBalanceChangeA = startingBalanceA - balanceOf(holder, tokenA);
|
||||||
|
mathint burnBalanceChangeB = startingBalanceB - balanceOf(holder, tokenB);
|
||||||
|
mathint burnBalanceChangeC = startingBalanceC - balanceOf(holder, tokenC);
|
||||||
|
|
||||||
|
// burning via burnBatch
|
||||||
|
burnBatch(e, holder, tokens, burnAmounts) at beforeBurns;
|
||||||
|
mathint burnBatchBalanceChangeA = startingBalanceA - balanceOf(holder, tokenA);
|
||||||
|
mathint burnBatchBalanceChangeB = startingBalanceB - balanceOf(holder, tokenB);
|
||||||
|
mathint burnBatchBalanceChangeC = startingBalanceC - balanceOf(holder, tokenC);
|
||||||
|
|
||||||
|
assert burnBalanceChangeA == burnBatchBalanceChangeA
|
||||||
|
&& burnBalanceChangeB == burnBatchBalanceChangeB
|
||||||
|
&& burnBalanceChangeC == burnBatchBalanceChangeC,
|
||||||
|
"Burning multiple tokens via burn or burnBatch must be equivalent";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If passed empty token and burn amount arrays, `burnBatch` must not change
|
||||||
|
/// token balances or address permissions.
|
||||||
|
rule burnBatchOnEmptyArraysChangesNothing {
|
||||||
|
uint256 token; address nonHolderA; address nonHolderB;
|
||||||
|
|
||||||
|
uint256 startingBalance = balanceOf(nonHolderA, token);
|
||||||
|
bool startingPermission = isApprovedForAll(nonHolderA, nonHolderB);
|
||||||
|
|
||||||
|
env e; address holder; uint256[] noTokens; uint256[] noBurnAmounts;
|
||||||
|
require noTokens.length == 0; require noBurnAmounts.length == 0;
|
||||||
|
|
||||||
|
burnBatch(e, holder, noTokens, noBurnAmounts);
|
||||||
|
|
||||||
|
uint256 endingBalance = balanceOf(nonHolderA, token);
|
||||||
|
bool endingPermission = isApprovedForAll(nonHolderA, nonHolderB);
|
||||||
|
|
||||||
|
assert startingBalance == endingBalance,
|
||||||
|
"burnBatch must not change token balances if passed empty arrays";
|
||||||
|
assert startingPermission == endingPermission,
|
||||||
|
"burnBatch must not change account permissions if passed empty arrays";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
/// This rule should always fail.
|
||||||
|
rule sanity {
|
||||||
|
method f; env e; calldataarg args;
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert false,
|
||||||
|
"This rule should always fail";
|
||||||
|
}
|
||||||
|
*/
|
||||||
130
certora/specs/ERC1155Pausable.spec
Normal file
130
certora/specs/ERC1155Pausable.spec
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
//// ## Verification of `ERC1155Pausable`
|
||||||
|
////
|
||||||
|
//// `ERC1155Pausable` extends existing `Pausable` functionality by requiring that a
|
||||||
|
//// contract not be in a `paused` state prior to a token transfer.
|
||||||
|
////
|
||||||
|
//// ### Assumptions and Simplifications
|
||||||
|
//// - Internal methods `_pause` and `_unpause` wrapped by functions callable from CVL
|
||||||
|
//// - Dummy functions created to verify `whenPaused` and `whenNotPaused` modifiers
|
||||||
|
////
|
||||||
|
////
|
||||||
|
//// ### Properties
|
||||||
|
|
||||||
|
methods {
|
||||||
|
balanceOf(address, uint256) returns uint256 envfree
|
||||||
|
paused() returns bool envfree
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When a contract is in a paused state, the token balance for a given user and
|
||||||
|
/// token must not change.
|
||||||
|
rule balancesUnchangedWhenPaused() {
|
||||||
|
address user; uint256 token;
|
||||||
|
uint256 balanceBefore = balanceOf(user, token);
|
||||||
|
|
||||||
|
require paused();
|
||||||
|
|
||||||
|
method f; calldataarg arg; env e;
|
||||||
|
f(e, arg);
|
||||||
|
|
||||||
|
uint256 balanceAfter = balanceOf(user, token);
|
||||||
|
|
||||||
|
assert balanceBefore == balanceAfter,
|
||||||
|
"Token balance for a user must not change in a paused contract";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When a contract is in a paused state, transfer methods must revert.
|
||||||
|
rule transferMethodsRevertWhenPaused (method f)
|
||||||
|
filtered {
|
||||||
|
f -> f.selector == safeTransferFrom(address,address,uint256,uint256,bytes).selector
|
||||||
|
|| f.selector == safeBatchTransferFrom(address,address,uint256[],uint256[],bytes).selector
|
||||||
|
}
|
||||||
|
{
|
||||||
|
require paused();
|
||||||
|
|
||||||
|
env e; calldataarg args;
|
||||||
|
f@withrevert(e, args);
|
||||||
|
|
||||||
|
assert lastReverted,
|
||||||
|
"Transfer methods must revert in a paused contract";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When a contract is in an unpaused state, calling `pause()` must pause.
|
||||||
|
rule pauseMethodPausesContract {
|
||||||
|
require !paused();
|
||||||
|
|
||||||
|
env e;
|
||||||
|
pause(e);
|
||||||
|
|
||||||
|
assert paused(),
|
||||||
|
"Calling pause must pause an unpaused contract";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When a contract is in a paused state, calling unpause() must unpause.
|
||||||
|
rule unpauseMethodUnpausesContract {
|
||||||
|
require paused();
|
||||||
|
|
||||||
|
env e;
|
||||||
|
unpause(e);
|
||||||
|
|
||||||
|
assert !paused(),
|
||||||
|
"Calling unpause must unpause a paused contract";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When a contract is in a paused state, calling pause() must revert.
|
||||||
|
rule cannotPauseWhilePaused {
|
||||||
|
require paused();
|
||||||
|
|
||||||
|
env e;
|
||||||
|
pause@withrevert(e);
|
||||||
|
|
||||||
|
assert lastReverted,
|
||||||
|
"A call to pause when already paused must revert";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When a contract is in an unpaused state, calling unpause() must revert.
|
||||||
|
rule cannotUnpauseWhileUnpaused {
|
||||||
|
require !paused();
|
||||||
|
|
||||||
|
env e;
|
||||||
|
unpause@withrevert(e);
|
||||||
|
|
||||||
|
assert lastReverted,
|
||||||
|
"A call to unpause when already unpaused must revert";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When a contract is in a paused state, functions with the `whenNotPaused`
|
||||||
|
/// modifier must revert.
|
||||||
|
/// @title `whenNotPaused` modifier causes revert if paused
|
||||||
|
rule whenNotPausedModifierCausesRevertIfPaused {
|
||||||
|
require paused();
|
||||||
|
|
||||||
|
env e; calldataarg args;
|
||||||
|
onlyWhenNotPausedMethod@withrevert(e, args);
|
||||||
|
|
||||||
|
assert lastReverted,
|
||||||
|
"Functions with the whenNotPaused modifier must revert if the contract is paused";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When a contract is in an unpaused state, functions with the `whenPaused`
|
||||||
|
/// modifier must revert.
|
||||||
|
/// @title `whenPaused` modifier causes revert if unpaused
|
||||||
|
rule whenPausedModifierCausesRevertIfUnpaused {
|
||||||
|
require !paused();
|
||||||
|
|
||||||
|
env e; calldataarg args;
|
||||||
|
onlyWhenPausedMethod@withrevert(e, args);
|
||||||
|
|
||||||
|
assert lastReverted,
|
||||||
|
"Functions with the whenPaused modifier must revert if the contract is not paused";
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
/// This rule should always fail.
|
||||||
|
rule sanity {
|
||||||
|
method f; env e; calldataarg args;
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert false,
|
||||||
|
"This rule should always fail";
|
||||||
|
}
|
||||||
|
*/
|
||||||
93
certora/specs/ERC1155Supply.spec
Normal file
93
certora/specs/ERC1155Supply.spec
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
//// ## Verification of `ERC1155Supply`
|
||||||
|
////
|
||||||
|
//// `ERC1155Supply` extends the `ERC1155` functionality. The contract creates a publicly callable `totalSupply` wrapper for the private `_totalSupply` method, a public `exists` method to check for a positive balance of a given token, and updates `_beforeTokenTransfer` to appropriately change the mapping `_totalSupply` in the context of minting and burning tokens.
|
||||||
|
////
|
||||||
|
//// ### Assumptions and Simplifications
|
||||||
|
//// - The `exists` method was wrapped in the `exists_wrapper` method because `exists` is a keyword in CVL.
|
||||||
|
//// - The public functions `burn`, `burnBatch`, `mint`, and `mintBatch` were implemented in the harnessing contract make their respective internal functions callable by the CVL. This was used to test the increase and decrease of `totalSupply` when tokens are minted and burned.
|
||||||
|
//// - We created the `onlyOwner` modifier to be used in the above functions so that they are not called in unrelated rules.
|
||||||
|
////
|
||||||
|
//// ### Properties
|
||||||
|
|
||||||
|
|
||||||
|
methods {
|
||||||
|
totalSupply(uint256) returns uint256 envfree
|
||||||
|
balanceOf(address, uint256) returns uint256 envfree
|
||||||
|
exists_wrapper(uint256) returns bool envfree
|
||||||
|
owner() returns address envfree
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given two different token ids, if total supply for one changes, then
|
||||||
|
/// total supply for other must not.
|
||||||
|
rule token_totalSupply_independence(method f)
|
||||||
|
filtered {
|
||||||
|
f -> f.selector != safeBatchTransferFrom(address,address,uint256[],uint256[],bytes).selector
|
||||||
|
}
|
||||||
|
{
|
||||||
|
uint256 token1; uint256 token2;
|
||||||
|
require token1 != token2;
|
||||||
|
|
||||||
|
uint256 token1_before = totalSupply(token1);
|
||||||
|
uint256 token2_before = totalSupply(token2);
|
||||||
|
|
||||||
|
env e; calldataarg args;
|
||||||
|
require e.msg.sender != owner(); // owner can call mintBatch and burnBatch in our harness
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
uint256 token1_after = totalSupply(token1);
|
||||||
|
uint256 token2_after = totalSupply(token2);
|
||||||
|
|
||||||
|
assert token1_after != token1_before => token2_after == token2_before,
|
||||||
|
"methods must not change the total supply of more than one token";
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
ghost mapping(uint256 => mathint) sumOfBalances {
|
||||||
|
init_state axiom forall uint256 token . sumOfBalances[token] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hook Sstore _balances[KEY uint256 token][KEY address user] uint256 newValue (uint256 oldValue) STORAGE {
|
||||||
|
sumOfBalances[token] = sumOfBalances[token] + newValue - oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The sum of the balances over all users must equal the total supply for a
|
||||||
|
/// given token.
|
||||||
|
invariant total_supply_is_sum_of_balances(uint256 token)
|
||||||
|
sumOfBalances[token] == totalSupply(token)
|
||||||
|
{
|
||||||
|
preserved {
|
||||||
|
requireInvariant balanceOfZeroAddressIsZero(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
/// The balance of a token for the zero address must be zero.
|
||||||
|
invariant balanceOfZeroAddressIsZero(uint256 token)
|
||||||
|
balanceOf(0, token) == 0
|
||||||
|
|
||||||
|
/// If a user has a token, then the token should exist.
|
||||||
|
rule held_tokens_should_exist {
|
||||||
|
address user; uint256 token;
|
||||||
|
|
||||||
|
requireInvariant balanceOfZeroAddressIsZero(token);
|
||||||
|
|
||||||
|
// This assumption is safe because of total_supply_is_sum_of_balances
|
||||||
|
require balanceOf(user, token) <= totalSupply(token);
|
||||||
|
|
||||||
|
// note: `exists_wrapper` just calls `exists`
|
||||||
|
assert balanceOf(user, token) > 0 => exists_wrapper(token),
|
||||||
|
"if a user's balance for a token is positive, the token must exist";
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
/*
|
||||||
|
rule sanity {
|
||||||
|
method f; env e; calldataarg args;
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert false;
|
||||||
|
}
|
||||||
|
*/
|
||||||
385
certora/specs/ERC20.spec
Normal file
385
certora/specs/ERC20.spec
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
import "erc20methods.spec"
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Ghost & hooks: sum of all balances │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
ghost sumOfBalances() returns uint256 {
|
||||||
|
init_state axiom sumOfBalances() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hook Sstore _balances[KEY address addr] uint256 newValue (uint256 oldValue) STORAGE {
|
||||||
|
havoc sumOfBalances assuming sumOfBalances@new() == sumOfBalances@old() + newValue - oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Invariant: totalSupply is the sum of all balances │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
invariant totalSupplyIsSumOfBalances()
|
||||||
|
totalSupply() == sumOfBalances()
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Invariant: balance of address(0) is 0 │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
invariant zeroAddressNoBalance()
|
||||||
|
balanceOf(0) == 0
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rules: only mint and burn can change total supply │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule noChangeTotalSupply() {
|
||||||
|
requireInvariant totalSupplyIsSumOfBalances();
|
||||||
|
|
||||||
|
env e;
|
||||||
|
method f;
|
||||||
|
calldataarg args;
|
||||||
|
|
||||||
|
uint256 totalSupplyBefore = totalSupply();
|
||||||
|
f(e, args);
|
||||||
|
uint256 totalSupplyAfter = totalSupply();
|
||||||
|
|
||||||
|
assert (totalSupplyAfter > totalSupplyBefore) => (f.selector == _mint(address,uint256).selector);
|
||||||
|
assert (totalSupplyAfter < totalSupplyBefore) => (f.selector == _burn(address,uint256).selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rules: only the token holder or an approved third party can reduce an account's balance │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule onlyAuthorizedCanTransfer() {
|
||||||
|
requireInvariant totalSupplyIsSumOfBalances();
|
||||||
|
|
||||||
|
env e;
|
||||||
|
method f;
|
||||||
|
calldataarg args;
|
||||||
|
address account;
|
||||||
|
|
||||||
|
uint256 allowanceBefore = allowance(account, e.msg.sender);
|
||||||
|
uint256 balanceBefore = balanceOf(account);
|
||||||
|
f(e, args);
|
||||||
|
uint256 balanceAfter = balanceOf(account);
|
||||||
|
|
||||||
|
assert (
|
||||||
|
balanceAfter < balanceBefore
|
||||||
|
) => (
|
||||||
|
f.selector == _burn(address,uint256).selector ||
|
||||||
|
e.msg.sender == account ||
|
||||||
|
balanceBefore - balanceAfter <= allowanceBefore
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rules: only the token holder (or a permit) can increase allowance. The spender can decrease it by using it │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule onlyHolderOfSpenderCanChangeAllowance() {
|
||||||
|
requireInvariant totalSupplyIsSumOfBalances();
|
||||||
|
|
||||||
|
env e;
|
||||||
|
method f;
|
||||||
|
calldataarg args;
|
||||||
|
address holder;
|
||||||
|
address spender;
|
||||||
|
|
||||||
|
uint256 allowanceBefore = allowance(holder, spender);
|
||||||
|
f(e, args);
|
||||||
|
uint256 allowanceAfter = allowance(holder, spender);
|
||||||
|
|
||||||
|
assert (
|
||||||
|
allowanceAfter > allowanceBefore
|
||||||
|
) => (
|
||||||
|
(f.selector == approve(address,uint256).selector && e.msg.sender == holder) ||
|
||||||
|
(f.selector == increaseAllowance(address,uint256).selector && e.msg.sender == holder) ||
|
||||||
|
(f.selector == permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert (
|
||||||
|
allowanceAfter < allowanceBefore
|
||||||
|
) => (
|
||||||
|
(f.selector == approve(address,uint256).selector && e.msg.sender == holder ) ||
|
||||||
|
(f.selector == decreaseAllowance(address,uint256).selector && e.msg.sender == holder ) ||
|
||||||
|
(f.selector == transferFrom(address,address,uint256).selector && e.msg.sender == spender) ||
|
||||||
|
(f.selector == permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rules: mint behavior and side effects │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule mint() {
|
||||||
|
requireInvariant totalSupplyIsSumOfBalances();
|
||||||
|
|
||||||
|
env e;
|
||||||
|
address to;
|
||||||
|
address other;
|
||||||
|
uint256 amount;
|
||||||
|
|
||||||
|
// env: function is not payable
|
||||||
|
require e.msg.sender != 0;
|
||||||
|
require e.msg.value == 0;
|
||||||
|
|
||||||
|
// cache state
|
||||||
|
uint256 toBalanceBefore = balanceOf(to);
|
||||||
|
uint256 otherBalanceBefore = balanceOf(other);
|
||||||
|
uint256 totalSupplyBefore = totalSupply();
|
||||||
|
|
||||||
|
// run transaction
|
||||||
|
_mint@withrevert(e, to, amount);
|
||||||
|
|
||||||
|
// check outcome
|
||||||
|
if (lastReverted) {
|
||||||
|
assert to == 0 || totalSupplyBefore + amount > to_uint256(max_uint256);
|
||||||
|
} else {
|
||||||
|
// updates balance and totalSupply
|
||||||
|
assert balanceOf(to) == toBalanceBefore + amount;
|
||||||
|
assert totalSupply() == totalSupplyBefore + amount;
|
||||||
|
|
||||||
|
// no other balance is modified
|
||||||
|
assert balanceOf(other) != otherBalanceBefore => other == to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rules: burn behavior and side effects │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule burn() {
|
||||||
|
requireInvariant totalSupplyIsSumOfBalances();
|
||||||
|
|
||||||
|
env e;
|
||||||
|
address from;
|
||||||
|
address other;
|
||||||
|
uint256 amount;
|
||||||
|
|
||||||
|
// env: function is not payable
|
||||||
|
require e.msg.sender != 0;
|
||||||
|
require e.msg.value == 0;
|
||||||
|
|
||||||
|
// cache state
|
||||||
|
uint256 fromBalanceBefore = balanceOf(from);
|
||||||
|
uint256 otherBalanceBefore = balanceOf(other);
|
||||||
|
uint256 totalSupplyBefore = totalSupply();
|
||||||
|
|
||||||
|
// run transaction
|
||||||
|
_burn@withrevert(e, from, amount);
|
||||||
|
|
||||||
|
// check outcome
|
||||||
|
if (lastReverted) {
|
||||||
|
assert from == 0 || fromBalanceBefore < amount;
|
||||||
|
} else {
|
||||||
|
// updates balance and totalSupply
|
||||||
|
assert balanceOf(from) == fromBalanceBefore - amount;
|
||||||
|
assert totalSupply() == totalSupplyBefore - amount;
|
||||||
|
|
||||||
|
// no other balance is modified
|
||||||
|
assert balanceOf(other) != otherBalanceBefore => other == from;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: transfer behavior and side effects │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule transfer() {
|
||||||
|
requireInvariant totalSupplyIsSumOfBalances();
|
||||||
|
|
||||||
|
env e;
|
||||||
|
address holder = e.msg.sender;
|
||||||
|
address recipient;
|
||||||
|
address other;
|
||||||
|
uint256 amount;
|
||||||
|
|
||||||
|
// env: function is not payable
|
||||||
|
require e.msg.sender != 0;
|
||||||
|
require e.msg.value == 0;
|
||||||
|
|
||||||
|
// cache state
|
||||||
|
uint256 holderBalanceBefore = balanceOf(holder);
|
||||||
|
uint256 recipientBalanceBefore = balanceOf(recipient);
|
||||||
|
uint256 otherBalanceBefore = balanceOf(other);
|
||||||
|
|
||||||
|
// run transaction
|
||||||
|
transfer@withrevert(e, recipient, amount);
|
||||||
|
|
||||||
|
// check outcome
|
||||||
|
if (lastReverted) {
|
||||||
|
assert holder == 0 || recipient == 0 || amount > holderBalanceBefore;
|
||||||
|
} else {
|
||||||
|
// balances of holder and recipient are updated
|
||||||
|
assert balanceOf(holder) == holderBalanceBefore - (holder == recipient ? 0 : amount);
|
||||||
|
assert balanceOf(recipient) == recipientBalanceBefore + (holder == recipient ? 0 : amount);
|
||||||
|
|
||||||
|
// no other balance is modified
|
||||||
|
assert balanceOf(other) != otherBalanceBefore => (other == holder || other == recipient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: transferFrom behavior and side effects │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule transferFrom() {
|
||||||
|
requireInvariant totalSupplyIsSumOfBalances();
|
||||||
|
|
||||||
|
env e;
|
||||||
|
address holder;
|
||||||
|
address recipient;
|
||||||
|
address other;
|
||||||
|
uint256 amount;
|
||||||
|
|
||||||
|
// env: function is not payable
|
||||||
|
require e.msg.sender != 0;
|
||||||
|
require e.msg.value == 0;
|
||||||
|
|
||||||
|
// cache state
|
||||||
|
uint256 allowanceBefore = allowance(holder, e.msg.sender);
|
||||||
|
uint256 holderBalanceBefore = balanceOf(holder);
|
||||||
|
uint256 recipientBalanceBefore = balanceOf(recipient);
|
||||||
|
uint256 otherBalanceBefore = balanceOf(other);
|
||||||
|
|
||||||
|
// run transaction
|
||||||
|
transferFrom@withrevert(e, holder, recipient, amount);
|
||||||
|
|
||||||
|
// check outcome
|
||||||
|
if (lastReverted) {
|
||||||
|
assert holder == 0 || recipient == 0 || amount > holderBalanceBefore || amount > allowanceBefore;
|
||||||
|
} else {
|
||||||
|
// allowance is valid & updated
|
||||||
|
assert allowanceBefore >= amount;
|
||||||
|
assert allowance(holder, e.msg.sender) == (allowanceBefore == max_uint256 ? to_uint256(max_uint256) : allowanceBefore - amount);
|
||||||
|
|
||||||
|
// balances of holder and recipient are updated
|
||||||
|
assert balanceOf(holder) == holderBalanceBefore - (holder == recipient ? 0 : amount);
|
||||||
|
assert balanceOf(recipient) == recipientBalanceBefore + (holder == recipient ? 0 : amount);
|
||||||
|
|
||||||
|
// no other balance is modified
|
||||||
|
assert balanceOf(other) != otherBalanceBefore => (other == holder || other == recipient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: approve behavior and side effects │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule approve() {
|
||||||
|
requireInvariant totalSupplyIsSumOfBalances();
|
||||||
|
|
||||||
|
env e;
|
||||||
|
address holder = e.msg.sender;
|
||||||
|
address spender;
|
||||||
|
address otherHolder;
|
||||||
|
address otherSpender;
|
||||||
|
uint256 amount;
|
||||||
|
|
||||||
|
// env: function is not payable
|
||||||
|
require e.msg.sender != 0;
|
||||||
|
require e.msg.value == 0;
|
||||||
|
|
||||||
|
// cache state
|
||||||
|
uint256 otherAllowanceBefore = allowance(otherHolder, otherSpender);
|
||||||
|
|
||||||
|
// run transaction
|
||||||
|
approve@withrevert(e, spender, amount);
|
||||||
|
|
||||||
|
// check outcome
|
||||||
|
if (lastReverted) {
|
||||||
|
assert spender == 0;
|
||||||
|
} else {
|
||||||
|
// allowance is updated
|
||||||
|
assert allowance(holder, spender) == amount;
|
||||||
|
|
||||||
|
// other allowances are untouched
|
||||||
|
assert allowance(otherHolder, otherSpender) != otherAllowanceBefore => (otherHolder == holder && otherSpender == spender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: increaseAllowance behavior and side effects │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule increaseAllowance() {
|
||||||
|
requireInvariant totalSupplyIsSumOfBalances();
|
||||||
|
|
||||||
|
env e;
|
||||||
|
address holder = e.msg.sender;
|
||||||
|
address spender;
|
||||||
|
address otherHolder;
|
||||||
|
address otherSpender;
|
||||||
|
uint256 amount;
|
||||||
|
|
||||||
|
// env: function is not payable
|
||||||
|
require e.msg.sender != 0;
|
||||||
|
require e.msg.value == 0;
|
||||||
|
|
||||||
|
// cache state
|
||||||
|
uint256 allowanceBefore = allowance(holder, spender);
|
||||||
|
uint256 otherAllowanceBefore = allowance(otherHolder, otherSpender);
|
||||||
|
|
||||||
|
// run transaction
|
||||||
|
increaseAllowance@withrevert(e, spender, amount);
|
||||||
|
|
||||||
|
// check outcome
|
||||||
|
if (lastReverted) {
|
||||||
|
assert spender == 0 || allowanceBefore + amount > max_uint256;
|
||||||
|
} else {
|
||||||
|
// allowance is updated
|
||||||
|
assert allowance(holder, spender) == allowanceBefore + amount;
|
||||||
|
|
||||||
|
// other allowances are untouched
|
||||||
|
assert allowance(otherHolder, otherSpender) != otherAllowanceBefore => (otherHolder == holder && otherSpender == spender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: decreaseAllowance behavior and side effects │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule decreaseAllowance() {
|
||||||
|
requireInvariant totalSupplyIsSumOfBalances();
|
||||||
|
|
||||||
|
env e;
|
||||||
|
address holder = e.msg.sender;
|
||||||
|
address spender;
|
||||||
|
address otherHolder;
|
||||||
|
address otherSpender;
|
||||||
|
uint256 amount;
|
||||||
|
|
||||||
|
// env: function is not payable
|
||||||
|
require e.msg.sender != 0;
|
||||||
|
require e.msg.value == 0;
|
||||||
|
|
||||||
|
// cache state
|
||||||
|
uint256 allowanceBefore = allowance(holder, spender);
|
||||||
|
uint256 otherAllowanceBefore = allowance(otherHolder, otherSpender);
|
||||||
|
|
||||||
|
// run transaction
|
||||||
|
decreaseAllowance@withrevert(e, spender, amount);
|
||||||
|
|
||||||
|
// check outcome
|
||||||
|
if (lastReverted) {
|
||||||
|
assert spender == 0 || allowanceBefore < amount;
|
||||||
|
} else {
|
||||||
|
// allowance is updated
|
||||||
|
assert allowance(holder, spender) == allowanceBefore - amount;
|
||||||
|
|
||||||
|
// other allowances are untouched
|
||||||
|
assert allowance(otherHolder, otherSpender) != otherAllowanceBefore => (otherHolder == holder && otherSpender == spender);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
certora/specs/ERC20FlashMint.spec
Normal file
30
certora/specs/ERC20FlashMint.spec
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import "erc20methods.spec"
|
||||||
|
|
||||||
|
methods {
|
||||||
|
maxFlashLoan(address) returns(uint256) envfree
|
||||||
|
_burn(address account, uint256 amount) returns(bool) => specBurn(account, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
ghost mapping(address => uint256) trackedBurnAmount;
|
||||||
|
|
||||||
|
// returns is needed to overcome current CVL limitations: "could not type expression "specBurn(account,amount)", message: A summary must return a simple type, but specBurn(account,amount) returns 'void'"
|
||||||
|
function specBurn(address account, uint256 amount) returns bool {
|
||||||
|
trackedBurnAmount[account] = amount;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that if flashLoan() call is successful, then proper amount of tokens was burnt(fee + flashLoan amount)
|
||||||
|
rule letsWatchItBurns(env e) {
|
||||||
|
address receiver;
|
||||||
|
address token;
|
||||||
|
uint256 amount;
|
||||||
|
bytes data;
|
||||||
|
|
||||||
|
uint256 feeBefore = flashFee(e, token, amount);
|
||||||
|
|
||||||
|
flashLoan(e, receiver, token, amount, data);
|
||||||
|
|
||||||
|
uint256 burned = trackedBurnAmount[receiver];
|
||||||
|
|
||||||
|
assert to_mathint(amount + feeBefore) == burned, "cheater";
|
||||||
|
}
|
||||||
299
certora/specs/ERC20Votes.spec
Normal file
299
certora/specs/ERC20Votes.spec
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
import "erc20methods.spec"
|
||||||
|
|
||||||
|
methods {
|
||||||
|
// IVotes
|
||||||
|
getVotes(address) returns (uint256) envfree
|
||||||
|
getPastVotes(address, uint256) returns (uint256) // not envfree (reads block.number)
|
||||||
|
getPastTotalSupply(uint256) returns (uint256) // not envfree (reads block.number)
|
||||||
|
delegates(address) returns (address) envfree
|
||||||
|
delegate(address) // not envfree (reads msg.sender)
|
||||||
|
delegateBySig(address, uint256, uint256, uint8, bytes32, bytes32) // not envfree (reads msg.sender)
|
||||||
|
|
||||||
|
// ERC20Votes
|
||||||
|
checkpoints(address, uint32) envfree
|
||||||
|
numCheckpoints(address) returns (uint32) envfree
|
||||||
|
_maxSupply() returns (uint224) envfree
|
||||||
|
_delegate(address, address) // not envfree (reads block.number when creating checkpoint)
|
||||||
|
|
||||||
|
// harnesss functions
|
||||||
|
ckptFromBlock(address, uint32) returns (uint32) envfree
|
||||||
|
ckptVotes(address, uint32) returns (uint224) envfree
|
||||||
|
unsafeNumCheckpoints(address) returns (uint256) envfree
|
||||||
|
|
||||||
|
// solidity generated getters
|
||||||
|
_delegates(address) returns (address) envfree
|
||||||
|
}
|
||||||
|
|
||||||
|
// gets the most recent votes for a user
|
||||||
|
ghost userVotes(address) returns uint224 {
|
||||||
|
init_state axiom forall address a. userVotes(a) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sums the total votes for all users
|
||||||
|
ghost totalVotes() returns uint224 {
|
||||||
|
init_state axiom totalVotes() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ghost lastIndex(address) returns uint32;
|
||||||
|
|
||||||
|
// helper
|
||||||
|
hook Sstore _checkpoints[KEY address account][INDEX uint32 index].votes uint224 newVotes (uint224 oldVotes) STORAGE {
|
||||||
|
havoc userVotes assuming
|
||||||
|
userVotes@new(account) == newVotes;
|
||||||
|
|
||||||
|
havoc totalVotes assuming
|
||||||
|
totalVotes@new() == totalVotes@old() + newVotes - userVotes(account);
|
||||||
|
|
||||||
|
havoc lastIndex assuming
|
||||||
|
lastIndex@new(account) == index;
|
||||||
|
}
|
||||||
|
|
||||||
|
ghost lastFromBlock(address) returns uint32;
|
||||||
|
|
||||||
|
ghost doubleFromBlock(address) returns bool {
|
||||||
|
init_state axiom forall address a. doubleFromBlock(a) == false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hook Sstore _checkpoints[KEY address account][INDEX uint32 index].fromBlock uint32 newBlock (uint32 oldBlock) STORAGE {
|
||||||
|
havoc lastFromBlock assuming
|
||||||
|
lastFromBlock@new(account) == newBlock;
|
||||||
|
|
||||||
|
havoc doubleFromBlock assuming
|
||||||
|
doubleFromBlock@new(account) == (newBlock == lastFromBlock(account));
|
||||||
|
}
|
||||||
|
|
||||||
|
// sum of user balances is >= total amount of delegated votes
|
||||||
|
// fails on burn. This is because burn does not remove votes from the users
|
||||||
|
invariant votes_solvency()
|
||||||
|
totalSupply() >= to_uint256(totalVotes())
|
||||||
|
filtered { f -> f.selector != _burn(address, uint256).selector }
|
||||||
|
{
|
||||||
|
preserved with(env e) {
|
||||||
|
require forall address account. numCheckpoints(account) < 1000000;
|
||||||
|
}
|
||||||
|
preserved _burn(address a, uint256 amount) with(env e) {
|
||||||
|
require _delegates(0) == 0;
|
||||||
|
require forall address a2. (_delegates(a) != _delegates(a2)) && (balanceOf(_delegates(a)) + balanceOf(_delegates(a2)) <= totalVotes());
|
||||||
|
require balanceOf(_delegates(a)) < totalVotes();
|
||||||
|
require amount < 100000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for some checkpoint, the fromBlock is less than the current block number
|
||||||
|
invariant blockNum_constrains_fromBlock(address account, uint32 index, env e)
|
||||||
|
ckptFromBlock(account, index) < e.block.number
|
||||||
|
filtered { f -> !f.isView }
|
||||||
|
|
||||||
|
// numCheckpoints are less than maxInt
|
||||||
|
// passes because numCheckpoints does a safeCast
|
||||||
|
// invariant maxInt_constrains_numBlocks(address account)
|
||||||
|
// numCheckpoints(account) < 4294967295 // 2^32
|
||||||
|
|
||||||
|
// can't have more checkpoints for a given account than the last from block
|
||||||
|
// passes
|
||||||
|
invariant fromBlock_constrains_numBlocks(address account)
|
||||||
|
numCheckpoints(account) <= ckptFromBlock(account, numCheckpoints(account) - 1)
|
||||||
|
filtered { f -> !f.isView }
|
||||||
|
{
|
||||||
|
preserved with(env e) {
|
||||||
|
require e.block.number >= ckptFromBlock(account, numCheckpoints(account) - 1); // this should be true from the invariant above!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for any given checkpoint, the fromBlock must be greater than the checkpoint
|
||||||
|
// this proves the above invariant in combination with the below invariant
|
||||||
|
// if checkpoint has a greater fromBlock than the last, and the FromBlock is always greater than the pos.
|
||||||
|
// Then the number of positions must be less than the currentFromBlock
|
||||||
|
// ^note that the tool is assuming it's possible for the starting fromBlock to be 0 or anything, and does not know the current starting block
|
||||||
|
// passes + rule sanity
|
||||||
|
invariant fromBlock_greaterThanEq_pos(address account, uint32 pos)
|
||||||
|
ckptFromBlock(account, pos) >= pos
|
||||||
|
filtered { f -> !f.isView }
|
||||||
|
|
||||||
|
// a larger index must have a larger fromBlock
|
||||||
|
// passes + rule sanity
|
||||||
|
invariant fromBlock_increasing(address account, uint32 pos, uint32 pos2)
|
||||||
|
pos > pos2 => ckptFromBlock(account, pos) > ckptFromBlock(account, pos2)
|
||||||
|
filtered { f -> !f.isView }
|
||||||
|
|
||||||
|
|
||||||
|
// converted from an invariant to a rule to slightly change the logic
|
||||||
|
// if the fromBlock is the same as before, then the number of checkpoints stays the same
|
||||||
|
// however if the fromBlock is new than the number of checkpoints increases
|
||||||
|
// passes, fails rule sanity because tautology check seems to be bugged
|
||||||
|
rule unique_checkpoints_rule(method f) {
|
||||||
|
env e; calldataarg args;
|
||||||
|
address account;
|
||||||
|
uint32 num_ckpts_ = numCheckpoints(account);
|
||||||
|
uint32 fromBlock_ = num_ckpts_ == 0 ? 0 : ckptFromBlock(account, num_ckpts_ - 1);
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
uint32 _num_ckpts = numCheckpoints(account);
|
||||||
|
uint32 _fromBlock = _num_ckpts == 0 ? 0 : ckptFromBlock(account, _num_ckpts - 1);
|
||||||
|
|
||||||
|
assert fromBlock_ == _fromBlock => num_ckpts_ == _num_ckpts || _num_ckpts == 1, "same fromBlock, new checkpoint";
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes neither account has delegated
|
||||||
|
// currently fails due to this scenario. A has maxint number of checkpoints
|
||||||
|
// an additional checkpoint is added which overflows and sets A's votes to 0
|
||||||
|
// passes + rule sanity (- a bad tautology check)
|
||||||
|
rule transfer_safe() {
|
||||||
|
env e;
|
||||||
|
uint256 amount;
|
||||||
|
address a; address b;
|
||||||
|
require delegates(a) != delegates(b); // confirmed if they both delegate to the same person then transfer keeps the votes the same
|
||||||
|
require numCheckpoints(delegates(a)) < 1000000;
|
||||||
|
require numCheckpoints(delegates(b)) < 1000000;
|
||||||
|
uint256 votesA_pre = getVotes(delegates(a));
|
||||||
|
uint256 votesB_pre = getVotes(delegates(b));
|
||||||
|
uint224 totalVotes_pre = totalVotes();
|
||||||
|
transferFrom(e, a, b, amount);
|
||||||
|
|
||||||
|
uint224 totalVotes_post = totalVotes();
|
||||||
|
uint256 votesA_post = getVotes(delegates(a));
|
||||||
|
uint256 votesB_post = getVotes(delegates(b));
|
||||||
|
// if an account that has not delegated transfers balance to an account that has, it will increase the total supply of votes
|
||||||
|
assert totalVotes_pre == totalVotes_post, "transfer changed total supply";
|
||||||
|
assert delegates(a) != 0 => votesA_pre - votesA_post == amount, "A lost the wrong amount of votes";
|
||||||
|
assert delegates(b) != 0 => votesB_post - votesB_pre == amount, "B lost the wrong amount of votes";
|
||||||
|
}
|
||||||
|
|
||||||
|
// for any given function f, if the delegate is changed the function must be delegate or delegateBySig
|
||||||
|
// passes
|
||||||
|
rule delegates_safe(method f)
|
||||||
|
filtered { f -> (
|
||||||
|
f.selector != delegate(address).selector &&
|
||||||
|
f.selector != _delegate(address, address).selector &&
|
||||||
|
f.selector != delegateBySig(address, uint256, uint256, uint8, bytes32, bytes32).selector
|
||||||
|
) }
|
||||||
|
{
|
||||||
|
env e; calldataarg args;
|
||||||
|
address account;
|
||||||
|
address pre = delegates(account);
|
||||||
|
f(e, args);
|
||||||
|
address post = delegates(account);
|
||||||
|
assert pre == post, "invalid delegate change";
|
||||||
|
}
|
||||||
|
|
||||||
|
// delegates increases the delegatee's votes by the proper amount
|
||||||
|
// passes + rule sanity
|
||||||
|
rule delegatee_receives_votes() {
|
||||||
|
env e;
|
||||||
|
address delegator; address delegatee;
|
||||||
|
|
||||||
|
require delegates(delegator) != delegatee;
|
||||||
|
require delegatee != 0x0;
|
||||||
|
|
||||||
|
uint256 delegator_bal = balanceOf(delegator);
|
||||||
|
uint256 votes_= getVotes(delegatee);
|
||||||
|
|
||||||
|
_delegate(e, delegator, delegatee);
|
||||||
|
|
||||||
|
require lastIndex(delegatee) < 1000000;
|
||||||
|
|
||||||
|
uint256 _votes = getVotes(delegatee);
|
||||||
|
|
||||||
|
assert _votes == votes_ + delegator_bal, "delegatee did not receive votes";
|
||||||
|
}
|
||||||
|
|
||||||
|
// passes + rule sanity
|
||||||
|
rule previous_delegatee_votes_removed() {
|
||||||
|
env e;
|
||||||
|
address delegator; address delegatee; address third;
|
||||||
|
|
||||||
|
require third != delegatee;
|
||||||
|
require delegates(delegator) == third;
|
||||||
|
require numCheckpoints(third) < 1000000;
|
||||||
|
|
||||||
|
uint256 delegator_bal = balanceOf(delegator);
|
||||||
|
uint256 votes_ = getVotes(third);
|
||||||
|
|
||||||
|
_delegate(e, delegator, delegatee);
|
||||||
|
|
||||||
|
uint256 _votes = getVotes(third);
|
||||||
|
|
||||||
|
assert third != 0x0 => _votes == votes_ - delegator_bal, "votes not removed from the previous delegatee";
|
||||||
|
}
|
||||||
|
|
||||||
|
// passes with rule sanity
|
||||||
|
rule delegate_contained() {
|
||||||
|
env e;
|
||||||
|
address delegator; address delegatee; address other;
|
||||||
|
|
||||||
|
require other != delegatee;
|
||||||
|
require other != delegates(delegator);
|
||||||
|
|
||||||
|
uint256 votes_ = getVotes(other);
|
||||||
|
|
||||||
|
_delegate(e, delegator, delegatee);
|
||||||
|
|
||||||
|
uint256 _votes = getVotes(other);
|
||||||
|
|
||||||
|
assert votes_ == _votes, "votes not contained";
|
||||||
|
}
|
||||||
|
|
||||||
|
rule delegate_no_frontrunning(method f) {
|
||||||
|
env e; calldataarg args;
|
||||||
|
address delegator; address delegatee; address third; address other;
|
||||||
|
|
||||||
|
require numCheckpoints(delegatee) < 1000000;
|
||||||
|
require numCheckpoints(third) < 1000000;
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
uint256 delegator_bal = balanceOf(delegator);
|
||||||
|
uint256 delegatee_votes_ = getVotes(delegatee);
|
||||||
|
uint256 third_votes_ = getVotes(third);
|
||||||
|
uint256 other_votes_ = getVotes(other);
|
||||||
|
require delegates(delegator) == third;
|
||||||
|
require third != delegatee;
|
||||||
|
require other != third;
|
||||||
|
require other != delegatee;
|
||||||
|
require delegatee != 0x0;
|
||||||
|
|
||||||
|
_delegate(e, delegator, delegatee);
|
||||||
|
|
||||||
|
uint256 _delegatee_votes = getVotes(delegatee);
|
||||||
|
uint256 _third_votes = getVotes(third);
|
||||||
|
uint256 _other_votes = getVotes(other);
|
||||||
|
|
||||||
|
|
||||||
|
// previous delegatee loses all of their votes
|
||||||
|
// delegatee gains that many votes
|
||||||
|
// third loses any votes delegated to them
|
||||||
|
assert _delegatee_votes == delegatee_votes_ + delegator_bal, "delegatee did not receive votes";
|
||||||
|
assert third != 0 => _third_votes == third_votes_ - delegator_bal, "votes not removed from third";
|
||||||
|
assert other_votes_ == _other_votes, "delegate not contained";
|
||||||
|
}
|
||||||
|
|
||||||
|
rule onMint() {
|
||||||
|
env e;
|
||||||
|
uint256 amount;
|
||||||
|
address account;
|
||||||
|
|
||||||
|
uint256 fromBlock = e.block.number;
|
||||||
|
uint224 totalVotesBefore = totalVotes();
|
||||||
|
uint256 totalSupplyBefore = totalSupply();
|
||||||
|
|
||||||
|
_mint(e, account, amount);
|
||||||
|
|
||||||
|
assert totalVotes() == totalVotesBefore, "totalVotes changed";
|
||||||
|
assert getPastTotalSupply(e, fromBlock) == totalSupplyBefore, "previous totalSupply not saved properly";
|
||||||
|
}
|
||||||
|
|
||||||
|
rule onBurn() {
|
||||||
|
env e;
|
||||||
|
uint256 amount;
|
||||||
|
address account;
|
||||||
|
|
||||||
|
uint256 fromBlock = e.block.number;
|
||||||
|
uint224 totalVotesBefore = totalVotes();
|
||||||
|
uint256 totalSupplyBefore = totalSupply();
|
||||||
|
|
||||||
|
_burn(e, account, amount);
|
||||||
|
|
||||||
|
assert totalVotes() == totalVotesBefore, "totalVotes changed";
|
||||||
|
assert getPastTotalSupply(e, fromBlock) == totalSupplyBefore, "previous totalSupply not saved properly";
|
||||||
|
}
|
||||||
255
certora/specs/ERC20Wrapper.spec
Normal file
255
certora/specs/ERC20Wrapper.spec
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
import "ERC20.spec"
|
||||||
|
|
||||||
|
methods {
|
||||||
|
underlying() returns(address) envfree
|
||||||
|
underlyingTotalSupply() returns(uint256) envfree
|
||||||
|
underlyingBalanceOf(address) returns(uint256) envfree
|
||||||
|
|
||||||
|
depositFor(address, uint256) returns(bool)
|
||||||
|
withdrawTo(address, uint256) returns(bool)
|
||||||
|
_recover(address) returns(uint256)
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Invariants //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
use invariant totalSupplyIsSumOfBalances
|
||||||
|
|
||||||
|
// totalsupply of wrapped should be less than or equal to underlying (assuming no external transfer) - solvency
|
||||||
|
invariant whatAboutTotal(env e)
|
||||||
|
totalSupply() <= underlyingTotalSupply()
|
||||||
|
filtered { f -> f.selector != certorafallback_0().selector && !f.isView }
|
||||||
|
{
|
||||||
|
preserved with (env e2) {
|
||||||
|
require underlyingBalanceOf(currentContract) <= underlyingTotalSupply();
|
||||||
|
requireInvariant totalSupplyIsSumOfBalances;
|
||||||
|
require e.msg.sender == e2.msg.sender;
|
||||||
|
}
|
||||||
|
preserved depositFor(address account, uint256 amount) with (env e3){
|
||||||
|
require totalSupply() + amount <= underlyingTotalSupply();
|
||||||
|
}
|
||||||
|
preserved _mint(address account, uint256 amount) with (env e4){
|
||||||
|
require totalSupply() + amount <= underlyingTotalSupply();
|
||||||
|
}
|
||||||
|
preserved _burn(address account, uint256 amount) with (env e5){
|
||||||
|
require balanceOf(account) >= amount;
|
||||||
|
requireInvariant totalSupplyIsSumOfBalances;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// totalsupply of wrapped should be less than or equal to the underlying balanceOf contract (assuming no external transfer) - solvency
|
||||||
|
invariant underTotalAndContractBalanceOfCorrelation(env e)
|
||||||
|
totalSupply() <= underlyingBalanceOf(currentContract)
|
||||||
|
{
|
||||||
|
preserved with (env e2) {
|
||||||
|
require underlying() != currentContract;
|
||||||
|
require e.msg.sender != currentContract;
|
||||||
|
require e.msg.sender == e2.msg.sender;
|
||||||
|
requireInvariant totalSupplyIsSumOfBalances;
|
||||||
|
}
|
||||||
|
preserved _mint(address account, uint256 amount) with (env e4){
|
||||||
|
require totalSupply() + amount <= underlyingBalanceOf(currentContract);
|
||||||
|
require underlying() != currentContract;
|
||||||
|
}
|
||||||
|
preserved _burn(address account, uint256 amount) with (env e5){
|
||||||
|
require balanceOf(account) >= amount;
|
||||||
|
requireInvariant totalSupplyIsSumOfBalances;
|
||||||
|
}
|
||||||
|
preserved depositFor(address account, uint256 amount) with (env e3){
|
||||||
|
require totalSupply() + amount <= underlyingBalanceOf(currentContract);
|
||||||
|
require underlyingBalanceOf(currentContract) + amount < max_uint256;
|
||||||
|
require underlying() != currentContract;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Rules //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Check that values are updated correctly by `depositFor()`
|
||||||
|
rule depositForSpecBasic(env e) {
|
||||||
|
address account;
|
||||||
|
uint256 amount;
|
||||||
|
|
||||||
|
require e.msg.sender != currentContract;
|
||||||
|
require underlying() != currentContract;
|
||||||
|
|
||||||
|
uint256 wrapperTotalBefore = totalSupply();
|
||||||
|
uint256 underlyingTotalBefore = underlyingTotalSupply();
|
||||||
|
uint256 underlyingThisBalanceBefore = underlyingBalanceOf(currentContract);
|
||||||
|
|
||||||
|
depositFor(e, account, amount);
|
||||||
|
|
||||||
|
uint256 wrapperTotalAfter = totalSupply();
|
||||||
|
uint256 underlyingTotalAfter = underlyingTotalSupply();
|
||||||
|
uint256 underlyingThisBalanceAfter = underlyingBalanceOf(currentContract);
|
||||||
|
|
||||||
|
assert wrapperTotalBefore == to_uint256(wrapperTotalAfter - amount), "wrapper total wrong update";
|
||||||
|
assert underlyingTotalBefore == underlyingTotalAfter, "underlying total was updated";
|
||||||
|
assert underlyingThisBalanceBefore == to_uint256(underlyingThisBalanceAfter - amount), "underlying this balance wrong update";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that values are updated correctly by `depositFor()`
|
||||||
|
rule depositForSpecWrapper(env e) {
|
||||||
|
address account;
|
||||||
|
uint256 amount;
|
||||||
|
|
||||||
|
require underlying() != currentContract;
|
||||||
|
|
||||||
|
uint256 wrapperUserBalanceBefore = balanceOf(account);
|
||||||
|
uint256 wrapperSenderBalanceBefore = balanceOf(e.msg.sender);
|
||||||
|
|
||||||
|
depositFor(e, account, amount);
|
||||||
|
|
||||||
|
uint256 wrapperUserBalanceAfter = balanceOf(account);
|
||||||
|
uint256 wrapperSenderBalanceAfter = balanceOf(e.msg.sender);
|
||||||
|
|
||||||
|
assert account == e.msg.sender => wrapperUserBalanceBefore == wrapperSenderBalanceBefore
|
||||||
|
&& wrapperUserBalanceAfter == wrapperSenderBalanceAfter
|
||||||
|
&& wrapperUserBalanceBefore == to_uint256(wrapperUserBalanceAfter - amount)
|
||||||
|
, "wrapper balances wrong update";
|
||||||
|
|
||||||
|
assert account != e.msg.sender => wrapperUserBalanceBefore == to_uint256(wrapperUserBalanceAfter - amount)
|
||||||
|
&& wrapperSenderBalanceBefore == wrapperSenderBalanceAfter
|
||||||
|
, "wrapper balances wrong update";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that values are updated correctly by `depositFor()`
|
||||||
|
rule depositForSpecUnderlying(env e) {
|
||||||
|
address account;
|
||||||
|
uint256 amount;
|
||||||
|
|
||||||
|
require e.msg.sender != currentContract;
|
||||||
|
require underlying() != currentContract;
|
||||||
|
|
||||||
|
uint256 underlyingSenderBalanceBefore = underlyingBalanceOf(e.msg.sender);
|
||||||
|
uint256 underlyingUserBalanceBefore = underlyingBalanceOf(account);
|
||||||
|
|
||||||
|
depositFor(e, account, amount);
|
||||||
|
|
||||||
|
uint256 underlyingSenderBalanceAfter = underlyingBalanceOf(e.msg.sender);
|
||||||
|
uint256 underlyingUserBalanceAfter = underlyingBalanceOf(account);
|
||||||
|
|
||||||
|
assert account == e.msg.sender => underlyingSenderBalanceBefore == underlyingUserBalanceBefore
|
||||||
|
&& underlyingSenderBalanceAfter == underlyingUserBalanceAfter
|
||||||
|
&& underlyingSenderBalanceBefore == to_uint256(underlyingSenderBalanceAfter + amount)
|
||||||
|
, "underlying balances wrong update";
|
||||||
|
|
||||||
|
assert account != e.msg.sender
|
||||||
|
&& account == currentContract => underlyingSenderBalanceBefore == to_uint256(underlyingSenderBalanceAfter + amount)
|
||||||
|
&& underlyingUserBalanceBefore == to_uint256(underlyingUserBalanceAfter - amount)
|
||||||
|
, "underlying balances wrong update";
|
||||||
|
|
||||||
|
assert account != e.msg.sender
|
||||||
|
&& account != currentContract => underlyingSenderBalanceBefore == to_uint256(underlyingSenderBalanceAfter + amount)
|
||||||
|
&& underlyingUserBalanceBefore == to_uint256(underlyingUserBalanceAfter)
|
||||||
|
, "underlying balances wrong update";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that values are updated correctly by `withdrawTo()`
|
||||||
|
rule withdrawToSpecBasic(env e) {
|
||||||
|
address account;
|
||||||
|
uint256 amount;
|
||||||
|
|
||||||
|
require underlying() != currentContract;
|
||||||
|
|
||||||
|
uint256 wrapperTotalBefore = totalSupply();
|
||||||
|
uint256 underlyingTotalBefore = underlyingTotalSupply();
|
||||||
|
|
||||||
|
withdrawTo(e, account, amount);
|
||||||
|
|
||||||
|
uint256 wrapperTotalAfter = totalSupply();
|
||||||
|
uint256 underlyingTotalAfter = underlyingTotalSupply();
|
||||||
|
|
||||||
|
assert wrapperTotalBefore == to_uint256(wrapperTotalAfter + amount), "wrapper total wrong update";
|
||||||
|
assert underlyingTotalBefore == underlyingTotalAfter, "underlying total was updated";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that values are updated correctly by `withdrawTo()`
|
||||||
|
rule withdrawToSpecWrapper(env e) {
|
||||||
|
address account; uint256 amount;
|
||||||
|
|
||||||
|
require underlying() != currentContract;
|
||||||
|
|
||||||
|
uint256 wrapperUserBalanceBefore = balanceOf(account);
|
||||||
|
uint256 wrapperSenderBalanceBefore = balanceOf(e.msg.sender);
|
||||||
|
|
||||||
|
withdrawTo(e, account, amount);
|
||||||
|
|
||||||
|
uint256 wrapperUserBalanceAfter = balanceOf(account);
|
||||||
|
uint256 wrapperSenderBalanceAfter = balanceOf(e.msg.sender);
|
||||||
|
|
||||||
|
assert account == e.msg.sender => wrapperUserBalanceBefore == wrapperSenderBalanceBefore
|
||||||
|
&& wrapperUserBalanceAfter == wrapperSenderBalanceAfter
|
||||||
|
&& wrapperUserBalanceBefore == to_uint256(wrapperUserBalanceAfter + amount)
|
||||||
|
, "wrapper user balance wrong update";
|
||||||
|
|
||||||
|
assert account != e.msg.sender => wrapperSenderBalanceBefore == to_uint256(wrapperSenderBalanceAfter + amount)
|
||||||
|
&& wrapperUserBalanceBefore == wrapperUserBalanceAfter
|
||||||
|
, "wrapper user balance wrong update";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Check that values are updated correctly by `withdrawTo()`
|
||||||
|
rule withdrawToSpecUnderlying(env e) {
|
||||||
|
address account; uint256 amount;
|
||||||
|
|
||||||
|
require e.msg.sender != currentContract;
|
||||||
|
require underlying() != currentContract;
|
||||||
|
|
||||||
|
uint256 underlyingSenderBalanceBefore = underlyingBalanceOf(e.msg.sender);
|
||||||
|
uint256 underlyingUserBalanceBefore = underlyingBalanceOf(account);
|
||||||
|
uint256 underlyingThisBalanceBefore = underlyingBalanceOf(currentContract);
|
||||||
|
|
||||||
|
withdrawTo(e, account, amount);
|
||||||
|
|
||||||
|
uint256 underlyingSenderBalanceAfter = underlyingBalanceOf(e.msg.sender);
|
||||||
|
uint256 underlyingUserBalanceAfter = underlyingBalanceOf(account);
|
||||||
|
uint256 underlyingThisBalanceAfter = underlyingBalanceOf(currentContract);
|
||||||
|
|
||||||
|
assert account == e.msg.sender => underlyingSenderBalanceBefore == underlyingUserBalanceBefore
|
||||||
|
&& underlyingSenderBalanceAfter == underlyingUserBalanceAfter
|
||||||
|
&& underlyingUserBalanceBefore == to_uint256(underlyingUserBalanceAfter - amount)
|
||||||
|
, "underlying balances wrong update (acc == sender)";
|
||||||
|
|
||||||
|
assert account != e.msg.sender && account == currentContract => underlyingUserBalanceBefore == underlyingUserBalanceAfter
|
||||||
|
&& underlyingSenderBalanceBefore == underlyingSenderBalanceAfter
|
||||||
|
, "underlying balances wrong update (acc == contract)";
|
||||||
|
|
||||||
|
assert account != e.msg.sender && account != currentContract => underlyingUserBalanceBefore == to_uint256(underlyingUserBalanceAfter - amount)
|
||||||
|
&& underlyingSenderBalanceBefore == underlyingSenderBalanceAfter
|
||||||
|
&& underlyingThisBalanceBefore == to_uint256(underlyingThisBalanceAfter + amount)
|
||||||
|
, "underlying balances wrong update (acc != contract)";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that values are updated correctly by `_recover()`
|
||||||
|
rule recoverSpec(env e) {
|
||||||
|
address account;
|
||||||
|
uint256 amount;
|
||||||
|
|
||||||
|
uint256 wrapperTotalBefore = totalSupply();
|
||||||
|
uint256 wrapperUserBalanceBefore = balanceOf(account);
|
||||||
|
uint256 wrapperSenderBalanceBefore = balanceOf(e.msg.sender);
|
||||||
|
uint256 underlyingThisBalanceBefore = underlyingBalanceOf(currentContract);
|
||||||
|
|
||||||
|
mathint value = underlyingThisBalanceBefore - wrapperTotalBefore;
|
||||||
|
|
||||||
|
_recover(e, account);
|
||||||
|
|
||||||
|
uint256 wrapperTotalAfter = totalSupply();
|
||||||
|
uint256 wrapperUserBalanceAfter = balanceOf(account);
|
||||||
|
uint256 wrapperSenderBalanceAfter = balanceOf(e.msg.sender);
|
||||||
|
|
||||||
|
assert wrapperTotalBefore == to_uint256(wrapperTotalAfter - value), "wrapper total wrong update";
|
||||||
|
|
||||||
|
assert e.msg.sender == account => wrapperUserBalanceBefore == wrapperSenderBalanceBefore
|
||||||
|
&& wrapperUserBalanceAfter == wrapperSenderBalanceAfter
|
||||||
|
&& wrapperUserBalanceBefore == to_uint256(wrapperUserBalanceAfter - value)
|
||||||
|
, "wrapper balances wrong update";
|
||||||
|
|
||||||
|
assert e.msg.sender != account => wrapperUserBalanceBefore == to_uint256(wrapperUserBalanceAfter - value)
|
||||||
|
&& wrapperSenderBalanceBefore == wrapperSenderBalanceAfter
|
||||||
|
, "wrapper balances wrong update";
|
||||||
|
}
|
||||||
271
certora/specs/ERC721Votes.spec
Normal file
271
certora/specs/ERC721Votes.spec
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
using Checkpoints as Checkpoints
|
||||||
|
|
||||||
|
methods {
|
||||||
|
// functions
|
||||||
|
checkpoints(address, uint32) envfree
|
||||||
|
numCheckpoints(address) returns (uint32) envfree
|
||||||
|
getVotes(address) returns (uint256) envfree
|
||||||
|
getPastVotes(address, uint256) returns (uint256)
|
||||||
|
getPastTotalSupply(uint256) returns (uint256)
|
||||||
|
delegates(address) returns (address) envfree
|
||||||
|
delegate(address)
|
||||||
|
_delegate(address, address)
|
||||||
|
delegateBySig(address, uint256, uint256, uint8, bytes32, bytes32)
|
||||||
|
nonces(address) returns (uint256)
|
||||||
|
totalSupply() returns (uint256) envfree
|
||||||
|
_maxSupply() returns (uint224) envfree
|
||||||
|
|
||||||
|
// harnesss functions
|
||||||
|
ckptFromBlock(address, uint32) returns (uint32) envfree
|
||||||
|
ckptVotes(address, uint32) returns (uint224) envfree
|
||||||
|
mint(address, uint256)
|
||||||
|
burn(uint256)
|
||||||
|
unsafeNumCheckpoints(address) returns (uint256) envfree
|
||||||
|
|
||||||
|
// solidity generated getters
|
||||||
|
_delegation(address) returns (address) envfree
|
||||||
|
|
||||||
|
// external functions
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// gets the most recent votes for a user
|
||||||
|
ghost userVotes(address) returns uint224{
|
||||||
|
init_state axiom forall address a. userVotes(a) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sums the total votes for all users
|
||||||
|
ghost totalVotes() returns mathint {
|
||||||
|
init_state axiom totalVotes() == 0;
|
||||||
|
axiom totalVotes() >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hook Sstore _checkpoints[KEY address account].votes uint224 newVotes (uint224 oldVotes) STORAGE {
|
||||||
|
havoc userVotes assuming
|
||||||
|
userVotes@new(account) == newVotes;
|
||||||
|
|
||||||
|
havoc totalVotes assuming
|
||||||
|
totalVotes@new() == totalVotes@old() + to_mathint(newVotes) - to_mathint(userVotes(account));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ghost lastFromBlock(address) returns uint32;
|
||||||
|
|
||||||
|
ghost doubleFromBlock(address) returns bool {
|
||||||
|
init_state axiom forall address a. doubleFromBlock(a) == false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
hook Sstore _checkpoints[KEY address account].fromBlock uint32 newBlock (uint32 oldBlock) STORAGE {
|
||||||
|
havoc lastFromBlock assuming
|
||||||
|
lastFromBlock@new(account) == newBlock;
|
||||||
|
|
||||||
|
havoc doubleFromBlock assuming
|
||||||
|
doubleFromBlock@new(account) == (newBlock == lastFromBlock(account));
|
||||||
|
}
|
||||||
|
|
||||||
|
// for some checkpoint, the fromBlock is less than the current block number
|
||||||
|
// passes but fails rule sanity from hash on delegate by sig
|
||||||
|
invariant timestamp_constrains_fromBlock(address account, uint32 index, env e)
|
||||||
|
ckptFromBlock(account, index) < e.block.number
|
||||||
|
filtered { f -> !f.isView }
|
||||||
|
{
|
||||||
|
preserved {
|
||||||
|
require index < numCheckpoints(account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// numCheckpoints are less than maxInt
|
||||||
|
// passes because numCheckpoints does a safeCast
|
||||||
|
// invariant maxInt_constrains_numBlocks(address account)
|
||||||
|
// numCheckpoints(account) < 4294967295 // 2^32
|
||||||
|
|
||||||
|
// can't have more checkpoints for a given account than the last from block
|
||||||
|
// passes
|
||||||
|
invariant fromBlock_constrains_numBlocks(address account)
|
||||||
|
numCheckpoints(account) <= ckptFromBlock(account, numCheckpoints(account) - 1)
|
||||||
|
filtered { f -> !f.isView }
|
||||||
|
{ preserved with(env e) {
|
||||||
|
require e.block.number >= ckptFromBlock(account, numCheckpoints(account) - 1); // this should be true from the invariant above!!
|
||||||
|
}}
|
||||||
|
|
||||||
|
// for any given checkpoint, the fromBlock must be greater than the checkpoint
|
||||||
|
// this proves the above invariant in combination with the below invariant
|
||||||
|
// if checkpoint has a greater fromBlock than the last, and the FromBlock is always greater than the pos.
|
||||||
|
// Then the number of positions must be less than the currentFromBlock
|
||||||
|
// ^note that the tool is assuming it's possible for the starting fromBlock to be 0 or anything, and does not know the current starting block
|
||||||
|
// passes + rule sanity
|
||||||
|
invariant fromBlock_greaterThanEq_pos(address account, uint32 pos)
|
||||||
|
ckptFromBlock(account, pos) >= pos
|
||||||
|
filtered { f -> !f.isView }
|
||||||
|
|
||||||
|
// a larger index must have a larger fromBlock
|
||||||
|
// passes + rule sanity
|
||||||
|
invariant fromBlock_increasing(address account, uint32 pos, uint32 pos2)
|
||||||
|
pos > pos2 => ckptFromBlock(account, pos) > ckptFromBlock(account, pos2)
|
||||||
|
filtered { f -> !f.isView }
|
||||||
|
|
||||||
|
|
||||||
|
// converted from an invariant to a rule to slightly change the logic
|
||||||
|
// if the fromBlock is the same as before, then the number of checkpoints stays the same
|
||||||
|
// however if the fromBlock is new than the number of checkpoints increases
|
||||||
|
// passes, fails rule sanity because tautology check seems to be bugged
|
||||||
|
rule unique_checkpoints_rule(method f) {
|
||||||
|
env e; calldataarg args;
|
||||||
|
address account;
|
||||||
|
uint32 num_ckpts_ = numCheckpoints(account);
|
||||||
|
uint32 fromBlock_ = num_ckpts_ == 0 ? 0 : ckptFromBlock(account, num_ckpts_ - 1);
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
uint32 _num_ckpts = numCheckpoints(account);
|
||||||
|
uint32 _fromBlock = _num_ckpts == 0 ? 0 : ckptFromBlock(account, _num_ckpts - 1);
|
||||||
|
|
||||||
|
|
||||||
|
assert fromBlock_ == _fromBlock => num_ckpts_ == _num_ckpts || _num_ckpts == 1, "same fromBlock, new checkpoint";
|
||||||
|
// this assert fails consistently
|
||||||
|
// assert !doubleFromBlock(account) => ckpts_ != _ckpts, "new fromBlock but total checkpoints not being increased";
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes neither account has delegated
|
||||||
|
// currently fails due to this scenario. A has maxint number of checkpoints
|
||||||
|
// an additional checkpoint is added which overflows and sets A's votes to 0
|
||||||
|
// passes + rule sanity (- a bad tautology check)
|
||||||
|
rule transfer_safe() {
|
||||||
|
env e;
|
||||||
|
uint256 ID;
|
||||||
|
address a; address b;
|
||||||
|
|
||||||
|
require delegates(a) != delegates(b); // confirmed if they both delegate to the same person then transfer keeps the votes the same
|
||||||
|
require numCheckpoints(delegates(a)) < 1000000;
|
||||||
|
require numCheckpoints(delegates(b)) < 1000000;
|
||||||
|
|
||||||
|
uint256 votesA_pre = getVotes(delegates(a));
|
||||||
|
uint256 votesB_pre = getVotes(delegates(b));
|
||||||
|
|
||||||
|
mathint totalVotes_pre = totalVotes();
|
||||||
|
|
||||||
|
transferFrom(e, a, b, ID);
|
||||||
|
|
||||||
|
mathint totalVotes_post = totalVotes();
|
||||||
|
uint256 votesA_post = getVotes(delegates(a));
|
||||||
|
uint256 votesB_post = getVotes(delegates(b));
|
||||||
|
|
||||||
|
// if an account that has not delegated transfers balance to an account that has, it will increase the total supply of votes
|
||||||
|
assert totalVotes_pre == totalVotes_post, "transfer changed total supply";
|
||||||
|
assert delegates(a) != 0 => votesA_pre - 1 == votesA_post, "A lost the wrong amount of votes";
|
||||||
|
assert delegates(b) != 0 => votesB_pre + 1 == votesB_post, "B gained the wrong amount of votes";
|
||||||
|
}
|
||||||
|
|
||||||
|
// for any given function f, if the delegate is changed the function must be delegate or delegateBySig
|
||||||
|
// passes
|
||||||
|
rule delegates_safe(method f) filtered {f -> (f.selector != delegate(address).selector &&
|
||||||
|
f.selector != _delegate(address, address).selector &&
|
||||||
|
f.selector != delegateBySig(address, uint256, uint256, uint8, bytes32, bytes32).selector) }
|
||||||
|
{
|
||||||
|
env e; calldataarg args;
|
||||||
|
address account;
|
||||||
|
address pre = delegates(account);
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
address post = delegates(account);
|
||||||
|
|
||||||
|
assert pre == post, "invalid delegate change";
|
||||||
|
}
|
||||||
|
|
||||||
|
// delegates increases the delegatee's votes by the proper amount
|
||||||
|
// passes + rule sanity
|
||||||
|
rule delegatee_receives_votes() {
|
||||||
|
env e;
|
||||||
|
address delegator; address delegatee;
|
||||||
|
|
||||||
|
require numCheckpoints(delegatee) < 1000000;
|
||||||
|
require delegates(delegator) != delegatee;
|
||||||
|
require delegatee != 0x0;
|
||||||
|
|
||||||
|
|
||||||
|
uint256 delegator_bal = balanceOf(e, delegator);
|
||||||
|
uint256 votes_= getVotes(delegatee);
|
||||||
|
|
||||||
|
_delegate(e, delegator, delegatee);
|
||||||
|
|
||||||
|
uint256 _votes = getVotes(delegatee);
|
||||||
|
assert _votes == votes_ + delegator_bal, "delegatee did not receive votes";
|
||||||
|
}
|
||||||
|
|
||||||
|
// passes + rule sanity
|
||||||
|
rule previous_delegatee_votes_removed() {
|
||||||
|
env e;
|
||||||
|
address delegator; address delegatee; address third;
|
||||||
|
|
||||||
|
require third != delegatee;
|
||||||
|
require delegates(delegator) == third;
|
||||||
|
require numCheckpoints(third) < 1000000;
|
||||||
|
|
||||||
|
uint256 delegator_bal = balanceOf(e, delegator);
|
||||||
|
uint256 votes_ = getVotes(third);
|
||||||
|
|
||||||
|
_delegate(e, delegator, delegatee);
|
||||||
|
|
||||||
|
uint256 _votes = getVotes(third);
|
||||||
|
|
||||||
|
assert third != 0x0 => _votes == votes_ - delegator_bal, "votes not removed from the previous delegatee";
|
||||||
|
}
|
||||||
|
|
||||||
|
// passes with rule sanity
|
||||||
|
rule delegate_contained() {
|
||||||
|
env e;
|
||||||
|
address delegator; address delegatee; address other;
|
||||||
|
|
||||||
|
require other != delegatee;
|
||||||
|
require other != delegates(delegator);
|
||||||
|
|
||||||
|
uint256 votes_ = getVotes(other);
|
||||||
|
|
||||||
|
_delegate(e, delegator, delegatee);
|
||||||
|
|
||||||
|
uint256 _votes = getVotes(other);
|
||||||
|
|
||||||
|
assert votes_ == _votes, "votes not contained";
|
||||||
|
}
|
||||||
|
|
||||||
|
rule delegate_no_frontrunning(method f) {
|
||||||
|
env e; calldataarg args;
|
||||||
|
address delegator; address delegatee; address third; address other;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
require numCheckpoints(delegatee) < 1000000;
|
||||||
|
require numCheckpoints(third) < 1000000;
|
||||||
|
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
uint256 delegator_bal = balanceOf(e, delegator);
|
||||||
|
uint256 delegatee_votes_ = getVotes(delegatee);
|
||||||
|
uint256 third_votes_ = getVotes(third);
|
||||||
|
uint256 other_votes_ = getVotes(other);
|
||||||
|
require delegates(delegator) == third;
|
||||||
|
require third != delegatee;
|
||||||
|
require other != third;
|
||||||
|
require other != delegatee;
|
||||||
|
require delegatee != 0x0;
|
||||||
|
|
||||||
|
_delegate(e, delegator, delegatee);
|
||||||
|
|
||||||
|
uint256 _delegatee_votes = getVotes(delegatee);
|
||||||
|
uint256 _third_votes = getVotes(third);
|
||||||
|
uint256 _other_votes = getVotes(other);
|
||||||
|
|
||||||
|
|
||||||
|
// previous delegatee loses all of their votes
|
||||||
|
// delegatee gains that many votes
|
||||||
|
// third loses any votes delegated to them
|
||||||
|
assert _delegatee_votes == delegatee_votes_ + delegator_bal, "delegatee did not receive votes";
|
||||||
|
assert third != 0 => _third_votes == third_votes_ - delegator_bal, "votes not removed from third";
|
||||||
|
assert other_votes_ == _other_votes, "delegate not contained";
|
||||||
|
}
|
||||||
@ -2,332 +2,348 @@
|
|||||||
///////////////////// Governor.sol base definitions //////////////////////////
|
///////////////////// Governor.sol base definitions //////////////////////////
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
using ERC20VotesHarness as erc20votes
|
|
||||||
|
|
||||||
methods {
|
methods {
|
||||||
|
hashProposal(address[],uint256[],bytes[],bytes32) returns uint256 envfree
|
||||||
|
state(uint256) returns uint8
|
||||||
|
proposalThreshold() returns uint256 envfree
|
||||||
proposalSnapshot(uint256) returns uint256 envfree // matches proposalVoteStart
|
proposalSnapshot(uint256) returns uint256 envfree // matches proposalVoteStart
|
||||||
proposalDeadline(uint256) returns uint256 envfree // matches proposalVoteEnd
|
proposalDeadline(uint256) returns uint256 envfree // matches proposalVoteEnd
|
||||||
hashProposal(address[],uint256[],bytes[],bytes32) returns uint256 envfree
|
proposalProposer(uint256) returns address envfree
|
||||||
isExecuted(uint256) returns bool envfree
|
|
||||||
isCanceled(uint256) returns bool envfree
|
propose(address[], uint256[], bytes[], string) returns uint256
|
||||||
execute(address[], uint256[], bytes[], bytes32) returns uint256
|
execute(address[], uint256[], bytes[], bytes32) returns uint256
|
||||||
hasVoted(uint256, address) returns bool
|
cancel(address[], uint256[], bytes[], bytes32) returns uint256
|
||||||
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)
|
getVotes(address, uint256) returns uint256 => DISPATCHER(true)
|
||||||
|
getVotesWithParams(address, uint256, bytes) returns uint256 => DISPATCHER(true)
|
||||||
|
castVote(uint256, uint8) returns uint256
|
||||||
|
castVoteWithReason(uint256, uint8, string) returns uint256
|
||||||
|
castVoteWithReasonAndParams(uint256, uint8, string, bytes) returns uint256
|
||||||
|
|
||||||
getPastTotalSupply(uint256 t) returns uint256 => PER_CALLEE_CONSTANT
|
// GovernorTimelockController
|
||||||
getPastVotes(address a, uint256 t) returns uint256 => PER_CALLEE_CONSTANT
|
queue(address[], uint256[], bytes[], bytes32) returns uint256
|
||||||
|
|
||||||
//scheduleBatch(address[],uint256[],bytes[],bytes32,bytes32,uint256) => DISPATCHER(true)
|
// GovernorCountingSimple
|
||||||
//executeBatch(address[], uint256[], bytes[], bytes32, bytes32) => DISPATCHER(true)
|
hasVoted(uint256, address) returns bool envfree
|
||||||
|
updateQuorumNumerator(uint256)
|
||||||
|
|
||||||
|
// harness functions
|
||||||
|
getAgainstVotes(uint256) returns uint256 envfree
|
||||||
|
getAbstainVotes(uint256) returns uint256 envfree
|
||||||
|
getForVotes(uint256) returns uint256 envfree
|
||||||
|
getExecutor(uint256) returns bool envfree
|
||||||
|
isExecuted(uint256) returns bool envfree
|
||||||
|
isCanceled(uint256) returns bool envfree
|
||||||
|
|
||||||
|
// full harness functions
|
||||||
|
getPastTotalSupply(uint256) returns uint256 => DISPATCHER(true)
|
||||||
|
/// getPastVotes(address, uint256) returns uint256 => DISPATCHER(true)
|
||||||
|
|
||||||
|
// internal functions made public in harness:
|
||||||
|
quorumReached(uint256) returns bool
|
||||||
|
voteSucceeded(uint256) returns bool envfree
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
/*
|
||||||
//////////////////////////////// Definitions /////////////////////////////////
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
│ Definitions │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
|
||||||
|
definition proposalCreated(uint256 pId) returns bool =
|
||||||
|
proposalSnapshot(pId) > 0 && proposalDeadline(pId) > 0 && proposalProposer(pId) != 0;
|
||||||
|
|
||||||
// proposal was created - relation proved in noStartBeforeCreation
|
/*
|
||||||
definition proposalCreated(uint256 pId) returns bool = proposalSnapshot(pId) > 0;
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Helper functions │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
*/
|
||||||
///////////////////////////// Helper Functions ///////////////////////////////
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
function helperFunctionsWithRevert(uint256 proposalId, method f, env e) {
|
function helperFunctionsWithRevert(uint256 proposalId, method f, env e) {
|
||||||
address[] targets; uint256[] values; bytes[] calldatas; string reason; bytes32 descriptionHash;
|
address[] targets; uint256[] values; bytes[] calldatas; string reason; bytes32 descriptionHash;
|
||||||
uint8 support; uint8 v; bytes32 r; bytes32 s;
|
uint8 support; uint8 v; bytes32 r; bytes32 s; bytes params;
|
||||||
if (f.selector == propose(address[], uint256[], bytes[], string).selector) {
|
|
||||||
uint256 result = propose@withrevert(e, targets, values, calldatas, reason);
|
if (f.selector == propose(address[], uint256[], bytes[], string).selector)
|
||||||
require(result == proposalId);
|
{
|
||||||
} else if (f.selector == execute(address[], uint256[], bytes[], bytes32).selector) {
|
uint256 result = propose@withrevert(e, targets, values, calldatas, reason);
|
||||||
uint256 result = execute@withrevert(e, targets, values, calldatas, descriptionHash);
|
require result == proposalId;
|
||||||
require(result == proposalId);
|
}
|
||||||
} else if (f.selector == castVote(uint256, uint8).selector) {
|
else if (f.selector == execute(address[], uint256[], bytes[], bytes32).selector)
|
||||||
castVote@withrevert(e, proposalId, support);
|
{
|
||||||
} else if (f.selector == castVoteWithReason(uint256, uint8, string).selector) {
|
uint256 result = execute@withrevert(e, targets, values, calldatas, descriptionHash);
|
||||||
|
require result == proposalId;
|
||||||
|
}
|
||||||
|
else if (f.selector == queue(address[], uint256[], bytes[], bytes32).selector)
|
||||||
|
{
|
||||||
|
uint256 result = queue@withrevert(e, targets, values, calldatas, descriptionHash);
|
||||||
|
require result == proposalId;
|
||||||
|
}
|
||||||
|
else if (f.selector == cancel(address[], uint256[], bytes[], bytes32).selector)
|
||||||
|
{
|
||||||
|
uint256 result = cancel@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);
|
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 == castVoteWithReasonAndParams(uint256,uint8,string,bytes).selector)
|
||||||
} else if (f.selector == queue(address[], uint256[], bytes[], bytes32).selector) {
|
{
|
||||||
queue@withrevert(e, targets, values, calldatas, descriptionHash);
|
castVoteWithReasonAndParams@withrevert(e, proposalId, support, reason, params);
|
||||||
} else {
|
}
|
||||||
|
else if (f.selector == castVoteBySig(uint256, uint8,uint8, bytes32, bytes32).selector)
|
||||||
|
{
|
||||||
|
castVoteBySig@withrevert(e, proposalId, support, v, r, s);
|
||||||
|
}
|
||||||
|
else if (f.selector == castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32).selector)
|
||||||
|
{
|
||||||
|
castVoteWithReasonAndParamsBySig@withrevert(e, proposalId, support, reason, params, v, r, s);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
calldataarg args;
|
calldataarg args;
|
||||||
f@withrevert(e, args);
|
f@withrevert(e, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
///////////////////////////////////////////////////// State Diagram //////////////////////////////////////////////////////////
|
│ Invariant: Votes start and end are either initialized (non zero) or uninitialized (zero) simultaneously │
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
│ │
|
||||||
// //
|
│ This invariant assumes that the block number cannot be 0 at any stage of the contract cycle │
|
||||||
// castVote(s)() //
|
│ This is very safe assumption as usually the 0 block is genesis block which is uploaded with data │
|
||||||
// ------------- propose() ---------------------- time pass --------------- time passes ----------- //
|
│ by the developers and will not be valid to raise proposals (at the current way that block chain is functioning) │
|
||||||
// | No Proposal | --------> | Before Start (Delay) | --------> | Voting Period | ----------------------> | execute() | //
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
// ------------- ---------------------- --------------- -> Executed/Canceled ----------- //
|
|
||||||
// ------------------------------------------------------------|---------------|-------------------------|--------------> //
|
|
||||||
// t start end timelock //
|
|
||||||
// //
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
*/
|
*/
|
||||||
|
invariant proposalStateConsistency(uint256 pId)
|
||||||
|
(proposalProposer(pId) != 0 <=> proposalSnapshot(pId) != 0) && (proposalProposer(pId) != 0 <=> proposalDeadline(pId) != 0)
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
{
|
||||||
///////////////////////////////// Global Valid States /////////////////////////////////
|
preserved with (env e) {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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;
|
require e.block.number > 0;
|
||||||
}}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A proposal starting block number must be less or equal than the proposal end date
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
*/
|
│ Invariant: cancel => created │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
invariant canceledImplyCreated(uint pId)
|
||||||
|
isCanceled(pId) => proposalCreated(pId)
|
||||||
|
{
|
||||||
|
preserved with (env e) {
|
||||||
|
requireInvariant proposalStateConsistency(pId);
|
||||||
|
require e.block.number > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Invariant: executed => created │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
invariant executedImplyCreated(uint pId)
|
||||||
|
isExecuted(pId) => proposalCreated(pId)
|
||||||
|
{
|
||||||
|
preserved with (env e) {
|
||||||
|
requireInvariant proposalStateConsistency(pId);
|
||||||
|
require e.block.number > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Invariant: Votes start before it ends │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
invariant voteStartBeforeVoteEnd(uint256 pId)
|
invariant voteStartBeforeVoteEnd(uint256 pId)
|
||||||
// from < to <= because snapshot and deadline can be the same block number if delays are set to 0
|
proposalSnapshot(pId) <= proposalDeadline(pId)
|
||||||
// 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 <
|
preserved {
|
||||||
(proposalSnapshot(pId) > 0 => proposalSnapshot(pId) <= proposalDeadline(pId))
|
requireInvariant proposalStateConsistency(pId);
|
||||||
// (proposalSnapshot(pId) > 0 => proposalSnapshot(pId) <= proposalDeadline(pId))
|
}
|
||||||
{ preserved {
|
}
|
||||||
requireInvariant startAndEndDatesNonZero(pId);
|
|
||||||
}}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A proposal cannot be both executed and canceled simultaneously.
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
*/
|
│ Invariant: A proposal cannot be both executed and canceled simultaneously │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
invariant noBothExecutedAndCanceled(uint256 pId)
|
invariant noBothExecutedAndCanceled(uint256 pId)
|
||||||
!isExecuted(pId) || !isCanceled(pId)
|
!isExecuted(pId) || !isCanceled(pId)
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A proposal could be executed only if quorum was reached and vote succeeded
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
*/
|
│ Rule: No double proposition │
|
||||||
rule executionOnlyIfQuoromReachedAndVoteSucceeded(uint256 pId, env e, method f){
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
bool isExecutedBefore = isExecuted(pId);
|
*/
|
||||||
bool quorumReachedBefore = _quorumReached(e, pId);
|
rule noDoublePropose(uint256 pId, env e) {
|
||||||
bool voteSucceededBefore = _voteSucceeded(pId);
|
require proposalCreated(pId);
|
||||||
|
|
||||||
calldataarg args;
|
address[] targets; uint256[] values; bytes[] calldatas; string reason;
|
||||||
f(e, args);
|
uint256 result = propose(e, targets, values, calldatas, reason);
|
||||||
|
|
||||||
bool isExecutedAfter = isExecuted(pId);
|
assert result != pId, "double proposal";
|
||||||
assert (!isExecutedBefore && isExecutedAfter) => (quorumReachedBefore && voteSucceededBefore), "quorum was changed";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
/*
|
||||||
////////////////////////////////// In-State Rules /////////////////////////////////////
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
│ Rule: Once a proposal is created, voteStart, voteEnd and proposer are immutable │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule immutableFieldsAfterProposalCreation(uint256 pId, env e, method f, calldataarg arg) {
|
||||||
|
require proposalCreated(pId);
|
||||||
|
|
||||||
//==========================================
|
uint256 voteStart = proposalSnapshot(pId);
|
||||||
//------------- Voting Period --------------
|
uint256 voteEnd = proposalDeadline(pId);
|
||||||
//==========================================
|
address proposer = proposalProposer(pId);
|
||||||
|
|
||||||
|
f(e, arg);
|
||||||
|
|
||||||
|
assert voteStart == proposalSnapshot(pId), "Start date was changed";
|
||||||
|
assert voteEnd == proposalDeadline(pId), "End date was changed";
|
||||||
|
assert proposer == proposalProposer(pId), "Proposer was changed";
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A user cannot vote twice
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
*/
|
│ Rule: A user cannot vote twice │
|
||||||
// Checked for castVote only. all 3 castVote functions call _castVote, so the completeness of the verification is counted on
|
│ │
|
||||||
// the fact that the 3 functions themselves makes no changes, but rather call an internal function to execute.
|
│ Checked for castVote only. all 3 castVote functions call _castVote, so the completeness of the verification is │
|
||||||
// That means that we do not check those 3 functions directly, however for castVote & castVoteWithReason it is quite trivial
|
│ counted on the fact that the 3 functions themselves makes no changes, but rather call an internal function to │
|
||||||
// to understand why this is ok. For castVoteBySig we basically assume that the signature referendum is correct without checking it.
|
│ execute. That means that we do not check those 3 functions directly, however for castVote & castVoteWithReason it │
|
||||||
// We could check each function separately and pass the rule, but that would have uglyfied the code with no concrete
|
│ is quite trivial to understand why this is ok. For castVoteBySig we basically assume that the signature referendum │
|
||||||
// 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.
|
│ is correct without checking it. We could check each function separately and pass the rule, but that would have │
|
||||||
rule doubleVoting(uint256 pId, uint8 sup, method f) {
|
│ uglyfied the code with no concrete benefit, as it is evident that nothing is happening in the first 2 functions │
|
||||||
env e;
|
│ (calling a view function), and we do not desire to check the signature verification. │
|
||||||
address user = e.msg.sender;
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
bool votedCheck = hasVoted(e, pId, user);
|
*/
|
||||||
|
rule noDoubleVoting(uint256 pId, env e, uint8 sup) {
|
||||||
|
bool votedCheck = hasVoted(pId, e.msg.sender);
|
||||||
|
|
||||||
castVote@withrevert(e, pId, sup);
|
castVote@withrevert(e, pId, sup);
|
||||||
|
|
||||||
assert votedCheck => lastReverted, "double voting occurred";
|
assert votedCheck => lastReverted, "double voting occurred";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//////////////////////////// State Transitions Rules //////////////////////////////////
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
//===========================================
|
|
||||||
//-------- Propose() --> End of Time --------
|
|
||||||
//===========================================
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Once a proposal is created, voteStart and voteEnd are immutable
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
*/
|
│ Rule: A proposal could be executed only if quorum was reached and vote succeeded │
|
||||||
rule immutableFieldsAfterProposalCreation(uint256 pId, method f) {
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
uint256 _voteStart = proposalSnapshot(pId);
|
*/
|
||||||
uint256 _voteEnd = proposalDeadline(pId);
|
rule executionOnlyIfQuoromReachedAndVoteSucceeded(uint256 pId, env e, method f, calldataarg args) {
|
||||||
|
require !isExecuted(pId);
|
||||||
|
|
||||||
require proposalCreated(pId); // startDate > 0
|
bool quorumReachedBefore = quorumReached(e, pId);
|
||||||
|
bool voteSucceededBefore = voteSucceeded(pId);
|
||||||
|
|
||||||
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);
|
f(e, args);
|
||||||
|
|
||||||
assert e.block.number < proposalDeadline(pId) => (!isExecuted(pId) && !isCanceled(pId)), "executed/cancelled before deadline";
|
assert isExecuted(pId) => (quorumReachedBefore && voteSucceededBefore), "quorum was changed";
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
////////////////////// 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: Voting cannot start at a block number prior to proposal’s creation block number │
|
||||||
rule allFunctionsRevertIfCanceled(method f) filtered {
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule noStartBeforeCreation(uint256 pId, env e, method f, calldataarg args){
|
||||||
|
require !proposalCreated(pId);
|
||||||
|
f(e, args);
|
||||||
|
assert proposalCreated(pId) => proposalSnapshot(pId) >= e.block.number, "starts before proposal";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: A proposal cannot be executed before it ends │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule noExecuteBeforeDeadline(uint256 pId, env e, method f, calldataarg args) {
|
||||||
|
require !isExecuted(pId);
|
||||||
|
f(e, args);
|
||||||
|
assert isExecuted(pId) => proposalDeadline(pId) <= e.block.number, "executed before deadline";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: All proposal specific (non-view) functions should revert if proposal is executed │
|
||||||
|
│ │
|
||||||
|
│ In this rule we show that if a function is executed, i.e. execute() was called on the proposal ID, 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(uint256 pId, env e, method f, calldataarg args) filtered {
|
||||||
f -> !f.isView && !f.isFallback
|
f -> !f.isView && !f.isFallback
|
||||||
&& f.selector != updateTimelock(address).selector
|
&& f.selector != updateTimelock(address).selector
|
||||||
&& f.selector != updateQuorumNumerator(uint256).selector
|
&& f.selector != updateQuorumNumerator(uint256).selector
|
||||||
&& f.selector != queue(address[],uint256[],bytes[],bytes32).selector
|
|
||||||
&& f.selector != relay(address,uint256,bytes).selector
|
&& f.selector != relay(address,uint256,bytes).selector
|
||||||
&& f.selector != 0xb9a61961 // __acceptAdmin()
|
&& f.selector != 0xb9a61961 // __acceptAdmin()
|
||||||
|
&& f.selector != onERC721Received(address,address,uint256,bytes).selector
|
||||||
|
&& f.selector != onERC1155Received(address,address,uint256,uint256,bytes).selector
|
||||||
|
&& f.selector != onERC1155BatchReceived(address,address,uint256[],uint256[],bytes).selector
|
||||||
} {
|
} {
|
||||||
env e; calldataarg args;
|
require isExecuted(pId);
|
||||||
uint256 pId;
|
|
||||||
require(isCanceled(pId));
|
|
||||||
requireInvariant noBothExecutedAndCanceled(pId);
|
requireInvariant noBothExecutedAndCanceled(pId);
|
||||||
requireInvariant canceledImplyStartAndEndDateNonZero(pId);
|
requireInvariant executedImplyCreated(pId);
|
||||||
|
|
||||||
helperFunctionsWithRevert(pId, f, e);
|
helperFunctionsWithRevert(pId, f, e);
|
||||||
|
|
||||||
assert(lastReverted, "Function was not reverted");
|
assert lastReverted, "Function was not reverted";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Proposal can be switched to executed only via execute() function
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
*/
|
│ Rule: All proposal specific (non-view) functions should revert if proposal is canceled │
|
||||||
rule executedOnlyAfterExecuteFunc(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash, method f) {
|
│ │
|
||||||
env e; calldataarg args;
|
│ In this rule we show that if a function is executed, i.e. execute() was called on the proposal ID, non of the │
|
||||||
uint256 pId;
|
│ proposal specific functions can make changes again. In executedOnlyAfterExecuteFunc we connected the executed │
|
||||||
bool executedBefore = isExecuted(pId);
|
│ attribute to the execute() function, showing that only execute() can change it, and that it will always change it. │
|
||||||
require(!executedBefore);
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule allFunctionsRevertIfCanceled(uint256 pId, env e, method f, calldataarg args) filtered {
|
||||||
|
f -> !f.isView && !f.isFallback
|
||||||
|
&& f.selector != updateTimelock(address).selector
|
||||||
|
&& f.selector != updateQuorumNumerator(uint256).selector
|
||||||
|
&& f.selector != relay(address,uint256,bytes).selector
|
||||||
|
&& f.selector != 0xb9a61961 // __acceptAdmin()
|
||||||
|
&& f.selector != onERC721Received(address,address,uint256,bytes).selector
|
||||||
|
&& f.selector != onERC1155Received(address,address,uint256,uint256,bytes).selector
|
||||||
|
&& f.selector != onERC1155BatchReceived(address,address,uint256[],uint256[],bytes).selector
|
||||||
|
} {
|
||||||
|
require isCanceled(pId);
|
||||||
|
requireInvariant noBothExecutedAndCanceled(pId);
|
||||||
|
requireInvariant canceledImplyCreated(pId);
|
||||||
|
|
||||||
helperFunctionsWithRevert(pId, f, e);
|
helperFunctionsWithRevert(pId, f, e);
|
||||||
|
|
||||||
bool executedAfter = isExecuted(pId);
|
assert lastReverted, "Function was not reverted";
|
||||||
assert(executedAfter != executedBefore => f.selector == execute(address[], uint256[], bytes[], bytes32).selector, "isExecuted only changes in the execute method");
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: Proposal can be switched state only by specific functions │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule stateOnlyAfterFunc(uint256 pId, env e, method f) {
|
||||||
|
bool createdBefore = proposalCreated(pId);
|
||||||
|
bool executedBefore = isExecuted(pId);
|
||||||
|
bool canceledBefore = isCanceled(pId);
|
||||||
|
|
||||||
|
helperFunctionsWithRevert(pId, f, e);
|
||||||
|
|
||||||
|
assert (proposalCreated(pId) != createdBefore)
|
||||||
|
=> (createdBefore == false && f.selector == propose(address[], uint256[], bytes[], string).selector),
|
||||||
|
"proposalCreated only changes in the propose method";
|
||||||
|
|
||||||
|
assert (isExecuted(pId) != executedBefore)
|
||||||
|
=> (executedBefore == false && f.selector == execute(address[], uint256[], bytes[], bytes32).selector),
|
||||||
|
"isExecuted only changes in the execute method";
|
||||||
|
|
||||||
|
assert (isCanceled(pId) != canceledBefore)
|
||||||
|
=> (canceledBefore == false && f.selector == cancel(address[], uint256[], bytes[], bytes32).selector),
|
||||||
|
"isCanceled only changes in the cancel method";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import "GovernorBase.spec"
|
import "GovernorBase.spec"
|
||||||
|
|
||||||
using ERC20VotesHarness as erc20votes
|
|
||||||
|
|
||||||
methods {
|
methods {
|
||||||
ghost_sum_vote_power_by_id(uint256) returns uint256 envfree
|
ghost_sum_vote_power_by_id(uint256) returns uint256 envfree
|
||||||
|
|
||||||
@ -9,9 +7,6 @@ methods {
|
|||||||
proposalVotes(uint256) returns (uint256, uint256, uint256) envfree
|
proposalVotes(uint256) returns (uint256, uint256, uint256) envfree
|
||||||
|
|
||||||
quorumNumerator() returns uint256
|
quorumNumerator() returns uint256
|
||||||
_executor() returns address
|
|
||||||
|
|
||||||
erc20votes._getPastVotes(address, uint256) returns uint256
|
|
||||||
|
|
||||||
getExecutor() returns address
|
getExecutor() returns address
|
||||||
|
|
||||||
@ -102,21 +97,21 @@ hook Sstore _proposalVotes [KEY uint256 pId].abstainVotes uint256 votes(uint256
|
|||||||
/*
|
/*
|
||||||
* sum of all votes casted is equal to the sum of voting power of those who voted, per each proposal
|
* sum of all votes casted is equal to the sum of voting power of those who voted, per each proposal
|
||||||
*/
|
*/
|
||||||
invariant SumOfVotesCastEqualSumOfPowerOfVotedPerProposal(uint256 pId)
|
invariant SumOfVotesCastEqualSumOfPowerOfVotedPerProposal(uint256 pId)
|
||||||
tracked_weight(pId) == ghost_sum_vote_power_by_id(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
|
* sum of all votes casted is equal to the sum of voting power of those who voted
|
||||||
*/
|
*/
|
||||||
invariant SumOfVotesCastEqualSumOfPowerOfVoted()
|
invariant SumOfVotesCastEqualSumOfPowerOfVoted()
|
||||||
sum_tracked_weight() == sum_all_votes_power()
|
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
|
* 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)
|
invariant OneIsNotMoreThanAll(uint256 pId)
|
||||||
sum_all_votes_power() >= tracked_weight(pId)
|
sum_all_votes_power() >= tracked_weight(pId)
|
||||||
|
|
||||||
|
|
||||||
@ -132,7 +127,7 @@ invariant OneIsNotMoreThanAll(uint256 pId)
|
|||||||
// the fact that the 3 functions themselves makes no changes, but rather call an internal function to execute.
|
// the fact that the 3 functions themselves makes no changes, 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
|
// 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.
|
// 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 separately and pass the rule, but that would have uglyfied the code with no concrete
|
// We could check each function separately 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.
|
// 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) {
|
rule noVoteForSomeoneElse(uint256 pId, uint8 sup, method f) {
|
||||||
env e; calldataarg args;
|
env e; calldataarg args;
|
||||||
@ -140,24 +135,24 @@ rule noVoteForSomeoneElse(uint256 pId, uint8 sup, method f) {
|
|||||||
address voter = e.msg.sender;
|
address voter = e.msg.sender;
|
||||||
address user;
|
address user;
|
||||||
|
|
||||||
bool hasVotedBefore_User = hasVoted(e, pId, user);
|
bool hasVotedBefore_User = hasVoted(pId, user);
|
||||||
|
|
||||||
castVote@withrevert(e, pId, sup);
|
castVote@withrevert(e, pId, sup);
|
||||||
require(!lastReverted);
|
require(!lastReverted);
|
||||||
|
|
||||||
bool hasVotedAfter_User = hasVoted(e, pId, user);
|
bool hasVotedAfter_User = hasVoted(pId, user);
|
||||||
|
|
||||||
assert user != voter => hasVotedBefore_User == hasVotedAfter_User;
|
assert user != voter => hasVotedBefore_User == hasVotedAfter_User;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Total voting tally is monotonically non-decreasing in every operation
|
* Total voting tally is monotonically non-decreasing in every operation
|
||||||
*/
|
*/
|
||||||
rule votingWeightMonotonicity(method f){
|
rule votingWeightMonotonicity(method f){
|
||||||
uint256 votingWeightBefore = sum_tracked_weight();
|
uint256 votingWeightBefore = sum_tracked_weight();
|
||||||
|
|
||||||
env e;
|
env e;
|
||||||
calldataarg args;
|
calldataarg args;
|
||||||
f(e, args);
|
f(e, args);
|
||||||
|
|
||||||
@ -172,12 +167,12 @@ rule votingWeightMonotonicity(method f){
|
|||||||
*/
|
*/
|
||||||
rule hasVotedCorrelation(uint256 pId, method f, env e, uint256 bn) {
|
rule hasVotedCorrelation(uint256 pId, method f, env e, uint256 bn) {
|
||||||
address acc = e.msg.sender;
|
address acc = e.msg.sender;
|
||||||
|
|
||||||
uint256 againstBefore = votesAgainst();
|
uint256 againstBefore = votesAgainst();
|
||||||
uint256 forBefore = votesFor();
|
uint256 forBefore = votesFor();
|
||||||
uint256 abstainBefore = votesAbstain();
|
uint256 abstainBefore = votesAbstain();
|
||||||
|
|
||||||
bool hasVotedBefore = hasVoted(e, pId, acc);
|
bool hasVotedBefore = hasVoted(pId, acc);
|
||||||
|
|
||||||
helperFunctionsWithRevert(pId, f, e);
|
helperFunctionsWithRevert(pId, f, e);
|
||||||
require(!lastReverted);
|
require(!lastReverted);
|
||||||
@ -185,10 +180,11 @@ rule hasVotedCorrelation(uint256 pId, method f, env e, uint256 bn) {
|
|||||||
uint256 againstAfter = votesAgainst();
|
uint256 againstAfter = votesAgainst();
|
||||||
uint256 forAfter = votesFor();
|
uint256 forAfter = votesFor();
|
||||||
uint256 abstainAfter = votesAbstain();
|
uint256 abstainAfter = votesAbstain();
|
||||||
|
|
||||||
bool hasVotedAfter = hasVoted(e, pId, acc);
|
|
||||||
|
|
||||||
assert (!hasVotedBefore && hasVotedAfter) => againstBefore <= againstAfter || forBefore <= forAfter || abstainBefore <= abstainAfter, "no correlation";
|
bool hasVotedAfter = hasVoted(pId, acc);
|
||||||
|
|
||||||
|
// want all vote categories to not decrease and at least one category to increase
|
||||||
|
assert (!hasVotedBefore && hasVotedAfter) => (againstBefore <= againstAfter && forBefore <= forAfter && abstainBefore <= abstainAfter), "no correlation: some category decreased";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
308
certora/specs/GovernorPreventLateQuorum.spec
Normal file
308
certora/specs/GovernorPreventLateQuorum.spec
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
import "GovernorBase.spec"
|
||||||
|
import "GovernorCountingSimple.spec"
|
||||||
|
|
||||||
|
using ERC20VotesHarness as token
|
||||||
|
|
||||||
|
methods {
|
||||||
|
// envfree
|
||||||
|
quorumNumerator(uint256) returns uint256
|
||||||
|
quorumDenominator() returns uint256 envfree
|
||||||
|
|
||||||
|
// harness
|
||||||
|
getDeprecatedQuorumNumerator() returns uint256 envfree
|
||||||
|
getQuorumNumeratorLength() returns uint256 envfree
|
||||||
|
getQuorumNumeratorLatest() returns uint256 envfree
|
||||||
|
getExtendedDeadline(uint256) returns uint64 envfree
|
||||||
|
getPastTotalSupply(uint256) returns (uint256) envfree
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Helper Functions //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function sanity() {
|
||||||
|
require getQuorumNumeratorLength() + 1 < max_uint;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
definition deadlineExtendable(env e, uint256 pId) returns bool = getExtendedDeadline(pId) == 0;
|
||||||
|
definition deadlineExtended(env e, uint256 pId) returns bool = getExtendedDeadline(pId) > 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
invariant deadlineConsistency(env e, uint256 pId)
|
||||||
|
(!quorumReached(e, pId) => deadlineExtendable(pId))
|
||||||
|
&&
|
||||||
|
(deadlineExtended(pId) => quorumReached(e, pId))
|
||||||
|
|
||||||
|
|
||||||
|
invariant proposalStateConsistencyExtended(uint256 pId)
|
||||||
|
!proposalCreated(pId) => (getAgainstVotes(pId) == 0 && getAbstainVotes(pId) == 0 && getForVotes(pId) == 0)
|
||||||
|
&& (proposalProposer(pId) == 0 <=> proposalSnapshot(pId) == 0)
|
||||||
|
&& (proposalProposer(pId) == 0 <=> proposalDeadline(pId) == 0)
|
||||||
|
{
|
||||||
|
preserved with (env e) {
|
||||||
|
require e.block.number > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a proposal has reached quorum then the proposal snapshot (start `block.number`) must be non-zero
|
||||||
|
*/
|
||||||
|
invariant quorumReachedEffect(env e, uint256 pId)
|
||||||
|
(quorumReached(e, pId) && getPastTotalSupply(proposalSnapshot(pId)) > 0) => proposalCreated(pId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The quorum numerator is always less than or equal to the quorum denominator.
|
||||||
|
*/
|
||||||
|
invariant quorumRatioLessThanOne(env e, uint256 blockNumber)
|
||||||
|
quorumNumerator(e, blockNumber) <= quorumDenominator()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a proposal's deadline has been extended, then the proposal must have been created and reached quorum.
|
||||||
|
*/
|
||||||
|
invariant cantExtendWhenQuorumUnreached(env e, uint256 pId)
|
||||||
|
getExtendedDeadline(pId) > 0 => (quorumReached(e, pId) && proposalCreated(pId))
|
||||||
|
//{
|
||||||
|
// preserved with (env e1) {
|
||||||
|
// require e1.block.number > proposalSnapshot(pId);
|
||||||
|
// setup(e1, e);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: `updateQuorumNumerator` can only change quorum requirements for future proposals. │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule quorumReachedCantChange(uint256 pId, env e, method f) filtered { f ->
|
||||||
|
!f.isFallback
|
||||||
|
&& !f.isView
|
||||||
|
&& f.selector != relay(address,uint256,bytes).selector
|
||||||
|
&& !castVoteSubset(f)
|
||||||
|
} {
|
||||||
|
sanity(); // not overflowing checkpoint struct
|
||||||
|
require proposalSnapshot(pId) < e.block.number; // vote has started
|
||||||
|
|
||||||
|
bool quorumReachedBefore = quorumReached(e, pId);
|
||||||
|
|
||||||
|
uint256 newQuorumNumerator;
|
||||||
|
updateQuorumNumerator(e, newQuorumNumerator);
|
||||||
|
|
||||||
|
assert quorumReachedBefore == quorumReached(e, pId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: Casting a vote must not decrease any category's total number of votes and increase at least one category's │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule hasVotedCorrelationNonzero(uint256 pId, env e, method f, calldataarg args) filtered { f ->
|
||||||
|
!f.isFallback
|
||||||
|
&& !f.isView
|
||||||
|
&& f.selector != relay(address,uint256,bytes).selector
|
||||||
|
&& !castVoteSubset(f)
|
||||||
|
} {
|
||||||
|
require getVotes(e, e.msg.sender, proposalSnapshot(pId)) > 0; // assuming voter has non-zero voting power
|
||||||
|
|
||||||
|
uint256 againstBefore = votesAgainst();
|
||||||
|
uint256 forBefore = votesFor();
|
||||||
|
uint256 abstainBefore = votesAbstain();
|
||||||
|
|
||||||
|
bool hasVotedBefore = hasVoted(pId, e.msg.sender);
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
uint256 againstAfter = votesAgainst();
|
||||||
|
uint256 forAfter = votesFor();
|
||||||
|
uint256 abstainAfter = votesAbstain();
|
||||||
|
|
||||||
|
bool hasVotedAfter = hasVoted(pId, e.msg.sender);
|
||||||
|
|
||||||
|
// want all vote categories to not decrease and at least one category to increase
|
||||||
|
assert
|
||||||
|
(!hasVotedBefore && hasVotedAfter) =>
|
||||||
|
(againstBefore <= againstAfter && forBefore <= forAfter && abstainBefore <= abstainAfter),
|
||||||
|
"after a vote is cast, the number of votes for each category must not decrease";
|
||||||
|
assert
|
||||||
|
(!hasVotedBefore && hasVotedAfter) =>
|
||||||
|
(againstBefore < againstAfter || forBefore < forAfter || abstainBefore < abstainAfter),
|
||||||
|
"after a vote is cast, the number of votes of at least one category must increase";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: Voting against a proposal does not count towards quorum. │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule againstVotesDontCount(uint256 pId, env e, method f, calldataarg args) filtered { f ->
|
||||||
|
!f.isFallback
|
||||||
|
&& !f.isView
|
||||||
|
&& f.selector != relay(address,uint256,bytes).selector
|
||||||
|
&& !castVoteSubset(f)
|
||||||
|
} {
|
||||||
|
bool quorumBefore = quorumReached(e, pId);
|
||||||
|
uint256 againstBefore = votesAgainst();
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
bool quorumAfter = quorumReached(e, pId);
|
||||||
|
uint256 againstAfter = votesAgainst();
|
||||||
|
|
||||||
|
assert againstBefore < againstAfter => quorumBefore == quorumAfter, "quorum must not be reached with an against vote";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Rule: │
|
||||||
|
│ * Deadline can never be reduced │
|
||||||
|
│ * If deadline increases then we are in `deadlineExtended` state and `castVote` was called. │
|
||||||
|
│ * A proposal's deadline can't change in `deadlineExtended` state. │
|
||||||
|
│ * A proposal's deadline can't be unextended. │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
rule deadlineChangeEffects(uint256 pId, env e, method f, calldataarg args) filtered { f ->
|
||||||
|
!f.isFallback
|
||||||
|
&& !f.isView
|
||||||
|
&& f.selector != relay(address,uint256,bytes).selector
|
||||||
|
&& !castVoteSubset(f)
|
||||||
|
} {
|
||||||
|
requireInvariant proposalStateConsistencyExtended(pId);
|
||||||
|
|
||||||
|
uint256 deadlineBefore = proposalDeadline(pId);
|
||||||
|
bool deadlineExtendedBefore = deadlineExtended(e, pId);
|
||||||
|
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
uint256 deadlineAfter = proposalDeadline(pId);
|
||||||
|
bool deadlineExtendedAfter = deadlineExtended(e, pId);
|
||||||
|
|
||||||
|
// deadline can never be reduced
|
||||||
|
assert deadlineBefore <= proposalDeadline(pId);
|
||||||
|
|
||||||
|
// deadline can only be extended in proposal or on cast vote
|
||||||
|
assert (
|
||||||
|
deadlineAfter > deadlineBefore
|
||||||
|
) => (
|
||||||
|
(!deadlineExtendedBefore && !deadlineExtendedAfter && f.selector == propose(address[], uint256[], bytes[], string).selector)
|
||||||
|
||
|
||||||
|
(!deadlineExtendedBefore && deadlineExtendedAfter && f.selector == castVoteBySig(uint256, uint8,uint8, bytes32, bytes32).selector)
|
||||||
|
);
|
||||||
|
|
||||||
|
// a deadline can only be extended once
|
||||||
|
assert deadlineExtendedBefore => deadlineBefore == deadlineAfter;
|
||||||
|
|
||||||
|
// a deadline cannot be un-extended
|
||||||
|
assert deadlineExtendedBefore => deadlineExtendedAfter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function setup(env e1, env e2) {
|
||||||
|
require getQuorumNumeratorLength() + 1 < max_uint;
|
||||||
|
require e2.block.number >= e1.block.number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The proposal with proposal id `pId` has not been created.
|
||||||
|
definition proposalNotCreated(env e, uint256 pId) returns bool =
|
||||||
|
proposalSnapshot(pId) == 0
|
||||||
|
&& proposalDeadline(pId) == 0
|
||||||
|
&& getAgainstVotes(pId) == 0
|
||||||
|
&& getAbstainVotes(pId) == 0
|
||||||
|
&& getForVotes(pId) == 0;
|
||||||
|
|
||||||
|
/// Method f is a version of `castVote` whose state changing effects are covered by `castVoteBySig`.
|
||||||
|
/// @dev castVoteBySig allows anyone to cast a vote for anyone else if they can supply the signature. Specifically,
|
||||||
|
/// it covers the case where the msg.sender supplies a signature for themselves which is normally done using the normal
|
||||||
|
/// `castVote`.
|
||||||
|
definition castVoteSubset(method f) returns bool =
|
||||||
|
f.selector == castVote(uint256, uint8).selector ||
|
||||||
|
f.selector == castVoteWithReason(uint256, uint8, string).selector ||
|
||||||
|
f.selector == castVoteWithReasonAndParams(uint256,uint8,string,bytes).selector ||
|
||||||
|
f.selector == castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32).selector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A created proposal must be in state `deadlineExtendable` or `deadlineExtended`.
|
||||||
|
* @dev We assume the total supply of the voting token is non-zero
|
||||||
|
*/
|
||||||
|
invariant proposalInOneState(env e1, uint256 pId)
|
||||||
|
getPastTotalSupply(0) > 0 => (proposalNotCreated(e1, pId) || deadlineExtendable(e1, pId) || deadlineExtended(e1, pId))
|
||||||
|
filtered { f -> !f.isFallback && !f.isView && !castVoteSubset(f) && f.selector != relay(address,uint256,bytes).selector }
|
||||||
|
{
|
||||||
|
preserved with (env e2) {
|
||||||
|
setup(e1, e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
215
certora/specs/Initializable.spec
Normal file
215
certora/specs/Initializable.spec
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
//// ## Verification of `Initializable`
|
||||||
|
////
|
||||||
|
//// `Initializable` is a contract used to make constructors for upgradable
|
||||||
|
//// contracts. This is accomplished by applying the `initializer` modifier to any
|
||||||
|
//// function that serves as a constructor, which makes this function only
|
||||||
|
//// callable once. The secondary modifier `reinitializer` allows for upgrades
|
||||||
|
//// that should run at most once after the contract is upgraded.
|
||||||
|
////
|
||||||
|
////
|
||||||
|
//// ### Assumptions and Simplifications
|
||||||
|
//// We assume `initializer()` and `reinitializer(1)` are equivalent if they
|
||||||
|
//// both guarantee `_initialized` to be set to 1 after a successful call. This
|
||||||
|
//// allows us to use `reinitializer(n)` as a general version that also handles
|
||||||
|
//// the regular `initialzer` case.
|
||||||
|
////
|
||||||
|
//// #### Harnessing
|
||||||
|
//// Two harness versions were implemented, a simple flat contract, and a
|
||||||
|
//// Multi-inheriting contract. The two versions together help us ensure there are
|
||||||
|
//// No unexpected results because of different implementations. `Initializable` can
|
||||||
|
//// Be used in many different ways but we believe these 2 cases provide good
|
||||||
|
//// Coverage for all cases. In both harnesses we use getter functions for
|
||||||
|
//// `_initialized` and `_initializing` and implement `initializer` and
|
||||||
|
//// `reinitializer` functions that use their respective modifiers. We also
|
||||||
|
//// Implement some versioned functions that are only callable in specific
|
||||||
|
//// Versions of the contract to mimic upgrading contracts.
|
||||||
|
////
|
||||||
|
//// #### Munging
|
||||||
|
//// Variables `_initialized` and `_initializing` were changed to have internal
|
||||||
|
//// visibility to be harnessable.
|
||||||
|
|
||||||
|
methods {
|
||||||
|
initialize(uint256, uint256, uint256) envfree
|
||||||
|
reinitialize(uint256, uint256, uint256, uint8) envfree
|
||||||
|
initialized() returns uint8 envfree
|
||||||
|
initializing() returns bool envfree
|
||||||
|
thisIsContract() returns bool envfree
|
||||||
|
|
||||||
|
returnsV1() returns uint256 envfree
|
||||||
|
returnsVN(uint8) returns uint256 envfree
|
||||||
|
returnsAV1() returns uint256 envfree
|
||||||
|
returnsAVN(uint8) returns uint256 envfree
|
||||||
|
returnsBV1() returns uint256 envfree
|
||||||
|
returnsBVN(uint8) returns uint256 envfree
|
||||||
|
a() returns uint256 envfree
|
||||||
|
b() returns uint256 envfree
|
||||||
|
val() returns uint256 envfree
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// #### Definitions ////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//// ***`isUninitialized:`*** A contract's `_initialized` variable is equal to 0.
|
||||||
|
definition isUninitialized() returns bool = initialized() == 0;
|
||||||
|
|
||||||
|
//// ***`isInitialized:`*** A contract's `_initialized` variable is greater than 0.
|
||||||
|
definition isInitialized() returns bool = initialized() > 0;
|
||||||
|
|
||||||
|
//// ***`isInitializedOnce:`*** A contract's `_initialized` variable is equal to 1.
|
||||||
|
definition isInitializedOnce() returns bool = initialized() == 1;
|
||||||
|
|
||||||
|
//// ***`isReinitialized:`*** A contract's `_initialized` variable is greater than 1.
|
||||||
|
definition isReinitialized() returns bool = initialized() > 1;
|
||||||
|
|
||||||
|
//// ***`isDisabled:`*** A contract's `_initialized` variable is equal to 255.
|
||||||
|
definition isDisabled() returns bool = initialized() == 255;
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// ### Properties ///////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Invariants /////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// A contract must only ever be in an initializing state while in the middle
|
||||||
|
/// of a transaction execution.
|
||||||
|
invariant notInitializing()
|
||||||
|
!initializing()
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Rules /////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// @title Only initialized once
|
||||||
|
/// @notice An initializable contract with a function that inherits the
|
||||||
|
/// initializer modifier must be initializable only once
|
||||||
|
rule initOnce() {
|
||||||
|
uint256 val; uint256 a; uint256 b;
|
||||||
|
|
||||||
|
require isInitialized();
|
||||||
|
initialize@withrevert(val, a, b);
|
||||||
|
assert lastReverted, "contract must only be initialized once";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Successfully calling reinitialize() with a version value of 1 must result
|
||||||
|
/// in `_initialized` being set to 1.
|
||||||
|
rule reinitializeEffects {
|
||||||
|
uint256 val; uint256 a; uint256 b;
|
||||||
|
|
||||||
|
reinitialize(val, a, b, 1);
|
||||||
|
|
||||||
|
assert isInitializedOnce(), "reinitialize(1) must set _initialized to 1";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Successfully calling `initialize()` must result in `_initialized` being set to 1.
|
||||||
|
/// @dev We assume `initialize()` and `reinitialize(1)` are equivalent if this rule
|
||||||
|
/// and the [above rule][#reinitializeEffects] both pass.
|
||||||
|
rule initializeEffects {
|
||||||
|
uint256 val; uint256 a; uint256 b;
|
||||||
|
|
||||||
|
initialize(val, a, b);
|
||||||
|
|
||||||
|
assert isInitializedOnce(), "initialize() must set _initialized to 1";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A disabled initializable contract must always stay disabled.
|
||||||
|
rule disabledStaysDisabled(method f) {
|
||||||
|
env e; calldataarg args;
|
||||||
|
|
||||||
|
bool disabledBefore = isDisabled();
|
||||||
|
f(e, args);
|
||||||
|
bool disabledAfter = isDisabled();
|
||||||
|
|
||||||
|
assert disabledBefore => disabledAfter, "a disabled initializer must stay disabled";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The variable `_initialized` must not decrease.
|
||||||
|
rule increasingInitialized(method f) {
|
||||||
|
env e; calldataarg args;
|
||||||
|
|
||||||
|
uint8 initBefore = initialized();
|
||||||
|
f(e, args);
|
||||||
|
uint8 initAfter = initialized();
|
||||||
|
assert initBefore <= initAfter, "_initialized must only increase";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If `reinitialize(...)` was called successfully, then the variable
|
||||||
|
/// `_initialized` must increase.
|
||||||
|
/// @title Reinitialize increases `init`
|
||||||
|
rule reinitializeIncreasesInit {
|
||||||
|
uint256 val; uint8 n; uint256 a; uint256 b;
|
||||||
|
|
||||||
|
uint8 initBefore = initialized();
|
||||||
|
reinitialize(val, a, b, n);
|
||||||
|
uint8 initAfter = initialized();
|
||||||
|
|
||||||
|
assert initAfter > initBefore, "calling reinitialize must increase _initialized";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `reinitialize(n)` must be callable if the contract is not in an
|
||||||
|
/// `_initializing` state and `n` is greater than `_initialized` and less than
|
||||||
|
/// 255
|
||||||
|
rule reinitializeLiveness {
|
||||||
|
uint256 val; uint8 n; uint256 a; uint256 b;
|
||||||
|
|
||||||
|
requireInvariant notInitializing();
|
||||||
|
uint8 initVal = initialized();
|
||||||
|
reinitialize@withrevert(val, a, b, n);
|
||||||
|
|
||||||
|
assert n > initVal => !lastReverted, "reinitialize(n) call must succeed if n was greater than _initialized";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If `reinitialize(n)` was called successfully then `n` was greater than
|
||||||
|
/// `_initialized`.
|
||||||
|
rule reinitializeRule {
|
||||||
|
uint256 val; uint8 n; uint256 a; uint256 b;
|
||||||
|
|
||||||
|
uint8 initBefore = initialized();
|
||||||
|
reinitialize(val, a, b, n);
|
||||||
|
|
||||||
|
assert n > initBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Functions implemented in the parent contract that require `_initialized` to
|
||||||
|
/// be a certain value are only callable when it is that value.
|
||||||
|
/// @title Reinitialize version check parent
|
||||||
|
rule reinitVersionCheckParent {
|
||||||
|
uint8 n;
|
||||||
|
|
||||||
|
returnsVN(n);
|
||||||
|
assert initialized() == n, "parent contract's version n functions must only be callable in version n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Functions implemented in the child contract that require `_initialized` to
|
||||||
|
/// be a certain value are only callable when it is that value.
|
||||||
|
/// @title Reinitialize version check child
|
||||||
|
rule reinitVersionCheckChild {
|
||||||
|
uint8 n;
|
||||||
|
|
||||||
|
returnsAVN(n);
|
||||||
|
assert initialized() == n, "child contract's version n functions must only be callable in version n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Functions implemented in the grandchild contract that require `_initialized`
|
||||||
|
/// to be a certain value are only callable when it is that value.
|
||||||
|
/// @title Reinitialize version check grandchild
|
||||||
|
rule reinitVersionCheckGrandchild {
|
||||||
|
uint8 n;
|
||||||
|
|
||||||
|
returnsBVN(n);
|
||||||
|
assert initialized() == n, "gransdchild contract's version n functions must only be callable in version n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calling parent initializer function must initialize all child contracts.
|
||||||
|
rule inheritanceCheck {
|
||||||
|
uint256 val; uint8 n; uint256 a; uint256 b;
|
||||||
|
|
||||||
|
reinitialize(val, a, b, n);
|
||||||
|
assert val() == val && a() == a && b() == b, "all child contract values must be initialized";
|
||||||
|
}
|
||||||
|
|
||||||
@ -136,4 +136,40 @@ rule possibleTotalVotes(uint256 pId, uint8 sup, env e, method f) {
|
|||||||
uint256 ps = proposalSnapshot(pId);
|
uint256 ps = proposalSnapshot(pId);
|
||||||
|
|
||||||
assert tracked_weight(pId) <= erc20votes.getPastTotalSupply(e, proposalSnapshot(pId)), "bla bla bla";
|
assert tracked_weight(pId) <= erc20votes.getPastTotalSupply(e, proposalSnapshot(pId)), "bla bla bla";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////// 2nd iteration with OZ //////////////////////////
|
||||||
|
|
||||||
|
function executionsCall(method f, env e, address target, uint256 value, bytes data,
|
||||||
|
bytes32 predecessor, bytes32 salt, uint256 delay,
|
||||||
|
address[] targets, uint256[] values, bytes[] calldatas) {
|
||||||
|
if (f.selector == execute(address, uint256, bytes, bytes32, bytes32).selector) {
|
||||||
|
execute(e, target, value, data, predecessor, salt);
|
||||||
|
} else if (f.selector == executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector) {
|
||||||
|
executeBatch(e, targets, values, calldatas, predecessor, salt);
|
||||||
|
} else {
|
||||||
|
calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// STATUS - in progress
|
||||||
|
// execute() is the only way to set timestamp to 1
|
||||||
|
rule getTimestampOnlyChange(method f, env e){
|
||||||
|
bytes32 id;
|
||||||
|
address target; uint256 value; bytes data; bytes32 predecessor; bytes32 salt; uint256 delay;
|
||||||
|
address[] targets; uint256[] values; bytes[] calldatas;
|
||||||
|
|
||||||
|
require (targets[0] == target && values[0] == value && calldatas[0] == data)
|
||||||
|
|| (targets[1] == target && values[1] == value && calldatas[1] == data)
|
||||||
|
|| (targets[2] == target && values[2] == value && calldatas[2] == data);
|
||||||
|
|
||||||
|
hashIdCorrelation(id, target, value, data, predecessor, salt);
|
||||||
|
|
||||||
|
executionsCall(f, e, target, value, data, predecessor, salt, delay, targets, values, calldatas);
|
||||||
|
|
||||||
|
assert getTimestamp(id) == 1 => f.selector == execute(address, uint256, bytes, bytes32, bytes32).selector
|
||||||
|
|| f.selector == executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector, "Did you find a way to break the system?";
|
||||||
|
}
|
||||||
|
|||||||
326
certora/specs/TimelockController.spec
Normal file
326
certora/specs/TimelockController.spec
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
methods {
|
||||||
|
PROPOSER_ROLE() returns(bytes32) envfree
|
||||||
|
|
||||||
|
_DONE_TIMESTAMP() returns(uint256) envfree
|
||||||
|
_minDelay() returns(uint256) envfree
|
||||||
|
_checkRole(bytes32) => DISPATCHER(true)
|
||||||
|
|
||||||
|
isOperation(bytes32) returns(bool) envfree
|
||||||
|
isOperationPending(bytes32) returns(bool) envfree
|
||||||
|
isOperationReady(bytes32) returns(bool)
|
||||||
|
isOperationDone(bytes32) returns(bool) envfree
|
||||||
|
getTimestamp(bytes32) returns(uint256) envfree
|
||||||
|
getMinDelay() returns(uint256) envfree
|
||||||
|
|
||||||
|
hashOperation(address, uint256, bytes, bytes32, bytes32) returns(bytes32) envfree
|
||||||
|
hashOperationBatch(address[], uint256[], bytes[], bytes32, bytes32) returns(bytes32) envfree
|
||||||
|
|
||||||
|
schedule(address, uint256, bytes, bytes32, bytes32, uint256)
|
||||||
|
scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256)
|
||||||
|
execute(address, uint256, bytes, bytes32, bytes32)
|
||||||
|
executeBatch(address[], uint256[], bytes[], bytes32, bytes32)
|
||||||
|
cancel(bytes32)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Functions //
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
function hashIdCorrelation(bytes32 id, address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt){
|
||||||
|
require data.length < 32;
|
||||||
|
require hashOperation(target, value, data, predecessor, salt) == id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Invariants //
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// `isOperation()` correctness check
|
||||||
|
invariant operationCheck(bytes32 id)
|
||||||
|
getTimestamp(id) > 0 <=> isOperation(id)
|
||||||
|
filtered { f -> !f.isView }
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// `isOperationPending()` correctness check
|
||||||
|
invariant pendingCheck(bytes32 id)
|
||||||
|
getTimestamp(id) > _DONE_TIMESTAMP() <=> isOperationPending(id)
|
||||||
|
filtered { f -> !f.isView }
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// `isOperationReady()` correctness check
|
||||||
|
invariant readyCheck(env e, bytes32 id)
|
||||||
|
(e.block.timestamp >= getTimestamp(id) && getTimestamp(id) > 1) <=> isOperationReady(e, id)
|
||||||
|
filtered { f -> !f.isView }
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// `isOperationDone()` correctness check
|
||||||
|
invariant doneCheck(bytes32 id)
|
||||||
|
getTimestamp(id) == _DONE_TIMESTAMP() <=> isOperationDone(id)
|
||||||
|
filtered { f -> !f.isView }
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Rules //
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
// STATE TRANSITIONS
|
||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Possible transitions: form `!isOperation()` to `!isOperation()` or `isOperationPending()` only
|
||||||
|
rule unsetPendingTransitionGeneral(method f, env e){
|
||||||
|
bytes32 id;
|
||||||
|
|
||||||
|
require !isOperation(id);
|
||||||
|
require e.block.timestamp > 1;
|
||||||
|
|
||||||
|
calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert isOperationPending(id) || !isOperation(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Possible transitions: form `!isOperation()` to `isOperationPending()` via `schedule()` and `scheduleBatch()` only
|
||||||
|
rule unsetPendingTransitionMethods(method f, env e){
|
||||||
|
bytes32 id;
|
||||||
|
|
||||||
|
require !isOperation(id);
|
||||||
|
|
||||||
|
calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert isOperationPending(id) => (f.selector == schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector
|
||||||
|
|| f.selector == scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector), "Why do we need to follow the schedule?";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Possible transitions: form `ready()` to `isOperationDone()` via `execute()` and `executeBatch()` only
|
||||||
|
rule readyDoneTransition(method f, env e){
|
||||||
|
bytes32 id;
|
||||||
|
|
||||||
|
require isOperationReady(e, id);
|
||||||
|
|
||||||
|
calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert isOperationDone(id) => f.selector == execute(address, uint256, bytes, bytes32, bytes32).selector
|
||||||
|
|| f.selector == executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector , "It's not isOperationDone yet!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// isOperationPending() -> cancelled() via cancel() only
|
||||||
|
rule pendingCancelledTransition(method f, env e){
|
||||||
|
bytes32 id;
|
||||||
|
|
||||||
|
require isOperationPending(id);
|
||||||
|
|
||||||
|
calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert !isOperation(id) => f.selector == cancel(bytes32).selector, "How you dare to cancel me?";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// isOperationDone() -> nowhere
|
||||||
|
rule doneToNothingTransition(method f, env e){
|
||||||
|
bytes32 id;
|
||||||
|
|
||||||
|
require isOperationDone(id);
|
||||||
|
|
||||||
|
calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert isOperationDone(id), "Did you find a way to escape? There is no way! HA-HA-HA";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
// THE REST
|
||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// only TimelockController contract can change minDelay
|
||||||
|
rule minDelayOnlyChange(method f, env e){
|
||||||
|
uint256 delayBefore = _minDelay();
|
||||||
|
|
||||||
|
calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
uint256 delayAfter = _minDelay();
|
||||||
|
|
||||||
|
assert delayBefore != delayAfter => e.msg.sender == currentContract, "You cannot change your destiny! Only I can!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// scheduled operation timestamp == block.timestamp + delay (kind of unit test)
|
||||||
|
rule scheduleCheck(method f, env e){
|
||||||
|
bytes32 id;
|
||||||
|
|
||||||
|
address target; uint256 value; bytes data ;bytes32 predecessor; bytes32 salt; uint256 delay;
|
||||||
|
|
||||||
|
hashIdCorrelation(id, target, value, data, predecessor, salt);
|
||||||
|
|
||||||
|
schedule(e, target, value, data, predecessor, salt, delay);
|
||||||
|
|
||||||
|
assert getTimestamp(id) == to_uint256(e.block.timestamp + delay), "Time doesn't obey to mortal souls";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Cannot call `execute()` on a isOperationPending (not ready) operation
|
||||||
|
rule cannotCallExecute(method f, env e){
|
||||||
|
address target; uint256 value; bytes data; bytes32 predecessor; bytes32 salt;
|
||||||
|
bytes32 id;
|
||||||
|
|
||||||
|
hashIdCorrelation(id, target, value, data, predecessor, salt);
|
||||||
|
require isOperationPending(id) && !isOperationReady(e, id);
|
||||||
|
|
||||||
|
execute@withrevert(e, target, value, data, predecessor, salt);
|
||||||
|
|
||||||
|
assert lastReverted, "you go against execution nature";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Cannot call `execute()` on a !isOperation operation
|
||||||
|
rule executeRevertsFromUnset(method f, env e, env e2){
|
||||||
|
address target; uint256 value; bytes data; bytes32 predecessor; bytes32 salt;
|
||||||
|
bytes32 id;
|
||||||
|
|
||||||
|
hashIdCorrelation(id, target, value, data, predecessor, salt);
|
||||||
|
require !isOperation(id);
|
||||||
|
|
||||||
|
execute@withrevert(e, target, value, data, predecessor, salt);
|
||||||
|
|
||||||
|
assert lastReverted, "you go against execution nature";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Execute reverts => state returns to isOperationPending
|
||||||
|
rule executeRevertsEffectCheck(method f, env e){
|
||||||
|
address target; uint256 value; bytes data; bytes32 predecessor; bytes32 salt;
|
||||||
|
bytes32 id;
|
||||||
|
|
||||||
|
hashIdCorrelation(id, target, value, data, predecessor, salt);
|
||||||
|
require isOperationPending(id) && !isOperationReady(e, id);
|
||||||
|
|
||||||
|
execute@withrevert(e, target, value, data, predecessor, salt);
|
||||||
|
bool reverted = lastReverted;
|
||||||
|
|
||||||
|
assert lastReverted => isOperationPending(id) && !isOperationReady(e, id), "you go against execution nature";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Canceled operations cannot be executed → can’t move from canceled to isOperationDone
|
||||||
|
rule cancelledNotExecuted(method f, env e){
|
||||||
|
bytes32 id;
|
||||||
|
|
||||||
|
require !isOperation(id);
|
||||||
|
require e.block.timestamp > 1;
|
||||||
|
|
||||||
|
calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert !isOperationDone(id), "The ship is not a creature of the air";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// Only proposers can schedule
|
||||||
|
rule onlyProposer(method f, env e) filtered { f -> f.selector == schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector
|
||||||
|
|| f.selector == scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector } {
|
||||||
|
bytes32 id;
|
||||||
|
bytes32 role;
|
||||||
|
address target; uint256 value; bytes data ;bytes32 predecessor; bytes32 salt; uint256 delay;
|
||||||
|
|
||||||
|
_checkRole@withrevert(e, PROPOSER_ROLE());
|
||||||
|
|
||||||
|
bool isCheckRoleReverted = lastReverted;
|
||||||
|
|
||||||
|
calldataarg args;
|
||||||
|
f@withrevert(e, args);
|
||||||
|
|
||||||
|
bool isScheduleReverted = lastReverted;
|
||||||
|
|
||||||
|
assert isCheckRoleReverted => isScheduleReverted, "Enemy was detected";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// if `ready` then has waited minimum period after isOperationPending()
|
||||||
|
rule cooldown(method f, env e, env e2){
|
||||||
|
bytes32 id;
|
||||||
|
address target; uint256 value; bytes data ;bytes32 predecessor; bytes32 salt; uint256 delay;
|
||||||
|
uint256 minDelay = getMinDelay();
|
||||||
|
|
||||||
|
hashIdCorrelation(id, target, value, data, predecessor, salt);
|
||||||
|
|
||||||
|
schedule(e, target, value, data, predecessor, salt, delay);
|
||||||
|
|
||||||
|
calldataarg args;
|
||||||
|
f(e, args);
|
||||||
|
|
||||||
|
assert isOperationReady(e2, id) => (e2.block.timestamp - e.block.timestamp >= minDelay), "No rush! When I'm ready, I'm ready";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// `schedule()` should change only one id's timestamp
|
||||||
|
rule scheduleChange(env e){
|
||||||
|
bytes32 id; bytes32 otherId;
|
||||||
|
address target; uint256 value; bytes data ;bytes32 predecessor; bytes32 salt; uint256 delay;
|
||||||
|
|
||||||
|
uint256 otherIdTimestampBefore = getTimestamp(otherId);
|
||||||
|
|
||||||
|
hashIdCorrelation(id, target, value, data, predecessor, salt);
|
||||||
|
|
||||||
|
schedule(e, target, value, data, predecessor, salt, delay);
|
||||||
|
|
||||||
|
assert id != otherId => otherIdTimestampBefore == getTimestamp(otherId), "Master of puppets, I'm pulling your strings";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// `execute()` should change only one id's timestamp
|
||||||
|
rule executeChange(env e){
|
||||||
|
bytes32 id; bytes32 otherId;
|
||||||
|
address target; uint256 value; bytes data ;bytes32 predecessor; bytes32 salt;
|
||||||
|
uint256 otherIdTimestampBefore = getTimestamp(otherId);
|
||||||
|
|
||||||
|
hashIdCorrelation(id, target, value, data, predecessor, salt);
|
||||||
|
|
||||||
|
execute(e, target, value, data, predecessor, salt);
|
||||||
|
|
||||||
|
assert id != otherId => otherIdTimestampBefore == getTimestamp(otherId), "Master of puppets, I'm pulling your strings";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// STATUS - verified
|
||||||
|
// `cancel()` should change only one id's timestamp
|
||||||
|
rule cancelChange(env e){
|
||||||
|
bytes32 id; bytes32 otherId;
|
||||||
|
|
||||||
|
uint256 otherIdTimestampBefore = getTimestamp(otherId);
|
||||||
|
|
||||||
|
cancel(e, id);
|
||||||
|
|
||||||
|
assert id != otherId => otherIdTimestampBefore == getTimestamp(otherId), "Master of puppets, I'm pulling your strings";
|
||||||
|
}
|
||||||
12
certora/specs/erc20methods.spec
Normal file
12
certora/specs/erc20methods.spec
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// erc20 methods
|
||||||
|
methods {
|
||||||
|
name() returns (string) envfree => DISPATCHER(true)
|
||||||
|
symbol() returns (string) envfree => DISPATCHER(true)
|
||||||
|
decimals() returns (uint8) envfree => DISPATCHER(true)
|
||||||
|
totalSupply() returns (uint256) envfree => DISPATCHER(true)
|
||||||
|
balanceOf(address) returns (uint256) envfree => DISPATCHER(true)
|
||||||
|
allowance(address,address) returns (uint256) envfree => DISPATCHER(true)
|
||||||
|
approve(address,uint256) returns (bool) => DISPATCHER(true)
|
||||||
|
transfer(address,uint256) returns (bool) => DISPATCHER(true)
|
||||||
|
transferFrom(address,address,uint256) returns (bool) => DISPATCHER(true)
|
||||||
|
}
|
||||||
@ -5,10 +5,8 @@ How it works:
|
|||||||
- If there is a non-reverting execution path, we reach the false assertion, and the sanity fails.
|
- 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.
|
- If all execution paths are reverting, we never call the assertion, and the method will pass this rule vacuously.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
rule sanity(method f) {
|
rule sanity(env e, method f, calldataarg arg) {
|
||||||
env e;
|
|
||||||
calldataarg arg;
|
|
||||||
f(e, arg);
|
f(e, arg);
|
||||||
assert false;
|
assert false;
|
||||||
}
|
}
|
||||||
Submodule lib/forge-std updated: eb980e1d4f...ca8d6e00ea
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
certora-cli==3.0.0
|
||||||
Reference in New Issue
Block a user