Core Contracts

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

Transferring wrong amount of USDC in `ZENO::redeem` and `redeemAll` functions, the user lost almost all the USDC that was used to buy ZENO tokens earlier.

Summary

Transferring wrong amount of USDC in ZENO::redeem and redeemAll functions, the user lost almost all the USDC that was used to buy ZENO tokens earlier.

Vulnerability Details

The redeem and redeemAll functions allow user to burn an amount of ZENO tokens in exchange for USDC. But the amount of USDC transferred to users is equal to the amount of ZENO tokens burned.

ZENO.sol#L61-L62 and ZENO.sol#L72-L73:

_burn(msg.sender, amount);
USDC.safeTransfer(msg.sender, amount); // the amount of USDC transfer to user are equal to amount of ZENO token burned

This amount is not multiplied by the price of ZENO tokens in USDC.

Compared to the cost (USDC amount) the user spent to buy ZENO tokens in Aution.sol::buy function: Auction.sol#L84-L97

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");
bidAmounts[msg.sender] += amount;
state.totalRemaining -= amount;
state.lastBidTime = block.timestamp;
state.lastBidder = msg.sender;
zeno.mint(msg.sender, amount);
emit ZENOPurchased(msg.sender, amount, price);
}

Unless the price is 1, the user will receive a much smaller amount of USDC than the original purchase amount.

Impact

The user lost almost all the USDC that was used to buy ZENO tokens earlier.

Tools Used

Manual review

Recommendations

Take the number of ZENO tokens multiplied by its price before transferring to the user.
In reddem:

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 amountToTransfer = amount * USDC.balanceOf(address(this)) / totalSupply();
totalZENORedeemed += amount;
_burn(msg.sender, amount);
- USDC.safeTransfer(msg.sender, amount);
+ USDC.safeTransfer(msg.sender, amountToTransfer);
}

And in redeemAll:

function redeemAll() external nonReentrant {
if (!isRedeemable()) {
revert BondNotRedeemable();
}
uint256 amount = balanceOf(msg.sender);
+ uint256 amountToTransfer = amount * USDC.balanceOf(address(this)) / totalSupply();
totalZENORedeemed += amount;
_burn(msg.sender, amount);
- USDC.safeTransfer(msg.sender, amount);
+ USDC.safeTransfer(msg.sender, amountToTransfer);
}
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.