From 7e430af642d3589fc494955b587b69d68acc2dca Mon Sep 17 00:00:00 2001 From: Elliot <34463580+ElliotFriedman@users.noreply.github.com> Date: Wed, 18 May 2022 14:53:41 -0700 Subject: [PATCH] USDC PSM (#76) * add usdc psm * add tests, fix logic error in OracleRef, add comments to deploy script * update deployment script to use correct floor and ceiling prices * update naming in tests, add additional docs in deploy script * change naming in usdc psm * remove unused vars in integration test * remove unused vars in test * remove incorrect comment * add comment in integration tests * add explainer comments to deploy script * update reserves threshold to be uint_max so that surplus can never be allocated * add USDC PSM to config file, add newlines and logging on etherscan verification --- contracts/refs/OracleRef.sol | 9 +- .../test/integration/IntegrationTest.t.sol | 11 - .../IntegrationTestPriceBoundPSM.t.sol | 6 - .../IntegrationTestPriceBoundPSMUSDC.t.sol | 333 ++++++++++++++++++ scripts/Config.ts | 2 + scripts/priceBoundPSMDeployUSDC.ts | 168 +++++++++ 6 files changed, 508 insertions(+), 21 deletions(-) delete mode 100644 contracts/test/integration/IntegrationTest.t.sol create mode 100644 contracts/test/integration/IntegrationTestPriceBoundPSMUSDC.t.sol create mode 100644 scripts/priceBoundPSMDeployUSDC.ts diff --git a/contracts/refs/OracleRef.sol b/contracts/refs/OracleRef.sol index e7ba5e6e9..d00ead9c4 100644 --- a/contracts/refs/OracleRef.sol +++ b/contracts/refs/OracleRef.sol @@ -105,6 +105,11 @@ abstract contract OracleRef is IOracleRef, CoreRef { } require(valid, "OracleRef: oracle invalid"); + // Invert the oracle price if necessary + if (doInvert) { + _peg = invert(_peg); + } + // Scale the oracle price by token decimals delta if necessary uint256 scalingFactor; if (decimalsNormalizer < 0) { @@ -115,10 +120,6 @@ abstract contract OracleRef is IOracleRef, CoreRef { _peg = _peg.mul(scalingFactor); } - // Invert the oracle price if necessary - if (doInvert) { - _peg = invert(_peg); - } return _peg; } diff --git a/contracts/test/integration/IntegrationTest.t.sol b/contracts/test/integration/IntegrationTest.t.sol deleted file mode 100644 index 218f84102..000000000 --- a/contracts/test/integration/IntegrationTest.t.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.0; - -import {DSTest} from "../unit/utils/DSTest.sol"; -import {StdLib} from "../unit/utils/StdLib.sol"; - -contract IntegrationTest is DSTest, StdLib { - function setUp() public {} - - function testPass() public {} -} diff --git a/contracts/test/integration/IntegrationTestPriceBoundPSM.t.sol b/contracts/test/integration/IntegrationTestPriceBoundPSM.t.sol index 1bf05a9b3..63f98ee11 100644 --- a/contracts/test/integration/IntegrationTestPriceBoundPSM.t.sol +++ b/contracts/test/integration/IntegrationTestPriceBoundPSM.t.sol @@ -347,10 +347,4 @@ contract IntegrationTestPriceBoundPSMTest is DSTest { vm.expectRevert(bytes("PegStabilityModule: Minting paused")); psm.mint(address(this), 100, 100); } - - /// @notice mint fails when price has not increased enough to get minAmountVoltOut - function testLog() public { - emit log_uint(psm.readOracle().value); - // emit logs(bytes(psm.isPriceValid())); - } } diff --git a/contracts/test/integration/IntegrationTestPriceBoundPSMUSDC.t.sol b/contracts/test/integration/IntegrationTestPriceBoundPSMUSDC.t.sol new file mode 100644 index 000000000..e138a6504 --- /dev/null +++ b/contracts/test/integration/IntegrationTestPriceBoundPSMUSDC.t.sol @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.4; + +import {ERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +import {MockPCVDepositV2} from "../../mock/MockPCVDepositV2.sol"; +import {IPCVDeposit} from "../../pcv/IPCVDeposit.sol"; +import {MockERC20} from "../../mock/MockERC20.sol"; +import {OraclePassThrough} from "../../oracle/OraclePassThrough.sol"; +import {ScalingPriceOracle} from "../../oracle/ScalingPriceOracle.sol"; +import {MockScalingPriceOracle} from "../../mock/MockScalingPriceOracle.sol"; +import {ICore} from "../../core/ICore.sol"; +import {Core} from "../../core/Core.sol"; +import {IVolt, Volt} from "../../volt/Volt.sol"; +import {PriceBoundPSM, PegStabilityModule} from "../../peg/PriceBoundPSM.sol"; +import {getCore, getMainnetAddresses, FeiTestAddresses} from "../unit/utils/Fixtures.sol"; +import {ERC20CompoundPCVDeposit} from "../../pcv/compound/ERC20CompoundPCVDeposit.sol"; +import {Vm} from "./../unit/utils/Vm.sol"; +import {DSTest} from "./../unit/utils/DSTest.sol"; + +contract IntegrationTestPriceBoundPSMUSDCTest is DSTest { + using SafeCast for *; + PriceBoundPSM private psm; + ICore private core = ICore(0xEC7AD284f7Ad256b64c6E69b84Eb0F48f42e8196); + IVolt private volt = IVolt(0x559eBC30b0E58a45Cc9fF573f77EF1e5eb1b3E18); + IERC20 private usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + IERC20 private underlyingToken = usdc; + + address public makerUSDCPSM = 0xAe2D4617c862309A3d75A0fFB358c7a5009c673F; + + /// ------------ Minting and RateLimited System Params ------------ + + uint256 public constant mintAmount = 10_000_000e6; + uint256 public constant voltMintAmount = 10_000_000e18; + + uint256 public constant bufferCap = 10_000_000e18; + uint256 public constant individualMaxBufferCap = 5_000_000e18; + uint256 public constant rps = 10_000e18; + + /// @notice live FEI PCV Deposit + ERC20CompoundPCVDeposit public immutable rariVoltPCVDeposit = + ERC20CompoundPCVDeposit(0xFeBDf448C8484834bb399d930d7E1bdC773E23bA); + + /// @notice Oracle Pass Through contract + OraclePassThrough public oracle = + OraclePassThrough(0x84dc71500D504163A87756dB6368CC8bB654592f); + + Vm public constant vm = Vm(HEVM_ADDRESS); + FeiTestAddresses public addresses = getMainnetAddresses(); + + /// these are inverted + uint256 voltFloorPrice = 9_000e12; /// 1 volt for .9 usdc is the max allowable price + uint256 voltCeilingPrice = 10_000e12; /// 1 volt for 1 usdc is the minimum price + uint256 reservesThreshold = type(uint256).max; /// max uint so that surplus can never be allocated into the pcv deposit + + function setUp() public { + PegStabilityModule.OracleParams memory oracleParams; + + oracleParams = PegStabilityModule.OracleParams({ + coreAddress: address(core), + oracleAddress: address(oracle), + backupOracle: address(0), + decimalsNormalizer: 12, + doInvert: true + }); + + /// create PSM + psm = new PriceBoundPSM( + voltFloorPrice, + voltCeilingPrice, + oracleParams, + 30, + 0, + reservesThreshold, + 10_000e18, + 10_000_000e18, + IERC20(address(usdc)), + rariVoltPCVDeposit + ); + + uint256 balance = usdc.balanceOf(makerUSDCPSM); + vm.prank(makerUSDCPSM); + usdc.transfer(address(this), balance); + + vm.prank(0x25dCffa22EEDbF0A69F6277e24C459108c186ecB); + core.grantGovernor(addresses.voltGovernorAddress); + + vm.startPrank(addresses.voltGovernorAddress); + + /// grant the PSM the PCV Controller role + core.grantMinter(addresses.voltGovernorAddress); + /// mint VOLT to the user + volt.mint(address(psm), voltMintAmount); + volt.mint(address(this), voltMintAmount); + vm.stopPrank(); + + usdc.transfer(address(psm), balance / 2); + } + + /// @notice PSM is set up correctly + function testSetUpCorrectly() public { + assertTrue(psm.doInvert()); + assertTrue(psm.isPriceValid()); + assertEq(psm.floor(), voltFloorPrice); + assertEq(psm.ceiling(), voltCeilingPrice); + assertEq(address(psm.oracle()), address(oracle)); + assertEq(address(psm.backupOracle()), address(0)); + assertEq(psm.decimalsNormalizer(), 12); + assertEq(psm.mintFeeBasisPoints(), 30); /// mint costs 30 bps + assertEq(psm.redeemFeeBasisPoints(), 0); /// redeem has no fee + assertEq(address(psm.underlyingToken()), address(usdc)); + assertEq(psm.reservesThreshold(), reservesThreshold); + } + + /// @notice PSM is set up correctly and redeem view function is working + function testGetRedeemAmountOut() public { + uint256 amountVoltIn = 100e12; + assertEq(psm.getRedeemAmountOut(amountVoltIn), 101); + } + + /// @notice PSM is set up correctly and view functions are working + function testGetMaxMintAmountOut() public { + uint256 startingBalance = volt.balanceOf(address(psm)); + assertEq(psm.getMaxMintAmountOut(), bufferCap + startingBalance); + + vm.startPrank(addresses.voltGovernorAddress); + volt.mint(address(psm), mintAmount); + vm.stopPrank(); + + assertEq( + psm.getMaxMintAmountOut(), + bufferCap + mintAmount + startingBalance + ); + } + + /// @notice PSM is set up correctly and view functions are working + function testGetMintAmountOut() public { + uint256 amountUSDCIn = 100; + assertApproxEq( + psm.getMintAmountOut(amountUSDCIn).toInt256(), + 9809e10, + 1 + ); /// values are within 9 basis points of each other + } + + /// @notice pcv deposit receives underlying token on mint + function testSwapUnderlyingForVoltAfterPriceIncrease() public { + uint256 amountStableIn = 101_000; + uint256 amountVoltOut = psm.getMintAmountOut(amountStableIn); + uint256 startingUserVoltBalance = volt.balanceOf(address(this)); + uint256 startingPSMUnderlyingBalance = underlyingToken.balanceOf( + address(psm) + ); + + underlyingToken.approve(address(psm), amountStableIn); + psm.mint(address(this), amountStableIn, amountVoltOut); + + uint256 endingUserVoltBalance = volt.balanceOf(address(this)); + uint256 endingPSMUnderlyingBalance = underlyingToken.balanceOf( + address(psm) + ); + + assertEq( + endingUserVoltBalance, + startingUserVoltBalance + amountVoltOut + ); + assertEq( + startingPSMUnderlyingBalance + amountStableIn, + endingPSMUnderlyingBalance + ); + } + + /// @notice pcv deposit gets depleted on redeem + function testSwapVoltForUSDC() public { + uint256 startingUserUnderlyingBalance = underlyingToken.balanceOf( + address(this) + ); + uint256 startingPSMUnderlyingBalance = underlyingToken.balanceOf( + address(psm) + ); + uint256 redeemAmountOut = psm.getRedeemAmountOut(mintAmount); + uint256 startingUserVOLTBalance = volt.balanceOf(address(this)); + + volt.approve(address(psm), mintAmount); + uint256 amountOut = psm.redeem( + address(this), + mintAmount, + redeemAmountOut + ); + + uint256 endingUserVOLTBalance = volt.balanceOf(address(this)); + uint256 endingUserUnderlyingBalance = underlyingToken.balanceOf( + address(this) + ); + uint256 endingPSMUnderlyingBalance = underlyingToken.balanceOf( + address(psm) + ); + + assertEq(startingUserVOLTBalance, endingUserVOLTBalance + mintAmount); + assertEq( + endingUserUnderlyingBalance, + startingUserUnderlyingBalance + amountOut + ); + assertEq( + endingPSMUnderlyingBalance, + startingPSMUnderlyingBalance - amountOut + ); + } + + /// @notice redeem fails without approval + function testSwapVoltForUSDCFailsWithoutApproval() public { + vm.expectRevert(bytes("ERC20: transfer amount exceeds allowance")); + + psm.redeem(address(this), mintAmount, mintAmount / 1e12); + } + + /// @notice mint fails without approval + function testSwapUnderlyingForVoltFailsWithoutApproval() public { + vm.expectRevert(bytes("ERC20: transfer amount exceeds allowance")); + + psm.mint(address(this), mintAmount, 0); + } + + /// @notice withdraw erc20 fails without correct permissions + function testERC20WithdrawFailure() public { + vm.expectRevert(bytes("CoreRef: Caller is not a PCV controller")); + + psm.withdrawERC20(address(underlyingToken), address(this), 100); + } + + /// @notice withdraw erc20 succeeds with correct permissions + function testERC20WithdrawSuccess() public { + vm.prank(addresses.voltGovernorAddress); + core.grantPCVController(address(this)); + + uint256 startingBalance = underlyingToken.balanceOf(address(this)); + psm.withdrawERC20(address(underlyingToken), address(this), mintAmount); + uint256 endingBalance = underlyingToken.balanceOf(address(this)); + + assertEq(endingBalance - startingBalance, mintAmount); + } + + /// @notice set global rate limited minter fails when caller is governor and new address is 0 + function testSetPCVDepositFailureZeroAddress() public { + vm.startPrank(addresses.voltGovernorAddress); + + vm.expectRevert( + bytes("PegStabilityModule: Invalid new surplus target") + ); + psm.setSurplusTarget(IPCVDeposit(address(0))); + + vm.stopPrank(); + } + + /// @notice set PCV deposit fails when caller is governor and new address is 0 + function testSetPCVDepositFailureNonGovernor() public { + vm.expectRevert( + bytes("CoreRef: Caller is not a governor or contract admin") + ); + psm.setSurplusTarget(IPCVDeposit(address(0))); + } + + /// @notice set PCV Deposit succeeds when caller is governor and underlying tokens match + function testSetPCVDepositSuccess() public { + vm.startPrank(addresses.voltGovernorAddress); + + MockPCVDepositV2 newPCVDeposit = new MockPCVDepositV2( + address(core), + address(underlyingToken), + 0, + 0 + ); + + psm.setSurplusTarget(IPCVDeposit(address(newPCVDeposit))); + + vm.stopPrank(); + + assertEq(address(newPCVDeposit), address(psm.surplusTarget())); + } + + /// @notice set mint fee succeeds + function testSetMintFeeSuccess() public { + vm.prank(addresses.voltGovernorAddress); + psm.setMintFee(100); + + assertEq(psm.mintFeeBasisPoints(), 100); + } + + /// @notice set mint fee fails unauthorized + function testSetMintFeeFailsWithoutCorrectRoles() public { + vm.expectRevert( + bytes("CoreRef: Caller is not a governor or contract admin") + ); + + psm.setMintFee(100); + } + + /// @notice set redeem fee succeeds + function testSetRedeemFeeSuccess() public { + vm.prank(addresses.voltGovernorAddress); + psm.setRedeemFee(100); + + assertEq(psm.redeemFeeBasisPoints(), 100); + } + + /// @notice set redeem fee fails unauthorized + function testSetRedeemFeeFailsWithoutCorrectRoles() public { + vm.expectRevert( + bytes("CoreRef: Caller is not a governor or contract admin") + ); + + psm.setRedeemFee(100); + } + + /// @notice redeem fails when paused + function testRedeemFailsWhenPaused() public { + vm.prank(addresses.voltGovernorAddress); + psm.pauseRedeem(); + + vm.expectRevert(bytes("PegStabilityModule: Redeem paused")); + psm.redeem(address(this), 100, 100); + } + + /// @notice mint fails when paused + function testMintFailsWhenPaused() public { + vm.prank(addresses.voltGovernorAddress); + psm.pauseMint(); + + vm.expectRevert(bytes("PegStabilityModule: Minting paused")); + psm.mint(address(this), 100, 100); + } +} diff --git a/scripts/Config.ts b/scripts/Config.ts index da4aa020d..de4dc7d43 100644 --- a/scripts/Config.ts +++ b/scripts/Config.ts @@ -13,6 +13,7 @@ const config = { PCV_DEPOSIT: '0x4188fbD7aDC72853E3275F1c3503E170994888D7', NON_CUSTODIAL_PSM: '0x18f251FC3CE0Cb690F13f62213aba343657d0E72', PRICE_BOUND_PSM: '0x985f9C331a9E4447C782B98D6693F5c7dF8e560e', + PRICE_BOUND_PSM_USDC: '0x0b9A7EA2FCA868C93640Dd77cF44df335095F501', MULTISIG_ADDRESS: '0x016177eDbB6809338Fda77b493cA01EA6D7Fc0D4', PROTOCOL_MULTISIG_ADDRESS: '0xcBB83206698E8788F85EFbEeeCAd17e53366EBDf', FEI_DAI_PSM: '0x2A188F9EB761F70ECEa083bA6c2A40145078dfc2', @@ -35,6 +36,7 @@ const config = { PSM_BUFFER_CAP: ethers.utils.parseEther('10000000'), // 10m VOLT FEI: '0x956F47F50A910163D8BF957Cf5846D573E7f87CA', DAI: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', POOL_8_FEI: '0xd8553552f8868C1Ef160eEdf031cF0BCf9686945', ZERO_ADDRESS: ethers.constants.AddressZero, PCV_GUARDIAN: '0x2c2b362e6ae0F080F39b90Cb5657E5550090D6C3', diff --git a/scripts/priceBoundPSMDeployUSDC.ts b/scripts/priceBoundPSMDeployUSDC.ts new file mode 100644 index 000000000..eb1b5da71 --- /dev/null +++ b/scripts/priceBoundPSMDeployUSDC.ts @@ -0,0 +1,168 @@ +import hre, { ethers } from 'hardhat'; +import { expect } from 'chai'; +import config from './Config'; +import { getImpersonatedSigner } from '@test/helpers'; + +/// ---------------------------------------------------------------- /// +/// /// +/// Price Bound Peg Stability Module /// +/// /// +/// ---------------------------------------------------------------- /// + +/// Description: This module is used to manage the stability of the peg. + +/// Steps: +/// 0 - Deploy USDC/VOLT PriceBoundPegStabilityModule +/// 1 - Pause Redemptions on the VOLT PriceBoundPegStabilityModule +/// 2 - Move VOLT into the PriceBoundPegStabilityModule to allow trades + +/// This contract needs no roles in the system, just funding to allow swaps + +/// ---------------------------------------------------------------- /// + +const { + CORE, + USDC, + VOLT, + PRICE_BOUND_PSM, + VOLT_FUSE_PCV_DEPOSIT, + MINT_FEE_BASIS_POINTS, + REDEEM_FEE_BASIS_POINTS, + PROTOCOL_MULTISIG_ADDRESS, + ORACLE_PASS_THROUGH_ADDRESS +} = config; + +const usdcReservesThreshold = ethers.constants.MaxUint256; /// max uint value so that we can never allocate surplus on this PSM to the pcv deposit +const mintLimitPerSecond = ethers.utils.parseEther('10000'); /// 10k Volt minted per second max, however this value is useless as this PSM will not be given the minter role +const voltPSMBufferCap = ethers.utils.parseEther('10000000'); /// 10m Volt max can be minted at a time, however this value is useless as this PSM will not be given the minter role +const initialUSDCPSMVoltAmount = ethers.utils.parseEther('1941000'); /// 1.941m Volt which equals $2m at current prices + +/// Oracle price gets scaled up by 1e12 to account for the differences in decimals of USDC and VOLT. +/// USDC has 6 decimals while Volt has 12, thus creating a difference that has to be normalized +const voltUSDCDecimalsNormalizer = 12; +/// Need to scale up price of floor and ceiling by 1e12 to account for decimal normalizer that is factored into price +const voltFloorPrice = '9000000000000000'; +const voltCeilingPrice = '10000000000000000'; + +/// Deploy the USDC PriceBoundPSM +const deploy = async () => { + const voltPSMFactory = await ethers.getContractFactory('PriceBoundPSM'); + + /// Deploy USDC Peg Stability Module + /// PSM will trade VOLT between 100 cents and 111 cents. If the Volt price exceeds 111 cents, + /// the floor price will have to be moved lower as the oracle price is inverted. + /// If price is outside of this band, the PSM will not allow trades + const voltPSM = await voltPSMFactory.deploy( + voltFloorPrice, + voltCeilingPrice, + { + coreAddress: CORE, + oracleAddress: ORACLE_PASS_THROUGH_ADDRESS, /// OPT + backupOracle: ethers.constants.AddressZero, + decimalsNormalizer: voltUSDCDecimalsNormalizer, + doInvert: true /// invert the price so that the Oracle and PSM works correctly + }, + MINT_FEE_BASIS_POINTS, + REDEEM_FEE_BASIS_POINTS, + usdcReservesThreshold, + mintLimitPerSecond, + voltPSMBufferCap, + USDC, + VOLT_FUSE_PCV_DEPOSIT + ); + + console.log('\nUSDC voltPSM: ', voltPSM.address); + + /// Wait for psm to deploy + await voltPSM.deployTransaction.wait(); + + /// ---------------------------- /// + /// Verify params /// + /// ---------------------------- /// + + /// oracle + expect(await voltPSM.doInvert()).to.be.true; + expect(await voltPSM.oracle()).to.be.equal(ORACLE_PASS_THROUGH_ADDRESS); + expect(await voltPSM.backupOracle()).to.be.equal(ethers.constants.AddressZero); + + /// volt + expect(await voltPSM.underlyingToken()).to.be.equal(USDC); + expect(await voltPSM.volt()).to.be.equal(VOLT); + + /// psm params + expect(await voltPSM.redeemFeeBasisPoints()).to.be.equal(REDEEM_FEE_BASIS_POINTS); /// 0 basis points + expect(await voltPSM.mintFeeBasisPoints()).to.be.equal(MINT_FEE_BASIS_POINTS); /// 30 basis points + expect(await voltPSM.reservesThreshold()).to.be.equal(usdcReservesThreshold); + expect(await voltPSM.surplusTarget()).to.be.equal(VOLT_FUSE_PCV_DEPOSIT); + expect(await voltPSM.rateLimitPerSecond()).to.be.equal(mintLimitPerSecond); + expect(await voltPSM.buffer()).to.be.equal(voltPSMBufferCap); + expect(await voltPSM.bufferCap()).to.be.equal(voltPSMBufferCap); + + /// price bound params + expect(await voltPSM.floor()).to.be.equal(voltFloorPrice); + expect(await voltPSM.ceiling()).to.be.equal(voltCeilingPrice); + expect(await voltPSM.isPriceValid()).to.be.true; + + /// balance check + expect(await voltPSM.balance()).to.be.equal(0); + expect(await voltPSM.voltBalance()).to.be.equal(0); + + if (hre.network.name === 'mainnet') { + await verifyDeployment(voltPSM.address); + console.log('\n ~~~ Successfully Verified PSM on Etherscan ~~~ '); + } else { + console.log(' ~~~ Simulating Multisig Steps ~~~ '); + const signer = await getImpersonatedSigner(PROTOCOL_MULTISIG_ADDRESS); + const feiPriceBoundPSM = await ethers.getContractAt('PriceBoundPSM', PRICE_BOUND_PSM); + + /// pause redemptions on this PSM + await voltPSM.connect(signer).pauseRedeem(); + + /// seed this PSM with $2m worth of Volt + await feiPriceBoundPSM.connect(signer).withdrawERC20(VOLT, voltPSM.address, initialUSDCPSMVoltAmount); + + /// validate deployment + expect(await voltPSM.voltBalance()).to.be.equal(initialUSDCPSMVoltAmount); + expect(await voltPSM.redeemPaused()).to.be.true; + console.log(' ~~~ Successfully Validated Multisig Steps ~~~ '); + } + + return { + voltPSM + }; +}; + +/// verify contract on etherscan +async function verifyDeployment(priceBoundPSMAddress: string) { + const oracleParams = { + coreAddress: CORE, + oracleAddress: ORACLE_PASS_THROUGH_ADDRESS, /// OPT + backupOracle: ethers.constants.AddressZero, + decimalsNormalizer: voltUSDCDecimalsNormalizer, + doInvert: true /// invert the price so that the Oracle and PSM works correctly + }; + + await hre.run('verify:verify', { + address: priceBoundPSMAddress, + + constructorArguments: [ + voltFloorPrice, + voltCeilingPrice, + oracleParams, + MINT_FEE_BASIS_POINTS, + REDEEM_FEE_BASIS_POINTS, + usdcReservesThreshold, + mintLimitPerSecond, + voltPSMBufferCap, + USDC, + VOLT_FUSE_PCV_DEPOSIT + ] + }); +} + +deploy() + .then(() => process.exit(0)) + .catch((err) => { + console.log(err); + process.exit(1); + });