Compare commits

...

13 Commits

Author SHA1 Message Date
dextracker
b0b76024d5 prettier and lint 2023-04-05 14:48:59 -04:00
dextracker
16039a7193 add deprecated tag 2023-04-05 14:32:41 -04:00
dextracker
b67ec9548a remove old fallback function sell/buy enforcement logic 2023-04-05 10:28:27 -04:00
Elena
3574ea5b27 Governance deployment scripts (#684)
* Add foundry deployment script and config

* Add generated by foundry build artifact for Treasury

* Add shortcut commands for goerli deployment

* Switch to a production version of predicting a deployment address

* Lower gas price

* Fix a copy-paste error

* Productionise and final test
2023-04-05 10:06:07 +03:00
Elena
b7bf5b5dfe Governance integration testing (#683)
* Segregate tests and mocks and wire up integration test base

* Switch to a production version of predicting a deployment address

* Add integration test for exchange governor migration

* Add integration test for treassury migration

* Add integration test for migrating the treasury

* Add governance upgrade action to transfer ZRX tokens to new governor

* Add governance upgrade action to transfer wCELO tokens to new governor

* Add governance upgrade action to transfer WYV tokens to new governor

* Turn on verbose logging
2023-04-05 09:35:08 +03:00
Elena
cfbb9c6f6c Governance audit and fixes (#690)
* Fix F-3: Explicit Variable Return audit report

* Reuse onlySecurityCouncil modifier and update its revert message

* Fix F-5: modifier Optimization audit report

* Fix F-7: Input Sanity Check audit report

* Add audit report

* Further gas optimise getting a checkpoint function by up to 100 gas

* Fix F-6: return Statement Optimization audit report

* Wrap logic in unchecked block to save 24 gas
in getVotes and getQuadraticVotes each

* Move audit doc into a dedicated folder
2023-04-04 18:06:38 +03:00
Elena
7e40dd1826 Remove offending test (#692) 2023-04-04 17:43:33 +03:00
Elena
bcbfbfa16c Decentralised governance of 0x protocol and treasury (#641)
* Install open zeppelin contracts

* Init foundry in governance

* Add wrapped ZRX token

* Add governance contracts testing to CI

* Set optimizer runs to default

* Upgrade to patched version of openzeppelin/contracts

* Test stakingakng / unwrapping ZRX

* Init npm package

* Lint fix, removing lib from gitignore

* Add openzeppelin contracts git submodule for foundry

* Add vanilla governor contract

* Fix reference paths to imported packages

* Temporarily switch to using a mocked version of ZRX

* Ignore foundry's lib in link checker

* Fix a conflict in gitignore between forge lib adn built lib

* Upload governance code coverage report to coveralls

* Flesh out test scenarios for wrapping/unwrapping

* Add basic ERC20 name and symbol tests

* Wire in basic timelock controller and governor test setup

* Test basic governor properties

* Add basic voting power delegation tests

* Add proposal execution happy path test

* Split ERC20Votes logic between wrapped token
and ZeroExVotes contracts

* Exclude BaseTest from coverage in coveralls

* Add protocol specific governor with produciton governance settings

* Add a dedicated instance for the treasury governor
This is currently using the default 1 token 1 vote mechanism but will be migrated

* Add test for updating governance settings
for voting delay, voting period and proposal threshold

* Create seperate timelock contract instance for treasury and protocol

* Test updating the timlock min delay

* Set timelock delay to 2 days for protocol and 1 sec for treasury

* Remove timelock from treasury governor

* Refactor _checkpointsLookup to return entire Checkpoint
instad of just number of votes

* Update the totalSupply checkpoints updating logic

* Quadratic voting power transfers and delegations

* Fix workflow yaml

* Initialise ZeroExVotes behind a ERC1967Proxy
Test it cannot be reinitialised

* Remove obsoleted console.logs from test

* Storage pack Checkpoint enum

* Remove keeping track of total balances for voting

* Switch to using the foundry artifact in test

* Fix rebase issue

* Add timelock control over the treasury governor

* Add test for wrapped token transfer

* Emit separate events for changing linear and quadratic voting power

* Add the ability to cancel a proposal

* Limit the governors' cancel function to security council only

* Eject security council after a proposal is cancelled

* Add ability for governance to set the security council

* Merge the governors test suites into one reusable set of tests

* Add an empty test function to base test contract
to remove it from coverage reports. Fudge but no other way to ignore it in report

* Security council can rollback protocol upgrades

* Upgrade to solidity 0.8.19

* Move IZeroExGovernor to src

* Abstract Security council interface into its own

* Emit events when assigning and ejecting the security council

* Use a cast to bytes4 instead of LibBytes

Co-authored-by: duncancmt <1207590+duncancmt@users.noreply.github.com>

* Writing total supply checkpoints and setup of
quorum percentage of quadratic total supply for treasure governor

* Add test for transferring tokens when delegating

* Rename IZeroExSecurityCouncil to ISecurityCouncil

* Add security council restrictions to governors

* Remove obsolete overflow check

* Improve test coverage

* Upgrade open-zeppelin contracts to 4.8.2

* Test delegation by signature

* Test non security council requests
to rollback protocol changes cannot be executed

* Better revert messages

* Test correct interfaces are supported

* Remove obsoleted funciton

* Further test delegation by signature scenario

* Split the delegation functionality tests

* Add test for initialisation of voting contract

* Add test for reading checkpoints

* Update code comments

* Fix compilation warnings

* Run smt checker

* Add checkpoint tests

* Rename parameter in moveEntireVotingPower to match the one in movePartialVotingPower

* Switch moveEntireVotingPower to a more generic moveVotingPower implementation
as in the open-zeppelin contracts

* Install foundry earlier in CI

* Switch movePartialVotingPower to the generic moveVotingPower implementation

* Write totalSupplyCheckpoints via the generic _writeCheckpoint

* Add threshold for quadratic voting power

* Remove autoinserted code by OZ

* Add openzeppelin/contracts-upgradable

* Add initializable base to Voting contract

* Fix terminogy error in natspec

* Fix code comment

* Remove obsoleted overrides and add a missing modifier to moveVotingPower

* Remove amount check

Co-authored-by: duncancmt <1207590+duncancmt@users.noreply.github.com>

* Fix a calculation error and clean tests

* Update thresholds for treasury governor

* Fix testShouldNotBeAbleToDelegateWithSignatureAfterExpiry

* Update from @duncancmt

without "memory-safe" the IR optimizer produces significantly worse code and it disables the stack limit evader

Co-authored-by: duncancmt <1207590+duncancmt@users.noreply.github.com>

* Add onlyProxy to initializer

* Fix quadratic voting weight base

* Rename voting parameter for clarity

* Make addresses immutable (#680)

* Make addresses immutable

* Fix linting issues

---------

Co-authored-by: elenadimitrova <elena@arenabg.com>

* Prevent griefing by a malicious ZeroExVotes upgrade (#681)

* Gas optimization

* Minimal change to prevent malicious ZeroExVotes from griefing

* Add demonstration of griefing upgrade

* Fix rebase issues with tests

* Fix prettier issues

* Add checks to test

---------

Co-authored-by: elenadimitrova <elena@arenabg.com>

* Rename SecurityCouncil contract

* Add timestamp to delegator balance updates

* Make quadraticThreshold `immutable` for gas efficiency

* Remove the logic for ejecting security council

* Switch balance timestamp to be a block number

* Test votes migration for adding a new vote weight mechanism (#674)

* Add Emacs files to .gitignore

* Make some functions unproected to demonstrate a migration

* Add example (broken) migration

* Add migration test for voting logic

* Try to simplify tests

* Fix compilation errors

* Fix underflow test with new logic

* Flesh out migration test for voting

* Replace cube root library

* Fix stack too deep in coverage

---------

Co-authored-by: elenadimitrova <elena@arenabg.com>

* Change test case to testFail

* Update contracts/governance/test/ZeroExVotesMigration.sol

Co-authored-by: duncancmt <1207590+duncancmt@users.noreply.github.com>

---------

Co-authored-by: duncancmt <1207590+duncancmt@users.noreply.github.com>
Co-authored-by: Duncan Townsend <git@duncancmt.com>
2023-03-22 15:03:05 +02:00
Github Actions
03fc744bbd Publish
- @0x/contracts-erc20@4.0.2
 - @0x/contracts-test-utils@5.4.49
 - @0x/contracts-treasury@1.4.42
 - @0x/contracts-utils@4.8.40
 - @0x/contracts-zero-ex@0.39.1
 - @0x/contract-addresses@8.2.0
 - @0x/contract-wrappers@13.22.18
 - @0x/protocol-utils@11.18.1
2023-03-10 01:13:24 +00:00
Github Actions
a23143d1eb Updated CHANGELOGS & MD docs 2023-03-10 01:13:21 +00:00
Savarn Dontamsetti (Sav)
c0ef6fece3 chore: update addresses to reflect AaveV3 L2 encoding removal and KyberElastic Addition (#678)
* Update addresses to reflect AaveV3 L2Encoding removal
2023-03-09 18:35:01 -05:00
Kevin Liao
03ea4e3dba feat:Add Kyber Elastic mixins (#677)
* add kyber elastic mixins

* update changelog

* fix changelog
2023-03-09 12:33:14 -08:00
Savarn Dontamsetti (Sav)
3e939f7780 Removing L2 Encoding from AaveV3 MixIn (#676) 2023-03-09 12:02:36 -05:00
75 changed files with 17532 additions and 120 deletions

View File

@@ -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
@@ -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: |
@@ -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
.gitignore vendored
View File

@@ -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
@@ -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
.gitmodules vendored
View File

@@ -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

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1678410794,
"version": "4.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1677693479,
"version": "4.0.1",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v4.0.2 - _March 10, 2023_
* Dependencies updated
## v4.0.1 - _March 1, 2023_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-erc20",
"version": "4.0.1",
"version": "4.0.2",
"engines": {
"node": ">=6.12"
},
@@ -24,7 +24,7 @@
},
"homepage": "https://github.com/0xProject/protocol",
"devDependencies": {
"@0x/contracts-utils": "^4.8.39",
"@0x/contracts-utils": "^4.8.40",
"@0x/ts-doc-gen": "^0.0.28",
"typedoc": "~0.16.11"
},

View File

@@ -0,0 +1,505 @@
{
"abi": [
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"inputs": [],
"payable": false,
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
],
"bytecode": {
"object": "0x60606040526b033b2e3c9fd0803ce8000000600355341561001c57fe5b5b600354600160a060020a0333166000908152602081905260409020555b5b61078d8061004a6000396000f300606060405236156100965763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde038114610098578063095ea7b31461014657806318160ddd1461018657806323b872dd146101a8578063313ce567146101ee57806370a082311461021457806395d89b411461024f578063a9059cbb146102fd578063dd62ed3e1461033d575bfe5b34156100a057fe5b6100a861037e565b60408051602080825283518183015283519192839290830191850190808383821561010c575b80518252602083111561010c577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016100ce565b505050905090810190601f1680156101385780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561014e57fe5b61017273ffffffffffffffffffffffffffffffffffffffff600435166024356103b5565b604080519115158252519081900360200190f35b341561018e57fe5b61019661042d565b60408051918252519081900360200190f35b34156101b057fe5b61017273ffffffffffffffffffffffffffffffffffffffff60043581169060243516604435610433565b604080519115158252519081900360200190f35b34156101f657fe5b6101fe6105d4565b6040805160ff9092168252519081900360200190f35b341561021c57fe5b61019673ffffffffffffffffffffffffffffffffffffffff600435166105d9565b60408051918252519081900360200190f35b341561025757fe5b6100a8610605565b60408051602080825283518183015283519192839290830191850190808383821561010c575b80518252602083111561010c577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016100ce565b505050905090810190601f1680156101385780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561030557fe5b61017273ffffffffffffffffffffffffffffffffffffffff6004351660243561063c565b604080519115158252519081900360200190f35b341561034557fe5b61019673ffffffffffffffffffffffffffffffffffffffff60043581169060243516610727565b60408051918252519081900360200190f35b60408051808201909152601181527f30782050726f746f636f6c20546f6b656e000000000000000000000000000000602082015281565b73ffffffffffffffffffffffffffffffffffffffff338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60035481565b73ffffffffffffffffffffffffffffffffffffffff808416600081815260016020908152604080832033909516835293815283822054928252819052918220548390108015906104835750828110155b80156104b6575073ffffffffffffffffffffffffffffffffffffffff841660009081526020819052604090205483810110155b156105c65773ffffffffffffffffffffffffffffffffffffffff808516600090815260208190526040808220805487019055918716815220805484900390557fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8110156105585773ffffffffffffffffffffffffffffffffffffffff808616600090815260016020908152604080832033909416835292905220805484900390555b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a3600191506105cb565b600091505b5b509392505050565b601281565b73ffffffffffffffffffffffffffffffffffffffff81166000908152602081905260409020545b919050565b60408051808201909152600381527f5a52580000000000000000000000000000000000000000000000000000000000602082015281565b73ffffffffffffffffffffffffffffffffffffffff3316600090815260208190526040812054829010801590610699575073ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604090205482810110155b156107185773ffffffffffffffffffffffffffffffffffffffff33811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a3506001610427565b506000610427565b5b92915050565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152600160209081526040808320938516835292905220545b929150505600a165627a7a723058202dbef854545f38e5b78ec251d65db5fa0f12b6f2f0a0039063735c2dc416d6310029",
"sourceMap": "4935:353:0:-;;;5056:8;5027:37;;5208:78;;;;;;;5268:11;;-1:-1:-1;;;;;5254:10:0;5245:20;:8;:20;;;;;;;;;;:34;5208:78;4935:353;;;;;;;",
"linkReferences": {}
},
"deployedBytecode": {
"object": "0x606060405236156100965763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde038114610098578063095ea7b31461014657806318160ddd1461018657806323b872dd146101a8578063313ce567146101ee57806370a082311461021457806395d89b411461024f578063a9059cbb146102fd578063dd62ed3e1461033d575bfe5b34156100a057fe5b6100a861037e565b60408051602080825283518183015283519192839290830191850190808383821561010c575b80518252602083111561010c577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016100ce565b505050905090810190601f1680156101385780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561014e57fe5b61017273ffffffffffffffffffffffffffffffffffffffff600435166024356103b5565b604080519115158252519081900360200190f35b341561018e57fe5b61019661042d565b60408051918252519081900360200190f35b34156101b057fe5b61017273ffffffffffffffffffffffffffffffffffffffff60043581169060243516604435610433565b604080519115158252519081900360200190f35b34156101f657fe5b6101fe6105d4565b6040805160ff9092168252519081900360200190f35b341561021c57fe5b61019673ffffffffffffffffffffffffffffffffffffffff600435166105d9565b60408051918252519081900360200190f35b341561025757fe5b6100a8610605565b60408051602080825283518183015283519192839290830191850190808383821561010c575b80518252602083111561010c577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016100ce565b505050905090810190601f1680156101385780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561030557fe5b61017273ffffffffffffffffffffffffffffffffffffffff6004351660243561063c565b604080519115158252519081900360200190f35b341561034557fe5b61019673ffffffffffffffffffffffffffffffffffffffff60043581169060243516610727565b60408051918252519081900360200190f35b60408051808201909152601181527f30782050726f746f636f6c20546f6b656e000000000000000000000000000000602082015281565b73ffffffffffffffffffffffffffffffffffffffff338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60035481565b73ffffffffffffffffffffffffffffffffffffffff808416600081815260016020908152604080832033909516835293815283822054928252819052918220548390108015906104835750828110155b80156104b6575073ffffffffffffffffffffffffffffffffffffffff841660009081526020819052604090205483810110155b156105c65773ffffffffffffffffffffffffffffffffffffffff808516600090815260208190526040808220805487019055918716815220805484900390557fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8110156105585773ffffffffffffffffffffffffffffffffffffffff808616600090815260016020908152604080832033909416835292905220805484900390555b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a3600191506105cb565b600091505b5b509392505050565b601281565b73ffffffffffffffffffffffffffffffffffffffff81166000908152602081905260409020545b919050565b60408051808201909152600381527f5a52580000000000000000000000000000000000000000000000000000000000602082015281565b73ffffffffffffffffffffffffffffffffffffffff3316600090815260208190526040812054829010801590610699575073ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604090205482810110155b156107185773ffffffffffffffffffffffffffffffffffffffff33811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a3506001610427565b506000610427565b5b92915050565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152600160209081526040808320938516835292905220545b929150505600a165627a7a723058202dbef854545f38e5b78ec251d65db5fa0f12b6f2f0a0039063735c2dc416d6310029",
"sourceMap": "4935:353:0:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5109:49;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;18:2:-1;;13:3;7:5;32;59:3;53:5;48:3;41:6;93:2;88:3;85:2;78:6;73:3;67:5;152:3;;;;;117:2;108:3;;;;130;172:5;167:4;181:3;3:186;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3523:190:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5027:37;;;;;;;;;;;;;;;;;;;;;;;;;;4369:562;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4986:35;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3415:102;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5164:37;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;18:2:-1;;13:3;7:5;32;59:3;53:5;48:3;41:6;93:2;88:3;85:2;78:6;73:3;67:5;152:3;;;;;117:2;108:3;;;;130;172:5;167:4;181:3;3:186;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2490:433:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3719:129;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5109:49;;;;;;;;;;;;;;;;;;;:::o;3523:190::-;3599:19;3607:10;3599:19;;3583:4;3599:19;;;:7;:19;;;;;;;;:29;;;;;;;;;;;;:38;;;3647;;;;;;;3583:4;;3599:29;:19;3647:38;;;;;;;;;;;-1:-1:-1;3702:4:0;3523:190;;;;;:::o;5027:37::-;;;;:::o;4369:562::-;4487:14;;;;4451:4;4487:14;;;:7;:14;;;;;;;;4502:10;4487:26;;;;;;;;;;;;4527:15;;;;;;;;;;:25;;;;;;:48;;;4569:6;4556:9;:19;;4527:48;:91;;;;-1:-1:-1;4605:13:0;;;:8;:13;;;;;;;;;;;4579:22;;;:39;;4527:91;4523:402;;;4634:13;;;;:8;:13;;;;;;;;;;;:23;;;;;;4671:15;;;;;;:25;;;;;;;4069:12;4714:20;;4710:95;;;4754:14;;;;;;;;:7;:14;;;;;;;;4769:10;4754:26;;;;;;;;;:36;;;;;;;4710:95;4834:3;4818:28;;4827:5;4818:28;;;4839:6;4818:28;;;;;;;;;;;;;;;;;;4867:4;4860:11;;;;4523:402;4909:5;4902:12;;4523:402;4369:562;;;;;;;:::o;4986:35::-;5019:2;4986:35;:::o;3415:102::-;3494:16;;;3468:7;3494:16;;;;;;;;;;;3415:102;;;;:::o;5164:37::-;;;;;;;;;;;;;;;;;;;:::o;2490:433::-;2635:20;2644:10;2635:20;2546:4;2635:20;;;;;;;;;;;:30;;;;;;:73;;-1:-1:-1;2695:13:0;;;:8;:13;;;;;;;;;;;2669:22;;;:39;;2635:73;2631:286;;;2724:20;2733:10;2724:20;;:8;:20;;;;;;;;;;;:30;;;;;;;2768:13;;;;;;;;;;:23;;;;;;2805:33;;;;;;;2768:13;;2805:33;;;;;;;;;;;-1:-1:-1;2859:4:0;2852:11;;2631:286;-1:-1:-1;2901:5:0;2894:12;;2631:286;2490:433;;;;;:::o;3719:129::-;3816:15;;;;3790:7;3816:15;;;:7;:15;;;;;;;;:25;;;;;;;;;;3719:129;;;;;:::o",
"linkReferences": {}
},
"methodIdentifiers": {
"allowance(address,address)": "dd62ed3e",
"approve(address,uint256)": "095ea7b3",
"balanceOf(address)": "70a08231",
"decimals()": "313ce567",
"name()": "06fdde03",
"symbol()": "95d89b41",
"totalSupply()": "18160ddd",
"transfer(address,uint256)": "a9059cbb",
"transferFrom(address,address,uint256)": "23b872dd"
},
"rawMetadata": "{\"compiler\":{\"version\":\"0.4.11+commit.68ef5810\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_spender\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_from\",\"type\":\"address\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"},{\"name\":\"_spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_spender\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"}],\"devdoc\":{\"methods\":{\"transferFrom(address,address,uint256)\":{\"details\":\"ERC20 transferFrom, modified such that an allowance of MAX_UINT represents an unlimited allowance.\",\"params\":{\"_from\":\"Address to transfer from.\",\"_to\":\"Address to transfer to.\",\"_value\":\"Amount to transfer.\"},\"return\":\"Success of transfer.\"}}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"contracts/erc20/src/ZRXToken.sol\":\"ZRXToken\"},\"libraries\":{},\"optimizer\":{\"enabled\":true,\"runs\":1000000},\"remappings\":[\":@0x/contracts-erc20/=contracts/erc20/\",\":@0x/contracts-utils/=contracts/utils/\",\":ds-test/=contracts/erc20/lib/forge-std/lib/ds-test/src/\",\":forge-std/=contracts/erc20/lib/forge-std/src/\"]},\"sources\":{\"contracts/erc20/src/ZRXToken.sol\":{\"keccak256\":\"0x8582c06b20f8b7d3d603b485b5d26f840e01d1986381b334a856833edcff0d47\",\"urls\":[\"bzzr://ef728dddbaa1e26baa6cc9fe0f83de5055bc0b17dfe488018f4ee59d68ccb5dd\"]}},\"version\":1}",
"metadata": {
"compiler": {
"version": "0.4.11+commit.68ef5810"
},
"language": "Solidity",
"output": {
"abi": [
{
"inputs": [],
"type": "function",
"name": "name",
"outputs": [
{
"internalType": null,
"name": "",
"type": "string"
}
]
},
{
"inputs": [
{
"internalType": null,
"name": "_spender",
"type": "address"
},
{
"internalType": null,
"name": "_value",
"type": "uint256"
}
],
"type": "function",
"name": "approve",
"outputs": [
{
"internalType": null,
"name": "",
"type": "bool"
}
]
},
{
"inputs": [],
"type": "function",
"name": "totalSupply",
"outputs": [
{
"internalType": null,
"name": "",
"type": "uint256"
}
]
},
{
"inputs": [
{
"internalType": null,
"name": "_from",
"type": "address"
},
{
"internalType": null,
"name": "_to",
"type": "address"
},
{
"internalType": null,
"name": "_value",
"type": "uint256"
}
],
"type": "function",
"name": "transferFrom",
"outputs": [
{
"internalType": null,
"name": "",
"type": "bool"
}
]
},
{
"inputs": [],
"type": "function",
"name": "decimals",
"outputs": [
{
"internalType": null,
"name": "",
"type": "uint8"
}
]
},
{
"inputs": [
{
"internalType": null,
"name": "_owner",
"type": "address"
}
],
"type": "function",
"name": "balanceOf",
"outputs": [
{
"internalType": null,
"name": "",
"type": "uint256"
}
]
},
{
"inputs": [],
"type": "function",
"name": "symbol",
"outputs": [
{
"internalType": null,
"name": "",
"type": "string"
}
]
},
{
"inputs": [
{
"internalType": null,
"name": "_to",
"type": "address"
},
{
"internalType": null,
"name": "_value",
"type": "uint256"
}
],
"type": "function",
"name": "transfer",
"outputs": [
{
"internalType": null,
"name": "",
"type": "bool"
}
]
},
{
"inputs": [
{
"internalType": null,
"name": "_owner",
"type": "address"
},
{
"internalType": null,
"name": "_spender",
"type": "address"
}
],
"type": "function",
"name": "allowance",
"outputs": [
{
"internalType": null,
"name": "",
"type": "uint256"
}
]
},
{
"inputs": [],
"type": "constructor"
},
{
"inputs": [
{
"internalType": null,
"name": "_from",
"type": "address",
"indexed": true
},
{
"internalType": null,
"name": "_to",
"type": "address",
"indexed": true
},
{
"internalType": null,
"name": "_value",
"type": "uint256",
"indexed": false
}
],
"type": "event",
"name": "Transfer",
"anonymous": false
},
{
"inputs": [
{
"internalType": null,
"name": "_owner",
"type": "address",
"indexed": true
},
{
"internalType": null,
"name": "_spender",
"type": "address",
"indexed": true
},
{
"internalType": null,
"name": "_value",
"type": "uint256",
"indexed": false
}
],
"type": "event",
"name": "Approval",
"anonymous": false
}
],
"devdoc": {
"methods": {
"transferFrom(address,address,uint256)": {
"details": "ERC20 transferFrom, modified such that an allowance of MAX_UINT represents an unlimited allowance.",
"params": {
"_from": "Address to transfer from.",
"_to": "Address to transfer to.",
"_value": "Amount to transfer."
},
"return": "Success of transfer."
}
}
},
"userdoc": {
"methods": {}
}
},
"settings": {
"remappings": [
":@0x/contracts-erc20/=contracts/erc20/",
":@0x/contracts-utils/=contracts/utils/",
":ds-test/=contracts/erc20/lib/forge-std/lib/ds-test/src/",
":forge-std/=contracts/erc20/lib/forge-std/src/"
],
"optimizer": {
"enabled": true,
"runs": 1000000
},
"compilationTarget": {
"contracts/erc20/src/ZRXToken.sol": "ZRXToken"
},
"libraries": {}
},
"sources": {
"contracts/erc20/src/ZRXToken.sol": {
"keccak256": "0x8582c06b20f8b7d3d603b485b5d26f840e01d1986381b334a856833edcff0d47",
"urls": ["bzzr://ef728dddbaa1e26baa6cc9fe0f83de5055bc0b17dfe488018f4ee59d68ccb5dd"],
"license": null
}
},
"version": 1
},
"id": 0
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,37 @@
[profile.default]
src = 'src'
out = 'out'
libs = ['lib', "../utils/contracts/src/"]
match_path = "test/unit/*.sol"
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
via_ir = true
[profile.integration]
match_path = "test/integration/*.sol"
gas_price = 31_000_000_000
[rpc_endpoints]
goerli = "${GOERLI_RPC_URL}"
mainnet = "${MAINNET_RPC_URL}"
[etherscan]
goerli = { key = "${ETHERSCAN_API_KEY}" }
[profile.smt.model_checker]
engine = 'chc'
timeout = 10_000
targets = [
'assert',
'constantCondition',
'divByZero',
'outOfBounds',
'underflow'
]
contracts = { 'src/ZeroExProtocolGovernor.sol' = [ 'ZeroExProtocolGovernor' ] }

View File

@@ -0,0 +1,25 @@
{
"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",
"test:integration": "source .env && FOUNDRY_PROFILE=integration forge test --fork-url $MAINNET_RPC_URL --fork-block-number 16884148 -vvv",
"goerli:deploy:zrxtoken": "source .env && forge script script/DeployToken.s.sol:Deploy --rpc-url $GOERLI_RPC_URL --broadcast --slow -vvvv",
"goerli:deploy:governance": "source .env && forge script script/DeployGovernance.s.sol:Deploy --rpc-url $GOERLI_RPC_URL --broadcast --slow -vvvv",
"mainnet:deploy:governance": "source .env && forge script script/DeployGovernance.s.sol:Deploy --rpc-url $MAINNET_RPC_URL --broadcast --slow -vvvv"
},
"repository": {
"type": "git",
"url": "https://github.com/0xProject/protocol.git"
},
"license": "Apache-2.0",
"dependencies": {}
}

View File

@@ -0,0 +1,78 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import "forge-std/Script.sol";
import "forge-std/console.sol";
import "forge-std/console2.sol";
import "@openzeppelin/token/ERC20/IERC20.sol";
import "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol";
import "../src/ZRXWrappedToken.sol";
import "../src/ZeroExVotes.sol";
import "../src/ZeroExTimelock.sol";
import "../src/ZeroExProtocolGovernor.sol";
import "../src/ZeroExTreasuryGovernor.sol";
contract Deploy is Script {
address internal constant DEPLOYER = 0xEf37aD2BACD70119F141140f7B5E46Cd53a65fc4;
address internal constant ZRX_TOKEN = 0xE41d2489571d322189246DaFA5ebDe1F4699F498;
address internal constant TREASURY = 0x0bB1810061C2f5b2088054eE184E6C79e1591101;
address internal constant EXCHANGE = 0xDef1C0ded9bec7F1a1670819833240f027b25EfF;
address payable internal constant SECURITY_COUNCIL = payable(DEPLOYER);
uint256 internal constant QUADRATIC_THRESHOLD = 1000000e18;
function setUp() public {}
function run() external {
vm.startBroadcast(vm.envAddress("DEPLOYER"));
console2.log("Zrx Token", ZRX_TOKEN);
address wTokenPrediction = predict(DEPLOYER, vm.getNonce(DEPLOYER) + 2);
ZeroExVotes votesImpl = new ZeroExVotes(wTokenPrediction, QUADRATIC_THRESHOLD);
ERC1967Proxy votesProxy = new ERC1967Proxy(address(votesImpl), abi.encodeCall(votesImpl.initialize, ()));
ZRXWrappedToken wToken = new ZRXWrappedToken(IERC20(ZRX_TOKEN), ZeroExVotes(address(votesProxy)));
assert(address(wToken) == wTokenPrediction);
console2.log("Wrapped Token", address(wToken));
ZeroExVotes votes = ZeroExVotes(address(votesProxy));
console2.log("Votes", address(votes));
address[] memory proposers = new address[](0);
address[] memory executors = new address[](0);
ZeroExTimelock protocolTimelock = new ZeroExTimelock(3 days, proposers, executors, DEPLOYER);
console2.log("Protocol timelock", address(protocolTimelock));
ZeroExProtocolGovernor protocolGovernor = new ZeroExProtocolGovernor(
IVotes(address(votes)),
protocolTimelock,
SECURITY_COUNCIL
);
protocolTimelock.grantRole(protocolTimelock.PROPOSER_ROLE(), address(protocolGovernor));
protocolTimelock.grantRole(protocolTimelock.EXECUTOR_ROLE(), address(protocolGovernor));
protocolTimelock.grantRole(protocolTimelock.CANCELLER_ROLE(), address(protocolGovernor));
console2.log("Protocol governor", address(protocolGovernor));
ZeroExTimelock treasuryTimelock = new ZeroExTimelock(2 days, proposers, executors, DEPLOYER);
console2.log("Treasury timelock", address(treasuryTimelock));
ZeroExTreasuryGovernor treasuryGovernor = new ZeroExTreasuryGovernor(
IVotes(address(votes)),
treasuryTimelock,
SECURITY_COUNCIL
);
treasuryTimelock.grantRole(treasuryTimelock.PROPOSER_ROLE(), address(treasuryGovernor));
treasuryTimelock.grantRole(treasuryTimelock.EXECUTOR_ROLE(), address(treasuryGovernor));
treasuryTimelock.grantRole(treasuryTimelock.CANCELLER_ROLE(), address(treasuryGovernor));
console2.log("Treasury governor", address(treasuryGovernor));
console2.log(unicode"0x governance deployed successfully 🎉");
vm.stopBroadcast();
}
function predict(address deployer, uint256 nonce) internal pure returns (address) {
require(nonce > 0 && nonce < 128, "Invalid nonce");
return address(uint160(uint256(keccak256(abi.encodePacked(bytes2(0xd694), deployer, bytes1(uint8(nonce)))))));
}
}

View File

@@ -0,0 +1,22 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import "forge-std/Script.sol";
import "forge-std/console2.sol";
contract Deploy is Script {
function setUp() public {}
function run() external {
vm.startBroadcast(vm.envAddress("DEPLOYER"));
bytes memory _bytecode = vm.getCode("./ZRXToken.json");
address zrxToken;
assembly {
zrxToken := create(0, add(_bytecode, 0x20), mload(_bytecode))
}
console2.log("Zrx Token", zrxToken);
console2.log(unicode"Zrx Token deployed successfully 🎉");
vm.stopBroadcast();
}
}

View File

@@ -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)))
}
}
}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,132 @@
// 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;
interface IZeroExVotes {
struct Checkpoint {
uint32 fromBlock;
uint96 votes;
uint96 quadraticVotes;
}
/**
* @dev Emitted when a token transfer or delegate change,
* results in changes to a delegate's quadratic number of votes.
*/
event DelegateQuadraticVotesChanged(
address indexed delegate,
uint256 previousQuadraticBalance,
uint256 newQuadraticBalance
);
/**
* @dev Emitted when a token transfer or delegate change, results in changes to a delegate's number of votes.
*/
event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);
/**
* @dev Emitted when the total supply of the token is changed due to minting and burning which results in
* the total supply checkpoint being writtenor updated.
*/
event TotalSupplyChanged(uint256 totalSupplyVotes, uint256 totalSupplyQuadraticVotes);
/**
* @dev Get the `pos`-th checkpoint for `account`.
*/
function checkpoints(address account, uint32 pos) external view returns (Checkpoint memory);
/**
* @dev Get number of checkpoints for `account`.
*/
function numCheckpoints(address account) external view returns (uint32);
/**
* @dev Gets the current votes balance for `account`
*/
function getVotes(address account) external view returns (uint256);
/**
* @dev Gets the current quadratic votes balance for `account`
*/
function getQuadraticVotes(address account) external view returns (uint256);
/**
* @dev Retrieve the number of votes for `account` at the end of `blockNumber`.
*
* Requirements:
*
* - `blockNumber` must have been already mined
*/
function getPastVotes(address account, uint256 blockNumber) external view returns (uint256);
/**
* @dev Retrieve the number of quadratic votes for `account` at the end of `blockNumber`.
*
* Requirements:
*
* - `blockNumber` must have been already mined
*/
function getPastQuadraticVotes(address account, uint256 blockNumber) external view returns (uint256);
/**
* @dev Retrieve the `totalSupply` at the end of `blockNumber`. Note, this value is the sum of all balances.
* It is but NOT the sum of all the delegated votes!
*
* Requirements:
*
* - `blockNumber` must have been already mined
*/
function getPastTotalSupply(uint256 blockNumber) external view returns (uint256);
/**
* @dev Retrieve the sqrt of `totalSupply` at the end of `blockNumber`. Note, this value is the square root of the
* sum of all balances.
* It is but NOT the sum of all the sqrt of the delegated votes!
*
* Requirements:
*
* - `blockNumber` must have been already mined
*/
function getPastQuadraticTotalSupply(uint256 blockNumber) external view returns (uint256);
/**
* @dev Moves the voting power corresponding to `amount` number of tokens from `src` to `dst`.
* Note that if the delegator isn't delegating to anyone before the function call `src` = address(0)
* @param src the delegatee we are moving voting power away from
* @param dst the delegatee we are moving voting power to
* @param srcBalance balance of the delegator whose delegatee is `src`. This is value _after_ the transfer.
* @param dstBalance balance of the delegator whose delegatee is `dst`. This is value _after_ the transfer.
* @param srcBalanceLastUpdated block number when balance of `src` was last updated.
* @param dstBalanceLastUpdated block number when balance of `dst` was last updated.
* @param amount The amount of tokens transferred from the source delegate to destination delegate.
*/
function moveVotingPower(
address src,
address dst,
uint256 srcBalance,
uint256 dstBalance,
uint96 srcBalanceLastUpdated,
uint96 dstBalanceLastUpdated,
uint256 amount
) external returns (bool);
function writeCheckpointTotalSupplyMint(uint256 accountBalance, uint256 amount) external returns (bool);
function writeCheckpointTotalSupplyBurn(uint256 accountBalance, uint256 amount) external returns (bool);
}

View File

@@ -0,0 +1,85 @@
// 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;
abstract contract SecurityCouncil {
address public securityCouncil;
event SecurityCouncilAssigned(address securityCouncil);
event SecurityCouncilEjected();
modifier onlySecurityCouncil() {
_checkSenderIsSecurityCouncil();
_;
}
/**
* @dev Checks that either a security council is assigned or the payloads array is a council assignment call.
*/
modifier securityCouncilAssigned(bytes[] memory payloads) {
if (securityCouncil == address(0) && !_payloadIsAssignSecurityCouncil(payloads)) {
revert("SecurityCouncil: security council not assigned and this is not an assignment call");
}
_;
}
/**
* @dev Assigns new security council.
*/
function assignSecurityCouncil(address _securityCouncil) public virtual {
securityCouncil = _securityCouncil;
emit SecurityCouncilAssigned(securityCouncil);
}
/**
* @dev Ejects the current security council via setting the security council address to 0.
* Security council is ejected after they either cancel a proposal or execute a protocol rollback.
*/
function ejectSecurityCouncil() internal {
securityCouncil = address(0);
emit SecurityCouncilEjected();
}
/**
* @dev Cancel existing proposal with the submitted `targets`, `values`, `calldatas` and `descriptionHash`.
*/
function cancel(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) public virtual;
function _payloadIsAssignSecurityCouncil(bytes[] memory payloads) private pure returns (bool) {
require(payloads.length == 1, "SecurityCouncil: more than 1 transaction in proposal");
bytes memory payload = payloads[0];
// Check this is as assignSecurityCouncil(address) transaction
// function signature for assignSecurityCouncil(address)
// = bytes4(keccak256("assignSecurityCouncil(address)"))
// = 0x2761c3cd
if (bytes4(payload) == bytes4(0x2761c3cd)) return true;
else return false;
}
function _checkSenderIsSecurityCouncil() private view {
require(msg.sender == securityCouncil, "SecurityCouncil: only security council allowed");
}
}

View File

@@ -0,0 +1,164 @@
// 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 "@openzeppelin/token/ERC20/ERC20.sol";
import "@openzeppelin/token/ERC20/extensions/draft-ERC20Permit.sol";
import "@openzeppelin/token/ERC20/extensions/ERC20Wrapper.sol";
import "@openzeppelin/governance/utils/IVotes.sol";
import "@openzeppelin/utils/math/SafeCast.sol";
import "./IZeroExVotes.sol";
import "./CallWithGas.sol";
contract ZRXWrappedToken is ERC20, ERC20Permit, ERC20Wrapper {
using CallWithGas for address;
struct DelegateInfo {
address delegate;
uint96 balanceLastUpdated;
}
constructor(
IERC20 wrappedToken,
IZeroExVotes _zeroExVotes
) ERC20("Wrapped ZRX", "wZRX") ERC20Permit("Wrapped ZRX") ERC20Wrapper(wrappedToken) {
zeroExVotes = _zeroExVotes;
}
IZeroExVotes public immutable zeroExVotes;
mapping(address => DelegateInfo) private _delegates;
bytes32 private constant _DELEGATION_TYPEHASH =
keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
/**
* @dev Emitted when an account changes their delegate.
*/
event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
// The functions below are the required overrides from the base contracts
function decimals() public pure override(ERC20, ERC20Wrapper) returns (uint8) {
return 18;
}
function _afterTokenTransfer(address from, address to, uint256 amount) internal override(ERC20) {
super._afterTokenTransfer(from, to, amount);
DelegateInfo memory fromDelegate = delegateInfo(from);
DelegateInfo memory toDelegate = delegateInfo(to);
uint256 fromBalance = fromDelegate.delegate == address(0) ? 0 : balanceOf(from) + amount;
uint256 toBalance = toDelegate.delegate == address(0) ? 0 : balanceOf(to) - amount;
if (fromDelegate.delegate != address(0)) _delegates[from].balanceLastUpdated = SafeCast.toUint96(block.number);
if (toDelegate.delegate != address(0)) _delegates[to].balanceLastUpdated = SafeCast.toUint96(block.number);
zeroExVotes.moveVotingPower(
fromDelegate.delegate,
toDelegate.delegate,
fromBalance,
toBalance,
fromDelegate.balanceLastUpdated,
toDelegate.balanceLastUpdated,
amount
);
}
function _mint(address account, uint256 amount) internal override(ERC20) {
super._mint(account, amount);
zeroExVotes.writeCheckpointTotalSupplyMint(balanceOf(account) - amount, amount);
}
function _burn(address account, uint256 amount) internal override(ERC20) {
super._burn(account, amount);
address(zeroExVotes).functionCallWithGas(
abi.encodeCall(zeroExVotes.writeCheckpointTotalSupplyBurn, (balanceOf(account) + amount, amount)),
500_000,
32
);
}
/**
* @dev Get the address `account` is currently delegating to.
*/
function delegates(address account) public view returns (address) {
return _delegates[account].delegate;
}
/**
* @dev Get the last block number when `account`'s balance changed.
*/
function delegatorBalanceLastUpdated(address account) public view returns (uint96) {
return _delegates[account].balanceLastUpdated;
}
function delegateInfo(address account) public view returns (DelegateInfo memory) {
return _delegates[account];
}
/**
* @dev Delegate votes from the sender to `delegatee`.
*/
function delegate(address delegatee) public {
_delegate(_msgSender(), delegatee);
}
/**
* @dev Delegates votes from signer to `delegatee`
*/
function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) public {
require(block.timestamp <= expiry, "ERC20Votes: signature expired");
address signer = ECDSA.recover(
_hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry))),
v,
r,
s
);
require(nonce == _useNonce(signer), "ERC20Votes: invalid nonce");
_delegate(signer, delegatee);
}
/**
* @dev Change delegation for `delegator` to `delegatee`.
*
* Emits events {DelegateChanged} and {IZeroExVotes-DelegateVotesChanged}.
*/
function _delegate(address delegator, address delegatee) internal virtual {
DelegateInfo memory delegateInfo = delegateInfo(delegator);
uint256 delegatorBalance = balanceOf(delegator);
_delegates[delegator] = DelegateInfo(delegatee, SafeCast.toUint96(block.timestamp));
emit DelegateChanged(delegator, delegateInfo.delegate, delegatee);
zeroExVotes.moveVotingPower(
delegateInfo.delegate,
delegatee,
delegatorBalance,
0,
delegateInfo.balanceLastUpdated,
0,
delegatorBalance
);
}
}

