Merge branch 'master' into 100/flash-loan-bath-modes

This commit is contained in:
eboado 2020-10-30 11:40:31 +01:00
commit 1954f609c2
10 changed files with 295 additions and 84 deletions

View File

@ -99,17 +99,18 @@ interface ILendingPool {
/**
* @dev emitted when a flashloan is executed
* @param target the address of the flashLoanReceiver
* @param assets the address of the assets being flashborrowed
* @param amounts the amount requested
* @param premiums the total fee on the amount
* @param asset the address of the assets being flashborrowed
* @param amount the amount requested
* @param premium the total fee on the amount
* @param referralCode the referral code of the caller
**/
event FlashLoan(
address indexed target,
uint256[] modes,
address[] assets,
uint256[] amounts,
uint256[] premiums,
// uint256[] modes,
// address indexed onBehalfOf,
address asset,
uint256 amount,
uint256 premium,
uint16 referralCode
);
@ -286,7 +287,7 @@ interface ILendingPool {
* @param receiver The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface.
* @param assets the address of the principal reserve
* @param amounts the amount requested for this flashloan
* @param modes the flashloan mode
* @param modes the flashloan borrow modes
* @param params a bytes array to be sent to the flashloan executor
* @param referralCode the referral code of the caller
**/
@ -295,6 +296,7 @@ interface ILendingPool {
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata modes,
address onBehalfOf,
bytes calldata params,
uint16 referralCode
) external;
@ -349,11 +351,14 @@ interface ILendingPool {
function getReserveData(address asset) external view returns (ReserveLogic.ReserveData memory);
function balanceDecreaseAllowed(
address reserve,
address user,
uint256 amount
) external view returns (bool);
function finalizeTransfer(
address asset,
address from,
address to,
uint256 amount,
uint256 balanceFromAfter,
uint256 balanceToBefore
) external;
function getReservesList() external view returns (address[] memory);

View File

@ -493,6 +493,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
uint256 currentAmount;
uint256 currentPremium;
uint256 currentAmountPlusPremium;
address debtToken;
}
/**
@ -503,6 +504,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
* @param assets The addresss of the assets being flashborrowed
* @param amounts The amounts requested for this flashloan for each asset
* @param modes Types of the debt to open if the flash loan is not returned. 0 -> Don't open any debt, just revert, 1 -> stable, 2 -> variable
* @param onBehalfOf If mode is not 0, then the address to take the debt onBehalfOf. The onBehalfOf address must already have approved `msg.sender` to incur the debt on their behalf.
* @param params Variadic packed params to pass to the receiver as extra information
* @param referralCode Referral code of the flash loan
**/
@ -511,6 +513,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata modes,
address onBehalfOf,
bytes calldata params,
uint16 referralCode
) external override {
@ -567,13 +570,21 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
vars.currentAmountPlusPremium
);
} else {
if (msg.sender != onBehalfOf) {
vars.debtToken = _reserves[vars.currentAsset].getDebtTokenAddress(modes[vars.i]);
_borrowAllowance[vars.debtToken][onBehalfOf][msg.sender] = _borrowAllowance[vars
.debtToken][onBehalfOf][msg.sender]
.sub(vars.currentAmount, Errors.BORROW_ALLOWANCE_ARE_NOT_ENOUGH);
}
//if the user didn't choose to return the funds, the system checks if there
//is enough collateral and eventually open a position
_executeBorrow(
ExecuteBorrowParams(
vars.currentAsset,
msg.sender,
msg.sender,
onBehalfOf,
vars.currentAmount,
modes[vars.i],
vars.currentATokenAddress,
@ -582,7 +593,13 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
)
);
}
emit FlashLoan(receiverAddress, modes, assets, amounts, premiums, referralCode);
emit FlashLoan(
receiverAddress,
vars.currentAsset,
vars.currentAmount,
vars.currentPremium,
referralCode
);
}
}
@ -723,29 +740,48 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
}
/**
* @dev validate if a balance decrease for an asset is allowed
* @dev validates and finalizes an aToken transfer
* @param asset the address of the reserve
* @param user the user related to the balance decrease
* @param from the user from which the aTokens are transferred
* @param to the user receiving the aTokens
* @param amount the amount being transferred/redeemed
* @return true if the balance decrease can be allowed, false otherwise
* @param balanceFromBefore the balance of the from user before the transfer
* @param balanceToBefore the balance of the to user before the transfer
*/
function balanceDecreaseAllowed(
function finalizeTransfer(
address asset,
address user,
uint256 amount
) external override view returns (bool) {
address from,
address to,
uint256 amount,
uint256 balanceFromBefore,
uint256 balanceToBefore
) external override {
_whenNotPaused();
return
GenericLogic.balanceDecreaseAllowed(
asset,
user,
amount,
_reserves,
_usersConfig[user],
_reservesList,
_reservesCount,
_addressesProvider.getPriceOracle()
);
require(msg.sender == _reserves[asset].aTokenAddress, Errors.CALLER_MUST_BE_AN_ATOKEN);
ValidationLogic.validateTransfer(
from,
_reserves,
_usersConfig[from],
_reservesList,
_reservesCount,
_addressesProvider.getPriceOracle()
);
uint256 reserveId = _reserves[asset].id;
if (from != to) {
if (balanceFromBefore.sub(amount) == 0) {
UserConfiguration.Map storage fromConfig = _usersConfig[from];
fromConfig.setUsingAsCollateral(reserveId, false);
}
if (balanceToBefore == 0 && amount != 0) {
UserConfiguration.Map storage toConfig = _usersConfig[to];
toConfig.setUsingAsCollateral(reserveId, true);
}
}
}
/**

View File

@ -46,6 +46,7 @@ library Errors {
string public constant NO_MORE_RESERVES_ALLOWED = '59';
string public constant INVALID_FLASH_LOAN_EXECUTOR_RETURN = '60';
string public constant INCONSISTENT_FLASHLOAN_PARAMS = '69';
string public constant CALLER_MUST_BE_AN_ATOKEN = '76';
// require error messages - aToken - DebtTokens
string public constant CALLER_MUST_BE_LENDING_POOL = '28'; // 'The caller of this function must be a lending pool'
@ -97,7 +98,6 @@ library Errors {
string public constant INVALID_DECIMALS = '73';
string public constant INVALID_RESERVE_FACTOR = '74';
enum CollateralManagerErrors {
NO_ERROR,
NO_COLLATERAL_AVAILABLE,

View File

@ -46,6 +46,11 @@ library ValidationLogic {
* @param reserveAddress the address of the reserve
* @param amount the amount to be withdrawn
* @param userBalance the balance of the user
* @param reservesData the reserves state
* @param userConfig the user configuration
* @param reserves the addresses of the reserves
* @param reservesCount the number of reserves
* @param oracle the price oracle
*/
function validateWithdraw(
address reserveAddress,
@ -331,10 +336,10 @@ library ValidationLogic {
* @param amounts the amounts for each asset being borrowed
**/
function validateFlashloan(
address[] memory assets,
uint256[] memory amounts,
uint256[] memory modes
) internal pure {
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata modes
) external pure {
require(
assets.length == amounts.length && assets.length == modes.length,
Errors.INCONSISTENT_FLASHLOAN_PARAMS
@ -398,4 +403,35 @@ library ValidationLogic {
return (uint256(Errors.CollateralManagerErrors.NO_ERROR), Errors.NO_ERRORS);
}
/**
* @dev validates an aToken transfer.
* @param from the user from which the aTokens are being transferred
* @param reservesData the state of all the reserves
* @param userConfig the state of the user for the specific reserve
* @param reserves the addresses of all the active reserves
* @param oracle the price oracle
*/
function validateTransfer(
address from,
mapping(address => ReserveLogic.ReserveData) storage reservesData,
UserConfiguration.Map storage userConfig,
mapping(uint256 => address) storage reserves,
uint256 reservesCount,
address oracle
) internal view {
(, , , , uint256 healthFactor) = GenericLogic.calculateUserAccountData(
from,
reservesData,
userConfig,
reserves,
reservesCount,
oracle
);
require(
healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
Errors.TRANSFER_NOT_ALLOWED
);
}
}

View File

@ -241,16 +241,6 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken {
return super.totalSupply();
}
/**
* @dev Used to validate transfers before actually executing them.
* @param user address of the user to check
* @param amount the amount to check
* @return true if the user can transfer amount, false otherwise
**/
function isTransferAllowed(address user, uint256 amount) public override view returns (bool) {
return POOL.balanceDecreaseAllowed(UNDERLYING_ASSET_ADDRESS, user, amount);
}
/**
* @dev transfers the underlying asset to the target. Used by the lendingpool to transfer
* assets in borrow(), redeem() and flashLoan()
@ -317,14 +307,24 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken {
uint256 amount,
bool validate
) internal {
if (validate) {
require(isTransferAllowed(from, amount), Errors.TRANSFER_NOT_ALLOWED);
}
uint256 index = POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS);
uint256 fromBalanceBefore = super.balanceOf(from).rayMul(index);
uint256 toBalanceBefore = super.balanceOf(to).rayMul(index);
super._transfer(from, to, amount.rayDiv(index));
if (validate) {
POOL.finalizeTransfer(
UNDERLYING_ASSET_ADDRESS,
from,
to,
amount,
fromBalanceBefore,
toBalanceBefore
);
}
emit BalanceTransfer(from, to, amount, index);
}

View File

@ -54,8 +54,6 @@ interface IAToken is IERC20, IScaledBalanceToken {
uint256 value
) external;
function isTransferAllowed(address user, uint256 amount) external view returns (bool);
/**
* @dev transfer the amount of the underlying asset to the user
* @param user address of the user

View File

@ -91,6 +91,7 @@ export enum ProtocolErrors {
REQUESTED_AMOUNT_TOO_SMALL = '25', // 'The requested amount is too small for a FlashLoan.'
INCONSISTENT_PROTOCOL_ACTUAL_BALANCE = '26', // 'The actual balance of the protocol is inconsistent'
CALLER_NOT_LENDING_POOL_CONFIGURATOR = '27', // 'The actual balance of the protocol is inconsistent'
BORROW_ALLOWANCE_ARE_NOT_ENOUGH = '54', // User borrows on behalf, but allowance are too small
INVALID_FLASH_LOAN_EXECUTOR_RETURN = '60', // The flash loan received returned 0 (EOA)
// require error messages - aToken

View File

@ -50,8 +50,8 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
);
});
it('User 0 deposits 1 WETH and user 1 tries to borrow, but the aTokens received as a transfer are not available as collateral (revert expected)', async () => {
const {users, pool, weth} = testEnv;
it('User 0 deposits 1 WETH and user 1 tries to borrow the WETH with the received DAI as collateral', async () => {
const {users, pool, weth, helpersContract} = testEnv;
const userAddress = await pool.signer.getAddress();
await weth.connect(users[0].signer).mint(await convertToCurrencyDecimals(weth.address, '1'));
@ -61,26 +61,6 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
await pool
.connect(users[0].signer)
.deposit(weth.address, ethers.utils.parseEther('1.0'), userAddress, '0');
await expect(
pool
.connect(users[1].signer)
.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);
});
it('User 1 sets the DAI as collateral and borrows, tries to transfer everything back to user 0 (revert expected)', async () => {
const {users, pool, aDai, dai, weth} = testEnv;
await pool.connect(users[1].signer).setUserUseReserveAsCollateral(dai.address, true);
const aDAItoTransfer = await convertToCurrencyDecimals(dai.address, '1000');
await pool
.connect(users[1].signer)
.borrow(
@ -91,9 +71,34 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
users[1].address
);
const userReserveData = await helpersContract.getUserReserveData(
weth.address,
users[1].address
);
expect(userReserveData.currentStableDebt.toString()).to.be.eq(ethers.utils.parseEther('0.1'));
});
it('User 1 tries to transfer all the DAI used as collateral back to user 0 (revert expected)', async () => {
const {users, pool, aDai, dai, weth} = testEnv;
const aDAItoTransfer = await convertToCurrencyDecimals(dai.address, '1000');
await expect(
aDai.connect(users[1].signer).transfer(users[0].address, aDAItoTransfer),
TRANSFER_NOT_ALLOWED
).to.be.revertedWith(TRANSFER_NOT_ALLOWED);
});
it('User 1 tries to transfer a small amount of DAI used as collateral back to user 0', async () => {
const {users, pool, aDai, dai, weth} = testEnv;
const aDAItoTransfer = await convertToCurrencyDecimals(dai.address, '100');
await aDai.connect(users[1].signer).transfer(users[0].address, aDAItoTransfer);
const user0Balance = await aDai.balanceOf(users[0].address);
expect(user0Balance.toString()).to.be.eq(aDAItoTransfer.toString());
});
});

View File

@ -22,6 +22,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
SAFEERC20_LOWLEVEL_CALL,
IS_PAUSED,
INVALID_FLASH_LOAN_EXECUTOR_RETURN,
BORROW_ALLOWANCE_ARE_NOT_ENOUGH,
} = ProtocolErrors;
before(async () => {
@ -48,6 +49,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
[weth.address],
[ethers.utils.parseEther('0.8')],
[0],
_mockFlashLoanReceiver.address,
'0x10',
'0'
);
@ -77,6 +79,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
[weth.address],
['1000720000000000000'],
[0],
_mockFlashLoanReceiver.address,
'0x10',
'0'
);
@ -108,6 +111,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
[weth.address],
[ethers.utils.parseEther('0.8')],
[0],
caller.address,
'0x10',
'0'
)
@ -128,6 +132,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
[weth.address],
[ethers.utils.parseEther('0.8')],
[0],
caller.address,
'0x10',
'0'
)
@ -148,6 +153,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
[weth.address],
[ethers.utils.parseEther('0.8')],
[4],
caller.address,
'0x10',
'0'
)
@ -176,6 +182,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
[weth.address],
[ethers.utils.parseEther('0.8')],
[2],
caller.address,
'0x10',
'0'
);
@ -194,14 +201,16 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
});
it('tries to take a flashloan that is bigger than the available liquidity (revert expected)', async () => {
const {pool, weth} = testEnv;
const {pool, weth, users} = testEnv;
const caller = users[1];
await expect(
pool.flashLoan(
pool.connect(caller.signer).flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
['1004415000000000000'], //slightly higher than the available liquidity
[2],
caller.address,
'0x10',
'0'
),
@ -210,10 +219,19 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
});
it('tries to take a flashloan using a non contract address as receiver (revert expected)', async () => {
const {pool, deployer, weth} = testEnv;
const {pool, deployer, weth, users} = testEnv;
const caller = users[1];
await expect(
pool.flashLoan(deployer.address, [weth.address], ['1000000000000000000'], [2], '0x10', '0')
pool.flashLoan(
deployer.address,
[weth.address],
['1000000000000000000'],
[2],
caller.address,
'0x10',
'0'
)
).to.be.reverted;
});
@ -242,6 +260,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
[usdc.address],
[flashloanAmount],
[0],
_mockFlashLoanReceiver.address,
'0x10',
'0'
);
@ -284,6 +303,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
[usdc.address],
[flashloanAmount],
[2],
caller.address,
'0x10',
'0'
)
@ -309,7 +329,15 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await pool
.connect(caller.signer)
.flashLoan(_mockFlashLoanReceiver.address, [usdc.address], [flashloanAmount], [2], '0x10', '0');
.flashLoan(
_mockFlashLoanReceiver.address,
[usdc.address],
[flashloanAmount],
[2],
caller.address,
'0x10',
'0'
);
const {variableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses(
usdc.address
);
@ -344,7 +372,15 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await expect(
pool
.connect(caller.signer)
.flashLoan(_mockFlashLoanReceiver.address, [weth.address], [flashAmount], [0], '0x10', '0')
.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
[flashAmount],
[0],
caller.address,
'0x10',
'0'
)
).to.be.revertedWith(SAFEERC20_LOWLEVEL_CALL);
});
@ -359,7 +395,15 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await pool
.connect(caller.signer)
.flashLoan(_mockFlashLoanReceiver.address, [weth.address], [flashAmount], [1], '0x10', '0');
.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
[flashAmount],
[1],
caller.address,
'0x10',
'0'
);
const {stableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses(weth.address);
@ -372,4 +416,82 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
expect(callerDebt.toString()).to.be.equal('800000000000000000', 'Invalid user debt');
});
it('Caller takes a WETH flashloan with mode = 1 onBehalfOf user without allowance', async () => {
const {dai, pool, weth, users, helpersContract} = testEnv;
const caller = users[5];
const onBehalfOf = users[4];
// Deposit 1000 dai for onBehalfOf user
await dai.connect(onBehalfOf.signer).mint(await convertToCurrencyDecimals(dai.address, '1000'));
await dai.connect(onBehalfOf.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000');
await pool
.connect(onBehalfOf.signer)
.deposit(dai.address, amountToDeposit, onBehalfOf.address, '0');
const flashAmount = ethers.utils.parseEther('0.8');
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
await expect(
pool
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
[flashAmount],
[1],
onBehalfOf.address,
'0x10',
'0'
)
).to.be.revertedWith(BORROW_ALLOWANCE_ARE_NOT_ENOUGH);
});
it('Caller takes a WETH flashloan with mode = 1 onBehalfOf user with allowance. A loan for onBehalfOf is creatd.', async () => {
const {dai, pool, weth, users, helpersContract} = testEnv;
const caller = users[5];
const onBehalfOf = users[4];
const flashAmount = ethers.utils.parseEther('0.8');
// Deposited for onBehalfOf user already, delegate borrow allowance
await pool
.connect(onBehalfOf.signer)
.delegateBorrowAllowance(weth.address, caller.address, 1, flashAmount);
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
await pool
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
[flashAmount],
[1],
onBehalfOf.address,
'0x10',
'0'
);
const {stableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses(weth.address);
const wethDebtToken = await getContract<StableDebtToken>(
eContractid.VariableDebtToken,
stableDebtTokenAddress
);
const onBehalfOfDebt = await wethDebtToken.balanceOf(onBehalfOf.address);
expect(onBehalfOfDebt.toString()).to.be.equal(
'800000000000000000',
'Invalid onBehalfOf user debt'
);
});
});

View File

@ -187,7 +187,15 @@ makeSuite('Pausable Pool', (testEnv: TestEnv) => {
await expect(
pool
.connect(caller.signer)
.flashLoan(_mockFlashLoanReceiver.address, [weth.address], [flashAmount], [1], '0x10', '0')
.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
[flashAmount],
[1],
caller.address,
'0x10',
'0'
)
).revertedWith(IS_PAUSED);
// Unpause pool