Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/remote-facilitator.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The following section explains how remote facilitators for GHO operate and outlines the contracts involved in the setup.

### [OwnableFacilitator](/src/contracts/facilitators/gsm/OwnableFacilitator.sol)
### [GhoDirectFacilitator](/src/contracts/facilitators/gsm/GhoDirectFacilitator.sol)

A minimal facilitator contract with minting and burning capabilities, controlled by a single entity. It must be granted the `Facilitator` role on the GHO token with a defined bucket capacity.

Expand All @@ -22,8 +22,8 @@ Remote facilitators function similarly to those on Ethereum, but the way GHO is

[On Ethereum]

1. An `OwnableFacilitator` contract is deployed to represent the minting strategy on the remote chain. It is granted the `Facilitator` role on the Ethereum `GhoToken` contract, with a defined bucket capacity X.
2. An amount X of GHO is minted via the `OwnableFacilitator` and bridged to the remote chain (typically via `CCIP`).
1. An `GhoDirectFacilitator` contract is deployed to represent the minting strategy on the remote chain. It is granted the `Facilitator` role on the Ethereum `GhoToken` contract, with a defined bucket capacity X.
2. An amount X of GHO is minted via the `GhoDirectFacilitator` and bridged to the remote chain (typically via `CCIP`).

[On the Remote Chain]

Expand Down
48 changes: 48 additions & 0 deletions src/contracts/facilitators/gsm/GhoDirectFacilitator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import {AccessControl} from 'src/contracts/dependencies/openzeppelin-contracts/contracts/access/AccessControl.sol';
import {IGhoToken} from 'src/contracts/gho/interfaces/IGhoToken.sol';
import {IGhoDirectFacilitator} from 'src/contracts/facilitators/gsm/interfaces/IGhoDirectFacilitator.sol';

