Core Contracts

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

Decimal precision mismatch between USDC (6 decimals) and ZENO (18 decimals) leads to incorrect token pricing and redemption amounts

Summary

The Auction and ZENO contracts fail to account for the decimal difference between USDC (6 decimals) and ZENO (18 decimals) tokens, leading to severely incorrect pricing calculations and redemption amounts.

Vulnerability Details

The contracts assume USDC and ZENO tokens have the same decimal precision, but:

  • USDC uses 6 decimals

  • ZENO inherits ERC20's default 18 decimals

This mismatch appears in two critical places:

  1. Auction's buy function:

function buy(uint256 amount) external whenActive {
require(amount <= state.totalRemaining, "Not enough ZENO remaining");
uint256 price = getPrice();
uint256 cost = price * amount;
require(usdc.transferFrom(msg.sender, businessAddress, cost), "Transfer failed");
//@audit why it's using amount and not cost?
bidAmounts[msg.sender] += amount;
state.totalRemaining -= amount;
state.lastBidTime = block.timestamp;
state.lastBidder = msg.sender;
zeno.mint(msg.sender, amount);
zeno.mint(msg.sender, amount);
emit ZENOPurchased(msg.sender, amount, price);
}

The price calculation cost = price * amount multiplies:

  • price (USDC - 6 decimals)

  • amount (ZENO - 18 decimals)

Without adjustment, this makes ZENO tokens 10^12 times more expensive than intended.

  1. ZENO's redeem function:

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 redemption transfers the same raw amount:

_burn(msg.sender, amount); // Burns ZENO (18 decimals)
USDC.safeTransfer(msg.sender, amount); // Transfers USDC (6 decimals)

This means users receive 10^12 times less USDC than they should.

Impact

  1. Users must pay 10^12 times more USDC than intended to purchase ZENO

  2. Users receive 10^12 times less USDC when redeeming ZENO

  3. Most transactions will revert due to insufficient balances

  4. If transactions succeed, users lose significant value

Proof of Code

Example with 1 ZENO token purchase:

price = 2 USDC = 2 * 10^6
amount = 1 ZENO = 1 * 10^18
cost = price * amount
= (2 * 10^6) * (1 * 10^18)
= 2 * 10^24 USDC wei
= 2,000,000,000,000,000,000 USDC

Instead of paying 2 USDC, users must pay 2 quintillion USDC.

Tools Used

Manual review

Recommendations

Add decimal normalization:

  1. In Auction.sol:

function buy(uint256 amount) external whenActive {
uint256 price = getPrice();
uint256 cost = (price * amount) / 1e12; // Normalize to USDC decimals
require(usdc.transferFrom(msg.sender, businessAddress, cost), "Transfer failed");
// ... rest of function
}
  1. In ZENO.sol:

function redeem(uint amount) external nonReentrant {
// ... checks ...
_burn(msg.sender, amount);
uint256 usdcAmount = amount / 1e12; // Convert to USDC decimals
USDC.safeTransfer(msg.sender, usdcAmount);
}
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

Auction.sol's buy() function multiplies ZENO amount (18 decimals) by price (6 decimals) without normalization, causing users to pay 1 trillion times the intended USDC amount

Support

FAQs

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

Give us feedback!