From 7bff2eed07f6a6001e4f07d31af05cf8d015987f Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 9 Jan 2023 15:02:29 -0800 Subject: [PATCH 01/23] PCV Oracle Accounting Comment --- contracts/pcv/PCVGuardian.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/contracts/pcv/PCVGuardian.sol b/contracts/pcv/PCVGuardian.sol index 762f62b62..551e80379 100644 --- a/contracts/pcv/PCVGuardian.sol +++ b/contracts/pcv/PCVGuardian.sol @@ -124,6 +124,19 @@ contract PCVGuardian is IPCVGuardian, CoreRefV2 { } } + // ---------- PCV Guardian State-Changing API ---------- + + // ----------------------------------------------------- + // ------------------- WARNING!!! --------------------- + // ----------------------------------------------------- + // USING THESE FUNCTIONS WILL BREAK ACCOUNTING + // IN THE PCV ORACLE. ONLY USE FUNCTIONS IN AN + // EMERGENCY SITUATION IF WITHDRAWING FROM PCV DEPOSITS. + // ----------------------------------------------------- + // WITHDRAWING FROM A PSM WILL NOT BREAK ACCOUNTING. + // ----------------------------------------------------- + // ----------------------------------------------------- + /// @notice governor-or-guardian-or-pcv-guard method to withdraw funds from a pcv deposit, by calling the withdraw() method on it /// @param pcvDeposit the address of the pcv deposit contract /// @param amount the amount to withdraw From d941e155be771f93ed5da18ae099d74b2809f4b2 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 9 Jan 2023 15:03:03 -0800 Subject: [PATCH 02/23] update time to 2024 for oracle start price --- contracts/deployment/SystemV2.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/deployment/SystemV2.sol b/contracts/deployment/SystemV2.sol index b6cf972ba..64c2f86f0 100644 --- a/contracts/deployment/SystemV2.sol +++ b/contracts/deployment/SystemV2.sol @@ -122,7 +122,7 @@ contract SystemV2 { /// ---------- ORACLE PARAMS ---------- - uint40 public constant VOLT_APR_START_TIME = 1672531200; /// 2023-01-01 + uint40 public constant VOLT_APR_START_TIME = 1704096000; /// 2023-01-01 uint200 public constant VOLT_START_PRICE = 1.05e18; uint16 public constant VOLT_MONTHLY_BASIS_POINTS = 14; From 8948fe9bdeae95c7e840a8ccb7d37768ce1a9e93 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 9 Jan 2023 15:04:50 -0800 Subject: [PATCH 03/23] Liquid PCV Deposit role count and member checks --- .../test/integration/IntegrationTestSystemV2.t.sol | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/contracts/test/integration/IntegrationTestSystemV2.t.sol b/contracts/test/integration/IntegrationTestSystemV2.t.sol index 3354858ae..78b0fc107 100644 --- a/contracts/test/integration/IntegrationTestSystemV2.t.sol +++ b/contracts/test/integration/IntegrationTestSystemV2.t.sol @@ -182,21 +182,13 @@ contract IntegrationTestSystemV2 is Test { ); // LIQUID_PCV_DEPOSIT_ROLE - assertEq(core.getRoleMemberCount(VoltRoles.LIQUID_PCV_DEPOSIT), 4); + assertEq(core.getRoleMemberCount(VoltRoles.LIQUID_PCV_DEPOSIT), 2); assertEq( core.getRoleMember(VoltRoles.LIQUID_PCV_DEPOSIT, 0), - address(systemV2.daipsm()) - ); - assertEq( - core.getRoleMember(VoltRoles.LIQUID_PCV_DEPOSIT, 1), - address(systemV2.usdcpsm()) - ); - assertEq( - core.getRoleMember(VoltRoles.LIQUID_PCV_DEPOSIT, 2), address(systemV2.morphoDaiPCVDeposit()) ); assertEq( - core.getRoleMember(VoltRoles.LIQUID_PCV_DEPOSIT, 3), + core.getRoleMember(VoltRoles.LIQUID_PCV_DEPOSIT, 1), address(systemV2.morphoUsdcPCVDeposit()) ); From b5cadfe7b72e71177bf5a35631bcb166c1d8ad35 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 9 Jan 2023 15:08:54 -0800 Subject: [PATCH 04/23] TODO in deployment file around start time and price --- contracts/deployment/SystemV2.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/deployment/SystemV2.sol b/contracts/deployment/SystemV2.sol index 64c2f86f0..2af4ee418 100644 --- a/contracts/deployment/SystemV2.sol +++ b/contracts/deployment/SystemV2.sol @@ -122,8 +122,8 @@ contract SystemV2 { /// ---------- ORACLE PARAMS ---------- - uint40 public constant VOLT_APR_START_TIME = 1704096000; /// 2023-01-01 - uint200 public constant VOLT_START_PRICE = 1.05e18; + uint40 public constant VOLT_APR_START_TIME = 1704096000; /// 2024-01-01 TODO change this based on when the system starts + uint200 public constant VOLT_START_PRICE = 1.05e18; /// TODO change this based on when the system starts uint16 public constant VOLT_MONTHLY_BASIS_POINTS = 14; function deploy() public { From 0b6e393ec572aafa2119328f863765fabaf00cc3 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 9 Jan 2023 15:13:56 -0800 Subject: [PATCH 05/23] Remove LIQUID_PCV_DEPOSIT role from psm --- contracts/deployment/SystemV2.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/deployment/SystemV2.sol b/contracts/deployment/SystemV2.sol index 2af4ee418..6d6248b2b 100644 --- a/contracts/deployment/SystemV2.sol +++ b/contracts/deployment/SystemV2.sol @@ -292,8 +292,6 @@ contract SystemV2 { core.createRole(VoltRoles.LIQUID_PCV_DEPOSIT, VoltRoles.GOVERNOR); core.createRole(VoltRoles.ILLIQUID_PCV_DEPOSIT, VoltRoles.GOVERNOR); - core.grantRole(VoltRoles.LIQUID_PCV_DEPOSIT, address(daipsm)); - core.grantRole(VoltRoles.LIQUID_PCV_DEPOSIT, address(usdcpsm)); core.grantRole( VoltRoles.LIQUID_PCV_DEPOSIT, address(morphoDaiPCVDeposit) From c097365503e480229f8ba6bc55d9ce1eaacbd866 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 9 Jan 2023 20:05:33 -0800 Subject: [PATCH 06/23] PCV Deposit V2 --- contracts/pcv/IPCVDepositV2.sol | 2 + contracts/pcv/PCVDepositV2.sol | 128 ++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 contracts/pcv/PCVDepositV2.sol diff --git a/contracts/pcv/IPCVDepositV2.sol b/contracts/pcv/IPCVDepositV2.sol index ce2e144fc..bac589bb7 100644 --- a/contracts/pcv/IPCVDepositV2.sol +++ b/contracts/pcv/IPCVDepositV2.sol @@ -11,4 +11,6 @@ interface IPCVDepositV2 is IPCVDeposit { function harvest() external; function accrue() external returns (uint256); + + function token() external returns (address); } diff --git a/contracts/pcv/PCVDepositV2.sol b/contracts/pcv/PCVDepositV2.sol new file mode 100644 index 000000000..5996cd043 --- /dev/null +++ b/contracts/pcv/PCVDepositV2.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.13; + +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {CoreRefV2} from "../refs/CoreRefV2.sol"; +import {IPCVDepositV2} from "./IPCVDepositV2.sol"; + +/// @title abstract contract for withdrawing ERC-20 tokens using a PCV Controller +/// @author Elliot Friedman +abstract contract PCVDepositV2 is IPCVDepositV2, CoreRefV2 { + using SafeERC20 for IERC20; + + /// ------------------------------------------ + /// ------------- State Variables ------------ + /// ------------------------------------------ + + /// @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. + uint128 public lastRecordedBalance; + + /// @notice track the last amount of profits earned by 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. + int128 public lastRecordedProfits; + + /// ------------- Internal Helpers ------------- + + /// @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. + /// @return profit accumulated since last _recordPNL() call. + function _recordPNL() internal returns (int256) { + /// first accrue interest in the underlying venue + _accrue(); + + /// ------ Check ------ + + /// then get the current balance from the market + uint256 currentBalance = balance(); + + /// save gas if contract has no balance + /// 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 0; + } + + /// currentBalance should always be greater than or equal to + /// the deposited amount, except on the same block a deposit occurs, or a loss event in morpho + /// SLOAD + uint128 _lastRecordedBalance = lastRecordedBalance; + + /// Compute profit + int128 profit = int128(int256(currentBalance)) - + int128(_lastRecordedBalance); + + int128 _lastRecordedProfits = lastRecordedProfits + profit; + + /// ------ Effects ------ + + /// SSTORE: record new amounts + lastRecordedProfits = _lastRecordedProfits; + lastRecordedBalance = uint128(currentBalance); + + /// profit is in underlying token + emit Harvest(token(), int256(profit), block.timestamp); + + return profit; + } + + /// @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. + /// 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 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 + /// as the function _recordPNL() is already called before _withdraw + function _withdraw( + address to, + uint256 amount, + bool recordPnl + ) private returns (int256 profit) { + /// ------ Effects ------ + + if (recordPnl) { + /// compute profit from interest accrued and emit a Harvest event + profit = _recordPNL(); + } + + /// 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 -= uint128(amount); + + /// ------ Interactions ------ + + _withdraw(amount); + IERC20(token()).safeTransfer(to, amount); + + emit Withdrawal(msg.sender, to, amount); + } + + /// ------------- Virtual Functions ------------- + + /// @notice function to get balance in the underlying market. + /// @return current balance of deposit + function balance() public view virtual override returns (uint256); + + /// @dev function to get the underlying token. + function token() public view virtual override returns (address); + + /// @dev function to accrue in the underlying market. + function _accrue() internal virtual; + + /// @dev function to accrue in the underlying market. + function _withdraw(uint256 amount) internal virtual; +} From 5726f39629cb50f8f99bdebdd436cb52e84ee83b Mon Sep 17 00:00:00 2001 From: Elliot Date: Tue, 17 Jan 2023 18:16:16 -0800 Subject: [PATCH 07/23] PCVDeposit V2 and Morpho PCV Deposit refactor --- contracts/deployment/SystemV2.sol | 12 +- .../mock/MockMorphoMaliciousReentrancy.sol | 6 +- contracts/pcv/IPCVDepositV2.sol | 64 +++- contracts/pcv/PCVDepositV2.sol | 164 ++++++++- .../pcv/morpho/MorphoCompoundPCVDeposit.sol | 314 ----------------- contracts/pcv/morpho/MorphoPCVDeposit.sol | 135 ++++++++ contracts/pcv/utils/ERC20Allocator.sol | 4 +- .../IntegrationTestERC20Allocator.t.sol | 22 +- ...egrationTestMorphoCompoundPCVDeposit.t.sol | 12 +- .../integration/IntegrationTestSystemV2.t.sol | 12 +- .../IntegrationTestCompoundPCVRouter.t.sol | 14 +- .../IntegrationTestRateLimiters.sol | 316 ++++++++++++++++++ .../PostProposalCheck.sol | 26 ++ contracts/test/integration/vip/vip14.sol | 14 +- .../InvariantTestMorphoPCVDeposit.t.sol | 11 +- .../pcv/morpho/MorphoCompoundPCVDeposit.t.sol | 24 +- .../test/unit/pcv/utils/ERC20Allocator.t.sol | 26 +- 17 files changed, 757 insertions(+), 419 deletions(-) delete mode 100644 contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol create mode 100644 contracts/pcv/morpho/MorphoPCVDeposit.sol create mode 100644 contracts/test/integration/post-proposal-checks/IntegrationTestRateLimiters.sol create mode 100644 contracts/test/integration/post-proposal-checks/PostProposalCheck.sol diff --git a/contracts/deployment/SystemV2.sol b/contracts/deployment/SystemV2.sol index 6d6248b2b..cd4e5f13a 100644 --- a/contracts/deployment/SystemV2.sol +++ b/contracts/deployment/SystemV2.sol @@ -27,7 +27,7 @@ import {MainnetAddresses} from "../test/integration/fixtures/MainnetAddresses.so import {PegStabilityModule} from "../peg/PegStabilityModule.sol"; import {ConstantPriceOracle} from "../oracle/ConstantPriceOracle.sol"; import {IPCVDeposit, PCVDeposit} from "../pcv/PCVDeposit.sol"; -import {MorphoCompoundPCVDeposit} from "../pcv/morpho/MorphoCompoundPCVDeposit.sol"; +import {MorphoPCVDeposit} from "../pcv/morpho/MorphoPCVDeposit.sol"; import {IVoltMigrator, VoltMigrator} from "../volt/VoltMigrator.sol"; import {IGlobalReentrancyLock, GlobalReentrancyLock} from "../core/GlobalReentrancyLock.sol"; import {IGlobalRateLimitedMinter, GlobalRateLimitedMinter} from "../limiter/GlobalRateLimitedMinter.sol"; @@ -57,8 +57,8 @@ contract SystemV2 { MigratorRouter public migratorRouter; /// PCV Deposits - MorphoCompoundPCVDeposit public morphoDaiPCVDeposit; - MorphoCompoundPCVDeposit public morphoUsdcPCVDeposit; + MorphoPCVDeposit public morphoDaiPCVDeposit; + MorphoPCVDeposit public morphoUsdcPCVDeposit; /// Peg Stability PegStabilityModule public daipsm; @@ -167,18 +167,20 @@ contract SystemV2 { ); /// PCV Deposits - morphoDaiPCVDeposit = new MorphoCompoundPCVDeposit( + morphoDaiPCVDeposit = new MorphoPCVDeposit( address(core), MainnetAddresses.CDAI, address(dai), + MainnetAddresses.COMP, MainnetAddresses.MORPHO, MainnetAddresses.MORPHO_LENS ); - morphoUsdcPCVDeposit = new MorphoCompoundPCVDeposit( + morphoUsdcPCVDeposit = new MorphoPCVDeposit( address(core), MainnetAddresses.CUSDC, address(usdc), + MainnetAddresses.COMP, MainnetAddresses.MORPHO, MainnetAddresses.MORPHO_LENS ); diff --git a/contracts/mock/MockMorphoMaliciousReentrancy.sol b/contracts/mock/MockMorphoMaliciousReentrancy.sol index 8d7efb12f..e7ab6884b 100644 --- a/contracts/mock/MockMorphoMaliciousReentrancy.sol +++ b/contracts/mock/MockMorphoMaliciousReentrancy.sol @@ -1,12 +1,12 @@ pragma solidity 0.8.13; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {MorphoCompoundPCVDeposit} from "contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol"; +import {MorphoPCVDeposit} from "contracts/pcv/morpho/MorphoPCVDeposit.sol"; contract MockMorphoMaliciousReentrancy { IERC20 public immutable token; mapping(address => uint256) public balances; - MorphoCompoundPCVDeposit public morphoCompoundPCVDeposit; + MorphoPCVDeposit public morphoCompoundPCVDeposit; constructor(IERC20 _token) { token = _token; @@ -17,7 +17,7 @@ contract MockMorphoMaliciousReentrancy { } function setMorphoCompoundPCVDeposit(address deposit) external { - morphoCompoundPCVDeposit = MorphoCompoundPCVDeposit(deposit); + morphoCompoundPCVDeposit = MorphoPCVDeposit(deposit); } function withdraw(address, uint256) external { diff --git a/contracts/pcv/IPCVDepositV2.sol b/contracts/pcv/IPCVDepositV2.sol index bac589bb7..15633e730 100644 --- a/contracts/pcv/IPCVDepositV2.sol +++ b/contracts/pcv/IPCVDepositV2.sol @@ -1,16 +1,70 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.13; -import {IPCVDeposit} from "./IPCVDeposit.sol"; - /// @title PCV V2 Deposit interface /// @author Volt Protocol -interface IPCVDepositV2 is IPCVDeposit { - // ----------- State changing api ----------- +interface IPCVDepositV2 { + // ----------- Events ----------- + + event Deposit(address indexed _from, uint256 _amount); + + event Withdrawal( + address indexed _caller, + address indexed _to, + uint256 _amount + ); + + event WithdrawERC20( + address indexed _caller, + address indexed _token, + address indexed _to, + uint256 _amount + ); + + event WithdrawETH( + address indexed _caller, + address indexed _to, + uint256 _amount + ); + + event Harvest(address indexed _token, int256 _profit, uint256 _timestamp); + + // ----------- PCV Controller only state changing api ----------- + + function withdraw(address to, uint256 amount) external; + function withdrawERC20(address token, address to, uint256 amount) external; + + // ----------- Permissionless State changing api ----------- + + /// @notice deposit ERC-20 tokens to underlying venue + /// non-reentrant to block malicious reentrant state changes + /// to the lastRecordedBalance variable + function deposit() external; + + /// @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; + /// @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 returns (uint256); - function token() external returns (address); + // ----------- Getters ----------- + + /// @notice gets the effective balance of "balanceReportedIn" token if the deposit were fully withdrawn + function balance() external view returns (uint256); + + /// @notice gets the token address in which this deposit returns its balance + function balanceReportedIn() external view returns (address); + + /// @notice address of underlying token + function token() external view returns (address); + + /// @notice address of reward token + function rewardToken() external view returns (address); } diff --git a/contracts/pcv/PCVDepositV2.sol b/contracts/pcv/PCVDepositV2.sol index 5996cd043..5b3ab019a 100644 --- a/contracts/pcv/PCVDepositV2.sol +++ b/contracts/pcv/PCVDepositV2.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 {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -12,6 +13,13 @@ import {IPCVDepositV2} from "./IPCVDepositV2.sol"; /// @author Elliot Friedman abstract contract PCVDepositV2 is IPCVDepositV2, CoreRefV2 { using SafeERC20 for IERC20; + using SafeCast for *; + + /// @notice reference to underlying token + address public immutable override rewardToken; + + /// @notice reference to underlying token + address public immutable override token; /// ------------------------------------------ /// ------------- State Variables ------------ @@ -29,6 +37,132 @@ abstract contract PCVDepositV2 is IPCVDepositV2, CoreRefV2 { /// most of the time. int128 public lastRecordedProfits; + constructor(address _token, address _rewardToken) { + token = _token; + rewardToken = _rewardToken; + } + + /// ------------------------------------------ + /// ----------- Permissionless API ----------- + /// ------------------------------------------ + + /// @notice deposit ERC-20 tokens to Morpho-Compound + /// non-reentrant to block malicious reentrant state changes + /// to the lastRecordedBalance variable + function deposit() public whenNotPaused globalLock(2) { + /// ------ Check ------ + + uint256 amount = IERC20(token).balanceOf(address(this)); + if (amount == 0) { + /// no op to prevent revert on empty deposit + return; + } + + int256 startingRecordedBalance = lastRecordedBalance.toInt256(); + + /// ------ Effects ------ + + /// compute profit from interest accrued and emit an event + /// if any profits or losses are realized + int256 profit = _recordPNL(); + + /// increment tracked recorded amount + /// this will be off by a hair, after a single block + /// negative delta turns to positive delta (assuming no loss). + lastRecordedBalance += uint128(amount); + + /// ------ Interactions ------ + + /// approval and deposit into underlying venue + _supply(amount); + + /// ------ Update Internal Accounting ------ + + int256 endingRecordedBalance = balance().toInt256(); + + _liquidPcvOracleHook( + endingRecordedBalance - startingRecordedBalance, + profit + ); + + 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 globalLock(2) { + uint256 claimedAmount = _claim(); + + emit Harvest(rewardToken, 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 globalLock(2) whenNotPaused returns (uint256) { + int256 profit = _recordPNL(); /// update deposit amount and fire harvest event + + /// if any amount of PCV is withdrawn and no gains, delta is negative + _liquidPcvOracleHook(profit, profit); + + 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 + /// @param amount of tokens withdrawn + function withdraw( + address to, + uint256 amount + ) external onlyPCVController globalLock(2) { + int256 profit = _withdraw(to, amount, true); + + /// if any amount of PCV is withdrawn and no gains, delta is negative + _liquidPcvOracleHook(-(amount.toInt256()) + profit, profit); + } + + /// @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 globalLock(2) { + /// compute profit from interest accrued and emit an event + int256 profit = _recordPNL(); + + int256 recordedBalance = lastRecordedBalance.toInt256(); + + /// withdraw last recorded amount as this was updated in record pnl + _withdraw(to, lastRecordedBalance, false); + + /// all PCV withdrawn, send call in with amount withdrawn negative if any amount is withdrawn + _liquidPcvOracleHook(-recordedBalance, profit); + } + + /// @notice withdraw ERC20 from the contract + /// @param tokenAddress address of the ERC20 to send + /// @param to address destination of the ERC20 + /// @param amount quantity of ERC20 to send + /// Calling this function will lead to incorrect + /// accounting in a PCV deposit that tracks + /// profits and or last recorded balance. + /// If a deposit records PNL, only use this + /// function in an emergency. + function withdrawERC20( + address tokenAddress, + address to, + uint256 amount + ) public virtual override onlyPCVController { + IERC20(tokenAddress).safeTransfer(to, amount); + emit WithdrawERC20(msg.sender, tokenAddress, to, amount); + } + /// ------------- Internal Helpers ------------- /// @notice records how much profit or loss has been accrued @@ -37,7 +171,7 @@ abstract contract PCVDepositV2 is IPCVDepositV2, CoreRefV2 { /// @return profit accumulated since last _recordPNL() call. function _recordPNL() internal returns (int256) { /// first accrue interest in the underlying venue - _accrue(); + _accrueUnderlying(); /// ------ Check ------ @@ -70,12 +204,12 @@ abstract contract PCVDepositV2 is IPCVDepositV2, CoreRefV2 { lastRecordedBalance = uint128(currentBalance); /// profit is in underlying token - emit Harvest(token(), int256(profit), block.timestamp); + emit Harvest(token, int256(profit), block.timestamp); return profit; } - /// @notice helper function to avoid repeated code in withdraw and withdrawAll + /// @notice helper 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. /// Morpho is assumed to be a loss-less venue. over the course of less than 1 block, @@ -105,24 +239,30 @@ abstract contract PCVDepositV2 is IPCVDepositV2, CoreRefV2 { /// ------ Interactions ------ - _withdraw(amount); - IERC20(token()).safeTransfer(to, amount); + /// remove funds from underlying venue + _withdrawUnderlyingVenue(amount); + /// transfer funds to recipient + IERC20(token).safeTransfer(to, amount); emit Withdrawal(msg.sender, to, amount); } /// ------------- Virtual Functions ------------- - /// @notice function to get balance in the underlying market. + /// @notice get balance in the underlying market. /// @return current balance of deposit function balance() public view virtual override returns (uint256); - /// @dev function to get the underlying token. - function token() public view virtual override returns (address); + /// @dev accrue interest in the underlying market. + function _accrueUnderlying() internal virtual; + + /// @dev withdraw from the underlying market. + function _withdrawUnderlyingVenue(uint256 amount) internal virtual; - /// @dev function to accrue in the underlying market. - function _accrue() internal virtual; + /// @dev deposit in the underlying market. + function _supply(uint256 amount) internal virtual; - /// @dev function to accrue in the underlying market. - function _withdraw(uint256 amount) internal virtual; + /// @dev claim rewards from the underlying market. + /// returns amount of reward tokens claimed + function _claim() internal virtual returns (uint256); } diff --git a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol b/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol deleted file mode 100644 index db6e9a345..000000000 --- a/contracts/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ /dev/null @@ -1,314 +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 {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import {ILens} from "./ILens.sol"; -import {ICToken} from "./ICompound.sol"; -import {IMorpho} from "./IMorpho.sol"; -import {CoreRefV2} from "../../refs/CoreRefV2.sol"; -import {Constants} from "../../Constants.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. -/// @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 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. -/// @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 { - 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; - - /// @notice reference to the lens contract for morpho-compound v2 - address public immutable lens; - - /// @notice reference to the morpho-compound v2 market - address public immutable morpho; - - /// @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; - - /// ------------------------------------------ - /// ------------- State Variables ------------- - /// ------------------------------------------ - - /// @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. - uint128 public lastRecordedBalance; - - /// @notice track the last amount of profits earned by 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. - int128 public lastRecordedProfits; - - /// @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, - address _morpho, - address _lens - ) CoreRefV2(_core) { - if (_underlying != address(Constants.WETH)) { - require( - ICToken(_cToken).underlying() == _underlying, - "MorphoCompoundPCVDeposit: Underlying mismatch" - ); - } - cToken = _cToken; - token = _underlying; - morpho = _morpho; - 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) { - (, , 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; - } - - /// ------------------------------------------ - /// ----------- Permissionless API ----------- - /// ------------------------------------------ - - /// @notice deposit ERC-20 tokens to Morpho-Compound - /// non-reentrant to block malicious reentrant state changes - /// to the lastRecordedBalance variable - function deposit() public whenNotPaused globalLock(2) { - /// ------ Check ------ - - uint256 amount = IERC20(token).balanceOf(address(this)); - if (amount == 0) { - /// no op to prevent revert on empty deposit - return; - } - - int256 startingRecordedBalance = lastRecordedBalance.toInt256(); - - /// ------ Effects ------ - - /// compute profit from interest accrued and emit an event - /// if any profits or losses are realized - int256 profit = _recordPNL(); - - /// increment tracked recorded amount - /// this will be off by a hair, after a single block - /// negative delta turns to positive delta (assuming no loss). - lastRecordedBalance += uint128(amount); - - /// ------ Interactions ------ - - IERC20(token).approve(address(morpho), amount); - IMorpho(morpho).supply( - cToken, /// cToken to supply liquidity to - address(this), /// the address of the user you want to supply on behalf of - amount - ); - - int256 endingRecordedBalance = balance().toInt256(); - - _liquidPcvOracleHook( - endingRecordedBalance - startingRecordedBalance, - profit - ); - - 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 globalLock(2) { - 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 globalLock(2) whenNotPaused returns (uint256) { - int256 profit = _recordPNL(); /// update deposit amount and fire harvest event - - /// if any amount of PCV is withdrawn and no gains, delta is negative - _liquidPcvOracleHook(profit, profit); - - 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 - /// @param amount of tokens withdrawn - function withdraw( - address to, - uint256 amount - ) external onlyPCVController globalLock(2) { - int256 profit = _withdraw(to, amount, true); - - /// if any amount of PCV is withdrawn and no gains, delta is negative - _liquidPcvOracleHook(-(amount.toInt256()) + profit, profit); - } - - /// @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 globalLock(2) { - /// compute profit from interest accrued and emit an event - int256 profit = _recordPNL(); - - int256 recordedBalance = lastRecordedBalance.toInt256(); - - /// withdraw last recorded amount as this was updated in record pnl - _withdraw(to, lastRecordedBalance, false); - - /// all PCV withdrawn, send call in with amount withdrawn negative if any amount is withdrawn - _liquidPcvOracleHook(-recordedBalance, profit); - } - - /// ------------------------------------------ - /// ------------- 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. - /// 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 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 - /// as the function _recordPNL() is already called before _withdraw - function _withdraw( - address to, - uint256 amount, - bool recordPnl - ) private returns (int256 profit) { - /// ------ Effects ------ - - if (recordPnl) { - /// compute profit from interest accrued and emit a Harvest event - profit = _recordPNL(); - } - - /// 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 -= uint128(amount); - - /// ------ Interactions ------ - - IMorpho(morpho).withdraw(cToken, amount); - IERC20(token).safeTransfer(to, amount); - - emit Withdrawal(msg.sender, to, amount); - } - - /// @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. - /// @return profit accumulated since last _recordPNL() call. - function _recordPNL() private returns (int256) { - /// first accrue interest in Compound and Morpho - IMorpho(morpho).updateP2PIndexes(cToken); - - /// ------ Check ------ - - /// then get the current balance from the market - uint256 currentBalance = balance(); - - /// save gas if contract has no balance - /// 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 0; - } - - /// currentBalance should always be greater than or equal to - /// the deposited amount, except on the same block a deposit occurs, or a loss event in morpho - /// SLOAD - uint128 _lastRecordedBalance = lastRecordedBalance; - int128 _lastRecordedProfits = lastRecordedProfits; - - /// Compute profit - int128 profit = int128(int256(currentBalance)) - - int128(_lastRecordedBalance); - - /// ------ Effects ------ - - /// SSTORE: record new amounts - lastRecordedProfits = _lastRecordedProfits + profit; - lastRecordedBalance = uint128(currentBalance); - - /// profit is in underlying token - emit Harvest(token, int256(profit), block.timestamp); - - return profit; - } -} diff --git a/contracts/pcv/morpho/MorphoPCVDeposit.sol b/contracts/pcv/morpho/MorphoPCVDeposit.sol new file mode 100644 index 000000000..06c45ccac --- /dev/null +++ b/contracts/pcv/morpho/MorphoPCVDeposit.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity =0.8.13; + +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"; +import {ICToken} from "./ICompound.sol"; +import {IMorpho} from "./IMorpho.sol"; +import {CoreRefV2} from "../../refs/CoreRefV2.sol"; +import {Constants} from "../../Constants.sol"; +import {PCVDepositV2} from "../PCVDepositV2.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. +/// @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 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. +/// @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 MorphoPCVDeposit is PCVDepositV2 { + using SafeERC20 for IERC20; + using SafeCast for *; + + /// ------------------------------------------ + /// ---------- Immutables/Constant ----------- + /// ------------------------------------------ + + /// @notice reference to the lens contract for morpho-compound v2 + address public immutable lens; + + /// @notice reference to the morpho-compound v2 market + address public immutable morpho; + + /// @notice cToken in compound this deposit tracks + /// used to inform morpho about the desired market to supply liquidity + address public immutable cToken; + + /// @param _core reference to the core contract + /// @param _cToken cToken this deposit references + /// @param _underlying Token denomination of this deposit + /// @param _rewardToken Reward 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, + address _rewardToken, + address _morpho, + address _lens + ) PCVDepositV2(_underlying, _rewardToken) CoreRefV2(_core) { + if (_underlying != address(Constants.WETH)) { + require( + ICToken(_cToken).underlying() == _underlying, + "MorphoPCVDeposit: Underlying mismatch" + ); + } + cToken = _cToken; + token = _underlying; + morpho = _morpho; + 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) { + (, , 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; + } + + /// ------------------------------------------ + /// ------------- Helper Methods ------------- + /// ------------------------------------------ + + /// @notice accrue interest in the underlying morpho venue + function _accrueUnderlying() internal override { + /// accrue interest in Morpho + IMorpho(morpho).updateP2PIndexes(cToken); + } + + /// @dev withdraw from the underlying morpho market. + function _withdrawUnderlyingVenue(uint256 amount) internal override { + IMorpho(morpho).withdraw(cToken, amount); + } + + /// @dev deposit in the underlying morpho market. + function _supply(uint256 amount) internal override { + IERC20(token).approve(address(morpho), amount); + IMorpho(morpho).supply( + cToken, /// cToken to supply liquidity to + address(this), /// the address of the user you want to supply on behalf of + amount + ); + } + + /// @dev claim rewards from the underlying Compound market. + /// returns amount of reward tokens claimed + function _claim() internal override returns (uint256) { + address[] memory cTokens = new address[](1); + cTokens[0] = cToken; + + return IMorpho(morpho).claimRewards(cTokens, false); /// bool set false to receive COMP + } +} diff --git a/contracts/pcv/utils/ERC20Allocator.sol b/contracts/pcv/utils/ERC20Allocator.sol index 300bbb434..ae5ea5af2 100644 --- a/contracts/pcv/utils/ERC20Allocator.sol +++ b/contracts/pcv/utils/ERC20Allocator.sol @@ -300,7 +300,7 @@ contract ERC20Allocator is IERC20Allocator, CoreRefV2 { /// reverts if not drip eligbile function getDripDetails( address psm, - PCVDeposit pcvDeposit + address pcvDeposit ) public view returns (uint256 amountToDrip, uint256 adjustedAmountToDrip) { PSMInfo memory toDrip = allPSMs[psm]; @@ -325,7 +325,7 @@ contract ERC20Allocator is IERC20Allocator, CoreRefV2 { /// amountToDrip = 1,000e6 amountToDrip = Math.min( - Math.min(targetBalanceDelta, pcvDeposit.balance()), + Math.min(targetBalanceDelta, PCVDeposit(pcvDeposit).balance()), /// adjust for decimals here as buffer is 1e18 scaled, /// and if token is not scaled by 1e18, then this amountToDrip could be over the buffer /// because buffer is 1e18 adjusted, and decimals normalizer is used to adjust up to the buffer diff --git a/contracts/test/integration/IntegrationTestERC20Allocator.t.sol b/contracts/test/integration/IntegrationTestERC20Allocator.t.sol index 9841e1ffc..32c2d9e78 100644 --- a/contracts/test/integration/IntegrationTestERC20Allocator.t.sol +++ b/contracts/test/integration/IntegrationTestERC20Allocator.t.sol @@ -17,7 +17,7 @@ import {ERC20Allocator} from "../../pcv/utils/ERC20Allocator.sol"; import {MainnetAddresses} from "./fixtures/MainnetAddresses.sol"; import {TimelockSimulation} from "./utils/TimelockSimulation.sol"; import {PegStabilityModule} from "../../peg/PegStabilityModule.sol"; -import {MorphoCompoundPCVDeposit} from "../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; +import {MorphoPCVDeposit} from "../../pcv/morpho/MorphoPCVDeposit.sol"; contract IntegrationTestERC20Allocator is DSTest { using SafeCast for *; @@ -26,14 +26,10 @@ contract IntegrationTestERC20Allocator is DSTest { PCVGuardian private immutable mainnetPCVGuardian = PCVGuardian(MainnetAddresses.PCV_GUARDIAN); - MorphoCompoundPCVDeposit private daiDeposit = - MorphoCompoundPCVDeposit( - MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT - ); - MorphoCompoundPCVDeposit private usdcDeposit = - MorphoCompoundPCVDeposit( - MainnetAddresses.MORPHO_COMPOUND_USDC_PCV_DEPOSIT - ); + MorphoPCVDeposit private daiDeposit = + MorphoPCVDeposit(MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT); + MorphoPCVDeposit private usdcDeposit = + MorphoPCVDeposit(MainnetAddresses.MORPHO_COMPOUND_USDC_PCV_DEPOSIT); PCVGuardian private immutable pcvGuardian = PCVGuardian(MainnetAddresses.PCV_GUARDIAN); @@ -116,7 +112,7 @@ contract IntegrationTestERC20Allocator is DSTest { daiDeposit.deposit(); (uint256 amountToDrip, uint256 adjustedAmountToDrip) = allocator - .getDripDetails(address(daiPSM), daiDeposit); + .getDripDetails(address(daiPSM), address(daiDeposit)); assertTrue(allocator.checkDripCondition(address(daiDeposit))); assertTrue(!allocator.checkSkimCondition(address(daiDeposit))); @@ -141,7 +137,7 @@ contract IntegrationTestERC20Allocator is DSTest { usdcDeposit.deposit(); /// deposit so it will be counted in balance (uint256 amountToDrip, uint256 adjustedAmountToDrip) = allocator - .getDripDetails(address(usdcPSM), usdcDeposit); + .getDripDetails(address(usdcPSM), address(usdcDeposit)); assertTrue(allocator.checkDripCondition(address(usdcDeposit))); assertTrue(allocator.checkActionAllowed(address(usdcDeposit))); @@ -231,7 +227,7 @@ contract IntegrationTestERC20Allocator is DSTest { daiDeposit.deposit(); (uint256 amountToDrip, uint256 adjustedAmountToDrip) = allocator - .getDripDetails(address(daiPSM), daiDeposit); + .getDripDetails(address(daiPSM), address(daiDeposit)); assertTrue(allocator.checkDripCondition(address(daiDeposit))); assertTrue(!allocator.checkSkimCondition(address(daiDeposit))); @@ -258,7 +254,7 @@ contract IntegrationTestERC20Allocator is DSTest { usdcDeposit.deposit(); /// deposit so it will be counted in balance (uint256 amountToDrip, uint256 adjustedAmountToDrip) = allocator - .getDripDetails(address(usdcPSM), usdcDeposit); + .getDripDetails(address(usdcPSM), address(usdcDeposit)); assertTrue(allocator.checkDripCondition(address(usdcDeposit))); assertTrue(allocator.checkActionAllowed(address(usdcDeposit))); diff --git a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol index 97fc6ba40..7a7156eb2 100644 --- a/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol @@ -16,7 +16,7 @@ import {SystemEntry} from "../../entry/SystemEntry.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 {MorphoPCVDeposit} from "../../pcv/morpho/MorphoPCVDeposit.sol"; import {TestAddresses as addresses} from "../unit/utils/TestAddresses.sol"; import {IGlobalReentrancyLock, GlobalReentrancyLock} from "../../core/GlobalReentrancyLock.sol"; @@ -28,8 +28,8 @@ contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { CoreV2 private core; SystemEntry public entry; GlobalReentrancyLock private lock; - MorphoCompoundPCVDeposit private daiDeposit; - MorphoCompoundPCVDeposit private usdcDeposit; + MorphoPCVDeposit private daiDeposit; + MorphoPCVDeposit private usdcDeposit; PCVGuardian private pcvGuardian; @@ -51,18 +51,20 @@ contract IntegrationTestMorphoCompoundPCVDeposit is DSTest { function setUp() public { core = getCoreV2(); lock = new GlobalReentrancyLock(address(core)); - daiDeposit = new MorphoCompoundPCVDeposit( + daiDeposit = new MorphoPCVDeposit( address(core), MainnetAddresses.CDAI, MainnetAddresses.DAI, + MainnetAddresses.COMP, MainnetAddresses.MORPHO, MainnetAddresses.MORPHO_LENS ); - usdcDeposit = new MorphoCompoundPCVDeposit( + usdcDeposit = new MorphoPCVDeposit( address(core), MainnetAddresses.CUSDC, MainnetAddresses.USDC, + MainnetAddresses.COMP, MainnetAddresses.MORPHO, MainnetAddresses.MORPHO_LENS ); diff --git a/contracts/test/integration/IntegrationTestSystemV2.t.sol b/contracts/test/integration/IntegrationTestSystemV2.t.sol index 78b0fc107..ee1bd6137 100644 --- a/contracts/test/integration/IntegrationTestSystemV2.t.sol +++ b/contracts/test/integration/IntegrationTestSystemV2.t.sol @@ -35,8 +35,8 @@ contract IntegrationTestSystemV2 is Test { PCVOracle pcvOracle; TimelockController timelockController; PCVGuardian pcvGuardian; - MorphoCompoundPCVDeposit morphoDaiPCVDeposit; - MorphoCompoundPCVDeposit morphoUsdcPCVDeposit; + MorphoPCVDeposit morphoDaiPCVDeposit; + MorphoPCVDeposit morphoUsdcPCVDeposit; PCVRouter pcvRouter; SystemEntry systemEntry; VoltMigrator voltMigrator; @@ -837,10 +837,10 @@ contract IntegrationTestSystemV2 is Test { Internal helper function, migrate the PCV from current system to V2 system */ function _migratePcv() internal returns (uint256) { - MorphoCompoundPCVDeposit oldMorphoDaiPCVDeposit = MorphoCompoundPCVDeposit( - MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT - ); - /*MorphoCompoundPCVDeposit oldMorphoUsdcPCVDeposit = MorphoCompoundPCVDeposit( + MorphoPCVDeposit oldMorphoDaiPCVDeposit = MorphoPCVDeposit( + MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT + ); + /*MorphoPCVDeposit oldMorphoUsdcPCVDeposit = MorphoPCVDeposit( MainnetAddresses.MORPHO_COMPOUND_USDC_PCV_DEPOSIT );*/ PCVGuardian oldPCVGuardian = PCVGuardian(MainnetAddresses.PCV_GUARDIAN); diff --git a/contracts/test/integration/compound/IntegrationTestCompoundPCVRouter.t.sol b/contracts/test/integration/compound/IntegrationTestCompoundPCVRouter.t.sol index 43d414257..6bfc32360 100644 --- a/contracts/test/integration/compound/IntegrationTestCompoundPCVRouter.t.sol +++ b/contracts/test/integration/compound/IntegrationTestCompoundPCVRouter.t.sol @@ -15,7 +15,7 @@ import {PCVGuardian} from "../../../pcv/PCVGuardian.sol"; import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; import {ArbitrumAddresses} from "../fixtures/ArbitrumAddresses.sol"; import {CompoundPCVRouter} from "../../../pcv/compound/CompoundPCVRouter.sol"; -import {MorphoCompoundPCVDeposit} from "../../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; +import {MorphoPCVDeposit} from "../../../pcv/morpho/MorphoPCVDeposit.sol"; contract IntegrationTestCompoundPCVRouter is DSTest { using SafeCast for *; @@ -36,14 +36,10 @@ contract IntegrationTestCompoundPCVRouter is DSTest { CompoundPCVRouter private compoundRouter = CompoundPCVRouter(MainnetAddresses.MORPHO_COMPOUND_PCV_ROUTER); - MorphoCompoundPCVDeposit private daiDeposit = - MorphoCompoundPCVDeposit( - MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT - ); - MorphoCompoundPCVDeposit private usdcDeposit = - MorphoCompoundPCVDeposit( - MainnetAddresses.MORPHO_COMPOUND_USDC_PCV_DEPOSIT - ); + MorphoPCVDeposit private daiDeposit = + MorphoPCVDeposit(MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT); + MorphoPCVDeposit private usdcDeposit = + MorphoPCVDeposit(MainnetAddresses.MORPHO_COMPOUND_USDC_PCV_DEPOSIT); address public immutable pcvGuard = MainnetAddresses.EOA_1; PCVGuardian public immutable pcvGuardian = diff --git a/contracts/test/integration/post-proposal-checks/IntegrationTestRateLimiters.sol b/contracts/test/integration/post-proposal-checks/IntegrationTestRateLimiters.sol new file mode 100644 index 000000000..25ff2dfbe --- /dev/null +++ b/contracts/test/integration/post-proposal-checks/IntegrationTestRateLimiters.sol @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.13; + +import {PostProposalCheck} from "./PostProposalCheck.sol"; + +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +import {VoltV2} from "../../../volt/VoltV2.sol"; +import {CoreV2} from "../../../core/CoreV2.sol"; +import {PCVOracle} from "../../../oracle/PCVOracle.sol"; +import {PCVDeposit} from "../../../pcv/PCVDeposit.sol"; +import {SystemEntry} from "../../../entry/SystemEntry.sol"; +import {ERC20Allocator} from "../../../pcv/utils/ERC20Allocator.sol"; +import {NonCustodialPSM} from "../../../peg/NonCustodialPSM.sol"; +import {GlobalRateLimitedMinter} from "../../../limiter/GlobalRateLimitedMinter.sol"; +import {GlobalSystemExitRateLimiter} from "../../../limiter/GlobalSystemExitRateLimiter.sol"; +import {PegStabilityModule} from "../../../peg/PegStabilityModule.sol"; + +contract IntegrationTestRateLimiters is PostProposalCheck { + using SafeCast for *; + + address public constant user = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7; + uint256 public snapshotAfterMints; + + CoreV2 private core; + VoltV2 private volt; + SystemEntry private systemEntry; + ERC20Allocator private allocator; + PegStabilityModule private usdcpsm; + PegStabilityModule private daipsm; + NonCustodialPSM private usdcncpsm; + NonCustodialPSM private daincpsm; + IERC20 private dai; + IERC20 private usdc; + GlobalRateLimitedMinter private grlm; + GlobalSystemExitRateLimiter private gserl; + PCVOracle private pcvOracle; + address private morphoUsdcPCVDeposit; + address private morphoDaiPCVDeposit; + + function setUp() public override { + super.setUp(); + + core = CoreV2(addresses.mainnet("CORE")); + volt = VoltV2(addresses.mainnet("VOLT")); + systemEntry = SystemEntry(addresses.mainnet("SYSTEM_ENTRY")); + allocator = ERC20Allocator(addresses.mainnet("PSM_ALLOCATOR")); + usdcpsm = PegStabilityModule(addresses.mainnet("PSM_USDC")); + daipsm = PegStabilityModule(addresses.mainnet("PSM_DAI")); + usdcncpsm = NonCustodialPSM(addresses.mainnet("PSM_NONCUSTODIAL_USDC")); + daincpsm = NonCustodialPSM(addresses.mainnet("PSM_NONCUSTODIAL_DAI")); + dai = IERC20(addresses.mainnet("DAI")); + usdc = IERC20(addresses.mainnet("USDC")); + grlm = GlobalRateLimitedMinter( + addresses.mainnet("GLOBAL_RATE_LIMITED_MINTER") + ); + gserl = GlobalSystemExitRateLimiter( + addresses.mainnet("GLOBAL_SYSTEM_EXIT_RATE_LIMITER") + ); + pcvOracle = PCVOracle(addresses.mainnet("PCV_ORACLE")); + morphoUsdcPCVDeposit = addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC"); + morphoDaiPCVDeposit = addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI"); + } + + /* + Flow of the first user that mints VOLT in the new system. + Performs checks on the global rate limits, and accounting + in the new system's PCV Oracle. + */ + function testUserPSMMint() public { + // read initial buffer left + uint256 bufferCap = grlm.bufferCap(); + uint256 initialBuffer = grlm.buffer(); + + // number of moved funds for tests + uint256 amount = initialBuffer / 2; + (, uint256 daiPSMTargetBalance, ) = allocator.allPSMs(address(daipsm)); + (, uint256 usdcPSMTargetBalance, ) = allocator.allPSMs( + address(usdcpsm) + ); + + // read initial pcv + (uint256 startLiquidPcv, , ) = pcvOracle.getTotalPcv(); + // read initial psm balances + uint256 startPsmDaiBalance = dai.balanceOf(address(daipsm)); + uint256 startPsmUsdcBalance = usdc.balanceOf(address(usdcpsm)); + + // user performs the first mint with DAI + vm.startPrank(user); + dai.approve(address(daipsm), amount); + daipsm.mint(user, amount, 0); + vm.stopPrank(); + + (, uint256 adjustedSkimAmount) = allocator.getSkimDetails( + address(morphoDaiPCVDeposit) + ); + uint256 gserlStartingBuffer = gserl.buffer(); + + // buffer has been used + uint256 voltReceived1 = volt.balanceOf(user); + assertEq(grlm.buffer(), initialBuffer - voltReceived1); + + allocator.skim(morphoDaiPCVDeposit); + + /// assert replenish occurred if not maxed out + assertEq( + Math.min( + gserlStartingBuffer + adjustedSkimAmount, + gserlStartingBuffer + ), + gserl.buffer() + ); + + // after first mint, pcv increased by amount + (uint256 liquidPcv2, , ) = pcvOracle.getTotalPcv(); + assertApproxEq( + liquidPcv2.toInt256(), + (startLiquidPcv + startPsmDaiBalance + amount - daiPSMTargetBalance) + .toInt256(), + 0 + ); + + // user performs the second mint wit USDC + vm.startPrank(user); + usdc.approve(address(usdcpsm), amount / 1e12); + usdcpsm.mint(user, amount / 1e12, 0); + vm.stopPrank(); + uint256 voltReceived2 = volt.balanceOf(user) - voltReceived1; + + (, adjustedSkimAmount) = allocator.getSkimDetails( + address(morphoUsdcPCVDeposit) + ); + gserlStartingBuffer = gserl.buffer(); + + // buffer has been used + assertEq(grlm.buffer(), initialBuffer - voltReceived1 - voltReceived2); + + allocator.skim(morphoUsdcPCVDeposit); + { + // after second mint, pcv is = 2 * amount + (uint256 liquidPcv3, , ) = pcvOracle.getTotalPcv(); + assertApproxEq( + liquidPcv3.toInt256(), + (liquidPcv2 + + startPsmUsdcBalance * + 1e12 + + amount - + usdcPSMTargetBalance * + 1e12).toInt256(), + 0 + ); + } + + /// assert replenish occurred if not maxed out + assertEq( + Math.min( + gserlStartingBuffer + adjustedSkimAmount, + gserlStartingBuffer + ), + gserl.buffer() + ); + + snapshotAfterMints = vm.snapshot(); + + vm.prank(address(core)); + grlm.setRateLimitPerSecond(5.787e18); + + // buffer replenishes over time + vm.warp(block.timestamp + 3 days); + + // above limit rate reverts + vm.startPrank(user); + dai.approve(address(daipsm), bufferCap * 2); + vm.expectRevert("RateLimited: rate limit hit"); + daipsm.mint(user, bufferCap * 2, 0); + vm.stopPrank(); + } + + function testRedeemsDaiPsm(uint88 voltAmount) public { + testUserPSMMint(); + + vm.revertTo(snapshotAfterMints); + vm.assume(voltAmount <= volt.balanceOf(user)); + + uint256 daiAmountOut = daipsm.getRedeemAmountOut(voltAmount); + deal(address(dai), address(daipsm), daiAmountOut); + + vm.startPrank(user); + + uint256 startingBuffer = grlm.buffer(); + uint256 startingDaiBalance = dai.balanceOf(user); + + volt.approve(address(daipsm), voltAmount); + daipsm.redeem(user, voltAmount, daiAmountOut); + + uint256 endingBuffer = grlm.buffer(); + uint256 endingDaiBalance = dai.balanceOf(user); + + assertEq(endingDaiBalance - startingDaiBalance, daiAmountOut); + assertEq(endingBuffer - startingBuffer, voltAmount); + + vm.stopPrank(); + + uint256 startingExitBuffer = gserl.buffer(); + (, uint256 expectedBufferDepletion) = allocator.getDripDetails( + address(daipsm), + address(morphoDaiPCVDeposit) + ); + allocator.drip(address(morphoDaiPCVDeposit)); + assertEq(startingExitBuffer - expectedBufferDepletion, gserl.buffer()); + } + + function testRedeemsDaiNcPsm(uint80 voltRedeemAmount) public { + vm.assume(voltRedeemAmount >= 1e18); + vm.assume(voltRedeemAmount <= 400_000e18); + testUserPSMMint(); + + vm.revertTo(snapshotAfterMints); + + uint256 daiAmountOut = daincpsm.getRedeemAmountOut(voltRedeemAmount); + deal(address(dai), morphoDaiPCVDeposit, daiAmountOut * 2); + systemEntry.deposit(morphoDaiPCVDeposit); + + vm.startPrank(user); + + uint256 startingBuffer = grlm.buffer(); + uint256 startingExitBuffer = gserl.buffer(); + uint256 startingDaiBalance = dai.balanceOf(user); + + volt.approve(address(daincpsm), voltRedeemAmount); + daincpsm.redeem(user, voltRedeemAmount, daiAmountOut); + + uint256 endingBuffer = grlm.buffer(); + uint256 endingExitBuffer = gserl.buffer(); + uint256 endingDaiBalance = dai.balanceOf(user); + + assertEq(endingBuffer - startingBuffer, voltRedeemAmount); /// grlm buffer replenished + assertEq(endingDaiBalance - startingDaiBalance, daiAmountOut); + assertEq(startingExitBuffer - endingExitBuffer, daiAmountOut); /// exit buffer depleted + + vm.stopPrank(); + } + + function testRedeemsUsdcNcPsm(uint80 voltRedeemAmount) public { + vm.assume(voltRedeemAmount >= 1e18); + vm.assume(voltRedeemAmount <= 400_000e18); + testUserPSMMint(); + + vm.revertTo(snapshotAfterMints); + + uint256 usdcAmountOut = usdcncpsm.getRedeemAmountOut(voltRedeemAmount); + deal(address(usdc), morphoUsdcPCVDeposit, usdcAmountOut * 2); + systemEntry.deposit(morphoUsdcPCVDeposit); + + vm.startPrank(user); + + uint256 startingBuffer = grlm.buffer(); + uint256 startingExitBuffer = gserl.buffer(); + uint256 startingUsdcBalance = usdc.balanceOf(user); + + volt.approve(address(usdcncpsm), voltRedeemAmount); + usdcncpsm.redeem(user, voltRedeemAmount, usdcAmountOut); + + uint256 endingBuffer = grlm.buffer(); + uint256 endingExitBuffer = gserl.buffer(); + uint256 endingUsdcBalance = usdc.balanceOf(user); + + assertEq(endingBuffer - startingBuffer, voltRedeemAmount); /// buffer replenished + assertEq(endingUsdcBalance - startingUsdcBalance, usdcAmountOut); + assertEq(startingExitBuffer - endingExitBuffer, usdcAmountOut * 1e12); /// ensure buffer adjusted up 12 decimals, buffer depleted + + vm.stopPrank(); + } + + function testRedeemsUsdcPsm(uint80 voltRedeemAmount) public { + vm.assume(voltRedeemAmount >= 1e18); + vm.assume(voltRedeemAmount <= 475_000e18); + testUserPSMMint(); + + vm.revertTo(snapshotAfterMints); + + uint256 usdcAmountOut = usdcpsm.getRedeemAmountOut(voltRedeemAmount); + deal(address(usdc), address(usdcpsm), usdcAmountOut); + + uint256 startingBuffer = grlm.buffer(); + uint256 startingExitBuffer = gserl.buffer(); + uint256 startingUsdcBalance = usdc.balanceOf(user); + + vm.startPrank(user); + volt.approve(address(usdcpsm), voltRedeemAmount); + usdcpsm.redeem(user, voltRedeemAmount, usdcAmountOut); + vm.stopPrank(); + + (, uint256 expectedBufferDepletion) = allocator.getDripDetails( + address(usdcpsm), + address(morphoUsdcPCVDeposit) + ); + allocator.drip(address(morphoUsdcPCVDeposit)); + uint256 endingExitBuffer = gserl.buffer(); + + assertEq( + startingExitBuffer - endingExitBuffer, + expectedBufferDepletion + ); /// ensure buffer adjusted up 12 decimals, buffer depleted + + assertEq(expectedBufferDepletion, gserl.bufferCap() - gserl.buffer()); + + uint256 endingBuffer = grlm.buffer(); + uint256 endingUsdcBalance = usdc.balanceOf(user); + + assertEq(endingBuffer - startingBuffer, voltRedeemAmount); + assertEq(endingUsdcBalance - startingUsdcBalance, usdcAmountOut); + } +} diff --git a/contracts/test/integration/post-proposal-checks/PostProposalCheck.sol b/contracts/test/integration/post-proposal-checks/PostProposalCheck.sol new file mode 100644 index 000000000..3188709d9 --- /dev/null +++ b/contracts/test/integration/post-proposal-checks/PostProposalCheck.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.13; + +import {Test} from "../../../../forge-std/src/Test.sol"; + +import {Addresses} from "../../proposals/Addresses.sol"; +import {TestProposals} from "../../proposals/TestProposals.sol"; + +contract PostProposalCheck is Test { + Addresses addresses; + uint256 preProposalsSnapshot; + uint256 postProposalsSnapshot; + + function setUp() public virtual { + preProposalsSnapshot = vm.snapshot(); + + // Run all pending proposals before doing e2e tests + TestProposals proposals = new TestProposals(); + proposals.setUp(); + proposals.setDebug(false); + proposals.testProposals(); + addresses = proposals.addresses(); + + postProposalsSnapshot = vm.snapshot(); + } +} diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol index 466710a8e..fad947ef0 100644 --- a/contracts/test/integration/vip/vip14.sol +++ b/contracts/test/integration/vip/vip14.sol @@ -17,7 +17,7 @@ import {ERC20Allocator} from "../../../pcv/utils/ERC20Allocator.sol"; import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; import {CompoundPCVRouter} from "../../../pcv/compound/CompoundPCVRouter.sol"; import {ITimelockSimulation} from "../utils/ITimelockSimulation.sol"; -import {MorphoCompoundPCVDeposit} from "../../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; +import {MorphoPCVDeposit} from "../../../pcv/morpho/MorphoPCVDeposit.sol"; /// Deployment Steps /// 1. deploy morpho dai deposit @@ -54,14 +54,10 @@ contract vip14 is DSTest, IVIP { 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 - ); + MorphoPCVDeposit public daiDeposit = + MorphoPCVDeposit(MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT); + MorphoPCVDeposit public usdcDeposit = + MorphoPCVDeposit(MainnetAddresses.MORPHO_COMPOUND_USDC_PCV_DEPOSIT); PCVGuardian public immutable pcvGuardian = PCVGuardian(MainnetAddresses.PCV_GUARDIAN); diff --git a/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol b/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol index 26a5f85d6..55edf0833 100644 --- a/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol +++ b/contracts/test/invariant/InvariantTestMorphoPCVDeposit.t.sol @@ -15,7 +15,7 @@ import {IPCVOracle} from "../../oracle/IPCVOracle.sol"; import {SystemEntry} from "../../entry/SystemEntry.sol"; import {MockPCVOracle} from "../../mock/MockPCVOracle.sol"; import {DSInvariantTest} from "../unit/utils/DSInvariantTest.sol"; -import {MorphoCompoundPCVDeposit} from "../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; +import {MorphoPCVDeposit} from "../../pcv/morpho/MorphoPCVDeposit.sol"; import {TestAddresses as addresses} from "../unit/utils/TestAddresses.sol"; import {IGlobalReentrancyLock, GlobalReentrancyLock} from "../../core/GlobalReentrancyLock.sol"; @@ -34,7 +34,7 @@ contract InvariantTestMorphoCompoundPCVDeposit is DSTest, DSInvariantTest { MockPCVOracle public pcvOracle; IGlobalReentrancyLock private lock; MorphoPCVDepositTest public morphoTest; - MorphoCompoundPCVDeposit public morphoDeposit; + MorphoPCVDeposit public morphoDeposit; Vm private vm = Vm(HEVM_ADDRESS); @@ -43,10 +43,11 @@ contract InvariantTestMorphoCompoundPCVDeposit is DSTest, DSInvariantTest { token = new MockERC20(); pcvOracle = new MockPCVOracle(); morpho = new MockMorpho(IERC20(address(token))); - morphoDeposit = new MorphoCompoundPCVDeposit( + morphoDeposit = new MorphoPCVDeposit( address(core), address(morpho), address(token), + address(0), /// no need for reward token address(morpho), address(morpho) ); @@ -123,10 +124,10 @@ contract MorphoPCVDepositTest is DSTest { MockMorpho public morpho; SystemEntry public entry; PCVGuardian public pcvGuardian; - MorphoCompoundPCVDeposit public morphoDeposit; + MorphoPCVDeposit public morphoDeposit; constructor( - MorphoCompoundPCVDeposit _morphoDeposit, + MorphoPCVDeposit _morphoDeposit, MockERC20 _token, MockMorpho _morpho, SystemEntry _entry, diff --git a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol index 2902de500..423c614ec 100644 --- a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol @@ -14,7 +14,7 @@ import {PCVGuardian} from "../../../../pcv/PCVGuardian.sol"; import {getCoreV2} from "./../../utils/Fixtures.sol"; import {SystemEntry} from "../../../../entry/SystemEntry.sol"; import {MockERC20, IERC20} from "../../../../mock/MockERC20.sol"; -import {MorphoCompoundPCVDeposit} from "../../../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; +import {MorphoPCVDeposit} from "../../../../pcv/morpho/MorphoPCVDeposit.sol"; import {TestAddresses as addresses} from "../../utils/TestAddresses.sol"; import {MockMorphoMaliciousReentrancy} from "../../../../mock/MockMorphoMaliciousReentrancy.sol"; import {IGlobalReentrancyLock, GlobalReentrancyLock} from "../../../../core/GlobalReentrancyLock.sol"; @@ -36,7 +36,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { SystemEntry public entry; MockMorpho private morpho; PCVGuardian private pcvGuardian; - MorphoCompoundPCVDeposit private morphoDeposit; + MorphoPCVDeposit private morphoDeposit; MockMorphoMaliciousReentrancy private maliciousMorpho; Vm public constant vm = Vm(HEVM_ADDRESS); @@ -59,10 +59,11 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { IERC20(address(token)) ); - morphoDeposit = new MorphoCompoundPCVDeposit( + morphoDeposit = new MorphoPCVDeposit( address(core), address(morpho), address(token), + address(0), address(morpho), address(morpho) ); @@ -103,11 +104,12 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { function testUnderlyingMismatchConstructionFails() public { MockCToken cToken = new MockCToken(address(1)); - vm.expectRevert("MorphoCompoundPCVDeposit: Underlying mismatch"); - new MorphoCompoundPCVDeposit( + vm.expectRevert("MorphoPCVDeposit: Underlying mismatch"); + new MorphoPCVDeposit( address(core), address(cToken), address(token), + address(0), address(morpho), address(morpho) ); @@ -253,8 +255,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { token.mint(address(morphoDeposit), amount); entry.deposit(address(morphoDeposit)); - MorphoCompoundPCVDeposit.Call[] - memory calls = new MorphoCompoundPCVDeposit.Call[](1); + MorphoPCVDeposit.Call[] memory calls = new MorphoPCVDeposit.Call[](1); calls[0].callData = abi.encodeWithSignature( "withdraw(address,uint256)", address(this), @@ -273,8 +274,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { vm.assume(amount != 0); token.mint(address(morphoDeposit), amount); - MorphoCompoundPCVDeposit.Call[] - memory calls = new MorphoCompoundPCVDeposit.Call[](2); + MorphoPCVDeposit.Call[] memory calls = new MorphoPCVDeposit.Call[](2); calls[0].callData = abi.encodeWithSignature( "approve(address,uint256)", address(morphoDeposit.morpho()), @@ -320,8 +320,7 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { //// access controls function testEmergencyActionFailsNonGovernor() public { - MorphoCompoundPCVDeposit.Call[] - memory calls = new MorphoCompoundPCVDeposit.Call[](1); + MorphoPCVDeposit.Call[] memory calls = new MorphoPCVDeposit.Call[](1); calls[0].callData = abi.encodeWithSignature( "withdraw(address,uint256)", address(this), @@ -346,10 +345,11 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { //// reentrancy function _reentrantSetup() private { - morphoDeposit = new MorphoCompoundPCVDeposit( + morphoDeposit = new MorphoPCVDeposit( address(core), address(maliciousMorpho), /// cToken is not used in mock morpho deposit address(token), + address(0), address(maliciousMorpho), address(maliciousMorpho) ); diff --git a/contracts/test/unit/pcv/utils/ERC20Allocator.t.sol b/contracts/test/unit/pcv/utils/ERC20Allocator.t.sol index 520313854..53c053aac 100644 --- a/contracts/test/unit/pcv/utils/ERC20Allocator.t.sol +++ b/contracts/test/unit/pcv/utils/ERC20Allocator.t.sol @@ -258,10 +258,7 @@ contract UnitTestERC20Allocator is DSTest { assertEq(gserl.buffer(), targetBalance / 2); (uint256 amountToDrip, uint256 adjustedAmountToDrip) = allocator - .getDripDetails( - address(newPsm), - PCVDeposit(address(newPcvDeposit)) - ); + .getDripDetails(address(newPsm), address(newPcvDeposit)); allocator.drip(address(newPcvDeposit)); @@ -310,10 +307,7 @@ contract UnitTestERC20Allocator is DSTest { assertEq(gserl.buffer(), targetBalance / 2); (uint256 amountToDrip, uint256 adjustedAmountToDrip) = allocator - .getDripDetails( - address(newPsm), - PCVDeposit(address(newPcvDeposit)) - ); + .getDripDetails(address(newPsm), address(newPcvDeposit)); allocator.drip(address(newPcvDeposit)); @@ -701,17 +695,14 @@ contract UnitTestERC20Allocator is DSTest { ( uint256 psmAmountToDrip, uint256 psmAdjustedAmountToDrip - ) = allocator.getDripDetails( - address(psm), - PCVDeposit(address(pcvDeposit)) - ); + ) = allocator.getDripDetails(address(psm), address(pcvDeposit)); ( uint256 newPsmAmountToDrip, uint256 newPsmAdjustedAmountToDrip ) = allocator.getDripDetails( address(newPsm), - PCVDeposit(address(newPcvDeposit)) + address(newPcvDeposit) ); /// drips are 0 because pcv deposits are not funded @@ -730,17 +721,14 @@ contract UnitTestERC20Allocator is DSTest { ( uint256 psmAmountToDrip, uint256 psmAdjustedAmountToDrip - ) = allocator.getDripDetails( - address(psm), - PCVDeposit(address(pcvDeposit)) - ); + ) = allocator.getDripDetails(address(psm), address(pcvDeposit)); ( uint256 newPsmAmountToDrip, uint256 newPsmAdjustedAmountToDrip ) = allocator.getDripDetails( address(newPsm), - PCVDeposit(address(newPcvDeposit)) + address(newPcvDeposit) ); assertEq(psmAmountToDrip, targetBalance); @@ -881,7 +869,7 @@ contract UnitTestERC20Allocator is DSTest { uint256 bufferStart = gserl.buffer(); (uint256 amountToDrip, uint256 adjustedAmountToDrip) = allocator - .getDripDetails(address(psm), PCVDeposit(address(pcvDeposit))); + .getDripDetails(address(psm), address(pcvDeposit)); /// this has to be true assertTrue(allocator.checkDripCondition(address(pcvDeposit))); From 0edc79a9758d34fd64b627116a8f26ef387bd51d Mon Sep 17 00:00:00 2001 From: Elliot Date: Tue, 17 Jan 2023 18:28:10 -0800 Subject: [PATCH 08/23] Update deployment script with new Morpho PCV Deposit --- contracts/pcv/morpho/MorphoPCVDeposit.sol | 2 +- contracts/pcv/utils/ERC20Allocator.sol | 2 +- .../IntegrationTestProposalPSMOracle.sol | 1 - contracts/test/proposals/vips/vip16.sol | 34 ++++++++++--------- contracts/test/unit/system/System.t.sol | 5 +-- 5 files changed, 23 insertions(+), 21 deletions(-) diff --git a/contracts/pcv/morpho/MorphoPCVDeposit.sol b/contracts/pcv/morpho/MorphoPCVDeposit.sol index 06c45ccac..879ce7f15 100644 --- a/contracts/pcv/morpho/MorphoPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoPCVDeposit.sol @@ -73,8 +73,8 @@ contract MorphoPCVDeposit is PCVDepositV2 { "MorphoPCVDeposit: Underlying mismatch" ); } + cToken = _cToken; - token = _underlying; morpho = _morpho; lens = _lens; } diff --git a/contracts/pcv/utils/ERC20Allocator.sol b/contracts/pcv/utils/ERC20Allocator.sol index 28e305441..3bde151ea 100644 --- a/contracts/pcv/utils/ERC20Allocator.sol +++ b/contracts/pcv/utils/ERC20Allocator.sol @@ -217,7 +217,7 @@ contract ERC20Allocator is IERC20Allocator, CoreRefV2 { (uint256 amountToDrip, uint256 adjustedAmountToDrip) = getDripDetails( psm, - pcvDeposit + address(pcvDeposit) ); /// Effects diff --git a/contracts/test/integration/proposal-checks/IntegrationTestProposalPSMOracle.sol b/contracts/test/integration/proposal-checks/IntegrationTestProposalPSMOracle.sol index 7db83f47f..d189a7413 100644 --- a/contracts/test/integration/proposal-checks/IntegrationTestProposalPSMOracle.sol +++ b/contracts/test/integration/proposal-checks/IntegrationTestProposalPSMOracle.sol @@ -84,7 +84,6 @@ contract IntegrationTestProposalPSMOracle is Test { IERC20 volt = IERC20(addresses.mainnet("VOLT")); IERC20 token = IERC20(addresses.mainnet("USDC")); uint256 amountTokens = 100e6; - uint256 time = block.timestamp; // Read pre-proposal VOLT minted for a known amount of USDC deal(address(token), address(this), amountTokens); diff --git a/contracts/test/proposals/vips/vip16.sol b/contracts/test/proposals/vips/vip16.sol index 1a5529f2f..f74c896a9 100644 --- a/contracts/test/proposals/vips/vip16.sol +++ b/contracts/test/proposals/vips/vip16.sol @@ -28,7 +28,7 @@ import {PegStabilityModule} from "../../../peg/PegStabilityModule.sol"; import {IPegStabilityModule} from "../../../peg/IPegStabilityModule.sol"; import {ConstantPriceOracle} from "../../../oracle/ConstantPriceOracle.sol"; import {IPCVDeposit, PCVDeposit} from "../../../pcv/PCVDeposit.sol"; -import {MorphoCompoundPCVDeposit} from "../../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; +import {MorphoPCVDeposit} from "../../../pcv/morpho/MorphoPCVDeposit.sol"; import {IVoltMigrator, VoltMigrator} from "../../../volt/VoltMigrator.sol"; import {IGlobalReentrancyLock, GlobalReentrancyLock} from "../../../core/GlobalReentrancyLock.sol"; import {IGlobalRateLimitedMinter, GlobalRateLimitedMinter} from "../../../limiter/GlobalRateLimitedMinter.sol"; @@ -161,21 +161,23 @@ contract vip16 is Proposal { /// PCV Deposits { - MorphoCompoundPCVDeposit morphoDaiPCVDeposit = new MorphoCompoundPCVDeposit( - addresses.mainnet("CORE"), - addresses.mainnet("CDAI"), - addresses.mainnet("DAI"), - addresses.mainnet("MORPHO"), - addresses.mainnet("MORPHO_LENS") - ); - - MorphoCompoundPCVDeposit morphoUsdcPCVDeposit = new MorphoCompoundPCVDeposit( - addresses.mainnet("CORE"), - addresses.mainnet("CUSDC"), - addresses.mainnet("USDC"), - addresses.mainnet("MORPHO"), - addresses.mainnet("MORPHO_LENS") - ); + MorphoPCVDeposit morphoDaiPCVDeposit = new MorphoPCVDeposit( + addresses.mainnet("CORE"), + addresses.mainnet("CDAI"), + addresses.mainnet("DAI"), + addresses.mainnet("COMP"), + addresses.mainnet("MORPHO"), + addresses.mainnet("MORPHO_LENS") + ); + + MorphoPCVDeposit morphoUsdcPCVDeposit = new MorphoPCVDeposit( + addresses.mainnet("CORE"), + addresses.mainnet("CUSDC"), + addresses.mainnet("USDC"), + addresses.mainnet("COMP"), + addresses.mainnet("MORPHO"), + addresses.mainnet("MORPHO_LENS") + ); addresses.addMainnet( "PCV_DEPOSIT_MORPHO_DAI", diff --git a/contracts/test/unit/system/System.t.sol b/contracts/test/unit/system/System.t.sol index 3a5df10e7..23607c366 100644 --- a/contracts/test/unit/system/System.t.sol +++ b/contracts/test/unit/system/System.t.sol @@ -22,7 +22,7 @@ import {VoltSystemOracle} from "../../../oracle/VoltSystemOracle.sol"; import {CompoundPCVRouter} from "../../../pcv/compound/CompoundPCVRouter.sol"; import {PegStabilityModule} from "../../../peg/PegStabilityModule.sol"; import {IScalingPriceOracle} from "../../../oracle/IScalingPriceOracle.sol"; -import {MorphoCompoundPCVDeposit} from "../../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; +import {MorphoPCVDeposit} from "../../../pcv/morpho/MorphoPCVDeposit.sol"; import {TestAddresses as addresses} from "../utils/TestAddresses.sol"; import {getCoreV2, getVoltAddresses, VoltAddresses} from "./../utils/Fixtures.sol"; import {IGlobalReentrancyLock, GlobalReentrancyLock} from "../../../core/GlobalReentrancyLock.sol"; @@ -628,10 +628,11 @@ contract SystemUnitTest is Test { bytes4(keccak256("supply(address,address,uint256)")) ); - MorphoCompoundPCVDeposit deposit = new MorphoCompoundPCVDeposit( + MorphoPCVDeposit deposit = new MorphoPCVDeposit( address(core), address(mock), address(usdc), + address(0), address(mock), address(mock) ); From 230221e07572d8fa2e4a8b6115b579e1e0c05872 Mon Sep 17 00:00:00 2001 From: Elliot Date: Tue, 17 Jan 2023 19:28:03 -0800 Subject: [PATCH 09/23] remove system v2 --- contracts/deployment/SystemV2.sol | 390 ----- .../integration/IntegrationTestSystemV2.t.sol | 1390 ----------------- 2 files changed, 1780 deletions(-) delete mode 100644 contracts/deployment/SystemV2.sol delete mode 100644 contracts/test/integration/IntegrationTestSystemV2.t.sol diff --git a/contracts/deployment/SystemV2.sol b/contracts/deployment/SystemV2.sol deleted file mode 100644 index cd4e5f13a..000000000 --- a/contracts/deployment/SystemV2.sol +++ /dev/null @@ -1,390 +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 {Test} from "../../forge-std/src/Test.sol"; -import {IVolt} from "../volt/IVolt.sol"; -import {CoreV2} from "../core/CoreV2.sol"; -import {VoltV2} from "../volt/VoltV2.sol"; -import {VoltRoles} from "../core/VoltRoles.sol"; -import {MockERC20} from "../mock/MockERC20.sol"; -import {PCVRouter} from "../pcv/PCVRouter.sol"; -import {Deviation} from "../utils/Deviation.sol"; -import {PCVOracle} from "../oracle/PCVOracle.sol"; -import {IPCVOracle} from "../oracle/IPCVOracle.sol"; -import {PCVGuardian} from "../pcv/PCVGuardian.sol"; -import {SystemEntry} from "../entry/SystemEntry.sol"; -import {MockCoreRefV2} from "../mock/MockCoreRefV2.sol"; -import {MigratorRouter} from "../pcv/MigratorRouter.sol"; -import {ERC20Allocator} from "../pcv/utils/ERC20Allocator.sol"; -import {NonCustodialPSM} from "../peg/NonCustodialPSM.sol"; -import {MakerPCVSwapper} from "../pcv/maker/MakerPCVSwapper.sol"; -import {VoltSystemOracle} from "../oracle/VoltSystemOracle.sol"; -import {MainnetAddresses} from "../test/integration/fixtures/MainnetAddresses.sol"; -import {PegStabilityModule} from "../peg/PegStabilityModule.sol"; -import {ConstantPriceOracle} from "../oracle/ConstantPriceOracle.sol"; -import {IPCVDeposit, PCVDeposit} from "../pcv/PCVDeposit.sol"; -import {MorphoPCVDeposit} from "../pcv/morpho/MorphoPCVDeposit.sol"; -import {IVoltMigrator, VoltMigrator} from "../volt/VoltMigrator.sol"; -import {IGlobalReentrancyLock, GlobalReentrancyLock} from "../core/GlobalReentrancyLock.sol"; -import {IGlobalRateLimitedMinter, GlobalRateLimitedMinter} from "../limiter/GlobalRateLimitedMinter.sol"; -import {IGlobalSystemExitRateLimiter, GlobalSystemExitRateLimiter} from "../limiter/GlobalSystemExitRateLimiter.sol"; - -contract SystemV2 { - using SafeCast for *; - - /// External - IERC20 public usdc = IERC20(MainnetAddresses.USDC); - IERC20 public dai = IERC20(MainnetAddresses.DAI); - IERC20 public voltV1 = IERC20(0x559eBC30b0E58a45Cc9fF573f77EF1e5eb1b3E18); - - /// Core - VoltV2 public volt; - CoreV2 public core; - TimelockController public timelockController; - GlobalReentrancyLock public lock; - GlobalRateLimitedMinter public grlm; - GlobalSystemExitRateLimiter public gserl; - - /// VOLT rate - VoltSystemOracle public vso; - - /// Volt Migration - VoltMigrator public voltMigrator; - MigratorRouter public migratorRouter; - - /// PCV Deposits - MorphoPCVDeposit public morphoDaiPCVDeposit; - MorphoPCVDeposit public morphoUsdcPCVDeposit; - - /// Peg Stability - PegStabilityModule public daipsm; - PegStabilityModule public usdcpsm; - NonCustodialPSM public usdcNonCustodialPsm; - NonCustodialPSM public daiNonCustodialPsm; - ERC20Allocator public allocator; - - /// PCV Movement - SystemEntry public systemEntry; - MakerPCVSwapper public pcvSwapperMaker; - PCVGuardian public pcvGuardian; - PCVRouter public pcvRouter; - - /// Accounting - PCVOracle public pcvOracle; - ConstantPriceOracle public daiConstantOracle; - ConstantPriceOracle public usdcConstantOracle; - - /// Parameters - uint256 public constant TIMELOCK_DELAY = 86400; - - /// ---------- RATE LIMITED MINTER PARAMS ---------- - - /// maximum rate limit per second is 100 VOLT - uint256 public constant MAX_RATE_LIMIT_PER_SECOND_MINTING = 100e18; - - /// replenish 0 VOLT per day - uint128 public constant RATE_LIMIT_PER_SECOND_MINTING = 0; - - /// buffer cap of 3m VOLT - uint128 public constant BUFFER_CAP_MINTING = 3_000_000e18; - - /// ---------- RATE LIMITED MINTER PARAMS ---------- - - /// maximum rate limit per second is $100 - uint256 public constant MAX_RATE_LIMIT_PER_SECOND_EXITING = 100e18; - - /// replenish 500k VOLT per day ($5.787 dollars per second) - uint128 public constant RATE_LIMIT_PER_SECOND_EXIT = 5787037037037037000; - - /// buffer cap of 1.5m VOLT - uint128 public constant BUFFER_CAP_EXITING = 500_000e18; - - /// ---------- ALLOCATOR PARAMS ---------- - - uint256 public constant ALLOCATOR_MAX_RATE_LIMIT_PER_SECOND = 1_000e18; /// 1k volt per second - uint128 public constant ALLOCATOR_RATE_LIMIT_PER_SECOND = 10e18; /// 10 volt per second - uint128 public constant ALLOCATOR_BUFFER_CAP = 1_000_000e18; /// buffer cap is 1m volt - - /// ---------- PSM PARAMS ---------- - - uint128 public constant VOLT_FLOOR_PRICE_USDC = 1.05e6; - uint128 public constant VOLT_CEILING_PRICE_USDC = 1.10e6; - uint128 public constant VOLT_FLOOR_PRICE_DAI = 1.05e18; - uint128 public constant VOLT_CEILING_PRICE_DAI = 1.10e18; - uint248 public constant USDC_TARGET_BALANCE = 10_000e6; - uint248 public constant DAI_TARGET_BALANCE = 10_000e18; - int8 public constant USDC_DECIMALS_NORMALIZER = 12; - int8 public constant DAI_DECIMALS_NORMALIZER = 0; - - /// ---------- ORACLE PARAMS ---------- - - uint40 public constant VOLT_APR_START_TIME = 1704096000; /// 2024-01-01 TODO change this based on when the system starts - uint200 public constant VOLT_START_PRICE = 1.05e18; /// TODO change this based on when the system starts - uint16 public constant VOLT_MONTHLY_BASIS_POINTS = 14; - - function deploy() public { - /// Core - core = new CoreV2(address(voltV1)); - - volt = new VoltV2(address(core)); - - lock = new GlobalReentrancyLock(address(core)); - - /// all addresses will be able to execute - address[] memory executorAddresses = new address[](0); - - address[] memory proposerCancellerAddresses = new address[](1); - proposerCancellerAddresses[0] = MainnetAddresses.GOVERNOR; - timelockController = new TimelockController( - TIMELOCK_DELAY, - proposerCancellerAddresses, - executorAddresses - ); - - grlm = new GlobalRateLimitedMinter( - address(core), - MAX_RATE_LIMIT_PER_SECOND_MINTING, - RATE_LIMIT_PER_SECOND_MINTING, - BUFFER_CAP_MINTING - ); - gserl = new GlobalSystemExitRateLimiter( - address(core), - MAX_RATE_LIMIT_PER_SECOND_EXITING, - RATE_LIMIT_PER_SECOND_EXIT, - BUFFER_CAP_EXITING - ); - - /// VOLT rate - vso = new VoltSystemOracle( - address(core), - VOLT_MONTHLY_BASIS_POINTS, - VOLT_APR_START_TIME, /// todo fill in actual value - VOLT_START_PRICE /// todo fetch this from the old oracle after warping forward 24 hours - ); - - /// PCV Deposits - morphoDaiPCVDeposit = new MorphoPCVDeposit( - address(core), - MainnetAddresses.CDAI, - address(dai), - MainnetAddresses.COMP, - MainnetAddresses.MORPHO, - MainnetAddresses.MORPHO_LENS - ); - - morphoUsdcPCVDeposit = new MorphoPCVDeposit( - address(core), - MainnetAddresses.CUSDC, - address(usdc), - MainnetAddresses.COMP, - MainnetAddresses.MORPHO, - MainnetAddresses.MORPHO_LENS - ); - - /// Peg Stability - daipsm = new PegStabilityModule( - address(core), - address(vso), - address(0), - 0, - false, - dai, - VOLT_FLOOR_PRICE_DAI, - VOLT_CEILING_PRICE_DAI - ); - usdcpsm = new PegStabilityModule( - address(core), - address(vso), - address(0), - -12, - false, - usdc, - VOLT_FLOOR_PRICE_USDC, - VOLT_CEILING_PRICE_USDC - ); - usdcNonCustodialPsm = new NonCustodialPSM( - address(core), - address(vso), - address(0), - -12, - false, - usdc, - VOLT_FLOOR_PRICE_USDC, - VOLT_CEILING_PRICE_USDC, - IPCVDeposit(address(morphoUsdcPCVDeposit)) - ); - daiNonCustodialPsm = new NonCustodialPSM( - address(core), - address(vso), - address(0), - 0, - false, - dai, - VOLT_FLOOR_PRICE_DAI, - VOLT_CEILING_PRICE_DAI, - IPCVDeposit(address(morphoDaiPCVDeposit)) - ); - allocator = new ERC20Allocator(address(core)); - - voltMigrator = new VoltMigrator(address(core), IVolt(address(volt))); - - migratorRouter = new MigratorRouter( - IVolt(address(volt)), - IVoltMigrator(address(voltMigrator)), - daipsm, - usdcpsm - ); - - /// PCV Movement - systemEntry = new SystemEntry(address(core)); - - pcvSwapperMaker = new MakerPCVSwapper(address(core)); - - address[] memory pcvGuardianSafeAddresses = new address[](4); - pcvGuardianSafeAddresses[0] = address(morphoDaiPCVDeposit); - pcvGuardianSafeAddresses[1] = address(morphoUsdcPCVDeposit); - pcvGuardianSafeAddresses[2] = address(usdcpsm); - pcvGuardianSafeAddresses[3] = address(daipsm); - - pcvGuardian = new PCVGuardian( - address(core), - address(timelockController), - pcvGuardianSafeAddresses - ); - - pcvRouter = new PCVRouter(address(core)); - - /// Accounting - pcvOracle = new PCVOracle(address(core)); - daiConstantOracle = new ConstantPriceOracle(address(core), 1e18); - usdcConstantOracle = new ConstantPriceOracle( - address(core), - 1e18 * 10 ** uint256(uint8(USDC_DECIMALS_NORMALIZER)) - ); - } - - function setUp(address deployer) public { - /// Set references in Core - core.setVolt(IVolt(address(volt))); - core.setGlobalRateLimitedMinter( - IGlobalRateLimitedMinter(address(grlm)) - ); - core.setGlobalSystemExitRateLimiter( - IGlobalSystemExitRateLimiter(address(gserl)) - ); - core.setPCVOracle(IPCVOracle(address(pcvOracle))); - core.setGlobalReentrancyLock(IGlobalReentrancyLock(address(lock))); - /// Grant Roles - core.grantGovernor(address(timelockController)); - core.grantGovernor(MainnetAddresses.GOVERNOR); /// team multisig - - core.grantPCVController(address(allocator)); - core.grantPCVController(address(pcvGuardian)); - core.grantPCVController(address(pcvRouter)); - core.grantPCVController(MainnetAddresses.GOVERNOR); /// team multisig - core.grantPCVController(address(daiNonCustodialPsm)); - core.grantPCVController(address(usdcNonCustodialPsm)); - - core.createRole(VoltRoles.PCV_MOVER, VoltRoles.GOVERNOR); - core.grantRole(VoltRoles.PCV_MOVER, MainnetAddresses.GOVERNOR); /// team multisig - - core.createRole(VoltRoles.LIQUID_PCV_DEPOSIT, VoltRoles.GOVERNOR); - core.createRole(VoltRoles.ILLIQUID_PCV_DEPOSIT, VoltRoles.GOVERNOR); - core.grantRole( - VoltRoles.LIQUID_PCV_DEPOSIT, - address(morphoDaiPCVDeposit) - ); - core.grantRole( - VoltRoles.LIQUID_PCV_DEPOSIT, - address(morphoUsdcPCVDeposit) - ); - - core.grantPCVGuard(MainnetAddresses.EOA_1); - core.grantPCVGuard(MainnetAddresses.EOA_2); - core.grantPCVGuard(MainnetAddresses.EOA_4); - - core.grantGuardian(address(pcvGuardian)); - - core.grantRateLimitedMinter(address(daipsm)); - core.grantRateLimitedMinter(address(usdcpsm)); - - core.grantRateLimitedRedeemer(address(daipsm)); - core.grantRateLimitedRedeemer(address(usdcpsm)); - core.grantRateLimitedRedeemer(address(daiNonCustodialPsm)); - core.grantRateLimitedRedeemer(address(usdcNonCustodialPsm)); - - core.grantSystemExitRateLimitDepleter(address(daiNonCustodialPsm)); - core.grantSystemExitRateLimitDepleter(address(usdcNonCustodialPsm)); - - core.grantSystemExitRateLimitReplenisher(address(allocator)); - - core.grantLocker(address(systemEntry)); - core.grantLocker(address(allocator)); - core.grantLocker(address(pcvOracle)); - core.grantLocker(address(daipsm)); - core.grantLocker(address(usdcpsm)); - core.grantLocker(address(morphoDaiPCVDeposit)); - core.grantLocker(address(morphoUsdcPCVDeposit)); - core.grantLocker(address(grlm)); - core.grantLocker(address(gserl)); - core.grantLocker(address(pcvRouter)); - core.grantLocker(address(pcvGuardian)); - core.grantLocker(address(daiNonCustodialPsm)); - core.grantLocker(address(usdcNonCustodialPsm)); - - core.grantMinter(address(grlm)); - - /// Allocator config - allocator.connectPSM( - address(usdcpsm), - USDC_TARGET_BALANCE, - USDC_DECIMALS_NORMALIZER - ); - allocator.connectPSM( - address(daipsm), - DAI_TARGET_BALANCE, - DAI_DECIMALS_NORMALIZER - ); - allocator.connectDeposit( - address(usdcpsm), - address(morphoUsdcPCVDeposit) - ); - allocator.connectDeposit(address(daipsm), address(morphoDaiPCVDeposit)); - - /// Configure PCV Oracle - address[] memory venues = new address[](2); - venues[0] = address(morphoDaiPCVDeposit); - venues[1] = address(morphoUsdcPCVDeposit); - - address[] memory oracles = new address[](2); - oracles[0] = address(daiConstantOracle); - oracles[1] = address(usdcConstantOracle); - - bool[] memory isLiquid = new bool[](2); - isLiquid[0] = true; - isLiquid[1] = true; - - pcvOracle.addVenues(venues, oracles, isLiquid); - - /// Configure PCV Router - address[] memory swappers = new address[](1); - swappers[0] = address(pcvSwapperMaker); - pcvRouter.addPCVSwappers(swappers); - - /// Allow all addresses to execute proposals once completed - timelockController.grantRole( - timelockController.EXECUTOR_ROLE(), - address(0) - ); - /// Cleanup - timelockController.renounceRole( - timelockController.TIMELOCK_ADMIN_ROLE(), - deployer - ); - core.revokeGovernor(deployer); - } -} diff --git a/contracts/test/integration/IntegrationTestSystemV2.t.sol b/contracts/test/integration/IntegrationTestSystemV2.t.sol deleted file mode 100644 index ee1bd6137..000000000 --- a/contracts/test/integration/IntegrationTestSystemV2.t.sol +++ /dev/null @@ -1,1390 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.13; - -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; - -import {Vm} from "../unit/utils/Vm.sol"; -import {Test} from "../../../forge-std/src/Test.sol"; - -// import everything from SystemV2 -import "../../deployment/SystemV2.sol"; -import {stdError} from "../unit/utils/StdLib.sol"; -import {VoltRoles} from "../../core/VoltRoles.sol"; -import {VoltMigrator} from "../../volt/VoltMigrator.sol"; -import {MigratorRouter} from "../../pcv/MigratorRouter.sol"; -import {TestAddresses as addresses} from "../unit/utils/TestAddresses.sol"; - -contract IntegrationTestSystemV2 is Test { - using SafeCast for *; - SystemV2 systemV2; - address public constant user = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7; - uint224 public constant mintAmount = 100_000_000e18; - - CoreV2 core; - IERC20 dai; - IERC20 usdc; - IVolt oldVolt = IVolt(MainnetAddresses.VOLT); - VoltV2 volt; - NonCustodialPSM daincpsm; - PegStabilityModule daipsm; - NonCustodialPSM usdcncpsm; - PegStabilityModule usdcpsm; - GlobalRateLimitedMinter grlm; - GlobalSystemExitRateLimiter gserl; - ERC20Allocator allocator; - PCVOracle pcvOracle; - TimelockController timelockController; - PCVGuardian pcvGuardian; - MorphoPCVDeposit morphoDaiPCVDeposit; - MorphoPCVDeposit morphoUsdcPCVDeposit; - PCVRouter pcvRouter; - SystemEntry systemEntry; - VoltMigrator voltMigrator; - MigratorRouter migratorRouter; - - function setUp() public { - systemV2 = new SystemV2(); - systemV2.deploy(); - systemV2.setUp(address(systemV2)); - core = systemV2.core(); - dai = systemV2.dai(); - usdc = systemV2.usdc(); - volt = systemV2.volt(); - grlm = systemV2.grlm(); - gserl = systemV2.gserl(); - daipsm = systemV2.daipsm(); - daincpsm = systemV2.daiNonCustodialPsm(); - usdcncpsm = systemV2.usdcNonCustodialPsm(); - usdcpsm = systemV2.usdcpsm(); - allocator = systemV2.allocator(); - pcvOracle = systemV2.pcvOracle(); - timelockController = systemV2.timelockController(); - pcvGuardian = systemV2.pcvGuardian(); - morphoDaiPCVDeposit = systemV2.morphoDaiPCVDeposit(); - morphoUsdcPCVDeposit = systemV2.morphoUsdcPCVDeposit(); - pcvRouter = systemV2.pcvRouter(); - systemEntry = systemV2.systemEntry(); - voltMigrator = systemV2.voltMigrator(); - migratorRouter = systemV2.migratorRouter(); - - uint256 BUFFER_CAP_MINTING = systemV2.BUFFER_CAP_MINTING(); - uint256 amount = BUFFER_CAP_MINTING / 2; - - deal(address(dai), user, amount); - vm.label(address(dai), "dai"); - vm.label(address(usdc), "usdc"); - vm.label(address(volt), "new volt"); - vm.label(address(daipsm), "daipsm"); - vm.label(address(usdcpsm), "usdcpsm"); - vm.label(address(oldVolt), "old volt"); - vm.label(address(this), "address this"); - vm.label(address(voltMigrator), "Volt Migrator"); - vm.label(address(migratorRouter), "Migrator Router"); - } - - /* - Validate that the smart contracts are correctly linked to each other. - */ - function testLinks() public { - // core references - assertEq(address(core.volt()), address(systemV2.volt())); - assertEq(address(core.vcon()), address(0)); - assertEq( - address(core.globalRateLimitedMinter()), - address(systemV2.grlm()) - ); - assertEq( - address(core.globalSystemExitRateLimiter()), - address(systemV2.gserl()) - ); - assertEq(address(core.pcvOracle()), address(systemV2.pcvOracle())); - - // psm allocator - assertEq( - allocator.pcvDepositToPSM(address(systemV2.morphoUsdcPCVDeposit())), - address(systemV2.usdcpsm()) - ); - assertEq( - allocator.pcvDepositToPSM(address(systemV2.morphoDaiPCVDeposit())), - address(systemV2.daipsm()) - ); - (address psmToken1, , ) = allocator.allPSMs(address(systemV2.daipsm())); - (address psmToken2, , ) = allocator.allPSMs( - address(systemV2.usdcpsm()) - ); - assertEq(psmToken1, address(systemV2.dai())); - assertEq(psmToken2, address(systemV2.usdc())); - - // pcv oracle - assertEq(pcvOracle.getAllVenues().length, 2); - assertEq( - pcvOracle.getAllVenues()[0], - address(systemV2.morphoDaiPCVDeposit()) - ); - assertEq( - pcvOracle.getAllVenues()[1], - address(systemV2.morphoUsdcPCVDeposit()) - ); - - // pcv router - assertTrue(pcvRouter.isPCVSwapper(address(systemV2.pcvSwapperMaker()))); - } - - /* - Test that the roles are properly configured in the new system and that no - additional roles are granted to unexpected addresses. - */ - function testRoles() public { - // GOVERNOR - assertEq(core.getRoleMemberCount(VoltRoles.GOVERNOR), 3); - assertEq(core.getRoleMember(VoltRoles.GOVERNOR, 0), address(core)); - assertEq( - core.getRoleMember(VoltRoles.GOVERNOR, 1), - MainnetAddresses.GOVERNOR - ); - assertEq( - core.getRoleMember(VoltRoles.GOVERNOR, 2), - address(systemV2.timelockController()) - ); - - // PCV_CONTROLLER - assertEq(core.getRoleMemberCount(VoltRoles.PCV_CONTROLLER), 6); - assertEq( - core.getRoleMember(VoltRoles.PCV_CONTROLLER, 0), - address(systemV2.allocator()) - ); - assertEq( - core.getRoleMember(VoltRoles.PCV_CONTROLLER, 1), - address(systemV2.pcvGuardian()) - ); - assertEq( - core.getRoleMember(VoltRoles.PCV_CONTROLLER, 2), - address(systemV2.pcvRouter()) - ); - assertEq( - core.getRoleMember(VoltRoles.PCV_CONTROLLER, 3), - MainnetAddresses.GOVERNOR - ); - assertEq( - core.getRoleMember(VoltRoles.PCV_CONTROLLER, 4), - address(systemV2.daiNonCustodialPsm()) - ); - assertEq( - core.getRoleMember(VoltRoles.PCV_CONTROLLER, 5), - address(systemV2.usdcNonCustodialPsm()) - ); - - // PCV_MOVER - assertEq(core.getRoleMemberCount(VoltRoles.PCV_MOVER), 1); - assertEq( - core.getRoleMember(VoltRoles.PCV_MOVER, 0), - MainnetAddresses.GOVERNOR - ); - - // LIQUID_PCV_DEPOSIT_ROLE - assertEq(core.getRoleMemberCount(VoltRoles.LIQUID_PCV_DEPOSIT), 2); - assertEq( - core.getRoleMember(VoltRoles.LIQUID_PCV_DEPOSIT, 0), - address(systemV2.morphoDaiPCVDeposit()) - ); - assertEq( - core.getRoleMember(VoltRoles.LIQUID_PCV_DEPOSIT, 1), - address(systemV2.morphoUsdcPCVDeposit()) - ); - - // ILLIQUID_PCV_DEPOSIT_ROLE - assertEq(core.getRoleMemberCount(VoltRoles.ILLIQUID_PCV_DEPOSIT), 0); - - // PCV_GUARD - assertEq(core.getRoleMemberCount(VoltRoles.PCV_GUARD), 3); - assertEq( - core.getRoleMember(VoltRoles.PCV_GUARD, 0), - MainnetAddresses.EOA_1 - ); - assertEq( - core.getRoleMember(VoltRoles.PCV_GUARD, 1), - MainnetAddresses.EOA_2 - ); - assertEq( - core.getRoleMember(VoltRoles.PCV_GUARD, 2), - MainnetAddresses.EOA_4 - ); - - // GUARDIAN - assertEq(core.getRoleMemberCount(VoltRoles.GUARDIAN), 1); - assertEq( - core.getRoleMember(VoltRoles.GUARDIAN, 0), - address(systemV2.pcvGuardian()) - ); - - // RATE_LIMIT_SYSTEM_ENTRY_DEPLETE_ROLE - assertEq( - core.getRoleMemberCount(VoltRoles.RATE_LIMIT_SYSTEM_ENTRY_DEPLETE), - 2 - ); - assertEq( - core.getRoleMember(VoltRoles.RATE_LIMIT_SYSTEM_ENTRY_DEPLETE, 0), - address(systemV2.daipsm()) - ); - assertEq( - core.getRoleMember(VoltRoles.RATE_LIMIT_SYSTEM_ENTRY_DEPLETE, 1), - address(systemV2.usdcpsm()) - ); - - // RATE_LIMIT_SYSTEM_ENTRY_REPLENISH_ROLE - assertEq( - core.getRoleMemberCount( - VoltRoles.RATE_LIMIT_SYSTEM_ENTRY_REPLENISH - ), - 4 - ); - assertEq( - core.getRoleMember(VoltRoles.RATE_LIMIT_SYSTEM_ENTRY_REPLENISH, 0), - address(systemV2.daipsm()) - ); - assertEq( - core.getRoleMember(VoltRoles.RATE_LIMIT_SYSTEM_ENTRY_REPLENISH, 1), - address(systemV2.usdcpsm()) - ); - assertEq( - core.getRoleMember(VoltRoles.RATE_LIMIT_SYSTEM_ENTRY_REPLENISH, 2), - address(systemV2.daiNonCustodialPsm()) - ); - assertEq( - core.getRoleMember(VoltRoles.RATE_LIMIT_SYSTEM_ENTRY_REPLENISH, 3), - address(systemV2.usdcNonCustodialPsm()) - ); - - // LOCKER_ROLE - assertEq(core.getRoleMemberCount(VoltRoles.LOCKER), 13); - assertEq( - core.getRoleMember(VoltRoles.LOCKER, 0), - address(systemV2.systemEntry()) - ); - assertEq( - core.getRoleMember(VoltRoles.LOCKER, 1), - address(systemV2.allocator()) - ); - assertEq( - core.getRoleMember(VoltRoles.LOCKER, 2), - address(systemV2.pcvOracle()) - ); - assertEq( - core.getRoleMember(VoltRoles.LOCKER, 3), - address(systemV2.daipsm()) - ); - assertEq( - core.getRoleMember(VoltRoles.LOCKER, 4), - address(systemV2.usdcpsm()) - ); - assertEq( - core.getRoleMember(VoltRoles.LOCKER, 5), - address(systemV2.morphoDaiPCVDeposit()) - ); - assertEq( - core.getRoleMember(VoltRoles.LOCKER, 6), - address(systemV2.morphoUsdcPCVDeposit()) - ); - assertEq( - core.getRoleMember(VoltRoles.LOCKER, 7), - address(systemV2.grlm()) - ); - assertEq( - core.getRoleMember(VoltRoles.LOCKER, 8), - address(systemV2.gserl()) - ); - assertEq( - core.getRoleMember(VoltRoles.LOCKER, 9), - address(systemV2.pcvRouter()) - ); - assertEq( - core.getRoleMember(VoltRoles.LOCKER, 10), - address(systemV2.pcvGuardian()) - ); - assertEq(core.getRoleMember(VoltRoles.LOCKER, 11), address(daincpsm)); - assertEq(core.getRoleMember(VoltRoles.LOCKER, 12), address(usdcncpsm)); - - // MINTER - assertEq(core.getRoleMemberCount(VoltRoles.MINTER), 1); - assertEq( - core.getRoleMember(VoltRoles.MINTER, 0), - address(systemV2.grlm()) - ); - - /// SYSTEM EXIT RATE LIMIT DEPLETER - assertEq( - core.getRoleMemberCount(VoltRoles.RATE_LIMIT_SYSTEM_EXIT_DEPLETE), - 2 - ); - assertEq( - core.getRoleMember(VoltRoles.RATE_LIMIT_SYSTEM_EXIT_DEPLETE, 0), - address(systemV2.daiNonCustodialPsm()) - ); - assertEq( - core.getRoleMember(VoltRoles.RATE_LIMIT_SYSTEM_EXIT_DEPLETE, 1), - address(systemV2.usdcNonCustodialPsm()) - ); - - /// SYSTEM EXIT RATE LIMIT REPLENISH - assertEq( - core.getRoleMemberCount(VoltRoles.RATE_LIMIT_SYSTEM_EXIT_DEPLETE), - 2 - ); - assertEq( - core.getRoleMember(VoltRoles.RATE_LIMIT_SYSTEM_EXIT_REPLENISH, 0), - address(systemV2.allocator()) - ); - } - - function testTimelockRoles() public { - bytes32 executor = timelockController.EXECUTOR_ROLE(); - bytes32 canceller = timelockController.CANCELLER_ROLE(); - bytes32 proposer = timelockController.PROPOSER_ROLE(); - - assertTrue(timelockController.hasRole(executor, address(0))); /// role open - assertTrue( - timelockController.hasRole(canceller, MainnetAddresses.GOVERNOR) - ); - assertTrue( - timelockController.hasRole(proposer, MainnetAddresses.GOVERNOR) - ); - assertTrue(!timelockController.hasRole(canceller, address(0))); /// role closed - assertTrue(!timelockController.hasRole(proposer, address(0))); /// role closed - } - - function testMultisigProposesTimelock() public { - uint256 ethSendAmount = 100 ether; - vm.deal(address(timelockController), ethSendAmount); - - assertEq(address(timelockController).balance, ethSendAmount); /// starts with 0 balance - - bytes memory data = ""; - bytes32 predecessor = bytes32(0); - bytes32 salt = bytes32(0); - address recipient = address(100); - - vm.prank(MainnetAddresses.GOVERNOR); - timelockController.schedule( - recipient, - ethSendAmount, - data, - predecessor, - salt, - 86400 - ); - bytes32 id = timelockController.hashOperation( - recipient, - ethSendAmount, - data, - predecessor, - salt - ); - - uint256 startingEthBalance = recipient.balance; - - assertTrue(!timelockController.isOperationDone(id)); /// operation is not done - assertTrue(!timelockController.isOperationReady(id)); /// operation is not ready - - vm.warp(block.timestamp + timelockController.getMinDelay()); - assertTrue(timelockController.isOperationReady(id)); /// operation is ready - - timelockController.execute( - recipient, - ethSendAmount, - data, - predecessor, - salt - ); - - assertTrue(timelockController.isOperationDone(id)); /// operation is done - - assertEq(address(timelockController).balance, 0); - assertEq(recipient.balance, ethSendAmount + startingEthBalance); /// assert receiver received their eth - } - - function testOraclePriceAndReference() public { - address oracleAddress = address(systemV2.vso()); - - assertEq(address(systemV2.daipsm().oracle()), oracleAddress); - assertEq(address(systemV2.usdcpsm().oracle()), oracleAddress); - - assertEq( - systemV2.vso().getCurrentOraclePrice(), - systemV2.VOLT_START_PRICE() - ); - } - - function testCoreReferences() public { - address coreAddress = address(core); - - assertEq(address(systemV2.daipsm().core()), coreAddress); - assertEq(address(systemV2.usdcpsm().core()), coreAddress); - assertEq(address(systemV2.volt().core()), coreAddress); - assertEq(address(systemV2.grlm().core()), coreAddress); - assertEq(address(systemV2.gserl().core()), coreAddress); - assertEq(address(systemV2.vso().core()), coreAddress); - assertEq(address(systemV2.morphoDaiPCVDeposit().core()), coreAddress); - assertEq(address(systemV2.morphoUsdcPCVDeposit().core()), coreAddress); - assertEq(address(systemV2.usdcNonCustodialPsm().core()), coreAddress); - assertEq(address(systemV2.daiNonCustodialPsm().core()), coreAddress); - assertEq(address(systemV2.allocator().core()), coreAddress); - assertEq(address(systemV2.systemEntry().core()), coreAddress); - assertEq(address(systemV2.pcvSwapperMaker().core()), coreAddress); - assertEq(address(systemV2.pcvGuardian().core()), coreAddress); - assertEq(address(systemV2.pcvRouter().core()), coreAddress); - assertEq(address(systemV2.pcvOracle().core()), coreAddress); - assertEq(address(systemV2.daiConstantOracle().core()), coreAddress); - assertEq(address(systemV2.usdcConstantOracle().core()), coreAddress); - } - - /* - Flow of the first user that mints VOLT in the new system. - Performs checks on the global rate limits, and accounting - in the new system's PCV Oracle. - */ - function testFirstUserMint() public { - // setup variables - uint256 BUFFER_CAP_MINTING = systemV2.BUFFER_CAP_MINTING(); - uint256 amount = BUFFER_CAP_MINTING / 2; - uint256 daiTargetBalance = systemV2.DAI_TARGET_BALANCE(); - uint256 expectDaiBalance = amount - daiTargetBalance; - - // at system deloy, buffer is full - assertEq(grlm.buffer(), BUFFER_CAP_MINTING); - - { - // at system deploy, pcv is 0 - ( - uint256 liquidPcv1, - uint256 illiquidPcv1, - uint256 totalPcv1 - ) = pcvOracle.getTotalPcv(); - assertEq(liquidPcv1, 0); - assertEq(illiquidPcv1, 0); - assertEq(totalPcv1, 0); - } - - // user performs the first mint - vm.startPrank(user); - dai.approve(address(daipsm), amount); - daipsm.mint(user, amount, 0); - vm.stopPrank(); - - // buffer has been used - uint256 voltReceived1 = volt.balanceOf(user); - assertEq(grlm.buffer(), BUFFER_CAP_MINTING - voltReceived1); - - // user received VOLT - assertTrue(voltReceived1 > (90 * amount) / 100); - // psm received DAI - assertEq(daipsm.balance(), amount); - - allocator.skim(address(morphoDaiPCVDeposit)); - { - // after first mint, pcv is = amount - ( - uint256 liquidPcv2, - uint256 illiquidPcv2, - uint256 totalPcv2 - ) = pcvOracle.getTotalPcv(); - assertApproxEq( - liquidPcv2.toInt256(), - expectDaiBalance.toInt256(), - 0 - ); - assertApproxEq( - totalPcv2.toInt256(), - expectDaiBalance.toInt256(), - 0 - ); - assertEq(illiquidPcv2, 0); - } - - // user performs the second mint - vm.startPrank(user); - usdc.approve(address(usdcpsm), amount / 1e12); - usdcpsm.mint(user, amount / 1e12, 0); - vm.stopPrank(); - uint256 voltReceived2 = volt.balanceOf(user) - voltReceived1; - - // buffer has been used - assertEq( - grlm.buffer(), - systemV2.BUFFER_CAP_MINTING() - voltReceived1 - voltReceived2 - ); - - // user received VOLT - assertTrue(voltReceived2 > (90 * amount) / 100); - // psm received USDC - assertEq(usdcpsm.balance(), amount / 1e12); - - allocator.skim(address(morphoUsdcPCVDeposit)); - { - // after second mint, pcv is = 2 * amount - ( - uint256 liquidPcv3, - uint256 illiquidPcv3, - uint256 totalPcv3 - ) = pcvOracle.getTotalPcv(); - assertEq(illiquidPcv3, 0); - assertApproxEq( - totalPcv3.toInt256(), - 2 * expectDaiBalance.toInt256(), - 0 - ); - assertApproxEq( - liquidPcv3.toInt256(), - 2 * expectDaiBalance.toInt256(), - 0 - ); - } - vm.snapshot(); - - vm.prank(address(core)); - grlm.setRateLimitPerSecond(5.787e18); - - // buffer replenishes over time - vm.warp(block.timestamp + 3 days); - - // above limit rate reverts - vm.startPrank(user); - dai.approve(address(daipsm), BUFFER_CAP_MINTING * 2); - vm.expectRevert("RateLimited: rate limit hit"); - daipsm.mint(user, BUFFER_CAP_MINTING * 2, 0); - vm.stopPrank(); - } - - function testRedeemsDaiPsm(uint88 voltAmount) public { - testFirstUserMint(); - - uint256 snapshotId = 0; /// roll back to before buffer replenished - vm.revertTo(snapshotId); - vm.assume(voltAmount <= volt.balanceOf(user)); - - { - uint256 daiAmountOut = daipsm.getRedeemAmountOut(voltAmount); - deal(address(dai), address(daipsm), daiAmountOut); - - vm.startPrank(user); - - uint256 startingBuffer = grlm.buffer(); - uint256 startingDaiBalance = dai.balanceOf(user); - - volt.approve(address(daipsm), voltAmount); - daipsm.redeem(user, voltAmount, daiAmountOut); - - uint256 endingBuffer = grlm.buffer(); - uint256 endingDaiBalance = dai.balanceOf(user); - - assertEq(endingDaiBalance - startingDaiBalance, daiAmountOut); - assertEq(endingBuffer - startingBuffer, voltAmount); - - vm.stopPrank(); - } - } - - function testRedeemsDaiNcPsm(uint80 voltRedeemAmount) public { - vm.assume(voltRedeemAmount >= 1e18); - vm.assume(voltRedeemAmount <= 475_000e18); - testFirstUserMint(); - - uint256 snapshotId = 0; /// roll back to before buffer replenished - vm.revertTo(snapshotId); - - { - uint256 daiAmountOut = daincpsm.getRedeemAmountOut( - voltRedeemAmount - ); - deal(address(dai), address(morphoDaiPCVDeposit), daiAmountOut * 2); - systemEntry.deposit(address(morphoDaiPCVDeposit)); - - vm.startPrank(user); - - uint256 startingBuffer = grlm.buffer(); - uint256 startingExitBuffer = gserl.buffer(); - uint256 startingDaiBalance = dai.balanceOf(user); - - volt.approve(address(daincpsm), voltRedeemAmount); - daincpsm.redeem(user, voltRedeemAmount, daiAmountOut); - - uint256 endingBuffer = grlm.buffer(); - uint256 endingExitBuffer = gserl.buffer(); - uint256 endingDaiBalance = dai.balanceOf(user); - - assertEq(endingBuffer - startingBuffer, voltRedeemAmount); /// grlm buffer replenished - assertEq(endingDaiBalance - startingDaiBalance, daiAmountOut); - assertEq(startingExitBuffer - endingExitBuffer, daiAmountOut); /// exit buffer depleted - - vm.stopPrank(); - } - } - - function testRedeemsUsdcNcPsm(uint80 voltRedeemAmount) public { - vm.assume(voltRedeemAmount >= 1e18); - vm.assume(voltRedeemAmount <= 475_000e18); - testFirstUserMint(); - - uint256 snapshotId = 0; /// roll back to before buffer replenished - vm.revertTo(snapshotId); - - { - uint256 usdcAmountOut = usdcncpsm.getRedeemAmountOut( - voltRedeemAmount - ); - deal( - address(usdc), - address(morphoUsdcPCVDeposit), - usdcAmountOut * 2 - ); - systemEntry.deposit(address(morphoUsdcPCVDeposit)); - - vm.startPrank(user); - - uint256 startingBuffer = grlm.buffer(); - uint256 startingExitBuffer = gserl.buffer(); - uint256 startingUsdcBalance = usdc.balanceOf(user); - - volt.approve(address(usdcncpsm), voltRedeemAmount); - usdcncpsm.redeem(user, voltRedeemAmount, usdcAmountOut); - - uint256 endingBuffer = grlm.buffer(); - uint256 endingExitBuffer = gserl.buffer(); - uint256 endingUsdcBalance = usdc.balanceOf(user); - - assertEq(endingBuffer - startingBuffer, voltRedeemAmount); /// buffer replenished - assertEq(endingUsdcBalance - startingUsdcBalance, usdcAmountOut); - assertEq( - startingExitBuffer - endingExitBuffer, - usdcAmountOut * 1e12 - ); /// ensure buffer adjusted up 12 decimals, buffer depleted - - vm.stopPrank(); - } - } - - function testRedeemsUsdcPsm(uint80 voltRedeemAmount) public { - vm.assume(voltRedeemAmount >= 1e18); - vm.assume(voltRedeemAmount <= 475_000e18); - testFirstUserMint(); - - uint256 snapshotId = 0; /// roll back to before buffer replenished - vm.revertTo(snapshotId); - - { - uint256 usdcAmountOut = usdcpsm.getRedeemAmountOut( - voltRedeemAmount - ); - deal(address(usdc), address(usdcpsm), usdcAmountOut); - - vm.startPrank(user); - - uint256 startingBuffer = grlm.buffer(); - uint256 startingUsdcBalance = usdc.balanceOf(user); - - volt.approve(address(usdcpsm), voltRedeemAmount); - usdcpsm.redeem(user, voltRedeemAmount, usdcAmountOut); - - uint256 endingBuffer = grlm.buffer(); - uint256 endingUsdcBalance = usdc.balanceOf(user); - - assertEq(endingBuffer - startingBuffer, voltRedeemAmount); - assertEq(endingUsdcBalance - startingUsdcBalance, usdcAmountOut); - - vm.stopPrank(); - } - } - - /* - Migrate PCV from current system to V2 system, and perform sanity checks on amounts. - */ - function testMigratePcv() public { - uint256 migratedPcv = _migratePcv(); - - // get PCV stats - (uint256 liquidPcv, uint256 illiquidPcv, uint256 totalPcv) = systemV2 - .pcvOracle() - .getTotalPcv(); - - // sanity check - assertEq(liquidPcv, totalPcv); - assertEq(illiquidPcv, 0); - assertGt(totalPcv, 1_500_000e18); - assertGt(migratedPcv, 1_500_000e18); - } - - /* - After migrating to V2 system, check that we can use PCVGuardian. - */ - function testPcvGuardian() public { - _migratePcv(); - uint256 amount = 100_000e18; - - uint256 depositDaiBalanceBefore = morphoDaiPCVDeposit.balance(); - uint256 safeAddressDaiBalanceBefore = dai.balanceOf( - address(timelockController) - ); - - vm.prank(MainnetAddresses.GOVERNOR); - pcvGuardian.withdrawToSafeAddress(address(morphoDaiPCVDeposit), amount); - - uint256 depositDaiBalanceAfter = morphoDaiPCVDeposit.balance(); - uint256 safeAddressDaiBalanceAfter = dai.balanceOf( - address(timelockController) - ); - - // tolerate 0.5% err because morpho withdrawals are not exact - assertGt( - safeAddressDaiBalanceAfter - safeAddressDaiBalanceBefore, - (995 * amount) / 1000 - ); - assertGt( - depositDaiBalanceBefore - depositDaiBalanceAfter, - (995 * amount) / 1000 - ); - } - - /* - After migrating to V2 system, check that we can use PCVRouter + MakerPCVSwapper. - Swap DAI to USDC, then USDC to DAI. - */ - function testPcvRouterWithSwap() public { - _migratePcv(); - uint256 amount = 100_000e18; - - uint256 depositDaiBalanceBefore = morphoDaiPCVDeposit.balance(); - uint256 depositUsdcBalanceBefore = morphoUsdcPCVDeposit.balance(); - - // Swap DAI to USDC - vm.startPrank(MainnetAddresses.GOVERNOR); // has PCV_MOVER role - pcvRouter.movePCV( - address(morphoDaiPCVDeposit), // source - address(morphoUsdcPCVDeposit), // destination - address(systemV2.pcvSwapperMaker()), // swapper - amount, // amount - address(systemV2.dai()), // sourceAsset - address(systemV2.usdc()), // destinationAsset - true, // sourceIsLiquid - true // destinationIsLiquid - ); - vm.stopPrank(); - - uint256 depositDaiBalanceAfter = morphoDaiPCVDeposit.balance(); - uint256 depositUsdcBalanceAfter = morphoUsdcPCVDeposit.balance(); - - // tolerate 0.5% err because morpho withdrawals are not exact - assertGt( - depositDaiBalanceBefore - depositDaiBalanceAfter, - (995 * amount) / 1000 - ); - assertGt( - depositUsdcBalanceAfter - depositUsdcBalanceBefore, - ((995 * amount) / 1e12) / 1000 - ); - - // Swap USDC to DAI (half of previous amount) - vm.startPrank(MainnetAddresses.GOVERNOR); // has PCV_MOVER role - pcvRouter.movePCV( - address(morphoUsdcPCVDeposit), // source - address(morphoDaiPCVDeposit), // destination - address(systemV2.pcvSwapperMaker()), // swapper - amount / 2e12, // amount - address(systemV2.usdc()), // sourceAsset - address(systemV2.dai()), // destinationAsset - true, // sourceIsLiquid - true // destinationIsLiquid - ); - vm.stopPrank(); - - uint256 depositDaiBalanceFinal = morphoDaiPCVDeposit.balance(); - uint256 depositUsdcBalanceFinal = morphoUsdcPCVDeposit.balance(); - - // tolerate 0.5% err because morpho withdrawals are not exact - assertGt( - depositDaiBalanceFinal - depositDaiBalanceAfter, - (995 * amount) / 2000 - ); - assertGt( - depositUsdcBalanceAfter - depositUsdcBalanceFinal, - ((995 * amount) / 1e12) / 2000 - ); - } - - /* - After migrating to V2 system, check that we can unset the PCVOracle in Core - and that it doesn't break PCV movements (only disables accounting). - */ - function testUnsetPcvOracle() public { - _migratePcv(); - - vm.prank(MainnetAddresses.GOVERNOR); - core.setPCVOracle(IPCVOracle(address(0))); - - vm.prank(MainnetAddresses.GOVERNOR); - pcvGuardian.withdrawToSafeAddress(address(morphoDaiPCVDeposit), 100e18); - - // No revert & PCV moved - assertEq(dai.balanceOf(pcvGuardian.safeAddress()), 100e18); - - // User redeems - vm.prank(address(systemV2.grlm())); - volt.mint(address(this), 100e18); - volt.approve(address(daipsm), 100e18); - daipsm.redeem(address(this), 100e18, 104e18); - assertGt(dai.balanceOf(address(this)), 104e18); - } - - /* - Internal helper function, migrate the PCV from current system to V2 system - */ - function _migratePcv() internal returns (uint256) { - MorphoPCVDeposit oldMorphoDaiPCVDeposit = MorphoPCVDeposit( - MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT - ); - /*MorphoPCVDeposit oldMorphoUsdcPCVDeposit = MorphoPCVDeposit( - MainnetAddresses.MORPHO_COMPOUND_USDC_PCV_DEPOSIT - );*/ - PCVGuardian oldPCVGuardian = PCVGuardian(MainnetAddresses.PCV_GUARDIAN); - - uint256 daiBalanceBefore = dai.balanceOf(MainnetAddresses.GOVERNOR); - uint256 usdcBalanceBefore = usdc.balanceOf(MainnetAddresses.GOVERNOR); - - // Move all DAI and USDC to Safe Address - vm.startPrank(MainnetAddresses.GOVERNOR); - oldPCVGuardian.withdrawAllToSafeAddress( - address(oldMorphoDaiPCVDeposit) - ); - //oldPCVGuardian.withdrawAllToSafeAddress(address(oldMorphoUsdcPCVDeposit)); // reverts because 0 - oldPCVGuardian.withdrawAllERC20ToSafeAddress( - MainnetAddresses.VOLT_DAI_PSM, - MainnetAddresses.DAI - ); - oldPCVGuardian.withdrawAllERC20ToSafeAddress( - MainnetAddresses.VOLT_DAI_PSM, - MainnetAddresses.VOLT - ); - oldPCVGuardian.withdrawAllERC20ToSafeAddress( - MainnetAddresses.VOLT_USDC_PSM, - MainnetAddresses.USDC - ); - oldPCVGuardian.withdrawAllERC20ToSafeAddress( - MainnetAddresses.VOLT_USDC_PSM, - MainnetAddresses.VOLT - ); - vm.stopPrank(); - - uint256 daiBalanceAfter = dai.balanceOf(MainnetAddresses.GOVERNOR); - uint256 usdcBalanceAfter = usdc.balanceOf(MainnetAddresses.GOVERNOR); - uint256 protocolDai = daiBalanceAfter - daiBalanceBefore; - uint256 protocolUsdc = usdcBalanceAfter - usdcBalanceBefore; - - // Move DAI to expected location - vm.startPrank(MainnetAddresses.GOVERNOR); - dai.transfer(address(systemV2.daipsm()), 100_000e18); - dai.transfer( - address(systemV2.morphoDaiPCVDeposit()), - protocolDai - 100_000e18 - ); - usdc.transfer(address(systemV2.usdcpsm()), protocolUsdc); - systemEntry.deposit(address(systemV2.morphoDaiPCVDeposit())); - vm.stopPrank(); - - return protocolDai + protocolUsdc * 1e12; - } - - function _migratorSetup() private { - vm.prank(address(timelockController)); - core.grantMinter(addresses.minterAddress); - } - - function testMigratorSetup() public { - assertEq(address(migratorRouter.newVolt()), address(volt)); - assertEq(address(migratorRouter.OLD_VOLT()), address(oldVolt)); - } - - function testExchangeTo(uint64 amountOldVoltToExchange) public { - _migratorSetup(); - oldVolt.approve(address(voltMigrator), type(uint256).max); - deal(address(oldVolt), address(this), amountOldVoltToExchange); - deal(address(volt), address(voltMigrator), amountOldVoltToExchange); - - uint256 newVoltTotalSupply = volt.totalSupply(); - uint256 oldVoltTotalSupply = oldVolt.totalSupply(); - - uint256 oldVoltBalanceBefore = oldVolt.balanceOf(address(this)); - uint256 newVoltBalanceBefore = volt.balanceOf(address(0xFFF)); - - voltMigrator.exchangeTo(address(0xFFF), amountOldVoltToExchange); - - uint256 oldVoltBalanceAfter = oldVolt.balanceOf(address(this)); - uint256 newVoltBalanceAfter = volt.balanceOf(address(0xFFF)); - - assertEq( - newVoltBalanceAfter, - newVoltBalanceBefore + amountOldVoltToExchange - ); - assertEq( - oldVoltBalanceAfter, - oldVoltBalanceBefore - amountOldVoltToExchange - ); - assertEq( - oldVolt.totalSupply(), - oldVoltTotalSupply - amountOldVoltToExchange - ); - assertEq(volt.totalSupply(), newVoltTotalSupply); /// new volt supply remains unchanged - } - - function testExchangeAllTo() public { - _migratorSetup(); - uint256 amountOldVoltToExchange = 10_000e18; - - oldVolt.approve(address(voltMigrator), type(uint256).max); - - deal(address(oldVolt), address(this), amountOldVoltToExchange); - deal(address(volt), address(voltMigrator), amountOldVoltToExchange); - - uint256 newVoltTotalSupply = volt.totalSupply(); - uint256 oldVoltTotalSupply = oldVolt.totalSupply(); - - uint256 oldVoltBalanceBefore = oldVolt.balanceOf(address(this)); - uint256 newVoltBalanceBefore = volt.balanceOf(address(0xFFF)); - - voltMigrator.exchangeAllTo(address(0xFFF)); - - uint256 oldVoltBalanceAfter = oldVolt.balanceOf(address(this)); - uint256 newVoltBalanceAfter = volt.balanceOf(address(0xFFF)); - - assertEq( - newVoltBalanceAfter, - newVoltBalanceBefore + oldVoltBalanceBefore - ); - assertEq(oldVoltBalanceAfter, 0); - assertEq( - oldVolt.totalSupply(), - oldVoltTotalSupply - oldVoltBalanceBefore - ); - assertEq(volt.totalSupply(), newVoltTotalSupply); - } - - function testExchangeFailsWhenApprovalNotGiven() public { - vm.expectRevert("ERC20: burn amount exceeds allowance"); - voltMigrator.exchange(1e18); - } - - function testExchangeToFailsWhenApprovalNotGiven() public { - vm.expectRevert("ERC20: burn amount exceeds allowance"); - voltMigrator.exchangeTo(address(0xFFF), 1e18); - } - - function testExchangeFailsMigratorUnderfunded() public { - _migratorSetup(); - uint256 amountOldVoltToExchange = 100_000_000e18; - - vm.prank(addresses.minterAddress); - volt.mint(address(voltMigrator), amountOldVoltToExchange); - deal(address(oldVolt), address(this), amountOldVoltToExchange); - - oldVolt.approve(address(voltMigrator), type(uint256).max); - - vm.prank(address(voltMigrator)); - volt.burn(mintAmount); - - vm.expectRevert(stdError.arithmeticError); - voltMigrator.exchange(amountOldVoltToExchange); - } - - function testExchangeAllFailsMigratorUnderfunded() public { - _migratorSetup(); - - oldVolt.approve(address(voltMigrator), type(uint256).max); - deal(address(oldVolt), address(this), mintAmount); - - vm.prank(addresses.minterAddress); - volt.mint(address(voltMigrator), mintAmount); - - vm.prank(address(voltMigrator)); - volt.burn(mintAmount); - - vm.expectRevert(stdError.arithmeticError); - voltMigrator.exchangeAll(); - } - - function testExchangeToFailsMigratorUnderfunded() public { - _migratorSetup(); - - deal(address(oldVolt), address(this), mintAmount); - - vm.prank(addresses.minterAddress); - volt.mint(address(voltMigrator), mintAmount); - - uint256 amountOldVoltToExchange = 100_000_000e18; - oldVolt.approve(address(voltMigrator), type(uint256).max); - - vm.prank(address(voltMigrator)); - volt.burn(mintAmount); - - vm.expectRevert(stdError.arithmeticError); - voltMigrator.exchangeTo(address(0xFFF), amountOldVoltToExchange); - } - - function testExchangeAllToFailsMigratorUnderfunded() public { - _migratorSetup(); - - vm.prank(addresses.minterAddress); - volt.mint(address(voltMigrator), mintAmount); - deal(address(oldVolt), address(this), mintAmount); - - oldVolt.approve(address(voltMigrator), type(uint256).max); - - vm.prank(address(voltMigrator)); - volt.burn(mintAmount); - - vm.expectRevert(stdError.arithmeticError); - voltMigrator.exchangeAllTo(address(0xFFF)); - } - - function testExchangeAllWhenApprovalNotGiven() public { - _migratorSetup(); - - uint256 oldVoltBalanceBefore = oldVolt.balanceOf(address(this)); - uint256 newVoltBalanceBefore = volt.balanceOf(address(this)); - - vm.expectRevert("VoltMigrator: no amount to exchange"); - voltMigrator.exchangeAll(); - - uint256 oldVoltBalanceAfter = oldVolt.balanceOf(address(this)); - uint256 newVoltBalanceAfter = volt.balanceOf(address(this)); - - assertEq(oldVoltBalanceBefore, oldVoltBalanceAfter); - assertEq(newVoltBalanceBefore, newVoltBalanceAfter); - } - - function testExchangeAllToWhenApprovalNotGiven() public { - uint256 oldVoltBalanceBefore = oldVolt.balanceOf(address(this)); - uint256 newVoltBalanceBefore = volt.balanceOf(address(0xFFF)); - - vm.expectRevert("VoltMigrator: no amount to exchange"); - voltMigrator.exchangeAllTo(address(0xFFF)); - - uint256 oldVoltBalanceAfter = oldVolt.balanceOf(address(this)); - uint256 newVoltBalanceAfter = volt.balanceOf(address(0xFFF)); - - assertEq(oldVoltBalanceBefore, oldVoltBalanceAfter); - assertEq(newVoltBalanceBefore, newVoltBalanceAfter); - } - - function testExchangeAllPartialApproval() public { - _migratorSetup(); - - deal(address(oldVolt), address(this), 100_000e18); - - uint256 amountOldVoltToExchange = oldVolt.balanceOf(address(this)) / 2; // exchange half of users balance - - oldVolt.approve(address(voltMigrator), amountOldVoltToExchange); - - vm.prank(addresses.minterAddress); - volt.mint(address(voltMigrator), amountOldVoltToExchange); - - uint256 newVoltTotalSupply = volt.totalSupply(); - uint256 oldVoltTotalSupply = oldVolt.totalSupply(); - - uint256 oldVoltBalanceBefore = oldVolt.balanceOf(address(this)); - uint256 newVoltBalanceBefore = volt.balanceOf(address(this)); - - voltMigrator.exchangeAllTo(address(this)); - - uint256 oldVoltBalanceAfter = oldVolt.balanceOf(address(this)); - uint256 newVoltBalanceAfter = volt.balanceOf(address(this)); - - assertEq( - newVoltBalanceAfter, - newVoltBalanceBefore + amountOldVoltToExchange - ); - assertEq( - oldVoltBalanceAfter, - oldVoltBalanceBefore - amountOldVoltToExchange - ); - - assertEq( - oldVolt.totalSupply(), - oldVoltTotalSupply - amountOldVoltToExchange - ); - assertEq(volt.totalSupply(), newVoltTotalSupply); - - assertEq(oldVoltBalanceAfter, oldVoltBalanceBefore / 2); - } - - function testExchangeAllToPartialApproval() public { - _migratorSetup(); - - uint256 amountOldVoltToExchange = mintAmount / 2; // exchange half of users balance - oldVolt.approve(address(voltMigrator), amountOldVoltToExchange); - - vm.prank(addresses.minterAddress); - volt.mint(address(voltMigrator), mintAmount); - - vm.prank(MainnetAddresses.GOVERNOR); - CoreV2(MainnetAddresses.CORE).grantMinter(address(this)); - oldVolt.mint(address(this), mintAmount); - - uint256 newVoltTotalSupply = volt.totalSupply(); - uint256 oldVoltTotalSupply = oldVolt.totalSupply(); - - uint256 oldVoltBalanceBefore = oldVolt.balanceOf(address(this)); - uint256 newVoltBalanceBefore = volt.balanceOf(address(0xFFF)); - - voltMigrator.exchangeAllTo(address(0xFFF)); - - uint256 oldVoltBalanceAfter = oldVolt.balanceOf(address(this)); - uint256 newVoltBalanceAfter = volt.balanceOf(address(0xFFF)); - - assertEq( - newVoltBalanceAfter, - newVoltBalanceBefore + amountOldVoltToExchange - ); - assertEq( - oldVoltBalanceAfter, - oldVoltBalanceBefore - amountOldVoltToExchange - ); - assertEq( - oldVolt.totalSupply(), - oldVoltTotalSupply - amountOldVoltToExchange - ); - assertEq(volt.totalSupply(), newVoltTotalSupply); - assertEq(oldVoltBalanceAfter, oldVoltBalanceBefore / 2); - } - - function testSweep() public { - uint256 amountToTransfer = 1_000_000e6; - - uint256 startingBalance = usdc.balanceOf( - MainnetAddresses.TIMELOCK_CONTROLLER - ); - - deal(MainnetAddresses.USDC, address(voltMigrator), amountToTransfer); - - vm.prank(MainnetAddresses.GOVERNOR); - voltMigrator.sweep( - address(usdc), - MainnetAddresses.TIMELOCK_CONTROLLER, - amountToTransfer - ); - - uint256 endingBalance = usdc.balanceOf( - MainnetAddresses.TIMELOCK_CONTROLLER - ); - - assertEq(endingBalance - startingBalance, amountToTransfer); - } - - function testSweepNonGovernorFails() public { - uint256 amountToTransfer = 1_000_000e6; - - deal(MainnetAddresses.USDC, address(voltMigrator), amountToTransfer); - - vm.expectRevert("CoreRef: Caller is not a governor"); - voltMigrator.sweep( - address(usdc), - MainnetAddresses.TIMELOCK_CONTROLLER, - amountToTransfer - ); - } - - function testSweepNewVoltFails() public { - uint256 amountToSweep = volt.balanceOf(address(voltMigrator)); - - vm.startPrank(MainnetAddresses.GOVERNOR); - vm.expectRevert("VoltMigrator: cannot sweep new Volt"); - voltMigrator.sweep( - address(volt), - MainnetAddresses.TIMELOCK_CONTROLLER, - amountToSweep - ); - vm.stopPrank(); - } - - function testRedeemUsdc(uint72 amountVoltIn) public { - _migratorSetup(); - - oldVolt.approve(address(migratorRouter), type(uint256).max); - - vm.prank(addresses.minterAddress); - volt.mint(address(voltMigrator), amountVoltIn); - - vm.prank(MainnetAddresses.GOVERNOR); - CoreV2(MainnetAddresses.CORE).grantMinter(address(this)); - oldVolt.mint(address(this), amountVoltIn); - - uint256 startBalance = usdc.balanceOf(address(this)); - uint256 minAmountOut = usdcpsm.getRedeemAmountOut(amountVoltIn); - - deal(address(usdc), address(usdcpsm), minAmountOut); - - uint256 currentPegPrice = systemV2.vso().getCurrentOraclePrice() / 1e12; - uint256 amountOut = (amountVoltIn * currentPegPrice) / 1e18; - - uint256 redeemedAmount = migratorRouter.redeemUSDC( - amountVoltIn, - minAmountOut - ); - uint256 endBalance = usdc.balanceOf(address(this)); - - assertApproxEq(minAmountOut.toInt256(), amountOut.toInt256(), 0); - assertEq(minAmountOut, endBalance - startBalance); - assertEq(redeemedAmount, minAmountOut); - } - - function testRedeemDai(uint72 amountVoltIn) public { - _migratorSetup(); - - oldVolt.approve(address(migratorRouter), type(uint256).max); - - vm.prank(addresses.minterAddress); - volt.mint(address(voltMigrator), amountVoltIn); - - vm.prank(MainnetAddresses.GOVERNOR); - CoreV2(MainnetAddresses.CORE).grantMinter(address(this)); - oldVolt.mint(address(this), amountVoltIn); - - uint256 startBalance = dai.balanceOf(address(this)); - uint256 minAmountOut = daipsm.getRedeemAmountOut(amountVoltIn); - - deal(address(dai), address(daipsm), minAmountOut); - - uint256 currentPegPrice = systemV2.vso().getCurrentOraclePrice(); - uint256 amountOut = (amountVoltIn * currentPegPrice) / 1e18; - - uint256 redeemedAmount = migratorRouter.redeemDai( - amountVoltIn, - minAmountOut - ); - uint256 endBalance = dai.balanceOf(address(this)); - - assertApproxEq(minAmountOut.toInt256(), amountOut.toInt256(), 0); - assertEq(minAmountOut, endBalance - startBalance); - assertEq(redeemedAmount, minAmountOut); - } - - function testRedeemDaiFailsUserNotEnoughVolt() public { - oldVolt.approve(address(migratorRouter), type(uint256).max); - - uint256 minAmountOut = daipsm.getRedeemAmountOut(mintAmount); - - vm.expectRevert("ERC20: transfer amount exceeds balance"); - migratorRouter.redeemDai(mintAmount, minAmountOut); - } - - function testRedeemUsdcFailsUserNotEnoughVolt() public { - oldVolt.approve(address(migratorRouter), type(uint256).max); - - uint256 minAmountOut = daipsm.getRedeemAmountOut(mintAmount); - - vm.expectRevert("ERC20: transfer amount exceeds balance"); - migratorRouter.redeemUSDC(mintAmount, minAmountOut); - } - - function testRedeemDaiFailUnderfundedPSM() public { - _migratorSetup(); - - oldVolt.approve(address(migratorRouter), type(uint256).max); - - vm.prank(addresses.minterAddress); - volt.mint(address(voltMigrator), mintAmount); - - vm.prank(MainnetAddresses.GOVERNOR); - CoreV2(MainnetAddresses.CORE).grantMinter(address(this)); - oldVolt.mint(address(this), mintAmount); - - uint256 balance = dai.balanceOf(address(daipsm)); - vm.prank(address(daipsm)); - dai.transfer(address(0), balance); - - uint256 minAmountOut = daipsm.getRedeemAmountOut(mintAmount); - - vm.expectRevert("Dai/insufficient-balance"); - migratorRouter.redeemDai(mintAmount, minAmountOut); - } - - function testRedeemUsdcFailUnderfundedPSM() public { - _migratorSetup(); - - oldVolt.approve(address(migratorRouter), type(uint256).max); - - vm.prank(addresses.minterAddress); - volt.mint(address(voltMigrator), mintAmount); - - vm.prank(MainnetAddresses.GOVERNOR); - CoreV2(MainnetAddresses.CORE).grantMinter(address(this)); - oldVolt.mint(address(this), mintAmount); - - uint256 balance = usdc.balanceOf(address(usdcpsm)); - vm.prank(address(usdcpsm)); - usdc.transfer(address(1), balance); - - uint256 minAmountOut = usdcpsm.getRedeemAmountOut(mintAmount); - - vm.expectRevert("ERC20: transfer amount exceeds balance"); - migratorRouter.redeemUSDC(mintAmount, minAmountOut); - } - - function testRedeemDaiFailNoUserApproval() public { - _migratorSetup(); - - vm.prank(MainnetAddresses.GOVERNOR); - CoreV2(MainnetAddresses.CORE).grantMinter(address(this)); - oldVolt.mint(address(this), mintAmount); - - vm.prank(addresses.minterAddress); - volt.mint(address(voltMigrator), mintAmount); - - uint256 minAmountOut = daipsm.getRedeemAmountOut(mintAmount); - - vm.expectRevert("ERC20: transfer amount exceeds allowance"); - migratorRouter.redeemDai(mintAmount, minAmountOut); - } - - function testRedeemUsdcFailNoUserApproval() public { - _migratorSetup(); - - vm.prank(MainnetAddresses.GOVERNOR); - CoreV2(MainnetAddresses.CORE).grantMinter(address(this)); - oldVolt.mint(address(this), mintAmount); - - vm.prank(addresses.minterAddress); - volt.mint(address(voltMigrator), mintAmount); - - uint256 minAmountOut = daipsm.getRedeemAmountOut(mintAmount); - - vm.expectRevert("ERC20: transfer amount exceeds allowance"); - migratorRouter.redeemUSDC(mintAmount, minAmountOut); - } - - function testRedeemDaiFailUnderfundedMigrator() public { - _migratorSetup(); - - oldVolt.approve(address(migratorRouter), type(uint256).max); - - vm.prank(MainnetAddresses.GOVERNOR); - CoreV2(MainnetAddresses.CORE).grantMinter(address(this)); - oldVolt.mint(address(this), mintAmount); - - uint256 minAmountOut = usdcpsm.getRedeemAmountOut(mintAmount); - - vm.expectRevert(stdError.arithmeticError); - migratorRouter.redeemDai(mintAmount, minAmountOut); - } - - function testRedeemUsdcFailUnderfundedMigrator() public { - _migratorSetup(); - - oldVolt.approve(address(migratorRouter), type(uint256).max); - - vm.prank(MainnetAddresses.GOVERNOR); - CoreV2(MainnetAddresses.CORE).grantMinter(address(this)); - oldVolt.mint(address(this), mintAmount); - - uint256 minAmountOut = usdcpsm.getRedeemAmountOut(mintAmount); - - vm.expectRevert(stdError.arithmeticError); - migratorRouter.redeemUSDC(mintAmount, minAmountOut); - } -} From 55f86f1db23fed47351167fd63c0047b2fe1e3df Mon Sep 17 00:00:00 2001 From: Elliot Date: Wed, 18 Jan 2023 15:13:29 -0800 Subject: [PATCH 10/23] add _withdrawAndTransfer function --- contracts/pcv/PCVDepositV2.sol | 5 +++-- contracts/pcv/morpho/MorphoPCVDeposit.sol | 13 +++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/contracts/pcv/PCVDepositV2.sol b/contracts/pcv/PCVDepositV2.sol index 5b3ab019a..7970d79b3 100644 --- a/contracts/pcv/PCVDepositV2.sol +++ b/contracts/pcv/PCVDepositV2.sol @@ -240,7 +240,8 @@ abstract contract PCVDepositV2 is IPCVDepositV2, CoreRefV2 { /// ------ Interactions ------ /// remove funds from underlying venue - _withdrawUnderlyingVenue(amount); + _withdrawAndTransfer(amount, to); + /// transfer funds to recipient IERC20(token).safeTransfer(to, amount); @@ -257,7 +258,7 @@ abstract contract PCVDepositV2 is IPCVDepositV2, CoreRefV2 { function _accrueUnderlying() internal virtual; /// @dev withdraw from the underlying market. - function _withdrawUnderlyingVenue(uint256 amount) internal virtual; + function _withdrawAndTransfer(uint256 amount, address to) internal virtual; /// @dev deposit in the underlying market. function _supply(uint256 amount) internal virtual; diff --git a/contracts/pcv/morpho/MorphoPCVDeposit.sol b/contracts/pcv/morpho/MorphoPCVDeposit.sol index 879ce7f15..c601f38ac 100644 --- a/contracts/pcv/morpho/MorphoPCVDeposit.sol +++ b/contracts/pcv/morpho/MorphoPCVDeposit.sol @@ -67,13 +67,6 @@ contract MorphoPCVDeposit is PCVDepositV2 { address _morpho, address _lens ) PCVDepositV2(_underlying, _rewardToken) CoreRefV2(_core) { - if (_underlying != address(Constants.WETH)) { - require( - ICToken(_cToken).underlying() == _underlying, - "MorphoPCVDeposit: Underlying mismatch" - ); - } - cToken = _cToken; morpho = _morpho; lens = _lens; @@ -110,8 +103,12 @@ contract MorphoPCVDeposit is PCVDepositV2 { } /// @dev withdraw from the underlying morpho market. - function _withdrawUnderlyingVenue(uint256 amount) internal override { + function _withdrawAndTransfer( + uint256 amount, + address to + ) internal override { IMorpho(morpho).withdraw(cToken, amount); + IERC20(token).safeTransfer(to, amount); } /// @dev deposit in the underlying morpho market. From 11e281a12cd0b0a1ce7c116c5db9fd26b97606ce Mon Sep 17 00:00:00 2001 From: Elliot Date: Wed, 18 Jan 2023 15:19:00 -0800 Subject: [PATCH 11/23] fix imports --- .../test/unit/pcv/compound/CompoundBadDebtSentinel.t.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/test/unit/pcv/compound/CompoundBadDebtSentinel.t.sol b/contracts/test/unit/pcv/compound/CompoundBadDebtSentinel.t.sol index 848a7c419..8b99eebaa 100644 --- a/contracts/test/unit/pcv/compound/CompoundBadDebtSentinel.t.sol +++ b/contracts/test/unit/pcv/compound/CompoundBadDebtSentinel.t.sol @@ -17,7 +17,7 @@ import {GenericCallMock} from "../../../../mock/GenericCallMock.sol"; import {MockPCVDepositV3} from "../../../../mock/MockPCVDepositV3.sol"; import {MockERC20, IERC20} from "../../../../mock/MockERC20.sol"; import {CompoundBadDebtSentinel} from "../../../../pcv/compound/CompoundBadDebtSentinel.sol"; -import {MorphoCompoundPCVDeposit} from "../../../../pcv/morpho/MorphoCompoundPCVDeposit.sol"; +import {MorphoPCVDeposit} from "../../../../pcv/morpho/MorphoPCVDeposit.sol"; import {TestAddresses as addresses} from "../../utils/TestAddresses.sol"; import {MockMorphoMaliciousReentrancy} from "../../../../mock/MockMorphoMaliciousReentrancy.sol"; import {IGlobalReentrancyLock, GlobalReentrancyLock} from "../../../../core/GlobalReentrancyLock.sol"; @@ -41,7 +41,7 @@ contract UnitTestCompoundBadDebtSentinel is Test { PCVGuardian private pcvGuardian; GenericCallMock private comptroller; MockPCVDepositV3 private safeAddress; - MorphoCompoundPCVDeposit private morphoDeposit; + MorphoPCVDeposit private morphoDeposit; CompoundBadDebtSentinel private badDebtSentinel; uint256 public badDebtThreshold = 1_000_000e18; @@ -67,10 +67,11 @@ contract UnitTestCompoundBadDebtSentinel is Test { comptroller = new GenericCallMock(); safeAddress = new MockPCVDepositV3(address(core), address(token)); - morphoDeposit = new MorphoCompoundPCVDeposit( + morphoDeposit = new MorphoPCVDeposit( address(core), address(morpho), address(token), + address(0), address(morpho), address(morpho) ); From f2cb3a3515edd60aeea33d9dbd6fae95fb3ad790 Mon Sep 17 00:00:00 2001 From: Elliot Date: Wed, 18 Jan 2023 16:18:46 -0800 Subject: [PATCH 12/23] PCVDepositV2 _withdrawAndTransfer function --- contracts/pcv/PCVDepositV2.sol | 3 --- .../unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol | 14 -------------- 2 files changed, 17 deletions(-) diff --git a/contracts/pcv/PCVDepositV2.sol b/contracts/pcv/PCVDepositV2.sol index 39e919ef8..10c7e11b8 100644 --- a/contracts/pcv/PCVDepositV2.sol +++ b/contracts/pcv/PCVDepositV2.sol @@ -239,9 +239,6 @@ abstract contract PCVDepositV2 is IPCVDepositV2, CoreRefV2 { /// remove funds from underlying venue _withdrawAndTransfer(amount, to); - /// transfer funds to recipient - IERC20(token).safeTransfer(to, amount); - emit Withdrawal(msg.sender, to, amount); } diff --git a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol index 423c614ec..d105ca478 100644 --- a/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol +++ b/contracts/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol @@ -101,20 +101,6 @@ contract UnitTestMorphoCompoundPCVDeposit is DSTest { assertEq(morphoDeposit.lastRecordedBalance(), 0); } - function testUnderlyingMismatchConstructionFails() public { - MockCToken cToken = new MockCToken(address(1)); - - vm.expectRevert("MorphoPCVDeposit: Underlying mismatch"); - new MorphoPCVDeposit( - address(core), - address(cToken), - address(token), - address(0), - address(morpho), - address(morpho) - ); - } - function testDeposit(uint120 depositAmount) public { assertEq(morphoDeposit.lastRecordedBalance(), 0); token.mint(address(morphoDeposit), depositAmount); From b8ffb03cf09035609e8ad2c3ea60210cd74ca133 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 19 Jan 2023 13:39:53 -0800 Subject: [PATCH 13/23] remove contracts folder --- .../IntegrationTestERC20Allocator.t.sol | 338 ------------------ .../IntegrationTestCompoundPCVRouter.t.sol | 210 ----------- contracts/test/integration/vip/vip14.sol | 300 ---------------- 3 files changed, 848 deletions(-) delete mode 100644 contracts/test/integration/IntegrationTestERC20Allocator.t.sol delete mode 100644 contracts/test/integration/compound/IntegrationTestCompoundPCVRouter.t.sol delete mode 100644 contracts/test/integration/vip/vip14.sol diff --git a/contracts/test/integration/IntegrationTestERC20Allocator.t.sol b/contracts/test/integration/IntegrationTestERC20Allocator.t.sol deleted file mode 100644 index 32c2d9e78..000000000 --- a/contracts/test/integration/IntegrationTestERC20Allocator.t.sol +++ /dev/null @@ -1,338 +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 {vip10} from "./vip/vip10.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 {PCVDeposit} from "../../pcv/PCVDeposit.sol"; -import {PCVGuardian} from "../../pcv/PCVGuardian.sol"; -import {ERC20Allocator} from "../../pcv/utils/ERC20Allocator.sol"; -import {MainnetAddresses} from "./fixtures/MainnetAddresses.sol"; -import {TimelockSimulation} from "./utils/TimelockSimulation.sol"; -import {PegStabilityModule} from "../../peg/PegStabilityModule.sol"; -import {MorphoPCVDeposit} from "../../pcv/morpho/MorphoPCVDeposit.sol"; - -contract IntegrationTestERC20Allocator is DSTest { - using SafeCast for *; - - Vm public constant vm = Vm(HEVM_ADDRESS); - - PCVGuardian private immutable mainnetPCVGuardian = - PCVGuardian(MainnetAddresses.PCV_GUARDIAN); - MorphoPCVDeposit private daiDeposit = - MorphoPCVDeposit(MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT); - MorphoPCVDeposit private usdcDeposit = - MorphoPCVDeposit(MainnetAddresses.MORPHO_COMPOUND_USDC_PCV_DEPOSIT); - - PCVGuardian private immutable pcvGuardian = - PCVGuardian(MainnetAddresses.PCV_GUARDIAN); - - Core private core = Core(MainnetAddresses.CORE); - PegStabilityModule private daiPSM = - PegStabilityModule(MainnetAddresses.VOLT_DAI_PSM); - PegStabilityModule private usdcPSM = - PegStabilityModule(MainnetAddresses.VOLT_USDC_PSM); - - IERC20 private dai = IERC20(MainnetAddresses.DAI); - IVolt private fei = IVolt(MainnetAddresses.FEI); - IERC20 private usdc = IERC20(MainnetAddresses.USDC); - - ERC20Allocator private allocator = - ERC20Allocator(MainnetAddresses.ERC20ALLOCATOR); - - uint256 public constant scalingFactorUsdc = 1e12; - int8 public constant decimalsNormalizerUsdc = 12; - int8 public constant decimalsNormalizerDai = 0; - - uint248 public constant targetUsdcBalance = 100_000e6; - uint248 public constant targetDaiBalance = 100_000e18; - - uint256 public constant maxRateLimitPerSecond = 1_000e18; /// 1k volt per second - /// @notice rate limit per second is designed to allow system to replenish - /// full buffercap every 24 hours assuming drip only - /// 500,000 / 86,400 = 5.787 VOLT per second - uint128 public constant rateLimitPerSecond = 5.78e18; - uint128 public constant bufferCap = 300_000e18; - - function setUp() public { - uint256 usdcBalance = usdc.balanceOf( - MainnetAddresses.KRAKEN_USDC_WHALE - ); - vm.prank(MainnetAddresses.KRAKEN_USDC_WHALE); - usdc.transfer(address(usdcDeposit), usdcBalance); - usdcDeposit.deposit(); - } - - function testSetup() public { - assertEq(address(allocator.core()), address(core)); - - { - ( - address psmToken, - uint248 psmTargetBalance, - int8 decimalsNormalizer - ) = allocator.allPSMs(address(daiPSM)); - address daipsm = allocator.pcvDepositToPSM(address(daiDeposit)); - - assertEq(psmTargetBalance, targetDaiBalance); - assertEq(decimalsNormalizer, decimalsNormalizerDai); - assertEq(psmToken, address(dai)); - assertEq(daipsm, address(daiPSM)); - } - - { - ( - address psmToken, - uint248 psmTargetBalance, - int8 decimalsNormalizer - ) = allocator.allPSMs(address(usdcPSM)); - address usdcpsm = allocator.pcvDepositToPSM(address(usdcDeposit)); - - assertEq(psmTargetBalance, targetUsdcBalance); - assertEq(decimalsNormalizer, decimalsNormalizerUsdc); - assertEq(psmToken, address(usdc)); - assertEq(usdcpsm, address(usdcPSM)); - } - } - - /// ------ DRIP ------ - - function testDripDai() public { - uint256 daiBalance = dai.balanceOf(address(daiPSM)); - - vm.prank(MainnetAddresses.GOVERNOR); - daiPSM.withdraw(address(daiDeposit), daiBalance); /// send all dai to pcv deposit - daiDeposit.deposit(); - - (uint256 amountToDrip, uint256 adjustedAmountToDrip) = allocator - .getDripDetails(address(daiPSM), address(daiDeposit)); - - assertTrue(allocator.checkDripCondition(address(daiDeposit))); - assertTrue(!allocator.checkSkimCondition(address(daiDeposit))); - assertTrue(allocator.checkActionAllowed(address(daiDeposit))); - allocator.drip(address(daiDeposit)); - assertTrue(!allocator.checkDripCondition(address(daiDeposit))); - assertTrue(!allocator.checkSkimCondition(address(daiDeposit))); - assertTrue(!allocator.checkActionAllowed(address(daiDeposit))); - - daiBalance = dai.balanceOf(address(daiPSM)); - - assertEq(amountToDrip, adjustedAmountToDrip); - assertEq(amountToDrip, targetDaiBalance); - assertEq(daiBalance, targetDaiBalance); - } - - function testDripUsdc() public { - uint256 usdcBalance = usdc.balanceOf(address(usdcPSM)); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcPSM.withdraw(address(usdcDeposit), usdcBalance); /// send all dai to pcv deposit - usdcDeposit.deposit(); /// deposit so it will be counted in balance - - (uint256 amountToDrip, uint256 adjustedAmountToDrip) = allocator - .getDripDetails(address(usdcPSM), address(usdcDeposit)); - - assertTrue(allocator.checkDripCondition(address(usdcDeposit))); - assertTrue(allocator.checkActionAllowed(address(usdcDeposit))); - allocator.drip(address(usdcDeposit)); - assertTrue(!allocator.checkDripCondition(address(usdcDeposit))); - assertTrue(!allocator.checkActionAllowed(address(usdcDeposit))); - - usdcBalance = usdc.balanceOf(address(usdcPSM)); - assertEq(amountToDrip * scalingFactorUsdc, adjustedAmountToDrip); - assertEq(amountToDrip, targetUsdcBalance); - assertEq(usdcBalance, targetUsdcBalance); - } - - /// ------ SKIM ------ - - function testSkimDai() public { - uint256 daiBalance = daiDeposit.balance(); - uint256 daiPSMBalance = daiPSM.balance(); - - vm.prank(MainnetAddresses.GOVERNOR); - daiDeposit.withdraw(address(daiPSM), daiBalance); /// send all dai to psm - - (uint256 amountToSkim, uint256 adjustedAmountToSkim) = allocator - .getSkimDetails(address(daiDeposit)); - - uint256 skimAmount = daiPSM.balance() - targetDaiBalance; - assertEq(amountToSkim, skimAmount); - assertEq(adjustedAmountToSkim, skimAmount); - - assertTrue(!allocator.checkDripCondition(address(daiDeposit))); /// all assets are in dai psm, not eligble for skim - assertTrue(allocator.checkSkimCondition(address(daiDeposit))); - assertTrue(allocator.checkActionAllowed(address(daiDeposit))); - - allocator.skim(address(daiDeposit)); - - assertTrue(!allocator.checkDripCondition(address(daiDeposit))); - assertTrue(!allocator.checkSkimCondition(address(daiDeposit))); - assertTrue(!allocator.checkActionAllowed(address(daiDeposit))); - - assertEq(dai.balanceOf(address(daiPSM)), targetDaiBalance); - assertApproxEq( - daiDeposit.balance().toInt256(), - (daiBalance + daiPSMBalance - targetDaiBalance).toInt256(), - 0 - ); - } - - function testSkimUsdc() public { - uint256 usdcBalance = usdcDeposit.balance(); - uint256 usdcPSMBalance = usdcPSM.balance(); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.withdraw(address(usdcPSM), usdcBalance); /// send all usdc to psm - - (uint256 amountToSkim, uint256 adjustedAmountToSkim) = allocator - .getSkimDetails(address(usdcDeposit)); - - uint256 skimAmount = usdcPSM.balance() - targetUsdcBalance; - assertEq(amountToSkim, skimAmount); - assertEq(adjustedAmountToSkim, skimAmount * scalingFactorUsdc); - - assertTrue(!allocator.checkDripCondition(address(usdcDeposit))); /// all assets are in usdc psm, not eligble for skim - assertTrue(allocator.checkSkimCondition(address(usdcDeposit))); - assertTrue(allocator.checkActionAllowed(address(usdcDeposit))); - - allocator.skim(address(usdcDeposit)); - - assertTrue(!allocator.checkDripCondition(address(usdcDeposit))); - assertTrue(!allocator.checkSkimCondition(address(usdcDeposit))); - assertTrue(!allocator.checkActionAllowed(address(usdcDeposit))); - - assertEq(usdc.balanceOf(address(usdcPSM)), targetUsdcBalance); - assertApproxEq( - usdcDeposit.balance().toInt256(), - (usdcBalance + usdcPSMBalance - targetUsdcBalance).toInt256(), - 0 - ); - } - - /// ------ DRIP ------ - - function testDripDaiViaDoAction() public { - uint256 daiBalance = dai.balanceOf(address(daiPSM)); - - vm.prank(MainnetAddresses.GOVERNOR); - daiPSM.withdraw(address(daiDeposit), daiBalance); /// send all dai to pcv deposit - daiDeposit.deposit(); - - (uint256 amountToDrip, uint256 adjustedAmountToDrip) = allocator - .getDripDetails(address(daiPSM), address(daiDeposit)); - - assertTrue(allocator.checkDripCondition(address(daiDeposit))); - assertTrue(!allocator.checkSkimCondition(address(daiDeposit))); - assertTrue(allocator.checkActionAllowed(address(daiDeposit))); - - allocator.doAction(address(daiDeposit)); - - assertTrue(!allocator.checkDripCondition(address(daiDeposit))); - assertTrue(!allocator.checkSkimCondition(address(daiDeposit))); - assertTrue(!allocator.checkActionAllowed(address(daiDeposit))); - - daiBalance = dai.balanceOf(address(daiPSM)); - - assertEq(amountToDrip, adjustedAmountToDrip); - assertEq(amountToDrip, targetDaiBalance); - assertEq(daiBalance, targetDaiBalance); - } - - function testDripUsdcViaDoAction() public { - uint256 usdcBalance = usdc.balanceOf(address(usdcPSM)); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcPSM.withdraw(address(usdcDeposit), usdcBalance); /// send all dai to pcv deposit - usdcDeposit.deposit(); /// deposit so it will be counted in balance - - (uint256 amountToDrip, uint256 adjustedAmountToDrip) = allocator - .getDripDetails(address(usdcPSM), address(usdcDeposit)); - - assertTrue(allocator.checkDripCondition(address(usdcDeposit))); - assertTrue(allocator.checkActionAllowed(address(usdcDeposit))); - - allocator.doAction(address(usdcDeposit)); - - assertTrue(!allocator.checkDripCondition(address(usdcDeposit))); - assertTrue(!allocator.checkActionAllowed(address(usdcDeposit))); - - usdcBalance = usdc.balanceOf(address(usdcPSM)); - assertEq(amountToDrip * scalingFactorUsdc, adjustedAmountToDrip); - assertEq(amountToDrip, targetUsdcBalance); - assertEq(usdcBalance, targetUsdcBalance); - } - - /// ------ SKIM ------ - - function testSkimDaiViaDoAction() public { - uint256 daiBalance = daiDeposit.balance(); - uint256 daiPSMBalance = daiPSM.balance(); - - vm.prank(MainnetAddresses.GOVERNOR); - daiDeposit.withdraw(address(daiPSM), daiBalance); /// send all dai to psm - - (uint256 amountToSkim, uint256 adjustedAmountToSkim) = allocator - .getSkimDetails(address(daiDeposit)); - - uint256 skimAmount = daiPSM.balance() - targetDaiBalance; - assertEq(amountToSkim, skimAmount); - assertEq(adjustedAmountToSkim, skimAmount); - - assertTrue(!allocator.checkDripCondition(address(daiDeposit))); /// all assets are in dai psm, not eligble for skim - assertTrue(allocator.checkSkimCondition(address(daiDeposit))); - assertTrue(allocator.checkActionAllowed(address(daiDeposit))); - - allocator.doAction(address(daiDeposit)); - - assertTrue(!allocator.checkDripCondition(address(daiDeposit))); - assertTrue(!allocator.checkSkimCondition(address(daiDeposit))); - assertTrue(!allocator.checkActionAllowed(address(daiDeposit))); - - assertEq(dai.balanceOf(address(daiPSM)), targetDaiBalance); - assertApproxEq( - daiDeposit.balance().toInt256(), - (daiBalance + daiPSMBalance - targetDaiBalance).toInt256(), - 0 - ); - } - - function testSkimUsdcViaDoAction() public { - uint256 usdcBalance = usdcDeposit.balance(); - uint256 usdcPSMBalance = usdcPSM.balance(); - - vm.prank(MainnetAddresses.GOVERNOR); - usdcDeposit.withdraw(address(usdcPSM), usdcBalance); /// send all usdc to psm - - (uint256 amountToSkim, uint256 adjustedAmountToSkim) = allocator - .getSkimDetails(address(usdcDeposit)); - - uint256 skimAmount = usdcPSM.balance() - targetUsdcBalance; - assertEq(amountToSkim, skimAmount); - assertEq(adjustedAmountToSkim, skimAmount * scalingFactorUsdc); - - assertTrue(!allocator.checkDripCondition(address(usdcDeposit))); /// all assets are in usdc psm, not eligble for skim - assertTrue(allocator.checkSkimCondition(address(usdcDeposit))); - assertTrue(allocator.checkActionAllowed(address(usdcDeposit))); - - allocator.doAction(address(usdcDeposit)); - - assertTrue(!allocator.checkDripCondition(address(usdcDeposit))); - assertTrue(!allocator.checkSkimCondition(address(usdcDeposit))); - assertTrue(!allocator.checkActionAllowed(address(usdcDeposit))); - - assertEq(usdc.balanceOf(address(usdcPSM)), targetUsdcBalance); - assertApproxEq( - usdcDeposit.balance().toInt256(), - (usdcBalance + usdcPSMBalance - targetUsdcBalance).toInt256(), - 0 - ); - } -} diff --git a/contracts/test/integration/compound/IntegrationTestCompoundPCVRouter.t.sol b/contracts/test/integration/compound/IntegrationTestCompoundPCVRouter.t.sol deleted file mode 100644 index 6bfc32360..000000000 --- a/contracts/test/integration/compound/IntegrationTestCompoundPCVRouter.t.sol +++ /dev/null @@ -1,210 +0,0 @@ -pragma solidity 0.8.13; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; - -import {Vm} from "../../unit/utils/Vm.sol"; -import {Core} from "../../../core/Core.sol"; -import {CToken} from "../../../pcv/compound/CToken.sol"; -import {DSTest} from "../../unit/utils/DSTest.sol"; -import {IDSSPSM} from "./../../../pcv/maker/IDSSPSM.sol"; -import {stdError} from "../../unit/utils/StdLib.sol"; -import {MockERC20} from "../../../mock/MockERC20.sol"; -import {VoltRoles} from "../../../core/VoltRoles.sol"; -import {PCVGuardian} from "../../../pcv/PCVGuardian.sol"; -import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; -import {ArbitrumAddresses} from "../fixtures/ArbitrumAddresses.sol"; -import {CompoundPCVRouter} from "../../../pcv/compound/CompoundPCVRouter.sol"; -import {MorphoPCVDeposit} from "../../../pcv/morpho/MorphoPCVDeposit.sol"; - -contract IntegrationTestCompoundPCVRouter is DSTest { - using SafeCast for *; - - Vm public constant vm = Vm(HEVM_ADDRESS); - - Core private core = Core(MainnetAddresses.CORE); - IERC20 private usdc = IERC20(MainnetAddresses.USDC); - IERC20 private dai = IERC20(MainnetAddresses.DAI); - address private governor = MainnetAddresses.GOVERNOR; - - CToken private cDai = CToken(MainnetAddresses.CDAI); - CToken private cUsdc = CToken(MainnetAddresses.CUSDC); - - IDSSPSM public immutable daiPSM = - IDSSPSM(0x89B78CfA322F6C5dE0aBcEecab66Aee45393cC5A); - - CompoundPCVRouter private compoundRouter = - CompoundPCVRouter(MainnetAddresses.MORPHO_COMPOUND_PCV_ROUTER); - - MorphoPCVDeposit private daiDeposit = - MorphoPCVDeposit(MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT); - MorphoPCVDeposit private usdcDeposit = - MorphoPCVDeposit(MainnetAddresses.MORPHO_COMPOUND_USDC_PCV_DEPOSIT); - - address public immutable pcvGuard = MainnetAddresses.EOA_1; - PCVGuardian public immutable pcvGuardian = - PCVGuardian(MainnetAddresses.PCV_GUARDIAN); - - address public constant makerWard = - 0xBE8E3e3618f7474F8cB1d074A26afFef007E98FB; - - /// @notice scaling factor for USDC - uint256 public constant USDC_SCALING_FACTOR = 1e12; - - function setUp() public { - cDai.accrueInterest(); - cUsdc.accrueInterest(); - - // deal small amounts of DAI/USDC to ensure deposits are not empty - // (onchain conditions might change and the tests reverts if one - // or both of the deposits is empty) - address holder = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7; // 3pool - vm.deal(holder, 1 ether); - // USDC - vm.prank(holder); - usdc.transfer(address(usdcDeposit), 1000e6); - usdcDeposit.deposit(); - // DAI - vm.prank(holder); - dai.transfer(address(daiDeposit), 1000e18); - daiDeposit.deposit(); - } - - function testSetup() public { - assertTrue(core.isPCVController(address(compoundRouter))); - - assertEq( - address(compoundRouter.daiPSM()), - MainnetAddresses.MAKER_DAI_USDC_PSM - ); - assertEq(address(compoundRouter.daiPcvDeposit()), address(daiDeposit)); - assertEq( - address(compoundRouter.usdcPcvDeposit()), - address(usdcDeposit) - ); - } - - function testSwapDaiToUsdcSucceeds() public { - uint256 withdrawAmount = daiDeposit.balance(); - uint256 usdcStartingBalance = usdcDeposit.balance(); - - vm.prank(governor); - compoundRouter.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 testSwapUsdcToDaiSucceeds() public { - uint256 withdrawAmount = usdcDeposit.balance(); - uint256 daiStartingBalance = daiDeposit.balance(); - - vm.prank(governor); - compoundRouter.swapUsdcForDai(withdrawAmount); /// withdraw all balance - - assertApproxEq( - daiDeposit.balance().toInt256(), - (withdrawAmount * USDC_SCALING_FACTOR + daiStartingBalance) - .toInt256(), - 0 - ); - - assertTrue(usdcDeposit.balance() < 1e3); /// assert only dust remains - } - - function testSwapUsdcToDaiSucceedsPCVGuard() public { - uint256 withdrawAmount = usdcDeposit.balance(); - uint256 daiStartingBalance = daiDeposit.balance(); - - vm.prank(pcvGuard); - compoundRouter.swapUsdcForDai(withdrawAmount); /// withdraw all balance - - assertApproxEq( - daiDeposit.balance().toInt256(), - (withdrawAmount * USDC_SCALING_FACTOR + daiStartingBalance) - .toInt256(), - 0 - ); - assertTrue(usdcDeposit.balance() < 1e3); /// assert only dust remains - } - - function testSwapDaiToUsdcSucceedsPCVGuard() public { - uint256 withdrawAmount = daiDeposit.balance(); - uint256 usdcStartingBalance = usdcDeposit.balance(); - - vm.prank(pcvGuard); - compoundRouter.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 testSwapUsdcToDaiFailsNoLiquidity() public { - uint256 usdcBalance = usdc.balanceOf( - MainnetAddresses.KRAKEN_USDC_WHALE - ); - vm.prank(MainnetAddresses.KRAKEN_USDC_WHALE); - usdc.transfer(address(usdcDeposit), usdcBalance); - usdcDeposit.deposit(); - - uint256 withdrawAmount = usdcDeposit.balance(); - vm.startPrank(pcvGuard); - pcvGuardian.withdrawAllToSafeAddress(address(usdcDeposit)); /// pull all funds from usdc pcv deposit - vm.expectRevert(stdError.arithmeticError); - compoundRouter.swapUsdcForDai(withdrawAmount); /// withdraw all balance - vm.stopPrank(); - } - - function testSwapDaiToUsdcFailsNoLiquidity() public { - uint256 withdrawAmount = daiDeposit.balance(); - vm.startPrank(pcvGuard); - pcvGuardian.withdrawAllToSafeAddress(address(daiDeposit)); /// pull all funds from dai pcv deposit - vm.expectRevert(stdError.arithmeticError); /// CDAI has new cToken implementation that fails with a revert - compoundRouter.swapDaiForUsdc(withdrawAmount); /// withdraw all balance - vm.stopPrank(); - } - - function testSwapUsdcToDaiFailsUnauthorized() public { - uint256 withdrawAmount = usdcDeposit.balance(); - vm.expectRevert("UNAUTHORIZED"); - compoundRouter.swapUsdcForDai(withdrawAmount); - } - - function testSwapDaiToUsdcFailsUnauthorized() public { - uint256 withdrawAmount = daiDeposit.balance(); - vm.expectRevert("UNAUTHORIZED"); - compoundRouter.swapDaiForUsdc(withdrawAmount); - } - - function testSwapUsdcToDaiFailsTinNonZero() public { - vm.prank(makerWard); - daiPSM.file(bytes32("tin"), 1); - - uint256 withdrawAmount = usdcDeposit.balance(); - - vm.prank(pcvGuard); - vm.expectRevert("CompoundPCVRouter: maker fee not 0"); - compoundRouter.swapUsdcForDai(withdrawAmount); - } - - function testSwapDaiToUsdcFailsToutNonZero() public { - vm.prank(makerWard); - daiPSM.file(bytes32("tout"), 1); - - uint256 withdrawAmount = daiDeposit.balance(); - - vm.prank(pcvGuard); - vm.expectRevert("CompoundPCVRouter: maker fee not 0"); - compoundRouter.swapDaiForUsdc(withdrawAmount); /// withdraw all balance - } -} diff --git a/contracts/test/integration/vip/vip14.sol b/contracts/test/integration/vip/vip14.sol deleted file mode 100644 index fad947ef0..000000000 --- a/contracts/test/integration/vip/vip14.sol +++ /dev/null @@ -1,300 +0,0 @@ -//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 {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; -import {CompoundPCVRouter} from "../../../pcv/compound/CompoundPCVRouter.sol"; -import {ITimelockSimulation} from "../utils/ITimelockSimulation.sol"; -import {MorphoPCVDeposit} from "../../../pcv/morpho/MorphoPCVDeposit.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 - -/// 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 morpho deposits as safe addresses - -/// 8. pause dai compound pcv deposit -/// 9. pause usdc compound pcv deposit - -contract vip14 is DSTest, IVIP { - using SafeCast for uint256; - using SafeERC20 for IERC20; - Vm public constant vm = Vm(HEVM_ADDRESS); - - 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; - ITimelockSimulation.action[] private arbitrumProposal; - - CompoundPCVRouter public router = - CompoundPCVRouter(MainnetAddresses.MORPHO_COMPOUND_PCV_ROUTER); - MorphoPCVDeposit public daiDeposit = - MorphoPCVDeposit(MainnetAddresses.MORPHO_COMPOUND_DAI_PCV_DEPOSIT); - MorphoPCVDeposit public usdcDeposit = - MorphoPCVDeposit(MainnetAddresses.MORPHO_COMPOUND_USDC_PCV_DEPOSIT); - - PCVGuardian public immutable pcvGuardian = - PCVGuardian(MainnetAddresses.PCV_GUARDIAN); - - ERC20Allocator public immutable allocator = - ERC20Allocator(MainnetAddresses.ERC20ALLOCATOR); - - constructor() { - if (block.chainid != 1) return; /// keep ci pipeline happy - - 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.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" - }) - ); - } - - function getMainnetProposal() - public - view - override - returns (ITimelockSimulation.action[] memory) - { - return mainnetProposal; - } - - /// move all funds from compound deposits to morpho deposits - 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); - 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 - function mainnetValidate() public override { - assertEq(address(usdcDeposit.core()), core); - 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) - ); - - /// pcv guardian whitelist assertions - 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 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( - 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); - } - - function getArbitrumProposal() - public - pure - override - returns (ITimelockSimulation.action[] memory) - { - revert("no arbitrum proposal"); - } - - /// no-op, nothing to setup - function arbitrumSetup() public pure override { - revert("no arbitrum proposal"); - } - - function arbitrumValidate() public pure override { - revert("no arbitrum proposal"); - } -} From bd6faf7fd878666be490d176b32f2c27e7173da3 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 19 Jan 2023 20:53:16 -0800 Subject: [PATCH 14/23] Euler PCV Deposit --- src/pcv/euler/EulerPCVDeposit.sol | 77 +++++++++++++++++++++++++++++++ src/pcv/euler/IEToken.sol | 21 +++++++++ 2 files changed, 98 insertions(+) create mode 100644 src/pcv/euler/EulerPCVDeposit.sol create mode 100644 src/pcv/euler/IEToken.sol diff --git a/src/pcv/euler/EulerPCVDeposit.sol b/src/pcv/euler/EulerPCVDeposit.sol new file mode 100644 index 000000000..96abe9c91 --- /dev/null +++ b/src/pcv/euler/EulerPCVDeposit.sol @@ -0,0 +1,77 @@ +// 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 {IEToken} from "@voltprotocol/pcv/euler/IEToken.sol"; +import {CoreRefV2} from "@voltprotocol/refs/CoreRefV2.sol"; +import {PCVDepositV2} from "@voltprotocol/pcv/PCVDepositV2.sol"; + +/// @notice PCV Deposit for Euler +contract EulerPCVDeposit is PCVDepositV2 { + using SafeERC20 for IERC20; + + /// @notice euler main contract that receives tokens + address public immutable eulerMain; + + /// @notice reference to the euler token that represents an asset + IEToken public immutable eToken; + + /// @notice sub-account id for euler + uint256 public constant subAccountId = 0; + + /// @notice fetch underlying asset by calling pool and getting liquidity asset + /// @param _core reference to the Core contract + /// @param _eToken reference to the euler asset token + /// @param _eulerMain reference to the euler main address + constructor( + address _core, + address _eToken, + address _eulerMain, + address _underlying, + address _rewardToken + ) PCVDepositV2(_underlying, _rewardToken) CoreRefV2(_core) { + eToken = IEToken(_eToken); + eulerMain = _eulerMain; + address underlying = IEToken(_eToken).underlyingAsset(); + + require(underlying == _underlying, "EulerDeposit: underlying mismatch"); + } + + /// @notice return the amount of funds this contract owns in underlying tokens + function balance() public view override returns (uint256) { + return eToken.balanceOfUnderlying(address(this)); + } + + /// @notice deposit PCV into Euler. + function _supply(uint256 amount) internal override { + /// approve euler main to spend underlying token + IERC20(token).approve(eulerMain, amount); + /// deposit into eToken + eToken.deposit(subAccountId, amount); + } + + /// @notice withdraw PCV from Euler, only callable by PCV controller + /// @param to destination after funds are withdrawn from venue + /// @param amount of PCV to withdraw from the venue + function _withdrawAndTransfer( + uint256 amount, + address to + ) internal override { + eToken.withdraw(subAccountId, amount); + IERC20(token).safeTransfer(to, amount); + } + + /// @notice this is a no-op + /// euler distributes tokens through a merkle drop, + /// no need for claim functionality + function _claim() internal pure override returns (uint256) { + return 0; + } + + /// @notice this is a no-op + /// euler automatically gets the most up to date balance of each user + /// and does not require any poking + function _accrueUnderlying() internal override {} +} diff --git a/src/pcv/euler/IEToken.sol b/src/pcv/euler/IEToken.sol new file mode 100644 index 000000000..236a79140 --- /dev/null +++ b/src/pcv/euler/IEToken.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity =0.8.13; + +interface IEToken { + /// @notice withdraw from euler + function withdraw(uint256 subAccountId, uint256 amount) external; + + /// @notice deposit into euler + function deposit(uint256 subAccountId, uint256 amount) external; + + /// @notice returns balance of underlying including all interest accrued + function balanceOfUnderlying( + address account + ) external view returns (uint256); + + /// @notice returns address of underlying token + function underlyingAsset() external view returns (address); + + /// @notice returns balance of address + function balanceOf(address) external view returns (uint256); +} From 0561407cf6a3f93d38a480777c17f25e05dbbb25 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 19 Jan 2023 20:54:33 -0800 Subject: [PATCH 15/23] Morpho AAVE PCV Deposit --- src/pcv/morpho/MorphoAavePCVDeposit.sol | 125 ++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 src/pcv/morpho/MorphoAavePCVDeposit.sol diff --git a/src/pcv/morpho/MorphoAavePCVDeposit.sol b/src/pcv/morpho/MorphoAavePCVDeposit.sol new file mode 100644 index 000000000..948e38b18 --- /dev/null +++ b/src/pcv/morpho/MorphoAavePCVDeposit.sol @@ -0,0 +1,125 @@ +// 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 {ILens} from "@voltprotocol/pcv/morpho/ILens.sol"; +import {IMorpho} from "@voltprotocol/pcv/morpho/IMorpho.sol"; +import {CoreRefV2} from "@voltprotocol/refs/CoreRefV2.sol"; +import {Constants} from "@voltprotocol/Constants.sol"; +import {PCVDepositV2} from "@voltprotocol/pcv/PCVDepositV2.sol"; + +/// @notice PCV Deposit for Morpho-Aave 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. +/// @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 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. +/// @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. +/// TODO confirm that Aave 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 MorphoAavePCVDeposit is PCVDepositV2 { + using SafeERC20 for IERC20; + + /// ------------------------------------------ + /// ---------- Immutables/Constant ----------- + /// ------------------------------------------ + + /// @notice reference to the lens contract for morpho-aave v2 + address public immutable lens; + + /// @notice reference to the morpho-aave v2 market + address public immutable morpho; + + /// @notice aToken in aave this deposit tracks + /// used to inform morpho about the desired market to supply liquidity + address public immutable aToken; + + /// @param _core reference to the core contract + /// @param _aToken aToken this deposit references + /// @param _underlying Token denomination of this deposit + /// @param _rewardToken Reward 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 _aToken, + address _underlying, + address _rewardToken, + address _morpho, + address _lens + ) PCVDepositV2(_underlying, _rewardToken) CoreRefV2(_core) { + aToken = _aToken; + morpho = _morpho; + 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 aToken + function balance() public view override returns (uint256) { + (, , uint256 totalSupplied) = ILens(lens).getCurrentSupplyBalanceInOf( + aToken, + address(this) + ); + + return totalSupplied; + } + + /// ------------------------------------------ + /// ------------- Helper Methods ------------- + /// ------------------------------------------ + + /// @notice accrue interest in the underlying morpho venue + function _accrueUnderlying() internal override { + /// accrue interest in Morpho Aave + IMorpho(morpho).updateIndexes(aToken); + } + + /// @dev withdraw from the underlying morpho market. + function _withdrawAndTransfer( + uint256 amount, + address to + ) internal override { + IMorpho(morpho).withdraw(aToken, amount); + IERC20(token).safeTransfer(to, amount); + } + + /// @dev deposit in the underlying morpho market. + function _supply(uint256 amount) internal override { + IERC20(token).approve(address(morpho), amount); + IMorpho(morpho).supply( + aToken, /// aToken to supply liquidity to + address(this), /// the address of the user you want to supply on behalf of + amount + ); + } + + /// @dev claim rewards from the underlying aave market. + /// returns amount of reward tokens claimed + function _claim() internal override returns (uint256) { + address[] memory aTokens = new address[](1); + aTokens[0] = aToken; + + return IMorpho(morpho).claimRewards(aTokens, false); /// bool set false to receive COMP + } +} From 7c707214ffc75f412a9bd2f1aeaef1001f13e8d9 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 19 Jan 2023 20:55:12 -0800 Subject: [PATCH 16/23] move balanceReportedIn to PCV Deposit V2 --- src/pcv/PCVDepositV2.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/pcv/PCVDepositV2.sol b/src/pcv/PCVDepositV2.sol index f13d5b76f..72892e14d 100644 --- a/src/pcv/PCVDepositV2.sol +++ b/src/pcv/PCVDepositV2.sol @@ -45,6 +45,15 @@ abstract contract PCVDepositV2 is IPCVDepositV2, CoreRefV2 { rewardToken = _rewardToken; } + /// ------------------------------------------ + /// -------------- View Only API ------------- + /// ------------------------------------------ + + /// @notice return the underlying token denomination for this deposit + function balanceReportedIn() external view returns (address) { + return address(token); + } + /// ------------------------------------------ /// ----------- Permissionless API ----------- /// ------------------------------------------ From 2fc0868c2dc6ea19416ffaac5a05e8879df06328 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 19 Jan 2023 20:55:35 -0800 Subject: [PATCH 17/23] Morpho Aave and Euler PCV Deposit Tests --- ...egrationTestMorphoCompoundPCVDeposit.t.sol | 2 +- ...IntegrationTestCompoundBadDebtSentinel.sol | 10 +- .../IntegrationTestEulerPCVDeposit.sol | 129 +++++++++++++++++ .../IntegrationTestMorphoAavePCVDeposit.sol | 133 ++++++++++++++++++ .../IntegrationTestPCVGuardian.sol | 15 +- .../IntegrationTestPCVOracle.sol | 4 +- .../IntegrationTestPCVRouter.sol | 4 +- .../IntegrationTestRateLimiters.sol | 8 +- .../IntegrationTestRoles.sol | 44 +++++- 9 files changed, 328 insertions(+), 21 deletions(-) create mode 100644 test/integration/post-proposal-checks/IntegrationTestEulerPCVDeposit.sol create mode 100644 test/integration/post-proposal-checks/IntegrationTestMorphoAavePCVDeposit.sol diff --git a/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol b/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol index 34cb4b449..a45681253 100644 --- a/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol +++ b/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol @@ -90,7 +90,7 @@ contract IntegrationTestMorphoPCVDeposit is Test { vm.label(address(usdc), "USDC"); vm.label(address(dai), "DAI"); vm.label(0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67, "Morpho Lens"); - vm.label(0x8888882f8f843896699869179fB6E4f7e3B58888, "Morpho"); + vm.label(0x8888882f8f843896699869179fB6E4f7e3B58888, "MORPHO_COMPOUND"); vm.startPrank(DAI_USDC_USDT_CURVE_POOL); dai.transfer(address(daiDeposit), targetDaiBalance); diff --git a/test/integration/post-proposal-checks/IntegrationTestCompoundBadDebtSentinel.sol b/test/integration/post-proposal-checks/IntegrationTestCompoundBadDebtSentinel.sol index bcc2fe496..62f895bd8 100644 --- a/test/integration/post-proposal-checks/IntegrationTestCompoundBadDebtSentinel.sol +++ b/test/integration/post-proposal-checks/IntegrationTestCompoundBadDebtSentinel.sol @@ -17,10 +17,10 @@ contract IntegrationTestCompoundBadDebtSentinel is PostProposalCheck { addresses.mainnet("PCV_GUARDIAN") ); IPCVDepositV2 daiDeposit = IPCVDepositV2( - addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI") ); IPCVDepositV2 usdcDeposit = IPCVDepositV2( - addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC") ); address yearn = 0x342491C093A640c7c2347c4FFA7D8b9cBC84D1EB; @@ -58,17 +58,17 @@ contract IntegrationTestCompoundBadDebtSentinel is PostProposalCheck { addresses.mainnet("COMPOUND_BAD_DEBT_SENTINEL") ); IPCVDepositV2 daiDeposit = IPCVDepositV2( - addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI") ); IPCVDepositV2 usdcDeposit = IPCVDepositV2( - addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC") ); address yearn = 0x342491C093A640c7c2347c4FFA7D8b9cBC84D1EB; address[] memory users = new address[](2); users[0] = yearn; /// yearn is less than morpho, place it first to order list - users[1] = addresses.mainnet("MORPHO"); + users[1] = addresses.mainnet("MORPHO_COMPOUND"); assertEq(badDebtSentinel.getTotalBadDebt(users), 0); diff --git a/test/integration/post-proposal-checks/IntegrationTestEulerPCVDeposit.sol b/test/integration/post-proposal-checks/IntegrationTestEulerPCVDeposit.sol new file mode 100644 index 000000000..8ff97ccf2 --- /dev/null +++ b/test/integration/post-proposal-checks/IntegrationTestEulerPCVDeposit.sol @@ -0,0 +1,129 @@ +// 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 {console} from "@forge-std/console.sol"; +import {PCVGuardian} from "@voltprotocol/pcv/PCVGuardian.sol"; +import {SystemEntry} from "@voltprotocol/entry/SystemEntry.sol"; +import {IPCVDepositV2} from "@voltprotocol/pcv/IPCVDepositV2.sol"; +import {EulerPCVDeposit} from "@voltprotocol/pcv/euler/EulerPCVDeposit.sol"; +import {PostProposalCheck} from "@test/integration/post-proposal-checks/PostProposalCheck.sol"; + +contract IntegrationTestEulerPCVDeposit is PostProposalCheck { + using SafeCast for *; + + uint256 depositAmount = 1_000_000; + + function testCanDepositEuler() public { + SystemEntry entry = SystemEntry(addresses.mainnet("SYSTEM_ENTRY")); + EulerPCVDeposit daiDeposit = EulerPCVDeposit( + addresses.mainnet("PCV_DEPOSIT_EULER_DAI") + ); + EulerPCVDeposit usdcDeposit = EulerPCVDeposit( + addresses.mainnet("PCV_DEPOSIT_EULER_USDC") + ); + + deal( + addresses.mainnet("DAI"), + address(daiDeposit), + depositAmount * 1e18 + ); + deal( + addresses.mainnet("USDC"), + address(usdcDeposit), + depositAmount * 1e6 + ); + + entry.deposit(address(daiDeposit)); + entry.deposit(address(usdcDeposit)); + + assertApproxEq( + daiDeposit.balance().toInt256(), + (depositAmount * 1e18).toInt256(), + 0 + ); + assertApproxEq( + usdcDeposit.balance().toInt256(), + (depositAmount * 1e6).toInt256(), + 0 + ); + } + + function testHarvestEuler() public { + testCanDepositEuler(); + + SystemEntry entry = SystemEntry(addresses.mainnet("SYSTEM_ENTRY")); + EulerPCVDeposit daiDeposit = EulerPCVDeposit( + addresses.mainnet("PCV_DEPOSIT_EULER_DAI") + ); + EulerPCVDeposit usdcDeposit = EulerPCVDeposit( + addresses.mainnet("PCV_DEPOSIT_EULER_USDC") + ); + + entry.harvest(address(daiDeposit)); + entry.harvest(address(usdcDeposit)); + } + + function testAccrueEuler() public { + testCanDepositEuler(); + + SystemEntry entry = SystemEntry(addresses.mainnet("SYSTEM_ENTRY")); + EulerPCVDeposit daiDeposit = EulerPCVDeposit( + addresses.mainnet("PCV_DEPOSIT_EULER_DAI") + ); + EulerPCVDeposit usdcDeposit = EulerPCVDeposit( + addresses.mainnet("PCV_DEPOSIT_EULER_USDC") + ); + + uint256 daiBalance = entry.accrue(address(daiDeposit)); + uint256 usdcBalance = entry.accrue(address(usdcDeposit)); + + assertApproxEq( + daiBalance.toInt256(), + (depositAmount * 1e18).toInt256(), + 0 + ); + assertApproxEq( + usdcBalance.toInt256(), + (depositAmount * 1e6).toInt256(), + 0 + ); + } + + function testWithdrawPCVGuardian() public { + testCanDepositEuler(); + + PCVGuardian pcvGuardian = PCVGuardian( + addresses.mainnet("PCV_GUARDIAN") + ); + + address[2] memory addressesToClean = [ + addresses.mainnet("PCV_DEPOSIT_EULER_DAI"), + addresses.mainnet("PCV_DEPOSIT_EULER_USDC") + ]; + + vm.startPrank(addresses.mainnet("GOVERNOR")); + for (uint256 i = 0; i < addressesToClean.length; i++) { + pcvGuardian.withdrawAllToSafeAddress(addressesToClean[i]); + // Check only dust left after withdrawals + assertLt(IPCVDepositV2(addressesToClean[i]).balance(), 1e6); + } + vm.stopPrank(); + + // sanity checks + address safeAddress = pcvGuardian.safeAddress(); + require(safeAddress != address(0), "Safe address is 0 address"); + assertApproxEq( + IERC20(addresses.mainnet("DAI")).balanceOf(safeAddress).toInt256(), + (depositAmount * 1e18).toInt256(), + 0 + ); // ~1M DAI + assertApproxEq( + IERC20(addresses.mainnet("USDC")).balanceOf(safeAddress).toInt256(), + (depositAmount * 1e6).toInt256(), + 0 + ); // ~1M USDC + } +} diff --git a/test/integration/post-proposal-checks/IntegrationTestMorphoAavePCVDeposit.sol b/test/integration/post-proposal-checks/IntegrationTestMorphoAavePCVDeposit.sol new file mode 100644 index 000000000..74fd8faef --- /dev/null +++ b/test/integration/post-proposal-checks/IntegrationTestMorphoAavePCVDeposit.sol @@ -0,0 +1,133 @@ +// 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 {console} from "@forge-std/console.sol"; +import {PCVGuardian} from "@voltprotocol/pcv/PCVGuardian.sol"; +import {SystemEntry} from "@voltprotocol/entry/SystemEntry.sol"; +import {IPCVDepositV2} from "@voltprotocol/pcv/IPCVDepositV2.sol"; +import {PostProposalCheck} from "@test/integration/post-proposal-checks/PostProposalCheck.sol"; +import {MorphoAavePCVDeposit} from "@voltprotocol/pcv/morpho/MorphoAavePCVDeposit.sol"; + +contract IntegrationTestMorphoAavePCVDeposit is PostProposalCheck { + using SafeCast for *; + + uint256 depositAmount = 1_000_000; + + function testCanDepositAave() public { + SystemEntry entry = SystemEntry(addresses.mainnet("SYSTEM_ENTRY")); + MorphoAavePCVDeposit daiDeposit = MorphoAavePCVDeposit( + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_DAI") + ); + MorphoAavePCVDeposit usdcDeposit = MorphoAavePCVDeposit( + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_USDC") + ); + + deal( + addresses.mainnet("DAI"), + address(daiDeposit), + depositAmount * 1e18 + ); + deal( + addresses.mainnet("USDC"), + address(usdcDeposit), + depositAmount * 1e6 + ); + + entry.deposit(address(daiDeposit)); + entry.deposit(address(usdcDeposit)); + + assertApproxEq( + daiDeposit.balance().toInt256(), + (depositAmount * 1e18).toInt256(), + 0 + ); + assertApproxEq( + usdcDeposit.balance().toInt256(), + (depositAmount * 1e6).toInt256(), + 0 + ); + } + + /// liquidity mining is over for aave, so harvesting fails + function testHarvestFailsAave() public { + testCanDepositAave(); + + SystemEntry entry = SystemEntry(addresses.mainnet("SYSTEM_ENTRY")); + MorphoAavePCVDeposit daiDeposit = MorphoAavePCVDeposit( + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_DAI") + ); + MorphoAavePCVDeposit usdcDeposit = MorphoAavePCVDeposit( + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_USDC") + ); + + vm.expectRevert(); + entry.harvest(address(daiDeposit)); + + vm.expectRevert(); + entry.harvest(address(usdcDeposit)); + } + + function testAccrueAave() public { + testCanDepositAave(); + + SystemEntry entry = SystemEntry(addresses.mainnet("SYSTEM_ENTRY")); + MorphoAavePCVDeposit daiDeposit = MorphoAavePCVDeposit( + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_DAI") + ); + MorphoAavePCVDeposit usdcDeposit = MorphoAavePCVDeposit( + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_USDC") + ); + + uint256 daiBalance = entry.accrue(address(daiDeposit)); + uint256 usdcBalance = entry.accrue(address(usdcDeposit)); + + assertApproxEq( + daiBalance.toInt256(), + (depositAmount * 1e18).toInt256(), + 0 + ); + assertApproxEq( + usdcBalance.toInt256(), + (depositAmount * 1e6).toInt256(), + 0 + ); + } + + function testWithdrawPCVGuardian() public { + testCanDepositAave(); + + PCVGuardian pcvGuardian = PCVGuardian( + addresses.mainnet("PCV_GUARDIAN") + ); + + address[2] memory addressesToClean = [ + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_DAI"), + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_USDC") + ]; + + vm.startPrank(addresses.mainnet("GOVERNOR")); + for (uint256 i = 0; i < addressesToClean.length; i++) { + pcvGuardian.withdrawAllToSafeAddress(addressesToClean[i]); + // Check only dust left after withdrawals + assertLt(IPCVDepositV2(addressesToClean[i]).balance(), 1e6); + } + vm.stopPrank(); + + // sanity checks + address safeAddress = pcvGuardian.safeAddress(); + require(safeAddress != address(0), "Safe address is 0 address"); + assertApproxEq( + IERC20(addresses.mainnet("DAI")).balanceOf(safeAddress).toInt256(), + (depositAmount * 1e18).toInt256(), + 0 + ); // ~1M DAI + assertApproxEq( + IERC20(addresses.mainnet("USDC")).balanceOf(safeAddress).toInt256(), + (depositAmount * 1e6).toInt256(), + 0 + ); // ~1M USDC + } +} diff --git a/test/integration/post-proposal-checks/IntegrationTestPCVGuardian.sol b/test/integration/post-proposal-checks/IntegrationTestPCVGuardian.sol index 151d445cf..7a070392f 100644 --- a/test/integration/post-proposal-checks/IntegrationTestPCVGuardian.sol +++ b/test/integration/post-proposal-checks/IntegrationTestPCVGuardian.sol @@ -14,14 +14,21 @@ contract IntegrationTestPCVGuardian is PostProposalCheck { ); vm.startPrank(addresses.mainnet("GOVERNOR")); - address[4] memory addressesToClean = [ + address[8] memory addressesToClean = [ addresses.mainnet("PSM_DAI"), addresses.mainnet("PSM_USDC"), - addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI"), - addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI"), + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC"), + addresses.mainnet("PCV_DEPOSIT_EULER_DAI"), + addresses.mainnet("PCV_DEPOSIT_EULER_USDC"), + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_DAI"), + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_DAI") ]; for (uint256 i = 0; i < addressesToClean.length; i++) { - pcvGuardian.withdrawAllToSafeAddress(addressesToClean[i]); + if (IPCVDepositV2(addressesToClean[i]).balance() != 0) { + pcvGuardian.withdrawAllToSafeAddress(addressesToClean[i]); + } + // Check only dust left after withdrawals assertLt(IPCVDepositV2(addressesToClean[i]).balance(), 1e6); } diff --git a/test/integration/post-proposal-checks/IntegrationTestPCVOracle.sol b/test/integration/post-proposal-checks/IntegrationTestPCVOracle.sol index 95848354e..dfc904833 100644 --- a/test/integration/post-proposal-checks/IntegrationTestPCVOracle.sol +++ b/test/integration/post-proposal-checks/IntegrationTestPCVOracle.sol @@ -29,7 +29,9 @@ contract IntegrationTestPCVOracle is PostProposalCheck { dai = IERC20(addresses.mainnet("DAI")); volt = VoltV2(addresses.mainnet("VOLT")); grlm = addresses.mainnet("GLOBAL_RATE_LIMITED_MINTER"); - morphoDaiPCVDeposit = addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI"); + morphoDaiPCVDeposit = addresses.mainnet( + "PCV_DEPOSIT_MORPHO_COMPOUND_DAI" + ); daipsm = PegStabilityModule(addresses.mainnet("PSM_DAI")); pcvOracle = PCVOracle(addresses.mainnet("PCV_ORACLE")); pcvGuardian = PCVGuardian(addresses.mainnet("PCV_GUARDIAN")); diff --git a/test/integration/post-proposal-checks/IntegrationTestPCVRouter.sol b/test/integration/post-proposal-checks/IntegrationTestPCVRouter.sol index 4cefd02eb..43acc9fda 100644 --- a/test/integration/post-proposal-checks/IntegrationTestPCVRouter.sol +++ b/test/integration/post-proposal-checks/IntegrationTestPCVRouter.sol @@ -15,10 +15,10 @@ contract IntegrationTestPCVRouter is PostProposalCheck { function testPcvRouterWithSwap() public { PCVRouter pcvRouter = PCVRouter(addresses.mainnet("PCV_ROUTER")); IPCVDepositV2 daiDeposit = IPCVDepositV2( - addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI") ); IPCVDepositV2 usdcDeposit = IPCVDepositV2( - addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC") ); address pcvMover = addresses.mainnet("GOVERNOR"); // an address with PCV_MOVER role diff --git a/test/integration/post-proposal-checks/IntegrationTestRateLimiters.sol b/test/integration/post-proposal-checks/IntegrationTestRateLimiters.sol index f554eae99..3b7937891 100644 --- a/test/integration/post-proposal-checks/IntegrationTestRateLimiters.sol +++ b/test/integration/post-proposal-checks/IntegrationTestRateLimiters.sol @@ -60,8 +60,12 @@ contract IntegrationTestRateLimiters is PostProposalCheck { addresses.mainnet("GLOBAL_SYSTEM_EXIT_RATE_LIMITER") ); pcvOracle = PCVOracle(addresses.mainnet("PCV_ORACLE")); - morphoUsdcPCVDeposit = addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC"); - morphoDaiPCVDeposit = addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI"); + morphoUsdcPCVDeposit = addresses.mainnet( + "PCV_DEPOSIT_MORPHO_COMPOUND_USDC" + ); + morphoDaiPCVDeposit = addresses.mainnet( + "PCV_DEPOSIT_MORPHO_COMPOUND_DAI" + ); } /* diff --git a/test/integration/post-proposal-checks/IntegrationTestRoles.sol b/test/integration/post-proposal-checks/IntegrationTestRoles.sol index 14db32256..e6b38b3d5 100644 --- a/test/integration/post-proposal-checks/IntegrationTestRoles.sol +++ b/test/integration/post-proposal-checks/IntegrationTestRoles.sol @@ -67,14 +67,30 @@ contract IntegrationTestRoles is PostProposalCheck { // PCV_DEPOSIT_ROLE assertEq(core.getRoleAdmin(VoltRoles.PCV_DEPOSIT), VoltRoles.GOVERNOR); - assertEq(core.getRoleMemberCount(VoltRoles.PCV_DEPOSIT), 2); + assertEq(core.getRoleMemberCount(VoltRoles.PCV_DEPOSIT), 6); assertEq( core.getRoleMember(VoltRoles.PCV_DEPOSIT, 0), - addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI") ); assertEq( core.getRoleMember(VoltRoles.PCV_DEPOSIT, 1), - addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC") + ); + assertEq( + core.getRoleMember(VoltRoles.PCV_DEPOSIT, 2), + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_DAI") + ); + assertEq( + core.getRoleMember(VoltRoles.PCV_DEPOSIT, 3), + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_USDC") + ); + assertEq( + core.getRoleMember(VoltRoles.PCV_DEPOSIT, 4), + addresses.mainnet("PCV_DEPOSIT_EULER_DAI") + ); + assertEq( + core.getRoleMember(VoltRoles.PCV_DEPOSIT, 5), + addresses.mainnet("PCV_DEPOSIT_EULER_USDC") ); // PCV_GUARD @@ -157,7 +173,7 @@ contract IntegrationTestRoles is PostProposalCheck { // LOCKER_ROLE assertEq(core.getRoleAdmin(VoltRoles.LOCKER), VoltRoles.GOVERNOR); - assertEq(core.getRoleMemberCount(VoltRoles.LOCKER), 13); + assertEq(core.getRoleMemberCount(VoltRoles.LOCKER), 17); assertEq( core.getRoleMember(VoltRoles.LOCKER, 0), addresses.mainnet("SYSTEM_ENTRY") @@ -180,11 +196,11 @@ contract IntegrationTestRoles is PostProposalCheck { ); assertEq( core.getRoleMember(VoltRoles.LOCKER, 5), - addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI") ); assertEq( core.getRoleMember(VoltRoles.LOCKER, 6), - addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC") ); assertEq( core.getRoleMember(VoltRoles.LOCKER, 7), @@ -210,6 +226,22 @@ contract IntegrationTestRoles is PostProposalCheck { core.getRoleMember(VoltRoles.LOCKER, 12), addresses.mainnet("PSM_NONCUSTODIAL_USDC") ); + assertEq( + core.getRoleMember(VoltRoles.LOCKER, 13), + addresses.mainnet("PCV_DEPOSIT_EULER_DAI") + ); + assertEq( + core.getRoleMember(VoltRoles.LOCKER, 14), + addresses.mainnet("PCV_DEPOSIT_EULER_USDC") + ); + assertEq( + core.getRoleMember(VoltRoles.LOCKER, 15), + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_DAI") + ); + assertEq( + core.getRoleMember(VoltRoles.LOCKER, 16), + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_USDC") + ); // MINTER assertEq(core.getRoleAdmin(VoltRoles.MINTER), VoltRoles.GOVERNOR); From dab899f51d441988807f176e4c8d2c5e1deb407e Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 19 Jan 2023 20:56:22 -0800 Subject: [PATCH 18/23] Add AAVE Morpho updateIndex function --- src/pcv/morpho/IMorpho.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pcv/morpho/IMorpho.sol b/src/pcv/morpho/IMorpho.sol index 417e448f9..f3c64102a 100644 --- a/src/pcv/morpho/IMorpho.sol +++ b/src/pcv/morpho/IMorpho.sol @@ -21,5 +21,9 @@ interface IMorpho { function liquidate(address _poolTokenBorrowedAddress, address _poolTokenCollateralAddress, address _borrower, uint256 _amount) external; function claimRewards(address[] calldata _cTokenAddresses, bool _tradeForMorphoToken) external returns (uint256 claimedAmount); + /// @notice compound accrue interest function function updateP2PIndexes(address _poolTokenAddress) external; + + /// @notice aave accrue interest function + function updateIndexes(address _poolTokenAddress) external; } From 0ce23ba8d94c5f27acd28222c59943baf284804f Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 19 Jan 2023 20:57:11 -0800 Subject: [PATCH 19/23] Deploy Euler and Morpho-Aave PCV Deposits --- test/proposals/Addresses.sol | 27 +++-- test/proposals/vips/vip15.sol | 2 +- test/proposals/vips/vip16.sol | 221 ++++++++++++++++++++++++++++------ test/proposals/vips/vip17.sol | 8 +- 4 files changed, 208 insertions(+), 50 deletions(-) diff --git a/test/proposals/Addresses.sol b/test/proposals/Addresses.sol index e0b446c8e..065cdef19 100644 --- a/test/proposals/Addresses.sol +++ b/test/proposals/Addresses.sol @@ -62,6 +62,11 @@ contract Addresses is Test { 0xF10d810De7F0Fbd455De30f8c43AbA56F253B73B ); + // ---------- EULER ADDRESSES ---------- + _addMainnet("EULER_MAIN", 0x27182842E098f60e3D576794A5bFFb0777E025d3); + _addMainnet("EUSDC", 0xEb91861f8A4e1C12333F42DCE8fB0Ecdc28dA716); + _addMainnet("EDAI", 0xe025E3ca2bE02316033184551D4d3Aa22024D9DC); + // ---------- ORACLE ADDRESSES ---------- _addMainnet( "ORACLE_PASS_THROUGH", @@ -126,6 +131,10 @@ contract Addresses is Test { 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B ); + // ---------- Aave ADDRESSES ---------- + _addMainnet("ADAI", 0x028171bCA77440897B824Ca71D1c56caC55b68A3); + _addMainnet("AUSDC", 0xBcca60bB61934080951369a648Fb03DF4F96263C); + // ---------- Maker ADDRESSES ---------- _addMainnet( "MAKER_DAI_USDC_PSM", @@ -187,18 +196,20 @@ contract Addresses is Test { ); // ---------- MORPHO ADDRESSES ---------- - _addMainnet("MORPHO", 0x8888882f8f843896699869179fB6E4f7e3B58888); - _addMainnet("MORPHO_LENS", 0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67); - // ---------- MAPLE ADDRESSES ---------- - _addMainnet("MPL_TOKEN", 0x33349B282065b0284d756F0577FB39c158F935e6); _addMainnet( - "MPL_ORTHOGONAL_POOL", - 0xFeBd6F15Df3B73DC4307B1d7E65D46413e710C27 + "MORPHO_COMPOUND", + 0x8888882f8f843896699869179fB6E4f7e3B58888 ); _addMainnet( - "MPL_ORTHOGONAL_REWARDS", - 0x7869D7a3B074b5fa484dc04798E254c9C06A5e90 + "MORPHO_LENS_COMPOUND", + 0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67 + ); + + _addMainnet("MORPHO_AAVE", 0x777777c9898D384F785Ee44Acfe945efDFf5f3E0); + _addMainnet( + "MORPHO_LENS_AAVE", + 0x507fA343d0A90786d86C7cd885f5C49263A91FF4 ); } diff --git a/test/proposals/vips/vip15.sol b/test/proposals/vips/vip15.sol index 94da07cd2..945e8e048 100644 --- a/test/proposals/vips/vip15.sol +++ b/test/proposals/vips/vip15.sol @@ -28,7 +28,7 @@ contract vip15 is TimelockProposal { /// ------- poke morpho to update p2p indexes ------- _pushTimelockAction( - addresses.mainnet("MORPHO"), + addresses.mainnet("MORPHO_COMPOUND"), abi.encodeWithSignature( "updateP2PIndexes(address)", addresses.mainnet("CDAI") diff --git a/test/proposals/vips/vip16.sol b/test/proposals/vips/vip16.sol index 88b67e611..44a4ffad4 100644 --- a/test/proposals/vips/vip16.sol +++ b/test/proposals/vips/vip16.sol @@ -23,7 +23,9 @@ import {ERC20Allocator} from "@voltprotocol/pcv/ERC20Allocator.sol"; import {NonCustodialPSM} from "@voltprotocol/peg/NonCustodialPSM.sol"; import {MakerPCVSwapper} from "@voltprotocol/pcv/maker/MakerPCVSwapper.sol"; import {VoltSystemOracle} from "@voltprotocol/oracle/VoltSystemOracle.sol"; +import {EulerPCVDeposit} from "@voltprotocol/pcv/euler/EulerPCVDeposit.sol"; import {MorphoPCVDeposit} from "@voltprotocol/pcv/morpho/MorphoPCVDeposit.sol"; +import {MorphoAavePCVDeposit} from "@voltprotocol/pcv/morpho/MorphoAavePCVDeposit.sol"; import {PegStabilityModule} from "@voltprotocol/peg/PegStabilityModule.sol"; import {IPegStabilityModule} from "@voltprotocol/peg/IPegStabilityModule.sol"; import {ConstantPriceOracle} from "@voltprotocol/oracle/ConstantPriceOracle.sol"; @@ -149,31 +151,88 @@ contract vip16 is Proposal { /// PCV Deposits { - MorphoPCVDeposit morphoDaiPCVDeposit = new MorphoPCVDeposit( + /// Morpho Compound Deposits + MorphoPCVDeposit morphoCompoundDaiPCVDeposit = new MorphoPCVDeposit( addresses.mainnet("CORE"), addresses.mainnet("CDAI"), addresses.mainnet("DAI"), addresses.mainnet("COMP"), - addresses.mainnet("MORPHO"), - addresses.mainnet("MORPHO_LENS") + addresses.mainnet("MORPHO_COMPOUND"), + addresses.mainnet("MORPHO_LENS_COMPOUND") ); - MorphoPCVDeposit morphoUsdcPCVDeposit = new MorphoPCVDeposit( + MorphoPCVDeposit morphoCompoundUsdcPCVDeposit = new MorphoPCVDeposit( + addresses.mainnet("CORE"), + addresses.mainnet("CUSDC"), + addresses.mainnet("USDC"), + addresses.mainnet("COMP"), + addresses.mainnet("MORPHO_COMPOUND"), + addresses.mainnet("MORPHO_LENS_COMPOUND") + ); + + addresses.addMainnet( + "PCV_DEPOSIT_MORPHO_COMPOUND_DAI", + address(morphoCompoundDaiPCVDeposit) + ); + addresses.addMainnet( + "PCV_DEPOSIT_MORPHO_COMPOUND_USDC", + address(morphoCompoundUsdcPCVDeposit) + ); + } + { + /// Morpho Aave Deposits + MorphoAavePCVDeposit morphoAaveDaiPCVDeposit = new MorphoAavePCVDeposit( + addresses.mainnet("CORE"), + addresses.mainnet("ADAI"), + addresses.mainnet("DAI"), + address(0), + addresses.mainnet("MORPHO_AAVE"), + addresses.mainnet("MORPHO_LENS_AAVE") + ); + + MorphoAavePCVDeposit morphoAaveUsdcPCVDeposit = new MorphoAavePCVDeposit( + addresses.mainnet("CORE"), + addresses.mainnet("AUSDC"), + addresses.mainnet("USDC"), + address(0), + addresses.mainnet("MORPHO_AAVE"), + addresses.mainnet("MORPHO_LENS_AAVE") + ); + + addresses.addMainnet( + "PCV_DEPOSIT_MORPHO_AAVE_DAI", + address(morphoAaveDaiPCVDeposit) + ); + addresses.addMainnet( + "PCV_DEPOSIT_MORPHO_AAVE_USDC", + address(morphoAaveUsdcPCVDeposit) + ); + } + { + /// Euler Deposits + EulerPCVDeposit eulerDaiPCVDeposit = new EulerPCVDeposit( + addresses.mainnet("CORE"), + addresses.mainnet("EDAI"), + addresses.mainnet("EULER_MAIN"), + addresses.mainnet("DAI"), + address(0) + ); + + EulerPCVDeposit eulerUsdcPCVDeposit = new EulerPCVDeposit( addresses.mainnet("CORE"), - addresses.mainnet("CUSDC"), + addresses.mainnet("EUSDC"), + addresses.mainnet("EULER_MAIN"), addresses.mainnet("USDC"), - addresses.mainnet("COMP"), - addresses.mainnet("MORPHO"), - addresses.mainnet("MORPHO_LENS") + address(0) ); addresses.addMainnet( - "PCV_DEPOSIT_MORPHO_DAI", - address(morphoDaiPCVDeposit) + "PCV_DEPOSIT_EULER_DAI", + address(eulerDaiPCVDeposit) ); addresses.addMainnet( - "PCV_DEPOSIT_MORPHO_USDC", - address(morphoUsdcPCVDeposit) + "PCV_DEPOSIT_EULER_USDC", + address(eulerUsdcPCVDeposit) ); } @@ -217,7 +276,9 @@ contract vip16 is Proposal { IERC20(addresses.mainnet("USDC")), VOLT_FLOOR_PRICE_USDC, VOLT_CEILING_PRICE_USDC, - IPCVDeposit(addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC")) + IPCVDeposit( + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC") + ) ); NonCustodialPSM daiNonCustodialPsm = new NonCustodialPSM( addresses.mainnet("CORE"), @@ -228,7 +289,9 @@ contract vip16 is Proposal { IERC20(addresses.mainnet("DAI")), VOLT_FLOOR_PRICE_DAI, VOLT_CEILING_PRICE_DAI, - IPCVDeposit(addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI")) + IPCVDeposit( + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI") + ) ); ERC20Allocator allocator = new ERC20Allocator( addresses.mainnet("CORE") @@ -281,15 +344,27 @@ contract vip16 is Proposal { addresses.mainnet("CORE") ); - address[] memory pcvGuardianSafeAddresses = new address[](4); + address[] memory pcvGuardianSafeAddresses = new address[](8); pcvGuardianSafeAddresses[0] = addresses.mainnet( - "PCV_DEPOSIT_MORPHO_DAI" + "PCV_DEPOSIT_MORPHO_COMPOUND_DAI" ); pcvGuardianSafeAddresses[1] = addresses.mainnet( - "PCV_DEPOSIT_MORPHO_USDC" + "PCV_DEPOSIT_MORPHO_COMPOUND_USDC" ); pcvGuardianSafeAddresses[2] = addresses.mainnet("PSM_USDC"); pcvGuardianSafeAddresses[3] = addresses.mainnet("PSM_DAI"); + pcvGuardianSafeAddresses[4] = addresses.mainnet( + "PCV_DEPOSIT_EULER_DAI" + ); + pcvGuardianSafeAddresses[5] = addresses.mainnet( + "PCV_DEPOSIT_EULER_USDC" + ); + pcvGuardianSafeAddresses[6] = addresses.mainnet( + "PCV_DEPOSIT_MORPHO_AAVE_DAI" + ); + pcvGuardianSafeAddresses[7] = addresses.mainnet( + "PCV_DEPOSIT_MORPHO_AAVE_USDC" + ); PCVGuardian pcvGuardian = new PCVGuardian( addresses.mainnet("CORE"), @@ -414,11 +489,27 @@ contract vip16 is Proposal { core.createRole(VoltRoles.PCV_DEPOSIT, VoltRoles.GOVERNOR); core.grantRole( VoltRoles.PCV_DEPOSIT, - addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI") ); core.grantRole( VoltRoles.PCV_DEPOSIT, - addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC") + ); + core.grantRole( + VoltRoles.PCV_DEPOSIT, + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_DAI") + ); + core.grantRole( + VoltRoles.PCV_DEPOSIT, + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_USDC") + ); + core.grantRole( + VoltRoles.PCV_DEPOSIT, + addresses.mainnet("PCV_DEPOSIT_EULER_DAI") + ); + core.grantRole( + VoltRoles.PCV_DEPOSIT, + addresses.mainnet("PCV_DEPOSIT_EULER_USDC") ); core.grantPCVGuard(addresses.mainnet("EOA_1")); @@ -457,14 +548,18 @@ contract vip16 is Proposal { core.grantLocker(addresses.mainnet("PCV_ORACLE")); core.grantLocker(addresses.mainnet("PSM_DAI")); core.grantLocker(addresses.mainnet("PSM_USDC")); - core.grantLocker(addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI")); - core.grantLocker(addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC")); + core.grantLocker(addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI")); + core.grantLocker(addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC")); core.grantLocker(addresses.mainnet("GLOBAL_RATE_LIMITED_MINTER")); core.grantLocker(addresses.mainnet("GLOBAL_SYSTEM_EXIT_RATE_LIMITER")); core.grantLocker(addresses.mainnet("PCV_ROUTER")); core.grantLocker(addresses.mainnet("PCV_GUARDIAN")); core.grantLocker(addresses.mainnet("PSM_NONCUSTODIAL_DAI")); core.grantLocker(addresses.mainnet("PSM_NONCUSTODIAL_USDC")); + core.grantLocker(addresses.mainnet("PCV_DEPOSIT_EULER_DAI")); + core.grantLocker(addresses.mainnet("PCV_DEPOSIT_EULER_USDC")); + core.grantLocker(addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_DAI")); + core.grantLocker(addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_USDC")); core.grantMinter(addresses.mainnet("GLOBAL_RATE_LIMITED_MINTER")); @@ -481,21 +576,29 @@ contract vip16 is Proposal { ); allocator.connectDeposit( addresses.mainnet("PSM_USDC"), - addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC") ); allocator.connectDeposit( addresses.mainnet("PSM_DAI"), - addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI") ); /// Configure PCV Oracle - address[] memory venues = new address[](2); - venues[0] = addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI"); - venues[1] = addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC"); - - address[] memory oracles = new address[](2); + address[] memory venues = new address[](6); + venues[0] = addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI"); + venues[1] = addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC"); + venues[2] = addresses.mainnet("PCV_DEPOSIT_EULER_DAI"); + venues[3] = addresses.mainnet("PCV_DEPOSIT_EULER_USDC"); + venues[4] = addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_DAI"); + venues[5] = addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_USDC"); + + address[] memory oracles = new address[](6); oracles[0] = addresses.mainnet("ORACLE_CONSTANT_DAI"); oracles[1] = addresses.mainnet("ORACLE_CONSTANT_USDC"); + oracles[2] = addresses.mainnet("ORACLE_CONSTANT_DAI"); + oracles[3] = addresses.mainnet("ORACLE_CONSTANT_USDC"); + oracles[4] = addresses.mainnet("ORACLE_CONSTANT_DAI"); + oracles[5] = addresses.mainnet("ORACLE_CONSTANT_USDC"); pcvOracle.addVenues(venues, oracles); @@ -593,13 +696,41 @@ contract vip16 is Proposal { ); assertEq( address( - CoreRefV2(addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI")).core() + CoreRefV2(addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI")) + .core() ), address(core) ); assertEq( address( - CoreRefV2(addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC")).core() + CoreRefV2(addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC")) + .core() + ), + address(core) + ); + assertEq( + address( + CoreRefV2(addresses.mainnet("PCV_DEPOSIT_EULER_DAI")).core() + ), + address(core) + ); + assertEq( + address( + CoreRefV2(addresses.mainnet("PCV_DEPOSIT_EULER_USDC")).core() + ), + address(core) + ); + assertEq( + address( + CoreRefV2(addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_DAI")) + .core() + ), + address(core) + ); + assertEq( + address( + CoreRefV2(addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_USDC")) + .core() ), address(core) ); @@ -690,13 +821,13 @@ contract vip16 is Proposal { // psm allocator assertEq( allocator.pcvDepositToPSM( - addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC") ), addresses.mainnet("PSM_USDC") ); assertEq( allocator.pcvDepositToPSM( - addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI") ), addresses.mainnet("PSM_DAI") ); @@ -710,14 +841,30 @@ contract vip16 is Proposal { assertEq(psmToken2, addresses.mainnet("USDC")); // pcv oracle - assertEq(pcvOracle.getVenues().length, 2); + assertEq(pcvOracle.getVenues().length, 6); assertEq( pcvOracle.getVenues()[0], - addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI") ); assertEq( pcvOracle.getVenues()[1], - addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC") + ); + assertEq( + pcvOracle.getVenues()[2], + addresses.mainnet("PCV_DEPOSIT_EULER_DAI") + ); + assertEq( + pcvOracle.getVenues()[3], + addresses.mainnet("PCV_DEPOSIT_EULER_USDC") + ); + assertEq( + pcvOracle.getVenues()[4], + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_DAI") + ); + assertEq( + pcvOracle.getVenues()[5], + addresses.mainnet("PCV_DEPOSIT_MORPHO_AAVE_USDC") ); // pcv router @@ -745,12 +892,12 @@ contract vip16 is Proposal { /// compound bad debt sentinel assertTrue( badDebtSentinel.isCompoundPcvDeposit( - addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC") ) ); assertTrue( badDebtSentinel.isCompoundPcvDeposit( - addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI") ) ); assertEq( diff --git a/test/proposals/vips/vip17.sol b/test/proposals/vips/vip17.sol index 4bab0f566..1cf0eccf1 100644 --- a/test/proposals/vips/vip17.sol +++ b/test/proposals/vips/vip17.sol @@ -63,7 +63,7 @@ contract vip17 is MultisigProposal { addresses.mainnet("USDC"), abi.encodeWithSignature( "transfer(address,uint256)", - addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC"), + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC"), PCV_USDC - PSM_LIQUID_RESERVE * 1e6 ), "Send Protocol USDC to Morpho-Compound USDC Deposit" @@ -73,7 +73,7 @@ contract vip17 is MultisigProposal { addresses.mainnet("SYSTEM_ENTRY"), abi.encodeWithSignature( "deposit(address)", - addresses.mainnet("PCV_DEPOSIT_MORPHO_USDC") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_USDC") ), "Deposit USDC" ); @@ -92,7 +92,7 @@ contract vip17 is MultisigProposal { addresses.mainnet("DAI"), abi.encodeWithSignature( "transfer(address,uint256)", - addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI"), + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI"), PCV_DAI - PSM_LIQUID_RESERVE * 1e18 ), "Send Protocol DAI to Morpho-Compound DAI Deposit" @@ -102,7 +102,7 @@ contract vip17 is MultisigProposal { addresses.mainnet("SYSTEM_ENTRY"), abi.encodeWithSignature( "deposit(address)", - addresses.mainnet("PCV_DEPOSIT_MORPHO_DAI") + addresses.mainnet("PCV_DEPOSIT_MORPHO_COMPOUND_DAI") ), "Deposit DAI" ); From 98b43cfc4b1efd87d53c63811a3c56ef82c24168 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 19 Jan 2023 20:57:40 -0800 Subject: [PATCH 20/23] remove compound pcv deposit --- src/pcv/morpho/MorphoCompoundPCVDeposit.sol | 269 -------------------- 1 file changed, 269 deletions(-) delete mode 100644 src/pcv/morpho/MorphoCompoundPCVDeposit.sol diff --git a/src/pcv/morpho/MorphoCompoundPCVDeposit.sol b/src/pcv/morpho/MorphoCompoundPCVDeposit.sol deleted file mode 100644 index 452c75d92..000000000 --- a/src/pcv/morpho/MorphoCompoundPCVDeposit.sol +++ /dev/null @@ -1,269 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.13; - -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import {ILens} from "@voltprotocol/pcv/morpho/ILens.sol"; -import {IMorpho} from "@voltprotocol/pcv/morpho/IMorpho.sol"; -import {ICToken} from "@voltprotocol/pcv/morpho/ICompound.sol"; -import {Constants} from "@voltprotocol/Constants.sol"; -import {CoreRefV2} from "@voltprotocol/refs/CoreRefV2.sol"; -import {CoreRefV2} from "@voltprotocol/refs/CoreRefV2.sol"; -import {PCVDeposit} from "@voltprotocol/pcv/PCVDeposit.sol"; -import {IPCVDepositV2} from "@voltprotocol/pcv/IPCVDepositV2.sol"; - -/// @title abstract contract for a PCV Deposit that handles ERC-20 tokens using a PCV Controller -/// @author Elliot Friedman -abstract contract PCVDepositV2 is IPCVDepositV2, CoreRefV2 { - using SafeERC20 for IERC20; - using SafeCast for *; - - /// @notice reference to underlying token - address public immutable override rewardToken; - - /// @notice reference to underlying token - address public immutable override token; - - /// ------------------------------------------ - /// ------------- State Variables ------------ - /// ------------------------------------------ - - /// @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. - uint128 public lastRecordedBalance; - - /// @notice track the last amount of profits earned by 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. - int128 public lastRecordedProfits; - - constructor(address _token, address _rewardToken) { - token = _token; - rewardToken = _rewardToken; - } - - /// ------------------------------------------ - /// ----------- Permissionless API ----------- - /// ------------------------------------------ - - /// @notice deposit ERC-20 tokens to Morpho-Compound - /// non-reentrant to block malicious reentrant state changes - /// to the lastRecordedBalance variable - function deposit() public whenNotPaused globalLock(2) { - /// ------ Check ------ - - uint256 amount = IERC20(token).balanceOf(address(this)); - if (amount == 0) { - /// no op to prevent revert on empty deposit - return; - } - - int256 startingRecordedBalance = lastRecordedBalance.toInt256(); - - /// ------ Effects ------ - - /// compute profit from interest accrued and emit an event - /// if any profits or losses are realized - int256 profit = _recordPNL(); - - /// increment tracked recorded amount - /// this will be off by a hair, after a single block - /// negative delta turns to positive delta (assuming no loss). - lastRecordedBalance += uint128(amount); - - /// ------ Interactions ------ - - /// approval and deposit into underlying venue - _supply(amount); - - /// ------ Update Internal Accounting ------ - - int256 endingRecordedBalance = balance().toInt256(); - - _pcvOracleHook(endingRecordedBalance - startingRecordedBalance, profit); - - 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 globalLock(2) { - uint256 claimedAmount = _claim(); - - emit Harvest(rewardToken, 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 globalLock(2) whenNotPaused returns (uint256) { - int256 profit = _recordPNL(); /// update deposit amount and fire harvest event - - /// if any amount of PCV is withdrawn and no gains, delta is negative - _pcvOracleHook(profit, profit); - - 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 - /// @param amount of tokens withdrawn - function withdraw( - address to, - uint256 amount - ) external onlyPCVController globalLock(2) { - int256 profit = _withdraw(to, amount, true); - - /// if any amount of PCV is withdrawn and no gains, delta is negative - _pcvOracleHook(-(amount.toInt256()) + profit, profit); - } - - /// @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 globalLock(2) { - /// compute profit from interest accrued and emit an event - int256 profit = _recordPNL(); - - int256 recordedBalance = lastRecordedBalance.toInt256(); - - /// withdraw last recorded amount as this was updated in record pnl - _withdraw(to, lastRecordedBalance, false); - - /// all PCV withdrawn, send call in with amount withdrawn negative if any amount is withdrawn - _pcvOracleHook(-recordedBalance, profit); - } - - /// @notice withdraw ERC20 from the contract - /// @param tokenAddress address of the ERC20 to send - /// @param to address destination of the ERC20 - /// @param amount quantity of ERC20 to send - /// Calling this function will lead to incorrect - /// accounting in a PCV deposit that tracks - /// profits and or last recorded balance. - /// If a deposit records PNL, only use this - /// function in an emergency. - function withdrawERC20( - address tokenAddress, - address to, - uint256 amount - ) public virtual override onlyPCVController { - IERC20(tokenAddress).safeTransfer(to, amount); - emit WithdrawERC20(msg.sender, tokenAddress, to, amount); - } - - /// ------------- Internal Helpers ------------- - - /// @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. - /// @return profit accumulated since last _recordPNL() call. - function _recordPNL() internal returns (int256) { - /// first accrue interest in the underlying venue - _accrueUnderlying(); - - /// ------ Check ------ - - /// then get the current balance from the market - uint256 currentBalance = balance(); - - /// save gas if contract has no balance - /// 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 0; - } - - /// currentBalance should always be greater than or equal to - /// the deposited amount, except on the same block a deposit occurs, or a loss event in morpho - /// SLOAD - uint128 _lastRecordedBalance = lastRecordedBalance; - - /// Compute profit - int128 profit = int128(int256(currentBalance)) - - int128(_lastRecordedBalance); - - int128 _lastRecordedProfits = lastRecordedProfits + profit; - - /// ------ Effects ------ - - /// SSTORE: record new amounts - lastRecordedProfits = _lastRecordedProfits; - lastRecordedBalance = uint128(currentBalance); - - /// profit is in underlying token - emit Harvest(token, int256(profit), block.timestamp); - - return profit; - } - - /// @notice helper 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. - /// 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 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 - /// as the function _recordPNL() is already called before _withdraw - function _withdraw( - address to, - uint256 amount, - bool recordPnl - ) private returns (int256 profit) { - /// ------ Effects ------ - - if (recordPnl) { - /// compute profit from interest accrued and emit a Harvest event - profit = _recordPNL(); - } - - /// 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 -= uint128(amount); - - /// ------ Interactions ------ - - /// remove funds from underlying venue - _withdrawAndTransfer(amount, to); - - emit Withdrawal(msg.sender, to, amount); - } - - /// ------------- Virtual Functions ------------- - - /// @notice get balance in the underlying market. - /// @return current balance of deposit - function balance() public view virtual override returns (uint256); - - /// @dev accrue interest in the underlying market. - function _accrueUnderlying() internal virtual; - - /// @dev withdraw from the underlying market. - function _withdrawAndTransfer(uint256 amount, address to) internal virtual; - - /// @dev deposit in the underlying market. - function _supply(uint256 amount) internal virtual; - - /// @dev claim rewards from the underlying market. - /// returns amount of reward tokens claimed - function _claim() internal virtual returns (uint256); -} From 645f59b628c6466115e2eaee8423d0c09511597f Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 19 Jan 2023 20:58:13 -0800 Subject: [PATCH 21/23] import clean up --- src/pcv/morpho/MorphoPCVDeposit.sol | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/pcv/morpho/MorphoPCVDeposit.sol b/src/pcv/morpho/MorphoPCVDeposit.sol index 4172820d8..3d2ffc513 100644 --- a/src/pcv/morpho/MorphoPCVDeposit.sol +++ b/src/pcv/morpho/MorphoPCVDeposit.sol @@ -2,14 +2,11 @@ pragma solidity =0.8.13; 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 "@voltprotocol/pcv/morpho/ILens.sol"; -import {ICToken} from "@voltprotocol/pcv/morpho/ICompound.sol"; import {IMorpho} from "@voltprotocol/pcv/morpho/IMorpho.sol"; import {CoreRefV2} from "@voltprotocol/refs/CoreRefV2.sol"; -import {Constants} from "@voltprotocol/Constants.sol"; import {PCVDepositV2} from "@voltprotocol/pcv/PCVDepositV2.sol"; /// @notice PCV Deposit for Morpho-Compound V2. @@ -37,7 +34,6 @@ import {PCVDepositV2} from "@voltprotocol/pcv/PCVDepositV2.sol"; /// the user a balance. contract MorphoPCVDeposit is PCVDepositV2 { using SafeERC20 for IERC20; - using SafeCast for *; /// ------------------------------------------ /// ---------- Immutables/Constant ----------- @@ -87,11 +83,6 @@ contract MorphoPCVDeposit is PCVDepositV2 { return totalSupplied; } - /// @notice returns the underlying token of this deposit - function balanceReportedIn() external view returns (address) { - return token; - } - /// ------------------------------------------ /// ------------- Helper Methods ------------- /// ------------------------------------------ From dd1bd799866a515370fd66613b4038ffe75117ba Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 19 Jan 2023 20:58:41 -0800 Subject: [PATCH 22/23] change unit test morpho address label --- test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol b/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol index b1c1afc66..615d7602f 100644 --- a/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol +++ b/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol @@ -83,7 +83,7 @@ contract UnitTestMorphoPCVDeposit is Test { core.setGlobalReentrancyLock(lock); vm.stopPrank(); - vm.label(address(morpho), "Morpho"); + vm.label(address(morpho), "MORPHO_COMPOUND"); vm.label(address(token), "Token"); vm.label(address(morphoDeposit), "MorphoDeposit"); From 00e7844ee198d984e43fef5d3d35e1b14273a461 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 19 Jan 2023 21:04:37 -0800 Subject: [PATCH 23/23] Rename MorphoPCVDeposit to MorphoCompoundPCVDeposit --- ...posit.sol => MorphoCompoundPCVDeposit.sol} | 2 +- ...egrationTestMorphoCompoundPCVDeposit.t.sol | 12 +++++----- .../InvariantTestMorphoPCVDeposit.t.sol | 18 +++++++-------- test/mock/MockMorphoMaliciousReentrancy.sol | 8 +++---- test/proposals/vips/vip16.sol | 20 ++++++++-------- .../compound/CompoundBadDebtSentinel.t.sol | 6 ++--- .../pcv/morpho/MorphoCompoundPCVDeposit.t.sol | 23 +++++++++++-------- test/unit/system/System.t.sol | 4 ++-- 8 files changed, 48 insertions(+), 45 deletions(-) rename src/pcv/morpho/{MorphoPCVDeposit.sol => MorphoCompoundPCVDeposit.sol} (99%) diff --git a/src/pcv/morpho/MorphoPCVDeposit.sol b/src/pcv/morpho/MorphoCompoundPCVDeposit.sol similarity index 99% rename from src/pcv/morpho/MorphoPCVDeposit.sol rename to src/pcv/morpho/MorphoCompoundPCVDeposit.sol index 3d2ffc513..0b36c34b9 100644 --- a/src/pcv/morpho/MorphoPCVDeposit.sol +++ b/src/pcv/morpho/MorphoCompoundPCVDeposit.sol @@ -32,7 +32,7 @@ import {PCVDepositV2} from "@voltprotocol/pcv/PCVDepositV2.sol"; /// 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 MorphoPCVDeposit is PCVDepositV2 { +contract MorphoCompoundPCVDeposit is PCVDepositV2 { using SafeERC20 for IERC20; /// ------------------------------------------ diff --git a/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol b/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol index a45681253..73b018480 100644 --- a/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol +++ b/test/integration/IntegrationTestMorphoCompoundPCVDeposit.t.sol @@ -9,12 +9,12 @@ import {CoreV2} from "@voltprotocol/core/CoreV2.sol"; import {getCoreV2} from "@test/unit/utils/Fixtures.sol"; import {PCVGuardian} from "@voltprotocol/pcv/PCVGuardian.sol"; import {SystemEntry} from "@voltprotocol/entry/SystemEntry.sol"; -import {MorphoPCVDeposit} from "@voltprotocol/pcv/morpho/MorphoPCVDeposit.sol"; +import {MorphoCompoundPCVDeposit} from "@voltprotocol/pcv/morpho/MorphoCompoundPCVDeposit.sol"; import {PegStabilityModule} from "@voltprotocol/peg/PegStabilityModule.sol"; import {TestAddresses as addresses} from "@test/unit/utils/TestAddresses.sol"; import {IGlobalReentrancyLock, GlobalReentrancyLock} from "@voltprotocol/core/GlobalReentrancyLock.sol"; -contract IntegrationTestMorphoPCVDeposit is Test { +contract IntegrationTestMorphoCompoundPCVDeposit is Test { using SafeCast for *; // Constant addresses @@ -33,8 +33,8 @@ contract IntegrationTestMorphoPCVDeposit is Test { CoreV2 private core; SystemEntry public entry; GlobalReentrancyLock private lock; - MorphoPCVDeposit private daiDeposit; - MorphoPCVDeposit private usdcDeposit; + MorphoCompoundPCVDeposit private daiDeposit; + MorphoCompoundPCVDeposit private usdcDeposit; PCVGuardian private pcvGuardian; @@ -53,7 +53,7 @@ contract IntegrationTestMorphoPCVDeposit is Test { function setUp() public { core = getCoreV2(); lock = new GlobalReentrancyLock(address(core)); - daiDeposit = new MorphoPCVDeposit( + daiDeposit = new MorphoCompoundPCVDeposit( address(core), CDAI, DAI, @@ -62,7 +62,7 @@ contract IntegrationTestMorphoPCVDeposit is Test { MORPHO_LENS ); - usdcDeposit = new MorphoPCVDeposit( + usdcDeposit = new MorphoCompoundPCVDeposit( address(core), CUSDC, USDC, diff --git a/test/invariant/InvariantTestMorphoPCVDeposit.t.sol b/test/invariant/InvariantTestMorphoPCVDeposit.t.sol index f040d9e48..42fff9f26 100644 --- a/test/invariant/InvariantTestMorphoPCVDeposit.t.sol +++ b/test/invariant/InvariantTestMorphoPCVDeposit.t.sol @@ -14,7 +14,7 @@ import {PCVGuardian} from "@voltprotocol/pcv/PCVGuardian.sol"; import {SystemEntry} from "@voltprotocol/entry/SystemEntry.sol"; import {MockPCVOracle} from "@test/mock/MockPCVOracle.sol"; import {InvariantTest} from "@test/invariant/InvariantTest.sol"; -import {MorphoPCVDeposit} from "@voltprotocol/pcv/morpho/MorphoPCVDeposit.sol"; +import {MorphoCompoundPCVDeposit} from "@voltprotocol/pcv/morpho/MorphoCompoundPCVDeposit.sol"; import {TestAddresses as addresses} from "@test/unit/utils/TestAddresses.sol"; import {IGlobalReentrancyLock, GlobalReentrancyLock} from "@voltprotocol/core/GlobalReentrancyLock.sol"; @@ -22,7 +22,7 @@ import {IGlobalReentrancyLock, GlobalReentrancyLock} from "@voltprotocol/core/Gl /// 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 InvariantTestMorphoPCVDeposit is Test, InvariantTest { +contract InvariantTestMorphoCompoundPCVDeposit is Test, InvariantTest { using SafeCast for *; /// TODO add invariant test for profit tracking @@ -34,15 +34,15 @@ contract InvariantTestMorphoPCVDeposit is Test, InvariantTest { PCVGuardian public pcvGuardian; MockPCVOracle public pcvOracle; IGlobalReentrancyLock private lock; - MorphoPCVDepositTest public morphoTest; - MorphoPCVDeposit public morphoDeposit; + MorphoCompoundPCVDepositTest public morphoTest; + MorphoCompoundPCVDeposit public morphoDeposit; function setUp() public { core = getCoreV2(); token = new MockERC20(); pcvOracle = new MockPCVOracle(); morpho = new MockMorpho(IERC20(address(token))); - morphoDeposit = new MorphoPCVDeposit( + morphoDeposit = new MorphoCompoundPCVDeposit( address(core), address(morpho), address(token), @@ -64,7 +64,7 @@ contract InvariantTestMorphoPCVDeposit is Test, InvariantTest { ); entry = new SystemEntry(address(core)); - morphoTest = new MorphoPCVDepositTest( + morphoTest = new MorphoCompoundPCVDepositTest( morphoDeposit, token, morpho, @@ -114,17 +114,17 @@ contract InvariantTestMorphoPCVDeposit is Test, InvariantTest { } } -contract MorphoPCVDepositTest is Test { +contract MorphoCompoundPCVDepositTest is Test { uint256 public totalDeposited; MockERC20 public token; MockMorpho public morpho; SystemEntry public entry; PCVGuardian public pcvGuardian; - MorphoPCVDeposit public morphoDeposit; + MorphoCompoundPCVDeposit public morphoDeposit; constructor( - MorphoPCVDeposit _morphoDeposit, + MorphoCompoundPCVDeposit _morphoDeposit, MockERC20 _token, MockMorpho _morpho, SystemEntry _entry, diff --git a/test/mock/MockMorphoMaliciousReentrancy.sol b/test/mock/MockMorphoMaliciousReentrancy.sol index ecc015bdb..8fb8bd15c 100644 --- a/test/mock/MockMorphoMaliciousReentrancy.sol +++ b/test/mock/MockMorphoMaliciousReentrancy.sol @@ -2,12 +2,12 @@ pragma solidity 0.8.13; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {MorphoPCVDeposit} from "@voltprotocol/pcv/morpho/MorphoPCVDeposit.sol"; +import {MorphoCompoundPCVDeposit} from "@voltprotocol/pcv/morpho/MorphoCompoundPCVDeposit.sol"; contract MockMorphoMaliciousReentrancy { IERC20 public immutable token; mapping(address => uint256) public balances; - MorphoPCVDeposit public morphoCompoundPCVDeposit; + MorphoCompoundPCVDeposit public morphoCompoundPCVDeposit; constructor(IERC20 _token) { token = _token; @@ -17,8 +17,8 @@ contract MockMorphoMaliciousReentrancy { return address(token); } - function setMorphoPCVDeposit(address deposit) external { - morphoCompoundPCVDeposit = MorphoPCVDeposit(deposit); + function setMorphoCompoundPCVDeposit(address deposit) external { + morphoCompoundPCVDeposit = MorphoCompoundPCVDeposit(deposit); } function withdraw(address, uint256) external { diff --git a/test/proposals/vips/vip16.sol b/test/proposals/vips/vip16.sol index 44a4ffad4..bd7334b4b 100644 --- a/test/proposals/vips/vip16.sol +++ b/test/proposals/vips/vip16.sol @@ -24,7 +24,7 @@ import {NonCustodialPSM} from "@voltprotocol/peg/NonCustodialPSM.sol"; import {MakerPCVSwapper} from "@voltprotocol/pcv/maker/MakerPCVSwapper.sol"; import {VoltSystemOracle} from "@voltprotocol/oracle/VoltSystemOracle.sol"; import {EulerPCVDeposit} from "@voltprotocol/pcv/euler/EulerPCVDeposit.sol"; -import {MorphoPCVDeposit} from "@voltprotocol/pcv/morpho/MorphoPCVDeposit.sol"; +import {MorphoCompoundPCVDeposit} from "@voltprotocol/pcv/morpho/MorphoCompoundPCVDeposit.sol"; import {MorphoAavePCVDeposit} from "@voltprotocol/pcv/morpho/MorphoAavePCVDeposit.sol"; import {PegStabilityModule} from "@voltprotocol/peg/PegStabilityModule.sol"; import {IPegStabilityModule} from "@voltprotocol/peg/IPegStabilityModule.sol"; @@ -152,16 +152,16 @@ contract vip16 is Proposal { /// PCV Deposits { /// Morpho Compound Deposits - MorphoPCVDeposit morphoCompoundDaiPCVDeposit = new MorphoPCVDeposit( - addresses.mainnet("CORE"), - addresses.mainnet("CDAI"), - addresses.mainnet("DAI"), - addresses.mainnet("COMP"), - addresses.mainnet("MORPHO_COMPOUND"), - addresses.mainnet("MORPHO_LENS_COMPOUND") - ); + MorphoCompoundPCVDeposit morphoCompoundDaiPCVDeposit = new MorphoCompoundPCVDeposit( + addresses.mainnet("CORE"), + addresses.mainnet("CDAI"), + addresses.mainnet("DAI"), + addresses.mainnet("COMP"), + addresses.mainnet("MORPHO_COMPOUND"), + addresses.mainnet("MORPHO_LENS_COMPOUND") + ); - MorphoPCVDeposit morphoCompoundUsdcPCVDeposit = new MorphoPCVDeposit( + MorphoCompoundPCVDeposit morphoCompoundUsdcPCVDeposit = new MorphoCompoundPCVDeposit( addresses.mainnet("CORE"), addresses.mainnet("CUSDC"), addresses.mainnet("USDC"), diff --git a/test/unit/pcv/compound/CompoundBadDebtSentinel.t.sol b/test/unit/pcv/compound/CompoundBadDebtSentinel.t.sol index ed8ef05f9..d2c749510 100644 --- a/test/unit/pcv/compound/CompoundBadDebtSentinel.t.sol +++ b/test/unit/pcv/compound/CompoundBadDebtSentinel.t.sol @@ -16,7 +16,7 @@ import {GenericCallMock} from "@test/mock/GenericCallMock.sol"; import {MockPCVDepositV3} from "@test/mock/MockPCVDepositV3.sol"; import {MockERC20, IERC20} from "@test/mock/MockERC20.sol"; import {CompoundBadDebtSentinel} from "@voltprotocol/pcv/compound/CompoundBadDebtSentinel.sol"; -import {MorphoPCVDeposit} from "@voltprotocol/pcv/morpho/MorphoPCVDeposit.sol"; +import {MorphoCompoundPCVDeposit} from "@voltprotocol/pcv/morpho/MorphoCompoundPCVDeposit.sol"; import {TestAddresses as addresses} from "@test/unit/utils/TestAddresses.sol"; import {MockMorphoMaliciousReentrancy} from "@test/mock/MockMorphoMaliciousReentrancy.sol"; import {IGlobalReentrancyLock, GlobalReentrancyLock} from "@voltprotocol/core/GlobalReentrancyLock.sol"; @@ -40,7 +40,7 @@ contract UnitTestCompoundBadDebtSentinel is Test { PCVGuardian private pcvGuardian; GenericCallMock private comptroller; MockPCVDepositV3 private safeAddress; - MorphoPCVDeposit private morphoDeposit; + MorphoCompoundPCVDeposit private morphoDeposit; CompoundBadDebtSentinel private badDebtSentinel; uint256 public badDebtThreshold = 1_000_000e18; @@ -66,7 +66,7 @@ contract UnitTestCompoundBadDebtSentinel is Test { comptroller = new GenericCallMock(); safeAddress = new MockPCVDepositV3(address(core), address(token)); - morphoDeposit = new MorphoPCVDeposit( + morphoDeposit = new MorphoCompoundPCVDeposit( address(core), address(morpho), address(token), diff --git a/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol b/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol index 615d7602f..f8bf2b1ea 100644 --- a/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol +++ b/test/unit/pcv/morpho/MorphoCompoundPCVDeposit.t.sol @@ -13,12 +13,12 @@ import {PCVGuardian} from "@voltprotocol/pcv/PCVGuardian.sol"; import {getCoreV2} from "@test/unit/utils/Fixtures.sol"; import {SystemEntry} from "@voltprotocol/entry/SystemEntry.sol"; import {MockERC20, IERC20} from "@test/mock/MockERC20.sol"; -import {MorphoPCVDeposit} from "@voltprotocol/pcv/morpho/MorphoPCVDeposit.sol"; +import {MorphoCompoundPCVDeposit} from "@voltprotocol/pcv/morpho/MorphoCompoundPCVDeposit.sol"; import {TestAddresses as addresses} from "@test/unit/utils/TestAddresses.sol"; import {MockMorphoMaliciousReentrancy} from "@test/mock/MockMorphoMaliciousReentrancy.sol"; import {IGlobalReentrancyLock, GlobalReentrancyLock} from "@voltprotocol/core/GlobalReentrancyLock.sol"; -contract UnitTestMorphoPCVDeposit is Test { +contract UnitTestMorphoCompoundPCVDeposit is Test { using SafeCast for *; event Deposit(address indexed _from, uint256 _amount); @@ -35,7 +35,7 @@ contract UnitTestMorphoPCVDeposit is Test { SystemEntry public entry; MockMorpho private morpho; PCVGuardian private pcvGuardian; - MorphoPCVDeposit private morphoDeposit; + MorphoCompoundPCVDeposit private morphoDeposit; MockMorphoMaliciousReentrancy private maliciousMorpho; /// @notice token to deposit @@ -56,7 +56,7 @@ contract UnitTestMorphoPCVDeposit is Test { IERC20(address(token)) ); - morphoDeposit = new MorphoPCVDeposit( + morphoDeposit = new MorphoCompoundPCVDeposit( address(core), address(morpho), address(token), @@ -87,7 +87,7 @@ contract UnitTestMorphoPCVDeposit is Test { vm.label(address(token), "Token"); vm.label(address(morphoDeposit), "MorphoDeposit"); - maliciousMorpho.setMorphoPCVDeposit(address(morphoDeposit)); + maliciousMorpho.setMorphoCompoundPCVDeposit(address(morphoDeposit)); } function testSetup() public { @@ -238,7 +238,8 @@ contract UnitTestMorphoPCVDeposit is Test { token.mint(address(morphoDeposit), amount); entry.deposit(address(morphoDeposit)); - MorphoPCVDeposit.Call[] memory calls = new MorphoPCVDeposit.Call[](1); + MorphoCompoundPCVDeposit.Call[] + memory calls = new MorphoCompoundPCVDeposit.Call[](1); calls[0].callData = abi.encodeWithSignature( "withdraw(address,uint256)", address(this), @@ -257,7 +258,8 @@ contract UnitTestMorphoPCVDeposit is Test { vm.assume(amount != 0); token.mint(address(morphoDeposit), amount); - MorphoPCVDeposit.Call[] memory calls = new MorphoPCVDeposit.Call[](2); + MorphoCompoundPCVDeposit.Call[] + memory calls = new MorphoCompoundPCVDeposit.Call[](2); calls[0].callData = abi.encodeWithSignature( "approve(address,uint256)", address(morphoDeposit.morpho()), @@ -303,7 +305,8 @@ contract UnitTestMorphoPCVDeposit is Test { //// access controls function testEmergencyActionFailsNonGovernor() public { - MorphoPCVDeposit.Call[] memory calls = new MorphoPCVDeposit.Call[](1); + MorphoCompoundPCVDeposit.Call[] + memory calls = new MorphoCompoundPCVDeposit.Call[](1); calls[0].callData = abi.encodeWithSignature( "withdraw(address,uint256)", address(this), @@ -328,7 +331,7 @@ contract UnitTestMorphoPCVDeposit is Test { //// reentrancy function _reentrantSetup() private { - morphoDeposit = new MorphoPCVDeposit( + morphoDeposit = new MorphoCompoundPCVDeposit( address(core), address(maliciousMorpho), /// cToken is not used in mock morpho deposit address(token), @@ -340,7 +343,7 @@ contract UnitTestMorphoPCVDeposit is Test { vm.prank(addresses.governorAddress); core.grantLocker(address(morphoDeposit)); - maliciousMorpho.setMorphoPCVDeposit(address(morphoDeposit)); + maliciousMorpho.setMorphoCompoundPCVDeposit(address(morphoDeposit)); } function testReentrantAccrueFails() public { diff --git a/test/unit/system/System.t.sol b/test/unit/system/System.t.sol index f8b8e5f4e..e4a231e62 100644 --- a/test/unit/system/System.t.sol +++ b/test/unit/system/System.t.sol @@ -20,7 +20,7 @@ import {MockPCVDepositV2} from "@test/mock/MockPCVDepositV2.sol"; import {VoltSystemOracle} from "@voltprotocol/oracle/VoltSystemOracle.sol"; import {PegStabilityModule} from "@voltprotocol/peg/PegStabilityModule.sol"; import {IPCVDepositBalances} from "@voltprotocol/pcv/IPCVDepositBalances.sol"; -import {MorphoPCVDeposit} from "@voltprotocol/pcv/morpho/MorphoPCVDeposit.sol"; +import {MorphoCompoundPCVDeposit} from "@voltprotocol/pcv/morpho/MorphoCompoundPCVDeposit.sol"; import {TestAddresses as addresses} from "@test/unit/utils/TestAddresses.sol"; import {IGlobalReentrancyLock, GlobalReentrancyLock} from "@voltprotocol/core/GlobalReentrancyLock.sol"; import {IGlobalRateLimitedMinter, GlobalRateLimitedMinter} from "@voltprotocol/rate-limits/GlobalRateLimitedMinter.sol"; @@ -611,7 +611,7 @@ contract SystemUnitTest is Test { bytes4(keccak256("supply(address,address,uint256)")) ); - MorphoPCVDeposit deposit = new MorphoPCVDeposit( + MorphoCompoundPCVDeposit deposit = new MorphoCompoundPCVDeposit( address(core), address(mock), address(usdc),