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):
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(); }
token.safeTransferFrom(msg.sender, address(this), _amount);
_safeMint(msg.sender, _tokenId);
if (_amount > price) {
uint256 refundAmount = _amount - price;
token.safeTransfer(msg.sender, refundAmount);
}
}
function _update(
address from,
address to,
uint256 amount
) internal virtual override {
if (baseTax == 0 || from == address(0) || to == address(0) ||
whitelistAddress[from] || whitelistAddress[to] ||
feeCollector == address(0)) {
super._update(from, to, amount);
return;
}
uint256 totalTax = amount.percentMul(baseTax);
uint256 burnAmount = totalTax * burnTaxRate / baseTax;
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:
Initial transfer (1100 RAAC):
Refund of excess (100 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(); }
token.safeTransferFrom(msg.sender, address(this), price);
_safeMint(msg.sender, _tokenId);
emit NFTMinted(msg.sender, _tokenId, price);
}