diff --git a/contracts/protocol/libraries/logic/BridgeLogic.sol b/contracts/protocol/libraries/logic/BridgeLogic.sol index d5f1ce9e2..0d8c8e2ae 100644 --- a/contracts/protocol/libraries/logic/BridgeLogic.sol +++ b/contracts/protocol/libraries/logic/BridgeLogic.sol @@ -63,7 +63,7 @@ library BridgeLogic { reserve.updateState(reserveCache); - ValidationLogic.validateSupply(reserveCache, amount); + ValidationLogic.validateSupply(reserveCache, reserve, amount); uint256 unbackedMintCap = reserveCache.reserveConfiguration.getUnbackedMintCap(); uint256 reserveDecimals = reserveCache.reserveConfiguration.getDecimals(); @@ -125,7 +125,8 @@ library BridgeLogic { uint256 added = backingAmount + fee; reserveCache.nextLiquidityIndex = reserve.cumulateToLiquidityIndex( - IERC20(reserveCache.aTokenAddress).totalSupply(), + IERC20(reserveCache.aTokenAddress).totalSupply() + + uint256(reserve.accruedToTreasury).rayMul(reserveCache.nextLiquidityIndex), feeToLP ); diff --git a/contracts/protocol/libraries/logic/FlashLoanLogic.sol b/contracts/protocol/libraries/logic/FlashLoanLogic.sol index fa800e321..521ed212c 100644 --- a/contracts/protocol/libraries/logic/FlashLoanLogic.sol +++ b/contracts/protocol/libraries/logic/FlashLoanLogic.sol @@ -231,7 +231,8 @@ library FlashLoanLogic { DataTypes.ReserveCache memory reserveCache = reserve.cache(); reserve.updateState(reserveCache); reserveCache.nextLiquidityIndex = reserve.cumulateToLiquidityIndex( - IERC20(reserveCache.aTokenAddress).totalSupply(), + IERC20(reserveCache.aTokenAddress).totalSupply() + + uint256(reserve.accruedToTreasury).rayMul(reserveCache.nextLiquidityIndex), premiumToLP ); diff --git a/contracts/protocol/libraries/logic/SupplyLogic.sol b/contracts/protocol/libraries/logic/SupplyLogic.sol index 98ad8e8f9..a9205864c 100644 --- a/contracts/protocol/libraries/logic/SupplyLogic.sol +++ b/contracts/protocol/libraries/logic/SupplyLogic.sol @@ -60,7 +60,7 @@ library SupplyLogic { reserve.updateState(reserveCache); - ValidationLogic.validateSupply(reserveCache, params.amount); + ValidationLogic.validateSupply(reserveCache, reserve, params.amount); reserve.updateInterestRates(reserveCache, params.asset, params.amount, 0); @@ -264,7 +264,12 @@ library SupplyLogic { if (useAsCollateral) { require( - ValidationLogic.validateUseAsCollateral(reservesData, reservesList, userConfig, reserveCache.reserveConfiguration), + ValidationLogic.validateUseAsCollateral( + reservesData, + reservesList, + userConfig, + reserveCache.reserveConfiguration + ), Errors.USER_IN_ISOLATION_MODE ); diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index fb2eda0c1..cf117ff4f 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -54,10 +54,11 @@ library ValidationLogic { * @param reserveCache The cached data of the reserve * @param amount The amount to be supplied */ - function validateSupply(DataTypes.ReserveCache memory reserveCache, uint256 amount) - internal - view - { + function validateSupply( + DataTypes.ReserveCache memory reserveCache, + DataTypes.ReserveData storage reserve, + uint256 amount + ) internal view { require(amount != 0, Errors.INVALID_AMOUNT); (bool isActive, bool isFrozen, , , bool isPaused) = reserveCache @@ -72,7 +73,9 @@ library ValidationLogic { supplyCap == 0 || (IAToken(reserveCache.aTokenAddress).scaledTotalSupply().rayMul( reserveCache.nextLiquidityIndex - ) + amount) <= + ) + + uint256(reserve.accruedToTreasury).rayMul(reserveCache.nextLiquidityIndex) + + amount) <= supplyCap * (10**reserveCache.reserveConfiguration.getDecimals()), Errors.SUPPLY_CAP_EXCEEDED ); @@ -650,7 +653,10 @@ library ValidationLogic { IERC20(reserve.variableDebtTokenAddress).totalSupply() == 0, Errors.VARIABLE_DEBT_SUPPLY_NOT_ZERO ); - require(IERC20(reserve.aTokenAddress).totalSupply() == 0, Errors.ATOKEN_SUPPLY_NOT_ZERO); + require( + IERC20(reserve.aTokenAddress).totalSupply() == 0 && reserve.accruedToTreasury == 0, + Errors.ATOKEN_SUPPLY_NOT_ZERO + ); } /** diff --git a/contracts/protocol/pool/PoolConfigurator.sol b/contracts/protocol/pool/PoolConfigurator.sol index b99610c2e..43cfddd19 100644 --- a/contracts/protocol/pool/PoolConfigurator.sol +++ b/contracts/protocol/pool/PoolConfigurator.sol @@ -480,9 +480,11 @@ contract PoolConfigurator is VersionedInitializable, IPoolConfigurator { } function _checkNoSuppliers(address asset) internal view { - uint256 totalATokens = IPoolDataProvider(_addressesProvider.getPoolDataProvider()) - .getATokenTotalSupply(asset); - require(totalATokens == 0, Errors.RESERVE_LIQUIDITY_NOT_ZERO); + (, uint256 accruedToTreasury, uint256 totalATokens, , , , , , , , , ) = IPoolDataProvider( + _addressesProvider.getPoolDataProvider() + ).getReserveData(asset); + + require(totalATokens == 0 && accruedToTreasury == 0, Errors.RESERVE_LIQUIDITY_NOT_ZERO); } function _checkNoBorrowers(address asset) internal view { diff --git a/test-suites/helpers/utils/calculations.ts b/test-suites/helpers/utils/calculations.ts index 9484c54ad..f28db4f29 100644 --- a/test-suites/helpers/utils/calculations.ts +++ b/test-suites/helpers/utils/calculations.ts @@ -269,7 +269,9 @@ export const calcExpectedReserveDataAfterBackUnbacked = ( expectedReserveData.liquidityIndex = cumulateToLiquidityIndex( expectedReserveData.liquidityIndex, - totalSupply, + totalSupply.add( + expectedReserveData.accruedToTreasuryScaled.rayMul(expectedReserveData.liquidityIndex) + ), premiumToLP ); diff --git a/test-suites/liquidity-indexes.spec.ts b/test-suites/liquidity-indexes.spec.ts new file mode 100644 index 000000000..a481d0bbc --- /dev/null +++ b/test-suites/liquidity-indexes.spec.ts @@ -0,0 +1,123 @@ +import {expect} from 'chai'; +import {BigNumber, ethers} from 'ethers'; +import {MAX_UINT_AMOUNT} from '../helpers/constants'; +import {makeSuite, TestEnv} from './helpers/make-suite'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import { + evmSnapshot, + evmRevert, + MockFlashLoanReceiver, + getMockFlashLoanReceiver, +} from '@aave/deploy-v3'; +import './helpers/utils/wadraymath'; + +declare var hre: HardhatRuntimeEnvironment; + +makeSuite('Pool: liquidity indexes misc tests', (testEnv: TestEnv) => { + const TOTAL_PREMIUM = 9; + const PREMIUM_TO_PROTOCOL = 3000; + + let _mockFlashLoanReceiver = {} as MockFlashLoanReceiver; + + let snap: string; + + const setupForFlashloan = async (testEnv: TestEnv) => { + const { + configurator, + pool, + weth, + aave, + dai, + users: [user0], + } = testEnv; + + _mockFlashLoanReceiver = await getMockFlashLoanReceiver(); + + await configurator.updateFlashloanPremiumTotal(TOTAL_PREMIUM); + await configurator.updateFlashloanPremiumToProtocol(PREMIUM_TO_PROTOCOL); + + const userAddress = user0.address; + const amountToDeposit = ethers.utils.parseEther('1'); + + await weth['mint(uint256)'](amountToDeposit); + + await weth.approve(pool.address, MAX_UINT_AMOUNT); + + await pool.deposit(weth.address, amountToDeposit, userAddress, '0'); + + await aave['mint(uint256)'](amountToDeposit); + + await aave.approve(pool.address, MAX_UINT_AMOUNT); + + await pool.deposit(aave.address, amountToDeposit, userAddress, '0'); + await dai['mint(uint256)'](amountToDeposit); + + await dai.approve(pool.address, MAX_UINT_AMOUNT); + + await pool.deposit(dai.address, amountToDeposit, userAddress, '0'); + }; + + before(async () => { + await setupForFlashloan(testEnv); + }); + + beforeEach(async () => { + snap = await evmSnapshot(); + }); + + afterEach(async () => { + await evmRevert(snap); + }); + + it('Validates that the flash loan fee properly takes into account both aToken supply and accruedToTreasury', async () => { + const { + pool, + helpersContract, + weth, + aWETH, + users: [depositorWeth], + } = testEnv; + + /** + * 1. Flashes 0.8 WETH + * 2. Flashes again 0.8 ETH (to have accruedToTreasury) + * 3. Validates that liquidity index took into account both aToken supply and accruedToTreasury + */ + + const wethFlashBorrowedAmount = ethers.utils.parseEther('0.8'); + + await pool.flashLoan( + _mockFlashLoanReceiver.address, + [weth.address], + [wethFlashBorrowedAmount], + [0], + _mockFlashLoanReceiver.address, + '0x10', + '0' + ); + + await pool.flashLoan( + _mockFlashLoanReceiver.address, + [weth.address], + [wethFlashBorrowedAmount], + [0], + _mockFlashLoanReceiver.address, + '0x10', + '0' + ); + + const wethReserveDataAfterSecondFlash = await helpersContract.getReserveData(weth.address); + + const totalScaledWithTreasuryAfterSecondFlash = ( + await aWETH.scaledBalanceOf(depositorWeth.address) + ).add(wethReserveDataAfterSecondFlash.accruedToTreasuryScaled.toString()); + + expect(await weth.balanceOf(aWETH.address)).to.be.closeTo( + BigNumber.from(totalScaledWithTreasuryAfterSecondFlash.toString()).rayMul( + wethReserveDataAfterSecondFlash.liquidityIndex + ), + 1, + 'Scaled total supply not (+/- 1) equal to WETH balance of aWETH' + ); + }); +});