Skip to content

Commit

Permalink
feat: implement gsm fee strategy factory
Browse files Browse the repository at this point in the history
  • Loading branch information
CheyenneAtapour committed Aug 2, 2024
1 parent 9359796 commit 25edbf7
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 19 deletions.
37 changes: 21 additions & 16 deletions src/contracts/misc/GhoGsmSteward.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';
import {FixedFeeStrategy} from '../facilitators/gsm/feeStrategy/FixedFeeStrategy.sol';
import {IGsm} from '../facilitators/gsm/interfaces/IGsm.sol';
import {IGsmFeeStrategy} from '../facilitators/gsm/feeStrategy/interfaces/IGsmFeeStrategy.sol';
import {IGhoGsmSteward} from './interfaces/IGhoGsmSteward.sol';
import {RiskCouncilControlled} from './RiskCouncilControlled.sol';
import {IGsmFeeStrategyFactory} from './interfaces/IGsmFeeStrategyFactory.sol';

/**
* @title GhoGsmSteward
Expand All @@ -16,18 +16,16 @@ import {RiskCouncilControlled} from './RiskCouncilControlled.sol';
* @dev Requires role GSM_CONFIGURATOR_ROLE on every GSM contract to be managed
*/
contract GhoGsmSteward is RiskCouncilControlled, IGhoGsmSteward {
using EnumerableSet for EnumerableSet.AddressSet;

/// @inheritdoc IGhoGsmSteward
uint256 public constant GSM_FEE_RATE_CHANGE_MAX = 0.0050e4; // 0.50%

/// @inheritdoc IGhoGsmSteward
uint256 public constant MINIMUM_DELAY = 2 days;

mapping(address => GsmDebounce) internal _gsmTimelocksByAddress;
/// @inheritdoc IGhoGsmSteward
address public immutable GSM_FEE_STRATEGY_FACTORY;

mapping(uint256 => mapping(uint256 => address)) internal _gsmFeeStrategiesByRates;
EnumerableSet.AddressSet internal _gsmFeeStrategies;
mapping(address => GsmDebounce) internal _gsmTimelocksByAddress;

/**
* @dev Only methods that are not timelocked can be called if marked by this modifier.
Expand All @@ -39,9 +37,17 @@ contract GhoGsmSteward is RiskCouncilControlled, IGhoGsmSteward {

/**
* @dev Constructor
* @param gsmFeeStrategyFactory The address of the GSM Fee Strategy Factory
* @param riskCouncil The address of the risk council
*/
constructor(address riskCouncil) RiskCouncilControlled(riskCouncil) {}
constructor(
address gsmFeeStrategyFactory,
address riskCouncil
) RiskCouncilControlled(riskCouncil) {
require(gsmFeeStrategyFactory != address(0), 'INVALID_GSM_FEE_STRATEGY_FACTORY');

GSM_FEE_STRATEGY_FACTORY = gsmFeeStrategyFactory;
}

/**
* @inheritdoc IGhoGsmSteward
Expand Down Expand Up @@ -83,17 +89,16 @@ contract GhoGsmSteward is RiskCouncilControlled, IGhoGsmSteward {
'INVALID_SELL_FEE_UPDATE'
);

address cachedStrategyAddress = _gsmFeeStrategiesByRates[buyFee][sellFee];
if (cachedStrategyAddress == address(0)) {
FixedFeeStrategy newRateStrategy = new FixedFeeStrategy(buyFee, sellFee);
cachedStrategyAddress = address(newRateStrategy);
_gsmFeeStrategiesByRates[buyFee][sellFee] = cachedStrategyAddress;
_gsmFeeStrategies.add(cachedStrategyAddress);
}
IGsmFeeStrategyFactory strategyFactory = IGsmFeeStrategyFactory(GSM_FEE_STRATEGY_FACTORY);
uint256[] memory buyFeeList = new uint256[](1);
uint256[] memory sellFeeList = new uint256[](1);
buyFeeList[0] = buyFee;
sellFeeList[0] = sellFee;
address strategy = strategyFactory.createStrategies(buyFeeList, sellFeeList)[0];

_gsmTimelocksByAddress[gsm].gsmFeeStrategyLastUpdated = uint40(block.timestamp);

IGsm(gsm).updateFeeStrategy(cachedStrategyAddress);
IGsm(gsm).updateFeeStrategy(strategy);
}

/**
Expand All @@ -107,7 +112,7 @@ contract GhoGsmSteward is RiskCouncilControlled, IGhoGsmSteward {
* @inheritdoc IGhoGsmSteward
*/
function getGsmFeeStrategies() external view returns (address[] memory) {
return _gsmFeeStrategies.values();
return IGsmFeeStrategyFactory(GSM_FEE_STRATEGY_FACTORY).getGsmFeeStrategies();
}

/// @inheritdoc IGhoGsmSteward
Expand Down
84 changes: 84 additions & 0 deletions src/contracts/misc/GsmFeeStrategyFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';
import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol';
import {IGsmFeeStrategyFactory} from './interfaces/IGsmFeeStrategyFactory.sol';
import {IGsmFeeStrategy} from 'src/contracts/facilitators/gsm/feeStrategy/interfaces/IGsmFeeStrategy.sol';
import {FixedFeeStrategy} from '../facilitators/gsm/feeStrategy/FixedFeeStrategy.sol';

/**
* @title GsmFeeStrategyFactory
* @author Aave Labs
* @notice Factory contract to create and keep record of Gsm Fee contracts
*/
contract GsmFeeStrategyFactory is VersionedInitializable, IGsmFeeStrategyFactory {
using EnumerableSet for EnumerableSet.AddressSet;

mapping(uint256 => mapping(uint256 => address)) internal _gsmFeeStrategiesByRates;
EnumerableSet.AddressSet internal _gsmFeeStrategies;

/**
* @notice GsmFeeStrategyFactory initializer
* @dev assumes that the addresses provided are deployed fee strategies.
* @param feeStrategiesList List of fee strategies
*/
function initialize(address[] memory feeStrategiesList) external initializer {
for (uint256 i = 0; i < feeStrategiesList.length; i++) {
address feeStrategy = feeStrategiesList[i];
uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);
uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);

_gsmFeeStrategiesByRates[buyFee][sellFee] = feeStrategy;
_gsmFeeStrategies.add(feeStrategy);

emit FeeStrategyCreated(feeStrategy, buyFee, sellFee);
}
}

