diff --git a/contracts/mainnet/connectors/gelato/aave-services/protection/events.sol b/contracts/mainnet/connectors/gelato/aave-services/protection/events.sol new file mode 100644 index 00000000..3cf7e9f7 --- /dev/null +++ b/contracts/mainnet/connectors/gelato/aave-services/protection/events.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.6; + +contract Events { + event LogSubmitProtection( + address indexed dsa, + address indexed action, + uint256 wantedHealthFactor, + uint256 minimumHealthFactor, + bool isPermanent + ); + event LogUpdateProtection( + address indexed dsa, + address indexed action, + uint256 wantedHealthFactor, + uint256 minimumHealthFactor, + bool isPermanent + ); + event LogCancelProtection(address indexed dsa, address indexed action); + event LogCancelAndRevoke(address indexed dsa, address indexed action); +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/gelato/aave-services/protection/helpers.sol b/contracts/mainnet/connectors/gelato/aave-services/protection/helpers.sol new file mode 100644 index 00000000..b795486a --- /dev/null +++ b/contracts/mainnet/connectors/gelato/aave-services/protection/helpers.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.6; +pragma abicoder v2; + +import { + LendingPoolInterface, + AaveServicesInterface, + IERC20 +} from "./interface.sol"; + +abstract contract Helpers { + // solhint-disable-next-line const-name-snakecase + LendingPoolInterface internal constant _lendingPool = + LendingPoolInterface(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9); + + // solhint-disable-next-line const-name-snakecase + AaveServicesInterface internal constant _aaveServices = + AaveServicesInterface(0xE3d373c78803C1d22cE96bdC43d47542835bBF42); + + // solhint-disable-next-line const-name-snakecase + address internal constant _protectionAction = + 0xD2579361F3C402938841774ECc1acdd51d3a4345; + + function _submitProtection( + uint256 _wantedHealthFactor, + uint256 _minimumHealthFactor, + bool _isPermanent + ) internal { + _giveAllowance(); + + _aaveServices.submitTask( + _protectionAction, + abi.encode( + _wantedHealthFactor, + _minimumHealthFactor, + address(this) + ), + _isPermanent + ); + } + + function _updateProtection( + uint256 _wantedHealthFactor, + uint256 _minimumHealthFactor, + bool _isPermanent + ) internal { + _giveAllowance(); + + _aaveServices.updateTask( + _protectionAction, + abi.encode( + _wantedHealthFactor, + _minimumHealthFactor, + address(this) + ), + _isPermanent + ); + } + + function _cancelProtection() internal { + _aaveServices.cancelTask(_protectionAction); + } + + function _giveAllowance() internal { + address[] memory aTokenList = _getATokenList(); + for (uint256 i = 0; i < aTokenList.length; i++) { + if ( + !(IERC20(aTokenList[i]).allowance( + address(this), + _protectionAction + ) == type(uint256).max) + ) { + IERC20(aTokenList[i]).approve( + _protectionAction, + type(uint256).max + ); + } + } + } + + function _revokeAllowance() internal { + address[] memory aTokenList = _getATokenList(); + for (uint256 i = 0; i < aTokenList.length; i++) { + if ( + !(IERC20(aTokenList[i]).allowance( + address(this), + _protectionAction + ) == 0) + ) { + IERC20(aTokenList[i]).approve(_protectionAction, 0); + } + } + } + + function _getATokenList() + internal + view + returns (address[] memory aTokenList) + { + address[] memory underlyingsList = _lendingPool.getReservesList(); + aTokenList = new address[](underlyingsList.length); + for (uint256 i = 0; i < underlyingsList.length; i++) { + aTokenList[i] = (_lendingPool.getReserveData(underlyingsList[i])) + .aTokenAddress; + } + } + + function _dsaHasProtection() internal view returns (bool) { + return + _aaveServices.taskByUsersAction(address(this), _protectionAction) != + bytes32(0); + } +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/gelato/aave-services/protection/interface.sol b/contracts/mainnet/connectors/gelato/aave-services/protection/interface.sol new file mode 100644 index 00000000..912da744 --- /dev/null +++ b/contracts/mainnet/connectors/gelato/aave-services/protection/interface.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.6; +pragma abicoder v2; + +struct ReserveData { + //stores the reserve configuration + ReserveConfigurationMap configuration; + //the liquidity index. Expressed in ray + uint128 liquidityIndex; + //variable borrow index. Expressed in ray + uint128 variableBorrowIndex; + //the current supply rate. Expressed in ray + uint128 currentLiquidityRate; + //the current variable borrow rate. Expressed in ray + uint128 currentVariableBorrowRate; + //the current stable borrow rate. Expressed in ray + uint128 currentStableBorrowRate; + uint40 lastUpdateTimestamp; + //tokens addresses + address aTokenAddress; + address stableDebtTokenAddress; + address variableDebtTokenAddress; + //address of the interest rate strategy + address interestRateStrategyAddress; + //the id of the reserve. Represents the position in the list of the active reserves + uint8 id; +} + +struct ReserveConfigurationMap { + //bit 0-15: LTV + //bit 16-31: Liq. threshold + //bit 32-47: Liq. bonus + //bit 48-55: Decimals + //bit 56: Reserve is active + //bit 57: reserve is frozen + //bit 58: borrowing is enabled + //bit 59: stable rate borrowing enabled + //bit 60-63: reserved + //bit 64-79: reserve factor + uint256 data; +} + +interface LendingPoolInterface { + function getReservesList() external view returns (address[] memory); + + function getReserveData(address asset) + external + view + returns (ReserveData memory); +} + +interface AaveServicesInterface { + function submitTask( + address _action, + bytes memory _taskData, + bool _isPermanent + ) external; + + function cancelTask(address _action) external; + + function updateTask( + address _action, + bytes memory _data, + bool _isPermanent + ) external; + + function taskByUsersAction(address _user, address _action) + external + view + returns (bytes32); +} + +interface IERC20 { + function approve(address spender, uint256 amount) external returns (bool); + + function allowance(address owner, address spender) + external + view + returns (uint256); +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/gelato/aave-services/protection/main.sol b/contracts/mainnet/connectors/gelato/aave-services/protection/main.sol new file mode 100644 index 00000000..79194c4a --- /dev/null +++ b/contracts/mainnet/connectors/gelato/aave-services/protection/main.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.6; +pragma abicoder v2; + +/** + * @title Aave Protection. + * @dev Protect DSA against Liquidation risk on Aave with Gelato. + */ + +import {Events} from "./events.sol"; +import {Helpers} from "./helpers.sol"; + +abstract contract GAaveProtectionResolver is Events, Helpers { + /// @dev Function for submitting a protection task + /// @param _wantedHealthFactor targeted health after protection. + /// @param _minimumHealthFactor trigger protection when current health + /// factor is below _minimumHealthFactor. + /// @param _isPermanent boolean to set a protection as permanent + function submitProtection( + uint256 _wantedHealthFactor, + uint256 _minimumHealthFactor, + bool _isPermanent + ) external payable { + _submitProtection( + _wantedHealthFactor, + _minimumHealthFactor, + _isPermanent + ); + emit LogSubmitProtection( + address(this), + _protectionAction, + _wantedHealthFactor, + _minimumHealthFactor, + _isPermanent + ); + } + + /// @dev Function for modifying a protection task + /// @param _wantedHealthFactor targeted health after protection. + /// @param _minimumHealthFactor trigger protection when current health + /// factor is below _minimumHealthFactor. + /// @param _isPermanent boolean to set a protection as permanent + function updateProtection( + uint256 _wantedHealthFactor, + uint256 _minimumHealthFactor, + bool _isPermanent + ) external payable { + _updateProtection( + _wantedHealthFactor, + _minimumHealthFactor, + _isPermanent + ); + emit LogUpdateProtection( + address(this), + _protectionAction, + _wantedHealthFactor, + _minimumHealthFactor, + _isPermanent + ); + } + + /// @dev Function for cancelling a protection task + function cancelProtection() external payable { + _cancelProtection(); + emit LogCancelProtection(address(this), _protectionAction); + } + + /// @dev Function for cancelling and removing allowance + /// of aToken to _protectionAction + function cancelAndRevoke() external payable { + if (_dsaHasProtection()) _cancelProtection(); + _revokeAllowance(); + emit LogCancelAndRevoke(address(this), _protectionAction); + } +} + +contract GAaveProtectionMainnetConnector is GAaveProtectionResolver { + // solhint-disable-next-line const-name-snakecase + string public constant name = "GAaveProtectionMainnetConnector-v1"; +} \ No newline at end of file diff --git a/contracts/polygon/connectors/gelato/aave-services/protection/events.sol b/contracts/polygon/connectors/gelato/aave-services/protection/events.sol new file mode 100644 index 00000000..813d521c --- /dev/null +++ b/contracts/polygon/connectors/gelato/aave-services/protection/events.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.6; + +contract Events { + event LogSubmitProtection( + address indexed dsa, + address indexed action, + uint256 wantedHealthFactor, + uint256 minimumHealthFactor, + bool isPermanent + ); + event LogUpdateProtection( + address indexed dsa, + address indexed action, + uint256 wantedHealthFactor, + uint256 minimumHealthFactor, + bool isPermanent + ); + event LogCancelProtection(address indexed dsa, address indexed action); + event LogCancelAndRevoke(address indexed dsa, address indexed action); +} diff --git a/contracts/polygon/connectors/gelato/aave-services/protection/helpers.sol b/contracts/polygon/connectors/gelato/aave-services/protection/helpers.sol new file mode 100644 index 00000000..eb41d5cb --- /dev/null +++ b/contracts/polygon/connectors/gelato/aave-services/protection/helpers.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.6; +pragma abicoder v2; + +import { + LendingPoolInterface, + AaveServicesInterface, + IERC20 +} from "./interface.sol"; + +abstract contract Helpers { + // solhint-disable-next-line const-name-snakecase + LendingPoolInterface internal constant _lendingPool = + LendingPoolInterface(0x8dFf5E27EA6b7AC08EbFdf9eB090F32ee9a30fcf); + + // solhint-disable-next-line const-name-snakecase + AaveServicesInterface internal constant _aaveServices = + AaveServicesInterface(0x18FAbC997fDd624764E1974b283B1b904b66d613); + + // solhint-disable-next-line const-name-snakecase + address internal constant _protectionAction = + 0xc38b6dbd0F84777AA4fae2d36FE1506428A22b9B; + + function _cancelProtection() internal { + _aaveServices.cancelTask(_protectionAction); + } + + function _submitProtection( + uint256 _wantedHealthFactor, + uint256 _minimumHealthFactor, + bool _isPermanent + ) internal { + _giveAllowance(); + + _aaveServices.submitTask( + _protectionAction, + abi.encode( + _wantedHealthFactor, + _minimumHealthFactor, + address(this) + ), + _isPermanent + ); + } + + function _updateProtection( + uint256 _wantedHealthFactor, + uint256 _minimumHealthFactor, + bool _isPermanent + ) internal { + _giveAllowance(); + + _aaveServices.updateTask( + _protectionAction, + abi.encode( + _wantedHealthFactor, + _minimumHealthFactor, + address(this) + ), + _isPermanent + ); + } + + function _giveAllowance() internal { + address[] memory aTokenList = _getATokenList(); + for (uint256 i = 0; i < aTokenList.length; i++) { + if ( + !(IERC20(aTokenList[i]).allowance( + address(this), + _protectionAction + ) == type(uint256).max) + ) { + IERC20(aTokenList[i]).approve( + _protectionAction, + type(uint256).max + ); + } + } + } + + function _revokeAllowance() internal { + address[] memory aTokenList = _getATokenList(); + for (uint256 i = 0; i < aTokenList.length; i++) { + if ( + !(IERC20(aTokenList[i]).allowance( + address(this), + _protectionAction + ) == 0) + ) { + IERC20(aTokenList[i]).approve(_protectionAction, 0); + } + } + } + + function _getATokenList() + internal + view + returns (address[] memory aTokenList) + { + address[] memory underlyingsList = _lendingPool.getReservesList(); + aTokenList = new address[](underlyingsList.length); + for (uint256 i = 0; i < underlyingsList.length; i++) { + aTokenList[i] = (_lendingPool.getReserveData(underlyingsList[i])) + .aTokenAddress; + } + } + + function _dsaHasProtection() internal view returns (bool) { + return + _aaveServices.taskByUsersAction(address(this), _protectionAction) != + bytes32(0); + } +} diff --git a/contracts/polygon/connectors/gelato/aave-services/protection/interface.sol b/contracts/polygon/connectors/gelato/aave-services/protection/interface.sol new file mode 100644 index 00000000..097afb22 --- /dev/null +++ b/contracts/polygon/connectors/gelato/aave-services/protection/interface.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.6; +pragma abicoder v2; + +struct ReserveData { + //stores the reserve configuration + ReserveConfigurationMap configuration; + //the liquidity index. Expressed in ray + uint128 liquidityIndex; + //variable borrow index. Expressed in ray + uint128 variableBorrowIndex; + //the current supply rate. Expressed in ray + uint128 currentLiquidityRate; + //the current variable borrow rate. Expressed in ray + uint128 currentVariableBorrowRate; + //the current stable borrow rate. Expressed in ray + uint128 currentStableBorrowRate; + uint40 lastUpdateTimestamp; + //tokens addresses + address aTokenAddress; + address stableDebtTokenAddress; + address variableDebtTokenAddress; + //address of the interest rate strategy + address interestRateStrategyAddress; + //the id of the reserve. Represents the position in the list of the active reserves + uint8 id; +} + +struct ReserveConfigurationMap { + //bit 0-15: LTV + //bit 16-31: Liq. threshold + //bit 32-47: Liq. bonus + //bit 48-55: Decimals + //bit 56: Reserve is active + //bit 57: reserve is frozen + //bit 58: borrowing is enabled + //bit 59: stable rate borrowing enabled + //bit 60-63: reserved + //bit 64-79: reserve factor + uint256 data; +} + +interface LendingPoolInterface { + function getReservesList() external view returns (address[] memory); + + function getReserveData(address asset) + external + view + returns (ReserveData memory); +} + +interface AaveServicesInterface { + function submitTask( + address _action, + bytes memory _taskData, + bool _isPermanent + ) external; + + function cancelTask(address _action) external; + + function updateTask( + address _action, + bytes memory _data, + bool _isPermanent + ) external; + + function taskByUsersAction(address _user, address _action) + external + view + returns (bytes32); +} + +interface IERC20 { + function approve(address spender, uint256 amount) external returns (bool); + + function allowance(address owner, address spender) + external + view + returns (uint256); +} diff --git a/contracts/polygon/connectors/gelato/aave-services/protection/main.sol b/contracts/polygon/connectors/gelato/aave-services/protection/main.sol new file mode 100644 index 00000000..b948af67 --- /dev/null +++ b/contracts/polygon/connectors/gelato/aave-services/protection/main.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.6; + +/** + * @title Aave Protection. + * @dev Protect DSA against Liquidation risk on Aave with Gelato. + */ + +import {Events} from "./events.sol"; +import {Helpers} from "./helpers.sol"; + +abstract contract GAaveProtectionResolver is Events, Helpers { + /// @dev Function for submitting a protection task + /// @param _wantedHealthFactor targeted health after protection. + /// @param _minimumHealthFactor trigger protection when current health + /// factor is below _minimumHealthFactor. + /// @param _isPermanent boolean to set a protection as permanent + function submitProtection( + uint256 _wantedHealthFactor, + uint256 _minimumHealthFactor, + bool _isPermanent + ) external payable { + _submitProtection( + _wantedHealthFactor, + _minimumHealthFactor, + _isPermanent + ); + emit LogSubmitProtection( + address(this), + _protectionAction, + _wantedHealthFactor, + _minimumHealthFactor, + _isPermanent + ); + } + + /// @dev Function for modifying a protection task + /// @param _wantedHealthFactor targeted health after protection. + /// @param _minimumHealthFactor trigger protection when current health + /// factor is below _minimumHealthFactor. + /// @param _isPermanent boolean to set a protection as permanent + function updateProtection( + uint256 _wantedHealthFactor, + uint256 _minimumHealthFactor, + bool _isPermanent + ) external payable { + _updateProtection( + _wantedHealthFactor, + _minimumHealthFactor, + _isPermanent + ); + emit LogUpdateProtection( + address(this), + _protectionAction, + _wantedHealthFactor, + _minimumHealthFactor, + _isPermanent + ); + } + + /// @dev Function for cancelling a protection task + function cancelProtection() external payable { + _cancelProtection(); + emit LogCancelProtection(address(this), _protectionAction); + } + + /// @dev Function for cancelling and removing allowance + /// of aToken to _protectionAction + function cancelAndRevoke() external payable { + if (_dsaHasProtection()) _cancelProtection(); + _revokeAllowance(); + emit LogCancelAndRevoke(address(this), _protectionAction); + } +} + +contract GAaveProtectionPolygonConnector is GAaveProtectionResolver { + // solhint-disable-next-line const-name-snakecase + string public constant name = "GelatoAaveProtectionPolygonConnector-v1"; +} diff --git a/docs/connectors.json b/docs/connectors.json index 4cdfad3a..d7fb01cb 100644 --- a/docs/connectors.json +++ b/docs/connectors.json @@ -25,6 +25,7 @@ "REFINANCE-A": "0x6f22931423e8ffC8d51f6E5aF73118fC64b27856", "INST-A": "0x52C2C4a0db049255fF345EB9D3Fb1f555b7a924A", "REFLEXER-A": "0xaC6dc28a6251F49Bbe5755E630107Dccde9ae2C8", + "GELATO-AAVE-A: "0x825832a5A589ed9500CaEE2aa2D2c3117218D6A9" "LIQUITY-A": "0x3643bA40B8e2bd8F77233BDB6abe38c218f31bFe", "UNISWAP-V3-A": "0x25B0c76dE86C3457b9B8b9ee3775F5a7b8D4c475", "B-COMPOUND-A": "0xa3EeFDc2de9DFA59968bEcff3E15b53E6162460f", @@ -37,6 +38,8 @@ "AUTHORITY-A": "0xf73C94402BC24148b744083eD02654EEc2C37D5B", "BASIC-A": "0x1cAF5EC802ca602E98139AD96A8f2B7BC524264E", "AAVE-CLAIM-A": "0xC7Cb1dE2721BFC0E0DA1b9D526bCdC54eF1C0eFC", + "PARASWAP-A": "0xFb3a1D56eD56F046721B9aCa749895100754578b", + "GELATO-AAAVE-A": "0x177Bd89A1D8643C9525D2DF131C1a6C513869299" "PARASWAP-A": "0xFb3a1D56eD56F046721B9aCa749895100754578b" }, "arbitrum" : {