diff --git a/contracts/contracts/interfaces/IVault.sol b/contracts/contracts/interfaces/IVault.sol
index e6b1668ca9..7e4d1ce83e 100644
--- a/contracts/contracts/interfaces/IVault.sol
+++ b/contracts/contracts/interfaces/IVault.sol
@@ -120,6 +120,8 @@ interface IVault {
function priceUnitRedeem(address asset) external view returns (uint256);
+ function floorPrice() external view returns (uint256);
+
function withdrawAllFromStrategy(address _strategyAddr) external;
function withdrawAllFromStrategies() external;
diff --git a/contracts/contracts/interfaces/chainlink/README.md b/contracts/contracts/interfaces/chainlink/README.md
new file mode 100644
index 0000000000..456fa4c904
--- /dev/null
+++ b/contracts/contracts/interfaces/chainlink/README.md
@@ -0,0 +1,3 @@
+# Diagrams
+
+
diff --git a/contracts/contracts/mocks/MockVault.sol b/contracts/contracts/mocks/MockVault.sol
index b563f9a725..9e45945331 100644
--- a/contracts/contracts/mocks/MockVault.sol
+++ b/contracts/contracts/mocks/MockVault.sol
@@ -10,6 +10,7 @@ contract MockVault is VaultCore {
using StableMath for uint256;
uint256 storedTotalValue;
+ uint256 public override floorPrice;
function setTotalValue(uint256 _value) public {
storedTotalValue = _value;
@@ -42,4 +43,8 @@ contract MockVault is VaultCore {
function setMaxSupplyDiff(uint256 _maxSupplyDiff) external onlyGovernor {
maxSupplyDiff = _maxSupplyDiff;
}
+
+ function setFloorPrice(uint256 _floorPrice) external {
+ floorPrice = _floorPrice;
+ }
}
diff --git a/contracts/contracts/mocks/curve/MockCurveOethEthPool.sol b/contracts/contracts/mocks/curve/MockCurveOethEthPool.sol
new file mode 100644
index 0000000000..40ae612150
--- /dev/null
+++ b/contracts/contracts/mocks/curve/MockCurveOethEthPool.sol
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import { MockCurveAbstractMetapool } from "./MockCurveAbstractMetapool.sol";
+import "../MintableERC20.sol";
+
+contract MockCurveOethEthPool is MockCurveAbstractMetapool {
+ constructor(address[2] memory _coins)
+ ERC20("Curve.fi Factory Pool: OETH", "OETHCRV-f")
+ {
+ coins = _coins;
+ }
+
+ // Simulate pool's EMA Oracle price
+ uint256 public price_oracle = 9995e14; // 0.9995
+
+ function setOraclePrice(uint256 _price) public {
+ price_oracle = _price;
+ }
+}
diff --git a/contracts/contracts/oracle/BaseOracle.sol b/contracts/contracts/oracle/BaseOracle.sol
new file mode 100644
index 0000000000..849535c73b
--- /dev/null
+++ b/contracts/contracts/oracle/BaseOracle.sol
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import { IOracleReceiver } from "./IOracleReceiver.sol";
+import { AggregatorV3Interface } from "../interfaces/chainlink/AggregatorV3Interface.sol";
+import { Governable } from "../governance/Governable.sol";
+
+/**
+ * @title BaseOracle
+ * @notice Generic Chainlink style oracle
+ * @author Origin Protocol Inc
+ */
+abstract contract BaseOracle is
+ AggregatorV3Interface,
+ IOracleReceiver,
+ Governable
+{
+ /// @notice Contract or account that can call addRoundData() to update the Oracle prices
+ address public oracleUpdater;
+
+ /// @notice Last round ID where isBadData is false and price is within maximum deviation
+ uint80 public lastCorrectRoundId;
+
+ /// @notice Historical Oracle prices
+ Round[] public rounds;
+
+ /// @notice Packed Round data struct
+ /// @notice price Oracle price to 18 decimals
+ /// @notice timestamp timestamp in seconds of price
+ struct Round {
+ uint128 price;
+ uint40 timestamp;
+ }
+
+ event SetOracleUpdater(address oracleUpdater);
+
+ constructor(address _oracleUpdater) Governable() {
+ _setOracleUpdater(_oracleUpdater);
+ }
+
+ /***************************************
+ Internal Setters
+ ****************************************/
+
+ /// @notice Sets the contract or account that can add Oracle prices
+ /// @param _oracleUpdater Address of the contract or account that can update the Oracle prices
+ function _setOracleUpdater(address _oracleUpdater) internal {
+ emit SetOracleUpdater(_oracleUpdater);
+ oracleUpdater = _oracleUpdater;
+ }
+
+ /***************************************
+ External Setters
+ ****************************************/
+
+ /// @notice Sets the contract or account that can update the Oracle prices
+ /// @param _oracleUpdater Address of the contract or account that can update the Oracle prices
+ function setOracleUpdater(address _oracleUpdater) external onlyGovernor {
+ _setOracleUpdater(_oracleUpdater);
+ }
+
+ /***************************************
+ Metadata
+ ****************************************/
+
+ /// @notice The number of decimals in the Oracle price.
+ function decimals()
+ external
+ pure
+ virtual
+ override
+ returns (uint8 _decimals)
+ {
+ _decimals = 18;
+ }
+
+ /// @notice The version number for the AggregatorV3Interface.
+ /// @dev Adheres to AggregatorV3Interface, which is different than typical semver
+ function version()
+ external
+ view
+ virtual
+ override
+ returns (uint256 _version)
+ {
+ _version = 1;
+ }
+
+ /***************************************
+ Oracle Receiver
+ ****************************************/
+
+ /// @notice Adds a new Oracle price by the Oracle updater.
+ /// Can not be run twice in the same block.
+ /// @param _price is the Oracle price with 18 decimals
+ function addPrice(uint128 _price) external override {
+ if (msg.sender != oracleUpdater) revert OnlyOracleUpdater();
+ if (_price == 0) revert NoPriceData();
+
+ // Can not add price in the same or previous blocks
+ uint256 _roundsLength = rounds.length;
+ if (
+ _roundsLength > 0 &&
+ block.timestamp <= rounds[_roundsLength - 1].timestamp
+ ) {
+ revert AddPriceSameBlock();
+ }
+
+ lastCorrectRoundId = uint80(_roundsLength);
+
+ rounds.push(
+ Round({ price: _price, timestamp: uint40(block.timestamp) })
+ );
+ }
+
+ /***************************************
+ Prices
+ ****************************************/
+
+ function _getRoundData(uint80 _roundId)
+ internal
+ view
+ returns (
+ uint80 roundId,
+ int256 answer,
+ uint256 startedAt,
+ uint256 updatedAt,
+ uint80 answeredInRound
+ )
+ {
+ if (rounds.length <= _roundId) revert NoPriceData();
+
+ Round memory _round = rounds[_roundId];
+ answer = int256(uint256(_round.price));
+
+ roundId = answeredInRound = _roundId;
+ startedAt = updatedAt = _round.timestamp;
+ }
+
+ /// @notice Returns the Oracle price data for a specific round.
+ /// @param _roundId The round ID
+ /// @return roundId The round ID
+ /// @return answer The Oracle price
+ /// @return startedAt Timestamp of when the round started
+ /// @return updatedAt Timestamp of when the round was updated
+ /// @return answeredInRound The round ID in which the answer was computed
+ function getRoundData(uint80 _roundId)
+ external
+ view
+ override
+ returns (
+ uint80 roundId,
+ int256 answer,
+ uint256 startedAt,
+ uint256 updatedAt,
+ uint80 answeredInRound
+ )
+ {
+ (
+ roundId,
+ answer,
+ startedAt,
+ updatedAt,
+ answeredInRound
+ ) = _getRoundData(_roundId);
+ }
+
+ /// @notice Returns the latest Oracle price data.
+ /// @return roundId The round ID
+ /// @return answer The Oracle price
+ /// @return startedAt Timestamp of when the round started
+ /// @return updatedAt Timestamp of when the round was updated
+ /// @return answeredInRound The round ID in which the answer was computed
+ function latestRoundData()
+ external
+ view
+ override
+ returns (
+ uint80 roundId,
+ int256 answer,
+ uint256 startedAt,
+ uint256 updatedAt,
+ uint80 answeredInRound
+ )
+ {
+ (
+ roundId,
+ answer,
+ startedAt,
+ updatedAt,
+ answeredInRound
+ ) = _getRoundData(lastCorrectRoundId);
+ }
+
+ /***************************************
+ Errors
+ ****************************************/
+
+ error AddPriceSameBlock();
+ error NoPriceData();
+ error OnlyOracleUpdater();
+}
diff --git a/contracts/contracts/oracle/IOracleReceiver.sol b/contracts/contracts/oracle/IOracleReceiver.sol
new file mode 100644
index 0000000000..b943e9b987
--- /dev/null
+++ b/contracts/contracts/oracle/IOracleReceiver.sol
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+interface IOracleReceiver {
+ function addPrice(uint128 price) external;
+}
diff --git a/contracts/contracts/oracle/OETHOracle.sol b/contracts/contracts/oracle/OETHOracle.sol
new file mode 100644
index 0000000000..6092ce5244
--- /dev/null
+++ b/contracts/contracts/oracle/OETHOracle.sol
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import { BaseOracle } from "./BaseOracle.sol";
+
+/**
+ * @title OETH Oracle
+ * @notice Chainlink style oracle for OETH/ETH
+ * @author Origin Protocol Inc
+ */
+contract OETHOracle is BaseOracle {
+ string public constant override description = "OETH / ETH";
+
+ /**
+ * @param _oracleUpdater Address of the contract that is authorized to add prices
+ */
+ constructor(address _oracleUpdater) BaseOracle(_oracleUpdater) {}
+}
diff --git a/contracts/contracts/oracle/OETHOracleUpdater.sol b/contracts/contracts/oracle/OETHOracleUpdater.sol
new file mode 100644
index 0000000000..ecafe0484c
--- /dev/null
+++ b/contracts/contracts/oracle/OETHOracleUpdater.sol
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import { IOracleReceiver } from "./IOracleReceiver.sol";
+import { IVault } from "../interfaces/IVault.sol";
+import { Governable } from "../governance/Governable.sol";
+import { ICurvePool } from "../strategies/ICurvePool.sol";
+
+/**
+ * @title OETH Oracle Updater
+ * @notice Gathers on-chain OETH pricing data and updates the OETHOracle contract.
+ * @author Origin Protocol Inc
+ */
+contract OETHOracleUpdater is Governable {
+ /// @notice Max OETH price when redeeming via the vault to 18 decimals.
+ /// The vault charges a 0.5% withdraw fee and the oracle prices of
+ /// the vault collateral assets are capped at 1 so the max price is 0.995.
+ /// @dev A new OETHOracleUpdater needs to be deployed if the vault withdraw fee changes.
+ uint256 public constant MAX_VAULT_PRICE = 995e15;
+
+ /// @notice The OETH/ETH Curve pool
+ ICurvePool public immutable curvePool;
+ /// @notice The OETH Vault
+ IVault public immutable vault;
+
+ struct OracleUpdaterConfig {
+ address vault;
+ address curvePool;
+ }
+
+ event AddPrice(
+ address indexed oracle,
+ uint256 answer,
+ uint256 vaultPrice,
+ uint256 marketPrice
+ );
+
+ /**
+ * @param _vault Address of the OETH Vault
+ * @param _curvePool Address of the OETH/ETH Curve pool
+ */
+ constructor(address _vault, address _curvePool) Governable() {
+ curvePool = ICurvePool(_curvePool);
+ vault = IVault(_vault);
+ }
+
+ /// @notice Adds a new on-chain, aggregated OETH/ETH price to 18 decimals to the specified Oracle.
+ /// @dev Callable by anyone as the prices are sourced and aggregated on-chain.
+ /// @param oracle Address of the Oracle that has authorized this contract to add prices.
+ function addPrice(IOracleReceiver oracle) external {
+ (
+ uint256 answer,
+ uint256 vaultPrice,
+ uint256 marketPrice
+ ) = _getPrices();
+
+ emit AddPrice(address(oracle), answer, vaultPrice, marketPrice);
+
+ // Add the new aggregated price to the oracle.
+ // Authorization is handled on Oracle side
+ oracle.addPrice(uint128(answer));
+ }
+
+ /**
+ * @dev Gets the OETH/ETH market price, vault floor price and aggregates to a OETH/ETH price to 18 decimals.
+ */
+ function _getPrices()
+ internal
+ view
+ returns (
+ uint256 answer,
+ uint256 vaultPrice,
+ uint256 marketPrice
+ )
+ {
+ // Get the aggregated market price from on-chain DEXs
+ marketPrice = _getMarketPrice();
+
+ // If market price is equal or above the vault price with the withdraw fee
+ if (marketPrice >= MAX_VAULT_PRICE) {
+ answer = marketPrice;
+ // Avoid getting the vault price as this is gas intensive.
+ // its not going to be higher than 0.995 with a 0.5% withdraw fee
+ vaultPrice = MAX_VAULT_PRICE;
+ } else {
+ // Get the price from the Vault. This includes the withdraw fee
+ // and the vault collateral assets priced using oracles
+ vaultPrice = vault.floorPrice();
+
+ if (marketPrice > vaultPrice) {
+ // Return the market price with the Vault price as the floor price
+ answer = marketPrice;
+ } else {
+ // Return the vault price
+ answer = vaultPrice;
+ }
+ }
+
+ // Cap the OETH/ETH price at 1
+ if (answer > 1e18) {
+ answer = 1e18;
+ }
+ }
+
+ /**
+ * @dev Gets the market prices from on-chain DEXs.
+ * Currently, this is Curve's OETH/ETH Exponential Moving Average (EMA) oracle.
+ * This can be expended later to support aggregation across multiple on-chain DEXs.
+ * For example, other OETH Curve, Balancer or Uniswap pools.
+ */
+ function _getMarketPrice() internal view returns (uint256 marketPrice) {
+ // Get the EMA oracle price from the Curve pool
+ marketPrice = curvePool.price_oracle();
+ }
+
+ /// @notice Get the latest prices from the OETH Vault and OETH/ETH Curve pool to 18 decimals.
+ /// @return answer the aggregated OETH/ETH price
+ /// @return vaultPrice the vault floor price if the market price is below the max vault floor price
+ /// @return marketPrice the latest market price
+ function getPrices()
+ external
+ view
+ returns (
+ uint256 answer,
+ uint256 vaultPrice,
+ uint256 marketPrice
+ )
+ {
+ (answer, vaultPrice, marketPrice) = _getPrices();
+ }
+}
diff --git a/contracts/contracts/oracle/README.md b/contracts/contracts/oracle/README.md
index b309bbbbf5..5c4feffdf3 100644
--- a/contracts/contracts/oracle/README.md
+++ b/contracts/contracts/oracle/README.md
@@ -14,16 +14,24 @@

-## Mix Oracle
+## OETH Oracle
### Hierarchy
-
+
-### Squashed
+### OETHOracle Squashed
-
+
-### Storage
+### OETHOracle Storage
+
+
+
+### OETHOracleUpdater Squashed
+
+
+
+### OETHOracleUpdater Storage
-
+
diff --git a/contracts/contracts/strategies/ICurvePool.sol b/contracts/contracts/strategies/ICurvePool.sol
index 9f2b5c9a28..ed5ac33f7e 100644
--- a/contracts/contracts/strategies/ICurvePool.sol
+++ b/contracts/contracts/strategies/ICurvePool.sol
@@ -36,4 +36,6 @@ interface ICurvePool {
uint256[3] calldata _amounts,
uint256 maxBurnAmount
) external;
+
+ function price_oracle() external view returns (uint256);
}
diff --git a/contracts/contracts/vault/VaultCore.sol b/contracts/contracts/vault/VaultCore.sol
index efa8f2ca41..cfa4afc241 100644
--- a/contracts/contracts/vault/VaultCore.sol
+++ b/contracts/contracts/vault/VaultCore.sol
@@ -11,13 +11,12 @@ pragma solidity ^0.8.0;
* @author Origin Protocol Inc
*/
-import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { StableMath } from "../utils/StableMath.sol";
import { IOracle } from "../interfaces/IOracle.sol";
import { IGetExchangeRateToken } from "../interfaces/IGetExchangeRateToken.sol";
-
-import "./VaultInitializer.sol";
+import { IStrategy, VaultInitializer } from "./VaultInitializer.sol";
contract VaultCore is VaultInitializer {
using SafeERC20 for IERC20;
@@ -565,6 +564,34 @@ contract VaultCore is VaultInitializer {
Pricing
****************************************/
+ /**
+ * @notice The value (USD or ETH) of the collateral assets received from
+ * redeeming 1 Origin Token (OUSD or OETH) from the Vault.
+ * This is the minimum price for the OToken. A better price is usually achieved by
+ * swapping the OToken on the Curve pool used for Automated Market Operations (AMO).
+ * For OETH, that's the Curve OETH/ETH pool.
+ * For OUSD, that's the Curve OUSD/3Crv pool.
+ * @param price the price to 18 decimals.
+ */
+ function floorPrice() external view virtual returns (uint256 price) {
+ // Get the assets for redeeming 1 OETH
+ // This has already had the redeem fee applied
+ uint256[] memory redeemAssets = _calculateRedeemOutputs(1e18);
+
+ // For each of the redeemed assets
+ for (uint256 i = 0; i < redeemAssets.length; ++i) {
+ // Sum the value of the vault asset = asset amount * oracle price
+ // For OUSD's USDC and USDT assets that are to 6 decimals, the oracle
+ // price is to 18 decimals, so we do not need to scale them up to 18 decimals
+ price +=
+ redeemAssets[i] *
+ IOracle(priceProvider).price(allAssets[i]);
+ }
+
+ // scale back down to 18 decimals as we multiplied two 18 decimals numbers to get the value.
+ price = price / 1e18;
+ }
+
/**
* @notice Returns the total price in 18 digit units for a given asset.
* Never goes above 1, since that is how we price mints.
@@ -662,15 +689,15 @@ contract VaultCore is VaultInitializer {
function _toUnitPrice(address _asset, bool isMint)
internal
view
- returns (uint256 price)
+ returns (uint256 price_)
{
UnitConversion conversion = assets[_asset].unitConversion;
- price = IOracle(priceProvider).price(_asset);
+ price_ = IOracle(priceProvider).price(_asset);
if (conversion == UnitConversion.GETEXCHANGERATE) {
uint256 exchangeRate = IGetExchangeRateToken(_asset)
.getExchangeRate();
- price = (price * 1e18) / exchangeRate;
+ price_ = (price_ * 1e18) / exchangeRate;
} else if (conversion != UnitConversion.DECIMALS) {
revert("Unsupported conversion type");
}
@@ -679,23 +706,23 @@ contract VaultCore is VaultInitializer {
* so the price checks are agnostic to underlying asset being
* pegged to a USD or to an ETH or having a custom exchange rate.
*/
- require(price <= MAX_UNIT_PRICE_DRIFT, "Vault: Price exceeds max");
- require(price >= MIN_UNIT_PRICE_DRIFT, "Vault: Price under min");
+ require(price_ <= MAX_UNIT_PRICE_DRIFT, "Vault: Price exceeds max");
+ require(price_ >= MIN_UNIT_PRICE_DRIFT, "Vault: Price under min");
if (isMint) {
/* Never price a normalized unit price for more than one
* unit of OETH/OUSD when minting.
*/
- if (price > 1e18) {
- price = 1e18;
+ if (price_ > 1e18) {
+ price_ = 1e18;
}
- require(price >= MINT_MINIMUM_UNIT_PRICE, "Asset price below peg");
+ require(price_ >= MINT_MINIMUM_UNIT_PRICE, "Asset price below peg");
} else {
/* Never give out more than 1 normalized unit amount of assets
* for one unit of OETH/OUSD when redeeming.
*/
- if (price < 1e18) {
- price = 1e18;
+ if (price_ < 1e18) {
+ price_ = 1e18;
}
}
}
diff --git a/contracts/contracts/vault/VaultStorage.sol b/contracts/contracts/vault/VaultStorage.sol
index 066754e38f..b71179b69c 100644
--- a/contracts/contracts/vault/VaultStorage.sol
+++ b/contracts/contracts/vault/VaultStorage.sol
@@ -72,6 +72,7 @@ contract VaultStorage is Initializable, Governable {
// slither-disable-next-line uninitialized-state
mapping(address => Asset) internal assets;
/// @dev list of all assets supported by the vault.
+ // slither-disable-next-line uninitialized-state
address[] internal allAssets;
// Strategies approved for use by the Vault
diff --git a/contracts/deploy/001_core.js b/contracts/deploy/001_core.js
index 49a38b9a5a..4082032235 100644
--- a/contracts/deploy/001_core.js
+++ b/contracts/deploy/001_core.js
@@ -1191,6 +1191,50 @@ const deployOUSDSwapper = async () => {
await vault.connect(sGovernor).setOracleSlippage(assetAddresses.USDT, 50);
};
+/**
+ * Deploy the OETHOracle contracts.
+ */
+const deployOETHOracle = async () => {
+ const { deployerAddr, governorAddr } = await getNamedAccounts();
+ const sDeployer = await ethers.provider.getSigner(deployerAddr);
+ const sGovernor = await ethers.provider.getSigner(governorAddr);
+
+ const oeth = await ethers.getContract("OETHProxy");
+ await deployWithConfirmation("MockCurveOethEthPool", [
+ [oeth.address, addresses.ETH],
+ ]);
+ const curveOethEthPool = await ethers.getContract("MockCurveOethEthPool");
+
+ const vault = await ethers.getContract("MockVault");
+
+ await deployWithConfirmation("OETHOracleUpdater", [
+ vault.address,
+ curveOethEthPool.address,
+ ]);
+ const cOETHOracleUpdater = await ethers.getContract("OETHOracleUpdater");
+
+ await withConfirmation(
+ cOETHOracleUpdater.connect(sDeployer).transferGovernance(governorAddr)
+ );
+ await withConfirmation(
+ cOETHOracleUpdater
+ .connect(sGovernor) // Claim governance with governor
+ .claimGovernance()
+ );
+
+ await deployWithConfirmation("OETHOracle", [cOETHOracleUpdater.address]);
+ const cOETHOracle = await ethers.getContract("OETHOracle");
+
+ await withConfirmation(
+ cOETHOracle.connect(sDeployer).transferGovernance(governorAddr)
+ );
+ await withConfirmation(
+ cOETHOracle
+ .connect(sGovernor) // Claim governance with governor
+ .claimGovernance()
+ );
+};
+
const main = async () => {
console.log("Running 001_core deployment...");
await deployOracles();
@@ -1216,6 +1260,7 @@ const main = async () => {
await deployWOusd();
await deployOETHSwapper();
await deployOUSDSwapper();
+ await deployOETHOracle();
console.log("001_core deploy done.");
return true;
};
diff --git a/contracts/deploy/081_oeth_oracle.js b/contracts/deploy/081_oeth_oracle.js
new file mode 100644
index 0000000000..e2eedf3d4a
--- /dev/null
+++ b/contracts/deploy/081_oeth_oracle.js
@@ -0,0 +1,80 @@
+const addresses = require("../utils/addresses");
+const {
+ deploymentWithGovernanceProposal,
+ withConfirmation,
+} = require("../utils/deploy");
+
+module.exports = deploymentWithGovernanceProposal(
+ {
+ deployName: "081_oeth_oracle",
+ forceDeploy: false,
+ // forceSkip: true,
+ reduceQueueTime: false,
+ deployerIsProposer: true,
+ // proposalId: "",
+ },
+ async ({ ethers, deployWithConfirmation }) => {
+ const { deployerAddr } = await getNamedAccounts();
+ const sDeployer = await ethers.provider.getSigner(deployerAddr);
+
+ // 1. Connect to the OETH Vault as its governor via the proxy
+ const cVaultProxy = await ethers.getContract("OETHVaultProxy");
+
+ // 2. Deploy the new Vault implementation
+ const dVaultCore = await deployWithConfirmation("OETHVaultCore");
+
+ // 3. Deploy the new Oracle contracts
+ const dOETHOracleUpdater = await deployWithConfirmation(
+ "OETHOracleUpdater",
+ [cVaultProxy.address, addresses.mainnet.CurveOETHMetaPool]
+ );
+ const cOETHOracleUpdater = await ethers.getContractAt(
+ "OETHOracleUpdater",
+ dOETHOracleUpdater.address
+ );
+ const dOETHOracle = await deployWithConfirmation("OETHOracle", [
+ dOETHOracleUpdater.address,
+ ]);
+ const cOETHOracle = await ethers.getContractAt(
+ "OETHOracle",
+ dOETHOracle.address
+ );
+
+ // 4. Transfer governance
+ await withConfirmation(
+ cOETHOracleUpdater
+ .connect(sDeployer)
+ .transferGovernance(addresses.mainnet.Timelock)
+ );
+ await withConfirmation(
+ cOETHOracle
+ .connect(sDeployer)
+ .transferGovernance(addresses.mainnet.Timelock)
+ );
+
+ // 4. Governance Actions
+ return {
+ name: "Upgrade the OETH Vault and deploy the OETH Oracle and Oracle Updater.",
+ actions: [
+ // 1. Upgrade the OETH Vault proxy to the new core vault implementation
+ {
+ contract: cVaultProxy,
+ signature: "upgradeTo(address)",
+ args: [dVaultCore.address],
+ },
+ // 2. Accept governance for OETHOracleUpdater
+ {
+ contract: cOETHOracleUpdater,
+ signature: "claimGovernance()",
+ args: [],
+ },
+ // 3. Accept governance for OETHOracle
+ {
+ contract: cOETHOracle,
+ signature: "claimGovernance()",
+ args: [],
+ },
+ ],
+ };
+ }
+);
diff --git a/contracts/docs/AggregatorV3Interface.svg b/contracts/docs/AggregatorV3Interface.svg
new file mode 100644
index 0000000000..061050d054
--- /dev/null
+++ b/contracts/docs/AggregatorV3Interface.svg
@@ -0,0 +1,28 @@
+
+
+
+
+
diff --git a/contracts/docs/OETHOracleHierarchy.svg b/contracts/docs/OETHOracleHierarchy.svg
new file mode 100644
index 0000000000..bb1363581e
--- /dev/null
+++ b/contracts/docs/OETHOracleHierarchy.svg
@@ -0,0 +1,60 @@
+
+
+
+
+
diff --git a/contracts/docs/OETHOracleRouterHierarchy.svg b/contracts/docs/OETHOracleRouterHierarchy.svg
index 1eefc3d304..d0122deb8e 100644
--- a/contracts/docs/OETHOracleRouterHierarchy.svg
+++ b/contracts/docs/OETHOracleRouterHierarchy.svg
@@ -4,78 +4,44 @@
-