From 8aaa03cffd9aba9b0325a42c35c9bebd3a97267d Mon Sep 17 00:00:00 2001 From: rndquu <119500907+rndquu@users.noreply.github.com> Date: Wed, 24 Apr 2024 16:14:58 +0300 Subject: [PATCH] feat: add ICurveStableSwapNG (#933) * feat: add ICurveStableSwapNG * feat: add plain pool setter * feat: add stable/ETH setter * feat: fetch Dollar price * feat: add Stable/USD setter --- .../src/dollar/facets/ManagerFacet.sol | 19 ++ .../src/dollar/facets/UbiquityPoolFacet.sol | 20 +++ .../dollar/interfaces/ICurveStableSwapNG.sol | 9 + .../src/dollar/interfaces/IUbiquityPool.sol | 21 +++ .../src/dollar/libraries/LibAppStorage.sol | 1 + .../src/dollar/libraries/LibUbiquityPool.sol | 88 +++++++++- .../dollar/mocks/MockCurveStableSwapNG.sol | 15 ++ .../test/diamond/facets/ManagerFacet.t.sol | 8 + .../diamond/facets/UbiquityPoolFacet.t.sol | 164 ++++++++++++++---- 9 files changed, 307 insertions(+), 38 deletions(-) create mode 100644 packages/contracts/src/dollar/interfaces/ICurveStableSwapNG.sol create mode 100644 packages/contracts/src/dollar/mocks/MockCurveStableSwapNG.sol diff --git a/packages/contracts/src/dollar/facets/ManagerFacet.sol b/packages/contracts/src/dollar/facets/ManagerFacet.sol index 2e7611cc8..62f1e23b4 100644 --- a/packages/contracts/src/dollar/facets/ManagerFacet.sol +++ b/packages/contracts/src/dollar/facets/ManagerFacet.sol @@ -144,6 +144,17 @@ contract ManagerFacet is Modifiers { store.stableSwapMetaPoolAddress = _stableSwapMetaPoolAddress; } + /** + * @notice Sets Curve's Dollar-Stablecoin plain pool address + * @dev `_stableSwapPlainPoolAddress` is used to fetch Dollar price in USD + * @param _stableSwapPlainPoolAddress Curve's Dollar-Stablecoin plain pool address + */ + function setStableSwapPlainPoolAddress( + address _stableSwapPlainPoolAddress + ) external onlyAdmin { + store.stableSwapPlainPoolAddress = _stableSwapPlainPoolAddress; + } + /** * @notice Sets staking contract address * @dev Staking contract participants deposit Curve LP tokens @@ -385,6 +396,14 @@ contract ManagerFacet is Modifiers { return store.stableSwapMetaPoolAddress; } + /** + * @notice Returns Curve's plain pool address for Dollar-Stablecoin pair + * @return Curve's plain pool address for Dollar-Stablecoin pair + */ + function stableSwapPlainPoolAddress() external view returns (address) { + return store.stableSwapPlainPoolAddress; + } + /** * @notice Returns staking address * @return Staking address diff --git a/packages/contracts/src/dollar/facets/UbiquityPoolFacet.sol b/packages/contracts/src/dollar/facets/UbiquityPoolFacet.sol index 6f9b7b058..b80320bc1 100644 --- a/packages/contracts/src/dollar/facets/UbiquityPoolFacet.sol +++ b/packages/contracts/src/dollar/facets/UbiquityPoolFacet.sol @@ -116,6 +116,15 @@ contract UbiquityPoolFacet is IUbiquityPool, Modifiers { return LibUbiquityPool.governanceEthPoolAddress(); } + /// @inheritdoc IUbiquityPool + function stableUsdPriceFeedInformation() + external + view + returns (address, uint256) + { + return LibUbiquityPool.stableUsdPriceFeedInformation(); + } + //==================== // Public functions //==================== @@ -293,6 +302,17 @@ contract UbiquityPoolFacet is IUbiquityPool, Modifiers { LibUbiquityPool.setRedemptionDelayBlocks(newRedemptionDelayBlocks); } + /// @inheritdoc IUbiquityPool + function setStableUsdChainLinkPriceFeed( + address newPriceFeedAddress, + uint256 newStalenessThreshold + ) external onlyAdmin { + LibUbiquityPool.setStableUsdChainLinkPriceFeed( + newPriceFeedAddress, + newStalenessThreshold + ); + } + /// @inheritdoc IUbiquityPool function toggleCollateral(uint256 collateralIndex) external onlyAdmin { LibUbiquityPool.toggleCollateral(collateralIndex); diff --git a/packages/contracts/src/dollar/interfaces/ICurveStableSwapNG.sol b/packages/contracts/src/dollar/interfaces/ICurveStableSwapNG.sol new file mode 100644 index 000000000..e6c548478 --- /dev/null +++ b/packages/contracts/src/dollar/interfaces/ICurveStableSwapNG.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ICurveStableSwapMetaNG} from "./ICurveStableSwapMetaNG.sol"; + +/** + * @notice Curve's interface for plain pool which contains only USD pegged assets + */ +interface ICurveStableSwapNG is ICurveStableSwapMetaNG {} diff --git a/packages/contracts/src/dollar/interfaces/IUbiquityPool.sol b/packages/contracts/src/dollar/interfaces/IUbiquityPool.sol index aa41b7e29..909ddc236 100644 --- a/packages/contracts/src/dollar/interfaces/IUbiquityPool.sol +++ b/packages/contracts/src/dollar/interfaces/IUbiquityPool.sol @@ -121,6 +121,16 @@ interface IUbiquityPool { */ function governanceEthPoolAddress() external view returns (address); + /** + * @notice Returns chainlink price feed information for stable/USD pair + * @dev Here stable coin refers to the 1st coin in the Curve's stable/Dollar plain pool + * @return Price feed address and staleness threshold in seconds + */ + function stableUsdPriceFeedInformation() + external + view + returns (address, uint256); + //==================== // Public functions //==================== @@ -328,6 +338,17 @@ interface IUbiquityPool { uint256 newRedemptionDelayBlocks ) external; + /** + * @notice Sets chainlink params for stable/USD price feed + * @dev Here stable coin refers to the 1st coin in the Curve's stable/Dollar plain pool + * @param newPriceFeedAddress New chainlink price feed address for stable/USD pair + * @param newStalenessThreshold New threshold in seconds when chainlink's stable/USD price feed answer should be considered stale + */ + function setStableUsdChainLinkPriceFeed( + address newPriceFeedAddress, + uint256 newStalenessThreshold + ) external; + /** * @notice Toggles (i.e. enables/disables) a particular collateral token * @param collateralIndex Collateral token index diff --git a/packages/contracts/src/dollar/libraries/LibAppStorage.sol b/packages/contracts/src/dollar/libraries/LibAppStorage.sol index 6bd153c67..e7b94dd4d 100644 --- a/packages/contracts/src/dollar/libraries/LibAppStorage.sol +++ b/packages/contracts/src/dollar/libraries/LibAppStorage.sol @@ -17,6 +17,7 @@ struct AppStorage { address stakingShareAddress; address stakingContractAddress; address stableSwapMetaPoolAddress; + address stableSwapPlainPoolAddress; address curve3PoolTokenAddress; // 3CRV address treasuryAddress; address governanceTokenAddress; diff --git a/packages/contracts/src/dollar/libraries/LibUbiquityPool.sol b/packages/contracts/src/dollar/libraries/LibUbiquityPool.sol index 695fc75cc..b3f8a5e51 100644 --- a/packages/contracts/src/dollar/libraries/LibUbiquityPool.sol +++ b/packages/contracts/src/dollar/libraries/LibUbiquityPool.sol @@ -6,7 +6,7 @@ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import {ICurveStableSwapMetaNG} from "../interfaces/ICurveStableSwapMetaNG.sol"; +import {ICurveStableSwapNG} from "../interfaces/ICurveStableSwapNG.sol"; import {ICurveTwocryptoOptimized} from "../interfaces/ICurveTwocryptoOptimized.sol"; import {IDollarAmoMinter} from "../interfaces/IDollarAmoMinter.sol"; import {IERC20Ubiquity} from "../interfaces/IERC20Ubiquity.sol"; @@ -103,6 +103,13 @@ library LibUbiquityPool { uint256 ethUsdPriceFeedStalenessThreshold; // Curve's CurveTwocryptoOptimized contract for Governance/ETH pair address governanceEthPoolAddress; + //================================ + // Dollar token pricing related + //================================ + // chainlink price feed for stable/USD pair + address stableUsdPriceFeedAddress; + // threshold in seconds when chainlink's stable/USD price feed answer should be considered stale + uint256 stableUsdPriceFeedStalenessThreshold; } /// @notice Struct used for detailed collateral information @@ -182,6 +189,11 @@ library LibUbiquityPool { ); /// @notice Emitted when a new redemption delay in blocks is set event RedemptionDelayBlocksSet(uint256 redemptionDelayBlocks); + /// @notice Emitted on setting chainlink's price feed for stable/USD pair + event StableUsdPriceFeedSet( + address newPriceFeedAddress, + uint256 newStalenessThreshold + ); //===================== // Modifiers @@ -362,7 +374,11 @@ library LibUbiquityPool { } /** - * @notice Returns Ubiquity Dollar token USD price (1e6 precision) from Curve Metapool (Ubiquity Dollar, Curve Tri-Pool LP) + * @notice Returns Ubiquity Dollar token USD price (1e6 precision) from Curve plain pool (Stable coin, Ubiquity Dollar) + * How it works: + * 1. Fetch Stable/USD quote from chainlink + * 2. Fetch Dollar/Stable quote from Curve's plain pool + * 3. Calculate Dollar token price in USD * @return dollarPriceUsd USD price of Ubiquity Dollar */ function getDollarPriceUsd() @@ -370,15 +386,39 @@ library LibUbiquityPool { view returns (uint256 dollarPriceUsd) { - // load storage shared across all libraries AppStorage storage store = LibAppStorage.appStorage(); - // get Dollar price from Curve Metapool (18 decimals) - uint256 dollarPriceUsdD18 = ICurveStableSwapMetaNG( - store.stableSwapMetaPoolAddress + UbiquityPoolStorage storage poolStorage = ubiquityPoolStorage(); + + // fetch Stable/USD quote from chainlink (8 decimals) + AggregatorV3Interface stableUsdPriceFeed = AggregatorV3Interface( + poolStorage.stableUsdPriceFeedAddress + ); + ( + , + int256 stableUsdAnswer, + , + uint256 stableUsdUpdatedAt, + + ) = stableUsdPriceFeed.latestRoundData(); + uint256 stableUsdPriceFeedDecimals = stableUsdPriceFeed.decimals(); + // validate Stable/USD chainlink response + require(stableUsdAnswer > 0, "Invalid Stable/USD price"); + require( + block.timestamp - stableUsdUpdatedAt < + poolStorage.stableUsdPriceFeedStalenessThreshold, + "Stale Stable/USD data" + ); + + // fetch Dollar/Stable quote from Curve's plain pool (18 decimals) + uint256 dollarPriceUsdD18 = ICurveStableSwapNG( + store.stableSwapPlainPoolAddress ).price_oracle(0); + // convert to 6 decimals dollarPriceUsd = dollarPriceUsdD18 .mul(UBIQUITY_POOL_PRICE_PRECISION) + .mul(uint256(stableUsdAnswer)) + .div(10 ** stableUsdPriceFeedDecimals) .div(1e18); } @@ -467,6 +507,23 @@ library LibUbiquityPool { return poolStorage.governanceEthPoolAddress; } + /** + * @notice Returns chainlink price feed information for stable/USD pair + * @dev Here stable coin refers to the 1st coin in the Curve's stable/Dollar plain pool + * @return Price feed address and staleness threshold in seconds + */ + function stableUsdPriceFeedInformation() + internal + view + returns (address, uint256) + { + UbiquityPoolStorage storage poolStorage = ubiquityPoolStorage(); + return ( + poolStorage.stableUsdPriceFeedAddress, + poolStorage.stableUsdPriceFeedStalenessThreshold + ); + } + //==================== // Public functions //==================== @@ -1124,6 +1181,25 @@ library LibUbiquityPool { emit RedemptionDelayBlocksSet(newRedemptionDelayBlocks); } + /** + * @notice Sets chainlink params for stable/USD price feed + * @dev Here stable coin refers to the 1st coin in the Curve's stable/Dollar plain pool + * @param newPriceFeedAddress New chainlink price feed address for stable/USD pair + * @param newStalenessThreshold New threshold in seconds when chainlink's stable/USD price feed answer should be considered stale + */ + function setStableUsdChainLinkPriceFeed( + address newPriceFeedAddress, + uint256 newStalenessThreshold + ) internal { + UbiquityPoolStorage storage poolStorage = ubiquityPoolStorage(); + + poolStorage.stableUsdPriceFeedAddress = newPriceFeedAddress; + poolStorage + .stableUsdPriceFeedStalenessThreshold = newStalenessThreshold; + + emit StableUsdPriceFeedSet(newPriceFeedAddress, newStalenessThreshold); + } + /** * @notice Toggles (i.e. enables/disables) a particular collateral token * @param collateralIndex Collateral token index diff --git a/packages/contracts/src/dollar/mocks/MockCurveStableSwapNG.sol b/packages/contracts/src/dollar/mocks/MockCurveStableSwapNG.sol new file mode 100644 index 000000000..d95f996b9 --- /dev/null +++ b/packages/contracts/src/dollar/mocks/MockCurveStableSwapNG.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ICurveStableSwapNG} from "../interfaces/ICurveStableSwapNG.sol"; +import {MockCurveStableSwapMetaNG} from "./MockCurveStableSwapMetaNG.sol"; + +contract MockCurveStableSwapNG is + ICurveStableSwapNG, + MockCurveStableSwapMetaNG +{ + constructor( + address _token0, + address _token1 + ) MockCurveStableSwapMetaNG(_token0, _token1) {} +} diff --git a/packages/contracts/test/diamond/facets/ManagerFacet.t.sol b/packages/contracts/test/diamond/facets/ManagerFacet.t.sol index 61c245bca..2e7909c97 100644 --- a/packages/contracts/test/diamond/facets/ManagerFacet.t.sol +++ b/packages/contracts/test/diamond/facets/ManagerFacet.t.sol @@ -85,6 +85,14 @@ contract ManagerFacetTest is DiamondTestSetup { assertEq(managerFacet.stableSwapMetaPoolAddress(), contract1); } + function testSetStableSwapPlainPoolAddress_ShouldSucceed() + public + prankAs(admin) + { + managerFacet.setStableSwapPlainPoolAddress(contract1); + assertEq(managerFacet.stableSwapPlainPoolAddress(), contract1); + } + function testSetStakingContractAddress_ShouldSucceed() public prankAs(admin) diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.t.sol index de0cf1807..afc0b2723 100644 --- a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.t.sol +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.t.sol @@ -7,7 +7,7 @@ import {IDollarAmoMinter} from "../../../src/dollar/interfaces/IDollarAmoMinter. import {LibUbiquityPool} from "../../../src/dollar/libraries/LibUbiquityPool.sol"; import {MockChainLinkFeed} from "../../../src/dollar/mocks/MockChainLinkFeed.sol"; import {MockERC20} from "../../../src/dollar/mocks/MockERC20.sol"; -import {MockCurveStableSwapMetaNG} from "../../../src/dollar/mocks/MockCurveStableSwapMetaNG.sol"; +import {MockCurveStableSwapNG} from "../../../src/dollar/mocks/MockCurveStableSwapNG.sol"; import {MockCurveTwocryptoOptimized} from "../../../src/dollar/mocks/MockCurveTwocryptoOptimized.sol"; contract MockDollarAmoMinter is IDollarAmoMinter { @@ -24,10 +24,11 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { MockDollarAmoMinter dollarAmoMinter; MockERC20 collateralToken; MockChainLinkFeed collateralTokenPriceFeed; - MockCurveStableSwapMetaNG curveDollarMetaPool; + MockCurveStableSwapNG curveDollarPlainPool; MockCurveTwocryptoOptimized curveGovernanceEthPool; - MockERC20 curveTriPoolLpToken; + MockERC20 stableToken; MockChainLinkFeed ethUsdPriceFeed; + MockChainLinkFeed stableUsdPriceFeed; MockERC20 wethToken; address user = address(1); @@ -60,6 +61,10 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { uint256 newRedeemPriceThreshold ); event RedemptionDelayBlocksSet(uint256 redemptionDelayBlocks); + event StableUsdPriceFeedSet( + address newPriceFeedAddress, + uint256 newStalenessThreshold + ); function setUp() public override { super.setUp(); @@ -75,16 +80,19 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { // init ETH/USD price feed ethUsdPriceFeed = new MockChainLinkFeed(); + // init stable/USD price feed + stableUsdPriceFeed = new MockChainLinkFeed(); + // init WETH token wethToken = new MockERC20("WETH", "WETH", 18); - // init Curve 3CRV-LP token - curveTriPoolLpToken = new MockERC20("3CRV", "3CRV", 18); + // init stable USD pegged token + stableToken = new MockERC20("STABLE", "STABLE", 18); - // init Curve Dollar-3CRV LP metapool - curveDollarMetaPool = new MockCurveStableSwapMetaNG( - address(dollarToken), - address(curveTriPoolLpToken) + // init Curve Stable-Dollar plain pool + curveDollarPlainPool = new MockCurveStableSwapNG( + address(stableToken), + address(dollarToken) ); // init Curve Governance-WETH crypto pool @@ -113,14 +121,23 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { // set ETH/USD price feed mock params ethUsdPriceFeed.updateMockParams( 1, // round id - 3000_00000000, // answer, 3000_00000000 = $3000 (8 decimals) + 2000_00000000, // answer, 2000_00000000 = $2000 (8 decimals) + block.timestamp, // started at + block.timestamp, // updated at + 1 // answered in round + ); + + // set stable/USD price feed mock params + stableUsdPriceFeed.updateMockParams( + 1, // round id + 100_000_000, // answer, 100_000_000 = $1.00 (8 decimals) block.timestamp, // started at block.timestamp, // updated at 1 // answered in round ); - // set ETH/Governance price to 30k in Curve pool mock - curveGovernanceEthPool.updateMockParams(30_000e18); + // set ETH/Governance price to 20k in Curve pool mock + curveGovernanceEthPool.updateMockParams(20_000e18); // set price feed for collateral token ubiquityPoolFacet.setCollateralChainLinkPriceFeed( @@ -135,6 +152,12 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { 1 days // price feed staleness threshold in seconds ); + // set price feed for stable/USD pair + ubiquityPoolFacet.setStableUsdChainLinkPriceFeed( + address(stableUsdPriceFeed), // price feed address + 1 days // price feed staleness threshold in seconds + ); + // enable collateral at index 0 ubiquityPoolFacet.toggleCollateral(0); // set mint and redeem fees @@ -159,8 +182,10 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { // add AMO minter ubiquityPoolFacet.addAmoMinter(address(dollarAmoMinter)); - // set metapool in manager facet - managerFacet.setStableSwapMetaPoolAddress(address(curveDollarMetaPool)); + // set Curve plain pool in manager facet + managerFacet.setStableSwapPlainPoolAddress( + address(curveDollarPlainPool) + ); // stop being admin vm.stopPrank(); @@ -325,6 +350,41 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { assertEq(amount, 100e18); } + function testGetDollarPriceUsd_ShouldRevertOnInvalidStableUsdChainlinkAnswer() + public + { + // set invalid answer from chainlink + stableUsdPriceFeed.updateMockParams( + 1, // round id + 0, // invalid answer + block.timestamp, // started at + block.timestamp, // updated at + 1 // answered in round + ); + + vm.expectRevert("Invalid Stable/USD price"); + ubiquityPoolFacet.getDollarPriceUsd(); + } + + function testGetDollarPriceUsd_ShouldRevertIfStableUsdChainlinkAnswerIsStale() + public + { + // set stale answer from chainlink + stableUsdPriceFeed.updateMockParams( + 1, // round id + 100_000_000, // answer, 100_000_000 = $1.00 + block.timestamp, // started at + block.timestamp, // updated at + 1 // answered in round + ); + + // wait 1 day + vm.warp(block.timestamp + 1 days); + + vm.expectRevert("Stale Stable/USD data"); + ubiquityPoolFacet.getDollarPriceUsd(); + } + function testGetDollarPriceUsd_ShouldReturnDollarPriceInUsd() public { uint256 dollarPriceUsd = ubiquityPoolFacet.getDollarPriceUsd(); assertEq(dollarPriceUsd, 1_000_000); @@ -369,9 +429,9 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { public { uint256 governancePriceUsd = ubiquityPoolFacet.getGovernancePriceUsd(); - // 1 ETH = $3000, 1 ETH = 30_000 Governance tokens - // Governance token USD price = (1 / 30000) * 3000 = 0.1 - assertEq(governancePriceUsd, 99999); // ~$0.09 + // 1 ETH = $2000, 1 ETH = 20_000 Governance tokens + // Governance token USD price = (1 / 20000) * 2000 = 0.1 + assertEq(governancePriceUsd, 100000); // $0.1 } function testGetRedeemCollateralBalance_ShouldReturnRedeemCollateralBalance() @@ -443,7 +503,7 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { assertEq( ubiquityPoolFacet.getRedeemGovernanceBalance(user), - 970209702097020970209 + 970200000000000000000 ); } @@ -455,6 +515,15 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { assertEq(governanceEthPoolAddress, address(curveGovernanceEthPool)); } + function testStableUsdPriceFeedInformation_ShouldReturnStableUsdPriceFeedInformation() + public + { + (address priceFeed, uint256 stalenessThreshold) = ubiquityPoolFacet + .stableUsdPriceFeedInformation(); + assertEq(priceFeed, address(stableUsdPriceFeed)); + assertEq(stalenessThreshold, 1 days); + } + //==================== // Public functions //==================== @@ -678,7 +747,7 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { assertEq(totalDollarMint, 99e18); assertEq(collateralNeeded, 0); - assertEq(governanceNeeded, 1000010000100001000010); // ~1000.01 = 100 Dollar * $0.1 Governance from oracle + assertEq(governanceNeeded, 1000000000000000000000); // 1000 = 100 Dollar * $0.1 Governance from oracle // balances after assertEq(collateralToken.balanceOf(address(ubiquityPoolFacet)), 0); @@ -718,7 +787,7 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { assertEq(totalDollarMint, 99e18); assertEq(collateralNeeded, 95e18); - assertEq(governanceNeeded, 50000500005000050000); // ~50 Governance tokens = $5 USD / $0.1 Governance from oracle + assertEq(governanceNeeded, 50000000000000000000); // 50 Governance tokens = $5 USD / $0.1 Governance from oracle // balances after assertEq(collateralToken.balanceOf(address(ubiquityPoolFacet)), 95e18); @@ -903,7 +972,7 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { // balances before assertEq(dollarToken.balanceOf(user), 99e18); - assertEq(governanceToken.balanceOf(user), 999989999899998999990); + assertEq(governanceToken.balanceOf(user), 1000000000000000000000); assertEq(governanceToken.balanceOf(address(ubiquityPoolFacet)), 0); assertEq(ubiquityPoolFacet.getRedeemCollateralBalance(user, 0), 0); assertEq(ubiquityPoolFacet.getRedeemGovernanceBalance(user), 0); @@ -918,15 +987,15 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { // balances after assertEq(dollarToken.balanceOf(user), 0); - assertEq(governanceToken.balanceOf(user), 999989999899998999990); + assertEq(governanceToken.balanceOf(user), 1000000000000000000000); assertEq( governanceToken.balanceOf(address(ubiquityPoolFacet)), - 970209702097020970209 + 970200000000000000000 ); assertEq(ubiquityPoolFacet.getRedeemCollateralBalance(user, 0), 0); assertEq( ubiquityPoolFacet.getRedeemGovernanceBalance(user), - 970209702097020970209 + 970200000000000000000 ); } @@ -956,7 +1025,7 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { // balances before assertEq(dollarToken.balanceOf(user), 99e18); - assertEq(governanceToken.balanceOf(user), 1949999499994999950000); // ~1950 + assertEq(governanceToken.balanceOf(user), 1950000000000000000000); // 1950 assertEq(governanceToken.balanceOf(address(ubiquityPoolFacet)), 0); assertEq(ubiquityPoolFacet.getRedeemCollateralBalance(user, 0), 0); assertEq(ubiquityPoolFacet.getRedeemGovernanceBalance(user), 0); @@ -971,10 +1040,10 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { // balances after assertEq(dollarToken.balanceOf(user), 0); - assertEq(governanceToken.balanceOf(user), 1949999499994999950000); // ~1950 + assertEq(governanceToken.balanceOf(user), 1950000000000000000000); // 1950 assertEq( governanceToken.balanceOf(address(ubiquityPoolFacet)), - 48510485104851048510 + 48510000000000000000 ); // ~48.5 assertEq( ubiquityPoolFacet.getRedeemCollateralBalance(user, 0), @@ -982,7 +1051,7 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { ); // ~92 assertEq( ubiquityPoolFacet.getRedeemGovernanceBalance(user), - 48510485104851048510 + 48510000000000000000 ); // ~48.5 } @@ -1043,14 +1112,14 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { assertEq(collateralToken.balanceOf(user), 5e18); assertEq( governanceToken.balanceOf(address(ubiquityPoolFacet)), - 48510485104851048510 + 48510000000000000000 ); // ~48 - assertEq(governanceToken.balanceOf(user), 1949999499994999950000); // ~1950 + assertEq(governanceToken.balanceOf(user), 1950000000000000000000); // ~1950 vm.prank(user); (uint256 governanceAmount, uint256 collateralAmount) = ubiquityPoolFacet .collectRedemption(0); - assertEq(governanceAmount, 48510485104851048510); // ~48 + assertEq(governanceAmount, 48510000000000000000); // ~48 assertEq(collateralAmount, 92169000000000000000); // ~92 = $95 - 2% redemption fee // balances after @@ -1060,7 +1129,7 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { ); // redemption fee left in the pool assertEq(collateralToken.balanceOf(user), 97.169 ether); assertEq(governanceToken.balanceOf(address(ubiquityPoolFacet)), 0); - assertEq(governanceToken.balanceOf(user), 1998509985099850998510); // ~1998 + assertEq(governanceToken.balanceOf(user), 1998510000000000000000); // ~1998 } function testCollectRedemption_ShouldRevert_IfCollateralDisabled() public { @@ -1509,6 +1578,37 @@ contract UbiquityPoolFacetTest is DiamondTestSetup { vm.stopPrank(); } + function testSetStableUsdChainLinkPriceFeed_ShouldSetStableUsdChainLinkPriceFeed() + public + { + vm.startPrank(admin); + + ( + address oldPriceFeedAddress, + uint256 oldStalenessThreshold + ) = ubiquityPoolFacet.stableUsdPriceFeedInformation(); + assertEq(oldPriceFeedAddress, address(stableUsdPriceFeed)); + assertEq(oldStalenessThreshold, 1 days); + + address newPriceFeedAddress = address(1); + uint256 newStalenessThreshold = 2 days; + vm.expectEmit(address(ubiquityPoolFacet)); + emit StableUsdPriceFeedSet(newPriceFeedAddress, newStalenessThreshold); + ubiquityPoolFacet.setStableUsdChainLinkPriceFeed( + newPriceFeedAddress, + newStalenessThreshold + ); + + ( + address updatedPriceFeedAddress, + uint256 updatedStalenessThreshold + ) = ubiquityPoolFacet.stableUsdPriceFeedInformation(); + assertEq(updatedPriceFeedAddress, newPriceFeedAddress); + assertEq(updatedStalenessThreshold, newStalenessThreshold); + + vm.stopPrank(); + } + function testToggleCollateral_ShouldToggleCollateral() public { vm.startPrank(admin);