View File

@@ -0,0 +1,145 @@
// 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 "./ZeroExTimelock.sol";
import "@openzeppelin/governance/Governor.sol";
import "@openzeppelin/governance/extensions/GovernorSettings.sol";
import "@openzeppelin/governance/extensions/GovernorCountingSimple.sol";
import "@openzeppelin/governance/extensions/GovernorVotes.sol";
import "@openzeppelin/governance/extensions/GovernorTimelockControl.sol";
contract ZeroExProtocolGovernor is
SecurityCouncil,
Governor,
GovernorSettings,
GovernorCountingSimple,
GovernorVotes,
GovernorTimelockControl
{
constructor(
IVotes _votes,
ZeroExTimelock _timelock,
address _securityCouncil
)
Governor("ZeroExProtocolGovernor")
GovernorSettings(14400 /* 2 days */, 50400 /* 7 days */, 1000000e18)
GovernorVotes(_votes)
GovernorTimelockControl(TimelockController(payable(_timelock)))
{
securityCouncil = _securityCouncil;
}
function quorum(uint256 blockNumber) public pure override returns (uint256) {
return 10000000e18;
}
// The following functions are overrides required by Solidity.
function votingDelay() public view override(IGovernor, GovernorSettings) returns (uint256) {
return super.votingDelay();
}
function votingPeriod() public view override(IGovernor, GovernorSettings) returns (uint256) {
return super.votingPeriod();
}
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) securityCouncilAssigned(calldatas) returns (uint256) {
return super.propose(targets, values, calldatas, description);
}
function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) {
return super.proposalThreshold();
}
function cancel(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) public override onlySecurityCouncil {
_cancel(targets, values, calldatas, descriptionHash);
}
// Like the GovernorTimelockControl.queue function but without the proposal checks,
// (as there's effectively no proposal).
// And also using a delay of 0 as opposed to the minimum delay of the timelock
function executeRollback(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) public onlySecurityCouncil {
// Execute the batch of rollbacks via the timelock controller
ZeroExTimelock timelockController = ZeroExTimelock(payable(timelock()));
timelockController.executeRollbackBatch(targets, values, calldatas, 0, descriptionHash);
}
function assignSecurityCouncil(address _securityCouncil) public override onlyGovernance {
super.assignSecurityCouncil(_securityCouncil);
}
function queue(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) public override securityCouncilAssigned(calldatas) returns (uint256) {
return super.queue(targets, values, calldatas, descriptionHash);
}
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);
}
}

