Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add factory #74

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions src/ChainlinkOracleFactory.sol
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.21;

import {IChainlinkOracle} from "./interfaces/IChainlinkOracle.sol";
import {IChainlinkOracleFactory} from "./interfaces/IChainlinkOracleFactory.sol";
import {AggregatorV3Interface} from "./libraries/ChainlinkDataFeedLib.sol";
import {IERC4626} from "./libraries/VaultLib.sol";

import {ChainlinkOracle} from "./ChainlinkOracle.sol";

/// @title ChainlinkOracleFactory
/// @author Morpho Labs
/// @custom:contact [email protected]
/// @notice This contract allows to create Chainlink oracles, and to index them easily.
contract ChainlinkOracleFactory is IChainlinkOracleFactory {
/// @notice Emitted when a new Chainlink oracle is created.
/// @param oracle The address of the Chainlink oracle.
/// @param caller The caller of the function.
/// @param baseVault Base vault.
/// @param baseVaultConversionSample The sample amount of base vault shares used to convert to underlying.
/// @param quoteVault Quote vault.
/// @param quoteVaultConversionSample The sample amount of quote vault shares used to convert to underlying.
/// @param salt The salt used for the MetaMorpho vault's CREATE2 address.
event CreateChainlinkOracle(
address oracle,
address caller,
address baseVault,
uint256 baseVaultConversionSample,
address quoteVault,
uint256 quoteVaultConversionSample,
bytes32 salt
);

/// @notice Emitted when a new Chainlink oracle is created.
/// @param oracle The address of the Chainlink oracle.
/// @param baseFeed1 First base feed.
/// @param baseFeed2 Second base feed.
/// @param quoteFeed1 First quote feed.
/// @param quoteFeed2 Second quote feed.
/// @param baseTokenDecimals Base token decimals.
/// @param quoteTokenDecimals Quote token decimals.
event CreateChainlinkOracleFeeds(
address oracle,
address baseFeed1,
address baseFeed2,
address quoteFeed1,
address quoteFeed2,
uint256 baseTokenDecimals,
uint256 quoteTokenDecimals
);
Comment on lines +24 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we can just log the address


/* STORAGE */

/// @inheritdoc IChainlinkOracleFactory
mapping(address => bool) public isChainlinkOracle;

/* EXTERNAL */

/// @inheritdoc IChainlinkOracleFactory
function createChainlinkOracle(
IERC4626 baseVault,
uint256 baseVaultConversionSample,
IERC4626 quoteVault,
uint256 quoteVaultConversionSample,
AggregatorV3Interface baseFeed1,
AggregatorV3Interface baseFeed2,
AggregatorV3Interface quoteFeed1,
AggregatorV3Interface quoteFeed2,
uint256 baseTokenDecimals,
uint256 quoteTokenDecimals,
bytes32 salt
) external returns (IChainlinkOracle oracle) {
oracle = IChainlinkOracle(
address(
new ChainlinkOracle{salt: salt}(
baseVault,
baseVaultConversionSample,
quoteVault,
quoteVaultConversionSample,
baseFeed1,
baseFeed2,
quoteFeed1,
quoteFeed2,
baseTokenDecimals,
quoteTokenDecimals
)
)
);

isChainlinkOracle[address(oracle)] = true;

emit CreateChainlinkOracle(
address(oracle),
msg.sender,
address(baseVault),
baseVaultConversionSample,
address(quoteVault),
quoteVaultConversionSample,
salt
);
emit CreateChainlinkOracleFeeds(
address(oracle),
address(baseFeed1),
address(baseFeed2),
address(quoteFeed1),
address(quoteFeed2),
baseTokenDecimals,
quoteTokenDecimals
);
}
}
52 changes: 52 additions & 0 deletions src/interfaces/IChainlinkOracleFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

import {IChainlinkOracle} from "./IChainlinkOracle.sol";
import {IERC4626} from "../libraries/VaultLib.sol";
import {AggregatorV3Interface} from "../libraries/ChainlinkDataFeedLib.sol";

