Skip to content

Commit

Permalink
feat: remove pool-monitor from the diamond
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandr-masl committed Sep 17, 2024
1 parent 1df5a77 commit dd032cf
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 83 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -20,52 +24,66 @@ 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;

emit VertexDropped();
}

function checkLiquidityVertex() external onlyAuthorized {
uint256 currentCollateralLiquidity = LibUbiquityPool
function checkLiquidityVertex() external onlyDefender {
uint256 currentCollateralLiquidity = ubiquityPoolFacet
.collateralUsdBalance();

require(currentCollateralLiquidity > 0, "Insufficient liquidity");
require(!monitorPaused, "Monitor paused");

if (currentCollateralLiquidity > liquidityVertex) {
liquidityVertex = currentCollateralLiquidity;

emit LiquidityVertexUpdated(liquidityVertex);
} else {
uint256 liquidityDiffPercentage = liquidityVertex
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions packages/contracts/src/dollar/libraries/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/test/diamond/DiamondTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ contract TestDiamond is DiamondTestSetup {
}

function testHasMultipleFacets() public {
assertEq(facetAddressList.length, 20);
assertEq(facetAddressList.length, 19);
}

function testFacetsHaveCorrectSelectors() public {
Expand Down
23 changes: 2 additions & 21 deletions packages/contracts/test/diamond/DiamondTestSetup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
Expand Down Expand Up @@ -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;
Expand All @@ -86,7 +84,6 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper {
StakingFacet stakingFacetImplementation;
StakingFormulasFacet stakingFormulasFacetImplementation;
UbiquityPoolFacet ubiquityPoolFacetImplementation;
PoolLiquidityMonitor poolLiquidityMonitorImplementation;

// facet names with addresses
string[] facetNames;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -236,8 +227,7 @@ abstract contract DiamondTestSetup is DiamondTestHelper, UUPSTestHelper {
"OwnershipFacet",
"StakingFacet",
"StakingFormulasFacet",
"UbiquityPoolFacet",
"PoolLiquidityMonitor"
"UbiquityPoolFacet"
];
DiamondInit.Args memory initArgs = DiamondInit.Args({
admin: admin,
Expand All @@ -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({
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@
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";
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);

Expand Down Expand Up @@ -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();
Expand All @@ -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();
}
}

0 comments on commit dd032cf

Please sign in to comment.