mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
Merge branch 'master' into fix/34
This commit is contained in:
commit
f428f69ebd
|
@ -2,6 +2,7 @@ import {usePlugin, BuidlerConfig} from '@nomiclabs/buidler/config';
|
|||
// @ts-ignore
|
||||
import {accounts} from './test-wallets.js';
|
||||
import {eEthereumNetwork} from './helpers/types';
|
||||
import { BUIDLEREVM_CHAINID, COVERAGE_CHAINID } from './helpers/constants';
|
||||
|
||||
usePlugin('@nomiclabs/buidler-ethers');
|
||||
usePlugin('buidler-typechain');
|
||||
|
@ -59,6 +60,7 @@ const config: any = {
|
|||
networks: {
|
||||
coverage: {
|
||||
url: 'http://localhost:8555',
|
||||
chainId: COVERAGE_CHAINID,
|
||||
},
|
||||
kovan: getCommonNetworkConfig(eEthereumNetwork.kovan, 42),
|
||||
ropsten: getCommonNetworkConfig(eEthereumNetwork.ropsten, 3),
|
||||
|
@ -68,7 +70,7 @@ const config: any = {
|
|||
blockGasLimit: DEFAULT_BLOCK_GAS_LIMIT,
|
||||
gas: DEFAULT_BLOCK_GAS_LIMIT,
|
||||
gasPrice: 8000000000,
|
||||
chainId: 31337,
|
||||
chainId: BUIDLEREVM_CHAINID,
|
||||
throwOnTransactionFailures: true,
|
||||
throwOnCallFailures: true,
|
||||
accounts: accounts.map(({secretKey, balance}: {secretKey: string; balance: string}) => ({
|
||||
|
|
|
@ -29,6 +29,13 @@ interface ILendingPool {
|
|||
**/
|
||||
event Withdraw(address indexed reserve, address indexed user, uint256 amount);
|
||||
|
||||
event BorrowAllowanceDelegated(
|
||||
address indexed asset,
|
||||
address indexed fromUser,
|
||||
address indexed toUser,
|
||||
uint256 interestRateMode,
|
||||
uint256 amount
|
||||
);
|
||||
/**
|
||||
* @dev emitted on borrow
|
||||
* @param reserve the address of the reserve
|
||||
|
@ -40,7 +47,8 @@ interface ILendingPool {
|
|||
**/
|
||||
event Borrow(
|
||||
address indexed reserve,
|
||||
address indexed user,
|
||||
address user,
|
||||
address indexed onBehalfOf,
|
||||
uint256 amount,
|
||||
uint256 borrowRateMode,
|
||||
uint256 borrowRate,
|
||||
|
@ -151,6 +159,27 @@ interface ILendingPool {
|
|||
**/
|
||||
function withdraw(address reserve, uint256 amount) external;
|
||||
|
||||
/**
|
||||
* @dev Sets allowance to borrow on a certain type of debt asset for a certain user address
|
||||
* @param asset The underlying asset of the debt token
|
||||
* @param user The user to give allowance to
|
||||
* @param interestRateMode Type of debt: 1 for stable, 2 for variable
|
||||
* @param amount Allowance amount to borrow
|
||||
**/
|
||||
function delegateBorrowAllowance(
|
||||
address asset,
|
||||
address user,
|
||||
uint256 interestRateMode,
|
||||
uint256 amount
|
||||
) external;
|
||||
|
||||
function getBorrowAllowance(
|
||||
address fromUser,
|
||||
address toUser,
|
||||
address asset,
|
||||
uint256 interestRateMode
|
||||
) external view returns (uint256);
|
||||
|
||||
/**
|
||||
* @dev Allows users to borrow a specific amount of the reserve currency, provided that the borrower
|
||||
* already deposited enough collateral.
|
||||
|
@ -162,7 +191,8 @@ interface ILendingPool {
|
|||
address reserve,
|
||||
uint256 amount,
|
||||
uint256 interestRateMode,
|
||||
uint16 referralCode
|
||||
uint16 referralCode,
|
||||
address onBehalfOf
|
||||
) external;
|
||||
|
||||
/**
|
||||
|
|
|
@ -52,6 +52,8 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
|||
mapping(address => ReserveLogic.ReserveData) internal _reserves;
|
||||
mapping(address => UserConfiguration.Map) internal _usersConfig;
|
||||
ILendingPoolAddressesProvider internal _addressesProvider;
|
||||
// debt token address => user who gives allowance => user who receives allowance => amount
|
||||
mapping(address => mapping(address => mapping(address => uint256))) internal _borrowAllowance;
|
||||
|
||||
address[] internal _reservesList;
|
||||
|
||||
|
@ -159,6 +161,35 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
|||
emit Withdraw(asset, msg.sender, amount);
|
||||
}
|
||||
|
||||
function getBorrowAllowance(
|
||||
address fromUser,
|
||||
address toUser,
|
||||
address asset,
|
||||
uint256 interestRateMode
|
||||
) external override view returns (uint256) {
|
||||
return
|
||||
_borrowAllowance[_reserves[asset].getDebtTokenAddress(interestRateMode)][fromUser][toUser];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets allowance to borrow on a certain type of debt asset for a certain user address
|
||||
* @param asset The underlying asset of the debt token
|
||||
* @param user The user to give allowance to
|
||||
* @param interestRateMode Type of debt: 1 for stable, 2 for variable
|
||||
* @param amount Allowance amount to borrow
|
||||
**/
|
||||
function delegateBorrowAllowance(
|
||||
address asset,
|
||||
address user,
|
||||
uint256 interestRateMode,
|
||||
uint256 amount
|
||||
) external override {
|
||||
address debtToken = _reserves[asset].getDebtTokenAddress(interestRateMode);
|
||||
|
||||
_borrowAllowance[debtToken][msg.sender][user] = amount;
|
||||
emit BorrowAllowanceDelegated(asset, msg.sender, user, interestRateMode, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Allows users to borrow a specific amount of the reserve currency, provided that the borrower
|
||||
* already deposited enough collateral.
|
||||
|
@ -166,20 +197,34 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
|||
* @param amount the amount to be borrowed
|
||||
* @param interestRateMode the interest rate mode at which the user wants to borrow. Can be 0 (STABLE) or 1 (VARIABLE)
|
||||
* @param referralCode a referral code for integrators
|
||||
* @param onBehalfOf address of the user who will receive the debt
|
||||
**/
|
||||
function borrow(
|
||||
address asset,
|
||||
uint256 amount,
|
||||
uint256 interestRateMode,
|
||||
uint16 referralCode
|
||||
uint16 referralCode,
|
||||
address onBehalfOf
|
||||
) external override {
|
||||
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
||||
|
||||
if (onBehalfOf != msg.sender) {
|
||||
address debtToken = reserve.getDebtTokenAddress(interestRateMode);
|
||||
|
||||
_borrowAllowance[debtToken][onBehalfOf][msg
|
||||
.sender] = _borrowAllowance[debtToken][onBehalfOf][msg.sender].sub(
|
||||
amount,
|
||||
Errors.BORROW_ALLOWANCE_ARE_NOT_ENOUGH
|
||||
);
|
||||
}
|
||||
_executeBorrow(
|
||||
ExecuteBorrowParams(
|
||||
asset,
|
||||
msg.sender,
|
||||
onBehalfOf,
|
||||
amount,
|
||||
interestRateMode,
|
||||
_reserves[asset].aTokenAddress,
|
||||
reserve.aTokenAddress,
|
||||
referralCode,
|
||||
true
|
||||
)
|
||||
|
@ -375,6 +420,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
|||
uint256 purchaseAmount,
|
||||
bool receiveAToken
|
||||
) external override {
|
||||
|
||||
address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager();
|
||||
|
||||
//solium-disable-next-line
|
||||
|
@ -513,6 +559,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
|||
ExecuteBorrowParams(
|
||||
asset,
|
||||
msg.sender,
|
||||
msg.sender,
|
||||
vars.amountPlusPremium.sub(vars.availableBalance),
|
||||
mode,
|
||||
vars.aTokenAddress,
|
||||
|
@ -804,6 +851,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
|||
struct ExecuteBorrowParams {
|
||||
address asset;
|
||||
address user;
|
||||
address onBehalfOf;
|
||||
uint256 amount;
|
||||
uint256 interestRateMode;
|
||||
address aTokenAddress;
|
||||
|
@ -817,7 +865,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
|||
**/
|
||||
function _executeBorrow(ExecuteBorrowParams memory vars) internal {
|
||||
ReserveLogic.ReserveData storage reserve = _reserves[vars.asset];
|
||||
UserConfiguration.Map storage userConfig = _usersConfig[msg.sender];
|
||||
UserConfiguration.Map storage userConfig = _usersConfig[vars.onBehalfOf];
|
||||
|
||||
address oracle = _addressesProvider.getPriceOracle();
|
||||
|
||||
|
@ -827,7 +875,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
|||
|
||||
ValidationLogic.validateBorrow(
|
||||
reserve,
|
||||
vars.asset,
|
||||
vars.onBehalfOf,
|
||||
vars.amount,
|
||||
amountInETH,
|
||||
vars.interestRateMode,
|
||||
|
@ -866,12 +914,13 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
|||
);
|
||||
|
||||
if (vars.releaseUnderlying) {
|
||||
IAToken(vars.aTokenAddress).transferUnderlyingTo(msg.sender, vars.amount);
|
||||
IAToken(vars.aTokenAddress).transferUnderlyingTo(vars.user, vars.amount);
|
||||
}
|
||||
|
||||
emit Borrow(
|
||||
vars.asset,
|
||||
msg.sender,
|
||||
vars.user,
|
||||
vars.onBehalfOf,
|
||||
vars.amount,
|
||||
vars.interestRateMode,
|
||||
ReserveLogic.InterestRateMode(vars.interestRateMode) == ReserveLogic.InterestRateMode.STABLE
|
||||
|
|
|
@ -22,6 +22,7 @@ import {PercentageMath} from '../libraries/math/PercentageMath.sol';
|
|||
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
|
||||
import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol';
|
||||
import {Errors} from '../libraries/helpers/Errors.sol';
|
||||
import {ValidationLogic} from '../libraries/logic/ValidationLogic.sol';
|
||||
|
||||
/**
|
||||
* @title LendingPoolLiquidationManager contract
|
||||
|
@ -44,6 +45,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
|
||||
mapping(address => ReserveLogic.ReserveData) internal reserves;
|
||||
mapping(address => UserConfiguration.Map) internal usersConfig;
|
||||
mapping(address => mapping(address => mapping(address => uint256))) internal _borrowAllowance;
|
||||
|
||||
address[] internal reservesList;
|
||||
|
||||
|
@ -89,15 +91,6 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
uint256 swappedCollateralAmount
|
||||
);
|
||||
|
||||
enum LiquidationErrors {
|
||||
NO_ERROR,
|
||||
NO_COLLATERAL_AVAILABLE,
|
||||
COLLATERAL_CANNOT_BE_LIQUIDATED,
|
||||
CURRRENCY_NOT_BORROWED,
|
||||
HEALTH_FACTOR_ABOVE_THRESHOLD,
|
||||
NOT_ENOUGH_LIQUIDITY
|
||||
}
|
||||
|
||||
struct LiquidationCallLocalVars {
|
||||
uint256 userCollateralBalance;
|
||||
uint256 userStableDebt;
|
||||
|
@ -113,6 +106,9 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
uint256 healthFactor;
|
||||
IAToken collateralAtoken;
|
||||
bool isCollateralEnabled;
|
||||
address principalAToken;
|
||||
uint256 errorCode;
|
||||
string errorMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -139,8 +135,8 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
uint256 purchaseAmount,
|
||||
bool receiveAToken
|
||||
) external returns (uint256, string memory) {
|
||||
ReserveLogic.ReserveData storage principalReserve = reserves[principal];
|
||||
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
|
||||
ReserveLogic.ReserveData storage principalReserve = reserves[principal];
|
||||
UserConfiguration.Map storage userConfig = usersConfig[user];
|
||||
|
||||
LiquidationCallLocalVars memory vars;
|
||||
|
@ -153,43 +149,29 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
addressesProvider.getPriceOracle()
|
||||
);
|
||||
|
||||
if (vars.healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
|
||||
return (
|
||||
uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
||||
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
||||
);
|
||||
}
|
||||
|
||||
vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress);
|
||||
|
||||
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
|
||||
|
||||
vars.isCollateralEnabled =
|
||||
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||
userConfig.isUsingAsCollateral(collateralReserve.id);
|
||||
|
||||
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
||||
if (!vars.isCollateralEnabled) {
|
||||
return (
|
||||
uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
||||
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
||||
);
|
||||
}
|
||||
|
||||
//if the user hasn't borrowed the specific currency defined by asset, it cannot be liquidated
|
||||
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(
|
||||
user,
|
||||
principalReserve
|
||||
);
|
||||
|
||||
if (vars.userStableDebt == 0 && vars.userVariableDebt == 0) {
|
||||
return (
|
||||
uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
||||
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
||||
);
|
||||
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateLiquidationCall(
|
||||
collateralReserve,
|
||||
principalReserve,
|
||||
userConfig,
|
||||
vars.healthFactor,
|
||||
vars.userStableDebt,
|
||||
vars.userVariableDebt
|
||||
);
|
||||
|
||||
if (Errors.LiquidationErrors(vars.errorCode) != Errors.LiquidationErrors.NO_ERROR) {
|
||||
return (vars.errorCode, vars.errorMsg);
|
||||
}
|
||||
|
||||
//all clear - calculate the max principal amount that can be liquidated
|
||||
vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress);
|
||||
|
||||
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
|
||||
|
||||
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt).percentMul(
|
||||
LIQUIDATION_CLOSE_FACTOR_PERCENT
|
||||
);
|
||||
|
@ -225,7 +207,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
);
|
||||
if (currentAvailableCollateral < vars.maxCollateralToLiquidate) {
|
||||
return (
|
||||
uint256(LiquidationErrors.NOT_ENOUGH_LIQUIDITY),
|
||||
uint256(Errors.LiquidationErrors.NOT_ENOUGH_LIQUIDITY),
|
||||
Errors.NOT_ENOUGH_LIQUIDITY_TO_LIQUIDATE
|
||||
);
|
||||
}
|
||||
|
@ -301,7 +283,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
receiveAToken
|
||||
);
|
||||
|
||||
return (uint256(LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -324,9 +306,8 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
address receiver,
|
||||
bytes calldata params
|
||||
) external returns (uint256, string memory) {
|
||||
ReserveLogic.ReserveData storage debtReserve = reserves[principal];
|
||||
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
|
||||
|
||||
ReserveLogic.ReserveData storage debtReserve = reserves[principal];
|
||||
UserConfiguration.Map storage userConfig = usersConfig[user];
|
||||
|
||||
LiquidationCallLocalVars memory vars;
|
||||
|
@ -339,36 +320,20 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
addressesProvider.getPriceOracle()
|
||||
);
|
||||
|
||||
if (
|
||||
msg.sender != user && vars.healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD
|
||||
) {
|
||||
return (
|
||||
uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
||||
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
||||
);
|
||||
}
|
||||
|
||||
if (msg.sender != user) {
|
||||
vars.isCollateralEnabled =
|
||||
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||
userConfig.isUsingAsCollateral(collateralReserve.id);
|
||||
|
||||
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
||||
if (!vars.isCollateralEnabled) {
|
||||
return (
|
||||
uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
||||
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve);
|
||||
|
||||
if (vars.userStableDebt == 0 && vars.userVariableDebt == 0) {
|
||||
return (
|
||||
uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
||||
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
||||
);
|
||||
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateRepayWithCollateral(
|
||||
collateralReserve,
|
||||
debtReserve,
|
||||
userConfig,
|
||||
user,
|
||||
vars.healthFactor,
|
||||
vars.userStableDebt,
|
||||
vars.userVariableDebt
|
||||
);
|
||||
|
||||
if (Errors.LiquidationErrors(vars.errorCode) != Errors.LiquidationErrors.NO_ERROR) {
|
||||
return (vars.errorCode, vars.errorMsg);
|
||||
}
|
||||
|
||||
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt);
|
||||
|
@ -412,7 +377,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
usersConfig[user].setUsingAsCollateral(collateralReserve.id, false);
|
||||
}
|
||||
|
||||
address principalAToken = debtReserve.aTokenAddress;
|
||||
vars.principalAToken = debtReserve.aTokenAddress;
|
||||
|
||||
// Notifies the receiver to proceed, sending as param the underlying already transferred
|
||||
ISwapAdapter(receiver).executeOperation(
|
||||
|
@ -425,8 +390,8 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
|
||||
//updating debt reserve
|
||||
debtReserve.updateState();
|
||||
debtReserve.updateInterestRates(principal, principalAToken, vars.actualAmountToLiquidate, 0);
|
||||
IERC20(principal).transferFrom(receiver, principalAToken, vars.actualAmountToLiquidate);
|
||||
debtReserve.updateInterestRates(principal, vars.principalAToken, vars.actualAmountToLiquidate, 0);
|
||||
IERC20(principal).transferFrom(receiver, vars.principalAToken, vars.actualAmountToLiquidate);
|
||||
|
||||
if (vars.userVariableDebt >= vars.actualAmountToLiquidate) {
|
||||
IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(
|
||||
|
@ -463,7 +428,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
vars.maxCollateralToLiquidate
|
||||
);
|
||||
|
||||
return (uint256(LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
}
|
||||
|
||||
struct AvailableCollateralToLiquidateLocalVars {
|
||||
|
|
|
@ -38,6 +38,7 @@ library Errors {
|
|||
string public constant INCONSISTENT_PROTOCOL_ACTUAL_BALANCE = '26'; // 'The actual balance of the protocol is inconsistent'
|
||||
string public constant CALLER_NOT_LENDING_POOL_CONFIGURATOR = '27'; // 'The actual balance of the protocol is inconsistent'
|
||||
string public constant INVALID_FLASHLOAN_MODE = '43'; //Invalid flashloan mode selected
|
||||
string public constant BORROW_ALLOWANCE_ARE_NOT_ENOUGH = '54'; // User borrows on behalf, but allowance are too small
|
||||
string public constant REENTRANCY_NOT_ALLOWED = '52';
|
||||
string public constant FAILED_REPAY_WITH_COLLATERAL = '53';
|
||||
|
||||
|
@ -73,4 +74,14 @@ library Errors {
|
|||
string public constant MULTIPLICATION_OVERFLOW = '44';
|
||||
string public constant ADDITION_OVERFLOW = '45';
|
||||
string public constant DIVISION_BY_ZERO = '46';
|
||||
|
||||
enum LiquidationErrors {
|
||||
NO_ERROR,
|
||||
NO_COLLATERAL_AVAILABLE,
|
||||
COLLATERAL_CANNOT_BE_LIQUIDATED,
|
||||
CURRRENCY_NOT_BORROWED,
|
||||
HEALTH_FACTOR_ABOVE_THRESHOLD,
|
||||
NOT_ENOUGH_LIQUIDITY,
|
||||
NO_ACTIVE_RESERVE
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,8 +121,30 @@ library ReserveLogic {
|
|||
}
|
||||
|
||||
/**
|
||||
* @dev Updates the state of the reserve by minting to the reserve treasury and calculate the new
|
||||
* reserve indexes
|
||||
* @dev returns an address of the debt token used for particular interest rate mode on asset.
|
||||
* @param reserve the reserve object
|
||||
* @param interestRateMode - STABLE or VARIABLE from ReserveLogic.InterestRateMode enum
|
||||
* @return an address of the corresponding debt token from reserve configuration
|
||||
**/
|
||||
function getDebtTokenAddress(ReserveLogic.ReserveData storage reserve, uint256 interestRateMode)
|
||||
internal
|
||||
view
|
||||
returns (address)
|
||||
{
|
||||
require(
|
||||
ReserveLogic.InterestRateMode.STABLE == ReserveLogic.InterestRateMode(interestRateMode) ||
|
||||
ReserveLogic.InterestRateMode.VARIABLE == ReserveLogic.InterestRateMode(interestRateMode),
|
||||
Errors.INVALID_INTEREST_RATE_MODE_SELECTED
|
||||
);
|
||||
return
|
||||
ReserveLogic.InterestRateMode.STABLE == ReserveLogic.InterestRateMode(interestRateMode)
|
||||
? reserve.stableDebtTokenAddress
|
||||
: reserve.variableDebtTokenAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates the liquidity cumulative index Ci and variable borrow cumulative index Bvc. Refer to the whitepaper for
|
||||
* a formal specification.
|
||||
* @param reserve the reserve object
|
||||
**/
|
||||
function updateState(ReserveData storage reserve) internal {
|
||||
|
|
|
@ -13,6 +13,7 @@ import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol';
|
|||
import {UserConfiguration} from '../configuration/UserConfiguration.sol';
|
||||
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
|
||||
import {Errors} from '../helpers/Errors.sol';
|
||||
import {Helpers} from '../helpers/Helpers.sol';
|
||||
|
||||
/**
|
||||
* @title ReserveLogic library
|
||||
|
@ -100,7 +101,7 @@ library ValidationLogic {
|
|||
/**
|
||||
* @dev validates a borrow.
|
||||
* @param reserve the reserve state from which the user is borrowing
|
||||
* @param reserveAddress the address of the reserve
|
||||
* @param userAddress the address of the user
|
||||
* @param amount the amount to be borrowed
|
||||
* @param amountInETH the amount to be borrowed, in ETH
|
||||
* @param interestRateMode the interest rate mode at which the user is borrowing
|
||||
|
@ -113,7 +114,7 @@ library ValidationLogic {
|
|||
|
||||
function validateBorrow(
|
||||
ReserveLogic.ReserveData storage reserve,
|
||||
address reserveAddress,
|
||||
address userAddress,
|
||||
uint256 amount,
|
||||
uint256 amountInETH,
|
||||
uint256 interestRateMode,
|
||||
|
@ -151,7 +152,7 @@ library ValidationLogic {
|
|||
vars.currentLiquidationThreshold,
|
||||
vars.healthFactor
|
||||
) = GenericLogic.calculateUserAccountData(
|
||||
msg.sender,
|
||||
userAddress,
|
||||
reservesData,
|
||||
userConfig,
|
||||
reserves,
|
||||
|
@ -192,7 +193,7 @@ library ValidationLogic {
|
|||
require(
|
||||
!userConfig.isUsingAsCollateral(reserve.id) ||
|
||||
reserve.configuration.getLtv() == 0 ||
|
||||
amount > IERC20(reserve.aTokenAddress).balanceOf(msg.sender),
|
||||
amount > IERC20(reserve.aTokenAddress).balanceOf(userAddress),
|
||||
Errors.CALLATERAL_SAME_AS_BORROWING_CURRENCY
|
||||
);
|
||||
|
||||
|
@ -329,4 +330,116 @@ library ValidationLogic {
|
|||
require(premium > 0, Errors.REQUESTED_AMOUNT_TOO_SMALL);
|
||||
require(mode <= uint256(ReserveLogic.InterestRateMode.VARIABLE), Errors.INVALID_FLASHLOAN_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Validates the liquidationCall() action
|
||||
* @param collateralReserve The reserve data of the collateral
|
||||
* @param principalReserve The reserve data of the principal
|
||||
* @param userConfig The user configuration
|
||||
* @param userHealthFactor The user's health factor
|
||||
* @param userStableDebt Total stable debt balance of the user
|
||||
* @param userVariableDebt Total variable debt balance of the user
|
||||
**/
|
||||
function validateLiquidationCall(
|
||||
ReserveLogic.ReserveData storage collateralReserve,
|
||||
ReserveLogic.ReserveData storage principalReserve,
|
||||
UserConfiguration.Map storage userConfig,
|
||||
uint256 userHealthFactor,
|
||||
uint256 userStableDebt,
|
||||
uint256 userVariableDebt
|
||||
) internal view returns(uint256, string memory) {
|
||||
if ( !collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE),
|
||||
Errors.NO_ACTIVE_RESERVE
|
||||
);
|
||||
}
|
||||
|
||||
if (userHealthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
||||
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
||||
);
|
||||
}
|
||||
|
||||
bool isCollateralEnabled =
|
||||
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||
userConfig.isUsingAsCollateral(collateralReserve.id);
|
||||
|
||||
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
||||
if (!isCollateralEnabled) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
||||
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
||||
);
|
||||
}
|
||||
|
||||
if (userStableDebt == 0 && userVariableDebt == 0) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
||||
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
||||
);
|
||||
}
|
||||
|
||||
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Validates the repayWithCollateral() action
|
||||
* @param collateralReserve The reserve data of the collateral
|
||||
* @param principalReserve The reserve data of the principal
|
||||
* @param userConfig The user configuration
|
||||
* @param user The address of the user
|
||||
* @param userHealthFactor The user's health factor
|
||||
* @param userStableDebt Total stable debt balance of the user
|
||||
* @param userVariableDebt Total variable debt balance of the user
|
||||
**/
|
||||
function validateRepayWithCollateral(
|
||||
ReserveLogic.ReserveData storage collateralReserve,
|
||||
ReserveLogic.ReserveData storage principalReserve,
|
||||
UserConfiguration.Map storage userConfig,
|
||||
address user,
|
||||
uint256 userHealthFactor,
|
||||
uint256 userStableDebt,
|
||||
uint256 userVariableDebt
|
||||
) internal view returns(uint256, string memory) {
|
||||
if ( !collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE),
|
||||
Errors.NO_ACTIVE_RESERVE
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
msg.sender != user && userHealthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD
|
||||
) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
||||
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
||||
);
|
||||
}
|
||||
|
||||
if (msg.sender != user) {
|
||||
bool isCollateralEnabled =
|
||||
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||
userConfig.isUsingAsCollateral(collateralReserve.id);
|
||||
|
||||
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
||||
if (!isCollateralEnabled) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
||||
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (userStableDebt == 0 && userVariableDebt == 0) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
||||
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
||||
);
|
||||
}
|
||||
|
||||
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,12 +23,19 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
|||
using SafeERC20 for ERC20;
|
||||
|
||||
uint256 public constant UINT_MAX_VALUE = uint256(-1);
|
||||
uint256 public constant ATOKEN_REVISION = 0x1;
|
||||
address public immutable UNDERLYING_ASSET_ADDRESS;
|
||||
address public immutable RESERVE_TREASURY_ADDRESS;
|
||||
LendingPool public immutable POOL;
|
||||
|
||||
/// @dev owner => next valid nonce to submit with permit()
|
||||
mapping (address => uint256) public _nonces;
|
||||
|
||||
uint256 public constant ATOKEN_REVISION = 0x1;
|
||||
|
||||
bytes32 public DOMAIN_SEPARATOR;
|
||||
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)");
|
||||
|
||||
modifier onlyLendingPool {
|
||||
require(msg.sender == address(POOL), Errors.CALLER_MUST_BE_LENDING_POOL);
|
||||
|
@ -56,6 +63,21 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
|||
string calldata tokenName,
|
||||
string calldata tokenSymbol
|
||||
) external virtual initializer {
|
||||
uint256 chainId;
|
||||
|
||||
//solium-disable-next-line
|
||||
assembly {
|
||||
chainId := chainid()
|
||||
}
|
||||
|
||||
DOMAIN_SEPARATOR = keccak256(abi.encode(
|
||||
EIP712_DOMAIN,
|
||||
keccak256(bytes(tokenName)),
|
||||
keccak256(EIP712_REVISION),
|
||||
chainId,
|
||||
address(this)
|
||||
));
|
||||
|
||||
_setName(tokenName);
|
||||
_setSymbol(tokenSymbol);
|
||||
_setDecimals(underlyingAssetDecimals);
|
||||
|
@ -191,6 +213,42 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
|||
return amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
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);
|
||||
}
|
||||
|
||||
function _transfer(
|
||||
address from,
|
||||
address to,
|
||||
|
|
|
@ -8,12 +8,16 @@ import {
|
|||
IReserveParams,
|
||||
tEthereumAddress,
|
||||
iBasicDistributionParams,
|
||||
eEthereumNetwork,
|
||||
} from './types';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {getParamPerPool} from './contracts-helpers';
|
||||
import {getParamPerPool, getParamPerNetwork} from './contracts-helpers';
|
||||
|
||||
export const TEST_SNAPSHOT_ID = '0x1';
|
||||
|
||||
export const BUIDLEREVM_CHAINID = 31337;
|
||||
export const COVERAGE_CHAINID = 1337;
|
||||
|
||||
// ----------------
|
||||
// MATH
|
||||
// ----------------
|
||||
|
@ -531,3 +535,18 @@ export const getFeeDistributionParamsCommon = (
|
|||
percentages,
|
||||
};
|
||||
};
|
||||
|
||||
export const getATokenDomainSeparatorPerNetwork = (
|
||||
network: eEthereumNetwork
|
||||
): tEthereumAddress =>
|
||||
getParamPerNetwork<tEthereumAddress>(
|
||||
{
|
||||
[eEthereumNetwork.coverage]: "0x95b73a72c6ecf4ccbbba5178800023260bad8e75cdccdb8e4827a2977a37c820",
|
||||
[eEthereumNetwork.buidlerevm]:
|
||||
"0x76cbbf8aa4b11a7c207dd79ccf8c394f59475301598c9a083f8258b4fafcfa86",
|
||||
[eEthereumNetwork.kovan]: "",
|
||||
[eEthereumNetwork.ropsten]: "",
|
||||
[eEthereumNetwork.main]: "",
|
||||
},
|
||||
network
|
||||
);
|
|
@ -33,6 +33,8 @@ import {StableDebtToken} from '../types/StableDebtToken';
|
|||
import {VariableDebtToken} from '../types/VariableDebtToken';
|
||||
import { ZERO_ADDRESS } from './constants';
|
||||
import {MockSwapAdapter} from '../types/MockSwapAdapter';
|
||||
import { signTypedData_v4, TypedData } from "eth-sig-util";
|
||||
import { fromRpcSig, ECDSASignature } from "ethereumjs-util";
|
||||
|
||||
export const registerContractInJsonDb = async (contractId: string, contractInstance: Contract) => {
|
||||
const currentNetwork = BRE.network.name;
|
||||
|
@ -449,10 +451,14 @@ const linkBytecode = (artifact: Artifact, libraries: any) => {
|
|||
};
|
||||
|
||||
export const getParamPerNetwork = <T>(
|
||||
{kovan, ropsten, main}: iParamsPerNetwork<T>,
|
||||
{kovan, ropsten, main, buidlerevm, coverage}: iParamsPerNetwork<T>,
|
||||
network: eEthereumNetwork
|
||||
) => {
|
||||
switch (network) {
|
||||
case eEthereumNetwork.coverage:
|
||||
return coverage;
|
||||
case eEthereumNetwork.buidlerevm:
|
||||
return buidlerevm;
|
||||
case eEthereumNetwork.kovan:
|
||||
return kovan;
|
||||
case eEthereumNetwork.ropsten:
|
||||
|
@ -489,3 +495,59 @@ export const convertToCurrencyUnits = async (tokenAddress: string, amount: strin
|
|||
const amountInCurrencyUnits = new BigNumber(amount).div(currencyUnit);
|
||||
return amountInCurrencyUnits.toFixed();
|
||||
};
|
||||
|
||||
export const buildPermitParams = (
|
||||
chainId: number,
|
||||
token: tEthereumAddress,
|
||||
revision: string,
|
||||
tokenName: string,
|
||||
owner: tEthereumAddress,
|
||||
spender: tEthereumAddress,
|
||||
nonce: number,
|
||||
deadline: string,
|
||||
value: tStringTokenSmallUnits
|
||||
) => ({
|
||||
types: {
|
||||
EIP712Domain: [
|
||||
{ name: "name", type: "string" },
|
||||
{ name: "version", type: "string" },
|
||||
{ name: "chainId", type: "uint256" },
|
||||
{ name: "verifyingContract", type: "address" },
|
||||
],
|
||||
Permit: [
|
||||
{ name: "owner", type: "address" },
|
||||
{ name: "spender", type: "address" },
|
||||
{ name: "value", type: "uint256" },
|
||||
{ name: "nonce", type: "uint256" },
|
||||
{ name: "deadline", type: "uint256" },
|
||||
],
|
||||
},
|
||||
primaryType: "Permit" as const,
|
||||
domain: {
|
||||
name: tokenName,
|
||||
version: revision,
|
||||
chainId: chainId,
|
||||
verifyingContract: token,
|
||||
},
|
||||
message: {
|
||||
owner,
|
||||
spender,
|
||||
value,
|
||||
nonce,
|
||||
deadline,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export const getSignatureFromTypedData = (
|
||||
privateKey: string,
|
||||
typedData: any // TODO: should be TypedData, from eth-sig-utils, but TS doesn't accept it
|
||||
): ECDSASignature => {
|
||||
const signature = signTypedData_v4(
|
||||
Buffer.from(privateKey.substring(2, 66), "hex"),
|
||||
{
|
||||
data: typedData,
|
||||
}
|
||||
);
|
||||
return fromRpcSig(signature);
|
||||
};
|
|
@ -5,6 +5,7 @@ export enum eEthereumNetwork {
|
|||
kovan = 'kovan',
|
||||
ropsten = 'ropsten',
|
||||
main = 'main',
|
||||
coverage = 'coverage'
|
||||
}
|
||||
|
||||
export enum AavePools {
|
||||
|
@ -245,6 +246,8 @@ export interface IMarketRates {
|
|||
}
|
||||
|
||||
export interface iParamsPerNetwork<T> {
|
||||
[eEthereumNetwork.coverage]: T;
|
||||
[eEthereumNetwork.buidlerevm]: T;
|
||||
[eEthereumNetwork.kovan]: T;
|
||||
[eEthereumNetwork.ropsten]: T;
|
||||
[eEthereumNetwork.main]: T;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"test-repay-with-collateral": "buidler test test/__setup.spec.ts test/repay-with-collateral.spec.ts",
|
||||
"test-liquidate-with-collateral": "buidler test test/__setup.spec.ts test/flash-liquidation-with-collateral.spec.ts",
|
||||
"test-flash": "buidler test test/__setup.spec.ts test/flashloan.spec.ts",
|
||||
"test-permit": "buidler test test/__setup.spec.ts test/atoken-permit.spec.ts",
|
||||
"dev:coverage": "buidler coverage --network coverage",
|
||||
"dev:deployment": "buidler dev-deployment",
|
||||
"dev:deployExample": "buidler deploy-Example",
|
||||
|
@ -58,7 +59,9 @@
|
|||
"tslint-config-prettier": "^1.18.0",
|
||||
"tslint-plugin-prettier": "^2.3.0",
|
||||
"typechain": "2.0.0",
|
||||
"typescript": "3.9.3"
|
||||
"typescript": "3.9.3",
|
||||
"eth-sig-util": "2.5.3",
|
||||
"ethereumjs-util": "7.0.2"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
|
312
test/atoken-permit.spec.ts
Normal file
312
test/atoken-permit.spec.ts
Normal file
|
@ -0,0 +1,312 @@
|
|||
import {
|
||||
MAX_UINT_AMOUNT,
|
||||
ZERO_ADDRESS,
|
||||
getATokenDomainSeparatorPerNetwork,
|
||||
BUIDLEREVM_CHAINID,
|
||||
} from '../helpers/constants';
|
||||
import {buildPermitParams, getSignatureFromTypedData} from '../helpers/contracts-helpers';
|
||||
import {expect} from 'chai';
|
||||
import {ethers} from 'ethers';
|
||||
import {eEthereumNetwork} from '../helpers/types';
|
||||
import {makeSuite, TestEnv} from './helpers/make-suite';
|
||||
import {BRE} from '../helpers/misc-utils';
|
||||
import {waitForTx} from './__setup.spec';
|
||||
|
||||
const {parseEther} = ethers.utils;
|
||||
|
||||
makeSuite('AToken: Permit', (testEnv: TestEnv) => {
|
||||
it('Checks the domain separator', async () => {
|
||||
const DOMAIN_SEPARATOR_ENCODED = getATokenDomainSeparatorPerNetwork(
|
||||
eEthereumNetwork.buidlerevm
|
||||
);
|
||||
|
||||
const {aDai} = testEnv;
|
||||
|
||||
const separator = await aDai.DOMAIN_SEPARATOR();
|
||||
|
||||
expect(separator).to.be.equal(DOMAIN_SEPARATOR_ENCODED, 'Invalid domain separator');
|
||||
});
|
||||
|
||||
it('Get aDAI for tests', async () => {
|
||||
const {dai, deployer, pool} = testEnv;
|
||||
|
||||
await dai.mint(parseEther('20000'));
|
||||
await dai.approve(pool.address, parseEther('20000'));
|
||||
await pool.deposit(dai.address, parseEther('20000'), deployer.address, 0);
|
||||
});
|
||||
|
||||
it('Reverts submitting a permit with 0 expiration', async () => {
|
||||
const {aDai, deployer, users} = testEnv;
|
||||
const owner = deployer;
|
||||
const spender = users[1];
|
||||
|
||||
const tokenName = await aDai.name();
|
||||
|
||||
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
|
||||
const expiration = 0;
|
||||
const nonce = (await aDai._nonces(owner.address)).toNumber();
|
||||
const permitAmount = ethers.utils.parseEther('2').toString();
|
||||
const msgParams = buildPermitParams(
|
||||
chainId,
|
||||
aDai.address,
|
||||
'1',
|
||||
tokenName,
|
||||
owner.address,
|
||||
spender.address,
|
||||
nonce,
|
||||
permitAmount,
|
||||
expiration.toFixed()
|
||||
);
|
||||
|
||||
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
|
||||
if (!ownerPrivateKey) {
|
||||
throw new Error('INVALID_OWNER_PK');
|
||||
}
|
||||
|
||||
expect((await aDai.allowance(owner.address, spender.address)).toString()).to.be.equal(
|
||||
'0',
|
||||
'INVALID_ALLOWANCE_BEFORE_PERMIT'
|
||||
);
|
||||
|
||||
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||
|
||||
await expect(
|
||||
aDai
|
||||
.connect(spender.signer)
|
||||
.permit(owner.address, spender.address, permitAmount, expiration, v, r, s)
|
||||
).to.be.revertedWith('INVALID_EXPIRATION');
|
||||
|
||||
expect((await aDai.allowance(owner.address, spender.address)).toString()).to.be.equal(
|
||||
'0',
|
||||
'INVALID_ALLOWANCE_AFTER_PERMIT'
|
||||
);
|
||||
});
|
||||
|
||||
it('Submits a permit with maximum expiration length', async () => {
|
||||
const {aDai, deployer, users} = testEnv;
|
||||
const owner = deployer;
|
||||
const spender = users[1];
|
||||
|
||||
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
|
||||
const deadline = MAX_UINT_AMOUNT;
|
||||
const nonce = (await aDai._nonces(owner.address)).toNumber();
|
||||
const permitAmount = parseEther('2').toString();
|
||||
const msgParams = buildPermitParams(
|
||||
chainId,
|
||||
aDai.address,
|
||||
'1',
|
||||
await aDai.name(),
|
||||
owner.address,
|
||||
spender.address,
|
||||
nonce,
|
||||
deadline,
|
||||
permitAmount
|
||||
);
|
||||
|
||||
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
|
||||
if (!ownerPrivateKey) {
|
||||
throw new Error('INVALID_OWNER_PK');
|
||||
}
|
||||
|
||||
expect((await aDai.allowance(owner.address, spender.address)).toString()).to.be.equal(
|
||||
'0',
|
||||
'INVALID_ALLOWANCE_BEFORE_PERMIT'
|
||||
);
|
||||
|
||||
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||
|
||||
await waitForTx(
|
||||
await aDai
|
||||
.connect(spender.signer)
|
||||
.permit(owner.address, spender.address, permitAmount, deadline, v, r, s)
|
||||
);
|
||||
|
||||
expect((await aDai._nonces(owner.address)).toNumber()).to.be.equal(1);
|
||||
});
|
||||
|
||||
it('Cancels the previous permit', async () => {
|
||||
const {aDai, deployer, users} = testEnv;
|
||||
const owner = deployer;
|
||||
const spender = users[1];
|
||||
|
||||
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
|
||||
const deadline = MAX_UINT_AMOUNT;
|
||||
const nonce = (await aDai._nonces(owner.address)).toNumber();
|
||||
const permitAmount = '0';
|
||||
const msgParams = buildPermitParams(
|
||||
chainId,
|
||||
aDai.address,
|
||||
'1',
|
||||
await aDai.name(),
|
||||
owner.address,
|
||||
spender.address,
|
||||
nonce,
|
||||
deadline,
|
||||
permitAmount
|
||||
);
|
||||
|
||||
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
|
||||
if (!ownerPrivateKey) {
|
||||
throw new Error('INVALID_OWNER_PK');
|
||||
}
|
||||
|
||||
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||
|
||||
expect((await aDai.allowance(owner.address, spender.address)).toString()).to.be.equal(
|
||||
ethers.utils.parseEther('2'),
|
||||
'INVALID_ALLOWANCE_BEFORE_PERMIT'
|
||||
);
|
||||
|
||||
await waitForTx(
|
||||
await aDai
|
||||
.connect(spender.signer)
|
||||
.permit(owner.address, spender.address, permitAmount, deadline, v, r, s)
|
||||
);
|
||||
expect((await aDai.allowance(owner.address, spender.address)).toString()).to.be.equal(
|
||||
permitAmount,
|
||||
'INVALID_ALLOWANCE_AFTER_PERMIT'
|
||||
);
|
||||
|
||||
expect((await aDai._nonces(owner.address)).toNumber()).to.be.equal(2);
|
||||
});
|
||||
|
||||
it('Tries to submit a permit with invalid nonce', async () => {
|
||||
const {aDai, deployer, users} = testEnv;
|
||||
const owner = deployer;
|
||||
const spender = users[1];
|
||||
|
||||
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
|
||||
const deadline = MAX_UINT_AMOUNT;
|
||||
const nonce = 1000;
|
||||
const permitAmount = '0';
|
||||
const msgParams = buildPermitParams(
|
||||
chainId,
|
||||
aDai.address,
|
||||
'1',
|
||||
await aDai.name(),
|
||||
owner.address,
|
||||
spender.address,
|
||||
nonce,
|
||||
deadline,
|
||||
permitAmount
|
||||
);
|
||||
|
||||
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
|
||||
if (!ownerPrivateKey) {
|
||||
throw new Error('INVALID_OWNER_PK');
|
||||
}
|
||||
|
||||
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||
|
||||
await expect(
|
||||
aDai
|
||||
.connect(spender.signer)
|
||||
.permit(owner.address, spender.address, permitAmount, deadline, v, r, s)
|
||||
).to.be.revertedWith('INVALID_SIGNATURE');
|
||||
});
|
||||
|
||||
it('Tries to submit a permit with invalid expiration (previous to the current block)', async () => {
|
||||
const {aDai, deployer, users} = testEnv;
|
||||
const owner = deployer;
|
||||
const spender = users[1];
|
||||
|
||||
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
|
||||
const expiration = '1';
|
||||
const nonce = (await aDai._nonces(owner.address)).toNumber();
|
||||
const permitAmount = '0';
|
||||
const msgParams = buildPermitParams(
|
||||
chainId,
|
||||
aDai.address,
|
||||
'1',
|
||||
await aDai.name(),
|
||||
owner.address,
|
||||
spender.address,
|
||||
nonce,
|
||||
expiration,
|
||||
permitAmount
|
||||
);
|
||||
|
||||
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
|
||||
if (!ownerPrivateKey) {
|
||||
throw new Error('INVALID_OWNER_PK');
|
||||
}
|
||||
|
||||
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||
|
||||
await expect(
|
||||
aDai
|
||||
.connect(spender.signer)
|
||||
.permit(owner.address, spender.address, expiration, permitAmount, v, r, s)
|
||||
).to.be.revertedWith('INVALID_EXPIRATION');
|
||||
});
|
||||
|
||||
it('Tries to submit a permit with invalid signature', async () => {
|
||||
const {aDai, deployer, users} = testEnv;
|
||||
const owner = deployer;
|
||||
const spender = users[1];
|
||||
|
||||
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
|
||||
const deadline = MAX_UINT_AMOUNT;
|
||||
const nonce = (await aDai._nonces(owner.address)).toNumber();
|
||||
const permitAmount = '0';
|
||||
const msgParams = buildPermitParams(
|
||||
chainId,
|
||||
aDai.address,
|
||||
'1',
|
||||
await aDai.name(),
|
||||
owner.address,
|
||||
spender.address,
|
||||
nonce,
|
||||
deadline,
|
||||
permitAmount
|
||||
);
|
||||
|
||||
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
|
||||
if (!ownerPrivateKey) {
|
||||
throw new Error('INVALID_OWNER_PK');
|
||||
}
|
||||
|
||||
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||
|
||||
await expect(
|
||||
aDai
|
||||
.connect(spender.signer)
|
||||
.permit(owner.address, ZERO_ADDRESS, permitAmount, deadline, v, r, s)
|
||||
).to.be.revertedWith('INVALID_SIGNATURE');
|
||||
});
|
||||
|
||||
it('Tries to submit a permit with invalid owner', async () => {
|
||||
const {aDai, deployer, users} = testEnv;
|
||||
const owner = deployer;
|
||||
const spender = users[1];
|
||||
|
||||
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
|
||||
const expiration = MAX_UINT_AMOUNT;
|
||||
const nonce = (await aDai._nonces(owner.address)).toNumber();
|
||||
const permitAmount = '0';
|
||||
const msgParams = buildPermitParams(
|
||||
chainId,
|
||||
aDai.address,
|
||||
'1',
|
||||
await aDai.name(),
|
||||
owner.address,
|
||||
spender.address,
|
||||
nonce,
|
||||
expiration,
|
||||
permitAmount
|
||||
);
|
||||
|
||||
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
|
||||
if (!ownerPrivateKey) {
|
||||
throw new Error('INVALID_OWNER_PK');
|
||||
}
|
||||
|
||||
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||
|
||||
await expect(
|
||||
aDai
|
||||
.connect(spender.signer)
|
||||
.permit(ZERO_ADDRESS, spender.address, expiration, permitAmount, v, r, s)
|
||||
).to.be.revertedWith('INVALID_OWNER');
|
||||
});
|
||||
});
|
|
@ -60,7 +60,13 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
|
|||
await expect(
|
||||
pool
|
||||
.connect(users[1].signer)
|
||||
.borrow(weth.address, ethers.utils.parseEther('0.1'), RateMode.Stable, AAVE_REFERRAL),
|
||||
.borrow(
|
||||
weth.address,
|
||||
ethers.utils.parseEther('0.1'),
|
||||
RateMode.Stable,
|
||||
AAVE_REFERRAL,
|
||||
users[1].address
|
||||
),
|
||||
COLLATERAL_BALANCE_IS_0
|
||||
).to.be.revertedWith(COLLATERAL_BALANCE_IS_0);
|
||||
});
|
||||
|
@ -73,7 +79,13 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
|
|||
|
||||
await pool
|
||||
.connect(users[1].signer)
|
||||
.borrow(weth.address, ethers.utils.parseEther('0.1'), RateMode.Stable, AAVE_REFERRAL);
|
||||
.borrow(
|
||||
weth.address,
|
||||
ethers.utils.parseEther('0.1'),
|
||||
RateMode.Stable,
|
||||
AAVE_REFERRAL,
|
||||
users[1].address
|
||||
);
|
||||
|
||||
await expect(
|
||||
aDai.connect(users[1].signer).transfer(users[0].address, aDAItoTransfer),
|
||||
|
|
|
@ -47,9 +47,9 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
|
|||
|
||||
const usdcPrice = await oracle.getAssetPrice(usdc.address);
|
||||
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0);
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
|
||||
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 1, 0);
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 1, 0, user.address);
|
||||
|
||||
const {userData: wethUserDataBefore} = await getContractsData(
|
||||
weth.address,
|
||||
|
@ -203,7 +203,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
|
|||
.toFixed(0)
|
||||
);
|
||||
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountUSDCToBorrow, 2, 0);
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountUSDCToBorrow, 2, 0, user.address);
|
||||
});
|
||||
|
||||
it('User 5 liquidates half the USDC loan of User 3 by swapping his WETH collateral', async () => {
|
||||
|
@ -464,7 +464,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
|
|||
.toFixed(0)
|
||||
);
|
||||
|
||||
await pool.connect(user.signer).borrow(dai.address, amountDAIToBorrow, 2, 0);
|
||||
await pool.connect(user.signer).borrow(dai.address, amountDAIToBorrow, 2, 0, user.address);
|
||||
});
|
||||
|
||||
it('It is not possible to do reentrancy on repayWithCollateral()', async () => {
|
||||
|
@ -736,7 +736,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
|
|||
await pool.connect(user.signer).deposit(weth.address, amountToDepositWeth, user.address, '0');
|
||||
await pool.connect(user.signer).deposit(dai.address, amountToDepositDAI, user.address, '0');
|
||||
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrowVariable, 2, 0);
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrowVariable, 2, 0, user.address);
|
||||
|
||||
const amountToRepay = amountToBorrowVariable;
|
||||
|
||||
|
@ -844,7 +844,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
|
|||
await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||
await pool.connect(user.signer).deposit(dai.address, amountDAIToDeposit, user.address, '0');
|
||||
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0);
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
|
||||
});
|
||||
|
||||
it('Liquidator tries to liquidates User 5 USDC loan by swapping his WETH collateral, should revert due WETH collateral disabled', async () => {
|
||||
|
|
|
@ -283,11 +283,41 @@ export const withdraw = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const delegateBorrowAllowance = async (
|
||||
reserveSymbol: string,
|
||||
amount: string,
|
||||
interestRateMode: string,
|
||||
user: SignerWithAddress,
|
||||
receiver: tEthereumAddress,
|
||||
expectedResult: string,
|
||||
testEnv: TestEnv,
|
||||
revertMessage?: string
|
||||
) => {
|
||||
const {pool} = testEnv;
|
||||
|
||||
const reserve = await getReserveAddressFromSymbol(reserveSymbol);
|
||||
const amountToDelegate = await convertToCurrencyDecimals(reserve, amount);
|
||||
|
||||
const delegateAllowancePromise = pool
|
||||
.connect(user.signer)
|
||||
.delegateBorrowAllowance(reserve, receiver, interestRateMode, amountToDelegate.toString());
|
||||
if (expectedResult === 'revert') {
|
||||
await expect(delegateAllowancePromise, revertMessage).to.be.reverted;
|
||||
return;
|
||||
} else {
|
||||
await delegateAllowancePromise;
|
||||
expect(
|
||||
(await pool.getBorrowAllowance(user.address, receiver, reserve, interestRateMode)).toString()
|
||||
).to.be.equal(amountToDelegate.toString(), 'borrowAllowance are set incorrectly');
|
||||
}
|
||||
};
|
||||
|
||||
export const borrow = async (
|
||||
reserveSymbol: string,
|
||||
amount: string,
|
||||
interestRateMode: string,
|
||||
user: SignerWithAddress,
|
||||
onBehalfOf: tEthereumAddress,
|
||||
timeTravel: string,
|
||||
expectedResult: string,
|
||||
testEnv: TestEnv,
|
||||
|
@ -299,15 +329,18 @@ export const borrow = async (
|
|||
|
||||
const {reserveData: reserveDataBefore, userData: userDataBefore} = await getContractsData(
|
||||
reserve,
|
||||
user.address,
|
||||
testEnv
|
||||
onBehalfOf,
|
||||
testEnv,
|
||||
user.address
|
||||
);
|
||||
|
||||
const amountToBorrow = await convertToCurrencyDecimals(reserve, amount);
|
||||
|
||||
if (expectedResult === 'success') {
|
||||
const txResult = await waitForTx(
|
||||
await pool.connect(user.signer).borrow(reserve, amountToBorrow, interestRateMode, '0')
|
||||
await pool
|
||||
.connect(user.signer)
|
||||
.borrow(reserve, amountToBorrow, interestRateMode, '0', onBehalfOf)
|
||||
);
|
||||
|
||||
const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult);
|
||||
|
@ -322,7 +355,7 @@ export const borrow = async (
|
|||
reserveData: reserveDataAfter,
|
||||
userData: userDataAfter,
|
||||
timestamp,
|
||||
} = await getContractsData(reserve, user.address, testEnv);
|
||||
} = await getContractsData(reserve, onBehalfOf, testEnv, user.address);
|
||||
|
||||
const expectedReserveData = calcExpectedReserveDataAfterBorrow(
|
||||
amountToBorrow.toString(),
|
||||
|
@ -369,7 +402,7 @@ export const borrow = async (
|
|||
// });
|
||||
} else if (expectedResult === 'revert') {
|
||||
await expect(
|
||||
pool.connect(user.signer).borrow(reserve, amountToBorrow, interestRateMode, '0'),
|
||||
pool.connect(user.signer).borrow(reserve, amountToBorrow, interestRateMode, '0', onBehalfOf),
|
||||
revertMessage
|
||||
).to.be.reverted;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@ import {
|
|||
repay,
|
||||
setUseAsCollateral,
|
||||
swapBorrowRateMode,
|
||||
rebalanceStableBorrowRate
|
||||
rebalanceStableBorrowRate,
|
||||
delegateBorrowAllowance,
|
||||
} from './actions';
|
||||
import {RateMode} from '../../helpers/types';
|
||||
|
||||
|
@ -59,7 +60,7 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
|
|||
|
||||
if (borrowRateMode) {
|
||||
if (borrowRateMode === 'none') {
|
||||
RateMode.None;
|
||||
rateMode = RateMode.None;
|
||||
} else if (borrowRateMode === 'stable') {
|
||||
rateMode = RateMode.Stable;
|
||||
} else if (borrowRateMode === 'variable') {
|
||||
|
@ -111,6 +112,27 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
|
|||
}
|
||||
break;
|
||||
|
||||
case 'delegateBorrowAllowance':
|
||||
{
|
||||
const {amount, toUser: toUserIndex} = action.args;
|
||||
const toUser = users[parseInt(toUserIndex, 10)].address;
|
||||
if (!amount || amount === '') {
|
||||
throw `Invalid amount to deposit into the ${reserve} reserve`;
|
||||
}
|
||||
|
||||
await delegateBorrowAllowance(
|
||||
reserve,
|
||||
amount,
|
||||
rateMode,
|
||||
user,
|
||||
toUser,
|
||||
expected,
|
||||
testEnv,
|
||||
revertMessage
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'withdraw':
|
||||
{
|
||||
const {amount} = action.args;
|
||||
|
@ -124,13 +146,27 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
|
|||
break;
|
||||
case 'borrow':
|
||||
{
|
||||
const {amount, timeTravel} = action.args;
|
||||
const {amount, timeTravel, onBehalfOf: onBehalfOfIndex} = action.args;
|
||||
|
||||
const onBehalfOf = onBehalfOfIndex
|
||||
? users[parseInt(onBehalfOfIndex)].address
|
||||
: user.address;
|
||||
|
||||
if (!amount || amount === '') {
|
||||
throw `Invalid amount to borrow from the ${reserve} reserve`;
|
||||
}
|
||||
|
||||
await borrow(reserve, amount, rateMode, user, timeTravel, expected, testEnv, revertMessage);
|
||||
await borrow(
|
||||
reserve,
|
||||
amount,
|
||||
rateMode,
|
||||
user,
|
||||
onBehalfOf,
|
||||
timeTravel,
|
||||
expected,
|
||||
testEnv,
|
||||
revertMessage
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
148
test/helpers/scenarios/credit-delegation.json
Normal file
148
test/helpers/scenarios/credit-delegation.json
Normal file
|
@ -0,0 +1,148 @@
|
|||
{
|
||||
"title": "LendingPool: credit delegation",
|
||||
"description": "Test cases for the credit delegation related functions.",
|
||||
"stories": [
|
||||
{
|
||||
"description": "User 0 deposits 1000 DAI, user 0 delegates borrowing of 1 WETH on variable to user 4, user 4 borrows 1 WETH variable on behalf of user 0",
|
||||
"actions": [
|
||||
{
|
||||
"name": "mint",
|
||||
"args": {
|
||||
"reserve": "WETH",
|
||||
"amount": "1000",
|
||||
"user": "0"
|
||||
},
|
||||
"expected": "success"
|
||||
},
|
||||
{
|
||||
"name": "approve",
|
||||
"args": {
|
||||
"reserve": "WETH",
|
||||
"user": "0"
|
||||
},
|
||||
"expected": "success"
|
||||
},
|
||||
{
|
||||
"name": "deposit",
|
||||
"args": {
|
||||
"reserve": "WETH",
|
||||
"amount": "1000",
|
||||
"user": "0"
|
||||
},
|
||||
"expected": "success"
|
||||
},
|
||||
{
|
||||
"name": "delegateBorrowAllowance",
|
||||
"args": {
|
||||
"reserve": "WETH",
|
||||
"amount": "2",
|
||||
"user": "0",
|
||||
"borrowRateMode": "variable",
|
||||
"toUser": "4"
|
||||
},
|
||||
"expected": "success"
|
||||
},
|
||||
{
|
||||
"name": "borrow",
|
||||
"args": {
|
||||
"reserve": "WETH",
|
||||
"amount": "1",
|
||||
"user": "4",
|
||||
"onBehalfOf": "0",
|
||||
"borrowRateMode": "variable"
|
||||
},
|
||||
"expected": "success"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "User 4 trying to borrow 1 WETH stable on behalf of user 0, revert expected",
|
||||
"actions": [
|
||||
{
|
||||
"name": "borrow",
|
||||
"args": {
|
||||
"reserve": "WETH",
|
||||
"amount": "1",
|
||||
"user": "4",
|
||||
"onBehalfOf": "0",
|
||||
"borrowRateMode": "stable"
|
||||
},
|
||||
"expected": "revert",
|
||||
"revertMessage": "54"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "User 0 delegates borrowing of 1 WETH to user 4, user 4 borrows 3 WETH variable on behalf of user 0, revert expected",
|
||||
"actions": [
|
||||
{
|
||||
"name": "delegateBorrowAllowance",
|
||||
"args": {
|
||||
"reserve": "WETH",
|
||||
"amount": "1",
|
||||
"user": "0",
|
||||
"borrowRateMode": "variable",
|
||||
"toUser": "4"
|
||||
},
|
||||
"expected": "success"
|
||||
},
|
||||
{
|
||||
"name": "borrow",
|
||||
"args": {
|
||||
"reserve": "WETH",
|
||||
"amount": "3",
|
||||
"user": "4",
|
||||
"onBehalfOf": "0",
|
||||
"borrowRateMode": "variable"
|
||||
},
|
||||
"expected": "revert",
|
||||
"revertMessage": "54"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "User 0 delegates borrowing of 1 WETH on stable to user 2, user 2 borrows 1 WETH stable on behalf of user 0",
|
||||
"actions": [
|
||||
{
|
||||
"name": "delegateBorrowAllowance",
|
||||
"args": {
|
||||
"reserve": "WETH",
|
||||
"amount": "1",
|
||||
"user": "0",
|
||||
"borrowRateMode": "stable",
|
||||
"toUser": "2"
|
||||
},
|
||||
"expected": "success"
|
||||
},
|
||||
{
|
||||
"name": "borrow",
|
||||
"args": {
|
||||
"reserve": "WETH",
|
||||
"amount": "1",
|
||||
"user": "2",
|
||||
"onBehalfOf": "0",
|
||||
"borrowRateMode": "stable"
|
||||
},
|
||||
"expected": "success"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "User 0 delegates borrowing of 1 WETH to user 2 with wrong borrowRateMode, revert expected",
|
||||
"actions": [
|
||||
{
|
||||
"name": "delegateBorrowAllowance",
|
||||
"args": {
|
||||
"reserve": "WETH",
|
||||
"amount": "1",
|
||||
"user": "0",
|
||||
"borrowRateMode": "random",
|
||||
"toUser": "2"
|
||||
},
|
||||
"expected": "revert",
|
||||
"revertMessage": "8"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -63,7 +63,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
|
|||
|
||||
await pool
|
||||
.connect(borrower.signer)
|
||||
.borrow(dai.address, amountDAIToBorrow, RateMode.Variable, '0');
|
||||
.borrow(dai.address, amountDAIToBorrow, RateMode.Variable, '0', borrower.address);
|
||||
|
||||
const userGlobalDataAfter = await pool.getUserAccountData(borrower.address);
|
||||
|
||||
|
@ -261,7 +261,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
|
|||
|
||||
await pool
|
||||
.connect(borrower.signer)
|
||||
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0');
|
||||
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0', borrower.address);
|
||||
|
||||
//drops HF below 1
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import {makeSuite} from './helpers/make-suite';
|
|||
import {ProtocolErrors, RateMode} from '../helpers/types';
|
||||
import {calcExpectedStableDebtTokenBalance} from './helpers/utils/calculations';
|
||||
import {getUserData} from './helpers/utils/helpers';
|
||||
import {parseEther} from 'ethers/lib/utils';
|
||||
|
||||
const chai = require('chai');
|
||||
|
||||
|
@ -23,6 +24,26 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
|||
BigNumber.config({DECIMAL_PLACES: 20, ROUNDING_MODE: BigNumber.ROUND_HALF_UP});
|
||||
});
|
||||
|
||||
it("It's not possible to liquidate on a non-active collateral or a non active principal", async () => {
|
||||
const {configurator, weth, pool, users, dai} = testEnv;
|
||||
const user = users[1];
|
||||
await configurator.deactivateReserve(weth.address);
|
||||
|
||||
await expect(
|
||||
pool.liquidationCall(weth.address, dai.address, user.address, parseEther('1000'), false)
|
||||
).to.be.revertedWith('2');
|
||||
|
||||
await configurator.activateReserve(weth.address);
|
||||
|
||||
await configurator.deactivateReserve(dai.address);
|
||||
|
||||
await expect(
|
||||
pool.liquidationCall(weth.address, dai.address, user.address, parseEther('1000'), false)
|
||||
).to.be.revertedWith('2');
|
||||
|
||||
await configurator.activateReserve(dai.address);
|
||||
});
|
||||
|
||||
it('LIQUIDATION - Deposits WETH, borrows DAI', async () => {
|
||||
const {dai, weth, users, pool, oracle} = testEnv;
|
||||
const depositor = users[0];
|
||||
|
@ -68,7 +89,7 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
|||
|
||||
await pool
|
||||
.connect(borrower.signer)
|
||||
.borrow(dai.address, amountDAIToBorrow, RateMode.Stable, '0');
|
||||
.borrow(dai.address, amountDAIToBorrow, RateMode.Stable, '0', borrower.address);
|
||||
|
||||
const userGlobalDataAfter = await pool.getUserAccountData(borrower.address);
|
||||
|
||||
|
@ -240,7 +261,7 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
|||
|
||||
await pool
|
||||
.connect(borrower.signer)
|
||||
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0');
|
||||
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0', borrower.address);
|
||||
|
||||
//drops HF below 1
|
||||
await oracle.setAssetPrice(
|
||||
|
|
|
@ -39,6 +39,44 @@ export const expectRepayWithCollateralEvent = (
|
|||
};
|
||||
|
||||
makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
||||
it("It's not possible to repayWithCollateral() on a non-active collateral or a non active principal", async () => {
|
||||
const {configurator, weth, pool, users, dai, mockSwapAdapter} = testEnv;
|
||||
const user = users[1];
|
||||
await configurator.deactivateReserve(weth.address);
|
||||
|
||||
await expect(
|
||||
pool
|
||||
.connect(user.signer)
|
||||
.repayWithCollateral(
|
||||
weth.address,
|
||||
dai.address,
|
||||
user.address,
|
||||
parseEther('100'),
|
||||
mockSwapAdapter.address,
|
||||
'0x'
|
||||
)
|
||||
).to.be.revertedWith('2');
|
||||
|
||||
await configurator.activateReserve(weth.address);
|
||||
|
||||
await configurator.deactivateReserve(dai.address);
|
||||
|
||||
await expect(
|
||||
pool
|
||||
.connect(user.signer)
|
||||
.repayWithCollateral(
|
||||
weth.address,
|
||||
dai.address,
|
||||
user.address,
|
||||
parseEther('100'),
|
||||
mockSwapAdapter.address,
|
||||
'0x'
|
||||
)
|
||||
).to.be.revertedWith('2');
|
||||
|
||||
await configurator.activateReserve(dai.address);
|
||||
});
|
||||
|
||||
it('User 1 provides some liquidity for others to borrow', async () => {
|
||||
const {pool, weth, dai, usdc, deployer} = testEnv;
|
||||
|
||||
|
@ -65,7 +103,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
|||
|
||||
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
||||
|
||||
await pool.connect(user.signer).borrow(dai.address, amountToBorrow, 2, 0);
|
||||
await pool.connect(user.signer).borrow(dai.address, amountToBorrow, 2, 0, user.address);
|
||||
});
|
||||
|
||||
it('It is not possible to do reentrancy on repayWithCollateral()', async () => {
|
||||
|
@ -187,7 +225,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
|||
|
||||
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
||||
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0);
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
|
||||
});
|
||||
|
||||
it('User 3 repays completely his USDC loan by swapping his WETH collateral', async () => {
|
||||
|
@ -309,9 +347,9 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
|||
|
||||
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
||||
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrowVariable, 2, 0);
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrowVariable, 2, 0, user.address);
|
||||
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrowStable, 1, 0);
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrowStable, 1, 0, user.address);
|
||||
|
||||
const amountToRepay = parseUnits('80', 6);
|
||||
|
||||
|
@ -450,7 +488,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
|||
await pool.connect(user.signer).deposit(weth.address, amountToDepositWeth, user.address, '0');
|
||||
await pool.connect(user.signer).deposit(dai.address, amountToDepositDAI, user.address, '0');
|
||||
|
||||
await pool.connect(user.signer).borrow(dai.address, amountToBorrowVariable, 2, 0);
|
||||
await pool.connect(user.signer).borrow(dai.address, amountToBorrowVariable, 2, 0, user.address);
|
||||
|
||||
const amountToRepay = parseEther('80');
|
||||
|
||||
|
@ -542,7 +580,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
|||
await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||
await pool.connect(user.signer).deposit(dai.address, amountDAIToDeposit, user.address, '0');
|
||||
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0);
|
||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
|
||||
});
|
||||
|
||||
it('User 5 tries to repay his USDC loan by swapping his WETH collateral, should not revert even with WETH collateral disabled', async () => {
|
||||
|
|
Loading…
Reference in New Issue
Block a user