From a5ddded3b65e392021505ad6414349f2a094ab4b Mon Sep 17 00:00:00 2001 From: Korrrba Date: Fri, 24 May 2024 16:11:51 +0200 Subject: [PATCH] test: add skeleton for UbiquityPoolFacet fuzz tests Base on facet tests. Update according to identified properties. --- .../facets/UbiquityPoolFacet.fuzz.t.sol | 274 ++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol new file mode 100644 index 000000000..6a580a46f --- /dev/null +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "forge-std/console.sol"; +import {DiamondTestSetup} from "../DiamondTestSetup.sol"; +import {IDollarAmoMinter} from "../../../src/dollar/interfaces/IDollarAmoMinter.sol"; +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 {MockCurveStableSwapNG} from "../../../src/dollar/mocks/MockCurveStableSwapNG.sol"; +import {MockCurveTwocryptoOptimized} from "../../../src/dollar/mocks/MockCurveTwocryptoOptimized.sol"; + +contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { + // mock three tokens: collateral token, stable token, wrapped ETH token + MockERC20 collateralToken; + MockERC20 stableToken; + MockERC20 wethToken; + + // mock three ChainLink price feeds, one for each token + MockChainLinkFeed collateralTokenPriceFeed; + MockChainLinkFeed ethUsdPriceFeed; + MockChainLinkFeed stableUsdPriceFeed; + + // mock two curve pools Stablecoin/Dollar and Governance/WETH + MockCurveStableSwapNG curveDollarPlainPool; + MockCurveTwocryptoOptimized curveGovernanceEthPool; + + address user = address(1); + + function setUp() public override { + super.setUp(); + + vm.startPrank(admin); + + collateralToken = new MockERC20("COLLATERAL", "CLT", 18); + wethToken = new MockERC20("WETH", "WETH", 18); + stableToken = new MockERC20("STABLE", "STABLE", 18); + + collateralTokenPriceFeed = new MockChainLinkFeed(); + ethUsdPriceFeed = new MockChainLinkFeed(); + stableUsdPriceFeed = new MockChainLinkFeed(); + + curveDollarPlainPool = new MockCurveStableSwapNG( + address(stableToken), + address(dollarToken) + ); + + curveGovernanceEthPool = new MockCurveTwocryptoOptimized( + address(governanceToken), + address(wethToken) + ); + + // add collateral token to the pool + uint256 poolCeiling = 50_000e18; // max 50_000 of collateral tokens is allowed + ubiquityPoolFacet.addCollateralToken( + address(collateralToken), + address(collateralTokenPriceFeed), + poolCeiling + ); + + // set collateral price initial feed mock params + collateralTokenPriceFeed.updateMockParams( + 1, // round id + 100_000_000, // answer, 100_000_000 = $1.00 (chainlink 8 decimals answer is converted to 6 decimals pool price) + block.timestamp, // started at + block.timestamp, // updated at + 1 // answered in round + ); + + // set ETH/USD price initial feed mock params + ethUsdPriceFeed.updateMockParams( + 1, // round id + 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 initial 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 initial price to 20k in Curve pool mock (20k GOV == 1 ETH) + curveGovernanceEthPool.updateMockParams(20_000e18); + + // set price feed for collateral token + ubiquityPoolFacet.setCollateralChainLinkPriceFeed( + address(collateralToken), // collateral token address + address(collateralTokenPriceFeed), // price feed address + 1 days // price feed staleness threshold in seconds + ); + + // set price feed for ETH/USD pair + ubiquityPoolFacet.setEthUsdChainLinkPriceFeed( + address(ethUsdPriceFeed), // price feed address + 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 initial fees + ubiquityPoolFacet.setFees( + 0, // collateral index + 10000, // 1% mint fee + 20000 // 2% redeem fee + ); + // set redemption delay to 2 blocks + ubiquityPoolFacet.setRedemptionDelayBlocks(2); + // set mint price threshold to $1.01 and redeem price to $0.99 + ubiquityPoolFacet.setPriceThresholds(1010000, 990000); + // set collateral ratio to 100% + ubiquityPoolFacet.setCollateralRatio(1_000_000); + // set Governance-ETH pool + ubiquityPoolFacet.setGovernanceEthPoolAddress( + address(curveGovernanceEthPool) + ); + + // set Curve plain pool in manager facet + managerFacet.setStableSwapPlainPoolAddress( + address(curveDollarPlainPool) + ); + + // stop being admin + vm.stopPrank(); + + // mint 2000 Governance tokens to the user + deal(address(governanceToken), user, 2000e18); + // mint 100 collateral tokens to the user + collateralToken.mint(address(user), 100e18); + // user approves the pool to transfer collateral + vm.prank(user); + collateralToken.approve(address(ubiquityPoolFacet), 100e18); + } + + //======================== + // Dollar Mint fuzz tests + //======================== + + function testMintDollar_FuzzCollateralRatio( + uint newCollateralRatio + ) public { + vm.assume(newCollateralRatio <= 100); + vm.prank(admin); + ubiquityPoolFacet.setPriceThresholds( + 1000000, // mint threshold + 990000 // redeem threshold + ); + + // fuzz collateral ratio + vm.prank(admin); + ubiquityPoolFacet.setCollateralRatio(newCollateralRatio); + + // balances before + assertEq(collateralToken.balanceOf(address(ubiquityPoolFacet)), 0); + assertEq(dollarToken.balanceOf(user), 0); + assertEq(governanceToken.balanceOf(user), 2000e18); + + vm.prank(user); + ( + uint256 totalDollarMint, + uint256 collateralNeeded, + uint256 governanceNeeded + ) = ubiquityPoolFacet.mintDollar( + 0, // collateral index + 100e18, // Dollar amount + 99e18, // min amount of Dollars to mint + 100e18, // max collateral to send + 1100e18, // max Governance tokens to send + false // force 1-to-1 mint (i.e. provide only collateral without Governance tokens) + ); + + assertEq(totalDollarMint, 99e18); + assertEq(collateralNeeded, 0); + assertEq(governanceNeeded, 1000000000000000000000); // 1000 = 100 Dollar * $0.1 Governance from oracle + + // balances after + assertEq(collateralToken.balanceOf(address(ubiquityPoolFacet)), 0); + assertEq(dollarToken.balanceOf(user), 99e18); + assertEq(governanceToken.balanceOf(user), 2000e18 - governanceNeeded); + } + + function testMintDollar_FuzzDollarPriceUsdTooLow( + uint256 dollarPriceUsd + ) public { + vm.prank(user); + vm.expectRevert("Dollar price too low"); + ubiquityPoolFacet.mintDollar( + 0, // collateral index + 100e18, // Dollar amount + 90e18, // min amount of Dollars to mint + 100e18, // max collateral to send + 0, // max Governance tokens to send + false // force 1-to-1 mint (i.e. provide only collateral without Governance tokens) + ); + } + + function testMintDollar_FuzzDollarAmountSlippage( + uint256 dollarAmount + ) public { + vm.prank(admin); + ubiquityPoolFacet.setPriceThresholds( + 1000000, // mint threshold + 990000 // redeem threshold + ); + + vm.prank(user); + vm.expectRevert("Dollar slippage"); + ubiquityPoolFacet.mintDollar( + 0, // collateral index + 100e18, // Dollar amount + 100e18, // min amount of Dollars to mint + 100e18, // max collateral to send + 0, // max Governance tokens to send + false // force 1-to-1 mint (i.e. provide only collateral without Governance tokens) + ); + } + + function testMintDollar_FuzzCollateralAmountSlippage( + uint256 collateralAmount + ) public { + vm.prank(admin); + ubiquityPoolFacet.setPriceThresholds( + 1000000, // mint threshold + 990000 // redeem threshold + ); + + vm.prank(user); + vm.expectRevert("Collateral slippage"); + ubiquityPoolFacet.mintDollar( + 0, // collateral index + 100e18, // Dollar amount + 90e18, // min amount of Dollars to mint + 10e18, // max collateral to send + 0, // max Governance tokens to send + false // force 1-to-1 mint (i.e. provide only collateral without Governance tokens) + ); + } + + function testMintDollar_FuzzGovernanceAmountSlippage( + uint256 governanceAmount + ) public { + vm.prank(admin); + ubiquityPoolFacet.setPriceThresholds( + 1000000, // mint threshold + 990000 // redeem threshold + ); + + // admin sets collateral ratio to 0% + vm.prank(admin); + ubiquityPoolFacet.setCollateralRatio(0); + + vm.prank(user); + vm.expectRevert("Governance slippage"); + ubiquityPoolFacet.mintDollar( + 0, // collateral index + 100e18, // Dollar amount + 90e18, // min amount of Dollars to mint + 10e18, // max collateral to send + 0, // max Governance tokens to send + false // force 1-to-1 mint (i.e. provide only collateral without Governance tokens) + ); + } +}