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

import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol';
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';
import {VersionedInitializable} from '@aave/periphery-v3/contracts/treasury/libs/VersionedInitializable.sol';
import {IGhoReserve} from './interfaces/IGhoReserve.sol';

/**
* @title GhoReserve
* @author Aave/TokenLogic
* @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 {
using EnumerableSet for EnumerableSet.AddressSet;

/// @inheritdoc IGhoReserve
address public immutable GHO_TOKEN;

/// Map of entities and their assigned capacity and amount of GHO used
mapping(address => GhoUsage) private _ghoUsage;

Copy link
Contributor

@miguelmtzinf miguelmtzinf May 16, 2025

Choose a reason for hiding this comment

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

Wondering if we should add global variables for total limit and total used. It's a bit hard to iterate over entities also, to calculate totals

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would total limit be every time we set a limit +/- to the global variable? For used it's easy to keep track

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah, would be keeping global accounting every time an entity updates its usage (limit or used). Not sure it's completely needed

/// Set of entities with a GHO limit available
EnumerableSet.AddressSet private _entities;

/**
* @dev Constructor
* @param ghoAddress The address of the GHO token on the remote chain
*/
constructor(address ghoAddress) {
require(ghoAddress != address(0), 'ZERO_ADDRESS_NOT_VALID');
GHO_TOKEN = ghoAddress;
}

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

/// @inheritdoc IGhoReserve
function use(uint256 amount) external {
GhoUsage storage entity = _ghoUsage[msg.sender];
require(entity.limit >= entity.used + amount, 'LIMIT_EXCEEDED');

entity.used += uint128(amount);
IERC20(GHO_TOKEN).transfer(msg.sender, amount);
emit GhoUsed(msg.sender, amount);
}

/// @inheritdoc IGhoReserve
function restore(uint256 amount) external {
_ghoUsage[msg.sender].used -= uint128(amount);
IERC20(GHO_TOKEN).transferFrom(msg.sender, address(this), amount);
emit GhoRestored(msg.sender, amount);
}

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

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

/// @inheritdoc IGhoReserve
function removeEntity(address entity) external onlyOwner {
require(_ghoUsage[entity].used == 0, 'ENTITY_GHO_USED_NOT_ZERO');
require(_entities.remove(entity), 'ENTITY_NOT_REMOVED');

emit EntityRemoved(entity);
}

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

emit GhoLimitUpdated(entity, limit);
}

/// @inheritdoc IGhoReserve
function getEntities() external view returns (address[] memory) {
return _entities.values();
}

/// @inheritdoc IGhoReserve
function getUsed(address entity) external view returns (uint256) {
return _ghoUsage[entity].used;
}

/// @inheritdoc IGhoReserve
function getUsage(address entity) external view returns (uint256, uint256) {
GhoUsage memory usage = _ghoUsage[entity];
return (usage.limit, usage.used);
}

/// @inheritdoc IGhoReserve
function getLimit(address entity) external view returns (uint256) {
return _ghoUsage[entity].limit;
}

/// @inheritdoc IGhoReserve
function isEntity(address entity) external view returns (bool) {
return _entities.contains(entity);
}

/// @inheritdoc IGhoReserve
function totalEntities() external view returns (uint256) {
return _entities.length();
}

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

/// @inheritdoc VersionedInitializable
function getRevision() internal pure virtual override returns (uint256) {
return GHO_REMOTE_RESERVE_REVISION();
}
}
86 changes: 76 additions & 10 deletions src/contracts/facilitators/gsm/Gsm.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol';
import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol';
import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol';
import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol';
import {IGhoReserve} from './interfaces/IGhoReserve.sol';
import {IGsm} from './interfaces/IGsm.sol';