///@inheritdoc IGsmFeeStrategyFactory
function createStrategies(
uint256[] memory buyFeeList,
uint256[] memory sellFeeList
) public returns (address[] memory) {
require(buyFeeList.length == sellFeeList.length, 'INVALID_FEE_LIST');
address[] memory strategies = new address[](buyFeeList.length);
for (uint256 i = 0; i < buyFeeList.length; i++) {
uint256 buyFee = buyFeeList[i];
uint256 sellFee = sellFeeList[i];
address cachedStrategy = _gsmFeeStrategiesByRates[buyFee][sellFee];

if (cachedStrategy == address(0)) {
cachedStrategy = address(new FixedFeeStrategy(buyFee, sellFee));
_gsmFeeStrategiesByRates[buyFee][sellFee] = cachedStrategy;
_gsmFeeStrategies.add(cachedStrategy);

emit FeeStrategyCreated(cachedStrategy, buyFee, sellFee);
}

strategies[i] = cachedStrategy;
}

return strategies;
}

///@inheritdoc IGsmFeeStrategyFactory
function getGsmFeeStrategies() external view returns (address[] memory) {
return _gsmFeeStrategies.values();
}

///@inheritdoc IGsmFeeStrategyFactory
function getStrategyByRates(uint256 buyFee, uint256 sellFee) external view returns (address) {
return _gsmFeeStrategiesByRates[buyFee][sellFee];
}

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

/// @inheritdoc VersionedInitializable
function getRevision() internal pure virtual override returns (uint256) {
return REVISION();
}
}
6 changes: 6 additions & 0 deletions src/contracts/misc/interfaces/IGhoGsmSteward.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ interface IGhoGsmSteward {
*/
function MINIMUM_DELAY() external view returns (uint256);

/**
* @notice Returns the address of the GSM Fee Strategy Factory
* @return The address of the GSM Fee Strategy Factory
*/
function GSM_FEE_STRATEGY_FACTORY() external view returns (address);

/**
* @notice Returns the address of the risk council
* @return The address of the RiskCouncil
Expand Down
49 changes: 49 additions & 0 deletions src/contracts/misc/interfaces/IGsmFeeStrategyFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IGsmFeeStrategyFactory {
/**
* @dev Emitted when a new strategy is created
* @param strategy The address of the new Gsm fee strategy
* @param buyFee The buy fee of the new strategy
* @param sellFee The sell fee of the new strategy
*/
event FeeStrategyCreated(
address indexed strategy,
uint256 indexed buyFee,
uint256 indexed sellFee
);

/**
* @notice Creates new Gsm Fee strategy contracts from lists of buy and sell fees
* @dev Returns the address of a cached contract if a strategy with same fees already exists
* @param buyFeeList The list of buy fees for Gsm fee strategies
* @param sellFeeList The list of sell fees for Gsm fee strategies
* @return The list of Gsm fee strategy contracts
*/
function createStrategies(
uint256[] memory buyFeeList,
uint256[] memory sellFeeList
) external returns (address[] memory);

/**
* @notice Returns all the fee strategy contracts of the factory
* @return The list of fee strategy contracts
*/
function getGsmFeeStrategies() external view returns (address[] memory);

/**
* @notice Returns the fee strategy contract which corresponds to the given fees.
* @dev Returns `address(0)` if there is no fee strategy for the given fees
* @param buyFee The buy fee of the fee strategy contract
* @param sellFee The sell fee of the fee strategy contract
* @return The address of the fee strategy contract
*/
function getStrategyByRates(uint256 buyFee, uint256 sellFee) external view returns (address);

/**
* @notice Returns the GsmFeeStrategyFactory revision number
* @return The revision number
*/
function REVISION() external pure returns (uint256);
}
6 changes: 4 additions & 2 deletions src/test/TestGhoBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import {SampleSwapFreezer} from '../contracts/facilitators/gsm/misc/SampleSwapFr
import {GsmRegistry} from '../contracts/facilitators/gsm/misc/GsmRegistry.sol';
import {IGhoGsmSteward} from '../contracts/misc/interfaces/IGhoGsmSteward.sol';
import {GhoGsmSteward} from '../contracts/misc/GhoGsmSteward.sol';
import {GsmFeeStrategyFactory} from '../contracts/misc/GsmFeeStrategyFactory.sol';

