Core Contracts

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

Zeno bonds are not redeemed at nominal value. Redeem value is not properly scaled. Users receives more than they should

Summary

Zeno redeedm consider that 1 wei bond is equivalent with 1wei of USDC token.
There are 2 problmes with it:

  • users doesn't redeem the cupon at it's face value (know also as nominal value);

  • the redeem amount is not scaled by Zeno decimals.

    Redeemers will receive an incorrect USDC amount back.

Vulnerability Details

First let's clarify how a zero cupon bond works. According to investopedia:

Zero-coupon bonds are issued at a deep discount and repay the par value at maturity.
The difference between the purchase price and the par value represents the investor's return.

The par value (or face value, or nominal value) is, according to Investopedia:

Face value is a financial term used to describe a security’s nominal or dollar value
as given by its issuer.
For bonds, it’s the amount paid to the holder at maturity. The face value of bonds
is often called “par value” or simply “par.”

So a cupon bond is issued at $95 and it can be redeemed at maturity for 100.

In ZENO::redeem the users receive the same amount of USDC as the amount of Zeno tokens redeemed.

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

There are 2 issues with this implementation:

  • the Zeno token is a standard token with 18 decimals, while the USDC token has 6 decimals (if the usdc is used).

  • the amount of Zeno tokens redeem are not multiplied by the bond's face value

By example for one bond (1e18) with face value of $100, the redeemer receives 1e12 * 1e6 USDC instead of 1e2 * 1e6 USDC amount.

Impact

Bonds are redeemed at the wrong price.
Redeemers receive more value than they should.

Tools Used

Recommendations

Add a new faceValue (or redeemValue) variable in AuctionState struct, or even in the Zeno contract.
Update redeem :

function redeem(uint amount) external nonReentrant {
...
totalZENORedeemed += amount;
_burn(msg.sender, amount);
+ uint256 amountToRedeem = amount * faceValue / 1e18;
- USDC.safeTransfer(msg.sender, amount);
+ USDC.safeTransfer(msg.sender, amountToRedeem);
}
Updates

Lead Judging Commences

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

ZENO.sol implements fixed 1:1 redemption with USDC regardless of auction purchase price, breaking zero-coupon bond economics and causing user funds to be permanently lost

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!