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

contractInteractions function lacks deadline update, enabling premature inheritance

Summary

The contractInteractions function in the InheritanceManager contract fails to update the inactivity deadline, unlike other owner-controlled functions like sendETH and sendERC20. This critical omission allows beneficiaries to trigger inheritance even when the owner is actively using the contract, potentially leading to unauthorized asset distribution.

Vulnerability Details

The InheritanceManager contract implements an inactivity-based inheritance system where beneficiaries can claim assets after 90 days of owner inactivity. To track activity, owner-controlled functions should call _setDeadline() to reset this timer. However, the contractInteractions function lacks this critical call

function contractInteractions(...){
...
// Missing _setDeadline() call here
}

this is unlike other owner functions for ex sentEth. This omission creates an inconsistent security model where some owner interactions reset the inactivity timer while others don't.

Impact

Basically this function contractInteractions plays as 'escape hedge'. Owner could do everyhting outlined in the contract, i.e. call every function in the contract and just avoid setting of deadlines _setDeadline, for same functionality

The impact is severe:

  1. Unauthorized Access: An owner who primarily uses the contractInteractions function (which is the most flexible function for interacting with external protocols) may have their assets claimed by beneficiaries despite being active.

  2. Premature Inheritance: Beneficiaries can trigger inheritance after 90 days from the last call to a function that properly updates the deadline, even if the owner has been actively using contractInteractions during that time.

  3. Asset Loss: All contract assets could be distributed to beneficiaries while the owner is still actively managing the contract.

  4. Trust Violation: The core security assumption of the contract (that inheritance only triggers after genuine owner inactivity) is broken.

  5. This function lacks many guard rails validations and could be used for dangeorus code interactions.

  6. Some scenarios might be explored where external selfdesctuct is being interacted with the call in contractInteractionsreturns true and then this results in inaccurate date reference -

    interactions[_target] = data;

Tools Used

  • Manual code review

  • Foundry testing framework

Recommendations

Add deadline update call - modify the contractInteractions function to include the _setDeadline() call

or

create a modifier:

modifier updateDeadline() {
_;
_setDeadline();
}

and attach to owner-controlled functions. Note as part of optimized design setDedline might be done in the beginning prior function execution.

PoC:

In the test testContractInteractionsVsSendETH we show no deadline update is being triggered for the same action as sendEth.

testOwnerSwitchesToContractInteractionsScenario shows owner is active but beneficiary is inheriting.

//SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {Test, console} from "forge-std/Test.sol";
import {InheritanceManager} from "../src/InheritanceManager.sol";
import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol";
contract ContractInteractionsNoDeadlinePoC is Test {
InheritanceManager im;
address owner = makeAddr("owner");
address recipient = makeAddr("recipient");
address beneficiary = makeAddr("beneficiary");
address beneficiary2 = makeAddr("beneficiary2");
function setUp() public {
vm.startPrank(owner);
im = new InheritanceManager();
vm.deal(address(im), 10 ether);
im.addBeneficiery(beneficiary);
im.addBeneficiery(beneficiary2);
vm.stopPrank();
}
function testContractInteractionsVsSendETH() public {
uint256 initialDeadline = im.getDeadline();
vm.warp(block.timestamp + 1 days);
console.log("Initial deadline:", initialDeadline);
vm.startPrank(owner);
im.sendETH(1 ether, recipient);
vm.stopPrank();
uint256 deadlineAfterSendETH = im.getDeadline();
console.log("Deadline after sendETH:", deadlineAfterSendETH);
assertTrue(
deadlineAfterSendETH > initialDeadline,
"sendETH should update the deadline"
);
vm.warp(block.timestamp + 1 days);
bytes memory emptyPayload = "";
vm.startPrank(owner);
im.contractInteractions(recipient, emptyPayload, 1 ether, false);
vm.stopPrank();
uint256 deadlineAfterContractInteractions = im.getDeadline();
console.log(
"Deadline after contractInteractions:",
deadlineAfterContractInteractions
);
assertEq(
deadlineAfterContractInteractions,
deadlineAfterSendETH,
"contractInteractions should not update the deadline"
);
}
function testOwnerSwitchesToContractInteractionsScenario() public {
vm.startPrank(owner);
console.log("Owner using sendETH (correctly updates deadline)");
im.sendETH(1 ether, recipient);
uint256 deadlineAfterSendETH = im.getDeadline();
console.log("Owner switches to contractInteractions (doesn't update deadline)");
vm.warp(block.timestamp + 60 days);
for (uint i = 0; i < 3; i++) {
im.contractInteractions(recipient, "", 0.5 ether, false);
vm.warp(block.timestamp + 10 days);
}
vm.stopPrank();
vm.prank(beneficiary);
im.inherit();
vm.prank(beneficiary);
im.withdrawInheritedFunds(address(0));
console.log("Final contract balance:", address(im).balance);
console.log("Beneficiary balance:", beneficiary.balance);
}
}
Updates

Lead Judging Commences

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

functions do not reset the deadline

Support

FAQs

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

Give us feedback!