Core Contracts

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

Large Discrepancy in USDC:ZENO Ratio Between `Auction:buy()` and `ZENO:redeemAll()`, Causing Significant Fund Loss

Summary

The redeemAll function in the ZENO contract enforces a fixed 1:1 redemption ratio between ZENO and USDC, while Auction:buy() allows purchases at a variable price determined by getPrice(). Due to lack of proper scaling, users may pay significantly more USDC than they can redeem, resulting in substantial financial losses.

Vulnerability Details

Reasoning startingPrice & reservePrice Picks to avoid Rounding Error in getPrice()

  • Found in contracts/zeno/Auction.sol at Line 76

69: function getPrice() public view returns (uint256) {
...
73: return state.startingPrice - (
74: (state.startingPrice - state.reservePrice) *
75: (block.timestamp - state.startTime) /
76: => (state.endTime - state.startTime)
77: );
78: }
  • The getPrice() function calculates the price of ZENO dynamically over time.

  • If (state.startingPrice - state.reservePrice) does not have enough precision, division by (state.endTime - state.startTime) can result in rounding errors.

  • Example: If the auction duration is 1 day (86400 seconds), then state.startingPrice - state.reservePrice must be at least 86400 (5 to 6 decimal places) to avoid loss of granularity.

Large Discrepancy in USDC:ZENO Ratio Between buy() and redeemAll()

In buy()

  • Found in contracts/zeno/Auction.sol at Line 87

84: function buy(uint256 amount) external whenActive {
...
86: uint256 price = getPrice();
87: => uint256 cost = price * amount;
88: require(usdc.transferFrom(msg.sender, businessAddress, cost), "Transfer failed");
...
97: }
  • The cost calculation is based on getPrice(), which must account for precision.

  • Given a 1-day auction duration, price should have at least 6 decimal places to ensure that users are charged the correct amount.

In redeemAll()

  • Found in contracts/zeno/ZENO.sol at Line 73

65: function redeemAll() external nonReentrant {
...
72: => _burn(msg.sender, amount);
73: => USDC.safeTransfer(msg.sender, amount);
74: }
  • The redeemAll function forces a 1:1 redemption ratio between ZENO and USDC.

Impact

  • Severity: High

  • Users suffer a significant loss of funds due to the mismatch in price calculation vs. redemption ratio.

  • Example Attack Scenario:

    • Suppose the auction lasts 1 day (86400 seconds) , startingPrice & reservePrice must be at least 1e6.

    • Alice buys 1 wei ZENO at the end of the auction, paying 1e6 USDC.

    • On redemption, Alice receives only 1 wei USDC (instead of 1e6 USDC) due to the fixed 1:1 redemption ratio.

    • Alice loses 99.99% of her funds.

Tool Used

Manual review

Recommendations

  • Adjust the redeemAll function to properly scale the redemption ratio based on the auction price.

  • Modify cost calculation in buy() to account for decimal precision of getPrice, ensuring USDC:ZENO remains within an acceptable range (e.g., 1.xx:1 where xx represents protocol fees).

uint256 cost = price * amount / PRICE_PRECISION;
Updates

Lead Judging Commences

inallhonesty Lead Judge 3 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

Support

FAQs

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