Enterprise-grade infrastructure that
allows you to launch in weeks, not months
Give your users access to yield on any asset and across any risk profile or compliance requirements.
Plug into global liquidity, offer instant loans, and maintain full ownership over the product experience.
Curate unique strategies to streamline lending at any scale or sophistication.
Transparent financial infrastructure. See it for yourself.
1// SPDX-License-Identifier: BUSL-1.1
2pragma solidity 0.8.19;
3
4import {
5 Id,
6 IMorphoStaticTyping,
7 IMorphoBase,
8 MarketParams,
9 Position,
10 Market,
11 Authorization,
12 Signature
13} from "./interfaces/IMorpho.sol";
14import {
15 IMorphoLiquidateCallback,
16 IMorphoRepayCallback,
17 IMorphoSupplyCallback,
18 IMorphoSupplyCollateralCallback,
19 IMorphoFlashLoanCallback
20} from "./interfaces/IMorphoCallbacks.sol";
21import {IIrm} from "./interfaces/IIrm.sol";
22import {IERC20} from "./interfaces/IERC20.sol";
23import {IOracle} from "./interfaces/IOracle.sol";
24
25import "./libraries/ConstantsLib.sol";
26import {UtilsLib} from "./libraries/UtilsLib.sol";
27import {EventsLib} from "./libraries/EventsLib.sol";
28import {ErrorsLib} from "./libraries/ErrorsLib.sol";
29import {MathLib, WAD} from "./libraries/MathLib.sol";
30import {SharesMathLib} from "./libraries/SharesMathLib.sol";
31import {MarketParamsLib} from "./libraries/MarketParamsLib.sol";
32import {SafeTransferLib} from "./libraries/SafeTransferLib.sol";
33
34/// @title Morpho
35/// @author Morpho Labs
36/// @custom:contact security@morpho.org
37/// @notice The Morpho contract.
38contract Morpho is IMorphoStaticTyping {
39 using MathLib for uint128;
40 using MathLib for uint256;
41 using UtilsLib for uint256;
42 using SharesMathLib for uint256;
43 using SafeTransferLib for IERC20;
44 using MarketParamsLib for MarketParams;
45
46 /* IMMUTABLES */
47
48 /// @inheritdoc IMorphoBase
49 bytes32 public immutable DOMAIN_SEPARATOR;
50
51 /* STORAGE */
52
53 /// @inheritdoc IMorphoBase
54 address public owner;
55 /// @inheritdoc IMorphoBase
56 address public feeRecipient;
57 /// @inheritdoc IMorphoStaticTyping
58 mapping(Id => mapping(address => Position)) public position;
59 /// @inheritdoc IMorphoStaticTyping
60 mapping(Id => Market) public market;
61 /// @inheritdoc IMorphoBase
62 mapping(address => bool) public isIrmEnabled;
63 /// @inheritdoc IMorphoBase
64 mapping(uint256 => bool) public isLltvEnabled;
65 /// @inheritdoc IMorphoBase
66 mapping(address => mapping(address => bool)) public isAuthorized;
67 /// @inheritdoc IMorphoBase
68 mapping(address => uint256) public nonce;
69 /// @inheritdoc IMorphoStaticTyping
70 mapping(Id => MarketParams) public idToMarketParams;
71
72 /* CONSTRUCTOR */
73
74 /// @param newOwner The new owner of the contract.
75 constructor(address newOwner) {
76 require(newOwner != address(0), ErrorsLib.ZERO_ADDRESS);
77
78 DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
79 owner = newOwner;
80
81 emit EventsLib.SetOwner(newOwner);
82 }
83
84 /* MODIFIERS */
85
86 /// @dev Reverts if the caller is not the owner.
87 modifier onlyOwner() {
88 require(msg.sender == owner, ErrorsLib.NOT_OWNER);
89 _;
90 }
91
92 /* ONLY OWNER FUNCTIONS */
93
94 /// @inheritdoc IMorphoBase
95 function setOwner(address newOwner) external onlyOwner {
96 require(newOwner != owner, ErrorsLib.ALREADY_SET);
97
98 owner = newOwner;
99
100 emit EventsLib.SetOwner(newOwner);
101 }
102
103 /// @inheritdoc IMorphoBase
104 function enableIrm(address irm) external onlyOwner {
105 require(!isIrmEnabled[irm], ErrorsLib.ALREADY_SET);
106
107 isIrmEnabled[irm] = true;
108
109 emit EventsLib.EnableIrm(irm);
110 }
111
112 /// @inheritdoc IMorphoBase
113 function enableLltv(uint256 lltv) external onlyOwner {
114 require(!isLltvEnabled[lltv], ErrorsLib.ALREADY_SET);
115 require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED);
116
117 isLltvEnabled[lltv] = true;
118
119 emit EventsLib.EnableLltv(lltv);
120 }
121
122 /// @inheritdoc IMorphoBase
123 function setFee(MarketParams memory marketParams, uint256 newFee) external onlyOwner {
124 Id id = marketParams.id();
125 require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
126 require(newFee != market[id].fee, ErrorsLib.ALREADY_SET);
127 require(newFee <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED);
128
129 // Accrue interest using the previous fee set before changing it.
130 _accrueInterest(marketParams, id);
131
132 // Safe "unchecked" cast.
133 market[id].fee = uint128(newFee);
134
135 emit EventsLib.SetFee(id, newFee);
136 }
137
138 /// @inheritdoc IMorphoBase
139 function setFeeRecipient(address newFeeRecipient) external onlyOwner {
140 require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
141
142 feeRecipient = newFeeRecipient;
143
144 emit EventsLib.SetFeeRecipient(newFeeRecipient);
145 }
146
147 /* MARKET CREATION */
148
149 /// @inheritdoc IMorphoBase
150 function createMarket(MarketParams memory marketParams) external {
151 Id id = marketParams.id();
152 require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
153 require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
154 require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
155
156 // Safe "unchecked" cast.
157 market[id].lastUpdate = uint128(block.timestamp);
158 idToMarketParams[id] = marketParams;
159
160 emit EventsLib.CreateMarket(id, marketParams);
161
162 // Call to initialize the IRM in case it is stateful.
163 if (marketParams.irm != address(0)) IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
164 }
165
166 /* SUPPLY MANAGEMENT */
167
168 /// @inheritdoc IMorphoBase
169 function supply(
170 MarketParams memory marketParams,
171 uint256 assets,
172 uint256 shares,
173 address onBehalf,
174 bytes calldata data
175 ) external returns (uint256, uint256) {
176 Id id = marketParams.id();
177 require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
178 require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
179 require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
180
181 _accrueInterest(marketParams, id);
182
183 if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
184 else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
185
186 position[id][onBehalf].supplyShares += shares;
187 market[id].totalSupplyShares += shares.toUint128();
188 market[id].totalSupplyAssets += assets.toUint128();
189
190 emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
191
192 if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
193
194 IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
195
196 return (assets, shares);
197 }
198
199 /// @inheritdoc IMorphoBase
200 function withdraw(
201 MarketParams memory marketParams,
202 uint256 assets,
203 uint256 shares,
204 address onBehalf,
205 address receiver
206 ) external returns (uint256, uint256) {
207 Id id = marketParams.id();
208 require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
209 require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
210 require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
211 // No need to verify that onBehalf != address(0) thanks to the following authorization check.
212 require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
213
214 _accrueInterest(marketParams, id);
215
216 if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
217 else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
218
219 position[id][onBehalf].supplyShares -= shares;
220 market[id].totalSupplyShares -= shares.toUint128();
221 market[id].totalSupplyAssets -= assets.toUint128();
222
223 require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
224
225 emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
226
227 IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
228
229 return (assets, shares);
230 }
231
232 /* BORROW MANAGEMENT */
233
234 /// @inheritdoc IMorphoBase
235 function borrow(
236 MarketParams memory marketParams,
237 uint256 assets,
238 uint256 shares,
239 address onBehalf,
240 address receiver
241 ) external returns (uint256, uint256) {
242 Id id = marketParams.id();
243 require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
244 require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
245 require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
246 // No need to verify that onBehalf != address(0) thanks to the following authorization check.
247 require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
248
249 _accrueInterest(marketParams, id);
250
251 if (assets > 0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
252 else assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
253
254 position[id][onBehalf].borrowShares += shares.toUint128();
255 market[id].totalBorrowShares += shares.toUint128();
256 market[id].totalBorrowAssets += assets.toUint128();
257
258 require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
259 require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
260
261 emit EventsLib.Borrow(id, msg.sender, onBehalf, receiver, assets, shares);
262
263 IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
264
265 return (assets, shares);
266 }
267
268 /// @inheritdoc IMorphoBase
269 function repay(
270 MarketParams memory marketParams,
271 uint256 assets,
272 uint256 shares,
273 address onBehalf,
274 bytes calldata data
275 ) external returns (uint256, uint256) {
276 Id id = marketParams.id();
277 require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
278 require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
279 require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
280
281 _accrueInterest(marketParams, id);
282
283 if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
284 else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
285
286 position[id][onBehalf].borrowShares -= shares.toUint128();
287 market[id].totalBorrowShares -= shares.toUint128();
288 market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
289
290 // `assets` may be greater than `totalBorrowAssets` by 1.
291 emit EventsLib.Repay(id, msg.sender, onBehalf, assets, shares);
292
293 if (data.length > 0) IMorphoRepayCallback(msg.sender).onMorphoRepay(assets, data);
294
295 IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
296
297 return (assets, shares);
298 }
299
300 /* COLLATERAL MANAGEMENT */
301
302 /// @inheritdoc IMorphoBase
303 function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
304 external
305 {
306 Id id = marketParams.id();
307 require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
308 require(assets != 0, ErrorsLib.ZERO_ASSETS);
309 require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
310
311 // Don't accrue interest because it's not required and it saves gas.
312
313 position[id][onBehalf].collateral += assets.toUint128();
314
315 emit EventsLib.SupplyCollateral(id, msg.sender, onBehalf, assets);
316
317 if (data.length > 0) IMorphoSupplyCollateralCallback(msg.sender).onMorphoSupplyCollateral(assets, data);
318
319 IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
320 }
321
322 /// @inheritdoc IMorphoBase
323 function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
324 external
325 {
326 Id id = marketParams.id();
327 require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
328 require(assets != 0, ErrorsLib.ZERO_ASSETS);
329 require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
330 // No need to verify that onBehalf != address(0) thanks to the following authorization check.
331 require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
332
333 _accrueInterest(marketParams, id);
334
335 position[id][onBehalf].collateral -= assets.toUint128();
336
337 require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
338
339 emit EventsLib.WithdrawCollateral(id, msg.sender, onBehalf, receiver, assets);
340
341 IERC20(marketParams.collateralToken).safeTransfer(receiver, assets);
342 }
343
344 /* LIQUIDATION */
345
346 /// @inheritdoc IMorphoBase
347 function liquidate(
348 MarketParams memory marketParams,
349 address borrower,
350 uint256 seizedAssets,
351 uint256 repaidShares,
352 bytes calldata data
353 ) external returns (uint256, uint256) {
354 Id id = marketParams.id();
355 require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
356 require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
357
358 _accrueInterest(marketParams, id);
359
360 {
361 uint256 collateralPrice = IOracle(marketParams.oracle).price();
362
363 require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
364
365 // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
366 uint256 liquidationIncentiveFactor = UtilsLib.min(
367 MAX_LIQUIDATION_INCENTIVE_FACTOR,
368 WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
369 );
370
371 if (seizedAssets > 0) {
372 uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE);
373
374 repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentiveFactor).toSharesUp(
375 market[id].totalBorrowAssets, market[id].totalBorrowShares
376 );
377 } else {
378 seizedAssets = repaidShares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares)
379 .wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);
380 }
381 }
382 uint256 repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
383
384 position[id][borrower].borrowShares -= repaidShares.toUint128();
385 market[id].totalBorrowShares -= repaidShares.toUint128();
386 market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
387
388 position[id][borrower].collateral -= seizedAssets.toUint128();
389
390 uint256 badDebtShares;
391 uint256 badDebtAssets;
392 if (position[id][borrower].collateral == 0) {
393 badDebtShares = position[id][borrower].borrowShares;
394 badDebtAssets = UtilsLib.min(
395 market[id].totalBorrowAssets,
396 badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares)
397 );
398
399 market[id].totalBorrowAssets -= badDebtAssets.toUint128();
400 market[id].totalSupplyAssets -= badDebtAssets.toUint128();
401 market[id].totalBorrowShares -= badDebtShares.toUint128();
402 position[id][borrower].borrowShares = 0;
403 }
404
405 // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
406 emit EventsLib.Liquidate(
407 id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtAssets, badDebtShares
408 );
409
410 IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
411
412 if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
413
414 IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
415
416 return (seizedAssets, repaidAssets);
417 }
418
419 /* FLASH LOANS */
420
421 /// @inheritdoc IMorphoBase
422 function flashLoan(address token, uint256 assets, bytes calldata data) external {
423 require(assets != 0, ErrorsLib.ZERO_ASSETS);
424
425 emit EventsLib.FlashLoan(msg.sender, token, assets);
426
427 IERC20(token).safeTransfer(msg.sender, assets);
428
429 IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
430
431 IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
432 }
433
434 /* AUTHORIZATION */
435
436 /// @inheritdoc IMorphoBase
437 function setAuthorization(address authorized, bool newIsAuthorized) external {
438 require(newIsAuthorized != isAuthorized[msg.sender][authorized], ErrorsLib.ALREADY_SET);
439
440 isAuthorized[msg.sender][authorized] = newIsAuthorized;
441
442 emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized);
443 }
444
445 /// @inheritdoc IMorphoBase
446 function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
447 /// Do not check whether authorization is already set because the nonce increment is a desired side effect.
448 require(block.timestamp <= authorization.deadline, ErrorsLib.SIGNATURE_EXPIRED);
449 require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE);
450
451 bytes32 hashStruct = keccak256(abi.encode(AUTHORIZATION_TYPEHASH, authorization));
452 bytes32 digest = keccak256(bytes.concat("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
453 address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
454
455 require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
456
457 emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, authorization.nonce);
458
459 isAuthorized[authorization.authorizer][authorization.authorized] = authorization.isAuthorized;
460
461 emit EventsLib.SetAuthorization(
462 msg.sender, authorization.authorizer, authorization.authorized, authorization.isAuthorized
463 );
464 }
465
466 /// @dev Returns whether the sender is authorized to manage `onBehalf`'s positions.
467 function _isSenderAuthorized(address onBehalf) internal view returns (bool) {
468 return msg.sender == onBehalf || isAuthorized[onBehalf][msg.sender];
469 }
470
471 /* INTEREST MANAGEMENT */
472
473 /// @inheritdoc IMorphoBase
474 function accrueInterest(MarketParams memory marketParams) external {
475 Id id = marketParams.id();
476 require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
477
478 _accrueInterest(marketParams, id);
479 }
480
481 /// @dev Accrues interest for the given market `marketParams`.
482 /// @dev Assumes that the inputs `marketParams` and `id` match.
483 function _accrueInterest(MarketParams memory marketParams, Id id) internal {
484 uint256 elapsed = block.timestamp - market[id].lastUpdate;
485 if (elapsed == 0) return;
486
487 if (marketParams.irm != address(0)) {
488 uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
489 uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
490 market[id].totalBorrowAssets += interest.toUint128();
491 market[id].totalSupplyAssets += interest.toUint128();
492
493 uint256 feeShares;
494 if (market[id].fee != 0) {
495 uint256 feeAmount = interest.wMulDown(market[id].fee);
496 // The fee amount is subtracted from the total supply in this calculation to compensate for the fact
497 // that total supply is already increased by the full interest (including the fee amount).
498 feeShares =
499 feeAmount.toSharesDown(market[id].totalSupplyAssets - feeAmount, market[id].totalSupplyShares);
500 position[id][feeRecipient].supplyShares += feeShares;
501 market[id].totalSupplyShares += feeShares.toUint128();
502 }
503
504 emit EventsLib.AccrueInterest(id, borrowRate, interest, feeShares);
505 }
506
507 // Safe "unchecked" cast.
508 market[id].lastUpdate = uint128(block.timestamp);
509 }
510
511 /* HEALTH CHECK */
512
513 /// @dev Returns whether the position of `borrower` in the given market `marketParams` is healthy.
514 /// @dev Assumes that the inputs `marketParams` and `id` match.
515 function _isHealthy(MarketParams memory marketParams, Id id, address borrower) internal view returns (bool) {
516 if (position[id][borrower].borrowShares == 0) return true;
517
518 uint256 collateralPrice = IOracle(marketParams.oracle).price();
519
520 return _isHealthy(marketParams, id, borrower, collateralPrice);
521 }
522
523 /// @dev Returns whether the position of `borrower` in the given market `marketParams` with the given
524 /// `collateralPrice` is healthy.
525 /// @dev Assumes that the inputs `marketParams` and `id` match.
526 /// @dev Rounds in favor of the protocol, so one might not be able to borrow exactly `maxBorrow` but one unit less.
527 function _isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice)
528 internal
529 view
530 returns (bool)
531 {
532 uint256 borrowed = uint256(position[id][borrower].borrowShares).toAssetsUp(
533 market[id].totalBorrowAssets, market[id].totalBorrowShares
534 );
535 uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE)
536 .wMulDown(marketParams.lltv);
537
538 return maxBorrow >= borrowed;
539 }
540
541 /* STORAGE VIEW */
542
543 /// @inheritdoc IMorphoBase
544 function extSloads(bytes32[] calldata slots) external view returns (bytes32[] memory res) {
545 uint256 nSlots = slots.length;
546
547 res = new bytes32[](nSlots);
548
549 for (uint256 i; i < nSlots;) {
550 bytes32 slot = slots[i++];
551
552 assembly ("memory-safe") {
553 mstore(add(res, mul(i, 32)), sload(slot))
554 }
555 }
556 }
557}