Core Contracts

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

Precision mismatch in redemption (`redeem` and `redeemAll`) of `ZENO` contract lead to incorrect fund transfers

Summary

The redeem and redeemAll functions in the ZENO contract do not properly account for the difference in decimal precision between ZENO (assumed 18 decimals) and USDC (6 decimals). This can lead to incorrect fund transfers where users receive significantly more or fewer USDC tokens than intended.

Vulnerability Details

The following code snippet highlights the issue:

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); // Issue: Incorrect decimal conversion
}
function redeemAll() external nonReentrant {
if (!isRedeemable()) {
revert BondNotRedeemable();
}
uint256 amount = balanceOf(msg.sender);
totalZENORedeemed += amount;
_burn(msg.sender, amount);
USDC.safeTransfer(msg.sender, amount); // Issue: Incorrect decimal conversion
}

Issue:

  • 1,000,000 times more USDC than intended.

  • If reversed (USDC has more decimals), users could receive far fewer tokens.

Scenario:

  1. ZENO has 18 decimals, and USDC has 6 decimals.

  2. A user with 1 ZENO calls redeem(1e18), expecting to receive 1 USDC.

  3. Instead, USDC.safeTransfer(msg.sender, 1e18) is called, sending 1,000,000 USDC.

  4. This incorrectly drains the contract of USDC.

Test:

it("should fail due to incorrect decimal conversion", async function () {
const zenoAmount = ethers.utils.parseUnits("1", 18); // 1 ZENO
const usdcAmount = ethers.utils.parseUnits("1", 6); // 1 USDC
await zeno.mint(user.address, zenoAmount);
await usdc.transfer(zeno.address, usdcAmount); // Ensure contract has USDC
await zeno.connect(user).redeem(zenoAmount);
const userUsdcBalance = await usdc.balanceOf(user.address);
console.log("User USDC Balance:", userUsdcBalance.toString());
expect(userUsdcBalance).to.equal(usdcAmount); // Should fail
});

Output:

User USDC Balance: 1000000000000000000 (should be 1e6 but is 1e18)

Impact

  1. Financial loss if users withdraw significantly more USDC than intended.

  2. Contract funds may be drained by malicious actors exploiting this bug.

Tools Used

Manual review.

Recommendations

Modify the redeem and redeemAll functions to properly convert between token decimals:

uint256 usdcAmount = amount / 1e12; // Convert from 18 to 6 decimals
USDC.safeTransfer(msg.sender, usdcAmount);
Updates

Lead Judging Commences

inallhonesty Lead Judge 2 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.