├── .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 |
--------------------------------------------------------------------------------