8000 Remote GSM by efecarranza · Pull Request #451 · aave/gho-core · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Remote GSM #451

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service 8000 and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
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
99 changes: 99 additions & 0 deletions src/contracts/facilitators/gsm/GhoReserve.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// 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 {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/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 {
/// @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

8000

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

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

_transferOwnership(initialOwner);
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 memory usage = _ghoUsage[msg.sender];
require(usage.limit >= usage.used + amount, 'LIMIT_REACHED');

_ghoUsage[msg.sender].used += uint128(amount);
Comment on lines +46 to +49
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
GhoUsage memory usage = _ghoUsage[msg.sender];
require(usage.limit >= usage.used + amount, 'LIMIT_REACHED');
_ghoUsage[msg.sender].used += uint128(amount);
GhoUsage storage entity = _ghoUsage[msg.sender];
require(entity.limit >= entity.used + amount, 'LIMIT_REACHED');
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 setEntityLimit(address entity, uint256 limit) external onlyOwner {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
function setEntityLimit(address entity, uint256 limit) external onlyOwner {
function setLimit(address entity, uint256 limit) external onlyOwner {

_ghoUsage[entity].limit = uint128(limit);

emit EntityLimitUpdated(entity, limit);
}

/// @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 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();
}
}
77 changes: 67 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,2 10000 2 @@ contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm {
return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee);
}

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

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

/**
* @dev Updates Fee Strategy
* @param feeStrategy The address of the new Fee Strategy
Expand Down Expand Up @@ -558,6 +600,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
38 changes: 18 additions & 20 deletions src/contracts/facilitators/gsm/Gsm4626.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol';
import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol';
import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol';
import {IGsm4626} from './interfaces/IGsm4626.sol';
import {IGhoReserve} from './interfaces/IGhoReserve.sol';
import {Gsm} from './Gsm.sol';

/**
Expand Down Expand Up @@ -43,14 +44,14 @@ contract Gsm4626 is Gsm, IGsm4626 {
) external notSeized onlyRole(CONFIGURATOR_ROLE) returns (uint256) {
require(amount > 0, 'INVALID_AMOUNT');

(, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this));
(, uint256 deficit) = _getCurrentBacking(ghoMinted);
uint256 usedGho = _getUsedGho();
(, uint256 deficit) = _getCurrentBacking(usedGho);
require(deficit > 0, 'NO_CURRENT_DEFICIT_BACKING');

uint256 ghoToBack = amount > deficit ? deficit : amount;

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

emit BackingProvided(msg.sender, GHO_TOKEN, ghoToBack, ghoToBack, deficit - ghoToBack);
return ghoToBack;
Expand All @@ -62,8 +63,7 @@ contract Gsm4626 is Gsm, IGsm4626 {
) external notSeized onlyRole(CONFIGURATOR_ROLE) returns (uint256) {
require(amount > 0, 'INVALID_AMOUNT');

(, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this));
(, uint256 deficit) = _getCurrentBacking(ghoMinted);
(, uint256 deficit) = _getCurrentBacking(_getUsedGho());
require(deficit > 0, 'NO_CURRENT_DEFICIT_BACKING');

uint128 deficitInUnderlying = IGsmPriceStrategy(PRICE_STRATEGY)
Expand Down Expand Up @@ -95,8 +95,7 @@ contract Gsm4626 is Gsm, IGsm4626 {

/// @inheritdoc IGsm4626
function getCurrentBacking() external view returns (uint256, uint256) {
(, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this));
return _getCurrentBacking(ghoMinted);
return _getCurrentBacking(_getUsedGho());
}

/// @inheritdoc IGhoFacilitator
Expand All @@ -116,36 +115,35 @@ contract Gsm4626 is Gsm, IGsm4626 {
/**
* @dev Cumulates yield in form of GHO, aimed to be redirected to the treasury
* @dev It mints GHO backed by the excess of underlying produced by the ERC4626 yield
* @dev If the GHO amount exceeds the amount available, it will mint up to the remaining capacity
* @dev If the GHO amount exceeds the amount available, it will mint up to the remaining limit
*/
function _cumulateYieldInGho() internal {
(uint256 ghoCapacity, uint256 ghoLevel) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(
address(this)
);
uint256 ghoAvailableToMint = ghoCapacity > ghoLevel ? ghoCapacity - ghoLevel : 0;
uint256 ghoLevel = _getUsedGho();
uint256 ghoLimit = _getLimit();
Comment on lines +121 to +122
Copy link
Contributor

Choose a reason for hiding this comment

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

should we create an internal function _getUsage that returns both?
apart from that, let's rename Level to Used please

uint256 ghoAvailableToMint = ghoLimit > ghoLevel ? ghoLimit - ghoLevel : 0;
(uint256 ghoExcess, ) = _getCurrentBacking(ghoLevel);
if (ghoExcess > 0 && ghoAvailableToMint > 0) {
ghoExcess = ghoExcess > ghoAvailableToMint ? ghoAvailableToMint : ghoExcess;
_accruedFees += uint128(ghoExcess);
IGhoToken(GHO_TOKEN).mint(address(this), ghoExcess);
IGhoReserve(_ghoReserve).use(ghoExcess);
}
}

/**
* @dev Calculates the excess or deficit of GHO minted, reflective of GSM backing
* @param ghoMinted The amount of GHO currently minted by the GSM
* @return The excess amount of GHO minted, relative to the value of the underlying
* @return The deficit of GHO minted, relative to the value of the underlying
* @param usedGho The amount of GHO currently used by the GSM
* @return The excess amount of GHO used, relative to the value of the underlying
* @return The deficit of GHO used, relative to the value of the underlying
*/
function _getCurrentBacking(uint256 ghoMinted) internal view returns (uint256, uint256) {
function _getCurrentBacking(uint256 usedGho) internal view returns (uint256, uint256) {
uint256 ghoToBack = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(
_currentExposure,
false
);
if (ghoToBack >= ghoMinted) {
return (ghoToBack - ghoMinted, 0);
if (ghoToBack >= usedGho) {
return (ghoToBack - usedGho, 0);
} else {
return (0, ghoMinted - ghoToBack);
return (0, usedGho - ghoToBack);
}
}
}
Loading
0