Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import {AutomationCompatibleInterface} from 'src/contracts/dependencies/chainlink/AutomationCompatibleInterface.sol';
import {IPoolAddressesProvider} from 'aave-v3-origin/contracts/interfaces/IPoolAddressesProvider.sol';
import {IGsm} from 'src/contracts/facilitators/gsm/interfaces/IGsm.sol';
import {OracleSwapFreezerBase} from 'src/contracts/facilitators/gsm/swapFreezer/OracleSwapFreezerBase.sol';

/**
* @title ChainlinkOracleSwapFreezer
* @notice Chainlink-compatible automated swap freezer for GSM.
*/
contract ChainlinkOracleSwapFreezer is OracleSwapFreezerBase, AutomationCompatibleInterface {
/**
* @dev Constructor
* @dev Freeze/unfreeze bounds are specified in USD with 8-decimal precision, like Aave v3 Price Oracles
* @dev Unfreeze boundaries are "contained" in freeze boundaries, where freezeLowerBound < unfreezeLowerBound and unfreezeUpperBound < freezeUpperBound
* @dev All bound ranges are inclusive
* @param gsm The GSM that this contract will trigger freezes/unfreezes on
* @param underlyingAsset The address of the collateral asset
* @param addressesProvider The Aave Addresses Provider for looking up the Price Oracle
* @param freezeLowerBound The lower price bound for freeze operations
* @param freezeUpperBound The upper price bound for freeze operations
* @param unfreezeLowerBound The lower price bound for unfreeze operations, must be 0 if unfreezing not allowed
* @param unfreezeUpperBound The upper price bound for unfreeze operations, must be 0 if unfreezing not allowed
* @param allowUnfreeze True if bounds verification should factor in the unfreeze boundary, false otherwise
*/
constructor(
IGsm gsm,
address underlyingAsset,
IPoolAddressesProvider addressesProvider,
uint128 freezeLowerBound,
uint128 freezeUpperBound,
uint128 unfreezeLowerBound,
uint128 unfreezeUpperBound,
bool allowUnfreeze
)
OracleSwapFreezerBase(
gsm,
underlyingAsset,
addressesProvider,
freezeLowerBound,
freezeUpperBound,
unfreezeLowerBound,
unfreezeUpperBound,
allowUnfreeze
)
{}

/// @inheritdoc AutomationCompatibleInterface
function performUpkeep(bytes calldata) external {
_execute();
}

/// @inheritdoc AutomationCompatibleInterface
function checkUpkeep(bytes calldata) external view returns (bool, bytes memory) {
return (_checkExecute(), '');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import {IGelatoOracleSwapFreezer} from 'src/contracts/facilitators/gsm/swapFreezer/interfaces/IGelatoOracleSwapFreezer.sol';
import {IPoolAddressesProvider} from 'aave-v3-origin/contracts/interfaces/IPoolAddressesProvider.sol';
import {IGsm} from 'src/contracts/facilitators/gsm/interfaces/IGsm.sol';
import {OracleSwapFreezerBase} from 'src/contracts/facilitators/gsm/swapFreezer/OracleSwapFreezerBase.sol';

/**
* @title GelatoOracleSwapFreezer
* @notice Gelato-compatible automated swap freezer for GSM.
*/
contract GelatoOracleSwapFreezer is OracleSwapFreezerBase, IGelatoOracleSwapFreezer {
/**
* @dev Constructor
* @dev Freeze/unfreeze bounds are specified in USD with 8-decimal precision, like Aave v3 Price Oracles
* @dev Unfreeze boundaries are "contained" in freeze boundaries, where freezeLowerBound < unfreezeLowerBound and unfreezeUpperBound < freezeUpperBound
* @dev All bound ranges are inclusive
* @param gsm The GSM that this contract will trigger freezes/unfreezes on
* @param underlyingAsset The address of the collateral asset
* @param addressesProvider The Aave Addresses Provider for looking up the Price Oracle
* @param freezeLowerBound The lower price bound for freeze operations
* @param freezeUpperBound The upper price bound for freeze operations
* @param unfreezeLowerBound The lower price bound for unfreeze operations, must be 0 if unfreezing not allowed
* @param unfreezeUpperBound The upper price bound for unfreeze operations, must be 0 if unfreezing not allowed
* @param allowUnfreeze True if bounds verification should factor in the unfreeze boundary, false otherwise
*/
constructor(
IGsm gsm,
address underlyingAsset,
IPoolAddressesProvider addressesProvider,
uint128 freezeLowerBound,
uint128 freezeUpperBound,
uint128 unfreezeLowerBound,
uint128 unfreezeUpperBound,
bool allowUnfreeze
)
OracleSwapFreezerBase(
gsm,
underlyingAsset,
addressesProvider,
freezeLowerBound,
freezeUpperBound,
unfreezeLowerBound,
unfreezeUpperBound,
allowUnfreeze
)
{}

/**
* @dev Performs the swap freeze action depending on the oracle value
*/
function performUpkeep(bytes calldata) external {
_execute();
}

/**
* @notice Returns whether the action can be performed and the encoded call data for execution.
* @return True if the action can be performed, false otherwise.
* @return The encoded call data for the action to be executed.
*/
function checkUpkeep(bytes calldata) external view returns (bool, bytes memory) {
return (_getAction() == Action.NONE ? false : true, abi.encodeCall(this.performUpkeep, ('')));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@ pragma solidity ^0.8.10;

import {IPoolAddressesProvider} from 'aave-v3-origin/contracts/interfaces/IPoolAddressesProvider.sol';
import {IPriceOracle} from 'aave-v3-origin/contracts/interfaces/IPriceOracle.sol';
import {AutomationCompatibleInterface} from 'src/contracts/dependencies/chainlink/AutomationCompatibleInterface.sol';
import {IGsm} from 'src/contracts/facilitators/gsm/interfaces/IGsm.sol';

/**
* @title OracleSwapFreezer
* @title OracleSwapFreezerBase
* @author Aave
* @notice Swap freezer that enacts the freeze action based on underlying oracle price, GSM's state and predefined price boundaries
* @dev Chainlink Automation-compatible contract using Aave V3 Price Oracle, where prices are USD denominated with 8-decimal precision
* @dev It uses Aave V3 Price Oracle, where prices are USD denominated with 8-decimal precision
* @dev Freeze action is executable if GSM is not seized, not frozen and price is outside of the freeze bounds
* @dev Unfreeze action is executable if GSM is not seized, frozen, unfreezing is allowed and price is inside the unfreeze bounds
*/
contract OracleSwapFreezer is AutomationCompatibleInterface {
abstract contract OracleSwapFreezerBase {
enum Action {
NONE,
FREEZE,
Expand All @@ -37,7 +36,7 @@ contract OracleSwapFreezer is AutomationCompatibleInterface {
* @dev All bound ranges are inclusive
* @param gsm The GSM that this contract will trigger freezes/unfreezes on
* @param underlyingAsset The address of the collateral asset
* @param addressProvider The Aave Addresses Provider for looking up the Price Oracle
* @param addressesProvider The Aave Addresses Provider for looking up the Price Oracle
* @param freezeLowerBound The lower price bound for freeze operations
* @param freezeUpperBound The upper price bound for freeze operations
* @param unfreezeLowerBound The lower price bound for unfreeze operations, must be 0 if unfreezing not allowed
Expand All @@ -47,7 +46,7 @@ contract OracleSwapFreezer is AutomationCompatibleInterface {
constructor(
IGsm gsm,
address underlyingAsset,
IPoolAddressesProvider addressProvider,
IPoolAddressesProvider addressesProvider,
uint128 freezeLowerBound,
uint128 freezeUpperBound,
uint128 unfreezeLowerBound,
Expand All @@ -67,16 +66,18 @@ contract OracleSwapFreezer is AutomationCompatibleInterface {
);
GSM = gsm;
UNDERLYING_ASSET = underlyingAsset;
ADDRESS_PROVIDER = addressProvider;
ADDRESS_PROVIDER = addressesProvider;
_freezeLowerBound = freezeLowerBound;
_freezeUpperBound = freezeUpperBound;
_unfreezeLowerBound = unfreezeLowerBound;
_unfreezeUpperBound = unfreezeUpperBound;
_allowUnfreeze = allowUnfreeze;
}

/// @inheritdoc AutomationCompatibleInterface
function performUpkeep(bytes calldata) external {
/**
* @dev Performs the swap freeze action depending on the oracle value
*/
function _execute() internal {
Action action = _getAction();
if (action == Action.FREEZE) {
GSM.setSwapFreeze(true);
Expand All @@ -85,9 +86,12 @@ contract OracleSwapFreezer is AutomationCompatibleInterface {
}
}

/// @inheritdoc AutomationCompatibleInterface
function checkUpkeep(bytes calldata) external view returns (bool, bytes memory) {
return (_getAction() == Action.NONE ? false : true, '');
/**
* @dev Returns whether the action can be performed
* @return True if the action can be performed, false otherwise.
*/
function _checkExecute() internal view returns (bool) {
return _getAction() == Action.NONE ? false : true;
}

/**
Expand Down
53 changes: 53 additions & 0 deletions tests/unit/TestChainlinkOracleSwapFreezer.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './TestOracleSwapFreezerBase.t.sol';

contract TestChainlinkOracleSwapFreezer is TestOracleSwapFreezerBase {
function _deployOracleSwapFreezer(
IGsm gsm,
address underlyingAsset,
IPoolAddressesProvider addressesProvider,
uint128 freezeLowerBound,
uint128 freezeUpperBound,
uint128 unfreezeLowerBound,
uint128 unfreezeUpperBound,
bool allowUnfreeze
) internal override returns (address) {
return
address(
new ChainlinkOracleSwapFreezer(
gsm,
underlyingAsset,
addressesProvider,
freezeLowerBound,
freezeUpperBound,
unfreezeLowerBound,
unfreezeUpperBound,
allowUnfreeze
)
);
}

function _checkAutomation(address swapFreezer) internal view override returns (bool) {
bytes memory returnData = Address.functionStaticCall(
swapFreezer,
abi.encodeWithSelector(AutomationCompatibleInterface.checkUpkeep.selector, '')
);
(bool shouldRunKeeper, ) = abi.decode(returnData, (bool, bytes));
return shouldRunKeeper;
}

function _checkAndPerformAutomation(address swapFreezer) internal override returns (bool) {
bytes memory returnData = Address.functionStaticCall(
swapFreezer,
abi.encodeWithSelector(AutomationCompatibleInterface.checkUpkeep.selector, '')
);
(bool shouldRunKeeper, bytes memory performData) = abi.decode(returnData, (bool, bytes));

if (shouldRunKeeper) {
AutomationCompatibleInterface(swapFreezer).performUpkeep(performData);
}
return shouldRunKeeper;
}
}
58 changes: 58 additions & 0 deletions tests/unit/TestGelatoOracleSwapFreezer.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './TestOracleSwapFreezerBase.t.sol';

contract TestGelatoOracleSwapFreezer is TestOracleSwapFreezerBase {
using Address for address;

function testCheckUpkeepReturnsCorrectSelector() public view {
(, bytes memory data) = IGelatoOracleSwapFreezer(swapFreezer).checkUpkeep('');
bytes4 selector;
assembly {
selector := mload(add(data, 32))
}
assertEq(selector, IGelatoOracleSwapFreezer.performUpkeep.selector);
}

function _deployOracleSwapFreezer(
IGsm gsm,
address underlyingAsset,
IPoolAddressesProvider addressesProvider,
uint128 freezeLowerBound,
uint128 freezeUpperBound,
uint128 unfreezeLowerBound,
uint128 unfreezeUpperBound,
bool allowUnfreeze
) internal override returns (address) {
return
address(
new GelatoOracleSwapFreezer(
gsm,
underlyingAsset,
addressesProvider,
freezeLowerBound,
freezeUpperBound,
unfreezeLowerBound,
unfreezeUpperBound,
allowUnfreeze
)
);
}

function _checkAutomation(address swapFreezer) internal view override returns (bool) {
(bool shouldRunKeeper, ) = IGelatoOracleSwapFreezer(swapFreezer).checkUpkeep('');
return shouldRunKeeper;
}

function _checkAndPerformAutomation(
address swapFreezer
) internal virtual override returns (bool) {
(bool shouldRunKeeper, bytes memory encodedPerformCall) = IGelatoOracleSwapFreezer(swapFreezer)
.checkUpkeep('');
if (shouldRunKeeper) {
swapFreezer.functionCall(encodedPerformCall);
}
return shouldRunKeeper;
}
}
8 changes: 8 additions & 0 deletions tests/unit/TestGhoBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import 'forge-std/Test.sol';
import 'forge-std/console2.sol';
import {Vm} from 'forge-std/Vm.sol';

// dependencies
import {Address} from 'openzeppelin-contracts/contracts/utils/Address.sol';
import {AutomationCompatibleInterface} from 'src/contracts/dependencies/chainlink/AutomationCompatibleInterface.sol';

// helpers
import {Constants} from '../helpers/Constants.sol';
import {DebtUtils} from '../helpers/DebtUtils.sol';
Expand Down Expand Up @@ -83,6 +87,10 @@ import {GhoGsmSteward} from 'src/contracts/misc/GhoGsmSteward.sol';
import {FixedFeeStrategyFactory} from 'src/contracts/facilitators/gsm/feeStrategy/FixedFeeStrategyFactory.sol';
import {GhoReserve} from 'src/contracts/facilitators/gsm/GhoReserve.sol';
import {OwnableFacilitator} from 'src/contracts/facilitators/gsm/OwnableFacilitator.sol';
import {OracleSwapFreezerBase} from 'src/contracts/facilitators/gsm/swapFreezer/OracleSwapFreezerBase.sol';
import {ChainlinkOracleSwapFreezer} from 'src/contracts/facilitators/gsm/swapFreezer/ChainlinkOracleSwapFreezer.sol';
import {GelatoOracleSwapFreezer} from 'src/contracts/facilitators/gsm/swapFreezer/GelatoOracleSwapFreezer.sol';
import {IGelatoOracleSwapFreezer} from 'src/contracts/facilitators/gsm/swapFreezer/interfaces/IGelatoOracleSwapFreezer.sol';

// CCIP contracts
import {MockUpgradeableLockReleaseTokenPool} from '../mocks/MockUpgradeableLockReleaseTokenPool.sol';
Expand Down
Loading
Loading