aere-contracts/contracts/oracle/AereOracleAdapter.sol
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
Aere Network public source. Everything here can be checked against the live
chain (chain id 2800, https://rpc.aere.network).

Scope note, stated up front rather than buried: consensus on chain 2800 is
classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at
the signature, precompile, account and transport layers. Nothing here makes the
consensus post-quantum, and no document in it should be read as claiming so.
2026-07-20 01:02:37 +03:00

122 lines
4.7 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "@openzeppelin/contracts/access/Ownable.sol";
import "./AerePyth.sol";
/// Existing AereOracle interface (median multi-reporter feed, deployed 2026-05-07).
interface IAereOracleLegacy {
function getPrice(bytes32 symbol) external view returns (uint128 price, uint64 timestamp, uint256 contributors);
}
/**
* @title AereOracleAdapter
* @notice Unified price-query interface for AERE Network.
*
* Bridges between:
* - Legacy AereOracle (single multi-reporter median feed for BTC/USD + ETH/USD)
* - AerePyth (Pyth-compatible pull oracle; 700+ feeds when public Pyth network onboards us)
*
* Routing logic (per symbol):
* - If a Pyth feed ID is registered for the symbol, prefer Pyth (newer, more
* trustworthy, signed by publisher set).
* - Else fall back to legacy AereOracle.
* - If neither has the symbol, revert.
*
* This adapter lets every AERE dApp keep using a single oracle address and one
* `getPrice(symbol)` signature, while the underlying data source migrates
* from AereOracle → AerePyth → canonical Pyth as the network matures.
*
* Symbol convention (bytes32):
* keccak256("BTC/USD"), keccak256("ETH/USD"), keccak256("AERE/USD"), etc.
*/
contract AereOracleAdapter is Ownable {
IAereOracleLegacy public legacy;
AerePyth public pyth;
/// symbol (keccak256) → Pyth price feed ID. If unset, fall back to legacy.
mapping(bytes32 => bytes32) public pythFeedId;
/// Maximum age (seconds) accepted for legacy oracle reads.
uint256 public legacyMaxAge = 600;
event PythRegistered(bytes32 indexed symbol, bytes32 indexed pythFeedId);
event SourceUpdated(string source, address indexed addr);
constructor(IAereOracleLegacy _legacy, AerePyth _pyth) {
legacy = _legacy;
pyth = _pyth;
}
// ───────────────────── Admin ─────────────────────
function setLegacy(IAereOracleLegacy _legacy) external onlyOwner {
legacy = _legacy;
emit SourceUpdated("legacy", address(_legacy));
}
function setPyth(AerePyth _pyth) external onlyOwner {
pyth = _pyth;
emit SourceUpdated("pyth", address(_pyth));
}
function registerPythFeed(bytes32 symbol, bytes32 feedId) external onlyOwner {
pythFeedId[symbol] = feedId;
emit PythRegistered(symbol, feedId);
}
function setLegacyMaxAge(uint256 _age) external onlyOwner {
legacyMaxAge = _age;
}
// ───────────────────── Unified read ─────────────────────
/**
* @notice Universal price read.
* @return price1e8 Price scaled to 1e8 (same as AereOracle's format).
* @return updatedAt unix seconds.
* @return source "pyth" or "legacy".
*/
function quote(bytes32 symbol) external view returns (uint256 price1e8, uint256 updatedAt, string memory source) {
bytes32 feedId = pythFeedId[symbol];
if (feedId != bytes32(0)) {
PythPrice memory p = pyth.getPriceUnsafe(feedId);
require(p.publishTime > 0, "Adapter: no pyth data");
// Pyth stores price + exponent; normalise to 1e8.
// expo is negative for fractional prices (e.g. -8 for $30000.12345678).
uint256 abs;
if (p.price < 0) {
// Negative price means market closed or invalid; revert.
revert("Adapter: negative price");
}
abs = uint256(uint64(p.price));
int32 expo = p.expo;
if (expo == -8) {
price1e8 = abs;
} else if (expo > -8) {
// expo is e.g. -6 (USDC), -2 (some equities). Scale up.
price1e8 = abs * (10 ** uint256(int256(-8 - expo)));
} else {
// expo is e.g. -12, -18 (some assets). Scale down.
price1e8 = abs / (10 ** uint256(int256(expo + 8)));
}
updatedAt = p.publishTime;
source = "pyth";
return (price1e8, updatedAt, source);
}
// Fall back to legacy AereOracle.
(uint128 lp, uint64 lts, ) = legacy.getPrice(symbol);
require(lp > 0, "Adapter: no legacy data");
require(block.timestamp - uint256(lts) <= legacyMaxAge, "Adapter: legacy stale");
price1e8 = uint256(lp);
updatedAt = uint256(lts);
source = "legacy";
}
/// Convenience: just return the price1e8.
function priceOf(bytes32 symbol) external view returns (uint256 price1e8) {
(price1e8, , ) = this.quote(symbol);
}
}