|
| 1 | +# Quadratic Vote |
| 2 | + |
| 3 | +## Summary |
| 4 | + |
| 5 | +`quadratic_vote` is a governance example that demonstrates Leo **library packages**. |
| 6 | + |
| 7 | +Two reusable libraries handle all the math: |
| 8 | + |
| 9 | +| Package | Kind | What it provides | |
| 10 | +|---------|------|-----------------| |
| 11 | +| `sqrt_math` | library | `isqrt` — integer square root via Newton's method | |
| 12 | +| `vote_math` | library | QV weights, credit costs, voter profiles (depends on `sqrt_math`) | |
| 13 | +| `governance.aleo` | program | Private vote records, proposal outcome checks | |
| 14 | + |
| 15 | +Under **Quadratic Voting (QV)**, a participant's vote weight equals ⌊√(token\_balance)⌋ and |
| 16 | +casting *k* votes costs *k²* credits. This makes influence grow with the square root of |
| 17 | +wealth rather than linearly — fairer to smaller holders. |
| 18 | + |
| 19 | +| Token balance | Vote weight | |
| 20 | +|---------------|-------------| |
| 21 | +| 1 | 1 | |
| 22 | +| 100 | 10 | |
| 23 | +| 225 | 15 | |
| 24 | +| 1 000 | 31 | |
| 25 | +| 10 000 | 100 (cap) | |
| 26 | + |
| 27 | +This example is inspired by the [Quadratic Voting](https://en.wikipedia.org/wiki/Quadratic_voting) |
| 28 | +mechanism used in decentralised governance systems such as Gitcoin Grants. |
| 29 | + |
| 30 | +## Noteworthy Features |
| 31 | + |
| 32 | +**Library packages** — `sqrt_math` and `vote_math` are pure libraries: they have no |
| 33 | +`.aleo` program name, are never deployed on their own, and only take effect when included |
| 34 | +by a program package. |
| 35 | + |
| 36 | +**Library submodules** — each extra `.leo` file under `src/` becomes a named module. No |
| 37 | +declaration is needed; the filename is the module name. Items are accessed as |
| 38 | +`lib_name::module_name::item`. |
| 39 | + |
| 40 | +**Cross-submodule calls within a library** — `vote_math::cost::build_profile` calls |
| 41 | +`vote_math::weight::compute` internally; `sqrt_math::checks::is_perfect_square` calls |
| 42 | +`sqrt_math::newton::isqrt`. |
| 43 | + |
| 44 | +**Cross-library submodule calls** — `vote_math::weight::compute` reaches directly into |
| 45 | +`sqrt_math::newton::isqrt`, demonstrating the full dependency chain |
| 46 | +(`governance → vote_math::cost → vote_math::weight → sqrt_math::newton`). |
| 47 | + |
| 48 | +**Top-level library constants** — `vote_math::MAX_WEIGHT`, `vote_math::MIN_BALANCE`, and |
| 49 | +`sqrt_math::ISQRT_MAX` are defined in each library's `lib.leo` and referenced by name from |
| 50 | +within submodules. |
| 51 | + |
| 52 | +**Library struct export** — `vote_math::cost::VoterProfile` is defined in a submodule (field: |
| 53 | +`weight: u32`) and used directly as a variable type inside `governance.aleo`. |
| 54 | + |
| 55 | +**Private vote receipt** — the `Vote` record is private; the token balance that drove the |
| 56 | +weight computation is never revealed on-chain. |
| 57 | + |
| 58 | +## How to Build |
| 59 | + |
| 60 | +### Option A — clone this repo and build in place |
| 61 | + |
| 62 | +```bash |
| 63 | +cd quadratic_vote |
| 64 | + |
| 65 | +cd sqrt_math && leo build && cd .. |
| 66 | +cd vote_math && leo build && cd .. |
| 67 | +cd governance && leo build && cd .. |
| 68 | +``` |
| 69 | + |
| 70 | +### Option B — create from scratch with the Leo CLI |
| 71 | + |
| 72 | +```bash |
| 73 | +# Create the two libraries and the program. |
| 74 | +leo new --library sqrt_math |
| 75 | +leo new --library vote_math |
| 76 | +leo new governance |
| 77 | + |
| 78 | +# Wire up dependencies. |
| 79 | +cd vote_math && leo add sqrt_math --local ../sqrt_math && cd .. |
| 80 | +cd governance && leo add sqrt_math --local ../sqrt_math && cd .. |
| 81 | +cd governance && leo add vote_math --local ../vote_math && cd .. |
| 82 | + |
| 83 | +# Copy source files, then build. |
| 84 | +cd sqrt_math && leo build && cd .. |
| 85 | +cd vote_math && leo build && cd .. |
| 86 | +cd governance && leo build && cd .. |
| 87 | +``` |
| 88 | + |
| 89 | +`leo new --library` creates a library package — `program.json` has no `.aleo` suffix and the |
| 90 | +source lives in `src/lib.leo` instead of `src/main.leo`. `leo add --local` records the |
| 91 | +dependency in `program.json` and lets the compiler resolve cross-package symbols. |
| 92 | + |
| 93 | +## How to Run |
| 94 | + |
| 95 | +All `leo run` commands are issued from inside the `governance/` directory. |
| 96 | + |
| 97 | +### Cast a vote |
| 98 | + |
| 99 | +A participant's token balance is kept private. The program derives their QV weight and |
| 100 | +returns a private `Vote` receipt. |
| 101 | + |
| 102 | +``` |
| 103 | +leo run cast_vote <proposal_id: field> <token_balance: u32> <approve: bool> |
| 104 | +``` |
| 105 | + |
| 106 | +**Alice (1 000 tokens, approval):** |
| 107 | +```bash |
| 108 | +leo run cast_vote 1field 1000u32 true |
| 109 | +``` |
| 110 | + |
| 111 | +Output: |
| 112 | +``` |
| 113 | +{ |
| 114 | + owner: aleo1...private, |
| 115 | + proposal_id: 1field.private, |
| 116 | + weight: 31u32.private, |
| 117 | + approve: true.private, |
| 118 | + _nonce: ...group.public, |
| 119 | + _version: 1u8.public |
| 120 | +} |
| 121 | +``` |
| 122 | + |
| 123 | +**Bob (225 tokens, rejection):** |
| 124 | +```bash |
| 125 | +leo run cast_vote 1field 225u32 false |
| 126 | +``` |
| 127 | + |
| 128 | +**Carol (10 000 tokens — hits the `MAX_WEIGHT` cap of 100):** |
| 129 | +```bash |
| 130 | +leo run cast_vote 1field 10000u32 true |
| 131 | +``` |
| 132 | + |
| 133 | +### Query the credit cost of casting votes |
| 134 | + |
| 135 | +``` |
| 136 | +leo run qv_cost <votes: u32> |
| 137 | +``` |
| 138 | + |
| 139 | +How much does it cost to cast 5 votes? |
| 140 | + |
| 141 | +```bash |
| 142 | +leo run qv_cost 5u32 |
| 143 | +# Output: 25u32 (= 5²) |
| 144 | +``` |
| 145 | + |
| 146 | +### Check whether a number is a perfect square |
| 147 | + |
| 148 | +``` |
| 149 | +leo run has_perfect_sqrt <token_balance: u32> |
| 150 | +``` |
| 151 | + |
| 152 | +This function bypasses `vote_math` entirely and calls `sqrt_math::checks::is_perfect_square` |
| 153 | +directly, illustrating that a program can reach any library submodule independently. |
| 154 | + |
| 155 | +```bash |
| 156 | +leo run has_perfect_sqrt 1024u32 # 1024 = 32² → true |
| 157 | +leo run has_perfect_sqrt 1000u32 # 31² = 961, 32² = 1024 → false |
| 158 | +``` |
| 159 | + |
| 160 | +### Check whether a proposal is passing |
| 161 | + |
| 162 | +``` |
| 163 | +leo run is_passing <approvals: u32> <rejections: u32> |
| 164 | +``` |
| 165 | + |
| 166 | +After tallying Alice (31) + Carol (100) = 131 approvals vs Bob's 15 rejections: |
| 167 | + |
| 168 | +```bash |
| 169 | +leo run is_passing 131u32 15u32 |
| 170 | +# Output: true |
| 171 | +``` |
| 172 | + |
| 173 | +## Run the unit tests |
| 174 | + |
| 175 | +From inside `governance/`: |
| 176 | + |
| 177 | +```bash |
| 178 | +leo test |
| 179 | +``` |
| 180 | + |
| 181 | +18 tests covering `cast_vote`, `qv_cost`, `has_perfect_sqrt`, and `is_passing`. |
| 182 | + |
| 183 | +## Run the full demo |
| 184 | + |
| 185 | +```bash |
| 186 | +bash run.sh |
| 187 | +``` |
| 188 | + |
| 189 | +## Project structure |
| 190 | + |
| 191 | +Each library is split into **submodules** — separate `.leo` files under `src/` that are |
| 192 | +automatically discovered by the compiler. No explicit `module` keyword is needed; the |
| 193 | +filename becomes the module name. |
| 194 | + |
| 195 | +``` |
| 196 | +quadratic_vote/ |
| 197 | +├── sqrt_math/ # Library: integer square root |
| 198 | +│ ├── program.json # "program": "sqrt_math" (no .aleo suffix) |
| 199 | +│ └── src/ |
| 200 | +│ ├── lib.leo # const ISQRT_MAX |
| 201 | +│ ├── newton.leo # fn isqrt(n) — Newton's method [module: newton] |
| 202 | +│ └── checks.leo # fn is_perfect_square(n) [module: checks] |
| 203 | +│ |
| 204 | +├── vote_math/ # Library: QV weight & cost formulas |
| 205 | +│ ├── program.json # depends on sqrt_math |
| 206 | +│ └── src/ |
| 207 | +│ ├── lib.leo # const MAX_WEIGHT, MIN_BALANCE |
| 208 | +│ ├── weight.leo # fn compute(balance), fn is_eligible(balance) [module: weight] |
| 209 | +│ └── cost.leo # struct VoterProfile { weight }, [module: cost] |
| 210 | +│ # fn credits_for_votes, fn build_profile(balance) |
| 211 | +│ |
| 212 | +├── governance/ # Program: private vote receipts |
| 213 | +│ ├── program.json # depends on sqrt_math + vote_math |
| 214 | +│ ├── inputs/governance.in |
| 215 | +│ ├── src/main.leo # record Vote |
| 216 | +│ │ # fn cast_vote — uses vote_math::weight + vote_math::cost |
| 217 | +│ │ # fn qv_cost — uses vote_math::cost |
| 218 | +│ │ # fn has_perfect_sqrt — uses sqrt_math::checks directly |
| 219 | +│ │ # fn is_passing |
| 220 | +│ └── tests/ |
| 221 | +│ └── test_governance.leo # @test fns covering cast_vote, qv_cost, has_perfect_sqrt, |
| 222 | +│ # is_passing (18 test cases) |
| 223 | +│ |
| 224 | +├── run.sh |
| 225 | +└── README.md |
| 226 | +``` |
| 227 | + |
| 228 | +### Call graph |
| 229 | + |
| 230 | +``` |
| 231 | +governance.aleo |
| 232 | +├── vote_math::weight::is_eligible(token_balance) |
| 233 | +│ └── vote_math::MIN_BALANCE (top-level lib constant) |
| 234 | +├── vote_math::cost::build_profile(token_balance) |
| 235 | +│ └── vote_math::weight::compute(balance) (cross-submodule call within vote_math) |
| 236 | +│ ├── sqrt_math::newton::isqrt(n) (cross-library submodule call) |
| 237 | +│ │ └── sqrt_math::ISQRT_MAX (top-level lib constant, safety assert) |
| 238 | +│ └── vote_math::MAX_WEIGHT (top-level lib constant) |
| 239 | +├── vote_math::cost::credits_for_votes(votes) |
| 240 | +└── sqrt_math::checks::is_perfect_square(n) (direct cross-library submodule call) |
| 241 | + └── sqrt_math::newton::isqrt(n) (cross-submodule call within sqrt_math) |
| 242 | + └── sqrt_math::ISQRT_MAX (top-level lib constant, safety assert) |
| 243 | +``` |
0 commit comments