Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
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
59 changes: 59 additions & 0 deletions src/contracts/facilitators/gsm/GhoRemoteVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol';
import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol';
import {IGhoRemoteVault} from './interfaces/IGhoRemoteVault.sol';

contract GhoRemoteVault is IGhoRemoteVault, AccessControl {
/// @inheritdoc IGhoRemoteVault
bytes32 public constant FUNDS_ADMIN_ROLE = 'FUNDS_ADMIN';

address public immutable GHO;

/// @dev Mapping to keep track of GHO withdrawn by an address
mapping(address => uint256) private _ghoWithdrawn;

/**
* @dev Throws if the caller does not have the FUNDS_ADMIN role
*/
modifier onlyFundsAdmin() {
require(_onlyFundsAdmin(), 'ONLY_FUNDS_ADMIN');
_;
}

/**
* @dev Constructor
* @param initialAdmin Address of the initial admin of the contract
* @param ghoAddress Address of GHO token on the remote chain
*/
constructor(address initialAdmin, address ghoAddress) {
require(ghoAddress != address(0), 'INVALID_ZERO_ADDRESS');

_grantRole(DEFAULT_ADMIN_ROLE, initialAdmin);
GHO = ghoAddress;
}

function withdrawGho(uint256 amount) external onlyFundsAdmin {
_ghoWithdrawn[msg.sender] += amount;
IERC20(GHO).transfer(msg.sender, amount);
}

function returnGho(uint256 amount) external onlyFundsAdmin {
_ghoWithdrawn[msg.sender] -= amount;
IERC20(GHO).transferFrom(msg.sender, address(this), amount);
}

function bridgeGho(uint256 amount) external onlyFundsAdmin {
// Intentionally left bank
}

/// @inheritdoc IGhoRemoteVault
function getWithdrawnGho(address withdrawer) external view returns (uint256) {
return _ghoWithdrawn[withdrawer];
}

function _onlyFundsAdmin() internal view returns (bool) {
return hasRole(FUNDS_ADMIN_ROLE, msg.sender);
}
}
52 changes: 43 additions & 9 deletions src/contracts/facilitators/gsm/Gsm.sol
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,12 @@ contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm {
_currentExposure = 0;
_updateExposureCap(0);

(, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this));
uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this));
if (underlyingBalance > 0) {
IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance);
}

emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted);
emit Seized(msg.sender, _ghoTreasury, underlyingBalance, _getGhoOutstanding());
return underlyingBalance;
}

Expand All @@ -237,14 +236,15 @@ contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm {
require(_isSeized, 'GSM_NOT_SEIZED');
require(amount > 0, 'INVALID_AMOUNT');

(, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this));
if (amount > ghoMinted) {
amount = ghoMinted;
uint256 ghoOutstanding = _getGhoOutstanding();
if (amount > ghoOutstanding) {
amount = ghoOutstanding;
}

IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount);
IGhoToken(GHO_TOKEN).burn(amount);
_handleGhoBurnAfterSeize(amount);

emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount));
emit BurnAfterSeize(msg.sender, amount, (ghoOutstanding - amount));
return amount;
}

Expand Down Expand Up @@ -405,8 +405,9 @@ contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm {

_currentExposure -= uint128(assetAmount);
_accruedFees += fee.toUint128();

IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold);
IGhoToken(GHO_TOKEN).burn(grossAmount);
_handleGhoSold(originator, grossAmount);
IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount);

emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee);
Expand Down Expand Up @@ -451,13 +452,38 @@ contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm {
_accruedFees += fee.toUint128();
IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount);

IGhoToken(GHO_TOKEN).mint(address(this), grossAmount);
_handleGhoBought(grossAmount);
IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought);

emit SellAsset(originator, receiver, assetAmount, grossAmount, fee);
return (assetAmount, ghoBought);
}

/**
* Transfers GHO sold by receiver to purchase underlying
* @param originator Originator of the request
* @param grossAmount The gross amount of GHO
*/
function _handleGhoSold(address originator, uint256 grossAmount) internal virtual {
IGhoToken(GHO_TOKEN).burn(grossAmount);
}

/**
* Transfers GHO bought by receiver and handles fees
* @param grossAmount The gross amount of GHO
*/
function _handleGhoBought(uint256 grossAmount) internal virtual {
IGhoToken(GHO_TOKEN).mint(address(this), grossAmount);
}

/**
* Transfers GHO in order to be burned after seize
* @param amount Amount of GHO to transfer in and burn
*/
function _handleGhoBurnAfterSeize(uint256 amount) internal virtual {
IGhoToken(GHO_TOKEN).burn(amount);
}

/**
* @dev Hook that is called before `sellAsset`.
* @dev This can be used to add custom logic
Expand Down Expand Up @@ -527,6 +553,14 @@ contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm {
return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee);
}

/**
* Returns the amount of GHO that has been sent out by the GSM
*/
function _getGhoOutstanding() internal view virtual returns (uint256) {
(, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this));
return ghoMinted;
}

