Euler diff: contracts/modules/Exec.sol

Before: contract 0x155020C32cEb5E1BFdD6217c2E7906c92bcAC8c1
After: git d7f62927eb58592d82302c87205490c26cccca7c
Files changed (5) hide show
  1. contracts/modules/Exec.sol +61 -71
  2. contracts/BaseLogic.sol +11 -5
  3. contracts/Storage.sol +2 -1
  4. contracts/Events.sol +2 -0
  5. contracts/Constants.sol +5 -0
contracts/modules/Exec.sol CHANGED
@@ -31,8 +31,11 @@
31
31
  bool success;
32
32
  bytes result;
33
33
  }
34
34
 
35
+ /// @notice Error containing results of a simulated batch dispatch
36
+ error BatchDispatchSimulation(EulerBatchItemResponse[] simulation);
37
+
35
38
  // Accessors
36
39
 
37
40
  /// @notice Compute aggregate liquidity for an account
38
41
  /// @param account User address
@@ -99,84 +102,20 @@
99
102
 
100
103
  /// @notice Execute several operations in a single transaction
101
104
  /// @param items List of operations to execute
102
105
  /// @param deferLiquidityChecks List of user accounts to defer liquidity checks for
103
- /// @return List of operation results
104
- function batchDispatch(EulerBatchItem[] calldata items, address[] calldata deferLiquidityChecks) public reentrantOK returns (EulerBatchItemResponse[] memory) {
105
- address msgSender = unpackTrailingParamMsgSender();
106
-
107
- for (uint i = 0; i < deferLiquidityChecks.length; ++i) {
108
- address account = deferLiquidityChecks[i];
109
-
110
- require(accountLookup[account].deferLiquidityStatus == DEFERLIQUIDITY__NONE, "e/batch/reentrancy");
111
- accountLookup[account].deferLiquidityStatus = DEFERLIQUIDITY__CLEAN;
112
- }
113
-
114
-
115
- EulerBatchItemResponse[] memory response = new EulerBatchItemResponse[](items.length);
116
-
117
- for (uint i = 0; i < items.length; ++i) {
118
- EulerBatchItem calldata item = items[i];
119
- address proxyAddr = item.proxyAddr;
120
-
121
- uint32 moduleId = trustedSenders[proxyAddr].moduleId;
122
- address moduleImpl = trustedSenders[proxyAddr].moduleImpl;
123
-
124
- require(moduleId != 0, "e/batch/unknown-proxy-addr");
125
- require(moduleId <= MAX_EXTERNAL_MODULEID, "e/batch/call-to-internal-module");
126
-
127
- if (moduleImpl == address(0)) moduleImpl = moduleLookup[moduleId];
128
- require(moduleImpl != address(0), "e/batch/module-not-installed");
129
-
130
- bytes memory inputWrapped = abi.encodePacked(item.data, uint160(msgSender), uint160(proxyAddr));
131
- (bool success, bytes memory result) = moduleImpl.delegatecall(inputWrapped);
132
-
133
- if (success || item.allowError) {
134
- EulerBatchItemResponse memory r = response[i];
135
- r.success = success;
136
- r.result = result;
137
- } else {
138
- revertBytes(result);
139
- }
140
- }
141
-
142
-
143
- for (uint i = 0; i < deferLiquidityChecks.length; ++i) {
144
- address account = deferLiquidityChecks[i];
145
-
146
- uint8 status = accountLookup[account].deferLiquidityStatus;
147
- accountLookup[account].deferLiquidityStatus = DEFERLIQUIDITY__NONE;
148
-
149
- if (status == DEFERLIQUIDITY__DIRTY) checkLiquidity(account);
150
- }
151
-
152
- return response;
106
+ function batchDispatch(EulerBatchItem[] calldata items, address[] calldata deferLiquidityChecks) external reentrantOK {
107
+ doBatchDispatch(items, deferLiquidityChecks, false);
153
108
  }
154
109
 
155
- /// @notice Results of a batchDispatch, but with extra information
156
- struct EulerBatchExtra {
157
- EulerBatchItemResponse[] responses;
158
- uint gasUsed;
159
- IRiskManager.AssetLiquidity[][] liquidities;
160
- }
161
-
162
- /// @notice Call batchDispatch, but return extra information. Only intended to be used with callStatic.
110
+ /// @notice Call batch dispatch, but instruct it to revert with the responses, before the liquidity checks.
163
111
  /// @param items List of operations to execute
164
112
  /// @param deferLiquidityChecks List of user accounts to defer liquidity checks for
165
- /// @param queryLiquidity List of user accounts to return detailed liquidity information for
166
- /// @return output Structure with extra information
167
- function batchDispatchExtra(EulerBatchItem[] calldata items, address[] calldata deferLiquidityChecks, address[] calldata queryLiquidity) external reentrantOK returns (EulerBatchExtra memory output) {
168
- {
169
- uint origGasLeft = gasleft();
170
- output.responses = batchDispatch(items, deferLiquidityChecks);
171
- output.gasUsed = origGasLeft - gasleft();
172
- }
113
+ /// @dev During simulation all batch items are executed, regardless of the `allowError` flag
114
+ function batchDispatchSimulate(EulerBatchItem[] calldata items, address[] calldata deferLiquidityChecks) external reentrantOK {
115
+ doBatchDispatch(items, deferLiquidityChecks, true);
173
116
 
174
- output.liquidities = new IRiskManager.AssetLiquidity[][](queryLiquidity.length);
175
-
176
- for (uint i = 0; i < queryLiquidity.length; ++i) {
177
- output.liquidities[i] = detailedLiquidity(queryLiquidity[i]);
178
- }
117
+ revert("e/batch/simulation-did-not-revert");
179
118
  }
180
119
 
181
120
 
182
121
  // Average liquidity tracking
@@ -330,5 +269,56 @@
330
269
  assembly {
331
270
  return(add(32, result), mload(result))
332
271
  }
333
272
  }
273
+
274
+ function doBatchDispatch(EulerBatchItem[] calldata items, address[] calldata deferLiquidityChecks, bool revertResponse) private {
275
+ address msgSender = unpackTrailingParamMsgSender();
276
+
277
+ for (uint i = 0; i < deferLiquidityChecks.length; ++i) {
278
+ address account = deferLiquidityChecks[i];
279
+
280
+ require(accountLookup[account].deferLiquidityStatus == DEFERLIQUIDITY__NONE, "e/batch/reentrancy");
281
+ accountLookup[account].deferLiquidityStatus = DEFERLIQUIDITY__CLEAN;
282
+ }
283
+
284
+
285
+ EulerBatchItemResponse[] memory response;
286
+ if (revertResponse) response = new EulerBatchItemResponse[](items.length);
287
+
288
+ for (uint i = 0; i < items.length; ++i) {
289
+ EulerBatchItem calldata item = items[i];
290
+ address proxyAddr = item.proxyAddr;
291
+
292
+ uint32 moduleId = trustedSenders[proxyAddr].moduleId;
293
+ address moduleImpl = trustedSenders[proxyAddr].moduleImpl;
294
+
295
+ require(moduleId != 0, "e/batch/unknown-proxy-addr");
296
+ require(moduleId <= MAX_EXTERNAL_MODULEID, "e/batch/call-to-internal-module");
297
+
298
+ if (moduleImpl == address(0)) moduleImpl = moduleLookup[moduleId];
299
+ require(moduleImpl != address(0), "e/batch/module-not-installed");
300
+
301
+ bytes memory inputWrapped = abi.encodePacked(item.data, uint160(msgSender), uint160(proxyAddr));
302
+ (bool success, bytes memory result) = moduleImpl.delegatecall(inputWrapped);
303
+
304
+ if (revertResponse) {
305
+ EulerBatchItemResponse memory r = response[i];
306
+ r.success = success;
307
+ r.result = result;
308
+ } else if (!(success || item.allowError)) {
309
+ revertBytes(result);
310
+ }
311
+ }
312
+
313
+ if (revertResponse) revert BatchDispatchSimulation(response);
314
+
315
+ for (uint i = 0; i < deferLiquidityChecks.length; ++i) {
316
+ address account = deferLiquidityChecks[i];
317
+
318
+ uint8 status = accountLookup[account].deferLiquidityStatus;
319
+ accountLookup[account].deferLiquidityStatus = DEFERLIQUIDITY__NONE;
320
+
321
+ if (status == DEFERLIQUIDITY__DIRTY) checkLiquidity(account);
322
+ }
323
+ }
334
324
  }
contracts/BaseLogic.sol CHANGED
@@ -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
- if (assetCache.totalBalances == 0) return 1e18;
289
- return (assetCache.poolSize + (assetCache.totalBorrows / INTERNAL_DEBT_PRECISION)) * 1e18 / assetCache.totalBalances;
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: 20000}(abi.encodeWithSelector(IERC20.balanceOf.selector, account));
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 {
contracts/Storage.sol CHANGED
@@ -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
  }
contracts/Events.sol CHANGED
@@ -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
  }
contracts/Constants.sol CHANGED
@@ -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;