function revokeSigningRole(address _account) external nonReentrant onlyOwner noneZeroAddress(_account) {
if (!s_isSigner[_account]) {
revert MultiSigTimelock__AccountIsNotASigner();
}
if (s_signerCount <= 1) {
revert MultiSigTimelock__CannotRevokeLastSigner();
}
uint256 indexToRemove = type(uint256).max;
for (uint256 i = 0; i < s_signerCount; i++) {
if (s_signers[i] == _account) {
indexToRemove = i;
break;
}
}
if (indexToRemove < s_signerCount - 1) {
s_signers[indexToRemove] = s_signers[s_signerCount - 1];
}
s_signers[s_signerCount - 1] = address(0);
s_signerCount -= 1;
s_isSigner[_account] = false;
_revokeRole(SIGNING_ROLE, _account);
}
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../src/MultiSigTimelock.sol";
contract MultiSigTimelockDeadlockTest is Test {
MultiSigTimelock multiSig;
address owner = address(0xA11CE);
address alice = address(0xB0B);
address bob = address(0xC0C);
address charlie = address(0xD0D);
address recipient = address(0xF0F);
function setUp() public {
vm.prank(owner);
multiSig = new MultiSigTimelock();
vm.startPrank(owner);
multiSig.grantSigningRole(alice);
multiSig.grantSigningRole(bob);
multiSig.grantSigningRole(charlie);
vm.stopPrank();
vm.deal(address(multiSig), 10 ether);
}
function testDeadlockOccursWhenRemovingTooManySigners() public {
vm.prank(owner);
uint256 txId = multiSig.proposeTransaction(recipient, 1 ether, "");
vm.prank(alice);
multiSig.confirmTransaction(txId);
vm.prank(bob);
multiSig.confirmTransaction(txId);
vm.prank(owner);
multiSig.revokeSigningRole(charlie);
vm.expectRevert();
vm.prank(bob);
multiSig.executeTransaction(txId);
}
}
function revokeSigningRole(address _account) external onlyOwner {
if (s_signerCount <= REQUIRED_CONFIRMATIONS) {
revert("Cannot remove signer: would drop below required confirmations");
}
...
}