aere-contracts/contracts/oracles/AereNavOracleAdapter.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
4.9 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title AereNavOracleAdapter — owner-attested per-asset RWA NAV price feed
* @notice Fixes audit finding F12. AereLendingOracle's FeedType.NavOracle path
* calls `IAereNavOracleAdapter(source).latestForAsset(bytes32)` and
* expects back a per-asset (nav, timestamp) pair it can normalise to an
* 18-decimal collateral price. The deployed AereNavOracle
* (0xC8D12E44f10b03477330b35115b432750831fEBD) does NOT and CANNOT
* satisfy that interface:
*
* - It stores Merkle ROOTS of per-epoch reserve snapshots, not
* per-asset values. Given only a bytes32 asset key you cannot
* recover a value from a root; you would need the full leaf
* preimage (asset, chainId, reserveAmount, decimals) PLUS a Merkle
* proof, and even then `verifyReserve` only returns a boolean.
* - Its leaf field is a total RESERVE AMOUNT (proof-of-backing, e.g.
* "5,000,000 USDC locked on mainnet"), which is a quantity, not a
* per-unit price. It does not map to a lending collateral price.
*
* So a passthrough adapter that "reads" the NavOracle is impossible.
* The honest fix (audit option (a)) is a PURPOSE-BUILT RWA-NAV price
* adapter with an OWNER-SET per-asset value feed — exactly the
* "Foundation-attested NAV pulled ... for RWAs that don't have a live
* market price" that AereLendingOracle's own NatSpec describes.
*
* DESIGN / SAFETY:
* - `setNav` is owner-only (Foundation). The adapter is the upstream
* FEED SOURCE for a NavOracle-type feed. The AereLendingOracle
* layered on top still enforces its own staleness window and
* deviation cap on every read, so a single attested value cannot
* jump the lending price beyond `maxDeviationBps`.
* - `latestForAsset` returns (0,0) for an un-attested asset, which
* AereLendingOracle already treats as InvalidFeedResponse. It never
* returns a future-dated timestamp.
* - NAV values are stored in the adapter's native decimals; the
* consuming feed's `sourceDecimals` handles normalisation. The
* Foundation convention is 18-decimal NAVs (sourceDecimals = 18).
*
* PROVENANCE:
* - `NAV_ORACLE` records the associated AereNavOracle whose public
* Merkle reserve attestations back these assets. It is stored for
* transparency ONLY and is NEVER read to derive a price (see above).
*/
contract AereNavOracleAdapter is Ownable {
/// @notice The associated reserve-attestation oracle (AereNavOracle).
/// Recorded for provenance; NOT read to derive prices (its data is
/// Merkle-rooted reserve totals, not per-asset prices).
address public immutable NAV_ORACLE;
struct Reading {
uint256 nav; // per-asset value in the adapter's native decimals (18 by convention)
uint64 timestamp; // block.timestamp at attestation
bool set;
}
/// @notice assetKey -> latest attested reading.
mapping(bytes32 => Reading) public readings;
/* --------------------------------- events ------------------------------- */
event NavSet(bytes32 indexed assetKey, uint256 nav, uint64 timestamp);
/* --------------------------------- errors ------------------------------- */
error ZeroNav();
/* ------------------------------- constructor ---------------------------- */
/// @param navOracle The associated AereNavOracle (provenance only; may be
/// address(0) if none). Not read for prices.
constructor(address navOracle) {
NAV_ORACLE = navOracle;
}
/* --------------------------------- admin -------------------------------- */
/// @notice Foundation attests the current per-asset NAV for `assetKey`.
/// Stamps `block.timestamp` so downstream staleness checks work.
function setNav(bytes32 assetKey, uint256 nav) external onlyOwner {
if (nav == 0) revert ZeroNav();
readings[assetKey] = Reading({ nav: nav, timestamp: uint64(block.timestamp), set: true });
emit NavSet(assetKey, nav, uint64(block.timestamp));
}
/* --------------------------------- view --------------------------------- */
/// @notice Matches AereLendingOracle's IAereNavOracleAdapter. Returns the
/// latest attested (nav, timestamp) for `assetKey`, or (0,0) if the
/// asset has never been attested (LendingOracle treats that as
/// InvalidFeedResponse).
function latestForAsset(bytes32 assetKey) external view returns (uint256 nav, uint64 timestamp) {
Reading storage r = readings[assetKey];
return (r.nav, r.timestamp);
}
}