Core Contracts

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

Rounding Issues in buy() Will Lead to Incorrect USDC Transfers

Summary

The Zeno::buy() function in the Auction contract is susceptible to rounding issues when calculating the total cost of purchasing ZENO tokens. This can lead to major discrepancies in the amount of USDC transferred or the number of ZENO tokens received.

Vulnerability Details

The function computes the cost of purchasing amount ZENO tokens using the Auction::getPrice() function:

uint256 cost = price * amount;

Since Solidity performs integer division, there is a possibility of precision loss when multiplying and dividing values, especially if USDC uses 6 decimals while ZENO has 18 decimals. This can lead to cases where users pay slightly more or less than expected.

Impact

  • Users will overpay or underpay due to rounding discrepancies.

  • Could lead to small amounts of USDC being effectively lost over multiple transactions.

  • Potential for inconsistencies in accounting and tracking.

PoC

contract MockUSDC is ERC20 {
constructor(uint256 initialSupply) ERC20("Mock USD Coin", "mUSDC") {
_mint(msg.sender, initialSupply);
}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
function decimals() public view virtual override returns (uint8) {
return 6;
}
}
contract ZENOTest is Test {
ZENO public zeno;
MockUSDC public usdc;
address user = address(0x123);
function setUp() public {
usdc = new MockUSDC(0); // Mock 6-decimal USDC
zeno = new ZENO(
address(usdc),
block.timestamp - 1,
"ZENO",
"ZENO",
address(this)
);
// Mint some USDC to contract for testing
MockUSDC(address(usdc)).mint(address(zeno), 1e18 + 1);
}
function testRedeemRoundingIssue() public {
// Mint 1.000000000000000001 ZENO (18 decimals)
zeno.mint(user, 1.000000000000000001 ether);
uint256 userZenoBalance = zeno.balanceOf(user);
console.log("userZenoBalance", userZenoBalance);
uint256 zenoDecimals = zeno.decimals();
uint256 wholeZENO = userZenoBalance / 10 ** zenoDecimals;
uint256 fractionalZENO = userZenoBalance % 10 ** zenoDecimals; // Remaining fraction
console.log("Readable ZENO:", wholeZENO, ".", fractionalZENO);
vm.prank(user);
zeno.redeem(1.000000000000000001 ether);
// Expect incorrect amount due to rounding error
uint256 usdcBalance = usdc.balanceOf(user);
uint256 usdcDecimals = usdc.decimals();
uint256 wholeUSDC = usdcBalance / 10 ** usdcDecimals;
uint256 fractionalUSDC = usdcBalance % 10 ** usdcDecimals; // Remaining fraction
console.log("USDC Received:", usdcBalance);
console.log("Readable USDC:", wholeUSDC, ".", fractionalUSDC);
}
}

Tools Used

  • Manual code review

Recommendations

  • Consider using a fixed-point arithmetic library to ensure proper rounding behavior.

  • Adjust calculations to take into account the decimal differences between USDC and ZENO.

  • Ensure rounding is handled in favor of the user to prevent unintended losses.

Updates

Lead Judging Commences

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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