/// @title IChainlinkOracleFactory
/// @author Morpho Labs
/// @custom:contact [email protected]
/// @notice Interface of Chainkink Oracle's factory.
interface IChainlinkOracleFactory {
/// @notice Whether a Chainlink oracle vault was created with the factory.
function isChainlinkOracle(address target) external view returns (bool);

/// @dev Here is the list of assumptions that guarantees the oracle behaves as expected:
/// - Feeds are either Chainlink-compliant or the address zero.
/// - Feeds have the same behavioral assumptions as Chainlink's.
/// - Feeds are set in the correct order.
/// - Decimals passed as argument are correct.
/// - The vault's sample shares quoted as assets and the base feed prices don't overflow when multiplied.
/// - The quote feed prices don't overflow when multiplied.
/// - The vault, if set, is ERC4626-compliant.
/// @param baseVault Base vault. Pass address zero to omit this parameter.
/// @param baseVaultConversionSample The sample amount of base vault shares used to convert to underlying.
/// Pass 1 if the base asset is not a vault. Should be chosen such that converting `baseVaultConversionSample` to
/// assets has enough precision.
/// @param quoteVault Quote vault. Pass address zero to omit this parameter.
/// @param quoteVaultConversionSample The sample amount of quote vault shares used to convert to underlying.
/// Pass 1 if the base asset is not a vault. Should be chosen such that converting `quoteVaultConversionSample` to
/// assets has enough precision.
/// @param baseFeed1 First base feed. Pass address zero if the price = 1.
/// @param baseFeed2 Second base feed. Pass address zero if the price = 1.
/// @param quoteFeed1 First quote feed. Pass address zero if the price = 1.
/// @param quoteFeed2 Second quote feed. Pass address zero if the price = 1.
/// @param baseTokenDecimals Base token decimals.
/// @param quoteTokenDecimals Quote token decimals.
/// @param salt The salt to use for the MetaMorpho vault's CREATE2 address.
function createChainlinkOracle(
IERC4626 baseVault,
uint256 baseVaultConversionSample,
IERC4626 quoteVault,
uint256 quoteVaultConversionSample,
AggregatorV3Interface baseFeed1,
AggregatorV3Interface baseFeed2,
AggregatorV3Interface quoteFeed1,
AggregatorV3Interface quoteFeed2,
uint256 baseTokenDecimals,
uint256 quoteTokenDecimals,
bytes32 salt
) external returns (IChainlinkOracle oracle);
}
94 changes: 94 additions & 0 deletions test/ChainlinkOracleFactoryTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import "./helpers/BaseTest.sol";

import "../src/ChainlinkOracleFactory.sol";

import {ChainlinkDataFeedLib} from "../src/libraries/ChainlinkDataFeedLib.sol";

