Core Contracts

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

Context:`ZENO.sol` (redeem function)

Summary

The redeem function in the ZENO contract does not validate whether the contract holds a sufficient USDC balance before attempting a transfer. This oversight can lead to failed transactions and permanently locked user funds, as ZENO tokens are burned before USDC is transferred.


Vulnerability Details

Issue: Missing USDC Balance Check Before Transfer

The redeem function allows users to burn ZENO tokens in exchange for USDC, but it does not verify if the contract has enough USDC before calling safeTransfer:

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();
}
// @audit-issue No USDC balance check before transfer
totalZENORedeemed += amount;
_burn(msg.sender, amount);
USDC.safeTransfer(msg.sender, amount); // ❌ No balance check before transfer
}

Impact

  1. If the contract does not have enough USDC, safeTransfer fails, reverting the transaction after the user's ZENO tokens have already been burned.

  2. This results in permanently lost user funds, as their ZENO balance is reduced but they receive no USDC.

  3. The protocol lacks an emergency recovery mechanism, leaving users with no way to reclaim lost value.

  4. The likelihood of this issue is high, as it can occur whenever redemptions exceed the contract’s USDC balance.


Tools Used

Manual Code Review,Hardhat

Proof of Concept (PoC) - Hardhat Test Case

I identified and confirmed this vulnerability using Hardhat, demonstrating that users can permanently lose their ZENO tokens when the contract has insufficient USDC balance.

Steps to Reproduce:

  1. Deploy the ZENO contract and assign an initial owner.

  2. Mint 1000 ZENO to a user.

  3. Do not send any USDC to the contract.

  4. Attempt to redeem 1000 ZENO.

  5. The transaction fails, but the user’s ZENO tokens are already burned.

Hardhat Test Script:

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("ZENO Vulnerability - Token Burn Before Balance Check", function() {
let zeno, usdc, owner, user;
const MINT_AMOUNT = ethers.utils.parseUnits("1000", 6);
beforeEach(async () => {
[owner, user] = await ethers.getSigners();
// Deploy USDC mock
const USDC = await ethers.getContractFactory("MockUSDC");
usdc = await USDC.deploy();
// Deploy ZENO contract
const ZENO = await ethers.getContractFactory("ZENO");
zeno = await ZENO.deploy(
usdc.address,
Math.floor(Date.now()/1000) - 3600, // Matured 1 hour ago
"ZENO Bond",
"ZENO",
owner.address
);
// Mint ZENO to user
await zeno.connect(owner).mint(user.address, MINT_AMOUNT);
});
it("Causes token loss on redemption with insufficient USDC", async function() {
// Verify initial state
expect(await zeno.balanceOf(user.address)).to.equal(MINT_AMOUNT);
expect(await usdc.balanceOf(zeno.address)).to.equal(0);
// Attempt redemption - will revert but burn tokens
await expect(
zeno.connect(user).redeem(MINT_AMOUNT)
).to.be.revertedWith("ERC20: transfer amount exceeds balance");
// Verify tokens were burned with no USDC received
expect(await zeno.balanceOf(user.address)).to.equal(0);
expect(await usdc.balanceOf(user.address)).to.equal(0);
});
});

Recommendation

Fix: Add a Balance Check Before Transferring USDC

Modify the redeem function to verify if the contract holds enough USDC before burning the user's ZENO tokens:

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();
}
uint256 contractUSDCBalance = USDC.balanceOf(address(this)); // ✅ Check USDC balance
if (contractUSDCBalance < amount) {
revert InsufficientUSDCBalance(); // ✅ Prevents burning tokens before transfer
}
totalZENORedeemed += amount;
_burn(msg.sender, amount);
USDC.safeTransfer(msg.sender, amount);
}

Final Assessment

Severity: High
Likelihood: High
Impact: Permanent loss of user funds due to token burning without fund validation
Recommended Fix Applied: Yes, add USDC balance verification before _burn

This fix ensures that redemptions only occur when the contract has sufficient USDC, preventing the loss of user funds.

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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