Skip to content

Commit 9ead0f2

Browse files
authored
Merge pull request #534 from aave/fix/387-emode-pricefeed-liquidations
Use eMode price oracle if set when liquidating
2 parents 17d7b9d + 28f72fe commit 9ead0f2

File tree

6 files changed

+182
-23
lines changed

6 files changed

+182
-23
lines changed

contracts/protocol/libraries/helpers/Errors.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,4 @@ library Errors {
9797
string public constant STABLE_BORROWING_ENABLED = '88'; // 'Stable borrowing is enabled'
9898
string public constant SILOED_BORROWING_VIOLATION = '89'; // 'User is trying to borrow multiple assets including a siloed one'
9999
string public constant RESERVE_DEBT_NOT_ZERO = '90'; // the total debt of the reserve needs to be 0
100-
}
100+
}

contracts/protocol/libraries/logic/LiquidationLogic.sol

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import {DataTypes} from '../../libraries/types/DataTypes.sol';
1010
import {ReserveLogic} from './ReserveLogic.sol';
1111
import {ValidationLogic} from './ValidationLogic.sol';
1212
import {GenericLogic} from './GenericLogic.sol';
13-
import {EModeLogic} from './EModeLogic.sol';
1413
import {IsolationModeLogic} from './IsolationModeLogic.sol';
14+
import {EModeLogic} from './EModeLogic.sol';
1515
import {UserConfiguration} from '../../libraries/configuration/UserConfiguration.sol';
1616
import {ReserveConfiguration} from '../../libraries/configuration/ReserveConfiguration.sol';
1717
import {IAToken} from '../../../interfaces/IAToken.sol';
@@ -81,6 +81,8 @@ library LiquidationLogic {
8181
uint256 healthFactor;
8282
uint256 liquidationProtocolFeeAmount;
8383
uint256 closeFactor;
84+
address collateralPriceSource;
85+
address debtPriceSource;
8486
IAToken collateralAToken;
8587
IPriceOracleGetter oracle;
8688
DataTypes.ReserveCache debtReserveCache;
@@ -156,12 +158,31 @@ library LiquidationLogic {
156158
? vars.maxLiquidatableDebt
157159
: params.debtToCover;
158160

159-
vars.liquidationBonus = EModeLogic.isInEModeCategory(
160-
params.userEModeCategory,
161-
collateralReserve.configuration.getEModeCategory()
162-
)
163-
? eModeCategories[params.userEModeCategory].liquidationBonus
164-
: collateralReserve.configuration.getLiquidationBonus();
161+
vars.collateralPriceSource = params.collateralAsset;
162+
vars.debtPriceSource = params.debtAsset;
163+
vars.liquidationBonus = collateralReserve.configuration.getLiquidationBonus();
164+
165+
if (params.userEModeCategory != 0) {
166+
address eModePriceSource = eModeCategories[params.userEModeCategory].priceSource;
167+
168+
if (
169+
EModeLogic.isInEModeCategory(
170+
params.userEModeCategory,
171+
collateralReserve.configuration.getEModeCategory()
172+
)
173+
) {
174+
vars.liquidationBonus = eModeCategories[params.userEModeCategory].liquidationBonus;
175+
176+
if (eModePriceSource != address(0)) {
177+
vars.collateralPriceSource = eModePriceSource;
178+
}
179+
}
180+
181+
// when in eMode, debt will always be in the same eMode category, can skip matching category check
182+
if (eModePriceSource != address(0)) {
183+
vars.debtPriceSource = eModePriceSource;
184+
}
185+
}
165186

166187
(
167188
vars.maxCollateralToLiquidate,
@@ -170,8 +191,8 @@ library LiquidationLogic {
170191
) = _calculateAvailableCollateralToLiquidate(
171192
collateralReserve,
172193
vars.debtReserveCache,
173-
params.collateralAsset,
174-
params.debtAsset,
194+
vars.collateralPriceSource,
195+
vars.debtPriceSource,
175196
vars.actualDebtToLiquidate,
176197
vars.userCollateralBalance,
177198
vars.liquidationBonus,

contracts/protocol/pool/PoolConfigurator.sol

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,12 @@ contract PoolConfigurator is VersionedInitializable, IPoolConfigurator {
265265
}
266266

267267
/// @inheritdoc IPoolConfigurator
268-
function setSiloedBorrowing(address asset, bool newSiloed) external override onlyRiskOrPoolAdmins {
269-
if(newSiloed) {
268+
function setSiloedBorrowing(address asset, bool newSiloed)
269+
external
270+
override
271+
onlyRiskOrPoolAdmins
272+
{
273+
if (newSiloed) {
270274
_checkNoBorrowers(asset);
271275
}
272276
DataTypes.ReserveConfigurationMap memory currentConfig = _pool.getConfiguration(asset);
@@ -481,9 +485,10 @@ contract PoolConfigurator is VersionedInitializable, IPoolConfigurator {
481485
require(totalATokens == 0, Errors.RESERVE_LIQUIDITY_NOT_ZERO);
482486
}
483487

484-
function _checkNoBorrowers(address asset) internal view {
485-
uint256 totalDebt = IPoolDataProvider(_addressesProvider.getPoolDataProvider())
486-
.getTotalDebt(asset);
488+
function _checkNoBorrowers(address asset) internal view {
489+
uint256 totalDebt = IPoolDataProvider(_addressesProvider.getPoolDataProvider()).getTotalDebt(
490+
asset
491+
);
487492
require(totalDebt == 0, Errors.RESERVE_DEBT_NOT_ZERO);
488493
}
489494

test-suites/configurator.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ const getReserveData = async (helpersContract: AaveProtocolDataProvider, asset:
8585

8686
makeSuite('PoolConfigurator', (testEnv: TestEnv) => {
8787
let baseConfigValues: ReserveConfigurationValues;
88-
const { RESERVE_LIQUIDITY_NOT_ZERO, INVALID_DEBT_CEILING, RESERVE_DEBT_NOT_ZERO } = ProtocolErrors;
88+
const { RESERVE_LIQUIDITY_NOT_ZERO, INVALID_DEBT_CEILING, RESERVE_DEBT_NOT_ZERO } =
89+
ProtocolErrors;
8990

9091
before(() => {
9192
const {
@@ -922,7 +923,7 @@ makeSuite('PoolConfigurator', (testEnv: TestEnv) => {
922923

923924
it('Resets the siloed borrowing mode. Tries to set siloed borrowing after the asset has been borrowed (revert expected)', async () => {
924925
const snap = await evmSnapshot();
925-
926+
926927
const {
927928
configurator,
928929
weth,
@@ -957,7 +958,6 @@ makeSuite('PoolConfigurator', (testEnv: TestEnv) => {
957958
);
958959

959960
await evmRevert(snap);
960-
961961
});
962962

963963
it('Sets a debt ceiling through the pool admin', async () => {

test-suites/liquidation-emode.spec.ts

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,8 +476,8 @@ makeSuite('Pool Liquidation: Liquidates borrows in eMode with price change', (te
476476

477477
const balanceAfter = await aDai.balanceOf(user1.address);
478478

479-
const debtPrice = await oracle.getAssetPrice(usdc.address);
480-
const collateralPrice = await oracle.getAssetPrice(dai.address);
479+
const debtPrice = await oracle.getAssetPrice(EMODE_ORACLE_ADDRESS);
480+
const collateralPrice = await oracle.getAssetPrice(EMODE_ORACLE_ADDRESS);
481481

482482
const expectedCollateralLiquidated = debtPrice
483483
.mul(toBorrow.div(2))
@@ -489,4 +489,133 @@ makeSuite('Pool Liquidation: Liquidates borrows in eMode with price change', (te
489489

490490
expect(collateralLiquidated).to.be.closeTo(expectedCollateralLiquidated, 2);
491491
});
492+
493+
it('Liquidation of non-eMode collateral with eMode debt in eMode with custom price feed', async () => {
494+
await evmRevert(snap);
495+
snap = await evmSnapshot();
496+
497+
const {
498+
helpersContract,
499+
oracle,
500+
configurator,
501+
pool,
502+
poolAdmin,
503+
dai,
504+
usdc,
505+
weth,
506+
aWETH,
507+
users: [user1, user2],
508+
} = testEnv;
509+
510+
// We need an extra oracle for prices. USe user address as asset in price oracle
511+
const EMODE_ORACLE_ADDRESS = user1.address;
512+
await oracle.setAssetPrice(EMODE_ORACLE_ADDRESS, utils.parseUnits('1', 8));
513+
await oracle.setAssetPrice(dai.address, utils.parseUnits('0.99', 8));
514+
await oracle.setAssetPrice(usdc.address, utils.parseUnits('1.01', 8));
515+
await oracle.setAssetPrice(weth.address, utils.parseUnits('4000', 8));
516+
517+
// Create category
518+
expect(
519+
await configurator
520+
.connect(poolAdmin.signer)
521+
.setEModeCategory(
522+
1,
523+
CATEGORY.ltv,
524+
CATEGORY.lt,
525+
CATEGORY.lb,
526+
EMODE_ORACLE_ADDRESS,
527+
CATEGORY.label
528+
)
529+
);
530+
531+
const categoryData = await pool.getEModeCategoryData(CATEGORY.id);
532+
533+
expect(categoryData.ltv).to.be.equal(CATEGORY.ltv, 'invalid eMode category ltv');
534+
expect(categoryData.liquidationThreshold).to.be.equal(
535+
CATEGORY.lt,
536+
'invalid eMode category liq threshold'
537+
);
538+
expect(categoryData.liquidationBonus).to.be.equal(
539+
CATEGORY.lb,
540+
'invalid eMode category liq bonus'
541+
);
542+
expect(categoryData.priceSource).to.be.equal(
543+
EMODE_ORACLE_ADDRESS,
544+
'invalid eMode category price source'
545+
);
546+
547+
// Add Dai and USDC to category
548+
await configurator.connect(poolAdmin.signer).setAssetEModeCategory(dai.address, CATEGORY.id);
549+
await configurator.connect(poolAdmin.signer).setAssetEModeCategory(usdc.address, CATEGORY.id);
550+
expect(await helpersContract.getReserveEModeCategory(dai.address)).to.be.eq(CATEGORY.id);
551+
expect(await helpersContract.getReserveEModeCategory(usdc.address)).to.be.eq(CATEGORY.id);
552+
553+
// User 1 supply 1 dai + 1 eth, user 2 supply 10000 usdc
554+
const wethSupplyAmount = utils.parseUnits('1', 18);
555+
const daiSupplyAmount = utils.parseUnits('1', 18);
556+
const usdcSupplyAmount = utils.parseUnits('10000', 6);
557+
558+
expect(await dai.connect(user1.signer)['mint(uint256)'](daiSupplyAmount));
559+
expect(await weth.connect(user1.signer)['mint(uint256)'](wethSupplyAmount));
560+
expect(await usdc.connect(user2.signer)['mint(uint256)'](usdcSupplyAmount.mul(2)));
561+
562+
expect(await dai.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT));
563+
expect(await weth.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT));
564+
expect(await usdc.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT));
565+
566+
expect(await pool.connect(user1.signer).supply(dai.address, daiSupplyAmount, user1.address, 0));
567+
expect(
568+
await pool.connect(user1.signer).supply(weth.address, wethSupplyAmount, user1.address, 0)
569+
);
570+
expect(
571+
await pool.connect(user2.signer).supply(usdc.address, usdcSupplyAmount, user2.address, 0)
572+
);
573+
574+
// Activate emode
575+
expect(await pool.connect(user1.signer).setUserEMode(CATEGORY.id));
576+
577+
// Borrow a as much usdc as possible
578+
const userData = await pool.getUserAccountData(user1.address);
579+
const toBorrow = userData.availableBorrowsBase.div(100);
580+
581+
expect(
582+
await pool
583+
.connect(user1.signer)
584+
.borrow(usdc.address, toBorrow, RateMode.Variable, 0, user1.address)
585+
);
586+
587+
// Drop weth price
588+
const oraclePrice = await oracle.getAssetPrice(EMODE_ORACLE_ADDRESS);
589+
590+
const userGlobalDataBefore = await pool.getUserAccountData(user1.address);
591+
expect(userGlobalDataBefore.healthFactor).to.be.gt(utils.parseUnits('1', 18));
592+
593+
await oracle.setAssetPrice(EMODE_ORACLE_ADDRESS, oraclePrice.mul(2));
594+
595+
const userGlobalDataAfter = await pool.getUserAccountData(user1.address);
596+
expect(userGlobalDataAfter.healthFactor).to.be.lt(utils.parseUnits('1', 18), INVALID_HF);
597+
598+
const balanceBefore = await aWETH.balanceOf(user1.address);
599+
600+
// Liquidate
601+
await pool
602+
.connect(user2.signer)
603+
.liquidationCall(weth.address, usdc.address, user1.address, toBorrow.div(2), false);
604+
605+
const balanceAfter = await aWETH.balanceOf(user1.address);
606+
607+
const debtPrice = await oracle.getAssetPrice(EMODE_ORACLE_ADDRESS);
608+
const collateralPrice = await oracle.getAssetPrice(weth.address);
609+
610+
const wethConfig = await helpersContract.getReserveConfigurationData(weth.address);
611+
612+
const expectedCollateralLiquidated = debtPrice
613+
.mul(toBorrow.div(2))
614+
.percentMul(wethConfig.liquidationBonus)
615+
.mul(BigNumber.from(10).pow(18))
616+
.div(collateralPrice.mul(BigNumber.from(10).pow(6)));
617+
618+
const collateralLiquidated = balanceBefore.sub(balanceAfter);
619+
expect(collateralLiquidated).to.be.closeTo(expectedCollateralLiquidated, 2);
620+
});
492621
});

test-suites/siloed-borrowing.spec.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,19 @@ makeSuite('Siloed borrowing', (testEnv: TestEnv) => {
3131
const wethSupplyAmount = utils.parseEther('1');
3232
const daiBorrowAmount = utils.parseEther('10');
3333
const daiSupplyAmount = utils.parseEther('1000');
34-
const usdcSupplyAmount = utils.parseUnits('1000',6);
34+
const usdcSupplyAmount = utils.parseUnits('1000', 6);
3535

3636
await dai.connect(users[0].signer)['mint(address,uint256)'](users[0].address, daiSupplyAmount);
3737
await dai.connect(users[0].signer).approve(pool.address, MAX_UINT_AMOUNT);
3838
await pool.connect(users[0].signer).supply(dai.address, daiSupplyAmount, users[0].address, '0');
3939

40-
await usdc.connect(users[1].signer)['mint(address,uint256)'](users[1].address, usdcSupplyAmount);
40+
await usdc
41+
.connect(users[1].signer)
42+
['mint(address,uint256)'](users[1].address, usdcSupplyAmount);
4143
await usdc.connect(users[1].signer).approve(pool.address, MAX_UINT_AMOUNT);
42-
await pool.connect(users[1].signer).supply(usdc.address, usdcSupplyAmount, users[1].address, '0');
44+
await pool
45+
.connect(users[1].signer)
46+
.supply(usdc.address, usdcSupplyAmount, users[1].address, '0');
4347

4448
await weth
4549
.connect(users[1].signer)

0 commit comments

Comments
 (0)