Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 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
80 changes: 80 additions & 0 deletions src/contracts/facilitators/gsm/GhoRemoteVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// 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 {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol';
import {IGhoRemoteVault} from './interfaces/IGhoRemoteVault.sol';

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

/// @inheritdoc IGhoRemoteVault
address public immutable GHO_TOKEN;

/// @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 ghoAddress Address of GHO token on the remote chain
*/
constructor(address ghoAddress) {
require(ghoAddress != address(0), 'INVALID_ZERO_ADDRESS');

GHO_TOKEN = ghoAddress;
}

/**
* @notice GhoRemoteVault initializer
* @param admin The address of the default admin role
*/
function initialize(address admin) external initializer {
require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID');
_grantRole(DEFAULT_ADMIN_ROLE, admin);
}

/// @inheritdoc IGhoRemoteVault
function withdrawGho(uint256 amount) external onlyFundsAdmin {
_ghoWithdrawn[msg.sender] += amount;
IERC20(GHO_TOKEN).transfer(msg.sender, amount);
}

/// @inheritdoc IGhoRemoteVault
function returnGho(uint256 amount) external onlyFundsAdmin {
_ghoWithdrawn[msg.sender] -= amount;
IERC20(GHO_TOKEN).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];
}

/// @inheritdoc IGhoRemoteVault
function GHO_REMOTE_VAULT_REVISION() public pure virtual override returns (uint256) {
return 1;
}

/// @inheritdoc VersionedInitializable
function getRevision() internal pure virtual override returns (uint256) {
return GHO_REMOTE_VAULT_REVISION();
}

function _onlyFundsAdmin() internal view returns (bool) {
return hasRole(FUNDS_ADMIN_ROLE, msg.sender);
}
}
53 changes: 44 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, _getUsedGho());
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 = _getUsedGho();
if (amount > ghoOutstanding) {
amount = ghoOutstanding;
}

IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount);
IGhoToken(GHO_TOKEN).burn(amount);
_burnGhoAfterSeize(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);
_restoreGho(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);
_useGho(grossAmount);
IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought);

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

/**
* @dev Restores GHO amount spent by the contract
* @param originator Originator of the request
* @param grossAmount The gross amount of GHO
*/
function _restoreGho(address originator, uint256 grossAmount) internal virtual {
IGhoToken(GHO_TOKEN).burn(grossAmount);
}

/**
* @dev Uses GHO token available to be used by the GSM
* @param grossAmount The gross amount of GHO
*/
function _useGho(uint256 grossAmount) internal virtual {
IGhoToken(GHO_TOKEN).mint(address(this), grossAmount);
}

/**
* @dev Handles GHO burning after seizure of GSM
* @param amount Amount of GHO to transfer in and burn
*/
function _burnGhoAfterSeize(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,15 @@ contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm {
return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee);
}

/**
* @dev Returns the total amount of GHO that has been spent
* @return The amount of GHO that has been spent
*/
function _getUsedGho() 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 _restoreGho(address originator, uint256 grossAmount) internal override {
GhoRemoteVault(_ghoVault).returnGho(grossAmount);
}

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

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

/// @inheritdoc Gsm
function _getUsedGho() 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);
}
}
56 changes: 56 additions & 0 deletions src/contracts/facilitators/gsm/interfaces/IGhoRemoteVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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);

/**
* @notice Returns the address of the GHO token
* @return The address of GHO token contract
*/
function GHO_TOKEN() external view returns (address);

/**
* @notice Accepts GHO to be repaied by caller
* @param amount The amount of GHO to return
*/
function returnGho(uint256 amount) external;

/**
* @notice Allows allowed caller to withdraw GHO from vault
* @param amount The amount of GHO to withdraw
*/
function withdrawGho(uint256 amount) external;

/**
* 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);

/**
* @notice Returns the GhoRemoteVault revision number
* @return The revision number
*/
function GHO_REMOTE_VAULT_REVISION() external pure 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;
}
Loading