[Governance] Understanding the how pending proposal state is derived in Governor.sol

In v5.0.0 the following snippet decides if the proposal is in pending state.

       uint256 snapshot = proposalSnapshot(proposalId);

        uint256 currentTimepoint = clock();

        if (snapshot >= currentTimepoint) {
            return ProposalState.Pending;

Where snapshot is the time at which voting starts which is proposalCreatonTime +votingDelay.

Consider the following scenario, where clock is block.number:

  1. Proposal created at block.number=1 with a votingDelay of 1 block.
  2. The logic above forces the voting to start from block.number=3 instead of block.number=2

That translates to a 2 block votingDelay instead of the configured 1 block delay.

Please correct me if I'm missing something.

Hey @_rahul!

This is correct. The reason is that because the votes weight is calculated at the proposal snapshot, a user can programatically cast a vote exactly when snapshot == currentTimepoint and then transfer the tokens to a different account. Since snapshot == currentTimepoint, the new token holder can still cast a vote, effectively doubling those tokens voting power.

Take a look to the _castVote function:

function _castVote(
    uint256 proposalId,
    address account,
    uint8 support,
    string memory reason,
    bytes memory params
) internal virtual returns (uint256) {
    _validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Active));

    uint256 weight = _getVotes(account, proposalSnapshot(proposalId), params);
    _countVote(proposalId, account, support, weight, params);


Taking your example, if voting starts exactly at block number 3, then the voting weight is that of block number 2, meaning that the account can no longer transfer its votes.