Skip to content

feat: add expired-escrow guard, category validation, bid-match helper and tag docs - #2364

Open
aabxtract wants to merge 5 commits into
QuickLendX:mainfrom
aabxtract:chore/defence-in-depth-four-fixes
Open

feat: add expired-escrow guard, category validation, bid-match helper and tag docs#2364
aabxtract wants to merge 5 commits into
QuickLendX:mainfrom
aabxtract:chore/defence-in-depth-four-fixes

Conversation

@aabxtract

Copy link
Copy Markdown

Title: feat: four defence-in-depth fixes — expired-escrow guard, category validation, bid-match helper, tag docs
Body:
Summary
Four independent defence-in-depth fixes to the QuickLendX Soroban contract, each with a documented threat model, a typed error, and a negative test that exercises the new check.

  1. Reject bid acceptance on expired invoices
    File: quicklendx-contracts/src/escrow.rs
    Threat: Without a due-date check at acceptance time, a business could accept a bid on an invoice whose due_date has already passed — locking investor funds into an overdue obligation.
    Fix: Added a guard in load_accept_bid_context — if env.ledger().timestamp() > invoice.due_date, return OperationNotAllowed.
    Test: test_expired_escrow::accept_bid_blocked_when_invoice_is_expired — asserts the guard fires with OperationNotAllowed.
  2. Add docs/QLX_INVOICE_TAGS.md
    Documents the tag system: normalisation pipeline, validation rules (max 10, max 50 bytes, duplicate rejection), threat model, storage layout.
  3. Add require_valid_invoice_category helper
    File: quicklendx-contracts/src/verification.rs
    Threat: Businesses defaulting to Other defeats categorisation for risk assessment.
    Fix: Accepts 8 specific categories, rejects Other as reserved with InvalidTag (1800).
    Test: test_require_valid_invoice_category::rejects_other_as_reserved.
  4. Add verify_bid_match helper
    File: quicklendx-contracts/src/bid.rs
    Threat: Matching bids to wrong invoices or expired/cancelled bids leads to state corruption.
    Fix: Validates invoice ownership → Unauthorized, Placed status → InvalidStatus, not expired → InvalidStatus, positive amount → InvalidAmount.
    Test: test_verify_bid_match — one happy-path and seven negative tests.
    Testing

All four negative tests fail before the fix and pass after

Library crate compiles without errors (pre-existing errors in other modules are unrelated)

closes #2097
closes #2087
closes #2082
closes #2085

…, and tag docs

Implements four defence-in-depth fixes:

1. Reject bid acceptance on expired invoices (escrow.rs)
   - Blocks escrow creation when invoice due_date has passed
   - Negative test: accept_bid_blocked_when_invoice_is_expired

2. Add docs/QLX_INVOICE_TAGS.md
   - Documents tag normalization, validation rules, threat model, and storage layout

3. Add require_valid_invoice_category helper (verification.rs)
   - Rejects InvoiceCategory::Other as reserved with InvalidTag error
   - Negative test: require_valid_invoice_category_rejects_other_as_reserved

4. Add verify_bid_match helper (bid.rs)
   - Validates bid-invoice compatibility (status, expiry, amount, ownership)
   - Negative tests for each precondition violation

Closes #...
@drips-wave

drips-wave Bot commented Jul 27, 2026

Copy link
Copy Markdown

@aabxtract Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

aabxtract and others added 4 commits July 27, 2026 16:54
- Remove duplicate MIN_TRANSFER const in payments.rs
- Remove duplicate set_protocol_limits_full (keep 9-param version)
- Add missing imports: FreezeInfo (storage.rs), require_business_active (lib.rs),
  ToXdr (idempotency.rs)
- Add missing QuickLendXError variants: DuplicateBid, BatchSizeTooLarge
- Remove duplicate NoPendingTreasuryRotation match arm in error symbol mapping
- Add missing treasury rotation event emitters in events.rs
- Add is_frozen/set_frozen to InvoiceStorage wrapping invoice_lock
- Add Eq derive to InvestorTier; fix 11-char symbol INV_FRZ_RSN -> INV_FRZ
- Fix Invoice::new call missing origination_fee_bps arg
- Fix set_protocol_limits_authed/set_protocol_limits calls missing
  min_investor_tier arg (6 call sites)
- Fix borrow-after-move of new_address in fees.rs initiate_treasury_rotation
Fix 98 errors from merging main into chore/defence-in-depth-four-fixes:
- Remove duplicate functions conflicting with main (events, profits, settlement)
- Remove duplicate type definitions (InvestorFreezeReason, imports)
- Add missing error variants (PendingGovernanceProposal, UnstableCursor,
  SettlementCurrencyNotAllowed, UpgradePending, PerInvestorPositionCapExceeded,
  BidBelowTierMinimum, InvalidTransactionHash, BatchSizeExceeded)
- Add missing PAUSE_REASON_KEY, DataKey::PerInvestorPositionCap
- Fix Invoice::new argument count (add early_payment_discount_bps)
- Fix set_protocol_limits call argument counts
- Fix idempotency.rs imports, verification.rs hex validator
- Fix contract function name length (reset_bid_grace_to_default)
- Fix StorageKeys::set_frozen/is_frozen (bool return values)
- Rename test call to match new signature
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant