From 22197713ae913bb58aacd5b3bffe12689f56e5eb Mon Sep 17 00:00:00 2001 From: MarcusDavidG Date: Thu, 10 Sep 2026 12:17:03 +0100 Subject: [PATCH 1/7] docs(arch): sync module map count and view taxonomy --- ARCHITECTURE.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 0dc433c..278d941 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -136,6 +136,9 @@ All events use single-element topic `symbol_short!`. | `wrem` | `WhitelistPayerRemovedEvent{invoice_id, payer}` | `remove_whitelisted_payer` | | `fee` | `FeeConfiguredEvent{fee_bps, collector}` | `set_protocol_fee` | | `fprev` | `FeePreviewedEvent{amount, fee}` | `preview_fee` | +| `paged` | _(no event; views)_ `get_creator_invoices_paged`/`get_payer_invoices_paged` + `*_total` | paged reads | +| `ttl` | _(no event; views)_ `get_ttl_hint`/`is_invoice_expired`/`is_invoice_terminal` | ttl reads | +| `audit` | _(no event; views)_ `get_audit_count`/`get_funding_remaining`/`get_fee_bps` | invariant reads | ## Checked Arithmetic (CAP-82) @@ -152,7 +155,7 @@ All payout math uses `checked_mul`/`checked_div`/`checked_add`/`checked_sub` to - `contracts/sharpy/src/lib.rs` — contract impl, storage helpers, `SharpyContract` - `contracts/sharpy/src/events.rs` — typed event helpers - `contracts/sharpy/src/types.rs` — `Invoice`, `SplitRule`, `DisputeState`, etc. -- `contracts/sharpy/src/test.rs` — 184 unit/integration tests +- `contracts/sharpy/src/test.rs` — 280+ unit/integration tests (stream/route/tranche/whitelist/fee/pagination/ttl/invariant suites) ## Auth Matrix (audit harness, closes #199) From f86ae3cc0eea783552be9abc37bf5491e0facd2d Mon Sep 17 00:00:00 2001 From: MarcusDavidG Date: Thu, 10 Sep 2026 12:17:03 +0100 Subject: [PATCH 2/7] docs(arch): note pure views carry no TTL writes --- ARCHITECTURE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 278d941..b89c7ee 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -37,7 +37,7 @@ instance storage (no TTL); every per-invoice entry plus the counters lives in pe | `("wlist", id)` | `WhitelistState` | persistent | Payer allowlist enforced in `pay` via `set/get/add/remove_whitelisted_payer` | | `fee` | `FeeConfig` | instance | Protocol fee bps + collector via `set/get_protocol_fee`/`preview_fee` | -TTL extension: `save_invoice`, creator/payer index writes, `credit_account`, `bump_invoice_ttl`, `set_invoice_notes`, +TTL extension: `save_invoice`, creator/payer index writes, `credit_account`, `bump_invoice_ttl`, `get_funding_remaining` reads need no TTL, `set_invoice_notes`, `set_invoice_tags`, `set_invoice_memo_ext`, `set_invoice_metadata` and `set_discount` call `extend_ttl(100_000, 6_307_200)` — bump to ~1 year if TTL < 100k ledgers (~6 days, CAP-78). Instance singletons (`admin`, `treasury`, `fee`) carry no TTL; escrow, recurring, pause, approval, archival, From 5de47639ce8aeada5bdc28ec82b904924c409316 Mon Sep 17 00:00:00 2001 From: MarcusDavidG Date: Thu, 10 Sep 2026 12:17:44 +0100 Subject: [PATCH 3/7] test(contract): add storage-key and view release proof --- contracts/sharpy/src/test.rs | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/contracts/sharpy/src/test.rs b/contracts/sharpy/src/test.rs index 5562c9a..88eb273 100644 --- a/contracts/sharpy/src/test.rs +++ b/contracts/sharpy/src/test.rs @@ -6431,3 +6431,49 @@ mod test_invariant_c { assert!(client.is_invoice_terminal(&id)); } } + +#[cfg(test)] +mod test_release_proof_a { + use soroban_sdk::{testutils::Address as _, Address, Env, Vec}; + use crate::SharpyContractClient; + fn setup() -> (Env, SharpyContractClient<'static>) { + let env = Env::default(); + env.mock_all_auths(); + let cid = env.register(crate::SharpyContract, ()); + let c = SharpyContractClient::new(&env, &cid); + let a = Address::generate(&env); + let t = Address::generate(&env); + c.initialize(&a, &t); + (env, c) + } + #[test] + fn test_storage_keys_present_via_getters() { + let (env, client) = setup(); + let creator = Address::generate(&env); + let r = Address::generate(&env); + let tok = Address::generate(&env); + let dl = env.ledger().timestamp() + 86400; + let opts = crate::types::InvoiceOptions { escrow_enabled: false, escrow_release_delay: None, split_rules: Vec::new(&env), auto_resolve_rules: Vec::new(&env), arbitrator: None }; + let id = client.create_invoice(&creator, &Vec::from_array(&env, [r]), &Vec::from_array(&env, [100i128]), &Vec::from_array(&env, [tok]), &dl, &opts); + assert!(client.get_invoice(&id).version == 1u32); + assert_eq!(client.get_invoice_count(), 1u64); + assert_eq!(client.get_invoice_version(&id), 1u32); + assert_eq!(client.get_contract_version(), 1u32); + assert_eq!(client.get_creator_invoice_total(&creator), 1u32); + } + #[test] + fn test_new_views_callable_release_proof() { + let (env, client) = setup(); + let creator = Address::generate(&env); + let r = Address::generate(&env); + let tok = Address::generate(&env); + let dl = env.ledger().timestamp() + 86400; + let opts = crate::types::InvoiceOptions { escrow_enabled: false, escrow_release_delay: None, split_rules: Vec::new(&env), auto_resolve_rules: Vec::new(&env), arbitrator: None }; + let id = client.create_invoice(&creator, &Vec::from_array(&env, [r]), &Vec::from_array(&env, [100i128]), &Vec::from_array(&env, [tok]), &dl, &opts); + assert_eq!(client.get_fee_bps(), 0u32); + assert_eq!(client.get_funding_remaining(&id), 100i128); + assert_eq!(client.get_tranche_remaining_bps(&id), 10_000u32); + assert_eq!(client.get_audit_count(&id), 0u32); + assert!(!client.is_invoice_terminal(&id)); + } +} From 195ff46bc76ff20061721e3db8681e072cf2ba35 Mon Sep 17 00:00:00 2001 From: MarcusDavidG Date: Thu, 10 Sep 2026 12:17:45 +0100 Subject: [PATCH 4/7] test(contract): add event taxonomy and stream proof tests --- contracts/sharpy/src/test.rs | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/contracts/sharpy/src/test.rs b/contracts/sharpy/src/test.rs index 88eb273..a32a78c 100644 --- a/contracts/sharpy/src/test.rs +++ b/contracts/sharpy/src/test.rs @@ -6477,3 +6477,45 @@ mod test_release_proof_a { assert!(!client.is_invoice_terminal(&id)); } } + +#[cfg(test)] +mod test_release_proof_b { + use soroban_sdk::{testutils::Address as _, Address, Env, Vec}; + use crate::SharpyContractClient; + fn setup() -> (Env, SharpyContractClient<'static>) { + let env = Env::default(); + env.mock_all_auths(); + let cid = env.register(crate::SharpyContract, ()); + let c = SharpyContractClient::new(&env, &cid); + let a = Address::generate(&env); + let t = Address::generate(&env); + c.initialize(&a, &t); + (env, c) + } + #[test] + fn test_event_taxonomy_views_resolve() { + let (env, client) = setup(); + let creator = Address::generate(&env); + let r = Address::generate(&env); + let tok = Address::generate(&env); + let dl = env.ledger().timestamp() + 86400; + let opts = crate::types::InvoiceOptions { escrow_enabled: false, escrow_release_delay: None, split_rules: Vec::new(&env), auto_resolve_rules: Vec::new(&env), arbitrator: None }; + let a = client.create_invoice(&creator, &Vec::from_array(&env, [r]), &Vec::from_array(&env, [100i128]), &Vec::from_array(&env, [tok]), &dl, &opts); + let b = client.create_invoice(&creator, &Vec::from_array(&env, [Address::generate(&env)]), &Vec::from_array(&env, [100i128]), &Vec::from_array(&env, [Address::generate(&env)]), &dl, &opts); + client.set_route(&creator, &a, &b); + assert_eq!(client.resolve_route(&a), b); + assert_eq!(client.resolve_route_chain(&a, &5u32), b); + assert_eq!(client.get_route_chain_len(&a), 1u32); + let payer = Address::generate(&env); + assert!(client.is_whitelisted_payer(&a, &payer)); + } + #[test] + fn test_stream_views_resolve() { + let (env, client) = setup(); + let r = Address::generate(&env); + let start = env.ledger().timestamp(); + client.create_stream(&91091u64, &r, &500i128, &start, &(start + 500), &start); + assert!(client.get_stream_state(&91091u64).is_some()); + assert_eq!(client.preview_vested(&91091u64), 0i128); + } +} From 05e0ce42939067b6011bf4a2c26e87950bf5d824 Mon Sep 17 00:00:00 2001 From: MarcusDavidG Date: Thu, 10 Sep 2026 12:18:07 +0100 Subject: [PATCH 5/7] test(contract): add treasury fee and count proof tests --- contracts/sharpy/src/test.rs | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/contracts/sharpy/src/test.rs b/contracts/sharpy/src/test.rs index a32a78c..defb4f7 100644 --- a/contracts/sharpy/src/test.rs +++ b/contracts/sharpy/src/test.rs @@ -6519,3 +6519,41 @@ mod test_release_proof_b { assert_eq!(client.preview_vested(&91091u64), 0i128); } } + +#[cfg(test)] +mod test_release_proof_c { + use soroban_sdk::{testutils::Address as _, Address, Env}; + use crate::SharpyContractClient; + fn setup() -> (Env, SharpyContractClient<'static>) { + let env = Env::default(); + env.mock_all_auths(); + let cid = env.register(crate::SharpyContract, ()); + let c = SharpyContractClient::new(&env, &cid); + let a = Address::generate(&env); + let t = Address::generate(&env); + c.initialize(&a, &t); + (env, c) + } + #[test] + fn test_treasury_and_fee_config_proof() { + let (env, client) = setup(); + let treasury = client.get_treasury(); + let _ = treasury; + assert!(client.get_protocol_fee().is_none()); + let collector = Address::generate(&env); + client.set_protocol_fee(&100u32, &collector); + assert_eq!(client.preview_fee(&1000i128), 10i128); + } + #[test] + fn test_invoice_count_increments() { + let (env, client) = setup(); + let c0 = client.get_invoice_count(); + let creator = Address::generate(&env); + let r = Address::generate(&env); + let tok = Address::generate(&env); + let dl = env.ledger().timestamp() + 86400; + let opts = crate::types::InvoiceOptions { escrow_enabled: false, escrow_release_delay: None, split_rules: Vec::new(&env), auto_resolve_rules: Vec::new(&env), arbitrator: None }; + let _ = client.create_invoice(&creator, &Vec::from_array(&env, [r]), &Vec::from_array(&env, [10i128]), &Vec::from_array(&env, [tok]), &dl, &opts); + assert_eq!(client.get_invoice_count(), c0 + 1); + } +} From 70c1d438335f7fa8eb68e8778345e5e19680f951 Mon Sep 17 00:00:00 2001 From: MarcusDavidG Date: Thu, 10 Sep 2026 12:18:45 +0100 Subject: [PATCH 6/7] test(contract): fix Vec import in release proof --- contracts/sharpy/src/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/sharpy/src/test.rs b/contracts/sharpy/src/test.rs index defb4f7..b896d9a 100644 --- a/contracts/sharpy/src/test.rs +++ b/contracts/sharpy/src/test.rs @@ -6522,7 +6522,7 @@ mod test_release_proof_b { #[cfg(test)] mod test_release_proof_c { - use soroban_sdk::{testutils::Address as _, Address, Env}; + use soroban_sdk::{testutils::Address as _, Address, Env, Vec}; use crate::SharpyContractClient; fn setup() -> (Env, SharpyContractClient<'static>) { let env = Env::default(); From 0f937d2ead16620c95a86844678fb6241562d738 Mon Sep 17 00:00:00 2001 From: MarcusDavidG Date: Thu, 10 Sep 2026 12:18:45 +0100 Subject: [PATCH 7/7] docs(changelog): record arch sync proof for #200 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c32796e..4ff93a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to the Sharpy smart contract are documented here. ## [Unreleased] +- docs(arch/changelog): sync storage/event taxonomy + module-map counts; storage-key and view release-notes proof — feat/release-sync (closes #200) - feat(contract): `get_funding_remaining(id)` invariant view + auth-matrix/CEI docs; double-release/refund/claim + remaining guards — feat/audit-harness (closes #199) - feat(contract): `get_audit_count(id)` + whitelist audit entries (`wlist`/`wrem`) + `inv_upd` ordering docs; per-path emission proof — feat/invupd-coverage (closes #198) - feat(contract): `is_invoice_terminal(id)` view + ttl-hint terminal/expired-mirror coverage (countdown, Released/Refunded/Cancelled zeros, extend revives) — feat/ttl-terminal (closes #197)