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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)

Expand All @@ -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)

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
126 changes: 126 additions & 0 deletions contracts/sharpy/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6431,3 +6431,129 @@ 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));
}
}

#[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);
}
}

#[cfg(test)]
mod test_release_proof_c {
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_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);
}
}
Loading