contract ChainlinkOracleFactoryTest is BaseTest {
using ChainlinkDataFeedLib for AggregatorV3Interface;

ChainlinkOracleFactory factory;

function setUp() public override {
super.setUp();

factory = new ChainlinkOracleFactory();
}

function testDeploySDaiUsdcOracle(bytes32 salt) public {
bytes32 initCodeHash = hashInitCode(
type(ChainlinkOracle).creationCode,
abi.encode(sDaiVault, 1e18, vaultZero, 1, daiEthFeed, feedZero, usdcEthFeed, feedZero, 18, 6)
);
address expectedAddress = computeCreate2Address(salt, initCodeHash, address(factory));

assertFalse(factory.isChainlinkOracle(expectedAddress), "isChainlinkOracle");

// vm.expectEmit(address(factory));
// emit ChainlinkOracleFactory.CreateChainlinkOracle(
// expectedAddress, address(this), address(sDaiVault), 1e18, address(vaultZero), 1, salt
// );
// emit ChainlinkOracleFactory.CreateChainlinkOracleFeeds(
// expectedAddress, address(daiEthFeed), address(feedZero), address(usdcEthFeed), address(feedZero), 8, 6
// );
IChainlinkOracle oracle = factory.createChainlinkOracle(
sDaiVault, 1e18, vaultZero, 1, daiEthFeed, feedZero, usdcEthFeed, feedZero, 18, 6, salt
);

assertEq(expectedAddress, address(oracle), "computeCreate2Address");

assertTrue(factory.isChainlinkOracle(address(oracle)), "isChainlinkOracle");

uint256 scaleFactor = 10 ** (36 + 6 + 18 - 18 - 18 - 18);

assertEq(address(oracle.BASE_VAULT()), address(sDaiVault), "BASE_VAULT");
assertEq(oracle.BASE_VAULT_CONVERSION_SAMPLE(), 1e18, "BASE_VAULT_CONVERSION_SAMPLE");
assertEq(address(oracle.QUOTE_VAULT()), address(vaultZero), "QUOTE_VAULT");
assertEq(oracle.QUOTE_VAULT_CONVERSION_SAMPLE(), 1, "QUOTE_VAULT_CONVERSION_SAMPLE");
assertEq(address(oracle.BASE_FEED_1()), address(daiEthFeed), "BASE_FEED_1");
assertEq(address(oracle.BASE_FEED_2()), address(feedZero), "BASE_FEED_2");
assertEq(address(oracle.QUOTE_FEED_1()), address(usdcEthFeed), "QUOTE_FEED_1");
assertEq(address(oracle.QUOTE_FEED_2()), address(feedZero), "QUOTE_FEED_2");
assertEq(oracle.SCALE_FACTOR(), scaleFactor, "SCALE_FACTOR");
}

function testDeployUsdcSDaiOracle(bytes32 salt) public {
bytes32 initCodeHash = hashInitCode(
type(ChainlinkOracle).creationCode,
abi.encode(vaultZero, 1, sDaiVault, 1e18, usdcEthFeed, feedZero, daiEthFeed, feedZero, 6, 18)
);
address expectedAddress = computeCreate2Address(salt, initCodeHash, address(factory));

assertFalse(factory.isChainlinkOracle(expectedAddress), "isChainlinkOracle");

// vm.expectEmit(address(factory));
// emit ChainlinkOracleFactory.CreateChainlinkOracle(
// expectedAddress, address(this), address(vaultZero), 1, address(sDaiVault), 1e18, salt
// );
// emit ChainlinkOracleFactory.CreateChainlinkOracleFeeds(
// expectedAddress, address(usdcEthFeed), address(feedZero), address(daiEthFeed), address(feedZero), 6, 18
// );
IChainlinkOracle oracle = factory.createChainlinkOracle(
vaultZero, 1, sDaiVault, 1e18, usdcEthFeed, feedZero, daiEthFeed, feedZero, 6, 18, salt
);

assertEq(expectedAddress, address(oracle), "computeCreate2Address");

assertTrue(factory.isChainlinkOracle(address(oracle)), "isChainlinkOracle");

uint256 scaleFactor = 10 ** (36 + 18 + 18 + 0 - 6 - 18 - 0) * 1e18;

assertEq(address(oracle.BASE_VAULT()), address(vaultZero), "BASE_VAULT");
assertEq(oracle.BASE_VAULT_CONVERSION_SAMPLE(), 1, "BASE_VAULT_CONVERSION_SAMPLE");
assertEq(address(oracle.QUOTE_VAULT()), address(sDaiVault), "QUOTE_VAULT");
assertEq(oracle.QUOTE_VAULT_CONVERSION_SAMPLE(), 1e18, "QUOTE_VAULT_CONVERSION_SAMPLE");
assertEq(address(oracle.BASE_FEED_1()), address(usdcEthFeed), "BASE_FEED_1");
assertEq(address(oracle.BASE_FEED_2()), address(feedZero), "BASE_FEED_2");
assertEq(address(oracle.QUOTE_FEED_1()), address(daiEthFeed), "QUOTE_FEED_1");
assertEq(address(oracle.QUOTE_FEED_2()), address(feedZero), "QUOTE_FEED_2");
assertEq(oracle.SCALE_FACTOR(), scaleFactor, "SCALE_FACTOR");
}
}
34 changes: 3 additions & 31 deletions test/ChainlinkOracleTest.sol
Original file line number Diff line number Diff line change
@@ -1,38 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import "../lib/forge-std/src/Test.sol";
import "../src/ChainlinkOracle.sol";
import "../src/libraries/ErrorsLib.sol";
import "./mocks/ChainlinkAggregatorMock.sol";

