Skip to main content

QuadraticVotingMechanism

Git Source

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

NameTypeDescription
_implementationaddressAddress of shared TokenizedAllocationMechanism implementation
_configAllocationConfigConfiguration struct with mechanism parameters
_alphaNumeratoruint256Alpha numerator (dimensionless ratio, 0 to _alphaDenominator)
_alphaDenominatoruint256Alpha 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

NameTypeDescription
proposeraddressAddress attempting to create proposal

Returns

NameTypeDescription
<none>boolauthorized True if proposer is keeper or management

_validateProposalHook

Hook to validate proposal exists

function _validateProposalHook(uint256 pid) internal view virtual override returns (bool);

Parameters

NameTypeDescription
piduint256Proposal ID to validate

Returns

NameTypeDescription
<none>boolvalid 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

NameTypeDescription
<none>boolauthorized 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

NameTypeDescription
<none>address
deposituint256Amount deposited in asset's native decimals

Returns

NameTypeDescription
votingPoweruint256Normalized 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

NameTypeDescription
amountuint256Token amount in asset's native decimals
assetDecimalsuint8Decimal places of the asset token

Returns

NameTypeDescription
normalizeduint256Amount 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

NameTypeDescription
piduint256Proposal ID to vote on
voteraddressAddress casting the vote
choiceTokenizedAllocationMechanism.VoteTypeVote type (must be VoteType.For)
weightuint256Number of votes to cast (dimensionless)
oldPoweruint256Voter's current voting power (in 18 decimals)

Returns

NameTypeDescription
<none>uint256newPower 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

NameTypeDescription
piduint256Proposal ID to check

Returns

NameTypeDescription
meetsQuorumboolTrue 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

NameTypeDescription
piduint256Proposal ID

Returns

NameTypeDescription
sharesuint256Total 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

NameTypeDescription
handledboolFalse to indicate default minting should be used
assetsTransferreduint2560 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

NameTypeDescription
<none>uint256Total 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

NameTypeDescription
piduint256Proposal ID

Returns

NameTypeDescription
sumContributionsuint256Total contribution amounts
sumSquareRootsuint256Sum of square roots for quadratic calculation
quadraticFundinguint256Quadratic funding component
linearFundinguint256Linear 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

NameTypeDescription
newNumeratoruint256Numerator of new alpha value (dimensionless ratio)
newDenominatoruint256Denominator 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

NameTypeDescription
matchingPoolAmountuint256Fixed amount of matching funds available (in token's native decimals)
totalUserDepositsuint256Total user deposits in the mechanism (in token's native decimals)

Returns

NameTypeDescription
optimalAlphaNumeratoruint256Calculated alpha numerator
optimalAlphaDenominatoruint256Calculated 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);