View File

@@ -0,0 +1,70 @@
// 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 "@openzeppelin/governance/TimelockController.sol";
contract ZeroExTimelock is TimelockController {
// minDelay is how long you have to wait before executing
// proposers is the list of addresses that can propose
// executors is the list of addresses that can execute
constructor(
uint256 minDelay,
address[] memory proposers,
address[] memory executors,
address admin
) TimelockController(minDelay, proposers, executors, admin) {}
/**
* @dev Execute a batch of rollback transactions. Similar to TimelockController.executeBatch function but without
* the timelock checks.
* Emits one {CallExecuted} event per transaction in the batch.
*
* Requirements:
*
* - the caller must have the 'executor' role.
*/
function executeRollbackBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt
) public payable onlyRoleOrOpenRole(EXECUTOR_ROLE) {
require(targets.length > 0, "ZeroExTimelock: empty targets");
require(targets.length == values.length, "ZeroExTimelock: length mismatch");
require(targets.length == payloads.length, "ZeroExTimelock: length mismatch");
bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt);
for (uint256 i = 0; i < targets.length; ++i) {
address target = targets[i];
uint256 value = values[i];
bytes calldata payload = payloads[i];
// Check this is a rollback transaction
// function signature for rollback(bytes4,address)
// = bytes4(keccak256("rollback(bytes4,address)"))
// = 0x9db64a40
require(bytes4(payload) == bytes4(0x9db64a40), "ZeroExTimelock: not rollback");
_execute(target, value, payload);
emit CallExecuted(id, i, target, value, payload);
}
}
}

View File

@@ -0,0 +1,154 @@
// 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 "@openzeppelin/governance/Governor.sol";
import "@openzeppelin/governance/extensions/GovernorSettings.sol";
import "@openzeppelin/governance/extensions/GovernorCountingSimple.sol";
import "@openzeppelin/governance/extensions/GovernorVotes.sol";
import "@openzeppelin/governance/extensions/GovernorVotesQuorumFraction.sol";
import "@openzeppelin/governance/extensions/GovernorTimelockControl.sol";
import "./IZeroExVotes.sol";
import "./SecurityCouncil.sol";
contract ZeroExTreasuryGovernor is
SecurityCouncil,
Governor,
GovernorSettings,
GovernorCountingSimple,
GovernorVotes,
GovernorVotesQuorumFraction,
GovernorTimelockControl
{
constructor(
IVotes votes,
TimelockController _timelock,
address _securityCouncil
)
Governor("ZeroExTreasuryGovernor")
GovernorSettings(14400 /* 2 days */, 50400 /* 7 days */, 250000e18)
GovernorVotes(votes)
GovernorVotesQuorumFraction(10)
GovernorTimelockControl(_timelock)
{
securityCouncil = _securityCouncil;
}
/**
* @dev Returns the "quadratic" quorum for a block number, in terms of number of votes:
* `quadratic total supply * numerator / denominator`
*/
function quorum(
uint256 blockNumber
) public view override(IGovernor, GovernorVotesQuorumFraction) returns (uint256) {
IZeroExVotes votes = IZeroExVotes(address(token));
return (votes.getPastQuadraticTotalSupply(blockNumber) * quorumNumerator(blockNumber)) / quorumDenominator();
}
// The following functions are overrides required by Solidity.
function votingDelay() public view override(IGovernor, GovernorSettings) returns (uint256) {
return super.votingDelay();
}
function votingPeriod() public view override(IGovernor, GovernorSettings) returns (uint256) {
return super.votingPeriod();
}
function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) {
return super.proposalThreshold();
}
/**
* Overwritten GovernorVotes implementation
* Read the quadratic voting weight from the token's built in snapshot mechanism (see {Governor-_getVotes}).
*/
function _getVotes(
address account,
uint256 blockNumber,
bytes memory /*params*/
) internal view virtual override(Governor, GovernorVotes) returns (uint256) {
return IZeroExVotes(address(token)).getPastQuadraticVotes(account, blockNumber);
}
function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) {
return super.state(proposalId);
}
function propose(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
string memory description
) public override(Governor, IGovernor) securityCouncilAssigned(calldatas) returns (uint256) {
return super.propose(targets, values, calldatas, description);
}
function cancel(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) public override onlySecurityCouncil {
_cancel(targets, values, calldatas, descriptionHash);
}
function assignSecurityCouncil(address _securityCouncil) public override onlyGovernance {
super.assignSecurityCouncil(_securityCouncil);
}
function queue(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) public override securityCouncilAssigned(calldatas) returns (uint256) {
return super.queue(targets, values, calldatas, descriptionHash);
}
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);
}
}

View File

@@ -0,0 +1,336 @@
// 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 "@openzeppelin/utils/math/SafeCast.sol";
import "@openzeppelin/utils/math/Math.sol";
import "@openzeppelin/token/ERC20/ERC20.sol";
import "@openzeppelin/governance/utils/IVotes.sol";
import "@openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin-contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin-contracts-upgradeable/proxy/utils/Initializable.sol";
import "./IZeroExVotes.sol";
contract ZeroExVotes is IZeroExVotes, Initializable, OwnableUpgradeable, UUPSUpgradeable {
address public immutable token;
uint256 public immutable quadraticThreshold;
mapping(address => Checkpoint[]) internal _checkpoints;
Checkpoint[] private _totalSupplyCheckpoints;
constructor(address _token, uint256 _quadraticThreshold) {
require(_token != address(0), "ZeroExVotes: token cannot be 0");
token = _token;
quadraticThreshold = _quadraticThreshold;
_disableInitializers();
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
modifier onlyToken() {
_checkSenderIsToken();
_;
}
function initialize() public virtual onlyProxy initializer {
__Ownable_init();
__UUPSUpgradeable_init();
}
/**
* @inheritdoc IZeroExVotes
*/
function checkpoints(address account, uint32 pos) public view returns (Checkpoint memory) {
return _checkpoints[account][pos];
}
/**
* @inheritdoc IZeroExVotes
*/
function numCheckpoints(address account) public view returns (uint32) {
return SafeCast.toUint32(_checkpoints[account].length);
}
/**
* @inheritdoc IZeroExVotes
*/
function getVotes(address account) public view returns (uint256) {
unchecked {
uint256 pos = _checkpoints[account].length;
return pos == 0 ? 0 : _unsafeAccess(_checkpoints[account], pos - 1).votes;
}
}
/**
* @inheritdoc IZeroExVotes
*/
function getQuadraticVotes(address account) public view returns (uint256) {
unchecked {
uint256 pos = _checkpoints[account].length;
return pos == 0 ? 0 : _unsafeAccess(_checkpoints[account], pos - 1).quadraticVotes;
}
}
/**
* @inheritdoc IZeroExVotes
*/
function getPastVotes(address account, uint256 blockNumber) public view returns (uint256) {
require(blockNumber < block.number, "ZeroExVotes: block not yet mined");
Checkpoint memory checkpoint = _checkpointsLookup(_checkpoints[account], blockNumber);
return checkpoint.votes;
}
/**
* @inheritdoc IZeroExVotes
*/
function getPastQuadraticVotes(address account, uint256 blockNumber) public view returns (uint256) {
require(blockNumber < block.number, "ZeroExVotes: block not yet mined");
Checkpoint memory checkpoint = _checkpointsLookup(_checkpoints[account], blockNumber);
return checkpoint.quadraticVotes;
}
/**
* @inheritdoc IZeroExVotes
*/
function getPastTotalSupply(uint256 blockNumber) public view returns (uint256) {
require(blockNumber < block.number, "ZeroExVotes: block not yet mined");
// Note that due to the disabled updates of `_totalSupplyCheckpoints` in `writeCheckpointTotalSupply` function
// this always returns 0.
Checkpoint memory checkpoint = _checkpointsLookup(_totalSupplyCheckpoints, blockNumber);
return checkpoint.votes;
}
/**
* @inheritdoc IZeroExVotes
*/
function getPastQuadraticTotalSupply(uint256 blockNumber) public view returns (uint256) {
require(blockNumber < block.number, "ZeroExVotes: block not yet mined");
// Note that due to the disabled updates of `_totalSupplyCheckpoints` in `writeCheckpointTotalSupply` function
// this always returns 0.
Checkpoint memory checkpoint = _checkpointsLookup(_totalSupplyCheckpoints, blockNumber);
return checkpoint.quadraticVotes;
}
/**
* @inheritdoc IZeroExVotes
*/
function moveVotingPower(
address src,
address dst,
uint256 srcBalance,
uint256 dstBalance,
uint96 srcBalanceLastUpdated,
uint96 dstBalanceLastUpdated,
uint256 amount
) public virtual onlyToken returns (bool) {
if (src != dst) {
if (src != address(0)) {
(
uint256 oldWeight,
uint256 newWeight,
uint256 oldQuadraticWeight,
uint256 newQuadraticWeight
) = _writeCheckpoint(_checkpoints[src], _subtract, srcBalance, srcBalanceLastUpdated, amount);
emit DelegateVotesChanged(src, oldWeight, newWeight);
emit DelegateQuadraticVotesChanged(src, oldQuadraticWeight, newQuadraticWeight);
}
if (dst != address(0)) {
(
uint256 oldWeight,
uint256 newWeight,
uint256 oldQuadraticWeight,
uint256 newQuadraticWeight
) = _writeCheckpoint(_checkpoints[dst], _add, dstBalance, dstBalanceLastUpdated, amount);
emit DelegateVotesChanged(dst, oldWeight, newWeight);
emit DelegateQuadraticVotesChanged(dst, oldQuadraticWeight, newQuadraticWeight);
}
}
return true;
}
/**
* @inheritdoc IZeroExVotes
*/
function writeCheckpointTotalSupplyMint(
uint256 accountBalance,
uint256 amount
) public virtual onlyToken returns (bool) {
(, uint256 newWeight, , uint256 newQuadraticWeight) = _writeCheckpoint(
_totalSupplyCheckpoints,
_add,
accountBalance,
0,
amount
);
emit TotalSupplyChanged(newWeight, newQuadraticWeight);
return true;
}
/**
* @inheritdoc IZeroExVotes
*/
function writeCheckpointTotalSupplyBurn(
uint256 accountBalance,
uint256 amount
) public virtual onlyToken returns (bool) {
(, uint256 newWeight, , uint256 newQuadraticWeight) = _writeCheckpoint(
_totalSupplyCheckpoints,
_subtract,
accountBalance,
0,
amount
);
emit TotalSupplyChanged(newWeight, newQuadraticWeight);
return true;
}
/**
* @dev Lookup a value in a list of (sorted) checkpoints.
* Implementation as in openzeppelin/token/ERC20/extensions/ERC20Votes.sol except here we return the entire
* checkpoint rather than part of it
*/
function _checkpointsLookup(
Checkpoint[] storage ckpts,
uint256 blockNumber
) internal view returns (Checkpoint memory checkpoint) {
// We run a binary search to look for the earliest checkpoint taken after `blockNumber`.
//
// Initially we check if the block is recent to narrow the search range.
// During the loop, the index of the wanted checkpoint remains in the range [low-1, high).
// With each iteration, either `low` or `high` is moved towards the middle of the range to maintain the
// invariant.
// - If the middle checkpoint is after `blockNumber`, we look in [low, mid)
// - If the middle checkpoint is before or equal to `blockNumber`, we look in [mid+1, high)
// Once we reach a single value (when low == high), we've found the right checkpoint at the index high-1, if not
// out of bounds (in which case we're looking too far in the past and the result is 0).
// Note that if the latest checkpoint available is exactly for `blockNumber`, we end up with an index that is
// past the end of the array, so we technically don't find a checkpoint after `blockNumber`, but it works out
// the same.
uint256 length = ckpts.length;
uint256 low = 0;
uint256 high = length;
if (length > 5) {
uint256 mid = length - Math.sqrt(length);
if (_unsafeAccess(ckpts, mid).fromBlock > blockNumber) {
high = mid;
} else {
low = mid + 1;
}
}
while (low < high) {
uint256 mid = Math.average(low, high);
if (_unsafeAccess(ckpts, mid).fromBlock > blockNumber) {
high = mid;
} else {
low = mid + 1;
}
}
// Leaving here for posterity this is the original OZ implementation which we've replaced
// return high == 0 ? 0 : _unsafeAccess(ckpts, high - 1).votes;
if (high != 0) checkpoint = _unsafeAccess(ckpts, high - 1);
}
function _writeCheckpoint(
Checkpoint[] storage ckpts,
function(uint256, uint256) view returns (uint256) op,
uint256 userBalance,
uint96 balanceLastUpdated,
uint256 delta
)
internal
virtual
returns (uint256 oldWeight, uint256 newWeight, uint256 oldQuadraticWeight, uint256 newQuadraticWeight)
{
uint256 pos = ckpts.length;
Checkpoint memory oldCkpt = pos == 0 ? Checkpoint(0, 0, 0) : _unsafeAccess(ckpts, pos - 1);
oldWeight = oldCkpt.votes;
newWeight = op(oldWeight, delta);
oldQuadraticWeight = oldCkpt.quadraticVotes;
// Remove the entire sqrt userBalance from quadratic voting power.
// Note that `userBalance` is value _after_ transfer.
if (pos > 0) {
uint256 oldQuadraticVotingPower = userBalance <= quadraticThreshold
? userBalance
: quadraticThreshold + Math.sqrt((userBalance - quadraticThreshold) * 1e18);
oldCkpt.quadraticVotes -= SafeCast.toUint96(oldQuadraticVotingPower);
}
// if wallet > threshold, calculate quadratic power over the treshold only, below threshold is linear
uint256 newBalance = op(userBalance, delta);
uint256 newQuadraticBalance = newBalance <= quadraticThreshold
? newBalance
: quadraticThreshold + Math.sqrt((newBalance - quadraticThreshold) * 1e18);
newQuadraticWeight = oldCkpt.quadraticVotes + newQuadraticBalance;
if (pos > 0 && oldCkpt.fromBlock == block.number) {
Checkpoint storage chpt = _unsafeAccess(ckpts, pos - 1);
chpt.votes = SafeCast.toUint96(newWeight);
chpt.quadraticVotes = SafeCast.toUint96(newQuadraticWeight);
} else {
ckpts.push(
Checkpoint({
fromBlock: SafeCast.toUint32(block.number),
votes: SafeCast.toUint96(newWeight),
quadraticVotes: SafeCast.toUint96(newQuadraticWeight)
})
);
}
}
function _add(uint256 a, uint256 b) private pure returns (uint256) {
return a + b;
}
function _subtract(uint256 a, uint256 b) private pure returns (uint256) {
return a - b;
}
/**
* @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds.
* Implementation from openzeppelin/token/ERC20/extensions/ERC20Votes.sol
* https://github.com/ethereum/solidity/issues/9117
*/
function _unsafeAccess(Checkpoint[] storage ckpts, uint256 pos) internal pure returns (Checkpoint storage result) {
assembly ("memory-safe") {
mstore(0, ckpts.slot)
result.slot := add(keccak256(0, 0x20), pos)
}
}
function _checkSenderIsToken() private {
require(msg.sender == token, "ZeroExVotes: only token allowed");
}
}

View File

@@ -0,0 +1,167 @@
// 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 "forge-std/Test.sol";
import "forge-std/console.sol";
import "@openzeppelin/token/ERC20/ERC20.sol";
import "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol";
import "./mocks/ZRXMock.sol";
import "../src/ZRXWrappedToken.sol";
import "../src/ZeroExVotes.sol";
import "../src/ZeroExTimelock.sol";
import "../src/ZeroExProtocolGovernor.sol";
import "../src/ZeroExTreasuryGovernor.sol";
contract BaseTest is Test {
address payable internal account1 = payable(vm.addr(1));
address payable internal account2 = payable(vm.addr(2));
address payable internal account3 = payable(vm.addr(3));
address payable internal account4 = payable(vm.addr(4));
address payable internal securityCouncil = payable(vm.addr(5));
uint256 internal quadraticThreshold = 1000000e18;
bytes32 internal constant DELEGATION_TYPEHASH =
keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
constructor() {
vm.deal(account1, 1e20);
vm.deal(account2, 1e20);
vm.deal(account3, 1e20);
vm.deal(account4, 1e20);
vm.deal(securityCouncil, 1e20);
}
function setupGovernance(
IERC20 zrxToken
) internal returns (ZRXWrappedToken, ZeroExVotes, ZeroExTimelock, ZeroExTimelock, address, address) {
(ZRXWrappedToken token, ZeroExVotes votes) = setupZRXWrappedToken(zrxToken);
vm.startPrank(account1);
address[] memory proposers = new address[](0);
address[] memory executors = new address[](0);
ZeroExTimelock protocolTimelock = new ZeroExTimelock(3 days, proposers, executors, account1);
ZeroExProtocolGovernor protocolGovernor = new ZeroExProtocolGovernor(
IVotes(address(votes)),
protocolTimelock,
securityCouncil
);
protocolTimelock.grantRole(protocolTimelock.PROPOSER_ROLE(), address(protocolGovernor));
protocolTimelock.grantRole(protocolTimelock.EXECUTOR_ROLE(), address(protocolGovernor));
protocolTimelock.grantRole(protocolTimelock.CANCELLER_ROLE(), address(protocolGovernor));
ZeroExTimelock treasuryTimelock = new ZeroExTimelock(2 days, proposers, executors, account1);
ZeroExTreasuryGovernor treasuryGovernor = new ZeroExTreasuryGovernor(
IVotes(address(votes)),
treasuryTimelock,
securityCouncil
);
treasuryTimelock.grantRole(treasuryTimelock.PROPOSER_ROLE(), address(treasuryGovernor));
treasuryTimelock.grantRole(treasuryTimelock.EXECUTOR_ROLE(), address(treasuryGovernor));
treasuryTimelock.grantRole(treasuryTimelock.CANCELLER_ROLE(), address(treasuryGovernor));
vm.stopPrank();
return (token, votes, protocolTimelock, treasuryTimelock, address(protocolGovernor), address(treasuryGovernor));
}
function setupZRXWrappedToken(IERC20 zrxToken) internal returns (ZRXWrappedToken, ZeroExVotes) {
vm.startPrank(account1);
address wTokenPrediction = predictAddress(account1, vm.getNonce(account1) + 2);
ZeroExVotes votesImpl = new ZeroExVotes(wTokenPrediction, quadraticThreshold);
ERC1967Proxy votesProxy = new ERC1967Proxy(address(votesImpl), abi.encodeCall(votesImpl.initialize, ()));
ZRXWrappedToken wToken = new ZRXWrappedToken(zrxToken, ZeroExVotes(address(votesProxy)));
vm.stopPrank();
assert(address(wToken) == wTokenPrediction);
return (wToken, ZeroExVotes(address(votesProxy)));
}
function mockZRXToken() internal returns (IERC20 zrxToken) {
vm.startPrank(account1);
bytes memory _bytecode = vm.getCode("./ZRXToken.json");
assembly {
zrxToken := create(0, add(_bytecode, 0x20), mload(_bytecode))
}
vm.stopPrank();
}
// Sourced from https://github.com/grappafinance/core/blob/master/src/test/utils/Utilities.sol
function predictAddress(address _origin, uint256 _nonce) public pure returns (address) {
if (_nonce == 0x00) {
return
address(
uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, bytes1(0x80)))))
);
}
if (_nonce <= 0x7f) {
return
address(
uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, uint8(_nonce)))))
);
}
if (_nonce <= 0xff) {
return
address(
uint160(
uint256(
keccak256(
abi.encodePacked(bytes1(0xd7), bytes1(0x94), _origin, bytes1(0x81), uint8(_nonce))
)
)
)
);
}
if (_nonce <= 0xffff) {
return
address(
uint160(
uint256(
keccak256(
abi.encodePacked(bytes1(0xd8), bytes1(0x94), _origin, bytes1(0x82), uint16(_nonce))
)
)
)
);
}
if (_nonce <= 0xffffff) {
return
address(
uint160(
uint256(
keccak256(
abi.encodePacked(bytes1(0xd9), bytes1(0x94), _origin, bytes1(0x83), uint24(_nonce))
)
)
)
);
}
return
address(
uint160(
uint256(
keccak256(abi.encodePacked(bytes1(0xda), bytes1(0x94), _origin, bytes1(0x84), uint32(_nonce)))
)
)
);
}
}