AggregatorV3Interface constant feedZero = AggregatorV3Interface(address(0));
// 8 decimals of precision
AggregatorV3Interface constant btcUsdFeed = AggregatorV3Interface(0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c);
// 8 decimals of precision
AggregatorV3Interface constant usdcUsdFeed = AggregatorV3Interface(0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6);
// 18 decimals of precision
AggregatorV3Interface constant btcEthFeed = AggregatorV3Interface(0xdeb288F737066589598e9214E782fa5A8eD689e8);
// 8 decimals of precision
AggregatorV3Interface constant wBtcBtcFeed = AggregatorV3Interface(0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23);
// 18 decimals of precision
AggregatorV3Interface constant stEthEthFeed = AggregatorV3Interface(0x86392dC19c0b719886221c78AB11eb8Cf5c52812);
// 18 decimals of precision
AggregatorV3Interface constant usdcEthFeed = AggregatorV3Interface(0x986b5E1e1755e3C2440e960477f25201B0a8bbD4);
// 8 decimals of precision
AggregatorV3Interface constant ethUsdFeed = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
// 18 decimals of precision
AggregatorV3Interface constant daiEthFeed = AggregatorV3Interface(0x773616E4d11A78F511299002da57A0a94577F1f4);

IERC4626 constant vaultZero = IERC4626(address(0));
IERC4626 constant sDaiVault = IERC4626(0x83F20F44975D03b1b09e64809B757c47f942BEeA);

contract ChainlinkOracleTest is Test {
using Math for uint256;
import "./helpers/BaseTest.sol";

function setUp() public {
vm.createSelectFork(vm.envString("ETH_RPC_URL"));
}
contract ChainlinkOracleTest is BaseTest {
using Math for uint256;

function testOracleWbtcUsdc() public {
ChainlinkOracle oracle =
Expand Down
34 changes: 34 additions & 0 deletions test/helpers/BaseTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import "../../lib/forge-std/src/Test.sol";
import "../../src/ChainlinkOracle.sol";
import "../../src/libraries/ErrorsLib.sol";
import "../mocks/ChainlinkAggregatorMock.sol";

AggregatorV3Interface constant feedZero = AggregatorV3Interface(address(0));
// 8 decimals of precision
AggregatorV3Interface constant btcUsdFeed = AggregatorV3Interface(0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c);
// 8 decimals of precision
AggregatorV3Interface constant usdcUsdFeed = AggregatorV3Interface(0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6);
// 18 decimals of precision
AggregatorV3Interface constant btcEthFeed = AggregatorV3Interface(0xdeb288F737066589598e9214E782fa5A8eD689e8);
// 8 decimals of precision
AggregatorV3Interface constant wBtcBtcFeed = AggregatorV3Interface(0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23);
// 18 decimals of precision
AggregatorV3Interface constant stEthEthFeed = AggregatorV3Interface(0x86392dC19c0b719886221c78AB11eb8Cf5c52812);
// 18 decimals of precision
AggregatorV3Interface constant usdcEthFeed = AggregatorV3Interface(0x986b5E1e1755e3C2440e960477f25201B0a8bbD4);
// 8 decimals of precision
AggregatorV3Interface constant ethUsdFeed = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
// 18 decimals of precision
AggregatorV3Interface constant daiEthFeed = AggregatorV3Interface(0x773616E4d11A78F511299002da57A0a94577F1f4);

IERC4626 constant vaultZero = IERC4626(address(0));
IERC4626 constant sDaiVault = IERC4626(0x83F20F44975D03b1b09e64809B757c47f942BEeA);

contract BaseTest is Test {
function setUp() public virtual {
vm.createSelectFork(vm.envString("ETH_RPC_URL"));
}
}
Loading