// CCIP contracts
import {UpgradeableTokenPool} from '../contracts/misc/deps/Dependencies.sol';
Expand Down Expand Up @@ -142,6 +143,7 @@ contract TestGhoBase is Test, Constants, Events {
GhoBucketCapacitySteward GHO_BUCKET_CAPACITY_STEWARD;

FixedRateStrategyFactory FIXED_RATE_STRATEGY_FACTORY;
GsmFeeStrategyFactory GSM_FEE_STRATEGY_FACTORY;
UpgradeableLockReleaseTokenPool GHO_TOKEN_POOL;

constructor() {
Expand Down Expand Up @@ -262,7 +264,6 @@ contract TestGhoBase is Test, Constants, Events {
address(USDC_4626_TOKEN),
6
);
GHO_GSM_FIXED_FEE_STRATEGY = new FixedFeeStrategy(DEFAULT_GSM_BUY_FEE, DEFAULT_GSM_SELL_FEE);
GHO_GSM_LAST_RESORT_LIQUIDATOR = new SampleLiquidator();
GHO_GSM_SWAP_FREEZER = new SampleSwapFreezer();
Gsm gsm = new Gsm(
Expand Down Expand Up @@ -357,7 +358,8 @@ contract TestGhoBase is Test, Constants, Events {
);

// Deploy Gho GSM Steward
GHO_GSM_STEWARD = new GhoGsmSteward(RISK_COUNCIL);
GSM_FEE_STRATEGY_FACTORY = new GsmFeeStrategyFactory();
GHO_GSM_STEWARD = new GhoGsmSteward(address(GSM_FEE_STRATEGY_FACTORY), RISK_COUNCIL);

// Deploy Gho Bucket Capacity Steward
GHO_BUCKET_CAPACITY_STEWARD = new GhoBucketCapacitySteward(
Expand Down
8 changes: 7 additions & 1 deletion src/test/TestGhoGsmSteward.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,21 @@ contract TestGhoGsmSteward is TestGhoBase {
assertEq(GHO_GSM_STEWARD.GSM_FEE_RATE_CHANGE_MAX(), GSM_FEE_RATE_CHANGE_MAX);
assertEq(GHO_GSM_STEWARD.MINIMUM_DELAY(), MINIMUM_DELAY_V2);

assertEq(GHO_GSM_STEWARD.GSM_FEE_STRATEGY_FACTORY(), address(GSM_FEE_STRATEGY_FACTORY));
assertEq(GHO_GSM_STEWARD.RISK_COUNCIL(), RISK_COUNCIL);

address[] memory gsmFeeStrategies = GHO_GSM_STEWARD.getGsmFeeStrategies();
assertEq(gsmFeeStrategies.length, 0);
}

function testRevertConstructorInvalidGsmFeeStrategyFactory() public {
vm.expectRevert('INVALID_GSM_FEE_STRATEGY_FACTORY');
new GhoGsmSteward(address(0), address(0x002));
}

function testRevertConstructorInvalidRiskCouncil() public {
vm.expectRevert('INVALID_RISK_COUNCIL');
new GhoGsmSteward(address(0));
new GhoGsmSteward(address(0x001), address(0));
}

function testUpdateGsmExposureCapUpwards() public {
Expand Down

0 comments on commit 25edbf7

Please sign in to comment.