@usdu-core/usdu-core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/.env.example +6 -0
  3. package/.prettierrc.json +7 -0
  4. package/LICENSE +674 -0
  5. package/README.md +244 -0
  6. package/contracts/curve/CurveAdapterV1.sol +310 -0
  7. package/contracts/curve/CurveAdapterV1_1.sol +198 -0
  8. package/contracts/curve/helpers/ICurveStableSwapNG.json +566 -0
  9. package/contracts/curve/helpers/ICurveStableSwapNG.sol +177 -0
  10. package/contracts/deploy/VaultDeployer.sol +110 -0
  11. package/contracts/morpho/MorphoAdapterV1.sol +255 -0
  12. package/contracts/morpho/MorphoAdapterV1_1.sol +137 -0
  13. package/contracts/morpho/MorphoAdapterV1_2.sol +126 -0
  14. package/contracts/morpho/helpers/AggregatorV3Interface.sol +24 -0
  15. package/contracts/morpho/helpers/ChainlinkDataFeedLib.sol +36 -0
  16. package/contracts/morpho/helpers/ConstantsLib.sol +20 -0
  17. package/contracts/morpho/helpers/ErrorsLib.sol +17 -0
  18. package/contracts/morpho/helpers/IERC4626.sol +6 -0
  19. package/contracts/morpho/helpers/IMetaMorphoV1_1.sol +229 -0
  20. package/contracts/morpho/helpers/IMetaMorphoV1_1Factory.sol +32 -0
  21. package/contracts/morpho/helpers/IMorpho.sol +361 -0
  22. package/contracts/morpho/helpers/IMorphoCallbacks.sol +52 -0
  23. package/contracts/morpho/helpers/IMorphoChainlinkOracleV2.sol +39 -0
  24. package/contracts/morpho/helpers/IMorphoChainlinkOracleV2Factory.sol +56 -0
  25. package/contracts/morpho/helpers/IOracle.sol +15 -0
  26. package/contracts/morpho/helpers/MarketParamsLib.sol +21 -0
  27. package/contracts/morpho/helpers/MathLib.sol +45 -0
  28. package/contracts/morpho/helpers/Morpho.sol.bak +517 -0
  29. package/contracts/morpho/helpers/MorphoChainlinkOracleV2.sol +157 -0
  30. package/contracts/morpho/helpers/PendingLib.sol +47 -0
  31. package/contracts/morpho/helpers/SharesMathLib.sol +45 -0
  32. package/contracts/morpho/helpers/VaultLib.sol +18 -0
  33. package/contracts/reward/RewardDistributionV1.sol +110 -0
  34. package/contracts/reward/RewardRouterV0.sol +63 -0
  35. package/contracts/reward/Rewards.example.json +72 -0
  36. package/contracts/stablecoin/IStablecoin.sol +211 -0
  37. package/contracts/stablecoin/IStablecoinMetadata.sol +27 -0
  38. package/contracts/stablecoin/IStablecoinModifier.sol +52 -0
  39. package/contracts/stablecoin/Stablecoin.sol +376 -0
  40. package/contracts/stablecoin/libraries/ConstantsLib.sol +13 -0
  41. package/contracts/stablecoin/libraries/ErrorsLib.sol +45 -0
  42. package/contracts/stablecoin/libraries/EventsLib.sol +74 -0
  43. package/contracts/stablecoin/libraries/PendingLib.sol +38 -0
  44. package/contracts/vault/VaultAdapterRecoverV1.sol +29 -0
  45. package/contracts/vault/VaultAdapterV1.sol +126 -0
  46. package/dist/index.d.mts +16154 -0
  47. package/dist/index.d.ts +16154 -0
  48. package/dist/index.js +21134 -0
  49. package/dist/index.mjs +21061 -0
  50. package/docs/CoreVault: Integration of new Markets.md +197 -0
  51. package/docs/CoreVault: SupplyQueue.md +11 -0
  52. package/docs/Markets USDC Vault.md +35 -0
  53. package/docs/Markets USDU Vault.md +89 -0
  54. package/docs/Markets WETH Vault.md +35 -0
  55. package/docs/Overview.drawio +117 -0
  56. package/exports/abis/curve/CurveAdapterV1.ts +599 -0
  57. package/exports/abis/curve/CurveAdapterV1_1.ts +609 -0
  58. package/exports/abis/curve/CurveAdapterV1_2.ts +721 -0
  59. package/exports/abis/curve/helper/ICurveStableSwapNG.ts +1589 -0
  60. package/exports/abis/morpho/MorphoAdapterV1.ts +516 -0
  61. package/exports/abis/morpho/MorphoAdapterV1_1.ts +489 -0
  62. package/exports/abis/morpho/MorphoAdapterV1_2.ts +459 -0
  63. package/exports/abis/morpho/helper/AggregatorV3Interface.ts +113 -0
  64. package/exports/abis/morpho/helper/IMetaMorphoV1_1.ts +1483 -0
  65. package/exports/abis/morpho/helper/IMetaMorphoV1_1Base.ts +607 -0
  66. package/exports/abis/morpho/helper/IMetaMorphoV1_1StaticTyping.ts +696 -0
  67. package/exports/abis/morpho/helper/IMorpho.ts +1024 -0
  68. package/exports/abis/morpho/helper/IMorphoBase.ts +886 -0
  69. package/exports/abis/morpho/helper/IMorphoChainlinkOracleV2.ts +132 -0
  70. package/exports/abis/morpho/helper/IMorphoChainlinkOracleV2Factory.ts +109 -0
  71. package/exports/abis/morpho/helper/IMorphoFlashLoanCallback.ts +20 -0
  72. package/exports/abis/morpho/helper/IMorphoLiquidateCallback.ts +20 -0
  73. package/exports/abis/morpho/helper/IMorphoRepayCallback.ts +20 -0
  74. package/exports/abis/morpho/helper/IMorphoStaticTyping.ts +1003 -0
  75. package/exports/abis/morpho/helper/IMorphoSupplyCallback.ts +20 -0
  76. package/exports/abis/morpho/helper/IMorphoSupplyCollateralCallback.ts +20 -0
  77. package/exports/abis/morpho/helper/IMulticall.ts +21 -0
  78. package/exports/abis/morpho/helper/IOracle.ts +15 -0
  79. package/exports/abis/morpho/helper/IOwnable.ts +55 -0
  80. package/exports/abis/morpho/helper/MorphoChainlinkOracleV2.ts +188 -0
  81. package/exports/abis/openzeppelin/ERC20.ts +310 -0
  82. package/exports/abis/openzeppelin/ERC20Permit.ts +520 -0
  83. package/exports/abis/openzeppelin/IERC20.ts +185 -0
  84. package/exports/abis/openzeppelin/IERC20Metadata.ts +224 -0
  85. package/exports/abis/openzeppelin/IERC20Permit.ts +77 -0
  86. package/exports/abis/openzeppelin/IERC4626.ts +614 -0
  87. package/exports/abis/reward/RewardDistributionV1.ts +246 -0
  88. package/exports/abis/stablecoin/ErrorsLib.ts +114 -0
  89. package/exports/abis/stablecoin/EventsLib.ts +372 -0
  90. package/exports/abis/stablecoin/IStablecoin.ts +642 -0
  91. package/exports/abis/stablecoin/IStablecoinMetadata.ts +856 -0
  92. package/exports/abis/stablecoin/IStablecoinModifier.ts +15 -0
  93. package/exports/abis/stablecoin/Stablecoin.ts +1922 -0
  94. package/exports/abis/termmax/ITermMaxVault.ts +2335 -0
  95. package/exports/abis/vault/VaultAdapterRecoverV1.ts +490 -0
  96. package/exports/abis/vault/VaultAdapterV1.ts +459 -0
  97. package/exports/address.config.ts +113 -0
  98. package/exports/address.types.ts +130 -0
  99. package/exports/index.ts +61 -0
  100. package/hardhat.config.ts +231 -0
  101. package/helper/store.args.ts +17 -0
  102. package/helper/wallet.info.ts +3 -0
  103. package/helper/wallet.ts +41 -0
  104. package/install-macos.sh +46 -0
  105. package/package.json +73 -0
  106. package/tsconfig.json +15 -0
  107. package/tsup.config.ts +10 -0
