Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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
65 changes: 53 additions & 12 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,14 @@ 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);

emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount));
_handleGhoBurnAfterSeize(amount);

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

Expand Down Expand Up @@ -405,8 +404,8 @@ 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, ghoSold, grossAmount);
IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount);

emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee);
Expand Down Expand Up @@ -451,13 +450,47 @@ 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);
IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought);
_handleGhoBought(receiver, ghoBought, fee);

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 ghoSold The total amount of GHO the user sells (gross amount in GHO plus fee)
* @param grossAmount The gross amount of GHO
*/
function _handleGhoSold(
address originator,
uint256 ghoSold,
uint256 grossAmount
) internal virtual {
IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold);
IGhoToken(GHO_TOKEN).burn(grossAmount);
}

/**
* Transfers GHO bought by receiver and handles fees
* @param receiver Address that bought GHO
* @param ghoBought The amount of GHO being purchased
* @param fee The fees generated in GHO for the GSM
*/
function _handleGhoBought(address receiver, uint256 ghoBought, uint256 fee) internal virtual {
IGhoToken(GHO_TOKEN).mint(address(this), ghoBought + fee);
IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought);
}

/**
* 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).transferFrom(msg.sender, address(this), amount);
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 +560,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
82 changes: 82 additions & 0 deletions src/contracts/facilitators/gsm/RemoteGsm.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol';
import {IRemoteGsm} from './interfaces/IRemoteGsm.sol';
import {MockCollector} from '../../../test/mocks/MockCollector.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 ghoSold,
uint256 grossAmount
) internal override {
IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold);
MockCollector(_ghoVault).payBackGho(grossAmount);
}

/// @inheritdoc Gsm
function _handleGhoBought(address receiver, uint256 ghoBought, uint256 fee) internal override {
MockCollector(_ghoVault).transferGho(address(this), fee);
MockCollector(_ghoVault).transferGho(receiver, ghoBought);
}

/// @inheritdoc Gsm
function _handleGhoBurnAfterSeize(uint256 amount) internal override {
IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount);
MockCollector(_ghoVault).payBackGho(amount);
MockCollector(_ghoVault).bridgeGho(amount);
}

/// @inheritdoc Gsm
function _getGhoOutstanding() internal view override returns (uint256) {
return MockCollector(_ghoVault).ghoOutstanding();
}

/**
* @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;
emit GhoVaultUpdated(oldVault, ghoVault);
}
}
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 {MockCollector} from './mocks/MockCollector.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;

MockCollector MOCK_COLLECTOR;

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

MOCK_COLLECTOR = new MockCollector();

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(MOCK_COLLECTOR)
);
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));

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

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