Oracle Design

The Oracle Adapter is the single source of truth for NAV data, bridging three feed types with unified validation. Each feed has its own trust model and its own staleness and deviation guards, because a government bond price and an off-chain credit report do not deserve the same treatment.

Data sources

FeedSourceTrust modelStalenessDeviation boundBandInterval
Asset prices (USDC/USD)ReflectorDecentralized1 hour2%0.90 to 1.105 min
Private credit NAVOff-chain report, backend, reporter keyCentralized in V1, disclosed7 days5%0.50 to 2.001 hour
Etherfuse bond priceEtherfuse API and on-chainDeterministic48 hoursNone applied0.50 to 2.001 hour

The Reflector USDC/USD feed exists to confirm peg integrity of the vault base asset, not to price the portfolio. The seven-day threshold on private credit NAV reflects the reporting cadence of off-chain credit instruments, which do not update hourly.

Validation pipeline

Every update is checked against the authorized reporter set, the feed's absolute band, monotonic timestamps that are never ahead of ledger time, the feed's minimum interval, and the deviation bound, before it can move state. A stale feed does not degrade quietly: the Vault reverts with OracleStale.

Why a deviation bound is not enough on its own

A deviation bound is relative, and there are two cases it cannot reach.

It cannot reach the first report for a feed, because a bound on a move needs something to move from. Before the band existed, push_nav(i128::MAX) was accepted and became the reference every later bound was measured as a percentage of. The band applies to every value including the first, which is what makes it the outer wall rather than a special case.

And it says nothing about how many reports there can be. Forty pushes of +5%, each one comfortably inside a 500 bps bound, moved a NAV by a factor of seven in forty seconds. The minimum interval limits that, and it is measured in ledger time between accepted values rather than in the timestamps the reporter supplies, because the reporter chooses those and can submit forty of them, a day apart, in the same minute.

The reference point lives in persistent storage rather than temporary. An expired reference does not weaken the monotonicity check, the deviation bound and the rate limit; it removes all three at once, and waiting out a TTL is not an attack anybody has to work at.

Failure modes

FailureImpactMitigation
Reporter offlineWithdrawals and allocations revertDeposits and stakes continue. Admin assigns a backup reporter.
Reporter compromisedFalse NAV pushedThe deviation bound rejects a large single move, the band rejects a value outside the feed's plausible range whatever the move, and the minimum interval stops the bound being walked past by repetition. Above a threshold of 1, a lone reporter also cannot commit a value at all.
Originator misreportsIncorrect NAVBackend reconciliation. Anything above 5% requires admin confirmation.
Reflector offlineDisplay onlyCore operations do not depend on Reflector.

The design principle is that an oracle failure should block state changes rather than corrupt them. Deposits and staking keep working when the reporter is down, because neither depends on a fresh NAV.

V1 to V2

V1 uses a single dedicated reporter key, disclosed as a centralized component. V2 is a multi-reporter quorum, and it now exists in the contract rather than only in this page.

Every feed carries a quorum threshold that defaults to 1, which is V1 exactly: one authorized address, one vote, and it lands. Raising a feed above 1 means a value commits only once that many distinct authorized reporters have submitted the same value for the same round, a round being one feed and one reported timestamp. A reporter gets one vote per round whatever it votes for. Partial agreement moves nothing: two reporters proposing two different values both sit short until enough of them converge on one.

Reaching quorum changes how many reporters have to agree before the guards above run. It does not change whether they run. The staleness, band, deviation and interval checks all bind on the value a round agreed on, unchanged.

Every feed on testnet runs at 1. The contract supports 2-of-3; what it waits on is reporter keys that are genuinely independent, which is an operational decision rather than a deployment one, and claiming the quorum while one key holds all three votes would be worse than not having it.

What the record of who voted is for

A round that commits advances the feed's timestamp, so monotonicity closes that round behind it. A round that is refused moves no state at all, so it stays open. The record of who has already voted is therefore kept on a refusal and dropped on a commit, which looks asymmetric and is the only version that works.

Dropping it on a refusal undoes the feature. A reporter seeds a value of its own, waits for the round to be refused on some other value, votes for its own a second time and reaches a quorum of two alone. That was the state of this code when it was first written, it committed the seeded value in a test, and the test that now asserts the refusal is what holds the line. A threshold of 1 is exempt, by definition rather than by exception, since one vote is the whole round.

What a quorum does not cover

It defends against one compromised or malfunctioning reporter. It does not defend against a colluding majority of a feed's reporter set, because collusion and honest agreement are the same thing on-chain, and it does not defend against reporters that are all honest and all reading the same wrong upstream source. Raising the threshold buys independence between reporters, not correctness of what they report.

Feed guards are write-once: a feed whose parameters need retuning gets a new feed id, so the change is visible to every consumer instead of silently loosening a bound under an unchanged name.