Core Contracts

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

Double taxation in RAACNFT::mint when refunding excess payment

Relevant GitHub Links

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/RAACNFT.sol#L38C1-L46C58

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/RToken.sol#L310

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/RAACToken.sol#L201

Summary

The RAACNFT.sol contract's mint function applies RAAC token tax twice when processing refunds for excess payments, causing users to lose more funds than documented or intended.

Vulnerability Details

When a user mints an NFT and sends more RAAC tokens than the NFT price, the contract processes a refund. Due to how RAACToken implements tax collection in its _update function, tax is applied both on the initial transfer to the contract and on the refund transfer back to the user.

Here's an example flow with default tax rates (1% swap tax + 0.5% burn tax = 1.5% total):

// In RAACNFT.sol
function mint(uint256 _tokenId, uint256 _amount) public override {
uint256 price = raac_hp.tokenToHousePrice(_tokenId);
if(price == 0) { revert RAACNFT__HousePrice(); }
if(price > _amount) { revert RAACNFT__InsufficientFundsMint(); }
// First tax applied here
token.safeTransferFrom(msg.sender, address(this), _amount);
// mint tokenId to user
_safeMint(msg.sender, _tokenId);
// Second tax applied on refund, both will trigger `_update()_` causing taxation both times
if (_amount > price) {
uint256 refundAmount = _amount - price;
token.safeTransfer(msg.sender, refundAmount);
}
}
// In RAACToken.sol
function _update(
address from,
address to,
uint256 amount
) internal virtual override {
// Skip tax for whitelisted addresses or when fee collector disabled
if (baseTax == 0 || from == address(0) || to == address(0) ||
whitelistAddress[from] || whitelistAddress[to] ||
feeCollector == address(0)) {
super._update(from, to, amount);
return;
}
// Calculate and apply taxes
uint256 totalTax = amount.percentMul(baseTax);
uint256 burnAmount = totalTax * burnTaxRate / baseTax;
// Send taxes and remaining amount
super._update(from, feeCollector, totalTax - burnAmount);
super._update(from, address(0), burnAmount);
super._update(from, to, amount - totalTax);
}

For a 1000 RAAC NFT purchase with 1100 RAAC sent:

  1. Initial transfer (1100 RAAC):

  • Tax (1.5%) = 16.5 RAAC

  • Received by contract = 1083.5 RAAC

  1. Refund of excess (100 RAAC):

  • Tax (1.5%) = 1.5 RAAC

  • Received by user = 98.5 RAAC

Total tax paid = ~18 RAAC instead of expected 15 RAAC (20% more than intended)

Impact

Users paying more than exact price lose 20% more in taxes than documented or necessary. This creates an undocumented loss of funds that affects any user who sends excess payment, whether through calculation error, UI rounding, or frontend issues.

Tools Used

Manual review

Recommendations

Modify the mint function to only accept exact payment:

function mint(uint256 _tokenId, uint256 _amount) public override {
uint256 price = raac_hp.tokenToHousePrice(_tokenId);
if(price == 0) { revert RAACNFT__HousePrice(); }
if(price != _amount) { revert RAACNFT__InvalidAmount(); } // Require exact amount
token.safeTransferFrom(msg.sender, address(this), price);
_safeMint(msg.sender, _tokenId);
emit NFTMinted(msg.sender, _tokenId, price);
}
Updates

Lead Judging Commences

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

DebtToken::mint miscalculates debt by applying interest twice, inflating borrow amounts and risking premature liquidations

Support

FAQs

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

Give us feedback!