@@ -0,0 +1,517 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity 0.8.19;
3
+
4
+ import {Id, IMorphoStaticTyping, IMorphoBase, MarketParams, Position, Market, Authorization, Signature} from './interfaces/IMorpho.sol';
5
+ import {IMorphoLiquidateCallback, IMorphoRepayCallback, IMorphoSupplyCallback, IMorphoSupplyCollateralCallback, IMorphoFlashLoanCallback} from './interfaces/IMorphoCallbacks.sol';
6
+ import {IIrm} from './interfaces/IIrm.sol';
7
+ import {IERC20} from './interfaces/IERC20.sol';
8
+ import {IOracle} from './interfaces/IOracle.sol';
9
+
10
+ import './libraries/ConstantsLib.sol';
11
+ import {UtilsLib} from './libraries/UtilsLib.sol';
12
+ import {EventsLib} from './libraries/EventsLib.sol';
13
+ import {ErrorsLib} from './libraries/ErrorsLib.sol';
14
+ import {MathLib, WAD} from './libraries/MathLib.sol';
15
+ import {SharesMathLib} from './libraries/SharesMathLib.sol';
16
+ import {MarketParamsLib} from './libraries/MarketParamsLib.sol';
17
+ import {SafeTransferLib} from './libraries/SafeTransferLib.sol';
18
+
19
+ /// @title Morpho
20
+ /// @author Morpho Labs
21
+ /// @custom:contact security@morpho.org
22
+ /// @notice The Morpho contract.
23
+ contract Morpho is IMorphoStaticTyping {
24
+ using MathLib for uint128;
25
+ using MathLib for uint256;
26
+ using UtilsLib for uint256;
27
+ using SharesMathLib for uint256;
28
+ using SafeTransferLib for IERC20;
29
+ using MarketParamsLib for MarketParams;
30
+
31
+ /* IMMUTABLES */
32
+
33
+ /// @inheritdoc IMorphoBase
34
+ bytes32 public immutable DOMAIN_SEPARATOR;
35
+
36
+ /* STORAGE */
37
+
38
+ /// @inheritdoc IMorphoBase
39
+ address public owner;
40
+ /// @inheritdoc IMorphoBase
41
+ address public feeRecipient;
42
+ /// @inheritdoc IMorphoStaticTyping
43
+ mapping(Id => mapping(address => Position)) public position;
44
+ /// @inheritdoc IMorphoStaticTyping
45
+ mapping(Id => Market) public market;
46
+ /// @inheritdoc IMorphoBase
47
+ mapping(address => bool) public isIrmEnabled;
48
+ /// @inheritdoc IMorphoBase
49
+ mapping(uint256 => bool) public isLltvEnabled;
50
+ /// @inheritdoc IMorphoBase
51
+ mapping(address => mapping(address => bool)) public isAuthorized;
52
+ /// @inheritdoc IMorphoBase
53
+ mapping(address => uint256) public nonce;
54
+ /// @inheritdoc IMorphoStaticTyping
55
+ mapping(Id => MarketParams) public idToMarketParams;
56
+
57
+ /* CONSTRUCTOR */
58
+
59
+ /// @param newOwner The new owner of the contract.
60
+ constructor(address newOwner) {
61
+ require(newOwner != address(0), ErrorsLib.ZERO_ADDRESS);
62
+
63
+ DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
64
+ owner = newOwner;
65
+
66
+ emit EventsLib.SetOwner(newOwner);
67
+ }
68
+
69
+ /* MODIFIERS */
70
+
71
+ /// @dev Reverts if the caller is not the owner.
72
+ modifier onlyOwner() {
73
+ require(msg.sender == owner, ErrorsLib.NOT_OWNER);
74
+ _;
75
+ }
76
+
77
+ /* ONLY OWNER FUNCTIONS */
78
+
79
+ /// @inheritdoc IMorphoBase
80
+ function setOwner(address newOwner) external onlyOwner {
81
+ require(newOwner != owner, ErrorsLib.ALREADY_SET);
82
+
83
+ owner = newOwner;
84
+
85
+ emit EventsLib.SetOwner(newOwner);
86
+ }
87
+
88
+ /// @inheritdoc IMorphoBase
89
+ function enableIrm(address irm) external onlyOwner {
90
+ require(!isIrmEnabled[irm], ErrorsLib.ALREADY_SET);
91
+
92
+ isIrmEnabled[irm] = true;
93
+
94
+ emit EventsLib.EnableIrm(irm);
95
+ }
96
+
97
+ /// @inheritdoc IMorphoBase
98
+ function enableLltv(uint256 lltv) external onlyOwner {
99
+ require(!isLltvEnabled[lltv], ErrorsLib.ALREADY_SET);
100
+ require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED);
101
+
102
+ isLltvEnabled[lltv] = true;
103
+
104
+ emit EventsLib.EnableLltv(lltv);
105
+ }
106
+
107
+ /// @inheritdoc IMorphoBase
108
+ function setFee(MarketParams memory marketParams, uint256 newFee) external onlyOwner {
109
+ Id id = marketParams.id();
110
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
111
+ require(newFee != market[id].fee, ErrorsLib.ALREADY_SET);
112
+ require(newFee <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED);
113
+
114
+ // Accrue interest using the previous fee set before changing it.
115
+ _accrueInterest(marketParams, id);
116
+
117
+ // Safe "unchecked" cast.
118
+ market[id].fee = uint128(newFee);
119
+
120
+ emit EventsLib.SetFee(id, newFee);
121
+ }
122
+
123
+ /// @inheritdoc IMorphoBase
124
+ function setFeeRecipient(address newFeeRecipient) external onlyOwner {
125
+ require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
126
+
127
+ feeRecipient = newFeeRecipient;
128
+
129
+ emit EventsLib.SetFeeRecipient(newFeeRecipient);
130
+ }
131
+
132
+ /* MARKET CREATION */
133
+
134
+ /// @inheritdoc IMorphoBase
135
+ function createMarket(MarketParams memory marketParams) external {
136
+ Id id = marketParams.id();
137
+ require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
138
+ require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
139
+ require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
140
+
141
+ // Safe "unchecked" cast.
142
+ market[id].lastUpdate = uint128(block.timestamp);
143
+ idToMarketParams[id] = marketParams;
144
+
145
+ emit EventsLib.CreateMarket(id, marketParams);
146
+
147
+ // Call to initialize the IRM in case it is stateful.
148
+ if (marketParams.irm != address(0)) IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
149
+ }
150
+
151
+ /* SUPPLY MANAGEMENT */
152
+
153
+ /// @inheritdoc IMorphoBase
154
+ function supply(
155
+ MarketParams memory marketParams,
156
+ uint256 assets,
157
+ uint256 shares,
158
+ address onBehalf,
159
+ bytes calldata data
160
+ ) external returns (uint256, uint256) {
161
+ Id id = marketParams.id();
162
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
163
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
164
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
165
+
166
+ _accrueInterest(marketParams, id);
167
+
168
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
169
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
170
+
171
+ position[id][onBehalf].supplyShares += shares;
172
+ market[id].totalSupplyShares += shares.toUint128();
173
+ market[id].totalSupplyAssets += assets.toUint128();
174
+
175
+ emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
176
+
177
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
178
+
179
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
180
+
181
+ return (assets, shares);
182
+ }
183
+
184
+ /// @inheritdoc IMorphoBase
185
+ function withdraw(
186
+ MarketParams memory marketParams,
187
+ uint256 assets,
188
+ uint256 shares,
189
+ address onBehalf,
190
+ address receiver
191
+ ) external returns (uint256, uint256) {
192
+ Id id = marketParams.id();
193
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
194
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
195
+ require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
196
+ // No need to verify that onBehalf != address(0) thanks to the following authorization check.
197
+ require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
198
+
199
+ _accrueInterest(marketParams, id);
200
+
201
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
202
+ else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
203
+
204
+ position[id][onBehalf].supplyShares -= shares;
205
+ market[id].totalSupplyShares -= shares.toUint128();
206
+ market[id].totalSupplyAssets -= assets.toUint128();
207
+
208
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
209
+
210
+ emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
211
+
212
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
213
+
214
+ return (assets, shares);
215
+ }
216
+
217
+ /* BORROW MANAGEMENT */
218
+
219
+ /// @inheritdoc IMorphoBase
220
+ function borrow(MarketParams memory marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) external returns (uint256, uint256) {
221
+ Id id = marketParams.id();
222
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
223
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
224
+ require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
225
+ // No need to verify that onBehalf != address(0) thanks to the following authorization check.
226
+ require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
227
+
228
+ _accrueInterest(marketParams, id);
229
+
230
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
231
+ else assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
232
+
233
+ position[id][onBehalf].borrowShares += shares.toUint128();
234
+ market[id].totalBorrowShares += shares.toUint128();
235
+ market[id].totalBorrowAssets += assets.toUint128();
236
+
237
+ require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
238
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
239
+
240
+ emit EventsLib.Borrow(id, msg.sender, onBehalf, receiver, assets, shares);
241
+
242
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
243
+
244
+ return (assets, shares);
245
+ }
246
+
247
+ /// @inheritdoc IMorphoBase
248
+ function repay(
249
+ MarketParams memory marketParams,
250
+ uint256 assets,
251
+ uint256 shares,
252
+ address onBehalf,
253
+ bytes calldata data
254
+ ) external returns (uint256, uint256) {
255
+ Id id = marketParams.id();
256
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
257
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
258
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
259
+
260
+ _accrueInterest(marketParams, id);
261
+
262
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
263
+ else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
264
+
265
+ position[id][onBehalf].borrowShares -= shares.toUint128();
266
+ market[id].totalBorrowShares -= shares.toUint128();
267
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
268
+
269
+ // `assets` may be greater than `totalBorrowAssets` by 1.
270
+ emit EventsLib.Repay(id, msg.sender, onBehalf, assets, shares);
271
+
272
+ if (data.length > 0) IMorphoRepayCallback(msg.sender).onMorphoRepay(assets, data);
273
+
274
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
275
+
276
+ return (assets, shares);
277
+ }
278
+
279
+ /* COLLATERAL MANAGEMENT */
280
+
281
+ /// @inheritdoc IMorphoBase
282
+ function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data) external {
283
+ Id id = marketParams.id();
284
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
285
+ require(assets != 0, ErrorsLib.ZERO_ASSETS);
286
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
287
+
288
+ // Don't accrue interest because it's not required and it saves gas.
289
+
290
+ position[id][onBehalf].collateral += assets.toUint128();
291
+
292
+ emit EventsLib.SupplyCollateral(id, msg.sender, onBehalf, assets);
293
+
294
+ if (data.length > 0) IMorphoSupplyCollateralCallback(msg.sender).onMorphoSupplyCollateral(assets, data);
295
+
296
+ IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
297
+ }
298
+
299
+ /// @inheritdoc IMorphoBase
300
+ function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver) external {
301
+ Id id = marketParams.id();
302
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
303
+ require(assets != 0, ErrorsLib.ZERO_ASSETS);
304
+ require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
305
+ // No need to verify that onBehalf != address(0) thanks to the following authorization check.
306
+ require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
307
+
308
+ _accrueInterest(marketParams, id);
309
+
310
+ position[id][onBehalf].collateral -= assets.toUint128();
311
+
312
+ require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
313
+
314
+ emit EventsLib.WithdrawCollateral(id, msg.sender, onBehalf, receiver, assets);
315
+
316
+ IERC20(marketParams.collateralToken).safeTransfer(receiver, assets);
317
+ }
318
+
319
+ /* LIQUIDATION */
320
+
321
+ /// @inheritdoc IMorphoBase
322
+ function liquidate(
323
+ MarketParams memory marketParams,
324
+ address borrower,
325
+ uint256 seizedAssets,
326
+ uint256 repaidShares,
327
+ bytes calldata data
328
+ ) external returns (uint256, uint256) {
329
+ Id id = marketParams.id();
330
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
331
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
332
+
333
+ _accrueInterest(marketParams, id);
334
+
335
+ {
336
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
337
+
338
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
339
+
340
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
341
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
342
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
343
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
344
+ );
345
+
346
+ if (seizedAssets > 0) {
347
+ uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE);
348
+
349
+ repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentiveFactor).toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
350
+ } else {
351
+ seizedAssets = repaidShares
352
+ .toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares)
353
+ .wMulDown(liquidationIncentiveFactor)
354
+ .mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);
355
+ }
356
+ }
357
+ uint256 repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
358
+
359
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
360
+ market[id].totalBorrowShares -= repaidShares.toUint128();
361
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
362
+
363
+ position[id][borrower].collateral -= seizedAssets.toUint128();
364
+
365
+ uint256 badDebtShares;
366
+ uint256 badDebtAssets;
367
+ if (position[id][borrower].collateral == 0) {
368
+ badDebtShares = position[id][borrower].borrowShares;
369
+ badDebtAssets = UtilsLib.min(market[id].totalBorrowAssets, badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares));
370
+
371
+ market[id].totalBorrowAssets -= badDebtAssets.toUint128();
372
+ market[id].totalSupplyAssets -= badDebtAssets.toUint128();
373
+ market[id].totalBorrowShares -= badDebtShares.toUint128();
374
+ position[id][borrower].borrowShares = 0;
375
+ }
376
+
377
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
378
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtAssets, badDebtShares);
379
+
380
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
381
+
382
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
383
+
384
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
385
+
386
+ return (seizedAssets, repaidAssets);
387
+ }
388
+
389
+ /* FLASH LOANS */
390
+
391
+ /// @inheritdoc IMorphoBase
392
+ function flashLoan(address token, uint256 assets, bytes calldata data) external {
393
+ require(assets != 0, ErrorsLib.ZERO_ASSETS);
394
+
395
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
396
+
397
+ IERC20(token).safeTransfer(msg.sender, assets);
398
+
399
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
400
+
401
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
402
+ }
403
+
404
+ /* AUTHORIZATION */
405
+
406
+ /// @inheritdoc IMorphoBase
407
+ function setAuthorization(address authorized, bool newIsAuthorized) external {
408
+ require(newIsAuthorized != isAuthorized[msg.sender][authorized], ErrorsLib.ALREADY_SET);
409
+
410
+ isAuthorized[msg.sender][authorized] = newIsAuthorized;
411
+
412
+ emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized);
413
+ }
414
+
415
+ /// @inheritdoc IMorphoBase
416
+ function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
417
+ /// Do not check whether authorization is already set because the nonce increment is a desired side effect.
418
+ require(block.timestamp <= authorization.deadline, ErrorsLib.SIGNATURE_EXPIRED);
419
+ require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE);
420
+
421
+ bytes32 hashStruct = keccak256(abi.encode(AUTHORIZATION_TYPEHASH, authorization));
422
+ bytes32 digest = keccak256(bytes.concat('\x19\x01', DOMAIN_SEPARATOR, hashStruct));
423
+ address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
424
+
425
+ require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
426
+
427
+ emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, authorization.nonce);
428
+
429
+ isAuthorized[authorization.authorizer][authorization.authorized] = authorization.isAuthorized;
430
+
431
+ emit EventsLib.SetAuthorization(msg.sender, authorization.authorizer, authorization.authorized, authorization.isAuthorized);
432
+ }
433
+
434
+ /// @dev Returns whether the sender is authorized to manage `onBehalf`'s positions.
435
+ function _isSenderAuthorized(address onBehalf) internal view returns (bool) {
436
+ return msg.sender == onBehalf || isAuthorized[onBehalf][msg.sender];
437
+ }
438
+
439
+ /* INTEREST MANAGEMENT */
440
+
441
+ /// @inheritdoc IMorphoBase
442
+ function accrueInterest(MarketParams memory marketParams) external {
443
+ Id id = marketParams.id();
444
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
445
+
446
+ _accrueInterest(marketParams, id);
447
+ }
448
+
449
+ /// @dev Accrues interest for the given market `marketParams`.
450
+ /// @dev Assumes that the inputs `marketParams` and `id` match.
451
+ function _accrueInterest(MarketParams memory marketParams, Id id) internal {
452
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
453
+ if (elapsed == 0) return;
454
+
455
+ if (marketParams.irm != address(0)) {
456
+ uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
457
+ uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
458
+ market[id].totalBorrowAssets += interest.toUint128();
459
+ market[id].totalSupplyAssets += interest.toUint128();
460
+
461
+ uint256 feeShares;
462
+ if (market[id].fee != 0) {
463
+ uint256 feeAmount = interest.wMulDown(market[id].fee);
464
+ // The fee amount is subtracted from the total supply in this calculation to compensate for the fact
465
+ // that total supply is already increased by the full interest (including the fee amount).
466
+ feeShares = feeAmount.toSharesDown(market[id].totalSupplyAssets - feeAmount, market[id].totalSupplyShares);
467
+ position[id][feeRecipient].supplyShares += feeShares;
468
+ market[id].totalSupplyShares += feeShares.toUint128();
469
+ }
470
+
471
+ emit EventsLib.AccrueInterest(id, borrowRate, interest, feeShares);
472
+ }
473
+
474
+ // Safe "unchecked" cast.
475
+ market[id].lastUpdate = uint128(block.timestamp);
476
+ }
477
+
478
+ /* HEALTH CHECK */
479
+
480
+ /// @dev Returns whether the position of `borrower` in the given market `marketParams` is healthy.
481
+ /// @dev Assumes that the inputs `marketParams` and `id` match.
482
+ function _isHealthy(MarketParams memory marketParams, Id id, address borrower) internal view returns (bool) {
483
+ if (position[id][borrower].borrowShares == 0) return true;
484
+
485
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
486
+
487
+ return _isHealthy(marketParams, id, borrower, collateralPrice);
488
+ }
489
+
490
+ /// @dev Returns whether the position of `borrower` in the given market `marketParams` with the given
491
+ /// `collateralPrice` is healthy.
492
+ /// @dev Assumes that the inputs `marketParams` and `id` match.
493
+ /// @dev Rounds in favor of the protocol, so one might not be able to borrow exactly `maxBorrow` but one unit less.
494
+ function _isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice) internal view returns (bool) {
495
+ uint256 borrowed = uint256(position[id][borrower].borrowShares).toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
496
+ uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(marketParams.lltv);
497
+
498
+ return maxBorrow >= borrowed;
499
+ }
500
+
501
+ /* STORAGE VIEW */
502
+
503
+ /// @inheritdoc IMorphoBase
504
+ function extSloads(bytes32[] calldata slots) external view returns (bytes32[] memory res) {
505
+ uint256 nSlots = slots.length;
506
+
507
+ res = new bytes32[](nSlots);
508
+
509
+ for (uint256 i; i < nSlots; ) {
510
+ bytes32 slot = slots[i++];
511
+
512
+ assembly ('memory-safe') {
513
+ mstore(add(res, mul(i, 32)), sload(slot))
514
+ }
515
+ }
516
+ }
517
+ }
@@ -0,0 +1,157 @@
1
+ // SPDX-License-Identifier: GPL-2.0-or-later
2
+ pragma solidity 0.8.20;
3
+
4
+ import {IOracle} from './IOracle.sol';
5
+ import {IMorphoChainlinkOracleV2} from './IMorphoChainlinkOracleV2.sol';
6
+
7
+ import {ErrorsLib} from './ErrorsLib.sol';
8
+ import {IERC4626, VaultLib} from './VaultLib.sol';
9
+ import {Math} from '@openzeppelin/contracts/utils/math/Math.sol';
10
+ import {AggregatorV3Interface, ChainlinkDataFeedLib} from './ChainlinkDataFeedLib.sol';
11
+
12
+ /// @title MorphoChainlinkOracleV2
13
+ /// @author Morpho Labs
14
+ /// @custom:contact security@morpho.org
15
+ /// @notice Morpho Blue oracle using Chainlink-compliant feeds.
16
+ contract MorphoChainlinkOracleV2 is IMorphoChainlinkOracleV2 {
17
+ using Math for uint256;
18
+ using VaultLib for IERC4626;
19
+ using ChainlinkDataFeedLib for AggregatorV3Interface;
20
+
21
+ /* IMMUTABLES */
22
+
23
+ /// @inheritdoc IMorphoChainlinkOracleV2
24
+ IERC4626 public immutable BASE_VAULT;
25
+
26
+ /// @inheritdoc IMorphoChainlinkOracleV2
27
+ uint256 public immutable BASE_VAULT_CONVERSION_SAMPLE;
28
+
29
+ /// @inheritdoc IMorphoChainlinkOracleV2
30
+ IERC4626 public immutable QUOTE_VAULT;
31
+
32
+ /// @inheritdoc IMorphoChainlinkOracleV2
33
+ uint256 public immutable QUOTE_VAULT_CONVERSION_SAMPLE;
34
+
35
+ /// @inheritdoc IMorphoChainlinkOracleV2
36
+ AggregatorV3Interface public immutable BASE_FEED_1;
37
+
38
+ /// @inheritdoc IMorphoChainlinkOracleV2
39
+ AggregatorV3Interface public immutable BASE_FEED_2;
40
+
41
+ /// @inheritdoc IMorphoChainlinkOracleV2
42
+ AggregatorV3Interface public immutable QUOTE_FEED_1;
43
+
44
+ /// @inheritdoc IMorphoChainlinkOracleV2
45
+ AggregatorV3Interface public immutable QUOTE_FEED_2;
46
+
47
+ /// @inheritdoc IMorphoChainlinkOracleV2
48
+ uint256 public immutable SCALE_FACTOR;
49
+
50
+ /* CONSTRUCTOR */
51
+
52
+ /// @dev Here is the list of assumptions that guarantees the oracle behaves as expected:
53
+ /// - The vaults, if set, are ERC4626-compliant.
54
+ /// - The feeds, if set, are Chainlink-interface-compliant.
55
+ /// - Decimals passed as argument are correct.
56
+ /// - The base vaults's sample shares quoted as assets and the base feed prices don't overflow when multiplied.
57
+ /// - The quote vault's sample shares quoted as assets and the quote feed prices don't overflow when multiplied.
58
+ /// @param baseVault Base vault. Pass address zero to omit this parameter.
59
+ /// @param baseVaultConversionSample The sample amount of base vault shares used to convert to underlying.
60
+ /// Pass 1 if the base asset is not a vault. Should be chosen such that converting `baseVaultConversionSample` to
61
+ /// assets has enough precision.
62
+ /// @param baseFeed1 First base feed. Pass address zero if the price = 1.
63
+ /// @param baseFeed2 Second base feed. Pass address zero if the price = 1.
64
+ /// @param baseTokenDecimals Base token decimals.
65
+ /// @param quoteVault Quote vault. Pass address zero to omit this parameter.
66
+ /// @param quoteVaultConversionSample The sample amount of quote vault shares used to convert to underlying.
67
+ /// Pass 1 if the quote asset is not a vault. Should be chosen such that converting `quoteVaultConversionSample` to
68
+ /// assets has enough precision.
69
+ /// @param quoteFeed1 First quote feed. Pass address zero if the price = 1.
70
+ /// @param quoteFeed2 Second quote feed. Pass address zero if the price = 1.
71
+ /// @param quoteTokenDecimals Quote token decimals.
72
+ /// @dev The base asset should be the collateral token and the quote asset the loan token.
73
+ constructor(
74
+ IERC4626 baseVault,
75
+ uint256 baseVaultConversionSample,
76
+ AggregatorV3Interface baseFeed1,
77
+ AggregatorV3Interface baseFeed2,
78
+ uint256 baseTokenDecimals,
79
+ IERC4626 quoteVault,
80
+ uint256 quoteVaultConversionSample,
81
+ AggregatorV3Interface quoteFeed1,
82
+ AggregatorV3Interface quoteFeed2,
83
+ uint256 quoteTokenDecimals
84
+ ) {
85
+ // The ERC4626 vault parameters are used to price their respective conversion samples of their respective
86
+ // shares, so it requires multiplying by `QUOTE_VAULT_CONVERSION_SAMPLE` and dividing
87
+ // by `BASE_VAULT_CONVERSION_SAMPLE` in the `SCALE_FACTOR` definition.
88
+ // Verify that vault = address(0) => vaultConversionSample = 1 for each vault.
89
+ require(
90
+ address(baseVault) != address(0) || baseVaultConversionSample == 1,
91
+ ErrorsLib.VAULT_CONVERSION_SAMPLE_IS_NOT_ONE
92
+ );
93
+ require(
94
+ address(quoteVault) != address(0) || quoteVaultConversionSample == 1,
95
+ ErrorsLib.VAULT_CONVERSION_SAMPLE_IS_NOT_ONE
96
+ );
97
+ require(baseVaultConversionSample != 0, ErrorsLib.VAULT_CONVERSION_SAMPLE_IS_ZERO);
98
+ require(quoteVaultConversionSample != 0, ErrorsLib.VAULT_CONVERSION_SAMPLE_IS_ZERO);
99
+
100
+ BASE_VAULT = baseVault;
101
+ BASE_VAULT_CONVERSION_SAMPLE = baseVaultConversionSample;
102
+ QUOTE_VAULT = quoteVault;
103
+ QUOTE_VAULT_CONVERSION_SAMPLE = quoteVaultConversionSample;
104
+ BASE_FEED_1 = baseFeed1;
105
+ BASE_FEED_2 = baseFeed2;
106
+ QUOTE_FEED_1 = quoteFeed1;
107
+ QUOTE_FEED_2 = quoteFeed2;
108
+
109
+ // In the following comment, we explain the general case (where we assume that no feed is the address zero)
110
+ // how to scale the output price as Morpho Blue expects, given the input feed prices.
111
+ // Similar explanations would hold in the case where some of the feeds are the address zero.
112
+
113
+ // Let B1, B2, Q1, Q2, C be 5 assets, each respectively having dB1, dB2, dQ1, dQ2, dC decimals.
114
+ // Let pB1 and pB2 be the base prices, and pQ1 and pQ2 the quote prices, so that:
115
+ // - pB1 is the quantity of 1e(dB2) assets B2 that can be exchanged for 1e(dB1) assets B1.
116
+ // - pB2 is the quantity of 1e(dC) assets C that can be exchanged for 1e(dB2) assets B2.
117
+ // - pQ1 is the quantity of 1e(dQ2) assets Q2 that can be exchanged for 1e(dQ1) assets Q1.
118
+ // - pQ2 is the quantity of 1e(dC) assets C that can be exchanged for 1e(dQ2) assets B2.
119
+
120
+ // Morpho Blue expects `price()` to be the quantity of 1 asset Q1 that can be exchanged for 1 asset B1,
121
+ // scaled by 1e36:
122
+ // 1e36 * (pB1 * 1e(dB2 - dB1)) * (pB2 * 1e(dC - dB2)) / ((pQ1 * 1e(dQ2 - dQ1)) * (pQ2 * 1e(dC - dQ2)))
123
+ // = 1e36 * (pB1 * 1e(-dB1) * pB2) / (pQ1 * 1e(-dQ1) * pQ2)
124
+
125
+ // Let fpB1, fpB2, fpQ1, fpQ2 be the feed precision of the respective prices pB1, pB2, pQ1, pQ2.
126
+ // Feeds return pB1 * 1e(fpB1), pB2 * 1e(fpB2), pQ1 * 1e(fpQ1) and pQ2 * 1e(fpQ2).
127
+
128
+ // Based on the implementation of `price()` below, the value of `SCALE_FACTOR` should thus satisfy:
129
+ // (pB1 * 1e(fpB1)) * (pB2 * 1e(fpB2)) * SCALE_FACTOR / ((pQ1 * 1e(fpQ1)) * (pQ2 * 1e(fpQ2)))
130
+ // = 1e36 * (pB1 * 1e(-dB1) * pB2) / (pQ1 * 1e(-dQ1) * pQ2)
131
+
132
+ // So SCALE_FACTOR = 1e36 * 1e(-dB1) * 1e(dQ1) * 1e(-fpB1) * 1e(-fpB2) * 1e(fpQ1) * 1e(fpQ2)
133
+ // = 1e(36 + dQ1 + fpQ1 + fpQ2 - dB1 - fpB1 - fpB2)
134
+ SCALE_FACTOR =
135
+ (10 **
136
+ (36 +
137
+ quoteTokenDecimals +
138
+ quoteFeed1.getDecimals() +
139
+ quoteFeed2.getDecimals() -
140
+ baseTokenDecimals -
141
+ baseFeed1.getDecimals() -
142
+ baseFeed2.getDecimals()) *
143
+ quoteVaultConversionSample) /
144
+ baseVaultConversionSample;
145
+ }
146
+
147
+ /* PRICE */
148
+
149
+ /// @inheritdoc IOracle
150
+ function price() external view returns (uint256) {
151
+ return
152
+ SCALE_FACTOR.mulDiv(
153
+ BASE_VAULT.getAssets(BASE_VAULT_CONVERSION_SAMPLE) * BASE_FEED_1.getPrice() * BASE_FEED_2.getPrice(),
154
+ QUOTE_VAULT.getAssets(QUOTE_VAULT_CONVERSION_SAMPLE) * QUOTE_FEED_1.getPrice() * QUOTE_FEED_2.getPrice()
155
+ );
156
+ }
157
+ }