From 5ba0def826489be0011a9caea4a3a0305abc3193 Mon Sep 17 00:00:00 2001 From: Elliot Date: Sun, 2 Oct 2022 14:58:28 -0700 Subject: [PATCH 01/84] add mock maple contract --- contracts/mock/MockMaplePool.sol | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 contracts/mock/MockMaplePool.sol diff --git a/contracts/mock/MockMaplePool.sol b/contracts/mock/MockMaplePool.sol new file mode 100644 index 000000000..163be2d6b --- /dev/null +++ b/contracts/mock/MockMaplePool.sol @@ -0,0 +1,9 @@ +pragma solidity =0.8.13; + +contract MockMaplePool { + address public liquidityAsset; + + constructor(address _liquidityAsset) { + liquidityAsset = _liquidityAsset; + } +} From 344cbc09c855998fa48c5d87073c30bf4f2728cd Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 3 Oct 2022 12:30:10 -0700 Subject: [PATCH 02/84] Add harvest event to the PCV Deposit Interface --- contracts/pcv/IPCVDeposit.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/pcv/IPCVDeposit.sol b/contracts/pcv/IPCVDeposit.sol index 6b0fe065c..30d208577 100644 --- a/contracts/pcv/IPCVDeposit.sol +++ b/contracts/pcv/IPCVDeposit.sol @@ -7,6 +7,7 @@ import "./IPCVDepositBalances.sol"; /// @author Fei Protocol interface IPCVDeposit is IPCVDepositBalances { // ----------- Events ----------- + event Deposit(address indexed _from, uint256 _amount); event Withdrawal( @@ -28,6 +29,8 @@ interface IPCVDeposit is IPCVDepositBalances { uint256 _amount ); + event Harvest(); + // ----------- State changing api ----------- function deposit() external; From 95368fedd7b0f41cfd79f40e4e31b5d7c3ec04df Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 3 Oct 2022 12:31:54 -0700 Subject: [PATCH 03/84] Morpho PCV Deposit and integration tests --- contracts/pcv/morpho/ICompound.sol | 398 ++++++++++++++++++ contracts/pcv/morpho/ILens.sol | 290 +++++++++++++ contracts/pcv/morpho/IMorpho.sol | 23 + .../pcv/morpho/MorphoCompoundPCVDeposit.sol | 102 +++++ ...egrationTestMorphoCompoundPCVDeposit.t.sol | 232 ++++++++++ 5 files changed, 1045 insertions(+) create mode 100644 contracts/pcv/morpho/ICompound.sol create mode 100644 contracts/pcv/morpho/ILens.sol create mode 100644 contracts/pcv/morpho/IMorpho.sol create mode 100644 contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol create mode 100644 contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol diff --git a/contracts/pcv/morpho/ICompound.sol b/contracts/pcv/morpho/ICompound.sol new file mode 100644 index 000000000..d1e971b29 --- /dev/null +++ b/contracts/pcv/morpho/ICompound.sol @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: GNU AGPLv3 +pragma solidity ^0.8.0; + +interface ICEth { + function accrueInterest() external returns (uint256); + + function borrowRate() external returns (uint256); + + function borrowIndex() external returns (uint256); + + function borrowBalanceStored(address) external returns (uint256); + + function mint() external payable; + + function exchangeRateCurrent() external returns (uint256); + + function exchangeRateStored() external view returns (uint256); + + function supplyRatePerBlock() external returns (uint256); + + function redeem(uint256) external returns (uint256); + + function redeemUnderlying(uint256) external returns (uint256); + + function approve(address spender, uint256 amount) external returns (bool); + + function transferFrom( + address, + address, + uint256 + ) external returns (bool); + + function transfer(address dst, uint256 amount) external returns (bool); + + function balanceOf(address) external returns (uint256); + + function balanceOfUnderlying(address account) external returns (uint256); + + function borrow(uint256) external returns (uint256); + + function repayBorrow() external payable; + + function borrowBalanceCurrent(address) external returns (uint256); + + function borrowRatePerBlock() external view returns (uint256); +} + +interface IComptroller { + struct CompMarketState { + /// @notice The market's last updated compBorrowIndex or compSupplyIndex + uint224 index; + /// @notice The block number the index was last updated at + uint32 block; + } + + function liquidationIncentiveMantissa() external view returns (uint256); + + function closeFactorMantissa() external view returns (uint256); + + function admin() external view returns (address); + + function oracle() external view returns (address); + + function borrowCaps(address) external view returns (uint256); + + function markets(address) + external + view + returns ( + bool isListed, + uint256 collateralFactorMantissa, + bool isComped + ); + + function enterMarkets(address[] calldata cTokens) + external + returns (uint256[] memory); + + function exitMarket(address cToken) external returns (uint256); + + function mintAllowed( + address cToken, + address minter, + uint256 mintAmount + ) external returns (uint256); + + function mintVerify( + address cToken, + address minter, + uint256 mintAmount, + uint256 mintTokens + ) external; + + function redeemAllowed( + address cToken, + address redeemer, + uint256 redeemTokens + ) external returns (uint256); + + function redeemVerify( + address cToken, + address redeemer, + uint256 redeemAmount, + uint256 redeemTokens + ) external; + + function borrowAllowed( + address cToken, + address borrower, + uint256 borrowAmount + ) external returns (uint256); + + function borrowVerify( + address cToken, + address borrower, + uint256 borrowAmount + ) external; + + function repayBorrowAllowed( + address cToken, + address payer, + address borrower, + uint256 repayAmount + ) external returns (uint256); + + function repayBorrowVerify( + address cToken, + address payer, + address borrower, + uint256 repayAmount, + uint256 borrowerIndex + ) external; + + function liquidateBorrowAllowed( + address cTokenBorrowed, + address cTokenCollateral, + address liquidator, + address borrower, + uint256 repayAmount + ) external returns (uint256); + + function liquidateBorrowVerify( + address cTokenBorrowed, + address cTokenCollateral, + address liquidator, + address borrower, + uint256 repayAmount, + uint256 seizeTokens + ) external; + + function seizeAllowed( + address cTokenCollateral, + address cTokenBorrowed, + address liquidator, + address borrower, + uint256 seizeTokens + ) external returns (uint256); + + function seizeVerify( + address cTokenCollateral, + address cTokenBorrowed, + address liquidator, + address borrower, + uint256 seizeTokens + ) external; + + function transferAllowed( + address cToken, + address src, + address dst, + uint256 transferTokens + ) external returns (uint256); + + function transferVerify( + address cToken, + address src, + address dst, + uint256 transferTokens + ) external; + + /*** Liquidity/Liquidation Calculations ***/ + + function liquidateCalculateSeizeTokens( + address cTokenBorrowed, + address cTokenCollateral, + uint256 repayAmount + ) external view returns (uint256, uint256); + + function getAccountLiquidity(address) + external + view + returns ( + uint256, + uint256, + uint256 + ); + + function getHypotheticalAccountLiquidity( + address, + address, + uint256, + uint256 + ) + external + returns ( + uint256, + uint256, + uint256 + ); + + function checkMembership(address, address) external view returns (bool); + + function claimComp(address holder) external; + + function claimComp(address holder, address[] memory cTokens) external; + + function compSpeeds(address) external view returns (uint256); + + function compSupplySpeeds(address) external view returns (uint256); + + function compBorrowSpeeds(address) external view returns (uint256); + + function compSupplyState(address) + external + view + returns (CompMarketState memory); + + function compBorrowState(address) + external + view + returns (CompMarketState memory); + + function getCompAddress() external view returns (address); + + function _setPriceOracle(address newOracle) external returns (uint256); + + function _setMintPaused(ICToken cToken, bool state) external returns (bool); + + function _setBorrowPaused(ICToken cToken, bool state) + external + returns (bool); + + function _setCollateralFactor( + ICToken cToken, + uint256 newCollateralFactorMantissa + ) external returns (uint256); + + function _setCompSpeeds( + ICToken[] memory cTokens, + uint256[] memory supplySpeeds, + uint256[] memory borrowSpeeds + ) external; +} + +interface IInterestRateModel { + function getBorrowRate( + uint256 cash, + uint256 borrows, + uint256 reserves + ) external view returns (uint256); + + function getSupplyRate( + uint256 cash, + uint256 borrows, + uint256 reserves, + uint256 reserveFactorMantissa + ) external view returns (uint256); +} + +interface ICToken { + function isCToken() external returns (bool); + + function transfer(address dst, uint256 amount) external returns (bool); + + function transferFrom( + address src, + address dst, + uint256 amount + ) external returns (bool); + + function approve(address spender, uint256 amount) external returns (bool); + + function allowance(address owner, address spender) + external + view + returns (uint256); + + function balanceOf(address owner) external view returns (uint256); + + function balanceOfUnderlying(address owner) external returns (uint256); + + function getAccountSnapshot(address account) + external + view + returns ( + uint256, + uint256, + uint256, + uint256 + ); + + function borrowRatePerBlock() external view returns (uint256); + + function supplyRatePerBlock() external view returns (uint256); + + function totalBorrowsCurrent() external returns (uint256); + + function borrowBalanceCurrent(address account) external returns (uint256); + + function borrowBalanceStored(address account) + external + view + returns (uint256); + + function exchangeRateCurrent() external returns (uint256); + + function exchangeRateStored() external view returns (uint256); + + function getCash() external view returns (uint256); + + function seize( + address liquidator, + address borrower, + uint256 seizeTokens + ) external returns (uint256); + + function borrowRate() external returns (uint256); + + function borrowIndex() external view returns (uint256); + + function borrow(uint256) external returns (uint256); + + function repayBorrow(uint256) external returns (uint256); + + function repayBorrowBehalf(address borrower, uint256 repayAmount) + external + returns (uint256); + + function liquidateBorrow( + address borrower, + uint256 repayAmount, + address cTokenCollateral + ) external returns (uint256); + + function underlying() external view returns (address); + + function mint(uint256) external returns (uint256); + + function redeemUnderlying(uint256) external returns (uint256); + + function accrueInterest() external returns (uint256); + + function totalSupply() external view returns (uint256); + + function totalBorrows() external view returns (uint256); + + function accrualBlockNumber() external view returns (uint256); + + function totalReserves() external view returns (uint256); + + function interestRateModel() external view returns (IInterestRateModel); + + function reserveFactorMantissa() external view returns (uint256); + + function initialExchangeRateMantissa() external view returns (uint256); + + /*** Admin Functions ***/ + + function _setPendingAdmin(address payable newPendingAdmin) + external + returns (uint256); + + function _acceptAdmin() external returns (uint256); + + function _setComptroller(IComptroller newComptroller) + external + returns (uint256); + + function _setReserveFactor(uint256 newReserveFactorMantissa) + external + returns (uint256); + + function _reduceReserves(uint256 reduceAmount) external returns (uint256); + + function _setInterestRateModel(IInterestRateModel newInterestRateModel) + external + returns (uint256); +} + +interface ICEther is ICToken { + function mint() external payable; + + function repayBorrow() external payable; +} + +interface ICompoundOracle { + function getUnderlyingPrice(address) external view returns (uint256); +} diff --git a/contracts/pcv/morpho/ILens.sol b/contracts/pcv/morpho/ILens.sol new file mode 100644 index 000000000..fb7720c35 --- /dev/null +++ b/contracts/pcv/morpho/ILens.sol @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GNU AGPLv3 +pragma solidity ^0.8.0; + +import "./ICompound.sol"; +import "./IMorpho.sol"; + +interface ILens { + /// STORAGE /// + + function MAX_BASIS_POINTS() external view returns (uint256); + + function WAD() external view returns (uint256); + + function morpho() external view returns (IMorpho); + + function comptroller() external view returns (IComptroller); + + /// GENERAL /// + + function getTotalSupply() + external + view + returns ( + uint256 p2pSupplyAmount, + uint256 poolSupplyAmount, + uint256 totalSupplyAmount + ); + + function getTotalBorrow() + external + view + returns ( + uint256 p2pBorrowAmount, + uint256 poolBorrowAmount, + uint256 totalBorrowAmount + ); + + /// MARKETS /// + + function isMarketCreated(address _poolToken) external view returns (bool); + + function isMarketCreatedAndNotPaused(address _poolToken) + external + view + returns (bool); + + function isMarketCreatedAndNotPausedNorPartiallyPaused(address _poolToken) + external + view + returns (bool); + + function getAllMarkets() + external + view + returns (address[] memory marketsCreated_); + + function getMainMarketData(address _poolToken) + external + view + returns ( + uint256 avgSupplyRatePerBlock, + uint256 avgBorrowRatePerBlock, + uint256 p2pSupplyAmount, + uint256 p2pBorrowAmount, + uint256 poolSupplyAmount, + uint256 poolBorrowAmount + ); + + function getAdvancedMarketData(address _poolToken) + external + view + returns ( + uint256 p2pSupplyIndex, + uint256 p2pBorrowIndex, + uint256 poolSupplyIndex, + uint256 poolBorrowIndex, + uint32 lastUpdateBlockNumber, + uint256 p2pSupplyDelta, + uint256 p2pBorrowDelta + ); + + function getMarketConfiguration(address _poolToken) + external + view + returns ( + address underlying, + bool isCreated, + bool p2pDisabled, + bool isPaused, + bool isPartiallyPaused, + uint16 reserveFactor, + uint16 p2pIndexCursor, + uint256 collateralFactor + ); + + function getTotalMarketSupply(address _poolToken) + external + view + returns (uint256 p2pSupplyAmount, uint256 poolSupplyAmount); + + function getTotalMarketBorrow(address _poolToken) + external + view + returns (uint256 p2pBorrowAmount, uint256 poolBorrowAmount); + + /// INDEXES /// + + function getCurrentP2PSupplyIndex(address _poolToken) + external + view + returns (uint256); + + function getCurrentP2PBorrowIndex(address _poolToken) + external + view + returns (uint256); + + function getCurrentPoolIndexes(address _poolToken) + external + view + returns ( + uint256 currentPoolSupplyIndex, + uint256 currentPoolBorrowIndex + ); + + function getIndexes(address _poolToken, bool _computeUpdatedIndexes) + external + view + returns ( + uint256 p2pSupplyIndex, + uint256 p2pBorrowIndex, + uint256 poolSupplyIndex, + uint256 poolBorrowIndex + ); + + /// USERS /// + + function getEnteredMarkets(address _user) + external + view + returns (address[] memory enteredMarkets); + + function getUserHealthFactor( + address _user, + address[] calldata _updatedMarkets + ) external view returns (uint256); + + function getUserBalanceStates( + address _user, + address[] calldata _updatedMarkets + ) + external + view + returns ( + uint256 collateralValue, + uint256 debtValue, + uint256 maxDebtValue + ); + + function getCurrentSupplyBalanceInOf(address _poolToken, address _user) + external + view + returns ( + uint256 balanceOnPool, + uint256 balanceInP2P, + uint256 totalBalance + ); + + function getCurrentBorrowBalanceInOf(address _poolToken, address _user) + external + view + returns ( + uint256 balanceOnPool, + uint256 balanceInP2P, + uint256 totalBalance + ); + + function getUserMaxCapacitiesForAsset(address _user, address _poolToken) + external + view + returns (uint256 withdrawable, uint256 borrowable); + + function getUserHypotheticalBalanceStates( + address _user, + address _poolToken, + uint256 _withdrawnAmount, + uint256 _borrowedAmount + ) external view returns (uint256 debtValue, uint256 maxDebtValue); + + function isLiquidatable(address _user, address[] memory _updatedMarkets) + external + view + returns (bool); + + function computeLiquidationRepayAmount( + address _user, + address _poolTokenBorrowed, + address _poolTokenCollateral, + address[] calldata _updatedMarkets + ) external view returns (uint256 toRepay); + + /// RATES /// + + function getAverageSupplyRatePerBlock(address _poolToken) + external + view + returns (uint256); + + function getAverageBorrowRatePerBlock(address _poolToken) + external + view + returns (uint256); + + function getRatesPerBlock(address _poolToken) + external + view + returns ( + uint256 p2pSupplyRate, + uint256 p2pBorrowRate, + uint256 poolSupplyRate, + uint256 poolBorrowRate + ); + + function getCurrentUserSupplyRatePerBlock(address _poolToken, address _user) + external + view + returns (uint256); + + function getCurrentUserBorrowRatePerBlock(address _poolToken, address _user) + external + view + returns (uint256); + + function getNextUserSupplyRatePerBlock( + address _poolToken, + address _user, + uint256 _amount + ) + external + view + returns ( + uint256 nextSupplyRatePerBlock, + uint256 balanceOnPool, + uint256 balanceInP2P, + uint256 totalBalance + ); + + function getNextUserBorrowRatePerBlock( + address _poolToken, + address _user, + uint256 _amount + ) + external + view + returns ( + uint256 nextBorrowRatePerBlock, + uint256 balanceOnPool, + uint256 balanceInP2P, + uint256 totalBalance + ); + + /// REWARDS /// + + function getUserUnclaimedRewards( + address[] calldata _poolTokens, + address _user + ) external view returns (uint256 unclaimedRewards); + + function getAccruedSupplierComp( + address _supplier, + address _poolToken, + uint256 _balance + ) external view returns (uint256); + + function getAccruedBorrowerComp( + address _borrower, + address _poolToken, + uint256 _balance + ) external view returns (uint256); + + function getCurrentCompSupplyIndex(address _poolToken) + external + view + returns (uint256); + + function getCurrentCompBorrowIndex(address _poolToken) + external + view + returns (uint256); +} diff --git a/contracts/pcv/morpho/IMorpho.sol b/contracts/pcv/morpho/IMorpho.sol new file mode 100644 index 000000000..c5ee57991 --- /dev/null +++ b/contracts/pcv/morpho/IMorpho.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GNU AGPLv3 +pragma solidity ^0.8.0; + +import {IComptroller} from "./ICompound.sol"; + +// prettier-ignore +interface IMorpho { + + /// STORAGE /// + + function comptroller() external view returns (IComptroller); + + /// USERS /// + + function supply(address _poolTokenAddress, address _onBehalf, uint256 _amount) external; + function supply(address _poolTokenAddress, address _onBehalf, uint256 _amount, uint256 _maxGasForMatching) external; + function borrow(address _poolTokenAddress, uint256 _amount) external; + function borrow(address _poolTokenAddress, uint256 _amount, uint256 _maxGasForMatching) external; + function withdraw(address _poolTokenAddress, uint256 _amount) external; + function repay(address _poolTokenAddress, address _onBehalf, uint256 _amount) external; + function liquidate(address _poolTokenBorrowedAddress, address _poolTokenCollateralAddress, address _borrower, uint256 _amount) external; + function claimRewards(address[] calldata _cTokenAddresses, bool _tradeForMorphoToken) external returns (uint256 claimedAmount); +} diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol new file mode 100644 index 000000000..f35c86015 --- /dev/null +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -0,0 +1,102 @@ +pragma solidity =0.8.13; + +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {ILens} from "./ILens.sol"; +import {IMorpho} from "./IMorpho.sol"; +import {ICompoundOracle, ICToken} from "./ICompound.sol"; +import {CoreRef} from "../../refs/CoreRef.sol"; +import {PCVDeposit} from "../PCVDeposit.sol"; + +/// @notice PCV Deposit for Morpho-Compound V2. +/// Implements the PCV Deposit interface to deposit and withdraw funds in Morpho +/// Liquidity profile of Morpho for this deposit is fully liquid for USDC and DAI +/// because the incentivized rates are higher than the P2P rate. +/// Only for depositing USDC and DAI. USDT is not in scope +contract MorphoCompoundPCVDeposit is PCVDeposit { + using SafeERC20 for IERC20; + + /// @notice reference to the lens contract for morpho-compound v2 + address public constant LENS = 0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67; + + /// @notice reference to the morpho-compound v2 market + IMorpho public constant MORPHO = + IMorpho(0x8888882f8f843896699869179fB6E4f7e3B58888); + + /// @notice reference to underlying token + address public immutable token; + + /// @notice cToken in compound this deposit tracks + /// used to inform morpho about the desired market to supply liquidity + address public immutable cToken; + + constructor(address _core, address _cToken) CoreRef(_core) { + cToken = _cToken; + token = ICToken(_cToken).underlying(); + } + + /// @notice Returns the distribution of assets supplied by this contract through Morpho-Compound. + /// @return sum of suppliedP2P and suppliedOnPool for the given CToken + function balance() public view override returns (uint256) { + (, , uint256 totalSupplied) = ILens(LENS).getCurrentSupplyBalanceInOf( + cToken, + address(this) + ); + + return totalSupplied; + } + + /// @notice returns the underlying token of this deposit + function balanceReportedIn() external view returns (address) { + return token; + } + + /// @notice deposit ERC-20 tokens to Morpho-Compound + function deposit() public whenNotPaused { + uint256 amount = IERC20(token).balanceOf(address(this)); + if (amount == 0) { + /// no op to prevent revert on empty deposit + return; + } + + IERC20(token).approve(address(MORPHO), amount); + MORPHO.supply( + cToken, /// cToken to supply liquidity to + address(this), /// the address of the user you want to supply on behalf of + amount + ); + + emit Deposit(msg.sender, amount); + } + + /// @notice withdraw tokens from the PCV allocation + /// @param to the address PCV will be sent to + /// @param amount of tokens withdrawn + function withdraw(address to, uint256 amount) external onlyPCVController { + MORPHO.withdraw(cToken, amount); + IERC20(token).safeTransfer(to, amount); + + emit Withdrawal(msg.sender, to, amount); + } + + /// @notice withdraw all tokens from Morpho + /// @param to the address PCV will be sent to + function withdrawAll(address to) external onlyPCVController { + uint256 amount = balance(); + MORPHO.withdraw(cToken, amount); + IERC20(token).safeTransfer(to, amount); + + emit Withdrawal(msg.sender, to, amount); + } + + /// @notice claim COMP rewards for supplying to Morpho + function harvest() external { + address[] memory cTokens = new address[](1); + cTokens[0] = cToken; + + MORPHO.claimRewards(cTokens, false); + + emit Harvest(); + } +} diff --git a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol new file mode 100644 index 000000000..84c01ee82 --- /dev/null +++ b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol @@ -0,0 +1,232 @@ +//SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity =0.8.13; + +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Vm} from "../unit/utils/Vm.sol"; +import {Core} from "../../core/Core.sol"; +import {IVolt} from "../../volt/IVolt.sol"; +import {DSTest} from "../unit/utils/DSTest.sol"; +import {IDSSPSM} from "../../pcv/maker/IDSSPSM.sol"; +import {Constants} from "../../Constants.sol"; +import {PCVGuardian} from "../../pcv/PCVGuardian.sol"; +import {MainnetAddresses} from "./fixtures/MainnetAddresses.sol"; +import {PegStabilityModule} from "../../peg/PegStabilityModule.sol"; +import {ERC20CompoundPCVDeposit} from "../../pcv/compound/ERC20CompoundPCVDeposit.sol"; +import {MorphoCompoundPCVDeposit} from "../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; + +import "hardhat/console.sol"; + +contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { + using SafeCast for *; + + Vm public constant vm = Vm(HEVM_ADDRESS); + + MorphoCompoundPCVDeposit private daiDeposit; + MorphoCompoundPCVDeposit private usdcDeposit; + + PCVGuardian private immutable pcvGuardian = + PCVGuardian(MainnetAddresses.PCV_GUARDIAN); + + Core private core = Core(MainnetAddresses.CORE); + PegStabilityModule private daiPSM = + PegStabilityModule(MainnetAddresses.VOLT_DAI_PSM); + + IERC20 private dai = IERC20(MainnetAddresses.DAI); + IERC20 private usdc = IERC20(MainnetAddresses.USDC); + IERC20 private comp = IERC20(MainnetAddresses.COMP); + + uint256 public daiBalance; + uint256 public usdcBalance; + + uint256 targetDaiBalance = 100_000e18; + uint256 targetUsdcBalance = 100_000e6; + + uint256 public constant epochLength = 100 days; + + function setUp() public { + daiDeposit = new MorphoCompoundPCVDeposit( + address(core), + MainnetAddresses.CDAI + ); + usdcDeposit = new MorphoCompoundPCVDeposit( + address(core), + MainnetAddresses.CUSDC + ); + + vm.label(address(daiDeposit), "Morpho DAI Compound PCV Deposit"); + vm.label(address(usdcDeposit), "Morpho USDC Compound PCV Deposit"); + vm.label(address(MainnetAddresses.CDAI), "CDAI"); + vm.label(address(MainnetAddresses.CUSDC), "CUSDC"); + vm.label(address(usdc), "USDC"); + vm.label(address(dai), "DAI"); + vm.label(0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67, "Morpho Lens"); + vm.label(0x8888882f8f843896699869179fB6E4f7e3B58888, "Morpho"); + + vm.startPrank(MainnetAddresses.DAI_USDC_USDT_CURVE_POOL); + dai.transfer(address(daiDeposit), targetDaiBalance); + usdc.transfer(address(usdcDeposit), targetUsdcBalance); + vm.stopPrank(); + + usdcDeposit.deposit(); + daiDeposit.deposit(); + } + + function testSetup() public { + assertEq(address(daiDeposit.core()), address(core)); + assertEq(address(usdcDeposit.core()), address(core)); + + assertEq(daiDeposit.balanceReportedIn(), address(dai)); + assertEq(usdcDeposit.balanceReportedIn(), address(usdc)); + + assertEq(address(daiDeposit.cToken()), address(MainnetAddresses.CDAI)); + assertEq( + address(usdcDeposit.cToken()), + address(MainnetAddresses.CUSDC) + ); + + assertEq(address(daiDeposit.token()), address(MainnetAddresses.DAI)); + assertEq(address(usdcDeposit.token()), address(MainnetAddresses.USDC)); + + assertApproxEq( + daiDeposit.balance().toInt256(), + targetDaiBalance.toInt256(), + 0 + ); + assertApproxEq( + usdcDeposit.balance().toInt256(), + targetUsdcBalance.toInt256(), + 0 + ); + } + + function testWithdraw() public { + vm.startPrank(MainnetAddresses.GOVERNOR); + usdcDeposit.withdraw(address(this), usdcDeposit.balance()); + daiDeposit.withdraw(address(this), daiDeposit.balance()); + vm.stopPrank(); + + assertApproxEq( + dai.balanceOf(address(this)).toInt256(), + targetDaiBalance.toInt256(), + 0 + ); + assertApproxEq( + usdc.balanceOf(address(this)).toInt256(), + targetUsdcBalance.toInt256(), + 0 + ); + } + + function testHarvest() public { + /// fast forward timestamps + block number + vm.warp(block.timestamp + epochLength); + vm.roll(block.number + epochLength / 12); + + uint256 startingCompBalance = comp.balanceOf(address(usdcDeposit)) + + comp.balanceOf(address(daiDeposit)); + usdcDeposit.harvest(); + daiDeposit.harvest(); + uint256 endingCompBalance = comp.balanceOf(address(usdcDeposit)) + + comp.balanceOf(address(daiDeposit)); + + uint256 compDelta = endingCompBalance - startingCompBalance; + + assertTrue(compDelta != 0); + } + + /// 2**80 / 1e18 = ~1.2m which is above target dai balance + function testWithdrawDaiFuzz(uint80 amount) public { + /// 1 fails in some underlying contract, and this isn't a scenario we are going to realistically have + /// as 1e9 wei of dai would always cost more in gas than the dai is worth + vm.assume(amount >= 1e9); + vm.assume(amount <= targetDaiBalance); + + vm.prank(MainnetAddresses.GOVERNOR); + daiDeposit.withdraw(address(this), amount); + + assertApproxEq( + dai.balanceOf(address(this)).toInt256(), + amount.toInt256(), + 0 + ); + + assertApproxEq( + daiDeposit.balance().toInt256(), + (targetDaiBalance - amount).toInt256(), + 0 + ); + } + + function testWithdrawUsdcFuzz(uint40 amount) public { + vm.assume(amount != 0); + vm.assume(amount <= targetUsdcBalance); + + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.withdraw(address(this), amount); + + assertApproxEq( + usdc.balanceOf(address(this)).toInt256(), + amount.toInt256(), + 0 + ); + + assertApproxEq( + usdcDeposit.balance().toInt256(), + (targetUsdcBalance - amount).toInt256(), + 0 + ); + } + + function testWithdrawAll() public { + vm.startPrank(MainnetAddresses.GOVERNOR); + usdcDeposit.withdrawAll(address(this)); + daiDeposit.withdrawAll(address(this)); + vm.stopPrank(); + + assertApproxEq( + dai.balanceOf(address(this)).toInt256(), + targetDaiBalance.toInt256(), + 0 + ); + assertApproxEq( + usdc.balanceOf(address(this)).toInt256(), + targetUsdcBalance.toInt256(), + 0 + ); + } + + function testDepositNoFundsSucceeds() public { + usdcDeposit.deposit(); + daiDeposit.deposit(); + } + + function testDepositWhenPausedFails() public { + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.pause(); + vm.expectRevert("Pausable: paused"); + usdcDeposit.deposit(); + + vm.prank(MainnetAddresses.GOVERNOR); + daiDeposit.pause(); + vm.expectRevert("Pausable: paused"); + daiDeposit.deposit(); + } + + function testWithdrawNonGovFails() public { + vm.expectRevert("CoreRef: Caller is not a PCV controller"); + usdcDeposit.withdraw(address(this), targetUsdcBalance); + + vm.expectRevert("CoreRef: Caller is not a PCV controller"); + daiDeposit.withdraw(address(this), targetDaiBalance); + } + + function testWithdrawAllNonGovFails() public { + vm.expectRevert("CoreRef: Caller is not a PCV controller"); + usdcDeposit.withdrawAll(address(this)); + + vm.expectRevert("CoreRef: Caller is not a PCV controller"); + daiDeposit.withdrawAll(address(this)); + } +} From 85e18d20a67a59ef1dd55f0d718523ba5b18703b Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 3 Oct 2022 12:33:06 -0700 Subject: [PATCH 04/84] Maple PCV Deposit, harvest functionality, COMP Token --- contracts/pcv/maple/IMplRewards.sol | 58 +++++ contracts/pcv/maple/IPool.sol | 93 +++++++ contracts/pcv/maple/MaplePCVDeposit.sol | 151 +++++++++++ .../IntegrationTestMaplePCVDeposit.t.sol | 239 ++++++++++++++++++ .../IntegrationTestPriceBoundPSM.t.sol | 8 - .../integration/fixtures/MainnetAddresses.sol | 2 + 6 files changed, 543 insertions(+), 8 deletions(-) create mode 100644 contracts/pcv/maple/IMplRewards.sol create mode 100644 contracts/pcv/maple/IPool.sol create mode 100644 contracts/pcv/maple/MaplePCVDeposit.sol create mode 100644 contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol diff --git a/contracts/pcv/maple/IMplRewards.sol b/contracts/pcv/maple/IMplRewards.sol new file mode 100644 index 000000000..096bb799b --- /dev/null +++ b/contracts/pcv/maple/IMplRewards.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity =0.8.13; + +interface IMplRewards { + // Views + function rewardsToken() external view returns (address); + + function stakingToken() external view returns (address); + + function periodFinish() external view returns (uint256); + + function rewardRate() external view returns (uint256); + + function rewardsDuration() external view returns (uint256); + + function lastUpdateTime() external view returns (uint256); + + function rewardPerTokenStored() external view returns (uint256); + + function lastPauseTime() external view returns (uint256); + + function paused() external view returns (bool); + + function userRewardPerTokenPaid(address) external view returns (uint256); + + function rewards(address) external view returns (uint256); + + function totalSupply() external view returns (uint256); + + function balanceOf(address) external view returns (uint256); + + function lastTimeRewardApplicable() external view returns (uint256); + + function rewardPerToken() external view returns (uint256); + + function earned(address) external view returns (uint256); + + function getRewardForDuration() external view returns (uint256); + + // Mutative + function stake(uint256) external; + + function withdraw(uint256) external; + + function getReward() external; + + function exit() external; + + function notifyRewardAmount(uint256) external; + + function updatePeriodFinish(uint256) external; + + function recoverERC20(address, uint256) external; + + function setRewardsDuration(uint256) external; + + function setPaused(bool) external; +} diff --git a/contracts/pcv/maple/IPool.sol b/contracts/pcv/maple/IPool.sol new file mode 100644 index 000000000..4dde5b7ed --- /dev/null +++ b/contracts/pcv/maple/IPool.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity =0.8.13; + +/// @notice maple pool interface +interface IPool { + function balanceOf(address) external view returns (uint256); + + /** + @dev Returns the amount of funds that an account has earned in total. + @dev accumulativeFundsOf(_owner) = withdrawableFundsOf(_owner) + withdrawnFundsOf(_owner) + = (pointsPerShare * balanceOf(_owner) + pointsCorrection[_owner]) / pointsMultiplier + @param _owner The address of a token holder. + @return The amount of funds that `_owner` has earned in total. + */ + function accumulativeFundsOf(address _owner) + external + view + returns (uint256); + + /** + @dev Returns the total amount of funds a given address is able to withdraw currently. + @param owner Address of FDT holder. + @return A uint256 representing the available funds for a given account. + */ + function withdrawableFundsOf(address owner) external view returns (uint256); + + /** + @dev Handles Liquidity Providers depositing of Liquidity Asset into the LiquidityLocker, minting PoolFDTs. + @dev It emits a `DepositDateUpdated` event. + @dev It emits a `BalanceUpdated` event. + @dev It emits a `Cooldown` event. + @param amt Amount of Liquidity Asset to deposit. + */ + function deposit(uint256 amt) external; + + function increaseCustodyAllowance(address, uint256) external; + + function poolState() external view returns (uint256); + + function claim(address, address) external returns (uint256[7] memory); + + function fundLoan( + address, + address, + uint256 + ) external; + + /** + @dev Activates the cooldown period to withdraw. It can't be called if the account is not providing liquidity. + @dev It emits a `Cooldown` event. + */ + function intendToWithdraw() external; + + /** + @dev Handles Liquidity Providers withdrawing of Liquidity Asset from the LiquidityLocker, burning PoolFDTs. + @dev It emits two `BalanceUpdated` event. + @param amt Amount of Liquidity Asset to withdraw. + */ + function withdraw(uint256 amt) external; + + /** + @dev Withdraws all available funds for a FDT holder. + */ + function withdrawFunds() external; + + function liquidityAsset() external view returns (address); + + function liquidityLocker() external view returns (address); + + function stakeAsset() external view returns (address); + + function stakeLocker() external view returns (address); + + function stakingFee() external view returns (uint256); + + function principalOut() external view returns (uint256); + + function liquidityCap() external view returns (uint256); + + function lockupPeriod() external view returns (uint256); + + function depositDate(address) external view returns (uint256); + + function debtLockers(address, address) external view returns (address); + + function withdrawCooldown(address) external view returns (uint256); + + function setLiquidityCap(uint256) external; + + function cancelWithdraw() external; + + function isDepositAllowed(uint256) external view returns (bool); +} diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol new file mode 100644 index 000000000..f6816a543 --- /dev/null +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -0,0 +1,151 @@ +pragma solidity =0.8.13; + +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {IPool} from "./IPool.sol"; +import {IMplRewards} from "./IMplRewards.sol"; +import {CoreRef} from "../../refs/CoreRef.sol"; +import {PCVDeposit} from "../PCVDeposit.sol"; + +/// @notice PCV Deposit for Maple +/// Allows depositing only by privileged role to prevent lockup period being extended by griefers + +/// Can only deposit USDC in this MAPLE PCV deposit +contract MaplePCVDeposit is PCVDeposit { + using SafeERC20 for IERC20; + + /// @notice reference to the Maple Pool where deposits and withdraws will originate + IPool public immutable pool; + + /// @notice reference to the Maple Staking Rewards Contract + IMplRewards public immutable mplRewards; + + /// @notice reference to the underlying token + IERC20 public immutable token; + + /// @notice scaling factor for USDC + /// @dev hardcoded to use USDC decimals as this is the only + /// supplied asset Volt Protocol will support + uint256 public constant scalingFactor = 1e12; + + /// @notice fetch underlying asset by calling pool and getting liquidity asset + /// @param _core reference to the Core contract + /// @param _pool reference to the Maple Pool contract + constructor( + address _core, + address _pool, + address _mplRewards + ) CoreRef(_core) { + token = IERC20(IPool(_pool).liquidityAsset()); + /// enforce underlying token is USDC + require( + address(token) == 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, + "MaplePCVDeposit: Underlying not USDC" + ); + pool = IPool(_pool); + mplRewards = IMplRewards(_mplRewards); + } + + /// @notice return the amount of funds this contract owns in Maple FDT's + /// without accounting for interest earned + /// does not account for unrealized losses in the venue + function balance() public view override returns (uint256) { + return pool.balanceOf(address(this)) / scalingFactor; + } + + /// @notice return the underlying token denomination for this deposit + function balanceReportedIn() external view returns (address) { + return address(token); + } + + /// @notice deposit PCV into Maple. + /// all deposits are subject to a minimum 90 day lockup, + /// no op if 0 token balance + /// deposits are then immediately staked to accrue MPL rewards + /// only pcv controller can deposit, as this contract would be vulnerable + /// to donation / griefing attacks if anyone could call deposit and extend lockup time + function deposit() external onlyPCVController { + uint256 amount = IERC20(token).balanceOf(address(this)); + if (amount == 0) { + /// no op to prevent wasted gas + return; + } + + /// pool deposit + token.approve(address(pool), amount); + pool.deposit(amount); + + /// stake pool FDT for MPL rewards + uint256 scaledDepositAmount = amount * scalingFactor; + pool.increaseCustodyAllowance(address(mplRewards), scaledDepositAmount); + mplRewards.stake(scaledDepositAmount); + + emit Deposit(msg.sender, amount); + } + + /// @notice function to start the cooldown process to withdraw + /// 1. lp lockup on deposit --> 90 days locked up and can't withdraw + /// 2. cool down period, call intend to withdraw --> + /// must wait 10 days before withdraw after calling intend to withdraw function + /// 3. after cool down and past the lockup period, + /// have 2 days to withdraw before cool down period restarts. + function signalIntentToWithdraw() external onlyPCVController { + pool.intendToWithdraw(); + } + + /// @notice function to cancel a withdraw + /// should only be used to allow a transfer when doing a withdrawERC20 call + function cancelWithdraw() external onlyPCVController { + pool.cancelWithdraw(); + } + + /// @notice withdraw PCV from Maple, only callable by PCV controller + /// @param to destination after funds are withdrawn from venue + /// @param amount of PCV to withdraw from the venue + function withdraw(address to, uint256 amount) + external + override + onlyPCVController + { + uint256 scaledWithdrawAmount = amount * scalingFactor; + + mplRewards.getReward(); /// get MPL rewards + /// this call will withdraw amount of principal requested, and then send + /// over any accrued interest. + /// expected behavior is that this contract + /// receives either amount of USDC, or amount of USDC + interest accrued + /// if lending losses were taken, receive less than amount + mplRewards.withdraw(scaledWithdrawAmount); /// decreases allowance + + /// withdraw from the pool + pool.withdraw(amount); + token.safeTransfer(to, amount); + + emit Withdrawal(msg.sender, to, amount); + } + + /// @notice withdraw all PCV from Maple + function withdrawAll(address to) external onlyPCVController { + uint256 amount = balance(); + mplRewards.exit(); /// unstakes from Maple reward contract and claims rewards + /// this call will withdraw all principal, + /// then send over any accrued interest. + /// expected behavior is that this contract + /// receives balance amount of USDC, or amount of USDC + interest accrued + /// if lending losses were taken, receive less than amount + pool.withdraw(amount); /// call pool and withdraw entire balance + + uint256 tokenBalance = IERC20(token).balanceOf(address(this)); + token.safeTransfer(to, tokenBalance); + + emit Withdrawal(msg.sender, to, tokenBalance); + } + + /// permissionless function to harvest rewards before withdraw + function harvest() external { + mplRewards.getReward(); + + emit Harvest(); + } +} diff --git a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol new file mode 100644 index 000000000..d8be5a8c6 --- /dev/null +++ b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol @@ -0,0 +1,239 @@ +//SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity =0.8.13; + +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Vm} from "../unit/utils/Vm.sol"; +import {Core} from "../../core/Core.sol"; +import {IVolt} from "../../volt/IVolt.sol"; +import {IPool} from "../../pcv/maple/IPool.sol"; +import {DSTest} from "../unit/utils/DSTest.sol"; +import {IDSSPSM} from "../../pcv/maker/IDSSPSM.sol"; +import {Constants} from "../../Constants.sol"; +import {PCVGuardian} from "../../pcv/PCVGuardian.sol"; +import {IMplRewards} from "../../pcv/maple/IMplRewards.sol"; +import {MockMaplePool} from "../../mock/MockMaplePool.sol"; +import {MaplePCVDeposit} from "../../pcv/maple/MaplePCVDeposit.sol"; +import {MainnetAddresses} from "./fixtures/MainnetAddresses.sol"; +import {PegStabilityModule} from "../../peg/PegStabilityModule.sol"; +import {ERC20CompoundPCVDeposit} from "../../pcv/compound/ERC20CompoundPCVDeposit.sol"; + +contract IntegrationTestMaplePCVDeposit is DSTest { + using SafeCast for *; + + Vm public constant vm = Vm(HEVM_ADDRESS); + + MaplePCVDeposit private usdcDeposit; + + PCVGuardian private immutable pcvGuardian = + PCVGuardian(MainnetAddresses.PCV_GUARDIAN); + + Core private core = Core(MainnetAddresses.CORE); + + IERC20 private usdc = IERC20(MainnetAddresses.USDC); + + uint256 public constant targetUsdcBalance = 100_000e6; + + /// @notice once you signal to withdraw after lockup, wait 10 days + uint256 public constant cooldownPeriod = 864000; + + /// @notice once you have waited for cool down period to pass + /// you have 2 days to withdraw before you have to request to withdraw again + uint256 public constant withdrawPeriod = 172800; + + IERC20 public constant maple = + IERC20(0x33349B282065b0284d756F0577FB39c158F935e6); + address public constant mplRewards = + 0x7869D7a3B074b5fa484dc04798E254c9C06A5e90; + address public constant maplePool = + 0xFeBd6F15Df3B73DC4307B1d7E65D46413e710C27; + + address public constant mapleOwner = + 0xd6d4Bcde6c816F17889f1Dd3000aF0261B03a196; + + function setUp() public { + usdcDeposit = new MaplePCVDeposit(address(core), maplePool, mplRewards); + + vm.label(address(usdcDeposit), "Maple USDC PCV Deposit"); + vm.label(address(usdc), "USDC Token"); + vm.label(address(maplePool), "Maple Pool"); + vm.label(address(mplRewards), "Maple Rewards"); + + vm.startPrank(MainnetAddresses.DAI_USDC_USDT_CURVE_POOL); + usdc.transfer(address(usdcDeposit), targetUsdcBalance); + vm.stopPrank(); + + /// governor has pcv controller role + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.deposit(); + } + + function testSetup() public { + assertEq(address(usdcDeposit.core()), address(core)); + assertEq(usdcDeposit.balanceReportedIn(), address(usdc)); + assertEq(address(usdcDeposit.pool()), maplePool); + assertEq(address(usdcDeposit.mplRewards()), mplRewards); + assertEq(address(usdcDeposit.token()), address(MainnetAddresses.USDC)); + assertApproxEq( + usdcDeposit.balance().toInt256(), + targetUsdcBalance.toInt256(), + 0 + ); + } + + function testDeployFailsNotUSDCUnderlying() public { + MockMaplePool mockPool = new MockMaplePool(address(this)); + + vm.expectRevert("MaplePCVDeposit: Underlying not USDC"); + new MaplePCVDeposit(address(core), address(mockPool), mplRewards); + } + + function testWithdraw() public { + uint256 rewardRate = IMplRewards(mplRewards).rewardRate(); + vm.prank(mapleOwner); + IMplRewards(mplRewards).notifyRewardAmount(rewardRate); + + vm.warp(block.timestamp + IPool(maplePool).lockupPeriod()); + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.signalIntentToWithdraw(); + + vm.warp(block.timestamp + cooldownPeriod); + + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.withdraw(address(this), targetUsdcBalance); + + uint256 mplBalance = maple.balanceOf(address(usdcDeposit)); + + assertEq(usdcDeposit.balance(), 0); + assertEq(usdc.balanceOf(address(this)), targetUsdcBalance); + assertTrue(mplBalance != 0); + } + + function testHarvest() public { + uint256 rewardRate = IMplRewards(mplRewards).rewardRate(); + vm.prank(mapleOwner); + IMplRewards(mplRewards).notifyRewardAmount(rewardRate); + + vm.warp(block.timestamp + IPool(maplePool).lockupPeriod()); + + usdcDeposit.harvest(); + + uint256 mplBalance = maple.balanceOf(address(usdcDeposit)); + + assertTrue(mplBalance != 0); + } + + function _testWithdraw(uint256 amount) private { + uint256 rewardRate = IMplRewards(mplRewards).rewardRate(); + vm.prank(mapleOwner); + IMplRewards(mplRewards).notifyRewardAmount(rewardRate); + + vm.warp( + block.timestamp + IPool(maplePool).lockupPeriod() - cooldownPeriod + ); + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.signalIntentToWithdraw(); + + vm.warp(block.timestamp + cooldownPeriod); + + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.withdraw(address(this), amount); + + uint256 mplBalance = maple.balanceOf(address(usdcDeposit)); + + uint256 targetBal = targetUsdcBalance - amount; + assertEq(usdcDeposit.balance(), targetBal); + assertEq(usdc.balanceOf(address(this)), amount); + assertTrue(mplBalance != 0); + } + + function testWithdrawAtCoolDownEnd() public { + _testWithdraw(targetUsdcBalance); + } + + function testWithdrawAtCoolDownEndFuzz(uint40 amount) public { + vm.assume(amount != 0); + vm.assume(amount <= targetUsdcBalance); + _testWithdraw(amount); + } + + function testWithdrawAll() public { + uint256 rewardRate = IMplRewards(mplRewards).rewardRate(); + vm.prank(mapleOwner); + IMplRewards(mplRewards).notifyRewardAmount(rewardRate); + + vm.warp( + block.timestamp + IPool(maplePool).lockupPeriod() - cooldownPeriod + ); + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.signalIntentToWithdraw(); + + vm.warp(block.timestamp + cooldownPeriod); + + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.withdrawAll(address(this)); + + uint256 mplBalance = maple.balanceOf(address(usdcDeposit)); + + assertEq(usdcDeposit.balance(), 0); + assertEq(usdc.balanceOf(address(this)), targetUsdcBalance); + assertTrue(mplBalance != 0); + } + + function testSignalWithdrawPCVControllerSucceeds() public { + uint256 blockTimestamp = 10_000; + + vm.warp(blockTimestamp); + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.signalIntentToWithdraw(); + + assertEq( + IPool(maplePool).withdrawCooldown(address(usdcDeposit)), + blockTimestamp + ); + } + + function testCancelWithdrawPCVControllerSucceeds() public { + uint256 blockTimestamp = 10_000; + + vm.warp(blockTimestamp); + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.signalIntentToWithdraw(); + + assertEq( + IPool(maplePool).withdrawCooldown(address(usdcDeposit)), + blockTimestamp + ); + + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.cancelWithdraw(); + + assertEq(IPool(maplePool).withdrawCooldown(address(usdcDeposit)), 0); + } + + function testDepositNotPCVControllerFails() public { + vm.expectRevert("CoreRef: Caller is not a PCV controller"); + usdcDeposit.deposit(); + } + + function testSignalWithdrawNonPCVControllerFails() public { + vm.expectRevert("CoreRef: Caller is not a PCV controller"); + usdcDeposit.signalIntentToWithdraw(); + } + + function testCancelWithdrawNonPCVControllerFails() public { + vm.expectRevert("CoreRef: Caller is not a PCV controller"); + usdcDeposit.cancelWithdraw(); + } + + function testWithdrawNonGovFails() public { + vm.expectRevert("CoreRef: Caller is not a PCV controller"); + usdcDeposit.withdraw(address(this), targetUsdcBalance); + } + + function testWithdrawAllNonGovFails() public { + vm.expectRevert("CoreRef: Caller is not a PCV controller"); + usdcDeposit.withdrawAll(address(this)); + } +} diff --git a/contracts/test/integration/IntegrationTestPriceBoundPSM.t.sol b/contracts/test/integration/IntegrationTestPriceBoundPSM.t.sol index 9a1a07f53..71a97ca6e 100644 --- a/contracts/test/integration/IntegrationTestPriceBoundPSM.t.sol +++ b/contracts/test/integration/IntegrationTestPriceBoundPSM.t.sol @@ -17,11 +17,8 @@ import {ERC20CompoundPCVDeposit} from "../../pcv/compound/ERC20CompoundPCVDeposi import {Vm} from "./../unit/utils/Vm.sol"; import {DSTest} from "./../unit/utils/DSTest.sol"; import {MainnetAddresses} from "./fixtures/MainnetAddresses.sol"; - import {Constants} from "../../Constants.sol"; -import "hardhat/console.sol"; - contract IntegrationTestPriceBoundPSMTest is DSTest { using SafeCast for *; PriceBoundPSM private psm; @@ -92,13 +89,8 @@ contract IntegrationTestPriceBoundPSMTest is DSTest { rariVoltPCVDeposit ); - console.log( - "isminter: ", - ICore(MainnetAddresses.FEI_CORE).isMinter(feiDaiPsm) - ); vm.prank(feiDaiPsm); fei.mint(address(this), mintAmount); - console.log("here: "); vm.startPrank(MainnetAddresses.GOVERNOR); diff --git a/contracts/test/integration/fixtures/MainnetAddresses.sol b/contracts/test/integration/fixtures/MainnetAddresses.sol index 703d19123..53a99bd96 100644 --- a/contracts/test/integration/fixtures/MainnetAddresses.sol +++ b/contracts/test/integration/fixtures/MainnetAddresses.sol @@ -96,6 +96,8 @@ library MainnetAddresses { address public constant CUSDC = 0x39AA39c021dfbaE8faC545936693aC917d5E7563; + address public constant COMP = 0xc00e94Cb662C3520282E6f5717214004A7f26888; + // ---------- Maker ADDRESSES ---------- address public constant MAKER_DAI_USDC_PSM = From fc92e6b5b9c8e4de442f3f73b6f6149d9c7dc62c Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 3 Oct 2022 19:23:12 -0700 Subject: [PATCH 05/84] Update comment in Compound PCV router to correctly label PCV deposit denominations --- contracts/pcv/compound/CompoundPCVRouter.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/pcv/compound/CompoundPCVRouter.sol b/contracts/pcv/compound/CompoundPCVRouter.sol index 59071c31e..119c30157 100644 --- a/contracts/pcv/compound/CompoundPCVRouter.sol +++ b/contracts/pcv/compound/CompoundPCVRouter.sol @@ -18,10 +18,10 @@ import {IPegStabilityModule} from "../../peg/IPegStabilityModule.sol"; contract CompoundPCVRouter is CoreRef { using SafeERC20 for IERC20; - /// @notice reference to the Compound PCV deposit for USDC + /// @notice reference to the Compound PCV deposit for DAI PCVDeposit public immutable daiPcvDeposit; - /// @notice reference to the Compound PCV deposit for DAI + /// @notice reference to the Compound PCV deposit for USDC PCVDeposit public immutable usdcPcvDeposit; /// @notice reference to the Maker DAI-USDC PSM that this router interacts with From 86ea05ef0339d5ad1bf5ee6a36d5b51a391364dc Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 3 Oct 2022 19:27:51 -0700 Subject: [PATCH 06/84] VIP-14 Integration Test Skeleton --- .../test/integration/IntegrationTestVIP14.sol | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 contracts/test/integration/IntegrationTestVIP14.sol diff --git a/contracts/test/integration/IntegrationTestVIP14.sol b/contracts/test/integration/IntegrationTestVIP14.sol new file mode 100644 index 000000000..55715dfb4 --- /dev/null +++ b/contracts/test/integration/IntegrationTestVIP14.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity =0.8.13; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {ICore} from "../../core/ICore.sol"; +import {IVolt} from "../../volt/Volt.sol"; +import {MainnetAddresses} from "./fixtures/MainnetAddresses.sol"; +import {vip14} from "./vip/vip14.sol"; +import {TimelockSimulation} from "./utils/TimelockSimulation.sol"; +import {PriceBoundPSM} from "../../peg/PriceBoundPSM.sol"; +import {IPCVGuardian} from "../../pcv/IPCVGuardian.sol"; + +contract IntegrationTestVIP14 is TimelockSimulation, vip14 { + using SafeCast for *; + PriceBoundPSM private psm = PriceBoundPSM(MainnetAddresses.VOLT_DAI_PSM); + + ICore private core = ICore(MainnetAddresses.CORE); + IERC20 dai = IERC20(MainnetAddresses.DAI); + IVolt volt = IVolt(MainnetAddresses.VOLT); + + uint256 public constant mintAmount = type(uint80).max; + + IPCVGuardian private immutable mainnetPCVGuardian = + IPCVGuardian(MainnetAddresses.PCV_GUARDIAN); + + function setUp() public { + mainnetSetup(); + simulate( + getMainnetProposal(), + TimelockController(payable(MainnetAddresses.TIMELOCK_CONTROLLER)), + mainnetPCVGuardian, + MainnetAddresses.GOVERNOR, + MainnetAddresses.EOA_1, + vm, + false + ); + mainnetValidate(); + } + + function testSkimDaiToMorphoDeposit() public {} + + function testSkimUsdcToMorphoDeposit() public {} + + function testDripUsdcToPsm() public {} + + function testDripDaiToPsm() public {} + + function testClaimCompRewardsDai() public {} + + function testClaimCompRewardsUsdc() public {} +} From 43ac3fb2ce0459bb07746a5c3e6e029e40191d8e Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 3 Oct 2022 19:42:48 -0700 Subject: [PATCH 07/84] Update TODO PCV Guard Verification --- contracts/test/integration/utils/PCVGuardVerification.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/test/integration/utils/PCVGuardVerification.sol b/contracts/test/integration/utils/PCVGuardVerification.sol index 38ba89d44..b2cd9d257 100644 --- a/contracts/test/integration/utils/PCVGuardVerification.sol +++ b/contracts/test/integration/utils/PCVGuardVerification.sol @@ -19,7 +19,7 @@ contract PCVGuardVerification is DSTest { using Deviation for *; using SafeCast for *; - /// TODO add arbitrum support + /// TODO add Morpho Deposits once deployed /// @notice all PSM's on mainnet address[] private allMainnetPCVDeposits = [ From c9ff1b14e198823cefa4aba5652e9dae4e1c4095 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 3 Oct 2022 19:49:08 -0700 Subject: [PATCH 08/84] VIP-14 Proposal, Deploys Router, Deploys Deposits, Deploys Oracle, Disconnect compound deposits from ERC20 allocator, connect new morpho deposits to ERC20 allocator --- contracts/test/integration/vip/Runner.sol | 4 +- contracts/test/integration/vip/vip14.sol | 353 ++++++++++++++++++++++ contracts/test/unit/utils/Vm.sol | 7 + 3 files changed, 362 insertions(+), 2 deletions(-) create mode 100644 contracts/test/integration/vip/vip14.sol diff --git a/contracts/test/integration/vip/Runner.sol b/contracts/test/integration/vip/Runner.sol index ba331e868..2d9c1e053 100644 --- a/contracts/test/integration/vip/Runner.sol +++ b/contracts/test/integration/vip/Runner.sol @@ -1,6 +1,6 @@ pragma solidity =0.8.13; -import {vip12} from "./vip12.sol"; +import {vip14} from "./vip14.sol"; // import {vipx} from "./vipx.sol"; import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; import {TimelockSimulation} from "../utils/TimelockSimulation.sol"; @@ -10,7 +10,7 @@ import {PCVGuardian} from "./../../../pcv/PCVGuardian.sol"; /// @dev test harness for running and simulating VOLT Improvement Proposals /// inherit the proposal to simulate -contract Runner is TimelockSimulation, vip12 { +contract Runner is TimelockSimulation, vip14 { /// @notice mainnet PCV Guardian PCVGuardian private immutable mainnetPCVGuardian = PCVGuardian(MainnetAddresses.PCV_GUARDIAN); diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol new file mode 100644 index 000000000..c6bb75c47 --- /dev/null +++ b/contracts/test/integration/vip/vip14.sol @@ -0,0 +1,353 @@ +//SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity =0.8.13; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; + +import {Vm} from "./../../unit/utils/Vm.sol"; +import {Core} from "../../../core/Core.sol"; +import {IVIP} from "./IVIP.sol"; +import {DSTest} from "./../../unit/utils/DSTest.sol"; +import {PCVDeposit} from "../../../pcv/PCVDeposit.sol"; +import {PCVGuardian} from "../../../pcv/PCVGuardian.sol"; +import {IPCVDeposit} from "../../../pcv/IPCVDeposit.sol"; +import {ERC20Allocator} from "../../../pcv/utils/ERC20Allocator.sol"; +import {MaplePCVDeposit} from "../../../pcv/maple/MaplePCVDeposit.sol"; +import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; +import {VoltSystemOracle} from "../../../oracle/VoltSystemOracle.sol"; +import {OraclePassThrough} from "../../../oracle/OraclePassThrough.sol"; +import {CompoundPCVRouter} from "../../../pcv/compound/CompoundPCVRouter.sol"; +import {ITimelockSimulation} from "../utils/ITimelockSimulation.sol"; +import {MorphoCompoundPCVDeposit} from "../../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; + +/// Deployment Steps +/// 1. deploy morpho dai deposit +/// 2. deploy morpho usdc deposit +/// 3. deploy compound pcv router pointed to morpho dai and usdc deposits +/// 4. deploy volt system oracle + +/// Governance Steps +/// 1. grant new PCV router PCV Controller role +/// 2. revoke PCV Controller role from old PCV Router + +/// 3. disconnect old dai compound deposit from allocator +/// 4. disconnect old usdc compound deposit from allocator + +/// 5. connect new dai morpho deposit to allocator +/// 6. connect new usdc morpho deposit to allocator + +/// 7. add deposits as safe addresses + +/// 8. connect new oracle to oracle pass through with updated rate + +contract vip14 is DSTest, IVIP { + using SafeCast for uint256; + using SafeERC20 for IERC20; + Vm public constant vm = Vm(HEVM_ADDRESS); + + address private dai = MainnetAddresses.DAI; + address private fei = MainnetAddresses.FEI; + address private usdc = MainnetAddresses.USDC; + address private core = MainnetAddresses.CORE; + + ITimelockSimulation.action[] private mainnetProposal; + + CompoundPCVRouter public immutable router; + MorphoCompoundPCVDeposit public immutable daiDeposit; + MorphoCompoundPCVDeposit public immutable usdcDeposit; + VoltSystemOracle public immutable oracle; + MaplePCVDeposit public immutable mapleDeposit; + + uint256 public immutable startTime; + + uint256 private constant monthlyChangeRateBasisPoints = 29; + + PCVGuardian private immutable pcvGuardian = + PCVGuardian(MainnetAddresses.PCV_GUARDIAN); + + VoltSystemOracle private immutable oldOracle = + VoltSystemOracle(MainnetAddresses.VOLT_SYSTEM_ORACLE_144_BIPS); + + ERC20Allocator private immutable allocator = + ERC20Allocator(MainnetAddresses.ERC20ALLOCATOR); + + uint256 targetMapleDepositAmount = 750_000e6; + + /// --------- Maple Addresses --------- + + address public constant mplRewards = + 0x7869D7a3B074b5fa484dc04798E254c9C06A5e90; + + address public constant maplePool = + 0xFeBd6F15Df3B73DC4307B1d7E65D46413e710C27; + + constructor() { + mapleDeposit = new MaplePCVDeposit(core, maplePool, mplRewards); + daiDeposit = new MorphoCompoundPCVDeposit(core, MainnetAddresses.CDAI); + usdcDeposit = new MorphoCompoundPCVDeposit( + core, + MainnetAddresses.CUSDC + ); + + router = new CompoundPCVRouter( + core, + PCVDeposit(address(daiDeposit)), + PCVDeposit(address(usdcDeposit)) + ); + + oracle = new VoltSystemOracle( + monthlyChangeRateBasisPoints, + block.timestamp, + oldOracle.getCurrentOraclePrice() + ); + + startTime = block.timestamp; + + address[] memory toWhitelist = new address[](2); + toWhitelist[0] = address(daiDeposit); + toWhitelist[1] = address(usdcDeposit); + + mainnetProposal.push( + ITimelockSimulation.action({ + value: 0, + target: MainnetAddresses.CORE, + arguments: abi.encodeWithSignature( + "grantPCVController(address)", + address(router) + ), + description: "Grant Morpho PCV Router PCV Controller Role" + }) + ); + mainnetProposal.push( + ITimelockSimulation.action({ + value: 0, + target: MainnetAddresses.CORE, + arguments: abi.encodeWithSignature( + "revokePCVController(address)", + MainnetAddresses.COMPOUND_PCV_ROUTER + ), + description: "Revoke PCV Controller Role from Compound PCV Router" + }) + ); + + /// disconnect unused compound deposits + mainnetProposal.push( + ITimelockSimulation.action({ + value: 0, + target: MainnetAddresses.ERC20ALLOCATOR, + arguments: abi.encodeWithSignature( + "deleteDeposit(address)", + MainnetAddresses.COMPOUND_DAI_PCV_DEPOSIT + ), + description: "Remove Compound DAI Deposit from ERC20Allocator" + }) + ); + mainnetProposal.push( + ITimelockSimulation.action({ + value: 0, + target: MainnetAddresses.ERC20ALLOCATOR, + arguments: abi.encodeWithSignature( + "deleteDeposit(address)", + MainnetAddresses.COMPOUND_USDC_PCV_DEPOSIT + ), + description: "Remove Compound USDC Deposit from ERC20Allocator" + }) + ); + + /// connect to compound deposits + mainnetProposal.push( + ITimelockSimulation.action({ + value: 0, + target: MainnetAddresses.ERC20ALLOCATOR, + arguments: abi.encodeWithSignature( + "connectDeposit(address,address)", + MainnetAddresses.VOLT_DAI_PSM, + address(daiDeposit) + ), + description: "Add Morpho DAI Deposit to ERC20Allocator" + }) + ); + mainnetProposal.push( + ITimelockSimulation.action({ + value: 0, + target: MainnetAddresses.ERC20ALLOCATOR, + arguments: abi.encodeWithSignature( + "connectDeposit(address,address)", + MainnetAddresses.VOLT_USDC_PSM, + address(usdcDeposit) + ), + description: "Add Morpho USDC Deposit to ERC20Allocator" + }) + ); + + mainnetProposal.push( + ITimelockSimulation.action({ + value: 0, + target: MainnetAddresses.PCV_GUARDIAN, + arguments: abi.encodeWithSignature( + "addWhitelistAddresses(address[])", + toWhitelist + ), + description: "Add USDC and DAI Morpho deposits to the PCV Guardian" + }) + ); + + mainnetProposal.push( + ITimelockSimulation.action({ + value: 0, + target: MainnetAddresses.ORACLE_PASS_THROUGH, + arguments: abi.encodeWithSignature( + "updateScalingPriceOracle(address)", + address(oracle) + ), + description: "Point Oracle Pass Through to new oracle address" + }) + ); + } + + function getMainnetProposal() + public + view + override + returns (ITimelockSimulation.action[] memory) + { + return mainnetProposal; + } + + /// TODO -> move funds to this address + + /// . move all funds from compound deposits to morpho deposits + /// . move all needed funds to Maple + function mainnetSetup() public override { + vm.startPrank(MainnetAddresses.GOVERNOR); + pcvGuardian.withdrawAllToSafeAddress( + MainnetAddresses.COMPOUND_DAI_PCV_DEPOSIT + ); + pcvGuardian.withdrawAllToSafeAddress( + MainnetAddresses.COMPOUND_USDC_PCV_DEPOSIT + ); + + uint256 usdcBalance = IERC20(usdc).balanceOf(MainnetAddresses.GOVERNOR); + IERC20(usdc).transfer( + address(usdcDeposit), + usdcBalance - targetMapleDepositAmount + ); + IERC20(usdc).transfer(address(usdcDeposit), targetMapleDepositAmount); + IERC20(dai).transfer( + address(daiDeposit), + IERC20(dai).balanceOf(MainnetAddresses.GOVERNOR) + ); + + usdcDeposit.deposit(); + daiDeposit.deposit(); + + vm.stopPrank(); + } + + /// assert core addresses are set correctly + /// assert dai and usdc compound pcv deposit are pcv guardian in whitelist + /// assert pcv deposits are set correctly in router + /// assert pcv deposits are set correctly in allocator + /// assert old pcv deposits are disconnected in allocator + /// assert oracle pass through is pointed to the proper Volt System Oracle + function mainnetValidate() public override { + assertEq(address(usdcDeposit.core()), core); + assertEq(address(daiDeposit.core()), core); + assertEq(address(router.core()), core); + + assertTrue(Core(core).isPCVController(address(router))); + assertTrue( + !Core(core).isPCVController(MainnetAddresses.COMPOUND_PCV_ROUTER) + ); + + assertTrue(pcvGuardian.isWhitelistAddress(address(daiDeposit))); + assertTrue(pcvGuardian.isWhitelistAddress(address(usdcDeposit))); + + /// router parameter validations + assertEq(address(router.daiPcvDeposit()), address(daiDeposit)); + assertEq(address(router.usdcPcvDeposit()), address(usdcDeposit)); + assertEq(address(router.DAI()), dai); + assertEq(address(router.USDC()), usdc); + assertEq(address(router.GEM_JOIN()), MainnetAddresses.GEM_JOIN); + assertEq(address(router.daiPSM()), MainnetAddresses.MAKER_DAI_USDC_PSM); + + /// old deposits disconnected in allocator + assertEq( + allocator.pcvDepositToPSM( + MainnetAddresses.COMPOUND_USDC_PCV_DEPOSIT + ), + address(0) + ); + assertEq( + allocator.pcvDepositToPSM( + MainnetAddresses.COMPOUND_DAI_PCV_DEPOSIT + ), + address(0) + ); + + /// new deposits connected in allocator + assertEq( + allocator.pcvDepositToPSM(address(daiDeposit)), + MainnetAddresses.VOLT_DAI_PSM + ); + assertEq( + allocator.pcvDepositToPSM(address(usdcDeposit)), + MainnetAddresses.VOLT_USDC_PSM + ); + + /// new pcv deposits set up correctly + assertEq(usdcDeposit.token(), MainnetAddresses.USDC); + assertEq(daiDeposit.token(), MainnetAddresses.DAI); + + assertEq(usdcDeposit.cToken(), MainnetAddresses.CUSDC); + assertEq(daiDeposit.cToken(), MainnetAddresses.CDAI); + + /// oracle pass through points to new scaling price oracle + assertEq( + address( + OraclePassThrough(MainnetAddresses.ORACLE_PASS_THROUGH) + .scalingPriceOracle() + ), + address(oracle) + ); + + /// volt system oracle + /// only 1 day of interest has accrued, so only .5 basis point diff in price between old and new oracle + assertApproxEq( + oracle.oraclePrice().toInt256(), + oldOracle.getCurrentOraclePrice().toInt256(), + 0 + ); + assertApproxEq( + oracle.getCurrentOraclePrice().toInt256(), + oldOracle.getCurrentOraclePrice().toInt256(), + 0 + ); + + assertEq( + oracle.monthlyChangeRateBasisPoints(), + monthlyChangeRateBasisPoints + ); + assertEq(oracle.periodStartTime(), startTime); + } + + function getArbitrumProposal() + public + view + override + returns (ITimelockSimulation.action[] memory) + { + revert("No Arbitrum proposal"); + } + + function arbitrumSetup() public override { + revert("No Arbitrum proposal"); + } + + /// assert oracle pass through is pointing to correct volt system oracle + function arbitrumValidate() public override { + revert("No Arbitrum proposal"); + } +} diff --git a/contracts/test/unit/utils/Vm.sol b/contracts/test/unit/utils/Vm.sol index 5459f52b7..76379dc90 100644 --- a/contracts/test/unit/utils/Vm.sol +++ b/contracts/test/unit/utils/Vm.sol @@ -54,6 +54,13 @@ interface Vm { // Sets an address' balance, (who, newBalance) function deal(address, uint256) external; + // Sets an address' balance in desired token, (token, who, newBalance) + function deal( + address, + address, + uint256 + ) external; + // Sets an address' code, (who, newCode) function etch(address, bytes calldata) external; From 38f7cdb333ee09d681a70e5ac8c32a2be47250f6 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 3 Oct 2022 19:50:10 -0700 Subject: [PATCH 09/84] Add skeleton of VIP-14 integration tests --- contracts/test/integration/IntegrationTestVIP14.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/test/integration/IntegrationTestVIP14.sol b/contracts/test/integration/IntegrationTestVIP14.sol index 55715dfb4..dee28d1b9 100644 --- a/contracts/test/integration/IntegrationTestVIP14.sol +++ b/contracts/test/integration/IntegrationTestVIP14.sol @@ -50,4 +50,8 @@ contract IntegrationTestVIP14 is TimelockSimulation, vip14 { function testClaimCompRewardsDai() public {} function testClaimCompRewardsUsdc() public {} + + function testSwapUsdcToDaiRouter() public {} + + function testSwapDaiToUsdcRouter() public {} } From cf6c21a9ed254c785bfcf40acc548020c01f65d9 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 3 Oct 2022 19:50:35 -0700 Subject: [PATCH 10/84] VIP-14 typescript implementation --- proposals/dao/vip_14.ts | 198 +++++++++++++++++++++ proposals/vip_14.ts | 86 +++++++++ protocol-configuration/mainnetAddresses.ts | 12 ++ test/helpers.ts | 16 ++ 4 files changed, 312 insertions(+) create mode 100644 proposals/dao/vip_14.ts create mode 100644 proposals/vip_14.ts diff --git a/proposals/dao/vip_14.ts b/proposals/dao/vip_14.ts new file mode 100644 index 000000000..8d43d8bb4 --- /dev/null +++ b/proposals/dao/vip_14.ts @@ -0,0 +1,198 @@ +import { + DeployUpgradeFunc, + NamedAddresses, + SetupUpgradeFunc, + TeardownUpgradeFunc, + ValidateUpgradeFunc +} from '@custom-types/types'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; +import { assertApproxEq } from '@test/helpers'; + +/* + +Volt Protocol Improvement Proposal #14 + +Description: + +/// Deployment Steps +/// 1. deploy morpho dai deposit +/// 2. deploy morpho usdc deposit +/// 3. deploy compound pcv router pointed to morpho dai and usdc deposits +/// 4. deploy volt system oracle + +Governance Steps: +1. Grant Morpho PCV Router PCV Controller Role +2. Revoke PCV Controller Role from Compound PCV Router +3. Remove Compound DAI Deposit from ERC20Allocator +4. Remove Compound USDC Deposit from ERC20Allocator +5. Add Morpho DAI Deposit to ERC20Allocator +6. Add Morpho USDC Deposit to ERC20Allocator +7. Add USDC and DAI Morpho deposits to the PCV Guardian +8. Point Oracle Pass Through to new oracle address + +*/ + +/// TODO update this to correct start time +let startTime; + +const monthlyChangeBasisPoints = 29; + +const vipNumber = '14'; + +const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => { + startTime = Math.floor(Date.now() / 1000).toString(); + + const voltSystemOracle = await ethers.getContractAt('VoltSystemOracle', addresses.voltSystemOracle); + + const currentPrice = await voltSystemOracle.getCurrentOraclePrice(); + + const morphoPCVDepositFactory = await ethers.getContractFactory('MorphoCompoundPCVDeposit'); + const compoundPCVRouterFactory = await ethers.getContractFactory('CompoundPCVRouter'); + const voltSystemOracleFactory = await ethers.getContractFactory('VoltSystemOracle'); + + const daiMorphoCompoundPCVDeposit = await morphoPCVDepositFactory.deploy(addresses.core, addresses.cDai); + await daiMorphoCompoundPCVDeposit.deployed(); + + const usdcMorphoCompoundPCVDeposit = await morphoPCVDepositFactory.deploy(addresses.core, addresses.cUsdc); + await usdcMorphoCompoundPCVDeposit.deployed(); + + const morphoCompoundPCVRouter = await compoundPCVRouterFactory.deploy( + addresses.core, + daiMorphoCompoundPCVDeposit.address, + usdcMorphoCompoundPCVDeposit.address + ); + await morphoCompoundPCVRouter.deployed(); + + const voltSystemOracle348Bips = await voltSystemOracleFactory.deploy( + monthlyChangeBasisPoints, + startTime, + currentPrice + ); + await voltSystemOracle348Bips.deployed(); + + console.log(`Volt System Oracle deployed ${voltSystemOracle348Bips.address}`); + console.log(`Morpho Compound PCV Router deployed ${morphoCompoundPCVRouter.address}`); + console.log(`Morpho Compound DAI PCV Deposit deployed ${daiMorphoCompoundPCVDeposit.address}`); + console.log(`Morpho Compound USDC PCV Deposit deployed ${usdcMorphoCompoundPCVDeposit.address}`); + + console.log(`Successfully Deployed VIP-${vipNumber}`); + return { + voltSystemOracle348Bips, + daiMorphoCompoundPCVDeposit, + usdcMorphoCompoundPCVDeposit, + morphoCompoundPCVRouter + }; +}; + +const setup: SetupUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; + +// Tears down any changes made in setup() that need to be +// cleaned up before doing any validation checks. +const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; + +// Run any validations required on the vip using mocha or console logging +// IE check balances, check state of contracts, etc. +const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { + const { + core, + voltSystemOracle, + oraclePassThrough, + voltSystemOracle348Bips, + daiMorphoCompoundPCVDeposit, + usdcMorphoCompoundPCVDeposit, + morphoCompoundPCVRouter, + compoundPCVRouter, + pcvGuardian, + erc20Allocator + } = contracts; + + /// Core address validations + expect(await daiMorphoCompoundPCVDeposit.core()).to.be.equal(core.address); + expect(await usdcMorphoCompoundPCVDeposit.core()).to.be.equal(core.address); + expect(await morphoCompoundPCVRouter.core()).to.be.equal(core.address); + + /// oracle pass through validation + expect(await oraclePassThrough.scalingPriceOracle()).to.be.equal(voltSystemOracle348Bips.address); + + /// pcv controller validation + expect(await core.isPCVController(compoundPCVRouter.address)).to.be.false; + expect(await core.isPCVController(morphoCompoundPCVRouter.address)).to.be.true; + + /// morpho compound PCV Router + expect(await morphoCompoundPCVRouter.USDC()).to.be.equal(addresses.usdc); + expect(await morphoCompoundPCVRouter.DAI()).to.be.equal(addresses.dai); + expect(await morphoCompoundPCVRouter.daiPcvDeposit()).to.be.equal(daiMorphoCompoundPCVDeposit.address); + expect(await morphoCompoundPCVRouter.usdcPcvDeposit()).to.be.equal(usdcMorphoCompoundPCVDeposit.address); + expect(await morphoCompoundPCVRouter.daiPSM()).to.be.equal(addresses.makerDaiUsdcPSM); + expect(await morphoCompoundPCVRouter.GEM_JOIN()).to.be.equal(addresses.makerGemJoin); + + /// pcv deposit validation + expect(await daiMorphoCompoundPCVDeposit.token()).to.be.equal(addresses.dai); + expect(await usdcMorphoCompoundPCVDeposit.token()).to.be.equal(addresses.usdc); + + expect(await daiMorphoCompoundPCVDeposit.cToken()).to.be.equal(addresses.cDai); + expect(await usdcMorphoCompoundPCVDeposit.cToken()).to.be.equal(addresses.cUsdc); + + expect(await daiMorphoCompoundPCVDeposit.MORPHO()).to.be.equal(addresses.morphoCompound); + expect(await usdcMorphoCompoundPCVDeposit.MORPHO()).to.be.equal(addresses.morphoCompound); + + expect(await daiMorphoCompoundPCVDeposit.LENS()).to.be.equal(addresses.morphoCompoundLens); + expect(await usdcMorphoCompoundPCVDeposit.LENS()).to.be.equal(addresses.morphoCompoundLens); + + /// pcv guardian validation + expect(await pcvGuardian.isWhitelistAddress(usdcMorphoCompoundPCVDeposit.address)).to.be.true; + expect(await pcvGuardian.isWhitelistAddress(daiMorphoCompoundPCVDeposit.address)).to.be.true; + + expect(await pcvGuardian.isWhitelistAddress(addresses.daiCompoundPCVDeposit)).to.be.true; + expect(await pcvGuardian.isWhitelistAddress(addresses.usdcCompoundPCVDeposit)).to.be.true; + + /// erc20 allocator validation + expect(await erc20Allocator.pcvDepositToPSM(addresses.usdcCompoundPCVDeposit)).to.be.equal( + ethers.constants.AddressZero + ); + expect(await erc20Allocator.pcvDepositToPSM(addresses.daiCompoundPCVDeposit)).to.be.equal( + ethers.constants.AddressZero + ); + + expect(await erc20Allocator.pcvDepositToPSM(addresses.usdcMorphoCompoundPCVDeposit)).to.be.equal( + addresses.usdcPriceBoundPSM + ); + expect(await erc20Allocator.pcvDepositToPSM(addresses.daiMorphoCompoundPCVDeposit)).to.be.equal( + addresses.daiPriceBoundPSM + ); + + /// volt system oracle validation + expect((await voltSystemOracle348Bips.periodStartTime()).toString()).to.be.equal(startTime.toString()); + expect((await voltSystemOracle348Bips.monthlyChangeRateBasisPoints()).toString()).to.be.equal( + monthlyChangeBasisPoints.toString() + ); + + await assertApproxEq( + await voltSystemOracle348Bips.getCurrentOraclePrice(), + await voltSystemOracle.getCurrentOraclePrice(), + 0 /// allow 0 bips of deviation + ); + + await assertApproxEq( + await voltSystemOracle348Bips.oraclePrice(), + await voltSystemOracle.getCurrentOraclePrice(), + 0 /// allow 0 bips of deviation + ); + + await assertApproxEq( + await voltSystemOracle348Bips.oraclePrice(), + await voltSystemOracle.getCurrentOraclePrice(), + 0 /// allow 0 bips of deviation + ); + + await assertApproxEq( + await voltSystemOracle348Bips.getCurrentOraclePrice(), + await voltSystemOracle348Bips.oraclePrice(), + 0 + ); + + console.log(`Successfully validated VIP-${vipNumber}`); +}; + +export { deploy, setup, teardown, validate }; diff --git a/proposals/vip_14.ts b/proposals/vip_14.ts new file mode 100644 index 000000000..26c4444ef --- /dev/null +++ b/proposals/vip_14.ts @@ -0,0 +1,86 @@ +import { ProposalDescription } from '@custom-types/types'; + +const vip_14: ProposalDescription = { + title: 'VIP-14: Morpho Maple PCV Deposit, Rate Update', + commands: [ + /// core actions + { + target: 'core', + values: '0', + method: 'grantPCVController(address)', + arguments: ['{morphoCompoundPCVRouter}'], + description: 'Grant Morpho PCV Router PCV Controller Role' + }, + { + target: 'core', + values: '0', + method: 'revokePCVController(address)', + arguments: ['{compoundPCVRouter}'], + description: 'Revoke PCV Controller Role from Compound PCV Router' + }, + /// allocator actions + { + target: 'erc20Allocator', + values: '0', + method: 'deleteDeposit(address)', + arguments: ['{daiCompoundPCVDeposit}'], + description: 'Remove Compound DAI Deposit from ERC20Allocator' + }, + { + target: 'erc20Allocator', + values: '0', + method: 'deleteDeposit(address)', + arguments: ['{usdcCompoundPCVDeposit}'], + description: 'Remove Compound USDC Deposit from ERC20Allocator' + }, + { + target: 'erc20Allocator', + values: '0', + method: 'connectDeposit(address,address)', + arguments: ['{daiPriceBoundPSM}', '{daiMorphoCompoundPCVDeposit}'], + description: 'Add Morpho DAI Deposit to ERC20Allocator' + }, + { + target: 'erc20Allocator', + values: '0', + method: 'connectDeposit(address,address)', + arguments: ['{usdcPriceBoundPSM}', '{usdcMorphoCompoundPCVDeposit}'], + description: 'Add Morpho USDC Deposit to ERC20Allocator' + }, + /// pcv guardian action + { + target: 'pcvGuardian', + values: '0', + method: 'addWhitelistAddresses(address[])', + arguments: [['{daiMorphoCompoundPCVDeposit}', '{usdcMorphoCompoundPCVDeposit}']], + description: 'Add USDC and DAI Morpho deposits to the PCV Guardian' + }, + /// oracle pass through setting Volt System Oracle + { + target: 'oraclePassThrough', + values: '0', + method: 'updateScalingPriceOracle(address)', + arguments: ['{voltSystemOracle348Bips}'], + description: 'Point Oracle Pass Through to new oracle address' + } + ], + description: ` + Deployment Steps + 1. deploy morpho dai deposit + 2. deploy morpho usdc deposit + 3. deploy compound pcv router pointed to morpho dai and usdc deposits + 4. deploy volt system oracle + + Governance Steps: + 1. Grant Morpho PCV Router PCV Controller Role + 2. Revoke PCV Controller Role from Compound PCV Router + 3. Remove Compound DAI Deposit from ERC20Allocator + 4. Remove Compound USDC Deposit from ERC20Allocator + 5. Add Morpho DAI Deposit to ERC20Allocator + 6. Add Morpho USDC Deposit to ERC20Allocator + 7. Add USDC and DAI Morpho deposits to the PCV Guardian + 8. Point Oracle Pass Through to new oracle address + ` +}; + +export default vip_14; diff --git a/protocol-configuration/mainnetAddresses.ts b/protocol-configuration/mainnetAddresses.ts index 9c9680bbe..06c85d0bc 100644 --- a/protocol-configuration/mainnetAddresses.ts +++ b/protocol-configuration/mainnetAddresses.ts @@ -118,6 +118,18 @@ const MainnetAddresses: MainnetAddresses = { category: AddressCategory.PCV, network: Network.Mainnet }, + morphoCompound: { + address: '0x8888882f8f843896699869179fB6E4f7e3B58888', + artifactName: 'unknown', + category: AddressCategory.External, + network: Network.Mainnet + }, + morphoCompoundLens: { + address: '0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67', + artifactName: 'unknown', + category: AddressCategory.External, + network: Network.Mainnet + }, compoundPCVRouter: { address: '0x6338Ec144279b1f05AF8C90216d90C5b54Fa4D8F', artifactName: 'CompoundPCVRouter', diff --git a/test/helpers.ts b/test/helpers.ts index 7aa0c9588..9da537f5d 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -161,6 +161,21 @@ async function expectApprox( } } +async function assertApproxEq( + actual: string | number | BigNumberish, + expected: string | number | BigNumberish, + allowableDeviation: string | number | BigNumberish +): Promise { + const actualBN = toBN(actual); + const expectedBN = toBN(expected); + const allowableDeviationBN = toBN(allowableDeviation); + + const diff = actualBN.sub(expectedBN); + const diffBasisPoints = diff.mul('10000').div(actual); + + expect(diffBasisPoints.lte(allowableDeviationBN)).to.be.true; +} + // expectApproxAbs(a, b, c) checks if b is between [a-c, a+c] async function expectApproxAbs( actual: string | number | BigNumberish, @@ -267,6 +282,7 @@ export { increaseTime, latestTime, expectApprox, + assertApproxEq, expectApproxAbs, deployDevelopmentWeth, getImpersonatedSigner, From c0ddda91acfe2a645fcd273955e328bb1b2c7706 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 3 Oct 2022 20:03:42 -0700 Subject: [PATCH 11/84] Morpho router swap integration tests --- .../test/integration/IntegrationTestVIP14.sol | 57 ++++++++++++++++--- contracts/test/integration/vip/vip14.sol | 14 ++--- 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/contracts/test/integration/IntegrationTestVIP14.sol b/contracts/test/integration/IntegrationTestVIP14.sol index dee28d1b9..badfd5afa 100644 --- a/contracts/test/integration/IntegrationTestVIP14.sol +++ b/contracts/test/integration/IntegrationTestVIP14.sol @@ -14,17 +14,15 @@ import {IPCVGuardian} from "../../pcv/IPCVGuardian.sol"; contract IntegrationTestVIP14 is TimelockSimulation, vip14 { using SafeCast for *; - PriceBoundPSM private psm = PriceBoundPSM(MainnetAddresses.VOLT_DAI_PSM); - - ICore private core = ICore(MainnetAddresses.CORE); - IERC20 dai = IERC20(MainnetAddresses.DAI); - IVolt volt = IVolt(MainnetAddresses.VOLT); - - uint256 public constant mintAmount = type(uint80).max; IPCVGuardian private immutable mainnetPCVGuardian = IPCVGuardian(MainnetAddresses.PCV_GUARDIAN); + /// @notice scaling factor for USDC + uint256 public constant USDC_SCALING_FACTOR = 1e12; + + address private governor = MainnetAddresses.GOVERNOR; + function setUp() public { mainnetSetup(); simulate( @@ -51,7 +49,48 @@ contract IntegrationTestVIP14 is TimelockSimulation, vip14 { function testClaimCompRewardsUsdc() public {} - function testSwapUsdcToDaiRouter() public {} + function testSwapUsdcToDaiRouter() public { + uint256 withdrawAmount = usdcDeposit.balance(); + uint256 daiStartingBalance = daiDeposit.balance(); + + vm.prank(governor); + router.swapUsdcForDai(withdrawAmount); + + assertApproxEq( + daiDeposit.balance().toInt256(), + (withdrawAmount * USDC_SCALING_FACTOR + daiStartingBalance) + .toInt256(), + 0 + ); + + assertTrue(usdcDeposit.balance() < 1e3); /// assert only dust remains + } - function testSwapDaiToUsdcRouter() public {} + function testSwapDaiToUsdcRouter() public { + uint256 withdrawAmount = daiDeposit.balance(); + uint256 usdcStartingBalance = usdcDeposit.balance(); + + vm.prank(governor); + router.swapDaiForUsdc(withdrawAmount); /// withdraw all balance + + assertApproxEq( + usdcDeposit.balance().toInt256(), + (withdrawAmount / USDC_SCALING_FACTOR + usdcStartingBalance) + .toInt256(), + 0 + ); + assertTrue(daiDeposit.balance() < 1e10); /// assert only dust remains + } + + function testSwapUsdcToDaiFailsUnauthorized() public { + uint256 withdrawAmount = usdcDeposit.balance(); + vm.expectRevert("UNAUTHORIZED"); + router.swapUsdcForDai(withdrawAmount); + } + + function testSwapDaiToUsdcFailsUnauthorized() public { + uint256 withdrawAmount = daiDeposit.balance(); + vm.expectRevert("UNAUTHORIZED"); + router.swapDaiForUsdc(withdrawAmount); + } } diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index c6bb75c47..40f9e88fc 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -47,10 +47,10 @@ contract vip14 is DSTest, IVIP { using SafeERC20 for IERC20; Vm public constant vm = Vm(HEVM_ADDRESS); - address private dai = MainnetAddresses.DAI; - address private fei = MainnetAddresses.FEI; - address private usdc = MainnetAddresses.USDC; - address private core = MainnetAddresses.CORE; + address public immutable dai = MainnetAddresses.DAI; + address public immutable fei = MainnetAddresses.FEI; + address public immutable usdc = MainnetAddresses.USDC; + address public immutable core = MainnetAddresses.CORE; ITimelockSimulation.action[] private mainnetProposal; @@ -62,15 +62,15 @@ contract vip14 is DSTest, IVIP { uint256 public immutable startTime; - uint256 private constant monthlyChangeRateBasisPoints = 29; + uint256 public constant monthlyChangeRateBasisPoints = 29; - PCVGuardian private immutable pcvGuardian = + PCVGuardian public immutable pcvGuardian = PCVGuardian(MainnetAddresses.PCV_GUARDIAN); VoltSystemOracle private immutable oldOracle = VoltSystemOracle(MainnetAddresses.VOLT_SYSTEM_ORACLE_144_BIPS); - ERC20Allocator private immutable allocator = + ERC20Allocator public immutable allocator = ERC20Allocator(MainnetAddresses.ERC20ALLOCATOR); uint256 targetMapleDepositAmount = 750_000e6; From 96c3b933171d57516e8ac9ef9e28dd139697fde9 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 3 Oct 2022 20:24:56 -0700 Subject: [PATCH 12/84] add arbitrum proposal to set volt price increases to 0 --- contracts/test/integration/vip/Runner.sol | 22 ++++----- contracts/test/integration/vip/vip14.sol | 57 ++++++++++++++++++----- 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/contracts/test/integration/vip/Runner.sol b/contracts/test/integration/vip/Runner.sol index 2d9c1e053..04da3361e 100644 --- a/contracts/test/integration/vip/Runner.sol +++ b/contracts/test/integration/vip/Runner.sol @@ -36,16 +36,16 @@ contract Runner is TimelockSimulation, vip14 { } function testProposalArbitrum() public { - // arbitrumSetup(); - // simulate( - // getArbitrumProposal(), - // TimelockController(payable(ArbitrumAddresses.TIMELOCK_CONTROLLER)), - // arbitrumPCVGuardian, - // ArbitrumAddresses.GOVERNOR, - // ArbitrumAddresses.EOA_1, - // vm, - // true - // ); - // arbitrumValidate(); + arbitrumSetup(); + simulate( + getArbitrumProposal(), + TimelockController(payable(ArbitrumAddresses.TIMELOCK_CONTROLLER)), + arbitrumPCVGuardian, + ArbitrumAddresses.GOVERNOR, + ArbitrumAddresses.EOA_1, + vm, + true + ); + arbitrumValidate(); } } diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index 40f9e88fc..59644e91a 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -16,6 +16,7 @@ import {IPCVDeposit} from "../../../pcv/IPCVDeposit.sol"; import {ERC20Allocator} from "../../../pcv/utils/ERC20Allocator.sol"; import {MaplePCVDeposit} from "../../../pcv/maple/MaplePCVDeposit.sol"; import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; +import {ArbitrumAddresses} from "../fixtures/ArbitrumAddresses.sol"; import {VoltSystemOracle} from "../../../oracle/VoltSystemOracle.sol"; import {OraclePassThrough} from "../../../oracle/OraclePassThrough.sol"; import {CompoundPCVRouter} from "../../../pcv/compound/CompoundPCVRouter.sol"; @@ -53,16 +54,18 @@ contract vip14 is DSTest, IVIP { address public immutable core = MainnetAddresses.CORE; ITimelockSimulation.action[] private mainnetProposal; + ITimelockSimulation.action[] private arbitrumProposal; - CompoundPCVRouter public immutable router; - MorphoCompoundPCVDeposit public immutable daiDeposit; - MorphoCompoundPCVDeposit public immutable usdcDeposit; - VoltSystemOracle public immutable oracle; - MaplePCVDeposit public immutable mapleDeposit; + CompoundPCVRouter public router; + MorphoCompoundPCVDeposit public daiDeposit; + MorphoCompoundPCVDeposit public usdcDeposit; + VoltSystemOracle public oracle; + MaplePCVDeposit public mapleDeposit; uint256 public immutable startTime; uint256 public constant monthlyChangeRateBasisPoints = 29; + uint256 public constant arbitrumMonthlyChangeRateBasisPoints = 0; PCVGuardian public immutable pcvGuardian = PCVGuardian(MainnetAddresses.PCV_GUARDIAN); @@ -84,6 +87,32 @@ contract vip14 is DSTest, IVIP { 0xFeBd6F15Df3B73DC4307B1d7E65D46413e710C27; constructor() { + startTime = block.timestamp; + + if (block.chainid != 1) { + oracle = new VoltSystemOracle( + arbitrumMonthlyChangeRateBasisPoints, + block.timestamp, + VoltSystemOracle(ArbitrumAddresses.VOLT_SYSTEM_ORACLE_144_BIPS) + .getCurrentOraclePrice() + ); + + /// construct separate proposal on arbitrum + arbitrumProposal.push( + ITimelockSimulation.action({ + value: 0, + target: ArbitrumAddresses.ORACLE_PASS_THROUGH, + arguments: abi.encodeWithSignature( + "updateScalingPriceOracle(address)", + address(oracle) + ), + description: "Point Oracle Pass Through to new oracle address" + }) + ); + + return; + } + mapleDeposit = new MaplePCVDeposit(core, maplePool, mplRewards); daiDeposit = new MorphoCompoundPCVDeposit(core, MainnetAddresses.CDAI); usdcDeposit = new MorphoCompoundPCVDeposit( @@ -103,8 +132,6 @@ contract vip14 is DSTest, IVIP { oldOracle.getCurrentOraclePrice() ); - startTime = block.timestamp; - address[] memory toWhitelist = new address[](2); toWhitelist[0] = address(daiDeposit); toWhitelist[1] = address(usdcDeposit); @@ -339,15 +366,21 @@ contract vip14 is DSTest, IVIP { override returns (ITimelockSimulation.action[] memory) { - revert("No Arbitrum proposal"); + return arbitrumProposal; } - function arbitrumSetup() public override { - revert("No Arbitrum proposal"); - } + /// no-op, nothing to setup + function arbitrumSetup() public override {} /// assert oracle pass through is pointing to correct volt system oracle function arbitrumValidate() public override { - revert("No Arbitrum proposal"); + /// oracle pass through points to new scaling price oracle + assertEq( + address( + OraclePassThrough(ArbitrumAddresses.ORACLE_PASS_THROUGH) + .scalingPriceOracle() + ), + address(oracle) + ); } } From 09a11e52a54f9a28675036ab0ee57dff3abedadd Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 3 Oct 2022 20:31:08 -0700 Subject: [PATCH 13/84] Disable mint tests on arbitrum, pause minting on arbitrum --- .../integration/utils/MintRedeemVerification.sol | 6 +++++- contracts/test/integration/vip/vip14.sol | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/contracts/test/integration/utils/MintRedeemVerification.sol b/contracts/test/integration/utils/MintRedeemVerification.sol index fded410f7..828124379 100644 --- a/contracts/test/integration/utils/MintRedeemVerification.sol +++ b/contracts/test/integration/utils/MintRedeemVerification.sol @@ -14,7 +14,6 @@ import "hardhat/console.sol"; /// @notice contract to verify that all PSM's are able to mint and redeem /// after a proposal contract MintRedeemVerification { - /// TODO add support for arbitrum /// @notice all PSM's on mainnet address[] private allMainnetPSMs = [ @@ -193,6 +192,11 @@ contract MintRedeemVerification { ? IVolt(MainnetAddresses.VOLT) : IVolt(ArbitrumAddresses.VOLT); + /// skip Arbitrum minting because minting is paused on Arbitrum due to deprecation + if (block.chainid != 1) { + revert("successfully minted on all PSMs"); + } + for (uint256 i = 0; i < allPSMs.length; i++) { /// pull all tokens from psm into this address and use them to purchase VOLT uint256 amountIn = allTokens[i].balanceOf(allPSMs[i]); diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index 59644e91a..e956bbe83 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -109,6 +109,22 @@ contract vip14 is DSTest, IVIP { description: "Point Oracle Pass Through to new oracle address" }) ); + arbitrumProposal.push( + ITimelockSimulation.action({ + value: 0, + target: ArbitrumAddresses.VOLT_USDC_PSM, + arguments: abi.encodeWithSignature("pauseMint()"), + description: "Pause minting on USDC PSM on Arbitrum" + }) + ); + arbitrumProposal.push( + ITimelockSimulation.action({ + value: 0, + target: ArbitrumAddresses.VOLT_DAI_PSM, + arguments: abi.encodeWithSignature("pauseMint()"), + description: "Pause minting on DAI PSM on Arbitrum" + }) + ); return; } From 1b11bdae95ced70a293f222906cbf9bb8278eee6 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 3 Oct 2022 20:37:50 -0700 Subject: [PATCH 14/84] Pause minting on Arbitrum PSM's --- contracts/test/integration/vip/vip14.sol | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index e956bbe83..b165d6ae7 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -13,11 +13,12 @@ import {DSTest} from "./../../unit/utils/DSTest.sol"; import {PCVDeposit} from "../../../pcv/PCVDeposit.sol"; import {PCVGuardian} from "../../../pcv/PCVGuardian.sol"; import {IPCVDeposit} from "../../../pcv/IPCVDeposit.sol"; +import {PriceBoundPSM} from "../../../peg/PriceBoundPSM.sol"; import {ERC20Allocator} from "../../../pcv/utils/ERC20Allocator.sol"; import {MaplePCVDeposit} from "../../../pcv/maple/MaplePCVDeposit.sol"; +import {VoltSystemOracle} from "../../../oracle/VoltSystemOracle.sol"; import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; import {ArbitrumAddresses} from "../fixtures/ArbitrumAddresses.sol"; -import {VoltSystemOracle} from "../../../oracle/VoltSystemOracle.sol"; import {OraclePassThrough} from "../../../oracle/OraclePassThrough.sol"; import {CompoundPCVRouter} from "../../../pcv/compound/CompoundPCVRouter.sol"; import {ITimelockSimulation} from "../utils/ITimelockSimulation.sol"; @@ -398,5 +399,18 @@ contract vip14 is DSTest, IVIP { ), address(oracle) ); + + assertTrue(PriceBoundPSM(ArbitrumAddresses.VOLT_USDC_PSM).mintPaused()); + assertTrue(PriceBoundPSM(ArbitrumAddresses.VOLT_DAI_PSM).mintPaused()); + + vm.expectRevert("PegStabilityModule: Minting paused"); + PriceBoundPSM(ArbitrumAddresses.VOLT_USDC_PSM).mint( + address(this), + 0, + 0 + ); + + vm.expectRevert("PegStabilityModule: Minting paused"); + PriceBoundPSM(ArbitrumAddresses.VOLT_DAI_PSM).mint(address(this), 0, 0); } } From 3a927a583d1900535b2fe3e10999b7a65a83c1fb Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 3 Oct 2022 20:39:04 -0700 Subject: [PATCH 15/84] remove newline in mintredeemverification --- contracts/test/integration/utils/MintRedeemVerification.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/test/integration/utils/MintRedeemVerification.sol b/contracts/test/integration/utils/MintRedeemVerification.sol index 828124379..c142a7130 100644 --- a/contracts/test/integration/utils/MintRedeemVerification.sol +++ b/contracts/test/integration/utils/MintRedeemVerification.sol @@ -14,7 +14,6 @@ import "hardhat/console.sol"; /// @notice contract to verify that all PSM's are able to mint and redeem /// after a proposal contract MintRedeemVerification { - /// @notice all PSM's on mainnet address[] private allMainnetPSMs = [ MainnetAddresses.VOLT_DAI_PSM, From 288fb42adda820997800e3cfefa9c52db72d803e Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 3 Oct 2022 20:57:57 -0700 Subject: [PATCH 16/84] VIP-14 Arbitrum deployment and governance scripts in typescript --- proposals/dao/vip_14_arbitrum.ts | 107 +++++++++++++++++++++++++++++++ proposals/vip_14_arbitrum.ts | 39 +++++++++++ 2 files changed, 146 insertions(+) create mode 100644 proposals/dao/vip_14_arbitrum.ts create mode 100644 proposals/vip_14_arbitrum.ts diff --git a/proposals/dao/vip_14_arbitrum.ts b/proposals/dao/vip_14_arbitrum.ts new file mode 100644 index 000000000..b71890967 --- /dev/null +++ b/proposals/dao/vip_14_arbitrum.ts @@ -0,0 +1,107 @@ +import { + DeployUpgradeFunc, + NamedAddresses, + SetupUpgradeFunc, + TeardownUpgradeFunc, + ValidateUpgradeFunc +} from '@custom-types/types'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; +import { assertApproxEq } from '@test/helpers'; + +/* + +Arbitrum Volt Protocol Improvement Proposal #14 + +Description: + +Deployment Steps +1. deploy volt system oracle with 0 monthlyChangeRateBasisPoints + +Governance Steps: +1. Point Oracle Pass Through to new oracle address +2. Pause DAI PSM +3. Pause USDC PSM + +*/ + +/// TODO update this to correct start time +let startTime; + +const monthlyChangeBasisPoints = 0; + +const vipNumber = '14'; + +const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => { + startTime = Math.floor(Date.now() / 1000).toString(); + + const voltSystemOracle = await ethers.getContractAt('VoltSystemOracle', addresses.arbitrumVoltSystemOracle); + + const currentPrice = await voltSystemOracle.getCurrentOraclePrice(); + + const voltSystemOracleFactory = await ethers.getContractFactory('VoltSystemOracle'); + + const voltSystemOracle0Bips = await voltSystemOracleFactory.deploy(monthlyChangeBasisPoints, startTime, currentPrice); + await voltSystemOracle0Bips.deployed(); + + console.log(`Volt System Oracle deployed ${voltSystemOracle0Bips.address}`); + + console.log(`Successfully Deployed VIP-${vipNumber}`); + return { + voltSystemOracle0Bips + }; +}; + +const setup: SetupUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; + +// Tears down any changes made in setup() that need to be +// cleaned up before doing any validation checks. +const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; + +// Run any validations required on the vip using mocha or console logging +// IE check balances, check state of contracts, etc. +const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { + const { + arbitrumUSDCPSM, + arbitrumDAIPSM, + arbitrumVoltSystemOracle, + arbitrumOraclePassThrough, + voltSystemOracle0Bips + } = contracts; + + /// oracle pass through validation + expect(await arbitrumOraclePassThrough.scalingPriceOracle()).to.be.equal(voltSystemOracle0Bips.address); + + /// PSMs fully paused + expect(await arbitrumDAIPSM.mintPaused()).to.be.true; + expect(await arbitrumUSDCPSM.mintPaused()).to.be.true; + + /// Volt System Oracle has correct price + await assertApproxEq( + await voltSystemOracle0Bips.getCurrentOraclePrice(), + await arbitrumVoltSystemOracle.getCurrentOraclePrice(), + 0 /// allow 0 bips of deviation + ); + + await assertApproxEq( + await voltSystemOracle0Bips.oraclePrice(), + await arbitrumVoltSystemOracle.getCurrentOraclePrice(), + 0 /// allow 0 bips of deviation + ); + + await assertApproxEq( + await voltSystemOracle0Bips.oraclePrice(), + await arbitrumVoltSystemOracle.getCurrentOraclePrice(), + 0 /// allow 0 bips of deviation + ); + + await assertApproxEq( + await voltSystemOracle0Bips.getCurrentOraclePrice(), + await voltSystemOracle0Bips.oraclePrice(), + 0 + ); + + console.log(`Successfully validated VIP-${vipNumber}`); +}; + +export { deploy, setup, teardown, validate }; diff --git a/proposals/vip_14_arbitrum.ts b/proposals/vip_14_arbitrum.ts new file mode 100644 index 000000000..3e18d4654 --- /dev/null +++ b/proposals/vip_14_arbitrum.ts @@ -0,0 +1,39 @@ +import { ProposalDescription } from '@custom-types/types'; + +const vip_14_arbitrum: ProposalDescription = { + title: 'VIP-14 Arbitrum: Rate Update to 0, Pause PSM Minting', + commands: [ + { + target: 'arbitrumOraclePassThrough', + values: '0', + method: 'updateScalingPriceOracle(address)', + arguments: ['{voltSystemOracle0Bips}'], + description: 'Point Arbitrum Oracle Pass Through to 0 basis point Volt System Oracle' + }, + { + target: 'arbitrumUSDCPSM', + values: '0', + method: 'pauseMint()', + arguments: [], + description: 'Pause minting on USDC PSM on Arbitrum' + }, + { + target: 'arbitrumDAIPSM', + values: '0', + method: 'pauseMint()', + arguments: [], + description: 'Pause minting on DAI PSM on Arbitrum' + } + ], + description: ` + Deployment Steps + 1. deploy volt system oracle + + Governance Steps: + 1. Point Oracle Pass Through to new oracle address + 2. Pause minting on DAI PSM on Arbitrum + 3. Pause minting on USDC PSM on Arbitrum + ` +}; + +export default vip_14_arbitrum; From 6323a8e3630749712f85733f01737fbb5a7f5c66 Mon Sep 17 00:00:00 2001 From: Elliot Date: Wed, 5 Oct 2022 00:50:36 -0700 Subject: [PATCH 17/84] remove vm.warp from morpho integration test --- .../integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol index 84c01ee82..525960c30 100644 --- a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol @@ -120,8 +120,7 @@ contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { } function testHarvest() public { - /// fast forward timestamps + block number - vm.warp(block.timestamp + epochLength); + /// fast forward block number amount vm.roll(block.number + epochLength / 12); uint256 startingCompBalance = comp.balanceOf(address(usdcDeposit)) + From f86be2000cfeac4266a877ed5333dabe3f455f90 Mon Sep 17 00:00:00 2001 From: Elliot Date: Wed, 5 Oct 2022 00:55:27 -0700 Subject: [PATCH 18/84] Morpho and Maple PCV Deposit refactor and integration tests --- contracts/pcv/maple/MaplePCVDeposit.sol | 4 +++- contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol | 11 ++++++----- .../integration/IntegrationTestMaplePCVDeposit.t.sol | 10 +++------- .../IntegrationTestMorphoCompoundPCVDeposit.t.sol | 11 ++++------- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index f6816a543..b91226da3 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -31,7 +31,8 @@ contract MaplePCVDeposit is PCVDeposit { /// @notice fetch underlying asset by calling pool and getting liquidity asset /// @param _core reference to the Core contract - /// @param _pool reference to the Maple Pool contract + /// @param _pool Maple Pool contract + /// @param _mplRewards Maple Rewards contract constructor( address _core, address _pool, @@ -126,6 +127,7 @@ contract MaplePCVDeposit is PCVDeposit { } /// @notice withdraw all PCV from Maple + /// @param to destination after funds are withdrawn from venue function withdrawAll(address to) external onlyPCVController { uint256 amount = balance(); mplRewards.exit(); /// unstakes from Maple reward contract and claims rewards diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index f35c86015..abfbab804 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -74,17 +74,18 @@ contract MorphoCompoundPCVDeposit is PCVDeposit { /// @param to the address PCV will be sent to /// @param amount of tokens withdrawn function withdraw(address to, uint256 amount) external onlyPCVController { - MORPHO.withdraw(cToken, amount); - IERC20(token).safeTransfer(to, amount); - - emit Withdrawal(msg.sender, to, amount); + _withdraw(to, amount); } /// @notice withdraw all tokens from Morpho /// @param to the address PCV will be sent to function withdrawAll(address to) external onlyPCVController { uint256 amount = balance(); - MORPHO.withdraw(cToken, amount); + _withdraw(to, amount); + } + + function _withdraw(address to, uint256 amount) private { + IMorpho(MORPHO).withdraw(cToken, amount); IERC20(token).safeTransfer(to, amount); emit Withdrawal(msg.sender, to, amount); diff --git a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol index d8be5a8c6..303e2800c 100644 --- a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol @@ -75,11 +75,7 @@ contract IntegrationTestMaplePCVDeposit is DSTest { assertEq(address(usdcDeposit.pool()), maplePool); assertEq(address(usdcDeposit.mplRewards()), mplRewards); assertEq(address(usdcDeposit.token()), address(MainnetAddresses.USDC)); - assertApproxEq( - usdcDeposit.balance().toInt256(), - targetUsdcBalance.toInt256(), - 0 - ); + assertEq(usdcDeposit.balance(), targetUsdcBalance); } function testDeployFailsNotUSDCUnderlying() public { @@ -227,12 +223,12 @@ contract IntegrationTestMaplePCVDeposit is DSTest { usdcDeposit.cancelWithdraw(); } - function testWithdrawNonGovFails() public { + function testWithdrawNonPCVControllerFails() public { vm.expectRevert("CoreRef: Caller is not a PCV controller"); usdcDeposit.withdraw(address(this), targetUsdcBalance); } - function testWithdrawAllNonGovFails() public { + function testWithdrawAllNonPCVControllerFails() public { vm.expectRevert("CoreRef: Caller is not a PCV controller"); usdcDeposit.withdrawAll(address(this)); } diff --git a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol index 525960c30..d9c98aaa5 100644 --- a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol @@ -165,11 +165,7 @@ contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { vm.prank(MainnetAddresses.GOVERNOR); usdcDeposit.withdraw(address(this), amount); - assertApproxEq( - usdc.balanceOf(address(this)).toInt256(), - amount.toInt256(), - 0 - ); + assertEq(usdc.balanceOf(address(this)), amount); assertApproxEq( usdcDeposit.balance().toInt256(), @@ -189,6 +185,7 @@ contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { targetDaiBalance.toInt256(), 0 ); + assertApproxEq( usdc.balanceOf(address(this)).toInt256(), targetUsdcBalance.toInt256(), @@ -213,7 +210,7 @@ contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { daiDeposit.deposit(); } - function testWithdrawNonGovFails() public { + function testWithdrawNonPCVControllerFails() public { vm.expectRevert("CoreRef: Caller is not a PCV controller"); usdcDeposit.withdraw(address(this), targetUsdcBalance); @@ -221,7 +218,7 @@ contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { daiDeposit.withdraw(address(this), targetDaiBalance); } - function testWithdrawAllNonGovFails() public { + function testWithdrawAllNonPCVControllerFails() public { vm.expectRevert("CoreRef: Caller is not a PCV controller"); usdcDeposit.withdrawAll(address(this)); From d72ddeab4a80fb2d2373287783deaeb07e822f0d Mon Sep 17 00:00:00 2001 From: Elliot Date: Wed, 5 Oct 2022 00:59:55 -0700 Subject: [PATCH 19/84] assertEq --- .../IntegrationTestMorphoCompoundPCVDeposit.t.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol index d9c98aaa5..1d4596926 100644 --- a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol @@ -145,11 +145,7 @@ contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { vm.prank(MainnetAddresses.GOVERNOR); daiDeposit.withdraw(address(this), amount); - assertApproxEq( - dai.balanceOf(address(this)).toInt256(), - amount.toInt256(), - 0 - ); + assertEq(dai.balanceOf(address(this)), amount); assertApproxEq( daiDeposit.balance().toInt256(), From 175b45c3308e00e38046ccaf4a5b832fcc6f1507 Mon Sep 17 00:00:00 2001 From: Elliot Date: Wed, 5 Oct 2022 01:43:04 -0700 Subject: [PATCH 20/84] add deposit call on maple deposit to vip 14 --- contracts/test/integration/vip/vip14.sol | 43 ++++++++++++++++++++-- proposals/dao/vip_14.ts | 20 +++++++++- proposals/vip_14.ts | 25 ++++++++++++- protocol-configuration/mainnetAddresses.ts | 12 ++++++ 4 files changed, 93 insertions(+), 7 deletions(-) diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index b165d6ae7..921e7fc2a 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -29,6 +29,7 @@ import {MorphoCompoundPCVDeposit} from "../../../pcv/morpho/MorphoCompoundPCVDep /// 2. deploy morpho usdc deposit /// 3. deploy compound pcv router pointed to morpho dai and usdc deposits /// 4. deploy volt system oracle +/// 5. deploy maple usdc deposit /// Governance Steps /// 1. grant new PCV router PCV Controller role @@ -149,9 +150,10 @@ contract vip14 is DSTest, IVIP { oldOracle.getCurrentOraclePrice() ); - address[] memory toWhitelist = new address[](2); + address[] memory toWhitelist = new address[](3); toWhitelist[0] = address(daiDeposit); toWhitelist[1] = address(usdcDeposit); + toWhitelist[2] = address(mapleDeposit); mainnetProposal.push( ITimelockSimulation.action({ @@ -234,7 +236,7 @@ contract vip14 is DSTest, IVIP { "addWhitelistAddresses(address[])", toWhitelist ), - description: "Add USDC and DAI Morpho deposits to the PCV Guardian" + description: "Add USDC and DAI Morpho deposits and Maple deposit to the PCV Guardian" }) ); @@ -249,6 +251,39 @@ contract vip14 is DSTest, IVIP { description: "Point Oracle Pass Through to new oracle address" }) ); + + mainnetProposal.push( + ITimelockSimulation.action({ + value: 0, + target: MainnetAddresses.CORE, + arguments: abi.encodeWithSignature( + "grantPCVController(address)", + MainnetAddresses.TIMELOCK_CONTROLLER + ), + description: "Grant PCV Controller Role to timelock controller" + }) + ); + + mainnetProposal.push( + ITimelockSimulation.action({ + value: 0, + target: address(mapleDeposit), + arguments: abi.encodeWithSignature("deposit()"), + description: "Deposit PCV into Maple" + }) + ); + + mainnetProposal.push( + ITimelockSimulation.action({ + value: 0, + target: MainnetAddresses.CORE, + arguments: abi.encodeWithSignature( + "revokePCVController(address)", + MainnetAddresses.TIMELOCK_CONTROLLER + ), + description: "Revoke PCV Controller Role from timelock controller" + }) + ); } function getMainnetProposal() @@ -278,7 +313,7 @@ contract vip14 is DSTest, IVIP { address(usdcDeposit), usdcBalance - targetMapleDepositAmount ); - IERC20(usdc).transfer(address(usdcDeposit), targetMapleDepositAmount); + IERC20(usdc).transfer(address(mapleDeposit), targetMapleDepositAmount); IERC20(dai).transfer( address(daiDeposit), IERC20(dai).balanceOf(MainnetAddresses.GOVERNOR) @@ -297,6 +332,7 @@ contract vip14 is DSTest, IVIP { /// assert old pcv deposits are disconnected in allocator /// assert oracle pass through is pointed to the proper Volt System Oracle function mainnetValidate() public override { + assertEq(address(mapleDeposit.core()), core); assertEq(address(usdcDeposit.core()), core); assertEq(address(daiDeposit.core()), core); assertEq(address(router.core()), core); @@ -308,6 +344,7 @@ contract vip14 is DSTest, IVIP { assertTrue(pcvGuardian.isWhitelistAddress(address(daiDeposit))); assertTrue(pcvGuardian.isWhitelistAddress(address(usdcDeposit))); + assertTrue(pcvGuardian.isWhitelistAddress(address(mapleDeposit))); /// router parameter validations assertEq(address(router.daiPcvDeposit()), address(daiDeposit)); diff --git a/proposals/dao/vip_14.ts b/proposals/dao/vip_14.ts index 8d43d8bb4..146087967 100644 --- a/proposals/dao/vip_14.ts +++ b/proposals/dao/vip_14.ts @@ -20,6 +20,7 @@ Description: /// 2. deploy morpho usdc deposit /// 3. deploy compound pcv router pointed to morpho dai and usdc deposits /// 4. deploy volt system oracle +/// 5. deploy maple usdc deposit Governance Steps: 1. Grant Morpho PCV Router PCV Controller Role @@ -47,6 +48,7 @@ const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: Named const currentPrice = await voltSystemOracle.getCurrentOraclePrice(); + const maplePCVDepositFactory = await ethers.getContractFactory('MaplePCVDeposit'); const morphoPCVDepositFactory = await ethers.getContractFactory('MorphoCompoundPCVDeposit'); const compoundPCVRouterFactory = await ethers.getContractFactory('CompoundPCVRouter'); const voltSystemOracleFactory = await ethers.getContractFactory('VoltSystemOracle'); @@ -64,6 +66,9 @@ const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: Named ); await morphoCompoundPCVRouter.deployed(); + const maplePCVDeposit = await maplePCVDepositFactory.deploy(addresses.core, addresses.mplPool, addresses.mplRewards); + await maplePCVDeposit.deployed(); + const voltSystemOracle348Bips = await voltSystemOracleFactory.deploy( monthlyChangeBasisPoints, startTime, @@ -71,13 +76,16 @@ const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: Named ); await voltSystemOracle348Bips.deployed(); + console.log(`Maple PCV Deposit deployed ${maplePCVDeposit.address}`); console.log(`Volt System Oracle deployed ${voltSystemOracle348Bips.address}`); console.log(`Morpho Compound PCV Router deployed ${morphoCompoundPCVRouter.address}`); console.log(`Morpho Compound DAI PCV Deposit deployed ${daiMorphoCompoundPCVDeposit.address}`); console.log(`Morpho Compound USDC PCV Deposit deployed ${usdcMorphoCompoundPCVDeposit.address}`); console.log(`Successfully Deployed VIP-${vipNumber}`); + return { + maplePCVDeposit, voltSystemOracle348Bips, daiMorphoCompoundPCVDeposit, usdcMorphoCompoundPCVDeposit, @@ -104,13 +112,15 @@ const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, morphoCompoundPCVRouter, compoundPCVRouter, pcvGuardian, - erc20Allocator + erc20Allocator, + maplePCVDeposit } = contracts; /// Core address validations - expect(await daiMorphoCompoundPCVDeposit.core()).to.be.equal(core.address); expect(await usdcMorphoCompoundPCVDeposit.core()).to.be.equal(core.address); + expect(await daiMorphoCompoundPCVDeposit.core()).to.be.equal(core.address); expect(await morphoCompoundPCVRouter.core()).to.be.equal(core.address); + expect(await maplePCVDeposit.core()).to.be.equal(core.address); /// oracle pass through validation expect(await oraclePassThrough.scalingPriceOracle()).to.be.equal(voltSystemOracle348Bips.address); @@ -140,9 +150,15 @@ const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, expect(await daiMorphoCompoundPCVDeposit.LENS()).to.be.equal(addresses.morphoCompoundLens); expect(await usdcMorphoCompoundPCVDeposit.LENS()).to.be.equal(addresses.morphoCompoundLens); + expect(await maplePCVDeposit.balanceReportedIn()).to.be.equal(addresses.usdc); + expect(await maplePCVDeposit.mplRewards()).to.be.equal(addresses.mplRewards); + expect(await maplePCVDeposit.token()).to.be.equal(addresses.usdc); + expect(await maplePCVDeposit.pool()).to.be.equal(addresses.mplPool); + /// pcv guardian validation expect(await pcvGuardian.isWhitelistAddress(usdcMorphoCompoundPCVDeposit.address)).to.be.true; expect(await pcvGuardian.isWhitelistAddress(daiMorphoCompoundPCVDeposit.address)).to.be.true; + expect(await pcvGuardian.isWhitelistAddress(maplePCVDeposit.address)).to.be.true; expect(await pcvGuardian.isWhitelistAddress(addresses.daiCompoundPCVDeposit)).to.be.true; expect(await pcvGuardian.isWhitelistAddress(addresses.usdcCompoundPCVDeposit)).to.be.true; diff --git a/proposals/vip_14.ts b/proposals/vip_14.ts index 26c4444ef..1e9703895 100644 --- a/proposals/vip_14.ts +++ b/proposals/vip_14.ts @@ -52,8 +52,8 @@ const vip_14: ProposalDescription = { target: 'pcvGuardian', values: '0', method: 'addWhitelistAddresses(address[])', - arguments: [['{daiMorphoCompoundPCVDeposit}', '{usdcMorphoCompoundPCVDeposit}']], - description: 'Add USDC and DAI Morpho deposits to the PCV Guardian' + arguments: [['{daiMorphoCompoundPCVDeposit}', '{usdcMorphoCompoundPCVDeposit}', '{maplePCVDeposit}']], + description: 'Add USDC and DAI Morpho deposits and Maple deposit to the PCV Guardian' }, /// oracle pass through setting Volt System Oracle { @@ -62,6 +62,27 @@ const vip_14: ProposalDescription = { method: 'updateScalingPriceOracle(address)', arguments: ['{voltSystemOracle348Bips}'], description: 'Point Oracle Pass Through to new oracle address' + }, + { + target: 'core', + values: '0', + method: 'grantPCVController(address)', + arguments: ['{timelockController}'], + description: 'Grant PCV Controller Role to timelock controller' + }, + { + target: 'maplePCVDeposit', + values: '0', + method: 'deposit()', + arguments: [], + description: 'Deposit PCV into Maple' + }, + { + target: 'core', + values: '0', + method: 'revokePCVController(address)', + arguments: ['{timelockController}'], + description: 'Revoke PCV Controller Role from timelock controller' } ], description: ` diff --git a/protocol-configuration/mainnetAddresses.ts b/protocol-configuration/mainnetAddresses.ts index 06c85d0bc..3c4bf90c8 100644 --- a/protocol-configuration/mainnetAddresses.ts +++ b/protocol-configuration/mainnetAddresses.ts @@ -70,6 +70,18 @@ const MainnetAddresses: MainnetAddresses = { category: AddressCategory.External, network: Network.Arbitrum }, + mplPool: { + address: '0xFeBd6F15Df3B73DC4307B1d7E65D46413e710C27', + artifactName: 'unknown', + category: AddressCategory.External, + network: Network.Mainnet + }, + mplRewards: { + address: '0x7869D7a3B074b5fa484dc04798E254c9C06A5e90', + artifactName: 'unknown', + category: AddressCategory.External, + network: Network.Mainnet + }, globalRateLimitedMinter: { address: '0x87945f59E008aDc9ed6210a8e061f009d6ace718', artifactName: 'IGlobalRateLimitedMinter', From 02453fabbebd87ea6b89bfdb976f978596cd2db0 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 6 Oct 2022 10:03:51 -0700 Subject: [PATCH 21/84] remove arbitrum typescript deployment, arbitrary execution as pcv controller --- contracts/pcv/maple/IPool.sol | 5 + contracts/pcv/maple/MaplePCVDeposit.sol | 55 ++++++++- .../IntegrationTestMaplePCVDeposit.t.sol | 70 ++++++++++++ proposals/dao/vip_14_arbitrum.ts | 107 ------------------ proposals/vip_14_arbitrum.ts | 39 ------- 5 files changed, 127 insertions(+), 149 deletions(-) delete mode 100644 proposals/dao/vip_14_arbitrum.ts delete mode 100644 proposals/vip_14_arbitrum.ts diff --git a/contracts/pcv/maple/IPool.sol b/contracts/pcv/maple/IPool.sol index 4dde5b7ed..b2ed0fe6c 100644 --- a/contracts/pcv/maple/IPool.sol +++ b/contracts/pcv/maple/IPool.sol @@ -17,6 +17,11 @@ interface IPool { view returns (uint256); + function custodyAllowance(address _owner, address _custodian) + external + view + returns (uint256); + /** @dev Returns the total amount of funds a given address is able to withdraw currently. @param owner Address of FDT holder. diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index b91226da3..865780729 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -2,11 +2,11 @@ pragma solidity =0.8.13; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {IPool} from "./IPool.sol"; -import {IMplRewards} from "./IMplRewards.sol"; import {CoreRef} from "../../refs/CoreRef.sol"; import {PCVDeposit} from "../PCVDeposit.sol"; +import {IMplRewards} from "./IMplRewards.sol"; /// @notice PCV Deposit for Maple /// Allows depositing only by privileged role to prevent lockup period being extended by griefers @@ -121,6 +121,10 @@ contract MaplePCVDeposit is PCVDeposit { /// withdraw from the pool pool.withdraw(amount); + + /// withdraw min between balance and amount as losses could be sustained in venue + /// causing less than amt to be withdrawn + amount = Math.min(token.balanceOf(address(this)), amount); token.safeTransfer(to, amount); emit Withdrawal(msg.sender, to, amount); @@ -144,10 +148,55 @@ contract MaplePCVDeposit is PCVDeposit { emit Withdrawal(msg.sender, to, tokenBalance); } - /// permissionless function to harvest rewards before withdraw + /// @notice get rewards and unstake from rewards contract + function exit() external onlyPCVController { + mplRewards.exit(); + } + + /// @notice unstake from rewards contract + function withdrawFromRewardsContract() external onlyPCVController { + uint256 rewardsBalance = mplRewards.balanceOf(address(this)); + mplRewards.withdraw(rewardsBalance); + } + + /// @notice get rewards and unstake from rewards contract + function withdrawWithoutExiting(address to, uint256 amount) + external + onlyPCVController + { + pool.withdraw(amount); + /// withdraw min between balance and amount as losses could be sustained in venue + /// causing less than amt to be withdrawn + amount = Math.min(token.balanceOf(address(this)), amount); + token.safeTransfer(to, amount); + } + + /// @notice permissionless function to harvest rewards before withdraw function harvest() external { mplRewards.getReward(); emit Harvest(); } + + struct Call { + address target; + bytes callData; + } + + /// @notice due to Maple's complexity, add this ability to be able to execute + /// arbitrary calldata against arbitrary addresses. + function emergencyAction(Call[] memory calls) + external + onlyPCVController + returns (bytes[] memory returnData) + { + returnData = new bytes[](calls.length); + for (uint256 i = 0; i < calls.length; i++) { + (bool success, bytes memory returned) = calls[i].target.call( + calls[i].callData + ); + require(success); + returnData[i] = returned; + } + } } diff --git a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol index 303e2800c..c97c75966 100644 --- a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol @@ -19,6 +19,8 @@ import {MainnetAddresses} from "./fixtures/MainnetAddresses.sol"; import {PegStabilityModule} from "../../peg/PegStabilityModule.sol"; import {ERC20CompoundPCVDeposit} from "../../pcv/compound/ERC20CompoundPCVDeposit.sol"; +import "hardhat/console.sol"; + contract IntegrationTestMaplePCVDeposit is DSTest { using SafeCast for *; @@ -208,6 +210,52 @@ contract IntegrationTestMaplePCVDeposit is DSTest { assertEq(IPool(maplePool).withdrawCooldown(address(usdcDeposit)), 0); } + function testExitPCVControllerSucceeds() public { + uint256 blockTimestamp = 10_000; + + vm.warp(blockTimestamp); + + console.log( + "mpl rewards balance: ", + IMplRewards(mplRewards).balanceOf(address(usdcDeposit)) + ); + + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.exit(); + + assertEq( + IPool(maplePool).custodyAllowance( + address(usdcDeposit), + address(mplRewards) + ), + 0 + ); + assertEq(IMplRewards(mplRewards).balanceOf(address(usdcDeposit)), 0); + } + + function testWithdrawFromRewardsContractPCVControllerSucceeds() public { + uint256 blockTimestamp = 10_000; + + vm.warp(blockTimestamp); + + console.log( + "mpl rewards balance: ", + IMplRewards(mplRewards).balanceOf(address(usdcDeposit)) + ); + + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.withdrawFromRewardsContract(); + + assertEq( + IPool(maplePool).custodyAllowance( + address(usdcDeposit), + address(mplRewards) + ), + 0 + ); + assertEq(IMplRewards(mplRewards).balanceOf(address(usdcDeposit)), 0); + } + function testDepositNotPCVControllerFails() public { vm.expectRevert("CoreRef: Caller is not a PCV controller"); usdcDeposit.deposit(); @@ -228,8 +276,30 @@ contract IntegrationTestMaplePCVDeposit is DSTest { usdcDeposit.withdraw(address(this), targetUsdcBalance); } + function testWithdrawWithoutExitingNonPCVControllerFails() public { + vm.expectRevert("CoreRef: Caller is not a PCV controller"); + usdcDeposit.withdrawWithoutExiting(address(this), targetUsdcBalance); + } + + function testWithdrawFromRewardsContractgNonPCVControllerFails() public { + vm.expectRevert("CoreRef: Caller is not a PCV controller"); + usdcDeposit.withdrawFromRewardsContract(); + } + function testWithdrawAllNonPCVControllerFails() public { vm.expectRevert("CoreRef: Caller is not a PCV controller"); usdcDeposit.withdrawAll(address(this)); } + + function testExitPCVControllerFails() public { + vm.expectRevert("CoreRef: Caller is not a PCV controller"); + usdcDeposit.exit(); + } + + function testEmergencyActionNonPCVControllerFails() public { + MaplePCVDeposit.Call[] memory calls = new MaplePCVDeposit.Call[](2); + + vm.expectRevert("CoreRef: Caller is not a PCV controller"); + usdcDeposit.emergencyAction(calls); + } } diff --git a/proposals/dao/vip_14_arbitrum.ts b/proposals/dao/vip_14_arbitrum.ts deleted file mode 100644 index b71890967..000000000 --- a/proposals/dao/vip_14_arbitrum.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { - DeployUpgradeFunc, - NamedAddresses, - SetupUpgradeFunc, - TeardownUpgradeFunc, - ValidateUpgradeFunc -} from '@custom-types/types'; -import { expect } from 'chai'; -import { ethers } from 'hardhat'; -import { assertApproxEq } from '@test/helpers'; - -/* - -Arbitrum Volt Protocol Improvement Proposal #14 - -Description: - -Deployment Steps -1. deploy volt system oracle with 0 monthlyChangeRateBasisPoints - -Governance Steps: -1. Point Oracle Pass Through to new oracle address -2. Pause DAI PSM -3. Pause USDC PSM - -*/ - -/// TODO update this to correct start time -let startTime; - -const monthlyChangeBasisPoints = 0; - -const vipNumber = '14'; - -const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => { - startTime = Math.floor(Date.now() / 1000).toString(); - - const voltSystemOracle = await ethers.getContractAt('VoltSystemOracle', addresses.arbitrumVoltSystemOracle); - - const currentPrice = await voltSystemOracle.getCurrentOraclePrice(); - - const voltSystemOracleFactory = await ethers.getContractFactory('VoltSystemOracle'); - - const voltSystemOracle0Bips = await voltSystemOracleFactory.deploy(monthlyChangeBasisPoints, startTime, currentPrice); - await voltSystemOracle0Bips.deployed(); - - console.log(`Volt System Oracle deployed ${voltSystemOracle0Bips.address}`); - - console.log(`Successfully Deployed VIP-${vipNumber}`); - return { - voltSystemOracle0Bips - }; -}; - -const setup: SetupUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; - -// Tears down any changes made in setup() that need to be -// cleaned up before doing any validation checks. -const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {}; - -// Run any validations required on the vip using mocha or console logging -// IE check balances, check state of contracts, etc. -const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { - const { - arbitrumUSDCPSM, - arbitrumDAIPSM, - arbitrumVoltSystemOracle, - arbitrumOraclePassThrough, - voltSystemOracle0Bips - } = contracts; - - /// oracle pass through validation - expect(await arbitrumOraclePassThrough.scalingPriceOracle()).to.be.equal(voltSystemOracle0Bips.address); - - /// PSMs fully paused - expect(await arbitrumDAIPSM.mintPaused()).to.be.true; - expect(await arbitrumUSDCPSM.mintPaused()).to.be.true; - - /// Volt System Oracle has correct price - await assertApproxEq( - await voltSystemOracle0Bips.getCurrentOraclePrice(), - await arbitrumVoltSystemOracle.getCurrentOraclePrice(), - 0 /// allow 0 bips of deviation - ); - - await assertApproxEq( - await voltSystemOracle0Bips.oraclePrice(), - await arbitrumVoltSystemOracle.getCurrentOraclePrice(), - 0 /// allow 0 bips of deviation - ); - - await assertApproxEq( - await voltSystemOracle0Bips.oraclePrice(), - await arbitrumVoltSystemOracle.getCurrentOraclePrice(), - 0 /// allow 0 bips of deviation - ); - - await assertApproxEq( - await voltSystemOracle0Bips.getCurrentOraclePrice(), - await voltSystemOracle0Bips.oraclePrice(), - 0 - ); - - console.log(`Successfully validated VIP-${vipNumber}`); -}; - -export { deploy, setup, teardown, validate }; diff --git a/proposals/vip_14_arbitrum.ts b/proposals/vip_14_arbitrum.ts deleted file mode 100644 index 3e18d4654..000000000 --- a/proposals/vip_14_arbitrum.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ProposalDescription } from '@custom-types/types'; - -const vip_14_arbitrum: ProposalDescription = { - title: 'VIP-14 Arbitrum: Rate Update to 0, Pause PSM Minting', - commands: [ - { - target: 'arbitrumOraclePassThrough', - values: '0', - method: 'updateScalingPriceOracle(address)', - arguments: ['{voltSystemOracle0Bips}'], - description: 'Point Arbitrum Oracle Pass Through to 0 basis point Volt System Oracle' - }, - { - target: 'arbitrumUSDCPSM', - values: '0', - method: 'pauseMint()', - arguments: [], - description: 'Pause minting on USDC PSM on Arbitrum' - }, - { - target: 'arbitrumDAIPSM', - values: '0', - method: 'pauseMint()', - arguments: [], - description: 'Pause minting on DAI PSM on Arbitrum' - } - ], - description: ` - Deployment Steps - 1. deploy volt system oracle - - Governance Steps: - 1. Point Oracle Pass Through to new oracle address - 2. Pause minting on DAI PSM on Arbitrum - 3. Pause minting on USDC PSM on Arbitrum - ` -}; - -export default vip_14_arbitrum; From 64177bbb12fa5acfb9f1690d7854536833b1b026 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 6 Oct 2022 13:59:48 -0700 Subject: [PATCH 22/84] Add pcv deposit pausing --- contracts/pcv/maple/MaplePCVDeposit.sol | 14 ++- .../IntegrationTestMaplePCVDeposit.t.sol | 116 +++++++++++++----- contracts/test/integration/vip/Runner.sol | 22 ++-- contracts/test/integration/vip/vip14.sol | 99 ++++----------- proposals/vip_14.ts | 21 +++- 5 files changed, 145 insertions(+), 127 deletions(-) diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index 865780729..16ae8c522 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -153,14 +153,16 @@ contract MaplePCVDeposit is PCVDeposit { mplRewards.exit(); } - /// @notice unstake from rewards contract + /// @notice unstake from rewards contract without getting rewards function withdrawFromRewardsContract() external onlyPCVController { - uint256 rewardsBalance = mplRewards.balanceOf(address(this)); + uint256 rewardsBalance = pool.balanceOf(address(this)); mplRewards.withdraw(rewardsBalance); } - /// @notice get rewards and unstake from rewards contract - function withdrawWithoutExiting(address to, uint256 amount) + /// @notice unstake from Pool FDT contract without getting rewards + /// @param to destination after funds are withdrawn from venue + /// @param amount of PCV to withdraw from the venue + function withdrawFromPool(address to, uint256 amount) external onlyPCVController { @@ -178,6 +180,7 @@ contract MaplePCVDeposit is PCVDeposit { emit Harvest(); } + /// @notice struct to pack calldata and targets for an emergency action struct Call { address target; bytes callData; @@ -185,9 +188,10 @@ contract MaplePCVDeposit is PCVDeposit { /// @notice due to Maple's complexity, add this ability to be able to execute /// arbitrary calldata against arbitrary addresses. + /// callable only by governor function emergencyAction(Call[] memory calls) external - onlyPCVController + onlyGovernor returns (bytes[] memory returnData) { returnData = new bytes[](calls.length); diff --git a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol index c97c75966..d64707361 100644 --- a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol @@ -19,8 +19,6 @@ import {MainnetAddresses} from "./fixtures/MainnetAddresses.sol"; import {PegStabilityModule} from "../../peg/PegStabilityModule.sol"; import {ERC20CompoundPCVDeposit} from "../../pcv/compound/ERC20CompoundPCVDeposit.sol"; -import "hardhat/console.sol"; - contract IntegrationTestMaplePCVDeposit is DSTest { using SafeCast for *; @@ -123,13 +121,7 @@ contract IntegrationTestMaplePCVDeposit is DSTest { } function _testWithdraw(uint256 amount) private { - uint256 rewardRate = IMplRewards(mplRewards).rewardRate(); - vm.prank(mapleOwner); - IMplRewards(mplRewards).notifyRewardAmount(rewardRate); - - vm.warp( - block.timestamp + IPool(maplePool).lockupPeriod() - cooldownPeriod - ); + _setRewardsAndWarp(); vm.prank(MainnetAddresses.GOVERNOR); usdcDeposit.signalIntentToWithdraw(); @@ -157,13 +149,7 @@ contract IntegrationTestMaplePCVDeposit is DSTest { } function testWithdrawAll() public { - uint256 rewardRate = IMplRewards(mplRewards).rewardRate(); - vm.prank(mapleOwner); - IMplRewards(mplRewards).notifyRewardAmount(rewardRate); - - vm.warp( - block.timestamp + IPool(maplePool).lockupPeriod() - cooldownPeriod - ); + _setRewardsAndWarp(); vm.prank(MainnetAddresses.GOVERNOR); usdcDeposit.signalIntentToWithdraw(); @@ -211,14 +197,7 @@ contract IntegrationTestMaplePCVDeposit is DSTest { } function testExitPCVControllerSucceeds() public { - uint256 blockTimestamp = 10_000; - - vm.warp(blockTimestamp); - - console.log( - "mpl rewards balance: ", - IMplRewards(mplRewards).balanceOf(address(usdcDeposit)) - ); + _setRewardsAndWarp(); vm.prank(MainnetAddresses.GOVERNOR); usdcDeposit.exit(); @@ -234,14 +213,25 @@ contract IntegrationTestMaplePCVDeposit is DSTest { } function testWithdrawFromRewardsContractPCVControllerSucceeds() public { - uint256 blockTimestamp = 10_000; + _setRewardsAndWarp(); - vm.warp(blockTimestamp); + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.withdrawFromRewardsContract(); - console.log( - "mpl rewards balance: ", - IMplRewards(mplRewards).balanceOf(address(usdcDeposit)) + assertEq( + IPool(maplePool).custodyAllowance( + address(usdcDeposit), + address(mplRewards) + ), + 0 ); + assertEq(IMplRewards(mplRewards).balanceOf(address(usdcDeposit)), 0); + } + + function testWithdrawFromRewardsContractAndWithdrawFromPoolPCVControllerSucceeds() + public + { + _setRewardsAndWarp(); vm.prank(MainnetAddresses.GOVERNOR); usdcDeposit.withdrawFromRewardsContract(); @@ -254,6 +244,58 @@ contract IntegrationTestMaplePCVDeposit is DSTest { 0 ); assertEq(IMplRewards(mplRewards).balanceOf(address(usdcDeposit)), 0); + + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.signalIntentToWithdraw(); + vm.warp(block.timestamp + cooldownPeriod); + + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.withdrawFromPool(address(this), targetUsdcBalance); + } + + function testExitRewardsContractAndWithdrawFromPoolPCVControllerSucceeds() + public + { + _setRewardsAndWarp(); + + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.exit(); + + assertEq( + IPool(maplePool).custodyAllowance( + address(usdcDeposit), + address(mplRewards) + ), + 0 + ); + assertEq(IMplRewards(mplRewards).balanceOf(address(usdcDeposit)), 0); + + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.signalIntentToWithdraw(); + vm.warp(block.timestamp + cooldownPeriod); + + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.withdrawFromPool(address(this), targetUsdcBalance); + } + + function testGovernorEmergencyActionExitSucceeds() public { + _setRewardsAndWarp(); + + MaplePCVDeposit.Call[] memory calls = new MaplePCVDeposit.Call[](1); + calls[0].callData = abi.encodeWithSignature("exit()"); + calls[0].target = mplRewards; + + vm.prank(MainnetAddresses.GOVERNOR); + usdcDeposit.emergencyAction(calls); + + assertEq( + IPool(maplePool).custodyAllowance( + address(usdcDeposit), + address(mplRewards) + ), + 0 + ); + assertEq(IMplRewards(mplRewards).balanceOf(address(usdcDeposit)), 0); } function testDepositNotPCVControllerFails() public { @@ -276,9 +318,9 @@ contract IntegrationTestMaplePCVDeposit is DSTest { usdcDeposit.withdraw(address(this), targetUsdcBalance); } - function testWithdrawWithoutExitingNonPCVControllerFails() public { + function testwithdrawFromPoolNonPCVControllerFails() public { vm.expectRevert("CoreRef: Caller is not a PCV controller"); - usdcDeposit.withdrawWithoutExiting(address(this), targetUsdcBalance); + usdcDeposit.withdrawFromPool(address(this), targetUsdcBalance); } function testWithdrawFromRewardsContractgNonPCVControllerFails() public { @@ -299,7 +341,17 @@ contract IntegrationTestMaplePCVDeposit is DSTest { function testEmergencyActionNonPCVControllerFails() public { MaplePCVDeposit.Call[] memory calls = new MaplePCVDeposit.Call[](2); - vm.expectRevert("CoreRef: Caller is not a PCV controller"); + vm.expectRevert("CoreRef: Caller is not a governor"); usdcDeposit.emergencyAction(calls); } + + function _setRewardsAndWarp() private { + uint256 rewardRate = IMplRewards(mplRewards).rewardRate(); + vm.prank(mapleOwner); + IMplRewards(mplRewards).notifyRewardAmount(rewardRate); + + vm.warp( + block.timestamp + IPool(maplePool).lockupPeriod() - cooldownPeriod + ); + } } diff --git a/contracts/test/integration/vip/Runner.sol b/contracts/test/integration/vip/Runner.sol index 04da3361e..2d9c1e053 100644 --- a/contracts/test/integration/vip/Runner.sol +++ b/contracts/test/integration/vip/Runner.sol @@ -36,16 +36,16 @@ contract Runner is TimelockSimulation, vip14 { } function testProposalArbitrum() public { - arbitrumSetup(); - simulate( - getArbitrumProposal(), - TimelockController(payable(ArbitrumAddresses.TIMELOCK_CONTROLLER)), - arbitrumPCVGuardian, - ArbitrumAddresses.GOVERNOR, - ArbitrumAddresses.EOA_1, - vm, - true - ); - arbitrumValidate(); + // arbitrumSetup(); + // simulate( + // getArbitrumProposal(), + // TimelockController(payable(ArbitrumAddresses.TIMELOCK_CONTROLLER)), + // arbitrumPCVGuardian, + // ArbitrumAddresses.GOVERNOR, + // ArbitrumAddresses.EOA_1, + // vm, + // true + // ); + // arbitrumValidate(); } } diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index 921e7fc2a..8ae5cf2a3 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -44,6 +44,10 @@ import {MorphoCompoundPCVDeposit} from "../../../pcv/morpho/MorphoCompoundPCVDep /// 7. add deposits as safe addresses /// 8. connect new oracle to oracle pass through with updated rate +/// 9. Grant PCV Controller to timelock +/// 10. Deposit funds in Maple PCV Deposit +/// 11. pause dai compound pcv deposit +/// 12. pause usdc compound pcv deposit contract vip14 is DSTest, IVIP { using SafeCast for uint256; @@ -64,7 +68,7 @@ contract vip14 is DSTest, IVIP { VoltSystemOracle public oracle; MaplePCVDeposit public mapleDeposit; - uint256 public immutable startTime; + uint256 public startTime; uint256 public constant monthlyChangeRateBasisPoints = 29; uint256 public constant arbitrumMonthlyChangeRateBasisPoints = 0; @@ -89,47 +93,8 @@ contract vip14 is DSTest, IVIP { 0xFeBd6F15Df3B73DC4307B1d7E65D46413e710C27; constructor() { - startTime = block.timestamp; - - if (block.chainid != 1) { - oracle = new VoltSystemOracle( - arbitrumMonthlyChangeRateBasisPoints, - block.timestamp, - VoltSystemOracle(ArbitrumAddresses.VOLT_SYSTEM_ORACLE_144_BIPS) - .getCurrentOraclePrice() - ); - - /// construct separate proposal on arbitrum - arbitrumProposal.push( - ITimelockSimulation.action({ - value: 0, - target: ArbitrumAddresses.ORACLE_PASS_THROUGH, - arguments: abi.encodeWithSignature( - "updateScalingPriceOracle(address)", - address(oracle) - ), - description: "Point Oracle Pass Through to new oracle address" - }) - ); - arbitrumProposal.push( - ITimelockSimulation.action({ - value: 0, - target: ArbitrumAddresses.VOLT_USDC_PSM, - arguments: abi.encodeWithSignature("pauseMint()"), - description: "Pause minting on USDC PSM on Arbitrum" - }) - ); - arbitrumProposal.push( - ITimelockSimulation.action({ - value: 0, - target: ArbitrumAddresses.VOLT_DAI_PSM, - arguments: abi.encodeWithSignature("pauseMint()"), - description: "Pause minting on DAI PSM on Arbitrum" - }) - ); - - return; - } + if (block.chainid != 1) return; /// keep ci pipeline happy + startTime = block.timestamp + 1 days; mapleDeposit = new MaplePCVDeposit(core, maplePool, mplRewards); daiDeposit = new MorphoCompoundPCVDeposit(core, MainnetAddresses.CDAI); @@ -146,7 +111,7 @@ contract vip14 is DSTest, IVIP { oracle = new VoltSystemOracle( monthlyChangeRateBasisPoints, - block.timestamp, + block.timestamp + 1 days, oldOracle.getCurrentOraclePrice() ); @@ -276,12 +241,18 @@ contract vip14 is DSTest, IVIP { mainnetProposal.push( ITimelockSimulation.action({ value: 0, - target: MainnetAddresses.CORE, - arguments: abi.encodeWithSignature( - "revokePCVController(address)", - MainnetAddresses.TIMELOCK_CONTROLLER - ), - description: "Revoke PCV Controller Role from timelock controller" + target: MainnetAddresses.COMPOUND_DAI_PCV_DEPOSIT, + arguments: abi.encodeWithSignature("pause()"), + description: "Pause Compound DAI PCV Deposit" + }) + ); + + mainnetProposal.push( + ITimelockSimulation.action({ + value: 0, + target: MainnetAddresses.COMPOUND_USDC_PCV_DEPOSIT, + arguments: abi.encodeWithSignature("pause()"), + description: "Pause Compound USDC PCV Deposit" }) ); } @@ -295,8 +266,6 @@ contract vip14 is DSTest, IVIP { return mainnetProposal; } - /// TODO -> move funds to this address - /// . move all funds from compound deposits to morpho deposits /// . move all needed funds to Maple function mainnetSetup() public override { @@ -420,34 +389,16 @@ contract vip14 is DSTest, IVIP { override returns (ITimelockSimulation.action[] memory) { - return arbitrumProposal; + revert("no arbitrum proposal"); } /// no-op, nothing to setup - function arbitrumSetup() public override {} + function arbitrumSetup() public override { + revert("no arbitrum proposal"); + } /// assert oracle pass through is pointing to correct volt system oracle function arbitrumValidate() public override { - /// oracle pass through points to new scaling price oracle - assertEq( - address( - OraclePassThrough(ArbitrumAddresses.ORACLE_PASS_THROUGH) - .scalingPriceOracle() - ), - address(oracle) - ); - - assertTrue(PriceBoundPSM(ArbitrumAddresses.VOLT_USDC_PSM).mintPaused()); - assertTrue(PriceBoundPSM(ArbitrumAddresses.VOLT_DAI_PSM).mintPaused()); - - vm.expectRevert("PegStabilityModule: Minting paused"); - PriceBoundPSM(ArbitrumAddresses.VOLT_USDC_PSM).mint( - address(this), - 0, - 0 - ); - - vm.expectRevert("PegStabilityModule: Minting paused"); - PriceBoundPSM(ArbitrumAddresses.VOLT_DAI_PSM).mint(address(this), 0, 0); + revert("no arbitrum proposal"); } } diff --git a/proposals/vip_14.ts b/proposals/vip_14.ts index 1e9703895..d9ec668ee 100644 --- a/proposals/vip_14.ts +++ b/proposals/vip_14.ts @@ -78,11 +78,18 @@ const vip_14: ProposalDescription = { description: 'Deposit PCV into Maple' }, { - target: 'core', + target: 'daiCompoundPCVDeposit', values: '0', - method: 'revokePCVController(address)', - arguments: ['{timelockController}'], - description: 'Revoke PCV Controller Role from timelock controller' + method: 'pause()', + arguments: [], + description: 'Pause Compound DAI PCV Deposit' + }, + { + target: 'usdcCompoundPCVDeposit', + values: '0', + method: 'pause()', + arguments: [], + description: 'Pause Compound USDC PCV Deposit' } ], description: ` @@ -99,8 +106,12 @@ const vip_14: ProposalDescription = { 4. Remove Compound USDC Deposit from ERC20Allocator 5. Add Morpho DAI Deposit to ERC20Allocator 6. Add Morpho USDC Deposit to ERC20Allocator - 7. Add USDC and DAI Morpho deposits to the PCV Guardian + 7. Add USDC and DAI Morpho deposits as well as Maple PCV deposit to the PCV Guardian 8. Point Oracle Pass Through to new oracle address + 9. Grant PCV Controller to timelock + 10. Deposit funds in Maple PCV Deposit + 11. pause dai compound pcv deposit + 12. pause usdc compound pcv deposit ` }; From 41b1496a07881d8e7487e6fd49f8dc4cffea309a Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 6 Oct 2022 14:01:51 -0700 Subject: [PATCH 23/84] update description with deployment steps --- proposals/vip_14.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/proposals/vip_14.ts b/proposals/vip_14.ts index d9ec668ee..36b9f2952 100644 --- a/proposals/vip_14.ts +++ b/proposals/vip_14.ts @@ -98,6 +98,7 @@ const vip_14: ProposalDescription = { 2. deploy morpho usdc deposit 3. deploy compound pcv router pointed to morpho dai and usdc deposits 4. deploy volt system oracle + 5. deploy maple pcv deposit Governance Steps: 1. Grant Morpho PCV Router PCV Controller Role From 513d8727abd710c431bdbf276afa8b993cdb4bd1 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 6 Oct 2022 14:03:41 -0700 Subject: [PATCH 24/84] deployment script gov description fix --- proposals/dao/vip_14.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/proposals/dao/vip_14.ts b/proposals/dao/vip_14.ts index 146087967..9ca4af96c 100644 --- a/proposals/dao/vip_14.ts +++ b/proposals/dao/vip_14.ts @@ -31,6 +31,10 @@ Governance Steps: 6. Add Morpho USDC Deposit to ERC20Allocator 7. Add USDC and DAI Morpho deposits to the PCV Guardian 8. Point Oracle Pass Through to new oracle address +9. Grant PCV Controller to timelock +10. Deposit funds in Maple PCV Deposit +11. pause dai compound pcv deposit +12. pause usdc compound pcv deposit */ From c231877195c3caaaad7e4a3fd2c62ad256084d6d Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 6 Oct 2022 16:18:07 -0700 Subject: [PATCH 25/84] Clarifying comments Maple Deposit --- contracts/pcv/maple/MaplePCVDeposit.sol | 27 ++++++++++++------- .../IntegrationTestMaplePCVDeposit.t.sol | 15 ++--------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index 16ae8c522..258858ad6 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -60,6 +60,8 @@ contract MaplePCVDeposit is PCVDeposit { return address(token); } + /// ---------- Happy Path APIs ---------- + /// @notice deposit PCV into Maple. /// all deposits are subject to a minimum 90 day lockup, /// no op if 0 token balance @@ -148,18 +150,30 @@ contract MaplePCVDeposit is PCVDeposit { emit Withdrawal(msg.sender, to, tokenBalance); } + /// @notice permissionless function to harvest rewards before withdraw + function harvest() external { + mplRewards.getReward(); + + emit Harvest(); + } + + /// ---------- Sad Path APIs ---------- + /// @notice get rewards and unstake from rewards contract + /// breaks functionality of happy path withdraw functions function exit() external onlyPCVController { mplRewards.exit(); } /// @notice unstake from rewards contract without getting rewards + /// breaks functionality of happy path withdraw functions function withdrawFromRewardsContract() external onlyPCVController { uint256 rewardsBalance = pool.balanceOf(address(this)); mplRewards.withdraw(rewardsBalance); } /// @notice unstake from Pool FDT contract without getting rewards + /// or unstaking from the reward contract. /// @param to destination after funds are withdrawn from venue /// @param amount of PCV to withdraw from the venue function withdrawFromPool(address to, uint256 amount) @@ -173,22 +187,15 @@ contract MaplePCVDeposit is PCVDeposit { token.safeTransfer(to, amount); } - /// @notice permissionless function to harvest rewards before withdraw - function harvest() external { - mplRewards.getReward(); - - emit Harvest(); - } - /// @notice struct to pack calldata and targets for an emergency action struct Call { address target; bytes callData; } - /// @notice due to Maple's complexity, add this ability to be able to execute - /// arbitrary calldata against arbitrary addresses. - /// callable only by governor + /// @notice due to Maple's complexity, add this ability to be able + /// to execute arbitrary calldata against arbitrary addresses. + /// only callable by governor function emergencyAction(Call[] memory calls) external onlyGovernor diff --git a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol index d64707361..101e17650 100644 --- a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol @@ -231,22 +231,11 @@ contract IntegrationTestMaplePCVDeposit is DSTest { function testWithdrawFromRewardsContractAndWithdrawFromPoolPCVControllerSucceeds() public { - _setRewardsAndWarp(); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.withdrawFromRewardsContract(); - - assertEq( - IPool(maplePool).custodyAllowance( - address(usdcDeposit), - address(mplRewards) - ), - 0 - ); - assertEq(IMplRewards(mplRewards).balanceOf(address(usdcDeposit)), 0); + testWithdrawFromRewardsContractPCVControllerSucceeds(); vm.prank(MainnetAddresses.GOVERNOR); usdcDeposit.signalIntentToWithdraw(); + vm.warp(block.timestamp + cooldownPeriod); vm.prank(MainnetAddresses.GOVERNOR); From 72101fa5a391ecda19018af470f69d8a9424b6f2 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 6 Oct 2022 17:11:02 -0700 Subject: [PATCH 26/84] update maple deposit to accurately track interest and losses --- contracts/pcv/maple/IPool.sol | 2 ++ contracts/pcv/maple/MaplePCVDeposit.sol | 5 ++++- .../IntegrationTestMorphoCompoundPCVDeposit.t.sol | 2 -- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/contracts/pcv/maple/IPool.sol b/contracts/pcv/maple/IPool.sol index b2ed0fe6c..ca9593eba 100644 --- a/contracts/pcv/maple/IPool.sol +++ b/contracts/pcv/maple/IPool.sol @@ -5,6 +5,8 @@ pragma solidity =0.8.13; interface IPool { function balanceOf(address) external view returns (uint256); + function recognizableLossesOf(address) external view returns (uint256); + /** @dev Returns the amount of funds that an account has earned in total. @dev accumulativeFundsOf(_owner) = withdrawableFundsOf(_owner) + withdrawnFundsOf(_owner) diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index 258858ad6..8b168673b 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -52,7 +52,10 @@ contract MaplePCVDeposit is PCVDeposit { /// without accounting for interest earned /// does not account for unrealized losses in the venue function balance() public view override returns (uint256) { - return pool.balanceOf(address(this)) / scalingFactor; + uint256 rawBalance = pool.balanceOf(address(this)) + + pool.accumulativeFundsOf(address(this)) - + pool.recognizableLossesOf(address(this)); + return rawBalance / scalingFactor; } /// @notice return the underlying token denomination for this deposit diff --git a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol index 1d4596926..0558b080c 100644 --- a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol @@ -16,8 +16,6 @@ import {PegStabilityModule} from "../../peg/PegStabilityModule.sol"; import {ERC20CompoundPCVDeposit} from "../../pcv/compound/ERC20CompoundPCVDeposit.sol"; import {MorphoCompoundPCVDeposit} from "../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; -import "hardhat/console.sol"; - contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { using SafeCast for *; From 979ce3159fdbf40894b0352112bbc37e1ffbc573 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 6 Oct 2022 17:11:32 -0700 Subject: [PATCH 27/84] update comment --- contracts/pcv/maple/MaplePCVDeposit.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index 8b168673b..76b9e6cfe 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -48,9 +48,9 @@ contract MaplePCVDeposit is PCVDeposit { mplRewards = IMplRewards(_mplRewards); } - /// @notice return the amount of funds this contract owns in Maple FDT's - /// without accounting for interest earned - /// does not account for unrealized losses in the venue + /// @notice return the amount of funds this contract owns in USDC + /// accounting for interest earned + /// and unrealized losses in the venue function balance() public view override returns (uint256) { uint256 rawBalance = pool.balanceOf(address(this)) + pool.accumulativeFundsOf(address(this)) - From 376e7d490a4eb0ef1a40f372a925eb6328134b11 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 6 Oct 2022 17:12:18 -0700 Subject: [PATCH 28/84] update comment pcv guardian ts --- proposals/dao/vip_14.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/dao/vip_14.ts b/proposals/dao/vip_14.ts index 9ca4af96c..bafa4dbcd 100644 --- a/proposals/dao/vip_14.ts +++ b/proposals/dao/vip_14.ts @@ -29,7 +29,7 @@ Governance Steps: 4. Remove Compound USDC Deposit from ERC20Allocator 5. Add Morpho DAI Deposit to ERC20Allocator 6. Add Morpho USDC Deposit to ERC20Allocator -7. Add USDC and DAI Morpho deposits to the PCV Guardian +7. Add USDC and DAI Morpho, and Maple deposits to the PCV Guardian 8. Point Oracle Pass Through to new oracle address 9. Grant PCV Controller to timelock 10. Deposit funds in Maple PCV Deposit From b3a184341b0a233a7e632f935db66c1699cdff02 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 6 Oct 2022 17:13:23 -0700 Subject: [PATCH 29/84] warn comment on maple deposit --- contracts/pcv/maple/MaplePCVDeposit.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index 76b9e6cfe..ade91012e 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -12,6 +12,8 @@ import {IMplRewards} from "./IMplRewards.sol"; /// Allows depositing only by privileged role to prevent lockup period being extended by griefers /// Can only deposit USDC in this MAPLE PCV deposit +/// @notice NEVER CONNECT THIS DEPOSIT INTO THE ALLOCATOR OR OTHER AUTOMATED PCV +/// SYSTEM. DEPOSITING LOCKS FUNDS FOR AN EXTENDED PERIOD OF TIME. contract MaplePCVDeposit is PCVDeposit { using SafeERC20 for IERC20; From 101415e1b4ffeba4b2f93fc87d56f46e291ca7d5 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 6 Oct 2022 17:28:30 -0700 Subject: [PATCH 30/84] add assertion --- contracts/test/integration/vip/vip14.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index 8ae5cf2a3..00acd3bdb 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -266,8 +266,8 @@ contract vip14 is DSTest, IVIP { return mainnetProposal; } - /// . move all funds from compound deposits to morpho deposits - /// . move all needed funds to Maple + /// move all funds from compound deposits to morpho deposits + /// move all needed funds to Maple function mainnetSetup() public override { vm.startPrank(MainnetAddresses.GOVERNOR); pcvGuardian.withdrawAllToSafeAddress( @@ -381,6 +381,7 @@ contract vip14 is DSTest, IVIP { monthlyChangeRateBasisPoints ); assertEq(oracle.periodStartTime(), startTime); + assertEq(oracle.getCurrentOraclePrice(), oracle.oraclePrice()); } function getArbitrumProposal() From 729fc0188c1b25d4d766128dcb2b06009d6f2690 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 6 Oct 2022 17:31:49 -0700 Subject: [PATCH 31/84] pause old pcv deposits assertions --- contracts/test/integration/vip/vip14.sol | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index 00acd3bdb..b9ed02db8 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -323,6 +323,14 @@ contract vip14 is DSTest, IVIP { assertEq(address(router.GEM_JOIN()), MainnetAddresses.GEM_JOIN); assertEq(address(router.daiPSM()), MainnetAddresses.MAKER_DAI_USDC_PSM); + /// old deposits paused + assertTrue( + PCVDeposit(MainnetAddresses.COMPOUND_USDC_PCV_DEPOSIT).paused() + ); + assertTrue( + PCVDeposit(MainnetAddresses.COMPOUND_DAI_PCV_DEPOSIT).paused() + ); + /// old deposits disconnected in allocator assertEq( allocator.pcvDepositToPSM( From 64a50ddfcf63e3cdd5260197609beaa035a3ffef Mon Sep 17 00:00:00 2001 From: Elliot Date: Fri, 7 Oct 2022 16:43:04 -0700 Subject: [PATCH 32/84] Remove new oracle deployment and depositing of pcv into maple --- contracts/test/integration/vip/vip14.sol | 102 ++--------------------- 1 file changed, 5 insertions(+), 97 deletions(-) diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index b9ed02db8..2a962cc53 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -16,10 +16,7 @@ import {IPCVDeposit} from "../../../pcv/IPCVDeposit.sol"; import {PriceBoundPSM} from "../../../peg/PriceBoundPSM.sol"; import {ERC20Allocator} from "../../../pcv/utils/ERC20Allocator.sol"; import {MaplePCVDeposit} from "../../../pcv/maple/MaplePCVDeposit.sol"; -import {VoltSystemOracle} from "../../../oracle/VoltSystemOracle.sol"; import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; -import {ArbitrumAddresses} from "../fixtures/ArbitrumAddresses.sol"; -import {OraclePassThrough} from "../../../oracle/OraclePassThrough.sol"; import {CompoundPCVRouter} from "../../../pcv/compound/CompoundPCVRouter.sol"; import {ITimelockSimulation} from "../utils/ITimelockSimulation.sol"; import {MorphoCompoundPCVDeposit} from "../../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; @@ -28,8 +25,7 @@ import {MorphoCompoundPCVDeposit} from "../../../pcv/morpho/MorphoCompoundPCVDep /// 1. deploy morpho dai deposit /// 2. deploy morpho usdc deposit /// 3. deploy compound pcv router pointed to morpho dai and usdc deposits -/// 4. deploy volt system oracle -/// 5. deploy maple usdc deposit +/// 4. deploy maple usdc deposit /// Governance Steps /// 1. grant new PCV router PCV Controller role @@ -43,11 +39,8 @@ import {MorphoCompoundPCVDeposit} from "../../../pcv/morpho/MorphoCompoundPCVDep /// 7. add deposits as safe addresses -/// 8. connect new oracle to oracle pass through with updated rate -/// 9. Grant PCV Controller to timelock -/// 10. Deposit funds in Maple PCV Deposit -/// 11. pause dai compound pcv deposit -/// 12. pause usdc compound pcv deposit +/// 8. pause dai compound pcv deposit +/// 9. pause usdc compound pcv deposit contract vip14 is DSTest, IVIP { using SafeCast for uint256; @@ -65,25 +58,14 @@ contract vip14 is DSTest, IVIP { CompoundPCVRouter public router; MorphoCompoundPCVDeposit public daiDeposit; MorphoCompoundPCVDeposit public usdcDeposit; - VoltSystemOracle public oracle; MaplePCVDeposit public mapleDeposit; - uint256 public startTime; - - uint256 public constant monthlyChangeRateBasisPoints = 29; - uint256 public constant arbitrumMonthlyChangeRateBasisPoints = 0; - PCVGuardian public immutable pcvGuardian = PCVGuardian(MainnetAddresses.PCV_GUARDIAN); - VoltSystemOracle private immutable oldOracle = - VoltSystemOracle(MainnetAddresses.VOLT_SYSTEM_ORACLE_144_BIPS); - ERC20Allocator public immutable allocator = ERC20Allocator(MainnetAddresses.ERC20ALLOCATOR); - uint256 targetMapleDepositAmount = 750_000e6; - /// --------- Maple Addresses --------- address public constant mplRewards = @@ -94,7 +76,6 @@ contract vip14 is DSTest, IVIP { constructor() { if (block.chainid != 1) return; /// keep ci pipeline happy - startTime = block.timestamp + 1 days; mapleDeposit = new MaplePCVDeposit(core, maplePool, mplRewards); daiDeposit = new MorphoCompoundPCVDeposit(core, MainnetAddresses.CDAI); @@ -109,12 +90,6 @@ contract vip14 is DSTest, IVIP { PCVDeposit(address(usdcDeposit)) ); - oracle = new VoltSystemOracle( - monthlyChangeRateBasisPoints, - block.timestamp + 1 days, - oldOracle.getCurrentOraclePrice() - ); - address[] memory toWhitelist = new address[](3); toWhitelist[0] = address(daiDeposit); toWhitelist[1] = address(usdcDeposit); @@ -205,39 +180,6 @@ contract vip14 is DSTest, IVIP { }) ); - mainnetProposal.push( - ITimelockSimulation.action({ - value: 0, - target: MainnetAddresses.ORACLE_PASS_THROUGH, - arguments: abi.encodeWithSignature( - "updateScalingPriceOracle(address)", - address(oracle) - ), - description: "Point Oracle Pass Through to new oracle address" - }) - ); - - mainnetProposal.push( - ITimelockSimulation.action({ - value: 0, - target: MainnetAddresses.CORE, - arguments: abi.encodeWithSignature( - "grantPCVController(address)", - MainnetAddresses.TIMELOCK_CONTROLLER - ), - description: "Grant PCV Controller Role to timelock controller" - }) - ); - - mainnetProposal.push( - ITimelockSimulation.action({ - value: 0, - target: address(mapleDeposit), - arguments: abi.encodeWithSignature("deposit()"), - description: "Deposit PCV into Maple" - }) - ); - mainnetProposal.push( ITimelockSimulation.action({ value: 0, @@ -278,11 +220,7 @@ contract vip14 is DSTest, IVIP { ); uint256 usdcBalance = IERC20(usdc).balanceOf(MainnetAddresses.GOVERNOR); - IERC20(usdc).transfer( - address(usdcDeposit), - usdcBalance - targetMapleDepositAmount - ); - IERC20(usdc).transfer(address(mapleDeposit), targetMapleDepositAmount); + IERC20(usdc).transfer(address(usdcDeposit), usdcBalance); IERC20(dai).transfer( address(daiDeposit), IERC20(dai).balanceOf(MainnetAddresses.GOVERNOR) @@ -299,7 +237,6 @@ contract vip14 is DSTest, IVIP { /// assert pcv deposits are set correctly in router /// assert pcv deposits are set correctly in allocator /// assert old pcv deposits are disconnected in allocator - /// assert oracle pass through is pointed to the proper Volt System Oracle function mainnetValidate() public override { assertEq(address(mapleDeposit.core()), core); assertEq(address(usdcDeposit.core()), core); @@ -311,6 +248,7 @@ contract vip14 is DSTest, IVIP { !Core(core).isPCVController(MainnetAddresses.COMPOUND_PCV_ROUTER) ); + /// pcv guardian whitelist assertions assertTrue(pcvGuardian.isWhitelistAddress(address(daiDeposit))); assertTrue(pcvGuardian.isWhitelistAddress(address(usdcDeposit))); assertTrue(pcvGuardian.isWhitelistAddress(address(mapleDeposit))); @@ -361,35 +299,6 @@ contract vip14 is DSTest, IVIP { assertEq(usdcDeposit.cToken(), MainnetAddresses.CUSDC); assertEq(daiDeposit.cToken(), MainnetAddresses.CDAI); - - /// oracle pass through points to new scaling price oracle - assertEq( - address( - OraclePassThrough(MainnetAddresses.ORACLE_PASS_THROUGH) - .scalingPriceOracle() - ), - address(oracle) - ); - - /// volt system oracle - /// only 1 day of interest has accrued, so only .5 basis point diff in price between old and new oracle - assertApproxEq( - oracle.oraclePrice().toInt256(), - oldOracle.getCurrentOraclePrice().toInt256(), - 0 - ); - assertApproxEq( - oracle.getCurrentOraclePrice().toInt256(), - oldOracle.getCurrentOraclePrice().toInt256(), - 0 - ); - - assertEq( - oracle.monthlyChangeRateBasisPoints(), - monthlyChangeRateBasisPoints - ); - assertEq(oracle.periodStartTime(), startTime); - assertEq(oracle.getCurrentOraclePrice(), oracle.oraclePrice()); } function getArbitrumProposal() @@ -406,7 +315,6 @@ contract vip14 is DSTest, IVIP { revert("no arbitrum proposal"); } - /// assert oracle pass through is pointing to correct volt system oracle function arbitrumValidate() public override { revert("no arbitrum proposal"); } From 4c0e8901b74a48f36b523070645f0e8f08242a63 Mon Sep 17 00:00:00 2001 From: Elliot Date: Fri, 7 Oct 2022 16:44:14 -0700 Subject: [PATCH 33/84] VIP-14 Integration tests --- .../test/integration/IntegrationTestVIP14.sol | 160 +++++++++++++++++- 1 file changed, 151 insertions(+), 9 deletions(-) diff --git a/contracts/test/integration/IntegrationTestVIP14.sol b/contracts/test/integration/IntegrationTestVIP14.sol index badfd5afa..2fb75ad33 100644 --- a/contracts/test/integration/IntegrationTestVIP14.sol +++ b/contracts/test/integration/IntegrationTestVIP14.sol @@ -4,13 +4,15 @@ pragma solidity =0.8.13; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; + +import {vip14} from "./vip/vip14.sol"; import {ICore} from "../../core/ICore.sol"; import {IVolt} from "../../volt/Volt.sol"; +import {IPCVDeposit} from "../../pcv/IPCVDeposit.sol"; +import {IPCVGuardian} from "../../pcv/IPCVGuardian.sol"; +import {PriceBoundPSM} from "../../peg/PriceBoundPSM.sol"; import {MainnetAddresses} from "./fixtures/MainnetAddresses.sol"; -import {vip14} from "./vip/vip14.sol"; import {TimelockSimulation} from "./utils/TimelockSimulation.sol"; -import {PriceBoundPSM} from "../../peg/PriceBoundPSM.sol"; -import {IPCVGuardian} from "../../pcv/IPCVGuardian.sol"; contract IntegrationTestVIP14 is TimelockSimulation, vip14 { using SafeCast for *; @@ -21,8 +23,14 @@ contract IntegrationTestVIP14 is TimelockSimulation, vip14 { /// @notice scaling factor for USDC uint256 public constant USDC_SCALING_FACTOR = 1e12; + uint256 public constant targetDaiBalance = 100_000e18; + + uint256 public constant targetUsdcBalance = 100_000e6; + address private governor = MainnetAddresses.GOVERNOR; + IERC20 public comp = IERC20(MainnetAddresses.COMP); + function setUp() public { mainnetSetup(); simulate( @@ -35,19 +43,153 @@ contract IntegrationTestVIP14 is TimelockSimulation, vip14 { false ); mainnetValidate(); + + vm.label(address(usdcDeposit), "USDC Deposit"); + vm.label(address(daiDeposit), "DAI Deposit"); + vm.label(address(allocator), "Allocator"); + vm.label(address(pcvGuardian), "PCV Guardian"); + vm.label(address(MainnetAddresses.VOLT_DAI_PSM), "VOLT_DAI_PSM"); + vm.label(address(MainnetAddresses.VOLT_USDC_PSM), "VOLT_USDC_PSM"); + } + + function testSkimDaiToMorphoDeposit() public { + vm.prank(MainnetAddresses.DAI_USDC_USDT_CURVE_POOL); + IERC20(dai).transfer( + MainnetAddresses.VOLT_DAI_PSM, + targetDaiBalance * 2 + ); + assertTrue( + IERC20(dai).balanceOf(MainnetAddresses.VOLT_DAI_PSM) >= + targetDaiBalance * 2 + ); + + uint256 daiSurplus = IERC20(dai).balanceOf( + MainnetAddresses.VOLT_DAI_PSM + ) - targetDaiBalance; + + uint256 startingDaiDepositBalance = daiDeposit.balance(); + + allocator.skim(address(daiDeposit)); + + uint256 endingDaiDepositBalance = daiDeposit.balance(); + + assertApproxEq( + (startingDaiDepositBalance + daiSurplus).toInt256(), + endingDaiDepositBalance.toInt256(), + 0 + ); + + assertEq( + IERC20(dai).balanceOf(MainnetAddresses.VOLT_DAI_PSM), + targetDaiBalance + ); + } + + function testSkimUsdcToMorphoDeposit() public { + vm.prank(MainnetAddresses.DAI_USDC_USDT_CURVE_POOL); + IERC20(usdc).transfer( + MainnetAddresses.VOLT_USDC_PSM, + targetUsdcBalance * 2 + ); + assertTrue( + IERC20(usdc).balanceOf(MainnetAddresses.VOLT_USDC_PSM) >= + targetUsdcBalance * 2 + ); + + uint256 usdcSurplus = IERC20(usdc).balanceOf( + MainnetAddresses.VOLT_USDC_PSM + ) - targetUsdcBalance; + + uint256 startingUsdcDepositBalance = usdcDeposit.balance(); + + allocator.skim(address(usdcDeposit)); + + uint256 endingUsdcDepositBalance = usdcDeposit.balance(); + + assertApproxEq( + (startingUsdcDepositBalance + usdcSurplus).toInt256(), + endingUsdcDepositBalance.toInt256(), + 0 + ); + assertEq( + IERC20(usdc).balanceOf(MainnetAddresses.VOLT_USDC_PSM), + targetUsdcBalance + ); } - function testSkimDaiToMorphoDeposit() public {} + function testDripUsdcToPsm() public { + vm.prank(MainnetAddresses.EOA_1); + mainnetPCVGuardian.withdrawAllToSafeAddress( + MainnetAddresses.VOLT_USDC_PSM + ); - function testSkimUsdcToMorphoDeposit() public {} + assertTrue( + IPCVDeposit(MainnetAddresses.VOLT_USDC_PSM).balance() <= 1e6 + ); - function testDripUsdcToPsm() public {} + uint256 startingDepositBalance = usdcDeposit.balance(); - function testDripDaiToPsm() public {} + allocator.drip(address(usdcDeposit)); - function testClaimCompRewardsDai() public {} + uint256 endingDepositBalance = usdcDeposit.balance(); - function testClaimCompRewardsUsdc() public {} + assertApproxEq( + (startingDepositBalance - endingDepositBalance).toInt256(), + targetUsdcBalance.toInt256(), + 0 + ); + assertEq( + IPCVDeposit(MainnetAddresses.VOLT_USDC_PSM).balance(), + targetUsdcBalance + ); + } + + function testDripDaiToPsm() public { + vm.prank(MainnetAddresses.EOA_1); + mainnetPCVGuardian.withdrawAllToSafeAddress( + MainnetAddresses.VOLT_DAI_PSM + ); + + uint256 startingDepositBalance = daiDeposit.balance(); + + assertTrue( + IPCVDeposit(MainnetAddresses.VOLT_DAI_PSM).balance() <= 1e18 + ); + + allocator.drip(address(daiDeposit)); + + uint256 endingDepositBalance = daiDeposit.balance(); + + assertApproxEq( + (startingDepositBalance - endingDepositBalance).toInt256(), + targetDaiBalance.toInt256(), + 0 + ); + assertEq( + IPCVDeposit(MainnetAddresses.VOLT_DAI_PSM).balance(), + targetDaiBalance + ); + } + + function testClaimCompRewardsDai() public { + uint256 startingCompBalance = comp.balanceOf(address(daiDeposit)); + + vm.roll(block.number + 100 days / 12); + daiDeposit.harvest(); + + uint256 endingCompBalance = comp.balanceOf(address(daiDeposit)); + assertTrue(endingCompBalance - startingCompBalance != 0); + } + + function testClaimCompRewardsUsdc() public { + uint256 startingCompBalance = comp.balanceOf(address(usdcDeposit)); + + vm.roll(block.number + 100 days / 12); + usdcDeposit.harvest(); + + uint256 endingCompBalance = comp.balanceOf(address(usdcDeposit)); + assertTrue(endingCompBalance - startingCompBalance != 0); + } function testSwapUsdcToDaiRouter() public { uint256 withdrawAmount = usdcDeposit.balance(); From 6619e943515ffbeae125fa7331c55607949c2bf0 Mon Sep 17 00:00:00 2001 From: Elliot Date: Fri, 7 Oct 2022 16:55:42 -0700 Subject: [PATCH 34/84] typescript proposal update --- proposals/dao/vip_14.ts | 67 ++--------------------------------------- proposals/vip_14.ts | 32 ++------------------ 2 files changed, 6 insertions(+), 93 deletions(-) diff --git a/proposals/dao/vip_14.ts b/proposals/dao/vip_14.ts index bafa4dbcd..519809a2f 100644 --- a/proposals/dao/vip_14.ts +++ b/proposals/dao/vip_14.ts @@ -19,8 +19,7 @@ Description: /// 1. deploy morpho dai deposit /// 2. deploy morpho usdc deposit /// 3. deploy compound pcv router pointed to morpho dai and usdc deposits -/// 4. deploy volt system oracle -/// 5. deploy maple usdc deposit +/// 4. deploy maple usdc deposit Governance Steps: 1. Grant Morpho PCV Router PCV Controller Role @@ -30,32 +29,17 @@ Governance Steps: 5. Add Morpho DAI Deposit to ERC20Allocator 6. Add Morpho USDC Deposit to ERC20Allocator 7. Add USDC and DAI Morpho, and Maple deposits to the PCV Guardian -8. Point Oracle Pass Through to new oracle address -9. Grant PCV Controller to timelock -10. Deposit funds in Maple PCV Deposit -11. pause dai compound pcv deposit -12. pause usdc compound pcv deposit +8. pause dai compound pcv deposit +9. pause usdc compound pcv deposit */ -/// TODO update this to correct start time -let startTime; - -const monthlyChangeBasisPoints = 29; - const vipNumber = '14'; const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => { - startTime = Math.floor(Date.now() / 1000).toString(); - - const voltSystemOracle = await ethers.getContractAt('VoltSystemOracle', addresses.voltSystemOracle); - - const currentPrice = await voltSystemOracle.getCurrentOraclePrice(); - const maplePCVDepositFactory = await ethers.getContractFactory('MaplePCVDeposit'); const morphoPCVDepositFactory = await ethers.getContractFactory('MorphoCompoundPCVDeposit'); const compoundPCVRouterFactory = await ethers.getContractFactory('CompoundPCVRouter'); - const voltSystemOracleFactory = await ethers.getContractFactory('VoltSystemOracle'); const daiMorphoCompoundPCVDeposit = await morphoPCVDepositFactory.deploy(addresses.core, addresses.cDai); await daiMorphoCompoundPCVDeposit.deployed(); @@ -73,15 +57,7 @@ const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: Named const maplePCVDeposit = await maplePCVDepositFactory.deploy(addresses.core, addresses.mplPool, addresses.mplRewards); await maplePCVDeposit.deployed(); - const voltSystemOracle348Bips = await voltSystemOracleFactory.deploy( - monthlyChangeBasisPoints, - startTime, - currentPrice - ); - await voltSystemOracle348Bips.deployed(); - console.log(`Maple PCV Deposit deployed ${maplePCVDeposit.address}`); - console.log(`Volt System Oracle deployed ${voltSystemOracle348Bips.address}`); console.log(`Morpho Compound PCV Router deployed ${morphoCompoundPCVRouter.address}`); console.log(`Morpho Compound DAI PCV Deposit deployed ${daiMorphoCompoundPCVDeposit.address}`); console.log(`Morpho Compound USDC PCV Deposit deployed ${usdcMorphoCompoundPCVDeposit.address}`); @@ -90,7 +66,6 @@ const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: Named return { maplePCVDeposit, - voltSystemOracle348Bips, daiMorphoCompoundPCVDeposit, usdcMorphoCompoundPCVDeposit, morphoCompoundPCVRouter @@ -108,9 +83,6 @@ const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { const { core, - voltSystemOracle, - oraclePassThrough, - voltSystemOracle348Bips, daiMorphoCompoundPCVDeposit, usdcMorphoCompoundPCVDeposit, morphoCompoundPCVRouter, @@ -126,9 +98,6 @@ const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, expect(await morphoCompoundPCVRouter.core()).to.be.equal(core.address); expect(await maplePCVDeposit.core()).to.be.equal(core.address); - /// oracle pass through validation - expect(await oraclePassThrough.scalingPriceOracle()).to.be.equal(voltSystemOracle348Bips.address); - /// pcv controller validation expect(await core.isPCVController(compoundPCVRouter.address)).to.be.false; expect(await core.isPCVController(morphoCompoundPCVRouter.address)).to.be.true; @@ -182,36 +151,6 @@ const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, addresses.daiPriceBoundPSM ); - /// volt system oracle validation - expect((await voltSystemOracle348Bips.periodStartTime()).toString()).to.be.equal(startTime.toString()); - expect((await voltSystemOracle348Bips.monthlyChangeRateBasisPoints()).toString()).to.be.equal( - monthlyChangeBasisPoints.toString() - ); - - await assertApproxEq( - await voltSystemOracle348Bips.getCurrentOraclePrice(), - await voltSystemOracle.getCurrentOraclePrice(), - 0 /// allow 0 bips of deviation - ); - - await assertApproxEq( - await voltSystemOracle348Bips.oraclePrice(), - await voltSystemOracle.getCurrentOraclePrice(), - 0 /// allow 0 bips of deviation - ); - - await assertApproxEq( - await voltSystemOracle348Bips.oraclePrice(), - await voltSystemOracle.getCurrentOraclePrice(), - 0 /// allow 0 bips of deviation - ); - - await assertApproxEq( - await voltSystemOracle348Bips.getCurrentOraclePrice(), - await voltSystemOracle348Bips.oraclePrice(), - 0 - ); - console.log(`Successfully validated VIP-${vipNumber}`); }; diff --git a/proposals/vip_14.ts b/proposals/vip_14.ts index 36b9f2952..1c34e925d 100644 --- a/proposals/vip_14.ts +++ b/proposals/vip_14.ts @@ -55,28 +55,6 @@ const vip_14: ProposalDescription = { arguments: [['{daiMorphoCompoundPCVDeposit}', '{usdcMorphoCompoundPCVDeposit}', '{maplePCVDeposit}']], description: 'Add USDC and DAI Morpho deposits and Maple deposit to the PCV Guardian' }, - /// oracle pass through setting Volt System Oracle - { - target: 'oraclePassThrough', - values: '0', - method: 'updateScalingPriceOracle(address)', - arguments: ['{voltSystemOracle348Bips}'], - description: 'Point Oracle Pass Through to new oracle address' - }, - { - target: 'core', - values: '0', - method: 'grantPCVController(address)', - arguments: ['{timelockController}'], - description: 'Grant PCV Controller Role to timelock controller' - }, - { - target: 'maplePCVDeposit', - values: '0', - method: 'deposit()', - arguments: [], - description: 'Deposit PCV into Maple' - }, { target: 'daiCompoundPCVDeposit', values: '0', @@ -97,8 +75,7 @@ const vip_14: ProposalDescription = { 1. deploy morpho dai deposit 2. deploy morpho usdc deposit 3. deploy compound pcv router pointed to morpho dai and usdc deposits - 4. deploy volt system oracle - 5. deploy maple pcv deposit + 4. deploy maple pcv deposit Governance Steps: 1. Grant Morpho PCV Router PCV Controller Role @@ -108,11 +85,8 @@ const vip_14: ProposalDescription = { 5. Add Morpho DAI Deposit to ERC20Allocator 6. Add Morpho USDC Deposit to ERC20Allocator 7. Add USDC and DAI Morpho deposits as well as Maple PCV deposit to the PCV Guardian - 8. Point Oracle Pass Through to new oracle address - 9. Grant PCV Controller to timelock - 10. Deposit funds in Maple PCV Deposit - 11. pause dai compound pcv deposit - 12. pause usdc compound pcv deposit + 8. pause dai compound pcv deposit + 9. pause usdc compound pcv deposit ` }; From 21d0bf7e554d2404d654cd5bab4fabddac0fb9a3 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 10 Oct 2022 18:29:01 -0700 Subject: [PATCH 35/84] Add dev comments to Morpho and Maple PCV Deposits --- contracts/pcv/maple/MaplePCVDeposit.sol | 47 +++++++++++++++---- .../pcv/morpho/MorphoCompoundPCVDeposit.sol | 7 ++- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index ade91012e..4f4c1308c 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -10,10 +10,27 @@ import {IMplRewards} from "./IMplRewards.sol"; /// @notice PCV Deposit for Maple /// Allows depositing only by privileged role to prevent lockup period being extended by griefers +/// Can only deposit USDC in this MAPLE PCV deposit due to scalingFactor being hardcoded +/// and underlying token is enforced as USDC. -/// Can only deposit USDC in this MAPLE PCV deposit /// @notice NEVER CONNECT THIS DEPOSIT INTO THE ALLOCATOR OR OTHER AUTOMATED PCV /// SYSTEM. DEPOSITING LOCKS FUNDS FOR AN EXTENDED PERIOD OF TIME. + +/// @dev On deposit, all Maple FDT tokens are immediately deposited into +/// the maple rewards contract that corresponds with the pool where funds are deposited. +/// On withdraw, the `signalIntentToWithdraw` function must be called. +/// In the withdraw function, the Maple FDT tokens are unstaked +/// from the rewards contract, this allows the underlying USDC to be withdrawn. +/// Maple withdraws have some interesting properties such as interest not being calculated into +/// the amount that is requested to be withdrawn. This means that asking to withdraw 100 USDC +/// could yield 101 USDC received in this PCV Deposit if interest has been earned, or it could +/// mean 95 USDC in this contract if losses are sustained in excess of interest earned. +/// Supporting these two situations adds additional code complexity, so unlike other PCV Deposits, +/// the amount withdrawn on this PCV Deposit is non-deterministic, so a sender could request 100 +/// USDC out, and get another amount out. Dealing with these different pathways would add additional +/// code and complexity, so instead, Math.min(token.balanceOf(Address(this)), amountToWithdraw) +/// is used to determine the amount of tokens that will be sent out of the contract +/// after withdraw is called on the Maple market. contract MaplePCVDeposit is PCVDeposit { using SafeERC20 for IERC20; @@ -131,10 +148,13 @@ contract MaplePCVDeposit is PCVDeposit { /// withdraw min between balance and amount as losses could be sustained in venue /// causing less than amt to be withdrawn - amount = Math.min(token.balanceOf(address(this)), amount); - token.safeTransfer(to, amount); + uint256 amountToTransfer = Math.min( + token.balanceOf(address(this)), + amount + ); + token.safeTransfer(to, amountToTransfer); - emit Withdrawal(msg.sender, to, amount); + emit Withdrawal(msg.sender, to, amountToTransfer); } /// @notice withdraw all PCV from Maple @@ -149,10 +169,10 @@ contract MaplePCVDeposit is PCVDeposit { /// if lending losses were taken, receive less than amount pool.withdraw(amount); /// call pool and withdraw entire balance - uint256 tokenBalance = IERC20(token).balanceOf(address(this)); - token.safeTransfer(to, tokenBalance); + uint256 amountToTransfer = IERC20(token).balanceOf(address(this)); + token.safeTransfer(to, amountToTransfer); - emit Withdrawal(msg.sender, to, tokenBalance); + emit Withdrawal(msg.sender, to, amountToTransfer); } /// @notice permissionless function to harvest rewards before withdraw @@ -164,6 +184,12 @@ contract MaplePCVDeposit is PCVDeposit { /// ---------- Sad Path APIs ---------- + /// Assume that using these functions will likely + /// break all happy path functions + /// Only use these function in an emergency situation + + /// ----------------------------------- + /// @notice get rewards and unstake from rewards contract /// breaks functionality of happy path withdraw functions function exit() external onlyPCVController { @@ -188,8 +214,11 @@ contract MaplePCVDeposit is PCVDeposit { pool.withdraw(amount); /// withdraw min between balance and amount as losses could be sustained in venue /// causing less than amt to be withdrawn - amount = Math.min(token.balanceOf(address(this)), amount); - token.safeTransfer(to, amount); + uint256 amountToTransfer = Math.min( + token.balanceOf(address(this)), + amount + ); + token.safeTransfer(to, amountToTransfer); } /// @notice struct to pack calldata and targets for an emergency action diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index abfbab804..e26cb4f79 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -13,7 +13,11 @@ import {PCVDeposit} from "../PCVDeposit.sol"; /// Implements the PCV Deposit interface to deposit and withdraw funds in Morpho /// Liquidity profile of Morpho for this deposit is fully liquid for USDC and DAI /// because the incentivized rates are higher than the P2P rate. -/// Only for depositing USDC and DAI. USDT is not in scope +/// Only for depositing USDC and DAI. USDT is not in scope. +/// @dev approves the Morpho Deposit to spend this PCV deposit's token, +/// and then calls supply on Morpho, which pulls the underlying token to Morpho, +/// drawing down on the approved amount to be spent, +/// and then giving this PCV Deposit mTokens in exchange for the underlying contract MorphoCompoundPCVDeposit is PCVDeposit { using SafeERC20 for IERC20; @@ -84,6 +88,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit { _withdraw(to, amount); } + /// @notice helper function to avoid repeated code in withdraw and withdrawAll function _withdraw(address to, uint256 amount) private { IMorpho(MORPHO).withdraw(cToken, amount); IERC20(token).safeTransfer(to, amount); From 1ce31297e8847f872e5b322d4a23de88ed1410e5 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 10 Oct 2022 18:30:13 -0700 Subject: [PATCH 36/84] license --- contracts/pcv/maple/MaplePCVDeposit.sol | 1 + contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol | 1 + 2 files changed, 2 insertions(+) diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index 4f4c1308c..95c64e93f 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity =0.8.13; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index e26cb4f79..04646acd5 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity =0.8.13; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; From 248a693e751c77d1ecf1d5eb439f8f95f98f60d6 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 10 Oct 2022 18:32:34 -0700 Subject: [PATCH 37/84] import visual cleanup --- contracts/pcv/maple/MaplePCVDeposit.sol | 1 + contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index 95c64e93f..648df45ee 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -4,6 +4,7 @@ pragma solidity =0.8.13; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + import {IPool} from "./IPool.sol"; import {CoreRef} from "../../refs/CoreRef.sol"; import {PCVDeposit} from "../PCVDeposit.sol"; diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index 04646acd5..3a4d18d8a 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -6,9 +6,9 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ILens} from "./ILens.sol"; import {IMorpho} from "./IMorpho.sol"; -import {ICompoundOracle, ICToken} from "./ICompound.sol"; import {CoreRef} from "../../refs/CoreRef.sol"; import {PCVDeposit} from "../PCVDeposit.sol"; +import {ICompoundOracle, ICToken} from "./ICompound.sol"; /// @notice PCV Deposit for Morpho-Compound V2. /// Implements the PCV Deposit interface to deposit and withdraw funds in Morpho From 97b92552d2cba0036ce40340ff74f67059f7f185 Mon Sep 17 00:00:00 2001 From: Elliot Date: Wed, 12 Oct 2022 22:24:55 -0700 Subject: [PATCH 38/84] PCV Deposit harvest emits amount harvested --- contracts/pcv/IPCVDeposit.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/pcv/IPCVDeposit.sol b/contracts/pcv/IPCVDeposit.sol index 30d208577..7f2d645e1 100644 --- a/contracts/pcv/IPCVDeposit.sol +++ b/contracts/pcv/IPCVDeposit.sol @@ -29,7 +29,7 @@ interface IPCVDeposit is IPCVDepositBalances { uint256 _amount ); - event Harvest(); + event Harvest(uint256 amount); // ----------- State changing api ----------- From 3358ab0d2adf8aab7defb60764274549b946fc5b Mon Sep 17 00:00:00 2001 From: Elliot Date: Wed, 12 Oct 2022 22:27:15 -0700 Subject: [PATCH 39/84] Add amount emitted from harvest in deposits --- contracts/pcv/maple/MaplePCVDeposit.sol | 10 +++++++++- contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol | 5 +++-- .../integration/IntegrationTestMaplePCVDeposit.t.sol | 7 +++++++ .../test/integration/fixtures/MainnetAddresses.sol | 11 +++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index 648df45ee..43697a7b1 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -45,6 +45,9 @@ contract MaplePCVDeposit is PCVDeposit { /// @notice reference to the underlying token IERC20 public immutable token; + /// @notice reference to the Maple token + IERC20 public immutable rewardsToken; + /// @notice scaling factor for USDC /// @dev hardcoded to use USDC decimals as this is the only /// supplied asset Volt Protocol will support @@ -67,6 +70,7 @@ contract MaplePCVDeposit is PCVDeposit { ); pool = IPool(_pool); mplRewards = IMplRewards(_mplRewards); + rewardsToken = IERC20(IMplRewards(_mplRewards).rewardsToken()); } /// @notice return the amount of funds this contract owns in USDC @@ -179,9 +183,13 @@ contract MaplePCVDeposit is PCVDeposit { /// @notice permissionless function to harvest rewards before withdraw function harvest() external { + uint256 preHarvestBalance = rewardsToken.balanceOf(address(this)); + mplRewards.getReward(); - emit Harvest(); + uint256 postHarvestBalance = rewardsToken.balanceOf(address(this)); + + emit Harvest(postHarvestBalance - preHarvestBalance); } /// ---------- Sad Path APIs ---------- diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index 3a4d18d8a..236a116b4 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -102,8 +102,9 @@ contract MorphoCompoundPCVDeposit is PCVDeposit { address[] memory cTokens = new address[](1); cTokens[0] = cToken; - MORPHO.claimRewards(cTokens, false); + /// set swap comp to morpho flag false to claim comp rewards + uint256 claimedAmount = MORPHO.claimRewards(cTokens, false); - emit Harvest(); + emit Harvest(claimedAmount); } } diff --git a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol index 101e17650..baf7f3efc 100644 --- a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol @@ -52,6 +52,9 @@ contract IntegrationTestMaplePCVDeposit is DSTest { address public constant mapleOwner = 0xd6d4Bcde6c816F17889f1Dd3000aF0261B03a196; + address public constant mapleToken = + 0x33349B282065b0284d756F0577FB39c158F935e6; + function setUp() public { usdcDeposit = new MaplePCVDeposit(address(core), maplePool, mplRewards); @@ -71,6 +74,10 @@ contract IntegrationTestMaplePCVDeposit is DSTest { function testSetup() public { assertEq(address(usdcDeposit.core()), address(core)); + assertEq( + address(usdcDeposit.rewardsToken()), + MainnetAddresses.MPL_TOKEN + ); assertEq(usdcDeposit.balanceReportedIn(), address(usdc)); assertEq(address(usdcDeposit.pool()), maplePool); assertEq(address(usdcDeposit.mplRewards()), mplRewards); diff --git a/contracts/test/integration/fixtures/MainnetAddresses.sol b/contracts/test/integration/fixtures/MainnetAddresses.sol index 53a99bd96..7be51b72d 100644 --- a/contracts/test/integration/fixtures/MainnetAddresses.sol +++ b/contracts/test/integration/fixtures/MainnetAddresses.sol @@ -154,4 +154,15 @@ library MainnetAddresses { address public constant GLOBAL_RATE_LIMITED_MINTER = 0x87945f59E008aDc9ed6210a8e061f009d6ace718; + + // ---------- MAPLE ADDRESSES ---------- + + address public constant MPL_TOKEN = + 0x33349B282065b0284d756F0577FB39c158F935e6; + + address public constant MPL_ORTHOGONAL_POOL = + 0xFeBd6F15Df3B73DC4307B1d7E65D46413e710C27; + + address public constant MPL_ORTHOGONAL_REWARDS = + 0x7869D7a3B074b5fa484dc04798E254c9C06A5e90; } From c56434f8cced654dc4733a3e3e870fb4a122d367 Mon Sep 17 00:00:00 2001 From: Elliot Date: Wed, 12 Oct 2022 23:08:10 -0700 Subject: [PATCH 40/84] attribution to MakerDAO multicall --- contracts/pcv/maple/MaplePCVDeposit.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index 43697a7b1..9564c8a6c 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -231,6 +231,9 @@ contract MaplePCVDeposit is PCVDeposit { token.safeTransfer(to, amountToTransfer); } + /// inspired by MakerDAO Multicall: + /// https://github.com/makerdao/multicall/blob/master/src/Multicall.sol + /// @notice struct to pack calldata and targets for an emergency action struct Call { address target; From 649ec46ff8853e08bedd27792ddb6850692594d9 Mon Sep 17 00:00:00 2001 From: Elliot Date: Sun, 16 Oct 2022 23:26:43 -0700 Subject: [PATCH 41/84] If rewards contract paused, do not stake --- contracts/pcv/maple/MaplePCVDeposit.sol | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index 9564c8a6c..8f55c920c 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -107,10 +107,16 @@ contract MaplePCVDeposit is PCVDeposit { token.approve(address(pool), amount); pool.deposit(amount); - /// stake pool FDT for MPL rewards - uint256 scaledDepositAmount = amount * scalingFactor; - pool.increaseCustodyAllowance(address(mplRewards), scaledDepositAmount); - mplRewards.stake(scaledDepositAmount); + /// only stake if not paused + if (!mplRewards.paused()) { + /// stake pool FDT for MPL rewards + uint256 scaledDepositAmount = amount * scalingFactor; + pool.increaseCustodyAllowance( + address(mplRewards), + scaledDepositAmount + ); + mplRewards.stake(scaledDepositAmount); + } emit Deposit(msg.sender, amount); } From 6de505694baa3ae14d47385d93914bfc4d820053 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 17 Oct 2022 18:09:07 -0700 Subject: [PATCH 42/84] =?UTF-8?q?Morpho=20=F0=9F=A6=8B=20&=20Maple=20?= =?UTF-8?q?=F0=9F=8D=81=20Slither=20Run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- slither/morpho-maple-slither.txt | 105 ++++++++++++++++++ .../slitheroutput.txt | 0 2 files changed, 105 insertions(+) create mode 100644 slither/morpho-maple-slither.txt rename slitheroutput.txt => slither/slitheroutput.txt (100%) diff --git a/slither/morpho-maple-slither.txt b/slither/morpho-maple-slither.txt new file mode 100644 index 000000000..a26496b9f --- /dev/null +++ b/slither/morpho-maple-slither.txt @@ -0,0 +1,105 @@ +MaplePCVDeposit.deposit() (contracts/pcv/maple/MaplePCVDeposit.sol#99-122) uses a dangerous strict equality: + - amount == 0 (contracts/pcv/maple/MaplePCVDeposit.sol#101) +MorphoCompoundPCVDeposit.deposit() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#61-76) uses a dangerous strict equality: + - amount == 0 (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#63) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities +Response: Strict equalities can be dangerous in the way described in the slither detector documentation. +However, if amount is 0 in either Maple or Morpho PCV Deposit, it is better to save gas and not execute further opcodes. +There is no unsafe behavior that is possible as a result of doing this strict equality that amount is 0. + +MaplePCVDeposit.deposit() (contracts/pcv/maple/MaplePCVDeposit.sol#99-122) ignores return value by token.approve(address(pool),amount) (contracts/pcv/maple/MaplePCVDeposit.sol#107) +MorphoCompoundPCVDeposit.deposit() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#61-76) ignores return value by IERC20(token).approve(address(MORPHO),amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#68) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return +Response: USDC returns true and does some validation that the addresses are incorrect. +As long as the validations succeed, the function will return true. If the validations fail, +the call will revert. +DAI returns true and has no validation checks, which means the only way the approve call can fail +is if it is out of gas. + +MaplePCVDeposit.constructor(address,address,address)._core (contracts/pcv/maple/MaplePCVDeposit.sol#61) shadows: + - CoreRef._core (contracts/refs/CoreRef.sol#11) (state variable) +MorphoCompoundPCVDeposit.constructor(address,address)._core (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#39) shadows: + - CoreRef._core (contracts/refs/CoreRef.sol#11) (state variable) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing +Response: Author acknowledges the issue, will not fix. + +MorphoCompoundPCVDeposit.constructor(address,address)._cToken (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#39) lacks a zero-check on : + - cToken = _cToken (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#40) + - token = ICToken(_cToken).underlying() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#41) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation +Response: Author acknowledges the issue, will not fix because integration tests will validate the parameter is set correctly. + +MaplePCVDeposit.emergencyAction(MaplePCVDeposit.Call[]) (contracts/pcv/maple/MaplePCVDeposit.sol#252-265) has external calls inside a loop: (success,returned) = calls[i].target.call(calls[i].callData) (contracts/pcv/maple/MaplePCVDeposit.sol#259-261) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop +Response: DoS attacks are not a valid vector when sender is a trusted governor and the function mutates no internal contract state. + +Reentrancy in MorphoCompoundPCVDeposit._withdraw(address,uint256) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#93-98): + External calls: + - IMorpho(MORPHO).withdraw(cToken,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#94) + - IERC20(token).safeTransfer(to,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#95) + Event emitted after the call(s): + - Withdrawal(msg.sender,to,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#97) +Reentrancy in MaplePCVDeposit.deposit() (contracts/pcv/maple/MaplePCVDeposit.sol#99-122): + External calls: + - token.approve(address(pool),amount) (contracts/pcv/maple/MaplePCVDeposit.sol#107) + - pool.deposit(amount) (contracts/pcv/maple/MaplePCVDeposit.sol#108) + - pool.increaseCustodyAllowance(address(mplRewards),scaledDepositAmount) (contracts/pcv/maple/MaplePCVDeposit.sol#114-117) + - mplRewards.stake(scaledDepositAmount) (contracts/pcv/maple/MaplePCVDeposit.sol#118) + Event emitted after the call(s): + - Deposit(msg.sender,amount) (contracts/pcv/maple/MaplePCVDeposit.sol#121) +Reentrancy in MorphoCompoundPCVDeposit.deposit() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#61-76): + External calls: + - IERC20(token).approve(address(MORPHO),amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#68) + - MORPHO.supply(cToken,address(this),amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#69-73) + Event emitted after the call(s): + - Deposit(msg.sender,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#75) +Reentrancy in MaplePCVDeposit.harvest() (contracts/pcv/maple/MaplePCVDeposit.sol#191-199): + External calls: + - mplRewards.getReward() (contracts/pcv/maple/MaplePCVDeposit.sol#194) + Event emitted after the call(s): + - Harvest(postHarvestBalance - preHarvestBalance) (contracts/pcv/maple/MaplePCVDeposit.sol#198) +Reentrancy in MorphoCompoundPCVDeposit.harvest() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#101-109): + External calls: + - claimedAmount = MORPHO.claimRewards(cTokens,false) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#106) + Event emitted after the call(s): + - Harvest(claimedAmount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#108) +Reentrancy in MaplePCVDeposit.withdraw(address,uint256) (contracts/pcv/maple/MaplePCVDeposit.sol#143-170): + External calls: + - mplRewards.getReward() (contracts/pcv/maple/MaplePCVDeposit.sol#150) + - mplRewards.withdraw(scaledWithdrawAmount) (contracts/pcv/maple/MaplePCVDeposit.sol#156) + - pool.withdraw(amount) (contracts/pcv/maple/MaplePCVDeposit.sol#159) + - token.safeTransfer(to,amountToTransfer) (contracts/pcv/maple/MaplePCVDeposit.sol#167) + Event emitted after the call(s): + - Withdrawal(msg.sender,to,amountToTransfer) (contracts/pcv/maple/MaplePCVDeposit.sol#169) +Reentrancy in MaplePCVDeposit.withdrawAll(address) (contracts/pcv/maple/MaplePCVDeposit.sol#174-188): + External calls: + - mplRewards.exit() (contracts/pcv/maple/MaplePCVDeposit.sol#176) + - pool.withdraw(amount) (contracts/pcv/maple/MaplePCVDeposit.sol#182) + - token.safeTransfer(to,amountToTransfer) (contracts/pcv/maple/MaplePCVDeposit.sol#185) + Event emitted after the call(s): + - Withdrawal(msg.sender,to,amountToTransfer) (contracts/pcv/maple/MaplePCVDeposit.sol#187) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3 +Response: Reentrancy is only an issue if it allows theft of funds and updating of state incorrectly in an intermediate state. +None of the listed functions mutate any state internal to the contract. If there was a collateralization oracle in the system, +then these issues would be worth investigating further and ensuring state could not get mixed up in a way that allowed an +attacker to manipulate the target price of Volt. + +Pragma version=0.8.13 (contracts/pcv/maple/MaplePCVDeposit.sol#2) necessitates a version too recent to be trusted. Consider deploying with 0.6.12/0.7.6/0.8.7 +Pragma version^0.8.0 (contracts/pcv/morpho/ICompound.sol#2) allows old versions +Pragma version^0.8.0 (contracts/pcv/morpho/ILens.sol#2) allows old versions +Pragma version^0.8.0 (contracts/pcv/morpho/IMorpho.sol#2) allows old versions +Pragma version=0.8.13 (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#2) necessitates a version too recent to be trusted. Consider deploying with 0.6.12/0.7.6/0.8.7 +solc-0.8.13 is not recommended for deployment +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity +Response: Author has reviewed the solidity 0.8.13 list of known bugs https://docs.soliditylang.org/en/v0.8.13/bugs.html +and not found any high issues that would lead to incorrect functioning of the smart contracts. + +Low level call in MaplePCVDeposit.emergencyAction(MaplePCVDeposit.Call[]) (contracts/pcv/maple/MaplePCVDeposit.sol#252-265): + - (success,returned) = calls[i].target.call(calls[i].callData) (contracts/pcv/maple/MaplePCVDeposit.sol#259-261) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls +Response: This low level call is intentional and can be used during an emergency action if funds need to be moved +out of Maple in a way the author has not yet conceived of. + +Constant MaplePCVDeposit.scalingFactor (contracts/pcv/maple/MaplePCVDeposit.sol#54) is not in UPPER_CASE_WITH_UNDERSCORES +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions +Response: This is a style issue and will not be fixed. diff --git a/slitheroutput.txt b/slither/slitheroutput.txt similarity index 100% rename from slitheroutput.txt rename to slither/slitheroutput.txt From e83c19555fb3af509e23a96602f1aa3d492acbc6 Mon Sep 17 00:00:00 2001 From: Elliot <34463580+ElliotFriedman@users.noreply.github.com> Date: Tue, 18 Oct 2022 13:14:13 -0400 Subject: [PATCH 43/84] Update contracts/pcv/morpho/ILens.sol Co-authored-by: Romain Milon --- contracts/pcv/morpho/ILens.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/pcv/morpho/ILens.sol b/contracts/pcv/morpho/ILens.sol index fb7720c35..d11c4c110 100644 --- a/contracts/pcv/morpho/ILens.sol +++ b/contracts/pcv/morpho/ILens.sol @@ -204,7 +204,11 @@ interface ILens { function getAverageSupplyRatePerBlock(address _poolToken) external view - returns (uint256); + returns ( + uint256 avgSupplyRatePerBlock, + uint256 p2pSupplyAmount, + uint256 poolSupplyAmount + ); function getAverageBorrowRatePerBlock(address _poolToken) external From 6ca9cc543ec64a68fa0734b906e6c1aa4bc6ebe3 Mon Sep 17 00:00:00 2001 From: Elliot <34463580+ElliotFriedman@users.noreply.github.com> Date: Tue, 18 Oct 2022 13:14:31 -0400 Subject: [PATCH 44/84] Update contracts/pcv/morpho/ILens.sol Co-authored-by: Romain Milon --- contracts/pcv/morpho/ILens.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/pcv/morpho/ILens.sol b/contracts/pcv/morpho/ILens.sol index d11c4c110..defa4bc91 100644 --- a/contracts/pcv/morpho/ILens.sol +++ b/contracts/pcv/morpho/ILens.sol @@ -213,7 +213,11 @@ interface ILens { function getAverageBorrowRatePerBlock(address _poolToken) external view - returns (uint256); + returns ( + uint256 avgBorrowRatePerBlock, + uint256 p2pBorrowAmount, + uint256 poolBorrowAmount + ); function getRatesPerBlock(address _poolToken) external From 49995838ef48de5e351d399b157fb2858b010104 Mon Sep 17 00:00:00 2001 From: Elliot Date: Tue, 18 Oct 2022 10:25:56 -0700 Subject: [PATCH 45/84] Update comment and constructor params to allow PCV deposit to hold ceth --- contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index 236a116b4..ce04ab895 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -18,7 +18,12 @@ import {ICompoundOracle, ICToken} from "./ICompound.sol"; /// @dev approves the Morpho Deposit to spend this PCV deposit's token, /// and then calls supply on Morpho, which pulls the underlying token to Morpho, /// drawing down on the approved amount to be spent, -/// and then giving this PCV Deposit mTokens in exchange for the underlying +/// and then giving this PCV Deposit credits on Morpho in exchange for the underlying +/// @dev PCV Guardian functions withdrawERC20ToSafeAddress and withdrawAllERC20ToSafeAddress +/// will not work with removing Morpho Tokens on the Morpho PCV Deposit because Morpho +/// has no concept of mTokens. This means if the contract is paused, or an issue is +/// surfaced in Morpho and liquidity is locked, Volt will need to rely on social +/// coordination with the Morpho team to recover funds. contract MorphoCompoundPCVDeposit is PCVDeposit { using SafeERC20 for IERC20; @@ -36,9 +41,13 @@ contract MorphoCompoundPCVDeposit is PCVDeposit { /// used to inform morpho about the desired market to supply liquidity address public immutable cToken; - constructor(address _core, address _cToken) CoreRef(_core) { + constructor( + address _core, + address _cToken, + address _underlying + ) CoreRef(_core) { cToken = _cToken; - token = ICToken(_cToken).underlying(); + token = _underlying; } /// @notice Returns the distribution of assets supplied by this contract through Morpho-Compound. From cb8ccd0e135989b07f948c7af6bd7970887b100b Mon Sep 17 00:00:00 2001 From: Elliot Date: Tue, 18 Oct 2022 10:26:41 -0700 Subject: [PATCH 46/84] fix integration tests and governance proposal --- .../IntegrationTestMorphoCompoundPCVDeposit.t.sol | 6 ++++-- contracts/test/integration/vip/vip14.sol | 9 +++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol index 0558b080c..054324149 100644 --- a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol @@ -46,11 +46,13 @@ contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { function setUp() public { daiDeposit = new MorphoCompoundPCVDeposit( address(core), - MainnetAddresses.CDAI + MainnetAddresses.CDAI, + MainnetAddresses.DAI ); usdcDeposit = new MorphoCompoundPCVDeposit( address(core), - MainnetAddresses.CUSDC + MainnetAddresses.CUSDC, + MainnetAddresses.USDC ); vm.label(address(daiDeposit), "Morpho DAI Compound PCV Deposit"); diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index 2a962cc53..9d5b666e7 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -78,10 +78,15 @@ contract vip14 is DSTest, IVIP { if (block.chainid != 1) return; /// keep ci pipeline happy mapleDeposit = new MaplePCVDeposit(core, maplePool, mplRewards); - daiDeposit = new MorphoCompoundPCVDeposit(core, MainnetAddresses.CDAI); + daiDeposit = new MorphoCompoundPCVDeposit( + core, + MainnetAddresses.CDAI, + MainnetAddresses.DAI + ); usdcDeposit = new MorphoCompoundPCVDeposit( core, - MainnetAddresses.CUSDC + MainnetAddresses.CUSDC, + MainnetAddresses.USDC ); router = new CompoundPCVRouter( From 1c06df377e8d1012deb4c01aa0752ca20d0b3c6c Mon Sep 17 00:00:00 2001 From: Elliot Date: Tue, 18 Oct 2022 10:27:19 -0700 Subject: [PATCH 47/84] typescript deployment script upgraded to pass 3rd parameter into morpho compound pcv deposits --- proposals/dao/vip_14.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/proposals/dao/vip_14.ts b/proposals/dao/vip_14.ts index 519809a2f..4ed55f065 100644 --- a/proposals/dao/vip_14.ts +++ b/proposals/dao/vip_14.ts @@ -41,10 +41,18 @@ const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: Named const morphoPCVDepositFactory = await ethers.getContractFactory('MorphoCompoundPCVDeposit'); const compoundPCVRouterFactory = await ethers.getContractFactory('CompoundPCVRouter'); - const daiMorphoCompoundPCVDeposit = await morphoPCVDepositFactory.deploy(addresses.core, addresses.cDai); + const daiMorphoCompoundPCVDeposit = await morphoPCVDepositFactory.deploy( + addresses.core, + addresses.cDai, + addresses.dai + ); await daiMorphoCompoundPCVDeposit.deployed(); - const usdcMorphoCompoundPCVDeposit = await morphoPCVDepositFactory.deploy(addresses.core, addresses.cUsdc); + const usdcMorphoCompoundPCVDeposit = await morphoPCVDepositFactory.deploy( + addresses.core, + addresses.cUsdc, + addresses.usdc + ); await usdcMorphoCompoundPCVDeposit.deployed(); const morphoCompoundPCVRouter = await compoundPCVRouterFactory.deploy( From 79a69816d994614c366fe79ecff9ae66625ee3c4 Mon Sep 17 00:00:00 2001 From: Elliot Date: Tue, 18 Oct 2022 11:02:29 -0700 Subject: [PATCH 48/84] remove pausing logic from maple smart contracts --- contracts/pcv/maple/MaplePCVDeposit.sol | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index 8f55c920c..9564c8a6c 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -107,16 +107,10 @@ contract MaplePCVDeposit is PCVDeposit { token.approve(address(pool), amount); pool.deposit(amount); - /// only stake if not paused - if (!mplRewards.paused()) { - /// stake pool FDT for MPL rewards - uint256 scaledDepositAmount = amount * scalingFactor; - pool.increaseCustodyAllowance( - address(mplRewards), - scaledDepositAmount - ); - mplRewards.stake(scaledDepositAmount); - } + /// stake pool FDT for MPL rewards + uint256 scaledDepositAmount = amount * scalingFactor; + pool.increaseCustodyAllowance(address(mplRewards), scaledDepositAmount); + mplRewards.stake(scaledDepositAmount); emit Deposit(msg.sender, amount); } From 91a817be027b1b68813f1813179a6d5a2a5e8782 Mon Sep 17 00:00:00 2001 From: Elliot Date: Tue, 18 Oct 2022 13:02:13 -0700 Subject: [PATCH 49/84] scalingFactor snakecase, emit withdrawal event in sad path api, add additional comments in mpl deposit --- contracts/mock/MockMaplePool.sol | 9 ----- contracts/pcv/maple/MaplePCVDeposit.sol | 40 +++++++++++-------- .../IntegrationTestMaplePCVDeposit.t.sol | 8 ---- 3 files changed, 23 insertions(+), 34 deletions(-) delete mode 100644 contracts/mock/MockMaplePool.sol diff --git a/contracts/mock/MockMaplePool.sol b/contracts/mock/MockMaplePool.sol deleted file mode 100644 index 163be2d6b..000000000 --- a/contracts/mock/MockMaplePool.sol +++ /dev/null @@ -1,9 +0,0 @@ -pragma solidity =0.8.13; - -contract MockMaplePool { - address public liquidityAsset; - - constructor(address _liquidityAsset) { - liquidityAsset = _liquidityAsset; - } -} diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index 9564c8a6c..1d741376b 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -12,7 +12,7 @@ import {IMplRewards} from "./IMplRewards.sol"; /// @notice PCV Deposit for Maple /// Allows depositing only by privileged role to prevent lockup period being extended by griefers -/// Can only deposit USDC in this MAPLE PCV deposit due to scalingFactor being hardcoded +/// Can only deposit USDC in this MAPLE PCV deposit due to the scaling factor being hardcoded /// and underlying token is enforced as USDC. /// @notice NEVER CONNECT THIS DEPOSIT INTO THE ALLOCATOR OR OTHER AUTOMATED PCV @@ -43,7 +43,8 @@ contract MaplePCVDeposit is PCVDeposit { IMplRewards public immutable mplRewards; /// @notice reference to the underlying token - IERC20 public immutable token; + IERC20 public immutable token = + IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); /// @notice reference to the Maple token IERC20 public immutable rewardsToken; @@ -51,9 +52,8 @@ contract MaplePCVDeposit is PCVDeposit { /// @notice scaling factor for USDC /// @dev hardcoded to use USDC decimals as this is the only /// supplied asset Volt Protocol will support - uint256 public constant scalingFactor = 1e12; + uint256 public constant SCALING_FACTOR = 1e12; - /// @notice fetch underlying asset by calling pool and getting liquidity asset /// @param _core reference to the Core contract /// @param _pool Maple Pool contract /// @param _mplRewards Maple Rewards contract @@ -62,12 +62,6 @@ contract MaplePCVDeposit is PCVDeposit { address _pool, address _mplRewards ) CoreRef(_core) { - token = IERC20(IPool(_pool).liquidityAsset()); - /// enforce underlying token is USDC - require( - address(token) == 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, - "MaplePCVDeposit: Underlying not USDC" - ); pool = IPool(_pool); mplRewards = IMplRewards(_mplRewards); rewardsToken = IERC20(IMplRewards(_mplRewards).rewardsToken()); @@ -80,7 +74,7 @@ contract MaplePCVDeposit is PCVDeposit { uint256 rawBalance = pool.balanceOf(address(this)) + pool.accumulativeFundsOf(address(this)) - pool.recognizableLossesOf(address(this)); - return rawBalance / scalingFactor; + return rawBalance / SCALING_FACTOR; } /// @notice return the underlying token denomination for this deposit @@ -108,7 +102,7 @@ contract MaplePCVDeposit is PCVDeposit { pool.deposit(amount); /// stake pool FDT for MPL rewards - uint256 scaledDepositAmount = amount * scalingFactor; + uint256 scaledDepositAmount = amount * SCALING_FACTOR; pool.increaseCustodyAllowance(address(mplRewards), scaledDepositAmount); mplRewards.stake(scaledDepositAmount); @@ -139,17 +133,21 @@ contract MaplePCVDeposit is PCVDeposit { override onlyPCVController { - uint256 scaledWithdrawAmount = amount * scalingFactor; + /// Rewards + + uint256 scaledWithdrawAmount = amount * SCALING_FACTOR; mplRewards.getReward(); /// get MPL rewards + mplRewards.withdraw(scaledWithdrawAmount); /// decreases allowance + + /// Principal + + /// withdraw from the pool /// this call will withdraw amount of principal requested, and then send /// over any accrued interest. /// expected behavior is that this contract /// receives either amount of USDC, or amount of USDC + interest accrued /// if lending losses were taken, receive less than amount - mplRewards.withdraw(scaledWithdrawAmount); /// decreases allowance - - /// withdraw from the pool pool.withdraw(amount); /// withdraw min between balance and amount as losses could be sustained in venue @@ -166,8 +164,14 @@ contract MaplePCVDeposit is PCVDeposit { /// @notice withdraw all PCV from Maple /// @param to destination after funds are withdrawn from venue function withdrawAll(address to) external onlyPCVController { - uint256 amount = balance(); + /// Rewards + mplRewards.exit(); /// unstakes from Maple reward contract and claims rewards + + /// Principal + + uint256 amount = balance(); + /// this call will withdraw all principal, /// then send over any accrued interest. /// expected behavior is that this contract @@ -229,6 +233,8 @@ contract MaplePCVDeposit is PCVDeposit { amount ); token.safeTransfer(to, amountToTransfer); + + emit Withdrawal(msg.sender, to, amountToTransfer); } /// inspired by MakerDAO Multicall: diff --git a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol index baf7f3efc..ba67fd1aa 100644 --- a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol @@ -13,7 +13,6 @@ import {IDSSPSM} from "../../pcv/maker/IDSSPSM.sol"; import {Constants} from "../../Constants.sol"; import {PCVGuardian} from "../../pcv/PCVGuardian.sol"; import {IMplRewards} from "../../pcv/maple/IMplRewards.sol"; -import {MockMaplePool} from "../../mock/MockMaplePool.sol"; import {MaplePCVDeposit} from "../../pcv/maple/MaplePCVDeposit.sol"; import {MainnetAddresses} from "./fixtures/MainnetAddresses.sol"; import {PegStabilityModule} from "../../peg/PegStabilityModule.sol"; @@ -85,13 +84,6 @@ contract IntegrationTestMaplePCVDeposit is DSTest { assertEq(usdcDeposit.balance(), targetUsdcBalance); } - function testDeployFailsNotUSDCUnderlying() public { - MockMaplePool mockPool = new MockMaplePool(address(this)); - - vm.expectRevert("MaplePCVDeposit: Underlying not USDC"); - new MaplePCVDeposit(address(core), address(mockPool), mplRewards); - } - function testWithdraw() public { uint256 rewardRate = IMplRewards(mplRewards).rewardRate(); vm.prank(mapleOwner); From 8f6b3d634137c56e410ba690cb4559ab33941b09 Mon Sep 17 00:00:00 2001 From: Elliot Date: Tue, 18 Oct 2022 13:59:27 -0700 Subject: [PATCH 50/84] remove withdrawAll function from maple pcv deposit --- contracts/pcv/maple/MaplePCVDeposit.sol | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol index 1d741376b..005cb7f41 100644 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ b/contracts/pcv/maple/MaplePCVDeposit.sol @@ -161,30 +161,6 @@ contract MaplePCVDeposit is PCVDeposit { emit Withdrawal(msg.sender, to, amountToTransfer); } - /// @notice withdraw all PCV from Maple - /// @param to destination after funds are withdrawn from venue - function withdrawAll(address to) external onlyPCVController { - /// Rewards - - mplRewards.exit(); /// unstakes from Maple reward contract and claims rewards - - /// Principal - - uint256 amount = balance(); - - /// this call will withdraw all principal, - /// then send over any accrued interest. - /// expected behavior is that this contract - /// receives balance amount of USDC, or amount of USDC + interest accrued - /// if lending losses were taken, receive less than amount - pool.withdraw(amount); /// call pool and withdraw entire balance - - uint256 amountToTransfer = IERC20(token).balanceOf(address(this)); - token.safeTransfer(to, amountToTransfer); - - emit Withdrawal(msg.sender, to, amountToTransfer); - } - /// @notice permissionless function to harvest rewards before withdraw function harvest() external { uint256 preHarvestBalance = rewardsToken.balanceOf(address(this)); From 397642b0c39c05550296025275b5a04715555f22 Mon Sep 17 00:00:00 2001 From: Elliot Date: Tue, 18 Oct 2022 14:00:06 -0700 Subject: [PATCH 51/84] remove unnecessary cast --- .../pcv/morpho/MorphoCompoundPCVDeposit.sol | 2 +- .../IntegrationTestMaplePCVDeposit.t.sol | 22 ------------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index ce04ab895..ef6269fc3 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -100,7 +100,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit { /// @notice helper function to avoid repeated code in withdraw and withdrawAll function _withdraw(address to, uint256 amount) private { - IMorpho(MORPHO).withdraw(cToken, amount); + MORPHO.withdraw(cToken, amount); IERC20(token).safeTransfer(to, amount); emit Withdrawal(msg.sender, to, amount); diff --git a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol index ba67fd1aa..1a11db448 100644 --- a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol @@ -147,23 +147,6 @@ contract IntegrationTestMaplePCVDeposit is DSTest { _testWithdraw(amount); } - function testWithdrawAll() public { - _setRewardsAndWarp(); - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.signalIntentToWithdraw(); - - vm.warp(block.timestamp + cooldownPeriod); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.withdrawAll(address(this)); - - uint256 mplBalance = maple.balanceOf(address(usdcDeposit)); - - assertEq(usdcDeposit.balance(), 0); - assertEq(usdc.balanceOf(address(this)), targetUsdcBalance); - assertTrue(mplBalance != 0); - } - function testSignalWithdrawPCVControllerSucceeds() public { uint256 blockTimestamp = 10_000; @@ -316,11 +299,6 @@ contract IntegrationTestMaplePCVDeposit is DSTest { usdcDeposit.withdrawFromRewardsContract(); } - function testWithdrawAllNonPCVControllerFails() public { - vm.expectRevert("CoreRef: Caller is not a PCV controller"); - usdcDeposit.withdrawAll(address(this)); - } - function testExitPCVControllerFails() public { vm.expectRevert("CoreRef: Caller is not a PCV controller"); usdcDeposit.exit(); From a2fcf0a03beb5e98ba95c866aee9dd21399a5c32 Mon Sep 17 00:00:00 2001 From: Elliot Date: Wed, 19 Oct 2022 13:18:47 -0700 Subject: [PATCH 52/84] remove Maple from PR --- contracts/pcv/maple/IMplRewards.sol | 58 ---- contracts/pcv/maple/IPool.sol | 100 ------ contracts/pcv/maple/MaplePCVDeposit.sol | 242 ------------- .../IntegrationTestMaplePCVDeposit.t.sol | 323 ------------------ contracts/test/integration/vip/vip14.sol | 22 +- 5 files changed, 3 insertions(+), 742 deletions(-) delete mode 100644 contracts/pcv/maple/IMplRewards.sol delete mode 100644 contracts/pcv/maple/IPool.sol delete mode 100644 contracts/pcv/maple/MaplePCVDeposit.sol delete mode 100644 contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol diff --git a/contracts/pcv/maple/IMplRewards.sol b/contracts/pcv/maple/IMplRewards.sol deleted file mode 100644 index 096bb799b..000000000 --- a/contracts/pcv/maple/IMplRewards.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity =0.8.13; - -interface IMplRewards { - // Views - function rewardsToken() external view returns (address); - - function stakingToken() external view returns (address); - - function periodFinish() external view returns (uint256); - - function rewardRate() external view returns (uint256); - - function rewardsDuration() external view returns (uint256); - - function lastUpdateTime() external view returns (uint256); - - function rewardPerTokenStored() external view returns (uint256); - - function lastPauseTime() external view returns (uint256); - - function paused() external view returns (bool); - - function userRewardPerTokenPaid(address) external view returns (uint256); - - function rewards(address) external view returns (uint256); - - function totalSupply() external view returns (uint256); - - function balanceOf(address) external view returns (uint256); - - function lastTimeRewardApplicable() external view returns (uint256); - - function rewardPerToken() external view returns (uint256); - - function earned(address) external view returns (uint256); - - function getRewardForDuration() external view returns (uint256); - - // Mutative - function stake(uint256) external; - - function withdraw(uint256) external; - - function getReward() external; - - function exit() external; - - function notifyRewardAmount(uint256) external; - - function updatePeriodFinish(uint256) external; - - function recoverERC20(address, uint256) external; - - function setRewardsDuration(uint256) external; - - function setPaused(bool) external; -} diff --git a/contracts/pcv/maple/IPool.sol b/contracts/pcv/maple/IPool.sol deleted file mode 100644 index ca9593eba..000000000 --- a/contracts/pcv/maple/IPool.sol +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity =0.8.13; - -/// @notice maple pool interface -interface IPool { - function balanceOf(address) external view returns (uint256); - - function recognizableLossesOf(address) external view returns (uint256); - - /** - @dev Returns the amount of funds that an account has earned in total. - @dev accumulativeFundsOf(_owner) = withdrawableFundsOf(_owner) + withdrawnFundsOf(_owner) - = (pointsPerShare * balanceOf(_owner) + pointsCorrection[_owner]) / pointsMultiplier - @param _owner The address of a token holder. - @return The amount of funds that `_owner` has earned in total. - */ - function accumulativeFundsOf(address _owner) - external - view - returns (uint256); - - function custodyAllowance(address _owner, address _custodian) - external - view - returns (uint256); - - /** - @dev Returns the total amount of funds a given address is able to withdraw currently. - @param owner Address of FDT holder. - @return A uint256 representing the available funds for a given account. - */ - function withdrawableFundsOf(address owner) external view returns (uint256); - - /** - @dev Handles Liquidity Providers depositing of Liquidity Asset into the LiquidityLocker, minting PoolFDTs. - @dev It emits a `DepositDateUpdated` event. - @dev It emits a `BalanceUpdated` event. - @dev It emits a `Cooldown` event. - @param amt Amount of Liquidity Asset to deposit. - */ - function deposit(uint256 amt) external; - - function increaseCustodyAllowance(address, uint256) external; - - function poolState() external view returns (uint256); - - function claim(address, address) external returns (uint256[7] memory); - - function fundLoan( - address, - address, - uint256 - ) external; - - /** - @dev Activates the cooldown period to withdraw. It can't be called if the account is not providing liquidity. - @dev It emits a `Cooldown` event. - */ - function intendToWithdraw() external; - - /** - @dev Handles Liquidity Providers withdrawing of Liquidity Asset from the LiquidityLocker, burning PoolFDTs. - @dev It emits two `BalanceUpdated` event. - @param amt Amount of Liquidity Asset to withdraw. - */ - function withdraw(uint256 amt) external; - - /** - @dev Withdraws all available funds for a FDT holder. - */ - function withdrawFunds() external; - - function liquidityAsset() external view returns (address); - - function liquidityLocker() external view returns (address); - - function stakeAsset() external view returns (address); - - function stakeLocker() external view returns (address); - - function stakingFee() external view returns (uint256); - - function principalOut() external view returns (uint256); - - function liquidityCap() external view returns (uint256); - - function lockupPeriod() external view returns (uint256); - - function depositDate(address) external view returns (uint256); - - function debtLockers(address, address) external view returns (address); - - function withdrawCooldown(address) external view returns (uint256); - - function setLiquidityCap(uint256) external; - - function cancelWithdraw() external; - - function isDepositAllowed(uint256) external view returns (bool); -} diff --git a/contracts/pcv/maple/MaplePCVDeposit.sol b/contracts/pcv/maple/MaplePCVDeposit.sol deleted file mode 100644 index 005cb7f41..000000000 --- a/contracts/pcv/maple/MaplePCVDeposit.sol +++ /dev/null @@ -1,242 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity =0.8.13; - -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; - -import {IPool} from "./IPool.sol"; -import {CoreRef} from "../../refs/CoreRef.sol"; -import {PCVDeposit} from "../PCVDeposit.sol"; -import {IMplRewards} from "./IMplRewards.sol"; - -/// @notice PCV Deposit for Maple -/// Allows depositing only by privileged role to prevent lockup period being extended by griefers -/// Can only deposit USDC in this MAPLE PCV deposit due to the scaling factor being hardcoded -/// and underlying token is enforced as USDC. - -/// @notice NEVER CONNECT THIS DEPOSIT INTO THE ALLOCATOR OR OTHER AUTOMATED PCV -/// SYSTEM. DEPOSITING LOCKS FUNDS FOR AN EXTENDED PERIOD OF TIME. - -/// @dev On deposit, all Maple FDT tokens are immediately deposited into -/// the maple rewards contract that corresponds with the pool where funds are deposited. -/// On withdraw, the `signalIntentToWithdraw` function must be called. -/// In the withdraw function, the Maple FDT tokens are unstaked -/// from the rewards contract, this allows the underlying USDC to be withdrawn. -/// Maple withdraws have some interesting properties such as interest not being calculated into -/// the amount that is requested to be withdrawn. This means that asking to withdraw 100 USDC -/// could yield 101 USDC received in this PCV Deposit if interest has been earned, or it could -/// mean 95 USDC in this contract if losses are sustained in excess of interest earned. -/// Supporting these two situations adds additional code complexity, so unlike other PCV Deposits, -/// the amount withdrawn on this PCV Deposit is non-deterministic, so a sender could request 100 -/// USDC out, and get another amount out. Dealing with these different pathways would add additional -/// code and complexity, so instead, Math.min(token.balanceOf(Address(this)), amountToWithdraw) -/// is used to determine the amount of tokens that will be sent out of the contract -/// after withdraw is called on the Maple market. -contract MaplePCVDeposit is PCVDeposit { - using SafeERC20 for IERC20; - - /// @notice reference to the Maple Pool where deposits and withdraws will originate - IPool public immutable pool; - - /// @notice reference to the Maple Staking Rewards Contract - IMplRewards public immutable mplRewards; - - /// @notice reference to the underlying token - IERC20 public immutable token = - IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); - - /// @notice reference to the Maple token - IERC20 public immutable rewardsToken; - - /// @notice scaling factor for USDC - /// @dev hardcoded to use USDC decimals as this is the only - /// supplied asset Volt Protocol will support - uint256 public constant SCALING_FACTOR = 1e12; - - /// @param _core reference to the Core contract - /// @param _pool Maple Pool contract - /// @param _mplRewards Maple Rewards contract - constructor( - address _core, - address _pool, - address _mplRewards - ) CoreRef(_core) { - pool = IPool(_pool); - mplRewards = IMplRewards(_mplRewards); - rewardsToken = IERC20(IMplRewards(_mplRewards).rewardsToken()); - } - - /// @notice return the amount of funds this contract owns in USDC - /// accounting for interest earned - /// and unrealized losses in the venue - function balance() public view override returns (uint256) { - uint256 rawBalance = pool.balanceOf(address(this)) + - pool.accumulativeFundsOf(address(this)) - - pool.recognizableLossesOf(address(this)); - return rawBalance / SCALING_FACTOR; - } - - /// @notice return the underlying token denomination for this deposit - function balanceReportedIn() external view returns (address) { - return address(token); - } - - /// ---------- Happy Path APIs ---------- - - /// @notice deposit PCV into Maple. - /// all deposits are subject to a minimum 90 day lockup, - /// no op if 0 token balance - /// deposits are then immediately staked to accrue MPL rewards - /// only pcv controller can deposit, as this contract would be vulnerable - /// to donation / griefing attacks if anyone could call deposit and extend lockup time - function deposit() external onlyPCVController { - uint256 amount = IERC20(token).balanceOf(address(this)); - if (amount == 0) { - /// no op to prevent wasted gas - return; - } - - /// pool deposit - token.approve(address(pool), amount); - pool.deposit(amount); - - /// stake pool FDT for MPL rewards - uint256 scaledDepositAmount = amount * SCALING_FACTOR; - pool.increaseCustodyAllowance(address(mplRewards), scaledDepositAmount); - mplRewards.stake(scaledDepositAmount); - - emit Deposit(msg.sender, amount); - } - - /// @notice function to start the cooldown process to withdraw - /// 1. lp lockup on deposit --> 90 days locked up and can't withdraw - /// 2. cool down period, call intend to withdraw --> - /// must wait 10 days before withdraw after calling intend to withdraw function - /// 3. after cool down and past the lockup period, - /// have 2 days to withdraw before cool down period restarts. - function signalIntentToWithdraw() external onlyPCVController { - pool.intendToWithdraw(); - } - - /// @notice function to cancel a withdraw - /// should only be used to allow a transfer when doing a withdrawERC20 call - function cancelWithdraw() external onlyPCVController { - pool.cancelWithdraw(); - } - - /// @notice withdraw PCV from Maple, only callable by PCV controller - /// @param to destination after funds are withdrawn from venue - /// @param amount of PCV to withdraw from the venue - function withdraw(address to, uint256 amount) - external - override - onlyPCVController - { - /// Rewards - - uint256 scaledWithdrawAmount = amount * SCALING_FACTOR; - - mplRewards.getReward(); /// get MPL rewards - mplRewards.withdraw(scaledWithdrawAmount); /// decreases allowance - - /// Principal - - /// withdraw from the pool - /// this call will withdraw amount of principal requested, and then send - /// over any accrued interest. - /// expected behavior is that this contract - /// receives either amount of USDC, or amount of USDC + interest accrued - /// if lending losses were taken, receive less than amount - pool.withdraw(amount); - - /// withdraw min between balance and amount as losses could be sustained in venue - /// causing less than amt to be withdrawn - uint256 amountToTransfer = Math.min( - token.balanceOf(address(this)), - amount - ); - token.safeTransfer(to, amountToTransfer); - - emit Withdrawal(msg.sender, to, amountToTransfer); - } - - /// @notice permissionless function to harvest rewards before withdraw - function harvest() external { - uint256 preHarvestBalance = rewardsToken.balanceOf(address(this)); - - mplRewards.getReward(); - - uint256 postHarvestBalance = rewardsToken.balanceOf(address(this)); - - emit Harvest(postHarvestBalance - preHarvestBalance); - } - - /// ---------- Sad Path APIs ---------- - - /// Assume that using these functions will likely - /// break all happy path functions - /// Only use these function in an emergency situation - - /// ----------------------------------- - - /// @notice get rewards and unstake from rewards contract - /// breaks functionality of happy path withdraw functions - function exit() external onlyPCVController { - mplRewards.exit(); - } - - /// @notice unstake from rewards contract without getting rewards - /// breaks functionality of happy path withdraw functions - function withdrawFromRewardsContract() external onlyPCVController { - uint256 rewardsBalance = pool.balanceOf(address(this)); - mplRewards.withdraw(rewardsBalance); - } - - /// @notice unstake from Pool FDT contract without getting rewards - /// or unstaking from the reward contract. - /// @param to destination after funds are withdrawn from venue - /// @param amount of PCV to withdraw from the venue - function withdrawFromPool(address to, uint256 amount) - external - onlyPCVController - { - pool.withdraw(amount); - /// withdraw min between balance and amount as losses could be sustained in venue - /// causing less than amt to be withdrawn - uint256 amountToTransfer = Math.min( - token.balanceOf(address(this)), - amount - ); - token.safeTransfer(to, amountToTransfer); - - emit Withdrawal(msg.sender, to, amountToTransfer); - } - - /// inspired by MakerDAO Multicall: - /// https://github.com/makerdao/multicall/blob/master/src/Multicall.sol - - /// @notice struct to pack calldata and targets for an emergency action - struct Call { - address target; - bytes callData; - } - - /// @notice due to Maple's complexity, add this ability to be able - /// to execute arbitrary calldata against arbitrary addresses. - /// only callable by governor - function emergencyAction(Call[] memory calls) - external - onlyGovernor - returns (bytes[] memory returnData) - { - returnData = new bytes[](calls.length); - for (uint256 i = 0; i < calls.length; i++) { - (bool success, bytes memory returned) = calls[i].target.call( - calls[i].callData - ); - require(success); - returnData[i] = returned; - } - } -} diff --git a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol deleted file mode 100644 index 1a11db448..000000000 --- a/contracts/test/integration/IntegrationTestMaplePCVDeposit.t.sol +++ /dev/null @@ -1,323 +0,0 @@ -//SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity =0.8.13; - -import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {Vm} from "../unit/utils/Vm.sol"; -import {Core} from "../../core/Core.sol"; -import {IVolt} from "../../volt/IVolt.sol"; -import {IPool} from "../../pcv/maple/IPool.sol"; -import {DSTest} from "../unit/utils/DSTest.sol"; -import {IDSSPSM} from "../../pcv/maker/IDSSPSM.sol"; -import {Constants} from "../../Constants.sol"; -import {PCVGuardian} from "../../pcv/PCVGuardian.sol"; -import {IMplRewards} from "../../pcv/maple/IMplRewards.sol"; -import {MaplePCVDeposit} from "../../pcv/maple/MaplePCVDeposit.sol"; -import {MainnetAddresses} from "./fixtures/MainnetAddresses.sol"; -import {PegStabilityModule} from "../../peg/PegStabilityModule.sol"; -import {ERC20CompoundPCVDeposit} from "../../pcv/compound/ERC20CompoundPCVDeposit.sol"; - -contract IntegrationTestMaplePCVDeposit is DSTest { - using SafeCast for *; - - Vm public constant vm = Vm(HEVM_ADDRESS); - - MaplePCVDeposit private usdcDeposit; - - PCVGuardian private immutable pcvGuardian = - PCVGuardian(MainnetAddresses.PCV_GUARDIAN); - - Core private core = Core(MainnetAddresses.CORE); - - IERC20 private usdc = IERC20(MainnetAddresses.USDC); - - uint256 public constant targetUsdcBalance = 100_000e6; - - /// @notice once you signal to withdraw after lockup, wait 10 days - uint256 public constant cooldownPeriod = 864000; - - /// @notice once you have waited for cool down period to pass - /// you have 2 days to withdraw before you have to request to withdraw again - uint256 public constant withdrawPeriod = 172800; - - IERC20 public constant maple = - IERC20(0x33349B282065b0284d756F0577FB39c158F935e6); - address public constant mplRewards = - 0x7869D7a3B074b5fa484dc04798E254c9C06A5e90; - address public constant maplePool = - 0xFeBd6F15Df3B73DC4307B1d7E65D46413e710C27; - - address public constant mapleOwner = - 0xd6d4Bcde6c816F17889f1Dd3000aF0261B03a196; - - address public constant mapleToken = - 0x33349B282065b0284d756F0577FB39c158F935e6; - - function setUp() public { - usdcDeposit = new MaplePCVDeposit(address(core), maplePool, mplRewards); - - vm.label(address(usdcDeposit), "Maple USDC PCV Deposit"); - vm.label(address(usdc), "USDC Token"); - vm.label(address(maplePool), "Maple Pool"); - vm.label(address(mplRewards), "Maple Rewards"); - - vm.startPrank(MainnetAddresses.DAI_USDC_USDT_CURVE_POOL); - usdc.transfer(address(usdcDeposit), targetUsdcBalance); - vm.stopPrank(); - - /// governor has pcv controller role - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.deposit(); - } - - function testSetup() public { - assertEq(address(usdcDeposit.core()), address(core)); - assertEq( - address(usdcDeposit.rewardsToken()), - MainnetAddresses.MPL_TOKEN - ); - assertEq(usdcDeposit.balanceReportedIn(), address(usdc)); - assertEq(address(usdcDeposit.pool()), maplePool); - assertEq(address(usdcDeposit.mplRewards()), mplRewards); - assertEq(address(usdcDeposit.token()), address(MainnetAddresses.USDC)); - assertEq(usdcDeposit.balance(), targetUsdcBalance); - } - - function testWithdraw() public { - uint256 rewardRate = IMplRewards(mplRewards).rewardRate(); - vm.prank(mapleOwner); - IMplRewards(mplRewards).notifyRewardAmount(rewardRate); - - vm.warp(block.timestamp + IPool(maplePool).lockupPeriod()); - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.signalIntentToWithdraw(); - - vm.warp(block.timestamp + cooldownPeriod); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.withdraw(address(this), targetUsdcBalance); - - uint256 mplBalance = maple.balanceOf(address(usdcDeposit)); - - assertEq(usdcDeposit.balance(), 0); - assertEq(usdc.balanceOf(address(this)), targetUsdcBalance); - assertTrue(mplBalance != 0); - } - - function testHarvest() public { - uint256 rewardRate = IMplRewards(mplRewards).rewardRate(); - vm.prank(mapleOwner); - IMplRewards(mplRewards).notifyRewardAmount(rewardRate); - - vm.warp(block.timestamp + IPool(maplePool).lockupPeriod()); - - usdcDeposit.harvest(); - - uint256 mplBalance = maple.balanceOf(address(usdcDeposit)); - - assertTrue(mplBalance != 0); - } - - function _testWithdraw(uint256 amount) private { - _setRewardsAndWarp(); - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.signalIntentToWithdraw(); - - vm.warp(block.timestamp + cooldownPeriod); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.withdraw(address(this), amount); - - uint256 mplBalance = maple.balanceOf(address(usdcDeposit)); - - uint256 targetBal = targetUsdcBalance - amount; - assertEq(usdcDeposit.balance(), targetBal); - assertEq(usdc.balanceOf(address(this)), amount); - assertTrue(mplBalance != 0); - } - - function testWithdrawAtCoolDownEnd() public { - _testWithdraw(targetUsdcBalance); - } - - function testWithdrawAtCoolDownEndFuzz(uint40 amount) public { - vm.assume(amount != 0); - vm.assume(amount <= targetUsdcBalance); - _testWithdraw(amount); - } - - function testSignalWithdrawPCVControllerSucceeds() public { - uint256 blockTimestamp = 10_000; - - vm.warp(blockTimestamp); - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.signalIntentToWithdraw(); - - assertEq( - IPool(maplePool).withdrawCooldown(address(usdcDeposit)), - blockTimestamp - ); - } - - function testCancelWithdrawPCVControllerSucceeds() public { - uint256 blockTimestamp = 10_000; - - vm.warp(blockTimestamp); - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.signalIntentToWithdraw(); - - assertEq( - IPool(maplePool).withdrawCooldown(address(usdcDeposit)), - blockTimestamp - ); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.cancelWithdraw(); - - assertEq(IPool(maplePool).withdrawCooldown(address(usdcDeposit)), 0); - } - - function testExitPCVControllerSucceeds() public { - _setRewardsAndWarp(); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.exit(); - - assertEq( - IPool(maplePool).custodyAllowance( - address(usdcDeposit), - address(mplRewards) - ), - 0 - ); - assertEq(IMplRewards(mplRewards).balanceOf(address(usdcDeposit)), 0); - } - - function testWithdrawFromRewardsContractPCVControllerSucceeds() public { - _setRewardsAndWarp(); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.withdrawFromRewardsContract(); - - assertEq( - IPool(maplePool).custodyAllowance( - address(usdcDeposit), - address(mplRewards) - ), - 0 - ); - assertEq(IMplRewards(mplRewards).balanceOf(address(usdcDeposit)), 0); - } - - function testWithdrawFromRewardsContractAndWithdrawFromPoolPCVControllerSucceeds() - public - { - testWithdrawFromRewardsContractPCVControllerSucceeds(); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.signalIntentToWithdraw(); - - vm.warp(block.timestamp + cooldownPeriod); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.withdrawFromPool(address(this), targetUsdcBalance); - } - - function testExitRewardsContractAndWithdrawFromPoolPCVControllerSucceeds() - public - { - _setRewardsAndWarp(); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.exit(); - - assertEq( - IPool(maplePool).custodyAllowance( - address(usdcDeposit), - address(mplRewards) - ), - 0 - ); - assertEq(IMplRewards(mplRewards).balanceOf(address(usdcDeposit)), 0); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.signalIntentToWithdraw(); - vm.warp(block.timestamp + cooldownPeriod); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.withdrawFromPool(address(this), targetUsdcBalance); - } - - function testGovernorEmergencyActionExitSucceeds() public { - _setRewardsAndWarp(); - - MaplePCVDeposit.Call[] memory calls = new MaplePCVDeposit.Call[](1); - calls[0].callData = abi.encodeWithSignature("exit()"); - calls[0].target = mplRewards; - - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.emergencyAction(calls); - - assertEq( - IPool(maplePool).custodyAllowance( - address(usdcDeposit), - address(mplRewards) - ), - 0 - ); - assertEq(IMplRewards(mplRewards).balanceOf(address(usdcDeposit)), 0); - } - - function testDepositNotPCVControllerFails() public { - vm.expectRevert("CoreRef: Caller is not a PCV controller"); - usdcDeposit.deposit(); - } - - function testSignalWithdrawNonPCVControllerFails() public { - vm.expectRevert("CoreRef: Caller is not a PCV controller"); - usdcDeposit.signalIntentToWithdraw(); - } - - function testCancelWithdrawNonPCVControllerFails() public { - vm.expectRevert("CoreRef: Caller is not a PCV controller"); - usdcDeposit.cancelWithdraw(); - } - - function testWithdrawNonPCVControllerFails() public { - vm.expectRevert("CoreRef: Caller is not a PCV controller"); - usdcDeposit.withdraw(address(this), targetUsdcBalance); - } - - function testwithdrawFromPoolNonPCVControllerFails() public { - vm.expectRevert("CoreRef: Caller is not a PCV controller"); - usdcDeposit.withdrawFromPool(address(this), targetUsdcBalance); - } - - function testWithdrawFromRewardsContractgNonPCVControllerFails() public { - vm.expectRevert("CoreRef: Caller is not a PCV controller"); - usdcDeposit.withdrawFromRewardsContract(); - } - - function testExitPCVControllerFails() public { - vm.expectRevert("CoreRef: Caller is not a PCV controller"); - usdcDeposit.exit(); - } - - function testEmergencyActionNonPCVControllerFails() public { - MaplePCVDeposit.Call[] memory calls = new MaplePCVDeposit.Call[](2); - - vm.expectRevert("CoreRef: Caller is not a governor"); - usdcDeposit.emergencyAction(calls); - } - - function _setRewardsAndWarp() private { - uint256 rewardRate = IMplRewards(mplRewards).rewardRate(); - vm.prank(mapleOwner); - IMplRewards(mplRewards).notifyRewardAmount(rewardRate); - - vm.warp( - block.timestamp + IPool(maplePool).lockupPeriod() - cooldownPeriod - ); - } -} diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index 9d5b666e7..77a863d10 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -15,7 +15,6 @@ import {PCVGuardian} from "../../../pcv/PCVGuardian.sol"; import {IPCVDeposit} from "../../../pcv/IPCVDeposit.sol"; import {PriceBoundPSM} from "../../../peg/PriceBoundPSM.sol"; import {ERC20Allocator} from "../../../pcv/utils/ERC20Allocator.sol"; -import {MaplePCVDeposit} from "../../../pcv/maple/MaplePCVDeposit.sol"; import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; import {CompoundPCVRouter} from "../../../pcv/compound/CompoundPCVRouter.sol"; import {ITimelockSimulation} from "../utils/ITimelockSimulation.sol"; @@ -25,7 +24,6 @@ import {MorphoCompoundPCVDeposit} from "../../../pcv/morpho/MorphoCompoundPCVDep /// 1. deploy morpho dai deposit /// 2. deploy morpho usdc deposit /// 3. deploy compound pcv router pointed to morpho dai and usdc deposits -/// 4. deploy maple usdc deposit /// Governance Steps /// 1. grant new PCV router PCV Controller role @@ -37,7 +35,7 @@ import {MorphoCompoundPCVDeposit} from "../../../pcv/morpho/MorphoCompoundPCVDep /// 5. connect new dai morpho deposit to allocator /// 6. connect new usdc morpho deposit to allocator -/// 7. add deposits as safe addresses +/// 7. add morpho deposits as safe addresses /// 8. pause dai compound pcv deposit /// 9. pause usdc compound pcv deposit @@ -58,7 +56,6 @@ contract vip14 is DSTest, IVIP { CompoundPCVRouter public router; MorphoCompoundPCVDeposit public daiDeposit; MorphoCompoundPCVDeposit public usdcDeposit; - MaplePCVDeposit public mapleDeposit; PCVGuardian public immutable pcvGuardian = PCVGuardian(MainnetAddresses.PCV_GUARDIAN); @@ -66,18 +63,9 @@ contract vip14 is DSTest, IVIP { ERC20Allocator public immutable allocator = ERC20Allocator(MainnetAddresses.ERC20ALLOCATOR); - /// --------- Maple Addresses --------- - - address public constant mplRewards = - 0x7869D7a3B074b5fa484dc04798E254c9C06A5e90; - - address public constant maplePool = - 0xFeBd6F15Df3B73DC4307B1d7E65D46413e710C27; - constructor() { if (block.chainid != 1) return; /// keep ci pipeline happy - mapleDeposit = new MaplePCVDeposit(core, maplePool, mplRewards); daiDeposit = new MorphoCompoundPCVDeposit( core, MainnetAddresses.CDAI, @@ -95,10 +83,9 @@ contract vip14 is DSTest, IVIP { PCVDeposit(address(usdcDeposit)) ); - address[] memory toWhitelist = new address[](3); + address[] memory toWhitelist = new address[](2); toWhitelist[0] = address(daiDeposit); toWhitelist[1] = address(usdcDeposit); - toWhitelist[2] = address(mapleDeposit); mainnetProposal.push( ITimelockSimulation.action({ @@ -181,7 +168,7 @@ contract vip14 is DSTest, IVIP { "addWhitelistAddresses(address[])", toWhitelist ), - description: "Add USDC and DAI Morpho deposits and Maple deposit to the PCV Guardian" + description: "Add USDC and DAI Morpho deposits to the PCV Guardian" }) ); @@ -214,7 +201,6 @@ contract vip14 is DSTest, IVIP { } /// move all funds from compound deposits to morpho deposits - /// move all needed funds to Maple function mainnetSetup() public override { vm.startPrank(MainnetAddresses.GOVERNOR); pcvGuardian.withdrawAllToSafeAddress( @@ -243,7 +229,6 @@ contract vip14 is DSTest, IVIP { /// assert pcv deposits are set correctly in allocator /// assert old pcv deposits are disconnected in allocator function mainnetValidate() public override { - assertEq(address(mapleDeposit.core()), core); assertEq(address(usdcDeposit.core()), core); assertEq(address(daiDeposit.core()), core); assertEq(address(router.core()), core); @@ -256,7 +241,6 @@ contract vip14 is DSTest, IVIP { /// pcv guardian whitelist assertions assertTrue(pcvGuardian.isWhitelistAddress(address(daiDeposit))); assertTrue(pcvGuardian.isWhitelistAddress(address(usdcDeposit))); - assertTrue(pcvGuardian.isWhitelistAddress(address(mapleDeposit))); /// router parameter validations assertEq(address(router.daiPcvDeposit()), address(daiDeposit)); From 49e18cdf861dbd8a30cd11071fe09c7cac6e26af Mon Sep 17 00:00:00 2001 From: Elliot Date: Wed, 19 Oct 2022 13:23:10 -0700 Subject: [PATCH 53/84] typescript vip-14 updates to only handling Morpho deposits --- proposals/dao/vip_14.ts | 20 ++------------------ proposals/vip_14.ts | 9 ++++----- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/proposals/dao/vip_14.ts b/proposals/dao/vip_14.ts index 4ed55f065..26f431779 100644 --- a/proposals/dao/vip_14.ts +++ b/proposals/dao/vip_14.ts @@ -7,7 +7,6 @@ import { } from '@custom-types/types'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import { assertApproxEq } from '@test/helpers'; /* @@ -19,7 +18,6 @@ Description: /// 1. deploy morpho dai deposit /// 2. deploy morpho usdc deposit /// 3. deploy compound pcv router pointed to morpho dai and usdc deposits -/// 4. deploy maple usdc deposit Governance Steps: 1. Grant Morpho PCV Router PCV Controller Role @@ -28,7 +26,7 @@ Governance Steps: 4. Remove Compound USDC Deposit from ERC20Allocator 5. Add Morpho DAI Deposit to ERC20Allocator 6. Add Morpho USDC Deposit to ERC20Allocator -7. Add USDC and DAI Morpho, and Maple deposits to the PCV Guardian +7. Add USDC and DAI Morpho deposits to the PCV Guardian 8. pause dai compound pcv deposit 9. pause usdc compound pcv deposit @@ -37,7 +35,6 @@ Governance Steps: const vipNumber = '14'; const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => { - const maplePCVDepositFactory = await ethers.getContractFactory('MaplePCVDeposit'); const morphoPCVDepositFactory = await ethers.getContractFactory('MorphoCompoundPCVDeposit'); const compoundPCVRouterFactory = await ethers.getContractFactory('CompoundPCVRouter'); @@ -62,10 +59,6 @@ const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: Named ); await morphoCompoundPCVRouter.deployed(); - const maplePCVDeposit = await maplePCVDepositFactory.deploy(addresses.core, addresses.mplPool, addresses.mplRewards); - await maplePCVDeposit.deployed(); - - console.log(`Maple PCV Deposit deployed ${maplePCVDeposit.address}`); console.log(`Morpho Compound PCV Router deployed ${morphoCompoundPCVRouter.address}`); console.log(`Morpho Compound DAI PCV Deposit deployed ${daiMorphoCompoundPCVDeposit.address}`); console.log(`Morpho Compound USDC PCV Deposit deployed ${usdcMorphoCompoundPCVDeposit.address}`); @@ -73,7 +66,6 @@ const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: Named console.log(`Successfully Deployed VIP-${vipNumber}`); return { - maplePCVDeposit, daiMorphoCompoundPCVDeposit, usdcMorphoCompoundPCVDeposit, morphoCompoundPCVRouter @@ -96,15 +88,13 @@ const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, morphoCompoundPCVRouter, compoundPCVRouter, pcvGuardian, - erc20Allocator, - maplePCVDeposit + erc20Allocator } = contracts; /// Core address validations expect(await usdcMorphoCompoundPCVDeposit.core()).to.be.equal(core.address); expect(await daiMorphoCompoundPCVDeposit.core()).to.be.equal(core.address); expect(await morphoCompoundPCVRouter.core()).to.be.equal(core.address); - expect(await maplePCVDeposit.core()).to.be.equal(core.address); /// pcv controller validation expect(await core.isPCVController(compoundPCVRouter.address)).to.be.false; @@ -131,15 +121,9 @@ const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, expect(await daiMorphoCompoundPCVDeposit.LENS()).to.be.equal(addresses.morphoCompoundLens); expect(await usdcMorphoCompoundPCVDeposit.LENS()).to.be.equal(addresses.morphoCompoundLens); - expect(await maplePCVDeposit.balanceReportedIn()).to.be.equal(addresses.usdc); - expect(await maplePCVDeposit.mplRewards()).to.be.equal(addresses.mplRewards); - expect(await maplePCVDeposit.token()).to.be.equal(addresses.usdc); - expect(await maplePCVDeposit.pool()).to.be.equal(addresses.mplPool); - /// pcv guardian validation expect(await pcvGuardian.isWhitelistAddress(usdcMorphoCompoundPCVDeposit.address)).to.be.true; expect(await pcvGuardian.isWhitelistAddress(daiMorphoCompoundPCVDeposit.address)).to.be.true; - expect(await pcvGuardian.isWhitelistAddress(maplePCVDeposit.address)).to.be.true; expect(await pcvGuardian.isWhitelistAddress(addresses.daiCompoundPCVDeposit)).to.be.true; expect(await pcvGuardian.isWhitelistAddress(addresses.usdcCompoundPCVDeposit)).to.be.true; diff --git a/proposals/vip_14.ts b/proposals/vip_14.ts index 1c34e925d..9108914e6 100644 --- a/proposals/vip_14.ts +++ b/proposals/vip_14.ts @@ -1,7 +1,7 @@ import { ProposalDescription } from '@custom-types/types'; const vip_14: ProposalDescription = { - title: 'VIP-14: Morpho Maple PCV Deposit, Rate Update', + title: 'VIP-14: Morpho PCV Deposit', commands: [ /// core actions { @@ -52,8 +52,8 @@ const vip_14: ProposalDescription = { target: 'pcvGuardian', values: '0', method: 'addWhitelistAddresses(address[])', - arguments: [['{daiMorphoCompoundPCVDeposit}', '{usdcMorphoCompoundPCVDeposit}', '{maplePCVDeposit}']], - description: 'Add USDC and DAI Morpho deposits and Maple deposit to the PCV Guardian' + arguments: [['{daiMorphoCompoundPCVDeposit}', '{usdcMorphoCompoundPCVDeposit}']], + description: 'Add USDC and DAI Morpho deposits to the PCV Guardian' }, { target: 'daiCompoundPCVDeposit', @@ -75,7 +75,6 @@ const vip_14: ProposalDescription = { 1. deploy morpho dai deposit 2. deploy morpho usdc deposit 3. deploy compound pcv router pointed to morpho dai and usdc deposits - 4. deploy maple pcv deposit Governance Steps: 1. Grant Morpho PCV Router PCV Controller Role @@ -84,7 +83,7 @@ const vip_14: ProposalDescription = { 4. Remove Compound USDC Deposit from ERC20Allocator 5. Add Morpho DAI Deposit to ERC20Allocator 6. Add Morpho USDC Deposit to ERC20Allocator - 7. Add USDC and DAI Morpho deposits as well as Maple PCV deposit to the PCV Guardian + 7. Add USDC and DAI Morpho deposits to the PCV Guardian 8. pause dai compound pcv deposit 9. pause usdc compound pcv deposit ` From 1a6b6647a1e2d60578d29984f350f1d7c04d0942 Mon Sep 17 00:00:00 2001 From: Elliot <34463580+ElliotFriedman@users.noreply.github.com> Date: Wed, 19 Oct 2022 16:41:16 -0400 Subject: [PATCH 54/84] Update contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol Co-authored-by: Erwan Beauvois --- contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index ef6269fc3..b8beaeb8a 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -100,8 +100,17 @@ contract MorphoCompoundPCVDeposit is PCVDeposit { /// @notice helper function to avoid repeated code in withdraw and withdrawAll function _withdraw(address to, uint256 amount) private { + // compute profit from interests and emit an event + uint256 _depositedAmount = depositedAmount; // SLOAD + uint256 _balance = balance(); + uint256 profit = _balance - _depositedAmount; + emit Harvest(address(token), int256(profit), block.timestamp); + MORPHO.withdraw(cToken, amount); IERC20(token).safeTransfer(to, amount); + + // update tracked deposit amount + depositedAmount = _balance - amount; emit Withdrawal(msg.sender, to, amount); } From a71e3b39b9c1f4f1712b86b48e235e612f2c9cb8 Mon Sep 17 00:00:00 2001 From: Elliot <34463580+ElliotFriedman@users.noreply.github.com> Date: Wed, 19 Oct 2022 16:41:51 -0400 Subject: [PATCH 55/84] Update contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol Co-authored-by: Erwan Beauvois --- contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index b8beaeb8a..709ec7574 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -80,6 +80,9 @@ contract MorphoCompoundPCVDeposit is PCVDeposit { address(this), /// the address of the user you want to supply on behalf of amount ); + + // increment tracked deposited amount + depositedAmount += amount; emit Deposit(msg.sender, amount); } From bc52fa4d21b8914b906d14aa796a3bb6b4c3a110 Mon Sep 17 00:00:00 2001 From: Elliot <34463580+ElliotFriedman@users.noreply.github.com> Date: Wed, 19 Oct 2022 16:42:20 -0400 Subject: [PATCH 56/84] Update contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol Co-authored-by: Erwan Beauvois --- contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index 709ec7574..1816a1401 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -126,6 +126,6 @@ contract MorphoCompoundPCVDeposit is PCVDeposit { /// set swap comp to morpho flag false to claim comp rewards uint256 claimedAmount = MORPHO.claimRewards(cTokens, false); - emit Harvest(claimedAmount); + emit Harvest(COMP, int256(claimedAmount), block.timestamp); } } From a96025a70ea56a7f824f48e56ff8c2246fb589f1 Mon Sep 17 00:00:00 2001 From: Elliot Date: Wed, 19 Oct 2022 13:44:16 -0700 Subject: [PATCH 57/84] remove revert in mint redeem verification --- contracts/test/integration/utils/MintRedeemVerification.sol | 5 ----- 1 file changed, 5 deletions(-) diff --git a/contracts/test/integration/utils/MintRedeemVerification.sol b/contracts/test/integration/utils/MintRedeemVerification.sol index 8fedfa0e7..e2ca00d8f 100644 --- a/contracts/test/integration/utils/MintRedeemVerification.sol +++ b/contracts/test/integration/utils/MintRedeemVerification.sol @@ -205,11 +205,6 @@ contract MintRedeemVerification { ? IVolt(MainnetAddresses.VOLT) : IVolt(ArbitrumAddresses.VOLT); - /// skip Arbitrum minting because minting is paused on Arbitrum due to deprecation - if (block.chainid != 1) { - revert("successfully minted on all PSMs"); - } - for (uint256 i = 0; i < allPSMs.length; i++) { /// pull all tokens from psm into this address and use them to purchase VOLT uint256 amountIn = allTokens[i].balanceOf(allPSMs[i]); From 024edd3e1ea590bfa08d8a5d00cd454d91ae131a Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 20 Oct 2022 02:04:08 -0700 Subject: [PATCH 58/84] Add profit tracking to Morpho PCV Deposit, fuzz tests, security tests with reentrancy suite --- contracts/mock/MockMorpho.sol | 49 +++ .../mock/MockMorphoMaliciousReentrancy.sol | 55 ++++ contracts/pcv/IPCVDeposit.sol | 2 +- contracts/pcv/morpho/IMorpho.sol | 2 + .../pcv/morpho/MorphoCompoundPCVDeposit.sol | 165 ++++++++-- ...egrationTestMorphoCompoundPCVDeposit.t.sol | 11 +- .../integration/fixtures/MainnetAddresses.sol | 7 + contracts/test/integration/vip/vip14.sol | 9 +- .../pcv/morpho/MorphoCompoundPCVDeposit.t.sol | 309 ++++++++++++++++++ 9 files changed, 576 insertions(+), 33 deletions(-) create mode 100644 contracts/mock/MockMorpho.sol create mode 100644 contracts/mock/MockMorphoMaliciousReentrancy.sol create mode 100644 contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol diff --git a/contracts/mock/MockMorpho.sol b/contracts/mock/MockMorpho.sol new file mode 100644 index 000000000..410167f6c --- /dev/null +++ b/contracts/mock/MockMorpho.sol @@ -0,0 +1,49 @@ +pragma solidity 0.8.13; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MockMorpho { + IERC20 public immutable token; + mapping(address => uint256) public balances; + + constructor(IERC20 _token) { + token = _token; + } + + function withdraw(address, uint256 amount) external { + balances[msg.sender] -= amount; + token.transfer(msg.sender, amount); + } + + function supply( + address, + address recipient, + uint256 amountUnderlying + ) external { + token.transferFrom(msg.sender, address(this), amountUnderlying); + balances[recipient] += amountUnderlying; + } + + function setBalance(address to, uint256 amount) external { + balances[to] = amount; + } + + function claimRewards(address cToken, bool swapForMorpho) + external + returns (uint256) + {} + + function updateP2PIndexes(address) external {} + + function getCurrentSupplyBalanceInOf(address, address _user) + external + view + returns ( + uint256 balanceOnPool, + uint256 balanceInP2P, + uint256 totalBalance + ) + { + return (0, 0, balances[_user]); + } +} diff --git a/contracts/mock/MockMorphoMaliciousReentrancy.sol b/contracts/mock/MockMorphoMaliciousReentrancy.sol new file mode 100644 index 000000000..4527fc7ce --- /dev/null +++ b/contracts/mock/MockMorphoMaliciousReentrancy.sol @@ -0,0 +1,55 @@ +pragma solidity 0.8.13; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {MorphoCompoundPCVDeposit} from "contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol"; + +contract MockMorphoMaliciousReentrancy { + IERC20 public immutable token; + mapping(address => uint256) public balances; + MorphoCompoundPCVDeposit public morphoCompoundPCVDeposit; + + constructor(IERC20 _token) { + token = _token; + } + + function setMorphoCompoundPCVDeposit(address deposit) external { + morphoCompoundPCVDeposit = MorphoCompoundPCVDeposit(deposit); + } + + function withdraw(address, uint256) external { + morphoCompoundPCVDeposit.accrue(); + } + + function supply( + address, + address, + uint256 + ) external { + morphoCompoundPCVDeposit.deposit(); + } + + function setBalance(address to, uint256 amount) external { + balances[to] = amount; + } + + function claimRewards(address cToken, bool swapForMorpho) + external + returns (uint256) + {} + + function updateP2PIndexes(address) external { + morphoCompoundPCVDeposit.accrue(); + } + + function getCurrentSupplyBalanceInOf(address, address _user) + external + view + returns ( + uint256 balanceOnPool, + uint256 balanceInP2P, + uint256 totalBalance + ) + { + return (0, 0, balances[_user]); + } +} diff --git a/contracts/pcv/IPCVDeposit.sol b/contracts/pcv/IPCVDeposit.sol index 7f2d645e1..2f3bb4a04 100644 --- a/contracts/pcv/IPCVDeposit.sol +++ b/contracts/pcv/IPCVDeposit.sol @@ -29,7 +29,7 @@ interface IPCVDeposit is IPCVDepositBalances { uint256 _amount ); - event Harvest(uint256 amount); + event Harvest(address indexed _token, int256 _profit, uint256 _timestamp); // ----------- State changing api ----------- diff --git a/contracts/pcv/morpho/IMorpho.sol b/contracts/pcv/morpho/IMorpho.sol index c5ee57991..201155d4d 100644 --- a/contracts/pcv/morpho/IMorpho.sol +++ b/contracts/pcv/morpho/IMorpho.sol @@ -20,4 +20,6 @@ interface IMorpho { function repay(address _poolTokenAddress, address _onBehalf, uint256 _amount) external; function liquidate(address _poolTokenBorrowedAddress, address _poolTokenCollateralAddress, address _borrower, uint256 _amount) external; function claimRewards(address[] calldata _cTokenAddresses, bool _tradeForMorphoToken) external returns (uint256 claimedAmount); + + function updateP2PIndexes(address _poolTokenAddress) external; } diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index 1816a1401..d28fd0e62 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity =0.8.13; +import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ILens} from "./ILens.sol"; @@ -10,6 +12,8 @@ import {CoreRef} from "../../refs/CoreRef.sol"; import {PCVDeposit} from "../PCVDeposit.sol"; import {ICompoundOracle, ICToken} from "./ICompound.sol"; +import "hardhat/console.sol"; + /// @notice PCV Deposit for Morpho-Compound V2. /// Implements the PCV Deposit interface to deposit and withdraw funds in Morpho /// Liquidity profile of Morpho for this deposit is fully liquid for USDC and DAI @@ -24,15 +28,19 @@ import {ICompoundOracle, ICToken} from "./ICompound.sol"; /// has no concept of mTokens. This means if the contract is paused, or an issue is /// surfaced in Morpho and liquidity is locked, Volt will need to rely on social /// coordination with the Morpho team to recover funds. -contract MorphoCompoundPCVDeposit is PCVDeposit { +contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { using SafeERC20 for IERC20; + using SafeCast for *; + + /// @notice reference to the COMP governance token + /// used for recording COMP rewards type in Harvest event + address public constant COMP = 0xc00e94Cb662C3520282E6f5717214004A7f26888; /// @notice reference to the lens contract for morpho-compound v2 - address public constant LENS = 0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67; + address public immutable lens; /// @notice reference to the morpho-compound v2 market - IMorpho public constant MORPHO = - IMorpho(0x8888882f8f843896699869179fB6E4f7e3B58888); + IMorpho public immutable morpho; /// @notice reference to underlying token address public immutable token; @@ -41,19 +49,31 @@ contract MorphoCompoundPCVDeposit is PCVDeposit { /// used to inform morpho about the desired market to supply liquidity address public immutable cToken; + /// @notice track the current amount of PCV deposited in the contract + uint256 public depositedAmount; + + /// @param _core reference to the core contract + /// @param _cToken cToken this deposit references + /// @param _underlying Token denomination of this deposit + /// @param _morpho reference to the morpho-compound v2 market + /// @param _lens reference to the morpho-compound v2 lens constructor( address _core, address _cToken, - address _underlying - ) CoreRef(_core) { + address _underlying, + address _morpho, + address _lens + ) CoreRef(_core) ReentrancyGuard() { cToken = _cToken; token = _underlying; + morpho = IMorpho(_morpho); + lens = _lens; } /// @notice Returns the distribution of assets supplied by this contract through Morpho-Compound. /// @return sum of suppliedP2P and suppliedOnPool for the given CToken function balance() public view override returns (uint256) { - (, , uint256 totalSupplied) = ILens(LENS).getCurrentSupplyBalanceInOf( + (, , uint256 totalSupplied) = ILens(lens).getCurrentSupplyBalanceInOf( cToken, address(this) ); @@ -67,22 +87,33 @@ contract MorphoCompoundPCVDeposit is PCVDeposit { } /// @notice deposit ERC-20 tokens to Morpho-Compound - function deposit() public whenNotPaused { + /// non-reentrant to block malicious reentrant state changes + /// to the depositedAmount variable + function deposit() public whenNotPaused nonReentrant { + /// ------ Check ------ + uint256 amount = IERC20(token).balanceOf(address(this)); if (amount == 0) { /// no op to prevent revert on empty deposit return; } - IERC20(token).approve(address(MORPHO), amount); - MORPHO.supply( + /// ------ Effects ------ + + /// compute profit from interest accrued and emit an event + _recordPNL(); + + /// increment tracked deposited amount + depositedAmount += amount; + + /// ------ Interactions ------ + + IERC20(token).approve(address(morpho), amount); + morpho.supply( cToken, /// cToken to supply liquidity to address(this), /// the address of the user you want to supply on behalf of amount ); - - // increment tracked deposited amount - depositedAmount += amount; emit Deposit(msg.sender, amount); } @@ -90,42 +121,120 @@ contract MorphoCompoundPCVDeposit is PCVDeposit { /// @notice withdraw tokens from the PCV allocation /// @param to the address PCV will be sent to /// @param amount of tokens withdrawn - function withdraw(address to, uint256 amount) external onlyPCVController { + function withdraw(address to, uint256 amount) + external + onlyPCVController + nonReentrant + { _withdraw(to, amount); } /// @notice withdraw all tokens from Morpho /// @param to the address PCV will be sent to - function withdrawAll(address to) external onlyPCVController { - uint256 amount = balance(); - _withdraw(to, amount); + function withdrawAll(address to) external onlyPCVController nonReentrant { + /// compute profit from interest accrued and emit an event + _recordPNL(); + + /// withdraw deposited amount as this was updated in record pnl + _withdraw(to, depositedAmount); } /// @notice helper function to avoid repeated code in withdraw and withdrawAll + /// anytime this function is called it is by an external function in this smart contract + /// with a reentrancy guard. this ensures depositedAmount never desynchronizes + /// Morpho is assumed to be a loss-less venue. over the course of less than 5 blocks, + /// it is possible to lose funds. However, over 5 blocks, deposits are expected to always + /// be in profit. + /// if losses are ever sustained, subtracting amount from depositedAmount will mean + /// that this function always reverts, meaning emergencyAction will need to be called function _withdraw(address to, uint256 amount) private { - // compute profit from interests and emit an event - uint256 _depositedAmount = depositedAmount; // SLOAD - uint256 _balance = balance(); - uint256 profit = _balance - _depositedAmount; - emit Harvest(address(token), int256(profit), block.timestamp); - - MORPHO.withdraw(cToken, amount); + /// ------ Effects ------ + + /// compute profit from interest accrued and emit an event + _recordPNL(); + + /// update tracked deposit amount + depositedAmount -= amount; + + /// ------ Interactions ------ + + morpho.withdraw(cToken, amount); IERC20(token).safeTransfer(to, amount); - - // update tracked deposit amount - depositedAmount = _balance - amount; emit Withdrawal(msg.sender, to, amount); } + /// @notice function that records how much profit has been accrued + /// since the last call and emits an event with all profit received + /// updates the amount deposited to include all interest earned + function _recordPNL() private { + /// first accrue interest in Compound and Morpho + morpho.updateP2PIndexes(cToken); + + /// then get the current balance from the market + uint256 currentBalance = balance(); + + /// currentBalance should always be greater than or equal to + /// the deposited amount + int256 profit = currentBalance.toInt256() - depositedAmount.toInt256(); + + /// record new deposited amount + depositedAmount = currentBalance; + + /// profit is in underlying token + emit Harvest(token, profit, block.timestamp); + } + /// @notice claim COMP rewards for supplying to Morpho + /// no need for reentrancy lock as no smart contract state is mutated + /// in this function. function harvest() external { address[] memory cTokens = new address[](1); cTokens[0] = cToken; /// set swap comp to morpho flag false to claim comp rewards - uint256 claimedAmount = MORPHO.claimRewards(cTokens, false); + uint256 claimedAmount = morpho.claimRewards(cTokens, false); emit Harvest(COMP, int256(claimedAmount), block.timestamp); } + + /// @notice function that emits an event tracking profits and losses + /// since the last contract interaction + /// then writes the current amount of PCV tracked in this contract + /// to depositedAmount + function accrue() external nonReentrant returns (uint256) { + _recordPNL(); /// update deposit amount and fire harvest event + + return depositedAmount; /// return updated deposit amount + } + + // ---------- Emergency Action ---------- + + /// inspired by MakerDAO Multicall: + /// https://github.com/makerdao/multicall/blob/master/src/Multicall.sol + + /// @notice struct to pack calldata and targets for an emergency action + struct Call { + address target; + bytes callData; + } + + /// @notice due to non transferability of Morpho positions, + /// add this ability to be able to execute arbitrary calldata + /// against arbitrary addresses. + /// only callable by governor + function emergencyAction(Call[] memory calls) + external + onlyGovernor + returns (bytes[] memory returnData) + { + returnData = new bytes[](calls.length); + for (uint256 i = 0; i < calls.length; i++) { + (bool success, bytes memory returned) = calls[i].target.call( + calls[i].callData + ); + require(success); + returnData[i] = returned; + } + } } diff --git a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol index 054324149..3e59f0998 100644 --- a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol @@ -47,12 +47,17 @@ contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { daiDeposit = new MorphoCompoundPCVDeposit( address(core), MainnetAddresses.CDAI, - MainnetAddresses.DAI + MainnetAddresses.DAI, + MainnetAddresses.MORPHO, + MainnetAddresses.MORPHO_LENS ); + usdcDeposit = new MorphoCompoundPCVDeposit( address(core), MainnetAddresses.CUSDC, - MainnetAddresses.USDC + MainnetAddresses.USDC, + MainnetAddresses.MORPHO, + MainnetAddresses.MORPHO_LENS ); vm.label(address(daiDeposit), "Morpho DAI Compound PCV Deposit"); @@ -71,6 +76,8 @@ contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { usdcDeposit.deposit(); daiDeposit.deposit(); + + vm.roll(block.number + 3); /// fast forward 3 blocks so that profit is not negative } function testSetup() public { diff --git a/contracts/test/integration/fixtures/MainnetAddresses.sol b/contracts/test/integration/fixtures/MainnetAddresses.sol index 6df6544b0..54aa6cd75 100644 --- a/contracts/test/integration/fixtures/MainnetAddresses.sol +++ b/contracts/test/integration/fixtures/MainnetAddresses.sol @@ -156,6 +156,13 @@ library MainnetAddresses { address public constant GLOBAL_RATE_LIMITED_MINTER = 0x87945f59E008aDc9ed6210a8e061f009d6ace718; + // ---------- MORPHO ADDRESSES ---------- + + address public constant MORPHO = 0x8888882f8f843896699869179fB6E4f7e3B58888; + + address public constant MORPHO_LENS = + 0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67; + // ---------- MAPLE ADDRESSES ---------- address public constant MPL_TOKEN = diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index 77a863d10..79a8f6616 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -69,12 +69,17 @@ contract vip14 is DSTest, IVIP { daiDeposit = new MorphoCompoundPCVDeposit( core, MainnetAddresses.CDAI, - MainnetAddresses.DAI + MainnetAddresses.DAI, + MainnetAddresses.MORPHO, + MainnetAddresses.MORPHO_LENS ); + usdcDeposit = new MorphoCompoundPCVDeposit( core, MainnetAddresses.CUSDC, - MainnetAddresses.USDC + MainnetAddresses.USDC, + MainnetAddresses.MORPHO, + MainnetAddresses.MORPHO_LENS ); router = new CompoundPCVRouter( diff --git a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol new file mode 100644 index 000000000..d60f5af6c --- /dev/null +++ b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol @@ -0,0 +1,309 @@ +pragma solidity =0.8.13; + +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +import {Vm} from "./../../utils/Vm.sol"; +import {ICore} from "../../../../core/ICore.sol"; +import {DSTest} from "./../../utils/DSTest.sol"; +import {MockERC20} from "../../../../mock/MockERC20.sol"; +import {MockMorpho} from "../../../../mock/MockMorpho.sol"; +import {TribeRoles} from "../../../../core/TribeRoles.sol"; +import {IPCVDeposit} from "../../../../pcv/IPCVDeposit.sol"; +import {PCVGuardAdmin} from "../../../../pcv/PCVGuardAdmin.sol"; +import {MockERC20, IERC20} from "../../../../mock/MockERC20.sol"; +import {MorphoCompoundPCVDeposit} from "../../../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; +import {MockMorphoMaliciousReentrancy} from "../../../../mock/MockMorphoMaliciousReentrancy.sol"; +import {getCore, getAddresses, VoltTestAddresses} from "./../../utils/Fixtures.sol"; + +import "hardhat/console.sol"; + +contract UnitTestMorphoCompoundPCVDeposit is DSTest { + using SafeCast for *; + + event Harvest(address indexed _token, int256 _profit, uint256 _timestamp); + + event Withdrawal( + address indexed _caller, + address indexed _to, + uint256 _amount + ); + + ICore private core; + + MorphoCompoundPCVDeposit private morphoDeposit; + MockMorpho private morpho; + MockMorphoMaliciousReentrancy private maliciousMorpho; + + Vm public constant vm = Vm(HEVM_ADDRESS); + + VoltTestAddresses public addresses = getAddresses(); + + /// @notice token to deposit + MockERC20 private token; + + function setUp() public { + core = getCore(); + token = new MockERC20(); + morpho = new MockMorpho(IERC20(address(token))); + maliciousMorpho = new MockMorphoMaliciousReentrancy( + IERC20(address(token)) + ); + + morphoDeposit = new MorphoCompoundPCVDeposit( + address(core), + address(0), /// cToken is not used in mock morpho deposit + address(token), + address(morpho), + address(morpho) + ); + + vm.label(address(morpho), "Morpho"); + vm.label(address(token), "Token"); + vm.label(address(morphoDeposit), "MorphoDeposit"); + + maliciousMorpho.setMorphoCompoundPCVDeposit(address(morphoDeposit)); + } + + function testSetup() public { + assertEq(morphoDeposit.token(), address(token)); + assertEq(morphoDeposit.lens(), address(morpho)); + assertEq(address(morphoDeposit.morpho()), address(morpho)); + assertEq(morphoDeposit.cToken(), address(0)); + assertEq(morphoDeposit.depositedAmount(), 0); + } + + function testDeposit(uint256 depositAmount) public { + assertEq(morphoDeposit.depositedAmount(), 0); + token.mint(address(morphoDeposit), depositAmount); + + morphoDeposit.deposit(); + + assertEq(morphoDeposit.depositedAmount(), depositAmount); + } + + function testDeposits(uint248[4] calldata depositAmount) public { + uint256 sumDeposit; + + for (uint256 i = 0; i < 4; i++) { + token.mint(address(morphoDeposit), depositAmount[i]); + + if (depositAmount[i] != 0) { + /// harvest event is not emitted if deposit amount is 0 + vm.expectEmit(true, false, false, true, address(morphoDeposit)); + emit Harvest(address(token), 0, block.timestamp); + } + morphoDeposit.deposit(); + + sumDeposit += depositAmount[i]; + assertEq(morphoDeposit.depositedAmount(), sumDeposit); + } + + assertEq( + morphoDeposit.depositedAmount(), + morpho.balances(address(morphoDeposit)) + ); + } + + function testWithdrawAll(uint248[4] calldata depositAmount) public { + testDeposits(depositAmount); + + uint256 sumDeposit; + for (uint256 i = 0; i < 4; i++) { + sumDeposit += depositAmount[i]; + } + + assertEq(token.balanceOf(address(this)), 0); + + vm.prank(addresses.pcvControllerAddress); + morphoDeposit.withdrawAll(address(this)); + + assertEq(token.balanceOf(address(this)), sumDeposit); + assertEq(morphoDeposit.balance(), 0); + assertEq(morphoDeposit.depositedAmount(), 0); + } + + function testAccrue( + uint248[4] calldata depositAmount, + uint120 profitAccrued + ) public { + testDeposits(depositAmount); + + uint256 sumDeposit; + for (uint256 i = 0; i < 4; i++) { + sumDeposit += depositAmount[i]; + } + morpho.setBalance(address(morphoDeposit), sumDeposit + profitAccrued); + + vm.expectEmit(true, false, false, true, address(morphoDeposit)); + emit Harvest( + address(token), + uint256(profitAccrued).toInt256(), + block.timestamp + ); + + assertEq(morphoDeposit.accrue(), sumDeposit + profitAccrued); + } + + function testWithdraw( + uint248[4] calldata depositAmount, + uint248[10] calldata withdrawAmount, + uint120 profitAccrued, + address to + ) public { + vm.assume(to != address(0)); + testAccrue(depositAmount, profitAccrued); + token.mint(address(morpho), profitAccrued); /// top up balance so withdraws don't revert + + uint256 sumDeposit = uint256(depositAmount[0]) + + uint256(depositAmount[1]) + + uint256(depositAmount[2]) + + uint256(depositAmount[3]) + + uint256(profitAccrued); + for (uint256 i = 0; i < 10; i++) { + uint256 amountToWithdraw = withdrawAmount[i]; + if (amountToWithdraw > sumDeposit) { + /// skip if not enough to withdraw + continue; + } + + sumDeposit -= amountToWithdraw; + + vm.prank(addresses.pcvControllerAddress); + + /// TODO finalize this later + // vm.expectEmit(true, true, false, true, address(morphoDeposit)); + // emit Withdrawal(addresses.pcvControllerAddress, to, amountToWithdraw); + + vm.expectEmit(true, false, false, true, address(morphoDeposit)); + emit Harvest(address(token), 0, block.timestamp); /// no profits as already accrued + morphoDeposit.withdraw(to, amountToWithdraw); + + assertEq(morphoDeposit.depositedAmount(), sumDeposit); + assertEq( + morphoDeposit.depositedAmount(), + morpho.balances(address(morphoDeposit)) + ); + } + } + + function testEmergencyActionWithdrawSucceedsGovernor(uint256 amount) + public + { + token.mint(address(morphoDeposit), amount); + morphoDeposit.deposit(); + + MorphoCompoundPCVDeposit.Call[] + memory calls = new MorphoCompoundPCVDeposit.Call[](1); + calls[0].callData = abi.encodeWithSignature( + "withdraw(address,uint256)", + address(this), + amount + ); + calls[0].target = address(morpho); + + vm.prank(addresses.governorAddress); + morphoDeposit.emergencyAction(calls); + + assertEq(morphoDeposit.depositedAmount(), amount); + assertEq(morphoDeposit.balance(), 0); + } + + function testEmergencyActionSucceedsGovernorDeposit(uint256 amount) public { + vm.assume(amount != 0); + token.mint(address(morphoDeposit), amount); + + MorphoCompoundPCVDeposit.Call[] + memory calls = new MorphoCompoundPCVDeposit.Call[](2); + calls[0].callData = abi.encodeWithSignature( + "approve(address,uint256)", + address(morphoDeposit.morpho()), + amount + ); + calls[0].target = address(token); + calls[1].callData = abi.encodeWithSignature( + "supply(address,address,uint256)", + address(morphoDeposit), + address(morphoDeposit), + amount + ); + calls[1].target = address(morpho); + + vm.prank(addresses.governorAddress); + morphoDeposit.emergencyAction(calls); + + console.log( + "morphoDeposit.depositedAmount(): ", + morphoDeposit.depositedAmount() + ); + console.log("morphoDeposit.balance(): ", morphoDeposit.balance()); + assertEq(morphoDeposit.depositedAmount(), 0); + assertEq(morphoDeposit.balance(), amount); + } + + //// access controls + + function testEmergencyActionFailsNonGovernor() public { + MorphoCompoundPCVDeposit.Call[] + memory calls = new MorphoCompoundPCVDeposit.Call[](1); + calls[0].callData = abi.encodeWithSignature( + "withdraw(address,uint256)", + address(this), + 100 + ); + calls[0].target = address(morpho); + + vm.expectRevert("CoreRef: Caller is not a governor"); + morphoDeposit.emergencyAction(calls); + } + + function testWithdrawFailsNonGovernor() public { + vm.expectRevert("CoreRef: Caller is not a PCV controller"); + morphoDeposit.withdraw(address(this), 100); + } + + function testWithdrawAllFailsNonGovernor() public { + vm.expectRevert("CoreRef: Caller is not a PCV controller"); + morphoDeposit.withdrawAll(address(this)); + } + + //// reentrancy + + function _reentrantSetup() private { + morphoDeposit = new MorphoCompoundPCVDeposit( + address(core), + address(0), /// cToken is not used in mock morpho deposit + address(token), + address(maliciousMorpho), + address(maliciousMorpho) + ); + + maliciousMorpho.setMorphoCompoundPCVDeposit(address(morphoDeposit)); + } + + function testReentrantAccrueFails() public { + _reentrantSetup(); + vm.expectRevert("ReentrancyGuard: reentrant call"); + morphoDeposit.accrue(); + } + + function testReentrantDepositFails() public { + _reentrantSetup(); + token.mint(address(morphoDeposit), 100); + vm.expectRevert("ReentrancyGuard: reentrant call"); + morphoDeposit.deposit(); + } + + function testReentrantWithdrawFails() public { + _reentrantSetup(); + vm.prank(addresses.pcvControllerAddress); + vm.expectRevert("ReentrancyGuard: reentrant call"); + morphoDeposit.withdraw(address(this), 0); + } + + function testReentrantWithdrawAllFails() public { + _reentrantSetup(); + vm.prank(addresses.pcvControllerAddress); + vm.expectRevert("ReentrancyGuard: reentrant call"); + morphoDeposit.withdrawAll(address(this)); + } +} From 30562222e9940e6277fe301e02af02a30edf9664 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 20 Oct 2022 02:13:01 -0700 Subject: [PATCH 59/84] update ts deployment script --- proposals/dao/vip_14.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/proposals/dao/vip_14.ts b/proposals/dao/vip_14.ts index 26f431779..278b3ebed 100644 --- a/proposals/dao/vip_14.ts +++ b/proposals/dao/vip_14.ts @@ -41,14 +41,18 @@ const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: Named const daiMorphoCompoundPCVDeposit = await morphoPCVDepositFactory.deploy( addresses.core, addresses.cDai, - addresses.dai + addresses.dai, + addresses.morphoCompound, + addresses.morphoCompoundLens ); await daiMorphoCompoundPCVDeposit.deployed(); const usdcMorphoCompoundPCVDeposit = await morphoPCVDepositFactory.deploy( addresses.core, addresses.cUsdc, - addresses.usdc + addresses.usdc, + addresses.morphoCompound, + addresses.morphoCompoundLens ); await usdcMorphoCompoundPCVDeposit.deployed(); @@ -115,11 +119,11 @@ const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, expect(await daiMorphoCompoundPCVDeposit.cToken()).to.be.equal(addresses.cDai); expect(await usdcMorphoCompoundPCVDeposit.cToken()).to.be.equal(addresses.cUsdc); - expect(await daiMorphoCompoundPCVDeposit.MORPHO()).to.be.equal(addresses.morphoCompound); - expect(await usdcMorphoCompoundPCVDeposit.MORPHO()).to.be.equal(addresses.morphoCompound); + expect(await daiMorphoCompoundPCVDeposit.morpho()).to.be.equal(addresses.morphoCompound); + expect(await usdcMorphoCompoundPCVDeposit.morpho()).to.be.equal(addresses.morphoCompound); - expect(await daiMorphoCompoundPCVDeposit.LENS()).to.be.equal(addresses.morphoCompoundLens); - expect(await usdcMorphoCompoundPCVDeposit.LENS()).to.be.equal(addresses.morphoCompoundLens); + expect(await daiMorphoCompoundPCVDeposit.lens()).to.be.equal(addresses.morphoCompoundLens); + expect(await usdcMorphoCompoundPCVDeposit.lens()).to.be.equal(addresses.morphoCompoundLens); /// pcv guardian validation expect(await pcvGuardian.isWhitelistAddress(usdcMorphoCompoundPCVDeposit.address)).to.be.true; From 457613d55a298dfb6c72e02718270e27cfdd1b31 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 20 Oct 2022 02:27:44 -0700 Subject: [PATCH 60/84] remove console.log --- contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol | 2 -- contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol | 2 -- 2 files changed, 4 deletions(-) diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index d28fd0e62..6c3dfe5cc 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -12,8 +12,6 @@ import {CoreRef} from "../../refs/CoreRef.sol"; import {PCVDeposit} from "../PCVDeposit.sol"; import {ICompoundOracle, ICToken} from "./ICompound.sol"; -import "hardhat/console.sol"; - /// @notice PCV Deposit for Morpho-Compound V2. /// Implements the PCV Deposit interface to deposit and withdraw funds in Morpho /// Liquidity profile of Morpho for this deposit is fully liquid for USDC and DAI diff --git a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol index d60f5af6c..8b0fdd6ef 100644 --- a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol @@ -15,8 +15,6 @@ import {MorphoCompoundPCVDeposit} from "../../../../pcv/morpho/MorphoCompoundPCV import {MockMorphoMaliciousReentrancy} from "../../../../mock/MockMorphoMaliciousReentrancy.sol"; import {getCore, getAddresses, VoltTestAddresses} from "./../../utils/Fixtures.sol"; -import "hardhat/console.sol"; - contract UnitTestMorphoCompoundPCVDeposit is DSTest { using SafeCast for *; From 42d92da02d02208fcec9017120a1305f6a2e48b4 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 20 Oct 2022 12:10:46 -0700 Subject: [PATCH 61/84] update comments on how fast interest accrues at current rates, fix expectEmit assertion, remove console logs --- .../pcv/morpho/MorphoCompoundPCVDeposit.sol | 38 +++++++++++++++---- ...egrationTestMorphoCompoundPCVDeposit.t.sol | 2 +- .../pcv/morpho/MorphoCompoundPCVDeposit.t.sol | 23 ++++++----- 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index 6c3dfe5cc..09d615fea 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -26,6 +26,15 @@ import {ICompoundOracle, ICToken} from "./ICompound.sol"; /// has no concept of mTokens. This means if the contract is paused, or an issue is /// surfaced in Morpho and liquidity is locked, Volt will need to rely on social /// coordination with the Morpho team to recover funds. +/// @dev Depositing and withdrawing in a single block will cause a very small loss +/// of funds, less than a pip. The way to not realize this loss is by depositing and +/// then withdrawing at least 1 block later. That way, interest accrues. +/// This is not a Morpho specific issue. Compound rounds in the protocol's favor. +/// The issue is caused by constraints inherent to solidity and the EVM. +/// There are no floating point numbers, this means there is precision loss, +/// and protocol engineers are forced to choose who to round in favor of. +/// Engineers must round in favor of the protocol to avoid deposits of 0 giving +/// the user a balance. contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { using SafeERC20 for IERC20; using SafeCast for *; @@ -117,6 +126,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { } /// @notice withdraw tokens from the PCV allocation + /// non-reentrant as state changes and external calls are made /// @param to the address PCV will be sent to /// @param amount of tokens withdrawn function withdraw(address to, uint256 amount) @@ -124,32 +134,43 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { onlyPCVController nonReentrant { - _withdraw(to, amount); + _withdraw(to, amount, true); } /// @notice withdraw all tokens from Morpho + /// non-reentrant as state changes and external calls are made /// @param to the address PCV will be sent to function withdrawAll(address to) external onlyPCVController nonReentrant { /// compute profit from interest accrued and emit an event _recordPNL(); /// withdraw deposited amount as this was updated in record pnl - _withdraw(to, depositedAmount); + _withdraw(to, depositedAmount, false); } /// @notice helper function to avoid repeated code in withdraw and withdrawAll /// anytime this function is called it is by an external function in this smart contract /// with a reentrancy guard. this ensures depositedAmount never desynchronizes - /// Morpho is assumed to be a loss-less venue. over the course of less than 5 blocks, - /// it is possible to lose funds. However, over 5 blocks, deposits are expected to always - /// be in profit. + /// Morpho is assumed to be a loss-less venue. over the course of less than 1 block, + /// it is possible to lose funds. However, after 1 block, deposits are expected to always + /// be in profit at least with current interest rates around 0.8% natively on Compound, ignoring all COMP rewards. /// if losses are ever sustained, subtracting amount from depositedAmount will mean /// that this function always reverts, meaning emergencyAction will need to be called - function _withdraw(address to, uint256 amount) private { + /// @param to recipient of withdraw funds + /// @param amount to withdraw + /// @param recordPnl whether or not to record PnL. Set to false in withdrawAll + /// as the function _recordPNL() is already called before _withdraw + function _withdraw( + address to, + uint256 amount, + bool recordPnl + ) private { /// ------ Effects ------ - /// compute profit from interest accrued and emit an event - _recordPNL(); + if (recordPnl) { + /// compute profit from interest accrued and emit a Harvest event + _recordPNL(); + } /// update tracked deposit amount depositedAmount -= amount; @@ -200,6 +221,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// since the last contract interaction /// then writes the current amount of PCV tracked in this contract /// to depositedAmount + /// @return the amount deposited after adding accrued interest function accrue() external nonReentrant returns (uint256) { _recordPNL(); /// update deposit amount and fire harvest event diff --git a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol index 3e59f0998..b794cd095 100644 --- a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol @@ -77,7 +77,7 @@ contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { usdcDeposit.deposit(); daiDeposit.deposit(); - vm.roll(block.number + 3); /// fast forward 3 blocks so that profit is not negative + vm.roll(block.number + 1); /// fast forward 1 block so that profit is positive } function testSetup() public { diff --git a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol index 8b0fdd6ef..0e92d6fbc 100644 --- a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol @@ -113,6 +113,12 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { assertEq(token.balanceOf(address(this)), 0); vm.prank(addresses.pcvControllerAddress); + vm.expectEmit(true, true, false, true, address(morphoDeposit)); + emit Withdrawal( + addresses.pcvControllerAddress, + address(this), + sumDeposit + ); morphoDeposit.withdrawAll(address(this)); assertEq(token.balanceOf(address(this)), sumDeposit); @@ -157,6 +163,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { uint256(depositAmount[2]) + uint256(depositAmount[3]) + uint256(profitAccrued); + for (uint256 i = 0; i < 10; i++) { uint256 amountToWithdraw = withdrawAmount[i]; if (amountToWithdraw > sumDeposit) { @@ -168,11 +175,12 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { vm.prank(addresses.pcvControllerAddress); - /// TODO finalize this later - // vm.expectEmit(true, true, false, true, address(morphoDeposit)); - // emit Withdrawal(addresses.pcvControllerAddress, to, amountToWithdraw); - - vm.expectEmit(true, false, false, true, address(morphoDeposit)); + vm.expectEmit(true, true, false, true, address(morphoDeposit)); + emit Withdrawal( + addresses.pcvControllerAddress, + to, + amountToWithdraw + ); emit Harvest(address(token), 0, block.timestamp); /// no profits as already accrued morphoDeposit.withdraw(to, amountToWithdraw); @@ -229,11 +237,6 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { vm.prank(addresses.governorAddress); morphoDeposit.emergencyAction(calls); - console.log( - "morphoDeposit.depositedAmount(): ", - morphoDeposit.depositedAmount() - ); - console.log("morphoDeposit.balance(): ", morphoDeposit.balance()); assertEq(morphoDeposit.depositedAmount(), 0); assertEq(morphoDeposit.balance(), amount); } From 26be8a2f7d494e025de38280c6389fe391b76f3c Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 20 Oct 2022 13:06:14 -0700 Subject: [PATCH 62/84] invariant tests Morpho --- .../InvariantTestMorphoPCVDeposit.t.sol | 109 ++++++++++++++++++ contracts/test/unit/utils/DSInvariantTest.sol | 16 +++ 2 files changed, 125 insertions(+) create mode 100644 contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol create mode 100644 contracts/test/unit/utils/DSInvariantTest.sol diff --git a/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol b/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol new file mode 100644 index 000000000..16721d2a5 --- /dev/null +++ b/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity =0.8.13; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {Vm} from "../unit/utils/Vm.sol"; +import {ICore} from "../../core/ICore.sol"; +import {DSTest} from "../unit/utils/DSTest.sol"; +import {MockERC20} from "../../mock/MockERC20.sol"; +import {MockMorpho} from "../../mock/MockMorpho.sol"; +import {DSInvariantTest} from "../unit/utils/DSInvariantTest.sol"; +import {MorphoCompoundPCVDeposit} from "../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; +import {getCore, getAddresses, VoltTestAddresses} from "../unit/utils/Fixtures.sol"; + +/// @dev Modified from Solmate ERC20 Invariant Test (https://github.com/transmissions11/solmate/blob/main/src/test/ERC20.t.sol) +contract InvariantTestMorphoCompoundPCVDeposit is DSTest, DSInvariantTest { + MorphoPCVDeposit public invariant; + ICore public core; + MorphoCompoundPCVDeposit private morphoDeposit; + MockMorpho private morpho; + MockERC20 private token; + + function setUp() public { + core = getCore(); + token = new MockERC20(); + morpho = new MockMorpho(IERC20(address(token))); + morphoDeposit = new MorphoCompoundPCVDeposit( + address(core), + address(0), /// cToken is not used in mock morpho deposit + address(token), + address(morpho), + address(morpho) + ); + invariant = new MorphoPCVDeposit(morphoDeposit, token, morpho); + + addTargetContract(address(invariant)); + } + + function invariantDepositedAmount() public { + assertEq(morphoDeposit.depositedAmount(), invariant.totalDeposited()); + assertEq(morphoDeposit.balance(), invariant.totalDeposited()); + } + + function invariantBalanceOf() public { + assertEq(morphoDeposit.balance(), morpho.balances(address(invariant))); + assertEq(morphoDeposit.balance(), token.balanceOf(address(morpho))); + } +} + +contract MorphoPCVDeposit is DSTest { + VoltTestAddresses public addresses = getAddresses(); + Vm private vm = Vm(HEVM_ADDRESS); + uint256 public totalDeposited; + MorphoCompoundPCVDeposit public immutable morphoDeposit; + MockERC20 private immutable token; + MockMorpho private immutable morpho; + + constructor( + MorphoCompoundPCVDeposit _morphoDeposit, + MockERC20 _token, + MockMorpho _morpho + ) { + morphoDeposit = _morphoDeposit; + token = _token; + morpho = _morpho; + } + + function increaseBalance(uint256 amount) public { + token.mint(address(morphoDeposit), amount); + morphoDeposit.deposit(); + unchecked { + /// unchecked because token or MockMorpho will revert + /// from an integer overflow + totalDeposited += amount; + } + } + + function decreaseBalance(uint256 amount) public { + if (amount > totalDeposited) return; + + vm.prank(addresses.pcvControllerAddress); + morphoDeposit.withdraw(address(this), amount); + unchecked { + /// unchecked because amount is always less than or equal + /// to totalDeposited + totalDeposited -= amount; + } + } + + function withdrawEntireBalance() public { + vm.prank(addresses.pcvControllerAddress); + morphoDeposit.withdrawAll(address(this)); + totalDeposited = 0; + } + + function increaseBalanceViaInterest(uint256 interestAmount) public { + token.mint(address(morpho), interestAmount); + morpho.setBalance( + address(morphoDeposit), + totalDeposited + interestAmount + ); + morphoDeposit.accrue(); /// accrue interest so morpho and pcv deposit are synced + unchecked { + /// unchecked because token or MockMorpho will revert + /// from an integer overflow + totalDeposited += interestAmount; + } + } +} diff --git a/contracts/test/unit/utils/DSInvariantTest.sol b/contracts/test/unit/utils/DSInvariantTest.sol new file mode 100644 index 000000000..5482ff2e1 --- /dev/null +++ b/contracts/test/unit/utils/DSInvariantTest.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity =0.8.13; + +contract DSInvariantTest { + address[] private targets; + + function targetContracts() public view virtual returns (address[] memory) { + require(targets.length > 0, "NO_TARGET_CONTRACTS"); + + return targets; + } + + function addTargetContract(address newTargetContract) internal virtual { + targets.push(newTargetContract); + } +} From 81cba104c8c210d8c6668790588fb80d0277b5f6 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 20 Oct 2022 14:59:01 -0700 Subject: [PATCH 63/84] add additional comments around depositedAmount --- contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index 09d615fea..0324d0e36 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -57,6 +57,9 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { address public immutable cToken; /// @notice track the current amount of PCV deposited in the contract + /// this is always out of date, except when accrue() is called + /// in the same block or transaction. This means the value is stale + /// most of the time. uint256 public depositedAmount; /// @param _core reference to the core contract @@ -111,6 +114,8 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { _recordPNL(); /// increment tracked deposited amount + /// this will be off by a hair, after a single block + /// delta disappears. depositedAmount += amount; /// ------ Interactions ------ From 0a90a64f867148b00329fb9aca6819439c83e1dc Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 20 Oct 2022 15:33:10 -0700 Subject: [PATCH 64/84] invariant tests for morpho compound pcv deposit --- contracts/mock/MockMorpho.sol | 4 +++ .../mock/MockMorphoMaliciousReentrancy.sol | 4 +++ .../pcv/morpho/MorphoCompoundPCVDeposit.sol | 23 ++++++++++++ .../InvariantTestMorphoPCVDeposit.t.sol | 35 +++++++++++-------- .../pcv/morpho/MorphoCompoundPCVDeposit.t.sol | 25 +++++++------ 5 files changed, 65 insertions(+), 26 deletions(-) diff --git a/contracts/mock/MockMorpho.sol b/contracts/mock/MockMorpho.sol index 410167f6c..bd94f9ae0 100644 --- a/contracts/mock/MockMorpho.sol +++ b/contracts/mock/MockMorpho.sol @@ -10,6 +10,10 @@ contract MockMorpho { token = _token; } + function underlying() external view returns (address) { + return address(token); + } + function withdraw(address, uint256 amount) external { balances[msg.sender] -= amount; token.transfer(msg.sender, amount); diff --git a/contracts/mock/MockMorphoMaliciousReentrancy.sol b/contracts/mock/MockMorphoMaliciousReentrancy.sol index 4527fc7ce..4fce08839 100644 --- a/contracts/mock/MockMorphoMaliciousReentrancy.sol +++ b/contracts/mock/MockMorphoMaliciousReentrancy.sol @@ -12,6 +12,10 @@ contract MockMorphoMaliciousReentrancy { token = _token; } + function underlying() external view returns (address) { + return address(token); + } + function setMorphoCompoundPCVDeposit(address deposit) external { morphoCompoundPCVDeposit = MorphoCompoundPCVDeposit(deposit); } diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index 0324d0e36..789312aaa 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -9,6 +9,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ILens} from "./ILens.sol"; import {IMorpho} from "./IMorpho.sol"; import {CoreRef} from "../../refs/CoreRef.sol"; +import {Constants} from "../../Constants.sol"; import {PCVDeposit} from "../PCVDeposit.sol"; import {ICompoundOracle, ICToken} from "./ICompound.sol"; @@ -74,6 +75,12 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { address _morpho, address _lens ) CoreRef(_core) ReentrancyGuard() { + if (_underlying != address(Constants.WETH)) { + require( + ICToken(_cToken).underlying() == _underlying, + "MorphoCompoundPCVDeposit: Underlying mismatch" + ); + } cToken = _cToken; token = _underlying; morpho = IMorpho(_morpho); @@ -170,6 +177,13 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { uint256 amount, bool recordPnl ) private { + /// ------ Check ------ + + /// no op if amount to withdraw is 0 + if (amount == 0) { + return; + } + /// ------ Effects ------ if (recordPnl) { @@ -195,13 +209,22 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// first accrue interest in Compound and Morpho morpho.updateP2PIndexes(cToken); + /// ------ Check ------ + /// then get the current balance from the market uint256 currentBalance = balance(); + /// save gas if contract has no balance + if (currentBalance == 0) { + return; + } + /// currentBalance should always be greater than or equal to /// the deposited amount int256 profit = currentBalance.toInt256() - depositedAmount.toInt256(); + /// ------ Effects ------ + /// record new deposited amount depositedAmount = currentBalance; diff --git a/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol b/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol index 16721d2a5..469a11bb5 100644 --- a/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol +++ b/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol @@ -12,13 +12,16 @@ import {DSInvariantTest} from "../unit/utils/DSInvariantTest.sol"; import {MorphoCompoundPCVDeposit} from "../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; import {getCore, getAddresses, VoltTestAddresses} from "../unit/utils/Fixtures.sol"; +/// note all variables have to be public and not immutable otherwise foundry +/// will not run invariant tests + /// @dev Modified from Solmate ERC20 Invariant Test (https://github.com/transmissions11/solmate/blob/main/src/test/ERC20.t.sol) contract InvariantTestMorphoCompoundPCVDeposit is DSTest, DSInvariantTest { - MorphoPCVDeposit public invariant; + MorphoPCVDepositTest public morphoTest; ICore public core; - MorphoCompoundPCVDeposit private morphoDeposit; - MockMorpho private morpho; - MockERC20 private token; + MorphoCompoundPCVDeposit public morphoDeposit; + MockMorpho public morpho; + MockERC20 public token; function setUp() public { core = getCore(); @@ -26,34 +29,36 @@ contract InvariantTestMorphoCompoundPCVDeposit is DSTest, DSInvariantTest { morpho = new MockMorpho(IERC20(address(token))); morphoDeposit = new MorphoCompoundPCVDeposit( address(core), - address(0), /// cToken is not used in mock morpho deposit + address(morpho), address(token), address(morpho), address(morpho) ); - invariant = new MorphoPCVDeposit(morphoDeposit, token, morpho); + morphoTest = new MorphoPCVDepositTest(morphoDeposit, token, morpho); - addTargetContract(address(invariant)); + addTargetContract(address(morphoTest)); } function invariantDepositedAmount() public { - assertEq(morphoDeposit.depositedAmount(), invariant.totalDeposited()); - assertEq(morphoDeposit.balance(), invariant.totalDeposited()); + assertEq(morphoDeposit.depositedAmount(), morphoTest.totalDeposited()); + assertEq(morphoDeposit.balance(), morphoTest.totalDeposited()); } function invariantBalanceOf() public { - assertEq(morphoDeposit.balance(), morpho.balances(address(invariant))); - assertEq(morphoDeposit.balance(), token.balanceOf(address(morpho))); + assertEq( + morphoDeposit.balance(), + morpho.balances(address(morphoDeposit)) + ); } } -contract MorphoPCVDeposit is DSTest { +contract MorphoPCVDepositTest is DSTest { VoltTestAddresses public addresses = getAddresses(); Vm private vm = Vm(HEVM_ADDRESS); uint256 public totalDeposited; - MorphoCompoundPCVDeposit public immutable morphoDeposit; - MockERC20 private immutable token; - MockMorpho private immutable morpho; + MorphoCompoundPCVDeposit public morphoDeposit; + MockERC20 public token; + MockMorpho public morpho; constructor( MorphoCompoundPCVDeposit _morphoDeposit, diff --git a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol index 0e92d6fbc..f1d3c23ac 100644 --- a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol @@ -49,7 +49,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { morphoDeposit = new MorphoCompoundPCVDeposit( address(core), - address(0), /// cToken is not used in mock morpho deposit + address(morpho), address(token), address(morpho), address(morpho) @@ -66,7 +66,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { assertEq(morphoDeposit.token(), address(token)); assertEq(morphoDeposit.lens(), address(morpho)); assertEq(address(morphoDeposit.morpho()), address(morpho)); - assertEq(morphoDeposit.cToken(), address(0)); + assertEq(morphoDeposit.cToken(), address(morpho)); assertEq(morphoDeposit.depositedAmount(), 0); } @@ -175,13 +175,16 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { vm.prank(addresses.pcvControllerAddress); - vm.expectEmit(true, true, false, true, address(morphoDeposit)); - emit Withdrawal( - addresses.pcvControllerAddress, - to, - amountToWithdraw - ); - emit Harvest(address(token), 0, block.timestamp); /// no profits as already accrued + if (amountToWithdraw != 0) { + vm.expectEmit(true, true, false, true, address(morphoDeposit)); + emit Withdrawal( + addresses.pcvControllerAddress, + to, + amountToWithdraw + ); + emit Harvest(address(token), 0, block.timestamp); /// no profits as already accrued + } + morphoDeposit.withdraw(to, amountToWithdraw); assertEq(morphoDeposit.depositedAmount(), sumDeposit); @@ -272,7 +275,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { function _reentrantSetup() private { morphoDeposit = new MorphoCompoundPCVDeposit( address(core), - address(0), /// cToken is not used in mock morpho deposit + address(maliciousMorpho), /// cToken is not used in mock morpho deposit address(token), address(maliciousMorpho), address(maliciousMorpho) @@ -298,7 +301,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { _reentrantSetup(); vm.prank(addresses.pcvControllerAddress); vm.expectRevert("ReentrancyGuard: reentrant call"); - morphoDeposit.withdraw(address(this), 0); + morphoDeposit.withdraw(address(this), 10); } function testReentrantWithdrawAllFails() public { From ab9c93edc75ac3878685451d2ab9f3a07affe747 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 20 Oct 2022 15:39:14 -0700 Subject: [PATCH 65/84] fix unit tests assertions around events being emitted --- .../test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol index f1d3c23ac..16f239c4c 100644 --- a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol @@ -18,6 +18,8 @@ import {getCore, getAddresses, VoltTestAddresses} from "./../../utils/Fixtures.s contract UnitTestMorphoCompoundPCVDeposit is DSTest { using SafeCast for *; + event Deposit(address indexed _from, uint256 _amount); + event Harvest(address indexed _token, int256 _profit, uint256 _timestamp); event Withdrawal( @@ -88,7 +90,10 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { if (depositAmount[i] != 0) { /// harvest event is not emitted if deposit amount is 0 vm.expectEmit(true, false, false, true, address(morphoDeposit)); - emit Harvest(address(token), 0, block.timestamp); + if (morphoDeposit.balance() != 0) { + emit Harvest(address(token), 0, block.timestamp); + } + emit Deposit(address(this), depositAmount[i]); } morphoDeposit.deposit(); From 1fc86aa30ab27f8ddc69b404909f4ecd91e02afb Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 20 Oct 2022 16:06:21 -0700 Subject: [PATCH 66/84] add underlying mismatch test --- contracts/mock/MockCToken.sol | 9 +++++++++ .../unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol | 14 ++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 contracts/mock/MockCToken.sol diff --git a/contracts/mock/MockCToken.sol b/contracts/mock/MockCToken.sol new file mode 100644 index 000000000..e7791c208 --- /dev/null +++ b/contracts/mock/MockCToken.sol @@ -0,0 +1,9 @@ +pragma solidity 0.8.13; + +contract MockCToken { + address public underlying; + + constructor(address _underlying) { + underlying = _underlying; + } +} diff --git a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol index 16f239c4c..d4aad55b7 100644 --- a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol @@ -6,6 +6,7 @@ import {Vm} from "./../../utils/Vm.sol"; import {ICore} from "../../../../core/ICore.sol"; import {DSTest} from "./../../utils/DSTest.sol"; import {MockERC20} from "../../../../mock/MockERC20.sol"; +import {MockCToken} from "../../../../mock/MockCToken.sol"; import {MockMorpho} from "../../../../mock/MockMorpho.sol"; import {TribeRoles} from "../../../../core/TribeRoles.sol"; import {IPCVDeposit} from "../../../../pcv/IPCVDeposit.sol"; @@ -72,6 +73,19 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { assertEq(morphoDeposit.depositedAmount(), 0); } + function testUnderlyingMismatchConstructionFails() public { + MockCToken cToken = new MockCToken(address(1)); + + vm.expectRevert("MorphoCompoundPCVDeposit: Underlying mismatch"); + new MorphoCompoundPCVDeposit( + address(core), + address(cToken), + address(token), + address(morpho), + address(morpho) + ); + } + function testDeposit(uint256 depositAmount) public { assertEq(morphoDeposit.depositedAmount(), 0); token.mint(address(morphoDeposit), depositAmount); From 72a45670f2b4024f9660af65232f333f047e114a Mon Sep 17 00:00:00 2001 From: Elliot Date: Fri, 21 Oct 2022 01:22:20 -0700 Subject: [PATCH 67/84] slither run 10-21 --- ...her.txt => morpho-maple-slither-10-18.txt} | 0 slither/slither-10-21-morpho.txt | 113 ++++++++++++++++++ 2 files changed, 113 insertions(+) rename slither/{morpho-maple-slither.txt => morpho-maple-slither-10-18.txt} (100%) create mode 100644 slither/slither-10-21-morpho.txt diff --git a/slither/morpho-maple-slither.txt b/slither/morpho-maple-slither-10-18.txt similarity index 100% rename from slither/morpho-maple-slither.txt rename to slither/morpho-maple-slither-10-18.txt diff --git a/slither/slither-10-21-morpho.txt b/slither/slither-10-21-morpho.txt new file mode 100644 index 000000000..e9bf64351 --- /dev/null +++ b/slither/slither-10-21-morpho.txt @@ -0,0 +1,113 @@ +MorphoCompoundPCVDeposit.deposit() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#109-138) uses a dangerous strict equality: + - amount == 0 (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#113) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities +Response: Strict equalities can be dangerous in the way described in the slither detector documentation. +However, if amount is 0 in either Maple or Morpho PCV Deposit, it is better to save gas and not execute further opcodes. +There is no unsafe behavior that is possible as a result of doing this strict equality that amount is 0. + +Reentrancy in MorphoCompoundPCVDeposit._withdraw(address,uint256,bool) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#175-203): + External calls: + - _recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#191) + - morpho.updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#210) + State variables written after the call(s): + - depositedAmount -= amount (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#195) +Reentrancy in MorphoCompoundPCVDeposit.deposit() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#109-138): + External calls: + - _recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#121) + - morpho.updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#210) + State variables written after the call(s): + - depositedAmount += amount (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#126) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1 +Response: Anytime that function _recordPNL is called, it is from a function with a reentrancy +lock. This means that reentrancy into this contract is not possible while a previous call has not +completed. Additionally, there is no way to make this contract in a way that conforms to CEI because +updateP2PIndexes must be called before getCurrentSupplyBalanceInOf is called. +All of these functions call _recordPNL(), however, they all have a non reentrant modifier so that they won't +allow depositedAmount to be manipulated through a reentrancy attack. + - withdraw + - withdrawAll + - accrue + - deposit + +MorphoCompoundPCVDeposit.deposit() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#109-138) ignores return value by IERC20(token).approve(address(morpho),amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#130) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return +Response: USDC returns true and does some validation that the addresses are incorrect. +As long as the validations succeed, the function will return true. If the validations fail, +the call will revert. +DAI returns true and has no validation checks, which means the only way the approve call can fail +is if it is out of gas. + +MorphoCompoundPCVDeposit.constructor(address,address,address,address,address)._core (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#72) shadows: + - CoreRef._core (contracts/refs/CoreRef.sol#11) (state variable) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing +Response: Author acknowledges the issue, will not fix. + +MorphoCompoundPCVDeposit.constructor(address,address,address,address,address)._lens (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#76) lacks a zero-check on : + - lens = _lens (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#87) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation +Response: Author acknowledges the issue, will not fix because integration tests will validate the parameter is set correctly. + +MorphoCompoundPCVDeposit.emergencyAction(MorphoCompoundPCVDeposit.Call[]) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#274-287) has external calls inside a loop: (success,returned) = calls[i].target.call(calls[i].callData) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#281-283) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop +Response: DoS attacks are not a valid vector when sender is a trusted governor and the function mutates no internal contract state. + +Reentrancy in MorphoCompoundPCVDeposit._recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#208-233): + External calls: + - morpho.updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#210) + State variables written after the call(s): + - depositedAmount = currentBalance (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#229) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2 +Response: Anytime that function _recordPNL is called, it is from a function with a reentrancy +lock. This means that reentrancy into this contract is not possible while a previous call has not +completed. Additionally, there is no way to make this contract in a way that conforms to CEI because +updateP2PIndexes must be called before getCurrentSupplyBalanceInOf is called. + +Reentrancy in MorphoCompoundPCVDeposit._recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#208-233): + External calls: + - morpho.updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#210) + Event emitted after the call(s): + - Harvest(token,profit,block.timestamp) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#232) +Reentrancy in ERC20Allocator._skim(address,address) (contracts/pcv/utils/ERC20Allocator.sol#194-223): + External calls: + - PCVDeposit(psm).withdraw(pcvDeposit,amountToSkim) (contracts/pcv/utils/ERC20Allocator.sol#217) + - PCVDeposit(pcvDeposit).deposit() (contracts/pcv/utils/ERC20Allocator.sol#220) + Event emitted after the call(s): + - Skimmed(amountToSkim,pcvDeposit) (contracts/pcv/utils/ERC20Allocator.sol#222) +Reentrancy in MorphoCompoundPCVDeposit._withdraw(address,uint256,bool) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#175-203): + External calls: + - _recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#191) + - morpho.updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#210) + - morpho.withdraw(cToken,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#199) + - IERC20(token).safeTransfer(to,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#200) + Event emitted after the call(s): + - Withdrawal(msg.sender,to,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#202) +Reentrancy in MorphoCompoundPCVDeposit.deposit() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#109-138): + External calls: + - _recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#121) + - morpho.updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#210) + - IERC20(token).approve(address(morpho),amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#130) + - morpho.supply(cToken,address(this),amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#131-135) + Event emitted after the call(s): + - Deposit(msg.sender,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#137) +Reentrancy in MorphoCompoundPCVDeposit.harvest() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#238-246): + External calls: + - claimedAmount = morpho.claimRewards(cTokens,false) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#243) + Event emitted after the call(s): + - Harvest(COMP,int256(claimedAmount),block.timestamp) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#245) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3 +Response: Reentrancy is only an issue if it allows theft of funds and updating of state incorrectly in an intermediate state. +None of the listed functions mutate any state internal to the contract. If there was a collateralization oracle in the system, +then these issues would be worth investigating further and ensuring state could not get mixed up in a way that allowed an +attacker to manipulate the target price of Volt. + +Pragma version=0.8.13 (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#2) necessitates a version too recent to be trusted. Consider deploying with 0.6.12/0.7.6/0.8.7 +solc-0.8.13 is not recommended for deployment +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity +Response: Author has reviewed the solidity 0.8.13 list of known bugs https://docs.soliditylang.org/en/v0.8.13/bugs.html +and not found any high issues that would lead to incorrect functioning of the smart contracts. + +Low level call in MorphoCompoundPCVDeposit.emergencyAction(MorphoCompoundPCVDeposit.Call[]) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#274-287): + - (success,returned) = calls[i].target.call(calls[i].callData) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#281-283) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls +Response: This low level call is intentional and can be used during an emergency action if funds need to be moved +out of Maple in a way the author has not yet conceived of. From c8ca5d78e4627f01a838c55a4ade72e5a1cbc5ac Mon Sep 17 00:00:00 2001 From: Elliot Date: Fri, 21 Oct 2022 11:03:31 -0700 Subject: [PATCH 68/84] add depositedAmount check equal 0 in recordPNL function --- contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index 789312aaa..46adc37f0 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -215,7 +215,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { uint256 currentBalance = balance(); /// save gas if contract has no balance - if (currentBalance == 0) { + if (currentBalance == 0 && depositedAmount == 0) { return; } From 414b443bcf20ea4909292abdf5763fe11aeb6a1d Mon Sep 17 00:00:00 2001 From: Elliot Date: Fri, 21 Oct 2022 11:22:52 -0700 Subject: [PATCH 69/84] address Erwan feedback --- .../pcv/morpho/MorphoCompoundPCVDeposit.sol | 16 +++++++--------- .../pcv/morpho/MorphoCompoundPCVDeposit.t.sol | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index 46adc37f0..7a953cd32 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -48,7 +48,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { address public immutable lens; /// @notice reference to the morpho-compound v2 market - IMorpho public immutable morpho; + address public immutable morpho; /// @notice reference to underlying token address public immutable token; @@ -83,7 +83,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { } cToken = _cToken; token = _underlying; - morpho = IMorpho(_morpho); + morpho = _morpho; lens = _lens; } @@ -128,7 +128,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// ------ Interactions ------ IERC20(token).approve(address(morpho), amount); - morpho.supply( + IMorpho(morpho).supply( cToken, /// cToken to supply liquidity to address(this), /// the address of the user you want to supply on behalf of amount @@ -166,8 +166,6 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// Morpho is assumed to be a loss-less venue. over the course of less than 1 block, /// it is possible to lose funds. However, after 1 block, deposits are expected to always /// be in profit at least with current interest rates around 0.8% natively on Compound, ignoring all COMP rewards. - /// if losses are ever sustained, subtracting amount from depositedAmount will mean - /// that this function always reverts, meaning emergencyAction will need to be called /// @param to recipient of withdraw funds /// @param amount to withdraw /// @param recordPnl whether or not to record PnL. Set to false in withdrawAll @@ -196,7 +194,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// ------ Interactions ------ - morpho.withdraw(cToken, amount); + IMorpho(morpho).withdraw(cToken, amount); IERC20(token).safeTransfer(to, amount); emit Withdrawal(msg.sender, to, amount); @@ -207,7 +205,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// updates the amount deposited to include all interest earned function _recordPNL() private { /// first accrue interest in Compound and Morpho - morpho.updateP2PIndexes(cToken); + IMorpho(morpho).updateP2PIndexes(cToken); /// ------ Check ------ @@ -240,7 +238,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { cTokens[0] = cToken; /// set swap comp to morpho flag false to claim comp rewards - uint256 claimedAmount = morpho.claimRewards(cTokens, false); + uint256 claimedAmount = IMorpho(morpho).claimRewards(cTokens, false); emit Harvest(COMP, int256(claimedAmount), block.timestamp); } @@ -250,7 +248,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// then writes the current amount of PCV tracked in this contract /// to depositedAmount /// @return the amount deposited after adding accrued interest - function accrue() external nonReentrant returns (uint256) { + function accrue() external nonReentrant whenNotPaused returns (uint256) { _recordPNL(); /// update deposit amount and fire harvest event return depositedAmount; /// return updated deposit amount diff --git a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol index d4aad55b7..94fa4545a 100644 --- a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol @@ -263,6 +263,22 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { assertEq(morphoDeposit.balance(), amount); } + //// paused + + function testDepositWhenPausedFails() public { + vm.prank(addresses.governorAddress); + morphoDeposit.pause(); + vm.expectRevert("Pausable: paused"); + morphoDeposit.deposit(); + } + + function testAccrueWhenPausedFails() public { + vm.prank(addresses.governorAddress); + morphoDeposit.pause(); + vm.expectRevert("Pausable: paused"); + morphoDeposit.accrue(); + } + //// access controls function testEmergencyActionFailsNonGovernor() public { From 4d59814351b3e16b4551b39265841ac51518a81b Mon Sep 17 00:00:00 2001 From: Elliot Date: Fri, 21 Oct 2022 11:35:44 -0700 Subject: [PATCH 70/84] add vip14 to runner, remove vip14a --- contracts/test/integration/vip/Runner.sol | 46 +++++++++++------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/contracts/test/integration/vip/Runner.sol b/contracts/test/integration/vip/Runner.sol index a64f556bb..2d9c1e053 100644 --- a/contracts/test/integration/vip/Runner.sol +++ b/contracts/test/integration/vip/Runner.sol @@ -1,6 +1,6 @@ pragma solidity =0.8.13; -import {vip14a} from "./vip14a.sol"; +import {vip14} from "./vip14.sol"; // import {vipx} from "./vipx.sol"; import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; import {TimelockSimulation} from "../utils/TimelockSimulation.sol"; @@ -10,7 +10,7 @@ import {PCVGuardian} from "./../../../pcv/PCVGuardian.sol"; /// @dev test harness for running and simulating VOLT Improvement Proposals /// inherit the proposal to simulate -contract Runner is TimelockSimulation, vip14a { +contract Runner is TimelockSimulation, vip14 { /// @notice mainnet PCV Guardian PCVGuardian private immutable mainnetPCVGuardian = PCVGuardian(MainnetAddresses.PCV_GUARDIAN); @@ -22,30 +22,30 @@ contract Runner is TimelockSimulation, vip14a { /// remove all function calls inside testProposal and don't inherit the VIP /// once the proposal is live and passed function testProposalMainnet() public { - // mainnetSetup(); - // simulate( - // getMainnetProposal(), - // TimelockController(payable(MainnetAddresses.TIMELOCK_CONTROLLER)), - // mainnetPCVGuardian, - // MainnetAddresses.GOVERNOR, - // MainnetAddresses.EOA_1, - // vm, - // true - // ); - // mainnetValidate(); - } - - function testProposalArbitrum() public { - arbitrumSetup(); + mainnetSetup(); simulate( - getArbitrumProposal(), - TimelockController(payable(ArbitrumAddresses.TIMELOCK_CONTROLLER)), - arbitrumPCVGuardian, - ArbitrumAddresses.GOVERNOR, - ArbitrumAddresses.EOA_1, + getMainnetProposal(), + TimelockController(payable(MainnetAddresses.TIMELOCK_CONTROLLER)), + mainnetPCVGuardian, + MainnetAddresses.GOVERNOR, + MainnetAddresses.EOA_1, vm, true ); - arbitrumValidate(); + mainnetValidate(); + } + + function testProposalArbitrum() public { + // arbitrumSetup(); + // simulate( + // getArbitrumProposal(), + // TimelockController(payable(ArbitrumAddresses.TIMELOCK_CONTROLLER)), + // arbitrumPCVGuardian, + // ArbitrumAddresses.GOVERNOR, + // ArbitrumAddresses.EOA_1, + // vm, + // true + // ); + // arbitrumValidate(); } } From 52f3ef64ccee4a9d8cdad5cb386387d455ec724b Mon Sep 17 00:00:00 2001 From: Elliot Date: Fri, 21 Oct 2022 13:21:35 -0700 Subject: [PATCH 71/84] change name of depositedAmount to lastRecordedBalance, update tests to accomodate this, and update comments --- .../pcv/morpho/MorphoCompoundPCVDeposit.sol | 30 +++++++++++-------- ...egrationTestMorphoCompoundPCVDeposit.t.sol | 7 +++++ contracts/test/integration/vip/vip14.sol | 5 ++++ .../InvariantTestMorphoPCVDeposit.t.sol | 7 +++-- .../pcv/morpho/MorphoCompoundPCVDeposit.t.sol | 20 ++++++------- 5 files changed, 44 insertions(+), 25 deletions(-) diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index 7a953cd32..3a4c99b21 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -57,11 +57,11 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// used to inform morpho about the desired market to supply liquidity address public immutable cToken; - /// @notice track the current amount of PCV deposited in the contract + /// @notice track the last amount of PCV recorded in the contract /// this is always out of date, except when accrue() is called /// in the same block or transaction. This means the value is stale /// most of the time. - uint256 public depositedAmount; + uint256 public lastRecordedBalance; /// @param _core reference to the core contract /// @param _cToken cToken this deposit references @@ -105,7 +105,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// @notice deposit ERC-20 tokens to Morpho-Compound /// non-reentrant to block malicious reentrant state changes - /// to the depositedAmount variable + /// to the lastRecordedBalance variable function deposit() public whenNotPaused nonReentrant { /// ------ Check ------ @@ -123,7 +123,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// increment tracked deposited amount /// this will be off by a hair, after a single block /// delta disappears. - depositedAmount += amount; + lastRecordedBalance += amount; /// ------ Interactions ------ @@ -157,12 +157,12 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { _recordPNL(); /// withdraw deposited amount as this was updated in record pnl - _withdraw(to, depositedAmount, false); + _withdraw(to, lastRecordedBalance, false); } /// @notice helper function to avoid repeated code in withdraw and withdrawAll /// anytime this function is called it is by an external function in this smart contract - /// with a reentrancy guard. this ensures depositedAmount never desynchronizes + /// with a reentrancy guard. this ensures lastRecordedBalance never desynchronizes /// Morpho is assumed to be a loss-less venue. over the course of less than 1 block, /// it is possible to lose funds. However, after 1 block, deposits are expected to always /// be in profit at least with current interest rates around 0.8% natively on Compound, ignoring all COMP rewards. @@ -190,7 +190,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { } /// update tracked deposit amount - depositedAmount -= amount; + lastRecordedBalance -= amount; /// ------ Interactions ------ @@ -213,18 +213,22 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { uint256 currentBalance = balance(); /// save gas if contract has no balance - if (currentBalance == 0 && depositedAmount == 0) { + /// if cost basis is 0 and last recorded balance is 0 + /// there is no profit or loss to record and no reason + /// to update lastRecordedBalance + if (currentBalance == 0 && lastRecordedBalance == 0) { return; } /// currentBalance should always be greater than or equal to - /// the deposited amount - int256 profit = currentBalance.toInt256() - depositedAmount.toInt256(); + /// the deposited amount, except on the same block a deposit occurs, or a loss event in morpho + int256 profit = currentBalance.toInt256() - + lastRecordedBalance.toInt256(); /// ------ Effects ------ /// record new deposited amount - depositedAmount = currentBalance; + lastRecordedBalance = currentBalance; /// profit is in underlying token emit Harvest(token, profit, block.timestamp); @@ -246,12 +250,12 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// @notice function that emits an event tracking profits and losses /// since the last contract interaction /// then writes the current amount of PCV tracked in this contract - /// to depositedAmount + /// to lastRecordedBalance /// @return the amount deposited after adding accrued interest function accrue() external nonReentrant whenNotPaused returns (uint256) { _recordPNL(); /// update deposit amount and fire harvest event - return depositedAmount; /// return updated deposit amount + return lastRecordedBalance; /// return updated pcv amount } // ---------- Emergency Action ---------- diff --git a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol index b794cd095..c08aecb9e 100644 --- a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol @@ -83,6 +83,10 @@ contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { function testSetup() public { assertEq(address(daiDeposit.core()), address(core)); assertEq(address(usdcDeposit.core()), address(core)); + assertEq(daiDeposit.morpho(), MainnetAddresses.MORPHO); + assertEq(usdcDeposit.morpho(), MainnetAddresses.MORPHO); + assertEq(daiDeposit.lens(), MainnetAddresses.MORPHO_LENS); + assertEq(usdcDeposit.lens(), MainnetAddresses.MORPHO_LENS); assertEq(daiDeposit.balanceReportedIn(), address(dai)); assertEq(usdcDeposit.balanceReportedIn(), address(usdc)); @@ -96,6 +100,9 @@ contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { assertEq(address(daiDeposit.token()), address(MainnetAddresses.DAI)); assertEq(address(usdcDeposit.token()), address(MainnetAddresses.USDC)); + assertEq(daiDeposit.lastRecordedBalance(), targetDaiBalance); + assertEq(usdcDeposit.lastRecordedBalance(), targetUsdcBalance); + assertApproxEq( daiDeposit.balance().toInt256(), targetDaiBalance.toInt256(), diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index 79a8f6616..9b25d4d62 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -238,6 +238,11 @@ contract vip14 is DSTest, IVIP { assertEq(address(daiDeposit.core()), core); assertEq(address(router.core()), core); + assertEq(daiDeposit.morpho(), MainnetAddresses.MORPHO); + assertEq(usdcDeposit.morpho(), MainnetAddresses.MORPHO); + assertEq(daiDeposit.lens(), MainnetAddresses.MORPHO_LENS); + assertEq(usdcDeposit.lens(), MainnetAddresses.MORPHO_LENS); + assertTrue(Core(core).isPCVController(address(router))); assertTrue( !Core(core).isPCVController(MainnetAddresses.COMPOUND_PCV_ROUTER) diff --git a/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol b/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol index 469a11bb5..fdbcfaa21 100644 --- a/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol +++ b/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol @@ -39,8 +39,11 @@ contract InvariantTestMorphoCompoundPCVDeposit is DSTest, DSInvariantTest { addTargetContract(address(morphoTest)); } - function invariantDepositedAmount() public { - assertEq(morphoDeposit.depositedAmount(), morphoTest.totalDeposited()); + function invariantLastRecordedBalance() public { + assertEq( + morphoDeposit.lastRecordedBalance(), + morphoTest.totalDeposited() + ); assertEq(morphoDeposit.balance(), morphoTest.totalDeposited()); } diff --git a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol index 94fa4545a..c566279b6 100644 --- a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol @@ -70,7 +70,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { assertEq(morphoDeposit.lens(), address(morpho)); assertEq(address(morphoDeposit.morpho()), address(morpho)); assertEq(morphoDeposit.cToken(), address(morpho)); - assertEq(morphoDeposit.depositedAmount(), 0); + assertEq(morphoDeposit.lastRecordedBalance(), 0); } function testUnderlyingMismatchConstructionFails() public { @@ -87,12 +87,12 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { } function testDeposit(uint256 depositAmount) public { - assertEq(morphoDeposit.depositedAmount(), 0); + assertEq(morphoDeposit.lastRecordedBalance(), 0); token.mint(address(morphoDeposit), depositAmount); morphoDeposit.deposit(); - assertEq(morphoDeposit.depositedAmount(), depositAmount); + assertEq(morphoDeposit.lastRecordedBalance(), depositAmount); } function testDeposits(uint248[4] calldata depositAmount) public { @@ -112,11 +112,11 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { morphoDeposit.deposit(); sumDeposit += depositAmount[i]; - assertEq(morphoDeposit.depositedAmount(), sumDeposit); + assertEq(morphoDeposit.lastRecordedBalance(), sumDeposit); } assertEq( - morphoDeposit.depositedAmount(), + morphoDeposit.lastRecordedBalance(), morpho.balances(address(morphoDeposit)) ); } @@ -142,7 +142,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { assertEq(token.balanceOf(address(this)), sumDeposit); assertEq(morphoDeposit.balance(), 0); - assertEq(morphoDeposit.depositedAmount(), 0); + assertEq(morphoDeposit.lastRecordedBalance(), 0); } function testAccrue( @@ -206,9 +206,9 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { morphoDeposit.withdraw(to, amountToWithdraw); - assertEq(morphoDeposit.depositedAmount(), sumDeposit); + assertEq(morphoDeposit.lastRecordedBalance(), sumDeposit); assertEq( - morphoDeposit.depositedAmount(), + morphoDeposit.lastRecordedBalance(), morpho.balances(address(morphoDeposit)) ); } @@ -232,7 +232,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { vm.prank(addresses.governorAddress); morphoDeposit.emergencyAction(calls); - assertEq(morphoDeposit.depositedAmount(), amount); + assertEq(morphoDeposit.lastRecordedBalance(), amount); assertEq(morphoDeposit.balance(), 0); } @@ -259,7 +259,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { vm.prank(addresses.governorAddress); morphoDeposit.emergencyAction(calls); - assertEq(morphoDeposit.depositedAmount(), 0); + assertEq(morphoDeposit.lastRecordedBalance(), 0); assertEq(morphoDeposit.balance(), amount); } From d1e26326ee53d6174078f9837464046249b5fd1c Mon Sep 17 00:00:00 2001 From: Elliot Date: Fri, 21 Oct 2022 13:54:29 -0700 Subject: [PATCH 72/84] update comments in MorphoPCVDeposit --- .../pcv/morpho/MorphoCompoundPCVDeposit.sol | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index 3a4c99b21..b2f9495d2 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -118,11 +118,12 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// ------ Effects ------ /// compute profit from interest accrued and emit an event + /// if any profits or losses are realized _recordPNL(); - /// increment tracked deposited amount + /// increment tracked recorded amount /// this will be off by a hair, after a single block - /// delta disappears. + /// negative delta turns to positive delta (assuming no loss). lastRecordedBalance += amount; /// ------ Interactions ------ @@ -156,16 +157,17 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// compute profit from interest accrued and emit an event _recordPNL(); - /// withdraw deposited amount as this was updated in record pnl + /// withdraw last recorded amount as this was updated in record pnl _withdraw(to, lastRecordedBalance, false); } /// @notice helper function to avoid repeated code in withdraw and withdrawAll /// anytime this function is called it is by an external function in this smart contract - /// with a reentrancy guard. this ensures lastRecordedBalance never desynchronizes + /// with a reentrancy guard. This ensures lastRecordedBalance never desynchronizes. /// Morpho is assumed to be a loss-less venue. over the course of less than 1 block, /// it is possible to lose funds. However, after 1 block, deposits are expected to always - /// be in profit at least with current interest rates around 0.8% natively on Compound, ignoring all COMP rewards. + /// be in profit at least with current interest rates around 0.8% natively on Compound, + /// ignoring all COMP and Morpho rewards. /// @param to recipient of withdraw funds /// @param amount to withdraw /// @param recordPnl whether or not to record PnL. Set to false in withdrawAll @@ -189,7 +191,9 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { _recordPNL(); } - /// update tracked deposit amount + /// update last recorded balance amount + /// if more than is owned is withdrawn, this line will revert + /// this line of code is both a check, and an effect lastRecordedBalance -= amount; /// ------ Interactions ------ From 513e3a926a8ad864195c9a4f00df3b687b1b2a42 Mon Sep 17 00:00:00 2001 From: Elliot Date: Fri, 21 Oct 2022 13:55:23 -0700 Subject: [PATCH 73/84] add unit test that checks implcit balance check in _withdraw function --- .../test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol index c566279b6..ade17aada 100644 --- a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol @@ -5,6 +5,7 @@ import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {Vm} from "./../../utils/Vm.sol"; import {ICore} from "../../../../core/ICore.sol"; import {DSTest} from "./../../utils/DSTest.sol"; +import {stdError} from "../../../unit/utils/StdLib.sol"; import {MockERC20} from "../../../../mock/MockERC20.sol"; import {MockCToken} from "../../../../mock/MockCToken.sol"; import {MockMorpho} from "../../../../mock/MockMorpho.sol"; @@ -263,6 +264,12 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { assertEq(morphoDeposit.balance(), amount); } + function testWithdrawFailsOverAmountHeld() public { + vm.prank(addresses.pcvControllerAddress); + vm.expectRevert(stdError.arithmeticError); /// reverts with underflow when trying to withdraw more than balance + morphoDeposit.withdraw(address(this), 1); + } + //// paused function testDepositWhenPausedFails() public { From 9c14d09be88bcb65844076461a59c9c47522896f Mon Sep 17 00:00:00 2001 From: Elliot Date: Fri, 21 Oct 2022 14:08:59 -0700 Subject: [PATCH 74/84] organize functions based on type and permission, add large block comments for organization --- .../pcv/morpho/MorphoCompoundPCVDeposit.sol | 82 ++++++++++++------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index b2f9495d2..219b68237 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -40,6 +40,10 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { using SafeERC20 for IERC20; using SafeCast for *; + /// ------------------------------------------ + /// ---------- Immutables/Constant ----------- + /// ------------------------------------------ + /// @notice reference to the COMP governance token /// used for recording COMP rewards type in Harvest event address public constant COMP = 0xc00e94Cb662C3520282E6f5717214004A7f26888; @@ -57,6 +61,10 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// used to inform morpho about the desired market to supply liquidity address public immutable cToken; + /// ------------------------------------------ + /// ------------- State Variable ------------- + /// ------------------------------------------ + /// @notice track the last amount of PCV recorded in the contract /// this is always out of date, except when accrue() is called /// in the same block or transaction. This means the value is stale @@ -87,6 +95,10 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { lens = _lens; } + /// ------------------------------------------ + /// ------------------ Views ----------------- + /// ------------------------------------------ + /// @notice Returns the distribution of assets supplied by this contract through Morpho-Compound. /// @return sum of suppliedP2P and suppliedOnPool for the given CToken function balance() public view override returns (uint256) { @@ -103,6 +115,10 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { return token; } + /// ------------------------------------------ + /// ----------- Permissionless API ----------- + /// ------------------------------------------ + /// @notice deposit ERC-20 tokens to Morpho-Compound /// non-reentrant to block malicious reentrant state changes /// to the lastRecordedBalance variable @@ -138,6 +154,34 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { emit Deposit(msg.sender, amount); } + /// @notice claim COMP rewards for supplying to Morpho. + /// Does not require reentrancy lock as no smart contract state is mutated + /// in this function. + function harvest() external { + address[] memory cTokens = new address[](1); + cTokens[0] = cToken; + + /// set swap comp to morpho flag false to claim comp rewards + uint256 claimedAmount = IMorpho(morpho).claimRewards(cTokens, false); + + emit Harvest(COMP, int256(claimedAmount), block.timestamp); + } + + /// @notice function that emits an event tracking profits and losses + /// since the last contract interaction + /// then writes the current amount of PCV tracked in this contract + /// to lastRecordedBalance + /// @return the amount deposited after adding accrued interest or realizing losses + function accrue() external nonReentrant whenNotPaused returns (uint256) { + _recordPNL(); /// update deposit amount and fire harvest event + + return lastRecordedBalance; /// return updated pcv amount + } + + /// ------------------------------------------ + /// ------------ Permissioned API ------------ + /// ------------------------------------------ + /// @notice withdraw tokens from the PCV allocation /// non-reentrant as state changes and external calls are made /// @param to the address PCV will be sent to @@ -161,6 +205,10 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { _withdraw(to, lastRecordedBalance, false); } + /// ------------------------------------------ + /// ------------- Helper Methods ------------- + /// ------------------------------------------ + /// @notice helper function to avoid repeated code in withdraw and withdrawAll /// anytime this function is called it is by an external function in this smart contract /// with a reentrancy guard. This ensures lastRecordedBalance never desynchronizes. @@ -204,9 +252,9 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { emit Withdrawal(msg.sender, to, amount); } - /// @notice function that records how much profit has been accrued - /// since the last call and emits an event with all profit received - /// updates the amount deposited to include all interest earned + /// @notice records how much profit or loss has been accrued + /// since the last call and emits an event with all profit or loss received. + /// Updates the lastRecordedBalance to include all realized profits or losses. function _recordPNL() private { /// first accrue interest in Compound and Morpho IMorpho(morpho).updateP2PIndexes(cToken); @@ -238,31 +286,9 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { emit Harvest(token, profit, block.timestamp); } - /// @notice claim COMP rewards for supplying to Morpho - /// no need for reentrancy lock as no smart contract state is mutated - /// in this function. - function harvest() external { - address[] memory cTokens = new address[](1); - cTokens[0] = cToken; - - /// set swap comp to morpho flag false to claim comp rewards - uint256 claimedAmount = IMorpho(morpho).claimRewards(cTokens, false); - - emit Harvest(COMP, int256(claimedAmount), block.timestamp); - } - - /// @notice function that emits an event tracking profits and losses - /// since the last contract interaction - /// then writes the current amount of PCV tracked in this contract - /// to lastRecordedBalance - /// @return the amount deposited after adding accrued interest - function accrue() external nonReentrant whenNotPaused returns (uint256) { - _recordPNL(); /// update deposit amount and fire harvest event - - return lastRecordedBalance; /// return updated pcv amount - } - - // ---------- Emergency Action ---------- + /// ------------------------------------------ + /// ------------ Emergency Action ------------ + /// ------------------------------------------ /// inspired by MakerDAO Multicall: /// https://github.com/makerdao/multicall/blob/master/src/Multicall.sol From 072b88d64572071a2e3f5638d21f54edd0695c73 Mon Sep 17 00:00:00 2001 From: Elliot Date: Sat, 22 Oct 2022 01:05:53 -0700 Subject: [PATCH 75/84] slither 22 run --- slither/slither-10-22-morpho.txt | 110 +++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 slither/slither-10-22-morpho.txt diff --git a/slither/slither-10-22-morpho.txt b/slither/slither-10-22-morpho.txt new file mode 100644 index 000000000..9a0d1fc27 --- /dev/null +++ b/slither/slither-10-22-morpho.txt @@ -0,0 +1,110 @@ +MorphoCompoundPCVDeposit.deposit() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#125-155) uses a dangerous strict equality: + - amount == 0 (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#129) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities +Response: Strict equalities can be dangerous in the way described in the slither detector documentation. +However, if amount is 0 in either Maple or Morpho PCV Deposit, it is better to save gas and not execute further opcodes. +There is no unsafe behavior that is possible as a result of doing this strict equality that amount is 0. + +Reentrancy in MorphoCompoundPCVDeposit._withdraw(address,uint256,bool) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#223-253): + External calls: + - _recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#239) + - IMorpho(morpho).updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#260) + State variables written after the call(s): + - lastRecordedBalance -= amount (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#245) +Reentrancy in MorphoCompoundPCVDeposit.deposit() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#125-155): + External calls: + - _recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#138) + - IMorpho(morpho).updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#260) + State variables written after the call(s): + - lastRecordedBalance += amount (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#143) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1 +Response: Anytime that function _recordPNL is called, it is from a function with a reentrancy +lock. This means that reentrancy into this contract is not possible while a previous call has not +completed. Additionally, there is no way to make this contract in a way that conforms to CEI because +updateP2PIndexes must be called before getCurrentSupplyBalanceInOf is called. +All of these functions call _recordPNL(), however, they all have a non reentrant modifier so that they won't +allow depositedAmount to be manipulated through a reentrancy attack. + - withdraw + - withdrawAll + - accrue + - deposit + +MorphoCompoundPCVDeposit.deposit() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#125-155) ignores return value by IERC20(token).approve(address(morpho),amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#147) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return +Response: USDC returns true and does some validation that the addresses are incorrect. +As long as the validations succeed, the function will return true. If the validations fail, +the call will revert. +DAI returns true and has no validation checks, which means the only way the approve call can fail +is if it is out of gas. + +MorphoCompoundPCVDeposit.constructor(address,address,address,address,address)._core (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#80) shadows: + - CoreRef._core (contracts/refs/CoreRef.sol#11) (state variable) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing +Response: Author acknowledges the issue, will not fix. + +MorphoCompoundPCVDeposit.constructor(address,address,address,address,address)._morpho (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#83) lacks a zero-check on : + - morpho = _morpho (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#94) +MorphoCompoundPCVDeposit.constructor(address,address,address,address,address)._lens (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#84) lacks a zero-check on : + - lens = _lens (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#95) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation +Response: Author acknowledges the issue, will not fix because integration tests will validate the parameter is set correctly. + +MorphoCompoundPCVDeposit.emergencyAction(MorphoCompoundPCVDeposit.Call[]) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#306-319) has external calls inside a loop: (success,returned) = calls[i].target.call(calls[i].callData) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#313-315) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop +Response: DoS attacks are not a valid vector when sender is a trusted governor and the function mutates no internal contract state. + +Reentrancy in MorphoCompoundPCVDeposit._recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#258-287): + External calls: + - IMorpho(morpho).updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#260) + State variables written after the call(s): + - lastRecordedBalance = currentBalance (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#283) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2 +Response: Anytime that function _recordPNL is called, it is from a function with a reentrancy +lock. This means that reentrancy into this contract is not possible while a previous call has not +completed. Additionally, there is no way to make this contract in a way that conforms to CEI because +updateP2PIndexes must be called before getCurrentSupplyBalanceInOf is called. + +Reentrancy in MorphoCompoundPCVDeposit._recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#258-287): + External calls: + - IMorpho(morpho).updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#260) + Event emitted after the call(s): + - Harvest(token,profit,block.timestamp) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#286) +Reentrancy in MorphoCompoundPCVDeposit._withdraw(address,uint256,bool) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#223-253): + External calls: + - _recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#239) + - IMorpho(morpho).updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#260) + - IMorpho(morpho).withdraw(cToken,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#249) + - IERC20(token).safeTransfer(to,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#250) + Event emitted after the call(s): + - Withdrawal(msg.sender,to,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#252) +Reentrancy in MorphoCompoundPCVDeposit.deposit() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#125-155): + External calls: + - _recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#138) + - IMorpho(morpho).updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#260) + - IERC20(token).approve(address(morpho),amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#147) + - IMorpho(morpho).supply(cToken,address(this),amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#148-152) + Event emitted after the call(s): + - Deposit(msg.sender,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#154) +Reentrancy in MorphoCompoundPCVDeposit.harvest() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#160-168): + External calls: + - claimedAmount = IMorpho(morpho).claimRewards(cTokens,false) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#165) + Event emitted after the call(s): + - Harvest(COMP,int256(claimedAmount),block.timestamp) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#167) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3 +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3 +Response: Reentrancy is only an issue if it allows theft of funds and updating of state incorrectly in an intermediate state. +None of the listed functions mutate any state internal to the contract. If there was a collateralization oracle in the system, +then these issues would be worth investigating further and ensuring state could not get mixed up in a way that allowed an +attacker to manipulate the target price of Volt. + +Pragma version=0.8.13 (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#2) necessitates a version too recent to be trusted. Consider deploying with 0.6.12/0.7.6/0.8.7 +solc-0.8.13 is not recommended for deployment +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity +Response: Author has reviewed the solidity 0.8.13 list of known bugs https://docs.soliditylang.org/en/v0.8.13/bugs.html +and not found any high issues that would lead to incorrect functioning of the smart contracts. + +Low level call in MorphoCompoundPCVDeposit.emergencyAction(MorphoCompoundPCVDeposit.Call[]) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#306-319): + - (success,returned) = calls[i].target.call(calls[i].callData) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#313-315) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls +Response: This low level call is intentional and can be used during an emergency action if funds need to be moved +out of Maple in a way the author has not yet conceived of. From a1e9b2f56550a21daa1b1ee12f1660c103b92b5c Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 24 Oct 2022 10:05:11 -0700 Subject: [PATCH 76/84] Add PCV Oracle callback to morpho PCV Deposit --- contracts/mock/MockPCVOracle.sol | 11 +++ contracts/pcv/morpho/IPCVOracle.sol | 7 ++ .../pcv/morpho/MorphoCompoundPCVDeposit.sol | 67 ++++++++++++++++++- contracts/test/integration/vip/vip14.sol | 2 + .../pcv/morpho/MorphoCompoundPCVDeposit.t.sol | 56 ++++++++++++++-- 5 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 contracts/mock/MockPCVOracle.sol create mode 100644 contracts/pcv/morpho/IPCVOracle.sol diff --git a/contracts/mock/MockPCVOracle.sol b/contracts/mock/MockPCVOracle.sol new file mode 100644 index 000000000..81f2e588c --- /dev/null +++ b/contracts/mock/MockPCVOracle.sol @@ -0,0 +1,11 @@ +pragma solidity 0.8.13; + +contract MockPCVOracle { + int256 public pcvAmount; + + /// @notice hook on PCV deposit, callable when pcv oracle is set + /// updates the oracle with the new liquid balance delta + function updateLiquidBalance(int256 pcvDelta) external { + pcvAmount += pcvDelta; + } +} diff --git a/contracts/pcv/morpho/IPCVOracle.sol b/contracts/pcv/morpho/IPCVOracle.sol new file mode 100644 index 000000000..b02700e1b --- /dev/null +++ b/contracts/pcv/morpho/IPCVOracle.sol @@ -0,0 +1,7 @@ +pragma solidity 0.8.13; + +interface IPCVOracle { + /// @notice hook on PCV deposit, callable when pcv oracle is set + /// updates the oracle with the new liquid balance delta + function updateLiquidBalance(int256 pcvDelta) external; +} diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index 219b68237..aeb26abff 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -10,6 +10,7 @@ import {ILens} from "./ILens.sol"; import {IMorpho} from "./IMorpho.sol"; import {CoreRef} from "../../refs/CoreRef.sol"; import {Constants} from "../../Constants.sol"; +import {IPCVOracle} from "./IPCVOracle.sol"; import {PCVDeposit} from "../PCVDeposit.sol"; import {ICompoundOracle, ICToken} from "./ICompound.sol"; @@ -40,6 +41,13 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { using SafeERC20 for IERC20; using SafeCast for *; + /// ------------------------------------------ + /// ----------------- Event ------------------ + /// ------------------------------------------ + + /// @notice emitted when the PCV Oracle address is updated + event PCVOracleUpdated(address oldOracle, address newOracle); + /// ------------------------------------------ /// ---------- Immutables/Constant ----------- /// ------------------------------------------ @@ -62,7 +70,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { address public immutable cToken; /// ------------------------------------------ - /// ------------- State Variable ------------- + /// ------------- State Variables ------------- /// ------------------------------------------ /// @notice track the last amount of PCV recorded in the contract @@ -71,6 +79,12 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// most of the time. uint256 public lastRecordedBalance; + /// @notice reference to the PCV Oracle. Settable by governance + /// if set, anytime PCV is updated, delta is sent in to update liquid + /// amount of PCV held + /// not set in the constructor + address public pcvOracle; + /// @param _core reference to the core contract /// @param _cToken cToken this deposit references /// @param _underlying Token denomination of this deposit @@ -131,6 +145,8 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { return; } + int256 startingRecordedBalance = lastRecordedBalance.toInt256(); + /// ------ Effects ------ /// compute profit from interest accrued and emit an event @@ -142,6 +158,8 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// negative delta turns to positive delta (assuming no loss). lastRecordedBalance += amount; + int256 endingRecordedBalance = lastRecordedBalance.toInt256(); + /// ------ Interactions ------ IERC20(token).approve(address(morpho), amount); @@ -151,6 +169,12 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { amount ); + if (pcvOracle != address(0)) { + IPCVOracle(pcvOracle).updateLiquidBalance( + endingRecordedBalance - startingRecordedBalance + ); + } + emit Deposit(msg.sender, amount); } @@ -173,8 +197,19 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// to lastRecordedBalance /// @return the amount deposited after adding accrued interest or realizing losses function accrue() external nonReentrant whenNotPaused returns (uint256) { + int256 startingRecordedBalance = lastRecordedBalance.toInt256(); + _recordPNL(); /// update deposit amount and fire harvest event + int256 endingRecordedBalance = lastRecordedBalance.toInt256(); + + if (pcvOracle != address(0)) { + /// if any amount of PCV is withdrawn and no gains, delta is negative + IPCVOracle(pcvOracle).updateLiquidBalance( + endingRecordedBalance - startingRecordedBalance + ); + } + return lastRecordedBalance; /// return updated pcv amount } @@ -191,18 +226,48 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { onlyPCVController nonReentrant { + int256 startingRecordedBalance = lastRecordedBalance.toInt256(); + _withdraw(to, amount, true); + + int256 endingRecordedBalance = lastRecordedBalance.toInt256(); + if (pcvOracle != address(0)) { + /// if any amount of PCV is withdrawn and no gains, delta is negative + IPCVOracle(pcvOracle).updateLiquidBalance( + endingRecordedBalance - startingRecordedBalance + ); + } } /// @notice withdraw all tokens from Morpho /// non-reentrant as state changes and external calls are made /// @param to the address PCV will be sent to function withdrawAll(address to) external onlyPCVController nonReentrant { + int256 startingRecordedBalance = lastRecordedBalance.toInt256(); + /// compute profit from interest accrued and emit an event _recordPNL(); /// withdraw last recorded amount as this was updated in record pnl _withdraw(to, lastRecordedBalance, false); + + int256 endingRecordedBalance = lastRecordedBalance.toInt256(); + + if (pcvOracle != address(0)) { + /// all PCV withdrawn, send call in with amount withdrawn negative if any amount is withdrawn + IPCVOracle(pcvOracle).updateLiquidBalance( + endingRecordedBalance - startingRecordedBalance + ); + } + } + + /// @notice set the pcv oracle address + /// @param _pcvOracle new pcv oracle to reference + function setPCVOracle(address _pcvOracle) external onlyGovernor { + address oldOracle = pcvOracle; + pcvOracle = _pcvOracle; + + emit PCVOracleUpdated(oldOracle, _pcvOracle); } /// ------------------------------------------ diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index 9b25d4d62..9c2a14089 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -238,6 +238,8 @@ contract vip14 is DSTest, IVIP { assertEq(address(daiDeposit.core()), core); assertEq(address(router.core()), core); + assertEq(daiDeposit.pcvOracle(), address(0)); + assertEq(usdcDeposit.pcvOracle(), address(0)); assertEq(daiDeposit.morpho(), MainnetAddresses.MORPHO); assertEq(usdcDeposit.morpho(), MainnetAddresses.MORPHO); assertEq(daiDeposit.lens(), MainnetAddresses.MORPHO_LENS); diff --git a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol index ade17aada..0b0863517 100644 --- a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol @@ -11,6 +11,7 @@ import {MockCToken} from "../../../../mock/MockCToken.sol"; import {MockMorpho} from "../../../../mock/MockMorpho.sol"; import {TribeRoles} from "../../../../core/TribeRoles.sol"; import {IPCVDeposit} from "../../../../pcv/IPCVDeposit.sol"; +import {MockPCVOracle} from "../../../../mock/MockPCVOracle.sol"; import {PCVGuardAdmin} from "../../../../pcv/PCVGuardAdmin.sol"; import {MockERC20, IERC20} from "../../../../mock/MockERC20.sol"; import {MorphoCompoundPCVDeposit} from "../../../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; @@ -87,7 +88,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { ); } - function testDeposit(uint256 depositAmount) public { + function testDeposit(uint120 depositAmount) public { assertEq(morphoDeposit.lastRecordedBalance(), 0); token.mint(address(morphoDeposit), depositAmount); @@ -96,7 +97,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { assertEq(morphoDeposit.lastRecordedBalance(), depositAmount); } - function testDeposits(uint248[4] calldata depositAmount) public { + function testDeposits(uint120[4] calldata depositAmount) public { uint256 sumDeposit; for (uint256 i = 0; i < 4; i++) { @@ -122,7 +123,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { ); } - function testWithdrawAll(uint248[4] calldata depositAmount) public { + function testWithdrawAll(uint120[4] calldata depositAmount) public { testDeposits(depositAmount); uint256 sumDeposit; @@ -147,7 +148,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { } function testAccrue( - uint248[4] calldata depositAmount, + uint120[4] calldata depositAmount, uint120 profitAccrued ) public { testDeposits(depositAmount); @@ -169,7 +170,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { } function testWithdraw( - uint248[4] calldata depositAmount, + uint120[4] calldata depositAmount, uint248[10] calldata withdrawAmount, uint120 profitAccrued, address to @@ -215,7 +216,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { } } - function testEmergencyActionWithdrawSucceedsGovernor(uint256 amount) + function testEmergencyActionWithdrawSucceedsGovernor(uint120 amount) public { token.mint(address(morphoDeposit), amount); @@ -237,7 +238,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { assertEq(morphoDeposit.balance(), 0); } - function testEmergencyActionSucceedsGovernorDeposit(uint256 amount) public { + function testEmergencyActionSucceedsGovernorDeposit(uint120 amount) public { vm.assume(amount != 0); token.mint(address(morphoDeposit), amount); @@ -286,6 +287,42 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { morphoDeposit.accrue(); } + function testSetPCVOracleSucceedsGovernor() public { + vm.prank(addresses.governorAddress); + morphoDeposit.setPCVOracle(address(this)); + assertEq(morphoDeposit.pcvOracle(), address(this)); + } + + function testSetPCVOracleSucceedsAndHookCalledSuccessfully( + uint120[4] calldata depositAmount, + uint248[10] calldata withdrawAmount, + uint120 profitAccrued, + address to + ) public { + MockPCVOracle oracle = new MockPCVOracle(); + vm.prank(addresses.governorAddress); + morphoDeposit.setPCVOracle(address(oracle)); + assertEq(morphoDeposit.pcvOracle(), address(oracle)); + + vm.assume(to != address(0)); + testWithdraw(depositAmount, withdrawAmount, profitAccrued, to); + + uint256 sumDeposit = uint256(depositAmount[0]) + + uint256(depositAmount[1]) + + uint256(depositAmount[2]) + + uint256(depositAmount[3]) + + uint256(profitAccrued); + + for (uint256 i = 0; i < 10; i++) { + if (withdrawAmount[i] > sumDeposit) { + continue; + } + sumDeposit -= withdrawAmount[i]; + } + + assertEq(oracle.pcvAmount(), sumDeposit.toInt256()); + } + //// access controls function testEmergencyActionFailsNonGovernor() public { @@ -312,6 +349,11 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { morphoDeposit.withdrawAll(address(this)); } + function testSetPCVOracleFailsNonGovernor() public { + vm.expectRevert("CoreRef: Caller is not a governor"); + morphoDeposit.setPCVOracle(address(this)); + } + //// reentrancy function _reentrantSetup() private { From 85191293afd0ec0de4c8a9ddb712e4fb3adab59e Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 24 Oct 2022 10:12:55 -0700 Subject: [PATCH 77/84] update deposit endingRecordedBalance to be exact amount pcv deposit owns after deposit --- contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index aeb26abff..5db9c90e5 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -158,7 +158,7 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// negative delta turns to positive delta (assuming no loss). lastRecordedBalance += amount; - int256 endingRecordedBalance = lastRecordedBalance.toInt256(); + int256 endingRecordedBalance = balance().toInt256(); /// ------ Interactions ------ From d7c9f6c88d624eeb75f6bf79148a64ee2f04de4b Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 24 Oct 2022 13:09:05 -0700 Subject: [PATCH 78/84] remove amount == 0 check in _withdraw, setPCVOracle records pnl and updates the liquid balance on the oracle, move balance cache to after supply --- .../pcv/morpho/MorphoCompoundPCVDeposit.sol | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol index 5db9c90e5..f7e53c84c 100644 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -158,8 +158,6 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { /// negative delta turns to positive delta (assuming no loss). lastRecordedBalance += amount; - int256 endingRecordedBalance = balance().toInt256(); - /// ------ Interactions ------ IERC20(token).approve(address(morpho), amount); @@ -169,6 +167,8 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { amount ); + int256 endingRecordedBalance = balance().toInt256(); + if (pcvOracle != address(0)) { IPCVOracle(pcvOracle).updateLiquidBalance( endingRecordedBalance - startingRecordedBalance @@ -267,6 +267,12 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { address oldOracle = pcvOracle; pcvOracle = _pcvOracle; + _recordPNL(); + + IPCVOracle(pcvOracle).updateLiquidBalance( + lastRecordedBalance.toInt256() + ); + emit PCVOracleUpdated(oldOracle, _pcvOracle); } @@ -290,13 +296,6 @@ contract MorphoCompoundPCVDeposit is PCVDeposit, ReentrancyGuard { uint256 amount, bool recordPnl ) private { - /// ------ Check ------ - - /// no op if amount to withdraw is 0 - if (amount == 0) { - return; - } - /// ------ Effects ------ if (recordPnl) { From ab72cd259890e2dc54ac2e5d8523bf7a7a5c15e2 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 24 Oct 2022 13:10:21 -0700 Subject: [PATCH 79/84] add unit/fuzz tests around calling setPCVOracle --- .../pcv/morpho/MorphoCompoundPCVDeposit.t.sol | 97 +++++++++++++------ 1 file changed, 66 insertions(+), 31 deletions(-) diff --git a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol index 0b0863517..822400ae6 100644 --- a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol @@ -165,8 +165,9 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { uint256(profitAccrued).toInt256(), block.timestamp ); - - assertEq(morphoDeposit.accrue(), sumDeposit + profitAccrued); + uint256 lastRecordedBalance = morphoDeposit.accrue(); + assertEq(lastRecordedBalance, sumDeposit + profitAccrued); + assertEq(lastRecordedBalance, morphoDeposit.lastRecordedBalance()); } function testWithdraw( @@ -216,6 +217,69 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { } } + function testSetPCVOracleSucceedsAndHookCalledSuccessfully( + uint120[4] calldata depositAmount, + uint248[10] calldata withdrawAmount, + uint120 profitAccrued, + address to + ) public { + MockPCVOracle oracle = new MockPCVOracle(); + + vm.prank(addresses.governorAddress); + morphoDeposit.setPCVOracle(address(oracle)); + + assertEq(morphoDeposit.pcvOracle(), address(oracle)); + + vm.assume(to != address(0)); + testWithdraw(depositAmount, withdrawAmount, profitAccrued, to); + + uint256 sumDeposit = uint256(depositAmount[0]) + + uint256(depositAmount[1]) + + uint256(depositAmount[2]) + + uint256(depositAmount[3]) + + uint256(profitAccrued); + + for (uint256 i = 0; i < 10; i++) { + if (withdrawAmount[i] > sumDeposit) { + continue; + } + sumDeposit -= withdrawAmount[i]; + } + morphoDeposit.accrue(); + + assertEq(oracle.pcvAmount(), sumDeposit.toInt256()); + } + + function testSetPCVOracleSucceedsAndHookCalledSuccessfullyAfterDeposit( + uint120[4] calldata depositAmount, + uint248[10] calldata withdrawAmount, + uint120 profitAccrued, + address to + ) public { + vm.assume(to != address(0)); + testWithdraw(depositAmount, withdrawAmount, profitAccrued, to); + + uint256 sumDeposit = uint256(depositAmount[0]) + + uint256(depositAmount[1]) + + uint256(depositAmount[2]) + + uint256(depositAmount[3]) + + uint256(profitAccrued); + + for (uint256 i = 0; i < 10; i++) { + if (withdrawAmount[i] > sumDeposit) { + continue; + } + sumDeposit -= withdrawAmount[i]; + } + + MockPCVOracle oracle = new MockPCVOracle(); + vm.prank(addresses.governorAddress); + morphoDeposit.setPCVOracle(address(oracle)); + assertEq(morphoDeposit.pcvOracle(), address(oracle)); + + assertEq(oracle.pcvAmount(), sumDeposit.toInt256()); + } + function testEmergencyActionWithdrawSucceedsGovernor(uint120 amount) public { @@ -288,39 +352,10 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { } function testSetPCVOracleSucceedsGovernor() public { - vm.prank(addresses.governorAddress); - morphoDeposit.setPCVOracle(address(this)); - assertEq(morphoDeposit.pcvOracle(), address(this)); - } - - function testSetPCVOracleSucceedsAndHookCalledSuccessfully( - uint120[4] calldata depositAmount, - uint248[10] calldata withdrawAmount, - uint120 profitAccrued, - address to - ) public { MockPCVOracle oracle = new MockPCVOracle(); vm.prank(addresses.governorAddress); morphoDeposit.setPCVOracle(address(oracle)); assertEq(morphoDeposit.pcvOracle(), address(oracle)); - - vm.assume(to != address(0)); - testWithdraw(depositAmount, withdrawAmount, profitAccrued, to); - - uint256 sumDeposit = uint256(depositAmount[0]) + - uint256(depositAmount[1]) + - uint256(depositAmount[2]) + - uint256(depositAmount[3]) + - uint256(profitAccrued); - - for (uint256 i = 0; i < 10; i++) { - if (withdrawAmount[i] > sumDeposit) { - continue; - } - sumDeposit -= withdrawAmount[i]; - } - - assertEq(oracle.pcvAmount(), sumDeposit.toInt256()); } //// access controls From 6cd43179f597eb1e2943fe3eac432bc94921d8c9 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 24 Oct 2022 13:20:47 -0700 Subject: [PATCH 80/84] add additional invariant tests for updateLiquidBalance hook --- .../InvariantTestMorphoPCVDeposit.t.sol | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol b/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol index fdbcfaa21..49f99a127 100644 --- a/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol +++ b/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity =0.8.13; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {Vm} from "../unit/utils/Vm.sol"; @@ -8,6 +9,7 @@ import {ICore} from "../../core/ICore.sol"; import {DSTest} from "../unit/utils/DSTest.sol"; import {MockERC20} from "../../mock/MockERC20.sol"; import {MockMorpho} from "../../mock/MockMorpho.sol"; +import {MockPCVOracle} from "../../mock/MockPCVOracle.sol"; import {DSInvariantTest} from "../unit/utils/DSInvariantTest.sol"; import {MorphoCompoundPCVDeposit} from "../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; import {getCore, getAddresses, VoltTestAddresses} from "../unit/utils/Fixtures.sol"; @@ -17,13 +19,19 @@ import {getCore, getAddresses, VoltTestAddresses} from "../unit/utils/Fixtures.s /// @dev Modified from Solmate ERC20 Invariant Test (https://github.com/transmissions11/solmate/blob/main/src/test/ERC20.t.sol) contract InvariantTestMorphoCompoundPCVDeposit is DSTest, DSInvariantTest { + using SafeCast for *; + MorphoPCVDepositTest public morphoTest; + MockPCVOracle public pcvOracle; ICore public core; MorphoCompoundPCVDeposit public morphoDeposit; MockMorpho public morpho; MockERC20 public token; + Vm private vm = Vm(HEVM_ADDRESS); + VoltTestAddresses public addresses = getAddresses(); function setUp() public { + pcvOracle = new MockPCVOracle(); core = getCore(); token = new MockERC20(); morpho = new MockMorpho(IERC20(address(token))); @@ -36,6 +44,9 @@ contract InvariantTestMorphoCompoundPCVDeposit is DSTest, DSInvariantTest { ); morphoTest = new MorphoPCVDepositTest(morphoDeposit, token, morpho); + vm.prank(addresses.governorAddress); + morphoDeposit.setPCVOracle(address(pcvOracle)); + addTargetContract(address(morphoTest)); } @@ -47,6 +58,15 @@ contract InvariantTestMorphoCompoundPCVDeposit is DSTest, DSInvariantTest { assertEq(morphoDeposit.balance(), morphoTest.totalDeposited()); } + function invariantPcvOracle() public { + assertEq( + morphoDeposit.lastRecordedBalance(), + pcvOracle.pcvAmount().toUint256() + ); + assertEq(morphoDeposit.lastRecordedBalance(), morphoDeposit.balance()); + assertEq(morphoDeposit.balance(), morphoTest.totalDeposited()); + } + function invariantBalanceOf() public { assertEq( morphoDeposit.balance(), From d2c285edb8684c21d725f962cccf2352a5b8dc6d Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 24 Oct 2022 17:58:36 -0700 Subject: [PATCH 81/84] slither run 10/24 --- slither/slither-10-24-morpho.txt | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 slither/slither-10-24-morpho.txt diff --git a/slither/slither-10-24-morpho.txt b/slither/slither-10-24-morpho.txt new file mode 100644 index 000000000..b485f04aa --- /dev/null +++ b/slither/slither-10-24-morpho.txt @@ -0,0 +1,41 @@ +Reentrancy in MorphoCompoundPCVDeposit._recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#322-351): + External calls: + - IMorpho(morpho).updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#324) + Event emitted after the call(s): + - Harvest(token,profit,block.timestamp) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#350) +Reentrancy in MorphoCompoundPCVDeposit._withdraw(address,uint256,bool) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#294-317): + External calls: + - _recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#303) + - IMorpho(morpho).updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#324) + - IMorpho(morpho).withdraw(cToken,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#313) + - IERC20(token).safeTransfer(to,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#314) + Event emitted after the call(s): + - Withdrawal(msg.sender,to,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#316) +Reentrancy in MorphoCompoundPCVDeposit.deposit() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#139-179): + External calls: + - _recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#154) + - IMorpho(morpho).updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#324) + - IERC20(token).approve(address(morpho),amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#163) + - IMorpho(morpho).supply(cToken,address(this),amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#164-168) + - IPCVOracle(pcvOracle).updateLiquidBalance(endingRecordedBalance - startingRecordedBalance) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#173-175) + Event emitted after the call(s): + - Deposit(msg.sender,amount) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#178) +Reentrancy in MorphoCompoundPCVDeposit.harvest() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#184-192): + External calls: + - claimedAmount = IMorpho(morpho).claimRewards(cTokens,false) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#189) + Event emitted after the call(s): + - Harvest(COMP,int256(claimedAmount),block.timestamp) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#191) +Reentrancy in MorphoCompoundPCVDeposit.setPCVOracle(address) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#266-277): + External calls: + - _recordPNL() (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#270) + - IMorpho(morpho).updateP2PIndexes(cToken) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#324) + - IPCVOracle(pcvOracle).updateLiquidBalance(lastRecordedBalance.toInt256()) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#272-274) + Event emitted after the call(s): + - PCVOracleUpdated(oldOracle,_pcvOracle) (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#276) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3 +Response: Author acknowledges that events are emitted after the calls. +However, this is not an issue as reentrancy locks are used anytime _recordPNL or updateLiquidBalance is called. + +Parameter MorphoCompoundPCVDeposit.setPCVOracle(address)._pcvOracle (contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol#266) is not in mixedCase +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions +Response: Author acknowledges the issue and will not fix. From 7fa0d4015c4e6b842a56a32c3091da14f4e1d45c Mon Sep 17 00:00:00 2001 From: Elliot Date: Tue, 25 Oct 2022 13:11:56 -0700 Subject: [PATCH 82/84] Deploy MorphoCompound PCV Depsosits, add addresses to mainnet address file, point vip14 solidity simulation to newly deployed smart contracts --- .../integration/fixtures/MainnetAddresses.sol | 10 ++++ contracts/test/integration/vip/vip14.sol | 35 ++++--------- protocol-configuration/mainnetAddresses.ts | 52 +++++++++++++------ 3 files changed, 56 insertions(+), 41 deletions(-) diff --git a/contracts/test/integration/fixtures/MainnetAddresses.sol b/contracts/test/integration/fixtures/MainnetAddresses.sol index 54aa6cd75..f234ccde7 100644 --- a/contracts/test/integration/fixtures/MainnetAddresses.sol +++ b/contracts/test/integration/fixtures/MainnetAddresses.sol @@ -34,6 +34,16 @@ library MainnetAddresses { address public constant COMPOUND_PCV_ROUTER = 0x6338Ec144279b1f05AF8C90216d90C5b54Fa4D8F; + // ---------- MORPHO ADDRESSES ---------- + address public constant MORPHO_COMPOUND_PCV_ROUTER = + 0x579C400eaCA4b1D84956E7bD284d97611f78BA4E; + + address public constant MORPHO_COMPOUND_DAI_PCV_DEPOSIT = + 0x7aB2f4A29048392EfE0B57FD17a3BedBcD0891DC; + + address public constant MORPHO_COMPOUND_USDC_PCV_DEPOSIT = + 0xF10d810De7F0Fbd455De30f8c43AbA56F253B73B; + // ---------- ORACLE ADDRESSES ---------- address public constant ORACLE_PASS_THROUGH = 0xe733985a92Bfd5BC676095561BacE90E04606E4a; diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index 9c2a14089..5757ee9f0 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -53,9 +53,16 @@ contract vip14 is DSTest, IVIP { ITimelockSimulation.action[] private mainnetProposal; ITimelockSimulation.action[] private arbitrumProposal; - CompoundPCVRouter public router; - MorphoCompoundPCVDeposit public daiDeposit; - MorphoCompoundPCVDeposit public usdcDeposit; + CompoundPCVRouter public router = + CompoundPCVRouter(MainnetAddresses.MORPHO_COMPOUND_PCV_ROUTER); + MorphoCompoundPCVDeposit public daiDeposit = + MorphoCompoundPCVDeposit( + MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT + ); + MorphoCompoundPCVDeposit public usdcDeposit = + MorphoCompoundPCVDeposit( + MainnetAddresses.MORPHO_COMPOUND_USDC_PCV_DEPOSIT + ); PCVGuardian public immutable pcvGuardian = PCVGuardian(MainnetAddresses.PCV_GUARDIAN); @@ -66,28 +73,6 @@ contract vip14 is DSTest, IVIP { constructor() { if (block.chainid != 1) return; /// keep ci pipeline happy - daiDeposit = new MorphoCompoundPCVDeposit( - core, - MainnetAddresses.CDAI, - MainnetAddresses.DAI, - MainnetAddresses.MORPHO, - MainnetAddresses.MORPHO_LENS - ); - - usdcDeposit = new MorphoCompoundPCVDeposit( - core, - MainnetAddresses.CUSDC, - MainnetAddresses.USDC, - MainnetAddresses.MORPHO, - MainnetAddresses.MORPHO_LENS - ); - - router = new CompoundPCVRouter( - core, - PCVDeposit(address(daiDeposit)), - PCVDeposit(address(usdcDeposit)) - ); - address[] memory toWhitelist = new address[](2); toWhitelist[0] = address(daiDeposit); toWhitelist[1] = address(usdcDeposit); diff --git a/protocol-configuration/mainnetAddresses.ts b/protocol-configuration/mainnetAddresses.ts index 8b3dc01f0..40a7ab43e 100644 --- a/protocol-configuration/mainnetAddresses.ts +++ b/protocol-configuration/mainnetAddresses.ts @@ -88,22 +88,54 @@ const MainnetAddresses: MainnetAddresses = { category: AddressCategory.Core, network: Network.Mainnet }, + + daiMorphoCompoundPCVDeposit: { + address: '0x7aB2f4A29048392EfE0B57FD17a3BedBcD0891DC', + artifactName: 'MorphoCompoundPCVDeposit', + category: AddressCategory.PCV, + network: Network.Mainnet + }, + usdcMorphoCompoundPCVDeposit: { + address: '0xF10d810De7F0Fbd455De30f8c43AbA56F253B73B', + artifactName: 'MorphoCompoundPCVDeposit', + category: AddressCategory.PCV, + network: Network.Mainnet + }, + morphoCompoundPCVRouter: { + address: '0x579C400eaCA4b1D84956E7bD284d97611f78BA4E', + artifactName: 'CompoundPCVRouter', + category: AddressCategory.PCV, + network: Network.Mainnet + }, + morphoCompound: { + address: '0x8888882f8f843896699869179fB6E4f7e3B58888', + artifactName: 'unknown', + category: AddressCategory.External, + network: Network.Mainnet + }, + morphoCompoundLens: { + address: '0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67', + artifactName: 'unknown', + category: AddressCategory.External, + network: Network.Mainnet + }, + daiCompoundPCVDeposit: { address: '0xE3cbfd618463B7198fa0743AbFA56170557cc880', artifactName: 'ERC20CompoundPCVDeposit', - category: AddressCategory.PCV, + category: AddressCategory.Deprecated, network: Network.Mainnet }, feiCompoundPCVDeposit: { address: '0x604556Bbc4aB70B3c73d7bb6c4867B6239511301', artifactName: 'ERC20CompoundPCVDeposit', - category: AddressCategory.PCV, + category: AddressCategory.Deprecated, network: Network.Mainnet }, usdcCompoundPCVDeposit: { address: '0x3B69e3073cf86099a9bbB650e8682D6FdCfb29db', artifactName: 'ERC20CompoundPCVDeposit', - category: AddressCategory.PCV, + category: AddressCategory.Deprecated, network: Network.Mainnet }, feiPriceBoundPSM: { @@ -130,22 +162,10 @@ const MainnetAddresses: MainnetAddresses = { category: AddressCategory.PCV, network: Network.Mainnet }, - morphoCompound: { - address: '0x8888882f8f843896699869179fB6E4f7e3B58888', - artifactName: 'unknown', - category: AddressCategory.External, - network: Network.Mainnet - }, - morphoCompoundLens: { - address: '0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67', - artifactName: 'unknown', - category: AddressCategory.External, - network: Network.Mainnet - }, compoundPCVRouter: { address: '0x6338Ec144279b1f05AF8C90216d90C5b54Fa4D8F', artifactName: 'CompoundPCVRouter', - category: AddressCategory.PCV, + category: AddressCategory.Deprecated, network: Network.Mainnet }, erc20Allocator: { From 0101e8d9562056fb3d134559a95364dbfb211627 Mon Sep 17 00:00:00 2001 From: Elliot Date: Tue, 25 Oct 2022 13:31:46 -0700 Subject: [PATCH 83/84] remove compound pcv deposits from pcv guard verification --- .../test/integration/utils/PCVGuardVerification.sol | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/contracts/test/integration/utils/PCVGuardVerification.sol b/contracts/test/integration/utils/PCVGuardVerification.sol index b2cd9d257..bf7068aa5 100644 --- a/contracts/test/integration/utils/PCVGuardVerification.sol +++ b/contracts/test/integration/utils/PCVGuardVerification.sol @@ -26,9 +26,8 @@ contract PCVGuardVerification is DSTest { MainnetAddresses.VOLT_DAI_PSM, MainnetAddresses.VOLT_FEI_PSM, MainnetAddresses.VOLT_USDC_PSM, - MainnetAddresses.COMPOUND_DAI_PCV_DEPOSIT, - MainnetAddresses.COMPOUND_FEI_PCV_DEPOSIT, - MainnetAddresses.COMPOUND_USDC_PCV_DEPOSIT + MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT, + MainnetAddresses.MORPHO_COMPOUND_USDC_PCV_DEPOSIT ]; /// @notice all PSM's on arbitrum @@ -204,13 +203,6 @@ contract PCVGuardVerification is DSTest { : allArbitrumPCVDeposits; for (uint256 i = 0; i < allDeposits.length; i++) { - /// currently there is no fei liquidity, so this withdraw all action will fail - if ( - MainnetAddresses.COMPOUND_FEI_PCV_DEPOSIT == - address(allDeposits[i]) - ) { - continue; - } vm.prank(MainnetAddresses.EOA_1); address pcvGuardian = block.chainid == 1 ? MainnetAddresses.PCV_GUARDIAN From f8672620a7f8ec92da5f1cce1879b47ec7c81783 Mon Sep 17 00:00:00 2001 From: Elliot Date: Tue, 25 Oct 2022 14:51:01 -0700 Subject: [PATCH 84/84] remove todo comment on adding morpho deposits in pcv guard verification --- contracts/test/integration/utils/PCVGuardVerification.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/test/integration/utils/PCVGuardVerification.sol b/contracts/test/integration/utils/PCVGuardVerification.sol index bf7068aa5..d4c4c80b5 100644 --- a/contracts/test/integration/utils/PCVGuardVerification.sol +++ b/contracts/test/integration/utils/PCVGuardVerification.sol @@ -19,8 +19,6 @@ contract PCVGuardVerification is DSTest { using Deviation for *; using SafeCast for *; - /// TODO add Morpho Deposits once deployed - /// @notice all PSM's on mainnet address[] private allMainnetPCVDeposits = [ MainnetAddresses.VOLT_DAI_PSM,