Summary
amountPerBeneficiary
in InheritanceManager.sol::withdrawInheritedFunds()
may have rounding error in certain scenarios when calculating how much ETH or ERC20 token each beneficiary should receive. This is more impactful if the underlying currency has smaller decimals.
Vulnerability Details
amountPerBeneficiary
divides the balance available in the contract by the number of beneficiaries. This could result in rounding error and funds to beneficiaries to not receive correct amount.
function withdrawInheritedFunds(address _asset) external {
if (!isInherited) {
revert NotYetInherited();
}
uint256 divisor = beneficiaries.length;
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);
}
}
}
Impact
function test_withdrawInheritedFundsRoundingError() public {
usdc.mint(address(im), 10e6);
vm.deal(address(im), 10e18);
console.log("IM initial ETH balance:", address(im).balance);
console.log("IM initial USDC balance:", usdc.balanceOf(address(im)));
address user2 = makeAddr("user2");
address user3 = makeAddr("user3");
vm.startPrank(owner);
im.addBeneficiery(user1);
im.addBeneficiery(user2);
im.addBeneficiery(user3);
vm.stopPrank();
vm.warp(90 days + 1);
vm.startPrank(user1);
im.inherit();
im.withdrawInheritedFunds(address(0));
console.log("user1.ETHbalance:", user1.balance);
console.log("user2.ETHbalance:", user2.balance);
console.log("user3.ETHbalance:", user3.balance);
console.log("IM remaining ETH balance:", address(im).balance);
im.withdrawInheritedFunds(address(usdc));
vm.stopPrank();
console.log("user1.USDCbalance:", usdc.balanceOf(user1));
console.log("user2.USDCbalance:", usdc.balanceOf(user2));
console.log("user3.USDCbalance:", usdc.balanceOf(user3));
console.log("IM remaining USDC balance:", usdc.balanceOf(address(im)));
}
Results
[PASS] test_withdrawInheritedFundsRoundingError() (gas: 404882)
Logs:
IM initial ETH balance: 10000000000000000000
IM initial USDC balance: 10000000
user1.ETHbalance: 3333333333333333333
user2.ETHbalance: 3333333333333333333
user3.ETHbalance: 3333333333333333333
IM remaining ETH balance: 1
user1.USDCbalance: 3333333
user2.USDCbalance: 3333333
user3.USDCbalance: 3333333
IM remaining USDC balance: 1
Tools Used
Manual review
Recommendations
If the underlying ERC20 token has a small decimal, can perform multiplying to increase precision in handling fractional values more effectively.