View File

@@ -0,0 +1,246 @@
// 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 "@openzeppelin/token/ERC20/IERC20.sol";
import "../mocks/IZeroExMock.sol";
import "../mocks/IZrxTreasuryMock.sol";
import "../mocks/IStakingMock.sol";
import "../BaseTest.t.sol";
import "../../src/ZRXWrappedToken.sol";
import "../../src/ZeroExVotes.sol";
import "../../src/ZeroExTimelock.sol";
import "../../src/ZeroExProtocolGovernor.sol";
import "../../src/ZeroExTreasuryGovernor.sol";
contract GovernanceE2ETest is BaseTest {
uint256 internal mainnetFork;
string internal MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL");
address internal constant ZRX_TOKEN = 0xE41d2489571d322189246DaFA5ebDe1F4699F498;
address internal constant MATIC_TOKEN = 0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0;
address internal constant WCELO_TOKEN = 0xE452E6Ea2dDeB012e20dB73bf5d3863A3Ac8d77a;
address internal constant WYV_TOKEN = 0x056017c55aE7AE32d12AeF7C679dF83A85ca75Ff;
address internal constant EXCHANGE_PROXY = 0xDef1C0ded9bec7F1a1670819833240f027b25EfF;
address internal constant EXCHANGE_GOVERNOR = 0x618F9C67CE7Bf1a50afa1E7e0238422601b0ff6e;
address internal constant TREASURY = 0x0bB1810061C2f5b2088054eE184E6C79e1591101;
address internal constant STAKING = 0xa26e80e7Dea86279c6d778D702Cc413E6CFfA777;
address internal staker = 0x5265Bde27F57E738bE6c1F6AB3544e82cdc92a8f;
bytes32 internal stakerPool = 0x0000000000000000000000000000000000000000000000000000000000000032;
bytes32[] internal staker_operated_poolIds = [stakerPool];
// voting power 1500000e18
address internal voter1 = 0x292c6DAE7417B3D31d8B6e1d2EeA0258d14C4C4b;
bytes32 internal voter1Pool = 0x0000000000000000000000000000000000000000000000000000000000000030;
bytes32[] internal voter1_operated_poolIds = [voter1Pool];
// voting power 1500000.5e18
address internal voter2 = 0x4990cE223209FCEc4ec4c1ff6E0E81eebD8Cca08;
bytes32 internal voter2Pool = 0x0000000000000000000000000000000000000000000000000000000000000031;
bytes32[] internal voter2_operated_poolIds = [voter2Pool];
// voting power 1500000e18
address internal voter3 = 0x5265Bde27F57E738bE6c1F6AB3544e82cdc92a8f;
bytes32 internal voter3Pool = 0x0000000000000000000000000000000000000000000000000000000000000032;
bytes32[] internal voter3_operated_poolIds = [voter3Pool];
// voting power 1500000e18
address internal voter4 = 0xcA9F5049c1Ea8FC78574f94B7Cf5bE5fEE354C31;
bytes32 internal voter4Pool = 0x0000000000000000000000000000000000000000000000000000000000000034;
bytes32[] internal voter4_operated_poolIds = [voter4Pool];
// voting power 1500000e18
address internal voter5 = 0xDBB5664a9DBCB98F6365804880e5b277B3155422;
bytes32 internal voter5Pool = 0x0000000000000000000000000000000000000000000000000000000000000035;
bytes32[] internal voter5_operated_poolIds = [voter5Pool];
// voting power 2291490.952353335e18
address internal voter6 = 0x9a4Eb1101C0c053505Bd71d2fFa27Ed902DEaD85;
bytes32 internal voter6Pool = 0x0000000000000000000000000000000000000000000000000000000000000029;
bytes32[] internal voter6_operated_poolIds = [voter6Pool];
// voting power 4575984.325e18
address internal voter7 = 0x9564177EC8052C92752a488a71769F710aA0A41D;
bytes32 internal voter7Pool = 0x0000000000000000000000000000000000000000000000000000000000000025;
bytes32[] internal voter7_operated_poolIds = [voter7Pool];
IERC20 internal token;
IERC20 internal maticToken;
IERC20 internal wceloToken;
IERC20 internal wyvToken;
IZeroExMock internal exchange;
IZrxTreasuryMock internal treasury;
IStakingMock internal staking;
ZRXWrappedToken internal wToken;
ZeroExVotes internal votes;
ZeroExTimelock internal protocolTimelock;
ZeroExTimelock internal treasuryTimelock;
ZeroExProtocolGovernor internal protocolGovernor;
ZeroExTreasuryGovernor internal treasuryGovernor;
function setUp() public {
mainnetFork = vm.createFork(MAINNET_RPC_URL);
vm.selectFork(mainnetFork);
token = IERC20(ZRX_TOKEN);
maticToken = IERC20(MATIC_TOKEN);
wceloToken = IERC20(WCELO_TOKEN);
wyvToken = IERC20(WYV_TOKEN);
exchange = IZeroExMock(payable(EXCHANGE_PROXY));
treasury = IZrxTreasuryMock(TREASURY);
staking = IStakingMock(STAKING);
address protocolGovernorAddress;
address treasuryGovernorAddress;
(
wToken,
votes,
protocolTimelock,
treasuryTimelock,
protocolGovernorAddress,
treasuryGovernorAddress
) = setupGovernance(token);
protocolGovernor = ZeroExProtocolGovernor(payable(protocolGovernorAddress));
treasuryGovernor = ZeroExTreasuryGovernor(payable(treasuryGovernorAddress));
}
function testProtocolGovernanceMigration() public {
// initially the zrx exchange is owned by the legacy exchange governor
assertEq(exchange.owner(), EXCHANGE_GOVERNOR);
// transfer ownership to new protocol governor
vm.prank(EXCHANGE_GOVERNOR);
exchange.transferOwnership(address(protocolGovernor));
assertEq(exchange.owner(), address(protocolGovernor));
}
function testTreasuryGovernanceMigration() public {
// Create a proposal to migrate to new governor
uint256 currentEpoch = staking.currentEpoch();
uint256 executionEpoch = currentEpoch + 2;
vm.startPrank(staker);
IZrxTreasuryMock.ProposedAction[] memory actions = new IZrxTreasuryMock.ProposedAction[](4);
// Transfer MATIC
uint256 maticBalance = maticToken.balanceOf(address(treasury));
actions[0] = IZrxTreasuryMock.ProposedAction({
target: MATIC_TOKEN,
data: abi.encodeCall(maticToken.transfer, (address(treasuryGovernor), maticBalance)),
value: 0
});
// Transfer ZRX
uint256 zrxBalance = token.balanceOf(address(treasury));
actions[1] = IZrxTreasuryMock.ProposedAction({
target: ZRX_TOKEN,
data: abi.encodeCall(token.transfer, (address(treasuryGovernor), zrxBalance)),
value: 0
});
// Transfer wCELO
uint256 wceloBalance = wceloToken.balanceOf(address(treasury));
actions[2] = IZrxTreasuryMock.ProposedAction({
target: WCELO_TOKEN,
data: abi.encodeCall(wceloToken.transfer, (address(treasuryGovernor), wceloBalance)),
value: 0
});
// Transfer WYV
uint256 wyvBalance = wyvToken.balanceOf(address(treasury));
actions[3] = IZrxTreasuryMock.ProposedAction({
target: WYV_TOKEN,
data: abi.encodeCall(wyvToken.transfer, (address(treasuryGovernor), wyvBalance)),
value: 0
});
uint256 proposalId = treasury.propose(
actions,
executionEpoch,
"Z-5 Migrate to new treasury governor",
staker_operated_poolIds
);
// Once a proposal is created, it becomes open for voting at the epoch after next (currentEpoch + 2)
// and is open for the voting period (currently set to 3 days).
uint256 epochDurationInSeconds = staking.epochDurationInSeconds(); // Currently set to 604800 seconds = 7 days
uint256 currentEpochEndTime = staking.currentEpochStartTimeInSeconds() + epochDurationInSeconds;
vm.warp(currentEpochEndTime + 1);
staking.endEpoch();
vm.warp(block.timestamp + epochDurationInSeconds + 1);
staking.endEpoch();
vm.stopPrank();
// quorum is 10,000,000e18 so reach that via the following votes
vm.prank(voter1);
treasury.castVote(proposalId, true, voter1_operated_poolIds);
vm.stopPrank();
vm.prank(voter2);
treasury.castVote(proposalId, true, voter2_operated_poolIds);
vm.stopPrank();
vm.prank(voter3);
treasury.castVote(proposalId, true, voter3_operated_poolIds);
vm.stopPrank();
vm.prank(voter4);
treasury.castVote(proposalId, true, voter4_operated_poolIds);
vm.stopPrank();
vm.prank(voter5);
treasury.castVote(proposalId, true, voter5_operated_poolIds);
vm.stopPrank();
vm.prank(voter6);
treasury.castVote(proposalId, true, voter6_operated_poolIds);
vm.stopPrank();
vm.prank(voter7);
treasury.castVote(proposalId, true, voter7_operated_poolIds);
vm.stopPrank();
vm.warp(block.timestamp + 3 days + 1);
// Execute proposal
treasury.execute(proposalId, actions);
// Assert value of treasury has correctly transferred
uint256 maticBalanceNewTreasury = maticToken.balanceOf(address(treasuryGovernor));
assertEq(maticBalanceNewTreasury, maticBalance);
uint256 zrxBalanceNewTreasury = token.balanceOf(address(treasuryGovernor));
assertEq(zrxBalanceNewTreasury, zrxBalance);
uint256 wceloBalanceNewTreasury = wceloToken.balanceOf(address(treasuryGovernor));
assertEq(wceloBalanceNewTreasury, wceloBalance);
uint256 wyvBalanceNewTreasury = wyvToken.balanceOf(address(treasuryGovernor));
assertEq(wyvBalanceNewTreasury, wyvBalance);
}
}

View File

@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
library CubeRoot {
/// @dev Returns the cube root of `x`.
/// Credit to pleasemarkdarkly under MIT license
// Originaly from https://github.com/pleasemarkdarkly/fei-protocol-core-hh/blob/main/contracts/utils/Roots.sol
function cbrt(uint y) internal pure returns (uint z) {
// Newton's method https://en.wikipedia.org/wiki/Cube_root#Numerical_methods
if (y > 7) {
z = y;
uint x = y / 3 + 1;
while (x < z) {
z = x;
x = (y / (x * x) + (2 * x)) / 3;
}
} else if (y != 0) {
z = 1;
}
}
}

View File

@@ -0,0 +1,36 @@
// 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 "@0x/contracts-utils/contracts/src/v08/interfaces/IOwnableV08.sol";
/// @dev Owner management and migration features.
interface IOwnableFeature is IOwnableV08 {
/// @dev Emitted when `migrate()` is called.
/// @param caller The caller of `migrate()`.
/// @param migrator The migration contract.
/// @param newOwner The address of the new owner.
event Migrated(address caller, address migrator, address newOwner);
/// @dev Execute a migration function in the context of the ZeroEx contract.
/// The result of the function being called should be the magic bytes
/// 0x2c64c5ef (`keccack('MIGRATE_SUCCESS')`). Only callable by the owner.
/// The owner will be temporarily set to `address(this)` inside the call.
/// Before returning, the owner will be set to `newOwner`.
/// @param target The migrator contract address.
/// @param newOwner The address of the new owner.
/// @param data The call data.
function migrate(address target, bytes calldata data, address newOwner) external;
}

View File

@@ -0,0 +1,48 @@
// 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;
pragma experimental ABIEncoderV2;
/// @dev Basic registry management features.
interface ISimpleFunctionRegistryFeature {
/// @dev A function implementation was updated via `extend()` or `rollback()`.
/// @param selector The function selector.
/// @param oldImpl The implementation contract address being replaced.
/// @param newImpl The replacement implementation contract address.
event ProxyFunctionUpdated(bytes4 indexed selector, address oldImpl, address newImpl);
/// @dev Roll back to a prior implementation of a function.
/// @param selector The function selector.
/// @param targetImpl The address of an older implementation of the function.
function rollback(bytes4 selector, address targetImpl) external;
/// @dev Register or replace a function.
/// @param selector The function selector.
/// @param impl The implementation contract for the function.
function extend(bytes4 selector, address impl) external;
/// @dev Retrieve the length of the rollback history for a function.
/// @param selector The function selector.
/// @return rollbackLength The number of items in the rollback history for
/// the function.
function getRollbackLength(bytes4 selector) external view returns (uint256 rollbackLength);
/// @dev Retrieve an entry in the rollback history for a function.
/// @param selector The function selector.
/// @param idx The index in the rollback history.
/// @return impl An implementation address for the function at
/// index `idx`.
function getRollbackEntryAtIndex(bytes4 selector, uint256 idx) external view returns (address impl);
}

View File

@@ -0,0 +1,106 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 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;
interface IStakingMock {
/// @dev Statuses that stake can exist in.
/// Any stake can be (re)delegated effective at the next epoch
/// Undelegated stake can be withdrawn if it is available in both the current and next epoch
enum StakeStatus {
UNDELEGATED,
DELEGATED
}
/// @dev Encapsulates a balance for the current and next epochs.
/// Note that these balances may be stale if the current epoch
/// is greater than `currentEpoch`.
/// @param currentEpoch The current epoch
/// @param currentEpochBalance Balance in the current epoch.
/// @param nextEpochBalance Balance in `currentEpoch+1`.
struct StoredBalance {
uint64 currentEpoch;
uint96 currentEpochBalance;
uint96 nextEpochBalance;
}
/// @dev Holds the metadata for a staking pool.
/// @param operator Operator of the pool.
/// @param operatorShare Fraction of the total balance owned by the operator, in ppm.
struct Pool {
address operator;
uint32 operatorShare;
}
/// @dev Create a new staking pool. The sender will be the operator of this pool.
/// Note that an operator must be payable.
/// @param operatorShare Portion of rewards owned by the operator, in ppm.
/// @param addOperatorAsMaker Adds operator to the created pool as a maker for convenience iff true.
/// @return poolId The unique pool id generated for this pool.
function createStakingPool(uint32 operatorShare, bool addOperatorAsMaker) external returns (bytes32 poolId);
/// @dev Returns the current staking epoch number.
/// @return epoch The current epoch.
function currentEpoch() external view returns (uint256 epoch);
/// @dev Returns the time (in seconds) at which the current staking epoch started.
/// @return startTime The start time of the current epoch, in seconds.
function currentEpochStartTimeInSeconds() external view returns (uint256 startTime);
/// @dev Returns the duration of an epoch in seconds. This value can be updated.
/// @return duration The duration of an epoch, in seconds.
function epochDurationInSeconds() external view returns (uint256 duration);
/// @dev Returns a staking pool
/// @param poolId Unique id of pool.
function getStakingPool(bytes32 poolId) external view returns (Pool memory);
/// @dev Gets global stake for a given status.
/// @param stakeStatus UNDELEGATED or DELEGATED
/// @return balance Global stake for given status.
function getGlobalStakeByStatus(StakeStatus stakeStatus) external view returns (StoredBalance memory balance);
/// @dev Gets an owner's stake balances by status.
/// @param staker Owner of stake.
/// @param stakeStatus UNDELEGATED or DELEGATED
/// @return balance Owner's stake balances for given status.
function getOwnerStakeByStatus(
address staker,
StakeStatus stakeStatus
) external view returns (StoredBalance memory balance);
/// @dev Returns the total stake delegated to a specific staking pool,
/// across all members.
/// @param poolId Unique Id of pool.
/// @return balance Total stake delegated to pool.
function getTotalStakeDelegatedToPool(bytes32 poolId) external view returns (StoredBalance memory balance);
/// @dev Returns the stake delegated to a specific staking pool, by a given staker.
/// @param staker of stake.
/// @param poolId Unique Id of pool.
/// @return balance Stake delegated to pool by staker.
function getStakeDelegatedToPoolByOwner(
address staker,
bytes32 poolId
) external view returns (StoredBalance memory balance);
function endEpoch() external returns (uint256);
function finalizePool(bytes32 poolId) external;
}

