Summary
The redeem and redeemAll functions transfer USDC to users based on the amount of ZENO tokens they are redeeming.
However, the contract does not check whether it holds enough USDC to fulfill the redemption request.
If the contract does not have sufficient USDC balance, the safeTransfer call will revert, causing the redemption to fail.
Vulnerability Details
The contract mints 1,000 ZENO tokens to User A.
The contract only holds 500 USDC (instead of 1,000 USDC).
After the maturity date, User A attempts to redeem all 1,000 ZENO tokens.
The safeTransfer call fails because the contract only has 500 USDC, and the transaction reverts.
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);
}
function redeemAll() external nonReentrant {
if (!isRedeemable()) {
revert BondNotRedeemable();
}
uint256 amount = balanceOf(msg.sender);
totalZENORedeemed += amount;
_burn(msg.sender, amount);
USDC.safeTransfer(msg.sender, amount);
}
function getDetails() external view returns (ZENODetails memory) {
return ZENODetails(address(this), MATURITY_DATE, name(), symbol());
}
}
Impact
Users may be unable to redeem their ZENO tokens for USDC, even after the maturity date.
This could lead to a loss of trust in the contract and financial losses for users.
Tools Used
Recommendations
To address this issue, the contract should ensure that it holds enough USDC to fulfill redemption requests. This can be done by adding a check in the redeem and redeemAll functions.
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 usdcBalance = USDC.balanceOf(address(this));
if (amount > usdcBalance) {
revert InsufficientUSDCBalance();
}
totalZENORedeemed += amount;
_burn(msg.sender, amount);
USDC.safeTransfer(msg.sender, amount);
}
function redeemAll() external nonReentrant {
if (!isRedeemable()) {
revert BondNotRedeemable();
}
uint256 amount = balanceOf(msg.sender);
uint256 usdcBalance = USDC.balanceOf(address(this));
if (amount > usdcBalance) {
revert InsufficientUSDCBalance();
}
totalZENORedeemed += amount;
_burn(msg.sender, amount);
USDC.safeTransfer(msg.sender, amount);
}