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

[H-3] In `InheritanceManager::buyOutEstateNFT`, an error in division, leads to partial payments to beneficiaries and loss of funds

Description: In InheritanceManager::buyOutEstateNFT, the total amount needed for a buyout is calculated and then divided up and sent to the other beneficiaries. The issue is when dividing the total amount for disbursement, it includes the paying beneficiary in the calculation even though their share isn't included. This incorrectly reduces the amount paid to the other beneficiaries in the buyout.

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); // @audit divisor is incorrect
}
}
nft.burnEstate(_nftID);
}

Impact: The beneficiaries being bought out are shorted the amount owed. What's more critical is if the owner actually passed away and these funds will be locked in the InheritanceManager manager contract with no way to get them out.

Proof of Concept:

Add the following unit test to InheritanceManagerTest.t.sol:

function test_buyOutEstateNFTLossOfFunds() public {
address user2 = makeAddr("user2");
address user3 = makeAddr("user3");
// Add user1, user2, and user3 as beneficiaries
vm.warp(1);
vm.startPrank(owner);
im.addBeneficiery(user1);
im.addBeneficiery(user2);
im.addBeneficiery(user3);
// Create an NFT estate
im.createEstateNFT("our beach-house", 6e6, address(usdc));
vm.stopPrank();
// Mint 4 USDC tokens for user3
usdc.mint(user3, 4e6);
// Skip ahead 90 days so timelock is expired
vm.warp(1 + 90 days);
// User3 approves the InheritanceManager contract to spend 4 USDC tokens
vm.startPrank(user3);
usdc.approve(address(im), 4e6);
// User3 calls inherit and buys out the NFT estate
im.inherit();
im.buyOutEstateNFT(1);
vm.stopPrank();
// User1 and User2 do not have 2 USDC tokens each
console.log("usdc.balanceOf(user1)", usdc.balanceOf(user1));
console.log("usdc.balanceOf(user2)", usdc.balanceOf(user2));
assertNotEq(usdc.balanceOf(user1), 2e6);
assertNotEq(usdc.balanceOf(user2), 2e6);
// The extra funds remain locked in the InheritanceManager contract
console.log("usdc.balanceOf(address(im))", usdc.balanceOf(address(im)));
assertNotEq(usdc.balanceOf(address(im)), 0);

Run the unit test: forge test --mt test_buyOutEstateNFTLossOfFunds:

Ran 1 test for test/InheritanceManagerTest.t.sol:InheritanceManagerTest
[PASS] test_buyOutEstateNFTLossOfFunds() (gas: 457255)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.87ms (1.19ms CPU time)

Recommended Mitigation: Fix the divisor by replacing with (Number Of Beneficiaries - 1). This value is calculated in the multiplier variable in InheritanceManager::buyOutEstateNFT.

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);
+ IERC20(assetToPay).safeTransfer(beneficiaries[i], finalAmount / multiplier);
}
}
nft.burnEstate(_nftID);
}
Updates

Lead Judging Commences

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

buyOutNFT has wrong denominator

Support

FAQs

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

Give us feedback!