Skip to content

Commit

Permalink
CDFacility: contract is disabled upon deployment
Browse files Browse the repository at this point in the history
  • Loading branch information
0xJem committed Jan 7, 2025
1 parent 7b718c5 commit fa4b099
Show file tree
Hide file tree
Showing 13 changed files with 297 additions and 14 deletions.
1 change: 1 addition & 0 deletions ROLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ This document describes the roles that are used in the Olympus protocol.
| emergency_restart | Emergency | Reactivates the TRSRY and/or MINTR modules |
| emergency_restart | EmissionManager | Reactivates the EmissionManager |
| emergency_shutdown | CDAuctioneer | Activate/deactivate the CDAuctioneer |
| emergency_shutdown | CDFacility | Activate/deactivate the CDFacility |
| emergency_shutdown | Clearinghouse | Allows shutting down the protocol in an emergency |
| emergency_shutdown | Emergency | Deactivates the TRSRY and/or MINTR modules |
| emergency_shutdown | EmissionManager | Deactivates the EmissionManager |
Expand Down
83 changes: 76 additions & 7 deletions src/policies/CDFacility.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract CDFacility is Policy, RolesConsumer, IConvertibleDepositFacility, Reent
// Constants

/// @notice The scale of the convertible deposit token
/// @dev This will typically be 10 ** decimals
/// @dev This will typically be 10 ** decimals, and is set by the `configureDependencies()` function
uint256 public SCALE;

// Modules
Expand All @@ -33,6 +33,13 @@ contract CDFacility is Policy, RolesConsumer, IConvertibleDepositFacility, Reent
CDEPOv1 public CDEPO;
CDPOSv1 public CDPOS;

/// @notice Whether the contract functionality has been activated
bool public locallyActive;

bytes32 public constant ROLE_EMERGENCY_SHUTDOWN = "emergency_shutdown";

bytes32 public constant ROLE_AUCTIONEER = "cd_auctioneer";

// ========== ERRORS ========== //

/// @notice An error that is thrown when the parameters are invalid
Expand All @@ -41,7 +48,8 @@ contract CDFacility is Policy, RolesConsumer, IConvertibleDepositFacility, Reent
// ========== SETUP ========== //

constructor(address kernel_) Policy(Kernel(kernel_)) {
// TODO disable until activated
// Disable functionality until initialized
locallyActive = false;
}

