diff --git a/storage/README.md b/storage/README.md new file mode 100644 index 0000000..97e7b61 --- /dev/null +++ b/storage/README.md @@ -0,0 +1,238 @@ +# Storage in Leo + +This example demonstrates **storage variables** in Leo 4 — on-chain state that replaces single-entry mappings with a simpler, typed API. + +## The Scenario + +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. + +## Program Architecture + +``` +treasury.aleo auditor.aleo + │ storage balance: u64 │ reads treasury.aleo::balance + │ storage config: FeeConfig │ reads treasury.aleo::config + │ storage deposit_log: [u64] │ reads treasury.aleo::deposit_log + │ storage is_frozen: bool │ reads treasury.aleo::is_frozen + │ (owns and writes all state) │ (read-only external access) + └──────────────────────────────────┘ +``` + +## Features Showcased + +### Storage Singletons + +A `storage` declaration creates a single named on-chain slot. Values are optional — `none` until set, clearable back to `none`. + +```leo +program treasury.aleo { + storage balance: u64; + storage is_frozen: bool; + storage deposit_count: u32; + + fn initialize() -> Final { + return final { + balance = 0u64; + is_frozen = false; + }; + } +} +``` + +All storage operations happen inside a `return final { ... };` block. The `Final` return type tells the AVM that the function modifies on-chain state. + +### Reading Storage + +Storage slots are optional. Use `unwrap()` when the value must exist, or `unwrap_or()` to provide a fallback: + +```leo +fn deposit(amount: u64) -> Final { + return final { + let current: u64 = balance.unwrap_or(0u64); // fallback if uninitialized + balance = current + amount; + }; +} +``` + +### Struct Storage + +Structs are stored and read as a single unit: + +```leo +struct FeeConfig { + rate: u64, + minimum: u64, +} + +program treasury.aleo { + storage config: FeeConfig; + + fn update_config(rate: u64, minimum: u64) -> Final { + return final { + config = FeeConfig { rate: rate, minimum: minimum }; + }; + } +} +``` + +### Vector Storage + +Vectors are dynamically-sized on-chain lists with `push`, `pop`, `get`, `set`, `len`, `clear`, and `swap_remove`: + +```leo +program treasury.aleo { + storage deposit_log: [u64]; + + fn deposit(amount: u64) -> Final { + return final { + deposit_log.push(amount); // append + }; + } + + fn undo_last_deposit() -> Final { + return final { + let removed: u64? = deposit_log.pop(); // remove the tail + assert(removed != none); + }; + } + + fn correct_deposit(index: u32, amount: u64) -> Final { + return final { + deposit_log.set(index, amount); // overwrite in place + }; + } + + fn drop_deposit(index: u32) -> Final { + return final { + let _ = deposit_log.swap_remove(index); // O(1) drop, swaps with tail + }; + } + + fn reset() -> Final { + return final { + deposit_log.clear(); // empty the list + }; + } +} +``` + +`get(i)` returns the element as an optional (`none` if `i >= len()`); `len()` returns the current length as `u32`. + +### Clearing Storage + +Assign `none` to clear a singleton, or call `.clear()` on a vector: + +```leo +fn reset() -> Final { + return final { + balance = none; // slot reverts to uninitialized + config = none; // struct slot cleared + deposit_log.clear(); // vector emptied + }; +} +``` + +### External Storage Access + +Any program can **read** another deployed program's storage using `program.aleo::variable` syntax. Writes are restricted to the owning program. + +```leo +import treasury.aleo; + +program auditor.aleo { + fn take_snapshot() -> Final { + return final { + // Read external singletons + let bal: u64 = treasury.aleo::balance.unwrap_or(0u64); + let frozen: bool = treasury.aleo::is_frozen.unwrap_or(false); + + // Read external struct + let cfg = treasury.aleo::config.unwrap_or( + treasury.aleo::FeeConfig { rate: 0u64, minimum: 0u64 } + ); + + // Read external vector + let count: u32 = treasury.aleo::deposit_log.len(); + let first = treasury.aleo::deposit_log.get(0u32); + }; + } +} +``` + +## Project Structure + +``` +storage/ +├── run.sh +├── treasury/ # Owns and writes all storage +│ ├── program.json +│ ├── src/ +│ │ └── main.leo +│ └── tests/ # @test functions that drive treasury locally +│ └── test_treasury.leo +└── auditor/ # Reads treasury storage externally + ├── program.json # lists treasury.aleo as a local dependency + ├── src/ + │ └── main.leo + └── tests/ # @test functions that exercise cross-program reads + └── test_auditor.leo +``` + +## Running the Example + +### Part 1 — Local execution (no devnode) + +Individual treasury operations can be tested locally with `leo run`: + +```bash +cd treasury +leo run initialize +leo run deposit 5000u64 +leo run withdraw 1000u64 +leo run update_config 500u64 25u64 +``` + +Note: each `leo run` starts with fresh state, so operations don't accumulate across calls. + +### Part 2 — Unit tests (no devnode) + +The `tests/` directories under each package contain `@test`-annotated functions that drive the storage transitions and assert on the resulting on-chain state — including cross-program reads from `auditor.aleo` against `treasury.aleo`. They run against an in-memory ledger: + +```bash +cd treasury && leo test # 16 tests covering every treasury op +cd ../auditor && leo test # 10 tests covering external reads + alerts +``` + +Each `@test` runs against a fresh deployment, so the test sets up its own state by calling treasury transitions before asserting. + +### Part 3 — External storage access (requires `leo devnode`) + +Cross-program storage reads require both 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 `storage/` directory: + +```bash +./run.sh +``` + +The script: +1. Runs local demos of treasury operations +2. Deploys `treasury.aleo` and initializes it with deposits +3. Deploys `auditor.aleo` (local dependency on treasury — Leo deploys both in order) +4. Calls `take_snapshot` — reads treasury balance, count, and frozen state +5. Calls `check_fee_config` — reads the treasury's `FeeConfig` struct +6. Calls `check_deposits` — reads the treasury's deposit log vector +7. Freezes the treasury, then snapshots again to show the alert triggers +8. Resets all treasury storage to `none` + +## Key Takeaways + +- **Storage singletons** replace single-entry mappings with a cleaner, typed API. +- **Struct storage** lets you group related fields into a single on-chain slot. +- **Vector storage** gives you a dynamically-sized on-chain list with push/pop/get/set/clear. +- **External reads** let any program inspect another's storage — no getter functions needed. +- **`none`** semantics: all storage starts as `none`; use `unwrap_or()` for safe fallbacks. diff --git a/storage/auditor/.env b/storage/auditor/.env new file mode 100644 index 0000000..ab204ee --- /dev/null +++ b/storage/auditor/.env @@ -0,0 +1,3 @@ +NETWORK=testnet +PRIVATE_KEY=APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH +ENDPOINT=http://localhost:3030 diff --git a/storage/auditor/build/abi.json b/storage/auditor/build/abi.json new file mode 100644 index 0000000..52cce5e --- /dev/null +++ b/storage/auditor/build/abi.json @@ -0,0 +1,98 @@ +{ + "program": "auditor.aleo", + "implements": [], + "structs": [ + { + "path": [ + "Snapshot" + ], + "fields": [ + { + "name": "balance", + "ty": { + "Primitive": { + "UInt": "U64" + } + } + }, + { + "name": "deposit_count", + "ty": { + "Primitive": { + "UInt": "U32" + } + } + }, + { + "name": "is_frozen", + "ty": { + "Primitive": "Boolean" + } + } + ] + } + ], + "records": [], + "mappings": [], + "storage_variables": [ + { + "name": "last_snapshot", + "ty": { + "Plaintext": { + "Struct": { + "path": [ + "Snapshot" + ], + "program": "auditor.aleo" + } + } + } + }, + { + "name": "is_alert", + "ty": { + "Plaintext": { + "Primitive": "Boolean" + } + } + } + ], + "functions": [ + { + "name": "take_snapshot", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "check_fee_config", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "check_deposits", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + } + ] +} \ No newline at end of file diff --git a/storage/auditor/build/imports/auditor.aleo b/storage/auditor/build/imports/auditor.aleo new file mode 100644 index 0000000..1b15557 --- /dev/null +++ b/storage/auditor/build/imports/auditor.aleo @@ -0,0 +1,94 @@ +import treasury.aleo; +program auditor.aleo; + +struct Snapshot: + balance as u64; + deposit_count as u32; + is_frozen as boolean; + +struct Optional__DmN5CQ9hzeK: + is_some as boolean; + val as u64; + +struct Optional__JzunLORyB8U: + is_some as boolean; + val as u32; + +struct Optional__ATAkdHctJwx: + is_some as boolean; + val as boolean; + +struct Optional__HgFNdm7FG0G: + is_some as boolean; + val as treasury.aleo/FeeConfig; + +mapping last_snapshot__: + key as boolean.public; + value as Snapshot.public; + +mapping is_alert__: + key as boolean.public; + value as boolean.public; + +function take_snapshot: + async take_snapshot into r0; + output r0 as auditor.aleo/take_snapshot.future; + +finalize take_snapshot: + contains treasury.aleo/balance__[false] into r0; + get.or_use treasury.aleo/balance__[false] 0u64 into r1; + ternary r0 r1 0u64 into r2; + cast r0 r2 into r3 as Optional__DmN5CQ9hzeK; + ternary r3.is_some r3.val 0u64 into r4; + contains treasury.aleo/deposit_count__[false] into r5; + get.or_use treasury.aleo/deposit_count__[false] 0u32 into r6; + ternary r5 r6 0u32 into r7; + cast r5 r7 into r8 as Optional__JzunLORyB8U; + ternary r8.is_some r8.val 0u32 into r9; + contains treasury.aleo/is_frozen__[false] into r10; + get.or_use treasury.aleo/is_frozen__[false] false into r11; + ternary r10 r11 false into r12; + cast r10 r12 into r13 as Optional__ATAkdHctJwx; + ternary r13.is_some r13.val false into r14; + cast r4 r9 r14 into r15 as Snapshot; + set r15 into last_snapshot__[false]; + set r14 into is_alert__[false]; + +function check_fee_config: + async check_fee_config into r0; + output r0 as auditor.aleo/check_fee_config.future; + +finalize check_fee_config: + contains treasury.aleo/config__[false] into r0; + cast 0u64 0u64 into r1 as treasury.aleo/FeeConfig; + get.or_use treasury.aleo/config__[false] r1 into r2; + cast 0u64 0u64 into r3 as treasury.aleo/FeeConfig; + ternary r0 r2.rate r3.rate into r4; + ternary r0 r2.minimum r3.minimum into r5; + cast r4 r5 into r6 as treasury.aleo/FeeConfig; + cast r0 r6 into r7 as Optional__HgFNdm7FG0G; + ternary r7.is_some r7.val.rate 0u64 into r8; + ternary r7.is_some r7.val.minimum 0u64 into r9; + cast r8 r9 into r10 as treasury.aleo/FeeConfig; + lte r10.rate 1000u64 into r11; + assert.eq r11 true; + +function check_deposits: + async check_deposits into r0; + output r0 as auditor.aleo/check_deposits.future; + +finalize check_deposits: + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r0; + gt r0 0u32 into r1; + assert.eq r1 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r2; + lt 0u32 r2 into r3; + get.or_use treasury.aleo/deposit_log__[0u32] 0u64 into r4; + ternary r3 r4 0u64 into r5; + cast r3 r5 into r6 as Optional__DmN5CQ9hzeK; + cast false 0u64 into r7 as Optional__DmN5CQ9hzeK; + is.neq r6 r7 into r8; + assert.eq r8 true; + +constructor: + assert.eq edition 0u16; diff --git a/storage/auditor/build/imports/auditor.aleo.abi.json b/storage/auditor/build/imports/auditor.aleo.abi.json new file mode 100644 index 0000000..52cce5e --- /dev/null +++ b/storage/auditor/build/imports/auditor.aleo.abi.json @@ -0,0 +1,98 @@ +{ + "program": "auditor.aleo", + "implements": [], + "structs": [ + { + "path": [ + "Snapshot" + ], + "fields": [ + { + "name": "balance", + "ty": { + "Primitive": { + "UInt": "U64" + } + } + }, + { + "name": "deposit_count", + "ty": { + "Primitive": { + "UInt": "U32" + } + } + }, + { + "name": "is_frozen", + "ty": { + "Primitive": "Boolean" + } + } + ] + } + ], + "records": [], + "mappings": [], + "storage_variables": [ + { + "name": "last_snapshot", + "ty": { + "Plaintext": { + "Struct": { + "path": [ + "Snapshot" + ], + "program": "auditor.aleo" + } + } + } + }, + { + "name": "is_alert", + "ty": { + "Plaintext": { + "Primitive": "Boolean" + } + } + } + ], + "functions": [ + { + "name": "take_snapshot", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "check_fee_config", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "check_deposits", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + } + ] +} \ No newline at end of file diff --git a/storage/auditor/build/imports/test_auditor.aleo b/storage/auditor/build/imports/test_auditor.aleo new file mode 100644 index 0000000..ded1b24 --- /dev/null +++ b/storage/auditor/build/imports/test_auditor.aleo @@ -0,0 +1,242 @@ +import treasury.aleo; +import auditor.aleo; +program test_auditor.aleo; + +struct Optional__DQLgKQ6OK1W: + is_some as boolean; + val as auditor.aleo/Snapshot; + +struct Optional__ATAkdHctJwx: + is_some as boolean; + val as boolean; + +struct Optional__DmN5CQ9hzeK: + is_some as boolean; + val as u64; + +function snapshot_reads_treasury: + call treasury.aleo/initialize into r0; + call treasury.aleo/deposit 5000u64 into r1; + call treasury.aleo/deposit 3000u64 into r2; + call auditor.aleo/take_snapshot into r3; + async snapshot_reads_treasury r0 r1 r2 r3 into r4; + output r4 as test_auditor.aleo/snapshot_reads_treasury.future; + +finalize snapshot_reads_treasury: + input r0 as treasury.aleo/initialize.future; + input r1 as treasury.aleo/deposit.future; + input r2 as treasury.aleo/deposit.future; + input r3 as auditor.aleo/take_snapshot.future; + await r0; + await r1; + await r2; + await r3; + contains auditor.aleo/last_snapshot__[false] into r4; + cast 0u64 0u32 false into r5 as auditor.aleo/Snapshot; + get.or_use auditor.aleo/last_snapshot__[false] r5 into r6; + cast 0u64 0u32 false into r7 as auditor.aleo/Snapshot; + ternary r4 r6.balance r7.balance into r8; + ternary r4 r6.deposit_count r7.deposit_count into r9; + ternary r4 r6.is_frozen r7.is_frozen into r10; + cast r8 r9 r10 into r11 as auditor.aleo/Snapshot; + cast r4 r11 into r12 as Optional__DQLgKQ6OK1W; + assert.eq r12.is_some true; + is.eq r12.val.balance 8000u64 into r13; + assert.eq r13 true; + is.eq r12.val.deposit_count 2u32 into r14; + assert.eq r14 true; + is.eq r12.val.is_frozen false into r15; + assert.eq r15 true; + contains auditor.aleo/is_alert__[false] into r16; + get.or_use auditor.aleo/is_alert__[false] false into r17; + ternary r16 r17 false into r18; + cast r16 r18 into r19 as Optional__ATAkdHctJwx; + assert.eq r19.is_some true; + is.eq r19.val false into r20; + assert.eq r20 true; + +function snapshot_defaults_on_empty: + call auditor.aleo/take_snapshot into r0; + async snapshot_defaults_on_empty r0 into r1; + output r1 as test_auditor.aleo/snapshot_defaults_on_empty.future; + +finalize snapshot_defaults_on_empty: + input r0 as auditor.aleo/take_snapshot.future; + await r0; + contains auditor.aleo/last_snapshot__[false] into r1; + cast 0u64 0u32 false into r2 as auditor.aleo/Snapshot; + get.or_use auditor.aleo/last_snapshot__[false] r2 into r3; + cast 0u64 0u32 false into r4 as auditor.aleo/Snapshot; + ternary r1 r3.balance r4.balance into r5; + ternary r1 r3.deposit_count r4.deposit_count into r6; + ternary r1 r3.is_frozen r4.is_frozen into r7; + cast r5 r6 r7 into r8 as auditor.aleo/Snapshot; + cast r1 r8 into r9 as Optional__DQLgKQ6OK1W; + assert.eq r9.is_some true; + is.eq r9.val.balance 0u64 into r10; + assert.eq r10 true; + is.eq r9.val.deposit_count 0u32 into r11; + assert.eq r11 true; + is.eq r9.val.is_frozen false into r12; + assert.eq r12 true; + contains auditor.aleo/is_alert__[false] into r13; + get.or_use auditor.aleo/is_alert__[false] false into r14; + ternary r13 r14 false into r15; + cast r13 r15 into r16 as Optional__ATAkdHctJwx; + assert.eq r16.is_some true; + is.eq r16.val false into r17; + assert.eq r17 true; + +function snapshot_detects_freeze: + call treasury.aleo/initialize into r0; + call treasury.aleo/freeze into r1; + call auditor.aleo/take_snapshot into r2; + async snapshot_detects_freeze r0 r1 r2 into r3; + output r3 as test_auditor.aleo/snapshot_detects_freeze.future; + +finalize snapshot_detects_freeze: + input r0 as treasury.aleo/initialize.future; + input r1 as treasury.aleo/freeze.future; + input r2 as auditor.aleo/take_snapshot.future; + await r0; + await r1; + await r2; + contains auditor.aleo/last_snapshot__[false] into r3; + cast 0u64 0u32 false into r4 as auditor.aleo/Snapshot; + get.or_use auditor.aleo/last_snapshot__[false] r4 into r5; + cast 0u64 0u32 false into r6 as auditor.aleo/Snapshot; + ternary r3 r5.balance r6.balance into r7; + ternary r3 r5.deposit_count r6.deposit_count into r8; + ternary r3 r5.is_frozen r6.is_frozen into r9; + cast r7 r8 r9 into r10 as auditor.aleo/Snapshot; + cast r3 r10 into r11 as Optional__DQLgKQ6OK1W; + assert.eq r11.is_some true; + is.eq r11.val.is_frozen true into r12; + assert.eq r12 true; + contains auditor.aleo/is_alert__[false] into r13; + get.or_use auditor.aleo/is_alert__[false] false into r14; + ternary r13 r14 false into r15; + cast r13 r15 into r16 as Optional__ATAkdHctJwx; + assert.eq r16.is_some true; + is.eq r16.val true into r17; + assert.eq r17 true; + +function alert_clears_after_unfreeze: + call treasury.aleo/initialize into r0; + call treasury.aleo/freeze into r1; + call auditor.aleo/take_snapshot into r2; + call treasury.aleo/unfreeze into r3; + call auditor.aleo/take_snapshot into r4; + async alert_clears_after_unfreeze r0 r1 r2 r3 r4 into r5; + output r5 as test_auditor.aleo/alert_clears_after_unfreeze.future; + +finalize alert_clears_after_unfreeze: + input r0 as treasury.aleo/initialize.future; + input r1 as treasury.aleo/freeze.future; + input r2 as auditor.aleo/take_snapshot.future; + input r3 as treasury.aleo/unfreeze.future; + input r4 as auditor.aleo/take_snapshot.future; + await r0; + await r1; + await r2; + await r3; + await r4; + contains auditor.aleo/last_snapshot__[false] into r5; + cast 0u64 0u32 false into r6 as auditor.aleo/Snapshot; + get.or_use auditor.aleo/last_snapshot__[false] r6 into r7; + cast 0u64 0u32 false into r8 as auditor.aleo/Snapshot; + ternary r5 r7.balance r8.balance into r9; + ternary r5 r7.deposit_count r8.deposit_count into r10; + ternary r5 r7.is_frozen r8.is_frozen into r11; + cast r9 r10 r11 into r12 as auditor.aleo/Snapshot; + cast r5 r12 into r13 as Optional__DQLgKQ6OK1W; + assert.eq r13.is_some true; + is.eq r13.val.is_frozen false into r14; + assert.eq r14 true; + contains auditor.aleo/is_alert__[false] into r15; + get.or_use auditor.aleo/is_alert__[false] false into r16; + ternary r15 r16 false into r17; + cast r15 r17 into r18 as Optional__ATAkdHctJwx; + assert.eq r18.is_some true; + is.eq r18.val false into r19; + assert.eq r19 true; + +function fee_config_passes_at_default: + call treasury.aleo/initialize into r0; + call auditor.aleo/check_fee_config into r1; + async fee_config_passes_at_default r0 r1 into r2; + output r2 as test_auditor.aleo/fee_config_passes_at_default.future; + +finalize fee_config_passes_at_default: + input r0 as treasury.aleo/initialize.future; + input r1 as auditor.aleo/check_fee_config.future; + await r0; + await r1; + +function fee_config_accepts_low_rate: + call treasury.aleo/update_config 50u64 1u64 into r0; + call auditor.aleo/check_fee_config into r1; + async fee_config_accepts_low_rate r0 r1 into r2; + output r2 as test_auditor.aleo/fee_config_accepts_low_rate.future; + +finalize fee_config_accepts_low_rate: + input r0 as treasury.aleo/update_config.future; + input r1 as auditor.aleo/check_fee_config.future; + await r0; + await r1; + +function fee_config_rejects_high_rate: + call treasury.aleo/update_config 1500u64 0u64 into r0; + call auditor.aleo/check_fee_config into r1; + async fee_config_rejects_high_rate r0 r1 into r2; + output r2 as test_auditor.aleo/fee_config_rejects_high_rate.future; + +finalize fee_config_rejects_high_rate: + input r0 as treasury.aleo/update_config.future; + input r1 as auditor.aleo/check_fee_config.future; + await r0; + await r1; + +function check_fee_config_uses_fallback: + call auditor.aleo/check_fee_config into r0; + async check_fee_config_uses_fallback r0 into r1; + output r1 as test_auditor.aleo/check_fee_config_uses_fallback.future; + +finalize check_fee_config_uses_fallback: + input r0 as auditor.aleo/check_fee_config.future; + await r0; + +function check_deposits_after_deposit: + call treasury.aleo/deposit 1000u64 into r0; + call auditor.aleo/check_deposits into r1; + async check_deposits_after_deposit r0 r1 into r2; + output r2 as test_auditor.aleo/check_deposits_after_deposit.future; + +finalize check_deposits_after_deposit: + input r0 as treasury.aleo/deposit.future; + input r1 as auditor.aleo/check_deposits.future; + await r0; + await r1; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r2; + is.eq r2 1u32 into r3; + assert.eq r3 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r4; + lt 0u32 r4 into r5; + get.or_use treasury.aleo/deposit_log__[0u32] 0u64 into r6; + ternary r5 r6 0u64 into r7; + cast r5 r7 into r8 as Optional__DmN5CQ9hzeK; + assert.eq r8.is_some true; + is.eq r8.val 1000u64 into r9; + assert.eq r9 true; + +function check_deposits_empty_fails: + call auditor.aleo/check_deposits into r0; + async check_deposits_empty_fails r0 into r1; + output r1 as test_auditor.aleo/check_deposits_empty_fails.future; + +finalize check_deposits_empty_fails: + input r0 as auditor.aleo/check_deposits.future; + await r0; + +constructor: + assert.eq edition 0u16; diff --git a/storage/auditor/build/imports/treasury.aleo b/storage/auditor/build/imports/treasury.aleo new file mode 100644 index 0000000..83f81af --- /dev/null +++ b/storage/auditor/build/imports/treasury.aleo @@ -0,0 +1,239 @@ +program treasury.aleo; + +struct FeeConfig: + rate as u64; + minimum as u64; + +struct Optional__ATAkdHctJwx: + is_some as boolean; + val as boolean; + +struct Optional__DmN5CQ9hzeK: + is_some as boolean; + val as u64; + +struct Optional__JzunLORyB8U: + is_some as boolean; + val as u32; + +mapping balance__: + key as boolean.public; + value as u64.public; + +mapping is_frozen__: + key as boolean.public; + value as boolean.public; + +mapping deposit_count__: + key as boolean.public; + value as u32.public; + +mapping config__: + key as boolean.public; + value as FeeConfig.public; + +mapping deposit_log__: + key as u32.public; + value as u64.public; + +mapping deposit_log__len__: + key as boolean.public; + value as u32.public; + +function initialize: + async initialize into r0; + output r0 as treasury.aleo/initialize.future; + +finalize initialize: + set 0u64 into balance__[false]; + set false into is_frozen__[false]; + set 0u32 into deposit_count__[false]; + cast 250u64 10u64 into r0 as FeeConfig; + set r0 into config__[false]; + +function deposit: + input r0 as u64.private; + async deposit r0 into r1; + output r1 as treasury.aleo/deposit.future; + +finalize deposit: + input r0 as u64.public; + contains is_frozen__[false] into r1; + get.or_use is_frozen__[false] false into r2; + ternary r1 r2 false into r3; + cast r1 r3 into r4 as Optional__ATAkdHctJwx; + ternary r4.is_some r4.val false into r5; + is.eq r5 false into r6; + assert.eq r6 true; + contains balance__[false] into r7; + get.or_use balance__[false] 0u64 into r8; + ternary r7 r8 0u64 into r9; + cast r7 r9 into r10 as Optional__DmN5CQ9hzeK; + ternary r10.is_some r10.val 0u64 into r11; + add r11 r0 into r12; + set r12 into balance__[false]; + contains deposit_count__[false] into r13; + get.or_use deposit_count__[false] 0u32 into r14; + ternary r13 r14 0u32 into r15; + cast r13 r15 into r16 as Optional__JzunLORyB8U; + ternary r16.is_some r16.val 0u32 into r17; + add r17 1u32 into r18; + set r18 into deposit_count__[false]; + get.or_use deposit_log__len__[false] 0u32 into r19; + add r19 1u32 into r20; + set r20 into deposit_log__len__[false]; + set r0 into deposit_log__[r19]; + +function freeze: + async freeze into r0; + output r0 as treasury.aleo/freeze.future; + +finalize freeze: + set true into is_frozen__[false]; + +function unfreeze: + async unfreeze into r0; + output r0 as treasury.aleo/unfreeze.future; + +finalize unfreeze: + set false into is_frozen__[false]; + +function update_config: + input r0 as u64.private; + input r1 as u64.private; + async update_config r0 r1 into r2; + output r2 as treasury.aleo/update_config.future; + +finalize update_config: + input r0 as u64.public; + input r1 as u64.public; + cast r0 r1 into r2 as FeeConfig; + set r2 into config__[false]; + +function withdraw: + input r0 as u64.private; + async withdraw r0 into r1; + output r1 as treasury.aleo/withdraw.future; + +finalize withdraw: + input r0 as u64.public; + contains is_frozen__[false] into r1; + get.or_use is_frozen__[false] false into r2; + ternary r1 r2 false into r3; + cast r1 r3 into r4 as Optional__ATAkdHctJwx; + ternary r4.is_some r4.val false into r5; + is.eq r5 false into r6; + assert.eq r6 true; + contains balance__[false] into r7; + get.or_use balance__[false] 0u64 into r8; + ternary r7 r8 0u64 into r9; + cast r7 r9 into r10 as Optional__DmN5CQ9hzeK; + assert.eq r10.is_some true; + gte r10.val r0 into r11; + assert.eq r11 true; + sub r10.val r0 into r12; + set r12 into balance__[false]; + +function undo_last_deposit: + async undo_last_deposit into r0; + output r0 as treasury.aleo/undo_last_deposit.future; + +finalize undo_last_deposit: + get.or_use deposit_log__len__[false] 0u32 into r0; + gt r0 0u32 into r1; + sub.w r0 1u32 into r2; + ternary r1 r2 r0 into r3; + set r3 into deposit_log__len__[false]; + get.or_use deposit_log__[r2] 0u64 into r4; + ternary r1 r4 0u64 into r5; + cast r1 r5 into r6 as Optional__DmN5CQ9hzeK; + cast false 0u64 into r7 as Optional__DmN5CQ9hzeK; + is.neq r6 r7 into r8; + assert.eq r8 true; + contains balance__[false] into r9; + get.or_use balance__[false] 0u64 into r10; + ternary r9 r10 0u64 into r11; + cast r9 r11 into r12 as Optional__DmN5CQ9hzeK; + assert.eq r6.is_some true; + ternary r12.is_some r12.val 0u64 into r13; + sub r13 r6.val into r14; + set r14 into balance__[false]; + contains deposit_count__[false] into r15; + get.or_use deposit_count__[false] 0u32 into r16; + ternary r15 r16 0u32 into r17; + cast r15 r17 into r18 as Optional__JzunLORyB8U; + ternary r18.is_some r18.val 0u32 into r19; + sub r19 1u32 into r20; + set r20 into deposit_count__[false]; + +function correct_deposit: + input r0 as u32.private; + input r1 as u64.private; + async correct_deposit r0 r1 into r2; + output r2 as treasury.aleo/correct_deposit.future; + +finalize correct_deposit: + input r0 as u32.public; + input r1 as u64.public; + get.or_use deposit_log__len__[false] 0u32 into r2; + lt r0 r2 into r3; + get.or_use deposit_log__[r0] 0u64 into r4; + ternary r3 r4 0u64 into r5; + cast r3 r5 into r6 as Optional__DmN5CQ9hzeK; + assert.eq r6.is_some true; + get.or_use deposit_log__len__[false] 0u32 into r7; + lt r0 r7 into r8; + assert.eq r8 true; + set r1 into deposit_log__[r0]; + contains balance__[false] into r9; + get.or_use balance__[false] 0u64 into r10; + ternary r9 r10 0u64 into r11; + cast r9 r11 into r12 as Optional__DmN5CQ9hzeK; + ternary r12.is_some r12.val 0u64 into r13; + add r13 r1 into r14; + sub r14 r6.val into r15; + set r15 into balance__[false]; + +function drop_deposit: + input r0 as u32.private; + async drop_deposit r0 into r1; + output r1 as treasury.aleo/drop_deposit.future; + +finalize drop_deposit: + input r0 as u32.public; + get.or_use deposit_log__len__[false] 0u32 into r1; + lt r0 r1 into r2; + assert.eq r2 true; + get deposit_log__[r0] into r3; + sub r1 1u32 into r4; + get deposit_log__[r4] into r5; + set r5 into deposit_log__[r0]; + set r4 into deposit_log__len__[false]; + contains balance__[false] into r6; + get.or_use balance__[false] 0u64 into r7; + ternary r6 r7 0u64 into r8; + cast r6 r8 into r9 as Optional__DmN5CQ9hzeK; + ternary r9.is_some r9.val 0u64 into r10; + sub r10 r3 into r11; + set r11 into balance__[false]; + contains deposit_count__[false] into r12; + get.or_use deposit_count__[false] 0u32 into r13; + ternary r12 r13 0u32 into r14; + cast r12 r14 into r15 as Optional__JzunLORyB8U; + ternary r15.is_some r15.val 0u32 into r16; + sub r16 1u32 into r17; + set r17 into deposit_count__[false]; + +function reset: + async reset into r0; + output r0 as treasury.aleo/reset.future; + +finalize reset: + remove balance__[false]; + remove is_frozen__[false]; + remove deposit_count__[false]; + remove config__[false]; + set 0u32 into deposit_log__len__[false]; + +constructor: + assert.eq edition 0u16; diff --git a/storage/auditor/build/imports/treasury.aleo.abi.json b/storage/auditor/build/imports/treasury.aleo.abi.json new file mode 100644 index 0000000..e7cb35f --- /dev/null +++ b/storage/auditor/build/imports/treasury.aleo.abi.json @@ -0,0 +1,290 @@ +{ + "program": "treasury.aleo", + "implements": [], + "structs": [ + { + "path": [ + "FeeConfig" + ], + "fields": [ + { + "name": "rate", + "ty": { + "Primitive": { + "UInt": "U64" + } + } + }, + { + "name": "minimum", + "ty": { + "Primitive": { + "UInt": "U64" + } + } + } + ] + } + ], + "records": [], + "mappings": [], + "storage_variables": [ + { + "name": "balance", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + } + }, + { + "name": "is_frozen", + "ty": { + "Plaintext": { + "Primitive": "Boolean" + } + } + }, + { + "name": "deposit_count", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U32" + } + } + } + }, + { + "name": "config", + "ty": { + "Plaintext": { + "Struct": { + "path": [ + "FeeConfig" + ], + "program": "treasury.aleo" + } + } + } + }, + { + "name": "deposit_log", + "ty": { + "Vector": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + } + } + } + ], + "functions": [ + { + "name": "initialize", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "deposit", + "is_final": true, + "const_parameters": [], + "inputs": [ + { + "name": "amount", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "freeze", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "unfreeze", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "update_config", + "is_final": true, + "const_parameters": [], + "inputs": [ + { + "name": "rate", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + }, + { + "name": "minimum", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "withdraw", + "is_final": true, + "const_parameters": [], + "inputs": [ + { + "name": "amount", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "undo_last_deposit", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "correct_deposit", + "is_final": true, + "const_parameters": [], + "inputs": [ + { + "name": "index", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U32" + } + } + }, + "mode": "None" + }, + { + "name": "amount", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "drop_deposit", + "is_final": true, + "const_parameters": [], + "inputs": [ + { + "name": "index", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U32" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "reset", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + } + ] +} \ No newline at end of file diff --git a/storage/auditor/build/main.aleo b/storage/auditor/build/main.aleo new file mode 100644 index 0000000..1b15557 --- /dev/null +++ b/storage/auditor/build/main.aleo @@ -0,0 +1,94 @@ +import treasury.aleo; +program auditor.aleo; + +struct Snapshot: + balance as u64; + deposit_count as u32; + is_frozen as boolean; + +struct Optional__DmN5CQ9hzeK: + is_some as boolean; + val as u64; + +struct Optional__JzunLORyB8U: + is_some as boolean; + val as u32; + +struct Optional__ATAkdHctJwx: + is_some as boolean; + val as boolean; + +struct Optional__HgFNdm7FG0G: + is_some as boolean; + val as treasury.aleo/FeeConfig; + +mapping last_snapshot__: + key as boolean.public; + value as Snapshot.public; + +mapping is_alert__: + key as boolean.public; + value as boolean.public; + +function take_snapshot: + async take_snapshot into r0; + output r0 as auditor.aleo/take_snapshot.future; + +finalize take_snapshot: + contains treasury.aleo/balance__[false] into r0; + get.or_use treasury.aleo/balance__[false] 0u64 into r1; + ternary r0 r1 0u64 into r2; + cast r0 r2 into r3 as Optional__DmN5CQ9hzeK; + ternary r3.is_some r3.val 0u64 into r4; + contains treasury.aleo/deposit_count__[false] into r5; + get.or_use treasury.aleo/deposit_count__[false] 0u32 into r6; + ternary r5 r6 0u32 into r7; + cast r5 r7 into r8 as Optional__JzunLORyB8U; + ternary r8.is_some r8.val 0u32 into r9; + contains treasury.aleo/is_frozen__[false] into r10; + get.or_use treasury.aleo/is_frozen__[false] false into r11; + ternary r10 r11 false into r12; + cast r10 r12 into r13 as Optional__ATAkdHctJwx; + ternary r13.is_some r13.val false into r14; + cast r4 r9 r14 into r15 as Snapshot; + set r15 into last_snapshot__[false]; + set r14 into is_alert__[false]; + +function check_fee_config: + async check_fee_config into r0; + output r0 as auditor.aleo/check_fee_config.future; + +finalize check_fee_config: + contains treasury.aleo/config__[false] into r0; + cast 0u64 0u64 into r1 as treasury.aleo/FeeConfig; + get.or_use treasury.aleo/config__[false] r1 into r2; + cast 0u64 0u64 into r3 as treasury.aleo/FeeConfig; + ternary r0 r2.rate r3.rate into r4; + ternary r0 r2.minimum r3.minimum into r5; + cast r4 r5 into r6 as treasury.aleo/FeeConfig; + cast r0 r6 into r7 as Optional__HgFNdm7FG0G; + ternary r7.is_some r7.val.rate 0u64 into r8; + ternary r7.is_some r7.val.minimum 0u64 into r9; + cast r8 r9 into r10 as treasury.aleo/FeeConfig; + lte r10.rate 1000u64 into r11; + assert.eq r11 true; + +function check_deposits: + async check_deposits into r0; + output r0 as auditor.aleo/check_deposits.future; + +finalize check_deposits: + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r0; + gt r0 0u32 into r1; + assert.eq r1 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r2; + lt 0u32 r2 into r3; + get.or_use treasury.aleo/deposit_log__[0u32] 0u64 into r4; + ternary r3 r4 0u64 into r5; + cast r3 r5 into r6 as Optional__DmN5CQ9hzeK; + cast false 0u64 into r7 as Optional__DmN5CQ9hzeK; + is.neq r6 r7 into r8; + assert.eq r8 true; + +constructor: + assert.eq edition 0u16; diff --git a/storage/auditor/build/program.json b/storage/auditor/build/program.json new file mode 100644 index 0000000..41676d0 --- /dev/null +++ b/storage/auditor/build/program.json @@ -0,0 +1,9 @@ +{ + "program": "auditor.aleo", + "version": "0.1.0", + "description": "", + "license": "", + "leo": "4.0.2", + "dependencies": null, + "dev_dependencies": null +} diff --git a/storage/auditor/program.json b/storage/auditor/program.json new file mode 100644 index 0000000..bf966f4 --- /dev/null +++ b/storage/auditor/program.json @@ -0,0 +1,9 @@ +{ + "program": "auditor.aleo", + "version": "0.1.0", + "description": "Auditor demonstrating external storage reads across programs.", + "license": "MIT", + "dependencies": [ + { "name": "treasury.aleo", "location": "local", "path": "../treasury" } + ] +} diff --git a/storage/auditor/src/main.leo b/storage/auditor/src/main.leo new file mode 100644 index 0000000..28b0cb1 --- /dev/null +++ b/storage/auditor/src/main.leo @@ -0,0 +1,80 @@ +// auditor.aleo demonstrates reading another program's storage externally. +// +// In Leo 4, any program can read another deployed program's storage +// variables using the `program.aleo::variable` syntax. This is read-only: +// only the owning program can write to its own storage. +// +// Key concepts: +// treasury.aleo::balance.unwrap() — read external singleton +// treasury.aleo::balance.unwrap_or(0u64) — read with fallback +// treasury.aleo::config.unwrap() — read external struct +// treasury.aleo::FeeConfig { ... } — reference external struct type +// treasury.aleo::deposit_log.get(0u32) — read external vector element +// treasury.aleo::deposit_log.len() — read external vector length + +import treasury.aleo; + +struct Snapshot { + balance: u64, + deposit_count: u32, + is_frozen: bool, +} + +program auditor.aleo { + // The auditor maintains its own storage for snapshots. + storage last_snapshot: Snapshot; + storage is_alert: bool; + + // Reads treasury state and stores a local snapshot. + // Demonstrates external singleton reads with unwrap_or. + fn take_snapshot() -> Final { + return final { + // Read external storage singletons. + let bal: u64 = treasury.aleo::balance.unwrap_or(0u64); + let deps: u32 = treasury.aleo::deposit_count.unwrap_or(0u32); + let frozen: bool = treasury.aleo::is_frozen.unwrap_or(false); + + // Write to local storage. + last_snapshot = Snapshot { + balance: bal, + deposit_count: deps, + is_frozen: frozen, + }; + + // Raise alert if treasury is frozen. + is_alert = frozen; + }; + } + + // Reads the treasury's fee configuration struct externally. + // Demonstrates cross-program struct access. + fn check_fee_config() -> Final { + return final { + // Read an external struct. The fallback uses the + // qualified struct type: treasury.aleo::FeeConfig. + let cfg = treasury.aleo::config.unwrap_or( + treasury.aleo::FeeConfig { rate: 0u64, minimum: 0u64 } + ); + + // Verify fee rate is within acceptable bounds (≤ 10%). + assert(cfg.rate <= 1000u64); + }; + } + + // Reads the treasury's deposit log vector externally. + // Demonstrates external vector access. + fn check_deposits() -> Final { + return final { + // Vector length — how many deposits have been logged. + let count: u32 = treasury.aleo::deposit_log.len(); + assert(count > 0u32); + + // Vector get — read the first deposit (returns optional). + let first = treasury.aleo::deposit_log.get(0u32); + assert(first != none); + }; + } + + @noupgrade + constructor() {} +} diff --git a/storage/auditor/tests/test_auditor.leo b/storage/auditor/tests/test_auditor.leo new file mode 100644 index 0000000..acef379 --- /dev/null +++ b/storage/auditor/tests/test_auditor.leo @@ -0,0 +1,157 @@ +// Unit tests for auditor.aleo. +// +// The auditor reads treasury.aleo's storage cross-program. Each test sets +// up treasury state by calling treasury transitions, runs the auditor's +// transition under test, and then asserts on both treasury *and* auditor +// storage from inside the test's finalize block. +// +// Run with: leo test +// (See ../README.md for details.) + +import treasury.aleo; +import auditor.aleo; + +program test_auditor.aleo { + // ── Snapshot mirrors treasury state set up by deposit + update_config ── + @test + fn snapshot_reads_treasury() -> Final { + let f0: Final = treasury.aleo::initialize(); + let f1: Final = treasury.aleo::deposit(5000u64); + let f2: Final = treasury.aleo::deposit(3000u64); + let f3: Final = auditor.aleo::take_snapshot(); + return final { + f0.run(); + f1.run(); + f2.run(); + f3.run(); + + let snap: auditor.aleo::Snapshot = auditor.aleo::last_snapshot.unwrap(); + assert(snap.balance == 8000u64); + assert(snap.deposit_count == 2u32); + assert(snap.is_frozen == false); + // Treasury isn't frozen, so the auditor's alert flag is clear. + assert(auditor.aleo::is_alert.unwrap() == false); + }; + } + + // ── Snapshot of an empty treasury falls back through unwrap_or ───────── + @test + fn snapshot_defaults_on_empty() -> Final { + // No initialize, no deposit — every treasury slot is none. + let f: Final = auditor.aleo::take_snapshot(); + return final { + f.run(); + let snap: auditor.aleo::Snapshot = auditor.aleo::last_snapshot.unwrap(); + assert(snap.balance == 0u64); + assert(snap.deposit_count == 0u32); + assert(snap.is_frozen == false); + assert(auditor.aleo::is_alert.unwrap() == false); + }; + } + + // ── Snapshot detects a freeze and raises the alert flag ───────────────── + @test + fn snapshot_detects_freeze() -> Final { + let f0: Final = treasury.aleo::initialize(); + let f1: Final = treasury.aleo::freeze(); + let f2: Final = auditor.aleo::take_snapshot(); + return final { + f0.run(); + f1.run(); + f2.run(); + assert(auditor.aleo::last_snapshot.unwrap().is_frozen == true); + assert(auditor.aleo::is_alert.unwrap() == true); + }; + } + + // ── A subsequent unfreeze + snapshot clears the alert flag ───────────── + @test + fn alert_clears_after_unfreeze() -> Final { + let f0: Final = treasury.aleo::initialize(); + let f1: Final = treasury.aleo::freeze(); + let f2: Final = auditor.aleo::take_snapshot(); + let f3: Final = treasury.aleo::unfreeze(); + let f4: Final = auditor.aleo::take_snapshot(); + return final { + f0.run(); + f1.run(); + f2.run(); + f3.run(); + f4.run(); + assert(auditor.aleo::last_snapshot.unwrap().is_frozen == false); + assert(auditor.aleo::is_alert.unwrap() == false); + }; + } + + // ── check_fee_config accepts a default-rate config ────────────────────── + @test + fn fee_config_passes_at_default() -> Final { + let f0: Final = treasury.aleo::initialize(); + let f1: Final = auditor.aleo::check_fee_config(); + return final { + f0.run(); + f1.run(); + }; + } + + // ── check_fee_config also accepts a tightened config ──────────────────── + @test + fn fee_config_accepts_low_rate() -> Final { + let f0: Final = treasury.aleo::update_config(50u64, 1u64); + let f1: Final = auditor.aleo::check_fee_config(); + return final { + f0.run(); + f1.run(); + }; + } + + // ── check_fee_config rejects a rate above 10% ─────────────────────────── + @test + @should_fail + fn fee_config_rejects_high_rate() -> Final { + // 1500 basis points = 15%, which exceeds the auditor's 10% cap. + let f0: Final = treasury.aleo::update_config(1500u64, 0u64); + let f1: Final = auditor.aleo::check_fee_config(); + return final { + f0.run(); + f1.run(); + }; + } + + // ── check_fee_config falls back when treasury never set a config ──────── + // The default fallback (rate 0, minimum 0) trivially satisfies rate ≤ 10%. + @test + fn check_fee_config_uses_fallback() -> Final { + let f: Final = auditor.aleo::check_fee_config(); + return final { + f.run(); + }; + } + + // ── check_deposits sees the treasury's logged deposits ───────────────── + @test + fn check_deposits_after_deposit() -> Final { + let f0: Final = treasury.aleo::deposit(1000u64); + let f1: Final = auditor.aleo::check_deposits(); + return final { + f0.run(); + f1.run(); + // Cross-check what the auditor read against treasury storage. + assert(treasury.aleo::deposit_log.len() == 1u32); + assert(treasury.aleo::deposit_log.get(0u32).unwrap() == 1000u64); + }; + } + + // ── check_deposits halts if the log is empty (assert count > 0) ───────── + @test + @should_fail + fn check_deposits_empty_fails() -> Final { + let f: Final = auditor.aleo::check_deposits(); + return final { + f.run(); + }; + } + + @noupgrade + constructor() {} +} diff --git a/storage/run.sh b/storage/run.sh new file mode 100755 index 0000000..e9d90f5 --- /dev/null +++ b/storage/run.sh @@ -0,0 +1,147 @@ +#!/bin/bash +# Demonstrates storage singletons, vectors, and external storage access in Leo. +# Run from the storage/ 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" + --consensus-version 14) + +# ─── Local demos (no devnode required) ──────────────────────────────────────── + +echo "" +echo "═══════════════════════════════════════════════" +echo " PART 1: Local execution (no devnode needed)" +echo "═══════════════════════════════════════════════" + +echo "" +echo "── treasury.aleo: storage singletons & vectors ─" +cd treasury + +echo "" +echo " Initialize treasury (sets balance, config, frozen flag):" +$LEO run initialize + +echo "" +echo " Deposit 5000 tokens (unwrap_or handles uninitialized storage):" +$LEO run deposit 5000u64 + +echo "" +echo " Deposit 3000 tokens:" +$LEO run deposit 3000u64 + +echo "" +echo " Withdraw 1000 tokens:" +$LEO run withdraw 1000u64 + +echo "" +echo " Update fee config (struct storage):" +$LEO run update_config 500u64 25u64 + +echo "" +echo " Correct an existing log entry (vector .set):" +$LEO run correct_deposit 0u32 4500u64 + +echo "" +echo " Drop an entry by swap_remove (vector .swap_remove):" +$LEO run drop_deposit 0u32 + +echo "" +echo " Freeze treasury:" +$LEO run freeze + +echo "" +echo " Unfreeze treasury:" +$LEO run unfreeze + +cd .. + +# ─── Devnode deployment and external storage access ─────────────────────────── + +echo "" +echo "═══════════════════════════════════════════════" +echo " PART 2: Deployment and external storage access" +echo " (requires leo devnode)" +echo "═══════════════════════════════════════════════" + +echo "" +echo "── Step 1: Deploy treasury.aleo and auditor.aleo ─" +echo " (auditor lists treasury as a local dependency," +echo " so Leo deploys treasury first, then auditor)" +cd auditor +$LEO deploy "${COMMON_OPTS[@]}" +cd .. + +echo "" +echo "── Step 2: Initialize treasury ──────────────" +cd treasury +$LEO execute treasury.aleo/initialize "${COMMON_OPTS[@]}" + +echo "" +echo "── Step 3: Deposit 5000 tokens ──────────────" +echo " (writes balance, increments count, pushes to vector)" +$LEO execute treasury.aleo/deposit 5000u64 "${COMMON_OPTS[@]}" + +echo "" +echo "── Step 4: Deposit 3000 tokens ──────────────" +$LEO execute treasury.aleo/deposit 3000u64 "${COMMON_OPTS[@]}" + +echo "" +echo "── Step 5: Update fee config ────────────────" +echo " (writes struct to storage)" +$LEO execute treasury.aleo/update_config 500u64 25u64 "${COMMON_OPTS[@]}" + +cd .. + +echo "" +echo "── Step 6: Take snapshot (read external singletons) ─" +echo " Reads treasury.aleo::balance, ::deposit_count, ::is_frozen" +cd auditor +$LEO execute auditor.aleo/take_snapshot "${COMMON_OPTS[@]}" + +echo "" +echo "── Step 7: Check fee config (read external struct) ─" +echo " Reads treasury.aleo::config, verifies rate ≤ 10%" +$LEO execute auditor.aleo/check_fee_config "${COMMON_OPTS[@]}" + +echo "" +echo "── Step 8: Check deposits (read external vector) ─" +echo " Reads treasury.aleo::deposit_log.len() and .get(0)" +$LEO execute auditor.aleo/check_deposits "${COMMON_OPTS[@]}" + +cd .. + +echo "" +echo "── Step 9: Freeze treasury ──────────────────" +cd treasury +$LEO execute treasury.aleo/freeze "${COMMON_OPTS[@]}" +cd .. + +echo "" +echo "── Step 10: Snapshot detects freeze ─────────" +echo " auditor reads is_frozen = true, sets is_alert = true" +cd auditor +$LEO execute auditor.aleo/take_snapshot "${COMMON_OPTS[@]}" +cd .. + +echo "" +echo "── Step 11: Reset treasury ──────────────────" +echo " Clears all storage to none, clears vector" +cd treasury +$LEO execute treasury.aleo/reset "${COMMON_OPTS[@]}" +cd .. + +echo "" +echo "Done! The auditor read treasury storage cross-program" +echo "and detected the freeze — all without direct function calls." diff --git a/storage/treasury/.env b/storage/treasury/.env new file mode 100644 index 0000000..ab204ee --- /dev/null +++ b/storage/treasury/.env @@ -0,0 +1,3 @@ +NETWORK=testnet +PRIVATE_KEY=APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH +ENDPOINT=http://localhost:3030 diff --git a/storage/treasury/build/abi.json b/storage/treasury/build/abi.json new file mode 100644 index 0000000..d963b59 --- /dev/null +++ b/storage/treasury/build/abi.json @@ -0,0 +1,290 @@ +{ + "program": "treasury.aleo", + "implements": [], + "structs": [ + { + "path": [ + "FeeConfig" + ], + "fields": [ + { + "name": "rate", + "ty": { + "Primitive": { + "UInt": "U64" + } + } + }, + { + "name": "minimum", + "ty": { + "Primitive": { + "UInt": "U64" + } + } + } + ] + } + ], + "records": [], + "mappings": [], + "storage_variables": [ + { + "name": "balance", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + } + }, + { + "name": "is_frozen", + "ty": { + "Plaintext": { + "Primitive": "Boolean" + } + } + }, + { + "name": "deposit_count", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U32" + } + } + } + }, + { + "name": "config", + "ty": { + "Plaintext": { + "Struct": { + "path": [ + "FeeConfig" + ], + "program": "treasury.aleo" + } + } + } + }, + { + "name": "deposit_log", + "ty": { + "Vector": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + } + } + } + ], + "functions": [ + { + "name": "initialize", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "deposit", + "is_final": true, + "const_parameters": [], + "inputs": [ + { + "name": "amount", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "withdraw", + "is_final": true, + "const_parameters": [], + "inputs": [ + { + "name": "amount", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "freeze", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "unfreeze", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "update_config", + "is_final": true, + "const_parameters": [], + "inputs": [ + { + "name": "rate", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + }, + { + "name": "minimum", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "undo_last_deposit", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "correct_deposit", + "is_final": true, + "const_parameters": [], + "inputs": [ + { + "name": "index", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U32" + } + } + }, + "mode": "None" + }, + { + "name": "amount", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "drop_deposit", + "is_final": true, + "const_parameters": [], + "inputs": [ + { + "name": "index", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U32" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "reset", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + } + ] +} \ No newline at end of file diff --git a/storage/treasury/build/imports/test_treasury.aleo b/storage/treasury/build/imports/test_treasury.aleo new file mode 100644 index 0000000..b765aef --- /dev/null +++ b/storage/treasury/build/imports/test_treasury.aleo @@ -0,0 +1,490 @@ +import treasury.aleo; +program test_treasury.aleo; + +struct Optional__DmN5CQ9hzeK: + is_some as boolean; + val as u64; + +struct Optional__ATAkdHctJwx: + is_some as boolean; + val as boolean; + +struct Optional__JzunLORyB8U: + is_some as boolean; + val as u32; + +struct Optional__HgFNdm7FG0G: + is_some as boolean; + val as treasury.aleo/FeeConfig; + +function unset_storage_reads_as_none: + async unset_storage_reads_as_none into r0; + output r0 as test_treasury.aleo/unset_storage_reads_as_none.future; + +finalize unset_storage_reads_as_none: + contains treasury.aleo/balance__[false] into r0; + get.or_use treasury.aleo/balance__[false] 0u64 into r1; + ternary r0 r1 0u64 into r2; + cast r0 r2 into r3 as Optional__DmN5CQ9hzeK; + ternary r3.is_some r3.val 0u64 into r4; + is.eq r4 0u64 into r5; + assert.eq r5 true; + contains treasury.aleo/is_frozen__[false] into r6; + get.or_use treasury.aleo/is_frozen__[false] false into r7; + ternary r6 r7 false into r8; + cast r6 r8 into r9 as Optional__ATAkdHctJwx; + ternary r9.is_some r9.val false into r10; + is.eq r10 false into r11; + assert.eq r11 true; + contains treasury.aleo/deposit_count__[false] into r12; + get.or_use treasury.aleo/deposit_count__[false] 0u32 into r13; + ternary r12 r13 0u32 into r14; + cast r12 r14 into r15 as Optional__JzunLORyB8U; + ternary r15.is_some r15.val 0u32 into r16; + is.eq r16 0u32 into r17; + assert.eq r17 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r18; + is.eq r18 0u32 into r19; + assert.eq r19 true; + +function initialize_sets_state: + call treasury.aleo/initialize into r0; + async initialize_sets_state r0 into r1; + output r1 as test_treasury.aleo/initialize_sets_state.future; + +finalize initialize_sets_state: + input r0 as treasury.aleo/initialize.future; + await r0; + contains treasury.aleo/balance__[false] into r1; + get.or_use treasury.aleo/balance__[false] 0u64 into r2; + ternary r1 r2 0u64 into r3; + cast r1 r3 into r4 as Optional__DmN5CQ9hzeK; + assert.eq r4.is_some true; + is.eq r4.val 0u64 into r5; + assert.eq r5 true; + contains treasury.aleo/is_frozen__[false] into r6; + get.or_use treasury.aleo/is_frozen__[false] false into r7; + ternary r6 r7 false into r8; + cast r6 r8 into r9 as Optional__ATAkdHctJwx; + assert.eq r9.is_some true; + is.eq r9.val false into r10; + assert.eq r10 true; + contains treasury.aleo/deposit_count__[false] into r11; + get.or_use treasury.aleo/deposit_count__[false] 0u32 into r12; + ternary r11 r12 0u32 into r13; + cast r11 r13 into r14 as Optional__JzunLORyB8U; + assert.eq r14.is_some true; + is.eq r14.val 0u32 into r15; + assert.eq r15 true; + contains treasury.aleo/config__[false] into r16; + cast 0u64 0u64 into r17 as treasury.aleo/FeeConfig; + get.or_use treasury.aleo/config__[false] r17 into r18; + cast 0u64 0u64 into r19 as treasury.aleo/FeeConfig; + ternary r16 r18.rate r19.rate into r20; + ternary r16 r18.minimum r19.minimum into r21; + cast r20 r21 into r22 as treasury.aleo/FeeConfig; + cast r16 r22 into r23 as Optional__HgFNdm7FG0G; + assert.eq r23.is_some true; + is.eq r23.val.rate 250u64 into r24; + assert.eq r24 true; + is.eq r23.val.minimum 10u64 into r25; + assert.eq r25 true; + +function deposit_accumulates_state: + call treasury.aleo/initialize into r0; + call treasury.aleo/deposit 5000u64 into r1; + call treasury.aleo/deposit 3000u64 into r2; + async deposit_accumulates_state r0 r1 r2 into r3; + output r3 as test_treasury.aleo/deposit_accumulates_state.future; + +finalize deposit_accumulates_state: + input r0 as treasury.aleo/initialize.future; + input r1 as treasury.aleo/deposit.future; + input r2 as treasury.aleo/deposit.future; + await r0; + await r1; + await r2; + contains treasury.aleo/balance__[false] into r3; + get.or_use treasury.aleo/balance__[false] 0u64 into r4; + ternary r3 r4 0u64 into r5; + cast r3 r5 into r6 as Optional__DmN5CQ9hzeK; + assert.eq r6.is_some true; + is.eq r6.val 8000u64 into r7; + assert.eq r7 true; + contains treasury.aleo/deposit_count__[false] into r8; + get.or_use treasury.aleo/deposit_count__[false] 0u32 into r9; + ternary r8 r9 0u32 into r10; + cast r8 r10 into r11 as Optional__JzunLORyB8U; + assert.eq r11.is_some true; + is.eq r11.val 2u32 into r12; + assert.eq r12 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r13; + is.eq r13 2u32 into r14; + assert.eq r14 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r15; + lt 0u32 r15 into r16; + get.or_use treasury.aleo/deposit_log__[0u32] 0u64 into r17; + ternary r16 r17 0u64 into r18; + cast r16 r18 into r19 as Optional__DmN5CQ9hzeK; + assert.eq r19.is_some true; + is.eq r19.val 5000u64 into r20; + assert.eq r20 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r21; + lt 1u32 r21 into r22; + get.or_use treasury.aleo/deposit_log__[1u32] 0u64 into r23; + ternary r22 r23 0u64 into r24; + cast r22 r24 into r25 as Optional__DmN5CQ9hzeK; + assert.eq r25.is_some true; + is.eq r25.val 3000u64 into r26; + assert.eq r26 true; + +function deposit_without_initialize: + call treasury.aleo/deposit 100u64 into r0; + async deposit_without_initialize r0 into r1; + output r1 as test_treasury.aleo/deposit_without_initialize.future; + +finalize deposit_without_initialize: + input r0 as treasury.aleo/deposit.future; + await r0; + contains treasury.aleo/balance__[false] into r1; + get.or_use treasury.aleo/balance__[false] 0u64 into r2; + ternary r1 r2 0u64 into r3; + cast r1 r3 into r4 as Optional__DmN5CQ9hzeK; + assert.eq r4.is_some true; + is.eq r4.val 100u64 into r5; + assert.eq r5 true; + contains treasury.aleo/deposit_count__[false] into r6; + get.or_use treasury.aleo/deposit_count__[false] 0u32 into r7; + ternary r6 r7 0u32 into r8; + cast r6 r8 into r9 as Optional__JzunLORyB8U; + assert.eq r9.is_some true; + is.eq r9.val 1u32 into r10; + assert.eq r10 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r11; + is.eq r11 1u32 into r12; + assert.eq r12 true; + +function withdraw_reduces_balance: + call treasury.aleo/initialize into r0; + call treasury.aleo/deposit 5000u64 into r1; + call treasury.aleo/withdraw 1500u64 into r2; + async withdraw_reduces_balance r0 r1 r2 into r3; + output r3 as test_treasury.aleo/withdraw_reduces_balance.future; + +finalize withdraw_reduces_balance: + input r0 as treasury.aleo/initialize.future; + input r1 as treasury.aleo/deposit.future; + input r2 as treasury.aleo/withdraw.future; + await r0; + await r1; + await r2; + contains treasury.aleo/balance__[false] into r3; + get.or_use treasury.aleo/balance__[false] 0u64 into r4; + ternary r3 r4 0u64 into r5; + cast r3 r5 into r6 as Optional__DmN5CQ9hzeK; + assert.eq r6.is_some true; + is.eq r6.val 3500u64 into r7; + assert.eq r7 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r8; + is.eq r8 1u32 into r9; + assert.eq r9 true; + +function overdraw_fails: + call treasury.aleo/initialize into r0; + call treasury.aleo/deposit 100u64 into r1; + call treasury.aleo/withdraw 101u64 into r2; + async overdraw_fails r0 r1 r2 into r3; + output r3 as test_treasury.aleo/overdraw_fails.future; + +finalize overdraw_fails: + input r0 as treasury.aleo/initialize.future; + input r1 as treasury.aleo/deposit.future; + input r2 as treasury.aleo/withdraw.future; + await r0; + await r1; + await r2; + +function withdraw_without_balance_fails: + call treasury.aleo/withdraw 1u64 into r0; + async withdraw_without_balance_fails r0 into r1; + output r1 as test_treasury.aleo/withdraw_without_balance_fails.future; + +finalize withdraw_without_balance_fails: + input r0 as treasury.aleo/withdraw.future; + await r0; + +function freeze_blocks_deposit: + call treasury.aleo/initialize into r0; + call treasury.aleo/freeze into r1; + call treasury.aleo/deposit 100u64 into r2; + async freeze_blocks_deposit r0 r1 r2 into r3; + output r3 as test_treasury.aleo/freeze_blocks_deposit.future; + +finalize freeze_blocks_deposit: + input r0 as treasury.aleo/initialize.future; + input r1 as treasury.aleo/freeze.future; + input r2 as treasury.aleo/deposit.future; + await r0; + await r1; + await r2; + +function freeze_blocks_withdraw: + call treasury.aleo/initialize into r0; + call treasury.aleo/deposit 500u64 into r1; + call treasury.aleo/freeze into r2; + call treasury.aleo/withdraw 100u64 into r3; + async freeze_blocks_withdraw r0 r1 r2 r3 into r4; + output r4 as test_treasury.aleo/freeze_blocks_withdraw.future; + +finalize freeze_blocks_withdraw: + input r0 as treasury.aleo/initialize.future; + input r1 as treasury.aleo/deposit.future; + input r2 as treasury.aleo/freeze.future; + input r3 as treasury.aleo/withdraw.future; + await r0; + await r1; + await r2; + await r3; + +function unfreeze_restores_deposits: + call treasury.aleo/initialize into r0; + call treasury.aleo/freeze into r1; + call treasury.aleo/unfreeze into r2; + call treasury.aleo/deposit 42u64 into r3; + async unfreeze_restores_deposits r0 r1 r2 r3 into r4; + output r4 as test_treasury.aleo/unfreeze_restores_deposits.future; + +finalize unfreeze_restores_deposits: + input r0 as treasury.aleo/initialize.future; + input r1 as treasury.aleo/freeze.future; + input r2 as treasury.aleo/unfreeze.future; + input r3 as treasury.aleo/deposit.future; + await r0; + await r1; + await r2; + await r3; + contains treasury.aleo/is_frozen__[false] into r4; + get.or_use treasury.aleo/is_frozen__[false] false into r5; + ternary r4 r5 false into r6; + cast r4 r6 into r7 as Optional__ATAkdHctJwx; + assert.eq r7.is_some true; + is.eq r7.val false into r8; + assert.eq r8 true; + contains treasury.aleo/balance__[false] into r9; + get.or_use treasury.aleo/balance__[false] 0u64 into r10; + ternary r9 r10 0u64 into r11; + cast r9 r11 into r12 as Optional__DmN5CQ9hzeK; + assert.eq r12.is_some true; + is.eq r12.val 42u64 into r13; + assert.eq r13 true; + +function update_config_replaces_struct: + call treasury.aleo/initialize into r0; + call treasury.aleo/update_config 500u64 25u64 into r1; + async update_config_replaces_struct r0 r1 into r2; + output r2 as test_treasury.aleo/update_config_replaces_struct.future; + +finalize update_config_replaces_struct: + input r0 as treasury.aleo/initialize.future; + input r1 as treasury.aleo/update_config.future; + await r0; + await r1; + contains treasury.aleo/config__[false] into r2; + cast 0u64 0u64 into r3 as treasury.aleo/FeeConfig; + get.or_use treasury.aleo/config__[false] r3 into r4; + cast 0u64 0u64 into r5 as treasury.aleo/FeeConfig; + ternary r2 r4.rate r5.rate into r6; + ternary r2 r4.minimum r5.minimum into r7; + cast r6 r7 into r8 as treasury.aleo/FeeConfig; + cast r2 r8 into r9 as Optional__HgFNdm7FG0G; + assert.eq r9.is_some true; + is.eq r9.val.rate 500u64 into r10; + assert.eq r10 true; + is.eq r9.val.minimum 25u64 into r11; + assert.eq r11 true; + +function undo_last_deposit_reverses: + call treasury.aleo/deposit 100u64 into r0; + call treasury.aleo/deposit 250u64 into r1; + call treasury.aleo/undo_last_deposit into r2; + async undo_last_deposit_reverses r0 r1 r2 into r3; + output r3 as test_treasury.aleo/undo_last_deposit_reverses.future; + +finalize undo_last_deposit_reverses: + input r0 as treasury.aleo/deposit.future; + input r1 as treasury.aleo/deposit.future; + input r2 as treasury.aleo/undo_last_deposit.future; + await r0; + await r1; + await r2; + contains treasury.aleo/balance__[false] into r3; + get.or_use treasury.aleo/balance__[false] 0u64 into r4; + ternary r3 r4 0u64 into r5; + cast r3 r5 into r6 as Optional__DmN5CQ9hzeK; + assert.eq r6.is_some true; + is.eq r6.val 100u64 into r7; + assert.eq r7 true; + contains treasury.aleo/deposit_count__[false] into r8; + get.or_use treasury.aleo/deposit_count__[false] 0u32 into r9; + ternary r8 r9 0u32 into r10; + cast r8 r10 into r11 as Optional__JzunLORyB8U; + assert.eq r11.is_some true; + is.eq r11.val 1u32 into r12; + assert.eq r12 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r13; + is.eq r13 1u32 into r14; + assert.eq r14 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r15; + lt 0u32 r15 into r16; + get.or_use treasury.aleo/deposit_log__[0u32] 0u64 into r17; + ternary r16 r17 0u64 into r18; + cast r16 r18 into r19 as Optional__DmN5CQ9hzeK; + assert.eq r19.is_some true; + is.eq r19.val 100u64 into r20; + assert.eq r20 true; + +function undo_on_empty_log_fails: + call treasury.aleo/undo_last_deposit into r0; + async undo_on_empty_log_fails r0 into r1; + output r1 as test_treasury.aleo/undo_on_empty_log_fails.future; + +finalize undo_on_empty_log_fails: + input r0 as treasury.aleo/undo_last_deposit.future; + await r0; + +function correct_deposit_overwrites: + call treasury.aleo/deposit 100u64 into r0; + call treasury.aleo/deposit 200u64 into r1; + call treasury.aleo/correct_deposit 1u32 250u64 into r2; + async correct_deposit_overwrites r0 r1 r2 into r3; + output r3 as test_treasury.aleo/correct_deposit_overwrites.future; + +finalize correct_deposit_overwrites: + input r0 as treasury.aleo/deposit.future; + input r1 as treasury.aleo/deposit.future; + input r2 as treasury.aleo/correct_deposit.future; + await r0; + await r1; + await r2; + contains treasury.aleo/balance__[false] into r3; + get.or_use treasury.aleo/balance__[false] 0u64 into r4; + ternary r3 r4 0u64 into r5; + cast r3 r5 into r6 as Optional__DmN5CQ9hzeK; + assert.eq r6.is_some true; + is.eq r6.val 350u64 into r7; + assert.eq r7 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r8; + lt 1u32 r8 into r9; + get.or_use treasury.aleo/deposit_log__[1u32] 0u64 into r10; + ternary r9 r10 0u64 into r11; + cast r9 r11 into r12 as Optional__DmN5CQ9hzeK; + assert.eq r12.is_some true; + is.eq r12.val 250u64 into r13; + assert.eq r13 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r14; + is.eq r14 2u32 into r15; + assert.eq r15 true; + +function drop_deposit_swap_removes: + call treasury.aleo/deposit 10u64 into r0; + call treasury.aleo/deposit 20u64 into r1; + call treasury.aleo/deposit 30u64 into r2; + call treasury.aleo/drop_deposit 0u32 into r3; + async drop_deposit_swap_removes r0 r1 r2 r3 into r4; + output r4 as test_treasury.aleo/drop_deposit_swap_removes.future; + +finalize drop_deposit_swap_removes: + input r0 as treasury.aleo/deposit.future; + input r1 as treasury.aleo/deposit.future; + input r2 as treasury.aleo/deposit.future; + input r3 as treasury.aleo/drop_deposit.future; + await r0; + await r1; + await r2; + await r3; + contains treasury.aleo/balance__[false] into r4; + get.or_use treasury.aleo/balance__[false] 0u64 into r5; + ternary r4 r5 0u64 into r6; + cast r4 r6 into r7 as Optional__DmN5CQ9hzeK; + assert.eq r7.is_some true; + is.eq r7.val 50u64 into r8; + assert.eq r8 true; + contains treasury.aleo/deposit_count__[false] into r9; + get.or_use treasury.aleo/deposit_count__[false] 0u32 into r10; + ternary r9 r10 0u32 into r11; + cast r9 r11 into r12 as Optional__JzunLORyB8U; + assert.eq r12.is_some true; + is.eq r12.val 2u32 into r13; + assert.eq r13 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r14; + is.eq r14 2u32 into r15; + assert.eq r15 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r16; + lt 0u32 r16 into r17; + get.or_use treasury.aleo/deposit_log__[0u32] 0u64 into r18; + ternary r17 r18 0u64 into r19; + cast r17 r19 into r20 as Optional__DmN5CQ9hzeK; + assert.eq r20.is_some true; + is.eq r20.val 30u64 into r21; + assert.eq r21 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r22; + lt 1u32 r22 into r23; + get.or_use treasury.aleo/deposit_log__[1u32] 0u64 into r24; + ternary r23 r24 0u64 into r25; + cast r23 r25 into r26 as Optional__DmN5CQ9hzeK; + assert.eq r26.is_some true; + is.eq r26.val 20u64 into r27; + assert.eq r27 true; + +function reset_clears_every_slot: + call treasury.aleo/initialize into r0; + call treasury.aleo/deposit 100u64 into r1; + call treasury.aleo/deposit 200u64 into r2; + call treasury.aleo/reset into r3; + async reset_clears_every_slot r0 r1 r2 r3 into r4; + output r4 as test_treasury.aleo/reset_clears_every_slot.future; + +finalize reset_clears_every_slot: + input r0 as treasury.aleo/initialize.future; + input r1 as treasury.aleo/deposit.future; + input r2 as treasury.aleo/deposit.future; + input r3 as treasury.aleo/reset.future; + await r0; + await r1; + await r2; + await r3; + contains treasury.aleo/balance__[false] into r4; + get.or_use treasury.aleo/balance__[false] 0u64 into r5; + ternary r4 r5 0u64 into r6; + cast r4 r6 into r7 as Optional__DmN5CQ9hzeK; + ternary r7.is_some r7.val 0u64 into r8; + is.eq r8 0u64 into r9; + assert.eq r9 true; + contains treasury.aleo/is_frozen__[false] into r10; + get.or_use treasury.aleo/is_frozen__[false] false into r11; + ternary r10 r11 false into r12; + cast r10 r12 into r13 as Optional__ATAkdHctJwx; + ternary r13.is_some r13.val false into r14; + is.eq r14 false into r15; + assert.eq r15 true; + contains treasury.aleo/deposit_count__[false] into r16; + get.or_use treasury.aleo/deposit_count__[false] 0u32 into r17; + ternary r16 r17 0u32 into r18; + cast r16 r18 into r19 as Optional__JzunLORyB8U; + ternary r19.is_some r19.val 0u32 into r20; + is.eq r20 0u32 into r21; + assert.eq r21 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r22; + is.eq r22 0u32 into r23; + assert.eq r23 true; + get.or_use treasury.aleo/deposit_log__len__[false] 0u32 into r24; + lt 0u32 r24 into r25; + get.or_use treasury.aleo/deposit_log__[0u32] 0u64 into r26; + ternary r25 r26 0u64 into r27; + cast r25 r27 into r28 as Optional__DmN5CQ9hzeK; + ternary r28.is_some r28.val 0u64 into r29; + is.eq r29 0u64 into r30; + assert.eq r30 true; + +constructor: + assert.eq edition 0u16; diff --git a/storage/treasury/build/imports/treasury.aleo b/storage/treasury/build/imports/treasury.aleo new file mode 100644 index 0000000..c11cf9d --- /dev/null +++ b/storage/treasury/build/imports/treasury.aleo @@ -0,0 +1,239 @@ +program treasury.aleo; + +struct FeeConfig: + rate as u64; + minimum as u64; + +struct Optional__ATAkdHctJwx: + is_some as boolean; + val as boolean; + +struct Optional__DmN5CQ9hzeK: + is_some as boolean; + val as u64; + +struct Optional__JzunLORyB8U: + is_some as boolean; + val as u32; + +mapping balance__: + key as boolean.public; + value as u64.public; + +mapping is_frozen__: + key as boolean.public; + value as boolean.public; + +mapping deposit_count__: + key as boolean.public; + value as u32.public; + +mapping config__: + key as boolean.public; + value as FeeConfig.public; + +mapping deposit_log__: + key as u32.public; + value as u64.public; + +mapping deposit_log__len__: + key as boolean.public; + value as u32.public; + +function initialize: + async initialize into r0; + output r0 as treasury.aleo/initialize.future; + +finalize initialize: + set 0u64 into balance__[false]; + set false into is_frozen__[false]; + set 0u32 into deposit_count__[false]; + cast 250u64 10u64 into r0 as FeeConfig; + set r0 into config__[false]; + +function deposit: + input r0 as u64.private; + async deposit r0 into r1; + output r1 as treasury.aleo/deposit.future; + +finalize deposit: + input r0 as u64.public; + contains is_frozen__[false] into r1; + get.or_use is_frozen__[false] false into r2; + ternary r1 r2 false into r3; + cast r1 r3 into r4 as Optional__ATAkdHctJwx; + ternary r4.is_some r4.val false into r5; + is.eq r5 false into r6; + assert.eq r6 true; + contains balance__[false] into r7; + get.or_use balance__[false] 0u64 into r8; + ternary r7 r8 0u64 into r9; + cast r7 r9 into r10 as Optional__DmN5CQ9hzeK; + ternary r10.is_some r10.val 0u64 into r11; + add r11 r0 into r12; + set r12 into balance__[false]; + contains deposit_count__[false] into r13; + get.or_use deposit_count__[false] 0u32 into r14; + ternary r13 r14 0u32 into r15; + cast r13 r15 into r16 as Optional__JzunLORyB8U; + ternary r16.is_some r16.val 0u32 into r17; + add r17 1u32 into r18; + set r18 into deposit_count__[false]; + get.or_use deposit_log__len__[false] 0u32 into r19; + add r19 1u32 into r20; + set r20 into deposit_log__len__[false]; + set r0 into deposit_log__[r19]; + +function withdraw: + input r0 as u64.private; + async withdraw r0 into r1; + output r1 as treasury.aleo/withdraw.future; + +finalize withdraw: + input r0 as u64.public; + contains is_frozen__[false] into r1; + get.or_use is_frozen__[false] false into r2; + ternary r1 r2 false into r3; + cast r1 r3 into r4 as Optional__ATAkdHctJwx; + ternary r4.is_some r4.val false into r5; + is.eq r5 false into r6; + assert.eq r6 true; + contains balance__[false] into r7; + get.or_use balance__[false] 0u64 into r8; + ternary r7 r8 0u64 into r9; + cast r7 r9 into r10 as Optional__DmN5CQ9hzeK; + assert.eq r10.is_some true; + gte r10.val r0 into r11; + assert.eq r11 true; + sub r10.val r0 into r12; + set r12 into balance__[false]; + +function freeze: + async freeze into r0; + output r0 as treasury.aleo/freeze.future; + +finalize freeze: + set true into is_frozen__[false]; + +function unfreeze: + async unfreeze into r0; + output r0 as treasury.aleo/unfreeze.future; + +finalize unfreeze: + set false into is_frozen__[false]; + +function update_config: + input r0 as u64.private; + input r1 as u64.private; + async update_config r0 r1 into r2; + output r2 as treasury.aleo/update_config.future; + +finalize update_config: + input r0 as u64.public; + input r1 as u64.public; + cast r0 r1 into r2 as FeeConfig; + set r2 into config__[false]; + +function undo_last_deposit: + async undo_last_deposit into r0; + output r0 as treasury.aleo/undo_last_deposit.future; + +finalize undo_last_deposit: + get.or_use deposit_log__len__[false] 0u32 into r0; + gt r0 0u32 into r1; + sub.w r0 1u32 into r2; + ternary r1 r2 r0 into r3; + set r3 into deposit_log__len__[false]; + get.or_use deposit_log__[r2] 0u64 into r4; + ternary r1 r4 0u64 into r5; + cast r1 r5 into r6 as Optional__DmN5CQ9hzeK; + cast false 0u64 into r7 as Optional__DmN5CQ9hzeK; + is.neq r6 r7 into r8; + assert.eq r8 true; + contains balance__[false] into r9; + get.or_use balance__[false] 0u64 into r10; + ternary r9 r10 0u64 into r11; + cast r9 r11 into r12 as Optional__DmN5CQ9hzeK; + assert.eq r6.is_some true; + ternary r12.is_some r12.val 0u64 into r13; + sub r13 r6.val into r14; + set r14 into balance__[false]; + contains deposit_count__[false] into r15; + get.or_use deposit_count__[false] 0u32 into r16; + ternary r15 r16 0u32 into r17; + cast r15 r17 into r18 as Optional__JzunLORyB8U; + ternary r18.is_some r18.val 0u32 into r19; + sub r19 1u32 into r20; + set r20 into deposit_count__[false]; + +function correct_deposit: + input r0 as u32.private; + input r1 as u64.private; + async correct_deposit r0 r1 into r2; + output r2 as treasury.aleo/correct_deposit.future; + +finalize correct_deposit: + input r0 as u32.public; + input r1 as u64.public; + get.or_use deposit_log__len__[false] 0u32 into r2; + lt r0 r2 into r3; + get.or_use deposit_log__[r0] 0u64 into r4; + ternary r3 r4 0u64 into r5; + cast r3 r5 into r6 as Optional__DmN5CQ9hzeK; + assert.eq r6.is_some true; + get.or_use deposit_log__len__[false] 0u32 into r7; + lt r0 r7 into r8; + assert.eq r8 true; + set r1 into deposit_log__[r0]; + contains balance__[false] into r9; + get.or_use balance__[false] 0u64 into r10; + ternary r9 r10 0u64 into r11; + cast r9 r11 into r12 as Optional__DmN5CQ9hzeK; + ternary r12.is_some r12.val 0u64 into r13; + add r13 r1 into r14; + sub r14 r6.val into r15; + set r15 into balance__[false]; + +function drop_deposit: + input r0 as u32.private; + async drop_deposit r0 into r1; + output r1 as treasury.aleo/drop_deposit.future; + +finalize drop_deposit: + input r0 as u32.public; + get.or_use deposit_log__len__[false] 0u32 into r1; + lt r0 r1 into r2; + assert.eq r2 true; + get deposit_log__[r0] into r3; + sub r1 1u32 into r4; + get deposit_log__[r4] into r5; + set r5 into deposit_log__[r0]; + set r4 into deposit_log__len__[false]; + contains balance__[false] into r6; + get.or_use balance__[false] 0u64 into r7; + ternary r6 r7 0u64 into r8; + cast r6 r8 into r9 as Optional__DmN5CQ9hzeK; + ternary r9.is_some r9.val 0u64 into r10; + sub r10 r3 into r11; + set r11 into balance__[false]; + contains deposit_count__[false] into r12; + get.or_use deposit_count__[false] 0u32 into r13; + ternary r12 r13 0u32 into r14; + cast r12 r14 into r15 as Optional__JzunLORyB8U; + ternary r15.is_some r15.val 0u32 into r16; + sub r16 1u32 into r17; + set r17 into deposit_count__[false]; + +function reset: + async reset into r0; + output r0 as treasury.aleo/reset.future; + +finalize reset: + remove balance__[false]; + remove is_frozen__[false]; + remove deposit_count__[false]; + remove config__[false]; + set 0u32 into deposit_log__len__[false]; + +constructor: + assert.eq edition 0u16; diff --git a/storage/treasury/build/imports/treasury.aleo.abi.json b/storage/treasury/build/imports/treasury.aleo.abi.json new file mode 100644 index 0000000..d963b59 --- /dev/null +++ b/storage/treasury/build/imports/treasury.aleo.abi.json @@ -0,0 +1,290 @@ +{ + "program": "treasury.aleo", + "implements": [], + "structs": [ + { + "path": [ + "FeeConfig" + ], + "fields": [ + { + "name": "rate", + "ty": { + "Primitive": { + "UInt": "U64" + } + } + }, + { + "name": "minimum", + "ty": { + "Primitive": { + "UInt": "U64" + } + } + } + ] + } + ], + "records": [], + "mappings": [], + "storage_variables": [ + { + "name": "balance", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + } + }, + { + "name": "is_frozen", + "ty": { + "Plaintext": { + "Primitive": "Boolean" + } + } + }, + { + "name": "deposit_count", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U32" + } + } + } + }, + { + "name": "config", + "ty": { + "Plaintext": { + "Struct": { + "path": [ + "FeeConfig" + ], + "program": "treasury.aleo" + } + } + } + }, + { + "name": "deposit_log", + "ty": { + "Vector": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + } + } + } + ], + "functions": [ + { + "name": "initialize", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "deposit", + "is_final": true, + "const_parameters": [], + "inputs": [ + { + "name": "amount", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "withdraw", + "is_final": true, + "const_parameters": [], + "inputs": [ + { + "name": "amount", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "freeze", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "unfreeze", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "update_config", + "is_final": true, + "const_parameters": [], + "inputs": [ + { + "name": "rate", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + }, + { + "name": "minimum", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "undo_last_deposit", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "correct_deposit", + "is_final": true, + "const_parameters": [], + "inputs": [ + { + "name": "index", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U32" + } + } + }, + "mode": "None" + }, + { + "name": "amount", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U64" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "drop_deposit", + "is_final": true, + "const_parameters": [], + "inputs": [ + { + "name": "index", + "ty": { + "Plaintext": { + "Primitive": { + "UInt": "U32" + } + } + }, + "mode": "None" + } + ], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + }, + { + "name": "reset", + "is_final": true, + "const_parameters": [], + "inputs": [], + "outputs": [ + { + "ty": "Final", + "mode": "None" + } + ] + } + ] +} \ No newline at end of file diff --git a/storage/treasury/build/main.aleo b/storage/treasury/build/main.aleo new file mode 100644 index 0000000..c11cf9d --- /dev/null +++ b/storage/treasury/build/main.aleo @@ -0,0 +1,239 @@ +program treasury.aleo; + +struct FeeConfig: + rate as u64; + minimum as u64; + +struct Optional__ATAkdHctJwx: + is_some as boolean; + val as boolean; + +struct Optional__DmN5CQ9hzeK: + is_some as boolean; + val as u64; + +struct Optional__JzunLORyB8U: + is_some as boolean; + val as u32; + +mapping balance__: + key as boolean.public; + value as u64.public; + +mapping is_frozen__: + key as boolean.public; + value as boolean.public; + +mapping deposit_count__: + key as boolean.public; + value as u32.public; + +mapping config__: + key as boolean.public; + value as FeeConfig.public; + +mapping deposit_log__: + key as u32.public; + value as u64.public; + +mapping deposit_log__len__: + key as boolean.public; + value as u32.public; + +function initialize: + async initialize into r0; + output r0 as treasury.aleo/initialize.future; + +finalize initialize: + set 0u64 into balance__[false]; + set false into is_frozen__[false]; + set 0u32 into deposit_count__[false]; + cast 250u64 10u64 into r0 as FeeConfig; + set r0 into config__[false]; + +function deposit: + input r0 as u64.private; + async deposit r0 into r1; + output r1 as treasury.aleo/deposit.future; + +finalize deposit: + input r0 as u64.public; + contains is_frozen__[false] into r1; + get.or_use is_frozen__[false] false into r2; + ternary r1 r2 false into r3; + cast r1 r3 into r4 as Optional__ATAkdHctJwx; + ternary r4.is_some r4.val false into r5; + is.eq r5 false into r6; + assert.eq r6 true; + contains balance__[false] into r7; + get.or_use balance__[false] 0u64 into r8; + ternary r7 r8 0u64 into r9; + cast r7 r9 into r10 as Optional__DmN5CQ9hzeK; + ternary r10.is_some r10.val 0u64 into r11; + add r11 r0 into r12; + set r12 into balance__[false]; + contains deposit_count__[false] into r13; + get.or_use deposit_count__[false] 0u32 into r14; + ternary r13 r14 0u32 into r15; + cast r13 r15 into r16 as Optional__JzunLORyB8U; + ternary r16.is_some r16.val 0u32 into r17; + add r17 1u32 into r18; + set r18 into deposit_count__[false]; + get.or_use deposit_log__len__[false] 0u32 into r19; + add r19 1u32 into r20; + set r20 into deposit_log__len__[false]; + set r0 into deposit_log__[r19]; + +function withdraw: + input r0 as u64.private; + async withdraw r0 into r1; + output r1 as treasury.aleo/withdraw.future; + +finalize withdraw: + input r0 as u64.public; + contains is_frozen__[false] into r1; + get.or_use is_frozen__[false] false into r2; + ternary r1 r2 false into r3; + cast r1 r3 into r4 as Optional__ATAkdHctJwx; + ternary r4.is_some r4.val false into r5; + is.eq r5 false into r6; + assert.eq r6 true; + contains balance__[false] into r7; + get.or_use balance__[false] 0u64 into r8; + ternary r7 r8 0u64 into r9; + cast r7 r9 into r10 as Optional__DmN5CQ9hzeK; + assert.eq r10.is_some true; + gte r10.val r0 into r11; + assert.eq r11 true; + sub r10.val r0 into r12; + set r12 into balance__[false]; + +function freeze: + async freeze into r0; + output r0 as treasury.aleo/freeze.future; + +finalize freeze: + set true into is_frozen__[false]; + +function unfreeze: + async unfreeze into r0; + output r0 as treasury.aleo/unfreeze.future; + +finalize unfreeze: + set false into is_frozen__[false]; + +function update_config: + input r0 as u64.private; + input r1 as u64.private; + async update_config r0 r1 into r2; + output r2 as treasury.aleo/update_config.future; + +finalize update_config: + input r0 as u64.public; + input r1 as u64.public; + cast r0 r1 into r2 as FeeConfig; + set r2 into config__[false]; + +function undo_last_deposit: + async undo_last_deposit into r0; + output r0 as treasury.aleo/undo_last_deposit.future; + +finalize undo_last_deposit: + get.or_use deposit_log__len__[false] 0u32 into r0; + gt r0 0u32 into r1; + sub.w r0 1u32 into r2; + ternary r1 r2 r0 into r3; + set r3 into deposit_log__len__[false]; + get.or_use deposit_log__[r2] 0u64 into r4; + ternary r1 r4 0u64 into r5; + cast r1 r5 into r6 as Optional__DmN5CQ9hzeK; + cast false 0u64 into r7 as Optional__DmN5CQ9hzeK; + is.neq r6 r7 into r8; + assert.eq r8 true; + contains balance__[false] into r9; + get.or_use balance__[false] 0u64 into r10; + ternary r9 r10 0u64 into r11; + cast r9 r11 into r12 as Optional__DmN5CQ9hzeK; + assert.eq r6.is_some true; + ternary r12.is_some r12.val 0u64 into r13; + sub r13 r6.val into r14; + set r14 into balance__[false]; + contains deposit_count__[false] into r15; + get.or_use deposit_count__[false] 0u32 into r16; + ternary r15 r16 0u32 into r17; + cast r15 r17 into r18 as Optional__JzunLORyB8U; + ternary r18.is_some r18.val 0u32 into r19; + sub r19 1u32 into r20; + set r20 into deposit_count__[false]; + +function correct_deposit: + input r0 as u32.private; + input r1 as u64.private; + async correct_deposit r0 r1 into r2; + output r2 as treasury.aleo/correct_deposit.future; + +finalize correct_deposit: + input r0 as u32.public; + input r1 as u64.public; + get.or_use deposit_log__len__[false] 0u32 into r2; + lt r0 r2 into r3; + get.or_use deposit_log__[r0] 0u64 into r4; + ternary r3 r4 0u64 into r5; + cast r3 r5 into r6 as Optional__DmN5CQ9hzeK; + assert.eq r6.is_some true; + get.or_use deposit_log__len__[false] 0u32 into r7; + lt r0 r7 into r8; + assert.eq r8 true; + set r1 into deposit_log__[r0]; + contains balance__[false] into r9; + get.or_use balance__[false] 0u64 into r10; + ternary r9 r10 0u64 into r11; + cast r9 r11 into r12 as Optional__DmN5CQ9hzeK; + ternary r12.is_some r12.val 0u64 into r13; + add r13 r1 into r14; + sub r14 r6.val into r15; + set r15 into balance__[false]; + +function drop_deposit: + input r0 as u32.private; + async drop_deposit r0 into r1; + output r1 as treasury.aleo/drop_deposit.future; + +finalize drop_deposit: + input r0 as u32.public; + get.or_use deposit_log__len__[false] 0u32 into r1; + lt r0 r1 into r2; + assert.eq r2 true; + get deposit_log__[r0] into r3; + sub r1 1u32 into r4; + get deposit_log__[r4] into r5; + set r5 into deposit_log__[r0]; + set r4 into deposit_log__len__[false]; + contains balance__[false] into r6; + get.or_use balance__[false] 0u64 into r7; + ternary r6 r7 0u64 into r8; + cast r6 r8 into r9 as Optional__DmN5CQ9hzeK; + ternary r9.is_some r9.val 0u64 into r10; + sub r10 r3 into r11; + set r11 into balance__[false]; + contains deposit_count__[false] into r12; + get.or_use deposit_count__[false] 0u32 into r13; + ternary r12 r13 0u32 into r14; + cast r12 r14 into r15 as Optional__JzunLORyB8U; + ternary r15.is_some r15.val 0u32 into r16; + sub r16 1u32 into r17; + set r17 into deposit_count__[false]; + +function reset: + async reset into r0; + output r0 as treasury.aleo/reset.future; + +finalize reset: + remove balance__[false]; + remove is_frozen__[false]; + remove deposit_count__[false]; + remove config__[false]; + set 0u32 into deposit_log__len__[false]; + +constructor: + assert.eq edition 0u16; diff --git a/storage/treasury/build/program.json b/storage/treasury/build/program.json new file mode 100644 index 0000000..c92eeba --- /dev/null +++ b/storage/treasury/build/program.json @@ -0,0 +1,9 @@ +{ + "program": "treasury.aleo", + "version": "0.1.0", + "description": "", + "license": "", + "leo": "4.0.2", + "dependencies": null, + "dev_dependencies": null +} diff --git a/storage/treasury/program.json b/storage/treasury/program.json new file mode 100644 index 0000000..c521487 --- /dev/null +++ b/storage/treasury/program.json @@ -0,0 +1,6 @@ +{ + "program": "treasury.aleo", + "version": "0.1.0", + "description": "Treasury demonstrating storage singletons, structs, and vectors.", + "license": "MIT" +} diff --git a/storage/treasury/src/main.leo b/storage/treasury/src/main.leo new file mode 100644 index 0000000..62485d7 --- /dev/null +++ b/storage/treasury/src/main.leo @@ -0,0 +1,166 @@ +// treasury.aleo demonstrates storage singletons, structs, and vectors. +// +// Storage replaces mappings for single-valued on-chain state. Each `storage` +// declaration creates a named slot that holds an optional value — initially +// `none`, settable to a typed value, and clearable back to `none`. +// +// All storage reads and writes happen inside a `return final { ... };` block. +// The `Final` return type tells the AVM these operations touch on-chain state. +// +// Key concepts: +// storage balance: u64; — singleton (one value on-chain) +// storage config: FeeConfig; — struct in storage +// storage deposit_log: [u64]; — vector (dynamically-sized on-chain list) +// balance.unwrap_or(0u64) — read with fallback for uninitialized slots +// balance = none; — clear a storage slot +// deposit_log.push(amount); — append to a vector +// deposit_log.pop(); — remove and return the last element +// deposit_log.get(0u32) — read a vector element (returns optional) +// deposit_log.set(0u32, 100u64); — overwrite an existing element +// deposit_log.swap_remove(0u32); — drop element by swapping with the tail +// deposit_log.len(); — current vector length +// deposit_log.clear(); — reset the vector to empty + +// Structs used in storage are declared at file scope (outside the program block). +struct FeeConfig { + rate: u64, // fee rate in basis points (e.g., 250 = 2.5%) + minimum: u64, // minimum fee amount +} + +program treasury.aleo { + // ── Singleton storage ─────────────────────────────────────────── + // Each declaration creates a single on-chain slot. + // All slots start as `none` and become `Some(value)` once set. + storage balance: u64; + storage is_frozen: bool; + storage deposit_count: u32; + + // ── Struct storage ────────────────────────────────────────────── + // Structs are stored as a single unit, set and read atomically. + storage config: FeeConfig; + + // ── Vector storage ────────────────────────────────────────────── + // Vectors are dynamically-sized on-chain lists. + // Operations: push, pop, get, set, clear, swap_remove, len. + storage deposit_log: [u64]; + + // Sets up the treasury with default configuration. + fn initialize() -> Final { + return final { + balance = 0u64; + is_frozen = false; + deposit_count = 0u32; + config = FeeConfig { rate: 250u64, minimum: 10u64 }; + }; + } + + // Adds `amount` to the treasury balance and logs it in the vector. + fn deposit(amount: u64) -> Final { + return final { + // unwrap_or gracefully handles uninitialized storage. + assert(is_frozen.unwrap_or(false) == false); + + let current: u64 = balance.unwrap_or(0u64); + balance = current + amount; + + deposit_count = deposit_count.unwrap_or(0u32) + 1u32; + + // Vector push — appends to the on-chain list. + deposit_log.push(amount); + }; + } + + // Subtracts `amount` from the treasury balance. + fn withdraw(amount: u64) -> Final { + return final { + assert(is_frozen.unwrap_or(false) == false); + + // unwrap() fails if the slot is none (no balance set yet). + let current: u64 = balance.unwrap(); + assert(current >= amount); + balance = current - amount; + }; + } + + // Freezes the treasury, blocking deposits and withdrawals. + fn freeze() -> Final { + return final { + is_frozen = true; + }; + } + + // Unfreezes the treasury. + fn unfreeze() -> Final { + return final { + is_frozen = false; + }; + } + + // Updates the fee configuration struct. + fn update_config(rate: u64, minimum: u64) -> Final { + return final { + config = FeeConfig { rate: rate, minimum: minimum }; + }; + } + + // Reverses the most recent deposit: pops the log, refunds the balance, + // and decrements the deposit count. Demonstrates `pop`, which both + // shrinks the vector and returns the removed element. + fn undo_last_deposit() -> Final { + return final { + // pop() returns the last element as an optional, or none if empty. + let removed: u64? = deposit_log.pop(); + assert(removed != none); + + // Mirror the pop in the running balance and counter so that + // treasury state stays consistent with the deposit log. + balance = balance.unwrap_or(0u64) - removed.unwrap(); + deposit_count = deposit_count.unwrap_or(0u32) - 1u32; + }; + } + + // Overwrites a logged deposit at a specific index. Demonstrates `set`, + // which replaces an existing element (asserts the index is in range). + // Useful, e.g., when correcting a bookkeeping mistake without reshaping + // the log. + fn correct_deposit(index: u32, amount: u64) -> Final { + return final { + // get() returns the previous value as an optional so we can + // adjust the running balance by the delta. + let previous: u64 = deposit_log.get(index).unwrap(); + deposit_log.set(index, amount); + + let current: u64 = balance.unwrap_or(0u64); + balance = current + amount - previous; + }; + } + + // Drops a logged deposit out of order by swapping with the tail. + // Demonstrates `swap_remove`, which is O(1) but does not preserve order. + fn drop_deposit(index: u32) -> Final { + return final { + // swap_remove() returns the removed element directly (no option), + // and asserts that the index is in range. + let removed: u64 = deposit_log.swap_remove(index); + + balance = balance.unwrap_or(0u64) - removed; + deposit_count = deposit_count.unwrap_or(0u32) - 1u32; + }; + } + + // Resets all storage to `none`, demonstrating storage clearing. + fn reset() -> Final { + return final { + balance = none; + is_frozen = none; + deposit_count = none; + config = none; + + // clear() empties the entire vector. + deposit_log.clear(); + }; + } + + @noupgrade + constructor() {} +} diff --git a/storage/treasury/tests/test_treasury.leo b/storage/treasury/tests/test_treasury.leo new file mode 100644 index 0000000..ba4f94a --- /dev/null +++ b/storage/treasury/tests/test_treasury.leo @@ -0,0 +1,263 @@ +// Unit tests for treasury.aleo. +// +// Each `@test` runs against a fresh deployment, so any state observed in a +// test must be produced inside that same test. Tests drive treasury +// transitions (which mutate storage in their `final` blocks) and then read +// storage back via `treasury.aleo::` access from the test's own +// finalize block. +// +// Run with: leo test +// (See ../README.md for details.) + +import treasury.aleo; + +program test_treasury.aleo { + // ── Defaults: everything reads as `none` before any write ─────────────── + @test + fn unset_storage_reads_as_none() -> Final { + return final { + assert(treasury.aleo::balance.unwrap_or(0u64) == 0u64); + assert(treasury.aleo::is_frozen.unwrap_or(false) == false); + assert(treasury.aleo::deposit_count.unwrap_or(0u32) == 0u32); + assert(treasury.aleo::deposit_log.len() == 0u32); + }; + } + + // ── initialize sets every singleton ───────────────────────────────────── + @test + fn initialize_sets_state() -> Final { + let f: Final = treasury.aleo::initialize(); + return final { + f.run(); + assert(treasury.aleo::balance.unwrap() == 0u64); + assert(treasury.aleo::is_frozen.unwrap() == false); + assert(treasury.aleo::deposit_count.unwrap() == 0u32); + + let cfg: treasury.aleo::FeeConfig = treasury.aleo::config.unwrap(); + assert(cfg.rate == 250u64); + assert(cfg.minimum == 10u64); + }; + } + + // ── deposit accumulates balance, count, and log ───────────────────────── + @test + fn deposit_accumulates_state() -> Final { + let f0: Final = treasury.aleo::initialize(); + let f1: Final = treasury.aleo::deposit(5000u64); + let f2: Final = treasury.aleo::deposit(3000u64); + return final { + f0.run(); + f1.run(); + f2.run(); + assert(treasury.aleo::balance.unwrap() == 8000u64); + assert(treasury.aleo::deposit_count.unwrap() == 2u32); + assert(treasury.aleo::deposit_log.len() == 2u32); + assert(treasury.aleo::deposit_log.get(0u32).unwrap() == 5000u64); + assert(treasury.aleo::deposit_log.get(1u32).unwrap() == 3000u64); + }; + } + + // ── deposit works on a brand-new treasury (unwrap_or fallback) ────────── + @test + fn deposit_without_initialize() -> Final { + let f: Final = treasury.aleo::deposit(100u64); + return final { + f.run(); + assert(treasury.aleo::balance.unwrap() == 100u64); + assert(treasury.aleo::deposit_count.unwrap() == 1u32); + assert(treasury.aleo::deposit_log.len() == 1u32); + }; + } + + // ── withdraw subtracts from balance ───────────────────────────────────── + @test + fn withdraw_reduces_balance() -> Final { + let f0: Final = treasury.aleo::initialize(); + let f1: Final = treasury.aleo::deposit(5000u64); + let f2: Final = treasury.aleo::withdraw(1500u64); + return final { + f0.run(); + f1.run(); + f2.run(); + assert(treasury.aleo::balance.unwrap() == 3500u64); + // Withdraw is independent of the deposit log. + assert(treasury.aleo::deposit_log.len() == 1u32); + }; + } + + // ── overdraw halts ────────────────────────────────────────────────────── + @test + @should_fail + fn overdraw_fails() -> Final { + let f0: Final = treasury.aleo::initialize(); + let f1: Final = treasury.aleo::deposit(100u64); + let f2: Final = treasury.aleo::withdraw(101u64); + return final { + f0.run(); + f1.run(); + f2.run(); + }; + } + + // ── withdraw before any deposit hits unwrap() and halts ───────────────── + @test + @should_fail + fn withdraw_without_balance_fails() -> Final { + // No initialize, no deposit — balance is none, so unwrap() halts. + let f: Final = treasury.aleo::withdraw(1u64); + return final { + f.run(); + }; + } + + // ── freeze blocks deposit ─────────────────────────────────────────────── + @test + @should_fail + fn freeze_blocks_deposit() -> Final { + let f0: Final = treasury.aleo::initialize(); + let f1: Final = treasury.aleo::freeze(); + let f2: Final = treasury.aleo::deposit(100u64); + return final { + f0.run(); + f1.run(); + f2.run(); + }; + } + + // ── freeze blocks withdraw ────────────────────────────────────────────── + @test + @should_fail + fn freeze_blocks_withdraw() -> Final { + let f0: Final = treasury.aleo::initialize(); + let f1: Final = treasury.aleo::deposit(500u64); + let f2: Final = treasury.aleo::freeze(); + let f3: Final = treasury.aleo::withdraw(100u64); + return final { + f0.run(); + f1.run(); + f2.run(); + f3.run(); + }; + } + + // ── unfreeze restores deposits ────────────────────────────────────────── + @test + fn unfreeze_restores_deposits() -> Final { + let f0: Final = treasury.aleo::initialize(); + let f1: Final = treasury.aleo::freeze(); + let f2: Final = treasury.aleo::unfreeze(); + let f3: Final = treasury.aleo::deposit(42u64); + return final { + f0.run(); + f1.run(); + f2.run(); + f3.run(); + assert(treasury.aleo::is_frozen.unwrap() == false); + assert(treasury.aleo::balance.unwrap() == 42u64); + }; + } + + // ── update_config replaces the struct atomically ──────────────────────── + @test + fn update_config_replaces_struct() -> Final { + let f0: Final = treasury.aleo::initialize(); + let f1: Final = treasury.aleo::update_config(500u64, 25u64); + return final { + f0.run(); + f1.run(); + let cfg: treasury.aleo::FeeConfig = treasury.aleo::config.unwrap(); + assert(cfg.rate == 500u64); + assert(cfg.minimum == 25u64); + }; + } + + // ── undo_last_deposit reverses the most recent deposit ────────────────── + @test + fn undo_last_deposit_reverses() -> Final { + let f0: Final = treasury.aleo::deposit(100u64); + let f1: Final = treasury.aleo::deposit(250u64); + let f2: Final = treasury.aleo::undo_last_deposit(); + return final { + f0.run(); + f1.run(); + f2.run(); + assert(treasury.aleo::balance.unwrap() == 100u64); + assert(treasury.aleo::deposit_count.unwrap() == 1u32); + assert(treasury.aleo::deposit_log.len() == 1u32); + assert(treasury.aleo::deposit_log.get(0u32).unwrap() == 100u64); + }; + } + + // ── undo_last_deposit on an empty log halts (pop returns none) ────────── + @test + @should_fail + fn undo_on_empty_log_fails() -> Final { + let f: Final = treasury.aleo::undo_last_deposit(); + return final { + f.run(); + }; + } + + // ── correct_deposit overwrites a logged amount and rebalances ─────────── + @test + fn correct_deposit_overwrites() -> Final { + let f0: Final = treasury.aleo::deposit(100u64); + let f1: Final = treasury.aleo::deposit(200u64); + // Originally logged 200 at index 1; correct it to 250. + let f2: Final = treasury.aleo::correct_deposit(1u32, 250u64); + return final { + f0.run(); + f1.run(); + f2.run(); + assert(treasury.aleo::balance.unwrap() == 350u64); + assert(treasury.aleo::deposit_log.get(1u32).unwrap() == 250u64); + // Log length is unchanged by `set`. + assert(treasury.aleo::deposit_log.len() == 2u32); + }; + } + + // ── drop_deposit removes out of order via swap_remove ─────────────────── + @test + fn drop_deposit_swap_removes() -> Final { + let f0: Final = treasury.aleo::deposit(10u64); + let f1: Final = treasury.aleo::deposit(20u64); + let f2: Final = treasury.aleo::deposit(30u64); + // Drop index 0 (value 10). swap_remove pulls the tail (30) into slot 0. + let f3: Final = treasury.aleo::drop_deposit(0u32); + return final { + f0.run(); + f1.run(); + f2.run(); + f3.run(); + assert(treasury.aleo::balance.unwrap() == 50u64); + assert(treasury.aleo::deposit_count.unwrap() == 2u32); + assert(treasury.aleo::deposit_log.len() == 2u32); + assert(treasury.aleo::deposit_log.get(0u32).unwrap() == 30u64); + assert(treasury.aleo::deposit_log.get(1u32).unwrap() == 20u64); + }; + } + + // ── reset clears every storage slot back to none / empty ──────────────── + @test + fn reset_clears_every_slot() -> Final { + let f0: Final = treasury.aleo::initialize(); + let f1: Final = treasury.aleo::deposit(100u64); + let f2: Final = treasury.aleo::deposit(200u64); + let f3: Final = treasury.aleo::reset(); + return final { + f0.run(); + f1.run(); + f2.run(); + f3.run(); + assert(treasury.aleo::balance.unwrap_or(0u64) == 0u64); + assert(treasury.aleo::is_frozen.unwrap_or(false) == false); + assert(treasury.aleo::deposit_count.unwrap_or(0u32) == 0u32); + assert(treasury.aleo::deposit_log.len() == 0u32); + // The first read after reset returns none. + assert(treasury.aleo::deposit_log.get(0u32).unwrap_or(0u64) == 0u64); + }; + } + + @noupgrade + constructor() {} +}