├── .circleci └── config.yml ├── .env.example ├── .eslintignore ├── .eslintrc ├── .github └── ISSUE_TEMPLATE │ └── feature-template.md ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── .solhintignore ├── LICENSE ├── README.md ├── VoltOracle.md ├── audits ├── Daniel-Luca-Audit-Report-9-18-2022.pdf ├── Volt Protocol - Zellic Audit Report.pdf ├── Volt_MythX_analysis.pdf ├── abg-volt-audit-kyle.pdf ├── abg-volt-audit-russel.pdf └── venue-audits │ ├── Compound Finance Security Report.pdf │ ├── Morpho Audit Report.pdf │ ├── Morpho Contracts Layout@2x.png │ ├── compound.md │ ├── morpho.md │ └── morpho_logic.png ├── contracts ├── .prettierrc ├── Constants.sol ├── core │ ├── Core.sol │ ├── ICore.sol │ ├── IPermissions.sol │ ├── IPermissionsRead.sol │ ├── L2Core.sol │ ├── Permissions.sol │ └── TribeRoles.sol ├── external │ ├── Decimal.sol │ └── IWETH.sol ├── libs │ └── CoreRefPausableLib.sol ├── mock │ ├── ERC20HoldingPCVDeposit.sol │ ├── ForceEth.sol │ ├── IERC20HoldingPCVDeposit.sol │ ├── MockCToken.sol │ ├── MockCore.sol │ ├── MockCoreRef.sol │ ├── MockERC20.sol │ ├── MockMorpho.sol │ ├── MockMorphoMaliciousReentrancy.sol │ ├── MockPCVDepositV2.sol │ ├── MockPCVOracle.sol │ ├── MockPSM.sol │ └── MockRateLimitedV2.sol ├── oracle │ ├── IOracle.sol │ ├── IOraclePassThrough.sol │ ├── IScalingPriceOracle.sol │ ├── IVoltSystemOracle.sol │ ├── OraclePassThrough.sol │ └── VoltSystemOracle.sol ├── pcv │ ├── IFeiPCVGuardian.sol │ ├── IPCVDeposit.sol │ ├── IPCVDepositBalances.sol │ ├── IPCVGuardAdmin.sol │ ├── IPCVGuardian.sol │ ├── PCVDeposit.sol │ ├── PCVGuardAdmin.sol │ ├── PCVGuardian.sol │ ├── compound │ │ ├── CErc20.sol │ │ ├── CToken.sol │ │ ├── CompoundPCVDepositBase.sol │ │ ├── CompoundPCVRouter.sol │ │ └── ERC20CompoundPCVDeposit.sol │ ├── maker │ │ ├── IDSSPSM.sol │ │ ├── IMakerRouter.sol │ │ └── MakerRouter.sol │ ├── morpho │ │ ├── ICompound.sol │ │ ├── ILens.sol │ │ ├── IMorpho.sol │ │ ├── IPCVOracle.sol │ │ └── MorphoCompoundPCVDeposit.sol │ └── utils │ │ ├── ERC20Allocator.sol │ │ └── IERC20Allocator.sol ├── peg │ ├── INonCustodialPSM.sol │ ├── IPegStabilityModule.sol │ ├── IPriceBound.sol │ ├── PegStabilityModule.sol │ └── PriceBoundPSM.sol ├── refs │ ├── CoreRef.sol │ ├── ICoreRef.sol │ ├── IOracleRef.sol │ └── OracleRef.sol ├── test │ ├── integration │ │ ├── IntegrationTestCompoundPCVDeposits.t.sol │ │ ├── IntegrationTestPCVGuardian.t.sol │ │ ├── IntegrationTestPriceBoundPSM.t.sol │ │ ├── IntegrationTestPriceBoundPSMDai.t.sol │ │ ├── IntegrationTestPriceBoundPSMUSDC.t.sol │ │ ├── IntegrationTestTimelockController.t.sol │ │ ├── IntegrationTestVIP15.sol │ │ ├── README.md │ │ ├── fixtures │ │ │ ├── ArbitrumAddresses.sol │ │ │ └── MainnetAddresses.sol │ │ ├── utils │ │ │ ├── AllArbitrumRoles.sol │ │ │ ├── AllMainnetRoles.sol │ │ │ ├── AllRoles.sol │ │ │ ├── AllRolesConfig.sol │ │ │ ├── ITimelockSimulation.sol │ │ │ ├── KArrayTree.sol │ │ │ ├── MintRedeemVerification.sol │ │ │ ├── OracleVerification.sol │ │ │ ├── PCVGuardVerification.sol │ │ │ ├── PCVGuardianWhitelist.sol │ │ │ ├── RoleHierarchy.sol │ │ │ ├── RoleHierarchyArbitrum.sol │ │ │ ├── RoleHierarchyMainnet.sol │ │ │ ├── RoleTesting.sol │ │ │ └── TimelockSimulation.sol │ │ └── vip │ │ │ ├── IVIP.sol │ │ │ ├── Runner.sol │ │ │ ├── examples │ │ │ ├── vip_x_grant.sol │ │ │ ├── vip_x_grant_succeed.sol │ │ │ └── vip_x_transfer.sol │ │ │ ├── vip10.sol │ │ │ ├── vip11.sol │ │ │ ├── vip12.sol │ │ │ ├── vip14.sol │ │ │ ├── vip14a.sol │ │ │ ├── vip15.sol │ │ │ ├── vip2.sol │ │ │ ├── vip4.sol │ │ │ ├── vip5.sol │ │ │ ├── vip6.sol │ │ │ ├── vip7.sol │ │ │ ├── vip8.sol │ │ │ └── vip9.sol │ ├── invariant │ │ └── InvariantTestMorphoPCVDeposit.t.sol │ └── unit │ │ ├── Volt.t.sol │ │ ├── core │ │ ├── Core.t.sol │ │ └── L2Core.t.sol │ │ ├── oracle │ │ ├── OraclePassThrough.t.sol │ │ └── VoltSystemOracle.t.sol │ │ ├── pcv │ │ ├── ERC20HoldingPCVDeposit.t.sol │ │ ├── PCVGuardAdmin.t.sol │ │ ├── PCVGuardian.t.sol │ │ ├── morpho │ │ │ └── MorphoCompoundPCVDeposit.t.sol │ │ └── utils │ │ │ ├── ERC20Allocator.t.sol │ │ │ └── ERC20AllocatorConnector.t.sol │ │ └── utils │ │ ├── DSInvariantTest.sol │ │ ├── DSTest.sol │ │ ├── Deviation.t.sol │ │ ├── Fixtures.sol │ │ ├── KArrayTree.t.sol │ │ ├── RateLimitedV2.t.sol │ │ ├── StdLib.sol │ │ └── Vm.sol ├── utils │ ├── Deviation.sol │ ├── IGlobalRateLimitedMinter.sol │ ├── IMultiRateLimited.sol │ ├── OtcEscrow.sol │ ├── RateLimited.sol │ ├── RateLimitedV2.sol │ └── Timed.sol ├── vcon │ └── Vcon.sol └── volt │ ├── IVolt.sol │ ├── Volt.sol │ └── minter │ ├── IVoltTimedMinter.sol │ └── RateLimitedMinter.sol ├── forge-std ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── foundry.toml ├── lib │ └── ds-test │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── default.nix │ │ ├── demo │ │ └── demo.sol │ │ ├── package.json │ │ └── src │ │ └── test.sol ├── package.json ├── src │ ├── Common.sol │ ├── Components.sol │ ├── Script.sol │ ├── StdAssertions.sol │ ├── StdCheats.sol │ ├── StdError.sol │ ├── StdJson.sol │ ├── StdMath.sol │ ├── StdStorage.sol │ ├── StdUtils.sol │ ├── Test.sol │ ├── Vm.sol │ ├── console.sol │ ├── console2.sol │ └── interfaces │ │ ├── IERC1155.sol │ │ ├── IERC165.sol │ │ ├── IERC20.sol │ │ ├── IERC4626.sol │ │ └── IERC721.sol └── test │ ├── StdAssertions.t.sol │ ├── StdCheats.t.sol │ ├── StdError.t.sol │ ├── StdMath.t.sol │ ├── StdStorage.t.sol │ ├── StdUtils.t.sol │ └── fixtures │ └── broadcast.log.json ├── foundry.toml ├── governance-docs └── VIP-2.md ├── hardhat.config.ts ├── mocha-reporter-config.json ├── package.json ├── proposals ├── dao │ ├── vip_1.ts │ ├── vip_10.ts │ ├── vip_11.ts │ ├── vip_11_arbitrum.ts │ ├── vip_12.ts │ ├── vip_14.ts │ ├── vip_14_arbitrum.ts │ ├── vip_15.ts │ ├── vip_2.ts │ ├── vip_2_arbitrum.ts │ ├── vip_3.ts │ ├── vip_6_arbitrum.ts │ ├── vip_7.ts │ ├── vip_8.ts │ ├── vip_9.ts │ └── vip_x.ts ├── vip_1.ts ├── vip_10.ts ├── vip_11.ts ├── vip_11_arbitrum.ts ├── vip_12.ts ├── vip_14.ts ├── vip_14_arbitrum.ts ├── vip_2.ts ├── vip_2_arbitrum.ts ├── vip_3.ts ├── vip_4.ts ├── vip_4_arbitrum.ts ├── vip_5.ts ├── vip_6_arbitrum.ts ├── vip_7.ts ├── vip_8.ts ├── vip_9.ts └── vip_x.ts ├── protocol-configuration ├── mainnetAddresses.ts └── networksForVerification.ts ├── remappings.txt ├── scripts ├── Config.ts ├── L2Config.ts ├── deploy │ └── migrations.ts ├── grlmNCPSMDeploy.ts ├── l2SPOdeploy.ts ├── l2configuration.ts ├── l2deploy.ts ├── l2validation.ts ├── l2verifyBlockExplorer.ts ├── oracleDeploy.ts ├── otcEscrow.ts ├── pcvDepositDeploy.ts ├── pcvGuardianDeploy.ts ├── priceBoundPSMDeploy.ts ├── priceBoundPSMDeployUSDC.ts ├── simulation │ ├── constructProposal.ts │ ├── constructProposalCalldata.ts │ ├── getProposalCalldata.ts │ └── simulateTimelockProposal.ts ├── systemDeploy.ts ├── timelockDeploy.ts └── utils │ ├── checkProposal.ts │ └── loadContracts.ts ├── slither.config.json ├── slither ├── morpho-maple-slither-10-18.txt ├── slither-10-21-morpho.txt ├── slither-10-22-morpho.txt ├── slither-10-24-morpho.txt └── slitheroutput.txt ├── test └── helpers.ts ├── tsconfig.json ├── types └── types.ts └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | build: 4 | working_directory: ~/repo 5 | docker: 6 | - image: circleci/node:14 7 | resource_class: xlarge 8 | steps: 9 | - checkout 10 | - run: 11 | name: 'Update NPM' 12 | command: sudo npm install -g npm@6.13.4 13 | - run: 14 | name: Install dependencies 15 | command: npm install 16 | - save_cache: 17 | key: repo-{{ .Environment.CIRCLE_SHA1 }} 18 | paths: 19 | - ~/repo 20 | 21 | lint: 22 | working_directory: ~/repo 23 | docker: 24 | - image: circleci/node:14 25 | steps: 26 | - restore_cache: 27 | keys: 28 | - repo-{{ .Environment.CIRCLE_SHA1 }} 29 | - run: 30 | name: Run linter 31 | command: npm run lint 32 | 33 | test-forge: 34 | working_directory: ~/repo 35 | docker: 36 | - image: cimg/node:16.14 37 | resource_class: xlarge 38 | steps: 39 | - checkout 40 | - restore_cache: 41 | keys: 42 | - repo-{{ .Environment.CIRCLE_SHA1 }} 43 | - run: 44 | name: Setup env 45 | command: echo "export PATH=$PATH:$(pwd)/.circleci" >> /home/circleci/.bashrc 46 | - run: 47 | name: Finish setting up env 48 | command: echo "export PATH=$PATH:$(pwd)/.foundry/bin" >> /home/circleci/.bashrc 49 | - run: 50 | name: Install Foundry 51 | command: curl -L https://foundry.paradigm.xyz | bash; source /home/circleci/.bashrc; $HOME/.foundry/bin/foundryup 52 | - run: 53 | name: Run tests 54 | command: | 55 | source /home/circleci/.bashrc 56 | export PATH=$PATH:/home/circleci/.foundry/bin; npm run test && npm run test:integration && npm run test:invariant && npm run test:proposal:mainnet 57 | 58 | workflows: 59 | main: 60 | jobs: 61 | - build 62 | - lint: 63 | requires: 64 | - build 65 | - test-forge: 66 | requires: 67 | - build 68 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | TESTNET_MODE= 2 | MAINNET_AGEUR_FEI_UNISWAPV2=0xF89CE5eD65737dA8440411544b0499c9FaD323B2 3 | MAINNET_ANGLE_STABLEMASTER=0x5adDc89785D75C86aB939E9e15bfBBb7Fc086A87 4 | MAINNET_ANGLE_POOLMANAGER=0x53b981389Cfc5dCDA2DC2e903147B5DD0E985F44 5 | MAINNET_ANGLE_STAKINGREWARDS=0xBcb307F590972B1C3188b7916d2969Cf75309dc6 6 | MAINNET_BONDING_CURVE_ORACLE=0x89714d3AC9149426219a3568543200D1964101C4 7 | MAINNET_CORE=0x8d5ED43dCa8C2F7dFB20CF7b53CC7E593635d7b9 8 | MAINNET_ETH_BONDING_CURVE=0xe1578B4a32Eaefcd563a9E6d0dc02a4213f673B7 9 | MAINNET_ETH_PCV_DRIPPER=0xDa079A280FC3e33Eb11A78708B369D5Ca2da54fE 10 | MAINNET_ETH_RESERVE_STABILIZER=0xa08A721dFB595753FFf335636674D76C455B275C 11 | MAINNET_ETH_UNISWAP_PCV_CONTROLLER=0x0760dfe09bd6d04d0df9a60c51f01ecedceb5132 12 | MAINNET_ETH_UNISWAP_PCV_CONTROLLER_01=0x7a165F8518A9Ec7d5DA15f4B77B1d7128B5D9188 13 | MAINNET_ETH_UNISWAP_PCV_DEPOSIT_01=0x9b0C6299D08fe823f2C0598d97A1141507e4ad86 14 | MAINNET_ETH_UNISWAP_PCV_DEPOSIT=0x5d6446880FCD004c851EA8920a628c70Ca101117 15 | MAINNET_FEI_ETH_PAIR=0x94B0A3d511b6EcDb17eBF877278Ab030acb0A878 16 | MAINNET_FEI_REWARDS_DISTRIBUTOR=0xEf1a94AF192A88859EAF3F3D8C1B9705542174C5 17 | MAINNET_FEI_ROUTER=0x9271D303b57c204636C38Df0eD339b18Bf98f909 18 | MAINNET_FEI_STAKING_REWARDS=0x18305DaAe09Ea2F4D51fAa33318be5978D251aBd 19 | MAINNET_FEI_TRIBE_PAIR=0x9928e4046d7c6513326cCeA028cD3e7a91c7590A 20 | MAINNET_FEI=0x956F47F50A910163D8BF957Cf5846D573E7f87CA 21 | MAINNET_GENESIS_GROUP=0xBFfB152b9392e38CdDc275D818a3Db7FE364596b 22 | MAINNET_GOVERNOR_ALPHA=0xE087F94c3081e1832dC7a22B48c6f2b5fAaE579B 23 | MAINNET_TIMELOCK=0x639572471f2f318464dc01066a56867130e45E25 24 | MAINNET_TRIBE=0xc7283b66Eb1EB5FB86327f08e1B5816b0720212B 25 | MAINNET_UNISWAP_INCENTIVE=0xfe5b6c2a87A976dCe20130c423C679f4d6044cD7 26 | MAINNET_UNISWAP_ORACLE=0x087F35bd241e41Fc28E43f0E8C58d283DD55bD65 27 | MAINNET_UNISWAP_ROUTER=0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D 28 | MAINNET_RATIO_PCV_CONTROLLER=0xfC1aD6eb84351597cD3b9B65179633697d65B920 29 | MAINNET_ETH_PCV_ADAPTER_TO_DEPOSIT=0x4c52aD4Ad171a58B57592893c37Cc81655e11611 30 | MAINNET_ETH_PCV_ADAPTER=0xB72dDeD4Fa321e093E2083B596404A56ffC5b574 31 | MAINNET_PROPOSER=0xe0ac4559739bd36f0913fb0a3f5bfc19bcbacd52 32 | MAINNET_VOTER=0xB8f482539F2d3Ae2C9ea6076894df36D1f632775 33 | MAINNET_WETH=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 34 | MAINNET_RARI_POOL_8_COMPTROLLER=0xc54172e34046c1653d1920d40333dd358c7a1af4 35 | MAINNET_RARI_POOL_8_FEI=0xd8553552f8868C1Ef160eEdf031cF0BCf9686945 36 | MAINNET_RARI_POOL_8_TRIBE=0xFd3300A9a74b3250F1b2AbC12B47611171910b07 37 | MAINNET_RARI_POOL_8_ETH=0xbB025D470162CC5eA24daF7d4566064EE7f5F111 38 | MAINNET_RARI_POOL_8_DAI=0x7e9cE3CAa9910cc048590801e64174957Ed41d43 39 | ETHERSCAN_KEY=etherscan_key 40 | ARBISCAN_KEY=arbiscan_key 41 | NODE_OPTIONS=--max-old-space-size=4096 42 | ETH_PRIVATE_KEY=insert_private_key_here 43 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | artifacts/ 2 | build/ 3 | cache/ 4 | coverage/ 5 | dist/ 6 | lib/ 7 | node_modules/ 8 | proposals/dao/old/ 9 | proposals/dao/fip_x.ts 10 | scripts/deploy/old/ -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint", "prettier"], 5 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], 6 | "rules": { 7 | "prettier/prettier": 2, 8 | "@typescript-eslint/no-empty-function": "off" 9 | } 10 | } 11 | 12 | /*{ 13 | "env": { 14 | "browser": true, 15 | "commonjs": true, 16 | "es2021": true 17 | }, 18 | "extends": ["airbnb-base"], 19 | "parserOptions": { 20 | "ecmaVersion": 12 21 | }, 22 | "rules": { 23 | "prefer-arrow-callback": 0, 24 | "comma-dangle": 0, 25 | "func-names": 0, 26 | "space-before-function-paren": 0, 27 | "max-len": 0, 28 | "no-multi-spaces": 0, 29 | "no-trailing-spaces": 0, 30 | "object-curly-spacing": 0, 31 | "no-tabs": 0, 32 | "no-unused-expressions": 0, 33 | "no-sequences": 0, 34 | "no-mixed-spaces-and-tabs": 0, 35 | "no-await-in-loop": 0, 36 | "guard-for-in": 0, 37 | "no-restricted-syntax": 0, 38 | "no-underscore-dangle": 0, 39 | "no-unused-vars": 0, 40 | "no-empty-function": 0, 41 | "no-console": 0 42 | }, 43 | "globals": { 44 | "artifacts": "readonly", 45 | "describe": "readonly", 46 | "beforeEach": "readonly", 47 | "it": "readonly" 48 | } 49 | }*/ 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Template 3 | about: Security Checklist for Features 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Feature Description** 11 | A clear description of your feature, the problem it solves, and how it solves the problem. 12 | 13 | **Testing** 14 | -[ ] Unit Testing 15 | -[ ] Integration Testing 16 | -[ ] Fuzz Testing 17 | -[ ] Governance Simulation Framework 18 | 19 | **Security Checklist** 20 | -[ ] No Reentrancy possible 21 | -[ ] Check-Effects-Interaction pattern is followed 22 | -[ ] Look at areas that interface with external contracts and ensure all assumptions about them are valid like share price only increases, etc. 23 | -[ ] Do a generic line-by-line review of the contracts. 24 | -[ ] Do another review from the perspective of every actor in the threat model. 25 | -[ ] Glance over the project's tests + code coverage and look deeper at areas lacking coverage. 26 | -[ ] Run tools like Slither/Solhint and review their output. 27 | -[ ] Look at related projects and their audits to check for any similar issues or oversights. 28 | -[ ] Create a threat model and make a list of theoretical high level attack vectors. 29 | Write negative tests. E.g., if users should NOT be able to withdraw within 100 blocks of depositing, then write a test where a users tries to withdraw early and make sure the user's attempt fails. 30 | -[ ] Write down your security assumptions. This doesn't have to be super formal. E.g., "We assume that the owner is not malicious, that the Chainlink oracles won't lie about the token price, that the Chainlink oracles will always report the price at least once every 24 hours, that all tokens that the owner approves are ERC20-compliant tokens with no transfer hooks, and that there will never be a chain reorg of more than 30 blocks". This helps you understand how things could possibly go wrong even if your contracts are bug-free. Auditors may also be able to tell you whether or not your assumptions are realistic. They may also be able point out assumptions you're making that you didn't realize you were making. 31 | -[ ] Areas of concern for review were found to be secure 32 | -[ ] Any public function that can be made external should be made external. This is not just a gas consideration, but it also reduces the cognitive overhead for auditors because it reduces the number of possible contexts in which the function can be called. 33 | -[ ] Use the latest major version of Solidity. 34 | 35 | **Audit Log** 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .eslintcache 2 | .env 3 | node_modules 4 | *.DS_Store* 5 | .vscode/ 6 | build/ 7 | coverage.json 8 | coverage/ 9 | tenderly.yaml 10 | artifacts 11 | cache 12 | types/contracts 13 | .eslintcache 14 | .xml 15 | yarn.lock 16 | todo 17 | out/ 18 | docs/ -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 120 6 | } -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "configureYulOptimizer": true 3 | }; -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "compiler-version" : ["warn", "^0.8.4"], 6 | "not-rely-on-time" : "off", 7 | "reason-string" : "off", 8 | "no-empty-blocks" : "off", 9 | "prettier/prettier": "error" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | contracts/mock/ 3 | contracts/Migrations.sol -------------------------------------------------------------------------------- /VoltOracle.md: -------------------------------------------------------------------------------- 1 | # Volt System Oracle 2 | 3 | The Volt System Oracle is an oracle that tracks the yield earned in the underlying venues of PCV, and then passes that yield on to Volt holders by setting a rate and increasing the Volt target price. 4 | 5 | ## Volt Oracle Architecture 6 | 7 | The Volt System Oracle sits behind an Oracle Pass Through contract that the time-lock owns. This allows for a changing out of the underlying PCV venues in which the Volt System Oracle points to. A change will occur "at-will" given significant enough deviation of the rates. 8 | 9 | The following is the Volt System Oracle Formula where p equals price at the start of the period, p1 equals new price after all changes are applied, and t equals time. 10 | 11 | $$ 12 | \begin{align*} 13 | Δt &= min(currentTimestamp - startTime, compoundingPeriod) \\ 14 | Δp &= p \cdot \frac{interestRate}{10,000} \\ 15 | p_{1} &= p + (\frac{Δp \cdot Δt}{compoundingPeriod}) 16 | \end{align*} 17 | $$ 18 | 19 | Compounding period for the Volt System Oracle is 30.42 days and does not take leap years into account. 20 | 21 | Interest accrues per second as long as block.timestamp is greater than start time. After the period is over, the function `compoundInterest` can be called, which sets the start time to the previous start time plus the period length. This means that if a previous period had not been compounded, `compoundInterest` can be called multiple times to catch the oracle price up to where it should be. Interest will not be factored into the current price if `compoundInterest` is not called after the period ends. This is expected behavior as this contract is meant to be as simple as possible. 22 | -------------------------------------------------------------------------------- /audits/Daniel-Luca-Audit-Report-9-18-2022.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/volt-protocol/volt-protocol-core/96cc40b6445ad417b603a832abafdf46bce39708/audits/Daniel-Luca-Audit-Report-9-18-2022.pdf -------------------------------------------------------------------------------- /audits/Volt Protocol - Zellic Audit Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/volt-protocol/volt-protocol-core/96cc40b6445ad417b603a832abafdf46bce39708/audits/Volt Protocol - Zellic Audit Report.pdf -------------------------------------------------------------------------------- /audits/Volt_MythX_analysis.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/volt-protocol/volt-protocol-core/96cc40b6445ad417b603a832abafdf46bce39708/audits/Volt_MythX_analysis.pdf -------------------------------------------------------------------------------- /audits/abg-volt-audit-kyle.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/volt-protocol/volt-protocol-core/96cc40b6445ad417b603a832abafdf46bce39708/audits/abg-volt-audit-kyle.pdf -------------------------------------------------------------------------------- /audits/abg-volt-audit-russel.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/volt-protocol/volt-protocol-core/96cc40b6445ad417b603a832abafdf46bce39708/audits/abg-volt-audit-russel.pdf -------------------------------------------------------------------------------- /audits/venue-audits/Compound Finance Security Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/volt-protocol/volt-protocol-core/96cc40b6445ad417b603a832abafdf46bce39708/audits/venue-audits/Compound Finance Security Report.pdf -------------------------------------------------------------------------------- /audits/venue-audits/Morpho Audit Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/volt-protocol/volt-protocol-core/96cc40b6445ad417b603a832abafdf46bce39708/audits/venue-audits/Morpho Audit Report.pdf -------------------------------------------------------------------------------- /audits/venue-audits/Morpho Contracts Layout@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/volt-protocol/volt-protocol-core/96cc40b6445ad417b603a832abafdf46bce39708/audits/venue-audits/Morpho Contracts Layout@2x.png -------------------------------------------------------------------------------- /audits/venue-audits/morpho_logic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/volt-protocol/volt-protocol-core/96cc40b6445ad417b603a832abafdf46bce39708/audits/venue-audits/morpho_logic.png -------------------------------------------------------------------------------- /contracts/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.sol", 5 | "options": { 6 | "printWidth": 80, 7 | "tabWidth": 4, 8 | "useTabs": false, 9 | "singleQuote": false, 10 | "bracketSpacing": false, 11 | "explicitTypes": "always" 12 | } 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /contracts/Constants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {IWETH} from "./external/IWETH.sol"; 5 | 6 | library Constants { 7 | /// @notice the denominator for basis points granularity (10,000) 8 | uint256 public constant BASIS_POINTS_GRANULARITY = 10_000; 9 | 10 | /// @notice the denominator for basis points granularity (10,000) expressed as an int data type 11 | int256 public constant BP_INT = int256(BASIS_POINTS_GRANULARITY); 12 | 13 | uint256 public constant ONE_YEAR = 365.25 days; 14 | 15 | int256 public constant ONE_YEAR_INT = int256(ONE_YEAR); 16 | 17 | /// @notice WETH9 address 18 | IWETH public constant WETH = 19 | IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); 20 | 21 | /// @notice WETH9 address 22 | IWETH public constant ARBITRUM_WETH = 23 | IWETH(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1); 24 | 25 | /// @notice USD stand-in address 26 | address public constant USD = 0x1111111111111111111111111111111111111111; 27 | 28 | /// @notice Wei per ETH, i.e. 10**18 29 | uint256 public constant ETH_GRANULARITY = 1e18; 30 | 31 | /// @notice number of decimals in ETH, 18 32 | uint256 public constant ETH_DECIMALS = 18; 33 | } 34 | -------------------------------------------------------------------------------- /contracts/core/Core.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {Vcon} from "../vcon/Vcon.sol"; 5 | import {IVolt, Volt, IERC20} from "../volt/Volt.sol"; 6 | import {ICore} from "./ICore.sol"; 7 | import {Permissions} from "./Permissions.sol"; 8 | import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; 9 | 10 | /// @title Source of truth for VOLT Protocol 11 | /// @author Fei Protocol 12 | /// @notice maintains roles, access control, Volt, Vcon, and the Vcon treasury 13 | contract Core is ICore, Permissions, Initializable { 14 | /// @notice the address of the FEI contract 15 | IVolt public override volt; 16 | 17 | /// @notice the address of the Vcon contract 18 | IERC20 public override vcon; 19 | 20 | function init() external initializer { 21 | volt = new Volt(address(this)); 22 | /// msg.sender already has the VOLT Minting abilities, so grant them governor as well 23 | _setupGovernor(msg.sender); 24 | } 25 | 26 | /// @notice governor only function to set the VCON token 27 | function setVcon(IERC20 _vcon) external onlyGovernor { 28 | vcon = _vcon; 29 | 30 | emit VconUpdate(_vcon); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/core/ICore.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {IPermissions} from "./IPermissions.sol"; 5 | import {IVolt, IERC20} from "../volt/IVolt.sol"; 6 | 7 | /// @title Core Interface 8 | /// @author Fei Protocol 9 | interface ICore is IPermissions { 10 | // ----------- Events ----------- 11 | event VoltUpdate(IERC20 indexed _volt); 12 | event VconUpdate(IERC20 indexed _vcon); 13 | 14 | // ----------- Getters ----------- 15 | 16 | function volt() external view returns (IVolt); 17 | 18 | function vcon() external view returns (IERC20); 19 | } 20 | -------------------------------------------------------------------------------- /contracts/core/IPermissions.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import "@openzeppelin/contracts/access/AccessControl.sol"; 5 | import "./IPermissionsRead.sol"; 6 | 7 | /// @title Permissions interface 8 | /// @author Fei Protocol 9 | interface IPermissions is IAccessControl, IPermissionsRead { 10 | // ----------- Governor only state changing api ----------- 11 | 12 | function createRole(bytes32 role, bytes32 adminRole) external; 13 | 14 | function grantMinter(address minter) external; 15 | 16 | function grantBurner(address burner) external; 17 | 18 | function grantPCVController(address pcvController) external; 19 | 20 | function grantGovernor(address governor) external; 21 | 22 | function grantGuardian(address guardian) external; 23 | 24 | function revokeMinter(address minter) external; 25 | 26 | function revokeBurner(address burner) external; 27 | 28 | function revokePCVController(address pcvController) external; 29 | 30 | function revokeGovernor(address governor) external; 31 | 32 | function revokeGuardian(address guardian) external; 33 | 34 | // ----------- Revoker only state changing api ----------- 35 | 36 | function revokeOverride(bytes32 role, address account) external; 37 | 38 | // ----------- Getters ----------- 39 | 40 | function GUARDIAN_ROLE() external view returns (bytes32); 41 | 42 | function GOVERN_ROLE() external view returns (bytes32); 43 | 44 | function BURNER_ROLE() external view returns (bytes32); 45 | 46 | function MINTER_ROLE() external view returns (bytes32); 47 | 48 | function PCV_CONTROLLER_ROLE() external view returns (bytes32); 49 | } 50 | -------------------------------------------------------------------------------- /contracts/core/IPermissionsRead.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | /// @title Permissions Read interface 5 | /// @author Fei Protocol 6 | interface IPermissionsRead { 7 | // ----------- Getters ----------- 8 | 9 | function isBurner(address _address) external view returns (bool); 10 | 11 | function isMinter(address _address) external view returns (bool); 12 | 13 | function isGovernor(address _address) external view returns (bool); 14 | 15 | function isGuardian(address _address) external view returns (bool); 16 | 17 | function isPCVController(address _address) external view returns (bool); 18 | } 19 | -------------------------------------------------------------------------------- /contracts/core/L2Core.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {Vcon} from "../vcon/Vcon.sol"; 5 | import {IVolt, Volt, IERC20} from "../volt/Volt.sol"; 6 | import {ICore} from "./ICore.sol"; 7 | import {Permissions} from "./Permissions.sol"; 8 | 9 | /// @title Source of truth for VOLT Protocol on L2 10 | /// @author Volt Protocol 11 | /// @notice maintains roles, access control, Volt, Vcon, and the Vcon treasury 12 | contract L2Core is ICore, Permissions { 13 | /// @notice the address of the VOLT contract 14 | IVolt public immutable override volt; 15 | 16 | /// @notice the address of the Vcon contract 17 | IERC20 public override vcon; 18 | 19 | constructor(IVolt _volt) { 20 | volt = _volt; 21 | /// give msg.sender the governor role 22 | _setupGovernor(msg.sender); 23 | } 24 | 25 | /// @notice governor only function to set the VCON token 26 | function setVcon(IERC20 _vcon) external onlyGovernor { 27 | vcon = _vcon; 28 | 29 | emit VconUpdate(_vcon); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contracts/core/TribeRoles.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | /** 5 | @title Tribe DAO ACL Roles 6 | @notice Holds a complete list of all roles which can be held by contracts inside Tribe DAO. 7 | Roles are broken up into 3 categories: 8 | * Major Roles - the most powerful roles in the Tribe DAO which should be carefully managed. 9 | * Admin Roles - roles with management capability over critical functionality. Should only be held by automated or optimistic mechanisms 10 | * Minor Roles - operational roles. May be held or managed by shorter optimistic timelocks or trusted multisigs. 11 | */ 12 | library TribeRoles { 13 | /*/////////////////////////////////////////////////////////////// 14 | Major Roles 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | /// @notice the ultimate role of Tribe. Controls all other roles and protocol functionality. 18 | bytes32 internal constant GOVERNOR = keccak256("GOVERN_ROLE"); 19 | 20 | /// @notice the protector role of Tribe. Admin of pause, veto, revoke, and minor roles 21 | bytes32 internal constant GUARDIAN = keccak256("GUARDIAN_ROLE"); 22 | 23 | /// @notice the role which can arbitrarily move PCV in any size from any contract 24 | bytes32 internal constant PCV_CONTROLLER = keccak256("PCV_CONTROLLER_ROLE"); 25 | 26 | /// @notice can mint FEI arbitrarily 27 | bytes32 internal constant MINTER = keccak256("MINTER_ROLE"); 28 | 29 | ///@notice is able to withdraw whitelisted PCV deposits to a safe address 30 | bytes32 internal constant PCV_GUARD = keccak256("PCV_GUARD_ROLE"); 31 | 32 | /*/////////////////////////////////////////////////////////////// 33 | Admin Roles 34 | //////////////////////////////////////////////////////////////*/ 35 | 36 | /// @notice manages the granting and revocation of PCV Guard roles 37 | bytes32 internal constant PCV_GUARD_ADMIN = 38 | keccak256("PCV_GUARD_ADMIN_ROLE"); 39 | 40 | /*/////////////////////////////////////////////////////////////// 41 | Minor Roles 42 | //////////////////////////////////////////////////////////////*/ 43 | 44 | /// @notice capable of changing PCV Deposit and Global Rate Limited Minter in the PSM 45 | bytes32 internal constant PSM_ADMIN_ROLE = keccak256("PSM_ADMIN_ROLE"); 46 | } 47 | -------------------------------------------------------------------------------- /contracts/external/IWETH.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.13; 2 | 3 | interface IWETH { 4 | function deposit() external payable; 5 | 6 | function transfer(address to, uint256 value) external returns (bool); 7 | 8 | function withdraw(uint256) external; 9 | } 10 | -------------------------------------------------------------------------------- /contracts/libs/CoreRefPausableLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {CoreRef} from "../refs/CoreRef.sol"; 5 | import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol"; 6 | 7 | /// @title PausableLib 8 | /// @notice PausableLib is a library that can be used to pause and unpause contracts, among other utilities. 9 | /// @dev This library should only be used on contracts that implement CoreRef. 10 | library CoreRefPausableLib { 11 | function _pause(address _pauseableCoreRefAddress) internal { 12 | CoreRef(_pauseableCoreRefAddress).pause(); 13 | } 14 | 15 | function _unpause(address _pauseableCoreRefAddress) internal { 16 | CoreRef(_pauseableCoreRefAddress).unpause(); 17 | } 18 | 19 | function _paused( 20 | address _pauseableCoreRefAddres 21 | ) internal view returns (bool) { 22 | return CoreRef(_pauseableCoreRefAddres).paused(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/mock/ForceEth.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | contract ForceEth { 5 | constructor() payable {} 6 | 7 | receive() external payable {} 8 | 9 | function forceEth(address to) public { 10 | selfdestruct(payable(to)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/mock/IERC20HoldingPCVDeposit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity =0.8.13; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | 6 | /// @title ERC20HoldingPCVDeposit 7 | /// @notice PCVDeposit that is used to hold ERC20 tokens as a safe harbour. Deposit is a no-op 8 | interface IERC20HoldingPCVDeposit { 9 | /// @notice Token which the balance is reported in 10 | function token() external view returns (IERC20); 11 | 12 | /////// READ-ONLY Methods ///////////// 13 | 14 | /// @notice No-op deposit 15 | function deposit() external; 16 | 17 | /// @notice Withdraw underlying 18 | /// @param amountUnderlying of tokens withdrawn 19 | /// @param to the address to send PCV to 20 | function withdraw(address to, uint256 amountUnderlying) external; 21 | 22 | /// @notice Withdraw all of underlying 23 | /// @param to the address to send PCV to 24 | function withdrawAll(address to) external; 25 | 26 | /// @notice Wraps all ETH held by the contract to WETH. Permissionless, anyone can call it 27 | function wrapETH() external; 28 | } 29 | -------------------------------------------------------------------------------- /contracts/mock/MockCToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.13; 2 | 3 | contract MockCToken { 4 | address public underlying; 5 | 6 | constructor(address _underlying) { 7 | underlying = _underlying; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /contracts/mock/MockCore.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./../core/Permissions.sol"; 5 | import "../vcon/Vcon.sol"; 6 | import "../volt/Volt.sol"; 7 | 8 | import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; 9 | 10 | /// @title Mock Source of truth for Fei Protocol 11 | /// @author Fei Protocol 12 | /// @notice maintains roles, access control, fei, tribe, genesisGroup, and the TRIBE treasury 13 | contract MockCore is Permissions, Initializable { 14 | /// @notice the address of the FEI contract 15 | IVolt public volt; 16 | 17 | /// @notice the address of the TRIBE contract 18 | IERC20 public vcon; 19 | 20 | constructor() { 21 | uint256 chainId; 22 | assembly { 23 | chainId := chainid() 24 | } 25 | 26 | require(chainId != 1, "MockCore: cannot deploy to mainnet"); 27 | } 28 | 29 | function init(address recipient) external initializer { 30 | /// emulate the real core as much as possible 31 | _setupGovernor(msg.sender); 32 | 33 | Volt _volt = new Volt(address(this)); 34 | volt = IVolt(_volt); 35 | 36 | /// give all VCON to the recipient 37 | /// grant timelock the minter role 38 | Vcon _vcon = new Vcon(recipient, msg.sender); 39 | vcon = IERC20(address(_vcon)); 40 | 41 | _setupGovernor(msg.sender); 42 | } 43 | 44 | /// @notice checks if address is a minter 45 | /// @return true _address is a minter 46 | // only virtual for testing mock override 47 | function isMinter(address) external view virtual override returns (bool) { 48 | return true; 49 | } 50 | 51 | /// @notice checks if address is a burner 52 | /// @return true _address is a burner 53 | // only virtual for testing mock override 54 | function isBurner(address) external view virtual override returns (bool) { 55 | return true; 56 | } 57 | 58 | /// @notice checks if address is a controller 59 | /// @return true _address is a controller 60 | // only virtual for testing mock override 61 | function isPCVController( 62 | address 63 | ) external view virtual override returns (bool) { 64 | return true; 65 | } 66 | 67 | /// @notice checks if address is a governor 68 | /// @return true _address is a governor 69 | // only virtual for testing mock override 70 | function isGovernor(address) public view virtual override returns (bool) { 71 | return true; 72 | } 73 | 74 | /// @notice checks if address is a guardian 75 | /// @return true _address is a guardian 76 | // only virtual for testing mock override 77 | function isGuardian(address) public view virtual override returns (bool) { 78 | return true; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /contracts/mock/MockCoreRef.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import "../refs/CoreRef.sol"; 5 | 6 | contract MockCoreRef is CoreRef { 7 | constructor(address core) CoreRef(core) { 8 | _setContractAdminRole(keccak256("MOCK_CORE_REF_ADMIN")); 9 | } 10 | 11 | function testMinter() public view onlyMinter {} 12 | 13 | function testBurner() public view onlyBurner {} 14 | 15 | function testPCVController() public view onlyPCVController {} 16 | 17 | function testGovernor() public view onlyGovernor {} 18 | 19 | function testGuardian() public view onlyGuardianOrGovernor {} 20 | 21 | function testOnlyGovernorOrAdmin() public view onlyGovernorOrAdmin {} 22 | } 23 | -------------------------------------------------------------------------------- /contracts/mock/MockERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract MockERC20 is ERC20, ERC20Burnable { 8 | constructor() ERC20("MockToken", "MCT") {} 9 | 10 | function mint(address account, uint256 amount) public returns (bool) { 11 | _mint(account, amount); 12 | return true; 13 | } 14 | 15 | function mockBurn(address account, uint256 amount) public returns (bool) { 16 | _burn(account, amount); 17 | return true; 18 | } 19 | 20 | function approveOverride( 21 | address owner, 22 | address spender, 23 | uint256 amount 24 | ) public { 25 | _approve(owner, spender, amount); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/mock/MockMorpho.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.13; 2 | 3 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 4 | 5 | contract MockMorpho { 6 | IERC20 public immutable token; 7 | mapping(address => uint256) public balances; 8 | 9 | constructor(IERC20 _token) { 10 | token = _token; 11 | } 12 | 13 | function underlying() external view returns (address) { 14 | return address(token); 15 | } 16 | 17 | function withdraw(address, uint256 amount) external { 18 | balances[msg.sender] -= amount; 19 | token.transfer(msg.sender, amount); 20 | } 21 | 22 | function supply( 23 | address, 24 | address recipient, 25 | uint256 amountUnderlying 26 | ) external { 27 | token.transferFrom(msg.sender, address(this), amountUnderlying); 28 | balances[recipient] += amountUnderlying; 29 | } 30 | 31 | function setBalance(address to, uint256 amount) external { 32 | balances[to] = amount; 33 | } 34 | 35 | function claimRewards( 36 | address cToken, 37 | bool swapForMorpho 38 | ) external returns (uint256) {} 39 | 40 | function updateP2PIndexes(address) external {} 41 | 42 | function getCurrentSupplyBalanceInOf( 43 | address, 44 | address _user 45 | ) 46 | external 47 | view 48 | returns ( 49 | uint256 balanceOnPool, 50 | uint256 balanceInP2P, 51 | uint256 totalBalance 52 | ) 53 | { 54 | return (0, 0, balances[_user]); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /contracts/mock/MockMorphoMaliciousReentrancy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.13; 2 | 3 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 4 | import {MorphoCompoundPCVDeposit} from "contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol"; 5 | 6 | contract MockMorphoMaliciousReentrancy { 7 | IERC20 public immutable token; 8 | mapping(address => uint256) public balances; 9 | MorphoCompoundPCVDeposit public morphoCompoundPCVDeposit; 10 | 11 | constructor(IERC20 _token) { 12 | token = _token; 13 | } 14 | 15 | function underlying() external view returns (address) { 16 | return address(token); 17 | } 18 | 19 | function setMorphoCompoundPCVDeposit(address deposit) external { 20 | morphoCompoundPCVDeposit = MorphoCompoundPCVDeposit(deposit); 21 | } 22 | 23 | function withdraw(address, uint256) external { 24 | morphoCompoundPCVDeposit.accrue(); 25 | } 26 | 27 | function supply(address, address, uint256) external { 28 | morphoCompoundPCVDeposit.deposit(); 29 | } 30 | 31 | function setBalance(address to, uint256 amount) external { 32 | balances[to] = amount; 33 | } 34 | 35 | function claimRewards( 36 | address cToken, 37 | bool swapForMorpho 38 | ) external returns (uint256) {} 39 | 40 | function updateP2PIndexes(address) external { 41 | morphoCompoundPCVDeposit.accrue(); 42 | } 43 | 44 | function getCurrentSupplyBalanceInOf( 45 | address, 46 | address _user 47 | ) 48 | external 49 | view 50 | returns ( 51 | uint256 balanceOnPool, 52 | uint256 balanceInP2P, 53 | uint256 totalBalance 54 | ) 55 | { 56 | return (0, 0, balances[_user]); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /contracts/mock/MockPCVDepositV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import "../refs/CoreRef.sol"; 5 | import "../pcv/IPCVDeposit.sol"; 6 | 7 | contract MockPCVDepositV2 is IPCVDeposit, CoreRef { 8 | address public override balanceReportedIn; 9 | 10 | uint256 private resistantBalance; 11 | uint256 private resistantProtocolOwnedFei; 12 | 13 | constructor( 14 | address _core, 15 | address _token, 16 | uint256 _resistantBalance, 17 | uint256 _resistantProtocolOwnedFei 18 | ) CoreRef(_core) { 19 | balanceReportedIn = _token; 20 | resistantBalance = _resistantBalance; 21 | resistantProtocolOwnedFei = _resistantProtocolOwnedFei; 22 | } 23 | 24 | receive() external payable {} 25 | 26 | function set( 27 | uint256 _resistantBalance, 28 | uint256 _resistantProtocolOwnedFei 29 | ) public { 30 | resistantBalance = _resistantBalance; 31 | resistantProtocolOwnedFei = _resistantProtocolOwnedFei; 32 | } 33 | 34 | // gets the resistant token balance and protocol owned fei of this deposit 35 | function resistantBalanceAndVolt() 36 | external 37 | view 38 | override 39 | returns (uint256, uint256) 40 | { 41 | return (resistantBalance, resistantProtocolOwnedFei); 42 | } 43 | 44 | // IPCVDeposit V1 45 | function deposit() external override { 46 | resistantBalance = IERC20(balanceReportedIn).balanceOf(address(this)); 47 | } 48 | 49 | function withdraw(address to, uint256 amount) external override { 50 | IERC20(balanceReportedIn).transfer(to, amount); 51 | resistantBalance = IERC20(balanceReportedIn).balanceOf(address(this)); 52 | } 53 | 54 | function withdrawERC20( 55 | address token, 56 | address to, 57 | uint256 amount 58 | ) external override { 59 | IERC20(token).transfer(to, amount); 60 | } 61 | 62 | function withdrawETH( 63 | address payable to, 64 | uint256 amount 65 | ) external override onlyPCVController { 66 | to.transfer(amount); 67 | } 68 | 69 | function balance() external view override returns (uint256) { 70 | return IERC20(balanceReportedIn).balanceOf(address(this)); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /contracts/mock/MockPCVOracle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.13; 2 | 3 | contract MockPCVOracle { 4 | int256 public pcvAmount; 5 | 6 | /// @notice hook on PCV deposit, callable when pcv oracle is set 7 | /// updates the oracle with the new liquid balance delta 8 | function updateLiquidBalance(int256 pcvDelta) external { 9 | pcvAmount += pcvDelta; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /contracts/mock/MockPSM.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.8.13; 2 | 3 | contract MockPSM { 4 | address public underlying; 5 | 6 | constructor(address _underlying) { 7 | underlying = _underlying; 8 | } 9 | 10 | function setUnderlying(address newUnderlying) external { 11 | underlying = newUnderlying; 12 | } 13 | 14 | function balanceReportedIn() external view returns (address) { 15 | return underlying; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/mock/MockRateLimitedV2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.8.13; 2 | 3 | import "./../utils/RateLimitedV2.sol"; 4 | import "./../refs/CoreRef.sol"; 5 | 6 | contract MockRateLimitedV2 is RateLimitedV2 { 7 | constructor( 8 | address _core, 9 | uint256 _maxRateLimitPerSecond, 10 | uint128 _rateLimitPerSecond, 11 | uint128 _bufferCap 12 | ) 13 | RateLimitedV2(_maxRateLimitPerSecond, _rateLimitPerSecond, _bufferCap) 14 | CoreRef(_core) 15 | {} 16 | 17 | function depleteBuffer(uint256 amount) public { 18 | _depleteBuffer(amount); 19 | } 20 | 21 | function replenishBuffer(uint256 amount) public { 22 | _replenishBuffer(amount); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/oracle/IOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import "../external/Decimal.sol"; 5 | 6 | /// @title generic oracle interface for Fei Protocol 7 | /// @author Fei Protocol 8 | interface IOracle { 9 | // ----------- Events ----------- 10 | 11 | event Update(uint256 _peg); 12 | 13 | // ----------- State changing API ----------- 14 | 15 | function update() external; 16 | 17 | // ----------- Getters ----------- 18 | 19 | function read() external view returns (Decimal.D256 memory, bool); 20 | 21 | function isOutdated() external view returns (bool); 22 | } 23 | -------------------------------------------------------------------------------- /contracts/oracle/IOraclePassThrough.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {Decimal} from "../external/Decimal.sol"; 5 | import {IScalingPriceOracle} from "./IScalingPriceOracle.sol"; 6 | 7 | /// @notice interface to get data from the Scaling Price Oracle 8 | interface IOraclePassThrough { 9 | // ----------- Getters ----------- 10 | 11 | /// @notice reference to the scaling price oracle 12 | function scalingPriceOracle() external view returns (IScalingPriceOracle); 13 | 14 | /// @notice function to get the current oracle price for the OracleRef contract 15 | function read() 16 | external 17 | view 18 | returns (Decimal.D256 memory price, bool valid); 19 | 20 | /// @notice function to get the current oracle price for the entire system 21 | function getCurrentOraclePrice() external view returns (uint256); 22 | 23 | /// @notice function to get the current oracle price for the entire system 24 | function currPegPrice() external view returns (uint256); 25 | 26 | // ----------- Governor only state changing api ----------- 27 | 28 | /// @notice function to update the pointer to the scaling price oracle 29 | /// requires approval from both VOLT and FRAX governance to sign off on the change 30 | function updateScalingPriceOracle( 31 | IScalingPriceOracle newScalingPriceOracle 32 | ) external; 33 | 34 | /// @notice event emitted when the scaling price oracle is updated 35 | event ScalingPriceOracleUpdate( 36 | IScalingPriceOracle oldScalingPriceOracle, 37 | IScalingPriceOracle newScalingPriceOracle 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /contracts/oracle/IScalingPriceOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {Decimal} from "../external/Decimal.sol"; 5 | 6 | /// @notice contract that receives a chainlink price feed and then linearly interpolates that rate over 7 | /// a 1 month period into the VOLT price. Interest is compounded monthly when the rate is updated 8 | /// @author Elliot Friedman 9 | interface IScalingPriceOracle { 10 | /// @notice the time frame over which all changes in CPI data are applied 11 | /// 28 days was chosen as that is the shortest length of a month 12 | function TIMEFRAME() external view returns (uint256); 13 | 14 | /// @notice the maximum allowable deviation in basis points for a new chainlink oracle update 15 | /// only allow price changes by 20% in a month. 16 | /// Any change over this threshold in either direction will be rejected 17 | function MAXORACLEDEVIATION() external view returns (uint256); 18 | 19 | /// @notice get the current scaled oracle price 20 | /// applies the change smoothly over a 28 day period 21 | /// scaled by 18 decimals 22 | function getCurrentOraclePrice() external view returns (uint256); 23 | 24 | /// @notice current amount that oracle price is inflating/deflating by monthly in basis points 25 | function monthlyChangeRateBasisPoints() external view returns (int256); 26 | 27 | /// @notice oracle price. starts off at 1 scaled up by 18 decimals 28 | function oraclePrice() external view returns (uint256); 29 | 30 | /// @notice event when the monthly change rate is updated 31 | event CPIMonthlyChangeRateUpdate( 32 | int256 oldChangeRateBasisPoints, 33 | int256 newChangeRateBasisPoints 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /contracts/oracle/IVoltSystemOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | /// @notice interface for the Volt System Oracle 5 | interface IVoltSystemOracle { 6 | // ----------- Getters ----------- 7 | 8 | /// @notice function to get the current oracle price for the entire system 9 | function getCurrentOraclePrice() external view returns (uint256); 10 | 11 | /// @notice start time at which point interest will start accruing, and the 12 | /// current ScalingPriceOracle price will be snapshotted and saved 13 | function periodStartTime() external view returns (uint256); 14 | 15 | /// @notice oracle price. starts off at 1e18 and compounds monthly 16 | /// acts as an accumulator for interest earned in previous epochs 17 | /// returns the oracle price from the end of the last period 18 | function oraclePrice() external view returns (uint256); 19 | 20 | /// @notice current amount that oracle price is inflating by monthly in basis points 21 | /// does not support negative rates because PCV will not be deposited into negatively 22 | /// yielding venues. 23 | function monthlyChangeRateBasisPoints() external view returns (uint256); 24 | 25 | /// @notice the time frame over which all changes in the APR are applied 26 | /// one month was chosen because this is a temporary oracle 27 | function TIMEFRAME() external view returns (uint256); 28 | 29 | // ----------- Public State Changing API ----------- 30 | 31 | /// @notice public function that allows compounding of interest after duration has passed 32 | /// Sets accumulator to the current accrued interest, and then resets the timer. 33 | function compoundInterest() external; 34 | 35 | // ----------- Event ----------- 36 | 37 | /// @notice event emitted when the Volt system oracle compounds 38 | /// emits the end time of the period that completed and the new oracle price 39 | event InterestCompounded(uint256 periodStart, uint256 newOraclePrice); 40 | } 41 | -------------------------------------------------------------------------------- /contracts/oracle/OraclePassThrough.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {Decimal} from "../external/Decimal.sol"; 5 | import {CoreRef} from "./../refs/CoreRef.sol"; 6 | import {IScalingPriceOracle} from "./IScalingPriceOracle.sol"; 7 | import {IOraclePassThrough} from "./IOraclePassThrough.sol"; 8 | import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; 9 | 10 | /// @notice contract that passes all price calls to the Scaling Price Oracle 11 | /// The Scaling Price Oracle can be changed if there is a decision to change how data is interpolated 12 | /// without needing all contracts in the system to be upgraded, only this contract will have to change where it points 13 | /// @author Elliot Friedman 14 | contract OraclePassThrough is IOraclePassThrough, Ownable { 15 | using Decimal for Decimal.D256; 16 | 17 | /// @notice reference to the scaling price oracle 18 | IScalingPriceOracle public override scalingPriceOracle; 19 | 20 | constructor(IScalingPriceOracle _scalingPriceOracle) Ownable() { 21 | scalingPriceOracle = _scalingPriceOracle; 22 | } 23 | 24 | /// @notice updates the oracle price 25 | /// @dev no-op, ScalingPriceOracle is updated automatically 26 | /// added for backwards compatibility with OracleRef 27 | function update() public {} 28 | 29 | // ----------- Getters ----------- 30 | 31 | /// @notice function to get the current oracle price for the OracleRef contract 32 | function read() 33 | external 34 | view 35 | override 36 | returns (Decimal.D256 memory price, bool valid) 37 | { 38 | uint256 currentPrice = scalingPriceOracle.getCurrentOraclePrice(); 39 | 40 | price = Decimal.from(currentPrice).div(1e18); 41 | valid = true; 42 | } 43 | 44 | /// @notice function to get the current oracle price for the entire system 45 | function getCurrentOraclePrice() external view override returns (uint256) { 46 | return scalingPriceOracle.getCurrentOraclePrice(); 47 | } 48 | 49 | /// @notice function to get the current oracle price for the entire system 50 | function currPegPrice() external view override returns (uint256) { 51 | return scalingPriceOracle.getCurrentOraclePrice(); 52 | } 53 | 54 | // ----------- Governance only state changing api ----------- 55 | 56 | /// @notice function to update the pointer to the scaling price oracle 57 | /// requires approval from all parties on multisig to update 58 | function updateScalingPriceOracle( 59 | IScalingPriceOracle newScalingPriceOracle 60 | ) external override onlyOwner { 61 | IScalingPriceOracle oldScalingPriceOracle = scalingPriceOracle; 62 | scalingPriceOracle = newScalingPriceOracle; 63 | 64 | emit ScalingPriceOracleUpdate( 65 | oldScalingPriceOracle, 66 | newScalingPriceOracle 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /contracts/pcv/IPCVDeposit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import "./IPCVDepositBalances.sol"; 5 | 6 | /// @title a PCV Deposit interface 7 | /// @author Fei Protocol 8 | interface IPCVDeposit is IPCVDepositBalances { 9 | // ----------- Events ----------- 10 | 11 | event Deposit(address indexed _from, uint256 _amount); 12 | 13 | event Withdrawal( 14 | address indexed _caller, 15 | address indexed _to, 16 | uint256 _amount 17 | ); 18 | 19 | event WithdrawERC20( 20 | address indexed _caller, 21 | address indexed _token, 22 | address indexed _to, 23 | uint256 _amount 24 | ); 25 | 26 | event WithdrawETH( 27 | address indexed _caller, 28 | address indexed _to, 29 | uint256 _amount 30 | ); 31 | 32 | event Harvest(address indexed _token, int256 _profit, uint256 _timestamp); 33 | 34 | // ----------- State changing api ----------- 35 | 36 | function deposit() external; 37 | 38 | // ----------- PCV Controller only state changing api ----------- 39 | 40 | function withdraw(address to, uint256 amount) external; 41 | 42 | function withdrawERC20(address token, address to, uint256 amount) external; 43 | 44 | function withdrawETH(address payable to, uint256 amount) external; 45 | } 46 | -------------------------------------------------------------------------------- /contracts/pcv/IPCVDepositBalances.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | /// @title a PCV Deposit interface for only balance getters 5 | /// @author Fei Protocol 6 | interface IPCVDepositBalances { 7 | // ----------- Getters ----------- 8 | 9 | /// @notice gets the effective balance of "balanceReportedIn" token if the deposit were fully withdrawn 10 | function balance() external view returns (uint256); 11 | 12 | /// @notice gets the token address in which this deposit returns its balance 13 | function balanceReportedIn() external view returns (address); 14 | 15 | /// @notice gets the resistant token balance and protocol owned fei of this deposit 16 | function resistantBalanceAndVolt() external view returns (uint256, uint256); 17 | } 18 | -------------------------------------------------------------------------------- /contracts/pcv/IPCVGuardAdmin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | /// @title PCV GuardAdmin Interface 5 | /// @author Volt Protocol 6 | interface IPCVGuardAdmin { 7 | // Role Heirarchy 8 | // Governor admin of -> PCV_GUARD_ADMIN 9 | // PCV_GUARD_ADMIN admin of -> PCV_GUARD 10 | // This contract gets the PCV_GUARD_ADMIN role 11 | 12 | // ---------- Governor-Only State-Changing API ---------- 13 | 14 | /// @notice This function can only be called by the Governor role to grant the PCV Guard role 15 | /// @param newGuard address of the account to be revoked the role of PCV Guard 16 | function grantPCVGuardRole(address newGuard) external; 17 | 18 | // ---------- Governor-Or-Guardian-Only State-Changing API ---------- 19 | 20 | /// @notice This function can only be called by the Governor or Guardian roles to revoke the PCV Guard role 21 | /// @param oldGuard address of the account to be revoked the role of PCV Guard 22 | function revokePCVGuardRole(address oldGuard) external; 23 | } 24 | -------------------------------------------------------------------------------- /contracts/pcv/PCVDeposit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import "../refs/CoreRef.sol"; 5 | import "./IPCVDeposit.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | 8 | /// @title abstract contract for withdrawing ERC-20 tokens using a PCV Controller 9 | /// @author Fei Protocol 10 | abstract contract PCVDeposit is IPCVDeposit, CoreRef { 11 | using SafeERC20 for IERC20; 12 | 13 | /// @notice withdraw ERC20 from the contract 14 | /// @param token address of the ERC20 to send 15 | /// @param to address destination of the ERC20 16 | /// @param amount quantity of ERC20 to send 17 | function withdrawERC20( 18 | address token, 19 | address to, 20 | uint256 amount 21 | ) public virtual override onlyPCVController { 22 | _withdrawERC20(token, to, amount); 23 | } 24 | 25 | function _withdrawERC20( 26 | address token, 27 | address to, 28 | uint256 amount 29 | ) internal { 30 | IERC20(token).safeTransfer(to, amount); 31 | emit WithdrawERC20(msg.sender, token, to, amount); 32 | } 33 | 34 | /// @notice withdraw ETH from the contract 35 | /// @param to address to send ETH 36 | /// @param amountOut amount of ETH to send 37 | function withdrawETH( 38 | address payable to, 39 | uint256 amountOut 40 | ) external virtual override onlyPCVController { 41 | Address.sendValue(to, amountOut); 42 | emit WithdrawETH(msg.sender, to, amountOut); 43 | } 44 | 45 | function balance() public view virtual override returns (uint256); 46 | 47 | function resistantBalanceAndVolt() 48 | public 49 | view 50 | virtual 51 | override 52 | returns (uint256, uint256) 53 | { 54 | return (balance(), 0); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /contracts/pcv/PCVGuardAdmin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {IPCVGuardAdmin} from "./IPCVGuardAdmin.sol"; 5 | import {CoreRef} from "../refs/CoreRef.sol"; 6 | import {TribeRoles} from "../core/TribeRoles.sol"; 7 | import {ICore} from "../core/ICore.sol"; 8 | 9 | /// @title PCV Guard Admin 10 | /// @author Volt Protocol 11 | /// @notice This contract interfaces between access controls and the PCV Guard 12 | /// allowing for multiple roles to manage the PCV Guard role, as access controls only allow for 13 | /// a single admin for each role 14 | contract PCVGuardAdmin is IPCVGuardAdmin, CoreRef { 15 | constructor(address _core) CoreRef(_core) {} 16 | 17 | // ---------- Governor-Only State-Changing API ---------- 18 | 19 | /// @notice This function can only be called by the Governor role to grant the PCV Guard role 20 | /// @param newGuard address of the account to be made a PCV Guard 21 | function grantPCVGuardRole( 22 | address newGuard 23 | ) external override onlyGovernor { 24 | core().grantRole(TribeRoles.PCV_GUARD, newGuard); 25 | } 26 | 27 | // ---------- Governor-Or-Guardian-Only State-Changing API ---------- 28 | 29 | /// @notice This function can only be called by the Governor or Guardian roles to revoke the PCV Guard role 30 | /// @param oldGuard address of the account to be revoked the role of PCV Guard 31 | function revokePCVGuardRole( 32 | address oldGuard 33 | ) external override onlyGuardianOrGovernor { 34 | core().revokeRole(TribeRoles.PCV_GUARD, oldGuard); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/pcv/compound/CErc20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.8.13; 2 | 3 | interface CErc20 { 4 | function mint(uint256 amount) external returns (uint256); 5 | 6 | function underlying() external returns (address); 7 | } 8 | -------------------------------------------------------------------------------- /contracts/pcv/compound/CToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.8.13; 2 | 3 | interface CToken { 4 | function redeemUnderlying(uint256 redeemAmount) external returns (uint256); 5 | 6 | function exchangeRateStored() external view returns (uint256); 7 | 8 | function balanceOf(address account) external view returns (uint256); 9 | 10 | function isCToken() external view returns (bool); 11 | 12 | function isCEther() external view returns (bool); 13 | 14 | function accrueInterest() external returns (uint256); 15 | } 16 | -------------------------------------------------------------------------------- /contracts/pcv/compound/CompoundPCVDepositBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity =0.8.13; 3 | 4 | import {PCVDeposit} from "../PCVDeposit.sol"; 5 | import {CoreRef} from "../../refs/CoreRef.sol"; 6 | import {CToken} from "./CToken.sol"; 7 | 8 | /// @title base class for a Compound PCV Deposit 9 | /// @author Fei Protocol 10 | abstract contract CompoundPCVDepositBase is PCVDeposit { 11 | CToken public immutable cToken; 12 | 13 | uint256 private constant EXCHANGE_RATE_SCALE = 1e18; 14 | 15 | /// @notice Compound PCV Deposit constructor 16 | /// @param _core Volt Core for reference 17 | /// @param _cToken Compound cToken to deposit 18 | constructor(address _core, address _cToken) CoreRef(_core) { 19 | cToken = CToken(_cToken); 20 | require(cToken.isCToken(), "CompoundPCVDeposit: Not a cToken"); 21 | } 22 | 23 | /// @notice withdraw tokens from the PCV allocation 24 | /// @param amountUnderlying of tokens withdrawn 25 | /// @param to the address to send PCV to 26 | function withdraw( 27 | address to, 28 | uint256 amountUnderlying 29 | ) external override onlyPCVController whenNotPaused { 30 | require( 31 | cToken.redeemUnderlying(amountUnderlying) == 0, 32 | "CompoundPCVDeposit: redeem error" 33 | ); 34 | _transferUnderlying(to, amountUnderlying); 35 | emit Withdrawal(msg.sender, to, amountUnderlying); 36 | } 37 | 38 | /// @notice returns total balance of PCV in the Deposit excluding the VOLT 39 | /// @dev returns stale values from Compound if the market hasn't been updated 40 | function balance() public view override returns (uint256) { 41 | uint256 exchangeRate = cToken.exchangeRateStored(); 42 | return 43 | (cToken.balanceOf(address(this)) * exchangeRate) / 44 | EXCHANGE_RATE_SCALE; 45 | } 46 | 47 | function _transferUnderlying(address to, uint256 amount) internal virtual; 48 | } 49 | -------------------------------------------------------------------------------- /contracts/pcv/compound/ERC20CompoundPCVDeposit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import {CompoundPCVDepositBase} from "./CompoundPCVDepositBase.sol"; 6 | import {CErc20} from "./CErc20.sol"; 7 | import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 8 | 9 | /// @title ERC-20 implementation for a Compound PCV Deposit 10 | /// @author Fei Protocol 11 | contract ERC20CompoundPCVDeposit is CompoundPCVDepositBase { 12 | /// @notice the token underlying the cToken 13 | IERC20 public immutable token; 14 | 15 | /// @notice Compound ERC20 PCV Deposit constructor 16 | /// @param _core Volt Core for reference 17 | /// @param _cToken Compound cToken to deposit 18 | constructor( 19 | address _core, 20 | address _cToken 21 | ) CompoundPCVDepositBase(_core, _cToken) { 22 | token = IERC20(CErc20(_cToken).underlying()); 23 | } 24 | 25 | /// @notice deposit ERC-20 tokens to Compound 26 | function deposit() external override whenNotPaused { 27 | uint256 amount = token.balanceOf(address(this)); 28 | 29 | token.approve(address(cToken), amount); 30 | 31 | // Compound returns non-zero when there is an error 32 | require( 33 | CErc20(address(cToken)).mint(amount) == 0, 34 | "ERC20CompoundPCVDeposit: deposit error" 35 | ); 36 | 37 | emit Deposit(msg.sender, amount); 38 | } 39 | 40 | function _transferUnderlying(address to, uint256 amount) internal override { 41 | SafeERC20.safeTransfer(token, to, amount); 42 | } 43 | 44 | /// @notice display the related token of the balance reported 45 | function balanceReportedIn() public view override returns (address) { 46 | return address(token); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/pcv/maker/IDSSPSM.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity =0.8.13; 3 | 4 | /// @title Interface for the Maker DAO PSMs 5 | /// @dev gem refers to collateral tokens 6 | interface IDSSPSM { 7 | /// @notice Swap DAI for the underlying collateral type 8 | function buyGem(address usr, uint256 gemAmt) external; 9 | 10 | /// @notice Swap collateral type for DAI 11 | function sellGem(address usr, uint256 gemAmt) external; 12 | 13 | /// @notice redeem fee 14 | function tin() external view returns (uint256); 15 | 16 | /// @notice mint fee 17 | function tout() external view returns (uint256); 18 | 19 | /// @notice set mint or redeem fee 20 | function file(bytes32 what, uint256 data) external; 21 | } 22 | -------------------------------------------------------------------------------- /contracts/pcv/maker/IMakerRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity =0.8.13; 3 | 4 | interface IMakerRouter { 5 | /// @notice Function to swap from FEI to DAI 6 | /// @param amountFeiIn the amount of FEI to be deposited 7 | /// @param minDaiAmountOut the minimum amount of DAI expected to be received 8 | /// @param to the address the DAI should be sent to once swapped 9 | function swapFeiForDai( 10 | uint256 amountFeiIn, 11 | uint256 minDaiAmountOut, 12 | address to 13 | ) external; 14 | 15 | /// @notice Function to swap all of FEI balance to DAI 16 | /// @param to the address the DAI should be sent to once swapped 17 | function swapAllFeiForDai(address to) external; 18 | 19 | /// @notice Function to swap from FEI to USDC 20 | /// @dev Function will swap from FEI to DAI first then DAI to USDC 21 | /// @param amountFeiIn the amount of FEI to be deposited 22 | /// @param minDaiAmountOut the minimum amount of DAI expected to be received from FEI PSM 23 | /// @param to the address the USDC should be sent to once swapped 24 | function swapFeiForUsdc( 25 | uint256 amountFeiIn, 26 | uint256 minDaiAmountOut, 27 | address to 28 | ) external; 29 | 30 | /// @notice Function to swap all of FEI balance to USDC 31 | /// @param to the address the USDC should be sent to once swapped 32 | function swapAllFeiForUsdc(address to) external; 33 | 34 | /// @notice Function to swap for both DAI and USDC 35 | /// @dev Function will swap from FEI to DAI first then DAI to USDC 36 | /// @param amountFeiIn the amount of FEI to be deposited 37 | /// @param minDaiAmountOut the minimum amount of DAI expected to be received 38 | /// @param ratioUSDC the ratio of the DAI received we would like to swap to USDC - in basis point terms 39 | /// @param usdcTo the address the USDC should be sent to once swapped 40 | /// @param daiTo the address the DAI should be sent to once swapped 41 | function swapFeiForUsdcAndDai( 42 | uint256 amountFeiIn, 43 | uint256 minDaiAmountOut, 44 | address usdcTo, 45 | address daiTo, 46 | uint256 ratioUSDC 47 | ) external; 48 | 49 | /// @notice Function to swap all FEI balance for both DAI and USDC 50 | /// @param usdcTo the address the USDC should be sent to once swapped 51 | /// @param daiTo the address the DAI should be sent to once swapped 52 | /// @param ratioUSDC the ratio of the DAI received we would like to swap to USDC - in basis point terms 53 | function swapAllFeiForUsdcAndDai( 54 | address usdcTo, 55 | address daiTo, 56 | uint256 ratioUSDC 57 | ) external; 58 | 59 | /// @notice Function to withdraw tokens to an address 60 | /// @param token the token to withdraw 61 | /// @param amount the amount to send 62 | /// @param to the address the token should be sent to 63 | function withdrawERC20(address token, uint256 amount, address to) external; 64 | } 65 | -------------------------------------------------------------------------------- /contracts/pcv/morpho/IMorpho.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GNU AGPLv3 2 | pragma solidity ^0.8.0; 3 | 4 | import {IComptroller} from "./ICompound.sol"; 5 | 6 | // prettier-ignore 7 | interface IMorpho { 8 | 9 | /// STORAGE /// 10 | 11 | function comptroller() external view returns (IComptroller); 12 | 13 | /// USERS /// 14 | 15 | function supply(address _poolTokenAddress, address _onBehalf, uint256 _amount) external; 16 | function supply(address _poolTokenAddress, address _onBehalf, uint256 _amount, uint256 _maxGasForMatching) external; 17 | function borrow(address _poolTokenAddress, uint256 _amount) external; 18 | function borrow(address _poolTokenAddress, uint256 _amount, uint256 _maxGasForMatching) external; 19 | function withdraw(address _poolTokenAddress, uint256 _amount) external; 20 | function repay(address _poolTokenAddress, address _onBehalf, uint256 _amount) external; 21 | function liquidate(address _poolTokenBorrowedAddress, address _poolTokenCollateralAddress, address _borrower, uint256 _amount) external; 22 | function claimRewards(address[] calldata _cTokenAddresses, bool _tradeForMorphoToken) external returns (uint256 claimedAmount); 23 | 24 | function updateP2PIndexes(address _poolTokenAddress) external; 25 | } 26 | -------------------------------------------------------------------------------- /contracts/pcv/morpho/IPCVOracle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.13; 2 | 3 | interface IPCVOracle { 4 | /// @notice hook on PCV deposit, callable when pcv oracle is set 5 | /// updates the oracle with the new liquid balance delta 6 | function updateLiquidBalance(int256 pcvDelta) external; 7 | } 8 | -------------------------------------------------------------------------------- /contracts/peg/IPriceBound.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.13; 2 | 3 | interface IPriceBound { 4 | // ----------- Events ----------- 5 | 6 | /// @notice event emitted when minimum floor price is updated 7 | event OracleFloorUpdate(uint256 oldFloor, uint256 newFloor); 8 | 9 | /// @notice event emitted when maximum ceiling price is updated 10 | event OracleCeilingUpdate(uint256 oldCeiling, uint256 newCeiling); 11 | 12 | // ----------- Governor or admin only state changing api ----------- 13 | 14 | /// @notice sets the floor price in BP 15 | function setOracleFloorBasisPoints(uint256 newFloor) external; 16 | 17 | /// @notice sets the ceiling price in BP 18 | function setOracleCeilingBasisPoints(uint256 newCeiling) external; 19 | 20 | // ----------- Getters ----------- 21 | 22 | /// @notice get the floor price in basis points 23 | function floor() external view returns (uint256); 24 | 25 | /// @notice get the ceiling price in basis points 26 | function ceiling() external view returns (uint256); 27 | 28 | /// @notice return wether the current oracle price is valid or not 29 | function isPriceValid() external view returns (bool); 30 | } 31 | -------------------------------------------------------------------------------- /contracts/refs/ICoreRef.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import "../core/ICore.sol"; 5 | 6 | /// @title CoreRef interface 7 | /// @author Fei Protocol 8 | interface ICoreRef { 9 | // ----------- Events ----------- 10 | 11 | event CoreUpdate(address indexed oldCore, address indexed newCore); 12 | 13 | event ContractAdminRoleUpdate( 14 | bytes32 indexed oldContractAdminRole, 15 | bytes32 indexed newContractAdminRole 16 | ); 17 | 18 | // ----------- Governor only state changing api ----------- 19 | 20 | function setContractAdminRole(bytes32 newContractAdminRole) external; 21 | 22 | // ----------- Governor or Guardian only state changing api ----------- 23 | 24 | function pause() external; 25 | 26 | function unpause() external; 27 | 28 | // ----------- Getters ----------- 29 | 30 | function core() external view returns (ICore); 31 | 32 | function volt() external view returns (IVolt); 33 | 34 | function vcon() external view returns (IERC20); 35 | 36 | function voltBalance() external view returns (uint256); 37 | 38 | function vconBalance() external view returns (uint256); 39 | 40 | function CONTRACT_ADMIN_ROLE() external view returns (bytes32); 41 | 42 | function isContractAdmin(address admin) external view returns (bool); 43 | } 44 | -------------------------------------------------------------------------------- /contracts/refs/IOracleRef.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import "../oracle/IOracle.sol"; 5 | 6 | /// @title OracleRef interface 7 | /// @author Fei Protocol 8 | interface IOracleRef { 9 | // ----------- Events ----------- 10 | 11 | event OracleUpdate(address indexed oldOracle, address indexed newOracle); 12 | 13 | event InvertUpdate(bool oldDoInvert, bool newDoInvert); 14 | 15 | event DecimalsNormalizerUpdate( 16 | int256 oldDecimalsNormalizer, 17 | int256 newDecimalsNormalizer 18 | ); 19 | 20 | event BackupOracleUpdate( 21 | address indexed oldBackupOracle, 22 | address indexed newBackupOracle 23 | ); 24 | 25 | // ----------- State changing API ----------- 26 | 27 | function updateOracle() external; 28 | 29 | // ----------- Governor only state changing API ----------- 30 | 31 | function setOracle(address newOracle) external; 32 | 33 | function setBackupOracle(address newBackupOracle) external; 34 | 35 | function setDecimalsNormalizer(int256 newDecimalsNormalizer) external; 36 | 37 | function setDoInvert(bool newDoInvert) external; 38 | 39 | // ----------- Getters ----------- 40 | 41 | function oracle() external view returns (IOracle); 42 | 43 | function backupOracle() external view returns (IOracle); 44 | 45 | function doInvert() external view returns (bool); 46 | 47 | function decimalsNormalizer() external view returns (int256); 48 | 49 | function readOracle() external view returns (Decimal.D256 memory); 50 | 51 | function invert( 52 | Decimal.D256 calldata price 53 | ) external pure returns (Decimal.D256 memory); 54 | } 55 | -------------------------------------------------------------------------------- /contracts/test/integration/IntegrationTestCompoundPCVDeposits.t.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity =0.8.13; 3 | 4 | import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; 5 | import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; 6 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | import {Vm} from "../unit/utils/Vm.sol"; 8 | import {Core} from "../../core/Core.sol"; 9 | import {IVolt} from "../../volt/IVolt.sol"; 10 | import {DSTest} from "../unit/utils/DSTest.sol"; 11 | import {IDSSPSM} from "../../pcv/maker/IDSSPSM.sol"; 12 | import {Constants} from "../../Constants.sol"; 13 | import {PCVGuardian} from "../../pcv/PCVGuardian.sol"; 14 | import {MainnetAddresses} from "./fixtures/MainnetAddresses.sol"; 15 | import {PegStabilityModule} from "../../peg/PegStabilityModule.sol"; 16 | import {ERC20CompoundPCVDeposit} from "../../pcv/compound/ERC20CompoundPCVDeposit.sol"; 17 | 18 | contract IntegrationTestCompoundPCVDeposits is DSTest { 19 | using SafeCast for *; 20 | 21 | Vm public constant vm = Vm(HEVM_ADDRESS); 22 | 23 | ERC20CompoundPCVDeposit private daiDeposit = 24 | ERC20CompoundPCVDeposit(MainnetAddresses.COMPOUND_DAI_PCV_DEPOSIT); 25 | ERC20CompoundPCVDeposit private usdcDeposit = 26 | ERC20CompoundPCVDeposit(MainnetAddresses.COMPOUND_USDC_PCV_DEPOSIT); 27 | 28 | PCVGuardian private immutable pcvGuardian = 29 | PCVGuardian(MainnetAddresses.PCV_GUARDIAN); 30 | 31 | Core private core = Core(MainnetAddresses.CORE); 32 | PegStabilityModule private daiPSM = 33 | PegStabilityModule(MainnetAddresses.VOLT_DAI_PSM); 34 | 35 | IERC20 private dai = IERC20(MainnetAddresses.DAI); 36 | IVolt private fei = IVolt(MainnetAddresses.FEI); 37 | IERC20 private usdc = IERC20(MainnetAddresses.USDC); 38 | 39 | function testSetup() public { 40 | assertEq(address(daiDeposit.core()), address(core)); 41 | assertEq(address(usdcDeposit.core()), address(core)); 42 | 43 | assertEq(address(daiDeposit.cToken()), address(MainnetAddresses.CDAI)); 44 | assertEq( 45 | address(usdcDeposit.cToken()), 46 | address(MainnetAddresses.CUSDC) 47 | ); 48 | 49 | assertEq(address(daiDeposit.token()), address(MainnetAddresses.DAI)); 50 | assertEq(address(usdcDeposit.token()), address(MainnetAddresses.USDC)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /contracts/test/integration/IntegrationTestVIP15.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity =0.8.13; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; 6 | import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; 7 | 8 | import {vip15} from "./vip/vip15.sol"; 9 | import {ICore} from "../../core/ICore.sol"; 10 | import {IVolt} from "../../volt/Volt.sol"; 11 | import {IPCVDeposit} from "../../pcv/IPCVDeposit.sol"; 12 | import {IPCVGuardian} from "../../pcv/IPCVGuardian.sol"; 13 | import {PriceBoundPSM} from "../../peg/PriceBoundPSM.sol"; 14 | import {ERC20Allocator} from "../../pcv/utils/ERC20Allocator.sol"; 15 | import {MainnetAddresses} from "./fixtures/MainnetAddresses.sol"; 16 | import {TimelockSimulation} from "./utils/TimelockSimulation.sol"; 17 | 18 | contract IntegrationTestVIP15 is TimelockSimulation, vip15 { 19 | using SafeCast for *; 20 | 21 | IPCVGuardian private immutable mainnetPCVGuardian = 22 | IPCVGuardian(MainnetAddresses.PCV_GUARDIAN); 23 | 24 | ERC20Allocator private immutable allocator = 25 | ERC20Allocator(MainnetAddresses.ERC20ALLOCATOR); 26 | PriceBoundPSM private immutable usdcPsm = 27 | PriceBoundPSM(MainnetAddresses.VOLT_USDC_PSM); 28 | PriceBoundPSM private immutable daiPsm = 29 | PriceBoundPSM(MainnetAddresses.VOLT_DAI_PSM); 30 | 31 | uint256 startingPrice; 32 | uint256 endingPrice; 33 | 34 | function setUp() public { 35 | startingPrice = opt.getCurrentOraclePrice(); 36 | 37 | mainnetSetup(); 38 | simulate( 39 | getMainnetProposal(), 40 | TimelockController(payable(MainnetAddresses.TIMELOCK_CONTROLLER)), 41 | mainnetPCVGuardian, 42 | MainnetAddresses.GOVERNOR, 43 | MainnetAddresses.EOA_1, 44 | vm, 45 | false 46 | ); 47 | mainnetValidate(); 48 | 49 | endingPrice = opt.getCurrentOraclePrice(); 50 | 51 | vm.label(address(pcvGuardian), "PCV Guardian"); 52 | } 53 | 54 | function testPriceStaysWithinOneBasisPointAfterUpgrade() public { 55 | assertApproxEq(int256(startingPrice), int256(endingPrice), 0); 56 | } 57 | 58 | function testSkimFailsPaused() public { 59 | vm.expectRevert("Pausable: paused"); 60 | allocator.skim(MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT); 61 | } 62 | 63 | function testDripFailsPaused() public { 64 | vm.expectRevert("Pausable: paused"); 65 | allocator.drip(MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT); 66 | } 67 | 68 | function testDoActionFailsPaused() public { 69 | vm.expectRevert("Pausable: paused"); 70 | allocator.doAction(MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT); 71 | } 72 | 73 | function testMintFailsPausedDai() public { 74 | vm.expectRevert("PegStabilityModule: Minting paused"); 75 | daiPsm.mint(address(0), 0, 0); 76 | } 77 | 78 | function testMintFailsPausedUsdc() public { 79 | vm.expectRevert("PegStabilityModule: Minting paused"); 80 | usdcPsm.mint(address(0), 0, 0); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /contracts/test/integration/README.md: -------------------------------------------------------------------------------- 1 | # Integration Tests 2 | These are integration tests that are used to validate interactions with other protocols. 3 | 4 | To add a new test, ensure that the name of the contract test file includes `IntegrationTest`. The `forge` test command uses a regex of that string in order to run the `IntegrationTests` with the required mainnet keys etc. 5 | 6 | ## Purpose 7 | These integration tests are primarily for rapid development and tight feedback loops when building an integration with a third party protocol. They allow you to fork mainnet to replicate state and can be run in two modes: default and `latest`. 8 | 9 | The default mode is run through `npm run test:integration`. It forks mainnet from a block number supplied through the environment variable `FORK_BLOCK`, in order to make use of caching and so speed up the tests. To change the block from which the tests fork, change the `env` variable set in the `package.json` command. 10 | 11 | There is also a `latest` mode, run through `npm run test:integraton:latest`. This forks from the latest Mainnet block and allows you to run the integration tests in a manner that validates against the latest state on mainnet. It does not make use of caching. This is used in CI. 12 | 13 | ## How to run 14 | Make sure an environment variable `MAINNET_ALCHEMY_API_KEY` is in the namespace where you execute integration test commands: 15 | 16 | **Dev mode** 17 | `MAINNET_ALCHEMY_API_KEY=x npm run test:integration` 18 | 19 | **Latest mode** 20 | `MAINNET_ALCHEMY_API_KEY=x npm run test:integration:latest` -------------------------------------------------------------------------------- /contracts/test/integration/utils/AllArbitrumRoles.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {Core} from "../../../core/Core.sol"; 5 | import {ArbitrumAddresses} from "../fixtures/ArbitrumAddresses.sol"; 6 | import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; 7 | import {TribeRoles} from "../../../core/TribeRoles.sol"; 8 | import {DSTest} from "./../../unit/utils/DSTest.sol"; 9 | import {L2Core} from "../../../core/L2Core.sol"; 10 | import {Core} from "../../../core/Core.sol"; 11 | import {Vm} from "./../../unit/utils/Vm.sol"; 12 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 13 | import {RoleTesting} from "./RoleTesting.sol"; 14 | import {AllRolesConfig} from "./AllRolesConfig.sol"; 15 | 16 | contract ArbitrumTestAllArbitrumRoles is RoleTesting, AllRolesConfig { 17 | Core private core = Core(ArbitrumAddresses.CORE); 18 | 19 | function setUp() public { 20 | for (uint256 i = 0; i < allRoles.length; i++) { 21 | numEachRole.push(core.getRoleMemberCount(allRoles[i])); 22 | } 23 | 24 | /// Governors 25 | allAddresses[0].push(ArbitrumAddresses.CORE); 26 | allAddresses[0].push(ArbitrumAddresses.GOVERNOR); 27 | allAddresses[0].push(ArbitrumAddresses.TIMELOCK_CONTROLLER); 28 | 29 | /// Guardians 30 | allAddresses[1].push(ArbitrumAddresses.PCV_GUARDIAN); 31 | 32 | /// PCV Controllers 33 | allAddresses[2].push(ArbitrumAddresses.GOVERNOR); 34 | allAddresses[2].push(ArbitrumAddresses.PCV_GUARDIAN); 35 | allAddresses[2].push(ArbitrumAddresses.ERC20ALLOCATOR); 36 | 37 | /// PCV Guards 38 | allAddresses[4].push(ArbitrumAddresses.EOA_1); 39 | allAddresses[4].push(ArbitrumAddresses.EOA_2); 40 | allAddresses[4].push(ArbitrumAddresses.EOA_3); 41 | allAddresses[4].push(ArbitrumAddresses.EOA_4); 42 | 43 | /// PCV Guard Admin 44 | allAddresses[5].push(ArbitrumAddresses.PCV_GUARD_ADMIN); 45 | 46 | /// sanity check 47 | assert(numEachRole.length == allRoles.length); 48 | } 49 | 50 | /// load up number of roles from Core and ensure that they match up with numbers here 51 | function testRoleArity() public view { 52 | _testRoleArity(getAllRoles(), roleCounts, numEachRole); 53 | } 54 | 55 | /// assert that all addresses have the proper role 56 | function testRoleAddresses() public { 57 | _testRoleAddresses(getAllRoles(), allAddresses, core); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /contracts/test/integration/utils/AllMainnetRoles.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; 5 | import {TribeRoles} from "../../../core/TribeRoles.sol"; 6 | import {DSTest} from "./../../unit/utils/DSTest.sol"; 7 | import {L2Core} from "../../../core/L2Core.sol"; 8 | import {Core} from "../../../core/Core.sol"; 9 | import {Vm} from "./../../unit/utils/Vm.sol"; 10 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 11 | import {RoleTesting} from "./RoleTesting.sol"; 12 | import {AllRolesConfig} from "./AllRolesConfig.sol"; 13 | 14 | contract IntegrationTestAllMainnetRoles is RoleTesting, AllRolesConfig { 15 | Core private core = Core(MainnetAddresses.CORE); 16 | 17 | function setUp() public { 18 | for (uint256 i = 0; i < allRoles.length; i++) { 19 | numEachRole.push(core.getRoleMemberCount(allRoles[i])); 20 | } 21 | 22 | /// governors 23 | allAddresses[0].push(MainnetAddresses.CORE); 24 | allAddresses[0].push(MainnetAddresses.GOVERNOR); 25 | allAddresses[0].push(MainnetAddresses.TIMELOCK_CONTROLLER); 26 | 27 | /// pcv guardians 28 | allAddresses[1].push(MainnetAddresses.PCV_GUARDIAN); 29 | 30 | /// pcv controllers 31 | allAddresses[2].push(MainnetAddresses.GOVERNOR); 32 | allAddresses[2].push(MainnetAddresses.PCV_GUARDIAN); 33 | allAddresses[2].push(MainnetAddresses.ERC20ALLOCATOR); 34 | allAddresses[2].push(MainnetAddresses.MORPHO_COMPOUND_PCV_ROUTER); 35 | 36 | /// pcv guards 37 | allAddresses[4].push(MainnetAddresses.EOA_1); 38 | allAddresses[4].push(MainnetAddresses.EOA_2); 39 | allAddresses[4].push(MainnetAddresses.EOA_4); 40 | 41 | /// pcv guard admin 42 | allAddresses[5].push(MainnetAddresses.PCV_GUARD_ADMIN); 43 | 44 | /// sanity check 45 | assert(numEachRole.length == allRoles.length); 46 | } 47 | 48 | /// load up number of roles from Core and ensure that they match up with numbers here 49 | function testRoleArity() public view { 50 | _testRoleArity(getAllRoles(), roleCounts, numEachRole); 51 | } 52 | 53 | /// assert that all addresses have the proper role 54 | function testRoleAddresses() public { 55 | _testRoleAddresses(getAllRoles(), allAddresses, core); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /contracts/test/integration/utils/AllRolesConfig.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.8.13; 2 | 3 | import {TribeRoles} from "contracts/core/TribeRoles.sol"; 4 | 5 | contract AllRolesConfig { 6 | /// @notice all roles 7 | bytes32[7] public allRoles = [ 8 | TribeRoles.GOVERNOR, 9 | TribeRoles.GUARDIAN, 10 | TribeRoles.PCV_CONTROLLER, 11 | TribeRoles.MINTER, 12 | TribeRoles.PCV_GUARD, 13 | TribeRoles.PCV_GUARD_ADMIN, 14 | TribeRoles.PSM_ADMIN_ROLE 15 | ]; 16 | 17 | /// how many of each role exists 18 | uint256[] public numEachRole; 19 | 20 | /// @notice array of arrays that has all addresses in each role 21 | address[][7] public allAddresses; 22 | 23 | /// ------ @notice number of each role in the system ------ 24 | 25 | /// new timelock, multisig, core 26 | uint256 public constant numGovernors = 3; 27 | 28 | /// PCV Guardian 29 | uint256 public constant numGuardians = 1; 30 | 31 | /// multisig, PCV Guardian, ERC20Allocator, COMPOUND_PCV_ROUTER 32 | uint256 public constant numPCVControllers = 4; 33 | 34 | /// Global Rate Limited Minter 35 | uint256 public constant numMinters = 0; 36 | 37 | /// EOA1, EOA2 & EOA3 38 | uint256 public constant numPCVGuards = 3; 39 | 40 | /// PCV Guard Admin 41 | uint256 public constant numPCVGuardAdmins = 1; 42 | 43 | /// NA 44 | uint256 public constant numPSMAdmins = 0; 45 | 46 | /// @notice all the number of each roles in order of the allRoles array 47 | uint256[7] public roleCounts = [ 48 | numGovernors, 49 | numGuardians, 50 | numPCVControllers, 51 | numMinters, 52 | numPCVGuards, 53 | numPCVGuardAdmins, 54 | numPSMAdmins 55 | ]; 56 | 57 | function getAllRoles() public view returns (bytes32[] memory) { 58 | uint256 roleLen = allRoles.length; 59 | bytes32[] memory allRolesArray = new bytes32[](roleLen); 60 | 61 | for (uint256 i = 0; i < roleLen; i++) { 62 | allRolesArray[i] = allRoles[i]; 63 | } 64 | 65 | return allRolesArray; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contracts/test/integration/utils/ITimelockSimulation.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.8.13; 2 | 3 | import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; 4 | import {IPCVGuardian} from "../../../pcv/IPCVGuardian.sol"; 5 | import {Vm} from "./../../unit/utils/Vm.sol"; 6 | 7 | interface ITimelockSimulation { 8 | /// an array of actions makes up a proposal 9 | struct action { 10 | address target; 11 | uint256 value; 12 | bytes arguments; 13 | string description; 14 | } 15 | 16 | /// @notice simulate timelock proposal 17 | /// @param proposal an array of actions that compose a proposal 18 | /// @param timelock to execute the proposal against 19 | /// @param guardian to verify all transfers are authorized to hold PCV 20 | /// @param executor account to execute the proposal on the timelock 21 | /// @param proposer account to propose the proposal to the timelock 22 | /// @param vm reference to a foundry vm instance 23 | /// @param doLogging toggle to print out calldata and steps 24 | function simulate( 25 | action[] memory proposal, 26 | TimelockController timelock, 27 | IPCVGuardian guardian, 28 | address executor, 29 | address proposer, 30 | Vm vm, 31 | bool doLogging 32 | ) external; 33 | } 34 | -------------------------------------------------------------------------------- /contracts/test/integration/utils/OracleVerification.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.8.13; 2 | 3 | import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; 4 | 5 | import {Decimal} from "../../../external/Decimal.sol"; 6 | import {Deviation} from "../../../utils/Deviation.sol"; 7 | import {IOracleRef} from "../../../refs/IOracleRef.sol"; 8 | import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; 9 | import {ArbitrumAddresses} from "../fixtures/ArbitrumAddresses.sol"; 10 | 11 | /// @notice contract to verify that all PSM's have the same 12 | /// oracle price before and after a proposal 13 | contract OracleVerification { 14 | using Decimal for Decimal.D256; 15 | using SafeCast for *; 16 | using Deviation for *; 17 | 18 | /// @notice all PSM's on mainnet 19 | address[] private allMainnetPSMs = [ 20 | MainnetAddresses.VOLT_DAI_PSM, 21 | MainnetAddresses.VOLT_USDC_PSM 22 | ]; 23 | 24 | /// @notice all PSM's on arbitrum 25 | address[] private allArbitrumPSMs = [ 26 | ArbitrumAddresses.VOLT_DAI_PSM, 27 | ArbitrumAddresses.VOLT_USDC_PSM 28 | ]; 29 | 30 | /// @notice all oracle prices gathered during verification 31 | uint256[] private oraclePrices; 32 | 33 | /// @notice address all psm oracles should point to 34 | address private cachedOracle; 35 | 36 | /// @notice call before governance action 37 | function preActionVerifyOracle() internal { 38 | address[] storage psms = block.chainid == 1 39 | ? allMainnetPSMs 40 | : allArbitrumPSMs; 41 | 42 | for (uint256 i = 0; i < psms.length; i++) { 43 | if (cachedOracle == address(0)) { 44 | cachedOracle = address(IOracleRef(psms[i]).oracle()); 45 | } else { 46 | require( 47 | cachedOracle == address(IOracleRef(psms[i]).oracle()), 48 | "OracleVerification: Invalid oracle" 49 | ); 50 | } 51 | oraclePrices.push(IOracleRef(psms[i]).readOracle().value); 52 | } 53 | } 54 | 55 | /// @notice call after governance action to verify oracle values 56 | function postActionVerifyOracle() internal view { 57 | address[] storage psms = block.chainid == 1 58 | ? allMainnetPSMs 59 | : allArbitrumPSMs; 60 | 61 | for (uint256 i = 0; i < psms.length; i++) { 62 | uint256 deviationBasisPoints = Deviation 63 | .calculateDeviationThresholdBasisPoints( 64 | IOracleRef(psms[i]).readOracle().value.toInt256(), 65 | oraclePrices[i].toInt256() 66 | ); 67 | require( 68 | deviationBasisPoints == 0, 69 | "OracleVerification: Price not the same after proposal" 70 | ); 71 | require( 72 | cachedOracle == address(IOracleRef(psms[i]).oracle()), 73 | "OracleVerification: oracle not the same after proposal" 74 | ); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /contracts/test/integration/utils/PCVGuardianWhitelist.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.8.13; 2 | 3 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 4 | import {IPCVGuardian} from "../../../pcv/IPCVGuardian.sol"; 5 | import {ITimelockSimulation} from "./ITimelockSimulation.sol"; 6 | import {IPermissions} from "./../../../core/IPermissions.sol"; 7 | 8 | /// Only allow approvals and transfers of PCV to addresses in PCV Guardian, 9 | /// and only allow granting PCV controllers if they are subsequently added to 10 | /// the PCV Guardian 11 | contract PCVGuardianWhitelist { 12 | /// private so that contracts that inherit cannot write to functionDetectors 13 | mapping(bytes4 => bool) private functionDetectors; 14 | 15 | constructor() { 16 | functionDetectors[IERC20.transfer.selector] = true; 17 | functionDetectors[IERC20.approve.selector] = true; 18 | } 19 | 20 | /// @notice function to verify actions and ensure that granting a PCV Controller or transferring assets 21 | /// only happens to addresses that are on the PCV Guardian whitelist 22 | function verifyAction( 23 | ITimelockSimulation.action[] memory proposal, 24 | IPCVGuardian guardian 25 | ) public view { 26 | uint256 proposalLength = proposal.length; 27 | for (uint256 i = 0; i < proposalLength; i++) { 28 | bytes4 functionSig = bytesToBytes4(proposal[i].arguments); 29 | 30 | if (functionDetectors[functionSig]) { 31 | address recipient; 32 | bytes memory payload = proposal[i].arguments; 33 | assembly { 34 | recipient := mload(add(payload, 36)) 35 | } 36 | 37 | if (!guardian.isWhitelistAddress(recipient)) { 38 | revert( 39 | string( 40 | abi.encodePacked( 41 | "Address ", 42 | toString(abi.encodePacked(recipient)), 43 | " not in PCV Guardian whitelist" 44 | ) 45 | ) 46 | ); 47 | } 48 | } 49 | } 50 | } 51 | 52 | /// @notice function to grab the first 4 bytes of calldata payload 53 | function bytesToBytes4( 54 | bytes memory toSlice 55 | ) public pure returns (bytes4 functionSignature) { 56 | if (toSlice.length < 4) { 57 | return bytes4(0); 58 | } 59 | 60 | assembly { 61 | functionSignature := mload(add(toSlice, 0x20)) 62 | } 63 | } 64 | 65 | /// Credit ethereum stackexchange https://ethereum.stackexchange.com/a/58341 66 | function toString(bytes memory data) public pure returns (string memory) { 67 | bytes memory alphabet = "0123456789abcdef"; 68 | 69 | bytes memory str = new bytes(2 + data.length * 2); 70 | str[0] = "0"; 71 | str[1] = "x"; 72 | for (uint256 i = 0; i < data.length; i++) { 73 | str[2 + i * 2] = alphabet[uint256(uint8(data[i] >> 4))]; 74 | str[3 + i * 2] = alphabet[uint256(uint8(data[i] & 0x0f))]; 75 | } 76 | return string(str); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /contracts/test/integration/utils/RoleHierarchyArbitrum.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {Core, Vcon, Volt, IERC20, IVolt} from "../../../core/Core.sol"; 5 | import {ArbitrumAddresses} from "../fixtures/ArbitrumAddresses.sol"; 6 | import {TribeRoles} from "../../../core/TribeRoles.sol"; 7 | import {DSTest} from "./../../unit/utils/DSTest.sol"; 8 | import {L2Core} from "../../../core/L2Core.sol"; 9 | import {Core} from "../../../core/Core.sol"; 10 | import {Vm} from "./../../unit/utils/Vm.sol"; 11 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 12 | import {RoleTesting} from "./RoleTesting.sol"; 13 | import {KArrayTree} from "./KArrayTree.sol"; 14 | import {RoleHierarchy} from "./RoleHierarchy.sol"; 15 | 16 | contract RoleHierarchyArbitrumTest is RoleHierarchy { 17 | using KArrayTree for KArrayTree.Node; 18 | 19 | Core private core = Core(ArbitrumAddresses.CORE); 20 | 21 | function setUp() public { 22 | _loadTreeToMap(roleHierarchy, core); 23 | roleToAddress[roleHierarchy.getRole()].push(ArbitrumAddresses.GOVERNOR); /// must set governor address manually 24 | } 25 | 26 | function testGovernorRevokesSubordinates() public { 27 | _testGovernorRevokesSubordinates(ArbitrumAddresses.GOVERNOR, core); 28 | } 29 | 30 | function testRevokeAllSubordinates() public { 31 | _revokeSubordinates(roleHierarchy, core); /// revoke all subordinates 32 | _testAllSubordinatesRevoked(roleHierarchy, core); /// test that all subordinates no longer have their roles 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/test/integration/utils/RoleHierarchyMainnet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {Core, Vcon, Volt, IERC20, IVolt} from "../../../core/Core.sol"; 5 | import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; 6 | import {TribeRoles} from "../../../core/TribeRoles.sol"; 7 | import {DSTest} from "./../../unit/utils/DSTest.sol"; 8 | import {L2Core} from "../../../core/L2Core.sol"; 9 | import {Core} from "../../../core/Core.sol"; 10 | import {Vm} from "./../../unit/utils/Vm.sol"; 11 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 12 | import {RoleTesting} from "./RoleTesting.sol"; 13 | import {KArrayTree} from "./KArrayTree.sol"; 14 | import {RoleHierarchy} from "./RoleHierarchy.sol"; 15 | 16 | contract RoleHierarchyMainnetIntegrationTest is RoleHierarchy { 17 | using KArrayTree for KArrayTree.Node; 18 | 19 | Core private core = Core(MainnetAddresses.CORE); 20 | 21 | function setUp() public { 22 | _loadTreeToMap(roleHierarchy, core); 23 | roleToAddress[roleHierarchy.getRole()].push(MainnetAddresses.GOVERNOR); /// must set governor address manually 24 | } 25 | 26 | function testGovernorRevokesSubordinates() public { 27 | _testGovernorRevokesSubordinates(MainnetAddresses.GOVERNOR, core); 28 | } 29 | 30 | function testRevokeAllSubordinates() public { 31 | _revokeSubordinates(roleHierarchy, core); /// revoke all subordinates 32 | _testAllSubordinatesRevoked(roleHierarchy, core); /// test that all subordinates no longer have their roles 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/test/integration/utils/RoleTesting.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 5 | import {Core, Vcon, Volt, IERC20, IVolt} from "../../../core/Core.sol"; 6 | import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; 7 | import {TribeRoles} from "../../../core/TribeRoles.sol"; 8 | import {DSTest} from "./../../unit/utils/DSTest.sol"; 9 | import {L2Core} from "../../../core/L2Core.sol"; 10 | import {Core} from "../../../core/Core.sol"; 11 | import {Vm} from "./../../unit/utils/Vm.sol"; 12 | 13 | contract RoleTesting is DSTest { 14 | /// @notice map role to the string name 15 | mapping(bytes32 => string) roleToName; 16 | 17 | constructor() { 18 | roleToName[TribeRoles.GOVERNOR] = "GOVERNOR"; 19 | roleToName[TribeRoles.PCV_CONTROLLER] = "PCV_CONTROLLER"; 20 | roleToName[TribeRoles.GUARDIAN] = "GUARDIAN"; 21 | roleToName[TribeRoles.MINTER] = "MINTER"; 22 | roleToName[TribeRoles.PCV_GUARD] = "PCV_GUARD"; 23 | roleToName[TribeRoles.PCV_GUARD_ADMIN] = "PCV_GUARD_ADMIN"; 24 | roleToName[TribeRoles.PSM_ADMIN_ROLE] = "PSM_ADMIN_ROLE"; 25 | } 26 | 27 | /// load up number of roles from Core and ensure that they match up with numbers here 28 | function _testRoleArity( 29 | bytes32[] memory allRoles, 30 | uint256[7] memory roleCounts, 31 | uint256[] memory numEachRole 32 | ) internal view { 33 | for (uint256 i = 0; i < allRoles.length; i++) { 34 | if (i == 2 && block.chainid == 42161) { 35 | numEachRole[i] = 4; 36 | } // patch for difference in PCV controller roles on arbitrum & mainnet 37 | if (numEachRole[i] != roleCounts[i]) { 38 | revert( 39 | string( 40 | abi.encodePacked( 41 | "Arity mismatch for role ", 42 | roleToName[allRoles[i]], 43 | " got: ", 44 | Strings.toString(numEachRole[i]), 45 | " expected: ", 46 | Strings.toString(roleCounts[i]), 47 | " index: ", 48 | Strings.toString(i) 49 | ) 50 | ) 51 | ); 52 | } 53 | } 54 | } 55 | 56 | /// assert that all addresses have the proper role 57 | function _testRoleAddresses( 58 | bytes32[] memory allRoles, 59 | address[][7] memory allAddresses, 60 | Core core 61 | ) internal { 62 | for (uint256 i = 0; i < allRoles.length; i++) { 63 | for (uint256 j = 0; j < allAddresses[i].length; j++) { 64 | if (i == 2 && j == 2 && block.chainid == 42161) { 65 | continue; // patch for difference in PCV controller roles on arbitrum & mainnet 66 | } 67 | assertTrue(core.hasRole(allRoles[i], allAddresses[i][j])); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /contracts/test/integration/vip/IVIP.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.8.13; 2 | 3 | import {ITimelockSimulation} from "../utils/ITimelockSimulation.sol"; 4 | 5 | /// @notice standard interface all VIPs must comply with 6 | interface IVIP { 7 | /// @notice function to do any pre-test actions 8 | function mainnetSetup() external; 9 | 10 | function arbitrumSetup() external; 11 | 12 | /// @notice validate all changes post execution 13 | function mainnetValidate() external; 14 | 15 | function arbitrumValidate() external; 16 | 17 | /// @notice get the proposal calldata 18 | function getMainnetProposal() 19 | external 20 | returns (ITimelockSimulation.action[] memory proposal); 21 | 22 | function getArbitrumProposal() 23 | external 24 | returns (ITimelockSimulation.action[] memory proposal); 25 | } 26 | -------------------------------------------------------------------------------- /contracts/test/integration/vip/Runner.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.8.13; 2 | 3 | import {vip15} from "./vip15.sol"; 4 | // import {vipx} from "./vipx.sol"; 5 | import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; 6 | import {TimelockSimulation} from "../utils/TimelockSimulation.sol"; 7 | import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; 8 | import {ArbitrumAddresses} from "../fixtures/ArbitrumAddresses.sol"; 9 | import {PCVGuardian} from "./../../../pcv/PCVGuardian.sol"; 10 | 11 | /// @dev test harness for running and simulating VOLT Improvement Proposals 12 | /// inherit the proposal to simulate 13 | contract Runner is TimelockSimulation, vip15 { 14 | /// @notice mainnet PCV Guardian 15 | PCVGuardian private immutable mainnetPCVGuardian = 16 | PCVGuardian(MainnetAddresses.PCV_GUARDIAN); 17 | 18 | /// @notice arbitrum PCV Guardian 19 | PCVGuardian private immutable arbitrumPCVGuardian = 20 | PCVGuardian(ArbitrumAddresses.PCV_GUARDIAN); 21 | 22 | /// remove all function calls inside testProposal and don't inherit the VIP 23 | /// once the proposal is live and passed 24 | function testProposalMainnet() public { 25 | mainnetSetup(); 26 | simulate( 27 | getMainnetProposal(), 28 | TimelockController(payable(MainnetAddresses.TIMELOCK_CONTROLLER)), 29 | mainnetPCVGuardian, 30 | MainnetAddresses.GOVERNOR, 31 | MainnetAddresses.EOA_1, 32 | vm, 33 | true 34 | ); 35 | mainnetValidate(); 36 | } 37 | 38 | function testProposalArbitrum() public { 39 | // arbitrumSetup(); 40 | // simulate( 41 | // getArbitrumProposal(), 42 | // TimelockController(payable(ArbitrumAddresses.TIMELOCK_CONTROLLER)), 43 | // arbitrumPCVGuardian, 44 | // ArbitrumAddresses.GOVERNOR, 45 | // ArbitrumAddresses.EOA_1, 46 | // vm, 47 | // true 48 | // ); 49 | // arbitrumValidate(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /contracts/test/integration/vip/examples/vip_x_grant.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.8.13; 2 | 3 | import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; 4 | import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; 5 | 6 | import {ITimelockSimulation} from "../../utils/ITimelockSimulation.sol"; 7 | import {ArbitrumAddresses} from "../../fixtures/ArbitrumAddresses.sol"; 8 | import {MainnetAddresses} from "../../fixtures/MainnetAddresses.sol"; 9 | import {PriceBoundPSM} from "../../../../peg/PriceBoundPSM.sol"; 10 | import {AllRoles} from "./../../utils/AllRoles.sol"; 11 | import {DSTest} from "./../../../unit/utils/DSTest.sol"; 12 | import {Core} from "../../../../core/Core.sol"; 13 | import {Volt} from "../../../../volt/Volt.sol"; 14 | import {IVIP} from "./../IVIP.sol"; 15 | import {Vm} from "./../../../unit/utils/Vm.sol"; 16 | 17 | contract vip_x_grant is DSTest, IVIP { 18 | using SafeCast for *; 19 | 20 | Vm public constant vm = Vm(HEVM_ADDRESS); 21 | 22 | /// --------------- Mainnet --------------- 23 | 24 | /// this is an example proposal that will fail the PCV Guardian whitelist test 25 | /// as PCV is being transferrred to a non authorized smart contract 26 | function getMainnetProposal() 27 | public 28 | pure 29 | override 30 | returns (ITimelockSimulation.action[] memory proposal) 31 | { 32 | proposal = new ITimelockSimulation.action[](1); 33 | 34 | proposal[0].target = MainnetAddresses.CORE; 35 | proposal[0].value = 0; 36 | proposal[0].arguments = abi.encodeWithSignature( 37 | "grantPCVController(address)", 38 | MainnetAddresses.REVOKED_EOA_1 39 | ); 40 | proposal[0] 41 | .description = "Grant PCV Controller and not setting in whitelist of PCV Guardian fails preflight checks"; 42 | } 43 | 44 | function mainnetValidate() public override {} 45 | 46 | function mainnetSetup() public override {} 47 | 48 | /// --------------- Arbitrum --------------- 49 | 50 | function getArbitrumProposal() 51 | public 52 | pure 53 | override 54 | returns (ITimelockSimulation.action[] memory proposal) 55 | {} 56 | 57 | function arbitrumSetup() public override {} 58 | 59 | function arbitrumValidate() public override {} 60 | } 61 | -------------------------------------------------------------------------------- /contracts/test/integration/vip/examples/vip_x_grant_succeed.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.8.13; 2 | 3 | import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; 4 | import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; 5 | 6 | import {ITimelockSimulation} from "../../utils/ITimelockSimulation.sol"; 7 | import {ArbitrumAddresses} from "../../fixtures/ArbitrumAddresses.sol"; 8 | import {MainnetAddresses} from "../../fixtures/MainnetAddresses.sol"; 9 | import {PriceBoundPSM} from "../../../../peg/PriceBoundPSM.sol"; 10 | import {AllRoles} from "./../../utils/AllRoles.sol"; 11 | import {DSTest} from "./../../../unit/utils/DSTest.sol"; 12 | import {Core} from "../../../../core/Core.sol"; 13 | import {Volt} from "../../../../volt/Volt.sol"; 14 | import {IVIP} from "./../IVIP.sol"; 15 | import {Vm} from "./../../../unit/utils/Vm.sol"; 16 | 17 | contract vip_x_grant_succeed is DSTest, IVIP { 18 | using SafeCast for *; 19 | 20 | Vm public constant vm = Vm(HEVM_ADDRESS); 21 | 22 | /// --------------- Mainnet --------------- 23 | 24 | /// this is an example proposal that will fail the PCV Guardian whitelist test 25 | /// as PCV is being transferrred to a non authorized smart contract 26 | function getMainnetProposal() 27 | public 28 | pure 29 | override 30 | returns (ITimelockSimulation.action[] memory proposal) 31 | { 32 | proposal = new ITimelockSimulation.action[](2); 33 | 34 | proposal[0].target = MainnetAddresses.CORE; 35 | proposal[0].value = 0; 36 | proposal[0].arguments = abi.encodeWithSignature( 37 | "grantPCVController(address)", 38 | MainnetAddresses.GOVERNOR 39 | ); 40 | proposal[0].description = "Grant PCV Controller"; 41 | 42 | proposal[1].target = MainnetAddresses.PCV_GUARDIAN; 43 | proposal[1].value = 0; 44 | proposal[1].arguments = abi.encodeWithSignature( 45 | "addWhitelistAddress(address)", 46 | MainnetAddresses.GOVERNOR 47 | ); 48 | proposal[1].description = "Add governor to whitelist in PCV Guardian"; 49 | } 50 | 51 | function mainnetValidate() public override {} 52 | 53 | function mainnetSetup() public override {} 54 | 55 | /// --------------- Arbitrum --------------- 56 | 57 | function getArbitrumProposal() 58 | public 59 | pure 60 | override 61 | returns (ITimelockSimulation.action[] memory proposal) 62 | {} 63 | 64 | function arbitrumSetup() public override {} 65 | 66 | function arbitrumValidate() public override {} 67 | } 68 | -------------------------------------------------------------------------------- /contracts/test/integration/vip/examples/vip_x_transfer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.8.13; 2 | 3 | import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; 4 | import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; 5 | 6 | import {ITimelockSimulation} from "../../utils/ITimelockSimulation.sol"; 7 | import {ArbitrumAddresses} from "../../fixtures/ArbitrumAddresses.sol"; 8 | import {MainnetAddresses} from "../../fixtures/MainnetAddresses.sol"; 9 | import {PriceBoundPSM} from "../../../../peg/PriceBoundPSM.sol"; 10 | import {AllRoles} from "./../../utils/AllRoles.sol"; 11 | import {DSTest} from "./../../../unit/utils/DSTest.sol"; 12 | import {Core} from "../../../../core/Core.sol"; 13 | import {Volt} from "../../../../volt/Volt.sol"; 14 | import {IVIP} from "./../IVIP.sol"; 15 | import {Vm} from "./../../../unit/utils/Vm.sol"; 16 | 17 | contract vip_x_transfer is DSTest, IVIP { 18 | using SafeCast for *; 19 | 20 | Vm public constant vm = Vm(HEVM_ADDRESS); 21 | 22 | /// --------------- Mainnet --------------- 23 | 24 | /// this is an example proposal that will fail the PCV Guardian whitelist test 25 | /// as PCV is being transferrred to a non authorized smart contract 26 | function getMainnetProposal() 27 | public 28 | pure 29 | override 30 | returns (ITimelockSimulation.action[] memory proposal) 31 | { 32 | proposal = new ITimelockSimulation.action[](1); 33 | 34 | proposal[0].target = MainnetAddresses.VOLT; 35 | proposal[0].value = 0; 36 | proposal[0].arguments = abi.encodeWithSignature( 37 | "transfer(address,uint256)", 38 | MainnetAddresses.REVOKED_EOA_1, 39 | 1 40 | ); 41 | proposal[0] 42 | .description = "Transfer 1 VOLT to non whitelisted contract in PCV Guardian fails preflight checks"; 43 | } 44 | 45 | function mainnetValidate() public override {} 46 | 47 | function mainnetSetup() public override { 48 | vm.startPrank(MainnetAddresses.GOVERNOR); 49 | Core(MainnetAddresses.CORE).grantMinter(MainnetAddresses.GOVERNOR); 50 | Volt(MainnetAddresses.VOLT).mint( 51 | MainnetAddresses.TIMELOCK_CONTROLLER, 52 | 1 53 | ); 54 | vm.stopPrank(); 55 | } 56 | 57 | /// --------------- Arbitrum --------------- 58 | 59 | function getArbitrumProposal() 60 | public 61 | pure 62 | override 63 | returns (ITimelockSimulation.action[] memory proposal) 64 | {} 65 | 66 | function arbitrumSetup() public override {} 67 | 68 | function arbitrumValidate() public override {} 69 | } 70 | -------------------------------------------------------------------------------- /contracts/test/integration/vip/vip5.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.8.13; 2 | 3 | import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; 4 | import {ITimelockSimulation} from "../utils/ITimelockSimulation.sol"; 5 | import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; 6 | import {ArbitrumAddresses} from "../fixtures/ArbitrumAddresses.sol"; 7 | import {DSTest} from "./../../unit/utils/DSTest.sol"; 8 | import {Core} from "../../../core/Core.sol"; 9 | import {Vm} from "./../../unit/utils/Vm.sol"; 10 | import {IVIP} from "./IVIP.sol"; 11 | import {AllRoles} from "./../utils/AllRoles.sol"; 12 | import {Volt} from "../../../volt/Volt.sol"; 13 | 14 | contract vip5 is DSTest, IVIP, AllRoles { 15 | Vm public constant vm = Vm(HEVM_ADDRESS); 16 | 17 | uint256 constant voltBalance = 10_000_000e18; 18 | 19 | function getMainnetProposal() 20 | public 21 | pure 22 | override 23 | returns (ITimelockSimulation.action[] memory proposal) 24 | { 25 | proposal = new ITimelockSimulation.action[](1); 26 | 27 | proposal[0].target = MainnetAddresses.VOLT; 28 | proposal[0].value = 0; 29 | proposal[0].arguments = abi.encodeWithSignature( 30 | "burn(uint256)", 31 | voltBalance 32 | ); 33 | proposal[0].description = "Burn 10m VOLT in deprecated timelock"; 34 | } 35 | 36 | function mainnetSetup() public override {} 37 | 38 | /// assert all contracts have their correct number of roles now, 39 | /// and that the proper addresses have the correct role after the governance upgrade 40 | function mainnetValidate() public override { 41 | uint256 deprecatedTimelockVoltBalance = Volt(MainnetAddresses.VOLT) 42 | .balanceOf(MainnetAddresses.VOLT_TIMELOCK); 43 | 44 | assertEq(deprecatedTimelockVoltBalance, 0); 45 | } 46 | 47 | /// prevent errors by reverting on arbitrum proposal functions being called on this VIP 48 | function getArbitrumProposal() 49 | public 50 | pure 51 | override 52 | returns (ITimelockSimulation.action[] memory) 53 | { 54 | revert("no arbitrum proposal"); 55 | } 56 | 57 | function arbitrumSetup() public override { 58 | if (false) { 59 | roleToName[bytes32(0)] = ""; 60 | } 61 | revert("no arbitrum proposal"); 62 | } 63 | 64 | function arbitrumValidate() public override { 65 | if (false) { 66 | roleToName[bytes32(0)] = ""; 67 | } 68 | revert("no arbitrum proposal"); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /contracts/test/unit/Volt.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | 6 | import {IVolt} from "../../volt/Volt.sol"; 7 | import {Volt} from "../../volt/Volt.sol"; 8 | import {ICore} from "../../core/ICore.sol"; 9 | import {Core} from "../../core/Core.sol"; 10 | import {Vm} from "./utils/Vm.sol"; 11 | import {DSTest} from "./utils/DSTest.sol"; 12 | import {getCore, getAddresses, VoltTestAddresses} from "./utils/Fixtures.sol"; 13 | import {Address} from "@openzeppelin/contracts/utils/Address.sol"; 14 | 15 | contract UnitTestVolt is DSTest { 16 | IVolt private volt; 17 | ICore private core; 18 | 19 | Vm public constant vm = Vm(HEVM_ADDRESS); 20 | VoltTestAddresses public addresses = getAddresses(); 21 | 22 | function setUp() public { 23 | core = getCore(); 24 | 25 | volt = core.volt(); 26 | } 27 | 28 | function testDeployedMetaData() public { 29 | assertEq(volt.totalSupply(), 0); 30 | assertTrue(core.isGovernor(addresses.governorAddress)); 31 | } 32 | 33 | function testMintsVolt() public { 34 | uint256 mintAmount = 100; 35 | 36 | vm.prank(addresses.minterAddress); 37 | volt.mint(addresses.userAddress, mintAmount); 38 | 39 | assertEq(volt.balanceOf(addresses.userAddress), mintAmount); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contracts/test/unit/core/Core.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {Address} from "@openzeppelin/contracts/utils/Address.sol"; 5 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | import {Vm} from "./../utils/Vm.sol"; 8 | import {Volt} from "../../../volt/Volt.sol"; 9 | import {Core} from "../../../core/Core.sol"; 10 | import {IVolt} from "../../../volt/Volt.sol"; 11 | import {ICore} from "../../../core/ICore.sol"; 12 | import {DSTest} from "./../utils/DSTest.sol"; 13 | import {getCore, getAddresses, VoltTestAddresses} from "./../utils/Fixtures.sol"; 14 | 15 | contract UnitTestCore is DSTest { 16 | IVolt private volt; 17 | Core private core; 18 | 19 | Vm public constant vm = Vm(HEVM_ADDRESS); 20 | VoltTestAddresses public addresses = getAddresses(); 21 | 22 | function setUp() public { 23 | core = getCore(); 24 | 25 | volt = core.volt(); 26 | } 27 | 28 | function testGovernorSetsVcon() public { 29 | vm.prank(addresses.governorAddress); 30 | core.setVcon(IERC20(addresses.userAddress)); 31 | 32 | assertEq(address(core.vcon()), addresses.userAddress); 33 | } 34 | 35 | function testNonGovernorFailsSettingVcon() public { 36 | vm.expectRevert("Permissions: Caller is not a governor"); 37 | core.setVcon(IERC20(addresses.userAddress)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /contracts/test/unit/core/L2Core.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | 6 | import {IVolt} from "../../../volt/Volt.sol"; 7 | import {Volt} from "../../../volt/Volt.sol"; 8 | import {ICore} from "../../../core/ICore.sol"; 9 | import {L2Core, Vcon} from "../../../core/L2Core.sol"; 10 | import {Vm} from "./../utils/Vm.sol"; 11 | import {DSTest} from "./../utils/DSTest.sol"; 12 | import {getL2Core, getAddresses, VoltTestAddresses} from "./../utils/Fixtures.sol"; 13 | import {Address} from "@openzeppelin/contracts/utils/Address.sol"; 14 | import {MockERC20} from "./../../../mock/MockERC20.sol"; 15 | 16 | contract UnitTestL2Core is DSTest { 17 | L2Core private core; 18 | 19 | Vm public constant vm = Vm(HEVM_ADDRESS); 20 | VoltTestAddresses public addresses = getAddresses(); 21 | MockERC20 volt; 22 | Vcon vcon; 23 | 24 | function setUp() public { 25 | volt = new MockERC20(); 26 | 27 | // Deploy Core from Governor address 28 | vm.prank(addresses.governorAddress); 29 | core = new L2Core(IVolt(address(volt))); 30 | vcon = new Vcon(addresses.governorAddress, addresses.governorAddress); 31 | } 32 | 33 | function testSetup() public { 34 | assertEq(address(core.volt()), address(volt)); 35 | assertEq(address(core.vcon()), address(0)); /// vcon starts set to address 0 36 | 37 | assertTrue(core.isGovernor(address(core))); /// core contract is governor 38 | assertTrue(core.isGovernor(addresses.governorAddress)); /// msg.sender of contract is governor 39 | 40 | bytes32 governRole = core.GOVERN_ROLE(); 41 | /// assert all roles have the proper admin 42 | assertEq(core.getRoleAdmin(core.GOVERN_ROLE()), governRole); 43 | assertEq(core.getRoleAdmin(core.BURNER_ROLE()), governRole); 44 | assertEq(core.getRoleAdmin(core.MINTER_ROLE()), governRole); 45 | assertEq(core.getRoleAdmin(core.GUARDIAN_ROLE()), governRole); 46 | assertEq(core.getRoleAdmin(core.PCV_CONTROLLER_ROLE()), governRole); 47 | 48 | /// assert there is only 1 of each role 49 | assertEq(core.getRoleMemberCount(governRole), 2); /// msg.sender of contract and core is governor 50 | assertEq(core.getRoleMemberCount(core.BURNER_ROLE()), 0); /// this role has not been granted 51 | assertEq(core.getRoleMemberCount(core.MINTER_ROLE()), 0); /// this role has not been granted 52 | assertEq(core.getRoleMemberCount(core.GUARDIAN_ROLE()), 0); /// this role has not been granted 53 | assertEq(core.getRoleMemberCount(core.PCV_CONTROLLER_ROLE()), 0); /// this role has not been granted 54 | } 55 | 56 | function testGovernorSetsVcon() public { 57 | vm.prank(addresses.governorAddress); 58 | core.setVcon(IERC20(addresses.userAddress)); 59 | 60 | assertEq(address(core.vcon()), addresses.userAddress); 61 | } 62 | 63 | function testNonGovernorFailsSettingVcon() public { 64 | vm.expectRevert("Permissions: Caller is not a governor"); 65 | core.setVcon(IERC20(addresses.userAddress)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contracts/test/unit/oracle/OraclePassThrough.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {Vm} from "./../utils/Vm.sol"; 5 | import {DSTest} from "./../utils/DSTest.sol"; 6 | import {Decimal} from "./../../../external/Decimal.sol"; 7 | import {VoltSystemOracle} from "../../../oracle/VoltSystemOracle.sol"; 8 | import {OraclePassThrough} from "../../../oracle/OraclePassThrough.sol"; 9 | import {IScalingPriceOracle} from "../../../oracle/IScalingPriceOracle.sol"; 10 | 11 | contract UnitTestOraclePassThrough is DSTest { 12 | using Decimal for Decimal.D256; 13 | 14 | VoltSystemOracle private scalingPriceOracle; 15 | 16 | OraclePassThrough private oraclePassThrough; 17 | 18 | /// @notice increase price by 3.09% per month 19 | uint256 public constant monthlyChangeRateBasisPoints = 309; 20 | 21 | Vm public constant vm = Vm(HEVM_ADDRESS); 22 | 23 | function setUp() public { 24 | /// warp to 1 to set isTimeStarted to true 25 | vm.warp(1); 26 | 27 | scalingPriceOracle = new VoltSystemOracle( 28 | monthlyChangeRateBasisPoints, 29 | 1, 30 | 1e18 31 | ); 32 | 33 | oraclePassThrough = new OraclePassThrough( 34 | IScalingPriceOracle(address(scalingPriceOracle)) 35 | ); 36 | } 37 | 38 | function testSetup() public { 39 | assertEq( 40 | address(oraclePassThrough.scalingPriceOracle()), 41 | address(scalingPriceOracle) 42 | ); 43 | assertEq(oraclePassThrough.owner(), address(this)); 44 | } 45 | 46 | function testDataPassThroughSync() public { 47 | assertEq( 48 | oraclePassThrough.currPegPrice(), 49 | scalingPriceOracle.getCurrentOraclePrice() 50 | ); 51 | assertEq( 52 | oraclePassThrough.getCurrentOraclePrice(), 53 | scalingPriceOracle.getCurrentOraclePrice() 54 | ); 55 | 56 | (Decimal.D256 memory oPrice, bool oValid) = oraclePassThrough.read(); 57 | assertEq(oPrice.value, scalingPriceOracle.getCurrentOraclePrice()); 58 | assertTrue(oValid); 59 | } 60 | 61 | function testUpdateScalingPriceOracleFailureNotGovernor() public { 62 | vm.startPrank(address(0)); 63 | vm.expectRevert(bytes("Ownable: caller is not the owner")); 64 | 65 | oraclePassThrough.updateScalingPriceOracle( 66 | IScalingPriceOracle(address(scalingPriceOracle)) 67 | ); 68 | vm.stopPrank(); 69 | } 70 | 71 | function testUpdateScalingPriceOracleSuccess() public { 72 | IScalingPriceOracle newScalingPriceOracle = IScalingPriceOracle( 73 | address(new VoltSystemOracle(monthlyChangeRateBasisPoints, 1, 1e18)) 74 | ); 75 | 76 | oraclePassThrough.updateScalingPriceOracle(newScalingPriceOracle); 77 | 78 | /// assert that scaling price oracle was updated to new contract 79 | assertEq( 80 | address(newScalingPriceOracle), 81 | address(oraclePassThrough.scalingPriceOracle()) 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /contracts/test/unit/pcv/PCVGuardAdmin.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {PCVGuardAdmin} from "../../../pcv/PCVGuardAdmin.sol"; 5 | import {getCore, getAddresses, VoltTestAddresses} from "./../utils/Fixtures.sol"; 6 | import {TribeRoles} from "../../../core/TribeRoles.sol"; 7 | import {ICore} from "../../../core/ICore.sol"; 8 | import {DSTest} from "./../utils/DSTest.sol"; 9 | import {Vm} from "./../utils/Vm.sol"; 10 | 11 | contract UnitTestPCVGuardAdmin is DSTest { 12 | PCVGuardAdmin private pcvGuardAdmin; 13 | ICore private core; 14 | 15 | Vm public constant vm = Vm(HEVM_ADDRESS); 16 | VoltTestAddresses public addresses = getAddresses(); 17 | 18 | address public guard = address(0x123456789); 19 | 20 | function setUp() public { 21 | core = getCore(); 22 | 23 | pcvGuardAdmin = new PCVGuardAdmin(address(core)); 24 | 25 | vm.startPrank(addresses.governorAddress); 26 | 27 | // create the PCV_GUARD_ADMIN role and grant it to the PCVGuardAdmin contract 28 | core.createRole(TribeRoles.PCV_GUARD_ADMIN, TribeRoles.GOVERNOR); 29 | core.grantRole(TribeRoles.PCV_GUARD_ADMIN, address(pcvGuardAdmin)); 30 | 31 | // create the PCV guard role, and grant it to the 'guard' address 32 | core.createRole(TribeRoles.PCV_GUARD, TribeRoles.PCV_GUARD_ADMIN); 33 | pcvGuardAdmin.grantPCVGuardRole(guard); 34 | vm.stopPrank(); 35 | } 36 | 37 | function testPCVGuardAdminRole() public { 38 | assertTrue( 39 | core.hasRole(TribeRoles.PCV_GUARD_ADMIN, address(pcvGuardAdmin)) 40 | ); 41 | } 42 | 43 | function testGrantPCVGuard() public { 44 | vm.prank(addresses.governorAddress); 45 | pcvGuardAdmin.grantPCVGuardRole(address(0x1234)); 46 | 47 | assertTrue(core.hasRole(TribeRoles.PCV_GUARD, address(0x1234))); 48 | } 49 | 50 | function testGrantPCVGuardFailWhenNoRoles() public { 51 | vm.expectRevert(bytes("CoreRef: Caller is not a governor")); 52 | pcvGuardAdmin.grantPCVGuardRole(address(0x1234)); 53 | } 54 | 55 | function testGrantPCVGuardFailWhenGuardian() public { 56 | vm.prank(addresses.guardianAddress); 57 | vm.expectRevert(bytes("CoreRef: Caller is not a governor")); 58 | pcvGuardAdmin.grantPCVGuardRole(address(0x1234)); 59 | } 60 | 61 | function testRevokePCVGuardGovernor() public { 62 | vm.prank(addresses.governorAddress); 63 | pcvGuardAdmin.revokePCVGuardRole(guard); 64 | 65 | assertTrue(!core.hasRole(TribeRoles.PCV_GUARD, guard)); 66 | } 67 | 68 | function testRevokePCVGuardGuardian() public { 69 | vm.prank(addresses.guardianAddress); 70 | pcvGuardAdmin.revokePCVGuardRole(guard); 71 | 72 | assertTrue(!core.hasRole(TribeRoles.PCV_GUARD, guard)); 73 | } 74 | 75 | function testRevokePCVGuardFailWhenNoRole() public { 76 | vm.expectRevert(bytes("CoreRef: Caller is not a guardian or governor")); 77 | pcvGuardAdmin.revokePCVGuardRole(guard); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /contracts/test/unit/utils/DSInvariantTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity =0.8.13; 3 | 4 | contract DSInvariantTest { 5 | address[] private targets; 6 | 7 | function targetContracts() public view virtual returns (address[] memory) { 8 | require(targets.length > 0, "NO_TARGET_CONTRACTS"); 9 | 10 | return targets; 11 | } 12 | 13 | function addTargetContract(address newTargetContract) internal virtual { 14 | targets.push(newTargetContract); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/test/unit/utils/Deviation.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {Vm} from "./../utils/Vm.sol"; 5 | import "./../utils/DSTest.sol"; 6 | import {Constants} from "./../../../Constants.sol"; 7 | import {Deviation} from "./../../../utils/Deviation.sol"; 8 | import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; 9 | import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol"; 10 | 11 | contract UnitTestDeviation is DSTest { 12 | using SafeCast for *; 13 | using Deviation for *; 14 | 15 | uint256 maxDeviationThresholdBasisPoints = 10_000; 16 | 17 | Vm public constant vm = Vm(HEVM_ADDRESS); 18 | 19 | function testDeviation() public { 20 | int256 x = 275000; 21 | int256 y = 270000; 22 | 23 | int256 delta = x - y; 24 | uint256 absDeviation = delta.toUint256(); 25 | 26 | uint256 basisPoints = (absDeviation * 27 | Constants.BASIS_POINTS_GRANULARITY) / x.toUint256(); 28 | 29 | assertEq( 30 | basisPoints, 31 | Deviation.calculateDeviationThresholdBasisPoints(x, y) 32 | ); 33 | } 34 | 35 | function testWithinDeviation() public { 36 | int256 x = 275000; 37 | int256 y = 270000; 38 | 39 | assertTrue( 40 | maxDeviationThresholdBasisPoints.isWithinDeviationThreshold(x, y) 41 | ); 42 | } 43 | 44 | function testOutsideDeviation() public { 45 | int256 x = 275000; 46 | int256 y = 577500; 47 | 48 | assertTrue( 49 | !maxDeviationThresholdBasisPoints.isWithinDeviationThreshold(x, y) 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /contracts/test/unit/utils/KArrayTree.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.8.13; 2 | 3 | import {KArrayTree} from "../../integration/utils/KArrayTree.sol"; 4 | import {TribeRoles} from "../../../core/TribeRoles.sol"; 5 | import {DSTest} from "./DSTest.sol"; 6 | import {Vm} from "./Vm.sol"; 7 | 8 | contract KArrayTreeUnitTest is DSTest { 9 | using KArrayTree for KArrayTree.Node; 10 | 11 | KArrayTree.Node public tree; 12 | Vm public constant vm = Vm(HEVM_ADDRESS); 13 | 14 | function setUp() public { 15 | tree.setRole(TribeRoles.GOVERNOR); 16 | tree.insert(TribeRoles.GOVERNOR, TribeRoles.PCV_CONTROLLER); 17 | tree.insert(TribeRoles.GOVERNOR, TribeRoles.MINTER); 18 | tree.insert(TribeRoles.GOVERNOR, TribeRoles.GUARDIAN); 19 | tree.insert(TribeRoles.GOVERNOR, TribeRoles.PCV_GUARD_ADMIN); 20 | tree.insert(TribeRoles.PCV_GUARD_ADMIN, TribeRoles.PCV_GUARD); 21 | } 22 | 23 | function testSetup() public { 24 | /// tree should have a depth of 3 25 | /// GOVERNOR -> PCV GUARD ADMIN -> PCV GUARD 26 | assertEq(tree.getMaxDepth(), 3); 27 | 28 | /// tree should have 4 children under governor 29 | assertEq(tree.getCountImmediateChildren(), 4); 30 | 31 | /// tree should have 1 child under PCV GUARD ADMIN 32 | (bool found, KArrayTree.Node storage pcvGuardAdmin) = tree.traverse( 33 | TribeRoles.PCV_GUARD_ADMIN 34 | ); 35 | assertTrue(found); 36 | assertEq(pcvGuardAdmin.getCountImmediateChildren(), 1); 37 | 38 | (bool foundGuard, ) = tree.traverse(TribeRoles.PCV_GUARD); 39 | assertTrue(foundGuard); 40 | } 41 | 42 | function testAddDuplicateFails() public { 43 | vm.expectRevert("cannot insert duplicate"); 44 | tree.insert(TribeRoles.GOVERNOR); 45 | } 46 | 47 | function testAddDuplicateFailsFind() public { 48 | vm.expectRevert("cannot insert duplicate"); 49 | tree.insert(TribeRoles.GOVERNOR, TribeRoles.PCV_GUARD); 50 | } 51 | 52 | function testCanChangeRole() public { 53 | (bool foundGuard, KArrayTree.Node storage pcvGuard) = tree.traverse( 54 | TribeRoles.PCV_GUARD_ADMIN 55 | ); 56 | assertTrue(foundGuard); 57 | pcvGuard.setRole(bytes32(0)); 58 | assertTrue(tree.exists(bytes32(0))); 59 | } 60 | 61 | function testCannotChangeToExistingRole() public { 62 | vm.expectRevert("cannot set duplicate"); 63 | tree.setRole(TribeRoles.GOVERNOR); 64 | } 65 | 66 | function testFree() public { 67 | tree.free(); 68 | assertEq(tree.getMaxDepth(), 1); /// assert the whole tree got dropped except the root node 69 | assertEq(tree.getCountImmediateChildren(), 0); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /contracts/utils/Deviation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import {Constants} from "./../Constants.sol"; 5 | import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; 6 | 7 | /// @title contract that determines whether or not a new value is within 8 | /// an acceptable deviation threshold 9 | /// @author Elliot Friedman, FEI Protocol 10 | library Deviation { 11 | using SafeCast for *; 12 | 13 | /// @notice event that is emitted when the threshold is changed 14 | event DeviationThresholdUpdate(uint256 oldThreshold, uint256 newThreshold); 15 | 16 | /// @notice return the percent deviation between a and b in basis points terms 17 | function calculateDeviationThresholdBasisPoints( 18 | int256 a, 19 | int256 b 20 | ) internal pure returns (uint256) { 21 | int256 delta = a - b; 22 | int256 basisPoints = (delta * Constants.BP_INT) / a; 23 | 24 | return (basisPoints < 0 ? basisPoints * -1 : basisPoints).toUint256(); 25 | } 26 | 27 | /// @notice function to return whether or not the new price is within 28 | /// the acceptable deviation threshold 29 | function isWithinDeviationThreshold( 30 | uint256 maxDeviationThresholdBasisPoints, 31 | int256 oldValue, 32 | int256 newValue 33 | ) internal pure returns (bool) { 34 | return 35 | maxDeviationThresholdBasisPoints >= 36 | calculateDeviationThresholdBasisPoints(oldValue, newValue); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/utils/IGlobalRateLimitedMinter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import "./IMultiRateLimited.sol"; 5 | 6 | /// @notice global contract to handle rate limited minting of VOLT on a global level 7 | /// allows whitelisted minters to call in and specify the address to mint VOLT to within 8 | /// the calling contract's limits 9 | interface IGlobalRateLimitedMinter is IMultiRateLimited { 10 | /// @notice function that all VOLT minters call to mint VOLT 11 | /// pausable and depletes the msg.sender's buffer 12 | /// @param to the recipient address of the minted VOLT 13 | /// @param amount the amount of VOLT to mint 14 | function mintVolt(address to, uint256 amount) external; 15 | 16 | /// @notice mint VOLT to the target address and deplete the whole rate limited 17 | /// minter's buffer, pausable and completely depletes the msg.sender's buffer 18 | /// @param to the recipient address of the minted VOLT 19 | /// mints all VOLT that msg.sender has in the buffer 20 | function mintMaxAllowableVolt(address to) external; 21 | } 22 | -------------------------------------------------------------------------------- /contracts/utils/IMultiRateLimited.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | /// @title interface for putting a rate limit on how fast a contract can perform an action, e.g. Minting 5 | /// @author Fei Protocol 6 | interface IMultiRateLimited { 7 | // ----------- Events ----------- 8 | 9 | /// @notice emitted when a buffer is eaten into 10 | event IndividualBufferUsed( 11 | address rateLimitedAddress, 12 | uint256 amountUsed, 13 | uint256 bufferRemaining 14 | ); 15 | 16 | /// @notice emitted when rate limit is updated 17 | event IndividualRateLimitPerSecondUpdate( 18 | address rateLimitedAddress, 19 | uint256 oldRateLimitPerSecond, 20 | uint256 newRateLimitPerSecond 21 | ); 22 | 23 | /// @notice emitted when the non gov buffer cap max is updated 24 | event MultiBufferCapUpdate(uint256 oldBufferCap, uint256 newBufferCap); 25 | 26 | /// @notice emitted when the non gov buffer rate limit per second max is updated 27 | event MultiMaxRateLimitPerSecondUpdate( 28 | uint256 oldMaxRateLimitPerSecond, 29 | uint256 newMaxRateLimitPerSecond 30 | ); 31 | 32 | // ----------- View API ----------- 33 | 34 | /// @notice the rate per second for each address 35 | function getRateLimitPerSecond(address) external view returns (uint256); 36 | 37 | /// @notice the last time the buffer was used by each address 38 | function getLastBufferUsedTime(address) external view returns (uint256); 39 | 40 | /// @notice the cap of the buffer that can be used at once 41 | function getBufferCap(address) external view returns (uint256); 42 | 43 | /// @notice the amount of action that can be used before hitting limit 44 | /// @dev replenishes at rateLimitPerSecond per second up to bufferCap 45 | function individualBuffer(address) external view returns (uint112); 46 | 47 | // ----------- Governance State Changing API ----------- 48 | 49 | /// @notice update the non gov max rate limit per second 50 | function updateMaxRateLimitPerSecond( 51 | uint256 newMaxRateLimitPerSecond 52 | ) external; 53 | 54 | /// @notice update the non gov max buffer cap 55 | function updateMaxBufferCap(uint256 newBufferCap) external; 56 | 57 | /// @notice add an authorized contract, its per second replenishment and buffer set to the non governor caps 58 | function addAddressAsMinter(address) external; 59 | 60 | /// @notice add an authorized contract, its per second replenishment and buffer 61 | function addAddress(address, uint112, uint112) external; 62 | 63 | /// @notice update an authorized contract 64 | function updateAddress(address, uint112, uint112) external; 65 | 66 | /// @notice remove an authorized contract 67 | function removeAddress(address) external; 68 | } 69 | -------------------------------------------------------------------------------- /contracts/utils/OtcEscrow.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | /* 8 | Simple OTC Escrow contract to transfer tokens OTC 9 | Inspired and forked from BadgerDAO 10 | https://github.com/Badger-Finance/badger-system/blob/develop/contracts/badger-timelock/OtcEscrow.sol 11 | */ 12 | contract OtcEscrow { 13 | using SafeERC20 for IERC20; 14 | 15 | address public receivedToken; 16 | address public sentToken; 17 | address public recipient; 18 | 19 | address public beneficiary; 20 | uint256 public receivedAmount; 21 | uint256 public sentAmount; 22 | 23 | constructor( 24 | address beneficiary_, 25 | address recipient_, 26 | address receivedToken_, 27 | address sentToken_, 28 | uint256 receivedAmount_, 29 | uint256 sentAmount_ 30 | ) { 31 | beneficiary = beneficiary_; 32 | recipient = recipient_; 33 | 34 | receivedToken = receivedToken_; 35 | sentToken = sentToken_; 36 | 37 | receivedAmount = receivedAmount_; 38 | sentAmount = sentAmount_; 39 | } 40 | 41 | modifier onlyApprovedParties() { 42 | require(msg.sender == recipient || msg.sender == beneficiary); 43 | _; 44 | } 45 | 46 | /// @dev Atomically trade specified amount of receivedToken for control over sentToken in vesting contract 47 | /// @dev Either counterparty may execute swap if sufficient token approval is given by recipient 48 | function swap() public onlyApprovedParties { 49 | // Transfer expected receivedToken from beneficiary 50 | IERC20(receivedToken).safeTransferFrom( 51 | beneficiary, 52 | recipient, 53 | receivedAmount 54 | ); 55 | 56 | // Transfer sentToken to beneficiary 57 | IERC20(sentToken).safeTransfer(address(beneficiary), sentAmount); 58 | } 59 | 60 | /// @dev Return sentToken to Fei Protocol to revoke escrow deal 61 | function revoke() external { 62 | require(msg.sender == recipient, "onlyRecipient"); 63 | uint256 sentTokenBalance = IERC20(sentToken).balanceOf(address(this)); 64 | IERC20(sentToken).safeTransfer(recipient, sentTokenBalance); 65 | } 66 | 67 | function revokeReceivedToken() external onlyApprovedParties { 68 | uint256 receivedTokenBalance = IERC20(receivedToken).balanceOf( 69 | address(this) 70 | ); 71 | IERC20(receivedToken).safeTransfer(beneficiary, receivedTokenBalance); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /contracts/utils/Timed.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | /// @title an abstract contract for timed events 5 | /// @author Fei Protocol 6 | abstract contract Timed { 7 | /// @notice the start timestamp of the timed period 8 | uint256 public startTime; 9 | 10 | /// @notice the duration of the timed period 11 | uint256 public duration; 12 | 13 | event DurationUpdate(uint256 oldDuration, uint256 newDuration); 14 | 15 | event TimerReset(uint256 startTime); 16 | 17 | constructor(uint256 _duration) { 18 | _setDuration(_duration); 19 | } 20 | 21 | modifier duringTime() { 22 | require(isTimeStarted(), "Timed: time not started"); 23 | require(!isTimeEnded(), "Timed: time ended"); 24 | _; 25 | } 26 | 27 | modifier afterTime() { 28 | require(isTimeEnded(), "Timed: time not ended"); 29 | _; 30 | } 31 | 32 | modifier afterTimeInit() { 33 | require(isTimeEnded(), "Timed: time not ended, init"); 34 | _; 35 | _initTimed(); 36 | } 37 | 38 | /// @notice return true if time period has ended 39 | function isTimeEnded() public view returns (bool) { 40 | return remainingTime() == 0; 41 | } 42 | 43 | /// @notice number of seconds remaining until time is up 44 | /// @return remaining 45 | function remainingTime() public view returns (uint256) { 46 | return duration - timeSinceStart(); // duration always >= timeSinceStart which is on [0,d] 47 | } 48 | 49 | /// @notice number of seconds since contract was initialized 50 | /// @return timestamp 51 | /// @dev will be less than or equal to duration 52 | function timeSinceStart() public view returns (uint256) { 53 | if (!isTimeStarted()) { 54 | return 0; // uninitialized 55 | } 56 | uint256 _duration = duration; 57 | uint256 timePassed = block.timestamp - startTime; // block timestamp always >= startTime 58 | return timePassed > _duration ? _duration : timePassed; 59 | } 60 | 61 | function isTimeStarted() public view returns (bool) { 62 | return startTime != 0; 63 | } 64 | 65 | function _initTimed() internal { 66 | startTime = block.timestamp; 67 | 68 | emit TimerReset(block.timestamp); 69 | } 70 | 71 | function _setDuration(uint256 newDuration) internal { 72 | require(newDuration != 0, "Timed: zero duration"); 73 | 74 | uint256 oldDuration = duration; 75 | duration = newDuration; 76 | emit DurationUpdate(oldDuration, newDuration); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /contracts/volt/IVolt.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | 6 | /// @title FEI stablecoin interface 7 | /// @author Fei Protocol 8 | interface IVolt is IERC20 { 9 | // ----------- Events ----------- 10 | 11 | event Minting( 12 | address indexed _to, 13 | address indexed _minter, 14 | uint256 _amount 15 | ); 16 | 17 | event Burning( 18 | address indexed _to, 19 | address indexed _burner, 20 | uint256 _amount 21 | ); 22 | 23 | event IncentiveContractUpdate( 24 | address indexed _incentivized, 25 | address indexed _incentiveContract 26 | ); 27 | 28 | // ----------- State changing api ----------- 29 | 30 | function burn(uint256 amount) external; 31 | 32 | function permit( 33 | address owner, 34 | address spender, 35 | uint256 value, 36 | uint256 deadline, 37 | uint8 v, 38 | bytes32 r, 39 | bytes32 s 40 | ) external; 41 | 42 | // ----------- Minter only state changing api ----------- 43 | 44 | function mint(address account, uint256 amount) external; 45 | } 46 | -------------------------------------------------------------------------------- /contracts/volt/minter/IVoltTimedMinter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /// @title a Fei Timed Minter 6 | /// @author Fei Protocol 7 | interface IVoltTimedMinter { 8 | // ----------- Events ----------- 9 | 10 | event FeiMinting(address indexed caller, uint256 feiAmount); 11 | 12 | event TargetUpdate(address oldTarget, address newTarget); 13 | 14 | event MintAmountUpdate(uint256 oldMintAmount, uint256 newMintAmount); 15 | 16 | // ----------- State changing api ----------- 17 | 18 | function mint() external; 19 | 20 | // ----------- Governor only state changing api ----------- 21 | 22 | function setTarget(address newTarget) external; 23 | 24 | // ----------- Governor or Admin only state changing api ----------- 25 | 26 | function setFrequency(uint256 newFrequency) external; 27 | 28 | function setMintAmount(uint256 newMintAmount) external; 29 | 30 | // ----------- Getters ----------- 31 | 32 | function mintAmount() external view returns (uint256); 33 | 34 | function MIN_MINT_FREQUENCY() external view returns (uint256); 35 | 36 | function MAX_MINT_FREQUENCY() external view returns (uint256); 37 | 38 | function target() external view returns (address); 39 | } 40 | -------------------------------------------------------------------------------- /contracts/volt/minter/RateLimitedMinter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.13; 3 | 4 | import "../../utils/RateLimited.sol"; 5 | 6 | /// @title abstract contract for putting a rate limit on how fast a contract can mint FEI 7 | /// @author Fei Protocol 8 | abstract contract RateLimitedMinter is RateLimited { 9 | uint256 private constant MAX_FEI_LIMIT_PER_SECOND = 10_000e18; // 10000 volt/s or ~860m volt/day 10 | 11 | constructor( 12 | uint256 _feiLimitPerSecond, 13 | uint256 _mintingBufferCap, 14 | bool _doPartialMint 15 | ) 16 | RateLimited( 17 | MAX_FEI_LIMIT_PER_SECOND, 18 | _feiLimitPerSecond, 19 | _mintingBufferCap, 20 | _doPartialMint 21 | ) 22 | {} 23 | 24 | /// @notice override the FEI minting behavior to enforce a rate limit 25 | function _mintVolt(address to, uint256 amount) internal virtual override { 26 | uint256 mintAmount = _depleteBuffer(amount); 27 | super._mintVolt(to, mintAmount); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /forge-std/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright Contributors to Forge Standard Library 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE O THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE.R 26 | -------------------------------------------------------------------------------- /forge-std/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | fs_permissions = [{ access = "read-write", path = "./"}] 3 | 4 | [rpc_endpoints] 5 | # We intentionally use both dashes and underscores in the key names to ensure both are supported. 6 | # The RPC URLs below match the StdChains URLs but append a trailing slash for testing. 7 | mainnet = "https://api.mycryptoapi.com/eth/" 8 | optimism_goerli = "https://goerli.optimism.io/" 9 | arbitrum-one-goerli = "https://goerli-rollup.arbitrum.io/rpc/" 10 | 11 | [fmt] 12 | # These are all the `forge fmt` defaults. 13 | line_length = 120 14 | tab_width = 4 15 | bracket_spacing = false 16 | int_types = 'long' 17 | multiline_func_header = 'attributes_first' 18 | quote_style = 'double' 19 | number_underscore = 'preserve' 20 | single_line_statement_blocks = 'preserve' 21 | ignore = ["src/console.sol", "src/console2.sol"] -------------------------------------------------------------------------------- /forge-std/lib/ds-test/Makefile: -------------------------------------------------------------------------------- 1 | all:; dapp build 2 | 3 | test: 4 | -dapp --use solc:0.4.23 build 5 | -dapp --use solc:0.4.26 build 6 | -dapp --use solc:0.5.17 build 7 | -dapp --use solc:0.6.12 build 8 | -dapp --use solc:0.7.5 build 9 | 10 | demo: 11 | DAPP_SRC=demo dapp --use solc:0.7.5 build 12 | -hevm dapp-test --verbose 3 13 | 14 | .PHONY: test demo 15 | -------------------------------------------------------------------------------- /forge-std/lib/ds-test/default.nix: -------------------------------------------------------------------------------- 1 | { solidityPackage, dappsys }: solidityPackage { 2 | name = "ds-test"; 3 | src = ./src; 4 | } 5 | -------------------------------------------------------------------------------- /forge-std/lib/ds-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ds-test", 3 | "version": "1.0.0", 4 | "description": "Assertions, equality checks and other test helpers ", 5 | "bugs": "https://github.com/dapphub/ds-test/issues", 6 | "license": "GPL-3.0", 7 | "author": "Contributors to ds-test", 8 | "files": [ 9 | "src/*" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/dapphub/ds-test.git" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /forge-std/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forge-std", 3 | "version": "1.0.0", 4 | "description": "Forge Standard Library is a collection of helpful contracts and libraries for use with Forge and Foundry.", 5 | "homepage": "https://book.getfoundry.sh/forge/forge-std", 6 | "bugs": "https://github.com/foundry-rs/forge-std/issues", 7 | "license": "(Apache-2.0 OR MIT)", 8 | "author": "Contributors to Forge Standard Library", 9 | "files": [ 10 | "src/*" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/foundry-rs/forge-std.git" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /forge-std/src/Common.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | import {StdStorage, Vm} from "./Components.sol"; 5 | 6 | abstract contract CommonBase { 7 | address internal constant VM_ADDRESS = 8 | address(uint160(uint256(keccak256("hevm cheat code")))); 9 | uint256 internal constant UINT256_MAX = 10 | 115792089237316195423570985008687907853269984665640564039457584007913129639935; 11 | 12 | StdStorage internal stdstore; 13 | Vm internal constant vm = Vm(VM_ADDRESS); 14 | } 15 | -------------------------------------------------------------------------------- /forge-std/src/Components.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | import "./console.sol"; 5 | import "./console2.sol"; 6 | import "./StdAssertions.sol"; 7 | import "./StdCheats.sol"; 8 | import "./StdError.sol"; 9 | import "./StdJson.sol"; 10 | import "./StdMath.sol"; 11 | import "./StdStorage.sol"; 12 | import "./StdUtils.sol"; 13 | import "./Vm.sol"; 14 | -------------------------------------------------------------------------------- /forge-std/src/Script.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | import {CommonBase} from "./Common.sol"; 5 | // forgefmt: disable-next-line 6 | import {console, console2, StdCheatsSafe, stdJson, stdMath, StdStorage, stdStorageSafe, StdUtils, VmSafe} from "./Components.sol"; 7 | 8 | abstract contract ScriptBase is CommonBase { 9 | VmSafe internal constant vmSafe = VmSafe(VM_ADDRESS); 10 | } 11 | 12 | abstract contract Script is ScriptBase, StdCheatsSafe, StdUtils { 13 | bool public IS_SCRIPT = true; 14 | } 15 | -------------------------------------------------------------------------------- /forge-std/src/StdError.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Panics work for versions >=0.8.0, but we lowered the pragma to make this compatible with Test 3 | pragma solidity >=0.6.2 <0.9.0; 4 | 5 | library stdError { 6 | bytes public constant assertionError = 7 | abi.encodeWithSignature("Panic(uint256)", 0x01); 8 | bytes public constant arithmeticError = 9 | abi.encodeWithSignature("Panic(uint256)", 0x11); 10 | bytes public constant divisionError = 11 | abi.encodeWithSignature("Panic(uint256)", 0x12); 12 | bytes public constant enumConversionError = 13 | abi.encodeWithSignature("Panic(uint256)", 0x21); 14 | bytes public constant encodeStorageError = 15 | abi.encodeWithSignature("Panic(uint256)", 0x22); 16 | bytes public constant popError = 17 | abi.encodeWithSignature("Panic(uint256)", 0x31); 18 | bytes public constant indexOOBError = 19 | abi.encodeWithSignature("Panic(uint256)", 0x32); 20 | bytes public constant memOverflowError = 21 | abi.encodeWithSignature("Panic(uint256)", 0x41); 22 | bytes public constant zeroVarError = 23 | abi.encodeWithSignature("Panic(uint256)", 0x51); 24 | } 25 | -------------------------------------------------------------------------------- /forge-std/src/StdMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | library stdMath { 5 | int256 private constant INT256_MIN = 6 | -57896044618658097711785492504343953926634992332820282019728792003956564819968; 7 | 8 | function abs(int256 a) internal pure returns (uint256) { 9 | // Required or it will fail when `a = type(int256).min` 10 | if (a == INT256_MIN) { 11 | return 12 | 57896044618658097711785492504343953926634992332820282019728792003956564819968; 13 | } 14 | 15 | return uint256(a > 0 ? a : -a); 16 | } 17 | 18 | function delta(uint256 a, uint256 b) internal pure returns (uint256) { 19 | return a > b ? a - b : b - a; 20 | } 21 | 22 | function delta(int256 a, int256 b) internal pure returns (uint256) { 23 | // a and b are of the same sign 24 | // this works thanks to two's complement, the left-most bit is the sign bit 25 | if ((a ^ b) > -1) { 26 | return delta(abs(a), abs(b)); 27 | } 28 | 29 | // a and b are of opposite signs 30 | return abs(a) + abs(b); 31 | } 32 | 33 | function percentDelta( 34 | uint256 a, 35 | uint256 b 36 | ) internal pure returns (uint256) { 37 | uint256 absDelta = delta(a, b); 38 | 39 | return (absDelta * 1e18) / b; 40 | } 41 | 42 | function percentDelta(int256 a, int256 b) internal pure returns (uint256) { 43 | uint256 absDelta = delta(a, b); 44 | uint256 absB = abs(b); 45 | 46 | return (absDelta * 1e18) / absB; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /forge-std/src/Test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | import {CommonBase} from "./Common.sol"; 5 | import "ds-test/test.sol"; 6 | // forgefmt: disable-next-line 7 | import {console, console2, StdAssertions, StdCheats, stdError, stdJson, stdMath, StdStorage, stdStorage, StdUtils, Vm} from "./Components.sol"; 8 | 9 | abstract contract TestBase is CommonBase {} 10 | 11 | abstract contract Test is 12 | TestBase, 13 | DSTest, 14 | StdAssertions, 15 | StdCheats, 16 | StdUtils 17 | {} 18 | -------------------------------------------------------------------------------- /forge-std/src/interfaces/IERC165.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.6.2; 2 | 3 | interface IERC165 { 4 | /// @notice Query if a contract implements an interface 5 | /// @param interfaceID The interface identifier, as specified in ERC-165 6 | /// @dev Interface identification is specified in ERC-165. This function 7 | /// uses less than 30,000 gas. 8 | /// @return `true` if the contract implements `interfaceID` and 9 | /// `interfaceID` is not 0xffffffff, `false` otherwise 10 | function supportsInterface(bytes4 interfaceID) external view returns (bool); 11 | } 12 | -------------------------------------------------------------------------------- /forge-std/src/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.6.2; 2 | 3 | /// @dev Interface of the ERC20 standard as defined in the EIP. 4 | /// @dev This includes the optional name, symbol, and decimals metadata. 5 | interface IERC20 { 6 | /// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`). 7 | event Transfer(address indexed from, address indexed to, uint256 value); 8 | 9 | /// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value` 10 | /// is the new allowance. 11 | event Approval( 12 | address indexed owner, 13 | address indexed spender, 14 | uint256 value 15 | ); 16 | 17 | /// @notice Returns the amount of tokens in existence. 18 | function totalSupply() external view returns (uint256); 19 | 20 | /// @notice Returns the amount of tokens owned by `account`. 21 | function balanceOf(address account) external view returns (uint256); 22 | 23 | /// @notice Moves `amount` tokens from the caller's account to `to`. 24 | function transfer(address to, uint256 amount) external returns (bool); 25 | 26 | /// @notice Returns the remaining number of tokens that `spender` is allowed 27 | /// to spend on behalf of `owner` 28 | function allowance( 29 | address owner, 30 | address spender 31 | ) external view returns (uint256); 32 | 33 | /// @notice Sets `amount` as the allowance of `spender` over the caller's tokens. 34 | /// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 35 | function approve(address spender, uint256 amount) external returns (bool); 36 | 37 | /// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism. 38 | /// `amount` is then deducted from the caller's allowance. 39 | function transferFrom( 40 | address from, 41 | address to, 42 | uint256 amount 43 | ) external returns (bool); 44 | 45 | /// @notice Returns the name of the token. 46 | function name() external view returns (string memory); 47 | 48 | /// @notice Returns the symbol of the token. 49 | function symbol() external view returns (string memory); 50 | 51 | /// @notice Returns the decimals places of the token. 52 | function decimals() external view returns (uint8); 53 | } 54 | -------------------------------------------------------------------------------- /forge-std/test/StdError.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "../src/StdError.sol"; 5 | import "../src/Test.sol"; 6 | 7 | contract StdErrorsTest is Test { 8 | ErrorsTest test; 9 | 10 | function setUp() public { 11 | test = new ErrorsTest(); 12 | } 13 | 14 | function testExpectAssertion() public { 15 | vm.expectRevert(stdError.assertionError); 16 | test.assertionError(); 17 | } 18 | 19 | function testExpectArithmetic() public { 20 | vm.expectRevert(stdError.arithmeticError); 21 | test.arithmeticError(10); 22 | } 23 | 24 | function testExpectDiv() public { 25 | vm.expectRevert(stdError.divisionError); 26 | test.divError(0); 27 | } 28 | 29 | function testExpectMod() public { 30 | vm.expectRevert(stdError.divisionError); 31 | test.modError(0); 32 | } 33 | 34 | function testExpectEnum() public { 35 | vm.expectRevert(stdError.enumConversionError); 36 | test.enumConversion(1); 37 | } 38 | 39 | function testExpectEncodeStg() public { 40 | vm.expectRevert(stdError.encodeStorageError); 41 | test.encodeStgError(); 42 | } 43 | 44 | function testExpectPop() public { 45 | vm.expectRevert(stdError.popError); 46 | test.pop(); 47 | } 48 | 49 | function testExpectOOB() public { 50 | vm.expectRevert(stdError.indexOOBError); 51 | test.indexOOBError(1); 52 | } 53 | 54 | function testExpectMem() public { 55 | vm.expectRevert(stdError.memOverflowError); 56 | test.mem(); 57 | } 58 | 59 | function testExpectIntern() public { 60 | vm.expectRevert(stdError.zeroVarError); 61 | test.intern(); 62 | } 63 | } 64 | 65 | contract ErrorsTest { 66 | enum T { 67 | T1 68 | } 69 | 70 | uint256[] public someArr; 71 | bytes someBytes; 72 | 73 | function assertionError() public pure { 74 | assert(false); 75 | } 76 | 77 | function arithmeticError(uint256 a) public pure { 78 | a -= 100; 79 | } 80 | 81 | function divError(uint256 a) public pure { 82 | 100 / a; 83 | } 84 | 85 | function modError(uint256 a) public pure { 86 | 100 % a; 87 | } 88 | 89 | function enumConversion(uint256 a) public pure { 90 | T(a); 91 | } 92 | 93 | function encodeStgError() public { 94 | /// @solidity memory-safe-assembly 95 | assembly { 96 | sstore(someBytes.slot, 1) 97 | } 98 | keccak256(someBytes); 99 | } 100 | 101 | function pop() public { 102 | someArr.pop(); 103 | } 104 | 105 | function indexOOBError(uint256 a) public pure { 106 | uint256[] memory t = new uint256[](0); 107 | t[a]; 108 | } 109 | 110 | function mem() public pure { 111 | uint256 l = 2 ** 256 / 32; 112 | new uint256[](l); 113 | } 114 | 115 | function intern() public returns (uint256) { 116 | function(uint256) internal returns (uint256) x; 117 | x(2); 118 | return 7; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | optimizer = true 3 | optimizer_runs = 200 4 | lib-paths = "node_modules" 5 | contracts = "contracts" 6 | fuzz_max_global_rejects = 100000 7 | fuzz_max_local_rejects = 100000 8 | fuzz_runs = 500 9 | -------------------------------------------------------------------------------- /governance-docs/VIP-2.md: -------------------------------------------------------------------------------- 1 | # Volt Improvement Proposal 2 2 | 3 | This VIP aims to add all EOA's as guardians across both Arbitrum and Ethereum, swap out the CPI-U oracle with a new Volt System Oracle, reduce mint and burn fees on all PSMs, and revoke the proposer role from a deprecated account. 4 | 5 | ## Known Contract Limitations 6 | 7 | Currently the new Volt System Oracle uses linear interpolation to calculate price increases from the start of the period till the end of the period. This means that interest does not compound until the end of the period, so if purchased during the middle of a period, a user will not earn interest for the part of their deposit that purchased interest until the next period. 8 | 9 | If the current block timestamp is past the periodStart + timeframe, and `compoundInterest` has not been called, then interest will not accrue which could cause issues if users are able to buy Volt at a non updated price, call compoundInterest, and then sell the Volt back to the protocol at a higher price. However, this is a known issue and will be mitigated by creating keepers which trigger the compounding just after the period ends to minimize the impact of this issue. 10 | 11 | Overflows can cause reverts in getCurrentOraclePrice if the oraclePrice is too large. This would take approximately ~6,000 years to become an issue at current interest rates. 12 | 13 | The timeframe set in the Volt Oracle was chosen at 30.42 days, which is the average amount of time in a month, this was intentional and done for readability. 14 | 15 | Leap years are not accounted for in this oracle because we are not in a leap year, and this oracle is a temporary solution until Volt 2.0 ships. 16 | 17 | monthlyChangeRateBasisPoints is an immutable value set at construction time, and yields in underlying venues can fluctuate. If rates in underlying venues diverge too much from monthlyChangeRateBasisPoints, a new VoltSystemOracle can be deployed which has an updated rate. 18 | 19 | monthlyChangeRateBasisPoints might not allow full expressivity of rates in underlying venues due to its limitation of being between 1 and 10,000 and not allowing for yields smaller than 0.01% monthly. 20 | -------------------------------------------------------------------------------- /mocha-reporter-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporterEnabled": "spec, mocha-junit-reporter", 3 | "mochaJunitReporterReporterOptions": { 4 | "mochaFile": "./test-results/test-results.[hash].xml", 5 | "testsuitesTitle": true, 6 | "suiteTitleSeparatedBy": "." 7 | } 8 | } -------------------------------------------------------------------------------- /proposals/dao/vip_1.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | DeployUpgradeFunc, 4 | NamedAddresses, 5 | SetupUpgradeFunc, 6 | TeardownUpgradeFunc, 7 | ValidateUpgradeFunc 8 | } from '@custom-types/types'; 9 | import { getImpersonatedSigner } from '@test/helpers'; 10 | 11 | /* 12 | 13 | Timelock Proposal #1 14 | 15 | Description: 16 | 17 | Steps: 18 | 1 - First grant the timelock the governor role by the multisig 19 | 2 - Grant Timelock PCV Controller 20 | 3 - Validate state changes 21 | 22 | */ 23 | 24 | const vipNumber = '1'; 25 | 26 | // Do any deployments 27 | // This should exclusively include new contract deployments 28 | const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => { 29 | console.log(`No deploy actions for vip${vipNumber}`); 30 | return { 31 | // put returned contract objects here 32 | }; 33 | }; 34 | 35 | // Do any setup necessary for running the test. 36 | // This could include setting up Hardhat to impersonate accounts, 37 | // ensuring contracts have a specific state, etc. 38 | const setup: SetupUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { 39 | const signer = await getImpersonatedSigner(addresses.protocolMultisig); 40 | await contracts.core.connect(signer).grantGovernor(addresses.optimisticTimelock); 41 | console.log('setup function run'); 42 | }; 43 | 44 | // Tears down any changes made in setup() that need to be 45 | // cleaned up before doing any validation checks. 46 | const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { 47 | console.log(`No actions to complete in teardown for vip${vipNumber}`); 48 | }; 49 | 50 | // Run any validations required on the vip using mocha or console logging 51 | // IE check balances, check state of contracts, etc. 52 | const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { 53 | const { core, optimisticTimelock } = contracts; 54 | 55 | expect(await core.isGovernor(optimisticTimelock.address)).to.be.true; 56 | expect(await core.isPCVController(optimisticTimelock.address)).to.be.true; 57 | }; 58 | 59 | export { deploy, setup, teardown, validate }; 60 | -------------------------------------------------------------------------------- /proposals/dao/vip_11.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DeployUpgradeFunc, 3 | NamedAddresses, 4 | SetupUpgradeFunc, 5 | TeardownUpgradeFunc, 6 | ValidateUpgradeFunc 7 | } from '@custom-types/types'; 8 | import config from '../../scripts/config'; 9 | import { expect } from 'chai'; 10 | import { ethers } from 'hardhat'; 11 | const { STARTING_ORACLE_PRICE, ORACLE_PERIOD_START_TIME, MONTHLY_CHANGE_RATE_BASIS_POINTS } = config; 12 | 13 | /* 14 | 15 | Volt Protocol Improvement Proposal #11 16 | 17 | Description: Patch in a new system oracle and repoint all PSM's to it 18 | 19 | Steps: 20 | 1 - Deploy Volt System Oracle 21 | 2 - Point Oracle Pass Through to New Oracle 22 | 23 | */ 24 | 25 | const vipNumber = '11'; 26 | 27 | const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => { 28 | const VoltSystemOracleFactory = await ethers.getContractFactory('VoltSystemOracle'); 29 | 30 | const voltSystemOracle = await VoltSystemOracleFactory.deploy( 31 | MONTHLY_CHANGE_RATE_BASIS_POINTS, 32 | ORACLE_PERIOD_START_TIME, 33 | STARTING_ORACLE_PRICE 34 | ); 35 | await voltSystemOracle.deployed(); 36 | 37 | console.log(`Volt System Oracle ${voltSystemOracle.address}`); 38 | 39 | console.log(`Deployed volt system oracle VIP-${vipNumber}`); 40 | return { 41 | voltSystemOracle 42 | }; 43 | }; 44 | 45 | const setup: SetupUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; 46 | 47 | // Tears down any changes made in setup() that need to be 48 | // cleaned up before doing any validation checks. 49 | const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; 50 | 51 | // Run any validations required on the vip using mocha or console logging 52 | // IE check balances, check state of contracts, etc. 53 | const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { 54 | const { oraclePassThrough, voltSystemOracle, timelockController } = contracts; 55 | 56 | expect((await voltSystemOracle.oraclePrice()).toString()).to.be.equal(STARTING_ORACLE_PRICE); 57 | expect((await voltSystemOracle.getCurrentOraclePrice()).toString()).to.be.equal(STARTING_ORACLE_PRICE); 58 | expect((await voltSystemOracle.periodStartTime()).toString()).to.be.equal(ORACLE_PERIOD_START_TIME); 59 | expect(Number(await voltSystemOracle.monthlyChangeRateBasisPoints())).to.be.equal(MONTHLY_CHANGE_RATE_BASIS_POINTS); 60 | 61 | expect(await oraclePassThrough.scalingPriceOracle()).to.be.equal(voltSystemOracle.address); 62 | expect(await oraclePassThrough.owner()).to.be.equal(timelockController.address); 63 | 64 | console.log(`Successfully validated VIP-${vipNumber}`); 65 | }; 66 | 67 | export { deploy, setup, teardown, validate }; 68 | -------------------------------------------------------------------------------- /proposals/dao/vip_11_arbitrum.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DeployUpgradeFunc, 3 | NamedAddresses, 4 | SetupUpgradeFunc, 5 | TeardownUpgradeFunc, 6 | ValidateUpgradeFunc 7 | } from '@custom-types/types'; 8 | import config from '../../scripts/config'; 9 | import { expect } from 'chai'; 10 | import { ethers } from 'hardhat'; 11 | const { STARTING_ORACLE_PRICE, ORACLE_PERIOD_START_TIME, MONTHLY_CHANGE_RATE_BASIS_POINTS } = config; 12 | 13 | /* 14 | 15 | Volt Protocol Improvement Proposal #11 16 | 17 | Description: Patch in a new system oracle and repoint all PSM's to it 18 | 19 | Steps: 20 | 1 - Deploy Volt System Oracle 21 | 2 - Point Oracle Pass Through to New Oracle 22 | 23 | */ 24 | 25 | const vipNumber = '11'; 26 | 27 | const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => { 28 | const VoltSystemOracleFactory = await ethers.getContractFactory('VoltSystemOracle'); 29 | 30 | const arbitrumVoltSystemOracle = await VoltSystemOracleFactory.deploy( 31 | MONTHLY_CHANGE_RATE_BASIS_POINTS, 32 | ORACLE_PERIOD_START_TIME, 33 | STARTING_ORACLE_PRICE 34 | ); 35 | await arbitrumVoltSystemOracle.deployed(); 36 | 37 | console.log(`Volt System Oracle ${arbitrumVoltSystemOracle.address}`); 38 | 39 | console.log(`Deployed volt system oracle VIP-${vipNumber}`); 40 | return { 41 | arbitrumVoltSystemOracle 42 | }; 43 | }; 44 | 45 | const setup: SetupUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; 46 | 47 | // Tears down any changes made in setup() that need to be 48 | // cleaned up before doing any validation checks. 49 | const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; 50 | 51 | // Run any validations required on the vip using mocha or console logging 52 | // IE check balances, check state of contracts, etc. 53 | const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { 54 | const { arbitrumOraclePassThrough, arbitrumVoltSystemOracle, arbitrumTimelockController } = contracts; 55 | 56 | expect((await arbitrumVoltSystemOracle.oraclePrice()).toString()).to.be.equal(STARTING_ORACLE_PRICE); 57 | expect((await arbitrumVoltSystemOracle.getCurrentOraclePrice()).toString()).to.be.equal(STARTING_ORACLE_PRICE); 58 | expect((await arbitrumVoltSystemOracle.periodStartTime()).toString()).to.be.equal(ORACLE_PERIOD_START_TIME); 59 | expect(Number(await arbitrumVoltSystemOracle.monthlyChangeRateBasisPoints())).to.be.equal( 60 | MONTHLY_CHANGE_RATE_BASIS_POINTS 61 | ); 62 | 63 | expect(await arbitrumOraclePassThrough.scalingPriceOracle()).to.be.equal(arbitrumVoltSystemOracle.address); 64 | expect(await arbitrumOraclePassThrough.owner()).to.be.equal(arbitrumTimelockController.address); 65 | 66 | console.log(`Successfully validated VIP-${vipNumber}`); 67 | }; 68 | 69 | export { deploy, setup, teardown, validate }; 70 | -------------------------------------------------------------------------------- /proposals/dao/vip_12.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DeployUpgradeFunc, 3 | NamedAddresses, 4 | SetupUpgradeFunc, 5 | TeardownUpgradeFunc, 6 | ValidateUpgradeFunc 7 | } from '@custom-types/types'; 8 | import config from '../../scripts/config'; 9 | import { expect } from 'chai'; 10 | import { ethers } from 'hardhat'; 11 | const { STARTING_ORACLE_PRICE, ORACLE_PERIOD_START_TIME, MONTHLY_CHANGE_RATE_BASIS_POINTS } = config; 12 | 13 | /* 14 | 15 | Volt Protocol Improvement Proposal #12 16 | 17 | Description: Create Compound PCV Router and grant it the PCV Controller role 18 | 19 | Steps: 20 | 1 - Deploy Compound PCV Router 21 | 2 - Grant newly deployed compound pcv router the PCV Controller role 22 | 23 | */ 24 | 25 | const vipNumber = '12'; 26 | 27 | const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => { 28 | const compoundPCVRouterOracleFactory = await ethers.getContractFactory('CompoundPCVRouter'); 29 | 30 | const compoundPCVRouter = await compoundPCVRouterOracleFactory.deploy( 31 | addresses.core, 32 | addresses.daiCompoundPCVDeposit, 33 | addresses.usdcCompoundPCVDeposit 34 | ); 35 | 36 | await compoundPCVRouter.deployed(); 37 | 38 | console.log(`Compound PCV Router ${compoundPCVRouter.address}`); 39 | 40 | console.log(`Deployed Compound PCV Router VIP-${vipNumber}`); 41 | return { 42 | compoundPCVRouter 43 | }; 44 | }; 45 | 46 | const setup: SetupUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; 47 | 48 | // Tears down any changes made in setup() that need to be 49 | // cleaned up before doing any validation checks. 50 | const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; 51 | 52 | // Run any validations required on the vip using mocha or console logging 53 | // IE check balances, check state of contracts, etc. 54 | const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { 55 | const { compoundPCVRouter, core } = contracts; 56 | 57 | expect(await core.isPCVController(compoundPCVRouter.address)).to.be.true; 58 | 59 | expect(await compoundPCVRouter.core()).to.be.equal(addresses.core); 60 | expect(await compoundPCVRouter.USDC()).to.be.equal(addresses.usdc); 61 | expect(await compoundPCVRouter.DAI()).to.be.equal(addresses.dai); 62 | expect(await compoundPCVRouter.daiPcvDeposit()).to.be.equal(addresses.daiCompoundPCVDeposit); 63 | expect(await compoundPCVRouter.usdcPcvDeposit()).to.be.equal(addresses.usdcCompoundPCVDeposit); 64 | expect(await compoundPCVRouter.daiPSM()).to.be.equal(addresses.makerDaiUsdcPSM); 65 | expect(await compoundPCVRouter.GEM_JOIN()).to.be.equal(addresses.makerGemJoin); 66 | 67 | console.log(`Successfully validated VIP-${vipNumber}`); 68 | }; 69 | 70 | export { deploy, setup, teardown, validate }; 71 | -------------------------------------------------------------------------------- /proposals/dao/vip_15.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DeployUpgradeFunc, 3 | NamedAddresses, 4 | SetupUpgradeFunc, 5 | TeardownUpgradeFunc, 6 | ValidateUpgradeFunc 7 | } from '@custom-types/types'; 8 | import { expect } from 'chai'; 9 | import { ethers } from 'hardhat'; 10 | import { assertApproxEq } from '@test/helpers'; 11 | 12 | /* 13 | 14 | Volt Protocol Improvement Proposal #15 15 | 16 | Deployment Steps 17 | 1. deploy volt system oracle with 0 monthlyChangeRateBasisPoints 18 | Governance Steps: 19 | 20 | Governance Steps 21 | 1. Point Oracle Pass Through to new oracle address 22 | 2. Pause minting DAI PSM 23 | 3. Pause minting USDC PSM 24 | 25 | */ 26 | 27 | let startTime; 28 | 29 | const ZERO_MONTHLY_CHANGE_BASIS_POINTS = 0; 30 | 31 | const vipNumber = '15'; 32 | 33 | const currentPrice = '1062988312906423708'; 34 | 35 | const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => { 36 | startTime = Math.floor(Date.now() / 1000).toString(); 37 | 38 | const voltSystemOracleFactory = await ethers.getContractFactory('VoltSystemOracle'); 39 | 40 | const voltSystemOracle0Bips = await voltSystemOracleFactory.deploy( 41 | ZERO_MONTHLY_CHANGE_BASIS_POINTS, 42 | startTime, 43 | currentPrice 44 | ); 45 | await voltSystemOracle0Bips.deployed(); 46 | 47 | console.log(`Volt System Oracle deployed ${voltSystemOracle0Bips.address}`); 48 | 49 | console.log(`Successfully Deployed VIP-${vipNumber}`); 50 | return { 51 | voltSystemOracle0Bips 52 | }; 53 | }; 54 | 55 | const setup: SetupUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; 56 | 57 | // Tears down any changes made in setup() that need to be 58 | // cleaned up before doing any validation checks. 59 | const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; 60 | 61 | // Run any validations required on the vip using mocha or console logging 62 | // IE check balances, check state of contracts, etc. 63 | const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { 64 | const { usdcPriceBoundPSM, daiPriceBoundPSM, voltSystemOracle, oraclePassThrough, voltSystemOracle0Bips } = contracts; 65 | 66 | /// oracle pass through validation 67 | expect(await oraclePassThrough.scalingPriceOracle()).to.be.equal(voltSystemOracle0Bips.address); 68 | 69 | /// PSMs fully paused 70 | expect(await daiPriceBoundPSM.mintPaused()).to.be.true; 71 | expect(await usdcPriceBoundPSM.mintPaused()).to.be.true; 72 | 73 | /// Volt System Oracle has correct price 74 | await assertApproxEq( 75 | await voltSystemOracle0Bips.getCurrentOraclePrice(), 76 | await voltSystemOracle.getCurrentOraclePrice(), 77 | 0 /// allow 0 bips of deviation 78 | ); 79 | 80 | await assertApproxEq( 81 | await voltSystemOracle0Bips.oraclePrice(), 82 | await voltSystemOracle.getCurrentOraclePrice(), 83 | 0 /// allow 0 bips of deviation 84 | ); 85 | 86 | expect(await voltSystemOracle0Bips.getCurrentOraclePrice()).to.be.equal(await voltSystemOracle0Bips.oraclePrice()); 87 | 88 | console.log(`Successfully validated VIP-${vipNumber}`); 89 | }; 90 | 91 | export { deploy, setup, teardown, validate }; 92 | -------------------------------------------------------------------------------- /proposals/dao/vip_6_arbitrum.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DeployUpgradeFunc, 3 | NamedAddresses, 4 | SetupUpgradeFunc, 5 | TeardownUpgradeFunc, 6 | ValidateUpgradeFunc 7 | } from '@custom-types/types'; 8 | import { expect } from 'chai'; 9 | 10 | /* 11 | 12 | Volt Protocol Improvement Proposal #6 13 | 14 | Description: Set all PSM Mint Fees to 0 on Arbitrum 15 | 16 | Steps: 17 | 1 - Set mint fee on DAI PSM to 0 18 | 2 - Set mint fee on USDC PSM to 0 19 | 20 | */ 21 | 22 | const vipNumber = '6'; 23 | 24 | const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => { 25 | return {}; /// return empty object to silence typescript error 26 | }; 27 | 28 | const setup: SetupUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; 29 | 30 | // Tears down any changes made in setup() that need to be 31 | // cleaned up before doing any validation checks. 32 | const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; 33 | 34 | // Run any validations required on the vip using mocha or console logging 35 | // IE check balances, check state of contracts, etc. 36 | const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { 37 | const { arbitrumDAIPSM, arbitrumUSDCPSM } = contracts; 38 | 39 | expect(await arbitrumUSDCPSM.mintFeeBasisPoints()).to.be.equal(0); 40 | expect(await arbitrumUSDCPSM.redeemFeeBasisPoints()).to.be.equal(0); 41 | 42 | expect(await arbitrumDAIPSM.mintFeeBasisPoints()).to.be.equal(0); 43 | expect(await arbitrumDAIPSM.redeemFeeBasisPoints()).to.be.equal(0); 44 | 45 | console.log(`Successfully validated VIP-${vipNumber} on Arbitrum`); 46 | }; 47 | 48 | export { deploy, setup, teardown, validate }; 49 | -------------------------------------------------------------------------------- /proposals/dao/vip_8.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | DeployUpgradeFunc, 4 | NamedAddresses, 5 | SetupUpgradeFunc, 6 | TeardownUpgradeFunc, 7 | ValidateUpgradeFunc 8 | } from '@custom-types/types'; 9 | import { getImpersonatedSigner } from '@test/helpers'; 10 | import { ethers } from 'ethers'; 11 | 12 | /* 13 | 14 | Timelock Proposal #8 15 | 16 | Description: Enables the DAI PSM on Mainnet 17 | 18 | Steps: 19 | 1 - Pull all FEI from the FEI PSM to the multisg 20 | 2 - Pause redemptions on the FEI PSM 21 | 3 - Transfer all FEI from multisig to the timelock 22 | 4 - Timelock approves router to spend FEI 23 | 5 - Timelock calls router to swap FEI for DAI, DAI proceeds are sent to the DAI PSM 24 | 6 - Timelock revokes router approval to spend FEI 25 | */ 26 | 27 | const vipNumber = '8'; 28 | let startingFeiBalance = 0; 29 | 30 | // Do any deployments 31 | // This should exclusively include new contract deployments 32 | const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => { 33 | console.log(`No deploy actions for vip${vipNumber}`); 34 | return { 35 | // put returned contract objects here 36 | }; 37 | }; 38 | 39 | // Do any setup necessary for running the test. 40 | // This could include setting up Hardhat to impersonate accounts, 41 | // ensuring contracts have a specific state, etc. 42 | const setup: SetupUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { 43 | const { fei, pcvGuardian, volt } = contracts; 44 | const msigSigner = await getImpersonatedSigner(addresses.protocolMultisig); 45 | 46 | await pcvGuardian.connect(msigSigner).addWhitelistAddress(addresses.makerRouter); 47 | await pcvGuardian.connect(msigSigner).withdrawAllERC20ToSafeAddress(addresses.feiPriceBoundPSM, addresses.fei); 48 | 49 | startingFeiBalance = await fei.balanceOf(msigSigner.address); 50 | await fei.connect(msigSigner).safeTransfer(addresses.daiPriceBoundPSM, startingFeiBalance); 51 | await volt.connect(msigSigner).safeTransfer(addresses.daiPriceBoundPSM, ethers.utils.parseEther('2700000')); 52 | }; 53 | 54 | // Tears down any changes made in setup() that need to be 55 | // cleaned up before doing any validation checks. 56 | const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { 57 | console.log(`No actions to complete in teardown for vip${vipNumber}`); 58 | }; 59 | 60 | // Run any validations required on the vip using mocha or console logging 61 | // IE check balances, check state of contracts, etc. 62 | const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { 63 | const { fei, feiPriceBoundPSM, dai } = contracts; 64 | 65 | const daiBalance = await feiPriceBoundPSM.getRedeemAmountOut(startingFeiBalance); 66 | 67 | expect(await feiPriceBoundPSM.redeemPaused()).to.be.true; 68 | expect(await fei.balanceOf(addresses.feiPriceBoundPSM)).to.equal(0); 69 | expect(await dai.balanceOf(addresses.daiPriceBoundPSM)).to.equal(daiBalance); 70 | expect(await fei.allowance(addresses.timelockController, addresses.makerRouter)).to.equal(0); 71 | }; 72 | 73 | export { deploy, setup, teardown, validate }; 74 | -------------------------------------------------------------------------------- /proposals/dao/vip_x.ts: -------------------------------------------------------------------------------- 1 | import hre, { ethers, artifacts } from 'hardhat'; 2 | import { expect } from 'chai'; 3 | import { 4 | DeployUpgradeFunc, 5 | NamedAddresses, 6 | SetupUpgradeFunc, 7 | TeardownUpgradeFunc, 8 | ValidateUpgradeFunc 9 | } from '@custom-types/types'; 10 | 11 | /* 12 | 13 | Timelock Proposal #9001 14 | 15 | Description: 16 | 17 | Steps: 18 | 1 - 19 | 2 - 20 | 3 - 21 | 22 | */ 23 | 24 | const vipNumber = '1'; // Change me! 25 | 26 | // Do any deployments 27 | // This should exclusively include new contract deployments 28 | const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => { 29 | console.log(`No deploy actions for vip${vipNumber}`); 30 | return { 31 | // put returned contract objects here 32 | }; 33 | }; 34 | 35 | // Do any setup necessary for running the test. 36 | // This could include setting up Hardhat to impersonate accounts, 37 | // ensuring contracts have a specific state, etc. 38 | const setup: SetupUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { 39 | console.log(`No actions to complete in setup for vip${vipNumber}`); 40 | }; 41 | 42 | // Tears down any changes made in setup() that need to be 43 | // cleaned up before doing any validation checks. 44 | const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { 45 | console.log(`No actions to complete in teardown for vip${vipNumber}`); 46 | }; 47 | 48 | // Run any validations required on the vip using mocha or console logging 49 | // IE check balances, check state of contracts, etc. 50 | const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { 51 | console.log(`No actions to complete in validate for vip${vipNumber}`); 52 | }; 53 | 54 | export { deploy, setup, teardown, validate }; 55 | -------------------------------------------------------------------------------- /proposals/vip_1.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_1: ProposalDescription = { 4 | title: 'VIP-1: Grant Timelock PCV Controller', 5 | commands: [ 6 | { 7 | target: 'core', 8 | values: '0', 9 | method: 'grantPCVController(address)', 10 | arguments: ['{optimisticTimelock}'], 11 | description: 'Grant timelock PCV Controller role' 12 | } 13 | ], 14 | description: `Grant the Timelock PCV Controller Role` 15 | }; 16 | 17 | export default vip_1; 18 | -------------------------------------------------------------------------------- /proposals/vip_10.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_10: ProposalDescription = { 4 | title: 'VIP-10: ERC20 Allocator Deployment', 5 | commands: [ 6 | { 7 | target: 'erc20Allocator', 8 | values: '0', 9 | method: 'connectPSM(address,uint248,int8)', 10 | arguments: ['{usdcPriceBoundPSM}', '100000000000', '12'], 11 | description: 'Add USDC PSM to the ERC20 Allocator' 12 | }, 13 | { 14 | target: 'erc20Allocator', 15 | values: '0', 16 | method: 'connectPSM(address,uint248,int8)', 17 | arguments: ['{daiPriceBoundPSM}', '100000000000000000000000', '0'], 18 | description: 'Add DAI PSM to the ERC20 Allocator' 19 | }, 20 | { 21 | target: 'erc20Allocator', 22 | values: '0', 23 | method: 'connectDeposit(address,address)', 24 | arguments: ['{daiPriceBoundPSM}', '{daiCompoundPCVDeposit}'], 25 | description: 'Connect DAI deposit to PSM in ERC20 Allocator' 26 | }, 27 | { 28 | target: 'erc20Allocator', 29 | values: '0', 30 | method: 'connectDeposit(address,address)', 31 | arguments: ['{usdcPriceBoundPSM}', '{usdcCompoundPCVDeposit}'], 32 | description: 'Connect USDC deposit to PSM in ERC20 Allocator' 33 | }, 34 | { 35 | target: 'core', 36 | values: '0', 37 | method: 'grantPCVController(address)', 38 | arguments: ['{erc20Allocator}'], 39 | description: 'Grant ERC20 Allocator the PCV Controller Role' 40 | } 41 | ], 42 | description: 'Create ERC20Allocator, add DAI and USDC psm and compound pcv deposit to the allocator' 43 | }; 44 | 45 | export default vip_10; 46 | -------------------------------------------------------------------------------- /proposals/vip_11.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_11: ProposalDescription = { 4 | title: 'VIP-11: Mainnet Rate Update', 5 | commands: [ 6 | { 7 | target: 'oraclePassThrough', 8 | values: '0', 9 | method: 'updateScalingPriceOracle(address)', 10 | arguments: ['{voltSystemOracle}'], 11 | description: 'Set Volt System Oracle on Oracle Pass Through' 12 | } 13 | ], 14 | description: 'Upgrade volt oracle from 60 to 144 basis points annually' 15 | }; 16 | 17 | export default vip_11; 18 | -------------------------------------------------------------------------------- /proposals/vip_11_arbitrum.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_11: ProposalDescription = { 4 | title: 'VIP-11: Arbitrum Rate Update', 5 | commands: [ 6 | { 7 | target: 'arbitrumOraclePassThrough', 8 | values: '0', 9 | method: 'updateScalingPriceOracle(address)', 10 | arguments: ['{arbitrumVoltSystemOracle}'], 11 | description: 'Set Volt System Oracle on Oracle Pass Through' 12 | } 13 | ], 14 | description: 'Upgrade volt oracle from 60 to 144 basis points annually' 15 | }; 16 | 17 | export default vip_11; 18 | -------------------------------------------------------------------------------- /proposals/vip_12.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_12: ProposalDescription = { 4 | title: 'VIP-12: Compound PCV Router', 5 | commands: [ 6 | { 7 | target: 'core', 8 | values: '0', 9 | method: 'grantPCVController(address)', 10 | arguments: ['{compoundPCVRouter}'], 11 | description: 'Grant Compound PCV Router the PCV Controller Role' 12 | } 13 | ], 14 | description: 'VIP-12 Grant Compound PCV Router the PCV Controller Role' 15 | }; 16 | 17 | export default vip_12; 18 | -------------------------------------------------------------------------------- /proposals/vip_14_arbitrum.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_14_arbitrum: ProposalDescription = { 4 | title: 'VIP-14 Arbitrum: Rate Update to 0, Pause PSM Minting', 5 | commands: [ 6 | { 7 | target: 'arbitrumOraclePassThrough', 8 | values: '0', 9 | method: 'updateScalingPriceOracle(address)', 10 | arguments: ['{voltSystemOracle0Bips}'], 11 | description: 'Point Arbitrum Oracle Pass Through to 0 basis point Volt System Oracle' 12 | }, 13 | { 14 | target: 'arbitrumUSDCPSM', 15 | values: '0', 16 | method: 'pauseMint()', 17 | arguments: [], 18 | description: 'Pause minting on USDC PSM on Arbitrum' 19 | }, 20 | { 21 | target: 'arbitrumDAIPSM', 22 | values: '0', 23 | method: 'pauseMint()', 24 | arguments: [], 25 | description: 'Pause minting on DAI PSM on Arbitrum' 26 | } 27 | ], 28 | description: ` 29 | Deployment Steps 30 | 1. deploy volt system oracle 31 | 32 | Governance Steps: 33 | 1. Point Oracle Pass Through to new oracle address 34 | 2. Pause minting on USDC PSM on Arbitrum 35 | 3. Pause minting on DAI PSM on Arbitrum 36 | ` 37 | }; 38 | 39 | export default vip_14_arbitrum; 40 | -------------------------------------------------------------------------------- /proposals/vip_2.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_2: ProposalDescription = { 4 | title: 'VIP-2: New Oracle Upgrade', 5 | commands: [ 6 | /// set updated oracles 7 | { 8 | target: 'usdcPriceBoundPSM', 9 | values: '0', 10 | method: 'setOracle(address)', 11 | arguments: ['{voltSystemOraclePassThrough}'], 12 | description: 'Set Oracle Pass Through on USDC PSM' 13 | }, 14 | { 15 | target: 'feiPriceBoundPSM', 16 | values: '0', 17 | method: 'setOracle(address)', 18 | arguments: ['{voltSystemOraclePassThrough}'], 19 | description: 'Set oracle pass through on FEI PSM' 20 | }, 21 | /// reduce mint fee to 0 22 | { 23 | target: 'usdcPriceBoundPSM', 24 | values: '0', 25 | method: 'setMintFee(uint256)', 26 | arguments: ['0'], 27 | description: 'Set mint fee to 0' 28 | }, 29 | { 30 | target: 'feiPriceBoundPSM', 31 | values: '0', 32 | method: 'setMintFee(uint256)', 33 | arguments: ['0'], 34 | description: 'Set mint fee to 0' 35 | } 36 | ], 37 | description: `Point both FEI and USDC PSM to the new OraclePassThrough contract and set mint fee to 0 on both PSMs` 38 | }; 39 | 40 | export default vip_2; 41 | -------------------------------------------------------------------------------- /proposals/vip_2_arbitrum.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_2: ProposalDescription = { 4 | title: 'VIP-2: New Oracle Upgrade on Arbitrum', 5 | commands: [ 6 | { 7 | target: 'arbitrumDAIPSM', 8 | values: '0', 9 | method: 'setOracle(address)', 10 | arguments: ['{arbitrumOraclePassThrough}'], 11 | description: 'Set Oracle Pass Through on DAI PSM' 12 | }, 13 | { 14 | target: 'arbitrumUSDCPSM', 15 | values: '0', 16 | method: 'setOracle(address)', 17 | arguments: ['{arbitrumOraclePassThrough}'], 18 | description: 'Set Oracle Pass Through on USDC PSM' 19 | }, 20 | { 21 | target: 'arbitrumDAIPSM', 22 | values: '0', 23 | method: 'setMintFee(uint256)', 24 | arguments: ['5'], 25 | description: 'Set mint fee to 5 basis points on DAI PSM' 26 | }, 27 | { 28 | target: 'arbitrumUSDCPSM', 29 | values: '0', 30 | method: 'setMintFee(uint256)', 31 | arguments: ['5'], 32 | description: 'Set mint fee to 5 basis points on USDC PSM' 33 | } 34 | ], 35 | description: ` 36 | Point both DAI and USDC PSM to the new OraclePassThrough contract, set mint and redeem fee to 5 basis points 37 | ` 38 | }; 39 | 40 | export default vip_2; 41 | -------------------------------------------------------------------------------- /proposals/vip_3.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_3: ProposalDescription = { 4 | title: 'VIP-3: Repay Fei Loan', 5 | commands: [ 6 | { 7 | target: 'fei', 8 | values: '0', 9 | method: 'transfer(address,uint256)', 10 | arguments: ['{otcEscrowRepayment}', '10170000000000000000000000'], 11 | description: 'Transfer Fei to Otc Escrow' 12 | } 13 | ], 14 | description: `Transfer Fei to Otc Escrow Contract to repay loan` 15 | }; 16 | 17 | export default vip_3; 18 | -------------------------------------------------------------------------------- /proposals/vip_4.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_4: ProposalDescription = { 4 | title: 'VIP-4: Role Cleanup', 5 | commands: [ 6 | /// Role revocations 7 | { 8 | target: 'pcvGuardAdmin', 9 | values: '0', 10 | method: 'revokePCVGuardRole(address)', 11 | arguments: ['{pcvGuardRevoked1}'], 12 | description: 'Revoke PCV Guard role from revoked EOA1 by calling PCVGuardAdmin' 13 | }, 14 | { 15 | target: 'core', 16 | values: '0', 17 | method: 'revokeGuardian(address)', 18 | arguments: ['{pcvGuardEOA1}'], 19 | description: 'Revoke guardian from new EOA1' 20 | }, 21 | { 22 | target: 'core', 23 | values: '0', 24 | method: 'revokeMinter(address)', 25 | arguments: ['{globalRateLimitedMinter}'], 26 | description: 'Revoke minter from global rate limited minter' 27 | }, 28 | { 29 | target: 'core', 30 | values: '0', 31 | method: 'revokePCVController(address)', 32 | arguments: ['{nonCustodialFusePSM}'], 33 | description: 'Revoke PCV Controller from non custodial psm' 34 | }, 35 | { 36 | target: 'core', 37 | values: '0', 38 | method: 'revokeGuardian(address)', 39 | arguments: ['{protocolMultisig}'], 40 | description: 'Revoke Guardian Role from Multisig' 41 | }, 42 | /// Role additions 43 | { 44 | target: 'pcvGuardAdmin', 45 | values: '0', 46 | method: 'grantPCVGuardRole(address)', 47 | arguments: ['{pcvGuardEOA1}'], 48 | description: 'Grant EOA1 PCV Guard role' 49 | }, 50 | { 51 | target: 'pcvGuardAdmin', 52 | values: '0', 53 | method: 'grantPCVGuardRole(address)', 54 | arguments: ['{pcvGuardEOA2}'], 55 | description: 'Grant EOA2 PCV Guard role' 56 | }, 57 | { 58 | target: 'pcvGuardAdmin', 59 | values: '0', 60 | method: 'grantPCVGuardRole(address)', 61 | arguments: ['{pcvGuardEOA3}'], 62 | description: 'Grant EOA3 PCV Guard role' 63 | } 64 | ], 65 | description: `Revoke unused roles, add new roles` 66 | }; 67 | 68 | export default vip_4; 69 | -------------------------------------------------------------------------------- /proposals/vip_4_arbitrum.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_4: ProposalDescription = { 4 | title: 'VIP-4: Role Cleanup', 5 | commands: [ 6 | /// Role revocations 7 | { 8 | target: 'arbitrumPCVGuardAdmin', 9 | values: '0', 10 | method: 'revokePCVGuardRole(address)', 11 | arguments: ['{pcvGuardRevoked1}'], 12 | description: 'Revoke PCV Guard role from revoked EOA1 by calling PCVGuardAdmin' 13 | }, 14 | { 15 | target: 'arbitrumCore', 16 | values: '0', 17 | method: 'revokeGuardian(address)', 18 | arguments: ['{pcvGuardEOA1}'], 19 | description: 'Revoke EOA1 as a guardian' 20 | }, 21 | { 22 | target: 'arbitrumCore', 23 | values: '0', 24 | method: 'revokePCVController(address)', 25 | arguments: ['{arbitrumOptimisticTimelock}'], 26 | description: `Revoke Deprecated Timelock's PCV Controller role` 27 | }, 28 | /// Role additions 29 | { 30 | target: 'arbitrumPCVGuardAdmin', 31 | values: '0', 32 | method: 'grantPCVGuardRole(address)', 33 | arguments: ['{pcvGuardEOA1}'], 34 | description: 'Grant EOA 1 PCV Guard Role' 35 | }, 36 | { 37 | target: 'arbitrumPCVGuardAdmin', 38 | values: '0', 39 | method: 'grantPCVGuardRole(address)', 40 | arguments: ['{pcvGuardEOA2}'], 41 | description: 'Grant EOA 2 PCV Guard Role' 42 | }, 43 | { 44 | target: 'arbitrumPCVGuardAdmin', 45 | values: '0', 46 | method: 'grantPCVGuardRole(address)', 47 | arguments: ['{pcvGuardEOA3}'], 48 | description: 'Grant EOA 3 PCV Guard Role' 49 | } 50 | ], 51 | description: `Revoke unused roles, add new roles` 52 | }; 53 | 54 | export default vip_4; 55 | -------------------------------------------------------------------------------- /proposals/vip_5.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_5: ProposalDescription = { 4 | title: 'VIP-5: Burn Deprecated Timelock VOLT', 5 | commands: [ 6 | { 7 | target: 'volt', 8 | values: '0', 9 | method: 'burn(uint256)', 10 | arguments: ['10000000000000000000000000'], 11 | description: 'Burn 10m VOLT in deprecated timelock' 12 | } 13 | ], 14 | description: 'Burn 10m VOLT collateral from Tribe DAO loan' 15 | }; 16 | 17 | export default vip_5; 18 | -------------------------------------------------------------------------------- /proposals/vip_6_arbitrum.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_6: ProposalDescription = { 4 | title: `VIP-6: Set Mint and Redeem Fees to 0 on Arbitrum`, 5 | commands: [ 6 | { 7 | target: 'arbitrumDAIPSM', 8 | values: '0', 9 | method: 'setMintFee(uint256)', 10 | arguments: ['0'], 11 | description: 'Set mint fee to 0 basis points on DAI PSM' 12 | }, 13 | { 14 | target: 'arbitrumDAIPSM', 15 | values: '0', 16 | method: 'setRedeemFee(uint256)', 17 | arguments: ['0'], 18 | description: 'Set redeem fee to 0 basis points on DAI PSM' 19 | }, 20 | { 21 | target: 'arbitrumUSDCPSM', 22 | values: '0', 23 | method: 'setMintFee(uint256)', 24 | arguments: ['0'], 25 | description: 'Set mint fee to 0 basis points on USDC PSM' 26 | }, 27 | { 28 | target: 'arbitrumUSDCPSM', 29 | values: '0', 30 | method: 'setRedeemFee(uint256)', 31 | arguments: ['0'], 32 | description: 'Set redeem fee to 0 basis points on USDC PSM' 33 | } 34 | ], 35 | description: 'Set Mint Fees to 0 on Arbitrum' 36 | }; 37 | 38 | export default vip_6; 39 | -------------------------------------------------------------------------------- /proposals/vip_7.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_7: ProposalDescription = { 4 | title: 'VIP-7: PSM Restructuring', 5 | commands: [ 6 | { 7 | target: 'feiPriceBoundPSM', 8 | values: '0', 9 | method: 'pauseMint()', 10 | arguments: [], 11 | description: 'Pause Minting on the FEI PSM' 12 | }, 13 | { 14 | target: 'pcvGuardian', 15 | values: '0', 16 | method: 'addWhitelistAddress(address)', 17 | arguments: ['{daiPriceBoundPSM}'], 18 | description: 'Add DAI PSM to whitelisted addresses on PCV Guardian' 19 | }, 20 | { 21 | target: 'usdcPriceBoundPSM', 22 | values: '0', 23 | method: 'unpauseRedeem()', 24 | arguments: [], 25 | description: 'Unpause redemptions for USDC PSM' 26 | }, 27 | { 28 | target: 'daiPriceBoundPSM', 29 | values: '0', 30 | method: 'setOracle(address)', 31 | arguments: ['{voltSystemOraclePassThrough}'], 32 | description: 'Set Oracle Pass Through on DAI PSM' 33 | } 34 | ], 35 | description: 36 | 'Pauses minting on FEI PSM, remove VOLT liquidity from FEI PSM, adds DAI PSM to PCVGuardian whitelist, unpause redemptions on the USDC PSM' 37 | }; 38 | 39 | export default vip_7; 40 | -------------------------------------------------------------------------------- /proposals/vip_8.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_8: ProposalDescription = { 4 | title: 'VIP-8: Enable DAI PSM', 5 | commands: [ 6 | { 7 | target: 'feiPriceBoundPSM', 8 | values: '0', 9 | method: 'pauseRedeem()', 10 | arguments: [], 11 | description: 'Pause redemptions on the FEI PSM' 12 | }, 13 | { 14 | target: 'fei', 15 | values: '0', 16 | method: 'approve(address,uint256)', 17 | arguments: ['{makerRouter}', '2400000000000000000000000'], 18 | description: 'Timelock approves router to spend FEI' 19 | }, 20 | { 21 | target: 'makerRouter', 22 | values: '0', 23 | method: 'swapAllFeiForDai(address)', 24 | arguments: ['{daiPriceBoundPSM}'], 25 | description: 'Swaps FEI for DAI proceeds sent to DAI PSM' 26 | }, 27 | { 28 | target: 'fei', 29 | values: '0', 30 | method: 'approve(address,uint256)', 31 | arguments: ['{makerRouter}', 0], 32 | description: 'Timelock revokes router approval to spend FEI' 33 | } 34 | ], 35 | description: 'Enables the DAI PSM on Mainnet' 36 | }; 37 | 38 | export default vip_8; 39 | -------------------------------------------------------------------------------- /proposals/vip_9.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_9: ProposalDescription = { 4 | title: 'VIP-9: Whitelist Compound PCV Deposits in PCV Guardian', 5 | commands: [ 6 | { 7 | target: 'pcvGuardian', 8 | values: '0', 9 | method: 'addWhitelistAddresses(address[])', 10 | arguments: [['{daiCompoundPCVDeposit}', '{feiCompoundPCVDeposit}', '{usdcCompoundPCVDeposit}']], 11 | description: 'Add DAI, FEI, and USDC Compound PCV Deposit to PCV Guardian' 12 | } 13 | ], 14 | description: 'Add DAI, FEI, and USDC Compound PCV Deposit to PCV Guardian' 15 | }; 16 | 17 | export default vip_9; 18 | -------------------------------------------------------------------------------- /proposals/vip_x.ts: -------------------------------------------------------------------------------- 1 | import { ProposalDescription } from '@custom-types/types'; 2 | 3 | const vip_x: ProposalDescription = { 4 | title: 'VIP-X: Title', 5 | commands: [ 6 | { 7 | target: '', 8 | values: '', 9 | method: '', 10 | arguments: [], 11 | description: '' 12 | } 13 | ], 14 | description: 'vip_x will change the game!' 15 | }; 16 | 17 | export default vip_x; 18 | -------------------------------------------------------------------------------- /protocol-configuration/networksForVerification.ts: -------------------------------------------------------------------------------- 1 | // only verify on etherscan/arbiscan when on these specific networks 2 | const NetworksForVerification = { 3 | mainnet: true, 4 | arbitrumOne: true 5 | }; 6 | 7 | export default NetworksForVerification; 8 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @openzeppelin/=node_modules/@openzeppelin/ 2 | ds-test/=forge-std/lib/ds-test/src/ -------------------------------------------------------------------------------- /scripts/L2Config.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | 3 | const l2config = { 4 | /// Decimal normalizers for PSMs 5 | 6 | /// Oracle price gets scaled up by 1e12 to account for the differences in decimals of USDC and VOLT. 7 | /// USDC has 6 decimals while Volt has 18, thus creating a difference of 12 that has to be normalized 8 | voltUSDCDecimalsNormalizer: 12, 9 | /// Oracle price does not need to be scaled up because both tokens have 18 decimals 10 | voltDAIDecimalsNormalizer: 0, 11 | reservesThreshold: ethers.constants.MaxUint256.toString(), 12 | 13 | /// Floor and ceiling are inverted due to oracle price inversion 14 | voltDAIFloorPrice: 9_000, 15 | voltDAICeilingPrice: 10_000, 16 | 17 | /// Need to scale up price of floor and ceiling by 1e12 to account for decimal normalizer that is factored into oracle price 18 | voltUSDCFloorPrice: '9000000000000000', 19 | voltUSDCCeilingPrice: '10000000000000000', 20 | 21 | voltPSMBufferCap: 0, 22 | mintLimitPerSecond: 0, 23 | 24 | ADDRESS_ONE: '0x0000000000000000000000000000000000000001', 25 | L2_REDEEM_FEE_BASIS_POINTS: 5, 26 | 27 | ACTUAL_START_TIME: '1655251723', 28 | STARTING_L2_ORACLE_PRICE: '1033724384083385655', 29 | 30 | /// Chainlink 31 | L2_ARBITRUM_JOB_ID: ethers.utils.toUtf8Bytes('db685451903340c590d22eb505d49946'), 32 | L2_ARBITRUM_CHAINLINK_FEE: ethers.utils.parseEther('1'), /// 1 Link to request data on L2 33 | L2_ARBITRUM_CHAINLINK_ORACLE_ADDRESS: '0xf76F586F6aAC0c8dE147Eea75D76AB7c2f23eDC2', 34 | L2_ARBITRUM_CHAINLINK_TOKEN: '0xf97f4df75117a78c1A5a0DBb814Af92458539FB4', 35 | 36 | L2_ARBITRUM_PREVIOUS_MONTH: '289109', 37 | L2_ARBITRUM_CURRENT_MONTH: '292296', 38 | 39 | L2_ARBITRUM_PROTOCOL_MULTISIG_ADDRESS: '0x1A1075cef632624153176CCf19Ae0175953CF010', 40 | L2_ARBITRUM_VOLT: '0x6Ba6f18a290Cd55cf1B00be2bEc5c954cb29fAc5', 41 | L2_DAI: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1', /// DAI has the same address on both Optimism and Arbitrum 42 | L2_ARBITRUM_USDC: '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8', 43 | 44 | /// Roles 45 | PCV_GUARD_ROLE: ethers.utils.id('PCV_GUARD_ROLE'), 46 | PCV_GUARD_ADMIN_ROLE: ethers.utils.id('PCV_GUARD_ADMIN_ROLE'), 47 | GOVERN_ROLE: ethers.utils.id('GOVERN_ROLE') 48 | }; 49 | 50 | export default l2config; 51 | -------------------------------------------------------------------------------- /scripts/deploy/migrations.ts: -------------------------------------------------------------------------------- 1 | import MainnetAddresses from '../../protocol-configuration/mainnetAddresses'; 2 | import { ethers } from 'hardhat'; 3 | 4 | // Run the deployment for DEPLOY_FILE 5 | async function main() { 6 | const isArbitrumVip = process.env.ENABLE_ARBITRUM_FORKING; 7 | const proposalName = process.env.DEPLOY_FILE + (isArbitrumVip ? '_arbitrum' : ''); 8 | 9 | if (!proposalName) { 10 | throw new Error('DEPLOY_FILE env variable not set'); 11 | } 12 | 13 | const deployAddress = (await ethers.getSigners())[0].address; 14 | 15 | const mainnetAddresses = {}; 16 | Object.keys(MainnetAddresses).map((key) => { 17 | mainnetAddresses[key] = MainnetAddresses[key].address; 18 | return true; 19 | }); 20 | 21 | const { deploy } = await import(`@proposals/dao/${proposalName}`); 22 | 23 | await deploy(deployAddress, mainnetAddresses, true); 24 | } 25 | 26 | main() 27 | .then(() => process.exit(0)) 28 | .catch((err) => { 29 | console.log(err); 30 | process.exit(1); 31 | }); 32 | -------------------------------------------------------------------------------- /scripts/l2SPOdeploy.ts: -------------------------------------------------------------------------------- 1 | import l2config from './l2config'; 2 | import { getAllContractAddresses } from '@scripts/utils/loadContracts'; 3 | import { ethers } from 'hardhat'; 4 | import '@nomiclabs/hardhat-ethers'; 5 | import { expect } from 'chai'; 6 | 7 | const { 8 | /// bls cpi-u inflation data 9 | L2_ARBITRUM_PREVIOUS_MONTH, 10 | L2_ARBITRUM_CURRENT_MONTH, 11 | 12 | /// L2 chainlink 13 | STARTING_L2_ORACLE_PRICE, 14 | ACTUAL_START_TIME, 15 | L2_ARBITRUM_JOB_ID, 16 | L2_ARBITRUM_CHAINLINK_FEE, 17 | L2_ARBITRUM_CHAINLINK_TOKEN 18 | } = l2config; 19 | 20 | async function deploy() { 21 | /// -------- System Deployment -------- 22 | 23 | const addresses = await getAllContractAddresses(); 24 | const L2ScalingPriceOracleFactory = await ethers.getContractFactory('L2ScalingPriceOracle'); 25 | 26 | const scalingPriceOracle = await L2ScalingPriceOracleFactory.deploy( 27 | addresses.arbitrumFiewsChainlinkOracle, 28 | L2_ARBITRUM_JOB_ID, 29 | L2_ARBITRUM_CHAINLINK_FEE, 30 | L2_ARBITRUM_CURRENT_MONTH, 31 | L2_ARBITRUM_PREVIOUS_MONTH, 32 | L2_ARBITRUM_CHAINLINK_TOKEN, 33 | ACTUAL_START_TIME, 34 | STARTING_L2_ORACLE_PRICE 35 | ); 36 | await scalingPriceOracle.deployed(); 37 | 38 | console.log(`⚡L2ScalingPriceOracle⚡: ${scalingPriceOracle.address}`); 39 | 40 | expect(await scalingPriceOracle.getChainlinkTokenAddress()).to.be.equal(L2_ARBITRUM_CHAINLINK_TOKEN); 41 | expect(await scalingPriceOracle.monthlyChangeRateBasisPoints()).to.be.equal(110); 42 | expect(await scalingPriceOracle.previousMonth()).to.be.equal(L2_ARBITRUM_PREVIOUS_MONTH); 43 | expect(await scalingPriceOracle.currentMonth()).to.be.equal(L2_ARBITRUM_CURRENT_MONTH); 44 | expect(await scalingPriceOracle.oraclePrice()).to.be.equal(STARTING_L2_ORACLE_PRICE); 45 | expect(await scalingPriceOracle.startTime()).to.be.equal(ACTUAL_START_TIME); 46 | } 47 | 48 | deploy() 49 | .then(() => process.exit(0)) 50 | .catch((err) => { 51 | console.log(err); 52 | process.exit(1); 53 | }); 54 | -------------------------------------------------------------------------------- /scripts/l2configuration.ts: -------------------------------------------------------------------------------- 1 | import config from './config'; 2 | import { getAllContracts, getAllContractAddresses } from '@scripts/utils/loadContracts'; 3 | import { ethers } from 'hardhat'; 4 | import '@nomiclabs/hardhat-ethers'; 5 | 6 | const { PCV_GUARD_ADMIN_ROLE, PCV_GUARD_ROLE } = config; 7 | 8 | async function configuration() { 9 | const deployer = (await ethers.getSigners())[0]; 10 | 11 | const addresses = await getAllContractAddresses(); 12 | const { 13 | arbitrumOptimisticTimelock, 14 | arbitrumOraclePassThrough, 15 | arbitrumCore, 16 | arbitrumPCVGuardAdmin, 17 | arbitrumPCVGuardian 18 | } = await getAllContracts(); 19 | 20 | /// -------- Oracle Actions -------- 21 | 22 | /// transfer ownership to the multisig 23 | await arbitrumOraclePassThrough.transferOwnership(addresses.arbitrumProtocolMultisig); 24 | 25 | /// -------- PCV Guardian Actions -------- 26 | 27 | // Grant PCV Controller and Guardian Roles to the PCV Guardian Contract 28 | await arbitrumCore.grantPCVController(arbitrumPCVGuardian.address); 29 | await arbitrumCore.grantGuardian(arbitrumPCVGuardian.address); 30 | 31 | // Create the PCV_GUARD_ADMIN Role and Grant to the PCV Guard Admin Contract 32 | await arbitrumCore.createRole(PCV_GUARD_ADMIN_ROLE, await arbitrumCore.GOVERN_ROLE()); 33 | await arbitrumCore.grantRole(PCV_GUARD_ADMIN_ROLE, arbitrumPCVGuardAdmin.address); 34 | 35 | // Create the PCV Guard Role and grant the role to PCV Guards via the PCV Guard Admin contract 36 | await arbitrumCore.createRole(PCV_GUARD_ROLE, PCV_GUARD_ADMIN_ROLE); 37 | await arbitrumPCVGuardAdmin.grantPCVGuardRole(addresses.pcvGuardEOA1); 38 | await arbitrumPCVGuardAdmin.grantPCVGuardRole(addresses.pcvGuardEOA2); 39 | 40 | /// -------- Core Multisig and Timelock Actions -------- 41 | 42 | await arbitrumCore.grantGovernor(addresses.arbitrumProtocolMultisig); /// give multisig the governor role 43 | await arbitrumCore.grantPCVController(addresses.arbitrumProtocolMultisig); /// give multisig the PCV controller role 44 | 45 | await arbitrumCore.grantGovernor(arbitrumOptimisticTimelock.address); /// give timelock the governor role 46 | await arbitrumCore.grantPCVController(arbitrumOptimisticTimelock.address); /// give timelock the PCV controller role 47 | 48 | /// -------- Deployer Revokes Governor -------- 49 | 50 | /// deployer revokes their governor role from core 51 | await arbitrumCore.revokeGovernor(deployer.address); 52 | 53 | console.log(`\n ~~~~~ Configured Contracts on Arbitrum Successfully ~~~~~ \n`); 54 | } 55 | 56 | configuration() 57 | .then(() => process.exit(0)) 58 | .catch((err) => { 59 | console.log(err); 60 | process.exit(1); 61 | }); 62 | -------------------------------------------------------------------------------- /scripts/oracleDeploy.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'hardhat'; 2 | import config from './config'; 3 | 4 | const { 5 | JOB_ID, 6 | CHAINLINK_ORACLE_ADDRESS, 7 | CHAINLINK_FEE, 8 | /// bls cpi-u inflation data 9 | CURRENT_MONTH_INFLATION_DATA, 10 | PREVIOUS_MONTH_INFLATION_DATA 11 | } = config; 12 | 13 | /// ~~~ Oracle Contracts ~~~ 14 | 15 | /// 1. Scaling Price Oracle 16 | /// 2. Oracle Pass Through 17 | 18 | async function deployOracles() { 19 | const ScalingPriceOracleFactory = await ethers.getContractFactory('ScalingPriceOracle'); 20 | const OraclePassThroughFactory = await ethers.getContractFactory('OraclePassThrough'); 21 | 22 | const scalingPriceOracle = await ScalingPriceOracleFactory.deploy( 23 | CHAINLINK_ORACLE_ADDRESS, 24 | JOB_ID, 25 | CHAINLINK_FEE, 26 | CURRENT_MONTH_INFLATION_DATA, 27 | PREVIOUS_MONTH_INFLATION_DATA 28 | ); 29 | await scalingPriceOracle.deployed(); 30 | 31 | const oraclePassThrough = await OraclePassThroughFactory.deploy(scalingPriceOracle.address); 32 | await oraclePassThrough.deployed(); 33 | 34 | console.log('\n ~~~~~ Deployed Oracle Contracts Successfully ~~~~~ \n'); 35 | console.log(`OraclePassThrough: ${oraclePassThrough.address}`); 36 | console.log(`ScalingPriceOracle: ${scalingPriceOracle.address}`); 37 | } 38 | 39 | deployOracles() 40 | .then(() => process.exit(0)) 41 | .catch((err) => { 42 | console.log(err); 43 | process.exit(1); 44 | }); 45 | -------------------------------------------------------------------------------- /scripts/otcEscrow.ts: -------------------------------------------------------------------------------- 1 | import hre, { ethers } from 'hardhat'; 2 | import config from './config'; 3 | 4 | async function deploy() { 5 | const { FEI, VOLT, FEI_DAO_TIMELOCK, VOLT_SWAP_AMOUNT, VOLT_FUSE_PCV_DEPOSIT } = config; 6 | 7 | if (!FEI || !VOLT || !FEI_DAO_TIMELOCK || !VOLT_SWAP_AMOUNT || !VOLT_FUSE_PCV_DEPOSIT) { 8 | throw new Error('Variable not set'); 9 | } 10 | 11 | const factory = await ethers.getContractFactory('OtcEscrow'); 12 | const otcEscrow = await factory.deploy( 13 | FEI_DAO_TIMELOCK, // FEI DAO timelock receives the VOLT 14 | VOLT_FUSE_PCV_DEPOSIT, // VOLT FUSE PCV Deposit is the recipient of the FEI because it won't deposit the VOLT into fuse 15 | FEI, // transfer FEI from timelock to the VOLT FUSE PCV Deposit 16 | VOLT, // transfer VOLT to the FEI DAO timelock 17 | ethers.constants.WeiPerEther.mul(10_170_000), // 10.17m FEI as oracle price is currently $1.017 USD per VOLT 18 | VOLT_SWAP_AMOUNT // FEI DAO receives 10m VOLT 19 | ); 20 | await otcEscrow.deployed(); 21 | 22 | console.log('OTC deployed to: ', otcEscrow.address); 23 | } 24 | 25 | async function verify(otcEscrowAddress: string) { 26 | const { FEI, VOLT, FEI_DAO_TIMELOCK, VOLT_SWAP_AMOUNT, VOLT_FUSE_PCV_DEPOSIT } = config; 27 | 28 | await hre.run('verify:verify', { 29 | address: otcEscrowAddress, 30 | constructorArguments: [ 31 | FEI_DAO_TIMELOCK, // FEI DAO timelock receives the VOLT 32 | VOLT_FUSE_PCV_DEPOSIT, // VOLT FUSE PCV Deposit is the recipient of the FEI because it won't deposit the VOLT into fuse 33 | FEI, // transfer FEI from timelock to the VOLT FUSE PCV Deposit 34 | VOLT, // transfer VOLT to the FEI DAO timelock 35 | ethers.constants.WeiPerEther.mul(10_170_000), // 10.17m FEI as oracle price is currently $1.017 USD per VOLT 36 | VOLT_SWAP_AMOUNT // FEI DAO receives 10m VOLT 37 | ] 38 | }); 39 | 40 | console.log('OTC deployed to: ', otcEscrowAddress); 41 | } 42 | 43 | if (process.env.DEPLOY) { 44 | deploy() 45 | .then(() => process.exit(0)) 46 | .catch((err) => { 47 | console.log(err); 48 | process.exit(1); 49 | }); 50 | } else { 51 | verify(process.env.OTC_ESCROW_ADDRESS) 52 | .then(() => process.exit(0)) 53 | .catch((err) => { 54 | console.log(err); 55 | process.exit(1); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /scripts/pcvDepositDeploy.ts: -------------------------------------------------------------------------------- 1 | import hre, { ethers } from 'hardhat'; 2 | import config from './config'; 3 | 4 | const deploy = async () => { 5 | const core = config.CORE; 6 | 7 | const CTOKEN = process.env.CTOKEN; 8 | 9 | if (!CTOKEN) { 10 | throw new Error('CTOKEN environment variable contract address is not set'); 11 | } 12 | 13 | if (!core) { 14 | throw new Error('An environment variable contract address is not set'); 15 | } 16 | 17 | const erc20CompoundPCVDepositFactory = await ethers.getContractFactory('ERC20CompoundPCVDeposit'); 18 | const erc20CompoundPCVDeposit = await erc20CompoundPCVDepositFactory.deploy(core, CTOKEN); 19 | await erc20CompoundPCVDeposit.deployed(); 20 | 21 | console.log('ERC20CompoundPCVDeposit deployed to: ', erc20CompoundPCVDeposit.address); 22 | 23 | await hre.run('verify:verify', { 24 | address: erc20CompoundPCVDeposit.address, 25 | constructorArguments: [core, CTOKEN] 26 | }); 27 | 28 | return; 29 | }; 30 | 31 | deploy() 32 | .then(() => process.exit(0)) 33 | .catch((err) => { 34 | console.log(err); 35 | process.exit(1); 36 | }); 37 | -------------------------------------------------------------------------------- /scripts/simulation/constructProposalCalldata.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import { Interface } from '@ethersproject/abi'; 3 | import { utils } from 'ethers'; 4 | import { getAllContractAddresses, getAllContracts } from '@scripts/utils/loadContracts'; 5 | import { ProposalDescription, ExtendedAlphaProposal } from '@custom-types/types'; 6 | import constructProposal from './constructProposal'; 7 | 8 | /** 9 | * Take in a hardhat proposal object and output the proposal calldatas 10 | * See `proposals/utils/getProposalCalldata.js` on how to construct the proposal calldata 11 | */ 12 | export async function constructProposalCalldata(proposalName: string): Promise { 13 | const proposalInfo = (await import(`@proposals/${proposalName}`)).default as ProposalDescription; 14 | 15 | const contracts = await getAllContracts(); 16 | const contractAddresses = await getAllContractAddresses(); 17 | const proposal = (await constructProposal(proposalInfo, contracts, contractAddresses)) as ExtendedAlphaProposal; 18 | 19 | return getTimelockCalldata(proposal, proposalInfo); 20 | } 21 | 22 | function getTimelockCalldata(proposal: ExtendedAlphaProposal, proposalInfo: ProposalDescription): string { 23 | const proposeFuncFrag = new Interface([ 24 | 'function scheduleBatch(address[] calldata targets,uint256[] calldata values,bytes[] calldata data,bytes32 predecessor,bytes32 salt,uint256 delay) public', 25 | 'function executeBatch(address[] calldata targets,uint256[] calldata values,bytes[] calldata data,bytes32 predecessor,bytes32 salt) public' 26 | ]); 27 | 28 | const combinedCalldatas = []; 29 | for (let i = 0; i < proposal.targets.length; i++) { 30 | const sighash = utils.id(proposal.signatures[i]).slice(0, 10); 31 | combinedCalldatas.push(`${sighash}${proposal.calldatas[i].slice(2)}`); 32 | } 33 | 34 | const abiEncodedString = ethers.utils.defaultAbiCoder.encode(['string'], [proposalInfo.commands[0].description]); 35 | const salt = ethers.utils.keccak256(abiEncodedString); 36 | console.log(`salt: ${salt}`); 37 | 38 | const predecessor = ethers.constants.HashZero; 39 | 40 | const calldata = proposeFuncFrag.encodeFunctionData('scheduleBatch', [ 41 | proposal.targets, 42 | proposal.values, 43 | combinedCalldatas, 44 | predecessor, 45 | salt, 46 | 86400 47 | ]); 48 | 49 | const executeCalldata = proposeFuncFrag.encodeFunctionData('executeBatch', [ 50 | proposal.targets, 51 | proposal.values, 52 | combinedCalldatas, 53 | predecessor, 54 | salt 55 | ]); 56 | 57 | return `Calldata: ${calldata}\nExecute Calldata: ${executeCalldata}`; 58 | } 59 | -------------------------------------------------------------------------------- /scripts/simulation/getProposalCalldata.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from 'dotenv'; 2 | import { constructProposalCalldata } from './constructProposalCalldata'; 3 | 4 | dotenv.config(); 5 | 6 | /** 7 | * Take in a hardhat proposal object and output the proposal calldatas 8 | * See `proposals/utils/getProposalCalldata.js` on how to construct the proposal calldata 9 | */ 10 | async function getProposalCalldata() { 11 | const isArbitrumVip = process.env.ENABLE_ARBITRUM_FORKING; 12 | const proposalName = process.env.DEPLOY_FILE + (isArbitrumVip ? '_arbitrum' : ''); 13 | 14 | if (!proposalName) { 15 | throw new Error('DEPLOY_FILE env variable not set'); 16 | } 17 | 18 | console.log(await constructProposalCalldata(proposalName)); 19 | } 20 | 21 | getProposalCalldata() 22 | .then(() => process.exit(0)) 23 | .catch((err) => { 24 | console.log(err); 25 | process.exit(1); 26 | }); 27 | -------------------------------------------------------------------------------- /scripts/utils/loadContracts.ts: -------------------------------------------------------------------------------- 1 | import mainnetAddresses from '@protocol/mainnetAddresses'; 2 | import { ethers } from 'hardhat'; 3 | import { MainnetContracts, NamedAddresses } from '@custom-types/types'; 4 | 5 | interface MainnetContractJSONEntry { 6 | artifactName: string; 7 | address: string; 8 | } 9 | 10 | interface MainnetContractsJSON { 11 | [key: string]: MainnetContractJSONEntry; 12 | } 13 | 14 | export async function getAllContracts(): Promise { 15 | const addresses = mainnetAddresses as MainnetContractsJSON; 16 | const contractsAsArrayEntries = await Promise.all( 17 | Object.entries(addresses) 18 | .filter((entry) => entry[1].artifactName != 'unknown') 19 | .map(async (entry) => { 20 | return [entry[0], await ethers.getContractAt(entry[1].artifactName, entry[1].address)]; 21 | }) 22 | ); 23 | 24 | return Object.fromEntries(contractsAsArrayEntries) as MainnetContracts; 25 | } 26 | 27 | export function getAllContractAddresses(): NamedAddresses { 28 | const contracts: NamedAddresses = {}; 29 | const addresses = mainnetAddresses as MainnetContractsJSON; 30 | 31 | for (const mainnetAddressEntryName in addresses) { 32 | const mainnetAddressEntry = addresses[mainnetAddressEntryName]; 33 | contracts[mainnetAddressEntryName] = mainnetAddressEntry.address; 34 | } 35 | 36 | return contracts; 37 | } 38 | -------------------------------------------------------------------------------- /slither.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude_informational": false, 3 | "exclude_low": false, 4 | "exclude_medium": false, 5 | "exclude_high": false, 6 | "json": "", 7 | "disable_color": false, 8 | "filter_paths": "(mock/|test/|openzeppelin/)", 9 | "legacy_ast": false 10 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "allowJs": true, 5 | "checkJs": true, 6 | "noImplicitAny": false, 7 | "noImplicitThis": false, 8 | "target": "es2018", 9 | "module": "commonjs", 10 | "esModuleInterop": true, 11 | "outDir": "dist", 12 | "resolveJsonModule": true, 13 | "baseUrl": ".", 14 | "paths": { 15 | "@contracts/*": ["contracts/*"], 16 | "@custom-types/*": ["types/*"], // @types is reserved 17 | "@test/*": ["test/*"], 18 | "@proposals/*" : ["proposals/*"], 19 | "@protocol/*" : ["protocol-configuration/*"], 20 | "@scripts/*" : ["scripts/*"], 21 | }, 22 | }, 23 | "include": [ 24 | "test/*", "test/**/*", 25 | "./types/contracts/*", "./types/contracts/**/*", 26 | "./scripts", "proposals/**/*", 27 | "protocol-configuration/*", "protocol-configuration/**/*", 28 | "scripts/*", "scripts/**/*" 29 | ], 30 | "files": ["./hardhat.config.ts"] 31 | } 32 | --------------------------------------------------------------------------------