function configureDependencies() external override returns (Keycode[] memory dependencies) {
Expand Down Expand Up @@ -84,13 +92,16 @@ contract CDFacility is Policy, RolesConsumer, IConvertibleDepositFacility, Reent
// ========== CONVERTIBLE DEPOSIT ACTIONS ========== //

/// @inheritdoc IConvertibleDepositFacility
/// @dev This function reverts if:
/// - The caller does not have the ROLE_AUCTIONEER role
/// - The contract is not active
function create(
address account_,
uint256 amount_,
uint256 conversionPrice_,
uint48 expiry_,
bool wrap_
) external onlyRole("cd_auctioneer") nonReentrant returns (uint256 positionId) {
) external onlyRole(ROLE_AUCTIONEER) nonReentrant onlyActive returns (uint256 positionId) {
// Mint the CD token to the account
// This will also transfer the reserve token
CDEPO.mintFor(account_, amount_);
Expand Down Expand Up @@ -144,6 +155,7 @@ contract CDFacility is Policy, RolesConsumer, IConvertibleDepositFacility, Reent

/// @inheritdoc IConvertibleDepositFacility
/// @dev This function reverts if:
/// - The contract is not active
/// - The length of the positionIds_ array does not match the length of the amounts_ array
/// - account_ is not the owner of all of the positions
/// - The position is not valid
Expand All @@ -156,7 +168,12 @@ contract CDFacility is Policy, RolesConsumer, IConvertibleDepositFacility, Reent
address account_,
uint256[] memory positionIds_,
uint256[] memory amounts_
) external view returns (uint256 cdTokenIn, uint256 convertedTokenOut, address cdTokenSpender) {
)
external
view
onlyActive
returns (uint256 cdTokenIn, uint256 convertedTokenOut, address cdTokenSpender)
{
// Make sure the lengths of the arrays are the same
if (positionIds_.length != amounts_.length) revert CDF_InvalidArgs("array length");

Expand All @@ -178,6 +195,7 @@ contract CDFacility is Policy, RolesConsumer, IConvertibleDepositFacility, Reent

/// @inheritdoc IConvertibleDepositFacility
/// @dev This function reverts if:
/// - The contract is not active
/// - The length of the positionIds_ array does not match the length of the amounts_ array
/// - The caller is not the owner of all of the positions
/// - The position is not valid
Expand All @@ -189,7 +207,7 @@ contract CDFacility is Policy, RolesConsumer, IConvertibleDepositFacility, Reent
function convert(
uint256[] memory positionIds_,
uint256[] memory amounts_
) external nonReentrant returns (uint256 cdTokenIn, uint256 convertedTokenOut) {
) external nonReentrant onlyActive returns (uint256 cdTokenIn, uint256 convertedTokenOut) {
// Make sure the lengths of the arrays are the same
if (positionIds_.length != amounts_.length) revert CDF_InvalidArgs("array length");

Expand Down Expand Up @@ -254,11 +272,20 @@ contract CDFacility is Policy, RolesConsumer, IConvertibleDepositFacility, Reent
}

/// @inheritdoc IConvertibleDepositFacility
/// @dev This function reverts if:
/// - The contract is not active
/// - The length of the positionIds_ array does not match the length of the amounts_ array
/// - The caller is not the owner of all of the positions
/// - The position is not valid
/// - The position is not CDEPO
/// - The position has not expired
/// - The deposit amount is greater than the remaining deposit
/// - The deposit amount is 0
function previewReclaim(
address account_,
uint256[] memory positionIds_,
uint256[] memory amounts_
) external view returns (uint256 reclaimed, address cdTokenSpender) {
) external view onlyActive returns (uint256 reclaimed, address cdTokenSpender) {
// Make sure the lengths of the arrays are the same
if (positionIds_.length != amounts_.length) revert CDF_InvalidArgs("array length");

Expand All @@ -276,6 +303,7 @@ contract CDFacility is Policy, RolesConsumer, IConvertibleDepositFacility, Reent

/// @inheritdoc IConvertibleDepositFacility
/// @dev This function reverts if:
/// - The contract is not active
/// - The length of the positionIds_ array does not match the length of the amounts_ array
/// - The caller is not the owner of all of the positions
/// - The position is not valid
Expand All @@ -286,7 +314,7 @@ contract CDFacility is Policy, RolesConsumer, IConvertibleDepositFacility, Reent
function reclaim(
uint256[] memory positionIds_,
uint256[] memory amounts_
) external override nonReentrant returns (uint256 reclaimed) {
) external nonReentrant onlyActive returns (uint256 reclaimed) {
// Make sure the lengths of the arrays are the same
if (positionIds_.length != amounts_.length) revert CDF_InvalidArgs("array length");

Expand Down Expand Up @@ -348,4 +376,45 @@ contract CDFacility is Policy, RolesConsumer, IConvertibleDepositFacility, Reent
function convertedToken() external view returns (address) {
return address(MINTR.ohm());
}

// ========== ADMIN FUNCTIONS ========== //

/// @notice Activate the contract functionality
/// @dev This function will revert if:
/// - The caller does not have the ROLE_EMERGENCY_SHUTDOWN role
///
/// Note that if the contract is already active, this function will do nothing.
function activate() external onlyRole(ROLE_EMERGENCY_SHUTDOWN) {
// If the contract is already active, do nothing
if (locallyActive) return;

// Set the contract to active
locallyActive = true;

// Emit event
emit Activated();
}

/// @notice Deactivate the contract functionality
/// @dev This function will revert if:
/// - The caller does not have the ROLE_EMERGENCY_SHUTDOWN role
///
/// Note that if the contract is already inactive, this function will do nothing.
function deactivate() external onlyRole(ROLE_EMERGENCY_SHUTDOWN) {
// If the contract is already inactive, do nothing
if (!locallyActive) return;

// Set the contract to inactive
locallyActive = false;

// Emit event
emit Deactivated();
}

// ========== MODIFIERS ========== //

modifier onlyActive() {
if (!locallyActive) revert CDF_NotActive();
_;
}
}
8 changes: 5 additions & 3 deletions src/policies/interfaces/IConvertibleDepositFacility.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;

import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import {IERC4626} from "openzeppelin-contracts/contracts/interfaces/IERC4626.sol";

/// @title IConvertibleDepositFacility
/// @notice Interface for a contract that can perform functions related to convertible deposit tokens
interface IConvertibleDepositFacility {
Expand All @@ -13,6 +10,9 @@ interface IConvertibleDepositFacility {
event ConvertedDeposit(address indexed user, uint256 depositAmount, uint256 convertedAmount);
event ReclaimedDeposit(address indexed user, uint256 reclaimedAmount);

event Activated();
event Deactivated();

// ========== ERRORS ========== //

error CDF_InvalidArgs(string reason_);
Expand All @@ -27,6 +27,8 @@ interface IConvertibleDepositFacility {

error CDF_InvalidToken(uint256 positionId_, address token_);

error CDF_NotActive();

// ========== CONVERTIBLE DEPOSIT ACTIONS ========== //

/// @notice Creates a new convertible deposit position
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ contract ConvertibleDepositAuctioneerTest is Test {
rolesAdmin.grantRole(bytes32("cd_admin"), admin);
rolesAdmin.grantRole(bytes32("emergency_shutdown"), emergency);
rolesAdmin.grantRole(bytes32("cd_auctioneer"), address(auctioneer));

// Activate policy dependencies
vm.prank(emergency);
facility.activate();
}

// ========== HELPERS ========== //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {OlympusRoles} from "src/modules/ROLES/OlympusRoles.sol";
import {OlympusConvertibleDepository} from "src/modules/CDEPO/OlympusConvertibleDepository.sol";
import {OlympusConvertibleDepositPositions} from "src/modules/CDPOS/OlympusConvertibleDepositPositions.sol";
import {RolesAdmin} from "src/policies/RolesAdmin.sol";
import {ROLESv1} from "src/modules/ROLES/ROLES.v1.sol";

contract ConvertibleDepositFacilityTest is Test {
Kernel public kernel;
Expand All @@ -31,6 +32,7 @@ contract ConvertibleDepositFacilityTest is Test {
address public recipient = address(0x1);
address public auctioneer = address(0x2);
address public recipientTwo = address(0x3);
address public emergency = address(0x4);

uint48 public constant INITIAL_BLOCK = 1_000_000;
uint256 public constant CONVERSION_PRICE = 2e18;
Expand Down Expand Up @@ -65,6 +67,7 @@ contract ConvertibleDepositFacilityTest is Test {

// Grant roles
rolesAdmin.grantRole(bytes32("cd_auctioneer"), auctioneer);
rolesAdmin.grantRole(bytes32("emergency_shutdown"), emergency);
}

// ========== MODIFIERS ========== //
Expand Down Expand Up @@ -109,4 +112,22 @@ contract ConvertibleDepositFacilityTest is Test {
convertibleDepository.approve(spender_, amount_);
_;
}

modifier givenLocallyActive() {
vm.prank(emergency);
facility.activate();
_;
}

modifier givenLocallyInactive() {
vm.prank(emergency);
facility.deactivate();
_;
}

// ========== ASSERTIONS ========== //

function _expectRoleRevert(bytes32 role_) internal {
vm.expectRevert(abi.encodeWithSelector(ROLESv1.ROLES_RequireRole.selector, role_));
}
}
44 changes: 44 additions & 0 deletions src/test/policies/ConvertibleDepositFacility/activate.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: Unlicensed
pragma solidity 0.8.15;

import {ConvertibleDepositFacilityTest} from "./ConvertibleDepositFacilityTest.sol";

contract ActivateCDFTest is ConvertibleDepositFacilityTest {
event Activated();

// given the caller does not have the emergency_shutdown role
// [X] it reverts
// given the contract is already active
// [X] it does nothing
// [X] it sets the contract to active
// [X] it emits an Activated event

function test_callerDoesNotHaveRole_reverts() public {
_expectRoleRevert("emergency_shutdown");

// Call function
facility.activate();
}

function test_contractActive() public givenLocallyActive {
// Call function
vm.prank(emergency);
facility.activate();

// Assert state
assertEq(facility.locallyActive(), true, "active");
}

function test_success() public {
// Emits event
vm.expectEmit(true, true, true, true);
emit Activated();

// Call function
vm.prank(emergency);
facility.activate();

// Assert state
assertEq(facility.locallyActive(), true, "active");
}
}
17 changes: 17 additions & 0 deletions src/test/policies/ConvertibleDepositFacility/constructor.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Unlicensed
pragma solidity 0.8.15;

import {ConvertibleDepositFacilityTest} from "./ConvertibleDepositFacilityTest.sol";

import {CDFacility} from "src/policies/CDFacility.sol";

contract ConstructorCDFTest is ConvertibleDepositFacilityTest {
// [X] it sets the contract to inactive

function test_success() public {
facility = new CDFacility(address(kernel));

// Assert state
assertEq(facility.locallyActive(), false, "inactive");
}
}
Loading

0 comments on commit fa4b099

Please sign in to comment.