Voting power (Token Voting Plugin)
Token Voting Plugin draws every voter's weight from a token. Snapshots and delegation determine that weight. The token's clock determines the snapshot timepoint, and excluded accounts affect the total supply used for thresholds. Voting power also determines who is allowed to propose.
The plugin itself holds no balances or vote bookkeeping. It reads power from an IVotes token (OpenZeppelin's ERC20Votes and equivalents) through three functions: getVotes (current power), getPastVotes (power at a past point), and getPastTotalSupply (total at a past point). Everything below follows from those.
The snapshot
When a proposal is created it fixes a snapshot at the timepoint just before creation (block.number - 1, or block.timestamp - 1 for timestamp-based tokens). Every vote on that proposal is then weighed by getPastVotes(voter, snapshot), the voter's power at that fixed point, no matter when they actually vote.
The snapshot prevents double voting with the same tokens. Suppose Alice has 100 self-delegated tokens and Bob has none at the snapshot. Alice votes with 100 power, then transfers the tokens to Bob. Even if Bob self-delegates them, his power for this proposal is still zero: getPastVotes reads the earlier checkpoint. Transferring tokens or moving their delegation cannot make the same power count at a second address.
Using a past timepoint also prevents transactions in the proposal's creation block from changing its electorate. Acquiring or delegating tokens in response to a pending proposal is too late if both transactions enter the same block. Prepare holders before creation, even if the proposal's voting start is scheduled for later; waiting for that start does not refresh its snapshot.
A proposal with zero effective total supply at its snapshot reverts with NoVotingPower; zero delegated power at an individual account instead makes that account unable to vote.
Delegation
With IVotes, holding tokens grants zero voting power until the balance is delegated (even to yourself). getVotes reflects delegated power, not raw balance. A holder who never delegates has tokens but no vote.
How each token type handles this differs, which matters when choosing a token; see Governance tokens → delegation. A freshly minted GovernanceERC20 may leave holders undelegated (unless minting auto-delegates), while the wrapped token auto-delegates on receipt.
Whose supply counts: excluded accounts
The participation and approval thresholds are measured against effective total voting power. The plugin subtracts the historical delegated power of accounts excluded at installation:
totalVotingPower(snapshot) = getPastTotalSupply(snapshot) − Σ getPastVotes(excluded, snapshot)Non-circulating holdings, such as a treasury, vesting contract, or the DAO's own tokens, can make quorum harder to reach if their supply counts but they do not participate. The installation can name accounts whose delegated power should be subtracted, for example a treasury that self-delegates those holdings. This subtracts historical delegated power, not balanceOf: listing an undelegated treasury does not remove its token balance from the denominator.
The exclusion list cannot be changed after installation. The worst-case support check reads the effective snapshot supply again; changing the excluded set could change that check for an existing proposal while its participation and approval amounts remain frozen. Choose the list before installing.
Exclusion adjusts the denominator; it does not prevent the listed accounts from voting. Account for their delegation and voting behavior when choosing the list.
Block-based vs timestamp-based tokens
ERC20Votes tokens historically snapshot by block number, but some newer tokens snapshot by timestamp (per ERC-6372). The plugin detects which at install: it probes the token's clock() / CLOCK_MODE() and remembers whether it's timestamp-based (a token that doesn't implement ERC-6372 is treated as block-based). Every place that computes the snapshot then uses the matching unit. If a token's two clock signals disagree (it claims one mode but behaves like the other) the plugin refuses it (TokenClockMismatch), a guard against a token misrepresenting how its snapshots work. As a builder you don't manage this, but it's why an IVotes token must be consistent about its clock.
Who may propose
minProposerVotingPower sets a floor on how much power an address needs to open a proposal, and it's enforced outside the voting logic, by a small permission condition. TokenVotingSetup grants CREATE_PROPOSAL_PERMISSION_ID to any address but attaches a VotingPowerCondition; the condition reads the plugin's current minProposerVotingPower and checks the caller's power at the snapshot rule above. A floor of zero means anyone may propose.
This is a clean illustration of the condition pattern: "anyone above the bar" is expressed as an open grant plus a condition, and the bar can change (via voting settings) without touching the permission wiring.
Membership
The Token Voting Plugin implements IMembership: isMember is true for any address that currently holds a balance or has voting power, and the token is announced as the membership-defining contract so indexers know where to look. Note that "is a member" and "can meaningfully vote" differ: someone holding but not delegating is a member with zero power.
Keep in mind
- Delegate, or you have no power. Power is delegated, not held. Expect to guide token holders through delegating (to themselves, at least) before they can vote.
- Power is fixed at the snapshot. Buying or delegating more tokens after a proposal opens does nothing for that proposal, only the snapshot balance counts.
- The token must be consistent about its clock. An
IVotestoken that misreports block vs timestamp snapshots is rejected at install.
See also
- Token Voting Plugin — the plugin overview and lifecycle.
- Governance tokens — how each token type handles delegation.
- Majority voting — the thresholds total voting power feeds.
- Permission conditions — the mechanism gating proposal creation.