mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
Merge branch '33-add-native-credit-delegation' into 'master'
Resolve "Add native credit delegation" Closes #33 See merge request aave-tech/protocol-v2!40
This commit is contained in:
commit
fce0baffd5
|
@ -29,6 +29,13 @@ interface ILendingPool {
|
||||||
**/
|
**/
|
||||||
event Withdraw(address indexed reserve, address indexed user, uint256 amount);
|
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
|
* @dev emitted on borrow
|
||||||
* @param reserve the address of the reserve
|
* @param reserve the address of the reserve
|
||||||
|
@ -40,7 +47,8 @@ interface ILendingPool {
|
||||||
**/
|
**/
|
||||||
event Borrow(
|
event Borrow(
|
||||||
address indexed reserve,
|
address indexed reserve,
|
||||||
address indexed user,
|
address user,
|
||||||
|
address indexed onBehalfOf,
|
||||||
uint256 amount,
|
uint256 amount,
|
||||||
uint256 borrowRateMode,
|
uint256 borrowRateMode,
|
||||||
uint256 borrowRate,
|
uint256 borrowRate,
|
||||||
|
@ -151,6 +159,27 @@ interface ILendingPool {
|
||||||
**/
|
**/
|
||||||
function withdraw(address reserve, uint256 amount) external;
|
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
|
* @dev Allows users to borrow a specific amount of the reserve currency, provided that the borrower
|
||||||
* already deposited enough collateral.
|
* already deposited enough collateral.
|
||||||
|
@ -162,7 +191,8 @@ interface ILendingPool {
|
||||||
address reserve,
|
address reserve,
|
||||||
uint256 amount,
|
uint256 amount,
|
||||||
uint256 interestRateMode,
|
uint256 interestRateMode,
|
||||||
uint16 referralCode
|
uint16 referralCode,
|
||||||
|
address onBehalfOf
|
||||||
) external;
|
) external;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -48,6 +48,8 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
|
|
||||||
mapping(address => ReserveLogic.ReserveData) internal _reserves;
|
mapping(address => ReserveLogic.ReserveData) internal _reserves;
|
||||||
mapping(address => UserConfiguration.Map) internal _usersConfig;
|
mapping(address => UserConfiguration.Map) internal _usersConfig;
|
||||||
|
// debt token address => user who gives allowance => user who receives allowance => amount
|
||||||
|
mapping(address => mapping(address => mapping(address => uint256))) internal _borrowAllowance;
|
||||||
|
|
||||||
address[] internal _reservesList;
|
address[] internal _reservesList;
|
||||||
|
|
||||||
|
@ -159,6 +161,35 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
emit Withdraw(asset, msg.sender, amount);
|
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
|
* @dev Allows users to borrow a specific amount of the reserve currency, provided that the borrower
|
||||||
* already deposited enough collateral.
|
* already deposited enough collateral.
|
||||||
|
@ -166,20 +197,34 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
* @param amount the amount to be borrowed
|
* @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 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 referralCode a referral code for integrators
|
||||||
|
* @param onBehalfOf address of the user who will receive the debt
|
||||||
**/
|
**/
|
||||||
function borrow(
|
function borrow(
|
||||||
address asset,
|
address asset,
|
||||||
uint256 amount,
|
uint256 amount,
|
||||||
uint256 interestRateMode,
|
uint256 interestRateMode,
|
||||||
uint16 referralCode
|
uint16 referralCode,
|
||||||
|
address onBehalfOf
|
||||||
) external override {
|
) 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(
|
_executeBorrow(
|
||||||
ExecuteBorrowParams(
|
ExecuteBorrowParams(
|
||||||
asset,
|
asset,
|
||||||
msg.sender,
|
msg.sender,
|
||||||
|
onBehalfOf,
|
||||||
amount,
|
amount,
|
||||||
interestRateMode,
|
interestRateMode,
|
||||||
_reserves[asset].aTokenAddress,
|
reserve.aTokenAddress,
|
||||||
referralCode,
|
referralCode,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
@ -523,6 +568,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
ExecuteBorrowParams(
|
ExecuteBorrowParams(
|
||||||
asset,
|
asset,
|
||||||
msg.sender,
|
msg.sender,
|
||||||
|
msg.sender,
|
||||||
vars.amountPlusPremium.sub(vars.availableBalance),
|
vars.amountPlusPremium.sub(vars.availableBalance),
|
||||||
mode,
|
mode,
|
||||||
vars.aTokenAddress,
|
vars.aTokenAddress,
|
||||||
|
@ -752,6 +798,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
struct ExecuteBorrowParams {
|
struct ExecuteBorrowParams {
|
||||||
address asset;
|
address asset;
|
||||||
address user;
|
address user;
|
||||||
|
address onBehalfOf;
|
||||||
uint256 amount;
|
uint256 amount;
|
||||||
uint256 interestRateMode;
|
uint256 interestRateMode;
|
||||||
address aTokenAddress;
|
address aTokenAddress;
|
||||||
|
@ -765,7 +812,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
**/
|
**/
|
||||||
function _executeBorrow(ExecuteBorrowParams memory vars) internal {
|
function _executeBorrow(ExecuteBorrowParams memory vars) internal {
|
||||||
ReserveLogic.ReserveData storage reserve = _reserves[vars.asset];
|
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();
|
address oracle = _addressesProvider.getPriceOracle();
|
||||||
|
|
||||||
|
@ -775,7 +822,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
|
|
||||||
ValidationLogic.validateBorrow(
|
ValidationLogic.validateBorrow(
|
||||||
reserve,
|
reserve,
|
||||||
vars.asset,
|
vars.onBehalfOf,
|
||||||
vars.amount,
|
vars.amount,
|
||||||
amountInETH,
|
amountInETH,
|
||||||
vars.interestRateMode,
|
vars.interestRateMode,
|
||||||
|
@ -802,12 +849,12 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
currentStableRate = reserve.currentStableBorrowRate;
|
currentStableRate = reserve.currentStableBorrowRate;
|
||||||
|
|
||||||
IStableDebtToken(reserve.stableDebtTokenAddress).mint(
|
IStableDebtToken(reserve.stableDebtTokenAddress).mint(
|
||||||
vars.user,
|
vars.onBehalfOf,
|
||||||
vars.amount,
|
vars.amount,
|
||||||
currentStableRate
|
currentStableRate
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
IVariableDebtToken(reserve.variableDebtTokenAddress).mint(vars.user, vars.amount);
|
IVariableDebtToken(reserve.variableDebtTokenAddress).mint(vars.onBehalfOf, vars.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
reserve.updateInterestRates(
|
reserve.updateInterestRates(
|
||||||
|
@ -818,12 +865,13 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (vars.releaseUnderlying) {
|
if (vars.releaseUnderlying) {
|
||||||
IAToken(vars.aTokenAddress).transferUnderlyingTo(msg.sender, vars.amount);
|
IAToken(vars.aTokenAddress).transferUnderlyingTo(vars.user, vars.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit Borrow(
|
emit Borrow(
|
||||||
vars.asset,
|
vars.asset,
|
||||||
msg.sender,
|
vars.user,
|
||||||
|
vars.onBehalfOf,
|
||||||
vars.amount,
|
vars.amount,
|
||||||
vars.interestRateMode,
|
vars.interestRateMode,
|
||||||
ReserveLogic.InterestRateMode(vars.interestRateMode) == ReserveLogic.InterestRateMode.STABLE
|
ReserveLogic.InterestRateMode(vars.interestRateMode) == ReserveLogic.InterestRateMode.STABLE
|
||||||
|
|
|
@ -43,6 +43,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
|
|
||||||
mapping(address => ReserveLogic.ReserveData) internal reserves;
|
mapping(address => ReserveLogic.ReserveData) internal reserves;
|
||||||
mapping(address => UserConfiguration.Map) internal usersConfig;
|
mapping(address => UserConfiguration.Map) internal usersConfig;
|
||||||
|
mapping(address => mapping(address => mapping(address => uint256))) internal _borrowAllowance;
|
||||||
|
|
||||||
address[] internal reservesList;
|
address[] internal reservesList;
|
||||||
|
|
||||||
|
|
|
@ -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 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 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 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 REENTRANCY_NOT_ALLOWED = '52';
|
||||||
string public constant FAILED_REPAY_WITH_COLLATERAL = '53';
|
string public constant FAILED_REPAY_WITH_COLLATERAL = '53';
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,28 @@ library ReserveLogic {
|
||||||
return cumulated;
|
return cumulated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
* @dev Updates the liquidity cumulative index Ci and variable borrow cumulative index Bvc. Refer to the whitepaper for
|
||||||
* a formal specification.
|
* a formal specification.
|
||||||
|
|
|
@ -100,7 +100,7 @@ library ValidationLogic {
|
||||||
/**
|
/**
|
||||||
* @dev validates a borrow.
|
* @dev validates a borrow.
|
||||||
* @param reserve the reserve state from which the user is borrowing
|
* @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 amount the amount to be borrowed
|
||||||
* @param amountInETH the amount to be borrowed, in ETH
|
* @param amountInETH the amount to be borrowed, in ETH
|
||||||
* @param interestRateMode the interest rate mode at which the user is borrowing
|
* @param interestRateMode the interest rate mode at which the user is borrowing
|
||||||
|
@ -113,7 +113,7 @@ library ValidationLogic {
|
||||||
|
|
||||||
function validateBorrow(
|
function validateBorrow(
|
||||||
ReserveLogic.ReserveData storage reserve,
|
ReserveLogic.ReserveData storage reserve,
|
||||||
address reserveAddress,
|
address userAddress,
|
||||||
uint256 amount,
|
uint256 amount,
|
||||||
uint256 amountInETH,
|
uint256 amountInETH,
|
||||||
uint256 interestRateMode,
|
uint256 interestRateMode,
|
||||||
|
@ -151,7 +151,7 @@ library ValidationLogic {
|
||||||
vars.currentLiquidationThreshold,
|
vars.currentLiquidationThreshold,
|
||||||
vars.healthFactor
|
vars.healthFactor
|
||||||
) = GenericLogic.calculateUserAccountData(
|
) = GenericLogic.calculateUserAccountData(
|
||||||
msg.sender,
|
userAddress,
|
||||||
reservesData,
|
reservesData,
|
||||||
userConfig,
|
userConfig,
|
||||||
reserves,
|
reserves,
|
||||||
|
@ -192,7 +192,7 @@ library ValidationLogic {
|
||||||
require(
|
require(
|
||||||
!userConfig.isUsingAsCollateral(reserve.id) ||
|
!userConfig.isUsingAsCollateral(reserve.id) ||
|
||||||
reserve.configuration.getLtv() == 0 ||
|
reserve.configuration.getLtv() == 0 ||
|
||||||
amount > IERC20(reserve.aTokenAddress).balanceOf(msg.sender),
|
amount > IERC20(reserve.aTokenAddress).balanceOf(userAddress),
|
||||||
Errors.CALLATERAL_SAME_AS_BORROWING_CURRENCY
|
Errors.CALLATERAL_SAME_AS_BORROWING_CURRENCY
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,13 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
|
||||||
await expect(
|
await expect(
|
||||||
pool
|
pool
|
||||||
.connect(users[1].signer)
|
.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
|
COLLATERAL_BALANCE_IS_0
|
||||||
).to.be.revertedWith(COLLATERAL_BALANCE_IS_0);
|
).to.be.revertedWith(COLLATERAL_BALANCE_IS_0);
|
||||||
});
|
});
|
||||||
|
@ -73,7 +79,13 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
await pool
|
await pool
|
||||||
.connect(users[1].signer)
|
.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(
|
await expect(
|
||||||
aDai.connect(users[1].signer).transfer(users[0].address, aDAItoTransfer),
|
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);
|
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(
|
const {userData: wethUserDataBefore} = await getContractsData(
|
||||||
weth.address,
|
weth.address,
|
||||||
|
@ -203,7 +203,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
|
||||||
.toFixed(0)
|
.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 () => {
|
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)
|
.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 () => {
|
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(weth.address, amountToDepositWeth, user.address, '0');
|
||||||
await pool.connect(user.signer).deposit(dai.address, amountToDepositDAI, 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;
|
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 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).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 () => {
|
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 (
|
export const borrow = async (
|
||||||
reserveSymbol: string,
|
reserveSymbol: string,
|
||||||
amount: string,
|
amount: string,
|
||||||
interestRateMode: string,
|
interestRateMode: string,
|
||||||
user: SignerWithAddress,
|
user: SignerWithAddress,
|
||||||
|
onBehalfOf: tEthereumAddress,
|
||||||
timeTravel: string,
|
timeTravel: string,
|
||||||
expectedResult: string,
|
expectedResult: string,
|
||||||
testEnv: TestEnv,
|
testEnv: TestEnv,
|
||||||
|
@ -299,15 +329,18 @@ export const borrow = async (
|
||||||
|
|
||||||
const {reserveData: reserveDataBefore, userData: userDataBefore} = await getContractsData(
|
const {reserveData: reserveDataBefore, userData: userDataBefore} = await getContractsData(
|
||||||
reserve,
|
reserve,
|
||||||
user.address,
|
onBehalfOf,
|
||||||
testEnv
|
testEnv,
|
||||||
|
user.address
|
||||||
);
|
);
|
||||||
|
|
||||||
const amountToBorrow = await convertToCurrencyDecimals(reserve, amount);
|
const amountToBorrow = await convertToCurrencyDecimals(reserve, amount);
|
||||||
|
|
||||||
if (expectedResult === 'success') {
|
if (expectedResult === 'success') {
|
||||||
const txResult = await waitForTx(
|
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);
|
const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult);
|
||||||
|
@ -322,7 +355,7 @@ export const borrow = async (
|
||||||
reserveData: reserveDataAfter,
|
reserveData: reserveDataAfter,
|
||||||
userData: userDataAfter,
|
userData: userDataAfter,
|
||||||
timestamp,
|
timestamp,
|
||||||
} = await getContractsData(reserve, user.address, testEnv);
|
} = await getContractsData(reserve, onBehalfOf, testEnv, user.address);
|
||||||
|
|
||||||
const expectedReserveData = calcExpectedReserveDataAfterBorrow(
|
const expectedReserveData = calcExpectedReserveDataAfterBorrow(
|
||||||
amountToBorrow.toString(),
|
amountToBorrow.toString(),
|
||||||
|
@ -369,7 +402,7 @@ export const borrow = async (
|
||||||
// });
|
// });
|
||||||
} else if (expectedResult === 'revert') {
|
} else if (expectedResult === 'revert') {
|
||||||
await expect(
|
await expect(
|
||||||
pool.connect(user.signer).borrow(reserve, amountToBorrow, interestRateMode, '0'),
|
pool.connect(user.signer).borrow(reserve, amountToBorrow, interestRateMode, '0', onBehalfOf),
|
||||||
revertMessage
|
revertMessage
|
||||||
).to.be.reverted;
|
).to.be.reverted;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,8 @@ import {
|
||||||
repay,
|
repay,
|
||||||
setUseAsCollateral,
|
setUseAsCollateral,
|
||||||
swapBorrowRateMode,
|
swapBorrowRateMode,
|
||||||
rebalanceStableBorrowRate
|
rebalanceStableBorrowRate,
|
||||||
|
delegateBorrowAllowance,
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import {RateMode} from '../../helpers/types';
|
import {RateMode} from '../../helpers/types';
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
|
||||||
|
|
||||||
if (borrowRateMode) {
|
if (borrowRateMode) {
|
||||||
if (borrowRateMode === 'none') {
|
if (borrowRateMode === 'none') {
|
||||||
RateMode.None;
|
rateMode = RateMode.None;
|
||||||
} else if (borrowRateMode === 'stable') {
|
} else if (borrowRateMode === 'stable') {
|
||||||
rateMode = RateMode.Stable;
|
rateMode = RateMode.Stable;
|
||||||
} else if (borrowRateMode === 'variable') {
|
} else if (borrowRateMode === 'variable') {
|
||||||
|
@ -111,6 +112,27 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
|
||||||
}
|
}
|
||||||
break;
|
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':
|
case 'withdraw':
|
||||||
{
|
{
|
||||||
const {amount} = action.args;
|
const {amount} = action.args;
|
||||||
|
@ -124,13 +146,27 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
|
||||||
break;
|
break;
|
||||||
case 'borrow':
|
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 === '') {
|
if (!amount || amount === '') {
|
||||||
throw `Invalid amount to borrow from the ${reserve} reserve`;
|
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;
|
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
|
await pool
|
||||||
.connect(borrower.signer)
|
.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);
|
const userGlobalDataAfter = await pool.getUserAccountData(borrower.address);
|
||||||
|
|
||||||
|
@ -261,7 +261,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
|
||||||
|
|
||||||
await pool
|
await pool
|
||||||
.connect(borrower.signer)
|
.connect(borrower.signer)
|
||||||
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0');
|
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0', borrower.address);
|
||||||
|
|
||||||
//drops HF below 1
|
//drops HF below 1
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
|
|
||||||
await pool
|
await pool
|
||||||
.connect(borrower.signer)
|
.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);
|
const userGlobalDataAfter = await pool.getUserAccountData(borrower.address);
|
||||||
|
|
||||||
|
@ -240,7 +240,7 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
|
|
||||||
await pool
|
await pool
|
||||||
.connect(borrower.signer)
|
.connect(borrower.signer)
|
||||||
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0');
|
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0', borrower.address);
|
||||||
|
|
||||||
//drops HF below 1
|
//drops HF below 1
|
||||||
await oracle.setAssetPrice(
|
await oracle.setAssetPrice(
|
||||||
|
|
|
@ -65,7 +65,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
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 () => {
|
it('It is not possible to do reentrancy on repayWithCollateral()', async () => {
|
||||||
|
@ -187,7 +187,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
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 () => {
|
it('User 3 repays completely his USDC loan by swapping his WETH collateral', async () => {
|
||||||
|
@ -309,9 +309,9 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
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);
|
const amountToRepay = parseUnits('80', 6);
|
||||||
|
|
||||||
|
@ -450,7 +450,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
||||||
await pool.connect(user.signer).deposit(weth.address, amountToDepositWeth, user.address, '0');
|
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).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');
|
const amountToRepay = parseEther('80');
|
||||||
|
|
||||||
|
@ -542,7 +542,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
||||||
await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
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).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 () => {
|
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