Core Contracts

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

Roundup logic in `Auction::getPrice()` will see users pay quite higher on negligible prices

Summary

The getPrice() function is used to get the current price of ZENO tokens in USDC. But per its current logic, it will always round up to the nearest whole number, even on values that are very negligible.

PoC

This is the current logic used to get the slippage price of tokens:

function getPrice() public view returns (uint256) {
...
...
return state.startingPrice
- (
(state.startingPrice - state.reservePrice) * (block.timestamp - state.startTime)
/ (state.endTime - state.startTime)
);
}

If we run the following test, we can see the current slippage price:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console2} from "forge-std/Test.sol";
import {Auction} from "../contracts/zeno/Auction.sol";
import {ZENO} from "../contracts/zeno/ZENO.sol";
import {MockUSDC} from "../contracts/mocks/core/tokens/MockUSDC.sol";
contract AuctionTest is Test {
Auction auction;
ZENO zeno;
MockUSDC usdc;
address zenoOwner = address(0x1);
address businessAddress = address(0x2);
address buyer = address(0x3);
address auctionOwner = address(0x4);
uint256 startTime = 3600 seconds;
uint256 endTime = startTime + 3600 seconds; // i.e. auction lasts for 2 hours
uint256 startingPrice = 1e18; // 1 USDC per ZENO
uint256 reservePrice = 5e17; // 0.5 USDC per ZENO
uint256 totalAllocated = 1000e18; // 1000 ZENO tokens allocated for the auction
uint256 initialSupply = 10_000e18;
uint256 zenoMaturityDate = 1 hours;
function setUp() public {
usdc = new MockUSDC(initialSupply);
zeno = new ZENO(address(usdc), zenoMaturityDate, "ZTOKEN", "ZTK", zenoOwner);
auction = new Auction(
address(zeno),
address(usdc),
businessAddress,
startTime,
endTime,
startingPrice,
reservePrice,
totalAllocated,
auctionOwner
);
// Mint usdc to buyer for bidding
usdc.mint(buyer, 1000e18);
}
function testGetPrice() public {
console2.log(auction.getPrice()); // 1_000_000_000_000_000_000
vm.warp(4600 seconds);
console2.log(auction.getPrice()); // 861_111_111_111_111_112
}
}

But if we calculate the current slippage price using the logic in the getPrice() function:

1e18 - ((1e18 - 5e17) * (4600 - 3600) / (7200 - 3600))

We get:

861,111,111,111,111,111.11111111111111

The current logic rounds up even on very negligible amounts.

Impact

ZENO tokens could be listed at prices that would result in slippage prices becoming quite high. And because the getPrice() logic rounds up every amount, the buyer could end up paying significantly higher than expected.

Tools Used

  • Foundry

  • Manual Review

Recommendations

I added an internal function that only rounds up on amounts greater than .4, .1 - .4 rounds down.

function divideAndRound(uint256 numerator, uint256 denominator) internal pure returns (uint256) {
require(denominator > 0, "Division by zero or negative values not allowed!!!");
// Calculate the result of the division
uint256 quotient = numerator / denominator;
// Calculate the remainder
uint256 remainder = numerator % denominator;
// Check if the remainder is greater than or equal to half of the denominator by comparing 'remainder * 2' to the denominator
if ((remainder * 2) >= denominator) {
// Round up
quotient += 1;
}
return quotient;
}

Then this internal function is used to calculate the current slippage price in getPrice().

function getPrice() public view returns (uint256) {
...
...
- return state.startingPrice
- - (
- (state.startingPrice - state.reservePrice) * (block.timestamp - state.startTime)
- / (state.endTime - state.startTime)
- );
+ uint256 priceDifference = state.startingPrice - state.reservePrice;
+ uint256 timeElapsed = block.timestamp - state.startTime;
+ uint256 totalDuration = state.endTime - state.startTime;
+
+ uint256 product = priceDifference * timeElapsed;
+ uint256 quotient = divideAndRound(product, totalDuration);
+ uint256 price = state.startingPrice - quotient;
+ return price;
}

If we run our test we would get a round down result:

function testGetPrice() public {
console2.log(auction.getPrice()); // 1_000_000_000_000_000_000
vm.warp(4600 seconds);
console2.log(auction.getPrice()); // 861_111_111_111_111_111
}

And it also works for round up results when appropriate.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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