This document consolidates every security review conducted against the ARU contracts. The current production design (v2, per-request) completed an external audit with zero findings; the earlier v1 (epoch-batched) review findings are preserved below for the audit trail.
The active production contracts were independently audited by Nethermind Security. The review reported no issues at any severity.
Read the full report → NM-0902 — ARU Reserve (PDF)
| Auditor | Nethermind Security |
| Report | NM-0902 — ARU Reserve, Security Review |
| Date | June 23, 2026 |
| Commit | 1153227 (initial = final — no changes required) |
| Scope | ARU.sol, ARUPriceOracle.sol, ARUSubscriptionManager.sol (578 LoC) |
| Findings | 0 — Critical 0 / High 0 / Medium 0 / Low 0 / Undetermined 0 / Informational 0 / Best Practices 0 |
| Test suite assessment | High (113 tests, all passing) |
| Documentation assessment | Low (in-repo docstrings; the extended docs you're reading now were not part of the review package) |
The audited commit 1153227 is the current master HEAD of aru-evm, so the audited source and the deployed source are identical. On-chain bytecode can be reproduced from that commit — see Contracts & Addresses for the forge inspect procedure.
Scope note: the v2 audit covered only the per-request contracts. The v1 ARUMintRedeemManager (epoch-batched) has since been removed from the codebase and was not part of NM-0902. Its earlier review history follows.
The findings below were identified during internal and external review of the first-generation ARU contracts (ARUMintRedeemManager, epoch-batched) and were all resolved before that design was retired. The active production design is v2 — see Per-Request Flows — which does not carry forward the epoch state machine.
Documented here to preserve the audit trail and inform future reviews.
Issue: In the original completeMints / completeRedemptions, the epoch settled-flag was flipped before the per-AP loop. Any AP omitted from the aps array had non-zero request state that could never be processed. removeAP also rejected settled epochs, closing the only refund path.
Fix: Idempotent batched drain. completeMints removed the pre-loop flag flip; state is drained per-AP across however many batch calls are needed. Price is locked on first drain (epochMintPrice mapping) to prevent admin from approving later batches at a different price. Added closeEpochMints(epoch) which only succeeds once epochTotalMintCollateral[epoch] == 0, flipping the final epochMintSettled flag.
Status: Resolved in 8470866. Regression test: test_removeAP_afterPartialDrain_omittedAPRefundable.
epochTotal* accounting drift (Medium)Issue: Settlement loops never decremented epochTotalMintCollateral or epochTotalRedeemARU on the redeem side. Only refunds decremented. Post-settlement totals were permanently stale.
Fix: Added epochTotalMintCollateral[epoch] -= collateralAmount inside the settlement drain loops. Restores the invariant sum(requests) == epochTotal* at all times.
Status: Resolved in 8470866. Critical for #1's close precondition (epochTotal* == 0) to work.
Issue: The updateEpoch modifier used a while loop iterating once per missed epoch. After long inactivity (~4000+ epochs), catch-up gas exceeded block limit — full DoS.
Fix: Replaced with elapsed / EPOCH_DURATION integer division. Single EpochAdvanced event per advance regardless of skip count. Constant gas.
Status: Resolved in 8470866. No heartbeat keeper needed.
Issue: ARUPriceOracle.stalenessThreshold was stored but never enforced on-chain. Integrators expected Chainlink-compatible strict semantics from the parameter name, but the oracle silently returned stale prices.
Fix: Added latestRoundDataStrict() that reverts PriceStale when block.timestamp > priceTimestamp + stalenessThreshold. Existing latestRoundData() untouched for Chainlink compatibility.
Status: Resolved in 8470866. Makes stalenessThreshold actually load-bearing.
removeAP skip-empty (Low)Issue: Inner loop in removeAP reverted NoRequestInEpoch when an epoch had zero amount for the AP. Admin tooling needed bit-perfect off-chain reconciliation to avoid reverts — operational friction with no security benefit.
Fix: Changed revert NoRequestInEpoch to continue. Admin can pass superset lists.
Status: Resolved in 8470866.
Not a code finding — established explicit threat model covering 8 attack scenarios (T-1 through T-8), roles + powers table, accepted risks, and off-chain invariants the contracts assume. See Threat Model.
Not a code finding — established Foundry invariant-test harness design for future expansion. See Invariant Tests.
removeAP stranded funds after incomplete first call (Low)Issue: Distinct from Finding #5 — after the first removeAP call deactivated the AP (isAP[ap] = false), subsequent calls reverted with NotAnAP. Missed epochs from the first call were permanently stranded.
Fix: Made removeAP idempotent. If AP is active → deactivate + emit. If already inactive → skip deactivation, proceed with refund-cleanup. Removed unused NotAnAP error.
Status: Resolved in d827d85. Regression tests: test_removeAP_nonAP_noop, test_removeAP_idempotent_cleanupMissedEpoch.
requestMint / requestRedeemIssue: External safeTransferFrom fired before mapping writes. nonReentrant protected the flow, but canonical CEI pattern preferred.
Fix: Reordered — state mutations + events before the external call.
Status: Resolved in d827d85.
ARU did not explicitly inherit IARUIssue: The ARU token implemented the IARU interface methods but didn't declare is IARU. Interface drift could go undetected until runtime.
Fix: Added is IARU to the inheritance chain, plus explicit override(ERC20Burnable, IARU) on burn / burnFrom to resolve the public-vs-external visibility ambiguity between the OZ parent and the interface.
Status: Resolved in d827d85.
treasuryAllowance() view returning the live USDC allowance from treasury to the manager. Off-chain monitoring alerts when this drops below expected threshold — catches missed re-approvals after treasury rotation.ARUPriceOracle.setPrice reverts during large price jumps exceeding maxPriceChangeBps. Acknowledged as intended circuit breaker. Runbook: admin temporarily expands maxPriceChangeBps via setMaxPriceChangeBps during catastrophic NAV shifts to un-stick the relayer.107 tests green across:
ARU.t.sol — token, timelocks, permit, transfer, mint/burnARUMintRedeemManager.t.sol — full epoch flow, settlement, AP registry, pause/freeze, refunds, idempotent removeAP, invariant testsARUPriceOracle.t.sol — price updates, rate-of-change, staleness strict + non-strict, AggregatorV3 compliance(The current v2 suite — ARU.t.sol, ARUPriceOracle.t.sol, ARUSubscriptionManager.t.sol — is 113 tests green as verified in the NM-0902 report above.)
The v1 contracts reached production-ready with the above findings resolved. But the epoch model's 24h batching creates structural stress during market volatility:
v2 (per-request, per-AP price-bound) addresses both by decoupling settlement latency from a fixed window. See Per-Request Flows for the active design.
v1 ARUMintRedeemManager has since been removed from the codebase (commit 1153227); the audited production system is v2-only. Its review history is retained above for the audit trail.