/**
* @title GhoDirectFacilitator
* @author Aave/TokenLogic
* @notice GHO Facilitator used to directly mint GHO to a given address.
*/
contract GhoDirectFacilitator is AccessControl, IGhoDirectFacilitator {
/// @inheritdoc IGhoDirectFacilitator
bytes32 public constant MINTER_ROLE = keccak256('MINTER_ROLE');

/// @inheritdoc IGhoDirectFacilitator
bytes32 public constant BURNER_ROLE = keccak256('MINTER_ROLE');

/// @inheritdoc IGhoDirectFacilitator
address public immutable GHO_TOKEN;

/**
* @dev Constructor
* @param admin The address of the initial owner
* @param ghoAddress The address of GHO token
*/
constructor(address admin, address ghoAddress) {
require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID');
require(ghoAddress != address(0), 'ZERO_ADDRESS_NOT_VALID');

_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(MINTER_ROLE, admin);
_grantRole(BURNER_ROLE, admin);

GHO_TOKEN = ghoAddress;
}

/// @inheritdoc IGhoDirectFacilitator
function mint(address account, uint256 amount) external onlyRole(MINTER_ROLE) {
IGhoToken(GHO_TOKEN).mint(account, amount);
}

/// @inheritdoc IGhoDirectFacilitator
function burn(uint256 amount) external onlyRole(BURNER_ROLE) {
IGhoToken(GHO_TOKEN).burn(amount);
}
}
35 changes: 24 additions & 11 deletions src/contracts/facilitators/gsm/GhoReserve.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';
import {AccessControl} from 'src/contracts/dependencies/openzeppelin-contracts/contracts/access/AccessControl.sol';
import {SafeCast} from 'src/contracts/dependencies/openzeppelin-contracts/contracts/utils/math/SafeCast.sol';
import {IERC20} from 'aave-v3-origin/contracts/dependencies/openzeppelin/contracts/IERC20.sol';
import {VersionedInitializable} from 'aave-v3-origin/contracts/misc/aave-upgradeability/VersionedInitializable.sol';
Expand All @@ -14,10 +14,19 @@ import {IGhoReserve} from 'src/contracts/facilitators/gsm/interfaces/IGhoReserve
* @notice It allows approved entities to withdraw and return GHO funds, with a defined maximum withdrawal capacity per entity.
* @dev To be covered by a proxy contract.
*/
contract GhoReserve is Ownable, VersionedInitializable, IGhoReserve {
contract GhoReserve is AccessControl, VersionedInitializable, IGhoReserve {
using EnumerableSet for EnumerableSet.AddressSet;
using SafeCast for uint256;

/// @inheritdoc IGhoReserve
bytes32 public constant MANAGE_ENTITY_ROLE = keccak256('MANAGE_ENTITY_ROLE');

/// @inheritdoc IGhoReserve
bytes32 public constant SET_LIMIT_ROLE = keccak256('SET_LIMIT_ROLE');

/// @inheritdoc IGhoReserve
bytes32 public constant TRANSFER_ROLE = keccak256('TRANSFER_ROLE');

/// @inheritdoc IGhoReserve
address public immutable GHO_TOKEN;

Expand All @@ -31,18 +40,22 @@ contract GhoReserve is Ownable, VersionedInitializable, IGhoReserve {
* @dev Constructor
* @param ghoAddress The address of the GHO token on the remote chain
*/
constructor(address ghoAddress) Ownable(msg.sender) {
constructor(address ghoAddress) {
require(ghoAddress != address(0), 'ZERO_ADDRESS_NOT_VALID');
GHO_TOKEN = ghoAddress;
}

/**
* @dev Initializer
* @param newOwner The address of the new owner
* @param admin The address of the new owner
*/
function initialize(address newOwner) external initializer {
require(newOwner != address(0), 'ZERO_ADDRESS_NOT_VALID');
_transferOwnership(newOwner);
function initialize(address admin) external initializer {
require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID');

_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(MANAGE_ENTITY_ROLE, admin);
_grantRole(SET_LIMIT_ROLE, admin);
_grantRole(TRANSFER_ROLE, admin);
Comment on lines 55 to 58
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could only give default admin role, but i'm fine with this given that most likely it d be assigned to the Executor.
(sme in GhoDirectFacilitator)

}

/// @inheritdoc IGhoReserve
Expand All @@ -63,19 +76,19 @@ contract GhoReserve is Ownable, VersionedInitializable, IGhoReserve {
}

/// @inheritdoc IGhoReserve
function transfer(address to, uint256 amount) external onlyOwner {
function transfer(address to, uint256 amount) external onlyRole(TRANSFER_ROLE) {
IERC20(GHO_TOKEN).transfer(to, amount);
emit GhoTransferred(to, amount);
}

/// @inheritdoc IGhoReserve
function addEntity(address entity) external onlyOwner {
function addEntity(address entity) external onlyRole(MANAGE_ENTITY_ROLE) {
require(_entities.add(entity), 'ENTITY_ALREADY_EXISTS');
emit EntityAdded(entity);
}

/// @inheritdoc IGhoReserve
function removeEntity(address entity) external onlyOwner {
function removeEntity(address entity) external onlyRole(MANAGE_ENTITY_ROLE) {
GhoUsage memory usage = _ghoUsage[entity];
require(usage.used == 0, 'ENTITY_GHO_USED_NOT_ZERO');
require(usage.limit == 0, 'ENTITY_GHO_LIMIT_NOT_ZERO');
Expand All @@ -85,7 +98,7 @@ contract GhoReserve is Ownable, VersionedInitializable, IGhoReserve {
}

/// @inheritdoc IGhoReserve
function setLimit(address entity, uint256 limit) external onlyOwner {
function setLimit(address entity, uint256 limit) external onlyRole(SET_LIMIT_ROLE) {
require(_entities.contains(entity), 'ENTITY_DOES_NOT_EXIST');
_ghoUsage[entity].limit = limit.toUint128();

Expand Down
38 changes: 0 additions & 38 deletions src/contracts/facilitators/gsm/OwnableFacilitator.sol

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,38 @@
pragma solidity ^0.8.0;

/**
* @title IOwnableFacilitator
* @title IGhoDirectFacilitator
* @author Aave/TokenLogic
* @notice Defines the behaviour of an OwnableFacilitator
* @notice Defines the behaviour of an GhoDirectFacilitator
*/
interface IOwnableFacilitator {
interface IGhoDirectFacilitator {
/**
* @notice Mint an amount of GHO to an address
* @dev Only callable by the owner of the Facilitator.
* @dev Only callable by address with MINTER_ROLE.
* @param account The address receiving GHO
* @param amount The amount of GHO to be minted
*/
function mint(address account, uint256 amount) external;

/**
* @notice Burns an amount of GHO
* @dev Only callable by the owner of the Facilitator.
* @dev Only callable by address with BURNER_ROLE.
* @param amount The amount of GHO to be burned
*/
function burn(uint256 amount) external;

/**
* @notice Returns the identifier of the Minter Role
* @return The bytes32 id hash of the Minter role
*/
function MINTER_ROLE() external pure returns (bytes32);

/**
* @notice Returns the identifier of the Burner Role
* @return The bytes32 id hash of the Burner role
*/
function BURNER_ROLE() external pure returns (bytes32);

/**
* @notice Returns the address of the GHO token
* @return The address of GHO token contract
Expand Down
18 changes: 18 additions & 0 deletions src/contracts/facilitators/gsm/interfaces/IGhoReserve.sol
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,24 @@ interface IGhoReserve {
*/
function setLimit(address entity, uint256 limit) external;

/**
* @notice Returns the identifier of the ManageEntity Role
* @return The bytes32 id hash of the ManageEntity role
*/
function MANAGE_ENTITY_ROLE() external pure returns (bytes32);

/**
* @notice Returns the identifier of the SetLimit Role
* @return The bytes32 id hash of the SetLimit role
*/
function SET_LIMIT_ROLE() external pure returns (bytes32);

/**
* @notice Returns the identifier of the Transfer Role
* @return The bytes32 id hash of the Transfer role
*/
function TRANSFER_ROLE() external pure returns (bytes32);

/**
* @notice Returns the address of the GHO token
* @return The address of GHO token contract
Expand Down
9 changes: 9 additions & 0 deletions tests/helpers/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ contract Constants {
bytes32 public constant GSM_SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE');
bytes32 public constant GSM_LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE');

// admin role for GhoDirectFacilitator
bytes32 public constant MINTER_ROLE = keccak256('MINTER_ROLE');
bytes32 public constant BURNER_ROLE = keccak256('MINTER_ROLE');

// admin role for GhoReserve
bytes32 public constant MANAGE_ENTITY_ROLE = keccak256('MANAGE_ENTITY_ROLE');
bytes32 public constant SET_LIMIT_ROLE = keccak256('SET_LIMIT_ROLE');
bytes32 public constant TRANSFER_ROLE = keccak256('TRANSFER_ROLE');

// signature typehash for GSM
bytes32 public constant GSM_BUY_ASSET_WITH_SIG_TYPEHASH =
keccak256(
Expand Down
14 changes: 7 additions & 7 deletions tests/unit/TestGhoBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ import {IGhoGsmSteward} from 'src/contracts/misc/interfaces/IGhoGsmSteward.sol';
import {GhoGsmSteward} from 'src/contracts/misc/GhoGsmSteward.sol';
import {FixedFeeStrategyFactory} from 'src/contracts/facilitators/gsm/feeStrategy/FixedFeeStrategyFactory.sol';
import {GhoReserve} from 'src/contracts/facilitators/gsm/GhoReserve.sol';
import {OwnableFacilitator} from 'src/contracts/facilitators/gsm/OwnableFacilitator.sol';
import {GhoDirectFacilitator} from 'src/contracts/facilitators/gsm/GhoDirectFacilitator.sol';
import {OracleSwapFreezerBase} from 'src/contracts/facilitators/gsm/swapFreezer/OracleSwapFreezerBase.sol';
import {ChainlinkOracleSwapFreezer} from 'src/contracts/facilitators/gsm/swapFreezer/ChainlinkOracleSwapFreezer.sol';
import {GelatoOracleSwapFreezer} from 'src/contracts/facilitators/gsm/swapFreezer/GelatoOracleSwapFreezer.sol';
Expand Down Expand Up @@ -156,7 +156,7 @@ contract TestGhoBase is Test, Constants, Events {
MockUpgradeableLockReleaseTokenPool GHO_TOKEN_POOL;

GhoReserve GHO_RESERVE;
OwnableFacilitator OWNABLE_FACILITATOR;
GhoDirectFacilitator GHO_DIRECT_FACILITATOR;

constructor() {
setupGho();
Expand Down Expand Up @@ -226,11 +226,11 @@ contract TestGhoBase is Test, Constants, Events {
GHO_RESERVE = new GhoReserve(address(GHO_TOKEN));
GHO_RESERVE.initialize(address(this));

OWNABLE_FACILITATOR = new OwnableFacilitator(address(this), address(GHO_TOKEN));
// Give OwnableFacilitator twice the default capacity to fully fund two GSMs
GHO_DIRECT_FACILITATOR = new GhoDirectFacilitator(address(this), address(GHO_TOKEN));
// Give GhoDirectFacilitator twice the default capacity to fully fund two GSMs
GHO_TOKEN.addFacilitator(
address(OWNABLE_FACILITATOR),
'OwnableFacilitator',
address(GHO_DIRECT_FACILITATOR),
'GhoDirectFacilitator',
DEFAULT_CAPACITY * 2
);

Expand Down Expand Up @@ -292,7 +292,7 @@ contract TestGhoBase is Test, Constants, Events {
GHO_RESERVE.setLimit(address(GHO_GSM_4626), DEFAULT_CAPACITY);

// Mint twice default capacity for both GSMs to be fully funded
OWNABLE_FACILITATOR.mint(address(GHO_RESERVE), DEFAULT_CAPACITY * 2);
GHO_DIRECT_FACILITATOR.mint(address(GHO_RESERVE), DEFAULT_CAPACITY * 2);

GHO_GSM_FIXED_FEE_STRATEGY = new FixedFeeStrategy(DEFAULT_GSM_BUY_FEE, DEFAULT_GSM_SELL_FEE);
GHO_GSM.updateFeeStrategy(address(GHO_GSM_FIXED_FEE_STRATEGY));
Expand Down
Loading