From dd032cfe69df2e1f99128b52a9c39b1fdf65fdaf Mon Sep 17 00:00:00 2001 From: green Date: Tue, 17 Sep 2024 22:16:34 +0200 Subject: [PATCH] feat: remove pool-monitor from the diamond --- .../UbiquityPoolSecurityMonitor.sol} | 69 +++++++++++------- .../src/dollar/libraries/Constants.sol | 3 + .../contracts/test/diamond/DiamondTest.t.sol | 2 +- .../test/diamond/DiamondTestSetup.sol | 23 +----- .../monitors/PoolLiquidityMonitorTest.t.sol | 72 ++++++++++--------- 5 files changed, 86 insertions(+), 83 deletions(-) rename packages/contracts/src/dollar/{monitors/PoolLiquidityMonitor.sol => core/UbiquityPoolSecurityMonitor.sol} (50%) diff --git a/packages/contracts/src/dollar/monitors/PoolLiquidityMonitor.sol b/packages/contracts/src/dollar/core/UbiquityPoolSecurityMonitor.sol similarity index 50% rename from packages/contracts/src/dollar/monitors/PoolLiquidityMonitor.sol rename to packages/contracts/src/dollar/core/UbiquityPoolSecurityMonitor.sol index 438c88212..4f29df8d9 100644 --- a/packages/contracts/src/dollar/monitors/PoolLiquidityMonitor.sol +++ b/packages/contracts/src/dollar/core/UbiquityPoolSecurityMonitor.sol @@ -1,16 +1,20 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {Modifiers} from "../libraries/LibAppStorage.sol"; import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import {DEFAULT_ADMIN_ROLE} from "../libraries/Constants.sol"; -import {LibUbiquityPool} from "../libraries/LibUbiquityPool.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {AccessControlFacet} from "../facets/AccessControlFacet.sol"; +import {UbiquityPoolFacet} from "../facets/UbiquityPoolFacet.sol"; + +import "../libraries/Constants.sol"; import "forge-std/console.sol"; -contract PoolLiquidityMonitor is Modifiers { +contract UbiquityPoolSecurityMonitor is Initializable, UUPSUpgradeable { using SafeMath for uint256; - address public defenderRelayer; + AccessControlFacet public accessControlFacet; + UbiquityPoolFacet public ubiquityPoolFacet; uint256 public liquidityVertex; bool public monitorPaused; uint256 public thresholdPercentage; @@ -20,35 +24,50 @@ contract PoolLiquidityMonitor is Modifiers { event VertexDropped(); event PausedToggled(bool paused); - modifier onlyAuthorized() { + modifier onlyDefender() { require( - msg.sender == defenderRelayer, - "Not authorized: Only Defender Relayer allowed" + accessControlFacet.hasRole(DEFENDER_RELAYER_ROLE, msg.sender), + "Ubiquity Pool Security Monitor: not defender relayer" ); _; } + modifier onlyMonitorAdmin() { + require( + accessControlFacet.hasRole(DEFAULT_ADMIN_ROLE, msg.sender), + "Ubiquity Pool Security Monitor: not admin" + ); + _; + } + + function initialize( + address _accessControlFacet, + address _ubiquityPoolFacet + ) public initializer { + thresholdPercentage = 30; + + accessControlFacet = AccessControlFacet(_accessControlFacet); + ubiquityPoolFacet = UbiquityPoolFacet(_ubiquityPoolFacet); + } + + function _authorizeUpgrade( + address newImplementation + ) internal override onlyMonitorAdmin {} + function setThresholdPercentage( uint256 _newThresholdPercentage - ) external onlyAdmin { + ) external onlyMonitorAdmin { thresholdPercentage = _newThresholdPercentage; } - function setDefenderRelayer( - address _newDefenderRelayer - ) external onlyAdmin { - defenderRelayer = _newDefenderRelayer; - } - - function togglePaused() external onlyAdmin { + function togglePaused() external onlyMonitorAdmin { monitorPaused = !monitorPaused; emit PausedToggled(monitorPaused); } - function dropLiquidityVertex() external onlyAdmin { - uint256 currentCollateralLiquidity = LibUbiquityPool + function dropLiquidityVertex() external onlyMonitorAdmin { + uint256 currentCollateralLiquidity = ubiquityPoolFacet .collateralUsdBalance(); - require(currentCollateralLiquidity > 0, "Insufficient liquidity"); liquidityVertex = currentCollateralLiquidity; @@ -56,8 +75,8 @@ contract PoolLiquidityMonitor is Modifiers { emit VertexDropped(); } - function checkLiquidityVertex() external onlyAuthorized { - uint256 currentCollateralLiquidity = LibUbiquityPool + function checkLiquidityVertex() external onlyDefender { + uint256 currentCollateralLiquidity = ubiquityPoolFacet .collateralUsdBalance(); require(currentCollateralLiquidity > 0, "Insufficient liquidity"); @@ -65,7 +84,6 @@ contract PoolLiquidityMonitor is Modifiers { if (currentCollateralLiquidity > liquidityVertex) { liquidityVertex = currentCollateralLiquidity; - emit LiquidityVertexUpdated(liquidityVertex); } else { uint256 liquidityDiffPercentage = liquidityVertex @@ -74,11 +92,10 @@ contract PoolLiquidityMonitor is Modifiers { .div(liquidityVertex); if (liquidityDiffPercentage >= thresholdPercentage) { - monitorPaused = true; - - // Pause the UbiquityDollarToken - // Pause LibUbiquityPool by disabling collateral + // a) Pause the UbiquityDollarToken + // b) Pause LibUbiquityPool by disabling collateral + monitorPaused = true; emit MonitorPaused( currentCollateralLiquidity, liquidityDiffPercentage diff --git a/packages/contracts/src/dollar/libraries/Constants.sol b/packages/contracts/src/dollar/libraries/Constants.sol index 006e1e9c4..44f6355d6 100644 --- a/packages/contracts/src/dollar/libraries/Constants.sol +++ b/packages/contracts/src/dollar/libraries/Constants.sol @@ -68,6 +68,9 @@ bytes32 constant GOVERNANCE_TOKEN_MANAGER_ROLE = keccak256( "GOVERNANCE_TOKEN_MANAGER_ROLE" ); +/// @dev Role name for Governance token manager +bytes32 constant DEFENDER_RELAYER_ROLE = keccak256("DEFENDER_RELAYER_ROLE"); + /// @dev ETH pseudo address used to distinguish ERC20 tokens and ETH in `LibCollectableDust.sendDust()` address constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; diff --git a/packages/contracts/test/diamond/DiamondTest.t.sol b/packages/contracts/test/diamond/DiamondTest.t.sol index 2ad0a8790..f4b530683 100644 --- a/packages/contracts/test/diamond/DiamondTest.t.sol +++ b/packages/contracts/test/diamond/DiamondTest.t.sol @@ -19,7 +19,7 @@ contract TestDiamond is DiamondTestSetup { } function testHasMultipleFacets() public { - assertEq(facetAddressList.length, 20); + assertEq(facetAddressList.length, 19); } function testFacetsHaveCorrectSelectors() public { diff --git a/packages/contracts/test/diamond/DiamondTestSetup.sol b/packages/contracts/test/diamond/DiamondTestSetup.sol index 3eaab57be..39362a306 100644 --- a/packages/contracts/test/diamond/DiamondTestSetup.sol +++ b/packages/contracts/test/diamond/DiamondTestSetup.sol @@ -33,7 +33,6 @@ import {MockERC20} from "../../src/dollar/mocks/MockERC20.sol"; import {DiamondInit} from "../../src/dollar/upgradeInitializers/DiamondInit.sol"; import {DiamondTestHelper} from "../helpers/DiamondTestHelper.sol"; import {UUPSTestHelper} from "../helpers/UUPSTestHelper.sol"; -import {PoolLiquidityMonitor} from "../../src/dollar/monitors/PoolLiquidityMonitor.sol"; import {CREDIT_NFT_MANAGER_ROLE, CREDIT_TOKEN_BURNER_ROLE, CREDIT_TOKEN_MINTER_ROLE, CURVE_DOLLAR_MANAGER_ROLE, DOLLAR_TOKEN_BURNER_ROLE, DOLLAR_TOKEN_MINTER_ROLE, GOVERNANCE_TOKEN_BURNER_ROLE, GOVERNANCE_TOKEN_MANAGER_ROLE, GOVERNANCE_TOKEN_MINTER_ROLE, STAKING_SHARE_MINTER_ROLE} from "../../src/dollar/libraries/Constants.sol"; /** @@ -64,7 +63,6 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { StakingFacet stakingFacet; StakingFormulasFacet stakingFormulasFacet; UbiquityPoolFacet ubiquityPoolFacet; - PoolLiquidityMonitor poolLiquidityMonitor; // diamond facet implementation instances (should not be used in tests, use only on upgrades) AccessControlFacet accessControlFacetImplementation; @@ -86,7 +84,6 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { StakingFacet stakingFacetImplementation; StakingFormulasFacet stakingFormulasFacetImplementation; UbiquityPoolFacet ubiquityPoolFacetImplementation; - PoolLiquidityMonitor poolLiquidityMonitorImplementation; // facet names with addresses string[] facetNames; @@ -119,7 +116,6 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { bytes4[] selectorsOfStakingFacet; bytes4[] selectorsOfStakingFormulasFacet; bytes4[] selectorsOfUbiquityPoolFacet; - bytes4[] selectorsOfPoolLiquidityMonitor; /// @notice Deploys diamond and connects facets function setUp() public virtual { @@ -189,10 +185,6 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { "/out/UbiquityPoolFacet.sol/UbiquityPoolFacet.json" ); - selectorsOfPoolLiquidityMonitor = getSelectorsFromAbi( - "/out/PoolLiquidityMonitor.sol/PoolLiquidityMonitor.json" - ); - // deploy facet implementation instances accessControlFacetImplementation = new AccessControlFacet(); bondingCurveFacetImplementation = new BondingCurveFacet(); @@ -213,7 +205,6 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { stakingFacetImplementation = new StakingFacet(); stakingFormulasFacetImplementation = new StakingFormulasFacet(); ubiquityPoolFacetImplementation = new UbiquityPoolFacet(); - poolLiquidityMonitorImplementation = new PoolLiquidityMonitor(); // prepare diamond init args diamondInit = new DiamondInit(); @@ -236,8 +227,7 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { "OwnershipFacet", "StakingFacet", "StakingFormulasFacet", - "UbiquityPoolFacet", - "PoolLiquidityMonitor" + "UbiquityPoolFacet" ]; DiamondInit.Args memory initArgs = DiamondInit.Args({ admin: admin, @@ -257,7 +247,7 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { ) }); - FacetCut[] memory cuts = new FacetCut[](20); + FacetCut[] memory cuts = new FacetCut[](19); cuts[0] = ( FacetCut({ @@ -399,14 +389,6 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { }) ); - cuts[19] = ( - FacetCut({ - facetAddress: address(poolLiquidityMonitorImplementation), - action: FacetCutAction.Add, - functionSelectors: selectorsOfPoolLiquidityMonitor - }) - ); - // deploy diamond vm.prank(owner); diamond = new Diamond(_args, cuts); @@ -437,7 +419,6 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper { stakingFacet = StakingFacet(address(diamond)); stakingFormulasFacet = StakingFormulasFacet(address(diamond)); ubiquityPoolFacet = UbiquityPoolFacet(address(diamond)); - poolLiquidityMonitor = PoolLiquidityMonitor(address(diamond)); // get all addresses facetAddressList = diamondLoupeFacet.facetAddresses(); diff --git a/packages/contracts/test/dollar/monitors/PoolLiquidityMonitorTest.t.sol b/packages/contracts/test/dollar/monitors/PoolLiquidityMonitorTest.t.sol index 4f9aabd78..4257d41c2 100644 --- a/packages/contracts/test/dollar/monitors/PoolLiquidityMonitorTest.t.sol +++ b/packages/contracts/test/dollar/monitors/PoolLiquidityMonitorTest.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; import "forge-std/Test.sol"; -import "../../../src/dollar/monitors/PoolLiquidityMonitor.sol"; +import "../../../src/dollar/core/UbiquityPoolSecurityMonitor.sol"; import "../../helpers/LocalTestHelper.sol"; import {DiamondTestSetup} from "../../../test/diamond/DiamondTestSetup.sol"; import {DEFAULT_ADMIN_ROLE} from "../../../src/dollar/libraries/Constants.sol"; @@ -10,9 +10,10 @@ 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"; +import {ERC20Ubiquity} from "../../../src/dollar/core/ERC20Ubiquity.sol"; contract PoolLiquidityMonitorTest is DiamondTestSetup { - PoolLiquidityMonitor monitor; + UbiquityPoolSecurityMonitor monitor; address defenderRelayer = address(0x456); address unauthorized = address(0x123); @@ -137,7 +138,14 @@ contract PoolLiquidityMonitorTest is DiamondTestSetup { address(curveDollarPlainPool) ); - poolLiquidityMonitor.setDefenderRelayer(defenderRelayer); + accessControlFacet.grantRole(DEFENDER_RELAYER_ROLE, defenderRelayer); + + // Initialize the UbiquityPoolSecurityMonitor contract + monitor = new UbiquityPoolSecurityMonitor(); + monitor.initialize( + address(accessControlFacet), + address(ubiquityPoolFacet) + ); // stop being admin vm.stopPrank(); @@ -155,83 +163,77 @@ contract PoolLiquidityMonitorTest is DiamondTestSetup { } function testSetThresholdPercentage() public { - uint256 newThresholdPercentage = 30; + uint256 newThresholdPercentage = 20; vm.prank(admin); - poolLiquidityMonitor.setThresholdPercentage(newThresholdPercentage); + monitor.setThresholdPercentage(newThresholdPercentage); } function testUnauthorizedSetThresholdPercentage() public { uint256 newThresholdPercentage = 30; - vm.expectRevert("Manager: Caller is not admin"); - poolLiquidityMonitor.setThresholdPercentage(newThresholdPercentage); + vm.expectRevert("Ubiquity Pool Security Monitor: not admin"); + monitor.setThresholdPercentage(newThresholdPercentage); } function testDropLiquidityVertex() public { vm.prank(admin); - poolLiquidityMonitor.dropLiquidityVertex(); + monitor.dropLiquidityVertex(); } function testUnauthorizedDropLiquidityVertex() public { - vm.expectRevert("Manager: Caller is not admin"); - poolLiquidityMonitor.dropLiquidityVertex(); + vm.expectRevert("Ubiquity Pool Security Monitor: not admin"); + monitor.dropLiquidityVertex(); } function testTogglePaused() public { vm.prank(admin); - poolLiquidityMonitor.togglePaused(); + monitor.togglePaused(); } function testUnauthorizedTogglePaused() public { - vm.expectRevert("Manager: Caller is not admin"); - 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); + vm.expectRevert("Ubiquity Pool Security Monitor: not admin"); + monitor.togglePaused(); } function testCheckLiquidity() public { vm.prank(defenderRelayer); - poolLiquidityMonitor.checkLiquidityVertex(); + monitor.checkLiquidityVertex(); } function testUnauthorizedCheckLiquidity() public { vm.prank(unauthorized); - vm.expectRevert("Not authorized: Only Defender Relayer allowed"); + vm.expectRevert("Ubiquity Pool Security Monitor: not defender relayer"); - poolLiquidityMonitor.checkLiquidityVertex(); + monitor.checkLiquidityVertex(); } function testLiquidityDropBelowVertex() public { vm.prank(defenderRelayer); - poolLiquidityMonitor.checkLiquidityVertex(); + monitor.checkLiquidityVertex(); curveDollarPlainPool.updateMockParams(0.99e18); vm.prank(user); ubiquityPoolFacet.redeemDollar(0, 5e17, 0, 0); - // Call the checkLiquidityVertex function to test behavior after the liquidity drop vm.prank(defenderRelayer); - poolLiquidityMonitor.checkLiquidityVertex(); + monitor.checkLiquidityVertex(); - // Assert that the liquidity monitor paused after detecting a large drop - bool monitorPaused = poolLiquidityMonitor.monitorPaused(); + bool monitorPaused = monitor.monitorPaused(); assertTrue( monitorPaused, "Monitor should be paused after liquidity drop" ); } + + function testCheckLiquidityWhenPaused() public { + vm.prank(admin); + monitor.togglePaused(); + + vm.expectRevert("Monitor paused"); + + vm.prank(defenderRelayer); + monitor.checkLiquidityVertex(); + } }