From 1df5a772d665b975c71666cfd68826bbd4aca3bc Mon Sep 17 00:00:00 2001 From: green Date: Sun, 15 Sep 2024 15:16:40 +0200 Subject: [PATCH] test(pool-monitor): add liquidity drop test --- .../dollar/monitors/PoolLiquidityMonitor.sol | 2 - .../monitors/PoolLiquidityMonitorTest.t.sol | 184 ++++++++++++++++-- 2 files changed, 168 insertions(+), 18 deletions(-) diff --git a/packages/contracts/src/dollar/monitors/PoolLiquidityMonitor.sol b/packages/contracts/src/dollar/monitors/PoolLiquidityMonitor.sol index 6b56a56a3..438c88212 100644 --- a/packages/contracts/src/dollar/monitors/PoolLiquidityMonitor.sol +++ b/packages/contracts/src/dollar/monitors/PoolLiquidityMonitor.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "../facets/UbiquityPoolFacet.sol"; import {Modifiers} from "../libraries/LibAppStorage.sol"; import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol"; import {DEFAULT_ADMIN_ROLE} from "../libraries/Constants.sol"; @@ -11,7 +10,6 @@ import "forge-std/console.sol"; contract PoolLiquidityMonitor is Modifiers { using SafeMath for uint256; - UbiquityPoolFacet public ubiquityPoolFacet; address public defenderRelayer; uint256 public liquidityVertex; bool public monitorPaused; diff --git a/packages/contracts/test/dollar/monitors/PoolLiquidityMonitorTest.t.sol b/packages/contracts/test/dollar/monitors/PoolLiquidityMonitorTest.t.sol index 724c0ec8c..4f9aabd78 100644 --- a/packages/contracts/test/dollar/monitors/PoolLiquidityMonitorTest.t.sol +++ b/packages/contracts/test/dollar/monitors/PoolLiquidityMonitorTest.t.sol @@ -6,17 +6,152 @@ import "../../../src/dollar/monitors/PoolLiquidityMonitor.sol"; import "../../helpers/LocalTestHelper.sol"; import {DiamondTestSetup} from "../../../test/diamond/DiamondTestSetup.sol"; import {DEFAULT_ADMIN_ROLE} from "../../../src/dollar/libraries/Constants.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 PoolLiquidityMonitorTest is DiamondTestSetup { PoolLiquidityMonitor monitor; address defenderRelayer = address(0x456); address unauthorized = address(0x123); + 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.prank(admin); + 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); + + curveDollarPlainPool.updateMockParams(1.01e18); + + // 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) + ); + poolLiquidityMonitor.setDefenderRelayer(defenderRelayer); + + // stop being admin + vm.stopPrank(); + + // mint 2000 Governance tokens to the user + deal(address(governanceToken), user, 2000e18); + // 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); + + vm.prank(user); + ubiquityPoolFacet.mintDollar(0, 1e18, 0.9e18, 1e18, 0, true); } function testSetThresholdPercentage() public { @@ -34,8 +169,6 @@ contract PoolLiquidityMonitorTest is DiamondTestSetup { } function testDropLiquidityVertex() public { - vm.expectRevert("Insufficient liquidity"); - vm.prank(admin); poolLiquidityMonitor.dropLiquidityVertex(); } @@ -55,6 +188,25 @@ contract PoolLiquidityMonitorTest is DiamondTestSetup { poolLiquidityMonitor.togglePaused(); } + function testSetDefenderRelayer() public { + address newRelayer = address(0x789); + + vm.prank(admin); + poolLiquidityMonitor.setDefenderRelayer(newRelayer); + } + + function testUnauthorizedSetDefenderRelayer() public { + address newRelayer = address(0x789); + + vm.expectRevert("Manager: Caller is not admin"); + poolLiquidityMonitor.setDefenderRelayer(newRelayer); + } + + function testCheckLiquidity() public { + vm.prank(defenderRelayer); + poolLiquidityMonitor.checkLiquidityVertex(); + } + function testUnauthorizedCheckLiquidity() public { vm.prank(unauthorized); vm.expectRevert("Not authorized: Only Defender Relayer allowed"); @@ -62,24 +214,24 @@ contract PoolLiquidityMonitorTest is DiamondTestSetup { poolLiquidityMonitor.checkLiquidityVertex(); } - function testCheckLiquidity() public { - vm.expectRevert("Insufficient liquidity"); - + function testLiquidityDropBelowVertex() public { vm.prank(defenderRelayer); poolLiquidityMonitor.checkLiquidityVertex(); - } - function testSetDefenderRelayer() public { - address newRelayer = address(0x789); + curveDollarPlainPool.updateMockParams(0.99e18); - vm.prank(admin); - poolLiquidityMonitor.setDefenderRelayer(newRelayer); - } + vm.prank(user); + ubiquityPoolFacet.redeemDollar(0, 5e17, 0, 0); - function testUnauthorizedSetDefenderRelayer() public { - address newRelayer = address(0x789); + // Call the checkLiquidityVertex function to test behavior after the liquidity drop + vm.prank(defenderRelayer); + poolLiquidityMonitor.checkLiquidityVertex(); - vm.expectRevert("Manager: Caller is not admin"); - poolLiquidityMonitor.setDefenderRelayer(newRelayer); + // Assert that the liquidity monitor paused after detecting a large drop + bool monitorPaused = poolLiquidityMonitor.monitorPaused(); + assertTrue( + monitorPaused, + "Monitor should be paused after liquidity drop" + ); } }