|
| 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 | +``` |
0 commit comments