From b4d72bfc374cf23dc93f8d01174a89709a2816f9 Mon Sep 17 00:00:00 2001 From: Zer0dot Date: Fri, 29 Jan 2021 22:51:59 -0500 Subject: [PATCH] Pulled contracts for light deployment --- contracts/adapters/BaseUniswapAdapter.sol | 534 ++++++++++++++++++ .../adapters/UniswapLiquiditySwapAdapter.sol | 280 +++++++++ contracts/adapters/UniswapRepayAdapter.sol | 259 +++++++++ .../interfaces/IBaseUniswapAdapter.sol | 90 +++ .../deployments/ATokensAndRatesHelper.sol | 103 ++-- .../StableAndVariableTokensHelper.sol | 27 +- contracts/interfaces/IAToken.sol | 9 +- contracts/interfaces/IERC20WithPermit.sol | 16 + contracts/interfaces/IInitializableAToken.sol | 32 ++ .../interfaces/IInitializableDebtToken.sol | 30 + .../interfaces/ILendingPoolConfigurator.sol | 176 ++++++ contracts/interfaces/IStableDebtToken.sol | 10 +- contracts/interfaces/IUniswapV2Router02.sol | 24 + contracts/interfaces/IVariableDebtToken.sol | 9 +- .../mocks/swap/MockUniswapV2Router02.sol | 106 ++++ contracts/mocks/upgradeability/MockAToken.sol | 32 +- .../upgradeability/MockStableDebtToken.sol | 11 - .../upgradeability/MockVariableDebtToken.sol | 17 - .../protocol/lendingpool/LendingPool.sol | 46 +- .../lendingpool/LendingPoolConfigurator.sol | 374 +++++------- .../lendingpool/LendingPoolStorage.sol | 6 + .../libraries/logic/ValidationLogic.sol | 9 +- contracts/protocol/tokenization/AToken.sol | 133 +++-- .../tokenization/DelegationAwareAToken.sol | 23 +- .../tokenization/IncentivizedERC20.sol | 28 +- .../protocol/tokenization/StableDebtToken.sol | 78 ++- .../tokenization/VariableDebtToken.sol | 74 ++- .../tokenization/base/DebtTokenBase.sol | 46 +- 28 files changed, 2051 insertions(+), 531 deletions(-) create mode 100644 contracts/adapters/BaseUniswapAdapter.sol create mode 100644 contracts/adapters/UniswapLiquiditySwapAdapter.sol create mode 100644 contracts/adapters/UniswapRepayAdapter.sol create mode 100644 contracts/adapters/interfaces/IBaseUniswapAdapter.sol create mode 100644 contracts/interfaces/IERC20WithPermit.sol create mode 100644 contracts/interfaces/IInitializableAToken.sol create mode 100644 contracts/interfaces/IInitializableDebtToken.sol create mode 100644 contracts/interfaces/ILendingPoolConfigurator.sol create mode 100644 contracts/interfaces/IUniswapV2Router02.sol create mode 100644 contracts/mocks/swap/MockUniswapV2Router02.sol diff --git a/contracts/adapters/BaseUniswapAdapter.sol b/contracts/adapters/BaseUniswapAdapter.sol new file mode 100644 index 00000000..b04bc8a1 --- /dev/null +++ b/contracts/adapters/BaseUniswapAdapter.sol @@ -0,0 +1,534 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + +import {PercentageMath} from '../protocol/libraries/math/PercentageMath.sol'; +import {SafeMath} from '../dependencies/openzeppelin/contracts/SafeMath.sol'; +import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; +import {IERC20Detailed} from '../dependencies/openzeppelin/contracts/IERC20Detailed.sol'; +import {SafeERC20} from '../dependencies/openzeppelin/contracts/SafeERC20.sol'; +import {Ownable} from '../dependencies/openzeppelin/contracts/Ownable.sol'; +import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol'; +import {DataTypes} from '../protocol/libraries/types/DataTypes.sol'; +import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol'; +import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol'; +import {IERC20WithPermit} from '../interfaces/IERC20WithPermit.sol'; +import {FlashLoanReceiverBase} from '../flashloan/base/FlashLoanReceiverBase.sol'; +import {IBaseUniswapAdapter} from './interfaces/IBaseUniswapAdapter.sol'; + +/** + * @title BaseUniswapAdapter + * @notice Implements the logic for performing assets swaps in Uniswap V2 + * @author Aave + **/ +abstract contract BaseUniswapAdapter is FlashLoanReceiverBase, IBaseUniswapAdapter, Ownable { + using SafeMath for uint256; + using PercentageMath for uint256; + using SafeERC20 for IERC20; + + // Max slippage percent allowed + uint256 public constant override MAX_SLIPPAGE_PERCENT = 3000; // 30% + // FLash Loan fee set in lending pool + uint256 public constant override FLASHLOAN_PREMIUM_TOTAL = 9; + // USD oracle asset address + address public constant override USD_ADDRESS = 0x10F7Fc1F91Ba351f9C629c5947AD69bD03C05b96; + + address public immutable override WETH_ADDRESS; + IPriceOracleGetter public immutable override ORACLE; + IUniswapV2Router02 public immutable override UNISWAP_ROUTER; + + constructor( + ILendingPoolAddressesProvider addressesProvider, + IUniswapV2Router02 uniswapRouter, + address wethAddress + ) public FlashLoanReceiverBase(addressesProvider) { + ORACLE = IPriceOracleGetter(addressesProvider.getPriceOracle()); + UNISWAP_ROUTER = uniswapRouter; + WETH_ADDRESS = wethAddress; + } + + /** + * @dev Given an input asset amount, returns the maximum output amount of the other asset and the prices + * @param amountIn Amount of reserveIn + * @param reserveIn Address of the asset to be swap from + * @param reserveOut Address of the asset to be swap to + * @return uint256 Amount out of the reserveOut + * @return uint256 The price of out amount denominated in the reserveIn currency (18 decimals) + * @return uint256 In amount of reserveIn value denominated in USD (8 decimals) + * @return uint256 Out amount of reserveOut value denominated in USD (8 decimals) + */ + function getAmountsOut( + uint256 amountIn, + address reserveIn, + address reserveOut + ) + external + view + override + returns ( + uint256, + uint256, + uint256, + uint256, + address[] memory + ) + { + AmountCalc memory results = _getAmountsOutData(reserveIn, reserveOut, amountIn); + + return ( + results.calculatedAmount, + results.relativePrice, + results.amountInUsd, + results.amountOutUsd, + results.path + ); + } + + /** + * @dev Returns the minimum input asset amount required to buy the given output asset amount and the prices + * @param amountOut Amount of reserveOut + * @param reserveIn Address of the asset to be swap from + * @param reserveOut Address of the asset to be swap to + * @return uint256 Amount in of the reserveIn + * @return uint256 The price of in amount denominated in the reserveOut currency (18 decimals) + * @return uint256 In amount of reserveIn value denominated in USD (8 decimals) + * @return uint256 Out amount of reserveOut value denominated in USD (8 decimals) + */ + function getAmountsIn( + uint256 amountOut, + address reserveIn, + address reserveOut + ) + external + view + override + returns ( + uint256, + uint256, + uint256, + uint256, + address[] memory + ) + { + AmountCalc memory results = _getAmountsInData(reserveIn, reserveOut, amountOut); + + return ( + results.calculatedAmount, + results.relativePrice, + results.amountInUsd, + results.amountOutUsd, + results.path + ); + } + + /** + * @dev Swaps an exact `amountToSwap` of an asset to another + * @param assetToSwapFrom Origin asset + * @param assetToSwapTo Destination asset + * @param amountToSwap Exact amount of `assetToSwapFrom` to be swapped + * @param minAmountOut the min amount of `assetToSwapTo` to be received from the swap + * @return the amount received from the swap + */ + function _swapExactTokensForTokens( + address assetToSwapFrom, + address assetToSwapTo, + uint256 amountToSwap, + uint256 minAmountOut, + bool useEthPath + ) internal returns (uint256) { + uint256 fromAssetDecimals = _getDecimals(assetToSwapFrom); + uint256 toAssetDecimals = _getDecimals(assetToSwapTo); + + uint256 fromAssetPrice = _getPrice(assetToSwapFrom); + uint256 toAssetPrice = _getPrice(assetToSwapTo); + + uint256 expectedMinAmountOut = + amountToSwap + .mul(fromAssetPrice.mul(10**toAssetDecimals)) + .div(toAssetPrice.mul(10**fromAssetDecimals)) + .percentMul(PercentageMath.PERCENTAGE_FACTOR.sub(MAX_SLIPPAGE_PERCENT)); + + require(expectedMinAmountOut < minAmountOut, 'minAmountOut exceed max slippage'); + + // Approves the transfer for the swap. Approves for 0 first to comply with tokens that implement the anti frontrunning approval fix. + IERC20(assetToSwapFrom).safeApprove(address(UNISWAP_ROUTER), 0); + IERC20(assetToSwapFrom).safeApprove(address(UNISWAP_ROUTER), amountToSwap); + + address[] memory path; + if (useEthPath) { + path = new address[](3); + path[0] = assetToSwapFrom; + path[1] = WETH_ADDRESS; + path[2] = assetToSwapTo; + } else { + path = new address[](2); + path[0] = assetToSwapFrom; + path[1] = assetToSwapTo; + } + uint256[] memory amounts = + UNISWAP_ROUTER.swapExactTokensForTokens( + amountToSwap, + minAmountOut, + path, + address(this), + block.timestamp + ); + + emit Swapped(assetToSwapFrom, assetToSwapTo, amounts[0], amounts[amounts.length - 1]); + + return amounts[amounts.length - 1]; + } + + /** + * @dev Receive an exact amount `amountToReceive` of `assetToSwapTo` tokens for as few `assetToSwapFrom` tokens as + * possible. + * @param assetToSwapFrom Origin asset + * @param assetToSwapTo Destination asset + * @param maxAmountToSwap Max amount of `assetToSwapFrom` allowed to be swapped + * @param amountToReceive Exact amount of `assetToSwapTo` to receive + * @return the amount swapped + */ + function _swapTokensForExactTokens( + address assetToSwapFrom, + address assetToSwapTo, + uint256 maxAmountToSwap, + uint256 amountToReceive, + bool useEthPath + ) internal returns (uint256) { + uint256 fromAssetDecimals = _getDecimals(assetToSwapFrom); + uint256 toAssetDecimals = _getDecimals(assetToSwapTo); + + uint256 fromAssetPrice = _getPrice(assetToSwapFrom); + uint256 toAssetPrice = _getPrice(assetToSwapTo); + + uint256 expectedMaxAmountToSwap = + amountToReceive + .mul(toAssetPrice.mul(10**fromAssetDecimals)) + .div(fromAssetPrice.mul(10**toAssetDecimals)) + .percentMul(PercentageMath.PERCENTAGE_FACTOR.add(MAX_SLIPPAGE_PERCENT)); + + require(maxAmountToSwap < expectedMaxAmountToSwap, 'maxAmountToSwap exceed max slippage'); + + // Approves the transfer for the swap. Approves for 0 first to comply with tokens that implement the anti frontrunning approval fix. + IERC20(assetToSwapFrom).safeApprove(address(UNISWAP_ROUTER), 0); + IERC20(assetToSwapFrom).safeApprove(address(UNISWAP_ROUTER), maxAmountToSwap); + + address[] memory path; + if (useEthPath) { + path = new address[](3); + path[0] = assetToSwapFrom; + path[1] = WETH_ADDRESS; + path[2] = assetToSwapTo; + } else { + path = new address[](2); + path[0] = assetToSwapFrom; + path[1] = assetToSwapTo; + } + + uint256[] memory amounts = + UNISWAP_ROUTER.swapTokensForExactTokens( + amountToReceive, + maxAmountToSwap, + path, + address(this), + block.timestamp + ); + + emit Swapped(assetToSwapFrom, assetToSwapTo, amounts[0], amounts[amounts.length - 1]); + + return amounts[0]; + } + + /** + * @dev Get the price of the asset from the oracle denominated in eth + * @param asset address + * @return eth price for the asset + */ + function _getPrice(address asset) internal view returns (uint256) { + return ORACLE.getAssetPrice(asset); + } + + /** + * @dev Get the decimals of an asset + * @return number of decimals of the asset + */ + function _getDecimals(address asset) internal view returns (uint256) { + return IERC20Detailed(asset).decimals(); + } + + /** + * @dev Get the aToken associated to the asset + * @return address of the aToken + */ + function _getReserveData(address asset) internal view returns (DataTypes.ReserveData memory) { + return LENDING_POOL.getReserveData(asset); + } + + /** + * @dev Pull the ATokens from the user + * @param reserve address of the asset + * @param reserveAToken address of the aToken of the reserve + * @param user address + * @param amount of tokens to be transferred to the contract + * @param permitSignature struct containing the permit signature + */ + function _pullAToken( + address reserve, + address reserveAToken, + address user, + uint256 amount, + PermitSignature memory permitSignature + ) internal { + if (_usePermit(permitSignature)) { + IERC20WithPermit(reserveAToken).permit( + user, + address(this), + permitSignature.amount, + permitSignature.deadline, + permitSignature.v, + permitSignature.r, + permitSignature.s + ); + } + + // transfer from user to adapter + IERC20(reserveAToken).safeTransferFrom(user, address(this), amount); + + // withdraw reserve + LENDING_POOL.withdraw(reserve, amount, address(this)); + } + + /** + * @dev Tells if the permit method should be called by inspecting if there is a valid signature. + * If signature params are set to 0, then permit won't be called. + * @param signature struct containing the permit signature + * @return whether or not permit should be called + */ + function _usePermit(PermitSignature memory signature) internal pure returns (bool) { + return + !(uint256(signature.deadline) == uint256(signature.v) && uint256(signature.deadline) == 0); + } + + /** + * @dev Calculates the value denominated in USD + * @param reserve Address of the reserve + * @param amount Amount of the reserve + * @param decimals Decimals of the reserve + * @return whether or not permit should be called + */ + function _calcUsdValue( + address reserve, + uint256 amount, + uint256 decimals + ) internal view returns (uint256) { + uint256 ethUsdPrice = _getPrice(USD_ADDRESS); + uint256 reservePrice = _getPrice(reserve); + + return amount.mul(reservePrice).div(10**decimals).mul(ethUsdPrice).div(10**18); + } + + /** + * @dev Given an input asset amount, returns the maximum output amount of the other asset + * @param reserveIn Address of the asset to be swap from + * @param reserveOut Address of the asset to be swap to + * @param amountIn Amount of reserveIn + * @return Struct containing the following information: + * uint256 Amount out of the reserveOut + * uint256 The price of out amount denominated in the reserveIn currency (18 decimals) + * uint256 In amount of reserveIn value denominated in USD (8 decimals) + * uint256 Out amount of reserveOut value denominated in USD (8 decimals) + */ + function _getAmountsOutData( + address reserveIn, + address reserveOut, + uint256 amountIn + ) internal view returns (AmountCalc memory) { + // Subtract flash loan fee + uint256 finalAmountIn = amountIn.sub(amountIn.mul(FLASHLOAN_PREMIUM_TOTAL).div(10000)); + + address[] memory simplePath = new address[](2); + simplePath[0] = reserveIn; + simplePath[1] = reserveOut; + + uint256[] memory amountsWithoutWeth; + uint256[] memory amountsWithWeth; + + address[] memory pathWithWeth = new address[](3); + if (reserveIn != WETH_ADDRESS && reserveOut != WETH_ADDRESS) { + pathWithWeth[0] = reserveIn; + pathWithWeth[1] = WETH_ADDRESS; + pathWithWeth[2] = reserveOut; + + try UNISWAP_ROUTER.getAmountsOut(finalAmountIn, pathWithWeth) returns ( + uint256[] memory resultsWithWeth + ) { + amountsWithWeth = resultsWithWeth; + } catch { + amountsWithWeth = new uint256[](3); + } + } else { + amountsWithWeth = new uint256[](3); + } + + uint256 bestAmountOut; + try UNISWAP_ROUTER.getAmountsOut(finalAmountIn, simplePath) returns ( + uint256[] memory resultAmounts + ) { + amountsWithoutWeth = resultAmounts; + + bestAmountOut = (amountsWithWeth[2] > amountsWithoutWeth[1]) + ? amountsWithWeth[2] + : amountsWithoutWeth[1]; + } catch { + amountsWithoutWeth = new uint256[](2); + bestAmountOut = amountsWithWeth[2]; + } + + uint256 reserveInDecimals = _getDecimals(reserveIn); + uint256 reserveOutDecimals = _getDecimals(reserveOut); + + uint256 outPerInPrice = + finalAmountIn.mul(10**18).mul(10**reserveOutDecimals).div( + bestAmountOut.mul(10**reserveInDecimals) + ); + + return + AmountCalc( + bestAmountOut, + outPerInPrice, + _calcUsdValue(reserveIn, amountIn, reserveInDecimals), + _calcUsdValue(reserveOut, bestAmountOut, reserveOutDecimals), + (bestAmountOut == 0) ? new address[](2) : (bestAmountOut == amountsWithoutWeth[1]) + ? simplePath + : pathWithWeth + ); + } + + /** + * @dev Returns the minimum input asset amount required to buy the given output asset amount + * @param reserveIn Address of the asset to be swap from + * @param reserveOut Address of the asset to be swap to + * @param amountOut Amount of reserveOut + * @return Struct containing the following information: + * uint256 Amount in of the reserveIn + * uint256 The price of in amount denominated in the reserveOut currency (18 decimals) + * uint256 In amount of reserveIn value denominated in USD (8 decimals) + * uint256 Out amount of reserveOut value denominated in USD (8 decimals) + */ + function _getAmountsInData( + address reserveIn, + address reserveOut, + uint256 amountOut + ) internal view returns (AmountCalc memory) { + (uint256[] memory amounts, address[] memory path) = + _getAmountsInAndPath(reserveIn, reserveOut, amountOut); + + // Add flash loan fee + uint256 finalAmountIn = amounts[0].add(amounts[0].mul(FLASHLOAN_PREMIUM_TOTAL).div(10000)); + + uint256 reserveInDecimals = _getDecimals(reserveIn); + uint256 reserveOutDecimals = _getDecimals(reserveOut); + + uint256 inPerOutPrice = + amountOut.mul(10**18).mul(10**reserveInDecimals).div( + finalAmountIn.mul(10**reserveOutDecimals) + ); + + return + AmountCalc( + finalAmountIn, + inPerOutPrice, + _calcUsdValue(reserveIn, finalAmountIn, reserveInDecimals), + _calcUsdValue(reserveOut, amountOut, reserveOutDecimals), + path + ); + } + + /** + * @dev Calculates the input asset amount required to buy the given output asset amount + * @param reserveIn Address of the asset to be swap from + * @param reserveOut Address of the asset to be swap to + * @param amountOut Amount of reserveOut + * @return uint256[] amounts Array containing the amountIn and amountOut for a swap + */ + function _getAmountsInAndPath( + address reserveIn, + address reserveOut, + uint256 amountOut + ) internal view returns (uint256[] memory, address[] memory) { + address[] memory simplePath = new address[](2); + simplePath[0] = reserveIn; + simplePath[1] = reserveOut; + + uint256[] memory amountsWithoutWeth; + uint256[] memory amountsWithWeth; + address[] memory pathWithWeth = new address[](3); + + if (reserveIn != WETH_ADDRESS && reserveOut != WETH_ADDRESS) { + pathWithWeth[0] = reserveIn; + pathWithWeth[1] = WETH_ADDRESS; + pathWithWeth[2] = reserveOut; + + try UNISWAP_ROUTER.getAmountsIn(amountOut, pathWithWeth) returns ( + uint256[] memory resultsWithWeth + ) { + amountsWithWeth = resultsWithWeth; + } catch { + amountsWithWeth = new uint256[](3); + } + } else { + amountsWithWeth = new uint256[](3); + } + + try UNISWAP_ROUTER.getAmountsIn(amountOut, simplePath) returns ( + uint256[] memory resultAmounts + ) { + amountsWithoutWeth = resultAmounts; + + return + (amountsWithWeth[0] < amountsWithoutWeth[0] && amountsWithWeth[0] != 0) + ? (amountsWithWeth, pathWithWeth) + : (amountsWithoutWeth, simplePath); + } catch { + return (amountsWithWeth, pathWithWeth); + } + } + + /** + * @dev Calculates the input asset amount required to buy the given output asset amount + * @param reserveIn Address of the asset to be swap from + * @param reserveOut Address of the asset to be swap to + * @param amountOut Amount of reserveOut + * @return uint256[] amounts Array containing the amountIn and amountOut for a swap + */ + function _getAmountsIn( + address reserveIn, + address reserveOut, + uint256 amountOut, + bool useEthPath + ) internal view returns (uint256[] memory) { + address[] memory path; + + if (useEthPath) { + path = new address[](3); + path[0] = reserveIn; + path[1] = WETH_ADDRESS; + path[2] = reserveOut; + } else { + path = new address[](2); + path[0] = reserveIn; + path[1] = reserveOut; + } + + return UNISWAP_ROUTER.getAmountsIn(amountOut, path); + } + + /** + * @dev Emergency rescue for token stucked on this contract, as failsafe mechanism + * - Funds should never remain in this contract more time than during transactions + * - Only callable by the owner + **/ + function rescueTokens(IERC20 token) external onlyOwner { + token.transfer(owner(), token.balanceOf(address(this))); + } +} diff --git a/contracts/adapters/UniswapLiquiditySwapAdapter.sol b/contracts/adapters/UniswapLiquiditySwapAdapter.sol new file mode 100644 index 00000000..44745ad5 --- /dev/null +++ b/contracts/adapters/UniswapLiquiditySwapAdapter.sol @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + +import {BaseUniswapAdapter} from './BaseUniswapAdapter.sol'; +import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol'; +import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol'; +import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; + +/** + * @title UniswapLiquiditySwapAdapter + * @notice Uniswap V2 Adapter to swap liquidity. + * @author Aave + **/ +contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter { + struct PermitParams { + uint256[] amount; + uint256[] deadline; + uint8[] v; + bytes32[] r; + bytes32[] s; + } + + struct SwapParams { + address[] assetToSwapToList; + uint256[] minAmountsToReceive; + bool[] swapAllBalance; + PermitParams permitParams; + bool[] useEthPath; + } + + constructor( + ILendingPoolAddressesProvider addressesProvider, + IUniswapV2Router02 uniswapRouter, + address wethAddress + ) public BaseUniswapAdapter(addressesProvider, uniswapRouter, wethAddress) {} + + /** + * @dev Swaps the received reserve amount from the flash loan into the asset specified in the params. + * The received funds from the swap are then deposited into the protocol on behalf of the user. + * The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset and + * repay the flash loan. + * @param assets Address of asset to be swapped + * @param amounts Amount of the asset to be swapped + * @param premiums Fee of the flash loan + * @param initiator Address of the user + * @param params Additional variadic field to include extra params. Expected parameters: + * address[] assetToSwapToList List of the addresses of the reserve to be swapped to and deposited + * uint256[] minAmountsToReceive List of min amounts to be received from the swap + * bool[] swapAllBalance Flag indicating if all the user balance should be swapped + * uint256[] permitAmount List of amounts for the permit signature + * uint256[] deadline List of deadlines for the permit signature + * uint8[] v List of v param for the permit signature + * bytes32[] r List of r param for the permit signature + * bytes32[] s List of s param for the permit signature + */ + function executeOperation( + address[] calldata assets, + uint256[] calldata amounts, + uint256[] calldata premiums, + address initiator, + bytes calldata params + ) external override returns (bool) { + require(msg.sender == address(LENDING_POOL), 'CALLER_MUST_BE_LENDING_POOL'); + + SwapParams memory decodedParams = _decodeParams(params); + + require( + assets.length == decodedParams.assetToSwapToList.length && + assets.length == decodedParams.minAmountsToReceive.length && + assets.length == decodedParams.swapAllBalance.length && + assets.length == decodedParams.permitParams.amount.length && + assets.length == decodedParams.permitParams.deadline.length && + assets.length == decodedParams.permitParams.v.length && + assets.length == decodedParams.permitParams.r.length && + assets.length == decodedParams.permitParams.s.length && + assets.length == decodedParams.useEthPath.length, + 'INCONSISTENT_PARAMS' + ); + + for (uint256 i = 0; i < assets.length; i++) { + _swapLiquidity( + assets[i], + decodedParams.assetToSwapToList[i], + amounts[i], + premiums[i], + initiator, + decodedParams.minAmountsToReceive[i], + decodedParams.swapAllBalance[i], + PermitSignature( + decodedParams.permitParams.amount[i], + decodedParams.permitParams.deadline[i], + decodedParams.permitParams.v[i], + decodedParams.permitParams.r[i], + decodedParams.permitParams.s[i] + ), + decodedParams.useEthPath[i] + ); + } + + return true; + } + + struct SwapAndDepositLocalVars { + uint256 i; + uint256 aTokenInitiatorBalance; + uint256 amountToSwap; + uint256 receivedAmount; + address aToken; + } + + /** + * @dev Swaps an amount of an asset to another and deposits the new asset amount on behalf of the user without using + * a flash loan. This method can be used when the temporary transfer of the collateral asset to this contract + * does not affect the user position. + * The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset and + * perform the swap. + * @param assetToSwapFromList List of addresses of the underlying asset to be swap from + * @param assetToSwapToList List of addresses of the underlying asset to be swap to and deposited + * @param amountToSwapList List of amounts to be swapped. If the amount exceeds the balance, the total balance is used for the swap + * @param minAmountsToReceive List of min amounts to be received from the swap + * @param permitParams List of struct containing the permit signatures + * uint256 permitAmount Amount for the permit signature + * uint256 deadline Deadline for the permit signature + * uint8 v param for the permit signature + * bytes32 r param for the permit signature + * bytes32 s param for the permit signature + * @param useEthPath true if the swap needs to occur using ETH in the routing, false otherwise + */ + function swapAndDeposit( + address[] calldata assetToSwapFromList, + address[] calldata assetToSwapToList, + uint256[] calldata amountToSwapList, + uint256[] calldata minAmountsToReceive, + PermitSignature[] calldata permitParams, + bool[] calldata useEthPath + ) external { + require( + assetToSwapFromList.length == assetToSwapToList.length && + assetToSwapFromList.length == amountToSwapList.length && + assetToSwapFromList.length == minAmountsToReceive.length && + assetToSwapFromList.length == permitParams.length, + 'INCONSISTENT_PARAMS' + ); + + SwapAndDepositLocalVars memory vars; + + for (vars.i = 0; vars.i < assetToSwapFromList.length; vars.i++) { + vars.aToken = _getReserveData(assetToSwapFromList[vars.i]).aTokenAddress; + + vars.aTokenInitiatorBalance = IERC20(vars.aToken).balanceOf(msg.sender); + vars.amountToSwap = amountToSwapList[vars.i] > vars.aTokenInitiatorBalance + ? vars.aTokenInitiatorBalance + : amountToSwapList[vars.i]; + + _pullAToken( + assetToSwapFromList[vars.i], + vars.aToken, + msg.sender, + vars.amountToSwap, + permitParams[vars.i] + ); + + vars.receivedAmount = _swapExactTokensForTokens( + assetToSwapFromList[vars.i], + assetToSwapToList[vars.i], + vars.amountToSwap, + minAmountsToReceive[vars.i], + useEthPath[vars.i] + ); + + // Deposit new reserve + IERC20(assetToSwapToList[vars.i]).safeApprove(address(LENDING_POOL), 0); + IERC20(assetToSwapToList[vars.i]).safeApprove(address(LENDING_POOL), vars.receivedAmount); + LENDING_POOL.deposit(assetToSwapToList[vars.i], vars.receivedAmount, msg.sender, 0); + } + } + + /** + * @dev Swaps an `amountToSwap` of an asset to another and deposits the funds on behalf of the initiator. + * @param assetFrom Address of the underlying asset to be swap from + * @param assetTo Address of the underlying asset to be swap to and deposited + * @param amount Amount from flash loan + * @param premium Premium of the flash loan + * @param minAmountToReceive Min amount to be received from the swap + * @param swapAllBalance Flag indicating if all the user balance should be swapped + * @param permitSignature List of struct containing the permit signature + * @param useEthPath true if the swap needs to occur using ETH in the routing, false otherwise + */ + + struct SwapLiquidityLocalVars { + address aToken; + uint256 aTokenInitiatorBalance; + uint256 amountToSwap; + uint256 receivedAmount; + uint256 flashLoanDebt; + uint256 amountToPull; + } + + function _swapLiquidity( + address assetFrom, + address assetTo, + uint256 amount, + uint256 premium, + address initiator, + uint256 minAmountToReceive, + bool swapAllBalance, + PermitSignature memory permitSignature, + bool useEthPath + ) internal { + + SwapLiquidityLocalVars memory vars; + + vars.aToken = _getReserveData(assetFrom).aTokenAddress; + + vars.aTokenInitiatorBalance = IERC20(vars.aToken).balanceOf(initiator); + vars.amountToSwap = + swapAllBalance && vars.aTokenInitiatorBalance.sub(premium) <= amount + ? vars.aTokenInitiatorBalance.sub(premium) + : amount; + + vars.receivedAmount = + _swapExactTokensForTokens(assetFrom, assetTo, vars.amountToSwap, minAmountToReceive, useEthPath); + + // Deposit new reserve + IERC20(assetTo).safeApprove(address(LENDING_POOL), 0); + IERC20(assetTo).safeApprove(address(LENDING_POOL), vars.receivedAmount); + LENDING_POOL.deposit(assetTo, vars.receivedAmount, initiator, 0); + + vars.flashLoanDebt = amount.add(premium); + vars.amountToPull = vars.amountToSwap.add(premium); + + _pullAToken(assetFrom, vars.aToken, initiator, vars.amountToPull, permitSignature); + + // Repay flash loan + IERC20(assetFrom).safeApprove(address(LENDING_POOL), 0); + IERC20(assetFrom).safeApprove(address(LENDING_POOL), vars.flashLoanDebt); + } + + /** + * @dev Decodes the information encoded in the flash loan params + * @param params Additional variadic field to include extra params. Expected parameters: + * address[] assetToSwapToList List of the addresses of the reserve to be swapped to and deposited + * uint256[] minAmountsToReceive List of min amounts to be received from the swap + * bool[] swapAllBalance Flag indicating if all the user balance should be swapped + * uint256[] permitAmount List of amounts for the permit signature + * uint256[] deadline List of deadlines for the permit signature + * uint8[] v List of v param for the permit signature + * bytes32[] r List of r param for the permit signature + * bytes32[] s List of s param for the permit signature + * bool[] useEthPath true if the swap needs to occur using ETH in the routing, false otherwise + * @return SwapParams struct containing decoded params + */ + function _decodeParams(bytes memory params) internal pure returns (SwapParams memory) { + ( + address[] memory assetToSwapToList, + uint256[] memory minAmountsToReceive, + bool[] memory swapAllBalance, + uint256[] memory permitAmount, + uint256[] memory deadline, + uint8[] memory v, + bytes32[] memory r, + bytes32[] memory s, + bool[] memory useEthPath + ) = + abi.decode( + params, + (address[], uint256[], bool[], uint256[], uint256[], uint8[], bytes32[], bytes32[], bool[]) + ); + + return + SwapParams( + assetToSwapToList, + minAmountsToReceive, + swapAllBalance, + PermitParams(permitAmount, deadline, v, r, s), + useEthPath + ); + } +} \ No newline at end of file diff --git a/contracts/adapters/UniswapRepayAdapter.sol b/contracts/adapters/UniswapRepayAdapter.sol new file mode 100644 index 00000000..c4e7817e --- /dev/null +++ b/contracts/adapters/UniswapRepayAdapter.sol @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + +import {BaseUniswapAdapter} from './BaseUniswapAdapter.sol'; +import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol'; +import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol'; +import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; +import {DataTypes} from '../protocol/libraries/types/DataTypes.sol'; + +/** + * @title UniswapRepayAdapter + * @notice Uniswap V2 Adapter to perform a repay of a debt with collateral. + * @author Aave + **/ +contract UniswapRepayAdapter is BaseUniswapAdapter { + struct RepayParams { + address collateralAsset; + uint256 collateralAmount; + uint256 rateMode; + PermitSignature permitSignature; + bool useEthPath; + } + + constructor( + ILendingPoolAddressesProvider addressesProvider, + IUniswapV2Router02 uniswapRouter, + address wethAddress + ) public BaseUniswapAdapter(addressesProvider, uniswapRouter, wethAddress) {} + + /** + * @dev Uses the received funds from the flash loan to repay a debt on the protocol on behalf of the user. Then pulls + * the collateral from the user and swaps it to the debt asset to repay the flash loan. + * The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset, swap it + * and repay the flash loan. + * Supports only one asset on the flash loan. + * @param assets Address of debt asset + * @param amounts Amount of the debt to be repaid + * @param premiums Fee of the flash loan + * @param initiator Address of the user + * @param params Additional variadic field to include extra params. Expected parameters: + * address collateralAsset Address of the reserve to be swapped + * uint256 collateralAmount Amount of reserve to be swapped + * uint256 rateMode Rate modes of the debt to be repaid + * uint256 permitAmount Amount for the permit signature + * uint256 deadline Deadline for the permit signature + * uint8 v V param for the permit signature + * bytes32 r R param for the permit signature + * bytes32 s S param for the permit signature + */ + function executeOperation( + address[] calldata assets, + uint256[] calldata amounts, + uint256[] calldata premiums, + address initiator, + bytes calldata params + ) external override returns (bool) { + require(msg.sender == address(LENDING_POOL), 'CALLER_MUST_BE_LENDING_POOL'); + + RepayParams memory decodedParams = _decodeParams(params); + + _swapAndRepay( + decodedParams.collateralAsset, + assets[0], + amounts[0], + decodedParams.collateralAmount, + decodedParams.rateMode, + initiator, + premiums[0], + decodedParams.permitSignature, + decodedParams.useEthPath + ); + + return true; + } + + /** + * @dev Swaps the user collateral for the debt asset and then repay the debt on the protocol on behalf of the user + * without using flash loans. This method can be used when the temporary transfer of the collateral asset to this + * contract does not affect the user position. + * The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset + * @param collateralAsset Address of asset to be swapped + * @param debtAsset Address of debt asset + * @param collateralAmount Amount of the collateral to be swapped + * @param debtRepayAmount Amount of the debt to be repaid + * @param debtRateMode Rate mode of the debt to be repaid + * @param permitSignature struct containing the permit signature + * @param useEthPath struct containing the permit signature + + */ + function swapAndRepay( + address collateralAsset, + address debtAsset, + uint256 collateralAmount, + uint256 debtRepayAmount, + uint256 debtRateMode, + PermitSignature calldata permitSignature, + bool useEthPath + ) external { + DataTypes.ReserveData memory collateralReserveData = _getReserveData(collateralAsset); + DataTypes.ReserveData memory debtReserveData = _getReserveData(debtAsset); + + address debtToken = + DataTypes.InterestRateMode(debtRateMode) == DataTypes.InterestRateMode.STABLE + ? debtReserveData.stableDebtTokenAddress + : debtReserveData.variableDebtTokenAddress; + + uint256 currentDebt = IERC20(debtToken).balanceOf(msg.sender); + uint256 amountToRepay = debtRepayAmount <= currentDebt ? debtRepayAmount : currentDebt; + + if (collateralAsset != debtAsset) { + uint256 maxCollateralToSwap = collateralAmount; + if (amountToRepay < debtRepayAmount) { + maxCollateralToSwap = maxCollateralToSwap.mul(amountToRepay).div(debtRepayAmount); + } + + // Get exact collateral needed for the swap to avoid leftovers + uint256[] memory amounts = + _getAmountsIn(collateralAsset, debtAsset, amountToRepay, useEthPath); + require(amounts[0] <= maxCollateralToSwap, 'slippage too high'); + + // Pull aTokens from user + _pullAToken( + collateralAsset, + collateralReserveData.aTokenAddress, + msg.sender, + amounts[0], + permitSignature + ); + + // Swap collateral for debt asset + _swapTokensForExactTokens(collateralAsset, debtAsset, amounts[0], amountToRepay, useEthPath); + } else { + // Pull aTokens from user + _pullAToken( + collateralAsset, + collateralReserveData.aTokenAddress, + msg.sender, + amountToRepay, + permitSignature + ); + } + + // Repay debt. Approves 0 first to comply with tokens that implement the anti frontrunning approval fix + IERC20(debtAsset).safeApprove(address(LENDING_POOL), 0); + IERC20(debtAsset).safeApprove(address(LENDING_POOL), amountToRepay); + LENDING_POOL.repay(debtAsset, amountToRepay, debtRateMode, msg.sender); + } + + /** + * @dev Perform the repay of the debt, pulls the initiator collateral and swaps to repay the flash loan + * + * @param collateralAsset Address of token to be swapped + * @param debtAsset Address of debt token to be received from the swap + * @param amount Amount of the debt to be repaid + * @param collateralAmount Amount of the reserve to be swapped + * @param rateMode Rate mode of the debt to be repaid + * @param initiator Address of the user + * @param premium Fee of the flash loan + * @param permitSignature struct containing the permit signature + */ + function _swapAndRepay( + address collateralAsset, + address debtAsset, + uint256 amount, + uint256 collateralAmount, + uint256 rateMode, + address initiator, + uint256 premium, + PermitSignature memory permitSignature, + bool useEthPath + ) internal { + DataTypes.ReserveData memory collateralReserveData = _getReserveData(collateralAsset); + + // Repay debt. Approves for 0 first to comply with tokens that implement the anti frontrunning approval fix. + IERC20(debtAsset).safeApprove(address(LENDING_POOL), 0); + IERC20(debtAsset).safeApprove(address(LENDING_POOL), amount); + uint256 repaidAmount = IERC20(debtAsset).balanceOf(address(this)); + LENDING_POOL.repay(debtAsset, amount, rateMode, initiator); + repaidAmount = repaidAmount.sub(IERC20(debtAsset).balanceOf(address(this))); + + if (collateralAsset != debtAsset) { + uint256 maxCollateralToSwap = collateralAmount; + if (repaidAmount < amount) { + maxCollateralToSwap = maxCollateralToSwap.mul(repaidAmount).div(amount); + } + + uint256 neededForFlashLoanDebt = repaidAmount.add(premium); + uint256[] memory amounts = + _getAmountsIn(collateralAsset, debtAsset, neededForFlashLoanDebt, useEthPath); + require(amounts[0] <= maxCollateralToSwap, 'slippage too high'); + + // Pull aTokens from user + _pullAToken( + collateralAsset, + collateralReserveData.aTokenAddress, + initiator, + amounts[0], + permitSignature + ); + + // Swap collateral asset to the debt asset + _swapTokensForExactTokens(collateralAsset, debtAsset, amounts[0], neededForFlashLoanDebt, useEthPath); + } else { + // Pull aTokens from user + _pullAToken( + collateralAsset, + collateralReserveData.aTokenAddress, + initiator, + repaidAmount.add(premium), + permitSignature + ); + } + + // Repay flashloan. Approves for 0 first to comply with tokens that implement the anti frontrunning approval fix. + IERC20(debtAsset).safeApprove(address(LENDING_POOL), 0); + IERC20(debtAsset).safeApprove(address(LENDING_POOL), amount.add(premium)); + } + + /** + * @dev Decodes debt information encoded in the flash loan params + * @param params Additional variadic field to include extra params. Expected parameters: + * address collateralAsset Address of the reserve to be swapped + * uint256 collateralAmount Amount of reserve to be swapped + * uint256 rateMode Rate modes of the debt to be repaid + * uint256 permitAmount Amount for the permit signature + * uint256 deadline Deadline for the permit signature + * uint8 v V param for the permit signature + * bytes32 r R param for the permit signature + * bytes32 s S param for the permit signature + * @return RepayParams struct containing decoded params + */ + function _decodeParams(bytes memory params) internal pure returns (RepayParams memory) { + ( + address collateralAsset, + uint256 collateralAmount, + uint256 rateMode, + uint256 permitAmount, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s, + bool useEthPath + ) = + abi.decode( + params, + (address, uint256, uint256, uint256, uint256, uint8, bytes32, bytes32, bool) + ); + + return + RepayParams( + collateralAsset, + collateralAmount, + rateMode, + PermitSignature(permitAmount, deadline, v, r, s), + useEthPath + ); + } +} \ No newline at end of file diff --git a/contracts/adapters/interfaces/IBaseUniswapAdapter.sol b/contracts/adapters/interfaces/IBaseUniswapAdapter.sol new file mode 100644 index 00000000..82997b74 --- /dev/null +++ b/contracts/adapters/interfaces/IBaseUniswapAdapter.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + +import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol'; +import {IUniswapV2Router02} from '../../interfaces/IUniswapV2Router02.sol'; + +interface IBaseUniswapAdapter { + event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount); + + struct PermitSignature { + uint256 amount; + uint256 deadline; + uint8 v; + bytes32 r; + bytes32 s; + } + + struct AmountCalc { + uint256 calculatedAmount; + uint256 relativePrice; + uint256 amountInUsd; + uint256 amountOutUsd; + address[] path; + } + + function WETH_ADDRESS() external returns (address); + + function MAX_SLIPPAGE_PERCENT() external returns (uint256); + + function FLASHLOAN_PREMIUM_TOTAL() external returns (uint256); + + function USD_ADDRESS() external returns (address); + + function ORACLE() external returns (IPriceOracleGetter); + + function UNISWAP_ROUTER() external returns (IUniswapV2Router02); + + /** + * @dev Given an input asset amount, returns the maximum output amount of the other asset and the prices + * @param amountIn Amount of reserveIn + * @param reserveIn Address of the asset to be swap from + * @param reserveOut Address of the asset to be swap to + * @return uint256 Amount out of the reserveOut + * @return uint256 The price of out amount denominated in the reserveIn currency (18 decimals) + * @return uint256 In amount of reserveIn value denominated in USD (8 decimals) + * @return uint256 Out amount of reserveOut value denominated in USD (8 decimals) + * @return address[] The exchange path + */ + function getAmountsOut( + uint256 amountIn, + address reserveIn, + address reserveOut + ) + external + view + returns ( + uint256, + uint256, + uint256, + uint256, + address[] memory + ); + + /** + * @dev Returns the minimum input asset amount required to buy the given output asset amount and the prices + * @param amountOut Amount of reserveOut + * @param reserveIn Address of the asset to be swap from + * @param reserveOut Address of the asset to be swap to + * @return uint256 Amount in of the reserveIn + * @return uint256 The price of in amount denominated in the reserveOut currency (18 decimals) + * @return uint256 In amount of reserveIn value denominated in USD (8 decimals) + * @return uint256 Out amount of reserveOut value denominated in USD (8 decimals) + * @return address[] The exchange path + */ + function getAmountsIn( + uint256 amountOut, + address reserveIn, + address reserveOut + ) + external + view + returns ( + uint256, + uint256, + uint256, + uint256, + address[] memory + ); +} diff --git a/contracts/deployments/ATokensAndRatesHelper.sol b/contracts/deployments/ATokensAndRatesHelper.sol index a29dc6f2..400f7ca6 100644 --- a/contracts/deployments/ATokensAndRatesHelper.sol +++ b/contracts/deployments/ATokensAndRatesHelper.sol @@ -20,6 +20,20 @@ contract ATokensAndRatesHelper is Ownable { address private poolConfigurator; event deployedContracts(address aToken, address strategy); + struct InitDeploymentInput { + address asset; + uint256[6] rates; + } + + struct ConfigureReserveInput { + address asset; + uint256 baseLTV; + uint256 liquidationThreshold; + uint256 liquidationBonus; + uint256 reserveFactor; + bool stableBorrowingEnabled; + } + constructor( address payable _pool, address _addressesProvider, @@ -30,93 +44,40 @@ contract ATokensAndRatesHelper is Ownable { poolConfigurator = _poolConfigurator; } - function initDeployment( - address[] calldata assets, - string[] calldata symbols, - uint256[6][] calldata rates, - address treasuryAddress, - address incentivesController - ) external onlyOwner { - require(assets.length == symbols.length, 't Arrays not same length'); - require(rates.length == symbols.length, 'r Arrays not same length'); - for (uint256 i = 0; i < assets.length; i++) { + function initDeployment(InitDeploymentInput[] calldata inputParams) external onlyOwner { + for (uint256 i = 0; i < inputParams.length; i++) { emit deployedContracts( - address( - new AToken( - LendingPool(pool), - assets[i], - treasuryAddress, - StringLib.concat('Aave interest bearing ', symbols[i]), - StringLib.concat('a', symbols[i]), - incentivesController - ) - ), + address(new AToken()), address( new DefaultReserveInterestRateStrategy( LendingPoolAddressesProvider(addressesProvider), - rates[i][0], - rates[i][1], - rates[i][2], - rates[i][3], - rates[i][4], - rates[i][5] + inputParams[i].rates[0], + inputParams[i].rates[1], + inputParams[i].rates[2], + inputParams[i].rates[3], + inputParams[i].rates[4], + inputParams[i].rates[5] ) ) ); } } - function initReserve( - address[] calldata stables, - address[] calldata variables, - address[] calldata aTokens, - address[] calldata strategies, - uint8[] calldata reserveDecimals - ) external onlyOwner { - require(variables.length == stables.length); - require(aTokens.length == stables.length); - require(strategies.length == stables.length); - require(reserveDecimals.length == stables.length); - - for (uint256 i = 0; i < stables.length; i++) { - LendingPoolConfigurator(poolConfigurator).initReserve( - aTokens[i], - stables[i], - variables[i], - reserveDecimals[i], - strategies[i] - ); - } - } - - function configureReserves( - address[] calldata assets, - uint256[] calldata baseLTVs, - uint256[] calldata liquidationThresholds, - uint256[] calldata liquidationBonuses, - uint256[] calldata reserveFactors, - bool[] calldata stableBorrowingEnabled - ) external onlyOwner { - require(baseLTVs.length == assets.length); - require(liquidationThresholds.length == assets.length); - require(liquidationBonuses.length == assets.length); - require(stableBorrowingEnabled.length == assets.length); - require(reserveFactors.length == assets.length); - + function configureReserves(ConfigureReserveInput[] calldata inputParams) external onlyOwner { LendingPoolConfigurator configurator = LendingPoolConfigurator(poolConfigurator); - for (uint256 i = 0; i < assets.length; i++) { + for (uint256 i = 0; i < inputParams.length; i++) { configurator.configureReserveAsCollateral( - assets[i], - baseLTVs[i], - liquidationThresholds[i], - liquidationBonuses[i] + inputParams[i].asset, + inputParams[i].baseLTV, + inputParams[i].liquidationThreshold, + inputParams[i].liquidationBonus ); configurator.enableBorrowingOnReserve( - assets[i], - stableBorrowingEnabled[i] + inputParams[i].asset, + inputParams[i].stableBorrowingEnabled ); - configurator.setReserveFactor(assets[i], reserveFactors[i]); + configurator.setReserveFactor(inputParams[i].asset, inputParams[i].reserveFactor); } } } diff --git a/contracts/deployments/StableAndVariableTokensHelper.sol b/contracts/deployments/StableAndVariableTokensHelper.sol index 8f76bbe1..e31651f3 100644 --- a/contracts/deployments/StableAndVariableTokensHelper.sol +++ b/contracts/deployments/StableAndVariableTokensHelper.sol @@ -18,34 +18,11 @@ contract StableAndVariableTokensHelper is Ownable { addressesProvider = _addressesProvider; } - function initDeployment( - address[] calldata tokens, - string[] calldata symbols, - address incentivesController - ) external onlyOwner { + function initDeployment(address[] calldata tokens, string[] calldata symbols) external onlyOwner { require(tokens.length == symbols.length, 'Arrays not same length'); require(pool != address(0), 'Pool can not be zero address'); for (uint256 i = 0; i < tokens.length; i++) { - emit deployedContracts( - address( - new StableDebtToken( - pool, - tokens[i], - StringLib.concat('Aave stable debt bearing ', symbols[i]), - StringLib.concat('stableDebt', symbols[i]), - incentivesController - ) - ), - address( - new VariableDebtToken( - pool, - tokens[i], - StringLib.concat('Aave variable debt bearing ', symbols[i]), - StringLib.concat('variableDebt', symbols[i]), - incentivesController - ) - ) - ); + emit deployedContracts(address(new StableDebtToken()), address(new VariableDebtToken())); } } diff --git a/contracts/interfaces/IAToken.sol b/contracts/interfaces/IAToken.sol index 5fa1a4f0..ca338270 100644 --- a/contracts/interfaces/IAToken.sol +++ b/contracts/interfaces/IAToken.sol @@ -3,8 +3,10 @@ pragma solidity 0.6.12; import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; import {IScaledBalanceToken} from './IScaledBalanceToken.sol'; +import {IInitializableAToken} from './IInitializableAToken.sol'; +import {IAaveIncentivesController} from './IAaveIncentivesController.sol'; -interface IAToken is IERC20, IScaledBalanceToken { +interface IAToken is IERC20, IScaledBalanceToken, IInitializableAToken { /** * @dev Emitted after the mint action * @param from The address performing the mint @@ -85,4 +87,9 @@ interface IAToken is IERC20, IScaledBalanceToken { * @return The amount transferred **/ function transferUnderlyingTo(address user, uint256 amount) external returns (uint256); + + /** + * @dev Returns the address of the incentives controller contract + **/ + function getIncentivesController() external view returns (IAaveIncentivesController); } diff --git a/contracts/interfaces/IERC20WithPermit.sol b/contracts/interfaces/IERC20WithPermit.sol new file mode 100644 index 00000000..46466b90 --- /dev/null +++ b/contracts/interfaces/IERC20WithPermit.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; + +interface IERC20WithPermit is IERC20 { + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; +} diff --git a/contracts/interfaces/IInitializableAToken.sol b/contracts/interfaces/IInitializableAToken.sol new file mode 100644 index 00000000..9cffe6ce --- /dev/null +++ b/contracts/interfaces/IInitializableAToken.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +import {ILendingPool} from './ILendingPool.sol'; +import {IAaveIncentivesController} from './IAaveIncentivesController.sol'; + +/** + * @title IInitializableAToken + * @notice Interface for the initialize function on AToken + * @author Aave + **/ +interface IInitializableAToken { + /** + * @dev Initializes the aToken + * @param pool The address of the lending pool where this aToken will be used + * @param treasury The address of the Aave treasury, receiving the fees on this aToken + * @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 aTokenDecimals The decimals of the aToken, same as the underlying asset's + * @param aTokenName The name of the aToken + * @param aTokenSymbol The symbol of the aToken + */ + function initialize( + ILendingPool pool, + address treasury, + address underlyingAsset, + IAaveIncentivesController incentivesController, + uint8 aTokenDecimals, + string calldata aTokenName, + string calldata aTokenSymbol + ) external; +} diff --git a/contracts/interfaces/IInitializableDebtToken.sol b/contracts/interfaces/IInitializableDebtToken.sol new file mode 100644 index 00000000..0a85c968 --- /dev/null +++ b/contracts/interfaces/IInitializableDebtToken.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +import {ILendingPool} from './ILendingPool.sol'; +import {IAaveIncentivesController} from './IAaveIncentivesController.sol'; + +/** + * @title IInitializableDebtToken + * @notice Interface for the initialize function common between debt tokens + * @author Aave + **/ +interface IInitializableDebtToken { + /** + * @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 + ) external; +} diff --git a/contracts/interfaces/ILendingPoolConfigurator.sol b/contracts/interfaces/ILendingPoolConfigurator.sol new file mode 100644 index 00000000..8981db6c --- /dev/null +++ b/contracts/interfaces/ILendingPoolConfigurator.sol @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + +interface ILendingPoolConfigurator { + struct InitReserveInput { + address aTokenImpl; + address stableDebtTokenImpl; + address variableDebtTokenImpl; + uint8 underlyingAssetDecimals; + address interestRateStrategyAddress; + address underlyingAsset; + address treasury; + address incentivesController; + string underlyingAssetName; + string aTokenName; + string aTokenSymbol; + string variableDebtTokenName; + string variableDebtTokenSymbol; + string stableDebtTokenName; + string stableDebtTokenSymbol; + } + + struct UpdateATokenInput { + address asset; + address treasury; + address incentivesController; + string name; + string symbol; + address implementation; + } + + struct UpdateDebtTokenInput { + address asset; + address incentivesController; + string name; + string symbol; + address implementation; + } + + /** + * @dev Emitted when a reserve is initialized. + * @param asset The address of the underlying asset of the reserve + * @param aToken The address of the associated aToken contract + * @param stableDebtToken The address of the associated stable rate debt token + * @param variableDebtToken The address of the associated variable rate debt token + * @param interestRateStrategyAddress The address of the interest rate strategy for the reserve + **/ + event ReserveInitialized( + address indexed asset, + address indexed aToken, + address stableDebtToken, + address variableDebtToken, + address interestRateStrategyAddress + ); + + /** + * @dev Emitted when borrowing is enabled on a reserve + * @param asset The address of the underlying asset of the reserve + * @param stableRateEnabled True if stable rate borrowing is enabled, false otherwise + **/ + event BorrowingEnabledOnReserve(address indexed asset, bool stableRateEnabled); + + /** + * @dev Emitted when borrowing is disabled on a reserve + * @param asset The address of the underlying asset of the reserve + **/ + event BorrowingDisabledOnReserve(address indexed asset); + + /** + * @dev Emitted when the collateralization risk parameters for the specified asset are updated. + * @param asset The address of the underlying asset of the reserve + * @param ltv The loan to value of the asset when used as collateral + * @param liquidationThreshold The threshold at which loans using this asset as collateral will be considered undercollateralized + * @param liquidationBonus The bonus liquidators receive to liquidate this asset + **/ + event CollateralConfigurationChanged( + address indexed asset, + uint256 ltv, + uint256 liquidationThreshold, + uint256 liquidationBonus + ); + + /** + * @dev Emitted when stable rate borrowing is enabled on a reserve + * @param asset The address of the underlying asset of the reserve + **/ + event StableRateEnabledOnReserve(address indexed asset); + + /** + * @dev Emitted when stable rate borrowing is disabled on a reserve + * @param asset The address of the underlying asset of the reserve + **/ + event StableRateDisabledOnReserve(address indexed asset); + + /** + * @dev Emitted when a reserve is activated + * @param asset The address of the underlying asset of the reserve + **/ + event ReserveActivated(address indexed asset); + + /** + * @dev Emitted when a reserve is deactivated + * @param asset The address of the underlying asset of the reserve + **/ + event ReserveDeactivated(address indexed asset); + + /** + * @dev Emitted when a reserve is frozen + * @param asset The address of the underlying asset of the reserve + **/ + event ReserveFrozen(address indexed asset); + + /** + * @dev Emitted when a reserve is unfrozen + * @param asset The address of the underlying asset of the reserve + **/ + event ReserveUnfrozen(address indexed asset); + + /** + * @dev Emitted when a reserve factor is updated + * @param asset The address of the underlying asset of the reserve + * @param factor The new reserve factor + **/ + event ReserveFactorChanged(address indexed asset, uint256 factor); + + /** + * @dev Emitted when the reserve decimals are updated + * @param asset The address of the underlying asset of the reserve + * @param decimals The new decimals + **/ + event ReserveDecimalsChanged(address indexed asset, uint256 decimals); + + /** + * @dev Emitted when a reserve interest strategy contract is updated + * @param asset The address of the underlying asset of the reserve + * @param strategy The new address of the interest strategy contract + **/ + event ReserveInterestRateStrategyChanged(address indexed asset, address strategy); + + /** + * @dev Emitted when an aToken implementation is upgraded + * @param asset The address of the underlying asset of the reserve + * @param proxy The aToken proxy address + * @param implementation The new aToken implementation + **/ + event ATokenUpgraded( + address indexed asset, + address indexed proxy, + address indexed implementation + ); + + /** + * @dev Emitted when the implementation of a stable debt token is upgraded + * @param asset The address of the underlying asset of the reserve + * @param proxy The stable debt token proxy address + * @param implementation The new aToken implementation + **/ + event StableDebtTokenUpgraded( + address indexed asset, + address indexed proxy, + address indexed implementation + ); + + /** + * @dev Emitted when the implementation of a variable debt token is upgraded + * @param asset The address of the underlying asset of the reserve + * @param proxy The variable debt token proxy address + * @param implementation The new aToken implementation + **/ + event VariableDebtTokenUpgraded( + address indexed asset, + address indexed proxy, + address indexed implementation + ); +} diff --git a/contracts/interfaces/IStableDebtToken.sol b/contracts/interfaces/IStableDebtToken.sol index c24750bc..e39cf8b5 100644 --- a/contracts/interfaces/IStableDebtToken.sol +++ b/contracts/interfaces/IStableDebtToken.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.6.12; +import {IInitializableDebtToken} from './IInitializableDebtToken.sol'; +import {IAaveIncentivesController} from './IAaveIncentivesController.sol'; + /** * @title IStableDebtToken * @notice Defines the interface for the stable debt token @@ -8,7 +11,7 @@ pragma solidity 0.6.12; * @author Aave **/ -interface IStableDebtToken { +interface IStableDebtToken is IInitializableDebtToken { /** * @dev Emitted when new stable debt is minted * @param user The address of the user who triggered the minting @@ -122,4 +125,9 @@ interface IStableDebtToken { * @return The debt balance of the user since the last burn/mint action **/ function principalBalanceOf(address user) external view returns (uint256); + + /** + * @dev Returns the address of the incentives controller contract + **/ + function getIncentivesController() external view returns (IAaveIncentivesController); } diff --git a/contracts/interfaces/IUniswapV2Router02.sol b/contracts/interfaces/IUniswapV2Router02.sol new file mode 100644 index 00000000..af0f8280 --- /dev/null +++ b/contracts/interfaces/IUniswapV2Router02.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +interface IUniswapV2Router02 { + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapTokensForExactTokens( + uint amountOut, + uint amountInMax, + address[] calldata path, + address to, + uint deadline + ) external returns (uint256[] memory amounts); + + function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); + + function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); +} diff --git a/contracts/interfaces/IVariableDebtToken.sol b/contracts/interfaces/IVariableDebtToken.sol index 6a0c1555..d88c25fc 100644 --- a/contracts/interfaces/IVariableDebtToken.sol +++ b/contracts/interfaces/IVariableDebtToken.sol @@ -2,13 +2,15 @@ pragma solidity 0.6.12; import {IScaledBalanceToken} from './IScaledBalanceToken.sol'; +import {IInitializableDebtToken} from './IInitializableDebtToken.sol'; +import {IAaveIncentivesController} from './IAaveIncentivesController.sol'; /** * @title IVariableDebtToken * @author Aave * @notice Defines the basic interface for a variable debt token. **/ -interface IVariableDebtToken is IScaledBalanceToken { +interface IVariableDebtToken is IScaledBalanceToken, IInitializableDebtToken { /** * @dev Emitted after the mint action * @param from The address performing the mint @@ -52,4 +54,9 @@ interface IVariableDebtToken is IScaledBalanceToken { uint256 amount, uint256 index ) external; + + /** + * @dev Returns the address of the incentives controller contract + **/ + function getIncentivesController() external view returns (IAaveIncentivesController); } diff --git a/contracts/mocks/swap/MockUniswapV2Router02.sol b/contracts/mocks/swap/MockUniswapV2Router02.sol new file mode 100644 index 00000000..b7fd3f80 --- /dev/null +++ b/contracts/mocks/swap/MockUniswapV2Router02.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +import {IUniswapV2Router02} from '../../interfaces/IUniswapV2Router02.sol'; +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import {MintableERC20} from '../tokens/MintableERC20.sol'; + +contract MockUniswapV2Router02 is IUniswapV2Router02 { + mapping(address => uint256) internal _amountToReturn; + mapping(address => uint256) internal _amountToSwap; + mapping(address => mapping(address => mapping(uint256 => uint256))) internal _amountsIn; + mapping(address => mapping(address => mapping(uint256 => uint256))) internal _amountsOut; + uint256 internal defaultMockValue; + + function setAmountToReturn(address reserve, uint256 amount) public { + _amountToReturn[reserve] = amount; + } + + function setAmountToSwap(address reserve, uint256 amount) public { + _amountToSwap[reserve] = amount; + } + + function swapExactTokensForTokens( + uint256 amountIn, + uint256, /* amountOutMin */ + address[] calldata path, + address to, + uint256 /* deadline */ + ) external override returns (uint256[] memory amounts) { + IERC20(path[0]).transferFrom(msg.sender, address(this), amountIn); + + MintableERC20(path[1]).mint(_amountToReturn[path[0]]); + IERC20(path[1]).transfer(to, _amountToReturn[path[0]]); + + amounts = new uint256[](path.length); + amounts[0] = amountIn; + amounts[1] = _amountToReturn[path[0]]; + } + + function swapTokensForExactTokens( + uint256 amountOut, + uint256, /* amountInMax */ + address[] calldata path, + address to, + uint256 /* deadline */ + ) external override returns (uint256[] memory amounts) { + IERC20(path[0]).transferFrom(msg.sender, address(this), _amountToSwap[path[0]]); + + MintableERC20(path[1]).mint(amountOut); + IERC20(path[1]).transfer(to, amountOut); + + amounts = new uint256[](path.length); + amounts[0] = _amountToSwap[path[0]]; + amounts[1] = amountOut; + } + + function setAmountOut( + uint256 amountIn, + address reserveIn, + address reserveOut, + uint256 amountOut + ) public { + _amountsOut[reserveIn][reserveOut][amountIn] = amountOut; + } + + function setAmountIn( + uint256 amountOut, + address reserveIn, + address reserveOut, + uint256 amountIn + ) public { + _amountsIn[reserveIn][reserveOut][amountOut] = amountIn; + } + + function setDefaultMockValue(uint256 value) public { + defaultMockValue = value; + } + + function getAmountsOut(uint256 amountIn, address[] calldata path) + external + view + override + returns (uint256[] memory) + { + uint256[] memory amounts = new uint256[](path.length); + amounts[0] = amountIn; + amounts[1] = _amountsOut[path[0]][path[1]][amountIn] > 0 + ? _amountsOut[path[0]][path[1]][amountIn] + : defaultMockValue; + return amounts; + } + + function getAmountsIn(uint256 amountOut, address[] calldata path) + external + view + override + returns (uint256[] memory) + { + uint256[] memory amounts = new uint256[](path.length); + amounts[0] = _amountsIn[path[0]][path[1]][amountOut] > 0 + ? _amountsIn[path[0]][path[1]][amountOut] + : defaultMockValue; + amounts[1] = amountOut; + return amounts; + } +} diff --git a/contracts/mocks/upgradeability/MockAToken.sol b/contracts/mocks/upgradeability/MockAToken.sol index f0eda38c..7e7b6f2a 100644 --- a/contracts/mocks/upgradeability/MockAToken.sol +++ b/contracts/mocks/upgradeability/MockAToken.sol @@ -2,39 +2,11 @@ pragma solidity 0.6.12; import {AToken} from '../../protocol/tokenization/AToken.sol'; -import {LendingPool} from '../../protocol/lendingpool/LendingPool.sol'; +import {ILendingPool} from '../../interfaces/ILendingPool.sol'; +import {IAaveIncentivesController} from '../../interfaces/IAaveIncentivesController.sol'; contract MockAToken is AToken { - constructor( - LendingPool pool, - address underlyingAssetAddress, - address reserveTreasury, - string memory tokenName, - string memory tokenSymbol, - address incentivesController - ) - public - AToken( - pool, - underlyingAssetAddress, - reserveTreasury, - tokenName, - tokenSymbol, - incentivesController - ) - {} - function getRevision() internal pure override returns (uint256) { return 0x2; } - - function initialize( - uint8 _underlyingAssetDecimals, - string calldata _tokenName, - string calldata _tokenSymbol - ) external virtual override initializer { - _setName(_tokenName); - _setSymbol(_tokenSymbol); - _setDecimals(_underlyingAssetDecimals); - } } diff --git a/contracts/mocks/upgradeability/MockStableDebtToken.sol b/contracts/mocks/upgradeability/MockStableDebtToken.sol index 5030c171..cbc66664 100644 --- a/contracts/mocks/upgradeability/MockStableDebtToken.sol +++ b/contracts/mocks/upgradeability/MockStableDebtToken.sol @@ -4,17 +4,6 @@ pragma solidity 0.6.12; import {StableDebtToken} from '../../protocol/tokenization/StableDebtToken.sol'; contract MockStableDebtToken is StableDebtToken { - constructor( - address _pool, - address _underlyingAssetAddress, - string memory _tokenName, - string memory _tokenSymbol, - address incentivesController - ) - public - StableDebtToken(_pool, _underlyingAssetAddress, _tokenName, _tokenSymbol, incentivesController) - {} - function getRevision() internal pure override returns (uint256) { return 0x2; } diff --git a/contracts/mocks/upgradeability/MockVariableDebtToken.sol b/contracts/mocks/upgradeability/MockVariableDebtToken.sol index 0a9a03aa..497682c3 100644 --- a/contracts/mocks/upgradeability/MockVariableDebtToken.sol +++ b/contracts/mocks/upgradeability/MockVariableDebtToken.sol @@ -4,23 +4,6 @@ pragma solidity 0.6.12; import {VariableDebtToken} from '../../protocol/tokenization/VariableDebtToken.sol'; contract MockVariableDebtToken is VariableDebtToken { - constructor( - address _pool, - address _underlyingAssetAddress, - string memory _tokenName, - string memory _tokenSymbol, - address incentivesController - ) - public - VariableDebtToken( - _pool, - _underlyingAssetAddress, - _tokenName, - _tokenSymbol, - incentivesController - ) - {} - function getRevision() internal pure override returns (uint256) { return 0x2; } diff --git a/contracts/protocol/lendingpool/LendingPool.sol b/contracts/protocol/lendingpool/LendingPool.sol index 7c48f237..75e80230 100644 --- a/contracts/protocol/lendingpool/LendingPool.sol +++ b/contracts/protocol/lendingpool/LendingPool.sol @@ -49,10 +49,6 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage using PercentageMath for uint256; using SafeERC20 for IERC20; - //main configuration parameters - uint256 public constant MAX_STABLE_RATE_BORROW_SIZE_PERCENT = 2500; - uint256 public constant FLASHLOAN_PREMIUM_TOTAL = 9; - uint256 public constant MAX_NUMBER_RESERVES = 128; uint256 public constant LENDINGPOOL_REVISION = 0x2; modifier whenNotPaused() { @@ -86,9 +82,20 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage * - Caching the address of the LendingPoolAddressesProvider in order to reduce gas consumption * on subsequent operations * @param provider The address of the LendingPoolAddressesProvider + * @param maxStableRateBorrowSizePercent The percentage of available liquidity that can be borrowed at once at stable rate + * @param flashLoanPremiumTotal The fee on flash loans + * @param maxNumberOfReserves Maximum number of reserves supported to be listed in this LendingPool **/ - function initialize(ILendingPoolAddressesProvider provider) public initializer { + function initialize( + ILendingPoolAddressesProvider provider, + uint256 maxStableRateBorrowSizePercent, + uint256 flashLoanPremiumTotal, + uint256 maxNumberOfReserves + ) public initializer { _addressesProvider = provider; + _maxStableRateBorrowSizePercent = maxStableRateBorrowSizePercent; + _flashLoanPremiumTotal = flashLoanPremiumTotal; + _maxNumberOfReserves = maxNumberOfReserves; } /** @@ -144,7 +151,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage address asset, uint256 amount, address to - ) external override whenNotPaused returns (uint256) { + ) external override whenNotPaused returns (uint256) { DataTypes.ReserveData storage reserve = _reserves[asset]; address aToken = reserve.aTokenAddress; @@ -499,7 +506,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage for (vars.i = 0; vars.i < assets.length; vars.i++) { aTokenAddresses[vars.i] = _reserves[assets[vars.i]].aTokenAddress; - premiums[vars.i] = amounts[vars.i].mul(FLASHLOAN_PREMIUM_TOTAL).div(10000); + premiums[vars.i] = amounts[vars.i].mul(_flashLoanPremiumTotal).div(10000); IAToken(aTokenAddresses[vars.i]).transferUnderlyingTo(receiverAddress, amounts[vars.i]); } @@ -703,6 +710,27 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage return _addressesProvider; } + /** + * @dev Returns the percentage of available liquidity that can be borrowed at once at stable rate + */ + function MAX_STABLE_RATE_BORROW_SIZE_PERCENT() public view returns (uint256) { + return _maxStableRateBorrowSizePercent; + } + + /** + * @dev Returns the fee on flash loans + */ + function FLASHLOAN_PREMIUM_TOTAL() public view returns (uint256) { + return _flashLoanPremiumTotal; + } + + /** + * @dev Returns the maximum number of reserves supported to be listed in this LendingPool + */ + function MAX_NUMBER_RESERVES() public view returns (uint256) { + return _maxNumberOfReserves; + } + /** * @dev Validates and finalizes an aToken transfer * - Only callable by the overlying aToken of the `asset` @@ -847,7 +875,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage vars.amount, amountInETH, vars.interestRateMode, - MAX_STABLE_RATE_BORROW_SIZE_PERCENT, + _maxStableRateBorrowSizePercent, _reserves, userConfig, _reservesList, @@ -909,7 +937,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage function _addReserveToList(address asset) internal { uint256 reservesCount = _reservesCount; - require(reservesCount < MAX_NUMBER_RESERVES, Errors.LP_NO_MORE_RESERVES_ALLOWED); + require(reservesCount < _maxNumberOfReserves, Errors.LP_NO_MORE_RESERVES_ALLOWED); bool reserveAlreadyAdded = _reserves[asset].id != 0 || _reservesList[0] == asset; diff --git a/contracts/protocol/lendingpool/LendingPoolConfigurator.sol b/contracts/protocol/lendingpool/LendingPoolConfigurator.sol index bb3fb774..f42415ca 100644 --- a/contracts/protocol/lendingpool/LendingPoolConfigurator.sol +++ b/contracts/protocol/lendingpool/LendingPoolConfigurator.sol @@ -10,11 +10,14 @@ import { import {ReserveConfiguration} from '../libraries/configuration/ReserveConfiguration.sol'; import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol'; import {ILendingPool} from '../../interfaces/ILendingPool.sol'; -import {ITokenConfiguration} from '../../interfaces/ITokenConfiguration.sol'; import {IERC20Detailed} from '../../dependencies/openzeppelin/contracts/IERC20Detailed.sol'; import {Errors} from '../libraries/helpers/Errors.sol'; import {PercentageMath} from '../libraries/math/PercentageMath.sol'; import {DataTypes} from '../libraries/types/DataTypes.sol'; +import {IInitializableDebtToken} from '../../interfaces/IInitializableDebtToken.sol'; +import {IInitializableAToken} from '../../interfaces/IInitializableAToken.sol'; +import {IAaveIncentivesController} from '../../interfaces/IAaveIncentivesController.sol'; +import {ILendingPoolConfigurator} from '../../interfaces/ILendingPoolConfigurator.sol'; /** * @title LendingPoolConfigurator contract @@ -22,147 +25,11 @@ import {DataTypes} from '../libraries/types/DataTypes.sol'; * @dev Implements the configuration methods for the Aave protocol **/ -contract LendingPoolConfigurator is VersionedInitializable { +contract LendingPoolConfigurator is VersionedInitializable, ILendingPoolConfigurator { using SafeMath for uint256; using PercentageMath for uint256; using ReserveConfiguration for DataTypes.ReserveConfigurationMap; - /** - * @dev Emitted when a reserve is initialized. - * @param asset The address of the underlying asset of the reserve - * @param aToken The address of the associated aToken contract - * @param stableDebtToken The address of the associated stable rate debt token - * @param variableDebtToken The address of the associated variable rate debt token - * @param interestRateStrategyAddress The address of the interest rate strategy for the reserve - **/ - event ReserveInitialized( - address indexed asset, - address indexed aToken, - address stableDebtToken, - address variableDebtToken, - address interestRateStrategyAddress - ); - - /** - * @dev Emitted when borrowing is enabled on a reserve - * @param asset The address of the underlying asset of the reserve - * @param stableRateEnabled True if stable rate borrowing is enabled, false otherwise - **/ - event BorrowingEnabledOnReserve(address indexed asset, bool stableRateEnabled); - - /** - * @dev Emitted when borrowing is disabled on a reserve - * @param asset The address of the underlying asset of the reserve - **/ - event BorrowingDisabledOnReserve(address indexed asset); - - /** - * @dev Emitted when the collateralization risk parameters for the specified asset are updated. - * @param asset The address of the underlying asset of the reserve - * @param ltv The loan to value of the asset when used as collateral - * @param liquidationThreshold The threshold at which loans using this asset as collateral will be considered undercollateralized - * @param liquidationBonus The bonus liquidators receive to liquidate this asset - **/ - event CollateralConfigurationChanged( - address indexed asset, - uint256 ltv, - uint256 liquidationThreshold, - uint256 liquidationBonus - ); - - /** - * @dev Emitted when stable rate borrowing is enabled on a reserve - * @param asset The address of the underlying asset of the reserve - **/ - event StableRateEnabledOnReserve(address indexed asset); - - /** - * @dev Emitted when stable rate borrowing is disabled on a reserve - * @param asset The address of the underlying asset of the reserve - **/ - event StableRateDisabledOnReserve(address indexed asset); - - /** - * @dev Emitted when a reserve is activated - * @param asset The address of the underlying asset of the reserve - **/ - event ReserveActivated(address indexed asset); - - /** - * @dev Emitted when a reserve is deactivated - * @param asset The address of the underlying asset of the reserve - **/ - event ReserveDeactivated(address indexed asset); - - /** - * @dev Emitted when a reserve is frozen - * @param asset The address of the underlying asset of the reserve - **/ - event ReserveFrozen(address indexed asset); - - /** - * @dev Emitted when a reserve is unfrozen - * @param asset The address of the underlying asset of the reserve - **/ - event ReserveUnfrozen(address indexed asset); - - /** - * @dev Emitted when a reserve factor is updated - * @param asset The address of the underlying asset of the reserve - * @param factor The new reserve factor - **/ - event ReserveFactorChanged(address indexed asset, uint256 factor); - - /** - * @dev Emitted when the reserve decimals are updated - * @param asset The address of the underlying asset of the reserve - * @param decimals The new decimals - **/ - event ReserveDecimalsChanged(address indexed asset, uint256 decimals); - - /** - * @dev Emitted when a reserve interest strategy contract is updated - * @param asset The address of the underlying asset of the reserve - * @param strategy The new address of the interest strategy contract - **/ - event ReserveInterestRateStrategyChanged(address indexed asset, address strategy); - - /** - * @dev Emitted when an aToken implementation is upgraded - * @param asset The address of the underlying asset of the reserve - * @param proxy The aToken proxy address - * @param implementation The new aToken implementation - **/ - event ATokenUpgraded( - address indexed asset, - address indexed proxy, - address indexed implementation - ); - - /** - * @dev Emitted when the implementation of a stable debt token is upgraded - * @param asset The address of the underlying asset of the reserve - * @param proxy The stable debt token proxy address - * @param implementation The new aToken implementation - **/ - event StableDebtTokenUpgraded( - address indexed asset, - address indexed proxy, - address indexed implementation - ); - - /** - * @dev Emitted when the implementation of a variable debt token is upgraded - * @param asset The address of the underlying asset of the reserve - * @param proxy The variable debt token proxy address - * @param implementation The new aToken implementation - **/ - event VariableDebtTokenUpgraded( - address indexed asset, - address indexed proxy, - address indexed implementation - ); - ILendingPoolAddressesProvider internal addressesProvider; ILendingPool internal pool; @@ -191,114 +58,186 @@ contract LendingPoolConfigurator is VersionedInitializable { } /** - * @dev Initializes a reserve - * @param aTokenImpl The address of the aToken contract implementation - * @param stableDebtTokenImpl The address of the stable debt token contract - * @param variableDebtTokenImpl The address of the variable debt token contract - * @param underlyingAssetDecimals The decimals of the reserve underlying asset - * @param interestRateStrategyAddress The address of the interest rate strategy contract for this reserve + * @dev Initializes reserves in batch **/ - function initReserve( - address aTokenImpl, - address stableDebtTokenImpl, - address variableDebtTokenImpl, - uint8 underlyingAssetDecimals, - address interestRateStrategyAddress - ) public onlyPoolAdmin { - address asset = ITokenConfiguration(aTokenImpl).UNDERLYING_ASSET_ADDRESS(); + function batchInitReserve(InitReserveInput[] calldata inputParams) public onlyPoolAdmin { + ILendingPool cachedPool = pool; + for (uint256 i = 0; i < inputParams.length; i++) { + _initReserve(cachedPool, inputParams[i]); + } + } - require( - address(pool) == ITokenConfiguration(aTokenImpl).POOL(), - Errors.LPC_INVALID_ATOKEN_POOL_ADDRESS - ); - require( - address(pool) == ITokenConfiguration(stableDebtTokenImpl).POOL(), - Errors.LPC_INVALID_STABLE_DEBT_TOKEN_POOL_ADDRESS - ); - require( - address(pool) == ITokenConfiguration(variableDebtTokenImpl).POOL(), - Errors.LPC_INVALID_VARIABLE_DEBT_TOKEN_POOL_ADDRESS - ); - require( - asset == ITokenConfiguration(stableDebtTokenImpl).UNDERLYING_ASSET_ADDRESS(), - Errors.LPC_INVALID_STABLE_DEBT_TOKEN_UNDERLYING_ADDRESS - ); - require( - asset == ITokenConfiguration(variableDebtTokenImpl).UNDERLYING_ASSET_ADDRESS(), - Errors.LPC_INVALID_VARIABLE_DEBT_TOKEN_UNDERLYING_ADDRESS - ); - - address aTokenProxyAddress = _initTokenWithProxy(aTokenImpl, underlyingAssetDecimals); + function _initReserve(ILendingPool pool, InitReserveInput calldata inputParams) internal { + address aTokenProxyAddress = + _initTokenWithProxy( + inputParams.aTokenImpl, + abi.encodeWithSelector( + IInitializableAToken.initialize.selector, + pool, + inputParams.treasury, + inputParams.underlyingAsset, + IAaveIncentivesController(inputParams.incentivesController), + inputParams.underlyingAssetDecimals, + inputParams.aTokenName, + inputParams.aTokenSymbol + ) + ); address stableDebtTokenProxyAddress = - _initTokenWithProxy(stableDebtTokenImpl, underlyingAssetDecimals); + _initTokenWithProxy( + inputParams.stableDebtTokenImpl, + abi.encodeWithSelector( + IInitializableDebtToken.initialize.selector, + pool, + inputParams.underlyingAsset, + IAaveIncentivesController(inputParams.incentivesController), + inputParams.underlyingAssetDecimals, + inputParams.stableDebtTokenName, + inputParams.stableDebtTokenSymbol + ) + ); address variableDebtTokenProxyAddress = - _initTokenWithProxy(variableDebtTokenImpl, underlyingAssetDecimals); + _initTokenWithProxy( + inputParams.variableDebtTokenImpl, + abi.encodeWithSelector( + IInitializableDebtToken.initialize.selector, + pool, + inputParams.underlyingAsset, + IAaveIncentivesController(inputParams.incentivesController), + inputParams.underlyingAssetDecimals, + inputParams.variableDebtTokenName, + inputParams.variableDebtTokenSymbol + ) + ); pool.initReserve( - asset, + inputParams.underlyingAsset, aTokenProxyAddress, stableDebtTokenProxyAddress, variableDebtTokenProxyAddress, - interestRateStrategyAddress + inputParams.interestRateStrategyAddress ); - DataTypes.ReserveConfigurationMap memory currentConfig = pool.getConfiguration(asset); + DataTypes.ReserveConfigurationMap memory currentConfig = + pool.getConfiguration(inputParams.underlyingAsset); - currentConfig.setDecimals(underlyingAssetDecimals); + currentConfig.setDecimals(inputParams.underlyingAssetDecimals); currentConfig.setActive(true); currentConfig.setFrozen(false); - pool.setConfiguration(asset, currentConfig.data); + pool.setConfiguration(inputParams.underlyingAsset, currentConfig.data); emit ReserveInitialized( - asset, + inputParams.underlyingAsset, aTokenProxyAddress, stableDebtTokenProxyAddress, variableDebtTokenProxyAddress, - interestRateStrategyAddress + inputParams.interestRateStrategyAddress ); } /** * @dev Updates the aToken implementation for the reserve - * @param asset The address of the underlying asset of the reserve to be updated - * @param implementation The address of the new aToken implementation **/ - function updateAToken(address asset, address implementation) external onlyPoolAdmin { - DataTypes.ReserveData memory reserveData = pool.getReserveData(asset); + function updateAToken(UpdateATokenInput calldata inputParams) external onlyPoolAdmin { + ILendingPool cachedPool = pool; - _upgradeTokenImplementation(asset, reserveData.aTokenAddress, implementation); + DataTypes.ReserveData memory reserveData = cachedPool.getReserveData(inputParams.asset); - emit ATokenUpgraded(asset, reserveData.aTokenAddress, implementation); + DataTypes.ReserveConfigurationMap memory configuration = + cachedPool.getConfiguration(inputParams.asset); + + (, , , uint256 decimals, ) = configuration.getParamsMemory(); + + _upgradeTokenImplementation( + reserveData.aTokenAddress, + inputParams.implementation, + abi.encodeWithSelector( + IInitializableAToken.initialize.selector, + cachedPool, + inputParams.treasury, + inputParams.asset, + inputParams.incentivesController, + decimals, + inputParams.name, + inputParams.symbol + ) + ); + + emit ATokenUpgraded(inputParams.asset, reserveData.aTokenAddress, inputParams.implementation); } /** * @dev Updates the stable debt token implementation for the reserve - * @param asset The address of the underlying asset of the reserve to be updated - * @param implementation The address of the new aToken implementation **/ - function updateStableDebtToken(address asset, address implementation) external onlyPoolAdmin { - DataTypes.ReserveData memory reserveData = pool.getReserveData(asset); + function updateStableDebtToken(UpdateDebtTokenInput calldata inputParams) external onlyPoolAdmin { + ILendingPool cachedPool = pool; - _upgradeTokenImplementation(asset, reserveData.stableDebtTokenAddress, implementation); + DataTypes.ReserveData memory reserveData = cachedPool.getReserveData(inputParams.asset); - emit StableDebtTokenUpgraded(asset, reserveData.stableDebtTokenAddress, implementation); + DataTypes.ReserveConfigurationMap memory configuration = + cachedPool.getConfiguration(inputParams.asset); + + (, , , uint256 decimals, ) = configuration.getParamsMemory(); + + _upgradeTokenImplementation( + reserveData.stableDebtTokenAddress, + inputParams.implementation, + abi.encodeWithSelector( + IInitializableDebtToken.initialize.selector, + cachedPool, + inputParams.asset, + inputParams.incentivesController, + decimals, + inputParams.name, + inputParams.symbol + ) + ); + + emit StableDebtTokenUpgraded( + inputParams.asset, + reserveData.stableDebtTokenAddress, + inputParams.implementation + ); } /** * @dev Updates the variable debt token implementation for the asset - * @param asset The address of the underlying asset of the reserve to be updated - * @param implementation The address of the new aToken implementation **/ - function updateVariableDebtToken(address asset, address implementation) external onlyPoolAdmin { - DataTypes.ReserveData memory reserveData = pool.getReserveData(asset); + function updateVariableDebtToken(UpdateDebtTokenInput calldata inputParams) + external + onlyPoolAdmin + { + ILendingPool cachedPool = pool; - _upgradeTokenImplementation(asset, reserveData.variableDebtTokenAddress, implementation); + DataTypes.ReserveData memory reserveData = cachedPool.getReserveData(inputParams.asset); - emit VariableDebtTokenUpgraded(asset, reserveData.variableDebtTokenAddress, implementation); + DataTypes.ReserveConfigurationMap memory configuration = + cachedPool.getConfiguration(inputParams.asset); + + (, , , uint256 decimals, ) = configuration.getParamsMemory(); + + _upgradeTokenImplementation( + reserveData.variableDebtTokenAddress, + inputParams.implementation, + abi.encodeWithSelector( + IInitializableDebtToken.initialize.selector, + cachedPool, + inputParams.asset, + inputParams.incentivesController, + decimals, + inputParams.name, + inputParams.symbol + ) + ); + + emit VariableDebtTokenUpgraded( + inputParams.asset, + reserveData.variableDebtTokenAddress, + inputParams.implementation + ); } /** @@ -509,44 +448,27 @@ contract LendingPoolConfigurator is VersionedInitializable { pool.setPause(val); } - function _initTokenWithProxy(address implementation, uint8 decimals) internal returns (address) { + function _initTokenWithProxy(address implementation, bytes memory initParams) + internal + returns (address) + { InitializableImmutableAdminUpgradeabilityProxy proxy = new InitializableImmutableAdminUpgradeabilityProxy(address(this)); - bytes memory params = - abi.encodeWithSignature( - 'initialize(uint8,string,string)', - decimals, - IERC20Detailed(implementation).name(), - IERC20Detailed(implementation).symbol() - ); - - proxy.initialize(implementation, params); + proxy.initialize(implementation, initParams); return address(proxy); } function _upgradeTokenImplementation( - address asset, address proxyAddress, - address implementation + address implementation, + bytes memory initParams ) internal { InitializableImmutableAdminUpgradeabilityProxy proxy = InitializableImmutableAdminUpgradeabilityProxy(payable(proxyAddress)); - DataTypes.ReserveConfigurationMap memory configuration = pool.getConfiguration(asset); - - (, , , uint256 decimals, ) = configuration.getParamsMemory(); - - bytes memory params = - abi.encodeWithSignature( - 'initialize(uint8,string,string)', - uint8(decimals), - IERC20Detailed(implementation).name(), - IERC20Detailed(implementation).symbol() - ); - - proxy.upgradeToAndCall(implementation, params); + proxy.upgradeToAndCall(implementation, initParams); } function _checkNoLiquidity(address asset) internal view { diff --git a/contracts/protocol/lendingpool/LendingPoolStorage.sol b/contracts/protocol/lendingpool/LendingPoolStorage.sol index 4edff4a3..198a3eea 100644 --- a/contracts/protocol/lendingpool/LendingPoolStorage.sol +++ b/contracts/protocol/lendingpool/LendingPoolStorage.sol @@ -23,4 +23,10 @@ contract LendingPoolStorage { uint256 internal _reservesCount; bool internal _paused; + + uint256 internal _maxStableRateBorrowSizePercent; + + uint256 internal _flashLoanPremiumTotal; + + uint256 internal _maxNumberOfReserves; } diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index d08e7055..080b792d 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -89,20 +89,13 @@ library ValidationLogic { } struct ValidateBorrowLocalVars { - uint256 principalBorrowBalance; uint256 currentLtv; uint256 currentLiquidationThreshold; - uint256 requestedBorrowAmountETH; uint256 amountOfCollateralNeededETH; uint256 userCollateralBalanceETH; uint256 userBorrowBalanceETH; - uint256 borrowBalanceIncrease; - uint256 currentReserveStableRate; uint256 availableLiquidity; - uint256 finalUserBorrowRate; uint256 healthFactor; - DataTypes.InterestRateMode rateMode; - bool healthFactorBelowThreshold; bool isActive; bool isFrozen; bool borrowingEnabled; @@ -197,7 +190,7 @@ library ValidationLogic { * 3. Users will be able to borrow only a portion of the total available liquidity **/ - if (vars.rateMode == DataTypes.InterestRateMode.STABLE) { + if (interestRateMode == uint256(DataTypes.InterestRateMode.STABLE)) { //check if the borrow mode is stable and if stable rate borrowing is enabled on this reserve require(vars.stableRateBorrowingEnabled, Errors.VL_STABLE_BORROWING_NOT_ENABLED); diff --git a/contracts/protocol/tokenization/AToken.sol b/contracts/protocol/tokenization/AToken.sol index 00df0b61..1a47ebf4 100644 --- a/contracts/protocol/tokenization/AToken.sol +++ b/contracts/protocol/tokenization/AToken.sol @@ -9,13 +9,18 @@ import {WadRayMath} from '../libraries/math/WadRayMath.sol'; import {Errors} from '../libraries/helpers/Errors.sol'; import {VersionedInitializable} from '../libraries/aave-upgradeability/VersionedInitializable.sol'; import {IncentivizedERC20} from './IncentivizedERC20.sol'; +import {IAaveIncentivesController} from '../../interfaces/IAaveIncentivesController.sol'; /** * @title Aave ERC20 AToken * @dev Implementation of the interest bearing token for the Aave protocol * @author Aave */ -contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { +contract AToken is + VersionedInitializable, + IncentivizedERC20('ATOKEN_IMPL', 'ATOKEN_IMPL', 0), + IAToken +{ using WadRayMath for uint256; using SafeERC20 for IERC20; @@ -25,44 +30,46 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { bytes32 public constant PERMIT_TYPEHASH = keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); - uint256 public constant UINT_MAX_VALUE = uint256(-1); uint256 public constant ATOKEN_REVISION = 0x1; - address public immutable UNDERLYING_ASSET_ADDRESS; - address public immutable RESERVE_TREASURY_ADDRESS; - ILendingPool public immutable POOL; /// @dev owner => next valid nonce to submit with permit() mapping(address => uint256) public _nonces; bytes32 public DOMAIN_SEPARATOR; - modifier onlyLendingPool { - require(_msgSender() == address(POOL), Errors.CT_CALLER_MUST_BE_LENDING_POOL); - _; - } + ILendingPool internal _pool; + address internal _treasury; + address internal _underlyingAsset; + IAaveIncentivesController internal _incentivesController; - constructor( - ILendingPool pool, - address underlyingAssetAddress, - address reserveTreasuryAddress, - string memory tokenName, - string memory tokenSymbol, - address incentivesController - ) public IncentivizedERC20(tokenName, tokenSymbol, 18, incentivesController) { - POOL = pool; - UNDERLYING_ASSET_ADDRESS = underlyingAssetAddress; - RESERVE_TREASURY_ADDRESS = reserveTreasuryAddress; + modifier onlyLendingPool { + require(_msgSender() == address(_pool), Errors.CT_CALLER_MUST_BE_LENDING_POOL); + _; } function getRevision() internal pure virtual override returns (uint256) { return ATOKEN_REVISION; } + /** + * @dev Initializes the aToken + * @param pool The address of the lending pool where this aToken will be used + * @param treasury The address of the Aave treasury, receiving the fees on this aToken + * @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 aTokenDecimals The decimals of the aToken, same as the underlying asset's + * @param aTokenName The name of the aToken + * @param aTokenSymbol The symbol of the aToken + */ function initialize( - uint8 underlyingAssetDecimals, - string calldata tokenName, - string calldata tokenSymbol - ) external virtual initializer { + ILendingPool pool, + address treasury, + address underlyingAsset, + IAaveIncentivesController incentivesController, + uint8 aTokenDecimals, + string calldata aTokenName, + string calldata aTokenSymbol + ) external override initializer { uint256 chainId; //solium-disable-next-line @@ -73,16 +80,21 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { DOMAIN_SEPARATOR = keccak256( abi.encode( EIP712_DOMAIN, - keccak256(bytes(tokenName)), + keccak256(bytes(aTokenName)), keccak256(EIP712_REVISION), chainId, address(this) ) ); - _setName(tokenName); - _setSymbol(tokenSymbol); - _setDecimals(underlyingAssetDecimals); + _setName(aTokenName); + _setSymbol(aTokenSymbol); + _setDecimals(aTokenDecimals); + + _pool = pool; + _treasury = treasury; + _underlyingAsset = underlyingAsset; + _incentivesController = incentivesController; } /** @@ -103,7 +115,7 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { require(amountScaled != 0, Errors.CT_INVALID_BURN_AMOUNT); _burn(user, amountScaled); - IERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(receiverOfUnderlying, amount); + IERC20(_underlyingAsset).safeTransfer(receiverOfUnderlying, amount); emit Transfer(user, address(0), amount); emit Burn(user, receiverOfUnderlying, amount, index); @@ -145,14 +157,16 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { return; } + address treasury = _treasury; + // Compared to the normal mint, we don't check for rounding errors. // The amount to mint can easily be very small since it is a fraction of the interest ccrued. // In that case, the treasury will experience a (very small) loss, but it // wont cause potentially valid transactions to fail. - _mint(RESERVE_TREASURY_ADDRESS, amount.rayDiv(index)); + _mint(treasury, amount.rayDiv(index)); - emit Transfer(address(0), RESERVE_TREASURY_ADDRESS, amount); - emit Mint(RESERVE_TREASURY_ADDRESS, amount, index); + emit Transfer(address(0), treasury, amount); + emit Mint(treasury, amount, index); } /** @@ -185,7 +199,7 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { override(IncentivizedERC20, IERC20) returns (uint256) { - return super.balanceOf(user).rayMul(POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS)); + return super.balanceOf(user).rayMul(_pool.getReserveNormalizedIncome(_underlyingAsset)); } /** @@ -226,7 +240,7 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { return 0; } - return currentSupplyScaled.rayMul(POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS)); + return currentSupplyScaled.rayMul(_pool.getReserveNormalizedIncome(_underlyingAsset)); } /** @@ -237,6 +251,41 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { return super.totalSupply(); } + /** + * @dev Returns the address of the Aave treasury, receiving the fees on this aToken + **/ + function RESERVE_TREASURY_ADDRESS() public view returns (address) { + return _treasury; + } + + /** + * @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 For internal usage in the logic of the parent contract IncentivizedERC20 + **/ + function _getIncentivesController() internal view override returns (IAaveIncentivesController) { + return _incentivesController; + } + + /** + * @dev Returns the address of the incentives controller contract + **/ + function getIncentivesController() external view override returns (IAaveIncentivesController) { + return _getIncentivesController(); + } + /** * @dev Transfers the underlying asset to `target`. Used by the LendingPool to transfer * assets in borrow(), withdraw() and flashLoan() @@ -250,7 +299,7 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { onlyLendingPool returns (uint256) { - IERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(target, amount); + IERC20(_underlyingAsset).safeTransfer(target, amount); return amount; } @@ -305,7 +354,10 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { uint256 amount, bool validate ) internal { - uint256 index = POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS); + address underlyingAsset = _underlyingAsset; + ILendingPool pool = _pool; + + uint256 index = pool.getReserveNormalizedIncome(underlyingAsset); uint256 fromBalanceBefore = super.balanceOf(from).rayMul(index); uint256 toBalanceBefore = super.balanceOf(to).rayMul(index); @@ -313,14 +365,7 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { super._transfer(from, to, amount.rayDiv(index)); if (validate) { - POOL.finalizeTransfer( - UNDERLYING_ASSET_ADDRESS, - from, - to, - amount, - fromBalanceBefore, - toBalanceBefore - ); + pool.finalizeTransfer(underlyingAsset, from, to, amount, fromBalanceBefore, toBalanceBefore); } emit BalanceTransfer(from, to, amount, index); diff --git a/contracts/protocol/tokenization/DelegationAwareAToken.sol b/contracts/protocol/tokenization/DelegationAwareAToken.sol index 4e49f2b6..443e10fb 100644 --- a/contracts/protocol/tokenization/DelegationAwareAToken.sol +++ b/contracts/protocol/tokenization/DelegationAwareAToken.sol @@ -14,36 +14,17 @@ import {AToken} from './AToken.sol'; contract DelegationAwareAToken is AToken { modifier onlyPoolAdmin { require( - _msgSender() == ILendingPool(POOL).getAddressesProvider().getPoolAdmin(), + _msgSender() == ILendingPool(_pool).getAddressesProvider().getPoolAdmin(), Errors.CALLER_NOT_POOL_ADMIN ); _; } - constructor( - ILendingPool pool, - address underlyingAssetAddress, - address reserveTreasury, - string memory tokenName, - string memory tokenSymbol, - address incentivesController - ) - public - AToken( - pool, - underlyingAssetAddress, - reserveTreasury, - tokenName, - tokenSymbol, - incentivesController - ) - {} - /** * @dev Delegates voting power of the underlying asset to a `delegatee` address * @param delegatee The address that will receive the delegation **/ function delegateUnderlyingTo(address delegatee) external onlyPoolAdmin { - IDelegationToken(UNDERLYING_ASSET_ADDRESS).delegate(delegatee); + IDelegationToken(_underlyingAsset).delegate(delegatee); } } diff --git a/contracts/protocol/tokenization/IncentivizedERC20.sol b/contracts/protocol/tokenization/IncentivizedERC20.sol index 101eaf9a..fb831769 100644 --- a/contracts/protocol/tokenization/IncentivizedERC20.sol +++ b/contracts/protocol/tokenization/IncentivizedERC20.sol @@ -12,11 +12,9 @@ import {IAaveIncentivesController} from '../../interfaces/IAaveIncentivesControl * @notice Basic ERC20 implementation * @author Aave, inspired by the Openzeppelin ERC20 implementation **/ -contract IncentivizedERC20 is Context, IERC20, IERC20Detailed { +abstract contract IncentivizedERC20 is Context, IERC20, IERC20Detailed { using SafeMath for uint256; - IAaveIncentivesController internal immutable _incentivesController; - mapping(address => uint256) internal _balances; mapping(address => mapping(address => uint256)) private _allowances; @@ -28,13 +26,11 @@ contract IncentivizedERC20 is Context, IERC20, IERC20Detailed { constructor( string memory name, string memory symbol, - uint8 decimals, - address incentivesController + uint8 decimals ) public { _name = name; _symbol = symbol; _decimals = decimals; - _incentivesController = IAaveIncentivesController(incentivesController); } /** @@ -72,6 +68,12 @@ contract IncentivizedERC20 is Context, IERC20, IERC20Detailed { return _balances[account]; } + /** + * @return Abstract function implemented by the child aToken/debtToken. + * Done this way in order to not break compatibility with previous versions of aTokens/debtTokens + **/ + function _getIncentivesController() internal view virtual returns(IAaveIncentivesController); + /** * @dev Executes a transfer of tokens from _msgSender() to recipient * @param recipient The recipient of the tokens @@ -180,11 +182,11 @@ contract IncentivizedERC20 is Context, IERC20, IERC20Detailed { uint256 oldRecipientBalance = _balances[recipient]; _balances[recipient] = _balances[recipient].add(amount); - if (address(_incentivesController) != address(0)) { + if (address(_getIncentivesController()) != address(0)) { uint256 currentTotalSupply = _totalSupply; - _incentivesController.handleAction(sender, currentTotalSupply, oldSenderBalance); + _getIncentivesController().handleAction(sender, currentTotalSupply, oldSenderBalance); if (sender != recipient) { - _incentivesController.handleAction(recipient, currentTotalSupply, oldRecipientBalance); + _getIncentivesController().handleAction(recipient, currentTotalSupply, oldRecipientBalance); } } } @@ -200,8 +202,8 @@ contract IncentivizedERC20 is Context, IERC20, IERC20Detailed { uint256 oldAccountBalance = _balances[account]; _balances[account] = oldAccountBalance.add(amount); - if (address(_incentivesController) != address(0)) { - _incentivesController.handleAction(account, oldTotalSupply, oldAccountBalance); + if (address(_getIncentivesController()) != address(0)) { + _getIncentivesController().handleAction(account, oldTotalSupply, oldAccountBalance); } } @@ -216,8 +218,8 @@ contract IncentivizedERC20 is Context, IERC20, IERC20Detailed { uint256 oldAccountBalance = _balances[account]; _balances[account] = oldAccountBalance.sub(amount, 'ERC20: burn amount exceeds balance'); - if (address(_incentivesController) != address(0)) { - _incentivesController.handleAction(account, oldTotalSupply, oldAccountBalance); + if (address(_getIncentivesController()) != address(0)) { + _getIncentivesController().handleAction(account, oldTotalSupply, oldAccountBalance); } } diff --git a/contracts/protocol/tokenization/StableDebtToken.sol b/contracts/protocol/tokenization/StableDebtToken.sol index 78401405..f52c4931 100644 --- a/contracts/protocol/tokenization/StableDebtToken.sol +++ b/contracts/protocol/tokenization/StableDebtToken.sol @@ -5,6 +5,8 @@ import {DebtTokenBase} from './base/DebtTokenBase.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'; /** @@ -23,13 +25,35 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { mapping(address => uint256) internal _usersStableRate; uint40 internal _totalSupplyTimestamp; - constructor( - address pool, + 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, - string memory name, - string memory symbol, - address incentivesController - ) public DebtTokenBase(pool, underlyingAsset, name, symbol, incentivesController) {} + IAaveIncentivesController incentivesController, + uint8 debtTokenDecimals, + string memory debtTokenName, + string memory debtTokenSymbol + ) public override initializer { + _setName(debtTokenName); + _setSymbol(debtTokenSymbol); + _setDecimals(debtTokenDecimals); + + _pool = pool; + _underlyingAsset = underlyingAsset; + _incentivesController = incentivesController; + } /** * @dev Gets the revision of the stable debt token implementation @@ -300,6 +324,48 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { 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 diff --git a/contracts/protocol/tokenization/VariableDebtToken.sol b/contracts/protocol/tokenization/VariableDebtToken.sol index d2e27ac3..60a47e12 100644 --- a/contracts/protocol/tokenization/VariableDebtToken.sol +++ b/contracts/protocol/tokenization/VariableDebtToken.sol @@ -5,6 +5,8 @@ import {IVariableDebtToken} from '../../interfaces/IVariableDebtToken.sol'; import {WadRayMath} from '../libraries/math/WadRayMath.sol'; import {Errors} from '../libraries/helpers/Errors.sol'; import {DebtTokenBase} from './base/DebtTokenBase.sol'; +import {ILendingPool} from '../../interfaces/ILendingPool.sol'; +import {IAaveIncentivesController} from '../../interfaces/IAaveIncentivesController.sol'; /** * @title VariableDebtToken @@ -17,13 +19,35 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken { uint256 public constant DEBT_TOKEN_REVISION = 0x1; - constructor( - address pool, + 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, - string memory name, - string memory symbol, - address incentivesController - ) public DebtTokenBase(pool, underlyingAsset, name, symbol, incentivesController) {} + IAaveIncentivesController incentivesController, + uint8 debtTokenDecimals, + string memory debtTokenName, + string memory debtTokenSymbol + ) public override initializer { + _setName(debtTokenName); + _setSymbol(debtTokenSymbol); + _setDecimals(debtTokenDecimals); + + _pool = pool; + _underlyingAsset = underlyingAsset; + _incentivesController = incentivesController; + } /** * @dev Gets the revision of the stable debt token implementation @@ -44,7 +68,7 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken { return 0; } - return scaledBalance.rayMul(POOL.getReserveNormalizedVariableDebt(UNDERLYING_ASSET_ADDRESS)); + return scaledBalance.rayMul(_pool.getReserveNormalizedVariableDebt(_underlyingAsset)); } /** @@ -113,8 +137,7 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken { * @return The total supply **/ function totalSupply() public view virtual override returns (uint256) { - return - super.totalSupply().rayMul(POOL.getReserveNormalizedVariableDebt(UNDERLYING_ASSET_ADDRESS)); + return super.totalSupply().rayMul(_pool.getReserveNormalizedVariableDebt(_underlyingAsset)); } /** @@ -139,4 +162,37 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken { { 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 07bdef22..4d75bc2f 100644 --- a/contracts/protocol/tokenization/base/DebtTokenBase.sol +++ b/contracts/protocol/tokenization/base/DebtTokenBase.sol @@ -16,54 +16,20 @@ import {Errors} from '../../libraries/helpers/Errors.sol'; */ abstract contract DebtTokenBase is - IncentivizedERC20, + IncentivizedERC20('DEBTTOKEN_IMPL', 'DEBTTOKEN_IMPL', 0), VersionedInitializable, ICreditDelegationToken { - address public immutable UNDERLYING_ASSET_ADDRESS; - ILendingPool public immutable POOL; - mapping(address => mapping(address => uint256)) internal _borrowAllowances; /** * @dev Only lending pool can call functions marked by this modifier **/ modifier onlyLendingPool { - require(_msgSender() == address(POOL), Errors.CT_CALLER_MUST_BE_LENDING_POOL); + require(_msgSender() == address(_getLendingPool()), Errors.CT_CALLER_MUST_BE_LENDING_POOL); _; } - /** - * @dev The metadata of the token will be set on the proxy, that the reason of - * passing "NULL" and 0 as metadata - */ - constructor( - address pool, - address underlyingAssetAddress, - string memory name, - string memory symbol, - address incentivesController - ) public IncentivizedERC20(name, symbol, 18, incentivesController) { - POOL = ILendingPool(pool); - UNDERLYING_ASSET_ADDRESS = underlyingAssetAddress; - } - - /** - * @dev Initializes the debt token. - * @param name The name of the token - * @param symbol The symbol of the token - * @param decimals The decimals of the token - */ - function initialize( - uint8 decimals, - string memory name, - string memory symbol - ) public initializer { - _setName(name); - _setSymbol(symbol); - _setDecimals(decimals); - } - /** * @dev delegates borrowing power to a user on the specific debt token * @param delegatee the address receiving the delegated borrowing power @@ -73,7 +39,7 @@ abstract contract DebtTokenBase is **/ function approveDelegation(address delegatee, uint256 amount) external override { _borrowAllowances[_msgSender()][delegatee] = amount; - emit BorrowAllowanceDelegated(_msgSender(), delegatee, UNDERLYING_ASSET_ADDRESS, amount); + emit BorrowAllowanceDelegated(_msgSender(), delegatee, _getUnderlyingAssetAddress(), amount); } /** @@ -162,6 +128,10 @@ abstract contract DebtTokenBase is _borrowAllowances[delegator][delegatee] = newAllowance; - emit BorrowAllowanceDelegated(delegator, delegatee, UNDERLYING_ASSET_ADDRESS, newAllowance); + emit BorrowAllowanceDelegated(delegator, delegatee, _getUnderlyingAssetAddress(), newAllowance); } + + function _getUnderlyingAssetAddress() internal view virtual returns (address); + + function _getLendingPool() internal view virtual returns (ILendingPool); }