OpenEden Disclosed Report

total supply cap is only enforced on mint, not when the bonus multiplier is increased

Company
Created date
Oct 11 2025

Target

https://github.com/OpenEdenHQ/openeden.usdoexpress.audit/tree/f3f31d2ac15e3253cba342229f9d05495f95d6fd

Vulnerability Details

yo,

in USDO.sol, the total supply cap is only enforced on mint, not when the bonus multiplier is increased (the daily “rebase”), because totalSupply() is computed as convertToTokens(_totalShares) and that conversion multiplies by bonusMultiplier, any positive multiplier bump can push totalSupply() above _totalSupplyCap without a revert, breaking the intuitive invariant “total supply ≤ cap” and potentially blocking subsequent mints until the cap is manually raised, this behavior is not documented as intended in the public docs i reviewed,

  • USDO rebases daily via a “bonus multiplier”; balances are shares * bonusMultiplier and the multiplier is programmatically updated daily so holders “should expect to see their balances increase daily” (https://docs.openeden.com/usdo/usdo-token/bonus-multiplier)

  • the USDO docs include instant/general minting flows; if the cap is unintentionally breached by rebases, later mints will revert (availability impact to “instant mint”) (https://docs.openeden.com/usdo/usdo-token/minting-workflow)

probamitical code

main state & formulas

  • totalSupply() = convertToTokens(_totalShares) = (_totalShares * bonusMultiplier) / _BASE , increasing bonusMultiplier increases every balance and the total supply
  • cap storage: _totalSupplyCap with totalSupplyCap() getter and updateTotalSupplyCap() (admin)

where the cap is enforced (only on mint):

  • _mint() -> checkNewTotalSupply(amount) -> computes newTotalSupply = convertToTokens(_totalShares + shares) and reverts with USDOExceedsTotalSupplyCap if newTotalSupply > _totalSupplyCap , no other place enforces the cap

where the cap is not enforced (the crux):

  • _updateBonusMultiplier(uint256 _bonusMultiplier) requires only _bonusMultiplier >= _BASE and updates bonusMultiplier; no cap check here, exposed via:

    • updateBonusMultiplier (DEFAULT_ADMIN_ROLE)
    • addBonusMultiplier (MULTIPLIER_ROLE), which solely increases the multiplier

implication: if _totalShares > 0 and cap was set near current totalSupply, then any daily positive rebase (expected per docs) can make totalSupply() exceed _totalSupplyCap silently, from then on, any further mint will fail (since checkNewTotalSupply uses the now‑higher multiplier), effectively blocking mints until the admin increases the cap

impacts:

  • invariant / promise break: totalSupply() can exceed _totalSupplyCap after routine rebase updates. Nothing reverts when this happens
  • Availability / DoS on mint path: after the cap is exceeded by a rebase, all further mints revert until the cap is raised, affecting the “Instant Mint” product path (https://docs.openeden.com/usdo/usdo-token/minting-workflow)
  • controls risk / misreporting: If the issuer or integrators treat the cap as a hard upper bound (e.g., for dashboards, risk limits, or compliance), daily rebase can silently violate that assumption, causing functional misreporting or internal policy breaches,

Validation steps

scnerio:

  1. assume _totalShares = S and bonusMultiplier = 1e18 (_BASE), let _totalSupplyCap = C just above current convertToTokens(S)

  2. after a day, issuer increments multiplier: addBonusMultiplier(delta), so bonusMultiplier’ = 1e18 + delta

Observation:

  1. anyone can read:

    • USDO.totalSupply() -> now (S * (1e18 + delta)) / 1e18, which may be > C
    • USDO.totalSupplyCap() -> still C

this proves the invariant break on‑chain (view calls)

  1. secondary effect (operational):

an “instant mint” attempt (by an authorized client) reverts inside USDO.mint() because checkNewTotalSupply compares post‑mint supply vs. cap using the increased multiplier, this contradicts the expectation that instant mint is steadily available (unless the issuer updates the cap first)

poc:

goal: show totalSupply > totalSupplyCap after a routine multiplier increase

  1. (Admin, one‑time) updateTotalSupplyCap(C) set to a value slightly above current supply

  2. (Admin, optional) mint(alice, X) so that totalSupply is close to C

  3. (Admin, daily routine) addBonusMultiplier(delta) with delta > 0

  4. (anyone, read‑only) check:

    • USDO.totalSupply() -> now strictly larger (due to rebase)
    • USDO.totalSupplyCap() -> unchanged at C
    • if totalSupply() > C, the cap has been breached without any revert
  5. (authorized minter) attempt fresh mint through instant Mint path -> reverts inside USDO.mint() via USDOExceedsTotalSupplyCap (https://docs.openeden.com/usdo/usdo-token/minting-workflow)

Attachments

hidden
CommentsReport History
Comments on this report are hidden
Details
Statedisclosed
Severity
Medium
Bounty$27
Visibilitypartially
VulnerabilityOther
Participants (4)
company admin
author
triage team
triage team