Merge branch 'protocol-2.5' into feat/gas-optimization-5

This commit is contained in:
emilio 2021-06-15 12:40:14 +02:00
commit e1b9b1241a
6 changed files with 223 additions and 28 deletions

View File

@ -471,4 +471,17 @@ interface ILendingPool {
function updateFlashBorrowerAuthorization(address flashBorrower, bool authorized) external;
function isFlashBorrowerAuthorized(address flashBorrower) external view returns (bool);
function updateFlashloanPremiums(
uint256 flashLoanPremiumTotal,
uint256 flashLoanPremiumToProtocol
) external;
function MAX_STABLE_RATE_BORROW_SIZE_PERCENT() external view returns (uint256);
function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint256);
function FLASHLOAN_PREMIUM_TO_PROTOCOL() external view returns (uint256);
function MAX_NUMBER_RESERVES() external view returns (uint256);
}

View File

@ -233,6 +233,18 @@ interface ILendingPoolConfigurator {
**/
event RiskAdminUnregistered(address indexed admin);
/**
* @dev Emitted when a the total premium on flashloans is updated
* @param flashloanPremiumTotal the new premium
**/
event FlashloanPremiumTotalUpdated(uint256 flashloanPremiumTotal);
/**
* @dev Emitted when a the part of the premium that goes to protoco lis updated
* @param flashloanPremiumToProtocol the new premium
**/
event FlashloanPremiumToProcolUpdated(uint256 flashloanPremiumToProtocol);
/**
* @dev Initializes reserves in batch
* @param input The array of reserves initialization parameters
@ -410,4 +422,19 @@ interface ILendingPoolConfigurator {
* @param asset the address of the reserve to drop
**/
function dropReserve(address asset) external;
/**
* @dev Updates the total flash loan premium
* flash loan premium consist in 2 parts
* - A part is sent to aToken holders as extra balance
* - A part is collected by the protocol reserves
* @param flashloanPremiumTotal total premium in bps
*/
function updateFlashloanPremiumTotal(uint256 flashloanPremiumTotal) external;
/**
* @dev Updates the flash loan premium collected by protocol reserves
* @param flashloanPremiumToProtocol part of the premium sent to protocol
*/
function updateFlashloanPremiumToProtocol(uint256 flashloanPremiumToProtocol) external;
}

View File

