`MultiSigTimelock::revokeSigningRole() updates `s_isSigner` and `s_signers[]`, and revokes the signer’s AccessControl role. It does not iterate through pending transactions to clear old confirmations or decrement the confirmation counter.
Transactions may execute under the impression that quorum is met, but quorum includes votes from revoked signers.
When a signer is revoked using `MultiSigTimelock::revokeSigningRole`, their prior confirmations remain stored in `s_signatures[txnId][signer]` and counted in `s_transactions[txnId].confirmations`. This means transactions can appear to have quorum even though one or more confirmations were cast/approved by an entity/signer that is no longer authorized.
Add below code into MultiSigTimelockTest.t.sol file.
function testRevokedSignerStillCountsConfirmation() public {
multiSigTimelock.grantSigningRole(SIGNER_TWO);
multiSigTimelock.grantSigningRole(SIGNER_THREE);
multiSigTimelock.grantSigningRole(SIGNER_FOUR);
multiSigTimelock.grantSigningRole(SIGNER_FIVE);
uint256 txnId = multiSigTimelock.proposeTransaction(SPENDER_ONE, 1 ether, "");
multiSigTimelock.confirmTransaction(txnId);
vm.prank(SIGNER_TWO);
multiSigTimelock.confirmTransaction(txnId);
vm.prank(SIGNER_THREE);
multiSigTimelock.confirmTransaction(txnId);
MultiSigTimelock.Transaction memory txnBefore = multiSigTimelock.getTransaction(txnId);
assertEq(txnBefore.confirmations, 3, "Should have 3 confirmations before revocation");
multiSigTimelock.revokeSigningRole(SIGNER_THREE);
MultiSigTimelock.Transaction memory txnAfter = multiSigTimelock.getTransaction(txnId);
assertEq(txnAfter.confirmations, 3, "Revoked signer still counted in confirmations");
}