/**
* @dev Updates Fee Strategy
* @param feeStrategy The address of the new Fee Strategy
Expand Down
79 changes: 79 additions & 0 deletions src/contracts/facilitators/gsm/RemoteGsm.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol';
import {IRemoteGsm} from './interfaces/IRemoteGsm.sol';
import {GhoRemoteVault} from './GhoRemoteVault.sol';
import {Gsm} from './Gsm.sol';

/**
* @title RemoteGsm
* @author Aave
* @notice Remote GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO.
* @dev To be covered by a proxy contract.
*/
contract RemoteGsm is IRemoteGsm, Gsm {
address internal _ghoVault;

/**
* @dev Constructor
* @param ghoToken The address of the GHO token contract
* @param underlyingAsset The address of the collateral asset
* @param priceStrategy The address of the price strategy
* @param ghoVault Address of the GHO vault to fund GSM trades
*/
constructor(
address ghoToken,
address underlyingAsset,
address priceStrategy,
address ghoVault
) Gsm(ghoToken, underlyingAsset, priceStrategy) {
_updateGhoVault(ghoVault);
}

/// @inheritdoc IRemoteGsm
function updateGhoVault(address ghoVault) external onlyRole(CONFIGURATOR_ROLE) {
_updateGhoVault(ghoVault);
}

/// @inheritdoc IRemoteGsm
function getGhoVault() external view returns (address) {
return _ghoVault;
}

/// @inheritdoc Gsm
function _handleGhoSold(address originator, uint256 grossAmount) internal override {
GhoRemoteVault(_ghoVault).returnGho(grossAmount);
}

/// @inheritdoc Gsm
function _handleGhoBought(uint256 grossAmount) internal override {
GhoRemoteVault(_ghoVault).withdrawGho(grossAmount);
}

/// @inheritdoc Gsm
function _handleGhoBurnAfterSeize(uint256 amount) internal override {
GhoRemoteVault(_ghoVault).returnGho(amount);
GhoRemoteVault(_ghoVault).bridgeGho(amount);
}

/// @inheritdoc Gsm
function _getGhoOutstanding() internal view override returns (uint256) {
return GhoRemoteVault(_ghoVault).getWithdrawnGho(address(this));
}

/**
* @dev Updates address of GHO Vault
* @param ghoVault The address of the GHO vault for the GSM
*/
function _updateGhoVault(address ghoVault) internal {
require(ghoVault != address(0), 'ZERO_ADDRESS_NOT_VALID');
address oldVault = _ghoVault;
_ghoVault = ghoVault;

IGhoToken(GHO_TOKEN).approve(oldVault, 0);
IGhoToken(GHO_TOKEN).approve(ghoVault, type(uint256).max);

emit GhoVaultUpdated(oldVault, ghoVault);
}
}
32 changes: 32 additions & 0 deletions src/contracts/facilitators/gsm/interfaces/IGhoRemoteVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IAccessControl} from '@openzeppelin/contracts/access/IAccessControl.sol';

interface IGhoRemoteVault is IAccessControl {
/**
* @dev Emitted when GHO tokens are withdrawn
* @param user Address withdrawing GHO
* @param amount Amount of GHO withdrawn
*/
event GhoWithdrawn(address indexed user, uint256 amount);

/**
* @dev Emitted when GHO tokens are returned
* @param user Address returning GHO
* @param amount Amount of GHO returned
*/
event GhoReturned(address indexed user, uint256 amount);

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

/**
* Returns amount of GHO withdrawn by a specified address
* @param withdrawer Address of the contract that withdrew GHO from vault
*/
function getWithdrawnGho(address withdrawer) external view returns (uint256);
}
29 changes: 29 additions & 0 deletions src/contracts/facilitators/gsm/interfaces/IRemoteGsm.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IGsm} from './IGsm.sol';

/**
* @title IRemoteGsm
* @author Aave
* @notice Defines the behaviour of a Remote GHO Stability Module
*/
interface IRemoteGsm is IGsm {
/**
* @dev Emitted when the GSM's vault is updated
* @param oldVault The address of the old vault
* @param newVault The address of the new vault
*/
event GhoVaultUpdated(address oldVault, address newVault);

/**
* Returns the address of the GHO vault
*/
function getGhoVault() external view returns (address);

/**
* @notice Updates the GHO vault address
* @param ghoVault The new address of the vault holding GHO
*/
function updateGhoVault(address ghoVault) external;
}
21 changes: 20 additions & 1 deletion src/test/TestGhoBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {FixedRateStrategyFactory} from '../contracts/facilitators/aave/interestS
import {IGsm} from '../contracts/facilitators/gsm/interfaces/IGsm.sol';
import {Gsm} from '../contracts/facilitators/gsm/Gsm.sol';
import {Gsm4626} from '../contracts/facilitators/gsm/Gsm4626.sol';
import {RemoteGsm} from '../contracts/facilitators/gsm/RemoteGsm.sol';
import {FixedPriceStrategy} from '../contracts/facilitators/gsm/priceStrategy/FixedPriceStrategy.sol';
import {FixedPriceStrategy4626} from '../contracts/facilitators/gsm/priceStrategy/FixedPriceStrategy4626.sol';
import {IGsmFeeStrategy} from '../contracts/facilitators/gsm/feeStrategy/interfaces/IGsmFeeStrategy.sol';
Expand All @@ -87,6 +88,8 @@ import {IGhoCcipSteward} from '../contracts/misc/interfaces/IGhoCcipSteward.sol'
import {GhoCcipSteward} from '../contracts/misc/GhoCcipSteward.sol';
import {GhoBucketSteward} from '../contracts/misc/GhoBucketSteward.sol';

