mirror of
				https://github.com/Instadapp/aave-protocol-v2.git
				synced 2024-07-29 21:47:30 +00:00 
			
		
		
		
	Merge branch 'protocol-2.5' into feat/gas-optimization-1
This commit is contained in:
		
						commit
						759e163cb3
					
				|  | @ -95,6 +95,27 @@ interface IAToken is IERC20, IScaledBalanceToken, IInitializableAToken { | |||
|    **/ | ||||
|   function handleRepayment(address user, uint256 amount) external; | ||||
| 
 | ||||
|   /** | ||||
|    * @dev implements the permit function as for | ||||
|    * https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md | ||||
|    * @param owner The owner of the funds | ||||
|    * @param spender The spender | ||||
|    * @param value The amount | ||||
|    * @param deadline The deadline timestamp, type(uint256).max for max deadline | ||||
|    * @param v Signature param | ||||
|    * @param s Signature param | ||||
|    * @param r Signature param | ||||
|    */ | ||||
|   function permit( | ||||
|     address owner, | ||||
|     address spender, | ||||
|     uint256 value, | ||||
|     uint256 deadline, | ||||
|     uint8 v, | ||||
|     bytes32 r, | ||||
|     bytes32 s | ||||
|   ) external; | ||||
| 
 | ||||
|   /** | ||||
|    * @dev Returns the address of the incentives controller contract | ||||
|    **/ | ||||
|  |  | |||
|  | @ -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); | ||||
| } | ||||
|  |  | |||
|  | @ -244,6 +244,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 | ||||
|  |  | |||
|  | @ -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) | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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); | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
|  |  | |||
|  | @ -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. | ||||
|  |  | |||
|  | @ -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]; | ||||
| 
 | ||||
|     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); | ||||
|     _executeDeposit(asset, amount, onBehalfOf, referralCode); | ||||
|   } | ||||
| 
 | ||||
