From a5ddded3b65e392021505ad6414349f2a094ab4b Mon Sep 17 00:00:00 2001 From: Korrrba Date: Fri, 24 May 2024 16:11:51 +0200 Subject: [PATCH 01/15] 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) + ); + } +} From fe834cb5dc564ee4165ade3dbea4699c2443b235 Mon Sep 17 00:00:00 2001 From: Korrrba Date: Fri, 24 May 2024 16:32:47 +0200 Subject: [PATCH 02/15] test: assume max collateral ratio between 0 and 1_000_000 (100%) --- .../contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol index 6a580a46f..eecf24894 100644 --- a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol @@ -150,7 +150,7 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { function testMintDollar_FuzzCollateralRatio( uint newCollateralRatio ) public { - vm.assume(newCollateralRatio <= 100); + vm.assume(newCollateralRatio <= 1_000_000); vm.prank(admin); ubiquityPoolFacet.setPriceThresholds( 1000000, // mint threshold From b6e432da442178c72e4b13a6da4e3901b8873a76 Mon Sep 17 00:00:00 2001 From: Korrrba Date: Mon, 27 May 2024 10:22:57 +0200 Subject: [PATCH 03/15] test: set up correct fuzz parameters in testMintDollar_FuzzDollarPriceUsdTooLow --- .../test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol index eecf24894..2df54c09c 100644 --- a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol @@ -190,9 +190,18 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { assertEq(governanceToken.balanceOf(user), 2000e18 - governanceNeeded); } + /** + * @notice Fuzz Dollar minting scenario for Dollar price below threshold + * @param dollarPriceUsd Ubiquity Dollar token price from Curve pool (Stable coin/Ubiquity Dollar) + */ function testMintDollar_FuzzDollarPriceUsdTooLow( uint256 dollarPriceUsd ) public { + // Stable coin/USD ChainLink feed is mocked to $1.00 + // Mint price threshold set up to $1.01 == 1010000 + // Fuzz Dollar price in Curve plain pool (1 Stable coin / x Dollar) + vm.assume(dollarPriceUsd < 1010000000000000000); // 1.01e18 , less than threshold + curveDollarPlainPool.updateMockParams(dollarPriceUsd); vm.prank(user); vm.expectRevert("Dollar price too low"); ubiquityPoolFacet.mintDollar( From dc0125cd5166f75a035db8433132b21390aa506a Mon Sep 17 00:00:00 2001 From: Korrrba Date: Mon, 27 May 2024 11:44:54 +0200 Subject: [PATCH 04/15] test: set up correct fuzz parameters in testMintDollar_FuzzDollarAmountSlippage --- .../facets/UbiquityPoolFacet.fuzz.t.sol | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol index 2df54c09c..aab7fdcf9 100644 --- a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol @@ -214,21 +214,25 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { ); } + /** + * @notice Fuzz Dollar minting scenario for Dollar amount slippage. Max slippage is the acceptable + * difference between amount asked to mint, and the actual minted amount, including the minting fee. + * As an example if mint fee is set to 1%, any value above 99% of the amount should revert + * the mint with `Dollar slippage` error. + * @param dollarOutMin Minimal Ubiquity Dollar amount to mint, including the minting fee. + */ function testMintDollar_FuzzDollarAmountSlippage( - uint256 dollarAmount + uint256 dollarOutMin ) public { + vm.assume(dollarOutMin >= 99e18); vm.prank(admin); - ubiquityPoolFacet.setPriceThresholds( - 1000000, // mint threshold - 990000 // redeem threshold - ); - + curveDollarPlainPool.updateMockParams(1.01e18); vm.prank(user); vm.expectRevert("Dollar slippage"); ubiquityPoolFacet.mintDollar( 0, // collateral index 100e18, // Dollar amount - 100e18, // min amount of Dollars to mint + dollarOutMin, // 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) From df22fef4fe456f4783419729dd00911498e976e9 Mon Sep 17 00:00:00 2001 From: Korrrba Date: Mon, 27 May 2024 14:34:42 +0200 Subject: [PATCH 05/15] chore: increase max_test_rejects to 90000 Avoid too many rejects error in vm.assume fuzz tests. Described at https://github.com/foundry-rs/foundry/issues/1202 --- packages/contracts/foundry.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/contracts/foundry.toml b/packages/contracts/foundry.toml index 8c5721de8..40790d67e 100644 --- a/packages/contracts/foundry.toml +++ b/packages/contracts/foundry.toml @@ -42,3 +42,4 @@ src = 'src/dollar' [profile.intense.fuzz] runs = 100000 +max_test_rejects = 90000 From 485efe1fc1741a29daa8852858efd9de709af00d Mon Sep 17 00:00:00 2001 From: Korrrba Date: Wed, 29 May 2024 17:11:32 +0200 Subject: [PATCH 06/15] test: set correct parameters for collateral and governance slippage fuzz tests --- .../facets/UbiquityPoolFacet.fuzz.t.sol | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol index aab7fdcf9..f37cbf450 100644 --- a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol @@ -240,47 +240,42 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { } function testMintDollar_FuzzCollateralAmountSlippage( - uint256 collateralAmount + uint256 maxCollateralIn ) public { + vm.assume(maxCollateralIn <= 100e18); vm.prank(admin); - ubiquityPoolFacet.setPriceThresholds( - 1000000, // mint threshold - 990000 // redeem threshold - ); - + curveDollarPlainPool.updateMockParams(1.01e18); 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 + maxCollateralIn, // 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 + uint256 maxGovernanceIn ) public { + vm.assume(maxGovernanceIn <= 110e18); vm.prank(admin); - ubiquityPoolFacet.setPriceThresholds( - 1000000, // mint threshold - 990000 // redeem threshold - ); - + curveDollarPlainPool.updateMockParams(1.01e18); + // set ETH/Governance initial price to 2k in Curve pool mock (2k GOV == 1 ETH, 1 GOV == 1 USD) + curveGovernanceEthPool.updateMockParams(2_000e18); // 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 + 98e18, // min amount of Dollars to mint (2% fee included) + 0, // max collateral to send + maxGovernanceIn, // max Governance tokens to send false // force 1-to-1 mint (i.e. provide only collateral without Governance tokens) ); } From 809f14954cdca49a25bdce1199a623e31e5c1916 Mon Sep 17 00:00:00 2001 From: Korrrba Date: Wed, 29 May 2024 17:49:26 +0200 Subject: [PATCH 07/15] test: minting fuzz tests parameters tweaks --- .../facets/UbiquityPoolFacet.fuzz.t.sol | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol index f37cbf450..d37f53209 100644 --- a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol @@ -136,8 +136,8 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { // mint 2000 Governance tokens to the user deal(address(governanceToken), user, 2000e18); - // mint 100 collateral tokens to the user - collateralToken.mint(address(user), 100e18); + // mint 2000 collateral tokens to the user + collateralToken.mint(address(user), 2000e18); // user approves the pool to transfer collateral vm.prank(user); collateralToken.approve(address(ubiquityPoolFacet), 100e18); @@ -151,21 +151,28 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { uint newCollateralRatio ) public { vm.assume(newCollateralRatio <= 1_000_000); - vm.prank(admin); - ubiquityPoolFacet.setPriceThresholds( - 1000000, // mint threshold - 990000 // redeem threshold - ); - // fuzz collateral ratio vm.prank(admin); ubiquityPoolFacet.setCollateralRatio(newCollateralRatio); + curveDollarPlainPool.updateMockParams(1.01e18); + // set ETH/Governance initial price to 2k in Curve pool mock (2k GOV == 1 ETH, 1 GOV == 1 USD) + curveGovernanceEthPool.updateMockParams(2_000e18); // balances before assertEq(collateralToken.balanceOf(address(ubiquityPoolFacet)), 0); assertEq(dollarToken.balanceOf(user), 0); assertEq(governanceToken.balanceOf(user), 2000e18); + assertEq(collateralToken.balanceOf(user), 2000e18); + + // dollars and governance tokens should be provided to meet ratio requirements + uint256 maxCollateralIn; + if (newCollateralRatio == 0) maxCollateralIn = 0; + else maxCollateralIn = (1100e18) * (newCollateralRatio / 1_000_000); + uint256 maxGovernanceIn = 1100e18 - maxCollateralIn; + console.log(newCollateralRatio); + console.log(maxCollateralIn); + console.log(maxGovernanceIn); vm.prank(user); ( uint256 totalDollarMint, @@ -175,19 +182,21 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { 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) + 1100e18, // max collateral to send + maxGovernanceIn, // max Governance tokens to send + false // fractional mint allowed ); 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( + collateralToken.balanceOf(address(ubiquityPoolFacet)), + collateralNeeded + ); assertEq(governanceToken.balanceOf(user), 2000e18 - governanceNeeded); + assertEq(collateralToken.balanceOf(user), 2000e18 - collateralNeeded); } /** @@ -250,7 +259,7 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { ubiquityPoolFacet.mintDollar( 0, // collateral index 100e18, // Dollar amount - 90e18, // min amount of Dollars to mint + 99e18, // min amount of Dollars to mint maxCollateralIn, // max collateral to send 0, // max Governance tokens to send false // force 1-to-1 mint (i.e. provide only collateral without Governance tokens) @@ -260,7 +269,7 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { function testMintDollar_FuzzGovernanceAmountSlippage( uint256 maxGovernanceIn ) public { - vm.assume(maxGovernanceIn <= 110e18); + vm.assume(maxGovernanceIn <= 101e18); vm.prank(admin); curveDollarPlainPool.updateMockParams(1.01e18); // set ETH/Governance initial price to 2k in Curve pool mock (2k GOV == 1 ETH, 1 GOV == 1 USD) @@ -273,7 +282,7 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { ubiquityPoolFacet.mintDollar( 0, // collateral index 100e18, // Dollar amount - 98e18, // min amount of Dollars to mint (2% fee included) + 99e18, // min amount of Dollars to mint (1% fee included) 0, // max collateral to send maxGovernanceIn, // max Governance tokens to send false // force 1-to-1 mint (i.e. provide only collateral without Governance tokens) From eaa29b3f8808e8ab30c966ab77522479d0c2f9ee Mon Sep 17 00:00:00 2001 From: Korrrba Date: Thu, 30 May 2024 13:46:52 +0200 Subject: [PATCH 08/15] test: use ABDKMathQuad to fuzz fractional mint --- .../facets/UbiquityPoolFacet.fuzz.t.sol | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol index d37f53209..8c2857cea 100644 --- a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.19; import "forge-std/console.sol"; +import "abdk/ABDKMathQuad.sol"; import {DiamondTestSetup} from "../DiamondTestSetup.sol"; import {IDollarAmoMinter} from "../../../src/dollar/interfaces/IDollarAmoMinter.sol"; import {LibUbiquityPool} from "../../../src/dollar/libraries/LibUbiquityPool.sol"; @@ -11,6 +12,9 @@ import {MockCurveStableSwapNG} from "../../../src/dollar/mocks/MockCurveStableSw import {MockCurveTwocryptoOptimized} from "../../../src/dollar/mocks/MockCurveTwocryptoOptimized.sol"; contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { + using ABDKMathQuad for uint256; + using ABDKMathQuad for bytes16; + // mock three tokens: collateral token, stable token, wrapped ETH token MockERC20 collateralToken; MockERC20 stableToken; @@ -148,9 +152,10 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { //======================== function testMintDollar_FuzzCollateralRatio( - uint newCollateralRatio + uint256 newCollateralRatio ) public { - vm.assume(newCollateralRatio <= 1_000_000); + uint256 maxCollateralRatio = 1_000_000; // 100% + vm.assume(newCollateralRatio <= maxCollateralRatio); // fuzz collateral ratio vm.prank(admin); ubiquityPoolFacet.setCollateralRatio(newCollateralRatio); @@ -166,13 +171,16 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { // dollars and governance tokens should be provided to meet ratio requirements uint256 maxCollateralIn; + uint256 totalCollateralMaxAmount = 100e18; // total collateral from both should be enough to mint Dollar tokens if (newCollateralRatio == 0) maxCollateralIn = 0; - else maxCollateralIn = (1100e18) * (newCollateralRatio / 1_000_000); - uint256 maxGovernanceIn = 1100e18 - maxCollateralIn; + else + maxCollateralIn = totalCollateralMaxAmount + .fromUInt() + .mul(newCollateralRatio.fromUInt()) + .div(maxCollateralRatio.fromUInt()) + .toUInt(); + uint256 maxGovernanceIn = totalCollateralMaxAmount - maxCollateralIn; - console.log(newCollateralRatio); - console.log(maxCollateralIn); - console.log(maxGovernanceIn); vm.prank(user); ( uint256 totalDollarMint, @@ -182,7 +190,7 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { 0, // collateral index 100e18, // Dollar amount 99e18, // min amount of Dollars to mint - 1100e18, // max collateral to send + maxCollateralIn, // max collateral to send maxGovernanceIn, // max Governance tokens to send false // fractional mint allowed ); From 222355de776ece7d50e55ec68bcf0d5e840e75e7 Mon Sep 17 00:00:00 2001 From: Korrrba Date: Thu, 30 May 2024 13:57:08 +0200 Subject: [PATCH 09/15] test: add Dollar redeem fuzz tests skeleton --- .../facets/UbiquityPoolFacet.fuzz.t.sol | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol index 8c2857cea..cccf1cfe4 100644 --- a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol @@ -296,4 +296,39 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { false // force 1-to-1 mint (i.e. provide only collateral without Governance tokens) ); } + + //======================== + // Dollar Redeem fuzz tests + //======================== + + function testRedeemDollar_FuzzCollateralRatio( + uint256 newCollateralRatio + ) public {} + + /** + * @notice Fuzz Dollar redeeming scenario for Dollar price above threshold + * @param dollarPriceUsd Ubiquity Dollar token price from Curve pool (Stable coin/Ubiquity Dollar) + */ + function testRedeemDollar_FuzzDollarPriceUsdTooHigh( + uint256 dollarPriceUsd + ) public {} + + /** + * @notice Fuzz Dollar redeeming scenario for Dollar amount slippage. Max slippage is the acceptable + * difference between amount asked to redeem, and the actual redeemed amount, including the redeem fee. + * As an example if redeem fee is set to 2%, any value above 98% of the amount should revert + * the redeem with `Dollar slippage` error. + * @param dollarOutMin Minimal Ubiquity Dollar amount to redeem, including the redeeming fee. + */ + function testRedeemDollar_FuzzDollarAmountSlippage( + uint256 dollarOutMin + ) public {} + + function testRedeemDollar_FuzzCollateralAmountSlippage( + uint256 maxCollateralIn + ) public {} + + function testRedeemDollar_FuzzGovernanceAmountSlippage( + uint256 maxGovernanceIn + ) public {} } From e9e0e6c50ca0e4573348888fea546b7a3afb9bb9 Mon Sep 17 00:00:00 2001 From: Korrrba Date: Tue, 4 Jun 2024 23:00:49 +0200 Subject: [PATCH 10/15] test: add testRedeemDollar_FuzzDollarPriceUsdTooHigh --- .../facets/UbiquityPoolFacet.fuzz.t.sol | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol index cccf1cfe4..d018fad40 100644 --- a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol @@ -259,7 +259,7 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { function testMintDollar_FuzzCollateralAmountSlippage( uint256 maxCollateralIn ) public { - vm.assume(maxCollateralIn <= 100e18); + vm.assume(maxCollateralIn < 100e18); vm.prank(admin); curveDollarPlainPool.updateMockParams(1.01e18); vm.prank(user); @@ -277,7 +277,7 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { function testMintDollar_FuzzGovernanceAmountSlippage( uint256 maxGovernanceIn ) public { - vm.assume(maxGovernanceIn <= 101e18); + vm.assume(maxGovernanceIn < 1e18); vm.prank(admin); curveDollarPlainPool.updateMockParams(1.01e18); // set ETH/Governance initial price to 2k in Curve pool mock (2k GOV == 1 ETH, 1 GOV == 1 USD) @@ -311,7 +311,23 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { */ function testRedeemDollar_FuzzDollarPriceUsdTooHigh( uint256 dollarPriceUsd - ) public {} + ) public { + // Stable coin/USD ChainLink feed is mocked to $1.00 + // Redeem price threshold set up to $0.99 == 990_000 + // Fuzz Dollar price in Curve plain pool (1 Stable coin / x Dollar) + vm.assume(dollarPriceUsd > 990000999999999999); // 0.99e18 , greater than redeem threshold + vm.assume(dollarPriceUsd < 9900000000000000000); + vm.prank(admin); + curveDollarPlainPool.updateMockParams(dollarPriceUsd); + vm.prank(user); + vm.expectRevert("Dollar price too high"); + ubiquityPoolFacet.redeemDollar( + 0, // collateral index + 1e18, // Dollar amount + 0, // min Governance out + 1e18 // min collateral out + ); + } /** * @notice Fuzz Dollar redeeming scenario for Dollar amount slippage. Max slippage is the acceptable From 60651d5180f6c343531ad94364e2121c99720831 Mon Sep 17 00:00:00 2001 From: Korrrba Date: Wed, 5 Jun 2024 18:05:26 +0200 Subject: [PATCH 11/15] test: add redeem fuzz tests InsufficientCollateralAvailable CollateralSlippage --- .../facets/UbiquityPoolFacet.fuzz.t.sol | 53 +++++++++++++++---- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol index d018fad40..920e8aaae 100644 --- a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol @@ -330,19 +330,50 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { } /** - * @notice Fuzz Dollar redeeming scenario for Dollar amount slippage. Max slippage is the acceptable - * difference between amount asked to redeem, and the actual redeemed amount, including the redeem fee. - * As an example if redeem fee is set to 2%, any value above 98% of the amount should revert - * the redeem with `Dollar slippage` error. - * @param dollarOutMin Minimal Ubiquity Dollar amount to redeem, including the redeeming fee. + * @notice Fuzz Dollar redeeming scenario for insufficient collateral available in pool. + * @param collateralOut Minimal collateral amount to redeem. */ - function testRedeemDollar_FuzzDollarAmountSlippage( - uint256 dollarOutMin - ) public {} + function testRedeemDollar_FuzzInsufficientCollateralAvailable( + uint256 collateralOut + ) public { + vm.assume(collateralOut > 1e18); + vm.startPrank(admin); + curveDollarPlainPool.updateMockParams(0.99e18); + collateralToken.mint(address(ubiquityPoolFacet), 1e18); + dollarToken.mint(address(user), 1e18); + vm.stopPrank(); + vm.prank(user); + vm.expectRevert("Insufficient pool collateral"); + ubiquityPoolFacet.redeemDollar( + 0, // collateral index + 10e18, // Dollar amount + 0, // min Governance out + collateralOut // min collateral out + ); + } - function testRedeemDollar_FuzzCollateralAmountSlippage( - uint256 maxCollateralIn - ) public {} + /** + * @notice Fuzz Dollar redeeming scenario for collateral slippage. + * @param collateralOut Minimal collateral amount to redeem. + */ + function testRedeemDollar_FuzzCollateralSlippage( + uint256 collateralOut + ) public { + vm.assume(collateralOut >= 1e18); + vm.startPrank(admin); + curveDollarPlainPool.updateMockParams(0.99e18); + collateralToken.mint(address(ubiquityPoolFacet), 100e18); + dollarToken.mint(address(user), 1e18); + vm.stopPrank(); + vm.prank(user); + vm.expectRevert("Collateral slippage"); + ubiquityPoolFacet.redeemDollar( + 0, // collateral index + 1e18, // Dollar amount + 0, // min Governance out + collateralOut // min collateral out + ); + } function testRedeemDollar_FuzzGovernanceAmountSlippage( uint256 maxGovernanceIn From 3a58107e63e0448aff80db719780a3134d512ad4 Mon Sep 17 00:00:00 2001 From: Korrrba Date: Thu, 6 Jun 2024 16:28:55 +0200 Subject: [PATCH 12/15] test: add testRedeemDollar_FuzzGovernanceAmountSlippage --- .../facets/UbiquityPoolFacet.fuzz.t.sol | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol index 920e8aaae..027a799b4 100644 --- a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol @@ -375,7 +375,26 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { ); } + /** + * @notice Fuzz Dollar redeeming scenario for governance token slippage. + * @param governanceOut Minimal governance token amount to redeem. + */ function testRedeemDollar_FuzzGovernanceAmountSlippage( - uint256 maxGovernanceIn - ) public {} + uint256 governanceOut + ) public { + vm.assume(governanceOut >= 1e18); + vm.startPrank(admin); + curveDollarPlainPool.updateMockParams(0.99e18); + collateralToken.mint(address(ubiquityPoolFacet), 100e18); + dollarToken.mint(address(user), 1e18); + vm.stopPrank(); + vm.prank(user); + vm.expectRevert("Governance slippage"); + ubiquityPoolFacet.redeemDollar( + 0, // collateral index + 1e18, // Dollar amount + governanceOut, // min Governance out + 0 // min collateral out + ); + } } From 478e4df89fe9082e0a106c6e874fc01e5b910be5 Mon Sep 17 00:00:00 2001 From: Korrrba Date: Thu, 6 Jun 2024 16:51:54 +0200 Subject: [PATCH 13/15] test: add testRedeemDollar_FuzzRedemptionDelayBlocks --- .../facets/UbiquityPoolFacet.fuzz.t.sol | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol index 027a799b4..4b7997e80 100644 --- a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol @@ -301,9 +301,28 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { // Dollar Redeem fuzz tests //======================== - function testRedeemDollar_FuzzCollateralRatio( - uint256 newCollateralRatio - ) public {} + function testRedeemDollar_FuzzRedemptionDelayBlocks( + uint8 delayBlocks + ) public { + vm.assume(delayBlocks > 0); + vm.startPrank(admin); + curveDollarPlainPool.updateMockParams(0.99e18); + collateralToken.mint(address(ubiquityPoolFacet), 100e18); + dollarToken.mint(address(user), 1e18); + // set redemption delay to delayBlocks + ubiquityPoolFacet.setRedemptionDelayBlocks(delayBlocks); + vm.stopPrank(); + vm.prank(user); + ubiquityPoolFacet.redeemDollar( + 0, // collateral index + 1e18, // Dollar amount + 0, // min Governance out + 1e17 // min collateral out + ); + vm.roll(delayBlocks); // redemption possible at delayBlocks + 1 block, before that revert + vm.expectRevert("Too soon to collect redemption"); + ubiquityPoolFacet.collectRedemption(0); + } /** * @notice Fuzz Dollar redeeming scenario for Dollar price above threshold From ab85a3342305d1be7b90722fd5f0ca0a67d87a55 Mon Sep 17 00:00:00 2001 From: Korrrba Date: Fri, 14 Jun 2024 22:37:47 +0200 Subject: [PATCH 14/15] test: add testMintDollar_FuzzCorrectDollarAmountMinted --- .../facets/UbiquityPoolFacet.fuzz.t.sol | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol index 4b7997e80..8931cb98b 100644 --- a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol @@ -297,6 +297,35 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { ); } + function testMintDollar_FuzzCorrectDollarAmountMinted( + uint256 tokenAmountToMint + ) public { + vm.assume(tokenAmountToMint < 50_000e18); // collateral pool ceiling also set to 50k tokens + vm.startPrank(admin); + curveDollarPlainPool.updateMockParams(1.01e18); + // set ETH/Governance initial price to 2k in Curve pool mock (2k GOV == 1 ETH, 1 GOV == 1 USD) + curveGovernanceEthPool.updateMockParams(2_000e18); + // admin sets collateral ratio to 0% + ubiquityPoolFacet.setCollateralRatio(0); + deal(address(governanceToken), user, 50000e18); + vm.stopPrank(); + uint256 minDollarsToMint = tokenAmountToMint + .fromUInt() + .mul(uint(99).fromUInt()) + .div(uint(100).fromUInt()) + .toUInt(); // dollars to mint (1% fee included) + vm.prank(user); + (uint256 dollarsMinted, , ) = ubiquityPoolFacet.mintDollar( + 0, // collateral index + tokenAmountToMint, // Dollar amount to mint + minDollarsToMint, + 0, // max collateral to send + tokenAmountToMint, // max Governance tokens to send (1 GOV == 1 USD) + false // force 1-to-1 mint (i.e. provide only collateral without Governance tokens) + ); + assertEq(dollarsMinted, minDollarsToMint); + } + //======================== // Dollar Redeem fuzz tests //======================== From fce4ecb13f3081d93ae34eb16b03be66e94e9e10 Mon Sep 17 00:00:00 2001 From: Korrrba Date: Sat, 15 Jun 2024 09:15:14 +0200 Subject: [PATCH 15/15] test: add testMintDollar_FuzzCorrectDollarAmountRedeemed --- .../facets/UbiquityPoolFacet.fuzz.t.sol | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol index 8931cb98b..0bce0414d 100644 --- a/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol +++ b/packages/contracts/test/diamond/facets/UbiquityPoolFacet.fuzz.t.sol @@ -445,4 +445,30 @@ contract UbiquityPoolFacetFuzzTest is DiamondTestSetup { 0 // min collateral out ); } + + function testMintDollar_FuzzCorrectDollarAmountRedeemed( + uint256 tokenAmountToRedeem + ) public { + vm.assume(tokenAmountToRedeem < 50_000e18); + vm.startPrank(admin); + curveDollarPlainPool.updateMockParams(0.99e18); + dollarToken.mint(address(user), tokenAmountToRedeem); // make sure user has enough Dollars + collateralToken.mint(address(ubiquityPoolFacet), tokenAmountToRedeem); // make sure pool has enough collateral + uint256 dollarTokenBalanceBeforeRedeem = dollarToken.balanceOf(user); + vm.stopPrank(); + vm.prank(user); + ubiquityPoolFacet.redeemDollar( + 0, // collateral index + tokenAmountToRedeem, // Dollar amount + 0, // min Governance out + 0 // min collateral out + ); + vm.roll(3); // redemption delay set to 2 blocks + ubiquityPoolFacet.collectRedemption(0); + // balances after + assertEq( + dollarToken.balanceOf(user), + dollarTokenBalanceBeforeRedeem - tokenAmountToRedeem + ); + } }