Governance tokens must answer a tricky question: how many votes did an address have at a PAST point in time? Reading the current balance is exploitable — someone could borrow tokens via a flash loan, vote, and return them. OpenZeppelin's ERC20Votes solves this with checkpointed historical balances and a delegation system.
1// Voting power must be DELEGATED to activate (even to yourself):2function delegate(address delegatee) external;34// Read current and historical voting power:5function getVotes(address account) external view returns (uint256);6function getPastVotes(address account, uint256 timepoint)7external view returns (uint256);89// Internally, each balance/delegation change appends a checkpoint:10// struct Checkpoint { uint48 fromBlock; uint208 votes; }
Two ideas make this work. First, DELEGATION: holding tokens gives you zero votes until you delegate them (to yourself or someone else), which separates economic ownership from voting power. Second, CHECKPOINTS: every transfer or delegation writes a (block, votes) checkpoint, so getPastVotes() can look up power at a proposal's start block — immune to last-minute or flash-loan manipulation.
Costs and gotchas: writing checkpoints adds gas to every transfer and delegation. And because power must be delegated to count, a huge fraction of governance tokens often sit with zero active voting power simply because holders never delegated. Many DAOs nudge users to 'activate' their votes for this reason.
1// Activate your own voting power, then read it2await token.write.delegate([myAddress]);3const power = await token.read.getVotes([myAddress]);4// A Governor contract later calls getPastVotes(voter, proposalSnapshot)
Connect your wallet to mark this lesson as complete
Earn 25 EFFORT tokens