Skip to content
This repository was archived by the owner on May 24, 2024. It is now read-only.

Commit b8ee236

Browse files
hoytechdglowinski
authored andcommitted
Ability for governance to set custom collateral factor for a pair of assets (similar to AAVE e-mode)
1 parent dfaa778 commit b8ee236

File tree

7 files changed

+126
-2
lines changed

7 files changed

+126
-2
lines changed

TODO

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
Overrides:
2+
* liquidations: will incorrectly calculate liquidatable amount
3+
* verify adding fields to the end of LiquidityStatus doesn't break any ABIs
4+
* view can't correctly query collateral/liability values of individual assets
5+
? fix this in riskManager.computeAssetLiquidities
6+
* make sure user isn't entered into any other markets (with 0 balance)
7+
* attacker could dust their account and force-disable the override
8+
19
Lending logic:
210
g when a token has < 18 decimal places, and a user withdraws their full EToken balance, 0 out the remaining dust so user gets a storage refund
311

contracts/Events.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ abstract contract Events {
5757
event GovSetReserveFee(address indexed underlying, uint32 newReserveFee);
5858
event GovConvertReserves(address indexed underlying, address indexed recipient, uint amount);
5959
event GovSetChainlinkPriceFeed(address indexed underlying, address chainlinkAggregator);
60+
event GovSetOverride(address indexed liability, address indexed collateral, Storage.OverrideConfig newOverride);
6061

6162
event RequestSwap(address indexed accountIn, address indexed accountOut, address indexed underlyingIn, address underlyingOut, uint amount, uint swapType);
6263
event RequestSwapHub(address indexed accountIn, address indexed accountOut, address indexed underlyingIn, address underlyingOut, uint amountIn, uint amountOut, uint mode, address swapHandler);

contracts/IRiskManager.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ interface IRiskManager {
1919
uint liabilityValue;
2020
uint numBorrows;
2121
bool borrowIsolated;
22+
uint numCollaterals;
23+
bool overrideEnabled;
2224
}
2325

2426
struct AssetLiquidity {

contracts/Storage.sol

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,11 @@ abstract contract Storage is Constants {
9393
mapping(address => address) internal pTokenLookup; // PToken => underlying
9494
mapping(address => address) internal reversePTokenLookup; // underlying => PToken
9595
mapping(address => address) internal chainlinkPriceFeedLookup; // underlying => chainlinkAggregator
96+
97+
struct OverrideConfig {
98+
bool enabled;
99+
uint32 collateralFactor;
100+
}
101+
102+
mapping(address => mapping(address => OverrideConfig)) internal overrideLookup; // liability => collateral => OverrideConfig
96103
}

contracts/modules/Governance.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ contract Governance is BaseLogic {
117117
emit GovSetChainlinkPriceFeed(underlying, chainlinkAggregator);
118118
}
119119

120+
function setOverride(address liability, address collateral, OverrideConfig calldata newOverride) external nonReentrant governorOnly {
121+
require(underlyingLookup[liability].eTokenAddress != address(0), "e/gov/liability-not-activated");
122+
require(underlyingLookup[collateral].eTokenAddress != address(0), "e/gov/collateral-not-activated");
123+
124+
overrideLookup[liability][collateral] = newOverride;
125+
126+
emit GovSetOverride(liability, collateral, newOverride);
127+
}
120128

121129
// getters
122130

contracts/modules/RiskManager.sol

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,12 +287,23 @@ contract RiskManager is IRiskManager, BaseLogic {
287287

288288
// Liquidity
289289

290+
struct OverrideCache {
291+
address liability;
292+
uint liabilityValue;
293+
294+
address collateral;
295+
uint collateralValue;
296+
}
297+
290298
function computeLiquidityRaw(address account, address[] memory underlyings) private view returns (LiquidityStatus memory status) {
291299
status.collateralValue = 0;
292300
status.liabilityValue = 0;
293301
status.numBorrows = 0;
294302
status.borrowIsolated = false;
303+
status.numCollaterals = 0;
304+
status.overrideEnabled = false;
295305

306+
OverrideCache memory overrideCache;
296307
AssetConfig memory config;
297308
AssetStorage storage assetStorage;
298309
AssetCache memory assetCache;
@@ -311,11 +322,16 @@ contract RiskManager is IRiskManager, BaseLogic {
311322
(uint price,) = getPriceInternal(assetCache, config);
312323

313324
status.numBorrows++;
325+
overrideCache.liability = underlying;
326+
314327
if (config.borrowIsolated) status.borrowIsolated = true;
315328

316329
uint assetLiability = getCurrentOwed(assetStorage, assetCache, account);
317330

318331
if (balance != 0) { // self-collateralisation
332+
status.numCollaterals++;
333+
overrideCache.collateral = underlying;
334+
319335
uint balanceInUnderlying = balanceToUnderlyingAmount(assetCache, balance);
320336

321337
uint selfAmount = assetLiability;
@@ -337,19 +353,32 @@ contract RiskManager is IRiskManager, BaseLogic {
337353
status.borrowIsolated = true; // self-collateralised loans are always isolated
338354
}
339355

340-
assetLiability = assetLiability * price / 1e18;
356+
assetLiability = overrideCache.liabilityValue = assetLiability * price / 1e18;
341357
assetLiability = config.borrowFactor != 0 ? assetLiability * CONFIG_FACTOR_SCALE / config.borrowFactor : MAX_SANE_DEBT_AMOUNT;
342358
status.liabilityValue += assetLiability;
343359
} else if (balance != 0 && config.collateralFactor != 0) {
344360
initAssetCache(underlying, assetStorage, assetCache);
345361
(uint price,) = getPriceInternal(assetCache, config);
346362

363+
status.numCollaterals++;
364+
overrideCache.collateral = underlying;
365+
347366
uint balanceInUnderlying = balanceToUnderlyingAmount(assetCache, balance);
348-
uint assetCollateral = balanceInUnderlying * price / 1e18;
367+
uint assetCollateral = overrideCache.collateralValue = balanceInUnderlying * price / 1e18;
349368
assetCollateral = assetCollateral * config.collateralFactor / CONFIG_FACTOR_SCALE;
350369
status.collateralValue += assetCollateral;
351370
}
352371
}
372+
373+
if (status.numBorrows == 1 && status.numCollaterals == 1 && overrideCache.liability != overrideCache.collateral) {
374+
OverrideConfig memory overrideConfig = overrideLookup[overrideCache.liability][overrideCache.collateral];
375+
376+
if (overrideConfig.enabled) {
377+
status.overrideEnabled = true;
378+
status.collateralValue = overrideCache.collateralValue * overrideConfig.collateralFactor / CONFIG_FACTOR_SCALE;
379+
status.liabilityValue = overrideCache.liabilityValue;
380+
}
381+
}
353382
}
354383

355384
function computeLiquidity(address account) public view override returns (LiquidityStatus memory) {

test/override.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
const et = require('./lib/eTestLib');
2+
const scenarios = require('./lib/scenarios');
3+
4+
5+
et.testSet({
6+
desc: "overrides",
7+
8+
preActions: ctx => [
9+
...scenarios.basicLiquidity()(ctx),
10+
{ send: 'tokens.TST3.approve', args: [ctx.contracts.euler.address, et.MaxUint256,], },
11+
{ send: 'tokens.TST3.mint', args: [ctx.wallet.address, et.eth(100)], },
12+
{ send: 'eTokens.eTST3.deposit', args: [0, et.eth(10)], },
13+
14+
{ action: 'updateUniswapPrice', pair: 'TST/WETH', price: '2', },
15+
{ action: 'updateUniswapPrice', pair: 'TST2/WETH', price: '0.5', },
16+
{ action: 'updateUniswapPrice', pair: 'TST3/WETH', price: '0.25', },
17+
18+
{ action: 'setAssetConfig', tok: 'TST3', config: { borrowIsolated: false, borrowFactor: .5, }, },
19+
],
20+
})
21+
22+
23+
24+
.test({
25+
desc: "override basic",
26+
actions: ctx => [
27+
{ from: ctx.wallet2, send: 'dTokens.dTST.borrow', args: [0, et.eth(.1)], },
28+
29+
// Account starts off normal, with single collateral and single borrow
30+
31+
{ call: 'exec.liquidity', args: [ctx.wallet2.address], onResult: r => {
32+
et.equals(r.liabilityValue, 0.5, .001); // 0.1 * 2 / 0.4
33+
et.equals(r.collateralValue, 3.75, .001); // 10 * 0.5 * 0.75
34+
et.expect(r.overrideEnabled).to.equal(false);
35+
}, },
36+
37+
// Override is added for this liability/collateral pair
38+
39+
{ send: 'governance.setOverride', args: [
40+
ctx.contracts.tokens.TST.address,
41+
ctx.contracts.tokens.TST2.address,
42+
{
43+
enabled: true,
44+
collateralFactor: Math.floor(0.97 * 4e9),
45+
},
46+
], },
47+
48+
{ call: 'exec.liquidity', args: [ctx.wallet2.address], onResult: r => {
49+
et.equals(r.liabilityValue, 0.2, .001); // 0.1 * 2
50+
et.equals(r.collateralValue, 4.85, .001); // 10 * 0.5 * 0.97
51+
et.expect(r.overrideEnabled).to.equal(true);
52+
}, },
53+
54+
{ from: ctx.wallet2, send: 'dTokens.dTST3.borrow', args: [0, et.eth(.1)], },
55+
56+
// Additional borrow on account disables override
57+
58+
{ call: 'exec.liquidity', args: [ctx.wallet2.address], onResult: r => {
59+
et.equals(r.liabilityValue, 0.55, .001); // (0.1 * 2 / 0.4) + (0.1 * 0.25 / 0.5)
60+
et.equals(r.collateralValue, 3.75, .001); // 10 * 0.5 * 0.75
61+
et.expect(r.overrideEnabled).to.equal(false);
62+
}, },
63+
],
64+
})
65+
66+
67+
68+
69+
.run();

0 commit comments

Comments
 (0)