|     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); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | @ -144,43 +156,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage | |||
|     uint256 amount, | ||||
|     address to | ||||
|   ) external override whenNotPaused returns (uint256) { | ||||
|     DataTypes.ReserveData storage reserve = _reserves[asset]; | ||||
| 
 | ||||
|     address aToken = reserve.aTokenAddress; | ||||
| 
 | ||||
|     uint256 userBalance = IAToken(aToken).balanceOf(msg.sender); | ||||
| 
 | ||||
|     uint256 amountToWithdraw = amount; | ||||
| 
 | ||||
|     if (amount == type(uint256).max) { | ||||
|       amountToWithdraw = userBalance; | ||||
|     } | ||||
| 
 | ||||
|     ValidationLogic.validateWithdraw( | ||||
|       asset, | ||||
|       amountToWithdraw, | ||||
|       userBalance, | ||||
|       _reserves, | ||||
|       _usersConfig[msg.sender], | ||||
|       _reservesList, | ||||
|       _reservesCount, | ||||
|       _addressesProvider.getPriceOracle() | ||||
|     ); | ||||
| 
 | ||||
|     reserve.updateState(); | ||||
| 
 | ||||
|     reserve.updateInterestRates(asset, aToken, 0, amountToWithdraw); | ||||
| 
 | ||||
|     if (amountToWithdraw == userBalance) { | ||||
|       _usersConfig[msg.sender].setUsingAsCollateral(reserve.id, false); | ||||
|       emit ReserveUsedAsCollateralDisabled(asset, msg.sender); | ||||
|     } | ||||
| 
 | ||||
|     IAToken(aToken).burn(msg.sender, to, amountToWithdraw, reserve.liquidityIndex); | ||||
| 
 | ||||
|     emit Withdraw(asset, msg.sender, to, amountToWithdraw); | ||||
| 
 | ||||
|     return amountToWithdraw; | ||||
|     return _executeWithdraw(asset, amount, to); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | @ -206,7 +182,6 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage | |||
|     address onBehalfOf | ||||
|   ) external override whenNotPaused { | ||||
|     DataTypes.ReserveData storage reserve = _reserves[asset]; | ||||
| 
 | ||||
|     _executeBorrow( | ||||
|       ExecuteBorrowParams( | ||||
|         asset, | ||||
|  | @ -239,54 +214,37 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage | |||
|     uint256 rateMode, | ||||
|     address onBehalfOf | ||||
|   ) external override whenNotPaused returns (uint256) { | ||||
|     DataTypes.ReserveData storage reserve = _reserves[asset]; | ||||
| 
 | ||||
|     (uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(onBehalfOf, reserve); | ||||
| 
 | ||||
|     DataTypes.InterestRateMode interestRateMode = DataTypes.InterestRateMode(rateMode); | ||||
| 
 | ||||
|     ValidationLogic.validateRepay( | ||||
|       reserve, | ||||
|       amount, | ||||
|       interestRateMode, | ||||
|       onBehalfOf, | ||||
|       stableDebt, | ||||
|       variableDebt | ||||
|     ); | ||||
| 
 | ||||
|     uint256 paybackAmount = | ||||
|       interestRateMode == DataTypes.InterestRateMode.STABLE ? stableDebt : variableDebt; | ||||
| 
 | ||||
|     if (amount < paybackAmount) { | ||||
|       paybackAmount = amount; | ||||
|     return _executeRepay(asset, amount, rateMode, onBehalfOf); | ||||
|   } | ||||
| 
 | ||||
|     reserve.updateState(); | ||||
| 
 | ||||
|     if (interestRateMode == DataTypes.InterestRateMode.STABLE) { | ||||
|       IStableDebtToken(reserve.stableDebtTokenAddress).burn(onBehalfOf, paybackAmount); | ||||
|     } else { | ||||
|       IVariableDebtToken(reserve.variableDebtTokenAddress).burn( | ||||
|         onBehalfOf, | ||||
|         paybackAmount, | ||||
|         reserve.variableBorrowIndex | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     address aToken = reserve.aTokenAddress; | ||||
|     reserve.updateInterestRates(asset, aToken, paybackAmount, 0); | ||||
| 
 | ||||
|     if (stableDebt.add(variableDebt).sub(paybackAmount) == 0) { | ||||
|       _usersConfig[onBehalfOf].setBorrowing(reserve.id, false); | ||||
|     } | ||||
| 
 | ||||
|     IERC20(asset).safeTransferFrom(msg.sender, aToken, paybackAmount); | ||||
| 
 | ||||
|     IAToken(aToken).handleRepayment(msg.sender, paybackAmount); | ||||
| 
 | ||||
|     emit Repay(asset, onBehalfOf, msg.sender, paybackAmount); | ||||
| 
 | ||||
|     return paybackAmount; | ||||
|   /** | ||||
|    * @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); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | @ -929,6 +887,133 @@ 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, | ||||
|     address to | ||||
|   ) internal returns (uint256) { | ||||
|     DataTypes.ReserveData storage reserve = _reserves[asset]; | ||||
| 
 | ||||
|     address aToken = reserve.aTokenAddress; | ||||
| 
 | ||||
|     uint256 userBalance = IAToken(aToken).balanceOf(msg.sender); | ||||
| 
 | ||||
|     uint256 amountToWithdraw = amount; | ||||
| 
 | ||||
|     if (amount == type(uint256).max) { | ||||
|       amountToWithdraw = userBalance; | ||||
|     } | ||||
| 
 | ||||
|     ValidationLogic.validateWithdraw( | ||||
|       asset, | ||||
|       amountToWithdraw, | ||||
|       userBalance, | ||||
|       _reserves, | ||||
|       _usersConfig[msg.sender], | ||||
|       _reservesList, | ||||
|       _reservesCount, | ||||
|       _addressesProvider.getPriceOracle() | ||||
|     ); | ||||
| 
 | ||||
|     reserve.updateState(); | ||||
| 
 | ||||
|     reserve.updateInterestRates(asset, aToken, 0, amountToWithdraw); | ||||
| 
 | ||||
|     if (amountToWithdraw == userBalance) { | ||||
|       _usersConfig[msg.sender].setUsingAsCollateral(reserve.id, false); | ||||
|       emit ReserveUsedAsCollateralDisabled(asset, msg.sender); | ||||
|     } | ||||
| 
 | ||||
|     IAToken(aToken).burn(msg.sender, to, amountToWithdraw, reserve.liquidityIndex); | ||||
| 
 | ||||
|     emit Withdraw(asset, msg.sender, to, amountToWithdraw); | ||||
| 
 | ||||
|     return amountToWithdraw; | ||||
|   } | ||||
| 
 | ||||
|   function _executeRepay( | ||||
|     address asset, | ||||
|     uint256 amount, | ||||
|     uint256 rateMode, | ||||
|     address onBehalfOf | ||||
|   ) internal returns (uint256) { | ||||
|     DataTypes.ReserveData storage reserve = _reserves[asset]; | ||||
| 
 | ||||
|     (uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(onBehalfOf, reserve); | ||||
| 
 | ||||
|     DataTypes.InterestRateMode interestRateMode = DataTypes.InterestRateMode(rateMode); | ||||
| 
 | ||||
|     ValidationLogic.validateRepay( | ||||
|       reserve, | ||||
|       amount, | ||||
|       interestRateMode, | ||||
|       onBehalfOf, | ||||
|       stableDebt, | ||||
|       variableDebt | ||||
|     ); | ||||
| 
 | ||||
|     uint256 paybackAmount = | ||||
|       interestRateMode == DataTypes.InterestRateMode.STABLE ? stableDebt : variableDebt; | ||||
| 
 | ||||
|     if (amount < paybackAmount) { | ||||
|       paybackAmount = amount; | ||||
|     } | ||||
| 
 | ||||
|     reserve.updateState(); | ||||
| 
 | ||||
|     if (interestRateMode == DataTypes.InterestRateMode.STABLE) { | ||||
|       IStableDebtToken(reserve.stableDebtTokenAddress).burn(onBehalfOf, paybackAmount); | ||||
|     } else { | ||||
|       IVariableDebtToken(reserve.variableDebtTokenAddress).burn( | ||||
|         onBehalfOf, | ||||
|         paybackAmount, | ||||
|         reserve.variableBorrowIndex | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     address aToken = reserve.aTokenAddress; | ||||
|     reserve.updateInterestRates(asset, aToken, paybackAmount, 0); | ||||
| 
 | ||||
|     if (stableDebt.add(variableDebt).sub(paybackAmount) == 0) { | ||||
|       _usersConfig[onBehalfOf].setBorrowing(reserve.id, false); | ||||
|     } | ||||
| 
 | ||||
|     IERC20(asset).safeTransferFrom(msg.sender, aToken, paybackAmount); | ||||
| 
 | ||||
|     IAToken(aToken).handleRepayment(msg.sender, paybackAmount); | ||||
| 
 | ||||
|     emit Repay(asset, onBehalfOf, msg.sender, paybackAmount); | ||||
| 
 | ||||
|     return paybackAmount; | ||||
|   } | ||||
| 
 | ||||
|   function _addReserveToList(address asset) internal { | ||||
|     uint256 reservesCount = _reservesCount; | ||||
| 
 | ||||
|  |  | |||
|  | @ -341,7 +341,7 @@ contract AToken is | |||
|     uint8 v, | ||||
|     bytes32 r, | ||||
|     bytes32 s | ||||
|   ) external { | ||||
|   ) external override { | ||||
|     require(owner != address(0), 'INVALID_OWNER'); | ||||
|     //solium-disable-next-line | ||||
|     require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); | ||||
|  |  | |||
|  | @ -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<UiPoolDataProvider>(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( | ||||
|  |  | |||
|  | @ -363,3 +363,5 @@ export const getFlashLiquidationAdapter = async (address?: tEthereumAddress) => | |||
|         .address, | ||||
|     await getFirstSigner() | ||||
|   ); | ||||
| 
 | ||||
| export const getChainId = async () => (await DRE.ethers.provider.getNetwork()).chainId; | ||||
|  |  | |||
							
								
								
									
										21
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										21
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							|  | @ -7709,6 +7709,15 @@ | |||
|           "requires": { | ||||
|             "bn.js": "^5.0.0", | ||||
|             "randombytes": "^2.0.1" | ||||
|           }, | ||||
|           "dependencies": { | ||||
|             "bn.js": { | ||||
|               "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 | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "browserify-sign": { | ||||
|  | @ -7819,9 +7828,9 @@ | |||
|           }, | ||||
|           "dependencies": { | ||||
|             "node-gyp-build": { | ||||
|               "version": "3.7.0", | ||||
|               "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.7.0.tgz", | ||||
|               "integrity": "sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==", | ||||
|               "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 | ||||
|             } | ||||
|           } | ||||
|  | @ -14241,9 +14250,9 @@ | |||
|           }, | ||||
|           "dependencies": { | ||||
|             "node-gyp-build": { | ||||
|               "version": "3.7.0", | ||||
|               "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.7.0.tgz", | ||||
|               "integrity": "sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==", | ||||
|               "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 | ||||
|             } | ||||
|           } | ||||
|  |  | |||
|  | @ -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", | ||||
|  |  | |||
|  | @ -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}`); | ||||
|   }); | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
|  | @ -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>rateMode, | ||||
|       reserveDataBefore, | ||||
|       userDataBefore, | ||||
|       txTimestamp, | ||||
|       timestamp | ||||
|     ); | ||||
| 
 | ||||
|     const expectedUserData = calcExpectedUserDataAfterRepay( | ||||
|       amountToRepay, | ||||
|       <RateMode>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, | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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" | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   ] | ||||
| } | ||||
|  | @ -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); | ||||
|       }); | ||||
|  |  | |||
|  | @ -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); | ||||
|       }); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 The3D
						The3D