-
Notifications
You must be signed in to change notification settings - Fork 95
FeiTimedMinter + PCVEquityMinter #152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Joeysantoro
merged 64 commits into
feat/v2-RatelimitedMinter
from
feat/v2-TribeRewardsController
Sep 11, 2021
Merged
Changes from all commits
Commits
Show all changes
64 commits
Select commit
Hold shift + click to select a range
e3cdc84
Add first draft of Collateralization Oracle
eswak f057f91
Update CollateralizationOracle.sol to implement Joey's interface
eswak fe9131f
Add CollateralizationOracle mock contracts & unit tests
eswak 91a2c84
Update CR Oracle events
eswak 61b4efc
CR Oracle : spell the words x2y -> xToY
eswak f8c4eba
CR Oracle : isTokenInPcv uint8 -> bool
eswak 3023db5
CR Oracle : break on !found & pop() on find
eswak 69260fe
CR Oracle : break on !found & pop() on find
eswak 590a830
CR Oracle : small gas optimization on multiplication
eswak b261efe
CR Oracle : update interfaces & less reverts
eswak 2ddc62b
CR Oracle : add swapDeposit(old, new) function
eswak 92b32fe
No hardcoded oracle price of 0 for FEI
eswak 2c5dd58
Revert isOvercollateralized() if reading is invalid
eswak 925e94e
Remove .only in ERC20Dripper
eswak 9339dc6
Add mapping of excludedDeposits set by guardian
eswak 031a983
Remove pcvDeposits array
eswak a30c1b9
Use EnumerableSet & add getters
eswak 02b7700
Use signed int256 for protocol equity
eswak 2f5dfef
Name return variables in ICollateralizationOracle::pcvStats()
eswak 0a65c4e
CR oracle : remove unused code
eswak f99e3f9
Update IPCVDepositV2 balanceAndFei() -> resistantBalanceAndFei()
eswak 2a5fe44
CR Oracle : simplify code of swapDeposit (call remove & add)
eswak 982a70a
Add check in setOracle for non-zero token & oracle
eswak 09574a5
CR Oracle : fetch oracle price outside of deposit loop
eswak bf29d9e
Remove check on similar token in swapDeposit
eswak ff99edb
Merge pull request #146 from eswak/feature-collateralization-oracle
eswak 9b0a15c
temp
610da9b
update logic and add getters
b59dbca
rename to FEI timed minter and split
8ec5bb0
FeiTimedMinter test
01034fa
Add first draft of Collateralization Oracle
eswak ef896e0
Update CollateralizationOracle.sol to implement Joey's interface
eswak 5e143bb
Add CollateralizationOracle mock contracts & unit tests
eswak c1cd53e
Update CR Oracle events
eswak b377631
CR Oracle : spell the words x2y -> xToY
eswak a745558
CR Oracle : isTokenInPcv uint8 -> bool
eswak 3d7a212
CR Oracle : break on !found & pop() on find
eswak aebc226
CR Oracle : break on !found & pop() on find
eswak 3e41f4d
CR Oracle : small gas optimization on multiplication
eswak 6f7eb14
CR Oracle : update interfaces & less reverts
eswak 2f7eadc
CR Oracle : add swapDeposit(old, new) function
eswak 19be12b
No hardcoded oracle price of 0 for FEI
eswak 904ba47
Revert isOvercollateralized() if reading is invalid
eswak 13dcae7
Remove .only in ERC20Dripper
eswak a05a359
Add mapping of excludedDeposits set by guardian
eswak b580f75
Remove pcvDeposits array
eswak fbb5714
Use EnumerableSet & add getters
eswak d7f3453
Use signed int256 for protocol equity
eswak 19027a1
Name return variables in ICollateralizationOracle::pcvStats()
eswak cd1a4de
CR oracle : remove unused code
eswak b518e36
Update IPCVDepositV2 balanceAndFei() -> resistantBalanceAndFei()
eswak b026acc
CR Oracle : simplify code of swapDeposit (call remove & add)
eswak 320b9a0
Add check in setOracle for non-zero token & oracle
eswak 323a5db
CR Oracle : fetch oracle price outside of deposit loop
eswak 71ed330
Remove check on similar token in swapDeposit
eswak 1e60cfa
safecast PCVEquityMinter
0f34b33
remove 0 check on initialMintAmount
988855e
PCV Equity Minter tests
57a450c
lint
6916744
add rate limited minter to pcv equity minter
dca6263
Merge branch 'feat/v2' into feat/v2-TribeRewardsController
d2f4278
fix test
9cbaead
add a comment
922eed2
Merge branch 'feat/v2-RatelimitedMinter' into feat/v2-TribeRewardsCon…
Joeysantoro File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||
| pragma solidity ^0.8.4; | ||
|
|
||
| import "./MockOracle.sol"; | ||
| import "./MockCoreRef.sol"; | ||
|
|
||
| contract MockOracleCoreRef is MockOracle, MockCoreRef { | ||
| constructor(address core, uint256 usdPerEth) MockCoreRef(core) MockOracle(usdPerEth) {} | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||
| pragma solidity ^0.8.4; | ||
|
|
||
| import "../refs/CoreRef.sol"; | ||
| import "../pcv/IPCVDepositV2.sol"; | ||
|
|
||
| contract MockPCVDepositV2 is IPCVDepositV2, CoreRef { | ||
|
|
||
| address public override balanceReportedIn; | ||
|
|
||
| uint256 private resistantBalance; | ||
| uint256 private resistantProtocolOwnedFei; | ||
|
|
||
| constructor( | ||
| address _core, | ||
| address _token, | ||
| uint256 _resistantBalance, | ||
| uint256 _resistantProtocolOwnedFei | ||
| ) CoreRef(_core) { | ||
| balanceReportedIn = _token; | ||
| resistantBalance = _resistantBalance; | ||
| resistantProtocolOwnedFei = _resistantProtocolOwnedFei; | ||
| } | ||
|
|
||
| function set(uint256 _resistantBalance, uint256 _resistantProtocolOwnedFei) public { | ||
| resistantBalance = _resistantBalance; | ||
| resistantProtocolOwnedFei = _resistantProtocolOwnedFei; | ||
| } | ||
|
|
||
| // gets the resistant token balance and protocol owned fei of this deposit | ||
| function resistantBalanceAndFei() external view override returns (uint256, uint256) { | ||
| return (resistantBalance, resistantProtocolOwnedFei); | ||
| } | ||
|
|
||
| // IPCVDeposit V1 | ||
| function deposit() external override {} | ||
| function withdraw(address to, uint256 amount) external override {} | ||
| function withdrawERC20(address token, address to, uint256 amount) external override {} | ||
| function withdrawETH(address payable to, uint256 amount) external override {} | ||
| function balance() external override view returns (uint256) { | ||
| return resistantBalance; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,289 @@ | ||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||
| pragma solidity ^0.8.4; | ||
|
|
||
| import "./IOracle.sol"; | ||
| import "./ICollateralizationOracle.sol"; | ||
| import "../refs/CoreRef.sol"; | ||
| import "../pcv/IPCVDepositV2.sol"; | ||
| import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; | ||
|
|
||
| interface IPausable { | ||
| function paused() external view returns (bool); | ||
| } | ||
|
|
||
| /// @title Fei Protocol's Collateralization Oracle | ||
| /// @author eswak | ||
| /// @notice Reads a list of PCVDeposit that report their amount of collateral | ||
| /// and the amount of protocol-owned FEI they manage, to deduce the | ||
| /// protocol-wide collateralization ratio. | ||
| contract CollateralizationOracle is ICollateralizationOracle, CoreRef { | ||
| using Decimal for Decimal.D256; | ||
| using EnumerableSet for EnumerableSet.AddressSet; | ||
|
|
||
| // ----------- Events ----------- | ||
|
|
||
| event DepositAdd(address from, address indexed deposit, address indexed token); | ||
| event DepositRemove(address from, address indexed deposit); | ||
| event OracleUpdate(address from, address indexed token, address indexed oldOracle, address indexed newOracle); | ||
|
|
||
| // ----------- Properties ----------- | ||
|
|
||
| /// @notice List of PCVDeposits to exclude from calculations | ||
| mapping(address => bool) public excludedDeposits; | ||
|
|
||
| /// @notice Map of oracles to use to get USD values of assets held in | ||
| /// PCV deposits. This map is used to get the oracle address from | ||
| /// and ERC20 address. | ||
| mapping(address => address) public tokenToOracle; | ||
| /// @notice Map from token address to an array of deposit addresses. It is | ||
| // used to iterate on all deposits while making oracle requests | ||
| // only once. | ||
| mapping(address => EnumerableSet.AddressSet) private tokenToDeposits; | ||
| /// @notice Map from deposit address to token address. It is used like an | ||
| /// indexed version of the pcvDeposits array, to check existence | ||
| /// of a PCVdeposit in the current config. | ||
| mapping(address => address) public depositToToken; | ||
| /// @notice Array of all tokens held in the PCV. Used for iteration on tokens | ||
| /// and oracles. | ||
| EnumerableSet.AddressSet private tokensInPcv; | ||
|
|
||
| // ----------- Constructor ----------- | ||
|
|
||
| /// @notice CollateralizationOracle constructor | ||
| /// @param _core Fei Core for reference | ||
| constructor( | ||
| address _core | ||
| ) CoreRef(_core) {} | ||
|
|
||
| // ----------- Convenience getters ----------- | ||
|
|
||
| /// @notice returns true if a token is held in the pcv | ||
| function isTokenInPcv(address token) external view returns(bool) { | ||
| return tokensInPcv.contains(token); | ||
| } | ||
|
|
||
| /// @notice returns an array of the addresses of tokens held in the pcv. | ||
| function getTokensInPcv() external view returns(address[] memory) { | ||
| uint256 _length = tokensInPcv.length(); | ||
| address[] memory tokens = new address[](_length); | ||
| for (uint256 i = 0; i < _length; i++) { | ||
| tokens[i] = tokensInPcv.at(i); | ||
| } | ||
| return tokens; | ||
| } | ||
|
|
||
| /// @notice returns token at index i of the array of PCV tokens | ||
| function getTokenInPcv(uint256 i) external view returns(address) { | ||
| return tokensInPcv.at(i); | ||
| } | ||
|
|
||
| /// @notice returns an array of the deposits holding a given token. | ||
| function getDepositsForToken(address _token) external view returns(address[] memory) { | ||
| uint256 _length = tokenToDeposits[_token].length(); | ||
| address[] memory deposits = new address[](_length); | ||
| for (uint256 i = 0; i < _length; i++) { | ||
| deposits[i] = tokenToDeposits[_token].at(i); | ||
| } | ||
| return deposits; | ||
| } | ||
|
|
||
| /// @notice returns the address of deposit at index i of token _token | ||
| function getDepositForToken(address token, uint256 i) external view returns(address) { | ||
| return tokenToDeposits[token].at(i); | ||
| } | ||
|
|
||
| // ----------- State-changing methods ----------- | ||
|
|
||
| /// @notice Guardians can exclude & re-include some PCVDeposits from the list, | ||
| /// because a faulty deposit or a paused oracle that prevents reading | ||
| /// from certain deposits could be problematic. | ||
| /// @param _deposit : the deposit to exclude or re-enable. | ||
| /// @param _excluded : the new exclusion flag for this deposit. | ||
| function setDepositExclusion(address _deposit, bool _excluded) external onlyGuardianOrGovernor { | ||
| excludedDeposits[_deposit] = _excluded; | ||
| } | ||
|
|
||
| /// @notice Add a PCVDeposit to the list of deposits inspected by the | ||
| /// collateralization ratio oracle. | ||
| /// note : this function reverts if the deposit is already in the list. | ||
| /// note : this function reverts if the deposit's token has no oracle. | ||
| /// @param _deposit : the PCVDeposit to add to the list. | ||
| function addDeposit(address _deposit) public onlyGovernor { | ||
| // if the PCVDeposit is already listed, revert. | ||
| require(depositToToken[_deposit] == address(0), "CollateralizationOracle: deposit duplicate"); | ||
|
|
||
| // get the token in which the deposit reports its token | ||
| address _token = IPCVDepositV2(_deposit).balanceReportedIn(); | ||
|
|
||
| // revert if there is no oracle of this deposit's token | ||
| require(tokenToOracle[_token] != address(0), "CollateralizationOracle: no oracle"); | ||
|
|
||
| // update maps & arrays for faster access | ||
| depositToToken[_deposit] = _token; | ||
| tokenToDeposits[_token].add(_deposit); | ||
| tokensInPcv.add(_token); | ||
|
|
||
| // emit event | ||
| emit DepositAdd(msg.sender, _deposit, _token); | ||
| } | ||
|
|
||
| /// @notice Remove a PCVDeposit from the list of deposits inspected by | ||
| /// the collateralization ratio oracle. | ||
| /// note : this function reverts if the input deposit is not found. | ||
| /// @param _deposit : the PCVDeposit address to remove from the list. | ||
| function removeDeposit(address _deposit) public onlyGovernor { | ||
| // get the token in which the deposit reports its token | ||
| address _token = depositToToken[_deposit]; | ||
|
|
||
| // revert if the deposit is not found | ||
| require(_token != address(0), "CollateralizationOracle: deposit not found"); | ||
|
|
||
| // update maps & arrays for faster access | ||
| // deposits array for the deposit's token | ||
| depositToToken[_deposit] = address(0); | ||
| tokenToDeposits[_token].remove(_deposit); | ||
|
|
||
| // if it was the last deposit to have this token, remove this token from | ||
| // the arrays also | ||
| if (tokenToDeposits[_token].length() == 0) { | ||
| tokensInPcv.remove(_token); | ||
| } | ||
|
|
||
| // emit event | ||
| emit DepositRemove(msg.sender, _deposit); | ||
| } | ||
|
|
||
| /// @notice Swap a PCVDeposit with a new one, for instance when a new version | ||
| /// of a deposit (holding the same token) is deployed. | ||
| /// @param _oldDeposit : the PCVDeposit to remove from the list. | ||
| /// @param _newDeposit : the PCVDeposit to add to the list. | ||
| function swapDeposit(address _oldDeposit, address _newDeposit) external onlyGovernor { | ||
| removeDeposit(_oldDeposit); | ||
| addDeposit(_newDeposit); | ||
| } | ||
|
|
||
| /// @notice Set the price feed oracle (in USD) for a given asset. | ||
| /// @param _token : the asset to add price oracle for | ||
| /// @param _newOracle : price feed oracle for the given asset | ||
| function setOracle(address _token, address _newOracle) external onlyGovernor { | ||
| require(_token != address(0), "CollateralizationOracle: token must be != 0x0"); | ||
| require(_newOracle != address(0), "CollateralizationOracle: oracle must be != 0x0"); | ||
|
|
||
| // add oracle to the map(ERC20Address) => OracleAddress | ||
| address _oldOracle = tokenToOracle[_token]; | ||
| tokenToOracle[_token] = _newOracle; | ||
|
|
||
| // emit event | ||
| emit OracleUpdate(msg.sender, _token, _oldOracle, _newOracle); | ||
| } | ||
|
|
||
| // ----------- IOracle override methods ----------- | ||
| /// @notice update all oracles required for this oracle to work that are not | ||
| /// paused themselves. | ||
| function update() external override whenNotPaused { | ||
| for (uint256 i = 0; i < tokensInPcv.length(); i++) { | ||
| address _oracle = tokenToOracle[tokensInPcv.at(i)]; | ||
| if (!IPausable(_oracle).paused()) { | ||
| IOracle(_oracle).update(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // @notice returns true if any of the oracles required for this oracle to | ||
| // work are outdated. | ||
| function isOutdated() external override view returns (bool) { | ||
| bool _outdated = false; | ||
| for (uint256 i = 0; i < tokensInPcv.length() && !_outdated; i++) { | ||
| address _oracle = tokenToOracle[tokensInPcv.at(i)]; | ||
| if (!IPausable(_oracle).paused()) { | ||
| _outdated = _outdated || IOracle(_oracle).isOutdated(); | ||
| } | ||
| } | ||
| return _outdated; | ||
| } | ||
|
|
||
| /// @notice Get the current collateralization ratio of the protocol. | ||
| /// @return collateralRatio the current collateral ratio of the protocol. | ||
| /// @return validityStatus the current oracle validity status (false if any | ||
| /// of the oracles for tokens held in the PCV are invalid, or if | ||
| /// this contract is paused). | ||
| function read() public override view returns (Decimal.D256 memory collateralRatio, bool validityStatus) { | ||
| // fetch PCV stats | ||
| ( | ||
| uint256 _protocolControlledValue, | ||
| uint256 _userCirculatingFei, | ||
| , // we don't need protocol equity | ||
| bool _valid | ||
| ) = pcvStats(); | ||
|
|
||
| // The protocol collateralization ratio is defined as the total USD | ||
| // value of assets held in the PCV, minus the circulating FEI. | ||
| collateralRatio = Decimal.ratio(_protocolControlledValue, _userCirculatingFei); | ||
| validityStatus = _valid; | ||
| } | ||
|
|
||
| // ----------- ICollateralizationOracle override methods ----------- | ||
|
|
||
| /// @notice returns the Protocol-Controlled Value, User-circulating FEI, and | ||
| /// Protocol Equity. | ||
| /// @return protocolControlledValue : the total USD value of all assets held | ||
| /// by the protocol. | ||
| /// @return userCirculatingFei : the number of FEI not owned by the protocol. | ||
| /// @return protocolEquity : the difference between PCV and user circulating FEI. | ||
| /// If there are more circulating FEI than $ in the PCV, equity is 0. | ||
| /// @return validityStatus : the current oracle validity status (false if any | ||
| /// of the oracles for tokens held in the PCV are invalid, or if | ||
| /// this contract is paused). | ||
| function pcvStats() public override view returns ( | ||
| uint256 protocolControlledValue, | ||
| uint256 userCirculatingFei, | ||
| int256 protocolEquity, | ||
| bool validityStatus | ||
| ) { | ||
| uint256 _protocolControlledFei = 0; | ||
| validityStatus = !paused(); | ||
|
|
||
| // For each token... | ||
| for (uint256 i = 0; i < tokensInPcv.length(); i++) { | ||
| address _token = tokensInPcv.at(i); | ||
| uint256 _totalTokenBalance = 0; | ||
|
|
||
| // For each deposit... | ||
| for (uint256 j = 0; j < tokenToDeposits[_token].length(); j++) { | ||
| address _deposit = tokenToDeposits[_token].at(j); | ||
|
|
||
| // ignore deposits that are excluded by the Guardian | ||
| if (!excludedDeposits[_deposit]) { | ||
| // read the deposit, and increment token balance/protocol fei | ||
| (uint256 _depositBalance, uint256 _depositFei) = IPCVDepositV2(_deposit).resistantBalanceAndFei(); | ||
| _totalTokenBalance += _depositBalance; | ||
| _protocolControlledFei += _depositFei; | ||
| } | ||
| } | ||
|
|
||
| // If the protocol holds non-zero balance of tokens, fetch the oracle price to | ||
| // increment PCV by _totalTokenBalance * oracle price USD. | ||
| if (_totalTokenBalance != 0) { | ||
| (Decimal.D256 memory _oraclePrice, bool _oracleValid) = IOracle(tokenToOracle[_token]).read(); | ||
| if (!_oracleValid) { | ||
| validityStatus = false; | ||
| } | ||
| protocolControlledValue += _oraclePrice.mul(_totalTokenBalance).asUint256(); | ||
| } | ||
| } | ||
|
|
||
| userCirculatingFei = fei().totalSupply() - _protocolControlledFei; | ||
| protocolEquity = int256(protocolControlledValue) - int256(userCirculatingFei); | ||
| } | ||
|
|
||
| /// @notice returns true if the protocol is overcollateralized. Overcollateralization | ||
| /// is defined as the protocol having more assets in its PCV (Protocol | ||
| /// Controlled Value) than the circulating (user-owned) FEI, i.e. | ||
| /// a positive Protocol Equity. | ||
| /// Note: the validity status is ignored in this function. | ||
Joeysantoro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| function isOvercollateralized() external override view whenNotPaused returns (bool) { | ||
| (,, int256 _protocolEquity, bool _valid) = pcvStats(); | ||
| require(_valid, "CollateralizationOracle: reading is invalid"); | ||
| return _protocolEquity > 0; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.