@@ -20,8 +20,11 @@
|
|
20
20
|
function observations(uint256 index) external view returns (uint32 blockTimestamp, int56 tickCumulative, uint160 liquidityCumulative, bool initialized);
|
21
21
|
function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external;
|
22
22
|
}
|
23
23
|
|
24
|
+
interface IChainlinkAggregatorV2V3 {
|
25
|
+
function latestAnswer() external view returns (int256);
|
26
|
+
}
|
24
27
|
|
25
28
|
contract RiskManager is IRiskManager, BaseLogic {
|
26
29
|
// Construction
|
27
30
|
|
@@ -180,8 +183,24 @@
|
|
180
183
|
|
181
184
|
return (decodeSqrtPriceX96(underlying, underlyingDecimalsScaler, sqrtPriceX96), ago);
|
182
185
|
}
|
183
186
|
|
187
|
+
function callChainlinkLatestAnswer(address chainlinkAggregator) private view returns (uint price) {
|
188
|
+
(bool success, bytes memory data) = chainlinkAggregator.staticcall(abi.encodeWithSelector(IChainlinkAggregatorV2V3.latestAnswer.selector));
|
189
|
+
|
190
|
+
if (!success) {
|
191
|
+
return 0;
|
192
|
+
}
|
193
|
+
|
194
|
+
int256 answer = abi.decode(data, (int256));
|
195
|
+
if (answer <= 0) {
|
196
|
+
return 0;
|
197
|
+
}
|
198
|
+
|
199
|
+
price = uint(answer);
|
200
|
+
if (price > 1e36) price = 1e36;
|
201
|
+
}
|
202
|
+
|
184
203
|
function resolvePricingConfig(AssetCache memory assetCache, AssetConfig memory config) private view returns (address underlying, uint16 pricingType, uint32 pricingParameters, uint24 twapWindow, uint underlyingDecimalsScaler) {
|
185
204
|
if (assetCache.pricingType == PRICINGTYPE__FORWARDED) {
|
186
205
|
underlying = pTokenLookup[assetCache.underlying];
|
187
206
|
|
@@ -211,17 +230,28 @@
|
|
211
230
|
twapPeriod = twapWindow;
|
212
231
|
} else if (pricingType == PRICINGTYPE__UNISWAP3_TWAP) {
|
213
232
|
address pool = computeUniswapPoolAddress(underlying, uint24(pricingParameters));
|
214
233
|
(twap, twapPeriod) = callUniswapObserve(underlying, underlyingDecimalsScaler, pool, twapWindow);
|
234
|
+
} else if (pricingType == PRICINGTYPE__CHAINLINK) {
|
235
|
+
twap = callChainlinkLatestAnswer(chainlinkPriceFeedLookup[underlying]);
|
236
|
+
twapPeriod = 0;
|
237
|
+
|
238
|
+
// if price invalid and uniswap fallback pool configured get the price from uniswap
|
239
|
+
if (twap == 0 && uint24(pricingParameters) != 0) {
|
240
|
+
address pool = computeUniswapPoolAddress(underlying, uint24(pricingParameters));
|
241
|
+
(twap, twapPeriod) = callUniswapObserve(underlying, underlyingDecimalsScaler, pool, twapWindow);
|
242
|
+
}
|
243
|
+
|
244
|
+
require(twap != 0, "e/unable-to-get-the-price");
|
215
245
|
} else {
|
216
246
|
revert("e/unknown-pricing-type");
|
217
247
|
}
|
218
248
|
}
|
219
249
|
|
220
250
|
function getPrice(address underlying) external view override returns (uint twap, uint twapPeriod) {
|
221
251
|
AssetConfig memory config = resolveAssetConfig(underlying);
|
222
252
|
AssetStorage storage assetStorage = eTokenLookup[config.eTokenAddress];
|
223
|
-
AssetCache memory assetCache =
|
253
|
+
AssetCache memory assetCache = internalLoadAssetCacheRO(underlying, assetStorage);
|
224
254
|
|
225
255
|
(twap, twapPeriod) = getPriceInternal(assetCache, config);
|
226
256
|
}
|
227
257
|
|
@@ -230,9 +260,9 @@
|
|
230
260
|
|
231
261
|
function getPriceFull(address underlying) external view override returns (uint twap, uint twapPeriod, uint currPrice) {
|
232
262
|
AssetConfig memory config = resolveAssetConfig(underlying);
|
233
263
|
AssetStorage storage assetStorage = eTokenLookup[config.eTokenAddress];
|
234
|
-
AssetCache memory assetCache =
|
264
|
+
AssetCache memory assetCache = internalLoadAssetCacheRO(underlying, assetStorage);
|
235
265
|
|
236
266
|
(twap, twapPeriod) = getPriceInternal(assetCache, config);
|
237
267
|
|
238
268
|
(address newUnderlying, uint16 pricingType, uint32 pricingParameters, , uint underlyingDecimalsScaler) = resolvePricingConfig(assetCache, config);
|
@@ -242,8 +272,10 @@
|
|
242
272
|
} else if (pricingType == PRICINGTYPE__UNISWAP3_TWAP || pricingType == PRICINGTYPE__FORWARDED) {
|
243
273
|
address pool = computeUniswapPoolAddress(newUnderlying, uint24(pricingParameters));
|
244
274
|
(uint160 sqrtPriceX96,,,,,,) = IUniswapV3Pool(pool).slot0();
|
245
275
|
currPrice = decodeSqrtPriceX96(newUnderlying, underlyingDecimalsScaler, sqrtPriceX96);
|
276
|
+
} else if (pricingType == PRICINGTYPE__CHAINLINK) {
|
277
|
+
currPrice = twap;
|
246
278
|
} else {
|
247
279
|
revert("e/unknown-pricing-type");
|
248
280
|
}
|
249
281
|
}
|
@@ -256,13 +256,18 @@
|
|
256
256
|
}
|
257
257
|
}
|
258
258
|
|
259
259
|
function loadAssetCacheRO(address underlying, AssetStorage storage assetStorage) internal view returns (AssetCache memory assetCache) {
|
260
|
+
require(reentrancyLock == REENTRANCYLOCK__UNLOCKED, "e/ro-reentrancy");
|
260
261
|
initAssetCache(underlying, assetStorage, assetCache);
|
261
262
|
}
|
262
263
|
|
264
|
+
function internalLoadAssetCacheRO(address underlying, AssetStorage storage assetStorage) internal view returns (AssetCache memory assetCache) {
|
265
|
+
initAssetCache(underlying, assetStorage, assetCache);
|
266
|
+
}
|
263
267
|
|
264
268
|
|
269
|
+
|
265
270
|
// Utils
|
266
271
|
|
267
272
|
function decodeExternalAmount(AssetCache memory assetCache, uint externalAmount) internal pure returns (uint scaledAmount) {
|
268
273
|
require(externalAmount <= assetCache.maxExternalAmount, "e/amount-too-large");
|
@@ -284,10 +289,11 @@
|
|
284
289
|
return uint144(amount);
|
285
290
|
}
|
286
291
|
|
287
292
|
function computeExchangeRate(AssetCache memory assetCache) private pure returns (uint) {
|
288
|
-
|
289
|
-
|
293
|
+
uint totalAssets = assetCache.poolSize + (assetCache.totalBorrows / INTERNAL_DEBT_PRECISION);
|
294
|
+
if (totalAssets == 0 || assetCache.totalBalances == 0) return 1e18;
|
295
|
+
return totalAssets * 1e18 / assetCache.totalBalances;
|
290
296
|
}
|
291
297
|
|
292
298
|
function underlyingAmountToBalance(AssetCache memory assetCache, uint amount) internal pure returns (uint) {
|
293
299
|
uint exchangeRate = computeExchangeRate(assetCache);
|
@@ -306,9 +312,9 @@
|
|
306
312
|
|
307
313
|
function callBalanceOf(AssetCache memory assetCache, address account) internal view FREEMEM returns (uint) {
|
308
314
|
// We set a gas limit so that a malicious token can't eat up all gas and cause a liquidity check to fail.
|
309
315
|
|
310
|
-
(bool success, bytes memory data) = assetCache.underlying.staticcall{gas:
|
316
|
+
(bool success, bytes memory data) = assetCache.underlying.staticcall{gas: 200000}(abi.encodeWithSelector(IERC20.balanceOf.selector, account));
|
311
317
|
|
312
318
|
// If token's balanceOf() call fails for any reason, return 0. This prevents malicious tokens from causing liquidity checks to fail.
|
313
319
|
// If the contract doesn't exist (maybe because selfdestructed), then data.length will be 0 and we will return 0.
|
314
320
|
// Data length > 32 is allowed because some legitimate tokens append extra data that can be safely ignored.
|
@@ -441,13 +447,13 @@
|
|
441
447
|
|
442
448
|
if (owed > prevOwed) {
|
443
449
|
uint change = owed - prevOwed;
|
444
450
|
emit Borrow(assetCache.underlying, account, change);
|
445
|
-
emitViaProxy_Transfer(dTokenAddress, address(0), account, change);
|
451
|
+
emitViaProxy_Transfer(dTokenAddress, address(0), account, change / assetCache.underlyingDecimalsScaler);
|
446
452
|
} else if (prevOwed > owed) {
|
447
453
|
uint change = prevOwed - owed;
|
448
454
|
emit Repay(assetCache.underlying, account, change);
|
449
|
-
emitViaProxy_Transfer(dTokenAddress, account, address(0), change);
|
455
|
+
emitViaProxy_Transfer(dTokenAddress, account, address(0), change / assetCache.underlyingDecimalsScaler);
|
450
456
|
}
|
451
457
|
}
|
452
458
|
|
453
459
|
function increaseBorrow(AssetStorage storage assetStorage, AssetCache memory assetCache, address dTokenAddress, address account, uint amount) internal {
|
@@ -6,9 +6,9 @@
|
|
6
6
|
|
7
7
|
abstract contract Storage is Constants {
|
8
8
|
// Dispatcher and upgrades
|
9
9
|
|
10
|
-
uint reentrancyLock;
|
10
|
+
uint internal reentrancyLock;
|
11
11
|
|
12
12
|
address upgradeAdmin;
|
13
13
|
address governorAdmin;
|
14
14
|
|
@@ -91,5 +91,6 @@
|
|
91
91
|
mapping(address => AssetStorage) internal eTokenLookup; // EToken => AssetStorage
|
92
92
|
mapping(address => address) internal dTokenLookup; // DToken => EToken
|
93
93
|
mapping(address => address) internal pTokenLookup; // PToken => underlying
|
94
94
|
mapping(address => address) internal reversePTokenLookup; // underlying => PToken
|
95
|
+
mapping(address => address) internal chainlinkPriceFeedLookup; // underlying => chainlinkAggregator
|
95
96
|
}
|
@@ -36,8 +36,9 @@
|
|
36
36
|
event RequestWithdraw(address indexed account, uint amount);
|
37
37
|
event RequestMint(address indexed account, uint amount);
|
38
38
|
event RequestBurn(address indexed account, uint amount);
|
39
39
|
event RequestTransferEToken(address indexed from, address indexed to, uint amount);
|
40
|
+
event RequestDonate(address indexed account, uint amount);
|
40
41
|
|
41
42
|
event RequestBorrow(address indexed account, uint amount);
|
42
43
|
event RequestRepay(address indexed account, uint amount);
|
43
44
|
event RequestTransferDToken(address indexed from, address indexed to, uint amount);
|
@@ -54,7 +55,8 @@
|
|
54
55
|
event GovSetIRM(address indexed underlying, uint interestRateModel, bytes resetParams);
|
55
56
|
event GovSetPricingConfig(address indexed underlying, uint16 newPricingType, uint32 newPricingParameter);
|
56
57
|
event GovSetReserveFee(address indexed underlying, uint32 newReserveFee);
|
57
58
|
event GovConvertReserves(address indexed underlying, address indexed recipient, uint amount);
|
59
|
+
event GovSetChainlinkPriceFeed(address indexed underlying, address chainlinkAggregator);
|
58
60
|
|
59
61
|
event RequestSwap(address indexed accountIn, address indexed accountOut, address indexed underlyingIn, address underlyingOut, uint amount, uint swapType);
|
60
62
|
}
|
@@ -18,8 +18,9 @@
|
|
18
18
|
uint internal constant MAX_POSSIBLE_ENTERED_MARKETS = 2**32; // limited by size of AccountStorage.numMarketsEntered
|
19
19
|
uint internal constant CONFIG_FACTOR_SCALE = 4_000_000_000; // must fit into a uint32
|
20
20
|
uint internal constant RESERVE_FEE_SCALE = 4_000_000_000; // must fit into a uint32
|
21
21
|
uint32 internal constant DEFAULT_RESERVE_FEE = uint32(0.23 * 4_000_000_000);
|
22
|
+
uint internal constant INITIAL_RESERVES = 1e6;
|
22
23
|
uint internal constant INITIAL_INTEREST_ACCUMULATOR = 1e27;
|
23
24
|
uint internal constant AVERAGE_LIQUIDITY_PERIOD = 24 * 60 * 60;
|
24
25
|
uint16 internal constant MIN_UNISWAP3_OBSERVATION_CARDINALITY = 144;
|
25
26
|
uint24 internal constant DEFAULT_TWAP_WINDOW_SECONDS = 30 * 60;
|
@@ -41,10 +42,14 @@
|
|
41
42
|
|
42
43
|
uint16 internal constant PRICINGTYPE__PEGGED = 1;
|
43
44
|
uint16 internal constant PRICINGTYPE__UNISWAP3_TWAP = 2;
|
44
45
|
uint16 internal constant PRICINGTYPE__FORWARDED = 3;
|
46
|
+
uint16 internal constant PRICINGTYPE__CHAINLINK = 4;
|
45
47
|
|
48
|
+
// Correct pricing types are always less than this value
|
49
|
+
uint16 internal constant PRICINGTYPE__OUT_OF_BOUNDS = 5;
|
46
50
|
|
51
|
+
|
47
52
|
// Modules
|
48
53
|
|
49
54
|
// Public single-proxy modules
|
50
55
|
uint internal constant MODULEID__INSTALLER = 1;
|