View File

@@ -0,0 +1,24 @@
// 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 "./IOwnableFeature.sol";
import "./ISimpleFunctionRegistryFeature.sol";
/// @dev Minimal viable Exchange Proxy interface for governance use.
interface IZeroExMock is IOwnableFeature, ISimpleFunctionRegistryFeature {
/// @dev Fallback for just receiving ether.
receive() external payable;
}

View File

@@ -0,0 +1,159 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2021 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 "./IStakingMock.sol";
/// @dev Minimal viable Treasury interface for governance use.
interface IZrxTreasuryMock {
struct TreasuryParameters {
uint256 votingPeriod;
uint256 proposalThreshold;
uint256 quorumThreshold;
bytes32 defaultPoolId;
}
struct ProposedAction {
address target;
bytes data;
uint256 value;
}
struct Proposal {
bytes32 actionsHash;
uint256 executionEpoch;
uint256 voteEpoch;
uint256 votesFor;
uint256 votesAgainst;
bool executed;
}
event ProposalCreated(
address proposer,
bytes32[] operatedPoolIds,
uint256 proposalId,
ProposedAction[] actions,
uint256 executionEpoch,
string description
);
event VoteCast(address voter, bytes32[] operatedPoolIds, uint256 proposalId, bool support, uint256 votingPower);
event ProposalExecuted(uint256 proposalId);
function stakingProxy() external view returns (IStakingMock);
function defaultPoolId() external view returns (bytes32);
function votingPeriod() external view returns (uint256);
function proposalThreshold() external view returns (uint256);
function quorumThreshold() external view returns (uint256);
/// @dev Updates the proposal and quorum thresholds to the given
/// values. Note that this function is only callable by the
/// treasury contract itself, so the threshold can only be
/// updated via a successful treasury proposal.
/// @param newProposalThreshold The new value for the proposal threshold.
/// @param newQuorumThreshold The new value for the quorum threshold.
function updateThresholds(uint256 newProposalThreshold, uint256 newQuorumThreshold) external;
/// @dev Creates a proposal to send ZRX from this treasury on the
/// the given actions. Must have at least `proposalThreshold`
/// of voting power to call this function. See `getVotingPower`
/// for how voting power is computed. If a proposal is successfully
/// created, voting starts at the epoch after next (currentEpoch + 2).
/// If the vote passes, the proposal is executable during the
/// `executionEpoch`. See `hasProposalPassed` for the passing criteria.
/// @param actions The proposed ZRX actions. An action specifies a
/// contract call.
/// @param executionEpoch The epoch during which the proposal is to
/// be executed if it passes. Must be at least two epochs
/// from the current epoch.
/// @param description A text description for the proposal.
/// @param operatedPoolIds The pools operated by `msg.sender`. The
/// ZRX currently delegated to those pools will be accounted
/// for in the voting power.
/// @return proposalId The ID of the newly created proposal.
function propose(
ProposedAction[] calldata actions,
uint256 executionEpoch,
string calldata description,
bytes32[] calldata operatedPoolIds
) external returns (uint256 proposalId);
/// @dev Casts a vote for the given proposal. Only callable
/// during the voting period for that proposal.
/// One address can only vote once.
/// See `getVotingPower` for how voting power is computed.
/// @param proposalId The ID of the proposal to vote on.
/// @param support Whether to support the proposal or not.
/// @param operatedPoolIds The pools operated by `msg.sender`. The
/// ZRX currently delegated to those pools will be accounted
/// for in the voting power.
function castVote(uint256 proposalId, bool support, bytes32[] calldata operatedPoolIds) external;
/// @dev Casts a vote for the given proposal, by signature.
/// Only callable during the voting period for that proposal.
/// One address/voter can only vote once.
/// See `getVotingPower` for how voting power is computed.
/// @param proposalId The ID of the proposal to vote on.
/// @param support Whether to support the proposal or not.
/// @param operatedPoolIds The pools operated by the signer. The
/// ZRX currently delegated to those pools will be accounted
/// for in the voting power.
/// @param v the v field of the signature
/// @param r the r field of the signature
/// @param s the s field of the signature
function castVoteBySignature(
uint256 proposalId,
bool support,
bytes32[] memory operatedPoolIds,
uint8 v,
bytes32 r,
bytes32 s
) external;
/// @dev Executes a proposal that has passed and is
/// currently executable.
/// @param proposalId The ID of the proposal to execute.
/// @param actions Actions associated with the proposal to execute.
function execute(uint256 proposalId, ProposedAction[] memory actions) external payable;
/// @dev Returns the total number of proposals.
/// @return count The number of proposals.
function proposalCount() external view returns (uint256 count);
/// @dev Computes the current voting power of the given account.
/// Voting power is equal to:
/// (ZRX delegated to the default pool) +
/// 0.5 * (ZRX delegated to other pools) +
/// 0.5 * (ZRX delegated to pools operated by account)
/// @param account The address of the account.
/// @param operatedPoolIds The pools operated by `account`. The
/// ZRX currently delegated to those pools will be accounted
/// for in the voting power.
/// @return votingPower The current voting power of the given account.
function getVotingPower(
address account,
bytes32[] calldata operatedPoolIds
) external view returns (uint256 votingPower);
}

View File

@@ -0,0 +1,31 @@
// 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 "forge-std/Test.sol";
import "@openzeppelin/token/ERC20/ERC20.sol";
// TODO remove this contract and work with an instance of ZRX compiled with 0.4
// when the following is resolved https://linear.app/0xproject/issue/PRO-44/zrx-artifact-is-incompatible-with-foundry
contract ZRXMock is ERC20 {
constructor() ERC20("0x Protocol Token", "ZRX") {
_mint(msg.sender, 10 ** 27);
}
}

View File

@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract ZeroExMock {
mapping(bytes4 => address) public implementations;
function rollback(bytes4 selector, address targetImpl) public {
implementations[selector] = targetImpl;
}
}

View File

@@ -0,0 +1,32 @@
// 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 "../../src/ZeroExVotes.sol";
contract ZeroExVotesMalicious is ZeroExVotes {
constructor(address _token, uint256 _quadraticThreshold) ZeroExVotes(_token, _quadraticThreshold) {}
function writeCheckpointTotalSupplyBurn(
uint256 amount,
uint256 accountBalance
) public virtual override onlyToken returns (bool) {
revert("I am evil");
}
}

View File

@@ -0,0 +1,215 @@
// 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 {ZeroExVotes} from "../../src/ZeroExVotes.sol";
import {SafeCast} from "@openzeppelin/utils/math/SafeCast.sol";
import {Math} from "@openzeppelin/utils/math/Math.sol";
import {CubeRoot} from "./CubeRoot.sol";
contract ZeroExVotesMigration is ZeroExVotes {
uint32 public migrationBlock;
constructor(address _token, uint256 _quadraticThreshold) ZeroExVotes(_token, _quadraticThreshold) {}
function initialize() public virtual override onlyProxy reinitializer(2) {
migrationBlock = uint32(block.number);
}
struct CheckpointMigration {
uint32 fromBlock;
uint96 votes;
uint96 quadraticVotes;
uint32 migratedVotes;
}
function _toMigration(Checkpoint storage ckpt) internal pure returns (CheckpointMigration storage result) {
assembly {
result.slot := ckpt.slot
}
}
function _toMigration(Checkpoint[] storage ckpt) internal pure returns (CheckpointMigration[] storage result) {
assembly {
result.slot := ckpt.slot
}
}
function getMigratedVotes(address account) public view returns (uint256) {
uint256 pos = _checkpoints[account].length;
if (pos == 0) {
return 0;
}
Checkpoint storage ckpt = _unsafeAccess(_checkpoints[account], pos - 1);
if (ckpt.fromBlock <= migrationBlock) {
return 0;
}
return _toMigration(ckpt).migratedVotes;
}
function getPastMigratedVotes(address account, uint256 blockNumber) public view returns (uint256) {
require(blockNumber < block.number, "ZeroExVotesMigration: block not yet mined");
if (blockNumber <= migrationBlock) {
return 0;
}
Checkpoint storage checkpoint = _checkpointsLookupStorage(_checkpoints[account], blockNumber);
if (checkpoint.fromBlock <= migrationBlock) {
return 0;
}
return _toMigration(checkpoint).migratedVotes;
}
function _checkpointsLookupStorage(
Checkpoint[] storage ckpts,
uint256 blockNumber
) internal view returns (Checkpoint storage result) {
// We run a binary search to look for the earliest checkpoint taken after `blockNumber`.
//
// Initially we check if the block is recent to narrow the search range.
// During the loop, the index of the wanted checkpoint remains in the range [low-1, high).
// With each iteration, either `low` or `high` is moved towards the middle of the range to maintain the
// invariant.
// - If the middle checkpoint is after `blockNumber`, we look in [low, mid)
// - If the middle checkpoint is before or equal to `blockNumber`, we look in [mid+1, high)
// Once we reach a single value (when low == high), we've found the right checkpoint at the index high-1, if not
// out of bounds (in which case we're looking too far in the past and the result is 0).
// Note that if the latest checkpoint available is exactly for `blockNumber`, we end up with an index that is
// past the end of the array, so we technically don't find a checkpoint after `blockNumber`, but it works out
// the same.
uint256 length = ckpts.length;
uint256 low = 0;
uint256 high = length;
if (length > 5) {
uint256 mid = length - Math.sqrt(length);
if (_unsafeAccess(ckpts, mid).fromBlock > blockNumber) {
high = mid;
} else {
low = mid + 1;
}
}
while (low < high) {
uint256 mid = Math.average(low, high);
if (_unsafeAccess(ckpts, mid).fromBlock > blockNumber) {
high = mid;
} else {
low = mid + 1;
}
}
// Leaving here for posterity this is the original OZ implementation which we've replaced
// return high == 0 ? 0 : _unsafeAccess(ckpts, high - 1).votes;
// Checkpoint memory checkpoint = high == 0 ? Checkpoint(0, 0, 0) : _unsafeAccess(ckpts, high - 1);
// return checkpoint;
// TODO: bad. very bad. only works on accident
if (high > 0) {
result = _unsafeAccess(ckpts, high - 1);
} else {
// suppress compiler warning, which really shouldn't be suppressed
assembly {
result.slot := 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
}
}
}
// TODO: we're not handling totalSupply
// TODO: need to return the migrated weight
function _writeCheckpoint(
Checkpoint[] storage ckpts,
function(uint256, uint256) view returns (uint256) op,
uint256 userBalance,
uint96 balanceLastUpdated,
uint256 delta
)
internal
virtual
override
returns (uint256 oldWeight, uint256 newWeight, uint256 oldQuadraticWeight, uint256 newQuadraticWeight)
{
uint256 pos = ckpts.length;
CheckpointMigration memory oldCkpt = pos == 0
? CheckpointMigration(0, 0, 0, 0)
: _toMigration(_unsafeAccess(ckpts, pos - 1));
oldWeight = oldCkpt.votes;
newWeight = op(oldWeight, delta);
oldQuadraticWeight = oldCkpt.quadraticVotes;
if (pos > 0) {
deductOldWeightFromCheckpoint(oldCkpt, userBalance, balanceLastUpdated);
}
// if wallet > threshold, calculate quadratic power over the treshold only, below threshold is linear
uint256 newBalance = op(userBalance, delta);
uint256 newQuadraticBalance = newBalance <= quadraticThreshold
? newBalance
: quadraticThreshold + Math.sqrt((newBalance - quadraticThreshold) * 1e18);
newQuadraticWeight = oldCkpt.quadraticVotes + newQuadraticBalance;
uint256 newMigratedWeight = oldCkpt.migratedVotes + CubeRoot.cbrt(newBalance);
if (pos > 0 && oldCkpt.fromBlock == block.number) {
addCheckpoint(ckpts, pos, newWeight, newQuadraticWeight, newMigratedWeight);
} else {
_toMigration(ckpts).push(
CheckpointMigration({
fromBlock: SafeCast.toUint32(block.number),
votes: SafeCast.toUint96(newWeight),
quadraticVotes: SafeCast.toUint96(newQuadraticWeight),
migratedVotes: SafeCast.toUint32(newMigratedWeight)
})
);
}
}
function deductOldWeightFromCheckpoint(
CheckpointMigration memory oldCkpt,
uint256 userBalance,
uint96 balanceLastUpdated
) internal {
// Remove the entire sqrt userBalance from quadratic voting power.
// Note that `userBalance` is value _after_ transfer.
uint256 oldQuadraticVotingPower = userBalance <= quadraticThreshold
? userBalance
: quadraticThreshold + Math.sqrt((userBalance - quadraticThreshold) * 1e18);
oldCkpt.quadraticVotes -= SafeCast.toUint96(oldQuadraticVotingPower);
if (balanceLastUpdated > migrationBlock) {
oldCkpt.migratedVotes -= SafeCast.toUint32(CubeRoot.cbrt(userBalance));
}
}
function addCheckpoint(
Checkpoint[] storage ckpts,
uint256 pos,
uint256 newWeight,
uint256 newQuadraticWeight,
uint256 newMigratedWeight
) internal {
CheckpointMigration storage chpt = _toMigration(_unsafeAccess(ckpts, pos - 1));
chpt.votes = SafeCast.toUint96(newWeight);
chpt.quadraticVotes = SafeCast.toUint96(newQuadraticWeight);
chpt.migratedVotes = SafeCast.toUint32(newMigratedWeight);
}
}

View File

@@ -0,0 +1,315 @@
// 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 "../BaseTest.t.sol";
import "../../src/ZRXWrappedToken.sol";
import "@openzeppelin/token/ERC20/ERC20.sol";
contract ZRXWrappedTokenTest is BaseTest {
IERC20 private token;
ZRXWrappedToken private wToken;
ZeroExVotes private votes;
function setUp() public {
token = mockZRXToken();
(wToken, votes, , , , ) = setupGovernance(token);
vm.startPrank(account1);
token.transfer(account2, 100e18);
token.transfer(account3, 200e18);
vm.stopPrank();
}
function testShouldReturnCorrectSymbol() public {
string memory wZRXSymbol = wToken.symbol();
assertEq(wZRXSymbol, "wZRX");
}
function testShouldReturnCorrectName() public {
string memory wZRXName = wToken.name();
assertEq(wZRXName, "Wrapped ZRX");
}
function testShouldReturnCorrectNumberOfDecimals() public {
uint8 wZRXDecimals = wToken.decimals();
assertEq(wZRXDecimals, 18);
}
function testShouldBeAbleToWrapZRX() public {
vm.startPrank(account2);
// Approve the wrapped token and deposit 1e18 ZRX
token.approve(address(wToken), 1e18);
wToken.depositFor(account2, 1e18);
// Check the token balances even out
uint256 wTokenBalance = wToken.balanceOf(account2);
assertEq(wTokenBalance, 1e18);
uint256 tokenBalance = token.balanceOf(account2);
assertEq(tokenBalance, 100e18 - wTokenBalance);
}
function testShouldBeAbleToUnwrapToZRX() public {
vm.startPrank(account2);
// Approve the wrapped token and deposit 1e18 ZRX
token.approve(address(wToken), 1e18);
wToken.depositFor(account2, 1e18);
// Withdraw 1e6 wZRX back to ZRX to own account
wToken.withdrawTo(account2, 1e6);
// Check token balances even out
uint256 wTokenBalance = wToken.balanceOf(account2);
assertEq(wTokenBalance, 1e18 - 1e6);
uint256 tokenBalance = token.balanceOf(account2);
assertEq(tokenBalance, 100e18 - wTokenBalance);
}
function testShouldBeAbleToUnwrapToZRXToAnotherAccount() public {
vm.startPrank(account2);
// Approve the wrapped token and deposit 1e18 ZRX
token.approve(address(wToken), 1e18);
wToken.depositFor(account2, 1e18);
// Withdraw 1e7 wZRX back to ZRX to account4 (which owns no tokens to start with)
wToken.withdrawTo(account4, 1e7);
// Check token balances even out
uint256 wTokenBalance2 = wToken.balanceOf(account2);
assertEq(wTokenBalance2, 1e18 - 1e7);
uint256 tokenBalance4 = token.balanceOf(account4);
assertEq(tokenBalance4, 1e7);
uint256 tokenBalance2 = token.balanceOf(account2);
assertEq(tokenBalance2, 100e18 - wTokenBalance2 - tokenBalance4);
}
function testWrappedZRXTotalsAreCorrect() public {
// Wrap 1e18 and check total supply is correct
vm.startPrank(account2);
token.approve(address(wToken), 1e18);
wToken.depositFor(account2, 1e18);
vm.stopPrank();
uint256 wTokenBalance = wToken.totalSupply();
assertEq(wTokenBalance, 1e18);
// Wrap 2e18 more and check total supply is correct
vm.startPrank(account3);
token.approve(address(wToken), 2e18);
wToken.depositFor(account3, 2e18);
vm.stopPrank();
wTokenBalance = wToken.totalSupply();
assertEq(wTokenBalance, 1e18 + 2e18);
// Unwrap 1e7 and check total supply is correct
vm.startPrank(account2);
wToken.withdrawTo(account2, 1e7);
vm.stopPrank();
wTokenBalance = wToken.totalSupply();
assertEq(wTokenBalance, 3e18 - 1e7);
// Unwrap 8e17 and check total supply is correct
vm.startPrank(account2);
wToken.withdrawTo(account2, 8e17);
vm.stopPrank();
wTokenBalance = wToken.totalSupply();
assertEq(wTokenBalance, 3e18 - 1e7 - 8e17);
// We are not keeping record of total balances so check they are zero
assertEq(votes.getPastTotalSupply(0), 0);
assertEq(votes.getPastQuadraticTotalSupply(0), 0);
}
function testWhenMintingFirstTimeForAccountTotalSupplyCheckpointsAreCorrect() public {
vm.startPrank(account2);
// Approve the wrapped token and deposit 1e18 ZRX
token.approve(address(wToken), 1e18);
vm.roll(2);
wToken.depositFor(account2, 1e18);
vm.roll(3);
// Check the totals are correct
uint256 totalSupplyVotes = votes.getPastTotalSupply(2);
uint256 totalSupplyQuadraticVotes = votes.getPastQuadraticTotalSupply(2);
assertEq(totalSupplyVotes, 1e18);
assertEq(totalSupplyQuadraticVotes, 1e18);
}
function testWhenMintingForAccountWithExistingBalanceTotalSupplyCheckpointsAreCorrect() public {
vm.startPrank(account2);
// Approve the wrapped token and deposit 1e18 ZRX
token.approve(address(wToken), 5e18);
wToken.depositFor(account2, 1e18);
vm.roll(2);
// Depost 3e18 more for the same account
wToken.depositFor(account2, 3e18);
vm.roll(3);
// Check the totals are correct
uint256 totalSupplyVotes = votes.getPastTotalSupply(2);
uint256 totalSupplyQuadraticVotes = votes.getPastQuadraticTotalSupply(2);
assertEq(totalSupplyVotes, 4e18);
assertEq(totalSupplyQuadraticVotes, 4e18);
}
function testWhenMintingForMultipleAccountsTotalSupplyCheckpointsAreCorrect() public {
// Deposit 1e18 ZRX by account2
vm.startPrank(account2);
token.approve(address(wToken), 5e18);
wToken.depositFor(account2, 1e18);
vm.stopPrank();
// Deposit 2e18 ZRX by account3
vm.startPrank(account3);
token.approve(address(wToken), 2e18);
wToken.depositFor(account3, 2e18);
vm.stopPrank();
// Deposit 4e18 ZRX by account2
vm.startPrank(account2);
vm.roll(2);
wToken.depositFor(account2, 4e18);
vm.stopPrank();
vm.roll(3);
// Check the totals are correct
uint256 totalSupplyVotes = votes.getPastTotalSupply(2);
uint256 totalSupplyQuadraticVotes = votes.getPastQuadraticTotalSupply(2);
assertEq(totalSupplyVotes, 7e18);
assertEq(totalSupplyQuadraticVotes, 5e18 + 2e18);
}
function testWhenBurningForMultipleAccountsTotalSupplyCheckpointsAreCorrect() public {
// Deposit 5e18 ZRX by account2
vm.startPrank(account2);
token.approve(address(wToken), 5e18);
wToken.depositFor(account2, 5e18);
vm.stopPrank();
// Deposit 2e18 ZRX by account3
vm.startPrank(account3);
token.approve(address(wToken), 2e18);
wToken.depositFor(account3, 2e18);
vm.stopPrank();
// Burn 4e18 ZRX by account2
vm.startPrank(account2);
vm.roll(2);
wToken.withdrawTo(account2, 4e18);
vm.stopPrank();
vm.roll(3);
// Check the totals are correct
uint256 totalSupplyVotes = votes.getPastTotalSupply(2);
uint256 totalSupplyQuadraticVotes = votes.getPastQuadraticTotalSupply(2);
assertEq(totalSupplyVotes, 3e18);
assertEq(totalSupplyQuadraticVotes, 1e18 + 2e18);
}
function testShouldBeAbleToTransferCorrectly() public {
assertEq(wToken.balanceOf(account4), 0);
vm.startPrank(account2);
token.approve(address(wToken), 1e18);
wToken.depositFor(account2, 1e18);
wToken.transfer(account4, 1e17);
vm.stopPrank();
assertEq(wToken.balanceOf(account4), 1e17);
}
function testShouldTransferVotingPowerWhenTransferringTokens() public {
// Account 2 wraps ZRX and delegates voting power to itself
vm.startPrank(account2);
token.approve(address(wToken), 10e18);
wToken.depositFor(account2, 10e18);
wToken.delegate(account2);
wToken.transfer(account3, 3e18);
assertEq(wToken.balanceOf(account2), 7e18);
assertEq(wToken.balanceOf(account3), 3e18);
assertEq(votes.getVotes(account2), 7e18);
assertEq(votes.getQuadraticVotes(account2), 7e18);
// Since account3 is not delegating to anyone, they should have no voting power
assertEq(votes.getVotes(account3), 0);
assertEq(votes.getQuadraticVotes(account3), 0);
}
function testShouldUpdateVotingPowerWhenDepositing() public {
// Account 2 wraps ZRX and delegates voting power to itself
vm.startPrank(account2);
token.approve(address(wToken), 10e18);
wToken.depositFor(account2, 7e18);
wToken.delegate(account2);
assertEq(votes.getVotes(account2), 7e18);
assertEq(votes.getQuadraticVotes(account2), 7e18);
wToken.depositFor(account2, 2e18);
assertEq(votes.getVotes(account2), 9e18);
assertEq(votes.getQuadraticVotes(account2), 9e18);
}
function testShouldUpdateVotingPowerWhenWithdrawing() public {
// Account 2 wraps ZRX and delegates voting power to itself
vm.startPrank(account2);
token.approve(address(wToken), 10e18);
wToken.depositFor(account2, 10e18);
wToken.delegate(account2);
assertEq(votes.getVotes(account2), 10e18);
assertEq(votes.getQuadraticVotes(account2), 10e18);
wToken.withdrawTo(account2, 2e18);
assertEq(votes.getVotes(account2), 8e18);
assertEq(votes.getQuadraticVotes(account2), 8e18);
}
function testShouldSetDelegateBalanceLastUpdatedOnTransfer() public {
ZRXWrappedToken.DelegateInfo memory account2DelegateInfo = wToken.delegateInfo(account2);
assertEq(account2DelegateInfo.delegate, address(0));
assertEq(account2DelegateInfo.balanceLastUpdated, 0);
// Account 2 wraps ZRX and delegates voting power to account3
vm.startPrank(account2);
token.approve(address(wToken), 10e18);
wToken.depositFor(account2, 10e18);
wToken.delegate(account3);
account2DelegateInfo = wToken.delegateInfo(account2);
assertEq(account2DelegateInfo.delegate, account3);
assertEq(account2DelegateInfo.balanceLastUpdated, 1); // Set to the block.number
vm.roll(3);
wToken.transfer(account3, 3e18);
account2DelegateInfo = wToken.delegateInfo(account2);
assertEq(account2DelegateInfo.delegate, account3);
assertEq(account2DelegateInfo.balanceLastUpdated, 3);
}
}

