https://github.com/blockful/ranked-choice-voting
Onchain ranked-choice voting for DAOs with Copeland and Schulze methods
https://github.com/blockful/ranked-choice-voting
condorcet copeland dao ethereum foundry governance ranked-choice-voting schulze smart-contracts solidity voting
Last synced: 23 days ago
JSON representation
Onchain ranked-choice voting for DAOs with Copeland and Schulze methods
- Host: GitHub
- URL: https://github.com/blockful/ranked-choice-voting
- Owner: blockful
- License: mit
- Created: 2026-05-25T06:59:12.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-05-27T05:12:58.000Z (about 2 months ago)
- Last Synced: 2026-05-27T07:10:49.657Z (about 2 months ago)
- Topics: condorcet, copeland, dao, ethereum, foundry, governance, ranked-choice-voting, schulze, smart-contracts, solidity, voting
- Language: Solidity
- Size: 94.7 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Security: SECURITY.md
Awesome Lists containing this project
README
# ranked-choice-voting
Onchain ranked-choice voting for DAOs with Copeland and Schulze methods.
   
## Overview
Two standalone Solidity contracts that run pairwise-Condorcet ranked-choice elections fully onchain. Voters submit ranked ballots weighted by `IVotes` (ERC20Votes / ERC721Votes) snapshots. `CopelandVoting` and `SchulzeVoting` both implement the common `IRankedChoiceVoting` interface — integrate once, swap methods with a single constructor change. Each contract outputs a deterministic strict total order over all candidates; downstream consumers (Governors, council selectors, budget allocators) interpret that ordering however they need.
Aimed at DAOs and onchain organizations that need a Condorcet-method election — council selection, grant allocation, prioritized lists, multi-winner committees — where the canonical "winner-take-all" Governor pattern doesn't fit and an ordered ranking is the natural output.
Explicitly **not**: an audited contract, an instant-runoff (IRV) implementation, or an OpenZeppelin Governor replacement. It produces a ranking; combining that ranking with proposal execution is the integrator's job.
## Features
- Two methods, one interface — swap `CopelandVoting` <-> `SchulzeVoting` with no integration changes.
- Standalone — no coupling to OpenZeppelin Governor or any DAO framework.
- Generic — any `IVotes` token works (ERC20Votes, ERC721Votes, custom checkpointed tokens).
- Permissionless — anyone can create an election, cast, tally, or finalize.
- Replaceable ballots — voters can recast at any time before the deadline.
- Lazy batched tally — pay gas only when you need the result; spread across multiple transactions.
- Live preview — `getCurrentResult(id)` returns the ranking the contract would produce if `finalize()` were called right now, at any lifecycle phase.
- Deterministic ordering — always a strict total order, with method-specific tiebreaks (Copeland: Minimax then index; Schulze: strongest-path then index).
- Partial rankings — voters rank any subset of candidates; unranked candidates contribute nothing.
## Methods
- **Copeland** — each candidate's score is (head-to-head wins − losses); Minimax breaks ties, then candidate index. Cheaper to finalize (O(C²)) and easier to explain. Default choice unless you specifically expect cyclical preferences.
- **Schulze** — Floyd-Warshall over the widest-path semiring produces strongest-path scores; candidate index breaks ties. Resolves asymmetric Condorcet cycles decisively at the cost of O(C³) finalize.
Full methodology, worked examples and tiebreak rules: [docs/voting-methods.md](docs/voting-methods.md).
## Quick start
```bash
forge install
forge build
forge test
```
Requires [Foundry](https://book.getfoundry.sh/).
## Usage
```solidity
import {IRankedChoiceVoting} from "src/interfaces/IRankedChoiceVoting.sol";
import {CopelandVoting} from "src/CopelandVoting.sol";
// or: import {SchulzeVoting} from "src/SchulzeVoting.sol";
// Integrate against the common interface — swap methods freely.
IRankedChoiceVoting voting = IRankedChoiceVoting(address(new CopelandVoting()));
// 1. Create an election
bytes32[] memory candidates = new bytes32[](3);
candidates[0] = keccak256("Alice");
candidates[1] = keccak256("Bob");
candidates[2] = keccak256("Carol");
uint256 electionId = voting.createElection(IRankedChoiceVoting.ElectionConfig({
candidates: candidates,
votingToken: ensToken,
snapshotBlock: block.number - 1,
startTime: uint64(block.timestamp),
endTime: uint64(block.timestamp + 7 days),
metadataURI: bytes32(0)
}));
// 2. Voters cast ranked ballots
uint8[] memory ranking = new uint8[](3);
ranking[0] = 2; // Carol first
ranking[1] = 0; // Alice second
ranking[2] = 1; // Bob third
voting.castBallot(electionId, ranking);
// 3. Live preview at any time — no need to wait for finalize.
uint8[] memory provisional = voting.getCurrentResult(electionId);
// 4. After endTime, anyone tallies and finalizes
while (!voting.tallyBallots(electionId, 50)) {} // batched
voting.finalize(electionId);
// 5. Read the final result
uint8[] memory orderedWinners = voting.getRanking(electionId);
// orderedWinners[0] is the top-ranked candidate index
```
Limits: maximum 64 candidates per election; voter weights bounded by `int256` max.
## Deployments
Not yet deployed to mainnet. Run via the deploy scripts in [`script/`](script/) (`DeployCopelandVoting.s.sol`, `DeploySchulzeVoting.s.sol`).
## Documentation
- [docs/voting-methods.md](docs/voting-methods.md) — full methodology, worked examples, gas table, tiebreak rules.
- [docs/superpowers/specs/2026-05-25-multi-method-voting-design.md](docs/superpowers/specs/2026-05-25-multi-method-voting-design.md) — multi-method design spec.
- [docs/superpowers/specs/2026-05-24-copeland-voting-design.md](docs/superpowers/specs/2026-05-24-copeland-voting-design.md) — original Copeland design rationale and decision log.
- [docs/superpowers/plans/2026-05-25-multi-method-voting.md](docs/superpowers/plans/2026-05-25-multi-method-voting.md) — multi-method implementation plan.
## Contributing
Contributions welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, conventions, and PR process.
## Security
Report vulnerabilities per [SECURITY.md](SECURITY.md). **These contracts have not been independently audited** — production deployments should commission an audit first.
## License
[MIT](LICENSE).