import {GhoRemoteVault} from '../contracts/facilitators/gsm/GhoRemoteVault.sol';

contract TestGhoBase is Test, Constants, Events {
using WadRayMath for uint256;
using SafeCast for uint256;
Expand Down Expand Up @@ -125,6 +128,7 @@ contract TestGhoBase is Test, Constants, Events {
MockFlashBorrower FLASH_BORROWER;
Gsm GHO_GSM;
Gsm4626 GHO_GSM_4626;
RemoteGsm REMOTE_GSM;
FixedPriceStrategy GHO_GSM_FIXED_PRICE_STRATEGY;
FixedPriceStrategy4626 GHO_GSM_4626_FIXED_PRICE_STRATEGY;
FixedFeeStrategy GHO_GSM_FIXED_FEE_STRATEGY;
Expand All @@ -142,6 +146,8 @@ contract TestGhoBase is Test, Constants, Events {
FixedFeeStrategyFactory FIXED_FEE_STRATEGY_FACTORY;
MockUpgradeableLockReleaseTokenPool GHO_TOKEN_POOL;

GhoRemoteVault GHO_REMOTE_VAULT;

constructor() {
setupGho();
}
Expand Down Expand Up @@ -234,6 +240,8 @@ contract TestGhoBase is Test, Constants, Events {
GHO_TOKEN.addFacilitator(address(GHO_ATOKEN), 'Aave V3 Pool', DEFAULT_CAPACITY);
POOL.setGhoTokens(GHO_DEBT_TOKEN, GHO_ATOKEN);

GHO_REMOTE_VAULT = new GhoRemoteVault(address(this), address(GHO_TOKEN));

GHO_FLASH_MINTER = new GhoFlashMinter(
address(GHO_TOKEN),
TREASURY,
Expand Down Expand Up @@ -280,24 +288,35 @@ contract TestGhoBase is Test, Constants, Events {
address(GHO_GSM_4626_FIXED_PRICE_STRATEGY)
);
GHO_GSM_4626.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE);
REMOTE_GSM = new RemoteGsm(
address(GHO_TOKEN),
address(USDC_TOKEN),
address(GHO_GSM_FIXED_PRICE_STRATEGY),
address(GHO_REMOTE_VAULT)
);
REMOTE_GSM.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE);

GHO_GSM_FIXED_FEE_STRATEGY = new FixedFeeStrategy(DEFAULT_GSM_BUY_FEE, DEFAULT_GSM_SELL_FEE);
GHO_GSM.updateFeeStrategy(address(GHO_GSM_FIXED_FEE_STRATEGY));
GHO_GSM_4626.updateFeeStrategy(address(GHO_GSM_FIXED_FEE_STRATEGY));
REMOTE_GSM.updateFeeStrategy(address(GHO_GSM_FIXED_FEE_STRATEGY));

GHO_GSM.grantRole(GSM_LIQUIDATOR_ROLE, address(GHO_GSM_LAST_RESORT_LIQUIDATOR));
GHO_GSM.grantRole(GSM_SWAP_FREEZER_ROLE, address(GHO_GSM_SWAP_FREEZER));
GHO_GSM_4626.grantRole(GSM_LIQUIDATOR_ROLE, address(GHO_GSM_LAST_RESORT_LIQUIDATOR));
GHO_GSM_4626.grantRole(GSM_SWAP_FREEZER_ROLE, address(GHO_GSM_SWAP_FREEZER));
REMOTE_GSM.grantRole(GSM_LIQUIDATOR_ROLE, address(GHO_GSM_LAST_RESORT_LIQUIDATOR));
REMOTE_GSM.grantRole(GSM_SWAP_FREEZER_ROLE, address(GHO_GSM_SWAP_FREEZER));

GHO_TOKEN.addFacilitator(address(GHO_GSM), 'GSM Facilitator', DEFAULT_CAPACITY);
GHO_TOKEN.addFacilitator(address(GHO_GSM_4626), 'GSM 4626 Facilitator', DEFAULT_CAPACITY);

GHO_TOKEN.addFacilitator(FAUCET, 'Faucet Facilitator', type(uint128).max);

GHO_GSM_REGISTRY = new GsmRegistry(address(this));
FIXED_RATE_STRATEGY_FACTORY = new FixedRateStrategyFactory(address(PROVIDER));

GHO_REMOTE_VAULT.grantRole(FUNDS_ADMIN_ROLE, address(REMOTE_GSM));

// Deploy Gho Token Pool
address ARM_PROXY = makeAddr('ARM_PROXY');
address OWNER = makeAddr('OWNER');
Expand Down
Loading