Skip to content

Commit

Permalink
Merge pull request #733 from bgd-labs/fix/730-precision-liquidity-index
Browse files Browse the repository at this point in the history
Fixes #730. accruedToTreasury not being properly considered together with aToken supply
  • Loading branch information
miguelmtzinf authored Nov 15, 2022
2 parents 7d8b7bf + 3ebe5d5 commit 1b950ab
Show file tree
Hide file tree
Showing 11 changed files with 328 additions and 24 deletions.
2 changes: 1 addition & 1 deletion contracts/protocol/libraries/helpers/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ library Errors {
string public constant SUPPLY_CAP_EXCEEDED = '51'; // 'Supply cap is exceeded'
string public constant UNBACKED_MINT_CAP_EXCEEDED = '52'; // 'Unbacked mint cap is exceeded'
string public constant DEBT_CEILING_EXCEEDED = '53'; // 'Debt ceiling is exceeded'
string public constant ATOKEN_SUPPLY_NOT_ZERO = '54'; // 'AToken supply is not zero'
string public constant UNDERLYING_CLAIMABLE_RIGHTS_NOT_ZERO = '54'; // 'Claimable rights over underlying not zero (aToken supply or accruedToTreasury)'
string public constant STABLE_DEBT_NOT_ZERO = '55'; // 'Stable debt supply is not zero'
string public constant VARIABLE_DEBT_SUPPLY_NOT_ZERO = '56'; // 'Variable debt supply is not zero'
string public constant LTV_VALIDATION_FAILED = '57'; // 'Ltv validation failed'
Expand Down
5 changes: 3 additions & 2 deletions contracts/protocol/libraries/logic/BridgeLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -127,7 +127,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
);

Expand Down
3 changes: 2 additions & 1 deletion contracts/protocol/libraries/logic/FlashLoanLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
);

Expand Down
2 changes: 1 addition & 1 deletion contracts/protocol/libraries/logic/SupplyLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
19 changes: 11 additions & 8 deletions contracts/protocol/libraries/logic/ValidationLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -70,9 +71,8 @@ library ValidationLogic {
uint256 supplyCap = reserveCache.reserveConfiguration.getSupplyCap();
require(
supplyCap == 0 ||
(IAToken(reserveCache.aTokenAddress).scaledTotalSupply().rayMul(
reserveCache.nextLiquidityIndex
) + amount) <=
((IAToken(reserveCache.aTokenAddress).scaledTotalSupply() +
uint256(reserve.accruedToTreasury)).rayMul(reserveCache.nextLiquidityIndex) + amount) <=
supplyCap * (10**reserveCache.reserveConfiguration.getDecimals()),
Errors.SUPPLY_CAP_EXCEEDED
);
Expand Down Expand Up @@ -636,7 +636,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.UNDERLYING_CLAIMABLE_RIGHTS_NOT_ZERO
);
}

/**
Expand Down
8 changes: 5 additions & 3 deletions contracts/protocol/pool/PoolConfigurator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions helpers/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BigNumber } from 'ethers';
import {BigNumber} from 'ethers';

export interface SymbolMap<T> {
[symbol: string]: T;
Expand Down Expand Up @@ -123,7 +123,7 @@ export enum ProtocolErrors {
SUPPLY_CAP_EXCEEDED = '51', // 'Supply cap is exceeded'
UNBACKED_MINT_CAP_EXCEEDED = '52', // 'Unbacked mint cap is exceeded'
DEBT_CEILING_EXCEEDED = '53', // 'Debt ceiling is exceeded'
ATOKEN_SUPPLY_NOT_ZERO = '54', // 'AToken supply is not zero'
UNDERLYING_CLAIMABLE_RIGHTS_NOT_ZERO = '54', // 'Claimable rights over underlying not zero (aToken supply or accruedToTreasury)'
STABLE_DEBT_NOT_ZERO = '55', // 'Stable debt supply is not zero'
VARIABLE_DEBT_SUPPLY_NOT_ZERO = '56', // 'Variable debt supply is not zero'
LTV_VALIDATION_FAILED = '57', // 'Ltv validation failed'
Expand Down
4 changes: 3 additions & 1 deletion test-suites/helpers/utils/calculations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,9 @@ export const calcExpectedReserveDataAfterBackUnbacked = (

expectedReserveData.liquidityIndex = cumulateToLiquidityIndex(
expectedReserveData.liquidityIndex,
totalSupply,
totalSupply.add(
expectedReserveData.accruedToTreasuryScaled.rayMul(expectedReserveData.liquidityIndex)
),
premiumToLP
);

Expand Down
123 changes: 123 additions & 0 deletions test-suites/liquidity-indexes.spec.ts
Original file line number Diff line number Diff line change
@@ -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'
);
});
});
6 changes: 3 additions & 3 deletions test-suites/pool-drop-reserve.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ makeSuite('Pool: Drop Reserve', (testEnv: TestEnv) => {
let _mockFlashLoanReceiver = {} as MockFlashLoanReceiver;

const {
ATOKEN_SUPPLY_NOT_ZERO,
UNDERLYING_CLAIMABLE_RIGHTS_NOT_ZERO,
STABLE_DEBT_NOT_ZERO,
VARIABLE_DEBT_SUPPLY_NOT_ZERO,
ASSET_NOT_LISTED,
Expand Down Expand Up @@ -46,7 +46,7 @@ makeSuite('Pool: Drop Reserve', (testEnv: TestEnv) => {

await pool.deposit(dai.address, depositedAmount, deployer.address, 0);

await expect(configurator.dropReserve(dai.address)).to.be.revertedWith(ATOKEN_SUPPLY_NOT_ZERO);
await expect(configurator.dropReserve(dai.address)).to.be.revertedWith(UNDERLYING_CLAIMABLE_RIGHTS_NOT_ZERO);

await pool.connect(user1.signer).deposit(weth.address, depositedAmount, user1.address, 0);

Expand All @@ -71,7 +71,7 @@ makeSuite('Pool: Drop Reserve', (testEnv: TestEnv) => {
);

expect(await pool.connect(user1.signer).repay(dai.address, MAX_UINT_AMOUNT, 2, user1.address));
await expect(configurator.dropReserve(dai.address)).to.be.revertedWith(ATOKEN_SUPPLY_NOT_ZERO);
await expect(configurator.dropReserve(dai.address)).to.be.revertedWith(UNDERLYING_CLAIMABLE_RIGHTS_NOT_ZERO);
});

it('User 1 withdraw DAI, drop DAI reserve should succeed', async () => {
Expand Down
Loading

0 comments on commit 1b950ab

Please sign in to comment.