Finalized implementation, fixed tests

This commit is contained in:
The3D 2020-10-22 20:37:50 +02:00
parent af99c88b70
commit a2e2450bb3
7 changed files with 107 additions and 95 deletions

View File

@ -9,9 +9,9 @@ pragma solidity ^0.6.8;
**/ **/
interface IFlashLoanReceiver { interface IFlashLoanReceiver {
function executeOperation( function executeOperation(
address reserve, address[] calldata reserve,
uint256 amount, uint256[] calldata amounts,
uint256 fee, uint256[] calldata premiums,
bytes calldata params bytes calldata params
) external returns (bool); ) external returns (bool);
} }

View File

@ -99,16 +99,16 @@ interface ILendingPool {
/** /**
* @dev emitted when a flashloan is executed * @dev emitted when a flashloan is executed
* @param target the address of the flashLoanReceiver * @param target the address of the flashLoanReceiver
* @param reserve the address of the reserve * @param assets the address of the assets being flashborrowed
* @param amount the amount requested * @param amounts the amount requested
* @param totalPremium the total fee on the amount * @param premiums the total fee on the amount
* @param referralCode the referral code of the caller * @param referralCode the referral code of the caller
**/ **/
event FlashLoan( event FlashLoan(
address indexed target, address indexed target,
address indexed reserve, address[] assets,
uint256 amount, uint256[] amounts,
uint256 totalPremium, uint256[] premiums,
uint16 referralCode uint16 referralCode
); );
/** /**
@ -264,13 +264,14 @@ interface ILendingPool {
* as long as the amount taken plus a fee is returned. NOTE There are security concerns for developers of flashloan receiver contracts * as long as the amount taken plus a fee is returned. NOTE There are security concerns for developers of flashloan receiver contracts
* that must be kept into consideration. For further details please visit https://developers.aave.com * that must be kept into consideration. For further details please visit https://developers.aave.com
* @param receiver The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface. * @param receiver The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface.
* @param reserve the address of the principal reserve * @param assets the address of the principal reserve
* @param amount the amount requested for this flashloan * @param amounts the amount requested for this flashloan
* @param mode the flashloan mode
* @param params a bytes array to be sent to the flashloan executor * @param params a bytes array to be sent to the flashloan executor
* @param referralCode the referral code of the caller * @param referralCode the referral code of the caller
**/ **/
function flashLoan( function flashLoan(
address receiverAddress, address receiver,
address[] calldata assets, address[] calldata assets,
uint256[] calldata amounts, uint256[] calldata amounts,
uint256 mode, uint256 mode,

View File

@ -486,6 +486,12 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
IFlashLoanReceiver receiver; IFlashLoanReceiver receiver;
address oracle; address oracle;
ReserveLogic.InterestRateMode debtMode; ReserveLogic.InterestRateMode debtMode;
uint256 i;
address currentAsset;
address currentATokenAddress;
uint256 currentAmount;
uint256 currentPremium;
uint256 currentAmountPlusPremium;
} }
/** /**
@ -511,7 +517,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
FlashLoanLocalVars memory vars; FlashLoanLocalVars memory vars;
ValidationLogic.validateFlashloan(assets, amounts, mode, vars.premium); ValidationLogic.validateFlashloan(assets, amounts, mode);
address[] memory aTokenAddresses = new address[](assets.length); address[] memory aTokenAddresses = new address[](assets.length);
uint256[] memory premiums = new uint256[](assets.length); uint256[] memory premiums = new uint256[](assets.length);
@ -519,15 +525,13 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
vars.receiver = IFlashLoanReceiver(receiverAddress); vars.receiver = IFlashLoanReceiver(receiverAddress);
vars.debtMode = ReserveLogic.InterestRateMode(mode); vars.debtMode = ReserveLogic.InterestRateMode(mode);
for (uint256 i = 0; i < assets.length; i++) { for (vars.i = 0; vars.i < assets.length; vars.i++) {
ReserveLogic.ReserveData storage reserve = _reserves[assets[i]]; aTokenAddresses[vars.i] = _reserves[assets[vars.i]].aTokenAddress;
aTokenAddresses[i] = reserve.aTokenAddress; premiums[vars.i] = amounts[vars.i].mul(FLASHLOAN_PREMIUM_TOTAL).div(10000);
premiums[i] = amounts[i].mul(FLASHLOAN_PREMIUM_TOTAL).div(10000);
//transfer funds to the receiver //transfer funds to the receiver
IAToken(vars.aTokenAddress).transferUnderlyingTo(receiverAddress, amounts[i]); IAToken(aTokenAddresses[vars.i]).transferUnderlyingTo(receiverAddress, amounts[vars.i]);
} }
//execute action of the receiver //execute action of the receiver
@ -536,40 +540,49 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
Errors.INVALID_FLASH_LOAN_EXECUTOR_RETURN Errors.INVALID_FLASH_LOAN_EXECUTOR_RETURN
); );
for (uint256 i = 0; i < assets.length; i++) { for (vars.i = 0; vars.i < assets.length; vars.i++) {
uint256 amountPlusPremium = amounts[i].add(premiums[i]); vars.currentAsset = assets[vars.i];
vars.currentAmount = amounts[vars.i];
vars.currentPremium = premiums[vars.i];
vars.currentATokenAddress = aTokenAddresses[vars.i];
vars.currentAmountPlusPremium = amounts[vars.i].add(premiums[vars.i]);
if (vars.debtMode == ReserveLogic.InterestRateMode.NONE) { if (vars.debtMode == ReserveLogic.InterestRateMode.NONE) {
_reserves[assets[i]].updateState(); _reserves[vars.currentAsset].updateState();
_reserves[assets[i]].cumulateToLiquidityIndex( _reserves[vars.currentAsset].cumulateToLiquidityIndex(
IERC20(aTokenAddresses[i]).totalSupply(), IERC20(vars.currentATokenAddress).totalSupply(),
vars.premium vars.currentPremium
);
_reserves[vars.currentAsset].updateInterestRates(
assets[vars.i],
vars.currentATokenAddress,
vars.currentPremium,
0
); );
_reserves[assets[i]].updateInterestRates(assets[i], aTokenAddresses[i], vars.premium, 0);
IERC20(assets[i]).safeTransferFrom( IERC20(vars.currentAsset).safeTransferFrom(
receiverAddress, receiverAddress,
vars.aTokenAddresses[i], vars.currentATokenAddress,
vars.amountPlusPremium vars.currentAmountPlusPremium
); );
emit FlashLoan(receiverAddress, assets[i], amounts[i], vars.premium, referralCode);
} else { } else {
//if the user didn't choose to return the funds, the system checks if there //if the user didn't choose to return the funds, the system checks if there
//is enough collateral and eventually open a position //is enough collateral and eventually open a position
_executeBorrow( _executeBorrow(
ExecuteBorrowParams( ExecuteBorrowParams(
assets[i], vars.currentAsset,
msg.sender, msg.sender,
msg.sender, msg.sender,
amountPlusPremium, vars.currentAmount,
vars.mode, mode,
aTokenAddresses[i], vars.currentATokenAddress,
referralCode, referralCode,
false false
) )
); );
} }
emit FlashLoan(receiverAddress, assets, amounts, premiums, referralCode);
} }
} }

View File

@ -327,15 +327,14 @@ library ValidationLogic {
/** /**
* @dev validates a flashloan action * @dev validates a flashloan action
* @param mode the flashloan mode (0 = classic flashloan, 1 = open a stable rate loan, 2 = open a variable rate loan) * @param mode the flashloan mode (0 = classic flashloan, 1 = open a stable rate loan, 2 = open a variable rate loan)
* @param premium the premium paid on the flashloan * @param assets the assets being flashborrowed
* @param amounts the amounts for each asset being borrowed
**/ **/
function validateFlashloan( function validateFlashloan(
address[] memory assets, address[] memory assets,
address[] memory amounts, uint256[] memory amounts,
uint256 mode, uint256 mode
uint256 premium
) internal pure { ) internal pure {
require(premium > 0, Errors.REQUESTED_AMOUNT_TOO_SMALL);
require(mode <= uint256(ReserveLogic.InterestRateMode.VARIABLE), Errors.INVALID_FLASHLOAN_MODE); require(mode <= uint256(ReserveLogic.InterestRateMode.VARIABLE), Errors.INVALID_FLASHLOAN_MODE);
require(assets.length == amounts.length, Errors.INCONSISTENT_FLASHLOAN_PARAMS); require(assets.length == amounts.length, Errors.INCONSISTENT_FLASHLOAN_PARAMS);
} }

View File

@ -14,8 +14,8 @@ contract MockFlashLoanReceiver is FlashLoanReceiverBase {
ILendingPoolAddressesProvider internal _provider; ILendingPoolAddressesProvider internal _provider;
event ExecutedWithFail(address _reserve, uint256 _amount, uint256 _fee); event ExecutedWithFail(address[] _assets, uint256[] _amounts, uint256[] _premiums);
event ExecutedWithSuccess(address _reserve, uint256 _amount, uint256 _fee); event ExecutedWithSuccess(address[] _assets, uint256[] _amounts, uint256[] _premiums);
bool _failExecution; bool _failExecution;
uint256 _amountToApprove; uint256 _amountToApprove;
@ -44,33 +44,40 @@ contract MockFlashLoanReceiver is FlashLoanReceiverBase {
} }
function executeOperation( function executeOperation(
address reserve, address[] memory assets,
uint256 amount, uint256[] memory amounts,
uint256 fee, uint256[] memory premiums,
bytes memory params bytes memory params
) public override returns (bool) { ) public override returns (bool) {
params; params;
//mint to this contract the specific amount
MintableERC20 token = MintableERC20(reserve);
//check the contract has the specified balance
require(amount <= IERC20(reserve).balanceOf(address(this)), 'Invalid balance for the contract');
uint256 amountToReturn = (_amountToApprove != 0) ? _amountToApprove : amount.add(fee);
if (_failExecution) { if (_failExecution) {
emit ExecutedWithFail(reserve, amount, fee); emit ExecutedWithFail(assets, amounts, premiums);
return !_simulateEOA; return !_simulateEOA;
} }
for (uint256 i = 0; i < assets.length; i++) {
//mint to this contract the specific amount
MintableERC20 token = MintableERC20(assets[i]);
//check the contract has the specified balance
require(
amounts[i] <= IERC20(assets[i]).balanceOf(address(this)),
'Invalid balance for the contract'
);
uint256 amountToReturn = (_amountToApprove != 0)
? _amountToApprove
: amounts[i].add(premiums[i]);
//execution does not fail - mint tokens and return them to the _destination //execution does not fail - mint tokens and return them to the _destination
//note: if the reserve is eth, the mock contract must receive at least _fee ETH before calling executeOperation //note: if the reserve is eth, the mock contract must receive at least _fee ETH before calling executeOperation
token.mint(fee); token.mint(premiums[i]);
IERC20(reserve).approve(_addressesProvider.getLendingPool(), amountToReturn); IERC20(assets[i]).approve(_addressesProvider.getLendingPool(), amountToReturn);
}
emit ExecutedWithSuccess(reserve, amount, fee); emit ExecutedWithSuccess(assets, amounts, premiums);
return true; return true;
} }

View File

@ -48,8 +48,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await pool.flashLoan( await pool.flashLoan(
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
weth.address, [weth.address],
ethers.utils.parseEther('0.8'), [ethers.utils.parseEther('0.8')],
0, 0,
'0x10', '0x10',
'0' '0'
@ -77,8 +77,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
const reserveDataBefore = await helpersContract.getReserveData(weth.address); const reserveDataBefore = await helpersContract.getReserveData(weth.address);
const txResult = await pool.flashLoan( const txResult = await pool.flashLoan(
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
weth.address, [weth.address],
'1000720000000000000', ['1000720000000000000'],
0, 0,
'0x10', '0x10',
'0' '0'
@ -108,8 +108,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
.connect(caller.signer) .connect(caller.signer)
.flashLoan( .flashLoan(
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
weth.address, [weth.address],
ethers.utils.parseEther('0.8'), [ethers.utils.parseEther('0.8')],
0, 0,
'0x10', '0x10',
'0' '0'
@ -128,8 +128,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
.connect(caller.signer) .connect(caller.signer)
.flashLoan( .flashLoan(
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
weth.address, [weth.address],
ethers.utils.parseEther('0.8'), [ethers.utils.parseEther('0.8')],
0, 0,
'0x10', '0x10',
'0' '0'
@ -148,8 +148,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
.connect(caller.signer) .connect(caller.signer)
.flashLoan( .flashLoan(
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
weth.address, [weth.address],
ethers.utils.parseEther('0.8'), [ethers.utils.parseEther('0.8')],
4, 4,
'0x10', '0x10',
'0' '0'
@ -176,8 +176,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
.connect(caller.signer) .connect(caller.signer)
.flashLoan( .flashLoan(
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
weth.address, [weth.address],
ethers.utils.parseEther('0.8'), [ethers.utils.parseEther('0.8')],
2, 2,
'0x10', '0x10',
'0' '0'
@ -196,29 +196,14 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
expect(callerDebt.toString()).to.be.equal('800720000000000000', 'Invalid user debt'); expect(callerDebt.toString()).to.be.equal('800720000000000000', 'Invalid user debt');
}); });
it('tries to take a very small flashloan, which would result in 0 fees (revert expected)', async () => {
const {pool, weth} = testEnv;
await expect(
pool.flashLoan(
_mockFlashLoanReceiver.address,
weth.address,
'1', //1 wei loan
2,
'0x10',
'0'
)
).to.be.revertedWith(REQUESTED_AMOUNT_TOO_SMALL);
});
it('tries to take a flashloan that is bigger than the available liquidity (revert expected)', async () => { it('tries to take a flashloan that is bigger than the available liquidity (revert expected)', async () => {
const {pool, weth} = testEnv; const {pool, weth} = testEnv;
await expect( await expect(
pool.flashLoan( pool.flashLoan(
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
weth.address, [weth.address],
'1004415000000000000', //slightly higher than the available liquidity ['1004415000000000000'], //slightly higher than the available liquidity
2, 2,
'0x10', '0x10',
'0' '0'
@ -231,7 +216,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
const {pool, deployer, weth} = testEnv; const {pool, deployer, weth} = testEnv;
await expect( await expect(
pool.flashLoan(deployer.address, weth.address, '1000000000000000000', 2, '0x10', '0') pool.flashLoan(deployer.address, [weth.address], ['1000000000000000000'], 2, '0x10', '0')
).to.be.reverted; ).to.be.reverted;
}); });
@ -257,8 +242,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await pool.flashLoan( await pool.flashLoan(
_mockFlashLoanReceiver.address, _mockFlashLoanReceiver.address,
usdc.address, [usdc.address],
flashloanAmount, [flashloanAmount],
0, 0,
'0x10', '0x10',
'0' '0'
@ -297,7 +282,14 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await expect( await expect(
pool pool
.connect(caller.signer) .connect(caller.signer)
.flashLoan(_mockFlashLoanReceiver.address, usdc.address, flashloanAmount, 2, '0x10', '0') .flashLoan(
_mockFlashLoanReceiver.address,
[usdc.address],
[flashloanAmount],
2,
'0x10',
'0'
)
).to.be.revertedWith(COLLATERAL_BALANCE_IS_0); ).to.be.revertedWith(COLLATERAL_BALANCE_IS_0);
}); });
@ -320,7 +312,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await pool await pool
.connect(caller.signer) .connect(caller.signer)
.flashLoan(_mockFlashLoanReceiver.address, usdc.address, flashloanAmount, 2, '0x10', '0'); .flashLoan(_mockFlashLoanReceiver.address, [usdc.address], [flashloanAmount], 2, '0x10', '0');
const {variableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses( const {variableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses(
usdc.address usdc.address
); );
@ -355,7 +347,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await expect( await expect(
pool pool
.connect(caller.signer) .connect(caller.signer)
.flashLoan(_mockFlashLoanReceiver.address, weth.address, flashAmount, 0, '0x10', '0') .flashLoan(_mockFlashLoanReceiver.address, [weth.address], [flashAmount], 0, '0x10', '0')
).to.be.revertedWith(SAFEERC20_LOWLEVEL_CALL); ).to.be.revertedWith(SAFEERC20_LOWLEVEL_CALL);
}); });
@ -370,7 +362,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await pool await pool
.connect(caller.signer) .connect(caller.signer)
.flashLoan(_mockFlashLoanReceiver.address, weth.address, flashAmount, 1, '0x10', '0'); .flashLoan(_mockFlashLoanReceiver.address, [weth.address], [flashAmount], 1, '0x10', '0');
const {stableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses(weth.address); const {stableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses(weth.address);

View File

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