Core Contracts

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

Buyers will always pay astronomically higher than what they should pay whenever they want to buy `ZENO` tokens in an Auction

Summary

Precision error in Auction::buy() means buyers will always pay astronomically higher than they should when they want to buy ZENO tokens. Every single time.

P.S. I checked the LightChaser report for this contest. This finding is not reported. So, this cannot be invalidated ๐Ÿ™‚๐Ÿ˜Š

Vulnerability Details

The ::buy() function calculates the cost a buyer should pay by multiplying the amount of tokens they want to buy, and the current slippage price per token, and then requires that this amount is transferred from the buyer to the businessAddress.

/**
* 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; ๐Ÿ‘ˆ๐Ÿพ๐Ÿ‘ˆ๐Ÿพ
โ€‹
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);
}

But because both values (amount of tokens to be bought and current slippage price) are in e18, the cost ends up being in e36.

Impact

One of three things will happen:

  • If sufficient approval is given by the buyer to the Auction contract and the buyer has such amount in usdc worth (very unlikely), the buyer pays the astronomical cost

  • If sufficient approval is given by the buyer to the Auction contract and the buyer does not have such amount in usdc worth (very likely), the transfer reverts with an ERCInsuffientBalance() error

  • If the buyer doesn't give sufficient approval, the transfer reverts with an ERCInsufficientAllowance() error

P.S. I commented out zeno.mint(msg.sender, amount); because there is a bug related to it, and I want to post the traces of this finding without that bug interfering. I will also report that bug as a separate finding.

Tools Used

  • Foundry

PoC

Run the following test in Foundry:

function testBuyerPaysAstronomicalCost() public {
usdc.mint(buyer, 1e36); // Minted the buyer some crazy amount of usdc for the purpose of this test
โ€‹
vm.warp(startTime + 1 seconds);
โ€‹
console2.log("This is the usdc balance of the buyer before he buys any ZENO: ", usdc.balanceOf(buyer));
โ€‹
vm.startPrank(buyer);
usdc.approve(address(auction), type(uint256).max); // Buyer approves for type(uint256).max
auction.buy(1e18); // Buyer wants to buy 1 ZENO token
vm.stopPrank();
โ€‹
console2.log("This is the usdc balance of the buyer after he buys any ZENO: ", usdc.balanceOf(buyer));
console2.log(
"This is the amount of usdc that was transferred from the buyer's account: ", (amount * auction.getPrice())
);
}

And we can read the logs for the result:

[PASS] testBuyerPaysAstronomicalCost() (gas: 202811)
Logs:
This is the usdc balance of the buyer before he buys any ZENO: 1000000000000000000000000000000000000
This is the usdc balance of the buyer after he buys any ZENO: 138888888888888000000000000000000
This is the amount of usdc that was transferred from the buyer's account: 999861111111111112000000000000000000

Recommendations

Refactor the buy() function so that the cost is divided by 1e18 before it is being transferred from the buyer

function buy(uint256 amount) external whenActive {
...
...
- uint256 cost = price * amount;
+ uint256 cost = (price * amount) / 1e18;
โ€‹
require(usdc.transferFrom(msg.sender, businessAddress, cost), "Transfer failed");
โ€‹
...
...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 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.

Give us feedback!