diff --git a/contracts/test/integration/ArbitrumTestVoltSystemOracle.t.sol b/contracts/test/integration/ArbitrumTestVoltSystemOracle.t.sol deleted file mode 100644 index 322f44e85..000000000 --- a/contracts/test/integration/ArbitrumTestVoltSystemOracle.t.sol +++ /dev/null @@ -1,438 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.4; - -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 {DSTest} from "./../unit/utils/DSTest.sol"; -import {Decimal} from "./../../external/Decimal.sol"; -import {PriceBoundPSM} from "./../../peg/PriceBoundPSM.sol"; -import {IScalingPriceOracle, ScalingPriceOracle} from "./../../oracle/ScalingPriceOracle.sol"; -import {VoltSystemOracle} from "./../../oracle/VoltSystemOracle.sol"; -import {OraclePassThrough} from "./../../oracle/OraclePassThrough.sol"; -import {ArbitrumAddresses} from "./fixtures/ArbitrumAddresses.sol"; -import {Constants} from "../../Constants.sol"; -import {IVolt} from "../../volt/IVolt.sol"; -import {TimelockSimulation} from "./utils/TimelockSimulation.sol"; -import {vip2} from "./vip/vip2.sol"; - -contract ArbitrumTestVoltSystemOracle is TimelockSimulation, vip2 { - using Decimal for Decimal.D256; - using SafeCast for *; - - /// @notice reference to Volt - IVolt private volt = IVolt(ArbitrumAddresses.VOLT); - - /// @notice reference to Dai - IERC20 private dai = IERC20(ArbitrumAddresses.DAI); - - /// @notice reference to USDC - IERC20 private usdc = IERC20(ArbitrumAddresses.USDC); - - /// @notice scaling price oracle on Arbitrum today - ScalingPriceOracle private scalingPriceOracle = - ScalingPriceOracle(ArbitrumAddresses.DEPRECATED_SCALING_PRICE_ORACLE); - - /// @notice existing Oracle Pass Through deployed on mainnet - OraclePassThrough private immutable existingOraclePassThrough = - OraclePassThrough(ArbitrumAddresses.DEPRECATED_ORACLE_PASS_THROUGH); - - /// @notice new Volt System Oracle - VoltSystemOracle private voltSystemOracle = - VoltSystemOracle(ArbitrumAddresses.VOLT_SYSTEM_ORACLE); - - /// @notice new Oracle Pass Through - OraclePassThrough private oraclePassThrough = - OraclePassThrough(ArbitrumAddresses.ORACLE_PASS_THROUGH); - - /// @notice increase price by x% per month - uint256 public constant annualChangeRateBasisPoints = 200; - - /// @notice dai volt PSM - PriceBoundPSM private immutable daiPSM = - PriceBoundPSM(ArbitrumAddresses.VOLT_DAI_PSM); - - /// @notice usdc volt PSM - PriceBoundPSM private immutable usdcPSM = - PriceBoundPSM(ArbitrumAddresses.VOLT_USDC_PSM); - - /// @notice new Volt System Oracle start time - uint256 constant startTime = 1659468611; - - function setUp() public { - /// set mint fees to 5 bips - vm.startPrank(ArbitrumAddresses.GOVERNOR); - daiPSM.setMintFee(5); - usdcPSM.setMintFee(5); - vm.stopPrank(); - - vm.warp(startTime); - } - - function testSetup() public { - assertEq( - address(oraclePassThrough.scalingPriceOracle()), - address(voltSystemOracle) - ); - assertEq( - address(existingOraclePassThrough.scalingPriceOracle()), - address(scalingPriceOracle) - ); - } - - function testPriceEquivalenceAtTermEnd() public { - assertApproxEq( - scalingPriceOracle.getCurrentOraclePrice().toInt256(), - voltSystemOracle.getCurrentOraclePrice().toInt256(), - allowedDeviationArbitrum - ); - /// because start time is a little past when the calculated start time would be, - /// there is a slight but non zero deviation (835 seconds of unnacrued interest) - assertApproxEq( - voltSystemOracle.oraclePrice().toInt256(), - voltSystemOracle.getCurrentOraclePrice().toInt256(), - 0 - ); - assertApproxEq( - oraclePassThrough.getCurrentOraclePrice().toInt256(), - existingOraclePassThrough.getCurrentOraclePrice().toInt256(), - allowedDeviationArbitrum - ); - } - - /// swap out the old oracle for the new one and ensure the read functions - /// give the same value - function testMintSwapOraclePassThroughOnPSMs(uint96 mintAmount) public { - vm.assume(mintAmount > 1e18); - - uint256 startingAmountOutDai = daiPSM.getMintAmountOut(mintAmount); - uint256 startingAmountOutUSDC = usdcPSM.getMintAmountOut(mintAmount); - - vm.warp(startTime - 1 days); /// rewind the clock 1 day so that the timelock execution takes us back to start time - - /// simulate proposal execution so that the next set of assertions can be verified - /// with new upgrade in place - simulate( - getArbitrumProposal(), - TimelockController(payable(ArbitrumAddresses.TIMELOCK_CONTROLLER)), - ArbitrumAddresses.GOVERNOR, - ArbitrumAddresses.EOA_1, - vm, - false - ); - - uint256 endingAmountOutDai = daiPSM.getMintAmountOut(mintAmount); - uint256 endingAmountOutUSDC = usdcPSM.getMintAmountOut(mintAmount); - - assertApproxEq( - endingAmountOutDai.toInt256(), - startingAmountOutDai.toInt256(), - allowedDeviationArbitrum - ); - assertApproxEq( - endingAmountOutUSDC.toInt256(), - startingAmountOutUSDC.toInt256(), - allowedDeviationArbitrum - ); - } - - /// swap out the old oracle for the new one and ensure the read functions - /// give the same value - function testRedeemSwapOraclePassThroughOnPSMs(uint96 redeemAmount) public { - vm.assume(redeemAmount > 1e18); - uint256 startingAmountOutDai = daiPSM.getRedeemAmountOut(redeemAmount); - uint256 startingAmountOutUSDC = usdcPSM.getRedeemAmountOut( - redeemAmount - ); - - vm.warp(startTime - 1 days); /// rewind the clock 1 day so that the timelock execution takes us back to start time - - /// simulate proposal execution so that the next set of assertions can be verified - /// with new upgrade in place - simulate( - getArbitrumProposal(), - TimelockController(payable(ArbitrumAddresses.TIMELOCK_CONTROLLER)), - ArbitrumAddresses.GOVERNOR, - ArbitrumAddresses.EOA_1, - vm, - false - ); - - uint256 endingAmountOutDai = daiPSM.getRedeemAmountOut(redeemAmount); - uint256 endingAmountOutUSDC = usdcPSM.getRedeemAmountOut(redeemAmount); - - assertApproxEq( - endingAmountOutDai.toInt256(), - startingAmountOutDai.toInt256(), - allowedDeviationArbitrum - ); - assertApproxEq( - endingAmountOutUSDC.toInt256(), - startingAmountOutUSDC.toInt256(), - allowedDeviationArbitrum - ); - } - - /// assert swaps function the same after upgrading the scaling price oracle for Dai - function testMintParityAfterOracleUpgradeDAI() public { - uint256 amountStableIn = 101_000; - uint256 amountVoltOut = daiPSM.getMintAmountOut(amountStableIn); - uint256 startingUserVoltBalance = volt.balanceOf(address(this)); - - IVolt underlyingToken = IVolt(ArbitrumAddresses.DAI); - - vm.prank(ArbitrumAddresses.DAI_MINTER_1); /// fund with DAI - underlyingToken.mint(address(this), amountStableIn * 2); - - underlyingToken.approve(address(daiPSM), amountStableIn * 2); - daiPSM.mint(address(this), amountStableIn, amountVoltOut); - - uint256 endingUserVoltBalance = volt.balanceOf(address(this)); - assertEq( - endingUserVoltBalance, - startingUserVoltBalance + amountVoltOut - ); - - vm.warp(startTime - 1 days); /// rewind the clock 1 day so that the timelock execution takes us back to start time - - /// simulate proposal execution so that the next set of assertions can be verified - /// with new upgrade in place - simulate( - getArbitrumProposal(), - TimelockController(payable(ArbitrumAddresses.TIMELOCK_CONTROLLER)), - ArbitrumAddresses.GOVERNOR, - ArbitrumAddresses.EOA_1, - vm, - false - ); - - uint256 amountVoltOutAfterUpgrade = daiPSM.getMintAmountOut( - amountStableIn - ); - uint256 startingUserVoltBalanceAfterUpgrade = volt.balanceOf( - address(this) - ); - - daiPSM.mint( - address(this), - amountStableIn, - (amountVoltOut * - (Constants.BASIS_POINTS_GRANULARITY - - allowedDeviationArbitrum)) / - Constants.BASIS_POINTS_GRANULARITY - ); - - uint256 endingUserVoltBalanceAfterUpgrade = volt.balanceOf( - address(this) - ); - assertEq( - endingUserVoltBalanceAfterUpgrade, - startingUserVoltBalanceAfterUpgrade + amountVoltOutAfterUpgrade - ); - assertApproxEq( - amountVoltOutAfterUpgrade.toInt256(), - amountVoltOut.toInt256(), - allowedDeviationArbitrum - ); - } - - /// assert swaps function the same after upgrading the scaling price oracle for USDC - function testMintParityAfterOracleUpgradeUSDC() public { - uint256 amountStableIn = 10_100e6; - uint256 amountVoltOut = usdcPSM.getMintAmountOut(amountStableIn); - uint256 startingUserVoltBalance = volt.balanceOf(address(this)); - - IERC20 underlyingToken = IERC20(ArbitrumAddresses.USDC); - - uint256 underlyingTokenBalance = underlyingToken.balanceOf( - ArbitrumAddresses.USDC_WHALE - ); - vm.prank(ArbitrumAddresses.USDC_WHALE); - underlyingToken.transfer(address(this), underlyingTokenBalance); - - uint256 voltBalance = volt.balanceOf(ArbitrumAddresses.GOVERNOR); - vm.prank(ArbitrumAddresses.GOVERNOR); - volt.transfer(address(usdcPSM), voltBalance); - - underlyingToken.approve(address(usdcPSM), amountStableIn * 2); - usdcPSM.mint(address(this), amountStableIn, amountVoltOut); - - uint256 endingUserVoltBalance = volt.balanceOf(address(this)); - assertEq( - endingUserVoltBalance, - startingUserVoltBalance + amountVoltOut - ); - - vm.warp(startTime - 1 days); /// rewind the clock 1 day so that the timelock execution takes us back to start time - - /// simulate proposal execution so that the next set of assertions can be verified - /// with new upgrade in place - simulate( - getArbitrumProposal(), - TimelockController(payable(ArbitrumAddresses.TIMELOCK_CONTROLLER)), - ArbitrumAddresses.GOVERNOR, - ArbitrumAddresses.EOA_1, - vm, - false - ); - - uint256 amountVoltOutAfterUpgrade = usdcPSM.getMintAmountOut( - amountStableIn - ); - uint256 startingUserVoltBalanceAfterUpgrade = volt.balanceOf( - address(this) - ); - - usdcPSM.mint( - address(this), - amountStableIn, - (amountVoltOut * - (Constants.BASIS_POINTS_GRANULARITY - - allowedDeviationArbitrum)) / - Constants.BASIS_POINTS_GRANULARITY - ); - - uint256 endingUserVoltBalanceAfterUpgrade = volt.balanceOf( - address(this) - ); - assertEq( - endingUserVoltBalanceAfterUpgrade, - startingUserVoltBalanceAfterUpgrade + amountVoltOutAfterUpgrade - ); - assertApproxEq( - amountVoltOutAfterUpgrade.toInt256(), - amountVoltOut.toInt256(), - allowedDeviationArbitrum - ); - } - - /// assert redemptions function the same after upgrading the scaling price oracle for Dai - function testRedeemParityAfterOracleUpgradeDAI() public { - uint256 amountVoltIn = 1_000e18; - vm.prank(ArbitrumAddresses.GOVERNOR); /// fund with Volt - volt.transfer(address(this), amountVoltIn * 2); - - uint256 amountDaiOut = daiPSM.getRedeemAmountOut(amountVoltIn); - uint256 startingUserDaiBalance = dai.balanceOf(address(this)); - - volt.approve(address(daiPSM), amountVoltIn * 2); - daiPSM.redeem(address(this), amountVoltIn, amountDaiOut); - - uint256 endingUserDaiBalance = dai.balanceOf(address(this)); - assertEq(endingUserDaiBalance, startingUserDaiBalance + amountDaiOut); - - vm.warp(startTime - 1 days); /// rewind the clock 1 day so that the timelock execution takes us back to start time - - /// simulate proposal execution so that the next set of assertions can be verified - /// with new upgrade in place - simulate( - getArbitrumProposal(), - TimelockController(payable(ArbitrumAddresses.TIMELOCK_CONTROLLER)), - ArbitrumAddresses.GOVERNOR, - ArbitrumAddresses.EOA_1, - vm, - false - ); - - uint256 amountDaiOutAfterUpgrade = daiPSM.getRedeemAmountOut( - amountVoltIn - ); - uint256 startingUserDaiBalanceAfterUpgrade = dai.balanceOf( - address(this) - ); - - daiPSM.redeem(address(this), amountVoltIn, amountDaiOutAfterUpgrade); - - uint256 endingUserDaiBalanceAfterUpgrade = dai.balanceOf(address(this)); - assertEq( - endingUserDaiBalanceAfterUpgrade, - startingUserDaiBalanceAfterUpgrade + amountDaiOutAfterUpgrade - ); - assertApproxEq( - amountDaiOutAfterUpgrade.toInt256(), - amountDaiOut.toInt256(), - allowedDeviationArbitrum - ); - } - - /// assert redemptions function the same after upgrading the scaling price oracle for Usdc - function testRedeemParityAfterOracleUpgradeUSDC() public { - if (usdcPSM.redeemPaused()) { - vm.prank(ArbitrumAddresses.PCV_GUARDIAN); - usdcPSM.unpauseRedeem(); - } - - uint256 amountVoltIn = 1_000e18; - vm.prank(ArbitrumAddresses.GOVERNOR); /// fund with Volt - volt.transfer(address(this), amountVoltIn * 2); - - uint256 amountUsdcOut = usdcPSM.getRedeemAmountOut(amountVoltIn); - uint256 startingUserUsdcBalance = usdc.balanceOf(address(this)); - - volt.approve(address(usdcPSM), amountVoltIn * 2); - usdcPSM.redeem(address(this), amountVoltIn, amountUsdcOut); - - uint256 endingUserUsdcBalance = usdc.balanceOf(address(this)); - assertEq( - endingUserUsdcBalance, - startingUserUsdcBalance + amountUsdcOut - ); - - vm.warp(startTime - 1 days); /// rewind the clock 1 day so that the timelock execution takes us back to start time - - /// simulate proposal execution so that the next set of assertions can be verified - /// with new upgrade in place - simulate( - getArbitrumProposal(), - TimelockController(payable(ArbitrumAddresses.TIMELOCK_CONTROLLER)), - ArbitrumAddresses.GOVERNOR, - ArbitrumAddresses.EOA_1, - vm, - false - ); - - uint256 amountUsdcOutAfterUpgrade = usdcPSM.getRedeemAmountOut( - amountVoltIn - ); - uint256 startingUserUsdcBalanceAfterUpgrade = usdc.balanceOf( - address(this) - ); - - usdcPSM.redeem(address(this), amountVoltIn, amountUsdcOutAfterUpgrade); - - uint256 endingUserUsdcBalanceAfterUpgrade = usdc.balanceOf( - address(this) - ); - assertEq( - endingUserUsdcBalanceAfterUpgrade, - startingUserUsdcBalanceAfterUpgrade + amountUsdcOutAfterUpgrade - ); - assertApproxEq( - amountUsdcOutAfterUpgrade.toInt256(), - amountUsdcOut.toInt256(), - allowedDeviationArbitrum - ); - } - - function testSetMintFee() public { - vm.warp(startTime - 1 days); /// rewind the clock 1 day so that the timelock execution takes us back to start time - - /// simulate proposal execution so that the next set of assertions can be verified - /// with new upgrade in place - simulate( - getArbitrumProposal(), - TimelockController(payable(ArbitrumAddresses.TIMELOCK_CONTROLLER)), - ArbitrumAddresses.GOVERNOR, - ArbitrumAddresses.EOA_1, - vm, - false - ); - - uint256 endingFeeDai = daiPSM.mintFeeBasisPoints(); - uint256 endingFeeUsdc = usdcPSM.mintFeeBasisPoints(); - - assertEq(endingFeeDai, 5); - assertEq(endingFeeUsdc, 5); - } -} diff --git a/contracts/test/integration/IntegrationTestVoltSystemOracle.t.sol b/contracts/test/integration/IntegrationTestVoltSystemOracle.t.sol deleted file mode 100644 index 4725aae3b..000000000 --- a/contracts/test/integration/IntegrationTestVoltSystemOracle.t.sol +++ /dev/null @@ -1,435 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.4; - -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 {Core} from "../../core/Core.sol"; -import {Vm} from "./../unit/utils/Vm.sol"; -import {DSTest} from "./../unit/utils/DSTest.sol"; -import {Decimal} from "./../../external/Decimal.sol"; -import {PriceBoundPSM} from "./../../peg/PriceBoundPSM.sol"; -import {IScalingPriceOracle, ScalingPriceOracle} from "./../../oracle/ScalingPriceOracle.sol"; -import {VoltSystemOracle} from "./../../oracle/VoltSystemOracle.sol"; -import {OraclePassThrough} from "./../../oracle/OraclePassThrough.sol"; -import {MainnetAddresses} from "./fixtures/MainnetAddresses.sol"; -import {Constants} from "../../Constants.sol"; -import {IVolt} from "../../volt/IVolt.sol"; -import {TimelockSimulation} from "./utils/TimelockSimulation.sol"; -import {vip2} from "./vip/vip2.sol"; - -contract IntegrationTestVoltSystemOracle is TimelockSimulation, vip2 { - using Decimal for Decimal.D256; - using SafeCast for *; - - /// @notice reference to Volt - IVolt private volt = IVolt(MainnetAddresses.VOLT); - - /// @notice reference to Fei - IERC20 private fei = IERC20(MainnetAddresses.FEI); - - /// @notice reference to USDC - IERC20 private usdc = IERC20(MainnetAddresses.USDC); - - /// @notice scaling price oracle on mainnet today - ScalingPriceOracle private scalingPriceOracle = - ScalingPriceOracle(MainnetAddresses.DEPRECATED_SCALING_PRICE_ORACLE); - - /// @notice existing Oracle Pass Through deployed on mainnet - OraclePassThrough private immutable existingOraclePassThrough = - OraclePassThrough(MainnetAddresses.DEPRECATED_ORACLE_PASS_THROUGH); - - /// @notice new Volt System Oracle - VoltSystemOracle private voltSystemOracle = - VoltSystemOracle(MainnetAddresses.VOLT_SYSTEM_ORACLE); - - /// @notice new Oracle Pass Through - OraclePassThrough private oraclePassThrough = - OraclePassThrough(MainnetAddresses.ORACLE_PASS_THROUGH); - - /// @notice fei volt PSM - PriceBoundPSM private immutable feiPSM = - PriceBoundPSM(MainnetAddresses.VOLT_FEI_PSM); - - /// @notice usdc volt PSM - PriceBoundPSM private immutable usdcPSM = - PriceBoundPSM(MainnetAddresses.VOLT_USDC_PSM); - - /// @notice new Volt System Oracle start time - uint256 constant startTime = 1659467776; - - function setUp() public { - /// set mint fees to 0 so that the only change that is measured is the - /// difference between oracle prices - vm.startPrank(MainnetAddresses.GOVERNOR); - feiPSM.setMintFee(0); - usdcPSM.setMintFee(0); - vm.stopPrank(); - - vm.warp(startTime); - } - - function testSetup() public { - assertEq( - address(oraclePassThrough.scalingPriceOracle()), - address(voltSystemOracle) - ); - assertEq( - address(existingOraclePassThrough.scalingPriceOracle()), - address(scalingPriceOracle) - ); - } - - function testPriceEquivalenceAtTermEnd() public { - assertApproxEq( - scalingPriceOracle.getCurrentOraclePrice().toInt256(), - voltSystemOracle.getCurrentOraclePrice().toInt256(), - allowedDeviation - ); - /// because start time is a little past when the calculated start time would be, - /// there is a slight but non zero deviation (976 seconds of unnacrued interest) - assertApproxEq( - voltSystemOracle.oraclePrice().toInt256(), - voltSystemOracle.getCurrentOraclePrice().toInt256(), - 0 - ); - assertApproxEq( - oraclePassThrough.getCurrentOraclePrice().toInt256(), - existingOraclePassThrough.getCurrentOraclePrice().toInt256(), - allowedDeviation - ); - } - - /// swap out the old oracle for the new one and ensure the read functions - /// give the same value - function testMintSwapOraclePassThroughOnPSMs(uint96 mintAmount) public { - vm.assume(mintAmount > 1e18); - - uint256 startingAmountOutFei = feiPSM.getMintAmountOut(mintAmount); - uint256 startingAmountOutUSDC = usdcPSM.getMintAmountOut(mintAmount); - - vm.warp(startTime - 1 days); /// rewind the clock 1 day so that the timelock execution takes us back to start time - - /// simulate proposal execution so that the next set of assertions can be verified - /// with new upgrade in place - simulate( - getMainnetProposal(), - TimelockController(payable(MainnetAddresses.TIMELOCK_CONTROLLER)), - MainnetAddresses.GOVERNOR, - MainnetAddresses.EOA_1, - vm, - false - ); - - uint256 endingAmountOutFei = feiPSM.getMintAmountOut(mintAmount); - uint256 endingAmountOutUSDC = usdcPSM.getMintAmountOut(mintAmount); - - assertApproxEq( - endingAmountOutFei.toInt256(), - startingAmountOutFei.toInt256(), - allowedDeviation - ); - assertApproxEq( - endingAmountOutUSDC.toInt256(), - startingAmountOutUSDC.toInt256(), - allowedDeviation - ); - } - - /// swap out the old oracle for the new one and ensure the read functions - /// give the same value - function testRedeemSwapOraclePassThroughOnPSMs(uint96 redeemAmount) public { - vm.assume(redeemAmount > 1e18); - - uint256 startingAmountOutFei = feiPSM.getRedeemAmountOut(redeemAmount); - uint256 startingAmountOutUSDC = usdcPSM.getRedeemAmountOut( - redeemAmount - ); - - vm.warp(startTime - 1 days); /// rewind the clock 1 day so that the timelock execution takes us back to start time - - /// simulate proposal execution so that the next set of assertions can be verified - /// with new upgrade in place - simulate( - getMainnetProposal(), - TimelockController(payable(MainnetAddresses.TIMELOCK_CONTROLLER)), - MainnetAddresses.GOVERNOR, - MainnetAddresses.EOA_1, - vm, - false - ); - - uint256 endingAmountOutFei = feiPSM.getRedeemAmountOut(redeemAmount); - uint256 endingAmountOutUSDC = usdcPSM.getRedeemAmountOut(redeemAmount); - - assertApproxEq( - endingAmountOutFei.toInt256(), - startingAmountOutFei.toInt256(), - allowedDeviation - ); - assertApproxEq( - endingAmountOutUSDC.toInt256(), - startingAmountOutUSDC.toInt256(), - allowedDeviation - ); - } - - /// assert swaps function the same after upgrading the scaling price oracle for Fei - function testMintParityAfterOracleUpgradeFEI() public { - uint256 amountStableIn = 101_000e18; - uint256 amountVoltOut = feiPSM.getMintAmountOut(amountStableIn); - uint256 startingUserVoltBalance = volt.balanceOf(address(this)); - - IVolt underlyingToken = IVolt(MainnetAddresses.FEI); - - vm.prank(MainnetAddresses.FEI_DAO_TIMELOCK); /// fund with Fei - underlyingToken.mint(address(this), amountStableIn * 2); - - underlyingToken.approve(address(feiPSM), amountStableIn * 2); - feiPSM.mint(address(this), amountStableIn, amountVoltOut); - - uint256 endingUserVoltBalance = volt.balanceOf(address(this)); - assertEq( - endingUserVoltBalance, - startingUserVoltBalance + amountVoltOut - ); - - vm.warp(startTime - 1 days); /// rewind the clock 1 day so that the timelock execution takes us back to start time - - /// simulate proposal execution so that the next set of assertions can be verified - /// with new upgrade in place - simulate( - getMainnetProposal(), - TimelockController(payable(MainnetAddresses.TIMELOCK_CONTROLLER)), - MainnetAddresses.GOVERNOR, - MainnetAddresses.EOA_1, - vm, - false - ); - - assertEq(address(oraclePassThrough), address(feiPSM.oracle())); - - uint256 amountVoltOutAfterUpgrade = feiPSM.getMintAmountOut( - amountStableIn - ); - uint256 startingUserVoltBalanceAfterUpgrade = volt.balanceOf( - address(this) - ); - - /// apply 6 bip haircut to amount volt out as price increased by 5 bips when oracle swap happens - feiPSM.mint( - address(this), - amountStableIn, - (amountVoltOut * (10_000 - allowedDeviation)) / 10_000 - ); - - uint256 endingUserVoltBalanceAfterUpgrade = volt.balanceOf( - address(this) - ); - assertEq( - endingUserVoltBalanceAfterUpgrade, - startingUserVoltBalanceAfterUpgrade + amountVoltOutAfterUpgrade - ); - assertApproxEq( - amountVoltOutAfterUpgrade.toInt256(), - amountVoltOut.toInt256(), - allowedDeviation - ); - } - - /// assert swaps function the same after upgrading the scaling price oracle for USDC - function testMintParityAfterOracleUpgradeUSDC() public { - uint256 amountStableIn = 101_000; - uint256 amountVoltOut = usdcPSM.getMintAmountOut(amountStableIn); - uint256 startingUserVoltBalance = volt.balanceOf(address(this)); - - IERC20 underlyingToken = IERC20(MainnetAddresses.USDC); - - vm.prank(MainnetAddresses.MAKER_USDC_PSM); - underlyingToken.transfer(address(this), amountStableIn * 2); - - underlyingToken.approve(address(usdcPSM), amountStableIn * 2); - usdcPSM.mint(address(this), amountStableIn, amountVoltOut); - - uint256 endingUserVoltBalance = volt.balanceOf(address(this)); - assertEq( - endingUserVoltBalance, - startingUserVoltBalance + amountVoltOut - ); - - vm.warp(startTime - 1 days); /// rewind the clock 1 day so that the timelock execution takes us back to start time - - /// simulate proposal execution so that the next set of assertions can be verified - /// with new upgrade in place - simulate( - getMainnetProposal(), - TimelockController(payable(MainnetAddresses.TIMELOCK_CONTROLLER)), - MainnetAddresses.GOVERNOR, - MainnetAddresses.EOA_1, - vm, - false - ); - - uint256 amountVoltOutAfterUpgrade = usdcPSM.getMintAmountOut( - amountStableIn - ); - uint256 startingUserVoltBalanceAfterUpgrade = volt.balanceOf( - address(this) - ); - - usdcPSM.mint( - address(this), - amountStableIn, - (amountVoltOut * (10_000 - allowedDeviation)) / 10_000 - ); - - uint256 endingUserVoltBalanceAfterUpgrade = volt.balanceOf( - address(this) - ); - assertEq( - endingUserVoltBalanceAfterUpgrade, - startingUserVoltBalanceAfterUpgrade + amountVoltOutAfterUpgrade - ); - assertApproxEq( - amountVoltOutAfterUpgrade.toInt256(), - amountVoltOut.toInt256(), - allowedDeviation - ); - } - - /// assert redemptions function the same after upgrading the scaling price oracle for Fei - function testRedeemParityAfterOracleUpgradeFEI() public { - uint256 amountVoltIn = 10_000e18; - - vm.prank(MainnetAddresses.CORE); - Core(MainnetAddresses.CORE).grantMinter(MainnetAddresses.CORE); - - vm.prank(MainnetAddresses.CORE); /// fund with VOLT - volt.mint(address(this), amountVoltIn * 2); - - uint256 amountFeiOut = feiPSM.getRedeemAmountOut(amountVoltIn); - uint256 startingUserFeiBalance = fei.balanceOf(address(this)); - - volt.approve(address(feiPSM), amountVoltIn * 2); - feiPSM.redeem(address(this), amountVoltIn, amountFeiOut); - - uint256 endingUserFeiBalance = fei.balanceOf(address(this)); - assertEq(endingUserFeiBalance, startingUserFeiBalance + amountFeiOut); - - vm.warp(startTime - 1 days); /// rewind the clock 1 day so that the timelock execution takes us back to start time - - /// simulate proposal execution so that the next set of assertions can be verified - /// with new upgrade in place - simulate( - getMainnetProposal(), - TimelockController(payable(MainnetAddresses.TIMELOCK_CONTROLLER)), - MainnetAddresses.GOVERNOR, - MainnetAddresses.EOA_1, - vm, - false - ); - - uint256 amountFeiOutAfterUpgrade = feiPSM.getRedeemAmountOut( - amountVoltIn - ); - uint256 startingUserFeiBalanceAfterUpgrade = fei.balanceOf( - address(this) - ); - - feiPSM.redeem(address(this), amountVoltIn, amountFeiOutAfterUpgrade); - - uint256 endingUserFeiBalanceAfterUpgrade = fei.balanceOf(address(this)); - assertEq( - endingUserFeiBalanceAfterUpgrade, - startingUserFeiBalanceAfterUpgrade + amountFeiOutAfterUpgrade - ); - assertApproxEq( - amountFeiOutAfterUpgrade.toInt256(), - amountFeiOut.toInt256(), - allowedDeviation - ); - } - - /// assert redemptions function the same after upgrading the scaling price oracle for Usdc - function testRedeemParityAfterOracleUpgradeUSDC() public { - if (usdcPSM.redeemPaused()) { - vm.prank(MainnetAddresses.PCV_GUARDIAN); - usdcPSM.unpauseRedeem(); - } - - uint256 amountVoltIn = 10_000e18; - vm.prank(MainnetAddresses.CORE); - Core(MainnetAddresses.CORE).grantMinter(MainnetAddresses.CORE); - - vm.prank(MainnetAddresses.CORE); /// fund with VOLT - volt.mint(address(this), amountVoltIn * 2); - - uint256 amountUsdcOut = usdcPSM.getRedeemAmountOut(amountVoltIn); - uint256 startingUserUsdcBalance = usdc.balanceOf(address(this)); - - volt.approve(address(usdcPSM), amountVoltIn * 2); - usdcPSM.redeem(address(this), amountVoltIn, amountUsdcOut); - - uint256 endingUserUsdcBalance = usdc.balanceOf(address(this)); - assertEq( - endingUserUsdcBalance, - startingUserUsdcBalance + amountUsdcOut - ); - - vm.warp(startTime - 1 days); /// rewind the clock 1 day so that the timelock execution takes us back to start time - - /// simulate proposal execution so that the next set of assertions can be verified - /// with new upgrade in place - simulate( - getMainnetProposal(), - TimelockController(payable(MainnetAddresses.TIMELOCK_CONTROLLER)), - MainnetAddresses.GOVERNOR, - MainnetAddresses.EOA_1, - vm, - false - ); - - uint256 amountUsdcOutAfterUpgrade = usdcPSM.getRedeemAmountOut( - amountVoltIn - ); - uint256 startingUserUsdcBalanceAfterUpgrade = usdc.balanceOf( - address(this) - ); - - usdcPSM.redeem(address(this), amountVoltIn, amountUsdcOutAfterUpgrade); - - uint256 endingUserUsdcBalanceAfterUpgrade = usdc.balanceOf( - address(this) - ); - assertEq( - endingUserUsdcBalanceAfterUpgrade, - startingUserUsdcBalanceAfterUpgrade + amountUsdcOutAfterUpgrade - ); - assertApproxEq( - amountUsdcOutAfterUpgrade.toInt256(), - amountUsdcOut.toInt256(), - allowedDeviation - ); - } - - function testSetMintFee() public { - vm.warp(startTime - 1 days); /// rewind the clock 1 day so that the timelock execution takes us back to start time - - /// simulate proposal execution so that the next set of assertions can be verified - /// with new upgrade in place - simulate( - getMainnetProposal(), - TimelockController(payable(MainnetAddresses.TIMELOCK_CONTROLLER)), - MainnetAddresses.GOVERNOR, - MainnetAddresses.EOA_1, - vm, - false - ); - - uint256 endingFeeFei = feiPSM.mintFeeBasisPoints(); - uint256 endingFeeUsdc = usdcPSM.mintFeeBasisPoints(); - - assertEq(endingFeeFei, 0); - assertEq(endingFeeUsdc, 0); - } -} diff --git a/contracts/test/integration/utils/ITimelockSimulation.sol b/contracts/test/integration/utils/ITimelockSimulation.sol new file mode 100644 index 000000000..e6a7065b3 --- /dev/null +++ b/contracts/test/integration/utils/ITimelockSimulation.sol @@ -0,0 +1,33 @@ +pragma solidity =0.8.13; + +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {IPCVGuardian} from "../../../pcv/IPCVGuardian.sol"; +import {Vm} from "./../../unit/utils/Vm.sol"; + +interface ITimelockSimulation { + /// an array of actions makes up a proposal + struct action { + address target; + uint256 value; + bytes arguments; + string description; + } + + /// @notice simulate timelock proposal + /// @param proposal an array of actions that compose a proposal + /// @param timelock to execute the proposal against + /// @param guardian to verify all transfers are authorized to hold PCV + /// @param executor account to execute the proposal on the timelock + /// @param proposer account to propose the proposal to the timelock + /// @param vm reference to a foundry vm instance + /// @param doLogging toggle to print out calldata and steps + function simulate( + action[] memory proposal, + TimelockController timelock, + IPCVGuardian guardian, + address executor, + address proposer, + Vm vm, + bool doLogging + ) external; +} diff --git a/contracts/test/integration/utils/PCVGuardianWhitelist.sol b/contracts/test/integration/utils/PCVGuardianWhitelist.sol new file mode 100644 index 000000000..fe1d01ef6 --- /dev/null +++ b/contracts/test/integration/utils/PCVGuardianWhitelist.sol @@ -0,0 +1,80 @@ +pragma solidity =0.8.13; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IPCVGuardian} from "../../../pcv/IPCVGuardian.sol"; +import {ITimelockSimulation} from "./ITimelockSimulation.sol"; +import {IPermissions} from "./../../../core/IPermissions.sol"; + +/// Only allow approvals and transfers of PCV to addresses in PCV Guardian, +/// and only allow granting PCV controllers if they are subsequently added to +/// the PCV Guardian +contract PCVGuardianWhitelist { + mapping(bytes4 => bool) public functionDetectors; + + constructor() { + functionDetectors[IERC20.transfer.selector] = true; + functionDetectors[IERC20.approve.selector] = true; + functionDetectors[IPermissions.grantPCVController.selector] = true; + } + + /// @notice function to verify actions and ensure that granting a PCV Controller or transferring assets + /// only happens to addresses that are on the PCV Guardian whitelist + function verifyAction( + ITimelockSimulation.action[] memory proposal, + IPCVGuardian guardian + ) public { + uint256 proposalLength = proposal.length; + for (uint256 i = 0; i < proposalLength; i++) { + bytes4 functionSig = bytesToBytes4(proposal[i].arguments); + + if (functionDetectors[functionSig]) { + address recipient; + bytes memory payload = proposal[i].arguments; + assembly { + recipient := mload(add(payload, 36)) + } + + if (!guardian.isWhitelistAddress(recipient)) { + revert( + string( + abi.encodePacked( + "Address ", + toString(abi.encodePacked(recipient)), + " not in PCV Guardian whitelist" + ) + ) + ); + } + } + } + } + + /// @notice function to grab the first 4 bytes of calldata payload + function bytesToBytes4(bytes memory toSlice) + public + pure + returns (bytes4 functionSignature) + { + if (toSlice.length < 4) { + return bytes4(0); + } + + assembly { + functionSignature := mload(add(toSlice, 0x20)) + } + } + + /// Credit ethereum stackexchange https://ethereum.stackexchange.com/a/58341 + function toString(bytes memory data) public pure returns (string memory) { + bytes memory alphabet = "0123456789abcdef"; + + bytes memory str = new bytes(2 + data.length * 2); + str[0] = "0"; + str[1] = "x"; + for (uint256 i = 0; i < data.length; i++) { + str[2 + i * 2] = alphabet[uint256(uint8(data[i] >> 4))]; + str[3 + i * 2] = alphabet[uint256(uint8(data[i] & 0x0f))]; + } + return string(str); + } +} diff --git a/contracts/test/integration/utils/TimelockSimulation.sol b/contracts/test/integration/utils/TimelockSimulation.sol index 8f5901401..1523b4e97 100644 --- a/contracts/test/integration/utils/TimelockSimulation.sol +++ b/contracts/test/integration/utils/TimelockSimulation.sol @@ -3,21 +3,21 @@ pragma solidity =0.8.13; import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; import {DSTest} from "./../../unit/utils/DSTest.sol"; import {Vm} from "./../../unit/utils/Vm.sol"; +import {ITimelockSimulation} from "./ITimelockSimulation.sol"; +import {PCVGuardianWhitelist} from "./PCVGuardianWhitelist.sol"; +import {IPCVGuardian} from "../../../pcv/IPCVGuardian.sol"; import "hardhat/console.sol"; -contract TimelockSimulation is DSTest { - /// an array of actions makes up a proposal - struct action { - address target; - uint256 value; - bytes arguments; - string description; - } - +contract TimelockSimulation is + DSTest, + ITimelockSimulation, + PCVGuardianWhitelist +{ /// @notice simulate timelock proposal /// @param proposal an array of actions that compose a proposal /// @param timelock to execute the proposal against + /// @param guardian to verify all transfers are authorized to hold PCV /// @param executor account to execute the proposal on the timelock /// @param proposer account to propose the proposal to the timelock /// @param vm reference to a foundry vm instance @@ -25,11 +25,12 @@ contract TimelockSimulation is DSTest { function simulate( action[] memory proposal, TimelockController timelock, + IPCVGuardian guardian, address executor, address proposer, Vm vm, bool doLogging - ) public { + ) public override { uint256 delay = timelock.getMinDelay(); bytes32 salt = keccak256(abi.encode(proposal[0].description)); @@ -127,5 +128,7 @@ contract TimelockSimulation is DSTest { } else if (doLogging) { console.log("proposal already executed"); } + + verifyAction(proposal, guardian); } } diff --git a/contracts/test/integration/vip/Runner.sol b/contracts/test/integration/vip/Runner.sol index 57ad52b9c..04dd56b0f 100644 --- a/contracts/test/integration/vip/Runner.sol +++ b/contracts/test/integration/vip/Runner.sol @@ -1,14 +1,22 @@ pragma solidity =0.8.13; -// import {vipx} from "./vipx.sol"; +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; import {TimelockSimulation} from "../utils/TimelockSimulation.sol"; import {MainnetAddresses} from "../fixtures/MainnetAddresses.sol"; import {ArbitrumAddresses} from "../fixtures/ArbitrumAddresses.sol"; -import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {PCVGuardian} from "./../../../pcv/PCVGuardian.sol"; /// @dev test harness for running and simulating VOLT Improvement Proposals /// inherit the proposal to simulate contract Runner is TimelockSimulation { + /// @notice mainnet PCV Guardian + PCVGuardian private immutable mainnetPCVGuardian = + PCVGuardian(MainnetAddresses.PCV_GUARDIAN); + + /// @notice arbitrum PCV Guardian + PCVGuardian private immutable arbitrumPCVGuardian = + PCVGuardian(ArbitrumAddresses.PCV_GUARDIAN); + /// remove all function calls inside testProposal and don't inherit the VIP /// once the proposal is live and passed function testProposalMainnet() public { @@ -16,6 +24,7 @@ contract Runner is TimelockSimulation { // simulate( // getMainnetProposal(), // TimelockController(payable(MainnetAddresses.TIMELOCK_CONTROLLER)), + // mainnetPCVGuardian, // MainnetAddresses.GOVERNOR, // MainnetAddresses.EOA_1, // vm, @@ -29,6 +38,7 @@ contract Runner is TimelockSimulation { // simulate( // getArbitrumProposal(), // TimelockController(payable(ArbitrumAddresses.TIMELOCK_CONTROLLER)), + // arbitrumPCVGuardian, // ArbitrumAddresses.GOVERNOR, // ArbitrumAddresses.EOA_1, // vm, diff --git a/contracts/test/integration/vip/examples/vip_x_grant.sol b/contracts/test/integration/vip/examples/vip_x_grant.sol new file mode 100644 index 000000000..b8020dd95 --- /dev/null +++ b/contracts/test/integration/vip/examples/vip_x_grant.sol @@ -0,0 +1,60 @@ +pragma solidity =0.8.13; + +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +import {TimelockSimulation} from "../../utils/TimelockSimulation.sol"; +import {ArbitrumAddresses} from "../../fixtures/ArbitrumAddresses.sol"; +import {MainnetAddresses} from "../../fixtures/MainnetAddresses.sol"; +import {PriceBoundPSM} from "../../../../peg/PriceBoundPSM.sol"; +import {AllRoles} from "./../../utils/AllRoles.sol"; +import {DSTest} from "./../../../unit/utils/DSTest.sol"; +import {Core} from "../../../../core/Core.sol"; +import {Volt} from "../../../../volt/Volt.sol"; +import {IVIP} from "./../IVIP.sol"; +import {Vm} from "./../../../unit/utils/Vm.sol"; + +contract vip_x_grant is DSTest, IVIP { + using SafeCast for *; + + Vm public constant vm = Vm(HEVM_ADDRESS); + + /// --------------- Mainnet --------------- + + /// this is an example proposal that will fail the PCV Guardian whitelist test + /// as PCV is being transferrred to a non authorized smart contract + function getMainnetProposal() + public + pure + override + returns (TimelockSimulation.action[] memory proposal) + { + proposal = new TimelockSimulation.action[](1); + + proposal[0].target = MainnetAddresses.CORE; + proposal[0].value = 0; + proposal[0].arguments = abi.encodeWithSignature( + "grantPCVController(address)", + MainnetAddresses.REVOKED_EOA_1 + ); + proposal[0] + .description = "Grant PCV Controller and not setting in whitelist of PCV Guardian fails preflight checks"; + } + + function mainnetValidate() public override {} + + function mainnetSetup() public override {} + + /// --------------- Arbitrum --------------- + + function getArbitrumProposal() + public + pure + override + returns (TimelockSimulation.action[] memory proposal) + {} + + function arbitrumSetup() public override {} + + function arbitrumValidate() public override {} +} diff --git a/contracts/test/integration/vip/examples/vip_x_grant_succeed.sol b/contracts/test/integration/vip/examples/vip_x_grant_succeed.sol new file mode 100644 index 000000000..575389a5c --- /dev/null +++ b/contracts/test/integration/vip/examples/vip_x_grant_succeed.sol @@ -0,0 +1,67 @@ +pragma solidity =0.8.13; + +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +import {TimelockSimulation} from "../../utils/TimelockSimulation.sol"; +import {ArbitrumAddresses} from "../../fixtures/ArbitrumAddresses.sol"; +import {MainnetAddresses} from "../../fixtures/MainnetAddresses.sol"; +import {PriceBoundPSM} from "../../../../peg/PriceBoundPSM.sol"; +import {AllRoles} from "./../../utils/AllRoles.sol"; +import {DSTest} from "./../../../unit/utils/DSTest.sol"; +import {Core} from "../../../../core/Core.sol"; +import {Volt} from "../../../../volt/Volt.sol"; +import {IVIP} from "./../IVIP.sol"; +import {Vm} from "./../../../unit/utils/Vm.sol"; + +contract vip_x_grant_succeed is DSTest, IVIP { + using SafeCast for *; + + Vm public constant vm = Vm(HEVM_ADDRESS); + + /// --------------- Mainnet --------------- + + /// this is an example proposal that will fail the PCV Guardian whitelist test + /// as PCV is being transferrred to a non authorized smart contract + function getMainnetProposal() + public + pure + override + returns (TimelockSimulation.action[] memory proposal) + { + proposal = new TimelockSimulation.action[](2); + + proposal[0].target = MainnetAddresses.CORE; + proposal[0].value = 0; + proposal[0].arguments = abi.encodeWithSignature( + "grantPCVController(address)", + MainnetAddresses.GOVERNOR + ); + proposal[0].description = "Grant PCV Controller"; + + proposal[1].target = MainnetAddresses.PCV_GUARDIAN; + proposal[1].value = 0; + proposal[1].arguments = abi.encodeWithSignature( + "addWhitelistAddress(address)", + MainnetAddresses.GOVERNOR + ); + proposal[1].description = "Add governor to whitelist in PCV Guardian"; + } + + function mainnetValidate() public override {} + + function mainnetSetup() public override {} + + /// --------------- Arbitrum --------------- + + function getArbitrumProposal() + public + pure + override + returns (TimelockSimulation.action[] memory proposal) + {} + + function arbitrumSetup() public override {} + + function arbitrumValidate() public override {} +} diff --git a/contracts/test/integration/vip/examples/vip_x_transfer.sol b/contracts/test/integration/vip/examples/vip_x_transfer.sol new file mode 100644 index 000000000..8a2a1fac6 --- /dev/null +++ b/contracts/test/integration/vip/examples/vip_x_transfer.sol @@ -0,0 +1,69 @@ +pragma solidity =0.8.13; + +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +import {TimelockSimulation} from "../../utils/TimelockSimulation.sol"; +import {ArbitrumAddresses} from "../../fixtures/ArbitrumAddresses.sol"; +import {MainnetAddresses} from "../../fixtures/MainnetAddresses.sol"; +import {PriceBoundPSM} from "../../../../peg/PriceBoundPSM.sol"; +import {AllRoles} from "./../../utils/AllRoles.sol"; +import {DSTest} from "./../../../unit/utils/DSTest.sol"; +import {Core} from "../../../../core/Core.sol"; +import {Volt} from "../../../../volt/Volt.sol"; +import {IVIP} from "./../IVIP.sol"; +import {Vm} from "./../../../unit/utils/Vm.sol"; + +contract vip_x_transfer is DSTest, IVIP { + using SafeCast for *; + + Vm public constant vm = Vm(HEVM_ADDRESS); + + /// --------------- Mainnet --------------- + + /// this is an example proposal that will fail the PCV Guardian whitelist test + /// as PCV is being transferrred to a non authorized smart contract + function getMainnetProposal() + public + pure + override + returns (TimelockSimulation.action[] memory proposal) + { + proposal = new TimelockSimulation.action[](1); + + proposal[0].target = MainnetAddresses.VOLT; + proposal[0].value = 0; + proposal[0].arguments = abi.encodeWithSignature( + "transfer(address,uint256)", + MainnetAddresses.REVOKED_EOA_1, + 1 + ); + proposal[0] + .description = "Transfer 1 VOLT to non whitelisted contract in PCV Guardian fails preflight checks"; + } + + function mainnetValidate() public override {} + + function mainnetSetup() public override { + vm.startPrank(MainnetAddresses.GOVERNOR); + Core(MainnetAddresses.CORE).grantMinter(MainnetAddresses.GOVERNOR); + Volt(MainnetAddresses.VOLT).mint( + MainnetAddresses.TIMELOCK_CONTROLLER, + 1 + ); + vm.stopPrank(); + } + + /// --------------- Arbitrum --------------- + + function getArbitrumProposal() + public + pure + override + returns (TimelockSimulation.action[] memory proposal) + {} + + function arbitrumSetup() public override {} + + function arbitrumValidate() public override {} +}