Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decentralised governance of 0x protocol and treasury #641

Merged
merged 106 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
7f26c5d
Install open zeppelin contracts
elenadimitrova Jan 12, 2023
4f39ff1
Init foundry in governance
elenadimitrova Jan 13, 2023
2b42a6e
Add wrapped ZRX token
elenadimitrova Jan 13, 2023
1b9f4fe
Add governance contracts testing to CI
elenadimitrova Jan 19, 2023
3f18f9d
Set optimizer runs to default
elenadimitrova Jan 19, 2023
71cfb20
Upgrade to patched version of openzeppelin/contracts
elenadimitrova Jan 19, 2023
8ab6af4
Test stakingakng / unwrapping ZRX
elenadimitrova Jan 19, 2023
c07065f
Init npm package
elenadimitrova Jan 19, 2023
5ce63d4
Lint fix, removing lib from gitignore
elenadimitrova Jan 19, 2023
f222bda
Add openzeppelin contracts git submodule for foundry
elenadimitrova Jan 19, 2023
ca30706
Add vanilla governor contract
elenadimitrova Jan 19, 2023
265a50c
Fix reference paths to imported packages
elenadimitrova Jan 20, 2023
7546b9c
Temporarily switch to using a mocked version of ZRX
elenadimitrova Jan 21, 2023
07610c8
Ignore foundry's lib in link checker
elenadimitrova Jan 21, 2023
de21d3d
Fix a conflict in gitignore between forge lib adn built lib
elenadimitrova Jan 21, 2023
7bf9dce
Upload governance code coverage report to coveralls
elenadimitrova Jan 21, 2023
d80789c
Flesh out test scenarios for wrapping/unwrapping
elenadimitrova Jan 21, 2023
a36277f
Add basic ERC20 name and symbol tests
elenadimitrova Jan 21, 2023
5433b8a
Wire in basic timelock controller and governor test setup
elenadimitrova Jan 22, 2023
4e41229
Test basic governor properties
elenadimitrova Jan 22, 2023
f4db939
Add basic voting power delegation tests
elenadimitrova Jan 23, 2023
cccaf31
Add proposal execution happy path test
elenadimitrova Jan 23, 2023
e07260f
Split ERC20Votes logic between wrapped token
elenadimitrova Jan 30, 2023
0685866
Exclude BaseTest from coverage in coveralls
elenadimitrova Feb 1, 2023
adf73a7
Add protocol specific governor with produciton governance settings
elenadimitrova Feb 3, 2023
7e1b858
Add a dedicated instance for the treasury governor
elenadimitrova Feb 3, 2023
a656365
Add test for updating governance settings
elenadimitrova Feb 5, 2023
73bb6a4
Create seperate timelock contract instance for treasury and protocol
elenadimitrova Feb 6, 2023
6cff23d
Test updating the timlock min delay
elenadimitrova Feb 6, 2023
7691aef
Set timelock delay to 2 days for protocol and 1 sec for treasury
elenadimitrova Feb 6, 2023
0223e16
Remove timelock from treasury governor
elenadimitrova Feb 6, 2023
5fbfcb7
Refactor _checkpointsLookup to return entire Checkpoint
elenadimitrova Feb 8, 2023
1522fff
Update the totalSupply checkpoints updating logic
elenadimitrova Feb 8, 2023
204b2ee
Quadratic voting power transfers and delegations
elenadimitrova Feb 8, 2023
c704869
Fix workflow yaml
elenadimitrova Feb 10, 2023
3ae3b0d
Initialise ZeroExVotes behind a ERC1967Proxy
elenadimitrova Feb 10, 2023
e128c5b
Remove obsoleted console.logs from test
elenadimitrova Feb 13, 2023
7359a64
Storage pack Checkpoint enum
elenadimitrova Feb 13, 2023
a381b82
Remove keeping track of total balances for voting
elenadimitrova Feb 13, 2023
9fd66a1
Switch to using the foundry artifact in test
elenadimitrova Feb 19, 2023
c1966e0
Fix rebase issue
elenadimitrova Feb 19, 2023
f258bb4
Add timelock control over the treasury governor
elenadimitrova Feb 20, 2023
091e0a5
Add test for wrapped token transfer
elenadimitrova Feb 20, 2023
e223ba0
Emit separate events for changing linear and quadratic voting power
elenadimitrova Feb 20, 2023
d18c3ca
Add the ability to cancel a proposal
elenadimitrova Feb 21, 2023
94adc8f
Limit the governors' cancel function to security council only
elenadimitrova Feb 21, 2023
9f96dcb
Eject security council after a proposal is cancelled
elenadimitrova Feb 21, 2023
5afedda
Add ability for governance to set the security council
elenadimitrova Feb 22, 2023
b430183
Merge the governors test suites into one reusable set of tests
elenadimitrova Feb 22, 2023
e1c1416
Add an empty test function to base test contract
elenadimitrova Feb 22, 2023
905c993
Security council can rollback protocol upgrades
elenadimitrova Feb 26, 2023
1d9d324
Upgrade to solidity 0.8.19
elenadimitrova Feb 27, 2023
1484222
Move IZeroExGovernor to src
elenadimitrova Feb 27, 2023
14776b5
Abstract Security council interface into its own
elenadimitrova Feb 27, 2023
58f8dd1
Emit events when assigning and ejecting the security council
elenadimitrova Feb 27, 2023
9bca5b9
Use a cast to bytes4 instead of LibBytes
elenadimitrova Feb 27, 2023
5630eec
Writing total supply checkpoints and setup of
elenadimitrova Mar 2, 2023
2867181
Add test for transferring tokens when delegating
elenadimitrova Mar 2, 2023
43bc935
Rename IZeroExSecurityCouncil to ISecurityCouncil
elenadimitrova Mar 3, 2023
42e1b13
Add security council restrictions to governors
elenadimitrova Mar 3, 2023
bcbf039
Remove obsolete overflow check
elenadimitrova Mar 4, 2023
986ebf3
Improve test coverage
elenadimitrova Mar 4, 2023
094de01
Upgrade open-zeppelin contracts to 4.8.2
elenadimitrova Mar 5, 2023
9e873e1
Test delegation by signature
elenadimitrova Mar 5, 2023
6a86ba1
Test non security council requests
elenadimitrova Mar 5, 2023
4dbbc05
Better revert messages
elenadimitrova Mar 5, 2023
d361ba7
Test correct interfaces are supported
elenadimitrova Mar 5, 2023
73070bd
Remove obsoleted funciton
elenadimitrova Mar 5, 2023
99a7b73
Further test delegation by signature scenario
elenadimitrova Mar 5, 2023
76e12ab
Split the delegation functionality tests
elenadimitrova Mar 7, 2023
1d8e3ca
Add test for initialisation of voting contract
elenadimitrova Mar 7, 2023
d8a867f
Add test for reading checkpoints
elenadimitrova Mar 7, 2023
da8537c
Update code comments
elenadimitrova Mar 7, 2023
4283fbd
Fix compilation warnings
elenadimitrova Mar 7, 2023
2132b9a
Run smt checker
elenadimitrova Mar 7, 2023
0e84a1d
Add checkpoint tests
elenadimitrova Mar 8, 2023
7024a11
Rename parameter in moveEntireVotingPower to match the one in movePar…
elenadimitrova Mar 9, 2023
4132cfb
Switch moveEntireVotingPower to a more generic moveVotingPower implem…
elenadimitrova Mar 10, 2023
6c8ac9a
Install foundry earlier in CI
elenadimitrova Mar 11, 2023
a650418
Switch movePartialVotingPower to the generic moveVotingPower implemen…
elenadimitrova Mar 11, 2023
a54a94e
Write totalSupplyCheckpoints via the generic _writeCheckpoint
elenadimitrova Mar 11, 2023
46632d5
Add threshold for quadratic voting power
elenadimitrova Mar 11, 2023
8006646
Remove autoinserted code by OZ
elenadimitrova Mar 11, 2023
55806c6
Add openzeppelin/contracts-upgradable
elenadimitrova Mar 13, 2023
ce87bad
Add initializable base to Voting contract
elenadimitrova Mar 13, 2023
5773efd
Fix terminogy error in natspec
elenadimitrova Mar 13, 2023
403455b
Fix code comment
elenadimitrova Mar 13, 2023
545cf87
Remove obsoleted overrides and add a missing modifier to moveVotingPower
elenadimitrova Mar 14, 2023
b55baab
Remove amount check
elenadimitrova Mar 14, 2023
b8d67f0
Fix a calculation error and clean tests
elenadimitrova Mar 14, 2023
8961e1d
Update thresholds for treasury governor
elenadimitrova Mar 15, 2023
7be528e
Fix testShouldNotBeAbleToDelegateWithSignatureAfterExpiry
duncancmt Mar 13, 2023
89c77a7
Update from @duncancmt
elenadimitrova Mar 15, 2023
15210fa
Add onlyProxy to initializer
duncancmt Mar 7, 2023
0c1a7bc
Fix quadratic voting weight base
elenadimitrova Mar 15, 2023
9c47969
Rename voting parameter for clarity
elenadimitrova Mar 15, 2023
92efa2e
Make addresses immutable (#680)
duncancmt Mar 15, 2023
b1c83eb
Prevent griefing by a malicious ZeroExVotes upgrade (#681)
duncancmt Mar 17, 2023
fb6e1f0
Rename SecurityCouncil contract
elenadimitrova Mar 17, 2023
4f799ca
Add timestamp to delegator balance updates
elenadimitrova Mar 20, 2023
3070276
Make quadraticThreshold `immutable` for gas efficiency
duncancmt Mar 20, 2023
538c61f
Remove the logic for ejecting security council
elenadimitrova Mar 20, 2023
6410ffc
Switch balance timestamp to be a block number
elenadimitrova Mar 21, 2023
62fe71b
Test votes migration for adding a new vote weight mechanism (#674)
duncancmt Mar 22, 2023
d6c0c02
Change test case to testFail
elenadimitrova Mar 22, 2023
8d83419
Update contracts/governance/test/ZeroExVotesMigration.sol
elenadimitrova Mar 22, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ jobs:
- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Add foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Build solution
run: yarn build

Expand Down Expand Up @@ -78,11 +83,6 @@ jobs:
-p @0x/order-utils \
-m --serial -c test:ci

- name: Add foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Run Forge build for erc20
working-directory: contracts/erc20
run: |
Expand Down Expand Up @@ -135,3 +135,26 @@ jobs:
path: ./contracts/zero-ex/lcov.info
min_coverage: 6.98
exclude: '**/tests'

- name: Run Forge build on governance contracts
working-directory: ./contracts/governance
run: |
forge --version
forge build --sizes

- name: Run Forge tests on governance contracts
working-directory: ./contracts/governance
run: |
forge test -vvv --gas-report

- name: Run Forge coverage on governance contracts
working-directory: ./contracts/governance
run: |
forge coverage --report lcov

- name: Upload the coverage report to Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
base-path: ./contracts/governance/
path-to-lcov: ./contracts/governance/lcov.info
18 changes: 17 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,16 @@ typings/
.env

# built library using in commonjs module syntax
lib/
contracts/erc20/lib/
contracts/test-utils/lib/
contracts/treasury/lib/
contracts/utils/lib/
contracts/zero-ex/lib/
packages/contract-addresses/lib/
packages/contract-artifacts/lib/
packages/contract-wrappers/lib/
packages/protocol-utils/lib/

# UMD bundles that export the global variable
_bundles

Expand Down Expand Up @@ -97,10 +106,17 @@ out/
# typechain wrappers
contracts/zero-ex/typechain-wrappers/

# foundry packages
contracts/governance/cache
contracts/governance/out

# Doc README copy
packages/*/docs/README.md

.DS_Store
*~
\#*\#
.\#*

# the snapshot that gets built for migrations sure does have a ton of files
packages/migrations/0x_ganache_snapshot*
11 changes: 11 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,15 @@
url = https://github.com/foundry-rs/forge-std
[submodule "contracts/erc20/lib/forge-std"]
path = contracts/erc20/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "contracts/governance/lib/forge-std"]
path = contracts/governance/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "contracts/governance/lib/openzeppelin-contracts"]
path = contracts/governance/lib/openzeppelin-contracts
url = https://github.com/openzeppelin/openzeppelin-contracts
[submodule "contracts/governance/lib/openzeppelin-contracts-upgradeable"]
path = contracts/governance/lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
[submodule "lib/openzeppelin-contracts-upgradeable"]
branch = v4.8.2
505 changes: 505 additions & 0 deletions contracts/governance/ZRXToken.json

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions contracts/governance/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[profile.default]
src = 'src'
out = 'out'
libs = ['lib', "../utils/contracts/src/"]
fs_permissions = [{ access = "read", path = "./" }]
remappings = [
'@openzeppelin/=./lib/openzeppelin-contracts/contracts/',
'@openzeppelin-contracts-upgradeable/=./lib/openzeppelin-contracts-upgradeable/contracts/',
'@0x/contracts-utils/=../utils/',
]
solc = '0.8.19'
optimizer_runs = 20_000
elenadimitrova marked this conversation as resolved.
Show resolved Hide resolved
via_ir = true

[profile.smt.model_checker]
engine = 'chc'
timeout = 10_000
targets = [
'assert',
'constantCondition',
'divByZero',
'outOfBounds',
'underflow'
]
contracts = { 'src/ZeroExProtocolGovernor.sol' = [ 'ZeroExProtocolGovernor' ] }
1 change: 1 addition & 0 deletions contracts/governance/lib/forge-std
Submodule forge-std added at eb980e
1 change: 1 addition & 0 deletions contracts/governance/lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at d00ace
21 changes: 21 additions & 0 deletions contracts/governance/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@0x/governance",
"version": "1.0.0",
"description": "Governance implementation for the 0x protocol and treasury",
"main": "index.js",
"directories": {
"lib": "lib",
"test": "test"
},
"scripts": {
"test": "forge test",
"build": "forge build",
"build:smt": "FOUNDRY_PROFILE=smt forge build"
},
"repository": {
"type": "git",
"url": "https://github.com/0xProject/protocol.git"
},
"license": "Apache-2.0",
"dependencies": {}
}
169 changes: 169 additions & 0 deletions contracts/governance/src/CallWithGas.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

library CallWithGas {
/**
* @notice `staticcall` another contract forwarding a precomputed amount of
* gas.
* @dev contains protections against EIP-150-induced insufficient gas
* griefing
* @dev reverts iff the target is not a contract or we encounter an
* out-of-gas
* @return success true iff the call succeded and returned no more than
* `maxReturnBytes` of return data
* @return returnData the return data or revert reason of the call
* @param target the contract (reverts if non-contract) on which to make the
* `staticcall`
* @param data the calldata to pass
* @param callGas the gas to pass for the call. If the call requires more than
* the specified amount of gas and the caller didn't provide at
* least `callGas`, triggers an out-of-gas in the caller.
* @param maxReturnBytes Only this many bytes of return data are read back
* from the call. This prevents griefing the caller. If
* more bytes are returned or the revert reason is
* longer, success will be false and returnData will be
* `abi.encodeWithSignature("Error(string)", "CallWithGas: returnData too long")`
*/
function functionStaticCallWithGas(
address target,
bytes memory data,
uint256 callGas,
uint256 maxReturnBytes
) internal view returns (bool success, bytes memory returnData) {
assembly ("memory-safe") {
returnData := mload(0x40)
success := staticcall(callGas, target, add(data, 0x20), mload(data), add(returnData, 0x20), maxReturnBytes)

// As of the time this contract was written, `verbatim` doesn't work in
// inline assembly. Assignment of a value to a variable costs gas
// (although how much is unpredictable because it depends on the Yul/IR
// optimizer), as does the `GAS` opcode itself. Also solc tends to reorder
// the call to `gas()` with preparing the arguments for `div`. Therefore,
// the `gas()` below returns less than the actual amount of gas available
// for computation at the end of the call. That makes this check slightly
// too conservative. However, we do not correct for this because the
// correction would become outdated (possibly too permissive) if the
// opcodes are repriced.

// https://eips.ethereum.org/EIPS/eip-150
// https://ronan.eth.link/blog/ethereum-gas-dangers/
if iszero(or(success, or(returndatasize(), lt(div(callGas, 63), gas())))) {
// The call failed due to not enough gas left. We deliberately consume
// all remaining gas with `invalid` (instead of `revert`) to make this
// failure distinguishable to our caller.
invalid()
}

switch gt(returndatasize(), maxReturnBytes)
case 0 {
switch returndatasize()
case 0 {
returnData := 0x60
success := and(success, iszero(iszero(extcodesize(target))))
}
default {
mstore(returnData, returndatasize())
mstore(0x40, add(returnData, add(0x20, returndatasize())))
}
}
default {
// returnData = abi.encodeWithSignature("Error(string)", "CallWithGas: returnData too long")
success := 0
mstore(returnData, 0) // clear potentially dirty bits
mstore(add(returnData, 0x04), 0x6408c379a0) // length and selector
mstore(add(returnData, 0x24), 0x20)
mstore(add(returnData, 0x44), 0x20)
mstore(add(returnData, 0x64), "CallWithGas: returnData too long")
mstore(0x40, add(returnData, 0x84))
}
}
}

/// See `functionCallWithGasAndValue`
function functionCallWithGas(
address target,
bytes memory data,
uint256 callGas,
uint256 maxReturnBytes
) internal returns (bool success, bytes memory returnData) {
return functionCallWithGasAndValue(payable(target), data, callGas, 0, maxReturnBytes);
}

/**
* @notice `call` another contract forwarding a precomputed amount of gas.
* @notice Unlike `functionStaticCallWithGas`, a failure is not signaled if
* there is too much return data. Instead, it is simply truncated.
* @dev contains protections against EIP-150-induced insufficient gas griefing
* @dev reverts iff caller doesn't have enough native asset balance, the
* target is not a contract, or due to out-of-gas
* @return success true iff the call succeded
* @return returnData the return data or revert reason of the call
* @param target the contract (reverts if non-contract) on which to make the
* `call`
* @param data the calldata to pass
* @param callGas the gas to pass for the call. If the call requires more than
* the specified amount of gas and the caller didn't provide at
* least `callGas`, triggers an out-of-gas in the caller.
* @param value the amount of the native asset in wei to pass to the callee
* with the call
* @param maxReturnBytes Only this many bytes of return data/revert reason are
* read back from the call. This prevents griefing the
* caller. If more bytes are returned or the revert
* reason is longer, returnData will be truncated
*/
function functionCallWithGasAndValue(
address payable target,
bytes memory data,
uint256 callGas,
uint256 value,
uint256 maxReturnBytes
) internal returns (bool success, bytes memory returnData) {
if (value > 0 && (address(this).balance < value || target.code.length == 0)) {
return (success, returnData);
}

assembly ("memory-safe") {
returnData := mload(0x40)
success := call(callGas, target, value, add(data, 0x20), mload(data), add(returnData, 0x20), maxReturnBytes)

// As of the time this contract was written, `verbatim` doesn't work in
// inline assembly. Assignment of a value to a variable costs gas
// (although how much is unpredictable because it depends on the Yul/IR
// optimizer), as does the `GAS` opcode itself. Also solc tends to reorder
// the call to `gas()` with preparing the arguments for `div`. Therefore,
// the `gas()` below returns less than the actual amount of gas available
// for computation at the end of the call. That makes this check slightly
// too conservative. However, we do not correct for this because the
// correction would become outdated (possibly too permissive) if the
// opcodes are repriced.

// https://eips.ethereum.org/EIPS/eip-150
// https://ronan.eth.link/blog/ethereum-gas-dangers/
if iszero(or(success, or(returndatasize(), lt(div(callGas, 63), gas())))) {
// The call failed due to not enough gas left. We deliberately consume
// all remaining gas with `invalid` (instead of `revert`) to make this
// failure distinguishable to our caller.
invalid()
}

switch gt(returndatasize(), maxReturnBytes)
case 0 {
switch returndatasize()
case 0 {
returnData := 0x60
if iszero(value) {
success := and(success, iszero(iszero(extcodesize(target))))
}
}
default {
mstore(returnData, returndatasize())
mstore(0x40, add(returnData, add(0x20, returndatasize())))
}
}
default {
mstore(returnData, maxReturnBytes)
mstore(0x40, add(returnData, add(0x20, maxReturnBytes)))
}
}
}
}
39 changes: 39 additions & 0 deletions contracts/governance/src/IZeroExGovernor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: Apache-2.0
/*

Copyright 2023 ZeroEx Intl.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/
pragma solidity ^0.8.19;

import "./SecurityCouncil.sol";
import "@openzeppelin/governance/IGovernor.sol";
import "@openzeppelin/governance/extensions/IGovernorTimelock.sol";

abstract contract IZeroExGovernor is SecurityCouncil, IGovernor, IGovernorTimelock {
function token() public virtual returns (address);

function proposalThreshold() public view virtual returns (uint256);

function setVotingDelay(uint256 newVotingDelay) public virtual;

function setVotingPeriod(uint256 newVotingPeriod) public virtual;

function setProposalThreshold(uint256 newProposalThreshold) public virtual;

function proposalVotes(
uint256 proposalId
) public view virtual returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes);
}
Loading