Skip to content

Commit 19a1531

Browse files
sakulstrabrotherlymitekyzia551
authored
feat: add dedicated eMode creation to config engine
eMode creation & eMode updates sharing the same code has been problematic in the past due to concurrent proposals. The addition of dedicated eMode creation should prevent issues with collisions. --------- Co-authored-by: Harsh Pandey <[email protected]> Co-authored-by: Andrey <[email protected]>
1 parent 68728ba commit 19a1531

File tree

8 files changed

+251
-16
lines changed

8 files changed

+251
-16
lines changed

src/contracts/extensions/v3-config-engine/AaveV3ConfigEngine.sol

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,17 @@ contract AaveV3ConfigEngine is IAaveV3ConfigEngine {
175175
);
176176
}
177177

178+
/// @inheritdoc IAaveV3ConfigEngine
179+
function createEModeCategories(EModeCategoryCreation[] calldata creations) external {
180+
EMODE_ENGINE.functionDelegateCall(
181+
abi.encodeWithSelector(
182+
EModeEngine.executeEModeCategoriesCreate.selector,
183+
_getEngineConstants(),
184+
creations
185+
)
186+
);
187+
}
188+
178189
/// @inheritdoc IAaveV3ConfigEngine
179190
function updateEModeCategories(EModeCategoryUpdate[] calldata updates) external {
180191
EMODE_ENGINE.functionDelegateCall(

src/contracts/extensions/v3-config-engine/AaveV3Payload.sol

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ abstract contract AaveV3Payload {
4242
function execute() external {
4343
_preExecute();
4444

45+
IEngine.EModeCategoryCreation[] memory newEmodes = eModeCategoryCreations();
4546
IEngine.EModeCategoryUpdate[] memory eModeCategories = eModeCategoriesUpdates();
4647
IEngine.Listing[] memory listings = newListings();
4748
IEngine.ListingWithCustomImpl[] memory listingsCustom = newListingsCustom();
@@ -52,6 +53,12 @@ abstract contract AaveV3Payload {
5253
IEngine.AssetEModeUpdate[] memory assetsEModes = assetsEModeUpdates();
5354
IEngine.CapsUpdate[] memory caps = capsUpdates();
5455

56+
if (newEmodes.length != 0) {
57+
address(CONFIG_ENGINE).functionDelegateCall(
58+
abi.encodeWithSelector(CONFIG_ENGINE.createEModeCategories.selector, newEmodes)
59+
);
60+
}
61+
5562
if (eModeCategories.length != 0) {
5663
address(CONFIG_ENGINE).functionDelegateCall(
5764
abi.encodeWithSelector(CONFIG_ENGINE.updateEModeCategories.selector, eModeCategories)
@@ -143,6 +150,14 @@ abstract contract AaveV3Payload {
143150
/// @dev to be defined in the child with a list of priceFeeds to update
144151
function priceFeedsUpdates() public view virtual returns (IEngine.PriceFeedUpdate[] memory) {}
145152

153+
/// @dev to be defined in the child with a list of eMode categories to create
154+
function eModeCategoryCreations()
155+
public
156+
view
157+
virtual
158+
returns (IEngine.EModeCategoryCreation[] memory)
159+
{}
160+
146161
/// @dev to be defined in the child with a list of eMode categories to update
147162
function eModeCategoriesUpdates()
148163
public

src/contracts/extensions/v3-config-engine/IAaveV3ConfigEngine.sol

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,26 @@ interface IAaveV3ConfigEngine {
219219
string label;
220220
}
221221

222+
/**
223+
* @dev Example (mock):
224+
* EModeCategoryUpdate({
225+
* ltv: 60_00,
226+
* liqThreshold: 70_00,
227+
* liqBonus: 3_00,
228+
* label: 'WETH USDC',
229+
* borrowables:[USDC],
230+
* collaterals:[ETH]
231+
* })
232+
*/
233+
struct EModeCategoryCreation {
234+
uint256 ltv;
235+
uint256 liqThreshold;
236+
uint256 liqBonus;
237+
string label;
238+
address[] borrowables;
239+
address[] collaterals;
240+
}
241+
222242
/**
223243
* @dev Example (mock):
224244
* RateStrategyUpdate({
@@ -295,6 +315,13 @@ interface IAaveV3ConfigEngine {
295315
*/
296316
function updateBorrowSide(BorrowUpdate[] memory updates) external;
297317

318+
/**
319+
* @notice Performs creation of new e-mode categories, in the Aave pool configured in this engine instance
320+
* @param creations `EModeCategoryCreation[]` list of declarative creations containing the new parameters
321+
* More information on the documentation of the struct.
322+
*/
323+
function createEModeCategories(EModeCategoryCreation[] memory creations) external;
324+
298325
/**
299326
* @notice Performs an update of the e-mode categories, in the Aave pool configured in this engine instance
300327
* @param updates `EModeCategoryUpdate[]` list of declarative updates containing the new parameters

src/contracts/extensions/v3-config-engine/libraries/EModeEngine.sol

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,52 @@ library EModeEngine {
1111
using PercentageMath for uint256;
1212
using SafeCast for uint256;
1313

14+
error NoAvailableEmodeCategory();
15+
1416
function executeAssetsEModeUpdate(
1517
IEngine.EngineConstants calldata engineConstants,
1618
IEngine.AssetEModeUpdate[] memory updates
1719
) external {
1820
require(updates.length != 0, 'AT_LEAST_ONE_UPDATE_REQUIRED');
1921

20-
_configAssetsEMode(engineConstants.poolConfigurator, updates);
22+
_configAssetsEMode(engineConstants.poolConfigurator, engineConstants.pool, updates);
23+
}
24+
25+
function executeEModeCategoriesCreate(
26+
IEngine.EngineConstants calldata engineConstants,
27+
IEngine.EModeCategoryCreation[] memory creations
28+
) external {
29+
for (uint256 i; i < creations.length; i++) {
30+
require(
31+
keccak256(abi.encode(creations[i].label)) !=
32+
keccak256(abi.encode(EngineFlags.KEEP_CURRENT_STRING)),
33+
'INVALID_LABEL'
34+
);
35+
uint8 categoryId = _findFirstUnusedEmodeCategory(engineConstants.pool);
36+
engineConstants.poolConfigurator.setEModeCategory(
37+
categoryId,
38+
creations[i].ltv.toUint16(),
39+
creations[i].liqThreshold.toUint16(),
40+
// For reference, this is to simplify the interaction with the Aave protocol,
41+
// as there the definition is as e.g. 105% (5% bonus for liquidators)
42+
(100_00 + creations[i].liqBonus).toUint16(),
43+
creations[i].label
44+
);
45+
for (uint256 j; j < creations[i].collaterals.length; j++) {
46+
engineConstants.poolConfigurator.setAssetCollateralInEMode(
47+
creations[i].collaterals[j],
48+
categoryId,
49+
true
50+
);
51+
}
52+
for (uint256 k; k < creations[i].borrowables.length; k++) {
53+
engineConstants.poolConfigurator.setAssetBorrowableInEMode(
54+
creations[i].borrowables[k],
55+
categoryId,
56+
true
57+
);
58+
}
59+
}
2160
}
2261

2362
function executeEModeCategoriesUpdate(
@@ -31,9 +70,14 @@ library EModeEngine {
3170

3271
function _configAssetsEMode(
3372
IPoolConfigurator poolConfigurator,
73+
IPool pool,
3474
IEngine.AssetEModeUpdate[] memory updates
3575
) internal {
3676
for (uint256 i = 0; i < updates.length; i++) {
77+
DataTypes.CollateralConfig memory cfg = pool.getEModeCategoryCollateralConfig(
78+
updates[i].eModeCategory
79+
);
80+
require(cfg.liquidationThreshold != 0, 'INVALID_UPDATE');
3781
if (updates[i].collateral != EngineFlags.KEEP_CURRENT) {
3882
poolConfigurator.setAssetCollateralInEMode(
3983
updates[i].asset,
@@ -69,11 +113,13 @@ library EModeEngine {
69113
keccak256(abi.encode(updates[i].label)) !=
70114
keccak256(abi.encode(EngineFlags.KEEP_CURRENT_STRING));
71115

72-
if (notAllKeepCurrent && atLeastOneKeepCurrent) {
73-
DataTypes.CollateralConfig memory cfg = pool.getEModeCategoryCollateralConfig(
74-
updates[i].eModeCategory
75-
);
116+
DataTypes.CollateralConfig memory cfg = pool.getEModeCategoryCollateralConfig(
117+
updates[i].eModeCategory
118+
);
119+
// should only be able to update existing eModes, not create new ones
120+
require(cfg.liquidationThreshold != 0, 'INVALID_UPDATE');
76121

122+
if (notAllKeepCurrent && atLeastOneKeepCurrent) {
77123
if (updates[i].ltv == EngineFlags.KEEP_CURRENT) {
78124
updates[i].ltv = cfg.ltv;
79125
}
@@ -114,4 +160,15 @@ library EModeEngine {
114160
}
115161
}
116162
}
163+
164+
/**
165+
* @dev eModes must have a non-zero lt so we select the first that has a zero lt.
166+
*/
167+
function _findFirstUnusedEmodeCategory(IPool pool) private view returns (uint8) {
168+
// eMode id 0 is skipped intentially as it is the reserved default
169+
for (uint8 i = 1; i < 256; i++) {
170+
if (pool.getEModeCategoryCollateralConfig(i).liquidationThreshold == 0) return i;
171+
}
172+
revert NoAvailableEmodeCategory();
173+
}
117174
}

tests/extensions/v3-config-engine/AaveV3ConfigEngineTest.t.sol

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {AaveV3MockPriceFeedUpdate} from './mocks/AaveV3MockPriceFeedUpdate.sol';
1717
import {AaveV3MockEModeCategoryUpdate, AaveV3MockEModeCategoryUpdateEdgeBonus} from './mocks/AaveV3MockEModeCategoryUpdate.sol';
1818
import {AaveV3MockEModeCategoryUpdateNoChange} from './mocks/AaveV3MockEModeCategoryUpdateNoChange.sol';
1919
import {AaveV3MockAssetEModeUpdate} from './mocks/AaveV3MockAssetEModeUpdate.sol';
20+
import {AaveV3MockEModeCategoryCreation} from './mocks/AaveV3MockEModeCategoryCreation.sol';
2021

2122
import {ATokenInstance} from '../../../src/contracts/instances/ATokenInstance.sol';
2223
import {EModeConfiguration} from '../../../src/contracts/protocol/libraries/configuration/EModeConfiguration.sol';
@@ -507,14 +508,59 @@ contract AaveV3ConfigEngineTest is TestnetProcedures, ProtocolV3TestBase {
507508
);
508509
}
509510

511+
function testEModeCategoryCreation() public {
512+
AaveV3MockEModeCategoryCreation payload = new AaveV3MockEModeCategoryCreation(
513+
tokenList.weth,
514+
tokenList.usdx,
515+
tokenList.wbtc,
516+
tokenList.weth,
517+
configEngine
518+
);
519+
520+
vm.prank(roleList.marketOwner);
521+
contracts.aclManager.addPoolAdmin(address(payload));
522+
523+
payload.execute();
524+
525+
DataTypes.EModeCategory memory prevEmodeCategoryData;
526+
prevEmodeCategoryData.ltv = 50_00;
527+
prevEmodeCategoryData.liquidationThreshold = 60_00;
528+
prevEmodeCategoryData.liquidationBonus = 101_00; // 100_00 + 1_00
529+
prevEmodeCategoryData.label = 'No assets';
530+
531+
uint256 bitmap = contracts.poolProxy.getEModeCategoryBorrowableBitmap(1);
532+
assertEq(bitmap, 0);
533+
bitmap = contracts.poolProxy.getEModeCategoryCollateralBitmap(1);
534+
assertEq(bitmap, 0);
535+
_validateEmodeCategory(
536+
IPoolAddressesProvider(address(contracts.poolAddressesProvider)),
537+
1,
538+
prevEmodeCategoryData
539+
);
540+
541+
prevEmodeCategoryData.ltv = 97_40;
542+
prevEmodeCategoryData.liquidationThreshold = 97_60;
543+
prevEmodeCategoryData.liquidationBonus = 101_50; // 100_00 + 1_50
544+
prevEmodeCategoryData.label = 'Test';
545+
prevEmodeCategoryData.collateralBitmap = 10; // 1010
546+
prevEmodeCategoryData.borrowableBitmap = 12; // 1100
547+
_validateEmodeCategory(
548+
IPoolAddressesProvider(address(contracts.poolAddressesProvider)),
549+
2,
550+
prevEmodeCategoryData
551+
);
552+
}
553+
510554
function testEModeCategoryUpdates() public {
555+
EModeCategoryInput memory ct = _genCategoryOne();
556+
vm.prank(poolAdmin);
557+
contracts.poolConfiguratorProxy.setEModeCategory(ct.id, ct.ltv, ct.lt, ct.lb, ct.label);
558+
511559
AaveV3MockEModeCategoryUpdate payload = new AaveV3MockEModeCategoryUpdate(configEngine);
512560

513561
vm.prank(roleList.marketOwner);
514562
contracts.aclManager.addPoolAdmin(address(payload));
515563

516-
contracts.poolProxy.getEModeCategoryData(1);
517-
518564
createConfigurationSnapshot(
519565
'preTestEngineEModeCategoryUpdate',
520566
IPool(address(contracts.poolProxy))
@@ -543,6 +589,10 @@ contract AaveV3ConfigEngineTest is TestnetProcedures, ProtocolV3TestBase {
543589
}
544590

545591
function testEModeCategoryUpdatesWrongBonus() public {
592+
EModeCategoryInput memory ct = _genCategoryOne();
593+
vm.prank(poolAdmin);
594+
contracts.poolConfiguratorProxy.setEModeCategory(ct.id, ct.ltv, ct.lt, ct.lb, ct.label);
595+
546596
AaveV3MockEModeCategoryUpdateEdgeBonus payload = new AaveV3MockEModeCategoryUpdateEdgeBonus(
547597
configEngine
548598
);
@@ -556,6 +606,10 @@ contract AaveV3ConfigEngineTest is TestnetProcedures, ProtocolV3TestBase {
556606

557607
// TODO manage this after testFail* deprecation.
558608
function testEModeCategoryUpdatesNoChangeShouldNotEmit() public {
609+
EModeCategoryInput memory ct = _genCategoryOne();
610+
vm.prank(poolAdmin);
611+
contracts.poolConfiguratorProxy.setEModeCategory(ct.id, ct.ltv, ct.lt, ct.lb, ct.label);
612+
559613
AaveV3MockEModeCategoryUpdateNoChange payload = new AaveV3MockEModeCategoryUpdateNoChange(
560614
configEngine
561615
);
@@ -571,6 +625,9 @@ contract AaveV3ConfigEngineTest is TestnetProcedures, ProtocolV3TestBase {
571625

572626
// Same as testEModeCategoryUpdatesNoChangeShouldNotEmit, but this time should work, as we are not expecting any event emitted
573627
function testEModeCategoryUpdatesNoChange() public {
628+
EModeCategoryInput memory ct = _genCategoryOne();
629+
vm.prank(poolAdmin);
630+
contracts.poolConfiguratorProxy.setEModeCategory(ct.id, ct.ltv, ct.lt, ct.lb, ct.label);
574631
AaveV3MockEModeCategoryUpdateNoChange payload = new AaveV3MockEModeCategoryUpdateNoChange(
575632
configEngine
576633
);
@@ -610,12 +667,12 @@ contract AaveV3ConfigEngineTest is TestnetProcedures, ProtocolV3TestBase {
610667
}
611668

612669
function testAssetEModeUpdates() public {
670+
vm.prank(poolAdmin);
671+
contracts.poolConfiguratorProxy.setEModeCategory(1, 97_40, 97_60, 101_50, 'ETH Correlated');
672+
613673
address asset = tokenList.usdx;
614674
address asset2 = tokenList.wbtc;
615675

616-
AaveV3MockEModeCategoryUpdate payloadToAddEMode = new AaveV3MockEModeCategoryUpdate(
617-
configEngine
618-
);
619676
AaveV3MockAssetEModeUpdate payload = new AaveV3MockAssetEModeUpdate(
620677
asset,
621678
asset2,
@@ -624,11 +681,8 @@ contract AaveV3ConfigEngineTest is TestnetProcedures, ProtocolV3TestBase {
624681

625682
vm.startPrank(roleList.marketOwner);
626683
contracts.aclManager.addPoolAdmin(address(payload));
627-
contracts.aclManager.addPoolAdmin(address(payloadToAddEMode));
628684
vm.stopPrank();
629685

630-
payloadToAddEMode.execute();
631-
632686
createConfigurationSnapshot(
633687
'preTestEngineAssetEModeUpdate',
634688
IPool(address(contracts.poolProxy))
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.0;
3+
4+
import '../../../../src/contracts/extensions/v3-config-engine/AaveV3Payload.sol';
5+
6+
/**
7+
* @dev Smart contract for a mock emode category update, to be able to test
8+
* IMPORTANT Parameters are pseudo-random, DON'T USE THIS ANYHOW IN PRODUCTION
9+
* @dev Inheriting directly from AaveV3Payload for being able to inject a custom engine
10+
* @author BGD Labs
11+
*/
12+
contract AaveV3MockEModeCategoryCreation is AaveV3Payload {
13+
address immutable COLLATERAL_ONE;
14+
address immutable COLLATERAL_TWO;
15+
16+
address immutable BORROWABLE_ONE;
17+
address immutable BORROWABLE_TWO;
18+
19+
constructor(
20+
address collateral1,
21+
address collateral2,
22+
address borrowable1,
23+
address borrowable2,
24+
address customEngine
25+
) AaveV3Payload(IEngine(customEngine)) {
26+
COLLATERAL_ONE = collateral1;
27+
COLLATERAL_TWO = collateral2;
28+
BORROWABLE_ONE = borrowable1;
29+
BORROWABLE_TWO = borrowable2;
30+
}
31+
32+
function eModeCategoryCreations()
33+
public
34+
view
35+
override
36+
returns (IEngine.EModeCategoryCreation[] memory)
37+
{
38+
IEngine.EModeCategoryCreation[] memory eModeUpdates = new IEngine.EModeCategoryCreation[](2);
39+
40+
address[] memory empty = new address[](0);
41+
42+
eModeUpdates[0] = IEngine.EModeCategoryCreation({
43+
ltv: 50_00,
44+
liqThreshold: 60_00,
45+
liqBonus: 1_00,
46+
label: 'No assets',
47+
borrowables: empty,
48+
collaterals: empty
49+
});
50+
address[] memory collaterals = new address[](2);
51+
address[] memory borrowables = new address[](2);
52+
collaterals[0] = COLLATERAL_ONE;
53+
collaterals[1] = COLLATERAL_TWO;
54+
borrowables[0] = BORROWABLE_ONE;
55+
borrowables[1] = BORROWABLE_TWO;
56+
eModeUpdates[1] = IEngine.EModeCategoryCreation({
57+
ltv: 97_40,
58+
liqThreshold: 97_60,
59+
liqBonus: 1_50,
60+
label: 'Test',
61+
borrowables: borrowables,
62+
collaterals: collaterals
63+
});
64+
65+
return eModeUpdates;
66+
}
67+
68+
function getPoolContext() public pure override returns (IEngine.PoolContext memory) {
69+
return IEngine.PoolContext({networkName: 'Polygon', networkAbbreviation: 'Pol'});
70+
}
71+
}

0 commit comments

Comments
 (0)