From 3542b882022cf5d3884e29b5274bc9efb692b60b Mon Sep 17 00:00:00 2001 From: The3D Date: Fri, 23 Apr 2021 20:30:07 +0200 Subject: [PATCH] feat: added permissioned market implementation to the public repo --- contracts/interfaces/IPermissionManager.sol | 55 +++ .../configuration/PermissionManager.sol | 136 ++++++ .../protocol/lendingpool/LendingPool.sol | 23 +- .../lendingpool/PermissionedLendingPool.sol | 272 +++++++++++ .../protocol/libraries/helpers/Errors.sol | 5 + .../protocol/libraries/types/DataTypes.sol | 2 + .../PermissionedStableDebtToken.sol | 435 ++++++++++++++++++ .../PermissionedVariableDebtToken.sol | 209 +++++++++ .../tokenization/base/DebtTokenBase.sol | 2 +- .../base/PermissionedDebtTokenBase.sol | 47 ++ helpers/configuration.ts | 5 +- helpers/contracts-deployments.ts | 59 ++- helpers/init-helpers.ts | 80 ++-- helpers/types.ts | 13 + markets/aave-pro/commons.ts | 298 ++++++++++++ markets/aave-pro/index.ts | 56 +++ markets/aave-pro/rateStrategies.ts | 38 ++ markets/aave-pro/reservesConfigs.ts | 59 +++ package.json | 1 + tasks/migrations/pro.mainnet.ts | 60 +++ .../test-aave/permission-manager.spec.ts | 168 +++++++ 21 files changed, 1979 insertions(+), 44 deletions(-) create mode 100644 contracts/interfaces/IPermissionManager.sol create mode 100644 contracts/protocol/configuration/PermissionManager.sol create mode 100644 contracts/protocol/lendingpool/PermissionedLendingPool.sol create mode 100644 contracts/protocol/tokenization/PermissionedStableDebtToken.sol create mode 100644 contracts/protocol/tokenization/PermissionedVariableDebtToken.sol create mode 100644 contracts/protocol/tokenization/base/PermissionedDebtTokenBase.sol create mode 100644 markets/aave-pro/commons.ts create mode 100644 markets/aave-pro/index.ts create mode 100644 markets/aave-pro/rateStrategies.ts create mode 100644 markets/aave-pro/reservesConfigs.ts create mode 100644 tasks/migrations/pro.mainnet.ts create mode 100644 test-suites/test-aave/permission-manager.spec.ts diff --git a/contracts/interfaces/IPermissionManager.sol b/contracts/interfaces/IPermissionManager.sol new file mode 100644 index 00000000..646e0f6e --- /dev/null +++ b/contracts/interfaces/IPermissionManager.sol @@ -0,0 +1,55 @@ +pragma solidity 0.6.12; + +interface IPermissionManager { + + event RoleSet(address indexed user, uint256 indexed role, bool set); + event PermissionsAdminSet(address indexed user, bool set); + + /** + * @dev Allows owner to add new permission admins + * @param users The addresses of the users to promote to permission admin + **/ + function addPermissionAdmins(address[] calldata users) external; + + /** + * @dev Allows owner to remove permission admins + * @param users The addresses of the users to demote as permission admin + **/ + function removePermissionAdmins(address[] calldata users) external; + + /** + * @dev Allows owner to whitelist a set of addresses for multiple roles + * @param roles The list of roles to assign + * @param users The list of users to add to the corresponding role + **/ + function addPermissions(uint256[] calldata roles, address[] calldata users) external; + + /** + * @dev Allows owner to remove permissions on a set of addresses + * @param roles The list of roles to remove + * @param users The list of users to remove from the corresponding role + **/ + function removePermissions(uint256[] calldata roles, address[] calldata users) external; + + /** + * @dev Returns the permissions configuration for a specific account + * @param account The address of the user + * @return the set of permissions states for the account + **/ + function getAccountPermissions(address account) external view returns (uint256[] memory, uint256); + + /** + * @dev Used to query if a certain account has a certain role + * @param account The address of the user + * @return True if the account is in the specific role + **/ + function isInRole(address account, uint256 role) external view returns (bool); + + /** + * @dev Used to query if a certain account has the permissions admin role + * @param account The address of the user + * @return True if the account is a permissions admin, false otherwise + **/ + function isPermissionsAdmin(address account) external view returns (bool); + +} diff --git a/contracts/protocol/configuration/PermissionManager.sol b/contracts/protocol/configuration/PermissionManager.sol new file mode 100644 index 00000000..866d882b --- /dev/null +++ b/contracts/protocol/configuration/PermissionManager.sol @@ -0,0 +1,136 @@ +pragma solidity 0.6.12; + +import {IPermissionManager} from '../../interfaces/IPermissionManager.sol'; +import {Ownable} from '../../dependencies/openzeppelin/contracts/Ownable.sol'; + +/** + * @title PermissionManager contract + * @notice Implements basic whitelisting functions for different actors of the permissioned protocol + + * @author Aave + **/ +contract PermissionManager is IPermissionManager, Ownable { + mapping(address => uint256) _permissions; + mapping(address => uint256) _permissionsAdmins; + + uint256 public constant MAX_NUM_OF_ROLES = 256; + + modifier onlyPermissionAdmins(address user) { + require(_permissionsAdmins[user] > 0, 'CALLER_NOT_PERMISSIONS_ADMIN'); + _; + } + + /** + * @dev Allows owner to add new permission admins + * @param users The addresses of the users to promote to permission admin + **/ + function addPermissionAdmins(address[] calldata users) external override onlyOwner { + for (uint256 i = 0; i < users.length; i++) { + _permissionsAdmins[users[i]] = 1; + + emit PermissionsAdminSet(users[i], true); + } + } + + /** + * @dev Allows owner to remove permission admins + * @param users The addresses of the users to demote as permission admin + **/ + function removePermissionAdmins(address[] calldata users) external override onlyOwner { + for (uint256 i = 0; i < users.length; i++) { + _permissionsAdmins[users[i]] = 0; + + emit PermissionsAdminSet(users[i], false); + } + } + + /** + * @dev Allows permission admins to whitelist a set of addresses for multiple roles + * @param roles The list of roles to assign to the different users + * @param users The addresses of the users to assign to the corresponding role + **/ + function addPermissions(uint256[] calldata roles, address[] calldata users) + external + override + onlyPermissionAdmins(msg.sender) + { + require(roles.length == users.length, 'INCONSISTENT_ARRAYS_LENGTH'); + + for (uint256 i = 0; i < users.length; i++) { + uint256 role = roles[i]; + + require(role < MAX_NUM_OF_ROLES, 'INVALID_ROLE'); + + uint256 permissions = _permissions[users[i]]; + _permissions[users[i]] = permissions | (1 << role); + + emit RoleSet(users[i], roles[i], true); + } + } + + /** + * @dev Allows owner to remove permissions on a set of addresses for multiple roles + * @param roles The list of roles + * @param users The addresses of the users + **/ + function removePermissions(uint256[] calldata roles, address[] calldata users) + external + override + onlyPermissionAdmins(msg.sender) + { + require(roles.length == users.length, 'INCONSISTENT_ARRAYS_LENGTH'); + + for (uint256 i = 0; i < users.length; i++) { + uint256 role = roles[i]; + + require(role < MAX_NUM_OF_ROLES, 'INVALID_ROLE'); + + uint256 permissions = _permissions[users[i]]; + _permissions[users[i]] = permissions & ~(1 << role); + emit RoleSet(users[i], roles[i], false); + } + } + + /** + * @dev Returns the permissions configuration for a specific account + * @param account The address of the user + * @return the set of permissions states for the account + **/ + function getAccountPermissions(address account) + external + view + override + returns (uint256[] memory, uint256) + { + uint256[] memory roles = new uint256[](256); + uint256 rolesCount = 0; + uint256 accountPermissions = _permissions[account]; + + for (uint256 i = 0; i < 256; i++) { + if ((accountPermissions >> i) & 1 > 0) { + roles[rolesCount] = i; + rolesCount++; + } + } + + return (roles, rolesCount); + } + + /** + * @dev Used to query if a certain account is a depositor + * @param account The address of the user + * @return True if the account is a depositor, false otherwise + **/ + function isInRole(address account, uint256 role) public view override returns (bool) { + return (_permissions[account] >> role) & 1 > 0; + } + + /** + * @dev Used to query if a certain account is a depositor + * @param account The address of the user + * @return True if the account is a depositor, false otherwise + **/ + function isPermissionsAdmin(address account) public view override returns (bool) { + return _permissionsAdmins[account] > 0; + } +} diff --git a/contracts/protocol/lendingpool/LendingPool.sol b/contracts/protocol/lendingpool/LendingPool.sol index 531f007b..38b4baea 100644 --- a/contracts/protocol/lendingpool/LendingPool.sol +++ b/contracts/protocol/lendingpool/LendingPool.sol @@ -106,7 +106,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage uint256 amount, address onBehalfOf, uint16 referralCode - ) external override whenNotPaused { + ) public virtual override whenNotPaused { DataTypes.ReserveData storage reserve = _reserves[asset]; ValidationLogic.validateDeposit(reserve, amount); @@ -143,7 +143,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage address asset, uint256 amount, address to - ) external override whenNotPaused returns (uint256) { + ) public virtual override whenNotPaused returns (uint256) { return _executeWithdraw(asset, amount, to); } @@ -168,7 +168,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage uint256 interestRateMode, uint16 referralCode, address onBehalfOf - ) external override whenNotPaused { + ) public virtual override whenNotPaused { DataTypes.ReserveData storage reserve = _reserves[asset]; _executeBorrow( ExecuteBorrowParams( @@ -201,7 +201,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage uint256 amount, uint256 rateMode, address onBehalfOf - ) external override whenNotPaused returns (uint256) { + ) public virtual override whenNotPaused returns (uint256) { return _executeRepay(asset, amount, rateMode, onBehalfOf); } @@ -210,7 +210,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage * @param asset The address of the underlying asset borrowed * @param rateMode The rate mode that the user wants to swap to **/ - function swapBorrowRateMode(address asset, uint256 rateMode) external override whenNotPaused { + function swapBorrowRateMode(address asset, uint256 rateMode) public virtual override whenNotPaused { DataTypes.ReserveData storage reserve = _reserves[asset]; (uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(msg.sender, reserve); @@ -263,7 +263,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage * @param asset The address of the underlying asset borrowed * @param user The address of the user to be rebalanced **/ - function rebalanceStableBorrowRate(address asset, address user) external override whenNotPaused { + function rebalanceStableBorrowRate(address asset, address user) public virtual override whenNotPaused { DataTypes.ReserveData storage reserve = _reserves[asset]; IERC20 stableDebtToken = IERC20(reserve.stableDebtTokenAddress); @@ -301,7 +301,8 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage * @param useAsCollateral `true` if the user wants to use the deposit as collateral, `false` otherwise **/ function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) - external + public + virtual override whenNotPaused { @@ -344,7 +345,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage address user, uint256 debtToCover, bool receiveAToken - ) external override whenNotPaused { + ) public virtual override whenNotPaused { address collateralManager = _addressesProvider.getLendingPoolCollateralManager(); //solium-disable-next-line @@ -404,7 +405,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage address onBehalfOf, bytes calldata params, uint16 referralCode - ) external override whenNotPaused { + ) public virtual override whenNotPaused { FlashLoanLocalVars memory vars; ValidationLogic.validateFlashloan(assets, amounts); @@ -629,7 +630,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage } /** - * @dev Returns the fee on flash loans + * @dev Returns the fee on flash loans */ function FLASHLOAN_PREMIUM_TOTAL() public view returns (uint256) { return _flashLoanPremiumTotal; @@ -659,7 +660,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage uint256 amount, uint256 balanceFromBefore, uint256 balanceToBefore - ) external override whenNotPaused { + ) public virtual override whenNotPaused { require(msg.sender == _reserves[asset].aTokenAddress, Errors.LP_CALLER_MUST_BE_AN_ATOKEN); ValidationLogic.validateTransfer( diff --git a/contracts/protocol/lendingpool/PermissionedLendingPool.sol b/contracts/protocol/lendingpool/PermissionedLendingPool.sol new file mode 100644 index 00000000..fae6620a --- /dev/null +++ b/contracts/protocol/lendingpool/PermissionedLendingPool.sol @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.12; +pragma experimental ABIEncoderV2; + +import {LendingPool} from './LendingPool.sol'; +import {IPermissionManager} from '../../interfaces/IPermissionManager.sol'; +import {Errors} from '../libraries/helpers/Errors.sol'; +import {DataTypes} from '../libraries/types/DataTypes.sol'; + +/** + * @title PermissionedLendingPool + * @notice This smart contracts adds a permission layer to the LendingPool contract to enable whitelisting of users interacting with it + * @author Aave + **/ +contract PermissionedLendingPool is LendingPool { + //identifier for the permission manager contract in the addresses provider + bytes32 public constant PERMISSION_MANAGER = keccak256('PERMISSION_MANAGER'); + + modifier onlyDepositors(address user) { + require(_isInRole(msg.sender, DataTypes.Roles.DEPOSITOR), Errors.DEPOSITOR_UNAUTHORIZED); + _; + } + + modifier onlyBorrowers(address user) { + require(_isInRole(user, DataTypes.Roles.BORROWER), Errors.BORROWER_UNAUTHORIZED); + _; + } + + modifier onlyLiquidators { + require(_isInRole(msg.sender, DataTypes.Roles.LIQUIDATOR), Errors.LIQUIDATOR_UNAUTHORIZED); + _; + } + + modifier onlyDepositorsOrBorrowersOrLiquidators { + require( + _isInRole(msg.sender, DataTypes.Roles.DEPOSITOR) || + _isInRole(msg.sender, DataTypes.Roles.BORROWER) || + _isInRole(msg.sender, DataTypes.Roles.LIQUIDATOR), + Errors.REPAYER_UNAUTHORIZED + ); + _; + } + + modifier onlyStableRateManagers { + require( + _isInRole(msg.sender, DataTypes.Roles.STABLE_RATE_MANAGER), + Errors.CALLER_NOT_STABLE_RATE_MANAGER + ); + _; + } + + /** + * @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens. + * - E.g. User deposits 100 USDC and gets in return 100 aUSDC + * @param asset The address of the underlying asset to deposit + * @param amount The amount to be deposited + * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user + * wants to receive them on his own wallet, or a different address if the beneficiary of aTokens + * is a different wallet + * @param referralCode Code used to register the integrator originating the operation, for potential rewards. + * 0 if the action is executed directly by the user, without any middle-man + **/ + function deposit( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode + ) public virtual override onlyDepositors(onBehalfOf) { + super.deposit(asset, amount, onBehalfOf, referralCode); + } + + /** + * @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned + * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC + * @param asset The address of the underlying asset to withdraw + * @param amount The underlying amount to be withdrawn + * - Send the value type(uint256).max in order to withdraw the whole aToken balance + * @param to Address that will receive the underlying, same as msg.sender if the user + * wants to receive it on his own wallet, or a different address if the beneficiary is a + * different wallet + * @return The final amount withdrawn + **/ + function withdraw( + address asset, + uint256 amount, + address to + ) public virtual override onlyDepositors(msg.sender) returns (uint256) { + return super.withdraw(asset, amount, to); + } + + /** + * @dev Allows users to borrow a specific `amount` of the reserve underlying asset, provided that the borrower + * already deposited enough collateral, or he was given enough allowance by a credit delegator on the + * corresponding debt token (StableDebtToken or VariableDebtToken) + * - E.g. User borrows 100 USDC passing as `onBehalfOf` his own address, receiving the 100 USDC in his wallet + * and 100 stable/variable debt tokens, depending on the `interestRateMode` + * @param asset The address of the underlying asset to borrow + * @param amount The amount to be borrowed + * @param interestRateMode The interest rate mode at which the user wants to borrow: 1 for Stable, 2 for Variable + * @param referralCode Code used to register the integrator originating the operation, for potential rewards. + * 0 if the action is executed directly by the user, without any middle-man + * @param onBehalfOf Address of the user who will receive the debt. Should be the address of the borrower itself + * calling the function if he wants to borrow against his own collateral, or the address of the credit delegator + * if he has been given credit delegation allowance + **/ + function borrow( + address asset, + uint256 amount, + uint256 interestRateMode, + uint16 referralCode, + address onBehalfOf + ) public virtual override onlyBorrowers(onBehalfOf) { + super.borrow(asset, amount, interestRateMode, referralCode, onBehalfOf); + } + + /** + * @notice Repays a borrowed `amount` on a specific reserve, burning the equivalent debt tokens owned + * - E.g. User repays 100 USDC, burning 100 variable/stable debt tokens of the `onBehalfOf` address + * @param asset The address of the borrowed underlying asset previously borrowed + * @param amount The amount to repay + * - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode` + * @param rateMode The interest rate mode at of the debt the user wants to repay: 1 for Stable, 2 for Variable + * @param onBehalfOf Address of the user who will get his debt reduced/removed. Should be the address of the + * user calling the function if he wants to reduce/remove his own debt, or the address of any other + * other borrower whose debt should be removed + * @return The final amount repaid + **/ + function repay( + address asset, + uint256 amount, + uint256 rateMode, + address onBehalfOf + ) public virtual override onlyDepositorsOrBorrowersOrLiquidators returns (uint256) { + return super.repay(asset, amount, rateMode, onBehalfOf); + } + + /** + * @dev Allows a borrower to swap his debt between stable and variable mode, or viceversa + * @param asset The address of the underlying asset borrowed + * @param rateMode The rate mode that the user wants to swap to + **/ + function swapBorrowRateMode(address asset, uint256 rateMode) + public + virtual + override + onlyBorrowers(msg.sender) + { + super.swapBorrowRateMode(asset, rateMode); + } + + /** + * @dev Rebalances the stable interest rate of a user to the current stable rate defined on the reserve. + * - Users can be rebalanced if the following conditions are satisfied: + * 1. Usage ratio is above 95% + * 2. the current deposit APY is below REBALANCE_UP_THRESHOLD * maxVariableBorrowRate, which means that too much has been + * borrowed at a stable rate and depositors are not earning enough + * @param asset The address of the underlying asset borrowed + * @param user The address of the user to be rebalanced + **/ + function rebalanceStableBorrowRate(address asset, address user) + public + virtual + override + onlyStableRateManagers + { + super.rebalanceStableBorrowRate(asset, user); + } + + /** + * @dev Allows depositors to enable/disable a specific deposited asset as collateral + * @param asset The address of the underlying asset deposited + * @param useAsCollateral `true` if the user wants to use the deposit as collateral, `false` otherwise + **/ + function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) + public + virtual + override + onlyDepositors(msg.sender) + { + super.setUserUseReserveAsCollateral(asset, useAsCollateral); + } + + /** + * @dev Function to liquidate a non-healthy position collateral-wise, with Health Factor below 1 + * - The caller (liquidator) covers `debtToCover` amount of debt of the user getting liquidated, and receives + * a proportionally amount of the `collateralAsset` plus a bonus to cover market risk + * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation + * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation + * @param user The address of the borrower getting liquidated + * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover + * @param receiveAToken `true` if the liquidators wants to receive the collateral aTokens, `false` if he wants + * to receive the underlying collateral asset directly + **/ + function liquidationCall( + address collateralAsset, + address debtAsset, + address user, + uint256 debtToCover, + bool receiveAToken + ) public virtual override onlyLiquidators { + super.liquidationCall(collateralAsset, debtAsset, user, debtToCover, receiveAToken); + } + + /** + * @dev Allows smartcontracts to access the liquidity of the pool within one transaction, + * as long as the amount taken plus a fee is returned. + * IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept into consideration. + * For further details please visit https://developers.aave.com + * @param receiverAddress The address of the contract receiving the funds, implementing the IFlashLoanReceiver interface + * @param assets The addresses of the assets being flash-borrowed + * @param amounts The amounts amounts being flash-borrowed + * @param modes Types of the debt to open if the flash loan is not returned: + * 0 -> Don't open any debt, just revert if funds can't be transferred from the receiver + * 1 -> Open debt at stable rate for the value of the amount flash-borrowed to the `onBehalfOf` address + * 2 -> Open debt at variable rate for the value of the amount flash-borrowed to the `onBehalfOf` address + * @param onBehalfOf The address that will receive the debt in the case of using on `modes` 1 or 2 + * @param params Variadic packed params to pass to the receiver as extra information + * @param referralCode Code used to register the integrator originating the operation, for potential rewards. + * 0 if the action is executed directly by the user, without any middle-man + **/ + function flashLoan( + address receiverAddress, + address[] calldata assets, + uint256[] calldata amounts, + uint256[] calldata modes, + address onBehalfOf, + bytes calldata params, + uint16 referralCode + ) public virtual override { + //validating modes + for (uint256 i = 0; i < modes.length; i++) { + if (modes[i] == uint256(DataTypes.InterestRateMode.NONE)) { + require(_isInRole(msg.sender, DataTypes.Roles.BORROWER), Errors.BORROWER_UNAUTHORIZED); + } else { + require(_isInRole(onBehalfOf, DataTypes.Roles.BORROWER), Errors.BORROWER_UNAUTHORIZED); + } + } + super.flashLoan(receiverAddress, assets, amounts, modes, onBehalfOf, params, referralCode); + } + + /** + * @dev Validates and finalizes an aToken transfer + * - Only callable by the overlying aToken of the `asset` + * @param asset The address of the underlying asset of the aToken + * @param from The user from which the aTokens are transferred + * @param to The user receiving the aTokens + * @param amount The amount being transferred/withdrawn + * @param balanceFromBefore The aToken balance of the `from` user before the transfer + * @param balanceToBefore The aToken balance of the `to` user before the transfer + */ + function finalizeTransfer( + address asset, + address from, + address to, + uint256 amount, + uint256 balanceFromBefore, + uint256 balanceToBefore + ) public override { + require(_isInRole(from, DataTypes.Roles.DEPOSITOR), Errors.VL_TRANSFER_NOT_ALLOWED); + require(_isInRole(to, DataTypes.Roles.DEPOSITOR), Errors.VL_TRANSFER_NOT_ALLOWED); + + super.finalizeTransfer(asset, from, to, amount, balanceFromBefore, balanceToBefore); + } + + function _isInRole(address user, DataTypes.Roles role) internal view returns (bool) { + return + IPermissionManager(_addressesProvider.getAddress(PERMISSION_MANAGER)).isInRole( + user, + uint256(role) + ); + } +} diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index 8756d797..d6ff6368 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -103,6 +103,11 @@ library Errors { string public constant LP_NOT_CONTRACT = '78'; string public constant SDT_STABLE_DEBT_OVERFLOW = '79'; string public constant SDT_BURN_EXCEEDS_BALANCE = '80'; + string public constant DEPOSITOR_UNAUTHORIZED = '81'; + string public constant BORROWER_UNAUTHORIZED = '82'; + string public constant LIQUIDATOR_UNAUTHORIZED = '83'; + string public constant CALLER_NOT_STABLE_RATE_MANAGER = '84'; + string public constant REPAYER_UNAUTHORIZED = '85'; enum CollateralManagerErrors { NO_ERROR, diff --git a/contracts/protocol/libraries/types/DataTypes.sol b/contracts/protocol/libraries/types/DataTypes.sol index a19e5efc..0cb5db1d 100644 --- a/contracts/protocol/libraries/types/DataTypes.sol +++ b/contracts/protocol/libraries/types/DataTypes.sol @@ -46,4 +46,6 @@ library DataTypes { } enum InterestRateMode {NONE, STABLE, VARIABLE} + + enum Roles {DEPOSITOR, BORROWER, LIQUIDATOR, STABLE_RATE_MANAGER} } diff --git a/contracts/protocol/tokenization/PermissionedStableDebtToken.sol b/contracts/protocol/tokenization/PermissionedStableDebtToken.sol new file mode 100644 index 00000000..afc6fcba --- /dev/null +++ b/contracts/protocol/tokenization/PermissionedStableDebtToken.sol @@ -0,0 +1,435 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +import {PermissionedDebtTokenBase} from './base/PermissionedDebtTokenBase.sol'; +import {MathUtils} from '../libraries/math/MathUtils.sol'; +import {WadRayMath} from '../libraries/math/WadRayMath.sol'; +import {IStableDebtToken} from '../../interfaces/IStableDebtToken.sol'; +import {ILendingPool} from '../../interfaces/ILendingPool.sol'; +import {IAaveIncentivesController} from '../../interfaces/IAaveIncentivesController.sol'; +import {Errors} from '../libraries/helpers/Errors.sol'; + +/** + * @title PermissionedStableDebtToken + * @notice Implements a stable debt token to track the borrowing positions of users + * at stable rate mode, with permissioned roles on credit delegation + * @author Aave + **/ +contract PermissionedStableDebtToken is IStableDebtToken, PermissionedDebtTokenBase { + using WadRayMath for uint256; + + uint256 public constant DEBT_TOKEN_REVISION = 0x1; + + uint256 internal _avgStableRate; + mapping(address => uint40) internal _timestamps; + mapping(address => uint256) internal _usersStableRate; + uint40 internal _totalSupplyTimestamp; + + ILendingPool internal _pool; + address internal _underlyingAsset; + IAaveIncentivesController internal _incentivesController; + + /** + * @dev Initializes the debt token. + * @param pool The address of the lending pool where this aToken will be used + * @param underlyingAsset The address of the underlying asset of this aToken (E.g. WETH for aWETH) + * @param incentivesController The smart contract managing potential incentives distribution + * @param debtTokenDecimals The decimals of the debtToken, same as the underlying asset's + * @param debtTokenName The name of the token + * @param debtTokenSymbol The symbol of the token + */ + function initialize( + ILendingPool pool, + address underlyingAsset, + IAaveIncentivesController incentivesController, + uint8 debtTokenDecimals, + string memory debtTokenName, + string memory debtTokenSymbol, + bytes calldata params + ) public override initializer { + _setName(debtTokenName); + _setSymbol(debtTokenSymbol); + _setDecimals(debtTokenDecimals); + + _pool = pool; + _underlyingAsset = underlyingAsset; + _incentivesController = incentivesController; + + emit Initialized( + underlyingAsset, + address(pool), + address(incentivesController), + debtTokenDecimals, + debtTokenName, + debtTokenSymbol, + params + ); + } + + /** + * @dev Gets the revision of the stable debt token implementation + * @return The debt token implementation revision + **/ + function getRevision() internal pure virtual override returns (uint256) { + return DEBT_TOKEN_REVISION; + } + + /** + * @dev Returns the average stable rate across all the stable rate debt + * @return the average stable rate + **/ + function getAverageStableRate() external view virtual override returns (uint256) { + return _avgStableRate; + } + + /** + * @dev Returns the timestamp of the last user action + * @return The last update timestamp + **/ + function getUserLastUpdated(address user) external view virtual override returns (uint40) { + return _timestamps[user]; + } + + /** + * @dev Returns the stable rate of the user + * @param user The address of the user + * @return The stable rate of user + **/ + function getUserStableRate(address user) external view virtual override returns (uint256) { + return _usersStableRate[user]; + } + + /** + * @dev Calculates the current user debt balance + * @return The accumulated debt of the user + **/ + function balanceOf(address account) public view virtual override returns (uint256) { + uint256 accountBalance = super.balanceOf(account); + uint256 stableRate = _usersStableRate[account]; + if (accountBalance == 0) { + return 0; + } + uint256 cumulatedInterest = + MathUtils.calculateCompoundedInterest(stableRate, _timestamps[account]); + return accountBalance.rayMul(cumulatedInterest); + } + + struct MintLocalVars { + uint256 previousSupply; + uint256 nextSupply; + uint256 amountInRay; + uint256 newStableRate; + uint256 currentAvgStableRate; + } + + /** + * @dev Mints debt token to the `onBehalfOf` address. + * - Only callable by the LendingPool + * - The resulting rate is the weighted average between the rate of the new debt + * and the rate of the previous debt + * @param user The address receiving the borrowed underlying, being the delegatee in case + * of credit delegate, or same as `onBehalfOf` otherwise + * @param onBehalfOf The address receiving the debt tokens + * @param amount The amount of debt tokens to mint + * @param rate The rate of the debt being minted + **/ + function mint( + address user, + address onBehalfOf, + uint256 amount, + uint256 rate + ) external override onlyLendingPool returns (bool) { + MintLocalVars memory vars; + + if (user != onBehalfOf) { + _decreaseBorrowAllowance(onBehalfOf, user, amount); + } + + (, uint256 currentBalance, uint256 balanceIncrease) = _calculateBalanceIncrease(onBehalfOf); + + vars.previousSupply = totalSupply(); + vars.currentAvgStableRate = _avgStableRate; + vars.nextSupply = _totalSupply = vars.previousSupply.add(amount); + + vars.amountInRay = amount.wadToRay(); + + vars.newStableRate = _usersStableRate[onBehalfOf] + .rayMul(currentBalance.wadToRay()) + .add(vars.amountInRay.rayMul(rate)) + .rayDiv(currentBalance.add(amount).wadToRay()); + + require(vars.newStableRate <= type(uint128).max, Errors.SDT_STABLE_DEBT_OVERFLOW); + _usersStableRate[onBehalfOf] = vars.newStableRate; + + //solium-disable-next-line + _totalSupplyTimestamp = _timestamps[onBehalfOf] = uint40(block.timestamp); + + // Calculates the updated average stable rate + vars.currentAvgStableRate = _avgStableRate = vars + .currentAvgStableRate + .rayMul(vars.previousSupply.wadToRay()) + .add(rate.rayMul(vars.amountInRay)) + .rayDiv(vars.nextSupply.wadToRay()); + + _mint(onBehalfOf, amount.add(balanceIncrease), vars.previousSupply); + + emit Transfer(address(0), onBehalfOf, amount); + + emit Mint( + user, + onBehalfOf, + amount, + currentBalance, + balanceIncrease, + vars.newStableRate, + vars.currentAvgStableRate, + vars.nextSupply + ); + + return currentBalance == 0; + } + + /** + * @dev Burns debt of `user` + * @param user The address of the user getting his debt burned + * @param amount The amount of debt tokens getting burned + **/ + function burn(address user, uint256 amount) external override onlyLendingPool { + (, uint256 currentBalance, uint256 balanceIncrease) = _calculateBalanceIncrease(user); + + uint256 previousSupply = totalSupply(); + uint256 newAvgStableRate = 0; + uint256 nextSupply = 0; + uint256 userStableRate = _usersStableRate[user]; + + // Since the total supply and each single user debt accrue separately, + // there might be accumulation errors so that the last borrower repaying + // mght actually try to repay more than the available debt supply. + // In this case we simply set the total supply and the avg stable rate to 0 + if (previousSupply <= amount) { + _avgStableRate = 0; + _totalSupply = 0; + } else { + nextSupply = _totalSupply = previousSupply.sub(amount); + uint256 firstTerm = _avgStableRate.rayMul(previousSupply.wadToRay()); + uint256 secondTerm = userStableRate.rayMul(amount.wadToRay()); + + // For the same reason described above, when the last user is repaying it might + // happen that user rate * user balance > avg rate * total supply. In that case, + // we simply set the avg rate to 0 + if (secondTerm >= firstTerm) { + newAvgStableRate = _avgStableRate = _totalSupply = 0; + } else { + newAvgStableRate = _avgStableRate = firstTerm.sub(secondTerm).rayDiv(nextSupply.wadToRay()); + } + } + + if (amount == currentBalance) { + _usersStableRate[user] = 0; + _timestamps[user] = 0; + } else { + //solium-disable-next-line + _timestamps[user] = uint40(block.timestamp); + } + //solium-disable-next-line + _totalSupplyTimestamp = uint40(block.timestamp); + + if (balanceIncrease > amount) { + uint256 amountToMint = balanceIncrease.sub(amount); + _mint(user, amountToMint, previousSupply); + emit Mint( + user, + user, + amountToMint, + currentBalance, + balanceIncrease, + userStableRate, + newAvgStableRate, + nextSupply + ); + } else { + uint256 amountToBurn = amount.sub(balanceIncrease); + _burn(user, amountToBurn, previousSupply); + emit Burn(user, amountToBurn, currentBalance, balanceIncrease, newAvgStableRate, nextSupply); + } + + emit Transfer(user, address(0), amount); + } + + /** + * @dev Calculates the increase in balance since the last user interaction + * @param user The address of the user for which the interest is being accumulated + * @return The previous principal balance, the new principal balance and the balance increase + **/ + function _calculateBalanceIncrease(address user) + internal + view + returns ( + uint256, + uint256, + uint256 + ) + { + uint256 previousPrincipalBalance = super.balanceOf(user); + + if (previousPrincipalBalance == 0) { + return (0, 0, 0); + } + + // Calculation of the accrued interest since the last accumulation + uint256 balanceIncrease = balanceOf(user).sub(previousPrincipalBalance); + + return ( + previousPrincipalBalance, + previousPrincipalBalance.add(balanceIncrease), + balanceIncrease + ); + } + + /** + * @dev Returns the principal and total supply, the average borrow rate and the last supply update timestamp + **/ + function getSupplyData() + public + view + override + returns ( + uint256, + uint256, + uint256, + uint40 + ) + { + uint256 avgRate = _avgStableRate; + return (super.totalSupply(), _calcTotalSupply(avgRate), avgRate, _totalSupplyTimestamp); + } + + /** + * @dev Returns the the total supply and the average stable rate + **/ + function getTotalSupplyAndAvgRate() public view override returns (uint256, uint256) { + uint256 avgRate = _avgStableRate; + return (_calcTotalSupply(avgRate), avgRate); + } + + /** + * @dev Returns the total supply + **/ + function totalSupply() public view override returns (uint256) { + return _calcTotalSupply(_avgStableRate); + } + + /** + * @dev Returns the timestamp at which the total supply was updated + **/ + function getTotalSupplyLastUpdated() public view override returns (uint40) { + return _totalSupplyTimestamp; + } + + /** + * @dev Returns the principal debt balance of the user from + * @param user The user's address + * @return The debt balance of the user since the last burn/mint action + **/ + function principalBalanceOf(address user) external view virtual override returns (uint256) { + return super.balanceOf(user); + } + + /** + * @dev Returns the address of the underlying asset of this aToken (E.g. WETH for aWETH) + **/ + function UNDERLYING_ASSET_ADDRESS() public view returns (address) { + return _underlyingAsset; + } + + /** + * @dev Returns the address of the lending pool where this aToken is used + **/ + function POOL() public view returns (ILendingPool) { + return _pool; + } + + /** + * @dev Returns the address of the incentives controller contract + **/ + function getIncentivesController() external view override returns (IAaveIncentivesController) { + return _getIncentivesController(); + } + + /** + * @dev For internal usage in the logic of the parent contracts + **/ + function _getIncentivesController() internal view override returns (IAaveIncentivesController) { + return _incentivesController; + } + + /** + * @dev For internal usage in the logic of the parent contracts + **/ + function _getUnderlyingAssetAddress() internal view override returns (address) { + return _underlyingAsset; + } + + /** + * @dev For internal usage in the logic of the parent contracts + **/ + function _getLendingPool() internal view override returns (ILendingPool) { + return _pool; + } + + /** + * @dev Calculates the total supply + * @param avgRate The average rate at which the total supply increases + * @return The debt balance of the user since the last burn/mint action + **/ + function _calcTotalSupply(uint256 avgRate) internal view virtual returns (uint256) { + uint256 principalSupply = super.totalSupply(); + + if (principalSupply == 0) { + return 0; + } + + uint256 cumulatedInterest = + MathUtils.calculateCompoundedInterest(avgRate, _totalSupplyTimestamp); + + return principalSupply.rayMul(cumulatedInterest); + } + + /** + * @dev Mints stable debt tokens to an user + * @param account The account receiving the debt tokens + * @param amount The amount being minted + * @param oldTotalSupply the total supply before the minting event + **/ + function _mint( + address account, + uint256 amount, + uint256 oldTotalSupply + ) internal { + uint256 oldAccountBalance = _balances[account]; + _balances[account] = oldAccountBalance.add(amount); + + if (address(_incentivesController) != address(0)) { + _incentivesController.handleAction(account, oldTotalSupply, oldAccountBalance); + } + } + + /** + * @dev Burns stable debt tokens of an user + * @param account The user getting his debt burned + * @param amount The amount being burned + * @param oldTotalSupply The total supply before the burning event + **/ + function _burn( + address account, + uint256 amount, + uint256 oldTotalSupply + ) internal { + uint256 oldAccountBalance = _balances[account]; + _balances[account] = oldAccountBalance.sub(amount, Errors.SDT_BURN_EXCEEDS_BALANCE); + + if (address(_incentivesController) != address(0)) { + _incentivesController.handleAction(account, oldTotalSupply, oldAccountBalance); + } + } +} diff --git a/contracts/protocol/tokenization/PermissionedVariableDebtToken.sol b/contracts/protocol/tokenization/PermissionedVariableDebtToken.sol new file mode 100644 index 00000000..f587b5ce --- /dev/null +++ b/contracts/protocol/tokenization/PermissionedVariableDebtToken.sol @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +import {IVariableDebtToken} from '../../interfaces/IVariableDebtToken.sol'; +import {WadRayMath} from '../libraries/math/WadRayMath.sol'; +import {Errors} from '../libraries/helpers/Errors.sol'; +import {PermissionedDebtTokenBase} from './base/PermissionedDebtTokenBase.sol'; +import {ILendingPool} from '../../interfaces/ILendingPool.sol'; +import {IAaveIncentivesController} from '../../interfaces/IAaveIncentivesController.sol'; + +/** + * @title PermissionedVariableDebtToken + * @notice Implements a variable debt token to track the borrowing positions of users + * at variable rate mode, with permissioned roles on credit delegation + * @author Aave + **/ +contract PermissionedVariableDebtToken is PermissionedDebtTokenBase, IVariableDebtToken { + using WadRayMath for uint256; + + uint256 public constant DEBT_TOKEN_REVISION = 0x1; + + ILendingPool internal _pool; + address internal _underlyingAsset; + IAaveIncentivesController internal _incentivesController; + + /** + * @dev Initializes the debt token. + * @param pool The address of the lending pool where this aToken will be used + * @param underlyingAsset The address of the underlying asset of this aToken (E.g. WETH for aWETH) + * @param incentivesController The smart contract managing potential incentives distribution + * @param debtTokenDecimals The decimals of the debtToken, same as the underlying asset's + * @param debtTokenName The name of the token + * @param debtTokenSymbol The symbol of the token + */ + function initialize( + ILendingPool pool, + address underlyingAsset, + IAaveIncentivesController incentivesController, + uint8 debtTokenDecimals, + string memory debtTokenName, + string memory debtTokenSymbol, + bytes calldata params + ) public override initializer { + _setName(debtTokenName); + _setSymbol(debtTokenSymbol); + _setDecimals(debtTokenDecimals); + + _pool = pool; + _underlyingAsset = underlyingAsset; + _incentivesController = incentivesController; + + emit Initialized( + underlyingAsset, + address(pool), + address(incentivesController), + debtTokenDecimals, + debtTokenName, + debtTokenSymbol, + params + ); + } + + /** + * @dev Gets the revision of the stable debt token implementation + * @return The debt token implementation revision + **/ + function getRevision() internal pure virtual override returns (uint256) { + return DEBT_TOKEN_REVISION; + } + + /** + * @dev Calculates the accumulated debt balance of the user + * @return The debt balance of the user + **/ + function balanceOf(address user) public view virtual override returns (uint256) { + uint256 scaledBalance = super.balanceOf(user); + + if (scaledBalance == 0) { + return 0; + } + + return scaledBalance.rayMul(_pool.getReserveNormalizedVariableDebt(_underlyingAsset)); + } + + /** + * @dev Mints debt token to the `onBehalfOf` address + * - Only callable by the LendingPool + * @param user The address receiving the borrowed underlying, being the delegatee in case + * of credit delegate, or same as `onBehalfOf` otherwise + * @param onBehalfOf The address receiving the debt tokens + * @param amount The amount of debt being minted + * @param index The variable debt index of the reserve + * @return `true` if the the previous balance of the user is 0 + **/ + function mint( + address user, + address onBehalfOf, + uint256 amount, + uint256 index + ) external override onlyLendingPool returns (bool) { + if (user != onBehalfOf) { + _decreaseBorrowAllowance(onBehalfOf, user, amount); + } + + uint256 previousBalance = super.balanceOf(onBehalfOf); + uint256 amountScaled = amount.rayDiv(index); + require(amountScaled != 0, Errors.CT_INVALID_MINT_AMOUNT); + + _mint(onBehalfOf, amountScaled); + + emit Transfer(address(0), onBehalfOf, amount); + emit Mint(user, onBehalfOf, amount, index); + + return previousBalance == 0; + } + + /** + * @dev Burns user variable debt + * - Only callable by the LendingPool + * @param user The user whose debt is getting burned + * @param amount The amount getting burned + * @param index The variable debt index of the reserve + **/ + function burn( + address user, + uint256 amount, + uint256 index + ) external override onlyLendingPool { + uint256 amountScaled = amount.rayDiv(index); + require(amountScaled != 0, Errors.CT_INVALID_BURN_AMOUNT); + + _burn(user, amountScaled); + + emit Transfer(user, address(0), amount); + emit Burn(user, amount, index); + } + + /** + * @dev Returns the principal debt balance of the user from + * @return The debt balance of the user since the last burn/mint action + **/ + function scaledBalanceOf(address user) public view virtual override returns (uint256) { + return super.balanceOf(user); + } + + /** + * @dev Returns the total supply of the variable debt token. Represents the total debt accrued by the users + * @return The total supply + **/ + function totalSupply() public view virtual override returns (uint256) { + return super.totalSupply().rayMul(_pool.getReserveNormalizedVariableDebt(_underlyingAsset)); + } + + /** + * @dev Returns the scaled total supply of the variable debt token. Represents sum(debt/index) + * @return the scaled total supply + **/ + function scaledTotalSupply() public view virtual override returns (uint256) { + return super.totalSupply(); + } + + /** + * @dev Returns the principal balance of the user and principal total supply. + * @param user The address of the user + * @return The principal balance of the user + * @return The principal total supply + **/ + function getScaledUserBalanceAndSupply(address user) + external + view + override + returns (uint256, uint256) + { + return (super.balanceOf(user), super.totalSupply()); + } + + /** + * @dev Returns the address of the underlying asset of this aToken (E.g. WETH for aWETH) + **/ + function UNDERLYING_ASSET_ADDRESS() public view returns (address) { + return _underlyingAsset; + } + + /** + * @dev Returns the address of the incentives controller contract + **/ + function getIncentivesController() external view override returns (IAaveIncentivesController) { + return _getIncentivesController(); + } + + /** + * @dev Returns the address of the lending pool where this aToken is used + **/ + function POOL() public view returns (ILendingPool) { + return _pool; + } + + function _getIncentivesController() internal view override returns (IAaveIncentivesController) { + return _incentivesController; + } + + function _getUnderlyingAssetAddress() internal view override returns (address) { + return _underlyingAsset; + } + + function _getLendingPool() internal view override returns (ILendingPool) { + return _pool; + } +} diff --git a/contracts/protocol/tokenization/base/DebtTokenBase.sol b/contracts/protocol/tokenization/base/DebtTokenBase.sol index 4d75bc2f..e534ad05 100644 --- a/contracts/protocol/tokenization/base/DebtTokenBase.sol +++ b/contracts/protocol/tokenization/base/DebtTokenBase.sol @@ -37,7 +37,7 @@ abstract contract DebtTokenBase is * respect the liquidation constraints (even if delegated, a delegatee cannot * force a delegator HF to go below 1) **/ - function approveDelegation(address delegatee, uint256 amount) external override { + function approveDelegation(address delegatee, uint256 amount) public virtual override { _borrowAllowances[_msgSender()][delegatee] = amount; emit BorrowAllowanceDelegated(_msgSender(), delegatee, _getUnderlyingAssetAddress(), amount); } diff --git a/contracts/protocol/tokenization/base/PermissionedDebtTokenBase.sol b/contracts/protocol/tokenization/base/PermissionedDebtTokenBase.sol new file mode 100644 index 00000000..c095280d --- /dev/null +++ b/contracts/protocol/tokenization/base/PermissionedDebtTokenBase.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +import {DebtTokenBase} from './DebtTokenBase.sol'; +import {ILendingPool} from '../../../interfaces/ILendingPool.sol'; +import { + VersionedInitializable +} from '../../libraries/aave-upgradeability/VersionedInitializable.sol'; +import {IncentivizedERC20} from '../IncentivizedERC20.sol'; +import {Errors} from '../../libraries/helpers/Errors.sol'; +import {IPermissionManager} from '../../../interfaces/IPermissionManager.sol'; +import {DataTypes} from '../../libraries/types/DataTypes.sol'; + +/** + * @title PermissionedDebtTokenBase + * @notice Base contract for different types of debt tokens, like StableDebtToken or VariableDebtToken. Includes permissioned credit delegation + * @author Aave + */ + +abstract contract PermissionedDebtTokenBase is DebtTokenBase +{ + //identifier for the permission manager contract in the addresses provider + bytes32 public constant PERMISSION_MANAGER = keccak256('PERMISSION_MANAGER'); + + + modifier onlyBorrowers { + IPermissionManager permissionManager = + IPermissionManager(_getLendingPool().getAddressesProvider().getAddress(PERMISSION_MANAGER)); + + require( + permissionManager.isInRole(_msgSender(), uint256(DataTypes.Roles.BORROWER)), + Errors.BORROWER_UNAUTHORIZED + ); + _; + } + + /** + * @dev delegates borrowing power to a user on the specific debt token + * @param delegatee the address receiving the delegated borrowing power + * @param amount the maximum amount being delegated. Delegation will still + * respect the liquidation constraints (even if delegated, a delegatee cannot + * force a delegator HF to go below 1) + **/ + function approveDelegation(address delegatee, uint256 amount) public override onlyBorrowers { + super.approveDelegation(delegatee, amount); + } +} diff --git a/helpers/configuration.ts b/helpers/configuration.ts index 618eec82..919fdefc 100644 --- a/helpers/configuration.ts +++ b/helpers/configuration.ts @@ -8,6 +8,7 @@ import { } from './types'; import { getParamPerPool } from './contracts-helpers'; import AaveConfig from '../markets/aave'; +import AaveProConfig from '../markets/aave-pro'; import MaticConfig from '../markets/matic'; import AmmConfig from '../markets/amm'; import { CommonsConfig } from '../markets/aave/commons'; @@ -21,6 +22,7 @@ export enum ConfigNames { Aave = 'Aave', Matic = 'Matic', Amm = 'Amm', + AavePro = 'AavePro' } export const loadPoolConfig = (configName: ConfigNames): PoolConfiguration => { @@ -33,11 +35,12 @@ export const loadPoolConfig = (configName: ConfigNames): PoolConfiguration => { return AmmConfig; case ConfigNames.Commons: return CommonsConfig; + case ConfigNames.AavePro: + return AaveProConfig; default: throw new Error(`Unsupported pool configuration: ${Object.values(ConfigNames)}`); } }; - // ---------------- // PROTOCOL PARAMS PER POOL // ---------------- diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 07f34a91..eb518f85 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -49,6 +49,8 @@ import { WETH9MockedFactory, WETHGatewayFactory, FlashLiquidationAdapterFactory, + PermissionedVariableDebtTokenFactory, + PermissionedStableDebtTokenFactory, } from '../types'; import { withSaveAndVerify, @@ -331,7 +333,62 @@ export const deployVariableDebtToken = async ( return instance; }; -export const deployGenericStableDebtToken = async () => +export const deployStableDebtTokenByType = async (type: string) => { + + //if no instance type is provided, deploying the generic one by default + if(!type) { + return deployGenericStableDebtToken(); + } + + console.log("Deploying instance of ", type); + + switch(type) { + case eContractid.StableDebtToken: + return deployGenericStableDebtToken(); + case eContractid.PermissionedStableDebtToken: + return deployPermissionedStableDebtToken(); + default: + console.log("[stable]Cant find token type ", type); + throw "Invalid debt token type"; + } +} + +export const deployVariableDebtTokenByType = async (type: string) => { + + //if no instance type is provided, deploying the generic one by default + if(!type) { + return deployGenericVariableDebtToken();; + } + + switch(type) { + case eContractid.VariableDebtToken: + return deployGenericVariableDebtToken(); + case eContractid.PermissionedVariableDebtToken: + return deployPermissionedVariableDebtToken(); + default: + console.log("[variable]Cant find token type ", type); + throw "Invalid debt token type"; + } +} + + +export const deployPermissionedStableDebtToken = async () => + withSaveAndVerify( + await new PermissionedStableDebtTokenFactory(await getFirstSigner()).deploy(), + eContractid.PermissionedStableDebtToken, + [], + false + ); + +export const deployPermissionedVariableDebtToken = async () => + withSaveAndVerify( + await new PermissionedVariableDebtTokenFactory(await getFirstSigner()).deploy(), + eContractid.PermissionedVariableDebtToken, + [], + false + ); + + export const deployGenericStableDebtToken = async () => withSaveAndVerify( await new StableDebtTokenFactory(await getFirstSigner()).deploy(), eContractid.StableDebtToken, diff --git a/helpers/init-helpers.ts b/helpers/init-helpers.ts index eb483420..aa064745 100644 --- a/helpers/init-helpers.ts +++ b/helpers/init-helpers.ts @@ -24,10 +24,10 @@ import { deployDelegationAwareATokenImpl, deployGenericAToken, deployGenericATokenImpl, - deployGenericStableDebtToken, - deployGenericVariableDebtToken, deployStableDebtToken, + deployStableDebtTokenByType, deployVariableDebtToken, + deployVariableDebtTokenByType, } from './contracts-deployments'; import { ZERO_ADDRESS } from './constants'; import { isZeroAddress } from 'ethereumjs-util'; @@ -57,7 +57,6 @@ export const initReservesByHelper = async ( verify: boolean ): Promise => { let gasUsage = BigNumber.from('0'); - const stableAndVariableDeployer = await getStableAndVariableTokensHelper(); const addressProvider = await getLendingPoolAddressesProvider(); @@ -103,23 +102,31 @@ export const initReservesByHelper = async ( let aTokenType: Record = {}; let delegationAwareATokenImplementationAddress = ''; let aTokenImplementationAddress = ''; - let stableDebtTokenImplementationAddress = ''; - let variableDebtTokenImplementationAddress = ''; - // NOT WORKING ON MATIC, DEPLOYING INDIVIDUAL IMPLs INSTEAD - // const tx1 = await waitForTx( - // await stableAndVariableDeployer.initDeployment([ZERO_ADDRESS], ["1"]) - // ); - // console.log(tx1.events); - // tx1.events?.forEach((event, index) => { - // stableDebtTokenImplementationAddress = event?.args?.stableToken; - // variableDebtTokenImplementationAddress = event?.args?.variableToken; - // rawInsertContractAddressInDb(`stableDebtTokenImpl`, stableDebtTokenImplementationAddress); - // rawInsertContractAddressInDb(`variableDebtTokenImpl`, variableDebtTokenImplementationAddress); - // }); - //gasUsage = gasUsage.add(tx1.gasUsed); - stableDebtTokenImplementationAddress = await (await deployGenericStableDebtToken()).address; - variableDebtTokenImplementationAddress = await (await deployGenericVariableDebtToken()).address; + let stableDebtTokensAddresses = new Map(); + let variableDebtTokensAddresses = new Map(); + + let stableDebtTokenTypes = Object.entries(reservesParams).map(item => item[1].stableDebtTokenImpl); + let variableDebtTokenTypes = Object.entries(reservesParams).map(item => item[1].variableDebtTokenImpl); + + + // removing duplicates + stableDebtTokenTypes = [...new Set(stableDebtTokenTypes)]; + variableDebtTokenTypes = [...new Set(variableDebtTokenTypes)]; + + await Promise.all(stableDebtTokenTypes.map(async(typeName) => { + const name = typeName ?? eContractid.StableDebtToken; + const implAddress = await (await deployStableDebtTokenByType(name)).address; + stableDebtTokensAddresses.set(name, implAddress); + })); + + await Promise.all(variableDebtTokenTypes.map(async(typeName) => { + const name = typeName ?? eContractid.VariableDebtToken; + const implAddress = await (await deployVariableDebtTokenByType(name)).address; + variableDebtTokensAddresses.set(name, implAddress); + })); + + console.log("Debt tokens deployed, ", stableDebtTokensAddresses, variableDebtTokensAddresses); const aTokenImplementation = await deployGenericATokenImpl(verify); aTokenImplementationAddress = aTokenImplementation.address; @@ -186,8 +193,21 @@ export const initReservesByHelper = async ( } for (let i = 0; i < reserveSymbols.length; i++) { + + const symbol = reserveSymbols[i]; + + const stableDebtImpl = reservesParams[symbol].stableDebtTokenImpl ?? eContractid.StableDebtToken; + const variableDebtTokenImpl = reservesParams[symbol].variableDebtTokenImpl ?? eContractid.VariableDebtToken; + + const stableDebtAddress = stableDebtTokensAddresses.get(stableDebtImpl); + const variableDebtAddress = variableDebtTokensAddresses.get(variableDebtTokenImpl); + + if(!stableDebtAddress || !variableDebtAddress) { + throw "Could not find a proper debt token instance for the asset "+symbol; + } + let aTokenToUse: string; - if (aTokenType[reserveSymbols[i]] === 'generic') { + if (aTokenType[symbol] === 'generic') { aTokenToUse = aTokenImplementationAddress; } else { aTokenToUse = delegationAwareATokenImplementationAddress; @@ -195,20 +215,20 @@ export const initReservesByHelper = async ( initInputParams.push({ aTokenImpl: aTokenToUse, - stableDebtTokenImpl: stableDebtTokenImplementationAddress, - variableDebtTokenImpl: variableDebtTokenImplementationAddress, + stableDebtTokenImpl:stableDebtAddress, + variableDebtTokenImpl: variableDebtAddress, underlyingAssetDecimals: reserveInitDecimals[i], - interestRateStrategyAddress: strategyAddressPerAsset[reserveSymbols[i]], + interestRateStrategyAddress: strategyAddressPerAsset[symbol], underlyingAsset: reserveTokens[i], treasury: treasuryAddress, incentivesController: ZERO_ADDRESS, - underlyingAssetName: reserveSymbols[i], - aTokenName: `${aTokenNamePrefix} ${reserveSymbols[i]}`, - aTokenSymbol: `a${symbolPrefix}${reserveSymbols[i]}`, - variableDebtTokenName: `${variableDebtTokenNamePrefix} ${symbolPrefix}${reserveSymbols[i]}`, - variableDebtTokenSymbol: `variableDebt${symbolPrefix}${reserveSymbols[i]}`, - stableDebtTokenName: `${stableDebtTokenNamePrefix} ${reserveSymbols[i]}`, - stableDebtTokenSymbol: `stableDebt${symbolPrefix}${reserveSymbols[i]}`, + underlyingAssetName: symbol, + aTokenName: `${aTokenNamePrefix} ${symbol}`, + aTokenSymbol: `a${symbolPrefix}${symbol}`, + variableDebtTokenName: `${variableDebtTokenNamePrefix} ${symbolPrefix}${symbol}`, + variableDebtTokenSymbol: `variableDebt${symbolPrefix}${symbol}`, + stableDebtTokenName: `${stableDebtTokenNamePrefix} ${symbol}`, + stableDebtTokenSymbol: `stableDebt${symbolPrefix}${symbol}`, params: '0x10' }); } diff --git a/helpers/types.ts b/helpers/types.ts index ea8826a0..39e25318 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -87,6 +87,9 @@ export enum eContractid { UniswapLiquiditySwapAdapter = 'UniswapLiquiditySwapAdapter', UniswapRepayAdapter = 'UniswapRepayAdapter', FlashLiquidationAdapter = 'FlashLiquidationAdapter', + PermissionManager = 'PermissionManager', + PermissionedStableDebtToken = 'PermissionedStableDebtToken', + PermissionedVariableDebtToken = 'PermissionedVariableDebtToken', } /* @@ -270,6 +273,11 @@ export type iAavePoolAssets = Pick< | 'xSUSHI' >; +export type iAaveProPoolAssets = Pick< + iAssetsWithoutUSD, + 'USDC' | 'USDT' | 'WBTC' | 'WETH' +>; + export type iLpPoolAssets = Pick< iAssetsWithoutUSD, | 'DAI' @@ -356,6 +364,8 @@ export enum TokenContractId { export interface IReserveParams extends IReserveBorrowParams, IReserveCollateralParams { aTokenImpl: eContractid; + stableDebtTokenImpl?: eContractid; + variableDebtTokenImpl?: eContractid; reserveFactor: string; strategy: IInterestRateStrategyParams; } @@ -498,6 +508,9 @@ export interface IAaveConfiguration extends ICommonConfiguration { ReservesConfig: iAavePoolAssets; } +export interface IAaveProConfiguration extends ICommonConfiguration { + ReservesConfig: iAaveProPoolAssets; +} export interface IAmmConfiguration extends ICommonConfiguration { ReservesConfig: iLpPoolAssets; } diff --git a/markets/aave-pro/commons.ts b/markets/aave-pro/commons.ts new file mode 100644 index 00000000..71e04360 --- /dev/null +++ b/markets/aave-pro/commons.ts @@ -0,0 +1,298 @@ +import BigNumber from 'bignumber.js'; +import { oneEther, oneRay, RAY, ZERO_ADDRESS, MOCK_CHAINLINK_AGGREGATORS_PRICES } from '../../helpers/constants'; +import { ICommonConfiguration, eEthereumNetwork } from '../../helpers/types'; + +// ---------------- +// PROTOCOL GLOBAL PARAMS +// ---------------- + +export const CommonsConfig: ICommonConfiguration = { + MarketId: 'Commons', + ATokenNamePrefix: 'Aave interest bearing', + StableDebtTokenNamePrefix: 'Aave stable debt bearing', + VariableDebtTokenNamePrefix: 'Aave variable debt bearing', + SymbolPrefix: '', + ProviderId: 0, // Overriden in index.ts + ProtocolGlobalParams: { + TokenDistributorPercentageBase: '10000', + MockUsdPriceInWei: '5848466240000000', + UsdAddress: '0x10F7Fc1F91Ba351f9C629c5947AD69bD03C05b96', + NilAddress: '0x0000000000000000000000000000000000000000', + OneAddress: '0x0000000000000000000000000000000000000001', + AaveReferral: '0', + }, + + // ---------------- + // COMMON PROTOCOL PARAMS ACROSS POOLS AND NETWORKS + // ---------------- + + Mocks: { + AllAssetsInitialPrices: { + ...MOCK_CHAINLINK_AGGREGATORS_PRICES, + }, + }, + // TODO: reorg alphabetically, checking the reason of tests failing + LendingRateOracleRatesCommon: { + WETH: { + borrowRate: oneRay.multipliedBy(0.03).toFixed(), + }, + USDC: { + borrowRate: oneRay.multipliedBy(0.039).toFixed(), + }, + USDT: { + borrowRate: oneRay.multipliedBy(0.035).toFixed(), + }, + WBTC: { + borrowRate: oneRay.multipliedBy(0.03).toFixed(), + }, + }, + // ---------------- + // COMMON PROTOCOL ADDRESSES ACROSS POOLS + // ---------------- + + // If PoolAdmin/emergencyAdmin is set, will take priority over PoolAdminIndex/emergencyAdminIndex + PoolAdmin: { + [eEthereumNetwork.coverage]: undefined, + [eEthereumNetwork.buidlerevm]: undefined, + [eEthereumNetwork.coverage]: undefined, + [eEthereumNetwork.hardhat]: undefined, + [eEthereumNetwork.kovan]: undefined, + [eEthereumNetwork.ropsten]: undefined, + [eEthereumNetwork.main]: undefined, + [eEthereumNetwork.tenderlyMain]: undefined, + }, + PoolAdminIndex: 0, + EmergencyAdmin: { + [eEthereumNetwork.hardhat]: undefined, + [eEthereumNetwork.coverage]: undefined, + [eEthereumNetwork.buidlerevm]: undefined, + [eEthereumNetwork.kovan]: undefined, + [eEthereumNetwork.ropsten]: undefined, + [eEthereumNetwork.main]: undefined, + [eEthereumNetwork.tenderlyMain]: undefined, + }, + EmergencyAdminIndex: 1, + ProviderRegistry: { + [eEthereumNetwork.kovan]: '0x1E40B561EC587036f9789aF83236f057D1ed2A90', + [eEthereumNetwork.ropsten]: '', + [eEthereumNetwork.main]: '0x52D306e36E3B6B02c153d0266ff0f85d18BCD413', + [eEthereumNetwork.coverage]: '', + [eEthereumNetwork.hardhat]: '', + [eEthereumNetwork.buidlerevm]: '', + [eEthereumNetwork.tenderlyMain]: '0x52D306e36E3B6B02c153d0266ff0f85d18BCD413', + }, + ProviderRegistryOwner: { + [eEthereumNetwork.kovan]: '0x85e4A467343c0dc4aDAB74Af84448D9c45D8ae6F', + [eEthereumNetwork.ropsten]: '', + [eEthereumNetwork.main]: '0xbd723fc4f1d737dcfc48a07fe7336766d34cad5f', + [eEthereumNetwork.coverage]: '', + [eEthereumNetwork.hardhat]: '', + [eEthereumNetwork.buidlerevm]: '', + [eEthereumNetwork.tenderlyMain]: '0xbd723fc4f1d737dcfc48a07fe7336766d34cad5f', + }, + LendingRateOracle: { + [eEthereumNetwork.coverage]: '', + [eEthereumNetwork.hardhat]: '', + [eEthereumNetwork.buidlerevm]: '', + [eEthereumNetwork.kovan]: '',//'0xdCde9Bb6a49e37fA433990832AB541AE2d4FEB4a', + [eEthereumNetwork.ropsten]: '0x05dcca805a6562c1bdd0423768754acb6993241b', + [eEthereumNetwork.main]: '',//'0x8A32f49FFbA88aba6EFF96F45D8BD1D4b3f35c7D', + [eEthereumNetwork.tenderlyMain]: '0x8A32f49FFbA88aba6EFF96F45D8BD1D4b3f35c7D', + }, + LendingPoolCollateralManager: { + [eEthereumNetwork.coverage]: '', + [eEthereumNetwork.hardhat]: '', + [eEthereumNetwork.buidlerevm]: '', + [eEthereumNetwork.kovan]: '0x9269b6453d0d75370c4c85e5a42977a53efdb72a', + [eEthereumNetwork.ropsten]: '', + [eEthereumNetwork.main]: '0xbd4765210d4167CE2A5b87280D9E8Ee316D5EC7C', + [eEthereumNetwork.tenderlyMain]: '0xbd4765210d4167CE2A5b87280D9E8Ee316D5EC7C', + }, + LendingPoolConfigurator: { + [eEthereumNetwork.coverage]: '', + [eEthereumNetwork.hardhat]: '', + [eEthereumNetwork.buidlerevm]: '', + [eEthereumNetwork.kovan]: '', + [eEthereumNetwork.ropsten]: '', + [eEthereumNetwork.main]: '', + [eEthereumNetwork.tenderlyMain]: '', + }, + LendingPool: { + [eEthereumNetwork.coverage]: '', + [eEthereumNetwork.hardhat]: '', + [eEthereumNetwork.buidlerevm]: '', + [eEthereumNetwork.kovan]: '', + [eEthereumNetwork.ropsten]: '', + [eEthereumNetwork.main]: '', + [eEthereumNetwork.tenderlyMain]: '', + }, + WethGateway: { + [eEthereumNetwork.coverage]: '', + [eEthereumNetwork.hardhat]: '', + [eEthereumNetwork.buidlerevm]: '', + [eEthereumNetwork.kovan]: '0xf99b8E67a0E044734B01EC4586D1c88C9a869718', + [eEthereumNetwork.ropsten]: '', + [eEthereumNetwork.main]: '', + [eEthereumNetwork.tenderlyMain]: '', + }, + TokenDistributor: { + [eEthereumNetwork.coverage]: '', + [eEthereumNetwork.buidlerevm]: '', + [eEthereumNetwork.hardhat]: '', + [eEthereumNetwork.kovan]: '0x971efe90088f21dc6a36f610ffed77fc19710708', + [eEthereumNetwork.ropsten]: '0xeba2ea67942b8250d870b12750b594696d02fc9c', + [eEthereumNetwork.main]: '0xe3d9988f676457123c5fd01297605efdd0cba1ae', + [eEthereumNetwork.tenderlyMain]: '0xe3d9988f676457123c5fd01297605efdd0cba1ae', + }, + AaveOracle: { + [eEthereumNetwork.coverage]: '', + [eEthereumNetwork.hardhat]: '', + [eEthereumNetwork.buidlerevm]: '', + [eEthereumNetwork.kovan]: '',//'0xB8bE51E6563BB312Cbb2aa26e352516c25c26ac1', + [eEthereumNetwork.ropsten]: ZERO_ADDRESS, + [eEthereumNetwork.main]: '',//'0xA50ba011c48153De246E5192C8f9258A2ba79Ca9', + [eEthereumNetwork.tenderlyMain]: '0xA50ba011c48153De246E5192C8f9258A2ba79Ca9', + }, + FallbackOracle: { + [eEthereumNetwork.coverage]: '', + [eEthereumNetwork.hardhat]: '', + [eEthereumNetwork.buidlerevm]: '', + [eEthereumNetwork.kovan]: '0x50913E8E1c650E790F8a1E741FF9B1B1bB251dfe', + [eEthereumNetwork.ropsten]: '0xAD1a978cdbb8175b2eaeC47B01404f8AEC5f4F0d', + [eEthereumNetwork.main]: ZERO_ADDRESS, + [eEthereumNetwork.tenderlyMain]: ZERO_ADDRESS, + }, + ChainlinkAggregator: { + [eEthereumNetwork.coverage]: {}, + [eEthereumNetwork.hardhat]: {}, + [eEthereumNetwork.buidlerevm]: {}, + [eEthereumNetwork.kovan]: { + AAVE: '0xd04647B7CB523bb9f26730E9B6dE1174db7591Ad', + BAT: '0x0e4fcEC26c9f85c3D714370c98f43C4E02Fc35Ae', + BUSD: '0xbF7A18ea5DE0501f7559144e702b29c55b055CcB', + DAI: '0x22B58f1EbEDfCA50feF632bD73368b2FdA96D541', + ENJ: '0xfaDbe2ee798889F02d1d39eDaD98Eff4c7fe95D4', + KNC: '0xb8E8130d244CFd13a75D6B9Aee029B1C33c808A7', + LINK: '0x3Af8C569ab77af5230596Acf0E8c2F9351d24C38', + MANA: '0x1b93D8E109cfeDcBb3Cc74eD761DE286d5771511', + MKR: '0x0B156192e04bAD92B6C1C13cf8739d14D78D5701', + REN: '0xF1939BECE7708382b5fb5e559f630CB8B39a10ee', + SNX: '0xF9A76ae7a1075Fe7d646b06fF05Bd48b9FA5582e', + SUSD: '0xb343e7a1aF578FA35632435243D814e7497622f7', + TUSD: '0x7aeCF1c19661d12E962b69eBC8f6b2E63a55C660', + UNI: '0x17756515f112429471F86f98D5052aCB6C47f6ee', + USDC: '0x64EaC61A2DFda2c3Fa04eED49AA33D021AeC8838', + USDT: '0x0bF499444525a23E7Bb61997539725cA2e928138', + WBTC: '0xF7904a295A029a3aBDFFB6F12755974a958C7C25', + YFI: '0xC5d1B1DEb2992738C0273408ac43e1e906086B6C', + ZRX: '0xBc3f28Ccc21E9b5856E81E6372aFf57307E2E883', + USD: '0x9326BFA02ADD2366b30bacB125260Af641031331', + }, + [eEthereumNetwork.ropsten]: { + AAVE: ZERO_ADDRESS, + BAT: '0xafd8186c962daf599f171b8600f3e19af7b52c92', + BUSD: '0x0A32D96Ff131cd5c3E0E5AAB645BF009Eda61564', + DAI: '0x64b8e49baded7bfb2fd5a9235b2440c0ee02971b', + ENJ: ZERO_ADDRESS, + KNC: '0x19d97ceb36624a31d827032d8216dd2eb15e9845', + LINK: '0xb8c99b98913bE2ca4899CdcaF33a3e519C20EeEc', + MANA: '0xDab909dedB72573c626481fC98CEE1152b81DEC2', + MKR: '0x811B1f727F8F4aE899774B568d2e72916D91F392', + REN: ZERO_ADDRESS, + SNX: '0xA95674a8Ed9aa9D2E445eb0024a9aa05ab44f6bf', + SUSD: '0xe054b4aee7ac7645642dd52f1c892ff0128c98f0', + TUSD: '0x523ac85618df56e940534443125ef16daf785620', + UNI: ZERO_ADDRESS, + USDC: '0xe1480303dde539e2c241bdc527649f37c9cbef7d', + USDT: '0xc08fe0c4d97ccda6b40649c6da621761b628c288', + WBTC: '0x5b8B87A0abA4be247e660B0e0143bB30Cdf566AF', + YFI: ZERO_ADDRESS, + ZRX: '0x1d0052e4ae5b4ae4563cbac50edc3627ca0460d7', + USD: '0x8468b2bDCE073A157E560AA4D9CcF6dB1DB98507', + }, + [eEthereumNetwork.main]: { + AAVE: '0x6Df09E975c830ECae5bd4eD9d90f3A95a4f88012', + BAT: '0x0d16d4528239e9ee52fa531af613AcdB23D88c94', + BUSD: '0x614715d2Af89E6EC99A233818275142cE88d1Cfd', + DAI: '0x773616E4d11A78F511299002da57A0a94577F1f4', + ENJ: '0x24D9aB51950F3d62E9144fdC2f3135DAA6Ce8D1B', + KNC: '0x656c0544eF4C98A6a98491833A89204Abb045d6b', + LINK: '0xDC530D9457755926550b59e8ECcdaE7624181557', + MANA: '0x82A44D92D6c329826dc557c5E1Be6ebeC5D5FeB9', + MKR: '0x24551a8Fb2A7211A25a17B1481f043A8a8adC7f2', + REN: '0x3147D7203354Dc06D9fd350c7a2437bcA92387a4', + SNX: '0x79291A9d692Df95334B1a0B3B4AE6bC606782f8c', + SUSD: '0x8e0b7e6062272B5eF4524250bFFF8e5Bd3497757', + TUSD: '0x3886BA987236181D98F2401c507Fb8BeA7871dF2', + UNI: '0xD6aA3D25116d8dA79Ea0246c4826EB951872e02e', + USDC: '0x986b5E1e1755e3C2440e960477f25201B0a8bbD4', + USDT: '0xEe9F2375b4bdF6387aa8265dD4FB8F16512A1d46', + WBTC: '0xdeb288F737066589598e9214E782fa5A8eD689e8', + YFI: '0x7c5d4F8345e66f68099581Db340cd65B078C41f4', + ZRX: '0x2Da4983a622a8498bb1a21FaE9D8F6C664939962', + USD: '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419', + }, + [eEthereumNetwork.tenderlyMain]: { + AAVE: '0x6Df09E975c830ECae5bd4eD9d90f3A95a4f88012', + BAT: '0x0d16d4528239e9ee52fa531af613AcdB23D88c94', + BUSD: '0x614715d2Af89E6EC99A233818275142cE88d1Cfd', + DAI: '0x773616E4d11A78F511299002da57A0a94577F1f4', + ENJ: '0x24D9aB51950F3d62E9144fdC2f3135DAA6Ce8D1B', + KNC: '0x656c0544eF4C98A6a98491833A89204Abb045d6b', + LINK: '0xDC530D9457755926550b59e8ECcdaE7624181557', + MANA: '0x82A44D92D6c329826dc557c5E1Be6ebeC5D5FeB9', + MKR: '0x24551a8Fb2A7211A25a17B1481f043A8a8adC7f2', + REN: '0x3147D7203354Dc06D9fd350c7a2437bcA92387a4', + SNX: '0x79291A9d692Df95334B1a0B3B4AE6bC606782f8c', + SUSD: '0x8e0b7e6062272B5eF4524250bFFF8e5Bd3497757', + TUSD: '0x3886BA987236181D98F2401c507Fb8BeA7871dF2', + UNI: '0xD6aA3D25116d8dA79Ea0246c4826EB951872e02e', + USDC: '0x986b5E1e1755e3C2440e960477f25201B0a8bbD4', + USDT: '0xEe9F2375b4bdF6387aa8265dD4FB8F16512A1d46', + WBTC: '0xdeb288F737066589598e9214E782fa5A8eD689e8', + YFI: '0x7c5d4F8345e66f68099581Db340cd65B078C41f4', + ZRX: '0x2Da4983a622a8498bb1a21FaE9D8F6C664939962', + USD: '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419', + }, + }, + ReserveAssets: { + [eEthereumNetwork.coverage]: {}, + [eEthereumNetwork.hardhat]: {}, + [eEthereumNetwork.buidlerevm]: {}, + [eEthereumNetwork.main]: {}, + [eEthereumNetwork.kovan]: {}, + [eEthereumNetwork.ropsten]: {}, + [eEthereumNetwork.tenderlyMain]: {}, + }, + ReservesConfig: {}, + ATokenDomainSeparator: { + [eEthereumNetwork.coverage]: + '0x95b73a72c6ecf4ccbbba5178800023260bad8e75cdccdb8e4827a2977a37c820', + [eEthereumNetwork.hardhat]: + '0xbae024d959c6a022dc5ed37294cd39c141034b2ae5f02a955cce75c930a81bf5', + [eEthereumNetwork.buidlerevm]: + '0xbae024d959c6a022dc5ed37294cd39c141034b2ae5f02a955cce75c930a81bf5', + [eEthereumNetwork.kovan]: '', + [eEthereumNetwork.ropsten]: '', + [eEthereumNetwork.main]: '', + [eEthereumNetwork.tenderlyMain]: '', + }, + WETH: { + [eEthereumNetwork.coverage]: '', // deployed in local evm + [eEthereumNetwork.hardhat]: '', // deployed in local evm + [eEthereumNetwork.buidlerevm]: '', // deployed in local evm + [eEthereumNetwork.kovan]: '0xd0a1e359811322d97991e03f863a0c30c2cf029c', + [eEthereumNetwork.ropsten]: '0xc778417e063141139fce010982780140aa0cd5ab', + [eEthereumNetwork.main]: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + [eEthereumNetwork.tenderlyMain]: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + }, + ReserveFactorTreasuryAddress: { + [eEthereumNetwork.coverage]: '0x464c71f6c2f760dda6093dcb91c24c39e5d6e18c', + [eEthereumNetwork.hardhat]: '0x464c71f6c2f760dda6093dcb91c24c39e5d6e18c', + [eEthereumNetwork.buidlerevm]: '0x464c71f6c2f760dda6093dcb91c24c39e5d6e18c', + [eEthereumNetwork.kovan]: '0x464c71f6c2f760dda6093dcb91c24c39e5d6e18c', + [eEthereumNetwork.ropsten]: '0x464c71f6c2f760dda6093dcb91c24c39e5d6e18c', + [eEthereumNetwork.main]: '0x464c71f6c2f760dda6093dcb91c24c39e5d6e18c', + [eEthereumNetwork.tenderlyMain]: '0x464c71f6c2f760dda6093dcb91c24c39e5d6e18c', + }, +}; diff --git a/markets/aave-pro/index.ts b/markets/aave-pro/index.ts new file mode 100644 index 00000000..484fd99a --- /dev/null +++ b/markets/aave-pro/index.ts @@ -0,0 +1,56 @@ +import { IAaveProConfiguration, eEthereumNetwork } from '../../helpers/types'; + +import { CommonsConfig } from './commons'; +import { + strategyUSDC, + strategyUSDT, + strategyWBTC, + strategyWETH, +} from './reservesConfigs'; + +// ---------------- +// POOL--SPECIFIC PARAMS +// ---------------- + +export const AaveConfig: IAaveProConfiguration = { + ...CommonsConfig, + MarketId: 'Aave Pro market', + ProviderId: 1, + ReservesConfig: { + USDC: strategyUSDC, + USDT: strategyUSDT, + WBTC: strategyWBTC, + WETH: strategyWETH, + }, + ReserveAssets: { + [eEthereumNetwork.buidlerevm]: {}, + [eEthereumNetwork.hardhat]: {}, + [eEthereumNetwork.coverage]: {}, + [eEthereumNetwork.kovan]: { + USDC: '0xe22da380ee6B445bb8273C81944ADEB6E8450422', + USDT: '0x13512979ADE267AB5100878E2e0f485B568328a4', + WBTC: '0xD1B98B6607330172f1D991521145A22BCe793277', + WETH: '0xd0a1e359811322d97991e03f863a0c30c2cf029c', + }, + [eEthereumNetwork.ropsten]: { + USDC: '0x851dEf71f0e6A903375C1e536Bd9ff1684BAD802', + USDT: '0xB404c51BBC10dcBE948077F18a4B8E553D160084', + WBTC: '0xa0E54Ab6AA5f0bf1D62EC3526436F3c05b3348A0', + WETH: '0xc778417e063141139fce010982780140aa0cd5ab', + }, + [eEthereumNetwork.main]: { + USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + WBTC: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', + WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + }, + [eEthereumNetwork.tenderlyMain]: { + USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + WBTC: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', + WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + }, + }, +}; + +export default AaveConfig; diff --git a/markets/aave-pro/rateStrategies.ts b/markets/aave-pro/rateStrategies.ts new file mode 100644 index 00000000..47530b5c --- /dev/null +++ b/markets/aave-pro/rateStrategies.ts @@ -0,0 +1,38 @@ +import BigNumber from 'bignumber.js'; +import { oneRay } from '../../helpers/constants'; +import { IInterestRateStrategyParams } from '../../helpers/types'; + + +// USDC USDT +export const rateStrategyStable: IInterestRateStrategyParams = { + name: "rateStrategyStable", + optimalUtilizationRate: new BigNumber(0.9).multipliedBy(oneRay).toFixed(), + baseVariableBorrowRate: new BigNumber(0).multipliedBy(oneRay).toFixed(), + variableRateSlope1: new BigNumber(0.04).multipliedBy(oneRay).toFixed(), + variableRateSlope2: new BigNumber(0.60).multipliedBy(oneRay).toFixed(), + stableRateSlope1: new BigNumber(0.02).multipliedBy(oneRay).toFixed(), + stableRateSlope2: new BigNumber(0.60).multipliedBy(oneRay).toFixed(), +} + +// WETH +export const rateStrategyWETH: IInterestRateStrategyParams = { + name: "rateStrategyWETH", + optimalUtilizationRate: new BigNumber(0.65).multipliedBy(oneRay).toFixed(), + baseVariableBorrowRate: new BigNumber(0).multipliedBy(oneRay).toFixed(), + variableRateSlope1: new BigNumber(0.08).multipliedBy(oneRay).toFixed(), + variableRateSlope2: new BigNumber(1).multipliedBy(oneRay).toFixed(), + stableRateSlope1: new BigNumber(0.1).multipliedBy(oneRay).toFixed(), + stableRateSlope2: new BigNumber(1).multipliedBy(oneRay).toFixed(), +} + + +// WBTC +export const rateStrategyWBTC: IInterestRateStrategyParams = { + name: "rateStrategyWBTC", + optimalUtilizationRate: new BigNumber(0.65).multipliedBy(oneRay).toFixed(), + baseVariableBorrowRate: new BigNumber(0).multipliedBy(oneRay).toFixed(), + variableRateSlope1: new BigNumber(0.08).multipliedBy(oneRay).toFixed(), + variableRateSlope2: new BigNumber(3).multipliedBy(oneRay).toFixed(), + stableRateSlope1: new BigNumber(0.1).multipliedBy(oneRay).toFixed(), + stableRateSlope2: new BigNumber(3).multipliedBy(oneRay).toFixed(), +} \ No newline at end of file diff --git a/markets/aave-pro/reservesConfigs.ts b/markets/aave-pro/reservesConfigs.ts new file mode 100644 index 00000000..ad950062 --- /dev/null +++ b/markets/aave-pro/reservesConfigs.ts @@ -0,0 +1,59 @@ +import { eContractid, IReserveParams } from '../../helpers/types'; + +import { rateStrategyStable, rateStrategyWETH, rateStrategyWBTC } from './rateStrategies'; + +export const strategyUSDC: IReserveParams = { + strategy: rateStrategyStable, + baseLTVAsCollateral: '8000', + liquidationThreshold: '8500', + liquidationBonus: '10500', + borrowingEnabled: true, + stableBorrowRateEnabled: true, + reserveDecimals: '6', + aTokenImpl: eContractid.AToken, + stableDebtTokenImpl: eContractid.PermissionedStableDebtToken, + variableDebtTokenImpl: eContractid.PermissionedVariableDebtToken, + reserveFactor: '1000', +}; + +export const strategyUSDT: IReserveParams = { + strategy: rateStrategyStable, + baseLTVAsCollateral: '8000', + liquidationThreshold: '8500', + liquidationBonus: '10500', + borrowingEnabled: true, + stableBorrowRateEnabled: true, + reserveDecimals: '6', + aTokenImpl: eContractid.AToken, + stableDebtTokenImpl: eContractid.PermissionedStableDebtToken, + variableDebtTokenImpl: eContractid.PermissionedVariableDebtToken, + reserveFactor: '1000', +}; + +export const strategyWETH: IReserveParams = { + strategy: rateStrategyWETH, + baseLTVAsCollateral: '8000', + liquidationThreshold: '8250', + liquidationBonus: '10500', + borrowingEnabled: true, + stableBorrowRateEnabled: true, + reserveDecimals: '18', + aTokenImpl: eContractid.AToken, + stableDebtTokenImpl: eContractid.PermissionedStableDebtToken, + variableDebtTokenImpl: eContractid.PermissionedVariableDebtToken, + reserveFactor: '1000', +}; + +export const strategyWBTC: IReserveParams = { + strategy: rateStrategyWBTC, + baseLTVAsCollateral: '7000', + liquidationThreshold: '7500', + liquidationBonus: '11000', + borrowingEnabled: true, + stableBorrowRateEnabled: true, + reserveDecimals: '8', + aTokenImpl: eContractid.AToken, + stableDebtTokenImpl: eContractid.PermissionedStableDebtToken, + variableDebtTokenImpl: eContractid.PermissionedVariableDebtToken, + reserveFactor: '2000', +}; diff --git a/package.json b/package.json index dbeb8ce1..692e6cfa 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "aave:evm:dev:migration": "npm run compile && hardhat aave:dev", "aave:docker:full:migration": "npm run compile && npm run hardhat:docker -- aave:mainnet", "aave:kovan:full:migration": "npm run compile && npm run hardhat:kovan -- aave:mainnet --verify", + "pro:kovan:full:migration": "npm run compile && npm run hardhat:kovan -- pro:mainnet --verify", "matic:mumbai:full:migration": "npm run compile && npm run hardhat:mumbai matic:mainnet", "matic:matic:full:migration": "npm run compile && npm run hardhat:matic matic:mainnet", "amm:kovan:full:migration": "npm run compile && npm run hardhat:kovan -- amm:mainnet --verify", diff --git a/tasks/migrations/pro.mainnet.ts b/tasks/migrations/pro.mainnet.ts new file mode 100644 index 00000000..9afe20cc --- /dev/null +++ b/tasks/migrations/pro.mainnet.ts @@ -0,0 +1,60 @@ +import { task } from 'hardhat/config'; +import { checkVerification } from '../../helpers/etherscan-verification'; +import { ConfigNames } from '../../helpers/configuration'; +import { printContracts } from '../../helpers/misc-utils'; +import { usingTenderly } from '../../helpers/tenderly-utils'; + +task('pro:mainnet', 'Deploy development enviroment') + .addFlag('verify', 'Verify contracts at Etherscan') + .setAction(async ({ verify }, DRE) => { + const POOL_NAME = ConfigNames.AavePro; + await DRE.run('set-DRE'); + + // Prevent loss of gas verifying all the needed ENVs for Etherscan verification + if (verify) { + checkVerification(); + } + + console.log('Migration started\n'); + + + console.log('1. Deploy address provider'); + await DRE.run('full:deploy-address-provider', { pool: POOL_NAME }); + + console.log('2. Deploy permissions manager'); + await DRE.run('deploy-permission-manager', { pool: POOL_NAME }); + + console.log('3. Deploy lending pool'); + await DRE.run('full:deploy-lending-pool', { pool: POOL_NAME }); + + console.log('4. Deploy oracles'); + await DRE.run('full:deploy-oracles', { pool: POOL_NAME }); + + console.log('5. Deploy Data Provider'); + await DRE.run('full:data-provider', { pool: POOL_NAME }); + + console.log('6. Deploy WETH Gateway'); + await DRE.run('full-deploy-weth-gateway', { pool: POOL_NAME }); + + console.log('7. Initialize lending pool'); + await DRE.run('full:initialize-lending-pool', { pool: POOL_NAME }); + + if (verify) { + printContracts(); + console.log('7. Veryfing contracts'); + await DRE.run('verify:general', { all: true, pool: POOL_NAME }); + + console.log('8. Veryfing aTokens and debtTokens'); + await DRE.run('verify:tokens', { pool: POOL_NAME }); + } + + if (usingTenderly()) { + const postDeployHead = DRE.tenderlyRPC.getHead(); + const postDeployFork = DRE.tenderlyRPC.getFork(); + console.log('Tenderly Info'); + console.log('- Head', postDeployHead); + console.log('- Fork', postDeployFork); + } + console.log('\nFinished migrations'); + printContracts(); + }); diff --git a/test-suites/test-aave/permission-manager.spec.ts b/test-suites/test-aave/permission-manager.spec.ts new file mode 100644 index 00000000..93e3c902 --- /dev/null +++ b/test-suites/test-aave/permission-manager.spec.ts @@ -0,0 +1,168 @@ +import { expect } from 'chai'; +import { makeSuite, TestEnv } from './helpers/make-suite'; +import { deployContract } from '../../helpers/contracts-helpers'; + +import { PermissionManager } from '../../types'; + +makeSuite('Permission manager', (testEnv: TestEnv) => { + let permissionManager: PermissionManager; + const DEPOSITOR = 0, BORROWER = 1, LIQUIDATOR = 2; + + before('deploying a new Permission manager', async () => { + permissionManager = await deployContract('PermissionManager', []); + }); + + it('Adds user 0 as permission admin', async () => { + const { users } = testEnv; + + await permissionManager.addPermissionAdmins([users[0].address]); + + const isPermissionAdmin = await permissionManager.isPermissionsAdmin(users[0].address); + + expect(isPermissionAdmin).to.be.equal(true); + }); + + it('Registers a new depositor', async () => { + const { users } = testEnv; + + await permissionManager.connect(users[0].signer).addPermissions([DEPOSITOR], [users[0].address]); + + const isDepositor = await permissionManager.isInRole(users[0].address, DEPOSITOR); + const isBorrower = await permissionManager.isInRole(users[0].address, BORROWER); + const isLiquidator = await permissionManager.isInRole(users[0].address, LIQUIDATOR); + + expect(isDepositor).to.be.equal(true); + expect(isBorrower).to.be.equal(false); + expect(isLiquidator).to.be.equal(false); + }); + + it('Registers a new borrower', async () => { + const { users } = testEnv; + + await permissionManager.connect(users[0].signer).addPermissions([BORROWER], [users[0].address]); + + const isDepositor = await permissionManager.isInRole(users[0].address, DEPOSITOR); + const isBorrower = await permissionManager.isInRole(users[0].address, BORROWER); + const isLiquidator = await permissionManager.isInRole(users[0].address, LIQUIDATOR); + + expect(isDepositor).to.be.equal(true); + expect(isBorrower).to.be.equal(true); + expect(isLiquidator).to.be.equal(false); + }); + + it('Registers a new liquidator', async () => { + const { users } = testEnv; + + await permissionManager.connect(users[0].signer).addPermissions([LIQUIDATOR], [users[0].address]); + + const isDepositor = await permissionManager.isInRole(users[0].address, DEPOSITOR); + const isBorrower = await permissionManager.isInRole(users[0].address, BORROWER); + const isLiquidator = await permissionManager.isInRole(users[0].address, LIQUIDATOR); + + expect(isDepositor).to.be.equal(true); + expect(isBorrower).to.be.equal(true); + expect(isLiquidator).to.be.equal(true); + }); + + it('Checks getPermissions', async () => { + const { users } = testEnv; + + const { + 0: permissions, + } = await permissionManager.getAccountPermissions(users[0].address); + + const mappedPermissions = permissions.map(item => item.toString()); + + expect(mappedPermissions.indexOf(BORROWER.toString())).to.be.gte(0); + expect(mappedPermissions.indexOf(DEPOSITOR.toString())).to.be.gte(0); + expect(mappedPermissions.indexOf(LIQUIDATOR.toString())).to.be.gte(0); + }); + + it('Removes the depositor', async () => { + const { users } = testEnv; + + await permissionManager.connect(users[0].signer).removePermissions([DEPOSITOR], [users[0].address]); + + const isDepositor = await permissionManager.isInRole(users[0].address, DEPOSITOR); + const isBorrower = await permissionManager.isInRole(users[0].address, BORROWER); + const isLiquidator = await permissionManager.isInRole(users[0].address, LIQUIDATOR); + + expect(isDepositor).to.be.equal(false); + expect(isBorrower).to.be.equal(true); + expect(isLiquidator).to.be.equal(true); + }); + + it('Removes the borrower', async () => { + const { users } = testEnv; + + await permissionManager.connect(users[0].signer).removePermissions([BORROWER], [users[0].address]); + + const isDepositor = await permissionManager.isInRole(users[0].address, DEPOSITOR); + const isBorrower = await permissionManager.isInRole(users[0].address, BORROWER); + const isLiquidator = await permissionManager.isInRole(users[0].address, LIQUIDATOR); + + expect(isDepositor).to.be.equal(false); + expect(isBorrower).to.be.equal(false); + expect(isLiquidator).to.be.equal(true); + }); + + it('Removes the liquidator', async () => { + const { users } = testEnv; + + await permissionManager.connect(users[0].signer).removePermissions([LIQUIDATOR], [users[0].address]); + + const isDepositor = await permissionManager.isInRole(users[0].address, DEPOSITOR); + const isBorrower = await permissionManager.isInRole(users[0].address, BORROWER); + const isLiquidator = await permissionManager.isInRole(users[0].address, LIQUIDATOR); + + expect(isDepositor).to.be.equal(false); + expect(isBorrower).to.be.equal(false); + expect(isLiquidator).to.be.equal(false); + }); + + it('Checks getPermissions', async () => { + const { users } = testEnv; + + const { + 1: permissionsCount + } = await permissionManager.getAccountPermissions(users[0].address); + + + expect(permissionsCount).to.be.equal(0); + }); + + it('Checks that only the permissions manager can set permissions', async () => { + const { users } = testEnv; + + await expect(permissionManager.connect(users[1].signer).addPermissions([], [])).to.be + .reverted; + await expect(permissionManager.connect(users[1].signer).removePermissions([], [])).to.be + .reverted; + }); + + it('Checks that only the owner can add permissions admins', async () => { + const { users } = testEnv; + + await expect(permissionManager.connect(users[1].signer).addPermissionAdmins([])).to.be + .reverted; + await expect(permissionManager.connect(users[1].signer).addPermissionAdmins([])).to.be + .reverted; + }); + + + it('Add borrower role to user 0. Removes permission admin to user 0, check permission admin is removed and other permissions are not affected', async () => { + const { users } = testEnv; + + + await permissionManager.connect(users[0].signer).addPermissions([BORROWER], [users[0].address]); + + await permissionManager.removePermissionAdmins([users[0].address]); + + const isPermissionAdmin = await permissionManager.isPermissionsAdmin(users[0].address); + const isBorrower = await permissionManager.isInRole(users[0].address, BORROWER); + + + expect(isPermissionAdmin).to.be.equal(false); + expect(isBorrower).to.be.equal(true); + }); +});