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

Improper removal of beneficiary from the array

Summary

Function removeBeneficiary is not removing addreses from beneficiaries[] array - it's only replacing it with default value(address(0)). This couses several issues in functions that distribute funds.




Vulnerability Details

Function removeBeneficiary() for removing addresses by "delete" keyword, which only replace addresses in array with address(0), and it doesn't reduce length of beneficiaries[] array.

function removeBeneficiary(address _beneficiary) external onlyOwner {
uint256 indexToRemove = _getBeneficiaryIndex(_beneficiary);
delete beneficiaries[indexToRemove];
}

Functions withdrawInheritedFunds() and buyOutEstateNFT() use beneficiaries.length as divisor for deviding the funds. When funds are distributed, they're divided by the total number of beneficiaries (including deleted ones), resulting in each legitimate beneficiary receiving less than their fair share.

Also funds sent to address(0) will be essencially burned without change to recover them.

function withdrawInheritedFunds(address _asset) external {
if (!isInherited) {
revert NotYetInherited();
}
uint256 divisor = beneficiaries.length;//here
if (_asset == address(0)) {
uint256 ethAmountAvailable = address(this).balance;
uint256 amountPerBeneficiary = ethAmountAvailable / divisor;
for (uint256 i = 0; i < divisor; i++) {
address payable beneficiary = payable(beneficiaries[i]);
(bool success,) = beneficiary.call{value: amountPerBeneficiary}("");
require(success, "something went wrong");
}
} else {
uint256 assetAmountAvailable = IERC20(_asset).balanceOf(address(this));
uint256 amountPerBeneficiary = assetAmountAvailable / divisor;
for (uint256 i = 0; i < divisor; i++) {
IERC20(_asset).safeTransfer(beneficiaries[i], amountPerBeneficiary);
}
}
}
function buyOutEstateNFT(uint256 _nftID) external onlyBeneficiaryWithIsInherited {
uint256 value = nftValue[_nftID];
uint256 divisor = beneficiaries.length;//here
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);
}

It also unnecessarily consume more gas that is needed when going through empty addresses in while loop from onlyBeneficiaryWithIsInherited modifier.

modifier onlyBeneficiaryWithIsInherited() {
uint256 i = 0;
while (i < beneficiaries.length + 1) {
if (msg.sender == beneficiaries[i] && isInherited) {
break;
}
i++;
}
_;
}

Test case for foundry that shows bad calculations when calling withdrawInheritedFunds(address(0)) after one of the beneficiaries was removed:

function test_badWithdrawInheritedFunds() public {
address user2 = makeAddr("user2");
address user3 = makeAddr("user3");
address user4 = makeAddr("user4");
vm.startPrank(owner);
im.addBeneficiery(user1);
im.addBeneficiery(user2);
im.addBeneficiery(user3);
im.addBeneficiery(user4);
console.log("address on index 3(user4)", im.getAddresOnIndexBeneficiary(3));
im.removeBeneficiary(user4);
vm.stopPrank();
vm.warp(1);
vm.deal(address(im), 12e10);
vm.warp(1 + 90 days);
vm.startPrank(user1);
im.inherit();
im.withdrawInheritedFunds(address(0));
vm.stopPrank();
assertEq(user3.balance, 4e10, "user 3 should get 4e10 ");
}
│ └─ ← [Stop]
│ ├─ [0] 0x0000000000000000000000000000000000000000::fallback{value: 30000000000}()
│ │ └─ ← [Stop]
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::assertEq(30000000000 [3e10], 40000000000 [4e10], "user 3 should get 4e10 ") [staticcall]
│ └─ ← [Revert] user 3 should get 4e10 : 30000000000 != 40000000000
└─ ← [Revert] user 3 should get 4e10 : 30000000000 != 40000000000


Impact:
\

Funds sent to address(0) are effectively burned and cannot be recovered

When funds are distributed, they're divided by the total number of beneficiaries (including deleted ones), resulting in each legitimate beneficiary receiving less than their fair share

Tools Used

Foundry, for better visualization i have imported Console to test file:

import {Test, console} from "forge-std/Test.sol";

and I created getter function:

function getAddresOnIndexBeneficiary(
uint256 _index
) public returns (address) {
return beneficiaries[_index];
}

Recommendations

Implement removing by Swap and Pop pattern

Updates

Lead Judging Commences

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

Incorrect removal from beneficiary list causes funds to be send to 0 address

Support

FAQs

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