View File

@@ -0,0 +1,460 @@
// 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 IZeroExGovernorANY 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 "../BaseTest.t.sol";
import "../../src/IZeroExGovernor.sol";
import "../../src/ZeroExTimelock.sol";
import "../../src/ZeroExProtocolGovernor.sol";
import "../../src/ZRXWrappedToken.sol";
import "@openzeppelin/token/ERC20/ERC20.sol";
import "@openzeppelin/mocks/CallReceiverMock.sol";
abstract contract ZeroExGovernorBaseTest is BaseTest {
IERC20 public token;
ZRXWrappedToken internal wToken;
ZeroExVotes internal votes;
ZeroExTimelock internal timelock;
IZeroExGovernor internal governor;
CallReceiverMock internal callReceiverMock;
string internal governorName;
uint256 internal proposalThreshold;
event SecurityCouncilAssigned(address securityCouncil);
event SecurityCouncilEjected();
function initialiseAccounts() public {
vm.startPrank(account1);
token.transfer(account2, 10000000e18);
token.transfer(account3, 2000000e18);
token.transfer(account4, 3000000e18);
vm.stopPrank();
// Setup accounts 2,3 and 4 to vote
vm.startPrank(account2);
token.approve(address(wToken), 10000000e18);
wToken.depositFor(account2, 10000000e18);
wToken.delegate(account2);
vm.stopPrank();
vm.startPrank(account3);
token.approve(address(wToken), 2000000e18);
wToken.depositFor(account3, 2000000e18);
wToken.delegate(account3);
vm.stopPrank();
vm.startPrank(account4);
token.approve(address(wToken), 3000000e18);
wToken.depositFor(account4, 3000000e18);
wToken.delegate(account4);
vm.stopPrank();
callReceiverMock = new CallReceiverMock();
}
function setSecurityCouncil(address council) internal {
address[] memory targets = new address[](1);
targets[0] = address(governor);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSelector(governor.assignSecurityCouncil.selector, council);
vm.roll(2);
vm.startPrank(account2);
uint256 proposalId = governor.propose(targets, values, calldatas, "Assign new security council");
vm.stopPrank();
// Fast forward to after vote start
vm.roll(governor.proposalSnapshot(proposalId) + 1);
// Vote
vm.prank(account2);
governor.castVote(proposalId, 1); // Vote "for"
vm.stopPrank();
// Fast forward to vote end
vm.roll(governor.proposalDeadline(proposalId) + 1);
// Queue proposal
governor.queue(targets, values, calldatas, keccak256(bytes("Assign new security council")));
vm.warp(governor.proposalEta(proposalId) + 1);
// Execute proposal
governor.execute(targets, values, calldatas, keccak256("Assign new security council"));
assertEq(governor.securityCouncil(), council);
}
function testShouldReturnCorrectName() public {
assertEq(governor.name(), governorName);
}
function testShouldReturnCorrectVotingDelay() public {
assertEq(governor.votingDelay(), 14400);
}
function testShouldReturnCorrectVotingPeriod() public {
assertEq(governor.votingPeriod(), 50400);
}
function testShouldReturnCorrectProposalThreshold() public {
assertEq(governor.proposalThreshold(), proposalThreshold);
}
function testShouldReturnCorrectToken() public {
assertEq(address(governor.token()), address(votes));
}
function testShouldReturnCorrectTimelock() public {
assertEq(address(governor.timelock()), address(timelock));
}
function testShouldReturnCorrectSecurityCouncil() public {
assertEq(governor.securityCouncil(), securityCouncil);
}
function testCanAssignSecurityCouncil() public {
// Create a proposal
address[] memory targets = new address[](1);
targets[0] = address(governor);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSelector(governor.assignSecurityCouncil.selector, account1);
vm.roll(2);
vm.startPrank(account2);
uint256 proposalId = governor.propose(targets, values, calldatas, "Assign new security council");
vm.stopPrank();
// Fast forward to after vote start
vm.roll(governor.proposalSnapshot(proposalId) + 1);
// Vote
vm.prank(account2);
governor.castVote(proposalId, 1); // Vote "for"
vm.stopPrank();
// Fast forward to vote end
vm.roll(governor.proposalDeadline(proposalId) + 1);
// Queue proposal
governor.queue(targets, values, calldatas, keccak256(bytes("Assign new security council")));
vm.warp(governor.proposalEta(proposalId) + 1);
// Execute proposal
vm.expectEmit(true, false, false, false);
emit SecurityCouncilAssigned(account1);
governor.execute(targets, values, calldatas, keccak256("Assign new security council"));
assertEq(governor.securityCouncil(), account1);
}
function testCannotAssignSecurityCouncilOutsideOfGovernance() public {
vm.expectRevert("Governor: onlyGovernance");
governor.assignSecurityCouncil(account1);
}
// This functionality is currently not enabled
// Leaving this test for potential future use.
function testFailSecurityCouncilAreEjectedAfterCancellingAProposal() public {
// Create a proposal
address[] memory targets = new address[](1);
targets[0] = address(callReceiverMock);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSignature("mockFunction()");
vm.roll(2);
vm.startPrank(account2);
uint256 proposalId = governor.propose(targets, values, calldatas, "Proposal description");
vm.stopPrank();
// Fast forward to after vote start
vm.roll(governor.proposalSnapshot(proposalId) + 1);
// Vote
vm.prank(account2);
governor.castVote(proposalId, 1); // Vote "for"
vm.stopPrank();
// Fast forward to vote end
vm.roll(governor.proposalDeadline(proposalId) + 1);
IGovernor.ProposalState state = governor.state(proposalId);
assertEq(uint256(state), uint256(IGovernor.ProposalState.Succeeded));
// Queue proposal
governor.queue(targets, values, calldatas, keccak256(bytes("Proposal description")));
// Cancel the proposal
vm.warp(governor.proposalEta(proposalId));
vm.prank(securityCouncil);
vm.expectEmit(true, false, false, false);
emit SecurityCouncilEjected();
governor.cancel(targets, values, calldatas, keccak256(bytes("Proposal description")));
vm.stopPrank();
state = governor.state(proposalId);
assertEq(uint256(state), uint256(IGovernor.ProposalState.Canceled));
assertEq(governor.securityCouncil(), address(0));
}
function testWhenNoSecurityCouncilCannottSubmitProposals() public {
setSecurityCouncil(address(0));
address[] memory targets = new address[](1);
targets[0] = address(callReceiverMock);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSignature("mockFunction()");
vm.expectRevert("SecurityCouncil: security council not assigned and this is not an assignment call");
governor.propose(targets, values, calldatas, "Proposal description");
}
function testWhenNoSecurityCouncilCannotQueueSuccessfulProposals() public {
// Create a proposal
address[] memory targets = new address[](1);
targets[0] = address(callReceiverMock);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSignature("mockFunction()");
vm.roll(2);
vm.startPrank(account2);
uint256 proposalId = governor.propose(targets, values, calldatas, "Proposal description");
vm.stopPrank();
// Fast forward to after vote start
vm.roll(governor.proposalSnapshot(proposalId) + 1);
// Vote
vm.prank(account2);
governor.castVote(proposalId, 1); // Vote "for"
vm.stopPrank();
// Fast forward to vote end
vm.roll(governor.proposalDeadline(proposalId) + 1);
// Set security council to address(0)
setSecurityCouncil(address(0));
vm.expectRevert("SecurityCouncil: security council not assigned and this is not an assignment call");
governor.queue(targets, values, calldatas, keccak256(bytes("Proposal description")));
IGovernor.ProposalState state = governor.state(proposalId);
assertEq(uint256(state), uint256(IGovernor.ProposalState.Succeeded));
}
function testWhenNoSecurityCouncilCanPassProposalToAssignSecurityCouncil() public {
setSecurityCouncil(address(0));
setSecurityCouncil(account1);
}
function testCannotPassABadProposalToSetSecurityCouncil() public {
setSecurityCouncil(address(0));
address[] memory targets = new address[](2);
targets[0] = address(governor);
targets[1] = address(callReceiverMock);
uint256[] memory values = new uint256[](2);
values[0] = 0;
values[1] = 0;
bytes[] memory calldatas = new bytes[](2);
calldatas[0] = abi.encodeWithSelector(governor.assignSecurityCouncil.selector, account1);
calldatas[1] = abi.encodeWithSignature("mockFunction()");
vm.roll(2);
vm.startPrank(account2);
vm.expectRevert("SecurityCouncil: more than 1 transaction in proposal");
governor.propose(targets, values, calldatas, "Assign new security council");
}
function testCanUpdateVotingDelaySetting() public {
// Create a proposal
address[] memory targets = new address[](1);
targets[0] = address(governor);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSelector(governor.setVotingDelay.selector, 3 days);
vm.roll(2);
vm.startPrank(account2);
uint256 proposalId = governor.propose(targets, values, calldatas, "Increase voting delay to 3 days");
vm.stopPrank();
// Fast forward to after vote start
vm.roll(governor.proposalSnapshot(proposalId) + 1);
// Vote
vm.prank(account2);
governor.castVote(proposalId, 1); // Vote "for"
vm.stopPrank();
// Fast forward to vote end
vm.roll(governor.proposalDeadline(proposalId) + 1);
// Queue proposal
governor.queue(targets, values, calldatas, keccak256(bytes("Increase voting delay to 3 days")));
vm.warp(governor.proposalEta(proposalId) + 1);
// Execute proposal
governor.execute(targets, values, calldatas, keccak256("Increase voting delay to 3 days"));
assertEq(governor.votingDelay(), 3 days);
}
function testCanUpdateVotingPeriodSetting() public {
// Create a proposal
address[] memory targets = new address[](1);
targets[0] = address(governor);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSelector(governor.setVotingPeriod.selector, 14 days);
vm.roll(2);
vm.startPrank(account2);
uint256 proposalId = governor.propose(targets, values, calldatas, "Increase voting period to 14 days");
vm.stopPrank();
// Fast forward to after vote start
vm.roll(governor.proposalSnapshot(proposalId) + 1);
// Vote
vm.prank(account2);
governor.castVote(proposalId, 1); // Vote "for"
vm.stopPrank();
// Fast forward to vote end
vm.roll(governor.proposalDeadline(proposalId) + 1);
// Queue proposal
governor.queue(targets, values, calldatas, keccak256(bytes("Increase voting period to 14 days")));
vm.warp(governor.proposalEta(proposalId) + 1);
// Execute proposal
governor.execute(targets, values, calldatas, keccak256("Increase voting period to 14 days"));
assertEq(governor.votingPeriod(), 14 days);
}
function testCanUpdateProposalThresholdSetting() public {
// Create a proposal
address[] memory targets = new address[](1);
targets[0] = address(governor);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSelector(governor.setProposalThreshold.selector, 2000000e18);
vm.roll(2);
vm.startPrank(account2);
uint256 proposalId = governor.propose(targets, values, calldatas, "Increase proposal threshold to 2000000e18");
vm.stopPrank();
// Fast forward to after vote start
vm.roll(governor.proposalSnapshot(proposalId) + 1);
// Vote
vm.prank(account2);
governor.castVote(proposalId, 1); // Vote "for"
vm.stopPrank();
// Fast forward to vote end
vm.roll(governor.proposalDeadline(proposalId) + 1);
// Queue proposal
governor.queue(targets, values, calldatas, keccak256(bytes("Increase proposal threshold to 2000000e18")));
vm.warp(governor.proposalEta(proposalId) + 1);
// Execute proposal
governor.execute(targets, values, calldatas, keccak256("Increase proposal threshold to 2000000e18"));
assertEq(governor.proposalThreshold(), 2000000e18);
}
function testCanUpdateTimelockDelay() public {
// Create a proposal
address[] memory targets = new address[](1);
targets[0] = address(timelock);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSelector(timelock.updateDelay.selector, 7 days);
vm.roll(2);
vm.startPrank(account2);
uint256 proposalId = governor.propose(targets, values, calldatas, "Increase timelock delay to 7 days");
vm.stopPrank();
// Fast forward to after vote start
vm.roll(governor.proposalSnapshot(proposalId) + 1);
// Vote
vm.prank(account2);
governor.castVote(proposalId, 1); // Vote "for"
vm.stopPrank();
// Fast forward to vote end
vm.roll(governor.proposalDeadline(proposalId) + 1);
// Queue proposal
governor.queue(targets, values, calldatas, keccak256(bytes("Increase timelock delay to 7 days")));
vm.warp(governor.proposalEta(proposalId) + 1);
// Execute proposal
governor.execute(targets, values, calldatas, keccak256("Increase timelock delay to 7 days"));
assertEq(timelock.getMinDelay(), 7 days);
}
function testSupportsGovernanceInterfaces() public {
assertTrue(governor.supportsInterface(type(IGovernorTimelock).interfaceId));
assertTrue(governor.supportsInterface(type(IGovernor).interfaceId));
assertTrue(governor.supportsInterface(type(IERC1155Receiver).interfaceId));
}
}

View File

@@ -0,0 +1,169 @@
// 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 "./ZeroExGovernorBaseTest.t.sol";
import "../mocks/ZeroExMock.sol";
import "../../src/ZeroExProtocolGovernor.sol";
contract ZeroExProtocolGovernorTest is ZeroExGovernorBaseTest {
ZeroExProtocolGovernor internal protocolGovernor;
ZeroExMock internal zeroExMock;
uint256 internal quorum;
event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data);
function setUp() public {
governorName = "ZeroExProtocolGovernor";
proposalThreshold = 1000000e18;
quorum = 10000000e18;
address governorAddress;
token = mockZRXToken();
(wToken, votes, timelock, , governorAddress, ) = setupGovernance(token);
governor = IZeroExGovernor(governorAddress);
protocolGovernor = ZeroExProtocolGovernor(payable(governorAddress));
zeroExMock = new ZeroExMock();
initialiseAccounts();
}
function testShouldReturnCorrectQuorum() public {
assertEq(governor.quorum(block.number), quorum);
}
function testShouldBeAbleToExecuteASuccessfulProposal() public {
// Create a proposal
address[] memory targets = new address[](1);
targets[0] = address(callReceiverMock);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSignature("mockFunction()");
vm.roll(2);
vm.startPrank(account2);
uint256 proposalId = governor.propose(targets, values, calldatas, "Proposal description");
vm.stopPrank();
// Fast forward to after vote start
vm.roll(governor.proposalSnapshot(proposalId) + 1);
// Vote
vm.prank(account2);
governor.castVote(proposalId, 1); // Vote "for"
vm.stopPrank();
vm.prank(account3);
governor.castVote(proposalId, 0); // Vote "against"
vm.stopPrank();
vm.prank(account4);
governor.castVote(proposalId, 2); // Vote "abstain"
vm.stopPrank();
// Fast forward to vote end
vm.roll(governor.proposalDeadline(proposalId) + 1);
// Get vote results
(uint256 votesAgainst, uint256 votesFor, uint256 votesAbstain) = governor.proposalVotes(proposalId);
assertEq(votesFor, 10000000e18);
assertEq(votesAgainst, 2000000e18);
assertEq(votesAbstain, 3000000e18);
IGovernor.ProposalState state = governor.state(proposalId);
assertEq(uint256(state), uint256(IGovernor.ProposalState.Succeeded));
// Queue proposal
governor.queue(targets, values, calldatas, keccak256(bytes("Proposal description")));
vm.warp(governor.proposalEta(proposalId) + 1);
governor.execute(targets, values, calldatas, keccak256("Proposal description"));
state = governor.state(proposalId);
assertEq(uint256(state), uint256(IGovernor.ProposalState.Executed));
}
function testSecurityCouncilShouldBeAbleToExecuteRollback() public {
// Create a proposal
address[] memory targets = new address[](1);
targets[0] = address(zeroExMock);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
bytes4 testFunctionSig = 0xc853c969;
address testFunctionImpl = 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f;
calldatas[0] = abi.encodeWithSignature("rollback(bytes4,address)", testFunctionSig, testFunctionImpl);
// Security council adds the batch of rollbacks to the queue
vm.startPrank(securityCouncil);
bytes32 proposalId = timelock.hashOperationBatch(
targets,
values,
calldatas,
0,
keccak256(bytes("Emergency rollback"))
);
vm.expectEmit(true, true, true, true);
emit CallExecuted(proposalId, 0, targets[0], values[0], calldatas[0]);
// This functionality is currently not enabled
// Leaving this test for potential future use.
// vm.expectEmit(true, false, false, false);
// emit SecurityCouncilEjected();
protocolGovernor.executeRollback(targets, values, calldatas, keccak256(bytes("Emergency rollback")));
}
function testSecurityCouncilShouldNotBeAbleToExecuteArbitraryFunctions() public {
// Create a proposal
address[] memory targets = new address[](1);
targets[0] = address(callReceiverMock);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSignature("mockFunction()");
vm.startPrank(securityCouncil);
vm.expectRevert("ZeroExTimelock: not rollback");
protocolGovernor.executeRollback(targets, values, calldatas, keccak256(bytes("Proposal description")));
}
function testRollbackShouldNotBeExecutableByNonSecurityCouncilAccounts() public {
// Create a proposal
address[] memory targets = new address[](1);
targets[0] = address(zeroExMock);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
bytes4 testFunctionSig = 0xc853c969;
address testFunctionImpl = 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f;
calldatas[0] = abi.encodeWithSignature("rollback(bytes4,address)", testFunctionSig, testFunctionImpl);
vm.startPrank(account2);
vm.expectRevert("SecurityCouncil: only security council allowed");
protocolGovernor.executeRollback(targets, values, calldatas, keccak256(bytes("Emergency rollback")));
}
}

