aere-contracts/contracts/AereOracle.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

101 lines
3.8 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title AereOracle
* @notice Multi-feed price oracle with reporter quorum.
* @dev Reporters submit price updates per symbol (e.g. "AERE/USD", "BTC/USD").
* The on-chain price is the median of the latest submission from each
* authorized reporter, with staleness protection. Designed for use by
* AereSwap, AereStaking liquidations, and consumer dApps.
*/
contract AereOracle is Ownable {
uint8 public constant DECIMALS = 8;
uint64 public maxStaleness = 5 minutes;
address[] public reporters;
mapping(address => bool) public isReporter;
struct Submission {
uint128 price; // scaled to 8 decimals
uint64 timestamp;
}
// symbol => reporter => submission
mapping(bytes32 => mapping(address => Submission)) public submissions;
bytes32[] public symbols;
mapping(bytes32 => bool) private _knownSymbol;
event ReporterAdded(address indexed reporter);
event ReporterRemoved(address indexed reporter);
event PriceSubmitted(bytes32 indexed symbol, address indexed reporter, uint128 price, uint64 timestamp);
event SymbolAdded(bytes32 indexed symbol);
event MaxStalenessUpdated(uint64 newValue);
function addReporter(address r) external onlyOwner {
require(r != address(0) && !isReporter[r], "bad");
isReporter[r] = true;
reporters.push(r);
emit ReporterAdded(r);
}
function removeReporter(address r) external onlyOwner {
require(isReporter[r], "not reporter");
isReporter[r] = false;
for (uint256 i = 0; i < reporters.length; i++) {
if (reporters[i] == r) {
reporters[i] = reporters[reporters.length - 1];
reporters.pop();
break;
}
}
emit ReporterRemoved(r);
}
function setMaxStaleness(uint64 v) external onlyOwner {
require(v >= 30 seconds && v <= 1 hours, "range");
maxStaleness = v;
emit MaxStalenessUpdated(v);
}
function submit(bytes32 symbol, uint128 price) external {
require(isReporter[msg.sender], "not reporter");
require(price > 0, "price");
if (!_knownSymbol[symbol]) { _knownSymbol[symbol] = true; symbols.push(symbol); emit SymbolAdded(symbol); }
submissions[symbol][msg.sender] = Submission(price, uint64(block.timestamp));
emit PriceSubmitted(symbol, msg.sender, price, uint64(block.timestamp));
}
/// @notice Returns the median fresh price for a symbol and the count of contributors.
function getPrice(bytes32 symbol) external view returns (uint128 price, uint64 timestamp, uint256 contributors) {
uint256 n = reporters.length;
uint128[] memory fresh = new uint128[](n);
uint64 newest = 0;
uint256 count = 0;
for (uint256 i = 0; i < n; i++) {
Submission memory s = submissions[symbol][reporters[i]];
if (s.timestamp == 0) continue;
if (block.timestamp - s.timestamp > maxStaleness) continue;
fresh[count++] = s.price;
if (s.timestamp > newest) newest = s.timestamp;
}
require(count > 0, "no fresh data");
// insertion sort (small n in practice)
for (uint256 i = 1; i < count; i++) {
uint128 key = fresh[i];
uint256 j = i;
while (j > 0 && fresh[j - 1] > key) { fresh[j] = fresh[j - 1]; j--; }
fresh[j] = key;
}
price = (count % 2 == 1) ? fresh[count / 2] : uint128((uint256(fresh[count / 2 - 1]) + uint256(fresh[count / 2])) / 2);
return (price, newest, count);
}
function symbolCount() external view returns (uint256) { return symbols.length; }
function reporterCount() external view returns (uint256) { return reporters.length; }
}