/**
Expand Down Expand Up @@ -67,6 +68,7 @@ contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm {
uint128 internal _exposureCap;
uint128 internal _currentExposure;
uint128 internal _accruedFees;
address internal _ghoReserve;

/**
* @dev Require GSM to not be frozen for functions marked by this modifier
Expand Down Expand Up @@ -107,17 +109,20 @@ contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm {
* @param admin The address of the default admin role
* @param ghoTreasury The address of the GHO treasury
* @param exposureCap Maximum amount of user-supplied underlying asset in GSM
* @param ghoReserve The address of the GHO reserve to use tokens from
*/
function initialize(
address admin,
address ghoTreasury,
uint128 exposureCap
uint128 exposureCap,
address ghoReserve
) external initializer {
require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID');
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(CONFIGURATOR_ROLE, admin);
_updateGhoTreasury(ghoTreasury);
_updateExposureCap(exposureCap);
_updateGhoReserve(ghoReserve);
}

/// @inheritdoc IGsm
Expand Down Expand Up @@ -222,13 +227,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 +241,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 usedGho = _getUsedGho();
if (amount > usedGho) {
amount = usedGho;
}

IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount);
IGhoToken(GHO_TOKEN).burn(amount);
IGhoReserve(_ghoReserve).restore(amount);

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

Expand All @@ -258,6 +263,11 @@ contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm {
_updateExposureCap(exposureCap);
}

/// @inheritdoc IGsm
function updateGhoReserve(address newGhoReserve) external onlyRole(CONFIGURATOR_ROLE) {
_updateGhoReserve(newGhoReserve);
}

/// @inheritdoc IGhoFacilitator
function distributeFeesToTreasury() public virtual override {
uint256 accruedFees = _accruedFees;
Expand Down Expand Up @@ -363,6 +373,21 @@ contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm {
return _isSeized;
}

/// @inheritdoc IGsm
function getGhoReserve() external view returns (address) {
return _ghoReserve;
}

/// @inheritdoc IGsm
function getUsedGho() external view returns (uint256) {
return _getUsedGho();
}

/// @inheritdoc IGsm
function getLimit() external view returns (uint256) {
return _getLimit();
}

/// @inheritdoc IGsm
function canSwap() external view returns (bool) {
return !_isFrozen && !_isSeized;
Expand Down Expand Up @@ -405,8 +430,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);
IGhoReserve(_ghoReserve).restore(grossAmount);
IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount);

emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee);
Expand Down Expand Up @@ -451,7 +477,7 @@ 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);
IGhoReserve(_ghoReserve).use(grossAmount);
IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought);

emit SellAsset(originator, receiver, assetAmount, grossAmount, fee);
Expand Down Expand Up @@ -527,6 +553,31 @@ contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm {
return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee);
}

/**
* @dev Returns the maximum amount of GHO that can be used.
* @return The usage limit of GHO
*/
function _getLimit() internal view returns (uint256) {
return IGhoReserve(_ghoReserve).getLimit(address(this));
}

/**
* @dev Returns the usage data of a specified entity.
* @return The usage limit of GHO
* @return The amount of GHO used
*/
function _getUsage() internal view returns (uint256, uint256) {
return IGhoReserve(_ghoReserve).getUsage(address(this));
}

/**
* @dev Returns the amount of GHO currently used.
* @return The amount of GHO used
*/
function _getUsedGho() internal view returns (uint256) {
return IGhoReserve(_ghoReserve).getUsed(address(this));
}

/**
* @dev Updates Fee Strategy
* @param feeStrategy The address of the new Fee Strategy
Expand Down Expand Up @@ -558,6 +609,21 @@ contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm {
emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury);
}

/**
* @dev Updates the address of GHO reserve
* @param newGhoReserve The address of the GHO reserve for the GSM
*/
function _updateGhoReserve(address newGhoReserve) internal {
require(newGhoReserve != address(0), 'ZERO_ADDRESS_NOT_VALID');
address oldReserve = _ghoReserve;
_ghoReserve = newGhoReserve;

IGhoToken(GHO_TOKEN).approve(oldReserve, 0);
IGhoToken(GHO_TOKEN).approve(newGhoReserve, type(uint256).max);

emit GhoReserveUpdated(oldReserve, newGhoReserve);
}

/// @inheritdoc VersionedInitializable
function getRevision() internal pure virtual override returns (uint256) {
return GSM_REVISION();
Expand Down
Loading
Loading