@ -90,6 +90,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
_maxStableRateBorrowSizePercent = 2500;
_flashLoanPremiumTotal = 9;
_maxNumberOfReserves = 128;
_flashLoanPremiumToProtocol = 0;
}
/**
@ -438,12 +439,14 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
address currentAsset;
address currentATokenAddress;
uint256 currentAmount;
uint256 currentPremium;
uint256 currentPremiumToLP;
uint256 currentPremiumToProtocol;
uint256 currentAmountPlusPremium;
address debtToken;
address[] aTokenAddresses;
uint256[] premiums;
uint256[] totalPremiums;
uint256 flashloanPremiumTotal;
uint256 flashloanPremiumToProtocol;
}
/**
@ -475,30 +478,35 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
FlashLoanLocalVars memory vars;
vars.aTokenAddresses = new address[](assets.length);
vars.premiums = new uint256[](assets.length);
vars.totalPremiums = new uint256[](assets.length);
ValidationLogic.validateFlashloan(assets, amounts, _reserves);
vars.receiver = IFlashLoanReceiver(receiverAddress);
vars.flashloanPremiumTotal = _authorizedFlashBorrowers[msg.sender] ? 0 : _flashLoanPremiumTotal;
(vars.flashloanPremiumTotal, vars.flashloanPremiumToProtocol) = _authorizedFlashBorrowers[
msg.sender
]
? (0, 0)
: (_flashLoanPremiumTotal, _flashLoanPremiumToProtocol);
for (vars.i = 0; vars.i < assets.length; vars.i++) {
vars.aTokenAddresses[vars.i] = _reserves[assets[vars.i]].aTokenAddress;
vars.premiums[vars.i] = amounts[vars.i].percentMul(vars.flashloanPremiumTotal);
vars.totalPremiums[vars.i] = amounts[vars.i].percentMul(vars.flashloanPremiumTotal);
IAToken(vars.aTokenAddresses[vars.i]).transferUnderlyingTo(receiverAddress, amounts[vars.i]);
}
require(
vars.receiver.executeOperation(assets, amounts, vars.premiums, msg.sender, params),
vars.receiver.executeOperation(assets, amounts, vars.totalPremiums, msg.sender, params),
Errors.LP_INVALID_FLASH_LOAN_EXECUTOR_RETURN
);
for (vars.i = 0; vars.i < assets.length; vars.i++) {
vars.currentAsset = assets[vars.i];
vars.currentAmount = amounts[vars.i];
vars.currentPremium = vars.premiums[vars.i];
vars.currentATokenAddress = vars.aTokenAddresses[vars.i];
vars.currentAmountPlusPremium = vars.currentAmount.add(vars.currentPremium);
vars.currentAmountPlusPremium = vars.currentAmount.add(vars.totalPremiums[vars.i]);
vars.currentPremiumToProtocol = amounts[vars.i].percentMul(vars.flashloanPremiumToProtocol);
vars.currentPremiumToLP = vars.totalPremiums[vars.i].sub(vars.currentPremiumToProtocol);
if (DataTypes.InterestRateMode(modes[vars.i]) == DataTypes.InterestRateMode.NONE) {
DataTypes.ReserveData storage reserve = _reserves[vars.currentAsset];
@ -507,7 +515,10 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
reserve.updateState(reserveCache);
reserve.cumulateToLiquidityIndex(
IERC20(vars.currentATokenAddress).totalSupply(),
vars.currentPremium
vars.currentPremiumToLP
);
reserve.accruedToTreasury = reserve.accruedToTreasury.add(
vars.currentPremiumToProtocol.rayDiv(reserve.liquidityIndex)
);
reserve.updateInterestRates(
reserveCache,
@ -541,7 +552,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
msg.sender,
vars.currentAsset,
vars.currentAmount,
vars.currentPremium,
vars.totalPremiums[vars.i],
referralCode
);
}
@ -734,21 +745,28 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
/**
* @dev Returns the percentage of available liquidity that can be borrowed at once at stable rate
*/
function MAX_STABLE_RATE_BORROW_SIZE_PERCENT() public view returns (uint256) {
function MAX_STABLE_RATE_BORROW_SIZE_PERCENT() public view override returns (uint256) {
return _maxStableRateBorrowSizePercent;
}
/**
* @dev Returns the fee on flash loans
* @dev Returns the total fee on flash loans
*/
function FLASHLOAN_PREMIUM_TOTAL() public view returns (uint256) {
function FLASHLOAN_PREMIUM_TOTAL() public view override returns (uint256) {
return _flashLoanPremiumTotal;
}
/**
* @dev Returns the part of the flashloan fees sent to protocol
*/
function FLASHLOAN_PREMIUM_TO_PROTOCOL() public view override returns (uint256) {
return _flashLoanPremiumToProtocol;
}
/**
* @dev Returns the maximum number of reserves supported to be listed in this LendingPool
*/
function MAX_NUMBER_RESERVES() public view returns (uint256) {
function MAX_NUMBER_RESERVES() public view override returns (uint256) {
return _maxNumberOfReserves;
}
@ -884,6 +902,11 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
}
}
/**
* @dev Authorizes/Unauthorizes a flash borrower. Authorized borrowers pay no flash loan premium
* @param flashBorrower address of the flash borrower
* @param authorized `true` to authorize, `false` to unauthorize
*/
function updateFlashBorrowerAuthorization(address flashBorrower, bool authorized)
external
override
@ -892,10 +915,31 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
_authorizedFlashBorrowers[flashBorrower] = authorized;
}
/**
* @dev Returns whether a flashborrower is authorized (pays no premium)
* @param flashBorrower address of the flash borrower
* @return `true` if authorized, `false` if not
*/
function isFlashBorrowerAuthorized(address flashBorrower) external view override returns (bool) {
return _authorizedFlashBorrowers[flashBorrower];
}
/**
* @dev Updates flash loan premiums
* flash loan premium consist in 2 parts
* - A part is sent to aToken holders as extra balance
* - A part is collected by the protocol reserves
* @param flashLoanPremiumTotal total premium in bps
* @param flashLoanPremiumToProtocol part of the premium sent to protocol
*/
function updateFlashloanPremiums(
uint256 flashLoanPremiumTotal,
uint256 flashLoanPremiumToProtocol
) external override onlyLendingPoolConfigurator {
_flashLoanPremiumTotal = flashLoanPremiumTotal;
_flashLoanPremiumToProtocol = flashLoanPremiumToProtocol;
}
struct ExecuteBorrowParams {
address asset;
address user;

View File

@ -508,6 +508,26 @@ contract LendingPoolConfigurator is VersionedInitializable, ILendingPoolConfigur
return _riskAdmins[admin];
}
/// @inheritdoc ILendingPoolConfigurator
function updateFlashloanPremiumTotal(uint256 flashloanPremiumTotal)
external
override
onlyPoolAdmin
{
_pool.updateFlashloanPremiums(flashloanPremiumTotal, _pool.FLASHLOAN_PREMIUM_TO_PROTOCOL());
emit FlashloanPremiumTotalUpdated(flashloanPremiumTotal);
}
/// @inheritdoc ILendingPoolConfigurator
function updateFlashloanPremiumToProtocol(uint256 flashloanPremiumToProtocol)
external
override
onlyPoolAdmin
{
_pool.updateFlashloanPremiums(_pool.FLASHLOAN_PREMIUM_TOTAL(), flashloanPremiumToProtocol);
emit FlashloanPremiumToProcolUpdated(flashloanPremiumToProtocol);
}
function _initTokenWithProxy(address implementation, bytes memory initParams)
internal
returns (address)

