From 3112a1e77b34456bf22401a02aab02a65d6e22e9 Mon Sep 17 00:00:00 2001 From: Favourejiro Date: Tue, 28 Jul 2026 23:53:46 +0000 Subject: [PATCH] feat: event schema versioning, creator invoice cap, recipient validation (#502 #503 #505) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #502 — Soroban Event Schema Versioning - Add pub const *_V: u32 = 1 version constants for every event type in events.rs - Include migration guide comment block describing version history policy - Add invoice_limit_updated and recipient_account_missing event functions with version constant as first topic element Issue #503 — Invoice Count Per Creator Cap - Add CreatorInvoiceLimitReached = 36 to ContractError - Add open_invoice_count_key (persistent) and max_open_invoices_key (instance) to storage_keys.rs and lib.rs inline helpers - Enforce cap in _create_invoice_inner: panics with CreatorInvoiceLimitReached when open_count >= max (default 100 via DEFAULT_MAX_OPEN_INVOICES) - Decrement counter in _release_full (when no failed payouts) and cancel_invoice - Add set_max_open_invoices(env, admin, new_limit) admin function that stores the cap and emits InvoiceLimitUpdated(new_limit) Issue #505 — Recipient Account Existence Validation - Add RecipientAccountMissing = 37 to ContractError - Add failed_payouts_key (persistent Vec
) to storage_keys.rs and lib.rs - In _release_full payout loop: call try_invoke_contract(balance) before each transfer; on failure store recipient in failed_payouts_key and emit RecipientAccountMissing, skip the transfer, continue - Gate invoice status -> Released and counter decrement on failed_payouts empty - Add retry_failed_payout(env, invoice_id, recipient): re-validates existence, executes transfer, removes from failed list, finalises when list empties Storage snapshot - Register open_invoice_count_key, max_open_invoices_key, failed_payouts_key in storage_snapshot.rs - Add corresponding XDR entries to tests/snapshots/storage_keys.json Closes #502 Closes #503 Closes #505 --- contracts/split/src/error.rs | 4 + contracts/split/src/events.rs | 144 +++++++++++++++ contracts/split/src/lib.rs | 227 +++++++++++++++++++++++- contracts/split/src/storage_keys.rs | 47 +++++ contracts/split/src/storage_snapshot.rs | 7 + tests/snapshots/storage_keys.json | 3 + 6 files changed, 424 insertions(+), 8 deletions(-) diff --git a/contracts/split/src/error.rs b/contracts/split/src/error.rs index 9c540c2..d01de00 100644 --- a/contracts/split/src/error.rs +++ b/contracts/split/src/error.rs @@ -76,5 +76,9 @@ pub enum ContractError { CreatorCooldownActive = 34, /// Issue #473: Token is not in the allowed tokens list. UnauthorisedToken = 35, + /// Issue #503: Creator has reached the maximum number of open invoices. + CreatorInvoiceLimitReached = 36, + /// Issue #505: Payout recipient account does not exist on the ledger. + RecipientAccountMissing = 37, } diff --git a/contracts/split/src/events.rs b/contracts/split/src/events.rs index f149f58..c44d67d 100644 --- a/contracts/split/src/events.rs +++ b/contracts/split/src/events.rs @@ -1,6 +1,121 @@ use crate::types::{DisputeOutcome, InvoiceStatus, RepScore, TimelockAction}; use soroban_sdk::{contracttype, symbol_short, Address, BytesN, Env, String, Vec}; +// --------------------------------------------------------------------------- +// Issue #502: Event schema versioning +// +// Every event type carries a `u32` version constant as the FIRST topic element +// so that off-chain consumers can detect schema changes without parsing data. +// +// Version history: +// v1 (initial) — all events below. Bump the constant and add a note here +// whenever the topic list or data payload shape changes. +// +// Usage in publish calls: +// (VERSION_CONST, symbol_short!("split"), symbol_short!("tag"), id) +// --------------------------------------------------------------------------- + +pub const INVOICE_CREATED_V: u32 = 1; +pub const PAYMENT_RECEIVED_V: u32 = 1; +pub const PAYMENT_COMMITTED_V: u32 = 1; +pub const MILESTONE_RELEASED_V: u32 = 1; +pub const SURPLUS_CLAIMED_V: u32 = 1; +pub const INVOICE_RELEASED_V: u32 = 1; +pub const INVOICE_REFUNDED_V: u32 = 1; +pub const CONDITION_VERIFIED_V: u32 = 1; +pub const INVOICE_EXPIRED_V: u32 = 1; +pub const RECIPIENT_WHITELISTED_V: u32 = 1; +pub const RECIPIENT_REMOVED_WL_V: u32 = 1; +pub const REBATE_ACCRUED_V: u32 = 1; +pub const PAYER_REFUNDED_V: u32 = 1; +pub const RECIPIENT_ADDED_V: u32 = 1; +pub const SPLIT_ADJUSTED_V: u32 = 1; +pub const RECIPIENTS_REBALANCED_V: u32 = 1; +pub const INVOICE_ARCHIVED_V: u32 = 1; +pub const DELEGATE_SET_V: u32 = 1; +pub const DELEGATE_REVOKED_V: u32 = 1; +pub const INVOICE_PART_RELEASED_V: u32 = 1; +pub const PAYMENT_REMINDER_V: u32 = 1; +pub const PAYMENT_MATCHED_V: u32 = 1; +pub const INVOICE_CLONED_V: u32 = 1; +pub const INVOICE_PAUSED_V: u32 = 1; +pub const INVOICE_RESUMED_V: u32 = 1; +pub const INVOICE_FORCE_RESUMED_V: u32 = 1; +pub const PENDING_PAYOUT_CLAIMED_V: u32 = 1; +pub const INVOICE_RENEWED_V: u32 = 1; +pub const INVOICE_RATED_V: u32 = 1; +pub const RATE_LIMIT_HIT_V: u32 = 1; +pub const NFT_GATE_SET_V: u32 = 1; +pub const ACTION_QUEUED_V: u32 = 1; +pub const ACTION_EXECUTED_V: u32 = 1; +pub const ACTION_CANCELLED_V: u32 = 1; +pub const INVOICE_ADMIN_FROZEN_V: u32 = 1; +pub const INVOICE_ADMIN_UNFROZEN_V: u32 = 1; +pub const BATCH_ARCHIVED_V: u32 = 1; +pub const PARTIAL_REFUND_ISSUED_V: u32 = 1; +pub const PLATFORM_VOL_MILESTONE_V: u32 = 1; +pub const CREATOR_VOL_MILESTONE_V: u32 = 1; +pub const CIRCUIT_BREAKER_ACTIVATED_V: u32 = 1; +pub const CIRCUIT_BREAKER_DEACTIVATED_V: u32 = 1; +pub const FEE_WAIVER_GRANTED_V: u32 = 1; +pub const FEE_WAIVER_REVOKED_V: u32 = 1; +pub const FEE_TIERS_UPDATED_V: u32 = 1; +pub const FEE_TIER_APPLIED_V: u32 = 1; +pub const CREATOR_STATS_UPDATED_V: u32 = 1; +pub const INVOICE_STATE_CHANGED_V: u32 = 1; +pub const ALLOWLIST_UPDATED_V: u32 = 1; +pub const REFUND_CLAIMED_V: u32 = 1; +pub const UPGRADE_PROPOSED_V: u32 = 1; +pub const UPGRADE_EXECUTED_V: u32 = 1; +pub const UPGRADE_CANCELLED_V: u32 = 1; +pub const CONTRACT_PAUSED_V: u32 = 1; +pub const CONTRACT_UNPAUSED_V: u32 = 1; +pub const FUNDS_UNLOCKED_V: u32 = 1; +pub const METADATA_UPDATED_V: u32 = 1; +pub const RECIPIENT_PAID_V: u32 = 1; +pub const MILESTONE_REACHED_V: u32 = 1; +pub const FUNDING_CHECKPOINT_V: u32 = 1; +pub const DELEGATED_PAYMENT_V: u32 = 1; +pub const DISPUTE_RAISED_V: u32 = 1; +pub const DISPUTE_RESOLVED_V: u32 = 1; +pub const DISPUTE_EXPIRED_V: u32 = 1; +pub const FEE_PAID_V: u32 = 1; +pub const ORACLE_PRICE_FETCHED_V: u32 = 1; +pub const TRANCHE_RELEASED_V: u32 = 1; +pub const REP_UPDATED_V: u32 = 1; +pub const PAYOUT_FAILED_V: u32 = 1; +pub const INSTALMENT_TRANCHE_PAID_V: u32 = 1; +pub const ESCROW_HOLD_STARTED_V: u32 = 1; +pub const ESCROW_RESOLVED_V: u32 = 1; +pub const DELAYED_PAYOUT_SCHEDULED_V: u32 = 1; +pub const DELAYED_PAYOUT_CLAIMED_V: u32 = 1; +pub const CONTRACT_FROZEN_V: u32 = 1; +pub const CONTRACT_THAWED_V: u32 = 1; +pub const DUPLICATE_PAYMENT_REJECTED_V: u32 = 1; +pub const REFERRER_REWARDED_V: u32 = 1; +pub const GROUP_MEMBER_EXPIRED_V: u32 = 1; +pub const GROUP_ROLLBACK_TRIGGERED_V: u32 = 1; +pub const EARLY_BIRD_PAYMENT_V: u32 = 1; +pub const CREATOR_COOLDOWN_SET_V: u32 = 1; +pub const FUNDS_SWEPT_V: u32 = 1; +pub const TRUSTED_CALLER_ADDED_V: u32 = 1; +pub const TRUSTED_CALLER_REMOVED_V: u32 = 1; +pub const ROLE_GRANTED_V: u32 = 1; +pub const ROLE_REVOKED_V: u32 = 1; +pub const INVOICE_CANCELLED_V: u32 = 1; +pub const ADMIN_ACTION_PROPOSED_V: u32 = 1; +pub const ADMIN_ACTION_APPROVED_V: u32 = 1; +pub const ADMIN_ACTION_EXECUTED_V: u32 = 1; +pub const TEMPLATE_CREATED_V: u32 = 1; +pub const TEMPLATE_DELETED_V: u32 = 1; +pub const INVOICE_FROM_TEMPLATE_V: u32 = 1; +pub const REFUND_ISSUED_V: u32 = 1; +pub const RECIPIENT_ADDRESS_ROTATED_V: u32 = 1; +/// Issue #503: emitted when admin updates the per-creator open-invoice cap. v1 initial. +pub const INVOICE_LIMIT_UPDATED_V: u32 = 1; +/// Issue #505: emitted when a payout recipient account is missing on-ledger. v1 initial. +pub const RECIPIENT_ACCOUNT_MISSING_V: u32 = 1; + /// Emitted when a new invoice is created. /// Topics: (split, created, invoice_id) /// Data: (creator, total) @@ -1294,3 +1409,32 @@ pub fn invoice_from_template(env: &Env, invoice_id: u64, creator: &Address, temp ); } + +/// Issue #503: Emitted when admin updates the per-creator open-invoice cap. +/// Topics: (INVOICE_LIMIT_UPDATED_V, split, inv_lim) +/// Data: new_limit +pub fn invoice_limit_updated(env: &Env, new_limit: u32) { + env.events().publish( + ( + INVOICE_LIMIT_UPDATED_V, + symbol_short!("split"), + symbol_short!("inv_lim"), + ), + new_limit, + ); +} + +/// Issue #505: Emitted when a payout recipient account does not exist on-ledger. +/// Topics: (RECIPIENT_ACCOUNT_MISSING_V, split, rcp_mis, invoice_id) +/// Data: recipient +pub fn recipient_account_missing(env: &Env, invoice_id: u64, recipient: &Address) { + env.events().publish( + ( + RECIPIENT_ACCOUNT_MISSING_V, + symbol_short!("split"), + symbol_short!("rcp_mis"), + invoice_id, + ), + recipient.clone(), + ); +} diff --git a/contracts/split/src/lib.rs b/contracts/split/src/lib.rs index 3a9c2b1..14a0020 100644 --- a/contracts/split/src/lib.rs +++ b/contracts/split/src/lib.rs @@ -828,6 +828,21 @@ fn contributor_allowlist_key(invoice_id: u64) -> (Symbol, u64) { (symbol_short!("ctr_al"), invoice_id) } +/// Issue #503: number of currently-open invoices per creator — persistent storage. +fn open_invoice_count_key(creator: &Address) -> (Symbol, Address) { + (symbol_short!("op_inv_cn"), creator.clone()) +} + +/// Issue #503: admin-configured maximum open invoices per creator — instance storage. +fn max_open_invoices_key() -> Symbol { + symbol_short!("mx_op_inv") +} + +/// Issue #505: list of recipients whose payout failed (missing account) — persistent storage. +fn failed_payouts_key(invoice_id: u64) -> (Symbol, u64) { + (symbol_short!("fail_pay"), invoice_id) +} + fn commitment_expiry_key() -> Symbol { symbol_short!("com_exp") } @@ -2721,6 +2736,17 @@ impl SplitContract { env.storage().instance().set(&storage_quota_key(), &bytes); } + /// Issue #503: Set the maximum number of open invoices a single creator may hold at once. + /// Admin-only. Emits InvoiceLimitUpdated(new_limit). + pub fn set_max_open_invoices(env: Env, admin: Address, new_limit: u32) { + require_role(&env, &admin, AdminRole::Operator); + assert!(new_limit > 0, "new_limit must be positive"); + env.storage() + .instance() + .set(&max_open_invoices_key(), &new_limit); + events::invoice_limit_updated(&env, new_limit); + } + /// Returns the current global per-invoice storage quota in bytes (issue #425). pub fn get_storage_quota(env: Env) -> u64 { env.storage() @@ -4631,6 +4657,25 @@ impl SplitContract { .persistent() .set(&invoice_count_key(&creator), &(inv_cnt + 1)); + // Issue #503: enforce per-creator open-invoice cap. + const DEFAULT_MAX_OPEN_INVOICES: u32 = 100; + let max_open: u32 = env + .storage() + .instance() + .get(&max_open_invoices_key()) + .unwrap_or(DEFAULT_MAX_OPEN_INVOICES); + let open_count: u32 = env + .storage() + .persistent() + .get(&open_invoice_count_key(&creator)) + .unwrap_or(0u32); + if open_count >= max_open { + env.panic_with_error(ContractError::CreatorInvoiceLimitReached); + } + env.storage() + .persistent() + .set(&open_invoice_count_key(&creator), &(open_count + 1)); + let total: i128 = amounts.iter().sum(); let gov_opt: Option> = @@ -7861,6 +7906,109 @@ impl SplitContract { /// Claim a pending payout that was not transferred during release (issue #209). /// Recipient can claim their payout after the invoice is Released. + /// Issue #505: Retry a payout to a recipient whose account was missing at release time. + /// + /// Re-validates that the recipient account now exists on the ledger. If it does, executes + /// the transfer and removes them from FailedPayouts. If FailedPayouts is now empty, the + /// invoice is finalised (status → Released) and the open-invoice counter is decremented. + /// If the account is still missing, emits RecipientAccountMissing again and returns an error. + pub fn retry_failed_payout(env: Env, invoice_id: u64, recipient: Address) { + require_not_paused(&env); + let mut invoice = load_invoice(&env, invoice_id); + + // Load and validate the failed-payouts list. + let mut failed: Vec
= env + .storage() + .persistent() + .get(&failed_payouts_key(invoice_id)) + .expect("no failed payouts for this invoice"); + + assert!( + failed.iter().any(|a| a == recipient), + "recipient not in failed payouts" + ); + + let funding_token_client = token::Client::new(&env, &funding_token_for(&invoice)); + + // Re-check account existence. + let account_exists = env + .try_invoke_contract::( + &funding_token_client.address, + &symbol_short!("balance"), + (recipient.clone(),).into_val(&env), + ) + .is_ok(); + + if !account_exists { + events::recipient_account_missing(&env, invoice_id, &recipient); + env.panic_with_error(ContractError::RecipientAccountMissing); + } + + // Find recipient index and compute their payout amount. + let n = invoice.recipients.len(); + let total: i128 = invoice.amounts.iter().sum(); + let funded = invoice.funded; + let idx = invoice + .recipients + .iter() + .position(|r| r == recipient.clone()) + .expect("recipient not in invoice") as u32; + let amount = invoice.amounts.get(idx).unwrap(); + let payout = if n == 1 { + funded + } else { + checked_proportion(amount as u128, funded as u128, total as u128) + .expect("ArithmeticOverflow") + }; + + // Execute transfer. + funding_token_client.transfer( + &env.current_contract_address(), + &recipient, + &payout, + ); + + // Remove from failed list. + let mut new_failed: Vec
= Vec::new(&env); + for a in failed.iter() { + if a != recipient { + new_failed.push_back(a); + } + } + if new_failed.is_empty() { + env.storage().persistent().remove(&failed_payouts_key(invoice_id)); + } else { + env.storage() + .persistent() + .set(&failed_payouts_key(invoice_id), &new_failed); + } + + // If all payouts are now complete, finalise the invoice. + if new_failed.is_empty() { + invoice.status = InvoiceStatus::Released; + invoice.completion_time = Some(env.ledger().timestamp()); + save_invoice(&env, invoice_id, &invoice); + // Decrement open-invoice counter now that invoice is fully done. + let cnt: u32 = env + .storage() + .persistent() + .get(&open_invoice_count_key(&invoice.creator)) + .unwrap_or(0u32); + env.storage() + .persistent() + .set(&open_invoice_count_key(&invoice.creator), &cnt.saturating_sub(1)); + events::invoice_state_changed( + &env, + invoice_id, + Some(&InvoiceStatus::Pending), + &InvoiceStatus::Released, + &recipient, + ); + } + + events::recipient_paid(&env, invoice_id, &recipient, payout); + } + pub fn claim_pending_payout(env: Env, invoice_id: u64, recipient: Address) { recipient.require_auth(); @@ -8740,6 +8888,33 @@ impl SplitContract { continue; } + // Issue #505: verify recipient account existence before attempting payout. + // Use try_invoke_contract to call balance() on the funding token; if it fails + // the account has never been initialised on the ledger. + let account_exists = env + .try_invoke_contract::( + &funding_token_client.address, + &symbol_short!("balance"), + (recipient.clone(),).into_val(env), + ) + .is_ok(); + if !account_exists { + // Record the failure and emit event; skip transfer for this recipient. + let mut failed: Vec
= env + .storage() + .persistent() + .get(&failed_payouts_key(invoice_id)) + .unwrap_or_else(|| Vec::new(env)); + if !failed.iter().any(|a| a == recipient) { + failed.push_back(recipient.clone()); + } + env.storage() + .persistent() + .set(&failed_payouts_key(invoice_id), &failed); + events::recipient_account_missing(env, invoice_id, &recipient); + continue; + } + // Issue #482: use checked arithmetic to prevent overflow. let tax = checked_bps_of(proportional, invoice.tax_bps, 10_000u128) .expect("ArithmeticOverflow"); @@ -9078,7 +9253,18 @@ impl SplitContract { } } - invoice.status = InvoiceStatus::Released; + // Issue #505: only transition to Released if all recipients were paid. + // If any ended up in failed_payouts, stay Pending so retry_failed_payout can finish the job. + let has_failed_payouts = env + .storage() + .persistent() + .get::<(Symbol, u64), Vec
>(&failed_payouts_key(invoice_id)) + .map(|v| !v.is_empty()) + .unwrap_or(false); + + if !has_failed_payouts { + invoice.status = InvoiceStatus::Released; + } invoice.completion_time = Some(env.ledger().timestamp()); if invoice.insurance_fund > 0 { let token_client = token::Client::new(env, &invoice.tokens.get(0).expect("no token")); @@ -9089,16 +9275,29 @@ impl SplitContract { ); invoice.insurance_fund = 0; } + // Issue #503: decrement per-creator open-invoice counter on release (only when fully done). + if !has_failed_payouts { + let cnt: u32 = env + .storage() + .persistent() + .get(&open_invoice_count_key(&invoice.creator)) + .unwrap_or(0u32); + env.storage() + .persistent() + .set(&open_invoice_count_key(&invoice.creator), &cnt.saturating_sub(1)); + } save_invoice(env, invoice_id, invoice); append_audit_entry(env, invoice_id, symbol_short!("release"), actor); events::invoice_released(env, invoice_id, &invoice.recipients); - events::invoice_state_changed( - env, - invoice_id, - Some(&InvoiceStatus::Pending), - &InvoiceStatus::Released, - actor, - ); + if !has_failed_payouts { + events::invoice_state_changed( + env, + invoice_id, + Some(&InvoiceStatus::Pending), + &InvoiceStatus::Released, + actor, + ); + } notify_invoice( env, invoice_id, @@ -10132,6 +10331,18 @@ impl SplitContract { ); } + // Issue #503: decrement per-creator open-invoice counter on cancel. + { + let cnt: u32 = env + .storage() + .persistent() + .get(&open_invoice_count_key(&invoice.creator)) + .unwrap_or(0u32); + env.storage() + .persistent() + .set(&open_invoice_count_key(&invoice.creator), &cnt.saturating_sub(1)); + } + save_invoice(&env, invoice_id, &invoice); append_audit_entry(&env, invoice_id, symbol_short!("cancel"), &caller); diff --git a/contracts/split/src/storage_keys.rs b/contracts/split/src/storage_keys.rs index d07ec3c..f4a3348 100644 --- a/contracts/split/src/storage_keys.rs +++ b/contracts/split/src/storage_keys.rs @@ -332,3 +332,50 @@ pub fn contributor_allowlist_key(invoice_id: u64) -> (Symbol, u64) { (symbol_sho /// Issue #473: Allowed payment tokens list — persistent storage. pub fn allowed_tokens_key() -> Symbol { symbol_short!("alw_toks") } +// --------------------------------------------------------------------------- +// Issue #503: Per-creator open invoice count cap +// --------------------------------------------------------------------------- + +/// Number of currently-open invoices per creator — persistent storage. +/// Incremented on create_invoice, decremented on cancel/release. +pub fn open_invoice_count_key(creator: &Address) -> (Symbol, Address) { + (symbol_short!("op_inv_cn"), creator.clone()) +} + +/// Admin-configurable maximum open invoices per creator — instance storage. +/// Default value when unset: DEFAULT_MAX_OPEN_INVOICES (100). +pub fn max_open_invoices_key() -> Symbol { symbol_short!("mx_op_inv") } + +// --------------------------------------------------------------------------- +// Issue #505: Failed payout tracking +// --------------------------------------------------------------------------- + +/// Per-invoice list of recipient addresses whose payout failed (missing account) — persistent storage. +/// Key: (Symbol, u64) → Vec
+pub fn failed_payouts_key(invoice_id: u64) -> (Symbol, u64) { + (symbol_short!("fail_pay"), invoice_id) +} + +// --------------------------------------------------------------------------- +// Issue #503: Per-creator open invoice count cap +// --------------------------------------------------------------------------- + +/// Number of currently-open invoices per creator — persistent storage. +/// Incremented on create_invoice, decremented on cancel/release. +pub fn open_invoice_count_key(creator: &Address) -> (Symbol, Address) { + (symbol_short!("op_inv_cn"), creator.clone()) +} + +/// Admin-configurable maximum open invoices per creator — instance storage. +pub fn max_open_invoices_key() -> Symbol { symbol_short!("mx_op_inv") } + +// --------------------------------------------------------------------------- +// Issue #505: Failed payout tracking +// --------------------------------------------------------------------------- + +/// List of recipient addresses whose payout failed due to missing account — persistent storage. +/// Key: (Symbol, u64) → Vec
+pub fn failed_payouts_key(invoice_id: u64) -> (Symbol, u64) { + (symbol_short!("fail_pay"), invoice_id) +} + diff --git a/contracts/split/src/storage_snapshot.rs b/contracts/split/src/storage_snapshot.rs index 176d896..b2580c7 100644 --- a/contracts/split/src/storage_snapshot.rs +++ b/contracts/split/src/storage_snapshot.rs @@ -295,6 +295,13 @@ fn storage_key_snapshot() { // Issue #485: contributor allowlist key (Symbol, u64) keys.push(("contributor_allowlist_key", hex_xdr(&env, contributor_allowlist_key(1)))); + // Issue #503: per-creator open invoice count and cap keys + keys.push(("open_invoice_count_key", hex_xdr(&env, open_invoice_count_key(&a)))); + keys.push(("max_open_invoices_key", hex_xdr(&env, max_open_invoices_key()))); + + // Issue #505: failed payouts key (Symbol, u64) + keys.push(("failed_payouts_key", hex_xdr(&env, failed_payouts_key(1)))); + // Sort by key name for deterministic output keys.sort_by(|a, b| a.0.cmp(b.0)); diff --git a/tests/snapshots/storage_keys.json b/tests/snapshots/storage_keys.json index acac5b8..abd6556 100644 --- a/tests/snapshots/storage_keys.json +++ b/tests/snapshots/storage_keys.json @@ -42,6 +42,7 @@ "delegate_pay_key": "0000001000000001000000020000000f00000008646c67745f7061790000001200000000000000000000000000000000000000000000000000000000000000000000000000000000", "ext_vote_key": "0000001000000001000000020000000f000000086578745f766f7465000000050000000000000001", "factories_key": "0000000f00000009666163746f72696573000000", + "failed_payouts_key": "0000001000000001000000020000000f000000086661696c5f706179000000050000000000000001", "fee_tiers_key": "0000000f000000076665655f74727300", "funding_checkpoints_key": "0000000f00000007666e645f63686b00", "global_payer_limit_key": "0000000f00000009675f76656c5f6c696d000000", @@ -61,9 +62,11 @@ "kyc_contract_key": "0000000f000000076b79635f63747200", "last_checkpoint_key": "0000001000000001000000020000000f000000086c6173745f63686b000000050000000000000001", "max_cancel_bps_key": "0000000f000000096d785f636e6c5f6270000000", + "max_open_invoices_key": "0000000f000000096d785f6f705f696e76000000", "milestone_flags_key": "0000001000000001000000020000000f000000076d735f666c677300000000050000000000000001", "nft_gate_key": "0000000f000000076e66745f67746500", "nonce_key": "0000001000000001000000030000000f000000056e6f6e63650000000000000500000000000000010000001200000000000000000000000000000000000000000000000000000000000000000000000000000000", + "open_invoice_count_key": "0000001000000001000000020000000f000000096f705f696e765f636e0000000000001200000000000000000000000000000000000000000000000000000000000000000000000000000000", "paid_flags_key": "0000001000000001000000020000000f00000008706169645f666c67000000050000000000000001", "pause_exempt_key": "0000001000000001000000020000000f00000008705f6578656d70740000001200000000000000000000000000000000000000000000000000000000000000000000000000000000", "paused_fns_key": "0000000f0000000670735f666e730000",