Skip to content

Commit 5f014e0

Browse files
mohammadfawazMohammad Fawaz
authored andcommitted
Storage example
1 parent b418b59 commit 5f014e0

16 files changed

Lines changed: 1710 additions & 0 deletions

File tree

storage/README.md

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# Storage in Leo
2+
3+
This example demonstrates **storage variables** in Leo 4 — on-chain state that replaces single-entry mappings with a simpler, typed API.
4+
5+
## The Scenario
6+
7+
A treasury program tracks its balance, configuration, and deposit history using on-chain storage. A separate auditor program reads the treasury's storage externally to monitor its state — without the treasury needing to expose any getter functions.
8+
9+
## Program Architecture
10+
11+
```
12+
treasury.aleo auditor.aleo
13+
│ storage balance: u64 │ reads treasury.aleo::balance
14+
│ storage config: FeeConfig │ reads treasury.aleo::config
15+
│ storage deposit_log: [u64] │ reads treasury.aleo::deposit_log
16+
│ storage is_frozen: bool │ reads treasury.aleo::is_frozen
17+
│ (owns and writes all state) │ (read-only external access)
18+
└──────────────────────────────────┘
19+
```
20+
21+
## Features Showcased
22+
23+
### Storage Singletons
24+
25+
A `storage` declaration creates a single named on-chain slot. Values are optional — `none` until set, clearable back to `none`.
26+
27+
```leo
28+
program treasury.aleo {
29+
storage balance: u64;
30+
storage is_frozen: bool;
31+
storage deposit_count: u32;
32+
33+
fn initialize() -> Final {
34+
return final {
35+
balance = 0u64;
36+
is_frozen = false;
37+
};
38+
}
39+
}
40+
```
41+
42+
All storage operations happen inside a `return final { ... };` block. The `Final` return type tells the AVM that the function modifies on-chain state.
43+
44+
### Reading Storage
45+
46+
Storage slots are optional. Use `unwrap()` when the value must exist, or `unwrap_or()` to provide a fallback:
47+
48+
```leo
49+
fn deposit(amount: u64) -> Final {
50+
return final {
51+
let current: u64 = balance.unwrap_or(0u64); // fallback if uninitialized
52+
balance = current + amount;
53+
};
54+
}
55+
```
56+
57+
### Struct Storage
58+
59+
Structs are stored and read as a single unit:
60+
61+
```leo
62+
struct FeeConfig {
63+
rate: u64,
64+
minimum: u64,
65+
}
66+
67+
program treasury.aleo {
68+
storage config: FeeConfig;
69+
70+
fn update_config(rate: u64, minimum: u64) -> Final {
71+
return final {
72+
config = FeeConfig { rate: rate, minimum: minimum };
73+
};
74+
}
75+
}
76+
```
77+
78+
### Vector Storage
79+
80+
Vectors are dynamically-sized on-chain lists with `push`, `pop`, `get`, `set`, `clear`, `swap_remove`, and `len`:
81+
82+
```leo
83+
program treasury.aleo {
84+
storage deposit_log: [u64];
85+
86+
fn deposit(amount: u64) -> Final {
87+
return final {
88+
deposit_log.push(amount); // append
89+
};
90+
}
91+
92+
fn pop_last_deposit() -> Final {
93+
return final {
94+
let removed = deposit_log.pop(); // remove last
95+
assert(removed != none);
96+
};
97+
}
98+
99+
fn reset() -> Final {
100+
return final {
101+
deposit_log.clear(); // empty the list
102+
};
103+
}
104+
}
105+
```
106+
107+
### Clearing Storage
108+
109+
Assign `none` to clear a singleton, or call `.clear()` on a vector:
110+
111+
```leo
112+
fn reset() -> Final {
113+
return final {
114+
balance = none; // slot reverts to uninitialized
115+
config = none; // struct slot cleared
116+
deposit_log.clear(); // vector emptied
117+
};
118+
}
119+
```
120+
121+
### External Storage Access
122+
123+
Any program can **read** another deployed program's storage using `program.aleo::variable` syntax. Writes are restricted to the owning program.
124+
125+
```leo
126+
import treasury.aleo;
127+
128+
program auditor.aleo {
129+
fn take_snapshot() -> Final {
130+
return final {
131+
// Read external singletons
132+
let bal: u64 = treasury.aleo::balance.unwrap_or(0u64);
133+
let frozen: bool = treasury.aleo::is_frozen.unwrap_or(false);
134+
135+
// Read external struct
136+
let cfg = treasury.aleo::config.unwrap_or(
137+
treasury.aleo::FeeConfig { rate: 0u64, minimum: 0u64 }
138+
);
139+
140+
// Read external vector
141+
let count: u32 = treasury.aleo::deposit_log.len();
142+
let first = treasury.aleo::deposit_log.get(0u32);
143+
};
144+
}
145+
}
146+
```
147+
148+
## Project Structure
149+
150+
```
151+
storage/
152+
├── run.sh
153+
├── treasury/ # Owns and writes all storage
154+
│ ├── program.json
155+
│ └── src/
156+
│ └── main.leo
157+
└── auditor/ # Reads treasury storage externally
158+
├── program.json # lists treasury.aleo as a local dependency
159+
└── src/
160+
└── main.leo
161+
```
162+
163+
## Running the Example
164+
165+
### Part 1 — Local execution (no devnode)
166+
167+
Individual treasury operations can be tested locally with `leo run`:
168+
169+
```bash
170+
cd treasury
171+
leo run initialize
172+
leo run deposit 5000u64
173+
leo run withdraw 1000u64
174+
leo run update_config 500u64 25u64
175+
```
176+
177+
Note: each `leo run` starts with fresh state, so operations don't accumulate across calls.
178+
179+
### Part 2 — External storage access (requires `leo devnode`)
180+
181+
Cross-program storage reads require both programs to be deployed. Start a local devnode in a **separate terminal** first:
182+
183+
```bash
184+
leo devnode start --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH
185+
```
186+
187+
Then run the full demo from the `storage/` directory:
188+
189+
```bash
190+
./run.sh
191+
```
192+
193+
The script:
194+
1. Runs local demos of treasury operations
195+
2. Deploys `treasury.aleo` and initializes it with deposits
196+
3. Deploys `auditor.aleo` (local dependency on treasury — Leo deploys both in order)
197+
4. Calls `take_snapshot` — reads treasury balance, count, and frozen state
198+
5. Calls `check_fee_config` — reads the treasury's `FeeConfig` struct
199+
6. Calls `check_deposits` — reads the treasury's deposit log vector
200+
7. Freezes the treasury, then snapshots again to show the alert triggers
201+
8. Resets all treasury storage to `none`
202+
203+
## Key Takeaways
204+
205+
- **Storage singletons** replace single-entry mappings with a cleaner, typed API.
206+
- **Struct storage** lets you group related fields into a single on-chain slot.
207+
- **Vector storage** gives you a dynamically-sized on-chain list with push/pop/get/set/clear.
208+
- **External reads** let any program inspect another's storage — no getter functions needed.
209+
- **`none`** semantics: all storage starts as `none`; use `unwrap_or()` for safe fallbacks.

