OnRe EVM

QuantstampCode version: not recorded (see the report)

Quantstamp reviewed OnRe (OnRe EVM) on 22 Sep 2026: 11 findings, 0 critical; 3 marked fixed, 8 acknowledged.

No open critical findings
Open the reportOpen OnRe’s security record

Report hosted on certificate.quantstamp.com.

Trustblock does not review code.

Findings11

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High000
Medium021
Low062
MediumA Depeg Between Two Listed Stablecoins Can Drain the Redemption ReserveAcknowledged

Every listed asset is treated as worth exactly one dollar by the protocol, regardless of its market price.`PricingDenomination` has one member, `Usd` (`OnReTypes.sol:L4-L6`), every pricer ID resolves through `_usdPricerId(managedToken)` (`OnReIds.sol:L21-L27`), and both conversion functions, `_calculateTokenOutAmount` and `_calculateRedemptionAssetOutAmount` (`OnReMath.sol:L30-L59`), take only the NAV and two decimal exponents. With one listed asset, this does not create a cross-asset arbitrage opportunity, since the asset itself is the unit of account and the NAV scalar cancels across both legs. Once a second asset is listed, the two trade at par inside the protocol even when their market prices differ, so a depeg can become a standing arbitrage opportunity against the redemption reserve. The protocol never observes an external price through an oracle.

MediumCurrent Pricing-Vector APR Is Retroactively AppliedAcknowledged

The `_accrue()` function in `LibOnReBuffer.sol` calculates fee minting based on the time elapsed since the last accrual (`secondsElapsed = timestamp - state.lastAccrualTimestamp`). It fetches a single `currentApr` from the currently active pricing vector via `_currentPricing()` and applies it across this entire time window to calculate the `aprDelta`. While `_setBufferGrossApr()` and `_setBufferFeeConfig()` call `_accrue()` before writing changes, pricing-vector updates do not settle outstanding buffer accrual. Instead, pricing vectors in `LibOnRePricer.sol` advance passively. The `_activePricingVectorIndex()` function simply iterates backwards until it finds a vector where `startTime <= block.timestamp`. Because vectors activate silently based on absolute time, there is no automatic state hook that forces an accrual at the exact moment a new vector begins. If multiple days pass without a user action triggering `_accrue()`, a pricing vector boundary may be crossed silently. When the accrual finally triggers, the contract fetches only the newly active vector's APR and applies it retroactively to the entire `secondsElapsed` window. This effectively overwrites historical time periods with the wrong APR, leading to severe under-minting or over-minting of protocol fees.

LowApprover Removal Is Global and Does Not Survive Re Adding the Same KeyAcknowledged

`removeApprover` is the only revocation primitive for the EIP-712 gate. It is global, in that removing a key invalidates every outstanding attestation that key produced, for every user. However, it is not durable against later adding the same address as an approver. Combined with an `expiry` that has no upper bound (`type(uint64).max` is accepted and the signature can still be executed years later), an approval is effectively a standing authorization over the user's entire future interaction with the protocol. There is no revocation for a single approval and no `ApprovalUsed` event, so neither the operator nor the user can tell which attestations are outstanding.

LowCadence Wave Haircut Manipulation via Dust Transaction SpamAcknowledged

The Prop AMM leverages a cadence wave mechanic (`_cadenceWaveTargetHaircutScaled`) to dynamically penalize rapid, repetitive sell-offs within an active epoch. This penalty scales linearly with `state.currentSellTradeCount` until it hits `state.config.cadenceThreshold`, at which point it enforces the maximum configured cadence haircut (`cadenceWaveScaled`) on all subsequent quotes. However, `LibOnReOffer._previewExecution` only validates that the input amounts are strictly greater than zero, allowing for swaps of 1 wei. Furthermore, `LibOnRePropAmm._recordSell` increments `currentSellTradeCount` by 1 unconditionally for every sell trade, regardless of the token volume. This allows an attacker to exploit the system by spamming negligible "dust" trades. By artificially inflating the trade count at near-zero capital cost, the attacker can force the cadence wave to max out, aggressively penalizing innocent users who subsequently attempt to sell during that epoch.

LowEffective Sell Volume May Be MiscalculatedAcknowledged

The `_previewEffectiveSellVolume()` function calculates the `currentNet` selling pressure, after its neutralized by any existing buying pressure: ```js uint256 currentNet = state.currentSellValueStable > state.currentBuyValueStable ? state.currentSellValueStable - state.currentBuyValueStable : 0; ``` The function ends by adding `currentSellValueStable` to the net pressure so that the quote can price its own impact: ```js return decayedPrevious + effectiveCurrentNet + currentSellValueStable; ``` However, adding `currentSellValueStable` at the end discards potential buying pressure relief, as the `state.currentBuyValueStable` can be discarded rather than decrementing the `currentSellValueStable`.

LowIn Flight CCIP Transfers Can Be BlockedAcknowledged

`ManagedToken` notifies the buffer controller unconditionally on every supply change, and that notification resolves the current pricing, which requires an executable pricer. As a result `setPricerEnabled(false)`, `setManagedTokenEnabled(false)` and `deleteAllPricingVectors` each make `mint`, `burn` and `burnFrom` revert for every holder and every minter, not merely for the route the flag names. The documentation presents the first two as switches at the level of a route and of a token but their actual blast radius is the whole token. Because `ManagedToken` is a Chainlink `IBurnMintERC20`, this reaches cross chain traffic. A CCIP transfer already burned on the source chain cannot be minted on the destination while the flag is set, so value in flight is stranded for the duration rather than merely delayed at the source. The inversion is the notable part: `setKillSwitch(true)`, the switch that exists to freeze the system, leaves `mint` working, while three flags that read as narrow limits on scope stop the token entirely.

LowUnconventional Performance Fee MechanismAcknowledged

The `_calculateFeeSplit()` function divides the minted protocol shares (`bufferMintAmount`) into up to three allocations: `managementAmount`, `performanceAmount`, and `reserveAmount`. The performance fee is only triggered if the high-water mark policy is disabled or when the `currentNav` equals or exceeds the `state.performanceFeeHighWatermark`. However, when this condition evaluates to true, the fee applies the `state.performanceFeeBasisPoints` to the entirety of the `afterManagement` shares, which represents the total yield generated in that period. It does not isolate the excess profits above the watermark. Traditionally, a performance fee is a cut of the returns that strictly exceed a previous peak. In this implementation, the high-water mark acts strictly as a boolean on/off switch.

LowUpgrading an Existing Managed Token Proxy Silently Zeroes `decimals()`Acknowledged

On the upgrade path, it fires only when a proxy deployed against the `OnReToken` implementation that predates the rename (`2120ce6`) is carried across the upgrade rather than deployed fresh. `OnReToken` hard coded `decimals() pure returns (9)` and ended its layout with `address private _bufferController` at slot 5. `ManagedToken` appends `uint8 private _decimals`, written only from `InitializeParams` (`ManagedToken.sol:28,43,65`). Appending is normally the safe upgrade move, but a `uint8` gets no fresh slot, solidity packs it beside the 20 byte address already there. The old implementation only ever wrote bytes 0 to 19 of that word, so byte 20 reads zero and an in place `upgradeToAndCall` leaves `decimals() == 0`.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Part of OnRe’s security record

OnRe
Audits6 audits by 1 firmLast audit Sep 2026, by Quantstamp.
Critical findingsNo open critical findingsAcross 6 reports.
Open OnRe’s security record

Security firm

Quantstamp

236 projects reviewed · 332 reports · latest Oct 2026

See Quantstamp’s audits

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us