diff --git a/dynamic_dispatch/README.md b/dynamic_dispatch/README.md new file mode 100644 index 0000000..92a7bf3 --- /dev/null +++ b/dynamic_dispatch/README.md @@ -0,0 +1,155 @@ +# Dynamic Dispatch in Leo + +This example demonstrates two related Leo 4.0 features: **interfaces** and **dynamic dispatch**. + +## The Scenario + +Different governance systems call for different voting-power formulas. A _linear_ strategy grants one vote per token (simple majority), while a _quadratic_ strategy grants floor(√tokens) votes (reducing whale influence). We want a single governance contract that can apply **either** formula — or any future formula — without being redeployed. + +Dynamic dispatch makes this possible: the caller names the target strategy at runtime using the `identifier` type, and the AVM routes the call to whichever deployed program is named. + +## Program Architecture + +``` +voting_power.aleo quadratic_power.aleo + │ declares VotingStrategy │ implements VotingStrategy + │ implements (linear) │ (floor √balance votes) + └──────────┬─────────────────┘ + │ governance.aleo resolves the VotingStrategy interface + ▼ and dispatches to either program at runtime + governance.aleo + get_voting_power(strategy, balance) + proposal_passes(strategy, for_bal, against_bal) + compare_strategies(balance) +``` + +## Features Showcased + +### Interfaces + +An `interface` specifies the functions a program must expose. It is a _compile-time_ concept: Leo verifies the implementing program satisfies the contract; no interface bytecode appears on-chain. + +```leo +// In voting_power.aleo +interface VotingStrategy { + fn compute_power(balance: u64) -> u64; +} + +program voting_power.aleo : VotingStrategy { + fn compute_power(balance: u64) -> u64 { return balance; } + ... +} + +// In quadratic_power.aleo — same interface, different implementation +program quadratic_power.aleo : VotingStrategy { + fn compute_power(balance: u64) -> u64 { /* floor(√balance) */ ... } + ... +} +``` + +**Note on interface scoping:** The `VotingStrategy` interface is declared at the top level of each program's `.leo` file (outside the `program {}` block). An interface defined in one program is not automatically exported to programs that depend on it via `program.json`; each program re-declares the interface with the matching signature, and Leo's type checker verifies the contract at compile time. + +### Dynamic Dispatch + +The `identifier` type holds a program name resolved at runtime. The call syntax is: + +``` +Interface@(target)::function(args) +├─ Interface → interface declared in the same file +├─ @(target) → `identifier` value resolved at runtime (the program to call) +└─ ::function → function to invoke on that target +``` + +`governance.aleo` re-declares `VotingStrategy` locally and accepts `strategy: identifier` as a parameter, routing to whichever program the caller names: + +```leo +fn get_voting_power(strategy: identifier, balance: u64) -> u64 { + return VotingStrategy@(strategy)::compute_power(balance); +} +``` + +Identifier literals use single quotes and can appear inline when the target is known at compile time: + +```leo +fn compare_strategies(balance: u64) -> (u64, u64) { + let linear_power: u64 = VotingStrategy@('voting_power')::compute_power(balance); + let quadratic_power: u64 = VotingStrategy@('quadratic_power')::compute_power(balance); + return (linear_power, quadratic_power); +} +``` + +## Project Structure + +``` +dynamic_dispatch/ +├── run.sh +├── voting_power/ # VotingStrategy interface + linear implementation +│ ├── program.json +│ └── src/ +│ └── main.leo +├── quadratic_power/ # Quadratic implementation of VotingStrategy +│ ├── program.json +│ └── src/ +│ └── main.leo +└── governance/ # Dynamic dispatch hub + ├── program.json # lists voting_power.aleo + quadratic_power.aleo as network deps + └── src/ + └── main.leo +``` + +**Note on network dependencies:** `governance/program.json` lists `voting_power.aleo` and `quadratic_power.aleo` with `"location": "network"`. When you run `leo build` (or any command that triggers a build), Leo fetches the deployed bytecode from the devnode endpoint into `build/imports/`, making it available to the local VM for proof generation. + +## Running the Example + +### Part 1 — Local execution (no devnode) + +Individual programs can be tested locally with `leo run`: + +```bash +cd voting_power +leo run compute_power 10000u64 # → 10000 (linear: 1:1) +leo run compute_power 100u64 # → 100 + +cd ../quadratic_power +leo run compute_power 10000u64 # → 100 (quadratic: √10000) +leo run compute_power 100u64 # → 10 (quadratic: √100) +``` + +### Part 2 — Dynamic dispatch (requires `leo devnode`) + +Dynamic calls require all three programs to be deployed. Start a local devnode in a **separate terminal** first: + +```bash +leo devnode start --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH +``` + +Then run the full demo from the `dynamic_dispatch/` directory: + +```bash +./run.sh +``` + +The script: +1. Deploys `voting_power.aleo` — establishes the interface on-chain +2. Deploys `quadratic_power.aleo` — registers a second implementation +3. Deploys `governance.aleo` — the dispatch hub (holds no logic itself) +4. Calls `get_voting_power` with `'voting_power'` → routes to linear +5. Calls `get_voting_power` with `'quadratic_power'` → routes to quadratic +6. Calls `proposal_passes` to show diverging outcomes under each strategy +7. Calls `compare_strategies` to see both results side-by-side + +### Expected outputs + +| Function | Strategy | Balance | Result | +|---|---|---|---| +| `compute_power` | linear | 10 000 | 10 000 | +| `compute_power` | quadratic | 10 000 | 100 | +| `proposal_passes` | linear | 1 000 000 vs 10 000 | true (10 000× margin) | +| `proposal_passes` | quadratic | 1 000 000 vs 10 000 | true (10× margin) | +| `compare_strategies` | both | 10 000 | (10 000, 100) | + +## Key Takeaways + +- **Interfaces** are compile-time contracts — they enforce structure without on-chain overhead. +- **Dynamic dispatch** lets a single contract target any compliant program at runtime; adding a new strategy requires only a new deployment, not a governance upgrade. +- **`identifier` literals** (`'program_name'`) give you dynamic dispatch with a compile-time-known target when you want both flexibility and readability. diff --git a/dynamic_dispatch/governance/.env b/dynamic_dispatch/governance/.env new file mode 100644 index 0000000..ab204ee --- /dev/null +++ b/dynamic_dispatch/governance/.env @@ -0,0 +1,3 @@ +NETWORK=testnet +PRIVATE_KEY=APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH +ENDPOINT=http://localhost:3030 diff --git a/dynamic_dispatch/governance/build/abi.json b/dynamic_dispatch/governance/build/abi.json new file mode 100644 index 0000000..ecc635c --- /dev/null +++ b/dynamic_dispatch/governance/build/abi.json @@ -0,0 +1,133 @@ +{ + "program": "governance.aleo", + "structs": [], + "records": [], + "mappings": [], + "storage_variables": [], + "functions": [ + { + "name": "get_voting_power", + "is_final": false, + "inputs": [ + { + "name": "strategy", + "ty": { + "Plaintext": { + "Primitive": "Identifier" + } + }, + "mode": "None" + }, + { + "name": "balance", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ] + }, + { + "name": "proposal_passes", + "is_final": false, + "inputs": [ + { + "name": "strategy", + "ty": { + "Plaintext": { + "Primitive": "Identifier" + } + }, + "mode": "None" + }, + { + "name": "for_balance", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + }, + { + "name": "against_balance", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": { + "Plaintext": { + "Primitive": "Boolean" + } + }, + "mode": "None" + } + ] + }, + { + "name": "compare_strategies", + "is_final": false, + "inputs": [ + { + "name": "balance", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + }, + { + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ] + } + ] +} \ No newline at end of file diff --git a/dynamic_dispatch/governance/build/imports/quadratic_power.aleo b/dynamic_dispatch/governance/build/imports/quadratic_power.aleo new file mode 100644 index 0000000..ed125e8 --- /dev/null +++ b/dynamic_dispatch/governance/build/imports/quadratic_power.aleo @@ -0,0 +1,66 @@ +program quadratic_power.aleo; + +function compute_power: + input r0 as u64.private; + div r0 2u64 into r1; + add r1 1u64 into r2; + is.eq r2 0u64 into r3; + ternary r3 1u64 r2 into r4; + div r0 r4 into r5; + add r2 r5 into r6; + div r6 2u64 into r7; + lt r7 r2 into r8; + ternary r8 r7 r2 into r9; + is.eq r9 0u64 into r10; + ternary r10 1u64 r9 into r11; + div r0 r11 into r12; + add r9 r12 into r13; + div r13 2u64 into r14; + lt r14 r9 into r15; + ternary r15 r14 r9 into r16; + is.eq r16 0u64 into r17; + ternary r17 1u64 r16 into r18; + div r0 r18 into r19; + add r16 r19 into r20; + div r20 2u64 into r21; + lt r21 r16 into r22; + ternary r22 r21 r16 into r23; + is.eq r23 0u64 into r24; + ternary r24 1u64 r23 into r25; + div r0 r25 into r26; + add r23 r26 into r27; + div r27 2u64 into r28; + lt r28 r23 into r29; + ternary r29 r28 r23 into r30; + is.eq r30 0u64 into r31; + ternary r31 1u64 r30 into r32; + div r0 r32 into r33; + add r30 r33 into r34; + div r34 2u64 into r35; + lt r35 r30 into r36; + ternary r36 r35 r30 into r37; + is.eq r37 0u64 into r38; + ternary r38 1u64 r37 into r39; + div r0 r39 into r40; + add r37 r40 into r41; + div r41 2u64 into r42; + lt r42 r37 into r43; + ternary r43 r42 r37 into r44; + is.eq r44 0u64 into r45; + ternary r45 1u64 r44 into r46; + div r0 r46 into r47; + add r44 r47 into r48; + div r48 2u64 into r49; + lt r49 r44 into r50; + ternary r50 r49 r44 into r51; + is.eq r51 0u64 into r52; + ternary r52 1u64 r51 into r53; + div r0 r53 into r54; + add r51 r54 into r55; + div r55 2u64 into r56; + lt r56 r51 into r57; + ternary r57 r56 r51 into r58; + output r58 as u64.private; + +constructor: + assert.eq edition 0u16; diff --git a/dynamic_dispatch/governance/build/imports/voting_power.aleo b/dynamic_dispatch/governance/build/imports/voting_power.aleo new file mode 100644 index 0000000..c5274c6 --- /dev/null +++ b/dynamic_dispatch/governance/build/imports/voting_power.aleo @@ -0,0 +1,8 @@ +program voting_power.aleo; + +function compute_power: + input r0 as u64.private; + output r0 as u64.private; + +constructor: + assert.eq edition 0u16; diff --git a/dynamic_dispatch/governance/build/main.aleo b/dynamic_dispatch/governance/build/main.aleo new file mode 100644 index 0000000..4b2204d --- /dev/null +++ b/dynamic_dispatch/governance/build/main.aleo @@ -0,0 +1,26 @@ +program governance.aleo; + +function get_voting_power: + input r0 as identifier.private; + input r1 as u64.private; + call.dynamic r0 'aleo' 'compute_power' with r1 (as u64.private) into r2 (as u64.private); + output r2 as u64.private; + +function proposal_passes: + input r0 as identifier.private; + input r1 as u64.private; + input r2 as u64.private; + call.dynamic r0 'aleo' 'compute_power' with r1 (as u64.private) into r3 (as u64.private); + call.dynamic r0 'aleo' 'compute_power' with r2 (as u64.private) into r4 (as u64.private); + gt r3 r4 into r5; + output r5 as boolean.private; + +function compare_strategies: + input r0 as u64.private; + call.dynamic 'voting_power' 'aleo' 'compute_power' with r0 (as u64.private) into r1 (as u64.private); + call.dynamic 'quadratic_power' 'aleo' 'compute_power' with r0 (as u64.private) into r2 (as u64.private); + output r1 as u64.private; + output r2 as u64.private; + +constructor: + assert.eq edition 0u16; diff --git a/dynamic_dispatch/governance/build/program.json b/dynamic_dispatch/governance/build/program.json new file mode 100644 index 0000000..a61840a --- /dev/null +++ b/dynamic_dispatch/governance/build/program.json @@ -0,0 +1,9 @@ +{ + "program": "governance.aleo", + "version": "0.1.0", + "description": "", + "license": "", + "leo": "4.0.1", + "dependencies": null, + "dev_dependencies": null +} diff --git a/dynamic_dispatch/governance/program.json b/dynamic_dispatch/governance/program.json new file mode 100644 index 0000000..ec000a1 --- /dev/null +++ b/dynamic_dispatch/governance/program.json @@ -0,0 +1,10 @@ +{ + "program": "governance.aleo", + "version": "0.1.0", + "description": "Governance contract that routes voting power queries to any VotingStrategy implementation via dynamic dispatch.", + "license": "MIT", + "dependencies": [ + { "name": "voting_power.aleo", "location": "network", "path": null }, + { "name": "quadratic_power.aleo", "location": "network", "path": null } + ] +} diff --git a/dynamic_dispatch/governance/src/main.leo b/dynamic_dispatch/governance/src/main.leo new file mode 100644 index 0000000..75f65c5 --- /dev/null +++ b/dynamic_dispatch/governance/src/main.leo @@ -0,0 +1,59 @@ +// governance.aleo is the dynamic dispatch hub. +// +// It holds no voting logic itself — instead it delegates power computation +// to whichever VotingStrategy program the caller names at runtime. Adding +// a new strategy (e.g., a conviction-voting program) requires no changes +// to this contract: deploy the new program, pass its name as `strategy`, +// and governance.aleo routes to it automatically. +// +// Dynamic call syntax (unqualified — interface declared locally): +// VotingStrategy@(strategy)::compute_power(balance) +// ├─ VotingStrategy → the interface (must be declared in this file) +// ├─ @(strategy) → the runtime target (an `identifier` value) +// └─ ::compute_power → the function to invoke on that target +// +// The interface is re-declared here so Leo's type checker can verify the +// call signature. At the AVM level, the dispatch checks that the named +// program has a matching `compute_power(u64) -> u64` function. +interface VotingStrategy { + fn compute_power(balance: u64) -> u64; +} + +program governance.aleo { + // Returns the voting power for `balance` tokens under the given strategy. + // + // `strategy` is an `identifier` resolved at runtime — e.g.: + // 'voting_power' → linear: 10 000 tokens → 10 000 votes + // 'quadratic_power' → quadratic: 10 000 tokens → 100 votes + fn get_voting_power(strategy: identifier, balance: u64) -> u64 { + return VotingStrategy@(strategy)::compute_power(balance); + } + + // Returns true when the weighted "for" side outweighs the "against" side. + // + // The same balance figures yield very different margins depending on the + // strategy: under linear voting a whale dominates; under quadratic voting + // their edge is shrunk to the square root of the difference. + fn proposal_passes( + strategy: identifier, + for_balance: u64, + against_balance: u64 + ) -> bool { + let for_power: u64 = VotingStrategy@(strategy)::compute_power(for_balance); + let against_power: u64 = VotingStrategy@(strategy)::compute_power(against_balance); + return for_power > against_power; + } + + // Evaluates the same balance under both strategies and returns the results + // side-by-side. This function uses *identifier literals* ('voting_power' + // and 'quadratic_power') rather than a variable, showing that the target + // can be fixed at compile time when desired. + fn compare_strategies(balance: u64) -> (u64, u64) { + let linear_power: u64 = VotingStrategy@('voting_power')::compute_power(balance); + let quadratic_power: u64 = VotingStrategy@('quadratic_power')::compute_power(balance); + return (linear_power, quadratic_power); + } + + @noupgrade + constructor() {} +} diff --git a/dynamic_dispatch/quadratic_power/.env b/dynamic_dispatch/quadratic_power/.env new file mode 100644 index 0000000..ab204ee --- /dev/null +++ b/dynamic_dispatch/quadratic_power/.env @@ -0,0 +1,3 @@ +NETWORK=testnet +PRIVATE_KEY=APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH +ENDPOINT=http://localhost:3030 diff --git a/dynamic_dispatch/quadratic_power/build/abi.json b/dynamic_dispatch/quadratic_power/build/abi.json new file mode 100644 index 0000000..8235087 --- /dev/null +++ b/dynamic_dispatch/quadratic_power/build/abi.json @@ -0,0 +1,38 @@ +{ + "program": "quadratic_power.aleo", + "structs": [], + "records": [], + "mappings": [], + "storage_variables": [], + "functions": [ + { + "name": "compute_power", + "is_final": false, + "inputs": [ + { + "name": "balance", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ] + } + ] +} \ No newline at end of file diff --git a/dynamic_dispatch/quadratic_power/build/main.aleo b/dynamic_dispatch/quadratic_power/build/main.aleo new file mode 100644 index 0000000..ed125e8 --- /dev/null +++ b/dynamic_dispatch/quadratic_power/build/main.aleo @@ -0,0 +1,66 @@ +program quadratic_power.aleo; + +function compute_power: + input r0 as u64.private; + div r0 2u64 into r1; + add r1 1u64 into r2; + is.eq r2 0u64 into r3; + ternary r3 1u64 r2 into r4; + div r0 r4 into r5; + add r2 r5 into r6; + div r6 2u64 into r7; + lt r7 r2 into r8; + ternary r8 r7 r2 into r9; + is.eq r9 0u64 into r10; + ternary r10 1u64 r9 into r11; + div r0 r11 into r12; + add r9 r12 into r13; + div r13 2u64 into r14; + lt r14 r9 into r15; + ternary r15 r14 r9 into r16; + is.eq r16 0u64 into r17; + ternary r17 1u64 r16 into r18; + div r0 r18 into r19; + add r16 r19 into r20; + div r20 2u64 into r21; + lt r21 r16 into r22; + ternary r22 r21 r16 into r23; + is.eq r23 0u64 into r24; + ternary r24 1u64 r23 into r25; + div r0 r25 into r26; + add r23 r26 into r27; + div r27 2u64 into r28; + lt r28 r23 into r29; + ternary r29 r28 r23 into r30; + is.eq r30 0u64 into r31; + ternary r31 1u64 r30 into r32; + div r0 r32 into r33; + add r30 r33 into r34; + div r34 2u64 into r35; + lt r35 r30 into r36; + ternary r36 r35 r30 into r37; + is.eq r37 0u64 into r38; + ternary r38 1u64 r37 into r39; + div r0 r39 into r40; + add r37 r40 into r41; + div r41 2u64 into r42; + lt r42 r37 into r43; + ternary r43 r42 r37 into r44; + is.eq r44 0u64 into r45; + ternary r45 1u64 r44 into r46; + div r0 r46 into r47; + add r44 r47 into r48; + div r48 2u64 into r49; + lt r49 r44 into r50; + ternary r50 r49 r44 into r51; + is.eq r51 0u64 into r52; + ternary r52 1u64 r51 into r53; + div r0 r53 into r54; + add r51 r54 into r55; + div r55 2u64 into r56; + lt r56 r51 into r57; + ternary r57 r56 r51 into r58; + output r58 as u64.private; + +constructor: + assert.eq edition 0u16; diff --git a/dynamic_dispatch/quadratic_power/build/program.json b/dynamic_dispatch/quadratic_power/build/program.json new file mode 100644 index 0000000..2b7c59d --- /dev/null +++ b/dynamic_dispatch/quadratic_power/build/program.json @@ -0,0 +1,9 @@ +{ + "program": "quadratic_power.aleo", + "version": "0.1.0", + "description": "", + "license": "", + "leo": "4.0.1", + "dependencies": null, + "dev_dependencies": null +} diff --git a/dynamic_dispatch/quadratic_power/program.json b/dynamic_dispatch/quadratic_power/program.json new file mode 100644 index 0000000..64ccf4a --- /dev/null +++ b/dynamic_dispatch/quadratic_power/program.json @@ -0,0 +1,6 @@ +{ + "program": "quadratic_power.aleo", + "version": "0.1.0", + "description": "Quadratic voting strategy: floor(sqrt(balance)) votes per token.", + "license": "MIT" +} diff --git a/dynamic_dispatch/quadratic_power/src/main.leo b/dynamic_dispatch/quadratic_power/src/main.leo new file mode 100644 index 0000000..9311cb2 --- /dev/null +++ b/dynamic_dispatch/quadratic_power/src/main.leo @@ -0,0 +1,34 @@ +// QuadraticVoting reduces the influence of large token holders: +// a voter with N tokens receives floor(√N) votes instead of N. +// +// This program implements the VotingStrategy interface declared in +// voting_power.aleo. Once both programs are deployed, governance.aleo +// can route power queries to either implementation at runtime simply by +// changing the `strategy` identifier — no redeployment required. + +// Re-declare the interface so Leo's type checker can verify this program +// satisfies it at compile time. The signature must match voting_power.aleo's +// declaration exactly. +interface VotingStrategy { + fn compute_power(balance: u64) -> u64; +} + +program quadratic_power.aleo : VotingStrategy { + // Quadratic voting power: floor(√balance) votes. + // + // Uses Newton's method to compute the integer square root in 8 iterations. + // The loop bound is a compile-time constant, which is required by the + // underlying ZK circuit. + fn compute_power(balance: u64) -> u64 { + let x: u64 = balance / 2u64 + 1u64; + for i: u8 in 0u8..8u8 { + let divisor: u64 = x == 0u64 ? 1u64 : x; + let next: u64 = (x + balance / divisor) / 2u64; + x = next < x ? next : x; + } + return x; + } + + @noupgrade + constructor() {} +} diff --git a/dynamic_dispatch/run.sh b/dynamic_dispatch/run.sh new file mode 100755 index 0000000..ad62851 --- /dev/null +++ b/dynamic_dispatch/run.sh @@ -0,0 +1,106 @@ +#!/bin/bash +# Demonstrates interfaces and dynamic dispatch in Leo. +# Run from the dynamic_dispatch/ directory. +# +# Prerequisites: +# leo devnode must be running in a separate terminal: +# leo devnode start --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH + +set -euo pipefail + +LEO=${LEO:-leo} + +PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" +COMMON_OPTS=(-y --disable-update-check --broadcast + --network testnet + --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" + --private-key "$PRIVATE_KEY" + --consensus-heights "0,1,2,3,4,5,6,7,8,9,10,11,12,13") + +# ─── Local demos (no devnode required) ──────────────────────────────────────── + +echo "" +echo "═══════════════════════════════════════════════" +echo " PART 1: Local execution (no devnode needed)" +echo "═══════════════════════════════════════════════" + +echo "" +echo "── voting_power.aleo: linear strategy ─────────" +cd voting_power +echo " 10 000 tokens → linear voting power:" +$LEO run compute_power 10000u64 +echo " 100 tokens → linear voting power:" +$LEO run compute_power 100u64 +cd .. + +echo "" +echo "── quadratic_power.aleo: quadratic strategy ───" +cd quadratic_power +echo " 10 000 tokens → quadratic voting power (expected: 100):" +$LEO run compute_power 10000u64 +echo " 100 tokens → quadratic voting power (expected: 10):" +$LEO run compute_power 100u64 +cd .. + +# ─── Devnode deployment and dynamic dispatch ────────────────────────────────── + +echo "" +echo "═══════════════════════════════════════════════" +echo " PART 2: Deployment and dynamic dispatch" +echo " (requires leo devnode)" +echo "═══════════════════════════════════════════════" + +echo "" +echo "── Step 1: Deploy voting_power.aleo ───────────" +echo " (defines VotingStrategy interface + linear implementation)" +cd voting_power +$LEO deploy "${COMMON_OPTS[@]}" +cd .. + +echo "" +echo "── Step 2: Deploy quadratic_power.aleo ────────" +echo " (quadratic implementation of VotingStrategy)" +cd quadratic_power +$LEO deploy "${COMMON_OPTS[@]}" +cd .. + +echo "" +echo "── Step 3: Deploy governance.aleo ─────────────" +echo " (dynamic dispatch hub — no voting logic of its own)" +cd governance +$LEO deploy "${COMMON_OPTS[@]}" + +echo "" +echo "── Step 4: get_voting_power with variable identifier ─" +echo " The same function routes to different programs at runtime." + +echo "" +echo " strategy = 'voting_power' (linear), balance = 10 000" +$LEO execute governance.aleo/get_voting_power "'voting_power'" 10000u64 "${COMMON_OPTS[@]}" + +echo "" +echo " strategy = 'quadratic_power' (quadratic), balance = 10 000" +$LEO execute governance.aleo/get_voting_power "'quadratic_power'" 10000u64 "${COMMON_OPTS[@]}" + +echo "" +echo "── Step 5: proposal_passes — diverging outcomes ─" +echo " Whale: 1 000 000 tokens for vs Regular voter: 10 000 tokens against" + +echo "" +echo " Linear strategy — whale wins by 100x margin:" +$LEO execute governance.aleo/proposal_passes "'voting_power'" 1000000u64 10000u64 "${COMMON_OPTS[@]}" + +echo "" +echo " Quadratic strategy — whale wins by 10x margin (1000 vs 100):" +$LEO execute governance.aleo/proposal_passes "'quadratic_power'" 1000000u64 10000u64 "${COMMON_OPTS[@]}" + +echo "" +echo "── Step 6: compare_strategies (identifier literals) ─" +echo " Single call dispatches to both built-in targets." +$LEO execute governance.aleo/compare_strategies 10000u64 "${COMMON_OPTS[@]}" + +cd .. + +echo "" +echo "Done! Dynamic dispatch routed governance.aleo to different" +echo "VotingStrategy implementations without any contract redeployment." diff --git a/dynamic_dispatch/voting_power/.env b/dynamic_dispatch/voting_power/.env new file mode 100644 index 0000000..ab204ee --- /dev/null +++ b/dynamic_dispatch/voting_power/.env @@ -0,0 +1,3 @@ +NETWORK=testnet +PRIVATE_KEY=APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH +ENDPOINT=http://localhost:3030 diff --git a/dynamic_dispatch/voting_power/build/abi.json b/dynamic_dispatch/voting_power/build/abi.json new file mode 100644 index 0000000..8a26d1a --- /dev/null +++ b/dynamic_dispatch/voting_power/build/abi.json @@ -0,0 +1,38 @@ +{ + "program": "voting_power.aleo", + "structs": [], + "records": [], + "mappings": [], + "storage_variables": [], + "functions": [ + { + "name": "compute_power", + "is_final": false, + "inputs": [ + { + "name": "balance", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ] + } + ] +} \ No newline at end of file diff --git a/dynamic_dispatch/voting_power/build/main.aleo b/dynamic_dispatch/voting_power/build/main.aleo new file mode 100644 index 0000000..c5274c6 --- /dev/null +++ b/dynamic_dispatch/voting_power/build/main.aleo @@ -0,0 +1,8 @@ +program voting_power.aleo; + +function compute_power: + input r0 as u64.private; + output r0 as u64.private; + +constructor: + assert.eq edition 0u16; diff --git a/dynamic_dispatch/voting_power/build/program.json b/dynamic_dispatch/voting_power/build/program.json new file mode 100644 index 0000000..d617d74 --- /dev/null +++ b/dynamic_dispatch/voting_power/build/program.json @@ -0,0 +1,9 @@ +{ + "program": "voting_power.aleo", + "version": "0.1.0", + "description": "", + "license": "", + "leo": "4.0.1", + "dependencies": null, + "dev_dependencies": null +} diff --git a/dynamic_dispatch/voting_power/program.json b/dynamic_dispatch/voting_power/program.json new file mode 100644 index 0000000..676465b --- /dev/null +++ b/dynamic_dispatch/voting_power/program.json @@ -0,0 +1,6 @@ +{ + "program": "voting_power.aleo", + "version": "0.1.0", + "description": "Linear voting strategy defining the VotingStrategy interface.", + "license": "MIT" +} diff --git a/dynamic_dispatch/voting_power/src/main.leo b/dynamic_dispatch/voting_power/src/main.leo new file mode 100644 index 0000000..7bfc3db --- /dev/null +++ b/dynamic_dispatch/voting_power/src/main.leo @@ -0,0 +1,20 @@ +// The VotingStrategy interface specifies how a program computes voting power +// from a token balance. +// +// Any deployed program that satisfies this interface can be used as a +// dynamic dispatch target: governance.aleo identifies the strategy at runtime +// using the `identifier` type and routes the call accordingly. +interface VotingStrategy { + fn compute_power(balance: u64) -> u64; +} + +// LinearVoting is the reference implementation: one token = one vote. +program voting_power.aleo : VotingStrategy { + // Linear voting: one token = one vote. + fn compute_power(balance: u64) -> u64 { + return balance; + } + + @noupgrade + constructor() {} +}