@@ -37,6 +37,13 @@ contract ChainlinkOracle is IOracle {
37
37
38
38
/* CONSTRUCTOR */
39
39
40
+ /// @dev Here is the list of assumptions on the inputs that guarantees the oracle behaves as expected:
41
+ /// - Feeds are either Chainlink-compliant or the address zero.
42
+ /// - Feeds have the same behavioral assumptions as Chainlink's.
43
+ /// - Feeds are set in the correct order.
44
+ /// - Decimals passed as argument are correct.
45
+ /// - The vault conversion sample is low enough to avoid overflows.
46
+ /// - The vault, if set, is ERC4626-compliant.
40
47
/// @param vault Vault. Pass address zero to omit this parameter.
41
48
/// @param baseFeed1 First base feed. Pass address zero if the price = 1.
42
49
/// @param baseFeed2 Second base feed. Pass address zero if the price = 1.
@@ -55,41 +62,51 @@ contract ChainlinkOracle is IOracle {
55
62
uint256 baseTokenDecimals ,
56
63
uint256 quoteTokenDecimals
57
64
) {
58
- // The vault parameter is used for ERC4626 tokens, to price its shares.
59
- // It is used to price `VAULT_CONVERSION_SAMPLE` of the vault shares, so it requires dividing by that number,
60
- // hence the division by `VAULT_CONVERSION_SAMPLE` in the `SCALE_FACTOR` definition.
65
+ // The ERC4626 vault parameter is used to price `VAULT_CONVERSION_SAMPLE` of its shares, so it requires dividing
66
+ // by that number, hence the division by `VAULT_CONVERSION_SAMPLE` in the `SCALE_FACTOR` definition.
61
67
// Verify that vault = address(0) => vaultConversionSample = 1.
62
68
require (
63
69
address (vault) != address (0 ) || vaultConversionSample == 1 , ErrorsLib.VAULT_CONVERSION_SAMPLE_IS_NOT_ONE
64
70
);
71
+ require (vaultConversionSample != 0 , ErrorsLib.VAULT_CONVERSION_SAMPLE_IS_ZERO);
72
+
65
73
VAULT = vault;
66
74
VAULT_CONVERSION_SAMPLE = vaultConversionSample;
67
75
BASE_FEED_1 = baseFeed1;
68
76
BASE_FEED_2 = baseFeed2;
69
77
QUOTE_FEED_1 = quoteFeed1;
70
78
QUOTE_FEED_2 = quoteFeed2;
71
- // Let pB1 and pB2 be the base prices, and pQ1 and pQ2 the quote prices (price taking into account the
72
- // decimals of both tokens), in a common currency.
73
- // We tackle the most general case in the remainder of this comment, where we assume that no feed is the address
74
- // zero. Similar explanations would hold in the case where some of the feeds are the address zero.
75
- // Let dB1, dB2, dB3, and dQ1, dQ2, dQ3 be the decimals of the tokens involved.
76
- // For example, pB1 is the number of 1e(dB2) of the second base asset that can be obtained from 1e(dB1) of
77
- // the first base asset.
78
- // We notably have dB3 = dQ3, because those two quantities are the decimals of the same common currency.
79
- // Let fpB1, fpB2, fpQ1 and fpQ2 be the feed precision of the corresponding prices.
80
- // Chainlink feeds return pB1*1e(fpB1), pB2*1e(fpB2), pQ1*1e(fpQ1) and pQ2*1e(fpQ2).
81
- // Because the Blue oracle does not take into account decimals, `price()` should return
82
- // 1e36 * (pB1*1e(dB2-dB1) * pB2*1e(dB3-dB2)) / (pQ1*1e(dQ2-dQ1) * pQ2*1e(dQ3-dQ2))
83
- // Yet `price()` returns (pB1*1e(fpB1) * pB2*1e(fpB2) * SCALE_FACTOR) / (pQ1*1e(fpQ1) * pQ2*1e(fpQ2))
84
- // So 1e36 * pB1 * pB2 * 1e(-dB1) / (pQ1 * pQ2 * 1e(-dQ1)) =
85
- // (pB1*1e(fpB1) * pB2*1e(fpB2) * SCALE_FACTOR) / (pQ1*1e(fpQ1) * pQ2*1e(fpQ2))
79
+
80
+ // In the following comment, we explain the general case (where we assume that no feed is the address zero)
81
+ // how to scale the output price as Morpho Blue expects, given the input feed prices.
82
+ // Similar explanations would hold in the case where some of the feeds are the address zero.
83
+
84
+ // Let B1, B2, Q1, Q2, C be 5 assets, each respectively having dB1, dB2, dQ1, dQ2, dC decimals.
85
+ // Let pB1 and pB2 be the base prices, and pQ1 and pQ2 the quote prices, so that:
86
+ // - pB1 is the quantity of 1e(dB2) assets B2 that can be exchanged for 1e(dB1) assets B1.
87
+ // - pB2 is the quantity of 1e(dC) assets C that can be exchanged for 1e(dB2) assets B2.
88
+ // - pQ1 is the quantity of 1e(dQ2) assets Q2 that can be exchanged for 1e(dQ1) assets Q1.
89
+ // - pQ2 is the quantity of 1e(dC) assets C that can be exchanged for 1e(dQ2) assets B2.
90
+
91
+ // Morpho Blue expects `price()` to be the quantity of 1 asset Q1 that can be exchanged for 1 asset B1,
92
+ // scaled by 1e36:
93
+ // 1e36 * (pB1 * 1e(dB2 - dB1)) * (pB2 * 1e(dC - dB2)) / ((pQ1 * 1e(dQ2 - dQ1)) * (pQ2 * 1e(dC - dQ2)))
94
+ // = 1e36 * (pB1 * 1e(-dB1) * pB2) / (pQ1 * 1e(-dQ1) * pQ2)
95
+
96
+ // Let fpB1, fpB2, fpQ1, fpQ2 be the feed precision of the respective prices pB1, pB2, pQ1, pQ2.
97
+ // Chainlink feeds return pB1 * 1e(fpB1), pB2 * 1e(fpB2), pQ1 * 1e(fpQ1) and pQ2 * 1e(fpQ2).
98
+
99
+ // Based on the implementation of `price()` below, the value of `SCALE_FACTOR` should thus satisfy:
100
+ // (pB1 * 1e(fpB1)) * (pB2 * 1e(fpB2)) * SCALE_FACTOR / ((pQ1 * 1e(fpQ1)) * (pQ2 * 1e(fpQ2)))
101
+ // = 1e36 * (pB1 * 1e(-dB1) * pB2) / (pQ1 * 1e(-dQ1) * pQ2)
102
+
86
103
// So SCALE_FACTOR = 1e36 * 1e(-dB1) * 1e(dQ1) * 1e(-fpB1) * 1e(-fpB2) * 1e(fpQ1) * 1e(fpQ2)
87
104
// = 1e(36 + dQ1 + fpQ1 + fpQ2 - dB1 - fpB1 - fpB2)
88
105
SCALE_FACTOR = 10
89
106
** (
90
107
36 + quoteTokenDecimals + quoteFeed1.getDecimals () + quoteFeed2.getDecimals () - baseTokenDecimals
91
108
- baseFeed1.getDecimals () - baseFeed2.getDecimals ()
92
- ) / VAULT_CONVERSION_SAMPLE ;
109
+ ) / vaultConversionSample ;
93
110
}
94
111
95
112
/* PRICE */
0 commit comments