Skip to content

Commit d7a7bb5

Browse files
Merge pull request #73 from morpho-org/feat/vault-quote
vault as loan asset
2 parents 756347b + 2ce0440 commit d7a7bb5

15 files changed

+494
-211
lines changed

src/morpho-chainlink-v1/interfaces/IChainlinkOracle.sol

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,106 @@
11
// SPDX-License-Identifier: GPL-2.0-or-later
22
pragma solidity 0.8.21;
33

4-
import {IChainlinkOracle} from "./interfaces/IChainlinkOracle.sol";
54
import {IOracle} from "../../lib/morpho-blue/src/interfaces/IOracle.sol";
5+
import {IMorphoChainlinkOracleV2} from "./interfaces/IMorphoChainlinkOracleV2.sol";
66

7-
import {AggregatorV3Interface, ChainlinkDataFeedLib} from "./libraries/ChainlinkDataFeedLib.sol";
8-
import {IERC4626, VaultLib} from "./libraries/VaultLib.sol";
97
import {ErrorsLib} from "./libraries/ErrorsLib.sol";
8+
import {IERC4626, VaultLib} from "./libraries/VaultLib.sol";
109
import {Math} from "../../lib/openzeppelin-contracts/contracts/utils/math/Math.sol";
10+
import {AggregatorV3Interface, ChainlinkDataFeedLib} from "./libraries/ChainlinkDataFeedLib.sol";
1111

