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
6 changes: 6 additions & 0 deletions contracts/split/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5443,6 +5443,12 @@ impl SplitContract {
env.panic_with_error(e);
}
assert!(min_funding_bps <= 10_000, "min_funding_bps must be ≤ 10000");
// Issue #695: velocity_limit > 0 paired with velocity_window == 0 would cause
// division-by-zero or undefined behaviour when computing per-payer spend rates.
// Reject the configuration before any storage is written.
if velocity_limit > 0 && velocity_window == 0 {
panic_with_error!(env, ContractError::InvalidAmount);
}
assert_valid_bps(tax_bps).expect("tax_bps must be ≤ 10000");
assert_valid_bps(insurance_premium_bps).expect("insurance_premium_bps must be ≤ 10000");
// Issue #489 / #696: early-bird discounted platform fee must not exceed the
Expand Down
68 changes: 68 additions & 0 deletions contracts/split/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2907,6 +2907,74 @@ fn test_min_funding_bps_allows_release_above_threshold() {
assert_eq!(tk.balance(&recipient), 900);
}

// ---------------------------------------------------------------------------
// Issue #695: validate velocity_window is non-zero when velocity_limit is set
// ---------------------------------------------------------------------------

#[test]
#[should_panic]
fn test_velocity_limit_without_window_rejected() {
// velocity_limit=1000 with velocity_window=0 must be rejected with ContractError::InvalidAmount.
let (env, contract_id, token_id) = setup_initialized();
let c = client(&env, &contract_id);

let creator = Address::generate(&env);
let recipient = Address::generate(&env);

env.ledger().set_timestamp(1_000);

let mut recipients = Vec::new(&env);
recipients.push_back(recipient.clone());
let mut amounts = Vec::new(&env);
amounts.push_back(1_000_i128);

// velocity_limit > 0 but velocity_window == 0 — undefined behaviour; must panic.
c.create_invoice(
&creator,
&recipients,
&amounts,
&token_id,
&9_999_u64,
&InvoiceOptions {
velocity_limit: 1_000,
velocity_window: 0,
..default_options(&env)
},
);
}

#[test]
fn test_velocity_limit_with_valid_window_accepted() {
// velocity_limit=1000, velocity_window=3600 is a valid configuration and must be accepted.
let (env, contract_id, token_id) = setup_initialized();
let c = client(&env, &contract_id);

let creator = Address::generate(&env);
let recipient = Address::generate(&env);

env.ledger().set_timestamp(1_000);

let mut recipients = Vec::new(&env);
recipients.push_back(recipient.clone());
let mut amounts = Vec::new(&env);
amounts.push_back(1_000_i128);

// Both limit and window are set — valid; invoice creation must succeed.
let id = c.create_invoice(
&creator,
&recipients,
&amounts,
&token_id,
&9_999_u64,
&InvoiceOptions {
velocity_limit: 1_000,
velocity_window: 3_600,
..default_options(&env)
},
);
assert_eq!(c.get_invoice(&id).status, InvoiceStatus::Pending);
}

// ---------------------------------------------------------------------------
// Issue #85: generate_payment_proof
// ---------------------------------------------------------------------------
Expand Down