mirror of
				https://github.com/Instadapp/aave-protocol-v2.git
				synced 2024-07-29 21:47:30 +00:00 
			
		
		
		
	Merge pull request #64 from aave/feat/protocol-2.5/repayPermit-depositPermit
RepayWithPermit, depositWithPermit
This commit is contained in:
		
						commit
						77fe998e9f
					
				| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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];
 | 
			
		||||
    _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
 | 
			
		||||
| 
						 | 
				
			
			@ -845,6 +887,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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -363,3 +363,5 @@ export const getFlashLiquidationAdapter = async (address?: tEthereumAddress) =>
 | 
			
		|||
        .address,
 | 
			
		||||
    await getFirstSigner()
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
export const getChainId = async () => (await DRE.ethers.provider.getNetwork()).chainId;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>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