Core Contracts

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

Missing precision factor causes users pay more funds

Summary

Users can buy the ZENO token through the Auction and Auction.buy() function gets the price of ZENO from Auction.getPrice(). The Auction.getPrice() function returns the price of ZENO without any precision factor and this could return higher price than real, leading users pay more funds.

Vulnerability Details

When a user attempts to buy ZENO, they invoke the Auction.buy() function. The function gets the price gets the price of ZENO from Auction.getPrice().

We can check the price doesn't use any precision factor from the calculation of cost = price * amount.

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);
}

Since the price doesn't use any precision factor, it can lead to significant inaccuracies, especially in scenarios where the time interval for price adjustment is large relative to the price range.

Let's analyze following calculation. The precision loss can arise when state.endTime - state.startTime is significantly larger than state.startingPrice - state.reservePrice. This could be possible case, since price price doesn't use decimal for precision factor.

/**
Get current slippage price of ZENO
*/
function getPrice() public view returns (uint256) {
if (block.timestamp < state.startTime) return state.startingPrice;
if (block.timestamp >= state.endTime) return state.reservePrice;
@> return state.startingPrice - (
(state.startingPrice - state.reservePrice) *
(block.timestamp - state.startTime) /
(state.endTime - state.startTime)
);
}

Let's consider following case:

  • state.startingPrice = 50, state.reservePrice = 40: state.startingPrice - state.reservePrice = 10

  • state.startTime = 1730000000, state.endTime = 1730864000: state.endTime - state.startTime = 864000(10 days)

  • When block.timestamp = 1730086399, price = 50 - 10 * 86399 / 864000 = 50. If user want to buy 100 ZENO, he should pay 5000 usdc.

Let's use 1e8 decimal for price as precision factor:

  • price = 50 * 1e8 - 10 * 1e8 * 86399/86400 = 4023159722, cost = 4023159722 * 100 / 1e8 = 4023

The difference: 5000 - 4023 = 977

This is significant amount and users will pay more funds. This scenario is not real situation, but shows possible vulnerabilities in precision loss and should be fixed.

Impact

Users should pay more funds to buy ZENO due to missing precision factor.

Tools Used

Manual Review

Recommendations

Implement a precision factor in the price calculation within the getPrice() function.

/**
Get current slippage price of ZENO
*/
function getPrice() public view returns (uint256) {
if (block.timestamp < state.startTime) return state.startingPrice;
if (block.timestamp >= state.endTime) return state.reservePrice;
- return state.startingPrice - (
- (state.startingPrice - state.reservePrice) *
- (block.timestamp - state.startTime) /
- (state.endTime - state.startTime)
- );
+ return state.startingPrice * precisionFactor - (
+ (state.startingPrice - state.reservePrice) *
+ (block.timestamp - state.startTime) * precisionFactor/
+ (state.endTime - state.startTime)
+ );
}
/**
Bid on the ZENO auction
User will 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;
+ uint256 cost = price * amount / precisionFactor;
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);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Auction.sol's buy() function multiplies ZENO amount (18 decimals) by price (6 decimals) without normalization, causing users to pay 1 trillion times the intended USDC amount

Support

FAQs

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