Skip to content

Commit d77a69e

Browse files
authored
Merge branch 'main' into feature/issues-518-519-520-521
2 parents 82a1268 + 7507c8e commit d77a69e

14 files changed

Lines changed: 5920 additions & 7757 deletions

File tree

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ wasm32-unknown-unknown/
1313
.DS_Store
1414
Thumbs.db
1515
desktop.ini
16+
.vscode/
17+
.idea/
18+
*.log
19+
*.tmp
20+
*.bak
21+
test_snapshots/
22+
snapshots/
23+
**/snapshots/
24+
*.swp
25+
*.swo
26+
*~
1627

1728
# Editor/IDE
1829
.vscode/

STORAGE_KEY_REGISTRY.md

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
# Storage Key Registry
2+
3+
This document enumerates every storage key used by the StellarSplit contract, organized by storage tier and key type. Keys are defined in `contracts/split/src/storage_keys.rs` as `#[contracttype]` enums to prevent collisions and ensure XDR uniqueness.
4+
5+
## Storage Tiers
6+
7+
| Tier | Lifecycle | Keys | Typical TTL |
8+
|------|-----------|------|------------|
9+
| **instance** | Live during contract execution; wiped on upgrade unless explicitly preserved. | Singletons (config, counters, addresses) | Contract lifetime |
10+
| **persistent** | Survives contract upgrades; archived after `ARCHIVE_AFTER_LEDGERS` (~100k ledgers) | Per-entity state | 100k ledgers or longer |
11+
| **temporary** | Short-lived ephemeral state (not yet used) | None ||
12+
13+
## Instance Storage: `StorageKey` enum
14+
15+
Keys in this tier are contract-level singletons. Each variant carries no data (unit type).
16+
17+
| Variant | Value | Introduced | TTL | Purpose |
18+
|---------|-------|------------|-----|---------|
19+
| `Admin` | unit | v1 || Primary contract admin address |
20+
| `Admins` | unit | v1 || Map of admin roles → address lists |
21+
| `Paused` | unit | v1 || Global pause flag (bool) |
22+
| `PausedFns` | unit | v1 || Set of paused function names (Set<Symbol>) |
23+
| `Treasury` | unit | v1 || Platform treasury address |
24+
| `UsdcToken` | unit | v1 || USDC token contract address |
25+
| `CreationFee` | unit | v1 || Invoice creation fee (i128) in stroops |
26+
| `PlatformFeeBps` | unit | v1 || Platform fee rate (u32) in basis points |
27+
| `PlatformFeeWaiverList` | unit | v1 || Recipient-level platform fee waiver list (Vec<Address>) |
28+
| `CreatorFeeWaiver` | unit | #296 || Creator-level fee waiver list (Vec<Address>) |
29+
| `Counter` | unit | v1 || Invoice ID counter (u64) |
30+
| `GlobalPayerLimit` | unit | v1 || Global payer velocity limit (i128) |
31+
| `GlobalPayerWindow` | unit | v1 || Global payer velocity window (u32 ledgers) |
32+
| `StreamContract` | unit | #1 || Stellar payment streaming contract address |
33+
| `CreatorWhitelist` | unit | #4 || Creator whitelist enabled flag (bool) |
34+
| `Compliance` | unit | v1 || Compliance check contract address |
35+
| `KycContract` | unit | v1 || KYC verification contract address |
36+
| `RateLimit` | unit | v1 || Rate limit max invoices per window (u32) |
37+
| `RateWindow` | unit | v1 || Rate limit window duration (u32 seconds) |
38+
| `MaxCancelBps` | unit | v1 || Maximum cancellation rate (u32 basis points) |
39+
| `ReceiptFactory` | unit | v1 || Receipt token factory contract address |
40+
| `DashboardContract` | unit | v1 || Dashboard contract address |
41+
| `NftGate` | unit | #192 || NFT gate contract address |
42+
| `TimelockSecs` | unit | #185 || Timelock duration (u32 seconds) |
43+
| `TimelockActionCounter` | unit | #185 || Timelock action ID counter (u64) |
44+
| `FeeTiers` | unit | #285 || Volume-based fee tiers (Vec<FeeTier>) |
45+
| `PendingAdmin` | unit | v1 || Pending admin proposal address |
46+
| `GovernanceContract` | unit | v1 || External governance contract address |
47+
| `Factories` | unit | #145 || Authorized factory addresses (Vec<Address>) |
48+
| `DexContract` | unit | v1 || DEX contract address |
49+
| `TotalInvoices` | unit | #28 || Total invoices created (u64) |
50+
| `TotalVolume` | unit | #28 || Total funded volume (i128) |
51+
| `TotalReleased` | unit | v1 || Total released volume (i128) |
52+
| `TotalRefunded` | unit | v1 || Total refunded volume (i128) |
53+
| `TreasuryGroupCounter` | unit | v1 || Treasury group ID counter (u64) |
54+
| `ContractVersion` | unit | #279 || Current contract version (u32) |
55+
| `ArchiveAfterLedgers` | unit | v1 || Ledger threshold for archival (u64) |
56+
| `CircuitBreaker` | unit | #297 || Circuit breaker active flag (bool) |
57+
| `CircuitBreakerReason` | unit | #297 || Circuit breaker activation reason (String) |
58+
| `PlatformVolThresh` | unit | #276 || Platform volume milestone threshold (i128) |
59+
| `PlatformVolMile` | unit | #276 || Last platform milestone emitted (u32) |
60+
| `CreatorVolThresh` | unit | #276 || Creator volume milestone threshold (i128) |
61+
| `UpgradeProposal` | unit | #310 || Pending upgrade proposal (UpgradeProposal) |
62+
| `ProtocolFee` | unit | #326 || Protocol fee config (ProtocolFeeConfig) |
63+
| `ReentrancyGuard` | unit | v1 || Reentrancy guard flag (bool) |
64+
65+
## Persistent Storage: `InvoiceKey` enum
66+
67+
Keys keyed by invoice ID or sharded. All carry a u64 invoice ID unless noted.
68+
69+
| Variant | Key Shape | Introduced | Type | Purpose |
70+
|---------|-----------|------------|------|---------|
71+
| `Invoice(id)` | `(Symbol, u64)` | v1 | InvoiceCore | Core invoice fields (recipients, amounts, deadlines) |
72+
| `InvoiceExt(id)` | `(Symbol, u64)` | v1 | InvoiceExt | Extended fields (prerequisites, tranches, etc.) |
73+
| `InvoiceExt2(id)` | `(Symbol, u64)` | v1 | InvoiceExt2 | Secondary extended fields (release stages, etc.) |
74+
| `InvoiceCompact(id)` | `(Symbol, u64)` | #334 | Bytes | Compact XDR-encoded invoice data |
75+
| `InvoiceHot(id)` | `(Symbol, u64)` | v1 | InvoiceHot | Hot (frequently-accessed) fields; also in instance storage |
76+
| `AuditLog(id)` | `(Symbol, u64)` | v1 | Vec<AuditEntry> | Audit trail of invoice lifecycle events |
77+
| `PaymentShard(id, shard)` | `(Symbol, u64, u64)` | v1 | Vec<Payment> | Sharded payment list (8 shards per invoice) |
78+
| `ReleaseDelay(id)` | `(Symbol, u64)` | #327 | u64 | Release delay in ledgers |
79+
| `FundedAtLedger(id)` | `(Symbol, u64)` | #327 | u32 | Ledger sequence when fully funded |
80+
| `MetadataHash(id)` | `(Symbol, u64)` | #329 | BytesN<32> | Off-chain metadata hash (SHA-256) |
81+
| `PaidRecipients(id)` | `(Symbol, u64)` | #330 | Set<Address> | Recipients already paid via release_to_recipient |
82+
| `CompactStatus(id)` | `(Symbol, u64)` | #334 | u8 | Compact status byte (0=Pending, 1=Released, 2=Refunded, 3=Cancelled) |
83+
| `CompactDeadlineLedger(id)` | `(Symbol, u64)` | #334 | u32 | Compact deadline as ledger sequence |
84+
| `ConfidentialCount(id)` | `(Symbol, u64)` | #295 | u64 | Count of confidential payments |
85+
| `InvoiceGroup(id)` | `(Symbol, u64)` | v1 | u64 | Reverse lookup: invoice → group ID |
86+
| `InvoiceTreasury(id)` | `(Symbol, u64)` | v1 | TreasuryRecord | Invoice-level treasury allocation |
87+
| `Delegate(id)` | `(Symbol, u64)` | #43 | Address | Primary delegate address for this invoice |
88+
| `PaymentWindow(id)` | `(Symbol, u64)` | #168 | Vec<u64> | Sliding-window payment timestamps for rate limiting |
89+
| `Cert(id)` | `(Symbol, u64)` | v1 | PaymentCertificate | Payment completion certificate |
90+
| `DisputeRecord(id)` | `(Symbol, u64)` | #325 | DisputeRecord | Active dispute record |
91+
| `DisputeRaisedAt(id)` | `(Symbol, u64)` | #325 | u32 | Ledger at which dispute was raised |
92+
| `Refunded(id)` | `(Symbol, u64)` | #308 | Set<Address> | Set of addresses that received refunds |
93+
| `RecipientsList(id)` | `(Symbol, u64)` | #332 | Vec<Address> | Contiguous list of all recipients |
94+
| `AmountsList(id)` | `(Symbol, u64)` | #332 | Vec<i128> | Parallel amounts for each recipient |
95+
| `PaidFlags(id)` | `(Symbol, u64)` | #332 | u32 | Bit-vector of paid flags (up to 32 recipients per word) |
96+
| `MilestoneFlags(id)` | `(Symbol, u64)` | #333 | u8 | Milestone emission bitmask (Bit0=25%, Bit1=50%, etc.) |
97+
| `ArchiveMarker(id)` | `(Symbol, u64)` | v1 | bool | Set when invoice is moved to instance storage |
98+
| `CreatedLedger(id)` | `(Symbol, u64)` | v1 | u32 | Ledger sequence at invoice creation |
99+
| `SubscriptionParams(id)` | `(Symbol, u64)` | v1 | SubscriptionParams | Subscription parameters |
100+
| `SubscriptionSubscribers(id)` | `(Symbol, u64)` | v1 | Vec<Address> | Subscription subscriber list |
101+
| `ExtVote(id)` | `(Symbol, u64)` | v1 | ExternalVote | External governance vote entry |
102+
| `Group(id)` | `(Symbol, u64)` | v1 | InvoiceGroup | Invoice group definition |
103+
| `GroupTreasury(id)` | `(Symbol, u64)` | v1 | TreasuryRecord | Group-level treasury allocation |
104+
| `TimelockAction(id)` | `(Symbol, u64)` | #185 | TimelockAction | Timelock action entry (keyed by action_id) |
105+
106+
## Persistent Storage: `AddressKey` enum
107+
108+
Keys keyed by address (per-creator or per-payer state).
109+
110+
| Variant | Key Shape | Introduced | Type | Purpose |
111+
|---------|-----------|------------|------|---------|
112+
| `Reputation(addr)` | `(Symbol, Address)` | #24 | u64 | Per-payer reputation counter |
113+
| `Credit(addr)` | `(Symbol, Address)` | #38 | i128 | Per-payer credit score |
114+
| `ReferralCount(addr)` | `(Symbol, Address)` | #87 | u64 | Per-referrer referral count |
115+
| `RecipientInvoiceIds(addr)` | `(Symbol, Address)` | #40 | Vec<u64> | Per-recipient invoice ID index |
116+
| `DelegatePay(addr)` | `(Symbol, Address)` | v1 | bool | Delegate-pay authorization flag |
117+
| `RateUsage(addr)` | `(Symbol, Address)` | v1 | u64 | Per-creator rate limit usage within current window |
118+
| `InvoiceCount(addr)` | `(Symbol, Address)` | v1 | u64 | Per-creator invoice creation count |
119+
| `CancelCount(addr)` | `(Symbol, Address)` | v1 | u64 | Per-creator invoice cancellation count |
120+
| `CreatorStatsCount(addr)` | `(Symbol, Address)` | #299 | u64 | Total invoices created by creator |
121+
| `CreatorStatsVolume(addr)` | `(Symbol, Address)` | #299 | i128 | Total funded volume by creator |
122+
| `CreatorStatsReleased(addr)` | `(Symbol, Address)` | #299 | i128 | Total released volume by creator |
123+
| `CreatorStatsRefunded(addr)` | `(Symbol, Address)` | #299 | i128 | Total refunded volume by creator |
124+
| `CreatorStatsPayers(addr)` | `(Symbol, Address)` | #299 | u64 | Unique payers who funded creator's invoices |
125+
| `CreatorStatsAvgFunding(addr)` | `(Symbol, Address)` | #299 | u64 | Average funding time in ledgers |
126+
| `CreatorVolumeCap(addr)` | `(Symbol, Address)` | v1 | i128 | Admin-set volume cap for creator |
127+
| `CreatorVolumeUsed(addr)` | `(Symbol, Address)` | v1 | i128 | Creator volume used against cap |
128+
| `CreatorSelfLimit(addr)` | `(Symbol, Address)` | v1 | i128 | Creator self-imposed daily spending limit |
129+
| `CreatorSelfUsed(addr)` | `(Symbol, Address)` | v1 | i128 | Creator self-limit daily usage |
130+
| `CreatorSelfLimitDay(addr)` | `(Symbol, Address)` | v1 | u64 | Creator self-limit last reset day (Unix timestamp) |
131+
| `CreatorSelfLimitRaise(addr)` | `(Symbol, Address)` | v1 | i128 | Creator pending self-limit raise request amount |
132+
| `PauseExempt(addr)` | `(Symbol, Address)` | v1 | bool | Per-address pause exemption flag |
133+
| `GlobalVelocity(addr)` | `(Symbol, Address)` | v1 | Velocity | Global cross-invoice per-payer velocity state |
134+
| `CreatorVolMile(addr)` | `(Symbol, Address)` | #276 | u32 | Last creator volume milestone emitted |
135+
136+
## Persistent Storage: `CompoundKey` enum
137+
138+
Keys with two or three fields for efficient multi-dimensional lookups.
139+
140+
| Variant | Key Shape | Introduced | Type | Purpose |
141+
|---------|-----------|------------|------|---------|
142+
| `PendingPayout(id, recipient)` | `(Symbol, u64, Address)` | #209 | i128 | Pending payout per (invoice_id, recipient) pair |
143+
| `Channel(id, payer)` | `(Symbol, u64, Address)` | v1 | PaymentChannel | Payment channel state for (invoice, payer) |
144+
| `Nonce(id, payer)` | `(Symbol, u64, Address)` | #21 | u64 | Replay-protection nonce for (invoice, payer) |
145+
| `Velocity(id, payer)` | `(Symbol, u64, Address)` | v1 | Velocity | Per-payer velocity window for (invoice, payer) |
146+
| `ReceiptToken(id, payer)` | `(Symbol, u64, Address)` | v1 | Address | Receipt token contract address for (invoice, payer) |
147+
| `Accumulator(id, payer)` | `(Symbol, u64, Address)` | v1 | i128 | Micro-payment accumulator for (invoice, payer) |
148+
| `Reminder(id, address)` | `(Symbol, u64, Address)` | v1 | Reminder | Reminder entry for (invoice, address) pair |
149+
| `ConfidentialPay(id, payer)` | `(Symbol, u64, Address)` | #295 | ConfidentialPayment | Confidential payment record for (invoice, payer) |
150+
| `Delegation(id, on_behalf_of)` | `(Symbol, u64, Address)` | #315 | Address | Single-use delegation: (invoice, on_behalf_of) → delegate |
151+
| `PayerCooldown(id, payer)` | `(Symbol, u64, Address)` | #168 | u64 | Last payment ledger for (invoice, payer) cooldown |
152+
| `CreatorPayerSet(creator, payer)` | `(Symbol, Address, Address)` | #299 | bool | Unique-payer tracking flag for (creator, payer) |
153+
| `Template(creator, name)` | `(Symbol, Address, Symbol)` | v1 | InvoiceTemplate | Invoice template for (creator, name) |
154+
| `TemplateVersion(creator, name, version)` | `(Symbol, Address, Symbol, u32)` | #210 | InvoiceTemplate | Versioned template for (creator, name, version) |
155+
| `TemplateVersionCount(creator, name)` | `(Symbol, Address, Symbol)` | #210 | u32 | Template version counter for (creator, name) |
156+
157+
## Migration Guide
158+
159+
When renaming a storage key between contract versions, use the migration helpers in `storage_keys.rs`:
160+
161+
```rust
162+
// Migrate persistent storage
163+
migrate_persistent::<OldKey, NewKey, ValueType>(env, &old_key, &new_key);
164+
165+
// Migrate instance storage
166+
migrate_instance::<OldKey, NewKey, ValueType>(env, &old_key, &new_key);
167+
```
168+
169+
Example: migrating from old `admin_key()` to new `StorageKey::Admin`:
170+
171+
```rust
172+
pub fn upgrade(env: Env) {
173+
let old_admin_key = Symbol::new(&env, "admin"); // or symbol_short!("admin")
174+
migrate_instance::<Symbol, StorageKey, Address>(
175+
&env,
176+
&old_admin_key,
177+
&StorageKey::Admin,
178+
);
179+
}
180+
```
181+
182+
## Uniqueness Validation
183+
184+
Unit tests in `storage_keys.rs` verify that:
185+
1. No two `StorageKey` variants serialize to the same XDR value
186+
2. No two `InvoiceKey` variants serialize to the same XDR value
187+
3. No two `AddressKey` variants serialize to the same XDR value
188+
4. No two `CompoundKey` variants serialize to the same XDR value
189+
5. Different invoice IDs and parameters produce different keys
190+
191+
Run tests with:
192+
```sh
193+
cargo test --lib storage_keys
194+
```

contracts/invoice-escrow/src/errors.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,14 @@ pub enum Error {
3636
DeadlineNotPassed = 13,
3737
/// Recipient and amount vectors must be the same length and non-empty.
3838
InvalidRecipients = 14,
39+
/// Payer is blacklisted and cannot interact with invoices.
40+
PayerBlacklisted = 15,
41+
/// The blacklist appeal window has expired.
42+
AppealWindowExpired = 16,
43+
/// The payer is not blacklisted; cannot submit appeal or finalise.
44+
NotBlacklisted = 17,
45+
/// The blacklist entry has already been finalised.
46+
AlreadyFinalised = 18,
47+
/// Caller is not the blacklisted payer.
48+
NotBlacklistedPayer = 19,
3949
}

0 commit comments

Comments
 (0)