Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions dynamic_dispatch/README.md
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions dynamic_dispatch/governance/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NETWORK=testnet
PRIVATE_KEY=APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH
ENDPOINT=http://localhost:3030
133 changes: 133 additions & 0 deletions dynamic_dispatch/governance/build/abi.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
]
}
66 changes: 66 additions & 0 deletions dynamic_dispatch/governance/build/imports/quadratic_power.aleo
Original file line number Diff line number Diff line change
@@ -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;
8 changes: 8 additions & 0 deletions dynamic_dispatch/governance/build/imports/voting_power.aleo
Original file line number Diff line number Diff line change
@@ -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;
26 changes: 26 additions & 0 deletions dynamic_dispatch/governance/build/main.aleo
Original file line number Diff line number Diff line change
@@ -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;
Loading