Skip to content

Commit a6dad48

Browse files
authored
Merge pull request #77 from morpho-org/feat/factory-1
feat: factory
2 parents 648d968 + d6b8837 commit a6dad48

File tree

3 files changed

+193
-0
lines changed

3 files changed

+193
-0
lines changed
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: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
/// - Feeds are either Chainlink-compliant or the address zero.
23+
/// - Feeds have the same behavioral assumptions as Chainlink's.
24+
/// - Feeds are set in the correct order.
25+
/// - Decimals passed as argument are correct.
26+
/// - The vault's sample shares quoted as assets and the base feed prices don't overflow when multiplied.
27+
/// - The quote feed prices don't overflow when multiplied.
28+
/// - The vault, if set, is ERC4626-compliant.
29+
/// @param baseVault Base vault. Pass address zero to omit this parameter.
30+
/// @param baseVaultConversionSample The sample amount of base vault shares used to convert to underlying.
31+
/// Pass 1 if the base asset is not a vault. Should be chosen such that converting `baseVaultConversionSample` to
32+
/// assets has enough precision.
33+
/// @param baseFeed1 First base feed. Pass address zero if the price = 1.
34+
/// @param baseFeed2 Second base feed. Pass address zero if the price = 1.
35+
/// @param baseTokenDecimals Base token decimals.
36+
/// @param quoteVault Quote vault. Pass address zero to omit this parameter.
37+
/// @param quoteVaultConversionSample The sample amount of quote vault shares used to convert to underlying.
38+
/// Pass 1 if the quote asset is not a vault. Should be chosen such that converting `quoteVaultConversionSample` to
39+
/// assets has enough precision.
40+
/// @param quoteFeed1 First quote feed. Pass address zero if the price = 1.
41+
/// @param quoteFeed2 Second quote feed. Pass address zero if the price = 1.
42+
/// @param quoteTokenDecimals Quote token decimals.
43+
/// @param salt The salt to use for the MetaMorpho vault's CREATE2 address.
44+
function createMorphoChainlinkOracleV2(
45+
IERC4626 baseVault,
46+
uint256 baseVaultConversionSample,
47+
AggregatorV3Interface baseFeed1,
48+
AggregatorV3Interface baseFeed2,
49+
uint256 baseTokenDecimals,
50+
IERC4626 quoteVault,
51+
uint256 quoteVaultConversionSample,
52+
AggregatorV3Interface quoteFeed1,
53+
AggregatorV3Interface quoteFeed2,
54+
uint256 quoteTokenDecimals,
55+
bytes32 salt
56+
) external returns (MorphoChainlinkOracleV2 oracle);
57+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
pragma solidity ^0.8.0;
3+
4+
import "./helpers/Constants.sol";
5+
import "../lib/forge-std/src/Test.sol";
6+
import "../src/morpho-chainlink/MorphoChainlinkOracleV2Factory.sol";
7+
import {ChainlinkDataFeedLib} from "../src/morpho-chainlink/libraries/ChainlinkDataFeedLib.sol";
8+
9+
contract ChainlinkOracleFactoryTest is Test {
10+
using ChainlinkDataFeedLib for AggregatorV3Interface;
11+
12+
MorphoChainlinkOracleV2Factory factory;
13+
14+
function setUp() public {
15+
vm.createSelectFork(vm.envString("ETH_RPC_URL"));
16+
factory = new MorphoChainlinkOracleV2Factory();
17+
}
18+
19+
function testDeploySDaiUsdcOracle(bytes32 salt) public {
20+
bytes32 initCodeHash = hashInitCode(
21+
type(MorphoChainlinkOracleV2).creationCode,
22+
abi.encode(sDaiVault, 1e18, daiEthFeed, feedZero, 18, vaultZero, 1, usdcEthFeed, feedZero, 6)
23+
);
24+
address expectedAddress = computeCreate2Address(salt, initCodeHash, address(factory));
25+
26+
assertFalse(factory.isMorphoChainlinkOracleV2(expectedAddress), "isChainlinkOracle");
27+
28+
// vm.expectEmit(address(factory));
29+
// emit IMorphoChainlinkOracleV2Factory.CreateMorphoChainlinkOracleV2(address(this), expectedAddress);
30+
IMorphoChainlinkOracleV2 oracle = factory.createMorphoChainlinkOracleV2(
31+
sDaiVault, 1e18, daiEthFeed, feedZero, 18, vaultZero, 1, usdcEthFeed, feedZero, 6, salt
32+
);
33+
34+
assertEq(expectedAddress, address(oracle), "computeCreate2Address");
35+
36+
assertTrue(factory.isMorphoChainlinkOracleV2(address(oracle)), "isChainlinkOracle");
37+
38+
uint256 scaleFactor = 10 ** (36 + 6 + 18 - 18 - 18 - 18);
39+
40+
assertEq(address(oracle.BASE_VAULT()), address(sDaiVault), "BASE_VAULT");
41+
assertEq(oracle.BASE_VAULT_CONVERSION_SAMPLE(), 1e18, "BASE_VAULT_CONVERSION_SAMPLE");
42+
assertEq(address(oracle.QUOTE_VAULT()), address(vaultZero), "QUOTE_VAULT");
43+
assertEq(oracle.QUOTE_VAULT_CONVERSION_SAMPLE(), 1, "QUOTE_VAULT_CONVERSION_SAMPLE");
44+
assertEq(address(oracle.BASE_FEED_1()), address(daiEthFeed), "BASE_FEED_1");
45+
assertEq(address(oracle.BASE_FEED_2()), address(feedZero), "BASE_FEED_2");
46+
assertEq(address(oracle.QUOTE_FEED_1()), address(usdcEthFeed), "QUOTE_FEED_1");
47+
assertEq(address(oracle.QUOTE_FEED_2()), address(feedZero), "QUOTE_FEED_2");
48+
assertEq(oracle.SCALE_FACTOR(), scaleFactor, "SCALE_FACTOR");
49+
}
50+
51+
function testDeployUsdcSDaiOracle(bytes32 salt) public {
52+
bytes32 initCodeHash = hashInitCode(
53+
type(MorphoChainlinkOracleV2).creationCode,
54+
abi.encode(vaultZero, 1, usdcEthFeed, feedZero, 6, sDaiVault, 1e18, daiEthFeed, feedZero, 18)
55+
);
56+
address expectedAddress = computeCreate2Address(salt, initCodeHash, address(factory));
57+
58+
assertFalse(factory.isMorphoChainlinkOracleV2(expectedAddress), "isChainlinkOracle");
59+
60+
// vm.expectEmit(address(factory));
61+
// emit IMorphoChainlinkOracleV2Factory.CreateMorphoChainlinkOracleV2(address(this), expectedAddress);
62+
IMorphoChainlinkOracleV2 oracle = factory.createMorphoChainlinkOracleV2(
63+
vaultZero, 1, usdcEthFeed, feedZero, 6, sDaiVault, 1e18, daiEthFeed, feedZero, 18, salt
64+
);
65+
66+
assertEq(expectedAddress, address(oracle), "computeCreate2Address");
67+
68+
assertTrue(factory.isMorphoChainlinkOracleV2(address(oracle)), "isChainlinkOracle");
69+
70+
uint256 scaleFactor = 10 ** (36 + 18 + 18 + 0 - 6 - 18 - 0) * 1e18;
71+
72+
assertEq(address(oracle.BASE_VAULT()), address(vaultZero), "BASE_VAULT");
73+
assertEq(oracle.BASE_VAULT_CONVERSION_SAMPLE(), 1, "BASE_VAULT_CONVERSION_SAMPLE");
74+
assertEq(address(oracle.QUOTE_VAULT()), address(sDaiVault), "QUOTE_VAULT");
75+
assertEq(oracle.QUOTE_VAULT_CONVERSION_SAMPLE(), 1e18, "QUOTE_VAULT_CONVERSION_SAMPLE");
76+
assertEq(address(oracle.BASE_FEED_1()), address(usdcEthFeed), "BASE_FEED_1");
77+
assertEq(address(oracle.BASE_FEED_2()), address(feedZero), "BASE_FEED_2");
78+
assertEq(address(oracle.QUOTE_FEED_1()), address(daiEthFeed), "QUOTE_FEED_1");
79+
assertEq(address(oracle.QUOTE_FEED_2()), address(feedZero), "QUOTE_FEED_2");
80+
assertEq(oracle.SCALE_FACTOR(), scaleFactor, "SCALE_FACTOR");
81+
}
82+
}

0 commit comments

Comments
 (0)