View File

@@ -0,0 +1,97 @@
// 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 "./ZeroExGovernorBaseTest.t.sol";
contract ZeroExTreasuryGovernorTest is ZeroExGovernorBaseTest {
function setUp() public {
governorName = "ZeroExTreasuryGovernor";
proposalThreshold = 250000e18;
address governorAddress;
token = mockZRXToken();
(wToken, votes, , timelock, , governorAddress) = setupGovernance(token);
governor = IZeroExGovernor(governorAddress);
initialiseAccounts();
}
function testShouldReturnCorrectQuorum() public {
vm.roll(3);
uint256 totalSupplyQuadraticVotes = quadraticThreshold *
3 +
Math.sqrt((10000000e18 - quadraticThreshold) * 1e18) +
Math.sqrt((2000000e18 - quadraticThreshold) * 1e18) +
Math.sqrt((3000000e18 - quadraticThreshold) * 1e18);
uint256 quorum = (totalSupplyQuadraticVotes * 10) / 100;
assertEq(governor.quorum(2), quorum);
}
function testShouldBeAbleToExecuteASuccessfulProposal() public {
// Create a proposal
address[] memory targets = new address[](1);
targets[0] = address(callReceiverMock);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSignature("mockFunction()");
vm.roll(2);
vm.startPrank(account2);
uint256 proposalId = governor.propose(targets, values, calldatas, "Proposal description");
vm.stopPrank();
// Fast forward to after vote start
vm.roll(governor.proposalSnapshot(proposalId) + 1);
// Vote
vm.prank(account2);
governor.castVote(proposalId, 1); // Vote "for"
vm.stopPrank();
vm.prank(account3);
governor.castVote(proposalId, 0); // Vote "against"
vm.stopPrank();
vm.prank(account4);
governor.castVote(proposalId, 2); // Vote "abstain"
vm.stopPrank();
// Fast forward to vote end
vm.roll(governor.proposalDeadline(proposalId) + 1);
// Get vote results
(uint256 votesAgainst, uint256 votesFor, uint256 votesAbstain) = governor.proposalVotes(proposalId);
assertEq(votesFor, (quadraticThreshold + Math.sqrt((10000000e18 - quadraticThreshold) * 1e18)));
assertEq(votesAgainst, quadraticThreshold + Math.sqrt((2000000e18 - quadraticThreshold) * 1e18));
assertEq(votesAbstain, quadraticThreshold + Math.sqrt((3000000e18 - quadraticThreshold) * 1e18));
IGovernor.ProposalState state = governor.state(proposalId);
assertEq(uint256(state), uint256(IGovernor.ProposalState.Succeeded));
// Queue proposal
governor.queue(targets, values, calldatas, keccak256(bytes("Proposal description")));
vm.warp(governor.proposalEta(proposalId) + 1);
governor.execute(targets, values, calldatas, keccak256("Proposal description"));
state = governor.state(proposalId);
assertEq(uint256(state), uint256(IGovernor.ProposalState.Executed));
}
}

View File

