diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 094c2263..7e24fc10 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -1,4 +1,4 @@ -name: Aave Actions +name: Build on: push: diff --git a/README.md b/README.md index 1609baf9..d59473e7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0) +[![Build pass](https://github.com/AAVE/protocol-v2/actions/workflows/node.js.yml/badge.svg)](https://github.com/aave/protocol-v2/actions/workflows/node.js.yml) ``` .///. .///. //. .// `/////////////- `++:++` .++:++` :++` `++: `++:......---.` diff --git a/contracts/interfaces/IAaveIncentivesController.sol b/contracts/interfaces/IAaveIncentivesController.sol index c049bd77..efa132b2 100644 --- a/contracts/interfaces/IAaveIncentivesController.sol +++ b/contracts/interfaces/IAaveIncentivesController.sol @@ -3,9 +3,126 @@ pragma solidity 0.6.12; pragma experimental ABIEncoderV2; interface IAaveIncentivesController { + event RewardsAccrued(address indexed user, uint256 amount); + + event RewardsClaimed(address indexed user, address indexed to, uint256 amount); + + event RewardsClaimed( + address indexed user, + address indexed to, + address indexed claimer, + uint256 amount + ); + + event ClaimerSet(address indexed user, address indexed claimer); + + /* + * @dev Returns the configuration of the distribution for a certain asset + * @param asset The address of the reference asset of the distribution + * @return The asset index, the emission per second and the last updated timestamp + **/ + function getAssetData(address asset) + external + view + returns ( + uint256, + uint256, + uint256 + ); + + /** + * @dev Whitelists an address to claim the rewards on behalf of another address + * @param user The address of the user + * @param claimer The address of the claimer + */ + function setClaimer(address user, address claimer) external; + + /** + * @dev Returns the whitelisted claimer for a certain address (0x0 if not set) + * @param user The address of the user + * @return The claimer address + */ + function getClaimer(address user) external view returns (address); + + /** + * @dev Configure assets for a certain rewards emission + * @param assets The assets to incentivize + * @param emissionsPerSecond The emission for each asset + */ + function configureAssets(address[] calldata assets, uint256[] calldata emissionsPerSecond) + external; + + /** + * @dev Called by the corresponding asset on any update that affects the rewards distribution + * @param asset The address of the user + * @param userBalance The balance of the user of the asset in the lending pool + * @param totalSupply The total supply of the asset in the lending pool + **/ function handleAction( - address user, + address asset, uint256 userBalance, uint256 totalSupply ) external; + + /** + * @dev Returns the total of rewards of an user, already accrued + not yet accrued + * @param user The address of the user + * @return The rewards + **/ + function getRewardsBalance(address[] calldata assets, address user) + external + view + returns (uint256); + + /** + * @dev Claims reward for an user, on all the assets of the lending pool, accumulating the pending rewards + * @param amount Amount of rewards to claim + * @param to Address that will be receiving the rewards + * @return Rewards claimed + **/ + function claimRewards( + address[] calldata assets, + uint256 amount, + address to + ) external returns (uint256); + + /** + * @dev Claims reward for an user on behalf, on all the assets of the lending pool, accumulating the pending rewards. The caller must + * be whitelisted via "allowClaimOnBehalf" function by the RewardsAdmin role manager + * @param amount Amount of rewards to claim + * @param user Address to check and claim rewards + * @param to Address that will be receiving the rewards + * @return Rewards claimed + **/ + function claimRewardsOnBehalf( + address[] calldata assets, + uint256 amount, + address user, + address to + ) external returns (uint256); + + /** + * @dev returns the unclaimed rewards of the user + * @param user the address of the user + * @return the unclaimed user rewards + */ + function getUserUnclaimedRewards(address user) external view returns (uint256); + + /** + * @dev returns the unclaimed rewards of the user + * @param user the address of the user + * @param asset The asset to incentivize + * @return the user index for the asset + */ + function getUserAssetData(address user, address asset) external view returns (uint256); + + /** + * @dev for backward compatibility with previous implementation of the Incentives controller + */ + function REWARD_TOKEN() external view returns (address); + + /** + * @dev for backward compatibility with previous implementation of the Incentives controller + */ + function PRECISION() external view returns (uint8); } diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol index 1178d350..55cbbedd 100644 --- a/contracts/interfaces/ILendingPool.sol +++ b/contracts/interfaces/ILendingPool.sol @@ -254,6 +254,57 @@ interface ILendingPool { address onBehalfOf ) external returns (uint256); + /** + * @notice Deposit with transfer approval of asset to be deposited done via permit function + * see: https://eips.ethereum.org/EIPS/eip-2612 and https://eips.ethereum.org/EIPS/eip-713 + * @param asset The address of the underlying asset to deposit + * @param amount The amount to be deposited + * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user + * wants to receive them on his own wallet, or a different address if the beneficiary of aTokens + * is a different wallet + * @param referralCode Code used to register the integrator originating the operation, for potential rewards. + * 0 if the action is executed directly by the user, without any middle-man + * @param permitV V parameter of ERC712 permit sig + * @param permitR R parameter of ERC712 permit sig + * @param permitS S parameter of ERC712 permit sig + **/ + function depositWithPermit( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode, + uint256 deadline, + uint8 permitV, + bytes32 permitR, + bytes32 permitS + ) external; + + /** + * @notice Repay with transfer approval of asset to be repaid done via permit function + * see: https://eips.ethereum.org/EIPS/eip-2612 and https://eips.ethereum.org/EIPS/eip-713 + * @param asset The address of the borrowed underlying asset previously borrowed + * @param amount The amount to repay + * - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode` + * @param rateMode The interest rate mode at of the debt the user wants to repay: 1 for Stable, 2 for Variable + * @param onBehalfOf Address of the user who will get his debt reduced/removed. Should be the address of the + * user calling the function if he wants to reduce/remove his own debt, or the address of any other + * other borrower whose debt should be removed + * @param permitV V parameter of ERC712 permit sig + * @param permitR R parameter of ERC712 permit sig + * @param permitS S parameter of ERC712 permit sig + * @return The final amount repaid + **/ + function repayWithPermit( + address asset, + uint256 amount, + uint256 rateMode, + address onBehalfOf, + uint256 deadline, + uint8 permitV, + bytes32 permitR, + bytes32 permitS + ) external returns (uint256); + /** * @dev Allows a borrower to swap his debt between stable and variable mode, or viceversa * @param asset The address of the underlying asset borrowed diff --git a/contracts/misc/UiPoolDataProvider.sol b/contracts/misc/UiPoolDataProvider.sol index 65fdc4dd..63dd7a1c 100644 --- a/contracts/misc/UiPoolDataProvider.sol +++ b/contracts/misc/UiPoolDataProvider.sol @@ -4,6 +4,7 @@ pragma experimental ABIEncoderV2; import {IERC20Detailed} from '../dependencies/openzeppelin/contracts/IERC20Detailed.sol'; import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol'; +import {IAaveIncentivesController} from '../interfaces/IAaveIncentivesController.sol'; import {IUiPoolDataProvider} from './interfaces/IUiPoolDataProvider.sol'; import {ILendingPool} from '../interfaces/ILendingPool.sol'; import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol'; @@ -24,6 +25,13 @@ contract UiPoolDataProvider is IUiPoolDataProvider { using UserConfiguration for DataTypes.UserConfigurationMap; address public constant MOCK_USD_ADDRESS = 0x10F7Fc1F91Ba351f9C629c5947AD69bD03C05b96; + IAaveIncentivesController public immutable incentivesController; + IPriceOracleGetter public immutable oracle; + + constructor(IAaveIncentivesController _incentivesController, IPriceOracleGetter _oracle) public { + incentivesController = _incentivesController; + oracle = _oracle; + } function getInterestRateStrategySlopes(DefaultReserveInterestRateStrategy interestRateStrategy) internal @@ -50,11 +58,11 @@ contract UiPoolDataProvider is IUiPoolDataProvider { returns ( AggregatedReserveData[] memory, UserReserveData[] memory, + uint256, uint256 ) { ILendingPool lendingPool = ILendingPool(provider.getLendingPool()); - IPriceOracleGetter oracle = IPriceOracleGetter(provider.getPriceOracle()); address[] memory reserves = lendingPool.getReservesList(); DataTypes.UserConfigurationMap memory userConfig = lendingPool.getUserConfiguration(user); @@ -122,7 +130,43 @@ contract UiPoolDataProvider is IUiPoolDataProvider { DefaultReserveInterestRateStrategy(reserveData.interestRateStrategyAddress) ); + // incentives + if (address(0) != address(incentivesController)) { + ( + reserveData.aEmissionPerSecond, + reserveData.aIncentivesLastUpdateTimestamp, + reserveData.aTokenIncentivesIndex + ) = incentivesController.getAssetData(reserveData.aTokenAddress); + + ( + reserveData.sEmissionPerSecond, + reserveData.sIncentivesLastUpdateTimestamp, + reserveData.sTokenIncentivesIndex + ) = incentivesController.getAssetData(reserveData.stableDebtTokenAddress); + + ( + reserveData.vEmissionPerSecond, + reserveData.vIncentivesLastUpdateTimestamp, + reserveData.vTokenIncentivesIndex + ) = incentivesController.getAssetData(reserveData.variableDebtTokenAddress); + } + if (user != address(0)) { + // incentives + if (address(0) != address(incentivesController)) { + userReservesData[i].aTokenincentivesUserIndex = incentivesController.getUserAssetData( + user, + reserveData.aTokenAddress + ); + userReservesData[i].vTokenincentivesUserIndex = incentivesController.getUserAssetData( + user, + reserveData.variableDebtTokenAddress + ); + userReservesData[i].sTokenincentivesUserIndex = incentivesController.getUserAssetData( + user, + reserveData.stableDebtTokenAddress + ); + } // user reserve data userReservesData[i].underlyingAsset = reserveData.underlyingAsset; userReservesData[i].scaledATokenBalance = IAToken(reserveData.aTokenAddress) @@ -155,6 +199,12 @@ contract UiPoolDataProvider is IUiPoolDataProvider { } } } - return (reservesData, userReservesData, oracle.getAssetPrice(MOCK_USD_ADDRESS)); + + return ( + reservesData, + userReservesData, + oracle.getAssetPrice(MOCK_USD_ADDRESS), + incentivesController.getUserUnclaimedRewards(user) + ); } } diff --git a/contracts/misc/WETHGateway.sol b/contracts/misc/WETHGateway.sol index 336e8de2..39ecf909 100644 --- a/contracts/misc/WETHGateway.sol +++ b/contracts/misc/WETHGateway.sol @@ -131,6 +131,41 @@ contract WETHGateway is IWETHGateway, Ownable { _safeTransferETH(msg.sender, amount); } + /** + * @dev withdraws the WETH _reserves of msg.sender. + * @param lendingPool address of the targeted underlying lending pool + * @param amount amount of aWETH to withdraw and receive native ETH + * @param to address of the user who will receive native ETH + * @param deadline validity deadline of permit and so depositWithPermit signature + * @param permitV V parameter of ERC712 permit sig + * @param permitR R parameter of ERC712 permit sig + * @param permitS S parameter of ERC712 permit sig + */ + function withdrawETHWithPermit( + address lendingPool, + uint256 amount, + address to, + uint256 deadline, + uint8 permitV, + bytes32 permitR, + bytes32 permitS + ) external override { + IAToken aWETH = IAToken(ILendingPool(lendingPool).getReserveData(address(WETH)).aTokenAddress); + uint256 userBalance = aWETH.balanceOf(msg.sender); + uint256 amountToWithdraw = amount; + + // if amount is equal to uint(-1), the user wants to redeem everything + if (amount == type(uint256).max) { + amountToWithdraw = userBalance; + } + // chosing to permit `amount`and not `amountToWithdraw`, easier for frontends, intregrators. + aWETH.permit(msg.sender, address(this), amount, deadline, permitV, permitR, permitS); + aWETH.transferFrom(msg.sender, address(this), amountToWithdraw); + ILendingPool(lendingPool).withdraw(address(WETH), amountToWithdraw, address(this)); + WETH.withdraw(amountToWithdraw); + _safeTransferETH(to, amountToWithdraw); + } + /** * @dev transfer ETH to an address, revert if it fails. * @param to recipient of the transfer diff --git a/contracts/misc/interfaces/IUiPoolDataProvider.sol b/contracts/misc/interfaces/IUiPoolDataProvider.sol index 81a553e8..34f06d1c 100644 --- a/contracts/misc/interfaces/IUiPoolDataProvider.sol +++ b/contracts/misc/interfaces/IUiPoolDataProvider.sol @@ -3,6 +3,7 @@ pragma solidity 0.6.12; pragma experimental ABIEncoderV2; import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol'; +import {IAaveIncentivesController} from '../../interfaces/IAaveIncentivesController.sol'; interface IUiPoolDataProvider { struct AggregatedReserveData { @@ -41,12 +42,17 @@ interface IUiPoolDataProvider { uint256 variableRateSlope2; uint256 stableRateSlope1; uint256 stableRateSlope2; + // incentives + uint256 aEmissionPerSecond; + uint256 vEmissionPerSecond; + uint256 sEmissionPerSecond; + uint256 aIncentivesLastUpdateTimestamp; + uint256 vIncentivesLastUpdateTimestamp; + uint256 sIncentivesLastUpdateTimestamp; + uint256 aTokenIncentivesIndex; + uint256 vTokenIncentivesIndex; + uint256 sTokenIncentivesIndex; } - // - // struct ReserveData { - // uint256 averageStableBorrowRate; - // uint256 totalLiquidity; - // } struct UserReserveData { address underlyingAsset; @@ -56,38 +62,19 @@ interface IUiPoolDataProvider { uint256 scaledVariableDebt; uint256 principalStableDebt; uint256 stableBorrowLastUpdateTimestamp; + // incentives + uint256 aTokenincentivesUserIndex; + uint256 vTokenincentivesUserIndex; + uint256 sTokenincentivesUserIndex; } - // - // struct ATokenSupplyData { - // string name; - // string symbol; - // uint8 decimals; - // uint256 totalSupply; - // address aTokenAddress; - // } - function getReservesData(ILendingPoolAddressesProvider provider, address user) external view returns ( AggregatedReserveData[] memory, UserReserveData[] memory, + uint256, uint256 ); - - // function getUserReservesData(ILendingPoolAddressesProvider provider, address user) - // external - // view - // returns (UserReserveData[] memory); - // - // function getAllATokenSupply(ILendingPoolAddressesProvider provider) - // external - // view - // returns (ATokenSupplyData[] memory); - // - // function getATokenSupply(address[] calldata aTokens) - // external - // view - // returns (ATokenSupplyData[] memory); } diff --git a/contracts/misc/interfaces/IWETHGateway.sol b/contracts/misc/interfaces/IWETHGateway.sol index 78d913cd..326ece57 100644 --- a/contracts/misc/interfaces/IWETHGateway.sol +++ b/contracts/misc/interfaces/IWETHGateway.sol @@ -27,4 +27,14 @@ interface IWETHGateway { uint256 interesRateMode, uint16 referralCode ) external; + + function withdrawETHWithPermit( + address lendingPool, + uint256 amount, + address to, + uint256 deadline, + uint8 permitV, + bytes32 permitR, + bytes32 permitS + ) external; } diff --git a/contracts/mocks/tokens/MintableERC20.sol b/contracts/mocks/tokens/MintableERC20.sol index 56da583e..82e40b99 100644 --- a/contracts/mocks/tokens/MintableERC20.sol +++ b/contracts/mocks/tokens/MintableERC20.sol @@ -8,14 +8,67 @@ import {ERC20} from '../../dependencies/openzeppelin/contracts/ERC20.sol'; * @dev ERC20 minting logic */ contract MintableERC20 is ERC20 { + + bytes public constant EIP712_REVISION = bytes('1'); + bytes32 internal constant EIP712_DOMAIN = + keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); + bytes32 public constant PERMIT_TYPEHASH = + keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); + + mapping(address => uint256) public _nonces; + + bytes32 public DOMAIN_SEPARATOR; + constructor( string memory name, string memory symbol, uint8 decimals ) public ERC20(name, symbol) { + + uint256 chainId; + + assembly { + chainId := chainid() + } + + DOMAIN_SEPARATOR = keccak256( + abi.encode( + EIP712_DOMAIN, + keccak256(bytes(name)), + keccak256(EIP712_REVISION), + chainId, + address(this) + ) + ); _setupDecimals(decimals); } + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + require(owner != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[owner]; + bytes32 digest = + keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) + ) + ); + require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + _nonces[owner] = currentValidNonce.add(1); + _approve(owner, spender, value); + } + /** * @dev Function to mint tokens * @param value The amount of tokens to mint. diff --git a/contracts/protocol/lendingpool/LendingPool.sol b/contracts/protocol/lendingpool/LendingPool.sol index 1425b0ff..8c6ec24d 100644 --- a/contracts/protocol/lendingpool/LendingPool.sol +++ b/contracts/protocol/lendingpool/LendingPool.sol @@ -4,6 +4,7 @@ pragma experimental ABIEncoderV2; import {SafeMath} from '../../dependencies/openzeppelin/contracts/SafeMath.sol'; import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol'; +import {IERC20WithPermit} from '../../interfaces/IERC20WithPermit.sol'; import {SafeERC20} from '../../dependencies/openzeppelin/contracts/SafeERC20.sol'; import {Address} from '../../dependencies/openzeppelin/contracts/Address.sol'; import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol'; @@ -107,25 +108,36 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage address onBehalfOf, uint16 referralCode ) external override whenNotPaused { - DataTypes.ReserveData storage reserve = _reserves[asset]; + _executeDeposit(asset, amount, onBehalfOf, referralCode); + } - ValidationLogic.validateDeposit(reserve, amount); - - address aToken = reserve.aTokenAddress; - - reserve.updateState(); - reserve.updateInterestRates(asset, aToken, amount, 0); - - IERC20(asset).safeTransferFrom(msg.sender, aToken, amount); - - bool isFirstDeposit = IAToken(aToken).mint(onBehalfOf, amount, reserve.liquidityIndex); - - if (isFirstDeposit) { - _usersConfig[onBehalfOf].setUsingAsCollateral(reserve.id, true); - emit ReserveUsedAsCollateralEnabled(asset, onBehalfOf); - } - - emit Deposit(asset, msg.sender, onBehalfOf, amount, referralCode); + /** + * @notice Deposit with transfer approval of asset to be deposited done via permit function + * see: https://eips.ethereum.org/EIPS/eip-2612 and https://eips.ethereum.org/EIPS/eip-713 + * @param asset The address of the underlying asset to deposit + * @param amount The amount to be deposited + * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user + * wants to receive them on his own wallet, or a different address if the beneficiary of aTokens + * is a different wallet + * @param referralCode Code used to register the integrator originating the operation, for potential rewards. + * 0 if the action is executed directly by the user, without any middle-man + * @param deadline validity deadline of permit and so depositWithPermit signature + * @param permitV V parameter of ERC712 permit sig + * @param permitR R parameter of ERC712 permit sig + * @param permitS S parameter of ERC712 permit sig + **/ + function depositWithPermit( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode, + uint256 deadline, + uint8 permitV, + bytes32 permitR, + bytes32 permitS + ) external override { + IERC20WithPermit(asset).permit(msg.sender, address(this), amount, deadline, permitV, permitR, permitS); + _executeDeposit(asset, amount, onBehalfOf, referralCode); } /** @@ -205,6 +217,36 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage return _executeRepay(asset, amount, rateMode, onBehalfOf); } + /** + * @notice Repay with transfer approval of asset to be repaid done via permit function + * see: https://eips.ethereum.org/EIPS/eip-2612 and https://eips.ethereum.org/EIPS/eip-713 + * @param asset The address of the borrowed underlying asset previously borrowed + * @param amount The amount to repay + * - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode` + * @param rateMode The interest rate mode at of the debt the user wants to repay: 1 for Stable, 2 for Variable + * @param onBehalfOf Address of the user who will get his debt reduced/removed. Should be the address of the + * user calling the function if he wants to reduce/remove his own debt, or the address of any other + * other borrower whose debt should be removed + * @param deadline validity deadline of permit and so depositWithPermit signature + * @param permitV V parameter of ERC712 permit sig + * @param permitR R parameter of ERC712 permit sig + * @param permitS S parameter of ERC712 permit sig + * @return The final amount repaid + **/ + function repayWithPermit( + address asset, + uint256 amount, + uint256 rateMode, + address onBehalfOf, + uint256 deadline, + uint8 permitV, + bytes32 permitR, + bytes32 permitS + ) external override returns (uint256) { + IERC20WithPermit(asset).permit(msg.sender, address(this), amount, deadline, permitV, permitR, permitS); + return _executeRepay(asset, amount, rateMode, onBehalfOf); + } + /** * @dev Allows a borrower to swap his debt between stable and variable mode, or viceversa * @param asset The address of the underlying asset borrowed @@ -865,6 +907,33 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage ); } + function _executeDeposit( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode + ) internal { + DataTypes.ReserveData storage reserve = _reserves[asset]; + + ValidationLogic.validateDeposit(reserve, amount); + + address aToken = reserve.aTokenAddress; + + reserve.updateState(); + reserve.updateInterestRates(asset, aToken, amount, 0); + + IERC20(asset).safeTransferFrom(msg.sender, aToken, amount); + + bool isFirstDeposit = IAToken(aToken).mint(onBehalfOf, amount, reserve.liquidityIndex); + + if (isFirstDeposit) { + _usersConfig[onBehalfOf].setUsingAsCollateral(reserve.id, true); + emit ReserveUsedAsCollateralEnabled(asset, onBehalfOf); + } + + emit Deposit(asset, msg.sender, onBehalfOf, amount, referralCode); + } + function _executeWithdraw( address asset, uint256 amount, diff --git a/contracts/protocol/libraries/logic/GenericLogic.sol b/contracts/protocol/libraries/logic/GenericLogic.sol index d4081dda..5722dd2b 100644 --- a/contracts/protocol/libraries/logic/GenericLogic.sol +++ b/contracts/protocol/libraries/logic/GenericLogic.sol @@ -4,6 +4,7 @@ pragma experimental ABIEncoderV2; import {SafeMath} from '../../../dependencies/openzeppelin/contracts/SafeMath.sol'; import {IERC20} from '../../../dependencies/openzeppelin/contracts/IERC20.sol'; +import {IScaledBalanceToken} from '../../../interfaces/IScaledBalanceToken.sol'; import {ReserveLogic} from './ReserveLogic.sol'; import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol'; import {UserConfiguration} from '../configuration/UserConfiguration.sol'; @@ -116,10 +117,12 @@ library GenericLogic { } struct CalculateUserAccountDataVars { - uint256 reserveUnitPrice; - uint256 tokenUnit; - uint256 compoundedLiquidityBalance; - uint256 compoundedBorrowBalance; + uint256 assetPrice; + uint256 assetUnit; + uint256 userBalance; + uint256 userBalanceETH; + uint256 userDebt; + uint256 userDebtETH; uint256 decimals; uint256 ltv; uint256 liquidationThreshold; @@ -130,6 +133,8 @@ library GenericLogic { uint256 avgLtv; uint256 avgLiquidationThreshold; uint256 reservesLength; + uint256 normalizedIncome; + uint256 normalizedDebt; bool healthFactorBelowThreshold; address currentReserveAddress; bool usageAsCollateralEnabled; @@ -182,34 +187,41 @@ library GenericLogic { .configuration .getParams(); - vars.tokenUnit = 10**vars.decimals; - vars.reserveUnitPrice = IPriceOracleGetter(oracle).getAssetPrice(vars.currentReserveAddress); + vars.assetUnit = 10**vars.decimals; + vars.assetPrice = IPriceOracleGetter(oracle).getAssetPrice(vars.currentReserveAddress); if (vars.liquidationThreshold != 0 && userConfig.isUsingAsCollateral(vars.i)) { - vars.compoundedLiquidityBalance = IERC20(currentReserve.aTokenAddress).balanceOf(user); + vars.userBalance = IScaledBalanceToken(currentReserve.aTokenAddress).scaledBalanceOf(user); + if (vars.userBalance > 0) { + vars.normalizedIncome = currentReserve.getNormalizedIncome(); + vars.userBalance = vars.userBalance.rayMul(vars.normalizedIncome); + } - uint256 liquidityBalanceETH = - vars.reserveUnitPrice.mul(vars.compoundedLiquidityBalance).div(vars.tokenUnit); + vars.userBalanceETH = vars.assetPrice.mul(vars.userBalance).div(vars.assetUnit); - vars.totalCollateralInETH = vars.totalCollateralInETH.add(liquidityBalanceETH); + vars.totalCollateralInETH = vars.totalCollateralInETH.add(vars.userBalanceETH); - vars.avgLtv = vars.avgLtv.add(liquidityBalanceETH.mul(vars.ltv)); + vars.avgLtv = vars.avgLtv.add(vars.userBalanceETH.mul(vars.ltv)); vars.avgLiquidationThreshold = vars.avgLiquidationThreshold.add( - liquidityBalanceETH.mul(vars.liquidationThreshold) + vars.userBalanceETH.mul(vars.liquidationThreshold) ); } if (userConfig.isBorrowing(vars.i)) { - vars.compoundedBorrowBalance = IERC20(currentReserve.stableDebtTokenAddress).balanceOf( - user - ); - vars.compoundedBorrowBalance = vars.compoundedBorrowBalance.add( - IERC20(currentReserve.variableDebtTokenAddress).balanceOf(user) - ); + vars.userDebt = IScaledBalanceToken(currentReserve.variableDebtTokenAddress) + .scaledBalanceOf(user); - vars.totalDebtInETH = vars.totalDebtInETH.add( - vars.reserveUnitPrice.mul(vars.compoundedBorrowBalance).div(vars.tokenUnit) + if (vars.userDebt > 0) { + vars.normalizedDebt = currentReserve.getNormalizedDebt(); + vars.userDebt = vars.userDebt.rayMul(vars.normalizedDebt); + } + + vars.userDebt = vars.userDebt.add( + IERC20(currentReserve.stableDebtTokenAddress).balanceOf(user) ); + vars.userDebtETH = vars.assetPrice.mul(vars.userDebt).div(vars.assetUnit); + vars.totalDebtInETH = vars.totalDebtInETH.add(vars.userDebtETH); + } } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 07f34a91..68024d2a 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -55,12 +55,28 @@ import { registerContractInJsonDb, linkBytecode, insertContractAddressInDb, + deployContract, } from './contracts-helpers'; import { StableAndVariableTokensHelperFactory } from '../types/StableAndVariableTokensHelperFactory'; import { MintableDelegationERC20 } from '../types/MintableDelegationERC20'; import { readArtifact as buidlerReadArtifact } from '@nomiclabs/buidler/plugins'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { LendingPoolLibraryAddresses } from '../types/LendingPoolFactory'; +import { UiPoolDataProvider } from '../types'; +import { verifyContract } from './etherscan-verification'; + +export const deployUiPoolDataProvider = async ( + [incentivesController, aaveOracle]: [tEthereumAddress, tEthereumAddress], + verify?: boolean +) => { + const id = eContractid.UiPoolDataProvider; + const args: string[] = [incentivesController, aaveOracle]; + const instance = await deployContract(id, args); + if (verify) { + await verifyContract(instance.address, args); + } + return instance; +}; const readArtifact = async (id: string) => { if (DRE.network.name === eEthereumNetwork.buidlerevm) { @@ -546,7 +562,15 @@ export const deployMockVariableDebtToken = async ( }; export const deployMockAToken = async ( - args: [tEthereumAddress, tEthereumAddress, tEthereumAddress, tEthereumAddress, string, string, string], + args: [ + tEthereumAddress, + tEthereumAddress, + tEthereumAddress, + tEthereumAddress, + string, + string, + string + ], verify?: boolean ) => { const instance = await withSaveAndVerify( diff --git a/helpers/contracts-getters.ts b/helpers/contracts-getters.ts index 0f70c48b..33da4054 100644 --- a/helpers/contracts-getters.ts +++ b/helpers/contracts-getters.ts @@ -363,3 +363,5 @@ export const getFlashLiquidationAdapter = async (address?: tEthereumAddress) => .address, await getFirstSigner() ); + +export const getChainId = async () => (await DRE.ethers.provider.getNetwork()).chainId; diff --git a/package-lock.json b/package-lock.json index 4b51bc02..8310558b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3118,7 +3118,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -5530,8 +5529,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -6370,36 +6368,6 @@ "@ethersproject/strings": ">=5.0.0-beta.130" } }, - "@ethersproject/abstract-provider": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.0.8.tgz", - "integrity": "sha512-fqJXkewcGdi8LogKMgRyzc/Ls2js07yor7+g9KfPs09uPOcQLg7cc34JN+lk34HH9gg2HU0DIA5797ZR8znkfw==", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/bignumber": "^5.0.13", - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/logger": "^5.0.8", - "@ethersproject/networks": "^5.0.7", - "@ethersproject/properties": "^5.0.7", - "@ethersproject/transactions": "^5.0.9", - "@ethersproject/web": "^5.0.12" - } - }, - "@ethersproject/abstract-signer": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.0.10.tgz", - "integrity": "sha512-irx7kH7FDAeW7QChDPW19WsxqeB1d3XLyOLSXm0bfPqL1SS07LXWltBJUBUxqC03ORpAOcM3JQj57DU8JnVY2g==", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/abstract-provider": "^5.0.8", - "@ethersproject/bignumber": "^5.0.13", - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/logger": "^5.0.8", - "@ethersproject/properties": "^5.0.7" - } - }, "@ethersproject/address": { "version": "5.0.9", "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.0.9.tgz", @@ -6414,16 +6382,6 @@ "@ethersproject/rlp": "^5.0.7" } }, - "@ethersproject/base64": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.0.7.tgz", - "integrity": "sha512-S5oh5DVfCo06xwJXT8fQC68mvJfgScTl2AXvbYMsHNfIBTDb084Wx4iA9MNlEReOv6HulkS+gyrUM/j3514rSw==", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/bytes": "^5.0.9" - } - }, "@ethersproject/bignumber": { "version": "5.0.13", "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.13.tgz", @@ -6491,16 +6449,6 @@ "dev": true, "optional": true }, - "@ethersproject/networks": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.0.7.tgz", - "integrity": "sha512-dI14QATndIcUgcCBL1c5vUr/YsI5cCHLN81rF7PU+yS7Xgp2/Rzbr9+YqpC6NBXHFUASjh6GpKqsVMpufAL0BQ==", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/logger": "^5.0.8" - } - }, "@ethersproject/properties": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.7.tgz", @@ -6565,20 +6513,6 @@ "@ethersproject/signing-key": "^5.0.8" } }, - "@ethersproject/web": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.0.12.tgz", - "integrity": "sha512-gVxS5iW0bgidZ76kr7LsTxj4uzN5XpCLzvZrLp8TP+4YgxHfCeetFyQkRPgBEAJdNrexdSBayvyJvzGvOq0O8g==", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/base64": "^5.0.7", - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/logger": "^5.0.8", - "@ethersproject/properties": "^5.0.7", - "@ethersproject/strings": "^5.0.8" - } - }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -7778,9 +7712,9 @@ }, "dependencies": { "bn.js": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", - "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", "dev": true, "optional": true } @@ -7891,6 +7825,14 @@ "dev": true, "requires": { "node-gyp-build": "^4.2.0" + }, + "dependencies": { + "node-gyp-build": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", + "dev": true + } } }, "bytes": { @@ -7991,16 +7933,6 @@ } } }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, "caniuse-lite": { "version": "1.0.30001174", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001174.tgz", @@ -8519,7 +8451,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -8745,7 +8676,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -10915,8 +10845,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -10924,17 +10853,6 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "get-intrinsic": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.2.tgz", - "integrity": "sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, "get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -10982,6 +10900,14 @@ "requires": { "min-document": "^2.19.0", "process": "^0.11.10" + }, + "dependencies": { + "process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=", + "dev": true + } } }, "got": { @@ -11042,7 +10968,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -11080,8 +11005,7 @@ "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, "has-to-string-tag-x": { "version": "1.4.1", @@ -11354,8 +11278,7 @@ "is-callable": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", - "dev": true + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" }, "is-ci": { "version": "2.0.0", @@ -11378,8 +11301,7 @@ "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" }, "is-descriptor": { "version": "1.0.2", @@ -11428,8 +11350,7 @@ "is-negative-zero": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==" }, "is-object": { "version": "1.0.2", @@ -11458,7 +11379,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -11474,7 +11394,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -12317,8 +12236,7 @@ "object-inspect": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", - "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", - "dev": true + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" }, "object-is": { "version": "1.1.4", @@ -12328,13 +12246,33 @@ "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object-visit": { "version": "1.0.1", @@ -12349,12 +12287,32 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", "has-symbols": "^1.0.1", "object-keys": "^1.1.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "object.getownpropertydescriptors": { @@ -12626,12 +12584,6 @@ "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", "dev": true }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -12892,22 +12844,71 @@ }, "dependencies": { "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", + "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", "dev": true, "requires": { + "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.2", + "is-string": "^1.0.5", + "object-inspect": "^1.9.0", "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.0" + }, + "dependencies": { + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "dev": true + }, + "is-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + } } } } @@ -12995,6 +12996,15 @@ "uuid": "^3.3.2" } }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -13653,13 +13663,34 @@ "call-bind": "^1.0.0", "define-properties": "^1.1.3", "es-abstract": "^1.18.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "string.prototype.trimend": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", - "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -13669,7 +13700,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", - "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -13856,15 +13886,6 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", "dev": true - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } } } }, @@ -14226,6 +14247,14 @@ "dev": true, "requires": { "node-gyp-build": "^4.2.0" + }, + "dependencies": { + "node-gyp-build": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", + "dev": true + } } }, "utf8": { @@ -15204,7 +15233,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -15761,7 +15789,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -15787,8 +15814,7 @@ "has-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" }, "has-to-string-tag-x": { "version": "1.4.1", diff --git a/package.json b/package.json index dbeb8ce1..699061c5 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "test": "TS_NODE_TRANSPILE_ONLY=1 hardhat test ./test-suites/test-aave/*.spec.ts", "test-amm": "TS_NODE_TRANSPILE_ONLY=1 hardhat test ./test-suites/test-amm/*.spec.ts", "test-amm-scenarios": "TS_NODE_TRANSPILE_ONLY=1 hardhat test ./test-suites/test-amm/__setup.spec.ts test-suites/test-amm/scenario.spec.ts", - "test-scenarios": "npx hardhat test test-suites/test-aave/__setup.spec.ts test-suites/test-aave/scenario.spec.ts", + "test-scenarios": "hardhat test test-suites/test-aave/__setup.spec.ts test-suites/test-aave/scenario.spec.ts", "test-repay-with-collateral": "hardhat test test-suites/test-aave/__setup.spec.ts test-suites/test-aave/repay-with-collateral.spec.ts", "test-liquidate-with-collateral": "hardhat test test-suites/test-aave/__setup.spec.ts test-suites/test-aave/flash-liquidation-with-collateral.spec.ts", "test-liquidate-underlying": "hardhat test test-suites/test-aave/__setup.spec.ts test-suites/test-aave/liquidation-underlying.spec.ts", @@ -60,8 +60,11 @@ "ci:clean": "rm -rf ./artifacts ./cache ./types", "print-contracts:kovan": "npm run hardhat:kovan -- print-contracts", "print-contracts:main": "npm run hardhat:main -- print-contracts", - "print-contracts:ropsten": "npm run hardhat:ropsten -- print-contracts", - "dev:deployUIProvider": "npm run hardhat:kovan deploy-UiPoolDataProvider", + "print-contracts:ropsten": "npm run hardhat:main -- print-contracts", + "dev:deployUIProvider": "hardhat --network kovan deploy-UiPoolDataProvider --verify", + "main:deployUIProvider": "hardhat --network main deploy-UiPoolDataProvider --verify", + "matic:deployUIProvider": "hardhat --network matic deploy-UiPoolDataProvider", + "mumbai:deployUIProvider": "hardhat --network mumbai deploy-UiPoolDataProvider", "dev:deployUniswapRepayAdapter": "hardhat --network kovan deploy-UniswapRepayAdapter --provider 0x88757f2f99175387aB4C6a4b3067c77A695b0349 --router 0xfcd87315f0e4067070ade8682fcdbc3006631441 --weth 0xd0a1e359811322d97991e03f863a0c30c2cf029c", "dev:UniswapLiquiditySwapAdapter": "hardhat --network kovan deploy-UniswapLiquiditySwapAdapter --provider 0x88757f2f99175387aB4C6a4b3067c77A695b0349 --router 0xfcd87315f0e4067070ade8682fcdbc3006631441 --weth 0xd0a1e359811322d97991e03f863a0c30c2cf029c", "main:deployUniswapRepayAdapter": "hardhat --network main deploy-UniswapRepayAdapter --provider 0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5 --router 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D --weth 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", diff --git a/tasks/deployments/deploy-UiPoolDataProvider.ts b/tasks/deployments/deploy-UiPoolDataProvider.ts index 2d696702..6cc07449 100644 --- a/tasks/deployments/deploy-UiPoolDataProvider.ts +++ b/tasks/deployments/deploy-UiPoolDataProvider.ts @@ -1,27 +1,44 @@ import { task } from 'hardhat/config'; - -import { UiPoolDataProviderFactory } from '../../types'; -import { verifyContract } from '../../helpers/etherscan-verification'; -import { eContractid } from '../../helpers/types'; +import { eContractid, eEthereumNetwork, ePolygonNetwork } from '../../helpers/types'; +import { deployUiPoolDataProvider } from '../../helpers/contracts-deployments'; task(`deploy-${eContractid.UiPoolDataProvider}`, `Deploys the UiPoolDataProvider contract`) .addFlag('verify', 'Verify UiPoolDataProvider contract via Etherscan API.') .setAction(async ({ verify }, localBRE) => { await localBRE.run('set-DRE'); - if (!localBRE.network.config.chainId) { throw new Error('INVALID_CHAIN_ID'); } + const addressesByNetwork = { + [eEthereumNetwork.kovan]: { + incentivesController: '0x0000000000000000000000000000000000000000', + aaveOracle: '0x8fb777d67e9945e2c01936e319057f9d41d559e6', + }, + [eEthereumNetwork.main]: { + incentivesController: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5', + aaveOracle: '0xa50ba011c48153de246e5192c8f9258a2ba79ca9', + }, + [ePolygonNetwork.matic]: { + incentivesController: '0x357D51124f59836DeD84c8a1730D72B749d8BC23', + aaveOracle: '0x21451bD7b528896B4AB2b9764b521D6ed641708d', + }, + [ePolygonNetwork.mumbai]: { + incentivesController: '0xd41aE58e803Edf4304334acCE4DC4Ec34a63C644', + aaveOracle: '0xC365C653f7229894F93994CD0b30947Ab69Ff1D5', + }, + }; + console.log(`\n- UiPoolDataProvider deployment`); console.log(`\tDeploying UiPoolDataProvider implementation ...`); - const uiPoolDataProvider = await new UiPoolDataProviderFactory( - await localBRE.ethers.provider.getSigner() - ).deploy(); - await uiPoolDataProvider.deployTransaction.wait(); - console.log('uiPoolDataProvider.address', uiPoolDataProvider.address); - await verifyContract(uiPoolDataProvider.address, []); + const UiPoolDataProvider = await deployUiPoolDataProvider( + [ + addressesByNetwork[localBRE.network.name].incentivesController, + addressesByNetwork[localBRE.network.name].aaveOracle, + ], + verify + ); - console.log(`\tFinished UiPoolDataProvider proxy and implementation deployment`); + console.log(`\tFinished UiPoolDataProvider deployment: ${UiPoolDataProvider.address}`); }); diff --git a/test-suites/test-aave/helpers/actions.ts b/test-suites/test-aave/helpers/actions.ts index 3000576a..fac619f4 100644 --- a/test-suites/test-aave/helpers/actions.ts +++ b/test-suites/test-aave/helpers/actions.ts @@ -16,6 +16,7 @@ import { calcExpectedUserDataAfterWithdraw, } from './utils/calculations'; import { getReserveAddressFromSymbol, getReserveData, getUserData } from './utils/helpers'; +import { buildPermitParams, getSignatureFromTypedData } from '../../../helpers/contracts-helpers'; import { convertToCurrencyDecimals } from '../../../helpers/contracts-helpers'; import { @@ -23,6 +24,7 @@ import { getMintableERC20, getStableDebtToken, getVariableDebtToken, + getChainId, } from '../../../helpers/contracts-getters'; import { MAX_UINT_AMOUNT, ONE_YEAR } from '../../../helpers/constants'; import { SignerWithAddress, TestEnv } from './make-suite'; @@ -30,9 +32,10 @@ import { advanceTimeAndBlock, DRE, timeLatest, waitForTx } from '../../../helper import chai from 'chai'; import { ReserveData, UserReserveData } from './utils/interfaces'; -import { ContractReceipt } from 'ethers'; +import { ContractReceipt, Wallet } from 'ethers'; import { AToken } from '../../../types/AToken'; import { RateMode, tEthereumAddress } from '../../../helpers/types'; +import { MintableERC20Factory } from '../../../types'; const { expect } = chai; @@ -349,7 +352,7 @@ export const borrow = async ( ); const amountToBorrow = await convertToCurrencyDecimals(reserve, amount); - + if (expectedResult === 'success') { const txResult = await waitForTx( await pool @@ -515,6 +518,257 @@ export const repay = async ( } }; +export const depositWithPermit = async ( + reserveSymbol: string, + amount: string, + sender: SignerWithAddress, + senderPk: string, + onBehalfOf: tEthereumAddress, + sendValue: string, + expectedResult: string, + testEnv: TestEnv, + revertMessage?: string +) => { + const { pool } = testEnv; + + const reserve = await getReserveAddressFromSymbol(reserveSymbol); + const amountToDeposit = await convertToCurrencyDecimals(reserve, amount); + + const chainId = await getChainId(); + const token = new MintableERC20Factory(sender.signer).attach(reserve); + const highDeadline = '100000000000000000000000000'; + const nonce = await token._nonces(sender.address); + + const msgParams = buildPermitParams( + chainId, + reserve, + '1', + reserveSymbol, + sender.address, + pool.address, + nonce.toNumber(), + highDeadline, + amountToDeposit.toString() + ); + const { v, r, s } = getSignatureFromTypedData(senderPk, msgParams); + + const txOptions: any = {}; + + const { reserveData: reserveDataBefore, userData: userDataBefore } = await getContractsData( + reserve, + onBehalfOf, + testEnv, + sender.address + ); + + if (sendValue) { + txOptions.value = await convertToCurrencyDecimals(reserve, sendValue); + } + + if (expectedResult === 'success') { + const txResult = await waitForTx( + await pool + .connect(sender.signer) + .depositWithPermit( + reserve, + amountToDeposit, + onBehalfOf, + '0', + highDeadline, + v, + r, + s, + txOptions + ) + ); + + const { + reserveData: reserveDataAfter, + userData: userDataAfter, + timestamp, + } = await getContractsData(reserve, onBehalfOf, testEnv, sender.address); + + const { txCost, txTimestamp } = await getTxCostAndTimestamp(txResult); + + const expectedReserveData = calcExpectedReserveDataAfterDeposit( + amountToDeposit.toString(), + reserveDataBefore, + txTimestamp + ); + + const expectedUserReserveData = calcExpectedUserDataAfterDeposit( + amountToDeposit.toString(), + reserveDataBefore, + expectedReserveData, + userDataBefore, + txTimestamp, + timestamp, + txCost + ); + + expectEqual(reserveDataAfter, expectedReserveData); + expectEqual(userDataAfter, expectedUserReserveData); + + // truffleAssert.eventEmitted(txResult, "Deposit", (ev: any) => { + // const {_reserve, _user, _amount} = ev; + // return ( + // _reserve === reserve && + // _user === user && + // new BigNumber(_amount).isEqualTo(new BigNumber(amountToDeposit)) + // ); + // }); + } else if (expectedResult === 'revert') { + await expect( + pool + .connect(sender.signer) + .depositWithPermit( + reserve, + amountToDeposit, + onBehalfOf, + '0', + highDeadline, + v, + r, + s, + txOptions + ), + revertMessage + ).to.be.reverted; + } +}; + +export const repayWithPermit = async ( + reserveSymbol: string, + amount: string, + rateMode: string, + user: SignerWithAddress, + userPk: string, + onBehalfOf: SignerWithAddress, + sendValue: string, + expectedResult: string, + testEnv: TestEnv, + revertMessage?: string +) => { + const { pool } = testEnv; + const reserve = await getReserveAddressFromSymbol(reserveSymbol); + const highDeadline = '100000000000000000000000000'; + + const { reserveData: reserveDataBefore, userData: userDataBefore } = await getContractsData( + reserve, + onBehalfOf.address, + testEnv + ); + + let amountToRepay = '0'; + + if (amount !== '-1') { + amountToRepay = (await convertToCurrencyDecimals(reserve, amount)).toString(); + } else { + amountToRepay = MAX_UINT_AMOUNT; + } + amountToRepay = '0x' + new BigNumber(amountToRepay).toString(16); + + const chainId = await getChainId(); + const token = new MintableERC20Factory(user.signer).attach(reserve); + const nonce = await token._nonces(user.address); + + const msgParams = buildPermitParams( + chainId, + reserve, + '1', + reserveSymbol, + user.address, + pool.address, + nonce.toNumber(), + highDeadline, + amountToRepay + ); + const { v, r, s } = getSignatureFromTypedData(userPk, msgParams); + const txOptions: any = {}; + + if (sendValue) { + const valueToSend = await convertToCurrencyDecimals(reserve, sendValue); + txOptions.value = '0x' + new BigNumber(valueToSend.toString()).toString(16); + } + + if (expectedResult === 'success') { + const txResult = await waitForTx( + await pool + .connect(user.signer) + .repayWithPermit( + reserve, + amountToRepay, + rateMode, + onBehalfOf.address, + highDeadline, + v, + r, + s, + txOptions + ) + ); + + const { txCost, txTimestamp } = await getTxCostAndTimestamp(txResult); + + const { + reserveData: reserveDataAfter, + userData: userDataAfter, + timestamp, + } = await getContractsData(reserve, onBehalfOf.address, testEnv); + + const expectedReserveData = calcExpectedReserveDataAfterRepay( + amountToRepay, + rateMode, + reserveDataBefore, + userDataBefore, + txTimestamp, + timestamp + ); + + const expectedUserData = calcExpectedUserDataAfterRepay( + amountToRepay, + rateMode, + reserveDataBefore, + expectedReserveData, + userDataBefore, + user.address, + onBehalfOf.address, + txTimestamp, + timestamp + ); + + expectEqual(reserveDataAfter, expectedReserveData); + expectEqual(userDataAfter, expectedUserData); + + // truffleAssert.eventEmitted(txResult, "Repay", (ev: any) => { + // const {_reserve, _user, _repayer} = ev; + + // return ( + // _reserve.toLowerCase() === reserve.toLowerCase() && + // _user.toLowerCase() === onBehalfOf.toLowerCase() && + // _repayer.toLowerCase() === user.toLowerCase() + // ); + // }); + } else if (expectedResult === 'revert') { + await expect( + pool + .connect(user.signer) + .repayWithPermit( + reserve, + amountToRepay, + rateMode, + onBehalfOf.address, + highDeadline, + v, + r, + s, + txOptions + ), + revertMessage + ).to.be.reverted; + } +}; + export const setUseAsCollateral = async ( reserveSymbol: string, user: SignerWithAddress, diff --git a/test-suites/test-aave/helpers/scenario-engine.ts b/test-suites/test-aave/helpers/scenario-engine.ts index 492fa8b2..7ec22bd5 100644 --- a/test-suites/test-aave/helpers/scenario-engine.ts +++ b/test-suites/test-aave/helpers/scenario-engine.ts @@ -10,8 +10,11 @@ import { swapBorrowRateMode, rebalanceStableBorrowRate, delegateBorrowAllowance, + repayWithPermit, + depositWithPermit, } from './actions'; import { RateMode } from '../../../helpers/types'; +import { Wallet } from '@ethersproject/wallet'; export interface Action { name: string; @@ -73,6 +76,12 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv const user = users[parseInt(userIndex)]; + const userPrivateKey = require('../../../test-wallets.js').accounts[parseInt(userIndex) + 1] + .secretKey; + if (!userPrivateKey) { + throw new Error('INVALID_OWNER_PK'); + } + switch (name) { case 'mint': const { amount } = action.args; @@ -111,6 +120,30 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv ); } break; + case 'depositWithPermit': + { + const { amount, sendValue, onBehalfOf: onBehalfOfIndex } = action.args; + const onBehalfOf = onBehalfOfIndex + ? users[parseInt(onBehalfOfIndex)].address + : user.address; + + if (!amount || amount === '') { + throw `Invalid amount to deposit into the ${reserve} reserve`; + } + + await depositWithPermit( + reserve, + amount, + user, + userPrivateKey, + onBehalfOf, + sendValue, + expected, + testEnv, + revertMessage + ); + } + break; case 'delegateBorrowAllowance': { @@ -203,6 +236,40 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv } break; + case 'repayWithPermit': + { + const { amount, borrowRateMode, sendValue, deadline } = action.args; + let { onBehalfOf: onBehalfOfIndex } = action.args; + + if (!amount || amount === '') { + throw `Invalid amount to repay into the ${reserve} reserve`; + } + + let userToRepayOnBehalf: SignerWithAddress; + if (!onBehalfOfIndex || onBehalfOfIndex === '') { + console.log( + 'WARNING: No onBehalfOf specified for a repay action. Defaulting to the repayer address' + ); + userToRepayOnBehalf = user; + } else { + userToRepayOnBehalf = users[parseInt(onBehalfOfIndex)]; + } + + await repayWithPermit( + reserve, + amount, + rateMode, + user, + userPrivateKey, + userToRepayOnBehalf, + sendValue, + expected, + testEnv, + revertMessage + ); + } + break; + case 'setUseAsCollateral': { const { useAsCollateral } = action.args; diff --git a/test-suites/test-aave/helpers/scenarios/borrow-repayWithPermit-variable.json b/test-suites/test-aave/helpers/scenarios/borrow-repayWithPermit-variable.json new file mode 100644 index 00000000..85f5c723 --- /dev/null +++ b/test-suites/test-aave/helpers/scenarios/borrow-repayWithPermit-variable.json @@ -0,0 +1,191 @@ +{ + "title": "LendingPool: Borrow/repay with permit with Permit (variable rate)", + "description": "Test cases for the borrow function, variable mode.", + "stories": [ + { + "description": "User 2 deposits with permit 1 DAI to account for rounding errors", + "actions": [ + { + "name": "mint", + "args": { + "reserve": "DAI", + "amount": "1", + "user": "2" + }, + "expected": "success" + }, + { + "name": "depositWithPermit", + "args": { + "reserve": "DAI", + "amount": "1", + "user": "2" + }, + "expected": "success" + } + ] + }, + { + "description": "User 0 deposits with permit 1000 DAI, user 1 deposits 1 WETH as collateral and borrows 100 DAI at variable rate", + "actions": [ + { + "name": "mint", + "args": { + "reserve": "DAI", + "amount": "1000", + "user": "0" + }, + "expected": "success" + }, + { + "name": "depositWithPermit", + "args": { + "reserve": "DAI", + "amount": "1000", + "user": "0" + }, + "expected": "success" + }, + { + "name": "mint", + "args": { + "reserve": "WETH", + "amount": "1", + "user": "1" + }, + "expected": "success" + }, + { + "name": "approve", + "args": { + "reserve": "WETH", + "user": "1" + }, + "expected": "success" + }, + { + "name": "deposit", + "args": { + "reserve": "WETH", + "amount": "1", + "user": "1" + }, + "expected": "success" + }, + { + "name": "borrow", + "args": { + "reserve": "DAI", + "amount": "100", + "borrowRateMode": "variable", + "user": "1", + "timeTravel": "365" + }, + "expected": "success" + } + ] + }, + { + "description": "User 1 tries to borrow the rest of the DAI liquidity (revert expected)", + "actions": [ + { + "name": "borrow", + "args": { + "reserve": "DAI", + "amount": "900", + "borrowRateMode": "variable", + "user": "1" + }, + "expected": "revert", + "revertMessage": "There is not enough collateral to cover a new borrow" + } + ] + }, + { + "description": "User 1 tries to repay with permit 0 DAI (revert expected)", + "actions": [ + { + "name": "repayWithPermit", + "args": { + "reserve": "DAI", + "amount": "0", + "user": "1", + "onBehalfOf": "1" + }, + "expected": "revert", + "revertMessage": "Amount must be greater than 0" + } + ] + }, + { + "description": "User 1 repays with permit a small amount of DAI, enough to cover a small part of the interest", + "actions": [ + { + "name": "repayWithPermit", + "args": { + "reserve": "DAI", + "amount": "1.25", + "user": "1", + "onBehalfOf": "1", + "borrowRateMode": "variable" + }, + "expected": "success" + } + ] + }, + { + "description": "User 1 repays with permit the DAI borrow after one year", + "actions": [ + { + "name": "mint", + "description": "Mint 10 DAI to cover the interest", + "args": { + "reserve": "DAI", + "amount": "10", + "user": "1" + }, + "expected": "success" + }, + { + "name": "repayWithPermit", + "args": { + "reserve": "DAI", + "amount": "-1", + "user": "1", + "onBehalfOf": "1", + "borrowRateMode": "variable" + }, + "expected": "success" + } + ] + }, + { + "description": "User 0 withdraws the deposited DAI plus interest", + "actions": [ + { + "name": "withdraw", + "args": { + "reserve": "DAI", + "amount": "-1", + "user": "0" + }, + "expected": "success" + } + ] + }, + { + "description": "User 1 withdraws the collateral", + "actions": [ + { + "name": "withdraw", + "args": { + "reserve": "WETH", + "amount": "-1", + "user": "1" + }, + "expected": "success" + } + ] + } + ] +} diff --git a/test-suites/test-aave/scenario.spec.ts b/test-suites/test-aave/scenario.spec.ts index 68b792d5..8eeb0a95 100644 --- a/test-suites/test-aave/scenario.spec.ts +++ b/test-suites/test-aave/scenario.spec.ts @@ -35,7 +35,7 @@ fs.readdirSync(scenarioFolder).forEach((file) => { for (const story of scenario.stories) { it(story.description, async function () { - // Retry the test scenarios up to 4 times if an error happens, due erratic HEVM network errors + // Retry the test scenarios up to 4 times in case random HEVM network errors happen this.retries(4); await executeStory(story, testEnv); }); diff --git a/test-suites/test-amm/scenario.spec.ts b/test-suites/test-amm/scenario.spec.ts index f9c4d78b..0e457217 100644 --- a/test-suites/test-amm/scenario.spec.ts +++ b/test-suites/test-amm/scenario.spec.ts @@ -35,7 +35,7 @@ fs.readdirSync(scenarioFolder).forEach((file) => { for (const story of scenario.stories) { it(story.description, async function () { - // Retry the test scenarios up to 4 times if an error happens, due erratic HEVM network errors + // Retry the test scenarios up to 4 times in case random HEVM network errors happen this.retries(4); await executeStory(story, testEnv); });