storage/auditor/.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
NETWORK=testnet
2+
PRIVATE_KEY=APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH
3+
ENDPOINT=http://localhost:3030

storage/auditor/build/abi.json

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
{
2+
"program": "auditor.aleo",
3+
"structs": [
4+
{
5+
"path": [
6+
"Snapshot"
7+
],
8+
"fields": [
9+
{
10+
"name": "balance",
11+
"ty": {
12+
"Primitive": {
13+
"UInt": "U64"
14+
}
15+
}
16+
},
17+
{
18+
"name": "deposit_count",
19+
"ty": {
20+
"Primitive": {
21+
"UInt": "U32"
22+
}
23+
}
24+
},
25+
{
26+
"name": "is_frozen",
27+
"ty": {
28+
"Primitive": "Boolean"
29+
}
30+
}
31+
]
32+
}
33+
],
34+
"records": [],
35+
"mappings": [],
36+
"storage_variables": [
37+
{
38+
"name": "last_snapshot",
39+
"ty": {
40+
"Plaintext": {
41+
"Struct": {
42+
"path": [
43+
"Snapshot"
44+
],
45+
"program": "auditor.aleo"
46+
}
47+
}
48+
}
49+
},
50+
{
51+
"name": "is_alert",
52+
"ty": {
53+
"Plaintext": {
54+
"Primitive": "Boolean"
55+
}
56+
}
57+
}
58+
],
59+
"functions": [
60+
{
61+
"name": "take_snapshot",
62+
"is_final": true,
63+
"inputs": [],
64+
"outputs": [
65+
{
66+
"ty": "Final",
67+
"mode": "None"
68+
}
69+
]
70+
},
71+
{
72+
"name": "check_fee_config",
73+
"is_final": true,
74+
"inputs": [],
75+
"outputs": [
76+
{
77+
"ty": "Final",
78+
"mode": "None"
79+
}
80+
]
81+
},
82+
{
83+
"name": "check_deposits",
84+
"is_final": true,
85+
"inputs": [],
86+
"outputs": [
87+
{
88+
"ty": "Final",
89+
"mode": "None"
90+
}
91+
]
92+
}
93+
]
94+
}

0 commit comments

Comments
 (0)