Skip to content

Allow Vote Changes to Reset Timelock and Approval State #135

@Henry-E

Description

@Henry-E

Hey! Just noticed an interesting edge case with proposals in the Approved state that might be worth discussing.

Currently, once a proposal enters the Approved state (with its timelock countdown starting), there's no way to meaningfully change your vote if you were one of the approvers. While you can vote to cancel, this doesn't remove your original approval vote or reset the timelock countdown - it just adds your vote to a separate cancellation tally.

Here's a scenario where this gets tricky:
Let's say you have a timelock multisig where some keys get compromised. The attacker submits and approves a proposal, starting the 1-day timelock countdown. Even if you control one of the keys that voted to approve, changing that vote doesn't help much - you can't reset the timelock or move the proposal back to Active state. You'd need to reach a whole new threshold of cancel votes before the timelock expires.

Might be worth considering letting cancel votes remove approval votes (like reject votes do), or maybe resetting the timelock when approval votes change.

See:

/// Registers a cancellation vote.
pub fn cancel(&mut self, member: Pubkey, threshold: usize) -> Result<()> {
// Insert the vote of cancellation.
match self.cancelled.binary_search(&member) {
Ok(_) => return err!(MultisigError::AlreadyCancelled),
Err(pos) => self.cancelled.insert(pos, member),
};
// If current number of cancellations reaches threshold, mark the transaction as `Cancelled`.
if self.cancelled.len() >= threshold {
self.status = ProposalStatus::Cancelled {
timestamp: Clock::get()?.unix_timestamp,
};
}
Ok(())
}

vs.

pub fn reject(&mut self, member: Pubkey, cutoff: usize) -> Result<()> {
// If `member` has previously voted to approve, remove that vote.
if let Some(vote_index) = self.has_voted_approve(member.key()) {
self.remove_approval_vote(vote_index);
}
// Insert the vote of rejection.
match self.rejected.binary_search(&member) {
Ok(_) => return err!(MultisigError::AlreadyRejected),
Err(pos) => self.rejected.insert(pos, member),
};

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions