Ondo Finance

Ondo Finance

Cantina Security Report

Organization

@Ondofinance

Engagement Type

Cantina Reviews

Period

-

Researchers


Findings

Low Risk

1 findings

0 fixed

1 acknowledged

Informational

4 findings

2 fixed

2 acknowledged


Low Risk1 finding

  1. Contract deviations from Notion spec

    State

    Acknowledged

    Severity

    Severity: Low

    Submitted by

    r0bert


    Description

    Several behaviors differ from the stated design for the Chainlink-integrated sValue oracle:

    • Pause lead time not enforced: scheduleCorporateActionPause allows immediate or any future pause start; there is no 24h-in-advance requirement.
    • Freeze-at-pause-start support incomplete: getSValue only returns (sValue, paused) and does not include pauseStartTime, pendingSValue, or any price-at-pause-start. assetData is public, so callers can read pauseStartTime/pendingSValue directly, but there is no built-in “freeze price at pause start” mechanism.
    • Proportional drift over elapsed time not implemented: the code uses a flat per-update cap allowedDriftBps plus driftCooldown, not the “min(1%, 1% * timeDelta/24h)” proportional rule from the spec.
    • Non-reverting read not honored: getSValue reverts on unknown assets instead of returning a sentinel, contradicting the “oracle should never revert execution” assumption (though acceptable if all assets are preconfigured and calls are gated).
    • Pause duration floor too low for intended freeze: minimum pause can be set to 600s; spec expectation implies a stronger freeze window. Admin can reduce the safety window via setMinimumPauseDuration.
    • Pause metadata not surfaced in getSValue: pending values and pause start are not in the primary read API. Consumers must read assetData directly. If the intended API is only getSValue, this omits needed context for off-chain logic.

    Recommendation

    Consider aligning the contract with the published spec: enforce a minimum lead time for pauses; expose pauseStartTime and pendingSValue (or price-at-pause-start) in the read API to support freeze semantics, implement proportional drift relative to elapsed time, make reads non-reverting with sentinel values for unknown assets (if required by ops), raise the minimum pause floor to match the desired freeze window and surface pause metadata in the primary read path or document reliance on assetData. Document any intentional deviations if kept.

    Ondo Finance: Acknowledged. The original design document is somewhat out of date but we confirm that the current implementation reflects the intended and up to date spec.

Informational4 findings

  1. Unused custom error

    State

    Fixed

    PR #500

    Severity

    Severity: Informational

    Submitted by

    r0bert


    Description

    The interface declares an UnknownAsset() error but the SyntheticSharesOracle contract implementation never uses it.

    Recommendation

    Either remove the unused UnknownAsset error from the interface or use it consistently for missing-asset checks to align the ABI with the on-chain behavior.

    Ondo Finance: Fixed in PR #500.

    Cantina: Fix verified.

  2. getSValueBatch reverts on unknown asset

    State

    Acknowledged

    Severity

    Severity: Informational

    Submitted by

    r0bert


    Description

    getSValueBatch calls _getSValue for each asset and will revert if any asset is missing (AssetNotFound). This makes the batch all-or-nothing. A single unknown asset aborts the entire call. If the intended behavior is to retrieve what is available and skip unknown entries, this revert pattern prevents partial results.

    Recommendation

    If partial results are desired, return a sentinel/flag per index (e.g., sValue=0, paused=false and a boolean found) instead of reverting, or pre-check existence and skip/zero-fill unknown assets while still completing the batch.

    Ondo Finance: Acknowledged. Reverting getSValueBatch upon nonexistent asset is the desirable behavior. Updated the design doc to reflect this.

  3. Pause flag is advisory only

    State

    Acknowledged

    Severity

    Severity: Informational

    Submitted by

    r0bert


    Description

    Corporate-action pauses only toggle a boolean returned by getSValue and have no onchain enforcement on price usage. The read path surfaces paused but does not block consumption of sValue:

    function _getSValue(address asset) internal view returns (uint128 sValue, bool paused) {    Asset storage a = assetData[asset];    if (a.sValue == 0) revert AssetNotFound();    sValue = a.sValue;    paused = _isPauseActive(a);}

    If integrators fetch only sValue and ignore paused, they will continue using stale or unverified multipliers during corporate events, defeating the pause intent. Because of this downstream systems that omit the pause check can ingest and act on prices that should be withheld during corporate actions.

    Recommendation

    Require downstream consumers to gate price usage on the paused flag and alert/fail closed when true. Alternatively, add onchain checks in calling contracts to revert or refuse price use when paused is true so accidental consumption is prevented.

    Ondo Finance: Acknowledged. This is core to the design and will require downstream consumers to understand the meaning of "paused" in this context.

  4. Setter path cannot be frozen via params

    State

    Fixed

    PR #501

    Severity

    Severity: Informational

    Submitted by

    r0bert


    Description

    The setter path always permits some upward drift because allowedDriftBps must be > 0; setDriftParameters rejects allowedDriftBps == 0. Operationally, this means you cannot freeze nominal updates via parameters without invoking the admin-driven pause/corporate-action flow. If the setter role is suspected compromised or you want to halt routine bumps, there is no parameter-only way to stop it.

    Recommendation

    Consider allowing allowedDriftBps = 0 to freeze the nominal update path when desired, or add an explicit freeze flag for the setter role. Retain existing bounds checks otherwise.

    Ondo Finance: Fixed in PR #501.

    Cantina: Fix verified.