Core Contracts

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

Wrong Zeno Amount Minted in `Auction::buy()`

Summary

The Auction::buy() function mints ZENO tokens without adjusting for decimal precision. While USDC (6 decimals) is correctly transferred, ZENO (18 decimals) is minted incorrectly, leading to users receiving significantly fewer tokens than expected. also the amount in the ZENOPurchased event is also wrong.

Vulnerability Details

Lets take an example. If a user wants to buys 1 ZENO, in the buy function the correct cost calculation in USDC with example price is 1 * 98533334 = 98533334 = 98.533334 USDC. The businessAddress gets the correct amount of USDC. However, the buy function does not scale the amount correctly, resulting in only 1 Wei of ZENO being minted instead of 1e18 ZENO.

The decimals of zeno is suppose to be 6 which is a separate issue i have submitted.

/contracts/zeno/Auction.sol:84
84: function buy(uint256 amount) external whenActive {
85: require(amount <= state.totalRemaining, "Not enough ZENO remaining");
86: uint256 price = getPrice(); // @note in USDC decimals i.e. 6
87: uint256 cost = price * amount;
88:
89: require(usdc.transferFrom(msg.sender, businessAddress, cost), "Transfer failed");
90:
91: bidAmounts[msg.sender] += amount;
92: state.totalRemaining -= amount;
93: state.lastBidTime = block.timestamp;
94: state.lastBidder = msg.sender;
95:
96: zeno.mint(msg.sender, amount); // @audit zeno amount will be wrong
97: emit ZENOPurchased(msg.sender, amount, price);
98: }

Here we can see that at line number 87 we are computing the amount of usdc to be transferred to businessAddress. and at line number 96 the amount variable is being passed to zeno::mint() function.
The issue here is that user will transfer more USDC than zeno we mint for user , So when user redeem zeno token he will receive less USDC.

POC

/test/unit/Zeno/Integration.test.js:217
217: it("POC: Wrong Zeno Amount Minted", async function () {
218: let amountToBuy = 1;
219:
220: // Increase time to auctionStartTime
221: // Increase EVM time by 1 hour (3600 seconds)
222: await ethers.provider.send("evm_increaseTime", [3600*1.5]);
223:
224: // Mine a single block
225: await ethers.provider.send("evm_mine", []);
226:
227: const auctionStateForPrice = await auction1.state();
228: const price = await auction1.getPrice();
229: log(`Price: ${price}`);
230:
231: const cost = parseFloat(price) * amountToBuy;
232:
233: // Approve the auction contract
234: const allowance = await usdc.allowance(addr1.address, auction1Address);
235:
236: if (allowance < cost) {
237: const approveTx = await usdc
238: .connect(addr1)
239: .approve(auction1Address, cost);
240: await approveTx.wait();
241: }
242:
243: await expect(auction1.connect(addr1).buy(amountToBuy)).to.emit(
244: auction1,
245: "ZENOPurchased"
246: );
247:
248: // Check that user has correct amount of ZENO bonds
249: const userBalance = await zeno1.balanceOf(addr1.address);
250: const decimals = await zeno1.decimals();
251: expect(Number(userBalance)).to.equal(Number(amountToBuy*10**Number(decimals)));
252: });

Run the POC using npx hardhat test

Impact

Users receive significantly fewer ZENO tokens than they should, leading to loos of USDC amount when user redeem from Zeno contract.

Tools Used

Manual Review, Unit Testing

Recommendations

Update the buy function with the below recommended fix. As the Zeno decimals should be 6 instead of 18, we can pass cost variable instead of amount variable.

/contracts/zeno/Auction.sol
- zeno.mint(msg.sender, amount);
+ zeno.mint(msg.sender, cost);
Updates

Lead Judging Commences

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

ZENO.sol implements fixed 1:1 redemption with USDC regardless of auction purchase price, breaking zero-coupon bond economics and causing user funds to be permanently lost

Support

FAQs

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

Give us feedback!