View File

@ -31,4 +31,6 @@ contract LendingPoolStorage {
uint256 internal _maxNumberOfReserves;
mapping(address => bool) _authorizedFlashBorrowers;
uint256 internal _flashLoanPremiumToProtocol;
}

View File

@ -1,7 +1,7 @@
import BigNumber from 'bignumber.js';
import { TestEnv, makeSuite } from './helpers/make-suite';
import { APPROVAL_AMOUNT_LENDING_POOL, oneRay } from '../../helpers/constants';
import { APPROVAL_AMOUNT_LENDING_POOL, MAX_UINT_AMOUNT, oneRay } from '../../helpers/constants';
import { convertToCurrencyDecimals, getContract } from '../../helpers/contracts-helpers';
import { ethers } from 'ethers';
import { MockFlashLoanReceiver } from '../../types/MockFlashLoanReceiver';
@ -32,7 +32,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
});
it('Deposits WETH into the reserve', async () => {
const { pool, weth } = testEnv;
const { pool, weth, aave } = testEnv;
const userAddress = await pool.signer.getAddress();
const amountToDeposit = ethers.utils.parseEther('1');
@ -41,52 +41,124 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await weth.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.deposit(weth.address, amountToDeposit, userAddress, '0');
await aave.mint(amountToDeposit);
await aave.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.deposit(aave.address, amountToDeposit, userAddress, '0');
});
it('Takes WETH flashloan with mode = 0, returns the funds correctly', async () => {
const { pool, helpersContract, weth } = testEnv;
it('Takes WETH flash loan with mode = 0, returns the funds correctly', async () => {
const { pool, helpersContract, weth, aWETH } = testEnv;
const flashBorrowedAmount = ethers.utils.parseEther('0.8');
const fees = new BigNumber(flashBorrowedAmount.mul(9).div(10000).toString());
let reserveData = await helpersContract.getReserveData(weth.address);
const totalLiquidityBefore = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
await pool.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
[ethers.utils.parseEther('0.8')],
[flashBorrowedAmount],
[0],
_mockFlashLoanReceiver.address,
'0x10',
'0'
);
ethers.utils.parseUnits('10000');
await pool.mintToTreasury([weth.address]);
const reserveData = await helpersContract.getReserveData(weth.address);
reserveData = await helpersContract.getReserveData(weth.address);
const currentLiquidityRate = reserveData.liquidityRate;
const currentLiquidityIndex = reserveData.liquidityIndex;
const totalLiquidity = new BigNumber(reserveData.availableLiquidity.toString())
const totalLiquidityAfter = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
expect(totalLiquidity.toString()).to.be.equal('1000720000000000000');
expect(totalLiquidityBefore.plus(fees).toString()).to.be.equal(totalLiquidityAfter.toString());
expect(currentLiquidityRate.toString()).to.be.equal('0');
expect(currentLiquidityIndex.toString()).to.be.equal('1000720000000000000000000000');
});
it('Takes an authorized AAVE flash loan with mode = 0, returns the funds correctly', async () => {
const {
pool,
helpersContract,
aave,
configurator,
users: [, , , authorizedUser],
} = testEnv;
await configurator.authorizeFlashBorrower(authorizedUser.address);
const flashBorrowedAmount = ethers.utils.parseEther('0.8');
const fees = new BigNumber(0);
let reserveData = await helpersContract.getReserveData(aave.address);
const totalLiquidityBefore = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
await pool
.connect(authorizedUser.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
[aave.address],
[flashBorrowedAmount],
[0],
_mockFlashLoanReceiver.address,
'0x10',
'0'
);
await pool.mintToTreasury([aave.address]);
ethers.utils.parseUnits('10000');
reserveData = await helpersContract.getReserveData(aave.address);
const totalLiquidityAfter = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
expect(totalLiquidityBefore.plus(fees).toString()).to.be.equal(totalLiquidityAfter.toString());
});
it('Takes an ETH flashloan with mode = 0 as big as the available liquidity', async () => {
const { pool, helpersContract, weth } = testEnv;
const reserveDataBefore = await helpersContract.getReserveData(weth.address);
let reserveData = await helpersContract.getReserveData(weth.address);
const totalLiquidityBefore = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
const flashBorrowedAmount = totalLiquidityBefore.toString();
const fees = new BigNumber(flashBorrowedAmount).multipliedBy(9).dividedBy(10000).toString();
const txResult = await pool.flashLoan(
_mockFlashLoanReceiver.address,
[weth.address],
['1000720000000000000'],
[totalLiquidityBefore.toString()],
[0],
_mockFlashLoanReceiver.address,
'0x10',
'0'
);
const reserveData = await helpersContract.getReserveData(weth.address);
await pool.mintToTreasury([weth.address]);
reserveData = await helpersContract.getReserveData(weth.address);
const totalLiquidityAfter = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
const currentLiqudityRate = reserveData.liquidityRate;
const currentLiquidityIndex = reserveData.liquidityIndex;
@ -177,6 +249,12 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
let reserveData = await helpersContract.getReserveData(weth.address);
let totalLiquidityBefore = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
await pool
.connect(caller.signer)
.flashLoan(
@ -191,14 +269,25 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
const { variableDebtTokenAddress } = await helpersContract.getReserveTokensAddresses(
weth.address
);
reserveData = await helpersContract.getReserveData(weth.address);
const totalLiquidityAfter = new BigNumber(reserveData.availableLiquidity.toString())
.plus(reserveData.totalStableDebt.toString())
.plus(reserveData.totalVariableDebt.toString());
expect(totalLiquidityAfter.toString()).to.be.equal(
ethers.BigNumber.from(totalLiquidityBefore.toString())
);
const wethDebtToken = await getVariableDebtToken(variableDebtTokenAddress);
const callerDebt = await wethDebtToken.balanceOf(caller.address);
expect(callerDebt.toString()).to.be.equal('800000000000000000', 'Invalid user debt');
// repays debt for later, so no interest accrue
await weth.connect(caller.signer).mint(await convertToCurrencyDecimals(weth.address, '1000'));
await weth.connect(caller.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.connect(caller.signer).repay(weth.address, MAX_UINT_AMOUNT, 2, caller.address);
});
it('tries to take a flashloan that is bigger than the available liquidity (revert expected)', async () => {
const { pool, weth, users } = testEnv;
const caller = users[1];