Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Valid

Protocol Can Lose USDC funds during redemption Due to Decimal Miscalculation

Summary

In the ZENO contract, when users trade in their ZENO tokens for USDC after the maturity date, they get way too much USDC more than they should because the contract doesn’t adjust for the difference in how ZENO and USDC count their decimals.

Vulnerability Details

The ZENO contract inherits from OpenZeppelin’s ERC20 implementation, which assigns it 18 decimals by default, meaning 1 ZENO token is represented as 10^18 ZENO wei. In contrast, USDC, a standard ERC-20 token, uses 6 decimals, so 1 USDC token is 10^6 USDC wei. This difference is normal in blockchain tokens, but the ZENO contract doesn’t account for it when giving back USDC.

In the redeem function, the contract takes the ZENO amount a user wants to cash in, burns those ZENO tokens, and sends the same number of units in USDC.

function redeem(uint amount) external nonReentrant {
if (!isRedeemable()) {
revert BondNotRedeemable();
}
if (amount == 0) {
revert ZeroAmount();
}
uint256 totalAmount = balanceOf(msg.sender);
if (amount > totalAmount) {
revert InsufficientBalance();
}
totalZENORedeemed += amount;
_burn(msg.sender, amount);
USDC.safeTransfer(msg.sender, amount);
}

The line USDC.safeTransfer(msg.sender, amount) uses the same amount burned from ZENO and sends it as USDC. The protocol says ZENO is a “zero-coupon bond fully backed by USDC,” and users should “convert their ZENO tokens back into USDC” at maturity. That sounds like 1 ZENO should equal 1 USDC, but the code doesn’t do that.

The mint function just creates ZENO based on what the auction tells it, with no sign of scaling:

function mint(address to, uint256 amount) external onlyOwner {
if (amount == 0) {
revert ZeroAmount();
}
_mint(to, amount);
totalZENOMinted += amount;
}

Impact

  1. The first user to redeem could take all the USDC in the contract, leaving nothing for others. For 1 ZENO, they’d get trillions of USDC, far more than the contract could hold.

  2. Once the USDC runs out, redemptions fail, and the whole bond system stops working, hurting the real estate financing goal.

Tools Used

Manual Review

Recommendations

Change the USDC transfer to match 1 ZENO token to 1 USDC token. Add this line in redeem and redeemAll

USDC.safeTransfer(msg.sender, amount / 1000000000000); // Divide by 10^12
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Decimal precision mismatch between ZENO token (18 decimals) and USDC (6 decimals) not accounted for in redemption, causing calculation errors and incorrect payments

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!