Summary
Bidders will always get much less USDC(or other ERC20) than they should because of the incorrect mint amount passed when minting ZENO tokens, this will cause all sensible user redemptions to revert.
Vulnerability Details
ZENO tokens are primarily acquired through RAAC Auctions. During these auctions, users deposit their USDC in exchange for a certain amount of ZENO bonds until the auction concludes. They problem however lies in the 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");
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);
}
As shown with the @audit tag, the function uses the amount of bonds requested to be bought as the value for minting ZENO instead of the cost, which is the amount of USDC transferred from the user. Futhermore, when the maturity date expires and users want to convert their ZENO tokens back to USDC,
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 redeem function compares the amount to withdraw with the users balance and reverts if the user has insufficent balance and transfers the same amount to the user, this will cause the user to only be able to withdraw dust amounts equivalent to the amount of ZENO bonds bought and not the actual USDC value, a similar situation occurs in the redeemAll function.
function redeemAll() external nonReentrant {
if (!isRedeemable()) {
revert BondNotRedeemable();
}
uint256 amount = balanceOf(msg.sender);
totalZENORedeemed += amount;
_burn(msg.sender, amount);
USDC.safeTransfer(msg.sender, amount);
}
Impact
Users will not be able to withdraw their funds as the value of ZENO minted is much less than intended
Tools Used
Manual Review
Recommendations
Change the value of ZENO tokens minted from amount to cost
Bid on the ZENO auction
User will be able to buy ZENO tokens in exchange for USDC
*/
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;
```diff
+zeno.mint(msg.sender, cost);
-zeno.mint(msg.sender, amount);
```
emit ZENOPurchased(msg.sender, amount, price);
}