Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

Early Return in `InheritanceManager::buyOutEstateNFT` Prevents NFT Burning, Enabling Infinite Buy-Outs

Summary

Early Return in InheritanceManager::buyOutEstateNFT Prevents NFT Burning, Enabling Infinite Buy-Outs

Vulnerability Details

The InheritanceManager::buyOutEstateNFT function contains an early return statement when the caller is identified as a beneficiary. This skips the critical nft.burnEstate(_nftID) call, leaving the NFT unburned and allowing repeated buy-outs. Additionally, the payment distribution logic is flawed, underpaying beneficiaries.

Impact

High Severity

The NFT’s intended "burn-after-purchase" mechanism is broken, rendering the system’s economic model nonfunctional user can repeatedly call InheritanceManager::buyOutEstateNFT on the same NFT.

Tools Used

Manual code review
Foundry test case (provided)

PoC

1 - The test shows user1, user2, and user3 each call InheritanceManager::buyOutEstateNFT(1), spending 20 USDC each.

2 - The NFT (ID 1) is never burned, as confirmed by the absence of NFTFactory::burnEstate calls in the InheritanceManager logic.

function test_buyOutEstateNFTMultiple() public {
address owner = makeAddr("owner");
address user1 = makeAddr("user1");
address user2 = makeAddr("user2");
address user3 = makeAddr("user3");
vm.startPrank(owner);
im.addBeneficiery(user1);
im.addBeneficiery(user2);
im.addBeneficiery(user3);
im.createEstateNFT("our beach-house", 20, address(usdc));
vm.stopPrank();
usdc.mint(user1, 20);
usdc.mint(user2, 20);
usdc.mint(user3, 20);
vm.warp(1 + 90 days);
vm.startPrank(user1);
usdc.approve(address(im), 20);
im.inherit();
im.buyOutEstateNFT(1);
vm.stopPrank();
vm.startPrank(user2);
usdc.approve(address(im), 20);
im.inherit();
im.buyOutEstateNFT(1);
vm.stopPrank();
vm.startPrank(user3);
usdc.approve(address(im), 20);
im.inherit();
im.buyOutEstateNFT(1);
vm.stopPrank();
}

Recommendations

Ensure the NFT is burned after processing payments:

Ensure the NFT is burned after processing payments:
function buyOutEstateNFT(uint256 _nftID) external onlyBeneficiaryWithIsInherited {
uint256 value = nftValue[_nftID];
uint256 divisor = beneficiaries.length;
uint256 multiplier = beneficiaries.length - 1;
uint256 finalAmount = (value / divisor) * multiplier;
IERC20(assetToPay).safeTransferFrom(msg.sender, address(this), finalAmount);
for (uint256 i = 0; i < beneficiaries.length; i++) {
if (msg.sender == beneficiaries[i]) {
- return;
- } else {
IERC20(assetToPay).safeTransfer(beneficiaries[i], finalAmount / divisor);
}
}
nft.burnEstate(_nftID);
}
Updates

Lead Judging Commences

0xtimefliez Lead Judge 9 months ago
Submission Judgement Published
Validated
Assigned finding tags:

buyOutNFT has return instead of continue

Support

FAQs

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

Give us feedback!