QuadraticVotingMechanism
Inherits: BaseAllocationMechanism, ProperQF
Title: Quadratic Voting Mechanism
Author: Golem Foundation
Implements quadratic funding using ProperQF algorithm
Follows Yearn V3 proxy pattern with ProperQF voting strategy
QUADRATIC FUNDING:
- Cost of voting is quadratic: weight votes costs weight^2 power
- Prevents whale dominance (expensive to cast many votes)
- Benefits: Small contributors have proportionally more impact
VOTE COST EXAMPLES:
- 10 votes costs 100 voting power (10^2)
- 20 votes costs 400 voting power (20^2)
- 100 votes costs 10,000 voting power (100^2)
ONE-TIME VOTING:
Users can only vote ONCE per proposal.
- Vote is final and cannot be changed
- Cannot increase, decrease, or cancel vote
- Additional deposits create new voting power for other proposals
- UI must warn users before voting
VOTING POWER:
- Normalized to 18 decimals regardless of asset decimals
- Linear relationship: 1 asset = 1 voting power (after normalization)
- Each deposit adds to cumulative voting power
Notes:
-
security-contact: [email protected]
-
security: One-time voting prevents manipulation via vote adjustments
-
security: Quadratic cost reduces whale influence
State Variables
hasVoted
Tracks whether a voter has voted on a specific proposal
Maps pid → voter → hasVoted (prevents double voting)
mapping(uint256 => mapping(address => bool)) public hasVoted
Functions
constructor
Initialize QuadraticVotingMechanism with configuration and alpha parameters
Called by AllocationMechanismFactory during CREATE2 deployment Sets up ProperQF algorithm with specified alpha weighting
constructor(
address _implementation,
AllocationConfig memory _config,
uint256 _alphaNumerator,
uint256 _alphaDenominator
) BaseAllocationMechanism(_implementation, _config);
Parameters
| Name | Type | Description |
|---|---|---|
_implementation | address | Address of shared TokenizedAllocationMechanism implementation |
_config | AllocationConfig | Configuration struct with mechanism parameters |
_alphaNumerator | uint256 | Alpha numerator (dimensionless ratio, 0 to _alphaDenominator) |
_alphaDenominator | uint256 | Alpha denominator (dimensionless ratio, must be > 0) |
_beforeProposeHook
Hook to validate proposer authorization
Only keeper or management addresses can create proposals Prevents spam and maintains curation quality
function _beforeProposeHook(address proposer) internal view virtual override returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
proposer | address | Address attempting to create proposal |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | authorized True if proposer is keeper or management |
_validateProposalHook
Hook to validate proposal exists
function _validateProposalHook(uint256 pid) internal view virtual override returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
pid | uint256 | Proposal ID to validate |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | valid True if proposal exists |
_beforeSignupHook
Hook to authorize user registration
Allows all users to register, including multiple signups Each signup adds voting power that can be used on un-voted proposals IMPORTANT DESIGN CONSIDERATION: Pure quadratic voting (QV) should restrict to single signups to prevent double-spending of vote credits. However, quadratic funding (QF) variants allow multiple signups where users contribute their own funds to increase voting power. Derived contracts can override to enforce single-signup.
function _beforeSignupHook(address) internal virtual override returns (bool);
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | authorized True (all users can signup) |
_getVotingPowerHook
Hook to calculate voting power from deposit amount
Normalizes asset amount to 18 decimals for consistent voting power 1 token (in asset decimals) = 1 voting power (in 18 decimals) NORMALIZATION EXAMPLES:
- USDC (6 decimals): 1,000,000 (1 USDC) → 1e18 voting power
- WETH (18 decimals): 1e18 (1 WETH) → 1e18 voting power
- WBTC (8 decimals): 100,000,000 (1 WBTC) → 1e18 voting power
function _getVotingPowerHook(address, uint256 deposit)
internal
view
virtual
override
returns (uint256 votingPower);
Parameters
| Name | Type | Description |
|---|---|---|
<none> | address | |
deposit | uint256 | Amount deposited in asset's native decimals |
Returns
| Name | Type | Description |
|---|---|---|
votingPower | uint256 | Normalized voting power in 18 decimals |
_normalizeToDecimals
Internal helper to normalize token amount to 18 decimals
Matches voting power normalization logic in _getVotingPowerHook
function _normalizeToDecimals(uint256 amount, uint8 assetDecimals) internal pure returns (uint256 normalized);
Parameters
| Name | Type | Description |
|---|---|---|
amount | uint256 | Token amount in asset's native decimals |
assetDecimals | uint8 | Decimal places of the asset token |
Returns
| Name | Type | Description |
|---|---|---|
normalized | uint256 | Amount normalized to 18 decimals |
_processVoteHook
Hook to process vote with quadratic cost and single-vote enforcement
Implements quadratic voting: to cast W votes, you pay W² voting power Each voter can only vote ONCE per proposal (no adjustments) QUADRATIC COST FORMULA: cost = weight × weight EXAMPLES:
- Cast 10 votes → costs 100 voting power
- Cast 50 votes → costs 2,500 voting power
- Cast 100 votes → costs 10,000 voting power This makes whale attacks expensive while giving smaller voters proportionally more influence per token.
Notes:
-
security: Single-vote enforcement prevents manipulation via vote adjustments
-
security: Reverts if voter already voted on this proposal
function _processVoteHook(
uint256 pid,
address voter,
TokenizedAllocationMechanism.VoteType choice,
uint256 weight,
uint256 oldPower
) internal virtual override returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
pid | uint256 | Proposal ID to vote on |
voter | address | Address casting the vote |
choice | TokenizedAllocationMechanism.VoteType | Vote type (must be VoteType.For) |
weight | uint256 | Number of votes to cast (dimensionless) |
oldPower | uint256 | Voter's current voting power (in 18 decimals) |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | newPower Remaining voting power after quadratic cost deduction (in 18 decimals) |
_hasQuorumHook
Hook to check if proposal meets quorum threshold
Quorum based on total funding (quadratic + linear components) Uses ProperQF formula: F_j = α×(sum_sqrt)² + (1-α)×sum_contributions
function _hasQuorumHook(uint256 pid) internal view virtual override returns (bool meetsQuorum);
Parameters
| Name | Type | Description |
|---|---|---|
pid | uint256 | Proposal ID to check |
Returns
| Name | Type | Description |
|---|---|---|
meetsQuorum | bool | True if project funding ≥ quorum threshold |
_convertVotesToShares
Hook to convert proposal funding into allocation shares
Returns total funding amount (quadratic + linear components) Both components are already alpha-weighted by ProperQF
function _convertVotesToShares(uint256 pid) internal view virtual override returns (uint256 shares);
Parameters
| Name | Type | Description |
|---|---|---|
pid | uint256 | Proposal ID |
Returns
| Name | Type | Description |
|---|---|---|
shares | uint256 | Total funding to allocate in share base units |
_beforeFinalizeVoteTallyHook
Allow finalization once voting period ends
function _beforeFinalizeVoteTallyHook() internal pure virtual override returns (bool);
_getRecipientAddressHook
Get recipient address for proposal
function _getRecipientAddressHook(uint256 pid) internal view virtual override returns (address);
_requestCustomDistributionHook
Handle custom share distribution - returns false to use default minting
function _requestCustomDistributionHook(address, uint256)
internal
pure
virtual
override
returns (bool handled, uint256 assetsTransferred);
Returns
| Name | Type | Description |
|---|---|---|
handled | bool | False to indicate default minting should be used |
assetsTransferred | uint256 | 0 since no custom distribution is performed |
_calculateTotalAssetsHook
Calculate total assets including matching pool + user deposits for finalization
This snapshots the total asset balance in the contract during finalize
function _calculateTotalAssetsHook() internal view virtual override returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | Total assets available for allocation (matching pool + user signup deposits) |
getProposalFunding
Get project funding breakdown for a proposal
function getProposalFunding(uint256 pid)
external
view
returns (uint256 sumContributions, uint256 sumSquareRoots, uint256 quadraticFunding, uint256 linearFunding);
Parameters
| Name | Type | Description |
|---|---|---|
pid | uint256 | Proposal ID |
Returns
| Name | Type | Description |
|---|---|---|
sumContributions | uint256 | Total contribution amounts |
sumSquareRoots | uint256 | Sum of square roots for quadratic calculation |
quadraticFunding | uint256 | Quadratic funding component |
linearFunding | uint256 | Linear funding component |
setAlpha
Set the alpha parameter for quadratic vs linear funding weighting
Alpha determines the ratio: F_j = α × (sum_sqrt)² + (1-α) × sum_contributions
Only callable by owner (inherited from BaseAllocationMechanism via TokenizedAllocationMechanism)
function setAlpha(uint256 newNumerator, uint256 newDenominator) external;
Parameters
| Name | Type | Description |
|---|---|---|
newNumerator | uint256 | Numerator of new alpha value (dimensionless ratio) |
newDenominator | uint256 | Denominator of new alpha value (dimensionless ratio) |
calculateOptimalAlpha
Calculate optimal alpha for 1:1 shares-to-assets ratio given fixed matching pool amount
Internally normalizes amounts to 18 decimals to match quadratic/linear sum calculations
function calculateOptimalAlpha(uint256 matchingPoolAmount, uint256 totalUserDeposits)
external
view
returns (uint256 optimalAlphaNumerator, uint256 optimalAlphaDenominator);
Parameters
| Name | Type | Description |
|---|---|---|
matchingPoolAmount | uint256 | Fixed amount of matching funds available (in token's native decimals) |
totalUserDeposits | uint256 | Total user deposits in the mechanism (in token's native decimals) |
Returns
| Name | Type | Description |
|---|---|---|
optimalAlphaNumerator | uint256 | Calculated alpha numerator |
optimalAlphaDenominator | uint256 | Calculated alpha denominator |
receive
Reject ETH deposits to prevent permanent fund loss
Overrides BaseAllocationMechanism's receive() function This mechanism only supports ERC20 tokens, not native ETH
Note: security: Prevents accidental ETH loss
receive() external payable override;
Errors
ZeroAddressCannotPropose
error ZeroAddressCannotPropose();
OnlyForVotesSupported
error OnlyForVotesSupported();
InsufficientVotingPowerForQuadraticCost
error InsufficientVotingPowerForQuadraticCost();
AlreadyVoted
error AlreadyVoted(address voter, uint256 pid);