12-
/// @title ChainlinkOracle
12+
/// @title MorphoChainlinkOracleV2
1313
/// @author Morpho Labs
1414
/// @custom:contact security@morpho.org
1515
/// @notice Morpho Blue oracle using Chainlink-compliant feeds.
16-
contract ChainlinkOracle is IChainlinkOracle {
16+
contract MorphoChainlinkOracleV2 is IMorphoChainlinkOracleV2 {
1717
using Math for uint256;
1818
using VaultLib for IERC4626;
1919
using ChainlinkDataFeedLib for AggregatorV3Interface;
2020

2121
/* IMMUTABLES */
2222

23-
/// @inheritdoc IChainlinkOracle
24-
IERC4626 public immutable VAULT;
23+
/// @inheritdoc IMorphoChainlinkOracleV2
24+
IERC4626 public immutable BASE_VAULT;
2525

26-
/// @inheritdoc IChainlinkOracle
27-
uint256 public immutable VAULT_CONVERSION_SAMPLE;
26+
/// @inheritdoc IMorphoChainlinkOracleV2
27+
uint256 public immutable BASE_VAULT_CONVERSION_SAMPLE;
2828

29-
/// @inheritdoc IChainlinkOracle
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
3036
AggregatorV3Interface public immutable BASE_FEED_1;
3137

32-
/// @inheritdoc IChainlinkOracle
38+
/// @inheritdoc IMorphoChainlinkOracleV2
3339
AggregatorV3Interface public immutable BASE_FEED_2;
3440

35-
/// @inheritdoc IChainlinkOracle
41+
/// @inheritdoc IMorphoChainlinkOracleV2
3642
AggregatorV3Interface public immutable QUOTE_FEED_1;
3743

38-
/// @inheritdoc IChainlinkOracle
44+
/// @inheritdoc IMorphoChainlinkOracleV2
3945
AggregatorV3Interface public immutable QUOTE_FEED_2;
4046

41-
/// @inheritdoc IChainlinkOracle
47+
/// @inheritdoc IMorphoChainlinkOracleV2
4248
uint256 public immutable SCALE_FACTOR;
4349

4450
/* CONSTRUCTOR */
4551

4652
/// @dev Here is the list of assumptions that guarantees the oracle behaves as expected:
47-
/// - Feeds are either Chainlink-compliant or the address zero.
48-
/// - Feeds have the same behavioral assumptions as Chainlink's.
49-
/// - Feeds are set in the correct order.
53+
/// - The vaults, if set, are ERC4626-compliant.
54+
/// - The feeds, if set, are Chainlink-interface-compliant.
5055
/// - Decimals passed as argument are correct.
51-
/// - The vault's sample shares quoted as assets and the base feed prices don't overflow when multiplied.
52-
/// - The quote feed prices don't overflow when multiplied.
53-
/// - The vault, if set, is ERC4626-compliant.
54-
/// @param vault Vault. Pass address zero to omit this parameter.
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.
5562
/// @param baseFeed1 First base feed. Pass address zero if the price = 1.
5663
/// @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.
5769
/// @param quoteFeed1 First quote feed. Pass address zero if the price = 1.
5870
/// @param quoteFeed2 Second quote feed. Pass address zero if the price = 1.
59-
/// @param vaultConversionSample The sample amount of vault shares used to convert to the underlying asset.
60-
/// Pass 1 if the oracle does not use a vault. Should be chosen such that converting `vaultConversionSample` to
61-
/// assets has enough precision.
62-
/// @param baseTokenDecimals Base token decimals.
6371
/// @param quoteTokenDecimals Quote token decimals.
72+
/// @dev The base asset should be the collateral token and the quote asset the loan token.
6473
constructor(
65-
IERC4626 vault,
74+
IERC4626 baseVault,
75+
uint256 baseVaultConversionSample,
6676
AggregatorV3Interface baseFeed1,
6777
AggregatorV3Interface baseFeed2,
78+
uint256 baseTokenDecimals,
79+
IERC4626 quoteVault,
80+
uint256 quoteVaultConversionSample,
6881
AggregatorV3Interface quoteFeed1,
6982
AggregatorV3Interface quoteFeed2,
70-
uint256 vaultConversionSample,
71-
uint256 baseTokenDecimals,
7283
uint256 quoteTokenDecimals
7384
) {
74-
// The ERC4626 vault parameter is used to price `VAULT_CONVERSION_SAMPLE` of its shares, so it requires dividing
75-
// by that number, hence the division by `VAULT_CONVERSION_SAMPLE` in the `SCALE_FACTOR` definition.
76-
// Verify that vault = address(0) => vaultConversionSample = 1.
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+
);
7793
require(
78-
address(vault) != address(0) || vaultConversionSample == 1, ErrorsLib.VAULT_CONVERSION_SAMPLE_IS_NOT_ONE
94+
address(quoteVault) != address(0) || quoteVaultConversionSample == 1,
95+
ErrorsLib.VAULT_CONVERSION_SAMPLE_IS_NOT_ONE
7996
);
80-
require(vaultConversionSample != 0, ErrorsLib.VAULT_CONVERSION_SAMPLE_IS_ZERO);
97+
require(baseVaultConversionSample != 0, ErrorsLib.VAULT_CONVERSION_SAMPLE_IS_ZERO);
98+
require(quoteVaultConversionSample != 0, ErrorsLib.VAULT_CONVERSION_SAMPLE_IS_ZERO);
8199

82-
VAULT = vault;
83-
VAULT_CONVERSION_SAMPLE = vaultConversionSample;
100+
BASE_VAULT = baseVault;
101+
BASE_VAULT_CONVERSION_SAMPLE = baseVaultConversionSample;
102+
QUOTE_VAULT = quoteVault;
103+
QUOTE_VAULT_CONVERSION_SAMPLE = quoteVaultConversionSample;
84104
BASE_FEED_1 = baseFeed1;
85105
BASE_FEED_2 = baseFeed2;
86106
QUOTE_FEED_1 = quoteFeed1;
@@ -103,7 +123,7 @@ contract ChainlinkOracle is IChainlinkOracle {
103123
// = 1e36 * (pB1 * 1e(-dB1) * pB2) / (pQ1 * 1e(-dQ1) * pQ2)
104124

105125
// Let fpB1, fpB2, fpQ1, fpQ2 be the feed precision of the respective prices pB1, pB2, pQ1, pQ2.
106-
// Chainlink feeds return pB1 * 1e(fpB1), pB2 * 1e(fpB2), pQ1 * 1e(fpQ1) and pQ2 * 1e(fpQ2).
126+
// Feeds return pB1 * 1e(fpB1), pB2 * 1e(fpB2), pQ1 * 1e(fpQ1) and pQ2 * 1e(fpQ2).
107127

108128
// Based on the implementation of `price()` below, the value of `SCALE_FACTOR` should thus satisfy:
109129
// (pB1 * 1e(fpB1)) * (pB2 * 1e(fpB2)) * SCALE_FACTOR / ((pQ1 * 1e(fpQ1)) * (pQ2 * 1e(fpQ2)))
@@ -115,16 +135,16 @@ contract ChainlinkOracle is IChainlinkOracle {
115135
** (
116136
36 + quoteTokenDecimals + quoteFeed1.getDecimals() + quoteFeed2.getDecimals() - baseTokenDecimals
117137
- baseFeed1.getDecimals() - baseFeed2.getDecimals()
118-
) / vaultConversionSample;
138+
) * quoteVaultConversionSample / baseVaultConversionSample;
119139
}
120140

121141
/* PRICE */
122142

123143
/// @inheritdoc IOracle
124144
function price() external view returns (uint256) {
125145
return SCALE_FACTOR.mulDiv(
126-
VAULT.getAssets(VAULT_CONVERSION_SAMPLE) * BASE_FEED_1.getPrice() * BASE_FEED_2.getPrice(),
127-
QUOTE_FEED_1.getPrice() * QUOTE_FEED_2.getPrice()
146+
BASE_VAULT.getAssets(BASE_VAULT_CONVERSION_SAMPLE) * BASE_FEED_1.getPrice() * BASE_FEED_2.getPrice(),
147+
QUOTE_VAULT.getAssets(QUOTE_VAULT_CONVERSION_SAMPLE) * QUOTE_FEED_1.getPrice() * QUOTE_FEED_2.getPrice()
128148
);
129149
}
130150
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
pragma solidity 0.8.21;
3+
4+
import {IMorphoChainlinkOracleV2} from "./interfaces/IMorphoChainlinkOracleV2.sol";
5+
import {IMorphoChainlinkOracleV2Factory} from "./interfaces/IMorphoChainlinkOracleV2Factory.sol";
6+
import {AggregatorV3Interface} from "./libraries/ChainlinkDataFeedLib.sol";
7+
import {IERC4626} from "./libraries/VaultLib.sol";
8+
9+
import {MorphoChainlinkOracleV2} from "./MorphoChainlinkOracleV2.sol";
10+
11+
/// @title MorphoChainlinkOracleV2Factory
12+
/// @author Morpho Labs
13+
/// @custom:contact security@morpho.org
14+
/// @notice This contract allows to create MorphoChainlinkOracleV2 oracles, and to index them easily.
15+
contract MorphoChainlinkOracleV2Factory is IMorphoChainlinkOracleV2Factory {
16+
/* STORAGE */
17+
18+
/// @inheritdoc IMorphoChainlinkOracleV2Factory
19+
mapping(address => bool) public isMorphoChainlinkOracleV2;
20+
21+
/* EXTERNAL */
22+
23+
/// @inheritdoc IMorphoChainlinkOracleV2Factory
24+
function createMorphoChainlinkOracleV2(
25+
IERC4626 baseVault,
26+
uint256 baseVaultConversionSample,
27+
AggregatorV3Interface baseFeed1,
28+
AggregatorV3Interface baseFeed2,
29+
uint256 baseTokenDecimals,
30+
IERC4626 quoteVault,
31+
uint256 quoteVaultConversionSample,
32+
AggregatorV3Interface quoteFeed1,
33+
AggregatorV3Interface quoteFeed2,
34+
uint256 quoteTokenDecimals,
35+
bytes32 salt
36+
) external returns (MorphoChainlinkOracleV2 oracle) {
37+
oracle = new MorphoChainlinkOracleV2{salt: salt}(
38+
baseVault,
39+
baseVaultConversionSample,
40+
baseFeed1,
41+
baseFeed2,
42+
baseTokenDecimals,
43+
quoteVault,
44+
quoteVaultConversionSample,
45+
quoteFeed1,
46+
quoteFeed2,
47+
quoteTokenDecimals
48+
);
49+
50+
isMorphoChainlinkOracleV2[address(oracle)] = true;
51+
52+
emit CreateMorphoChainlinkOracleV2(msg.sender, address(oracle));
53+
}
54+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
pragma solidity >=0.5.0;
3+
4+
import {IERC4626} from "./IERC4626.sol";
5+
import {IOracle} from "../../../lib/morpho-blue/src/interfaces/IOracle.sol";
6+
import {AggregatorV3Interface} from "./AggregatorV3Interface.sol";
7+
8+
/// @title IMorphoChainlinkOracleV2
9+
/// @author Morpho Labs
10+
/// @custom:contact security@morpho.org
11+
/// @notice Interface of MorphoChainlinkOracleV2.
12+
interface IMorphoChainlinkOracleV2 is IOracle {
13+
/// @notice Returns the address of the base ERC4626 vault.
14+
function BASE_VAULT() external view returns (IERC4626);
15+
16+
/// @notice Returns the base vault conversion sample.
17+
function BASE_VAULT_CONVERSION_SAMPLE() external view returns (uint256);
18+
19+
/// @notice Returns the address of the quote ERC4626 vault.
20+
function QUOTE_VAULT() external view returns (IERC4626);
21+
22+
/// @notice Returns the quote vault conversion sample.
23+
function QUOTE_VAULT_CONVERSION_SAMPLE() external view returns (uint256);
24+
25+
/// @notice Returns the address of the first base feed.
26+
function BASE_FEED_1() external view returns (AggregatorV3Interface);
27+
28+
/// @notice Returns the address of the second base feed.
29+
function BASE_FEED_2() external view returns (AggregatorV3Interface);
30+
31+
/// @notice Returns the address of the first quote feed.
32+
function QUOTE_FEED_1() external view returns (AggregatorV3Interface);
33+
34+
/// @notice Returns the address of the second quote feed.
35+
function QUOTE_FEED_2() external view returns (AggregatorV3Interface);
36+
37+
/// @notice Returns the price scale factor, calculated at contract creation.
38+
function SCALE_FACTOR() external view returns (uint256);
39+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
pragma solidity >=0.5.0;
3+
4+
import {MorphoChainlinkOracleV2} from "../MorphoChainlinkOracleV2.sol";
5+
import {IERC4626} from "../libraries/VaultLib.sol";
6+
import {AggregatorV3Interface} from "../libraries/ChainlinkDataFeedLib.sol";
7+
8+
/// @title IMorphoChainlinkOracleV2Factory
9+
/// @author Morpho Labs
10+
/// @custom:contact security@morpho.org
11+
/// @notice Interface for MorphoChainlinkOracleV2Factory
12+
interface IMorphoChainlinkOracleV2Factory {
13+
/// @notice Emitted when a new Chainlink oracle is created.
14+
/// @param oracle The address of the Chainlink oracle.
15+
/// @param caller The caller of the function.
16+
event CreateMorphoChainlinkOracleV2(address caller, address oracle);
17+
18+
/// @notice Whether a Chainlink oracle vault was created with the factory.
19+
function isMorphoChainlinkOracleV2(address target) external view returns (bool);
20+
21+
/// @dev Here is the list of assumptions that guarantees the oracle behaves as expected:
22+
/// - The vaults, if set, are ERC4626-compliant.
23+
/// - The feeds, if set, are Chainlink-interface-compliant.
24+
/// - Decimals passed as argument are correct.
25+
/// - The base vaults's sample shares quoted as assets and the base feed prices don't overflow when multiplied.
26+
/// - The quote vault's sample shares quoted as assets and the quote feed prices don't overflow when multiplied.
27+
/// @param baseVault Base vault. Pass address zero to omit this parameter.
28+
/// @param baseVaultConversionSample The sample amount of base vault shares used to convert to underlying.
29+
/// Pass 1 if the base asset is not a vault. Should be chosen such that converting `baseVaultConversionSample` to
30+
/// assets has enough precision.
31+
/// @param baseFeed1 First base feed. Pass address zero if the price = 1.
32+
/// @param baseFeed2 Second base feed. Pass address zero if the price = 1.
33+
/// @param baseTokenDecimals Base token decimals.
34+
/// @param quoteVault Quote vault. Pass address zero to omit this parameter.
35+
/// @param quoteVaultConversionSample The sample amount of quote vault shares used to convert to underlying.
36+
/// Pass 1 if the quote asset is not a vault. Should be chosen such that converting `quoteVaultConversionSample` to
37+
/// assets has enough precision.
38+
/// @param quoteFeed1 First quote feed. Pass address zero if the price = 1.
39+
/// @param quoteFeed2 Second quote feed. Pass address zero if the price = 1.
40+
/// @param quoteTokenDecimals Quote token decimals.
41+
/// @param salt The salt to use for the CREATE2.
42+
/// @dev The base asset should be the collateral token and the quote asset the loan token.
43+
function createMorphoChainlinkOracleV2(
44+
IERC4626 baseVault,
45+
uint256 baseVaultConversionSample,
46+
AggregatorV3Interface baseFeed1,
47+
AggregatorV3Interface baseFeed2,
48+
uint256 baseTokenDecimals,
49+
IERC4626 quoteVault,
50+
uint256 quoteVaultConversionSample,
51+
AggregatorV3Interface quoteFeed1,
52+
AggregatorV3Interface quoteFeed2,
53+
uint256 quoteTokenDecimals,
54+
bytes32 salt
55+
) external returns (MorphoChainlinkOracleV2 oracle);
56+
}

0 commit comments

Comments
 (0)