@@ -0,0 +1,435 @@
// 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 "@openzeppelin/token/ERC20/ERC20.sol";
import "../BaseTest.t.sol";
import "../mocks/ZeroExVotesMalicious.sol";
import "../mocks/ZeroExVotesMigration.sol";
import "../../src/ZRXWrappedToken.sol";
import "../../src/ZeroExVotes.sol";
contract ZeroExVotesTest is BaseTest {
IERC20 internal token;
ZRXWrappedToken internal wToken;
ZeroExVotes internal votes;
function setUp() public {
token = mockZRXToken();
(wToken, votes) = setupZRXWrappedToken(token);
vm.startPrank(account1);
token.transfer(account2, 1700000e18);
token.transfer(account3, 1600000e18);
token.transfer(account4, 1000000e18);
vm.stopPrank();
}
function testShouldCorrectlyInitialiseToken() public {
assertEq(votes.token(), address(wToken));
}
function testShouldNotBeAbleToReinitialise() public {
vm.expectRevert("Initializable: contract is already initialized");
votes.initialize();
}
function testShouldBeAbleToMigrate() public {
vm.roll(block.number + 1);
vm.startPrank(account2);
token.approve(address(wToken), 100e18);
wToken.depositFor(account2, 100e18);
wToken.delegate(account3);
vm.stopPrank();
vm.startPrank(account3);
token.approve(address(wToken), 200e18);
wToken.depositFor(account3, 200e18);
wToken.delegate(account3);
vm.stopPrank();
assertEq(votes.getVotes(account3), 300e18);
assertEq(votes.getQuadraticVotes(account3), 300e18);
vm.roll(block.number + 1);
ZeroExVotesMigration newImpl = new ZeroExVotesMigration(address(wToken), quadraticThreshold);
assertFalse(
address(
uint160(
uint256(vm.load(address(votes), 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc))
)
) == address(newImpl)
);
vm.prank(account1);
votes.upgradeToAndCall(address(newImpl), abi.encodeWithSignature("initialize()"));
assertEq(
address(
uint160(
uint256(vm.load(address(votes), 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc))
)
),
address(newImpl)
);
ZeroExVotesMigration upgradedVotes = ZeroExVotesMigration(address(votes));
assertEq(upgradedVotes.getVotes(account3), 300e18);
assertEq(upgradedVotes.getQuadraticVotes(account3), 300e18);
vm.roll(block.number + 1);
vm.prank(account2);
wToken.transfer(address(this), 50e18);
assertEq(upgradedVotes.getVotes(account3), 250e18);
assertEq(upgradedVotes.getQuadraticVotes(account3), 250e18);
assertEq(upgradedVotes.getMigratedVotes(account3), CubeRoot.cbrt(50e18));
vm.prank(account3);
wToken.transfer(address(this), 100e18);
assertEq(upgradedVotes.getVotes(account3), 150e18);
assertEq(upgradedVotes.getQuadraticVotes(account3), 150e18);
assertEq(upgradedVotes.getMigratedVotes(account3), CubeRoot.cbrt(50e18) + CubeRoot.cbrt(100e18));
}
function testShouldNotBeAbleToStopBurn() public {
// wrap some token
vm.startPrank(account2);
token.approve(address(wToken), 1700000e18);
wToken.depositFor(account2, 1700000e18);
vm.stopPrank();
assertEq(token.balanceOf(account2), 0);
assertEq(wToken.balanceOf(account2), 1700000e18);
// malicious upgrade
vm.startPrank(account1);
IZeroExVotes maliciousImpl = new ZeroExVotesMalicious(votes.token(), votes.quadraticThreshold());
votes.upgradeTo(address(maliciousImpl));
vm.stopPrank();
// try to withdraw withdraw
vm.prank(account2);
wToken.withdrawTo(account2, 1700000e18);
assertEq(token.balanceOf(account2), 1700000e18);
assertEq(wToken.balanceOf(account2), 0);
}
function testShouldBeAbleToReadCheckpoints() public {
// Account 2 wraps ZRX and delegates voting power to account3
vm.startPrank(account2);
token.approve(address(wToken), 1700000e18);
wToken.depositFor(account2, 1700000e18);
vm.roll(2);
wToken.delegate(account3);
assertEq(votes.numCheckpoints(account3), 1);
IZeroExVotes.Checkpoint memory checkpoint = votes.checkpoints(account3, 0);
assertEq(checkpoint.fromBlock, 2);
assertEq(checkpoint.votes, 1700000e18);
assertEq(checkpoint.quadraticVotes, quadraticThreshold + Math.sqrt((1700000e18 - quadraticThreshold) * 1e18));
}
function testShouldBeAbleToSelfDelegateVotingPower() public {
// Check voting power initially is 0
assertEq(votes.getVotes(account2), 0);
assertEq(votes.getQuadraticVotes(account2), 0);
// Wrap ZRX and delegate voting power to themselves
vm.startPrank(account2);
token.approve(address(wToken), 1700000e18);
wToken.depositFor(account2, 1700000e18);
wToken.delegate(account2);
// Check voting power
assertEq(votes.getVotes(account2), 1700000e18);
assertEq(
votes.getQuadraticVotes(account2),
quadraticThreshold + Math.sqrt((1700000e18 - quadraticThreshold) * 1e18)
);
}
function testShouldBeAbleToDelegateVotingPowerToAnotherAccount() public {
// Check voting power initially is 0
assertEq(votes.getVotes(account3), 0);
assertEq(votes.getQuadraticVotes(account3), 0);
// Account 2 wraps ZRX and delegates voting power to account3
vm.startPrank(account2);
token.approve(address(wToken), 1700000e18);
wToken.depositFor(account2, 1700000e18);
wToken.delegate(account3);
// Check voting power
assertEq(votes.getVotes(account3), 1700000e18);
assertEq(
votes.getQuadraticVotes(account3),
quadraticThreshold + Math.sqrt((1700000e18 - quadraticThreshold) * 1e18)
);
}
function testShouldBeAbleToDelegateVotingPowerToAnotherAccountWithSignature() public {
uint256 nonce = 0;
uint256 expiry = type(uint256).max;
uint256 privateKey = 2;
// Account 2 wraps ZRX and delegates voting power to account3
vm.startPrank(account2);
token.approve(address(wToken), 1700000e18);
wToken.depositFor(account2, 1700000e18);
vm.stopPrank();
assertEq(wToken.delegates(account2), address(0));
assertEq(votes.getVotes(account3), 0);
assertEq(votes.getQuadraticVotes(account3), 0);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
privateKey,
keccak256(
abi.encodePacked(
"\x19\x01",
wToken.DOMAIN_SEPARATOR(),
keccak256(abi.encode(DELEGATION_TYPEHASH, account3, nonce, expiry))
)
)
);
wToken.delegateBySig(account3, nonce, expiry, v, r, s);
assertEq(wToken.delegates(account2), account3);
assertEq(votes.getVotes(account3), 1700000e18);
assertEq(
votes.getQuadraticVotes(account3),
quadraticThreshold + Math.sqrt((1700000e18 - quadraticThreshold) * 1e18)
);
}
function testShouldNotBeAbleToDelegateWithSignatureAfterExpiry() public {
uint256 nonce = 0;
uint256 expiry = block.timestamp - 1;
uint256 privateKey = 2;
// Account 2 wraps ZRX and delegates voting power to account3
vm.startPrank(account2);
token.approve(address(wToken), 1700000e18);
wToken.depositFor(account2, 1700000e18);
vm.stopPrank();
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
privateKey,
keccak256(
abi.encodePacked(
"\x19\x01",
wToken.DOMAIN_SEPARATOR(),
keccak256(abi.encode(DELEGATION_TYPEHASH, account3, nonce, expiry))
)
)
);
vm.expectRevert("ERC20Votes: signature expired");
wToken.delegateBySig(account3, nonce, expiry, v, r, s);
}
function testMultipleAccountsShouldBeAbleToDelegateVotingPowerToAccountWithNoTokensOnSameBlock() public {
// Check account4 voting power initially is 0
assertEq(votes.getVotes(account4), 0);
assertEq(votes.getQuadraticVotes(account4), 0);
// Account 2 wraps ZRX and delegates voting power to account4
vm.startPrank(account2);
token.approve(address(wToken), 1700000e18);
wToken.depositFor(account2, 1700000e18);
wToken.delegate(account4);
vm.stopPrank();
// Account 3 also wraps ZRX and delegates voting power to account4
vm.startPrank(account3);
token.approve(address(wToken), 1600000e18);
wToken.depositFor(account3, 1600000e18);
wToken.delegate(account4);
vm.stopPrank();
// Check voting power
assertEq(votes.getVotes(account4), 3300000e18);
assertEq(
votes.getQuadraticVotes(account4),
quadraticThreshold *
2 +
Math.sqrt((1700000e18 - quadraticThreshold) * 1e18) +
Math.sqrt((1600000e18 - quadraticThreshold) * 1e18)
);
}
function testMultipleAccountsShouldBeAbleToDelegateVotingPowerToAccountWithNoTokensOnDifferentBlock() public {
// Check account4 voting power initially is 0
assertEq(votes.getVotes(account4), 0);
assertEq(votes.getQuadraticVotes(account4), 0);
// Account 2 wraps ZRX and delegates voting power to account4
vm.startPrank(account2);
token.approve(address(wToken), 1700000e18);
wToken.depositFor(account2, 1700000e18);
wToken.delegate(account4);
vm.stopPrank();
// Different block height
vm.roll(2);
// Account 3 also wraps ZRX and delegates voting power to account4
vm.startPrank(account3);
token.approve(address(wToken), 1600000e18);
wToken.depositFor(account3, 1600000e18);
wToken.delegate(account4);
vm.stopPrank();
// Check voting power
assertEq(votes.getVotes(account4), 3300000e18);
assertEq(
votes.getQuadraticVotes(account4),
quadraticThreshold *
2 +
Math.sqrt((1700000e18 - quadraticThreshold) * 1e18) +
Math.sqrt((1600000e18 - quadraticThreshold) * 1e18)
);
}
function testComplexDelegationScenario() public {
// Account 2 wraps ZRX and delegates to itself
vm.startPrank(account2);
token.approve(address(wToken), 1700000e18);
wToken.depositFor(account2, 1000000e18);
wToken.delegate(account2);
vm.stopPrank();
assertEq(votes.getVotes(account2), 1000000e18);
assertEq(votes.getQuadraticVotes(account2), 1000000e18);
// Account 3 wraps ZRX and delegates to account4
vm.startPrank(account3);
token.approve(address(wToken), 500000e18);
wToken.depositFor(account3, 500000e18);
wToken.delegate(account4);
vm.stopPrank();
assertEq(votes.getVotes(account4), 500000e18);
assertEq(votes.getQuadraticVotes(account4), 500000e18);
// Voting power distribution now is as follows
// account2 -> account2 1000000e18 | 1000000e18
// account3 -> account4 500000e18 | 500000e18
// Account 2 deposits the remaining 700000e18 and delegates to account3
vm.startPrank(account2);
wToken.depositFor(account2, 700000e18);
wToken.delegate(account3);
vm.stopPrank();
assertEq(votes.getVotes(account3), 1700000e18);
assertEq(
votes.getQuadraticVotes(account3),
quadraticThreshold + Math.sqrt((1700000e18 - quadraticThreshold) * 1e18)
);
// Voting power distribution now is as follows
// account2 -> account3 1700000e18 | 1000000e18 + Math.sqrt((1700000e18 - 1000000e18) * 1e18)
// account3 -> account4 500000e18 | 500000e18
// Account 3 delegates to itself
vm.startPrank(account3);
wToken.delegate(account3);
vm.stopPrank();
assertEq(votes.getVotes(account3), 2200000e18);
assertEq(
votes.getQuadraticVotes(account3),
quadraticThreshold + Math.sqrt((1700000e18 - quadraticThreshold) * 1e18) + 500000e18
);
// Voting power distribution now is as follows
// account2, account3 -> account3 2200000e18 | 1000000e18 + Math.sqrt((2200000e18-1000000e18) *1e18) + 500000e18
// Check account2 and account4 no longer have voting power
assertEq(votes.getVotes(account2), 0);
assertEq(votes.getQuadraticVotes(account2), 0);
assertEq(votes.getVotes(account4), 0);
assertEq(votes.getQuadraticVotes(account4), 0);
}
function testCheckpointIsCorrectlyUpdatedOnTheSameBlock() public {
// Account 2 wraps ZRX and delegates 20e18 to itself
vm.startPrank(account2);
token.approve(address(wToken), 20e18);
wToken.depositFor(account2, 20e18);
wToken.delegate(account2);
vm.stopPrank();
assertEq(votes.numCheckpoints(account2), 1);
IZeroExVotes.Checkpoint memory checkpoint1Account2 = votes.checkpoints(account2, 0);
assertEq(checkpoint1Account2.fromBlock, 1);
assertEq(checkpoint1Account2.votes, 20e18);
assertEq(checkpoint1Account2.quadraticVotes, 20e18);
// Account 3 wraps ZRX and delegates 10e18 to account2
vm.startPrank(account3);
token.approve(address(wToken), 10e18);
wToken.depositFor(account3, 10e18);
wToken.delegate(account2);
vm.stopPrank();
assertEq(votes.numCheckpoints(account2), 1);
checkpoint1Account2 = votes.checkpoints(account2, 0);
assertEq(checkpoint1Account2.fromBlock, 1);
assertEq(checkpoint1Account2.votes, 30e18);
assertEq(checkpoint1Account2.quadraticVotes, 20e18 + 10e18);
}
function testCheckpointIsCorrectlyUpdatedOnDifferentBlocks() public {
// Account 2 wraps ZRX and delegates 20e18 to itself
vm.startPrank(account2);
token.approve(address(wToken), 20e18);
wToken.depositFor(account2, 20e18);
wToken.delegate(account2);
vm.stopPrank();
assertEq(votes.numCheckpoints(account2), 1);
IZeroExVotes.Checkpoint memory checkpoint1Account2 = votes.checkpoints(account2, 0);
assertEq(checkpoint1Account2.fromBlock, 1);
assertEq(checkpoint1Account2.votes, 20e18);
assertEq(checkpoint1Account2.quadraticVotes, 20e18);
vm.roll(2);
// Account 3 wraps ZRX and delegates 10e18 to account2
vm.startPrank(account3);
token.approve(address(wToken), 10e18);
wToken.depositFor(account3, 10e18);
wToken.delegate(account2);
vm.stopPrank();
assertEq(votes.numCheckpoints(account2), 2);
IZeroExVotes.Checkpoint memory checkpoint2Account2 = votes.checkpoints(account2, 1);
assertEq(checkpoint2Account2.fromBlock, 2);
assertEq(checkpoint2Account2.votes, 30e18);
assertEq(checkpoint2Account2.quadraticVotes, 20e18 + 10e18);
// Check the old checkpoint hasn't changed
checkpoint1Account2 = votes.checkpoints(account2, 0);
assertEq(checkpoint1Account2.fromBlock, 1);
assertEq(checkpoint1Account2.votes, 20e18);
assertEq(checkpoint1Account2.quadraticVotes, 20e18);
}
}

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1678410794,
"version": "5.4.49",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1677693479,
"version": "5.4.48",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v5.4.49 - _March 10, 2023_
* Dependencies updated
## v5.4.48 - _March 1, 2023_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-test-utils",
"version": "5.4.48",
"version": "5.4.49",
"engines": {
"node": ">=6.12"
},
@@ -41,7 +41,7 @@
"dependencies": {
"@0x/assert": "^3.0.35",
"@0x/base-contract": "^7.0.0",
"@0x/contract-addresses": "^8.1.0",
"@0x/contract-addresses": "^8.2.0",
"@0x/dev-utils": "^5.0.0",
"@0x/json-schemas": "^6.4.4",
"@0x/order-utils": "^10.4.28",

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1678410794,
"version": "1.4.42",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1677693479,
"version": "1.4.41",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.4.42 - _March 10, 2023_
* Dependencies updated
## v1.4.41 - _March 1, 2023_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-treasury",
"version": "1.4.41",
"version": "1.4.42",
"engines": {
"node": ">=6.12"
},
@@ -46,12 +46,12 @@
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/treasury",
"devDependencies": {
"@0x/abi-gen": "^5.8.1",
"@0x/contract-addresses": "^8.1.0",
"@0x/contract-addresses": "^8.2.0",
"@0x/contracts-asset-proxy": "^3.7.19",
"@0x/contracts-erc20": "3.3.57",
"@0x/contracts-gen": "^2.0.48",
"@0x/contracts-staking": "^2.0.45",
"@0x/contracts-test-utils": "^5.4.48",
"@0x/contracts-test-utils": "^5.4.49",
"@0x/sol-compiler": "^4.8.2",
"@0x/ts-doc-gen": "^0.0.28",
"@types/isomorphic-fetch": "^0.0.35",
@@ -73,7 +73,7 @@
},
"dependencies": {
"@0x/base-contract": "^7.0.0",
"@0x/protocol-utils": "^11.18.0",
"@0x/protocol-utils": "^11.18.1",
"@0x/subproviders": "^7.0.0",
"@0x/types": "^3.3.6",
"@0x/typescript-typings": "^5.3.1",

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1678410794,
"version": "4.8.40",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1677693479,
"version": "4.8.39",

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v4.8.40 - _March 10, 2023_
* Dependencies updated
## v4.8.39 - _March 1, 2023_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-utils",
"version": "4.8.39",
"version": "4.8.40",
"engines": {
"node": ">=6.12"
},
@@ -46,7 +46,7 @@
"devDependencies": {
"@0x/abi-gen": "^5.8.1",
"@0x/contracts-gen": "^2.0.48",
"@0x/contracts-test-utils": "^5.4.48",
"@0x/contracts-test-utils": "^5.4.49",
"@0x/dev-utils": "^5.0.0",
"@0x/order-utils": "^10.4.28",
"@0x/sol-compiler": "^4.8.2",

View File

@@ -1,4 +1,13 @@
[
{
"version": "0.39.1",
"changes": [
{
"note": "Add KyberElastic mixin for Optimism and BSC"
}
],
"timestamp": 1678410794
},
{
"version": "0.39.0",
"changes": [

View File

@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v0.39.1 - _March 10, 2023_
* Add KyberElastic mixin for Optimism and BSC
## v0.39.0 - _March 1, 2023_
* Add KyberElastic mixin for Ethereum, Polygon, Arbitrum, Avalanche

View File

@@ -119,7 +119,9 @@ contract FillQuoteTransformer is Transformer {
/// @dev Intermediate state variables to get around stack limits.
struct FillState {
uint256 ethRemaining;
/// deprecated
uint256 boughtAmount;
/// deprecated
uint256 soldAmount;
uint256 protocolFee;
uint256 takerTokenBalanceRemaining;
@@ -208,19 +210,6 @@ contract FillQuoteTransformer is Transformer {
// Fill the orders.
for (uint256 i = 0; i < data.fillSequence.length; ++i) {
// Check if we've hit our targets.
if (data.side == Side.Sell) {
// Market sell check.
if (state.soldAmount >= data.fillAmount) {
break;
}
} else {
// Market buy check.
if (state.boughtAmount >= data.fillAmount) {
break;
}
}
state.currentOrderType = OrderType(data.fillSequence[i]);
uint256 orderIndex = state.currentIndices[uint256(state.currentOrderType)];
// Fill the order.
@@ -238,30 +227,10 @@ contract FillQuoteTransformer is Transformer {
}
// Accumulate totals.
state.soldAmount = state.soldAmount.safeAdd(results.takerTokenSoldAmount);
state.boughtAmount = state.boughtAmount.safeAdd(results.makerTokenBoughtAmount);
state.ethRemaining = state.ethRemaining.safeSub(results.protocolFeePaid);
state.takerTokenBalanceRemaining = state.takerTokenBalanceRemaining.safeSub(results.takerTokenSoldAmount);
state.currentIndices[uint256(state.currentOrderType)]++;
}
// Ensure we hit our targets.
if (data.side == Side.Sell) {
// Market sell check.
if (state.soldAmount < data.fillAmount) {
LibTransformERC20RichErrors
.IncompleteFillSellQuoteError(address(data.sellToken), state.soldAmount, data.fillAmount)
.rrevert();
}
} else {
// Market buy check.
if (state.boughtAmount < data.fillAmount) {
LibTransformERC20RichErrors
.IncompleteFillBuyQuoteError(address(data.buyToken), state.boughtAmount, data.fillAmount)
.rrevert();
}
}
// Refund unspent protocol fees.
if (state.ethRemaining > 0 && data.refundReceiver != address(0)) {
bool transferSuccess;

View File

@@ -47,7 +47,7 @@ contract ArbitrumBridgeAdapter is
MixinWOOFi,
MixinZeroExBridge
{
constructor(IEtherToken weth) public MixinCurve(weth) MixinAaveV3(true) {}
constructor(IEtherToken weth) public MixinCurve(weth) {}
function _trade(
BridgeOrder memory order,

View File

@@ -45,7 +45,7 @@ contract AvalancheBridgeAdapter is
MixinWOOFi,
MixinZeroExBridge
{
constructor(IEtherToken weth) public MixinCurve(weth) MixinAaveV3(false) {}
constructor(IEtherToken weth) public MixinCurve(weth) {}
function _trade(
BridgeOrder memory order,

View File

@@ -21,6 +21,7 @@ import "./mixins/MixinCurve.sol";
import "./mixins/MixinDodo.sol";
import "./mixins/MixinDodoV2.sol";
import "./mixins/MixinKyberDmm.sol";
import "./mixins/MixinKyberElastic.sol";
import "./mixins/MixinMooniswap.sol";
import "./mixins/MixinNerve.sol";
import "./mixins/MixinUniswapV2.sol";
@@ -33,6 +34,7 @@ contract BSCBridgeAdapter is
MixinDodo,
MixinDodoV2,
MixinKyberDmm,
MixinKyberElastic,
MixinMooniswap,
MixinNerve,
MixinUniswapV2,
@@ -84,6 +86,11 @@ contract BSCBridgeAdapter is
return (0, true);
}
boughtAmount = _tradeKyberDmm(buyToken, sellAmount, order.bridgeData);
} else if (protocolId == BridgeProtocols.KYBERELASTIC) {
if (dryRun) {
return (0, true);
}
boughtAmount = _tradeKyberElastic(sellToken, sellAmount, order.bridgeData);
} else if (protocolId == BridgeProtocols.WOOFI) {
if (dryRun) {
return (0, true);

View File

@@ -21,6 +21,7 @@ import "./mixins/MixinAaveV3.sol";
import "./mixins/MixinBalancerV2Batch.sol";
import "./mixins/MixinCurve.sol";
import "./mixins/MixinCurveV2.sol";
import "./mixins/MixinKyberElastic.sol";
import "./mixins/MixinNerve.sol";
import "./mixins/MixinSolidly.sol";
import "./mixins/MixinSynthetix.sol";
@@ -34,6 +35,7 @@ contract OptimismBridgeAdapter is
MixinBalancerV2Batch,
MixinCurve,
MixinCurveV2,
MixinKyberElastic,
MixinNerve,
MixinSynthetix,
MixinUniswapV3,
@@ -41,7 +43,7 @@ contract OptimismBridgeAdapter is
MixinWOOFi,
MixinZeroExBridge
{
constructor(IEtherToken weth) public MixinCurve(weth) MixinAaveV3(true) {}
constructor(IEtherToken weth) public MixinCurve(weth) {}
/* solhint-disable function-max-lines */
function _trade(
@@ -102,6 +104,11 @@ contract OptimismBridgeAdapter is
return (0, true);
}
boughtAmount = _tradeWOOFi(sellToken, buyToken, sellAmount, order.bridgeData);
} else if (protocolId == BridgeProtocols.KYBERELASTIC) {
if (dryRun) {
return (0, true);
}
boughtAmount = _tradeKyberElastic(sellToken, sellAmount, order.bridgeData);
}
emit BridgeFill(order.source, sellToken, buyToken, sellAmount, boughtAmount);

View File

@@ -53,7 +53,7 @@ contract PolygonBridgeAdapter is
MixinWOOFi,
MixinZeroExBridge
{
constructor(IEtherToken weth) public MixinCurve(weth) MixinAaveV3(false) {}
constructor(IEtherToken weth) public MixinCurve(weth) {}
function _trade(
BridgeOrder memory order,

View File

@@ -47,63 +47,16 @@ interface IPool {
function withdraw(address asset, uint256 amount, address to) external returns (uint256);
}
// Minimal Aave V3 L2Pool interface
interface IL2Pool {
/**
* @notice Calldata efficient wrapper of the supply function on behalf of the caller
* @param args Arguments for the supply function packed in one bytes32
* 96 bits 16 bits 128 bits 16 bits
* | 0-padding | referralCode | shortenedAmount | assetId |
* @dev the shortenedAmount is cast to 256 bits at decode time, if type(uint128).max the value will be expanded to
* type(uint256).max
* @dev assetId is the index of the asset in the reservesList.
*/
function supply(bytes32 args) external;
/**
* @notice Calldata efficient wrapper of the withdraw function, withdrawing to the caller
* @param args Arguments for the withdraw function packed in one bytes32
* 112 bits 128 bits 16 bits
* | 0-padding | shortenedAmount | assetId |
* @dev the shortenedAmount is cast to 256 bits at decode time, if type(uint128).max the value will be expanded to
* type(uint256).max
* @dev assetId is the index of the asset in the reservesList.
*/
function withdraw(bytes32 args) external;
}
contract MixinAaveV3 {
using LibERC20TokenV06 for IERC20Token;
bool private immutable _isL2;
constructor(bool isL2) public {
_isL2 = isL2;
}
function _tradeAaveV3(
IERC20Token sellToken,
IERC20Token buyToken,
uint256 sellAmount,
bytes memory bridgeData
) internal returns (uint256) {
if (_isL2) {
(IL2Pool pool, address aToken, bytes32 l2Params) = abi.decode(bridgeData, (IL2Pool, address, bytes32));
sellToken.approveIfBelow(address(pool), sellAmount);
if (address(buyToken) == aToken) {
pool.supply(l2Params);
// 1:1 mapping token --> aToken and have the same number of decimals as the underlying token
return sellAmount;
} else if (address(sellToken) == aToken) {
pool.withdraw(l2Params);
return sellAmount;
}
revert("MixinAaveV3/UNSUPPORTED_TOKEN_PAIR");
}
(IPool pool, address aToken, ) = abi.decode(bridgeData, (IPool, address, bytes32));
(IPool pool, address aToken) = abi.decode(bridgeData, (IPool, address));
sellToken.approveIfBelow(address(pool), sellAmount);

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contracts-zero-ex",
"version": "0.39.0",
"version": "0.39.1",
"engines": {
"node": ">=6.12"
},
@@ -51,10 +51,10 @@
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/zero-ex",
"devDependencies": {
"@0x/abi-gen": "^5.8.1",
"@0x/contract-addresses": "^8.1.0",
"@0x/contract-addresses": "^8.2.0",
"@0x/contracts-erc20": "^3.3.57",
"@0x/contracts-gen": "^2.0.48",
"@0x/contracts-test-utils": "^5.4.48",
"@0x/contracts-test-utils": "^5.4.49",
"@0x/dev-utils": "^5.0.0",
"@0x/order-utils": "^10.4.28",
"@0x/sol-compiler": "^4.8.2",
@@ -80,7 +80,7 @@
},
"dependencies": {
"@0x/base-contract": "^7.0.0",
"@0x/protocol-utils": "^11.18.0",
"@0x/protocol-utils": "^11.18.1",
"@0x/subproviders": "^7.0.0",
"@0x/types": "^3.3.6",
"@0x/typescript-typings": "^5.3.1",

View File

@@ -38,8 +38,8 @@ contract SwapERC20ForERC20Test is Test, ForkUtils, TestUtils {
function test_swapERC20ForERC20OnKyberElastic() public {
for (uint256 i = 0; i < chains.length; i++) {
// kyberelastic mixin not deployed to these chains yet (bsc, fantom, optimism)
if (i == 1 || i == 4 || i == 5) {
// kyberelastic mixin not added to fantom yet
if (i == 4) {
continue;
}
vm.selectFork(forkIds[chains[i]]);

View File

@@ -33,7 +33,7 @@ contract SwapEthForERC20Test is Test, ForkUtils, TestUtils {
_setup();
}
function test_swapEthForERC20OnUniswap() public {
function swapEthForERC20OnUniswap() public {
log_string("SwapEthForERC20OnUniswap");
for (uint256 i = 0; i < chains.length; i++) {
//skip fantom/avax failing test

View File

@@ -40,7 +40,7 @@
"test:all": "wsrun --fast-exit --serial --exclude-missing -p $PKG -c test",
"test:contracts": "wsrun --serial -p $(echo ${npm_package_config_contractsPackages} ${npm_package_config_ignoreTestsForPackages} | tr ' ' '\n' | sort | uniq -u | tr '\n' ' ') --fast-exit --exclude-missing -c test",
"test:contracts:all": "wsrun --serial -p ${npm_package_config_contractsPackages} --fast-exit --exclude-missing -c test",
"test:links": "yarn check-md --ignore **/forge-std/README.md,**/lib/openzeppelin-contracts,**/node_modules",
"test:links": "yarn check-md --ignore **/forge-std/README.md,**/lib/openzeppelin-contracts,**/node_modules,**/lib",
"generate_doc": "node ./node_modules/@0x/monorepo-scripts/lib/doc_generate.js --config ./doc-gen-config.json",
"upload_md_docs": "aws s3 rm --recursive s3://docs-markdown; wsrun --exclude-missing -c s3:sync_md_docs",
"diff_md_docs:ci": "wsrun --exclude-missing -c diff_docs",
@@ -93,5 +93,7 @@
"resolutions": {
"**/bignumber.js": "^9.0.2"
},
"dependencies": {}
"dependencies": {
"@openzeppelin/contracts": "^4.8.1"
}
}

View File

@@ -1,4 +1,14 @@
[
{
"version": "8.2.0",
"changes": [
{
"note": "Upgrade Polygon, Avalanche, Arbitrum, and Optimism FillQuoteTransformers to remove Aave V3 L2 Encoding and support KyberElastic",
"pr": 678
}
],
"timestamp": 1678410794
},
{
"version": "8.1.0",
"changes": [

View File

@@ -6,6 +6,9 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v8.2.0 - _March 10, 2023_
* Upgrade Polygon, Avalanche, Arbitrum, and Optimism FillQuoteTransformers to remove Aave V3 L2 Encoding and support KyberElastic (#678)
## v8.1.0 - _March 1, 2023_
* Upgrade Mainnet and Polygon FillQuoteTransformers to support KyberElastic (#669)

View File

@@ -110,7 +110,7 @@
"wethTransformer": "0xe309d011cc6f189a3e8dcba85922715a019fed38",
"payTakerTransformer": "0xed8932ca083e1ef1960dea875a132926e6b242ab",
"affiliateFeeTransformer": "0xf79071e2f860d48a08fd7e091d4b126a1d757148",
"fillQuoteTransformer": "0x5fe5885aedc42cd51073c7e5af841e3a11556653",
"fillQuoteTransformer": "0xcc46d6a09cbdc75f2f174493c33cab45492d868b",
"positiveSlippageFeeTransformer": "0x8f5e7188f443a9a8dc180f4618fd23915043ea15"
}
},
@@ -156,7 +156,7 @@
"wethTransformer": "0x9b8b52391071d71cd4ad1e61d7f273268fa34c6c",
"payTakerTransformer": "0xb9a4c32547bc3cdc2ee2fb13cc1a0717dac9888f",
"affiliateFeeTransformer": "0x105679f99d668001370b4621ad8648ac570c860f",
"fillQuoteTransformer": "0x3fad2c4a73b9a8f263b2749cd266e43c99b988fa",
"fillQuoteTransformer": "0x7991f2c35ab19472dbfb6f27593f7f6f38fb3eab",
"positiveSlippageFeeTransformer": "0xadbfdc58a24b6dbc16f21541800f43dd6e282250"
}
},
@@ -225,7 +225,7 @@
"wethTransformer": "0x02ce7af6520e2862f961f5d7eda746642865179c",
"payTakerTransformer": "0xa6c3ca183a67fcb4299fb4199c12ca74874ca489",
"affiliateFeeTransformer": "0x3102aea537ecb6f164550b094663c82a8c53a972",
"fillQuoteTransformer": "0xfead4c05220a8fddc74008ae43199badc3becaf9",
"fillQuoteTransformer": "0xd140adb61d4e3e3978d4f32ac6b92240ff6e3a6e",
"positiveSlippageFeeTransformer": "0x9a4947d3fb77a7afc2c9cd6714bbae96dddde059"
}
},
@@ -248,7 +248,7 @@
"wethTransformer": "0x10e968968f49dd66a5efeebbb2edcb9c49c4fc49",
"payTakerTransformer": "0xd81e65fc9bb7323bdbef8b2cdddd3b83fe41d630",
"affiliateFeeTransformer": "0x970e318b8f074c20bf0cee06970f01dc7a761e50",
"fillQuoteTransformer": "0x2142414a2c19b4dc4ec050eb1391d1d49d096da1",
"fillQuoteTransformer": "0x5d3a221bad31c3f3c07bea2f1de9b3ec17664b69",
"positiveSlippageFeeTransformer": "0x20f935b037e8490d8027f2751f9452725eee01ad"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contract-addresses",
"version": "8.1.0",
"version": "8.2.0",
"engines": {
"node": ">=6.12"
},

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1678410794,
"version": "13.22.18",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1677693479,
"version": "13.22.17",

View File

@@ -6,6 +6,9 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v13.22.18 - _March 10, 2023_
* Dependencies updated
## v13.22.17 - _March 1, 2023_
* Dependencies updated

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/contract-wrappers",
"version": "13.22.17",
"version": "13.22.18",
"engines": {
"node": ">=6.12"
},
@@ -59,7 +59,7 @@
"dependencies": {
"@0x/assert": "^3.0.35",
"@0x/base-contract": "^7.0.0",
"@0x/contract-addresses": "^8.1.0",
"@0x/contract-addresses": "^8.2.0",
"@0x/json-schemas": "^6.4.4",
"@0x/types": "^3.3.6",
"@0x/utils": "^7.0.0",

View File

@@ -1,4 +1,13 @@
[
{
"timestamp": 1678410794,
"version": "11.18.1",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "11.18.0",
"changes": [

View File

@@ -6,6 +6,9 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v11.18.1 - _March 10, 2023_
* Dependencies updated
## v11.18.0 - _March 1, 2023_
* Add KyberElastic support

View File

@@ -1,6 +1,6 @@
{
"name": "@0x/protocol-utils",
"version": "11.18.0",
"version": "11.18.1",
"engines": {
"node": ">=6.12"
},
@@ -62,8 +62,8 @@
},
"dependencies": {
"@0x/assert": "^3.0.35",
"@0x/contract-addresses": "^8.1.0",
"@0x/contract-wrappers": "^13.22.17",
"@0x/contract-addresses": "^8.2.0",
"@0x/contract-wrappers": "^13.22.18",
"@0x/json-schemas": "^6.4.4",
"@0x/subproviders": "^7.0.0",
"@0x/utils": "^7.0.0",

View File

@@ -2775,6 +2775,11 @@
dependencies:
"@octokit/openapi-types" "^12.11.0"
"@openzeppelin/contracts@^4.8.1":
version "4.8.1"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.1.tgz#709cfc4bbb3ca9f4460d60101f15dac6b7a2d5e4"
integrity sha512-xQ6eUZl+RDyb/FiZe1h+U7qr/f4p/SrTSQcTPH2bjur3C5DbuW/zFgCU/b1P/xcIaEqJep+9ju4xDRi3rmChdQ==
"@sindresorhus/slugify@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/slugify/-/slugify-0.8.0.tgz#5550b7fa064f3a8a82651463ad635378054c72d0"