MultiSig Timelock

First Flight #55
Beginner FriendlyWallet
100 EXP
View results
Submission Details
Impact: low
Likelihood: high
Invalid

`proposeTransaction()` Restricted to Owner, Signers Cannot Propose Transactions

Root + Impact

Description

  • According to the project specification, any signer holding the SIGNING_ROLE should be able to propose transactions.

  • The proposeTransaction() function is currently protected by the onlyOwner modifier. This restricts transaction proposal rights exclusively to the contract owner. By limiting proposals to the owner, the contract breaks the intended invariant of equal governance among signers and undermines the decentralized nature of the multisig wallet.

    This mismatch between documentation and implementation creates a functional issue, where signers cannot initiate transactions, even though they are expected to have that authority once granted the role.


@> function proposeTransaction(address to, uint256 value, bytes calldata data)
external
nonReentrant
noneZeroAddress(to)
onlyOwner
returns (uint256)
{
return _proposeTransaction(to, value, data);
}

Risk

Likelihood:

  • Whenever a signer (non‑owner) attempts to propose a transaction. Because the function is explicitly restricted with onlyOwner, every attempt by a signer will revert.

Impact:

  • Signers cannot propose transactions, which contradicts the advertised functionality and may block legitimate use cases.

Proof of Concept

Running below code from multiSigTimelockTest.t.sol, it will proove that, other user apart from owner can not propose a tranxaction when call proposeTransaction(). Where by they are required to propose as per the project functionality.

function testProposeTransactionRevertsIfNonOwner() public {
address nonOwner = makeAddr("non_owner");
vm.prank(nonOwner);
vm.expectRevert();
multiSigTimelock.proposeTransaction(SPENDER_ONE, OWNER_BALANCE_ONE, hex"");
}

Recommended Mitigation

Replace the onlyOwner modifier with onlyRole(SIGNING_ROLE) so that any signer(both signer and owner) can propose transactions, not just the contract owner.

function proposeTransaction(address to, uint256 value, bytes calldata data)
external
nonReentrant
noneZeroAddress(to)
- onlyOwner
+ onlyRole(SIGNING_ROLE)
returns (uint256)
{
return _proposeTransaction(to, value, data);
}
Updates

Lead Judging Commences

kelechikizito Lead Judge
11 days ago
kelechikizito Lead Judge 4 days ago
Submission Judgement Published
Invalidated
Reason: Other

Support

FAQs

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

Give us feedback!