OpenEden Disclosed Report

Total Supply Cap Bypass Through Rounding Errors

Company
Created date
Oct 16 2025

Target

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

Vulnerability Details

Description

The protocol can mint tokens above the _totalSupplyCap due to cumulative rounding errors in the token-to-shares conversion process. This breaks a fundamental supply limit mechanism designed to protect the protocol.

Vulnerable Code

USDO.sol - Double Rounding Down

function checkNewTotalSupply(uint256 amount) public view returns (bool, uint256, uint256) {
    //@audit Two rounding downs(bug 1.1 & 1.2) will make it worse
    uint256 shares = convertToShares(amount); // bug 1.1 rounded down

    uint256 newTotalSupply = convertToTokens(_totalShares + shares); // bug 1.2 Also rounded down @audit will permit minting about above _totalSupplyCap

    return (newTotalSupply <= _totalSupplyCap, newTotalSupply, shares);
}

USDOExpressV2.sol - Using Rounded Down Value

function _safeMintInternal(address to, uint256 amt) internal {
    // @audit totalSupply() gets rounded down so it's possible to exceed _totalSupplyCap
    if (_usdo.totalSupply() + amt > _totalSupplyCap) revert TotalSupplyCapExceeded();

    _usdo.mint(to, amt);
}

Root Cause

The issue stems from multiple rounding operations that consistently round down, creating an accumulated error:

  1. First Rounding: convertToShares(amount) rounds down.
  2. Second Rounding: convertToTokens(_totalShares + shares) rounds down due to integer division
  3. Accumulation: Each mint operation adds to the cumulative rounding error
  4. This means they're more tokens than newTotalSupply accounts for since it's a product of muliple rounded down operarions and this inaccurate values is then used to check if newly minted shares cause a surplus in usdo cap.

Impact

Cap Violation: Actual token supply can exceed _totalSupplyCap which is a major invariant for the protocol, leading to loss of trust as well

Example Scenario

// Setup: _totalSupplyCap = 1000000e18, bonusMultiplier = 1.1e18
// Current supply: 999999e18 (just under cap)

// User tries to mint 2e18 tokens
uint256 shares = convertToShares(2e18); // Returns shares for ~1.818e18 tokens
uint256 newSupply = convertToTokens(totalShares + shares); // Returns ~999999.818e18

// Check passes: 999999.818e18 <= 1000000e18 ✓
// But actual minted amount: 2e18
// Actual new supply: 999999e18 + 2e18 = 1000001e18 > cap 

Validation steps

Available soon...

Attachments

hidden
CommentsReport History
Comments on this report are hidden
Details
Statedisclosed
Severity
Low
Bounty$57
Visibilitypartially
VulnerabilityBlockchain
Participants (4)
company admin
author
triage team
triage team