This tutorial explains how to use the QuadraticFunding contract, which implements a quadratic funding mechanism for distributing matching funds to projects on the Backr platform.
Each funding round has:
- Start and end time
- Matching pool
- Contribution limits (min/max)
- Project contributions tracking
- Participant eligibility
The matching amount for each project is calculated based on the square root of contributions, giving more weight to multiple small contributions over fewer large ones.
// Round configuration
RoundConfig memory config = RoundConfig({
startTime: block.timestamp + 1 days,
endTime: block.timestamp + 15 days,
minContribution: 0.1 ether,
maxContribution: 10 ether
});
// Create round with initial matching pool
quadraticFunding.createRound{value: 100 ether}(config);
Requirements:
- Only admins can create rounds
- Must include initial matching pool funds
- Valid time period (end > start)
- Valid contribution limits (max > min)
// Add more funds to matching pool
quadraticFunding.contributeToMatchingPool{value: 50 ether}(roundId);
// Verify participant eligibility
quadraticFunding.verifyParticipant(participantAddress, true);
// Contribute to a project
quadraticFunding.contribute{value: 1 ether}(projectId);
Requirements:
- Active round
- Eligible participant
- Contribution within min/max limits
- Round not cancelled
// Get total contributions for a project
uint256 total = quadraticFunding.getProjectContributions(roundId, projectId);
// Get individual contribution
uint256 amount = quadraticFunding.getContribution(
roundId,
projectId,
contributorAddress
);
// Check if round is active
bool active = quadraticFunding.isRoundActive();
// Only admins can cancel rounds
quadraticFunding.cancelRound();
// Calculate and distribute matching funds
quadraticFunding.finalizeRound();
Important Notes:
- Round must be ended
- Cannot be already finalized
- Must have contributions
- Not cancelled
// Get round statistics
RoundAnalytics memory analytics = quadraticFunding.getRoundAnalytics(roundId);
// Analytics include:
// - Unique contributors
// - Total projects
// - Average contribution
// - Median contribution
-
Round Lifecycle Events:
event RoundStarted(uint256 indexed roundId, uint256 matchingPool); event RoundFinalized(uint256 indexed roundId, uint256 totalMatching); event RoundCancelledEvent(uint256 indexed roundId); event RoundConfigured( uint256 indexed roundId, uint256 startTime, uint256 endTime, uint256 minContribution, uint256 maxContribution );
-
Contribution Events:
event ContributionAdded( uint256 indexed roundId, uint256 indexed projectId, address indexed contributor, uint256 amount ); event MatchingPoolContribution( uint256 indexed roundId, address indexed contributor, uint256 amount ); event MatchingFundsDistributed( uint256 indexed roundId, uint256 indexed projectId, uint256 amount );
-
Participant Management:
event ParticipantVerified( address indexed participant, bool eligible );
The matching amount for each project is calculated using the quadratic funding formula:
- Calculate square root of each project's total contributions
- Sum all square roots
- Distribute matching pool proportionally based on square root ratios
Example:
Project A: 100 ETH from 1 contributor
Project B: 100 ETH from 100 contributors
Square root calculation:
A = √100 = 10
B = √(1² × 100) = √100 = 10
Despite same total, they receive equal matching because B had more contributors
-
Round Creation
- Set appropriate duration (default 14 days)
- Consider contribution limits carefully
- Ensure sufficient matching pool
-
Participant Management
- Verify participants before round starts
- Document verification criteria
- Monitor eligibility status
-
Contribution Handling
- Monitor contribution patterns
- Track matching pool size
- Consider gas costs
-
Round Finalization
- Wait for round completion
- Verify all contributions processed
- Monitor matching distribution
Here's a full example of managing a funding round:
// 1. Create new round
RoundConfig memory config = RoundConfig({
startTime: block.timestamp + 1 days,
endTime: block.timestamp + 15 days,
minContribution: 0.1 ether,
maxContribution: 10 ether
});
quadraticFunding.createRound{value: 100 ether}(config);
// 2. Verify participants
address[] memory participants = getEligibleParticipants();
for (uint i = 0; i < participants.length; i++) {
quadraticFunding.verifyParticipant(participants[i], true);
}
// 3. Accept contributions during round
// (from participant perspective)
quadraticFunding.contribute{value: 1 ether}(projectId);
// 4. Monitor round progress
RoundAnalytics memory analytics = quadraticFunding.getRoundAnalytics(
currentRound
);
// 5. Finalize round after end
if (block.timestamp > config.endTime) {
quadraticFunding.finalizeRound();
}
// 6. Check matching results
uint256 matching = quadraticFunding.getMatchingAmount(
currentRound,
projectId
);
-
Round Management
- Verify round parameters
- Monitor matching pool
- Handle cancellations properly
-
Contribution Validation
- Check eligibility
- Verify contribution limits
- Monitor for manipulation
-
Matching Distribution
- Verify calculations
- Monitor fund transfers
- Handle edge cases
-
Analytics
- Track unusual patterns
- Monitor for gaming attempts
- Validate statistics
Common errors you might encounter:
RoundNotActive() // Round hasn't started or has ended
RoundAlreadyActive() // Cannot start new round while one is active
InsufficientContribution() // Contribution below minimum
RoundNotEnded() // Cannot finalize active round
RoundAlreadyFinalized() // Round already completed
NoContributions() // No contributions to process
MatchingPoolEmpty() // No matching funds available
RoundCancelledError() // Round was cancelled
UnauthorizedAdmin() // Not an admin
ContributionTooLow() // Below minimum contribution
ContributionTooHigh() // Above maximum contribution
ParticipantNotEligible() // Contributor not verified
InvalidRoundConfig() // Invalid round parameters