The Oracle Registry is a centralized price oracle system that provides USD valuations for all assets in the Flux protocol. It maps assets to oracle adapters and ensures consistent pricing across all vaults.
interface IOracleRegistry {
/**
* @notice Get USD value for an asset amount
* @param asset The asset address
* @param amount The amount in asset's native decimals
* @return valueUSD The USD value (18 decimals)
*/
function getValueUSD(address asset, uint256 amount)
external view returns (uint256 valueUSD);
/**
* @notice Get price of asset in USD
* @param asset The asset address
* @return priceUSD The price in USD (18 decimals)
*/
function getPriceUSD(address asset)
external view returns (uint256 priceUSD);
/**
* @notice Get oracle adapter for an asset
* @param asset The asset address
* @return oracle The oracle adapter contract
*/
function getOracle(address asset)
external view returns (IPriceOracle oracle);
/**
* @notice Set oracle adapter for an asset (governance only)
* @param asset The asset address
* @param oracle The oracle adapter contract
*/
function setOracle(address asset, IPriceOracle oracle) external;
}
// In ERC20AssetWrapper
function getValue(address account, address subaccount, bytes32 positionId)
external view returns (uint256)
{
// Step 1: Get position balance
uint256 balance = balances[account][subaccount][positionId];
if (balance == 0) return 0;
// Step 2: Query oracle registry via factory
IOracleRegistry registry = factory.oracleRegistry();
// Step 3: Get USD value (registry handles decimals)
return registry.getValueUSD(underlyingAsset, balance);
}
// In OracleRegistry
function getValueUSD(address asset, uint256 amount)
external view returns (uint256)
{
if (amount == 0) return 0;
// Get oracle adapter for this asset
IPriceOracle oracle = oracles[asset];
require(address(oracle) != address(0), "No oracle");
// Get price from adapter
uint256 priceUSD = oracle.getPrice(asset);
// Calculate value accounting for decimals
uint8 decimals = IERC20Metadata(asset).decimals();
return (amount * priceUSD) / (10 ** decimals);
}
interface IPriceOracle {
/**
* @notice Get USD price for an asset
* @param asset The asset address
* @return price The price in USD (18 decimals)
*/
function getPrice(address asset) external view returns (uint256 price);
}
contract ChainlinkOracleAdapter is IPriceOracle {
mapping(address => address) public priceFeeds; // asset → Chainlink feed
function getPrice(address asset) external view returns (uint256) {
address feed = priceFeeds[asset];
require(feed != address(0), "No feed");
(, int256 price,,,) = AggregatorV3Interface(feed).latestRoundData();
require(price > 0, "Invalid price");
// Chainlink feeds are typically 8 decimals
// Scale to 18 decimals
return uint256(price) * 1e10;
}
}
contract UniswapV3TWAPOracle is IPriceOracle {
IUniswapV3Pool public pool;
uint32 public twapPeriod; // e.g., 30 minutes
function getPrice(address asset) external view returns (uint256) {
// Get TWAP from Uniswap pool
(int24 tick,) = OracleLibrary.consult(pool, twapPeriod);
// Convert tick to price
uint256 price = OracleLibrary.getQuoteAtTick(
tick,
uint128(1e18), // 1 token
asset,
WETH
);
// Convert WETH price to USD
return convertToUSD(price);
}
}
contract EulerOracleAdapter is IPriceOracle {
IEulerOracle public eulerOracle;
function getPrice(address asset) external view returns (uint256) {
// Euler provides prices in native quote asset
(uint256 price,) = eulerOracle.getPrice(asset);
return price; // Already 18 decimals
}
}
contract FixedPriceOracle is IPriceOracle {
uint256 public immutable FIXED_PRICE;
constructor(uint256 _price) {
FIXED_PRICE = _price;
}
function getPrice(address) external view returns (uint256) {
return FIXED_PRICE; // e.g., 1e18 for $1 stablecoin
}
}
contract ExchangeRateOracle is IPriceOracle {
function getPrice(address asset) external view returns (uint256) {
// Example: wstETH → stETH exchange rate
if (asset == WSTETH) {
uint256 stEthPerWstEth = IWstETH(WSTETH).stEthPerToken();
uint256 stEthPrice = getStEthPrice(); // Get from another oracle
return (stEthPerWstEth * stEthPrice) / 1e18;
}
revert("Unsupported asset");
}
}
// Governance sets Chainlink adapter for WETH
IPriceOracle chainlinkAdapter = new ChainlinkOracleAdapter(
WETH,
CHAINLINK_ETH_USD_FEED
);
oracleRegistry.setOracle(WETH, chainlinkAdapter);
emit OracleSet(WETH, address(chainlinkAdapter));
// Switch WETH from Chainlink to Pyth
IPriceOracle pythAdapter = new PythOracleAdapter(WETH, PYTH_FEED_ID);
oracleRegistry.setOracle(WETH, pythAdapter);
// All wrappers automatically use new feed on next query
// Check if asset has oracle
IPriceOracle oracle = oracleRegistry.getOracle(WETH);
require(address(oracle) != address(0), "No oracle for WETH");
// Get current price
uint256 price = oracleRegistry.getPriceUSD(WETH);
console.log("WETH price:", price); // e.g., 3000e18 ($3000)
// Get value for amount
uint256 value = oracleRegistry.getValueUSD(WETH, 10e18);
console.log("10 WETH value:", value); // e.g., 30000e18 ($30,000)
function getPrice(address asset) external view returns (uint256) {
try primaryOracle.getPrice(asset) returns (uint256 price) {
return price;
} catch {
// Fall back to secondary oracle
return secondaryOracle.getPrice(asset);
}
}
// During manager unlock callback
vault.unlock(data);
↓
// Manager modifies positions
manager.fluxUnlockCallback(data)
↓
// Vault checks health at end of callback
strategy.evaluateLiquidation(vault, manager)
↓
// Strategy queries wrapper values
for each wrapper in manager.registeredWrappers:
value += wrapper.getValue(vault, manager, positionId)
↓
// Wrapper queries oracle registry
factory.oracleRegistry().getValueUSD(asset, balance)
↓
// Registry queries oracle adapter
oracle.getPrice(asset)
↓
// Strategy compares to liquidation threshold
healthRatio = totalValue / trueDebt
require(healthRatio >= liquidationThreshold)
// Liquidator calls
vault.liquidate(manager, additionalWrappers, data);
↓
// Vault checks position is liquidatable
(bool canLiquidate, uint256 minPayment,) = strategy.evaluateLiquidation(vault, manager);
require(canLiquidate);
↓
// Liquidator pays minPayment
// (calculated using oracle prices)
↓
// Liquidator extracts collateral
vault.locked_withdrawFromWrapper(wrapper, positionId, amount);
// 1. Deploy oracle registry
FluxOracleRegistry registry = new FluxOracleRegistry(governance);
// 2. Deploy oracle adapters
ChainlinkOracleAdapter chainlink = new ChainlinkOracleAdapter();
chainlink.setFeed(WETH, CHAINLINK_ETH_USD);
chainlink.setFeed(WBTC, CHAINLINK_BTC_USD);
chainlink.setFeed(USDC, CHAINLINK_USDC_USD);
// 3. Set oracles in registry
registry.setOracle(WETH, chainlink);
registry.setOracle(WBTC, chainlink);
registry.setOracle(USDC, chainlink);
// 4. Set registry in factory
fluxVaultFactory.setOracleRegistry(registry);
// Now all vaults use this registry
// Developer deploys wrapper for new asset
address newAssetWrapper = assetWrapperFactory.deployERC20Wrapper(newAsset);
// Governance sets oracle for new asset
IPriceOracle oracle = getOrCreateOracle(newAsset);
oracleRegistry.setOracle(newAsset, oracle);
// Governance whitelists wrapper
fluxVaultFactory.whitelistAsset(newAssetWrapper);
// Now vaults can use this asset