Skip to content

test(invoice-escrow): add negative off-chain sig expiry & multi-curre… - #302

Open
Joyyyb wants to merge 1 commit into
StellarState:devfrom
Joyyyb:feature/issue-160-170-test-coverage-signed-offchain-multicurrency
Open

test(invoice-escrow): add negative off-chain sig expiry & multi-curre…#302
Joyyyb wants to merge 1 commit into
StellarState:devfrom
Joyyyb:feature/issue-160-170-test-coverage-signed-offchain-multicurrency

Conversation

@Joyyyb

@Joyyyb Joyyyb commented Jul 29, 2026

Copy link
Copy Markdown

…ncy integration tests

Closes #160 — Negative Tests for Expired Off-Chain Signature Submissions Closes #170 — Integration Tests for Multi-Currency Escrow Settlement

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ISSUE #160 — contracts/invoice-escrow/src/test.rs
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Context

The fund_escrow_signed entry-point (PR #183) accepts an off-chain buyer approval consisting of (invoice_id, amount, nonce, expiry). The contract rejects the call when env.ledger().timestamp() > expiry, returning Error::SignatureExpired (code 22). Prior to this commit there were no tests that directly exercised the expiry rejection path, leaving a gap in negative-test coverage for this security-critical gate.

What was done

Seven new unit tests were appended to the existing test.rs test suite under the section heading:
// ========== Issue #160: Negative Tests for Expired Off-Chain
// Signature Submissions ==========

  1. test_fund_escrow_signed_rejects_expired_signature Sets the ledger timestamp to expiry + 1 and asserts Error::SignatureExpired is returned. Confirms escrow remains Created.

  2. test_fund_escrow_signed_rejects_at_exact_expiry_plus_one_boundary Verifies the boundary semantics: ledger_ts == expiry is NOT expired (the guard is current_ts > expiry, not >=), while ledger_ts == expiry+1 is expired. The passing half of this test exercises a successful signed fund at the exact boundary tick.

  3. test_fund_escrow_signed_succeeds_just_before_expiry Sets ledger to expiry - 1 and asserts the call succeeds, the escrow transitions to Funded, and tokens are transferred to the contract.

  4. test_fund_escrow_signed_expired_does_not_change_escrow_state First submits an expired signature (must fail), then submits a valid one. Asserts no state mutation or token movement occurred during the failed attempt, and that the escrow can still be funded afterwards.

  5. test_fund_escrow_signed_nonce_not_consumed_on_expiry Proves that when a submission is rejected for expiry the nonce counter is NOT incremented. The same nonce is reused in the subsequent valid call and must succeed (would return NonceAlreadyUsed if the nonce had been consumed).

  6. test_fund_escrow_signed_zero_expiry_always_expired Passes expiry = 0 with ledger_ts = 1 and verifies Error::SignatureExpired, covering the minimum-value edge-case for the expiry field.

  7. test_fund_escrow_signed_expiry_checked_before_nonce Consumes nonce 1 via a valid signed call, then submits an already-used nonce with an expired timestamp. Asserts SignatureExpired is returned rather than NonceAlreadyUsed, confirming evaluation order: expiry is checked first, nonce second.

How it was implemented

All tests follow the existing pattern in test.rs:
• env.mock_all_auths() so auth is bypassed for fund_escrow_signed.
• Register InvoiceEscrow + MockInvoiceToken + Stellar asset contract.
• Advance the ledger timestamp via env.ledger().with_mut(|li| ...).
• Call try_fund_escrow_signed and assert the returned Err value.
• For state-persistence checks, call get_escrow_status and verify
TokenClient balances are unchanged after a failed attempt.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ISSUE #170 — contracts/invoice-escrow/src/integration_test.rs ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Context

The existing integration test suite (tests 1–25) uses a single payment token per test. There were no tests that ran two escrows with different payment tokens against the same InvoiceEscrow contract instance to verify that multi-currency scenarios are handled correctly. Token balance isolation, fee collection in the right currency, per-escrow state persistence, and correct refund routing all needed explicit coverage.

What was done

Five new integration tests were appended to integration_test.rs under the section heading:
// Issue #170: Multi-Currency Escrow Settlement Integration Tests

A private helper fn register_currency(...) was also added to reduce boilerplate: it registers a new InvoiceToken + Stellar asset contract, initializes the invoice token against the shared escrow, and returns the clients for both.

  1. test_integration_multi_currency_two_escrows_settle_independently Creates two escrows (inv_a / inv_b) backed by different Stellar asset contracts on a single InvoiceEscrow instance. Funds and settles each in sequence. Asserts both reach Settled status and both invoice tokens are unlocked without interfering with each other.

  2. test_integration_multi_currency_settlement_token_isolation Settles escrow A (token A) and verifies token B balances are entirely unaffected. Then settles escrow B and checks token A balances remain unchanged. Escrow contract balance is verified to be zero in both currencies after settlement.

  3. test_integration_multi_currency_refund_returns_correct_token Settles escrow A normally, then advances time past the due date of escrow B and calls refund. Asserts buyer_b receives the refund in token B (not token A), and that token A is unaffected by the refund.

  4. test_integration_multi_currency_fee_collected_in_correct_token Uses a 5% fee. Settles two escrows of different sizes in different tokens and asserts the admin receives exactly 5% in token A from the A-settlement and exactly 5% in token B from the B-settlement. Cross- token fee leakage is verified to be zero.

  5. test_integration_multi_currency_state_persists_independently Calls get_escrow on both escrows after creation, after funding only A, and after settling A. Verifies that face_value, purchase_price, status, token address, commitment, due_dt, funded_amt, funder, and paid_amt are stored and read back correctly for each escrow without cross-escrow contamination.

How it was implemented

All tests use the existing setup() / create_and_fund() helpers where possible and the new register_currency() helper for second-token setup. Real InvoiceToken (invoice_token crate) and Stellar asset contracts are used throughout so cross-contract calls (mint, transfer, set_transfer_locked, decimals) execute as they would on-chain, matching the integration test philosophy of the file. env.mock_all_auths() is used so auth is not the variable under test.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Acceptance criteria satisfied
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
All implementation tasks completed (7 + 5 new test functions).
Error code assertions on every failure path (SignatureExpired,
NonceAlreadyUsed, EscrowStatus checks).
State storage persistence verified after each operation in both files.
Code follows project style (matches existing patterns in test.rs and
integration_test.rs).
PR targets the dev branch.

Description

Closes #

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📝 Documentation update
  • 🎨 UI/UX improvement
  • ♻️ Code refactoring
  • ✅ Test addition or update
  • 🔧 Configuration change

Checklist

  • My code follows the code style of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published

Testing

How to Test

  1. Step one
  2. Step two
  3. Step three

Test Coverage

  • Unit tests added/updated
  • Integration tests added/updated
  • E2E tests added/updated (if applicable)
  • Manual testing completed

Screenshots (if applicable)

Additional Notes

For Reviewers

  • Code quality and readability
  • Test coverage
  • Security implications
  • Performance impact
  • Breaking changes

…ncy integration tests

Closes StellarState#160 — Negative Tests for Expired Off-Chain Signature Submissions
Closes StellarState#170 — Integration Tests for Multi-Currency Escrow Settlement

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ISSUE StellarState#160 — contracts/invoice-escrow/src/test.rs
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Context
-------
The fund_escrow_signed entry-point (PR StellarState#183) accepts an off-chain buyer
approval consisting of (invoice_id, amount, nonce, expiry). The contract
rejects the call when env.ledger().timestamp() > expiry, returning
Error::SignatureExpired (code 22). Prior to this commit there were no
tests that directly exercised the expiry rejection path, leaving a gap in
negative-test coverage for this security-critical gate.

What was done
-------------
Seven new unit tests were appended to the existing test.rs test suite
under the section heading:
  // ========== Issue StellarState#160: Negative Tests for Expired Off-Chain
  //            Signature Submissions ==========

1. test_fund_escrow_signed_rejects_expired_signature
   Sets the ledger timestamp to expiry + 1 and asserts
   Error::SignatureExpired is returned. Confirms escrow remains Created.

2. test_fund_escrow_signed_rejects_at_exact_expiry_plus_one_boundary
   Verifies the boundary semantics: ledger_ts == expiry is NOT expired
   (the guard is current_ts > expiry, not >=), while ledger_ts == expiry+1
   is expired. The passing half of this test exercises a successful signed
   fund at the exact boundary tick.

3. test_fund_escrow_signed_succeeds_just_before_expiry
   Sets ledger to expiry - 1 and asserts the call succeeds, the escrow
   transitions to Funded, and tokens are transferred to the contract.

4. test_fund_escrow_signed_expired_does_not_change_escrow_state
   First submits an expired signature (must fail), then submits a valid
   one. Asserts no state mutation or token movement occurred during the
   failed attempt, and that the escrow can still be funded afterwards.

5. test_fund_escrow_signed_nonce_not_consumed_on_expiry
   Proves that when a submission is rejected for expiry the nonce counter
   is NOT incremented. The same nonce is reused in the subsequent valid
   call and must succeed (would return NonceAlreadyUsed if the nonce had
   been consumed).

6. test_fund_escrow_signed_zero_expiry_always_expired
   Passes expiry = 0 with ledger_ts = 1 and verifies Error::SignatureExpired,
   covering the minimum-value edge-case for the expiry field.

7. test_fund_escrow_signed_expiry_checked_before_nonce
   Consumes nonce 1 via a valid signed call, then submits an already-used
   nonce with an expired timestamp. Asserts SignatureExpired is returned
   rather than NonceAlreadyUsed, confirming evaluation order: expiry is
   checked first, nonce second.

How it was implemented
----------------------
All tests follow the existing pattern in test.rs:
  • env.mock_all_auths() so auth is bypassed for fund_escrow_signed.
  • Register InvoiceEscrow + MockInvoiceToken + Stellar asset contract.
  • Advance the ledger timestamp via env.ledger().with_mut(|li| ...).
  • Call try_fund_escrow_signed and assert the returned Err value.
  • For state-persistence checks, call get_escrow_status and verify
    TokenClient balances are unchanged after a failed attempt.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ISSUE StellarState#170 — contracts/invoice-escrow/src/integration_test.rs
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Context
-------
The existing integration test suite (tests 1–25) uses a single payment
token per test. There were no tests that ran two escrows with different
payment tokens against the same InvoiceEscrow contract instance to verify
that multi-currency scenarios are handled correctly. Token balance
isolation, fee collection in the right currency, per-escrow state
persistence, and correct refund routing all needed explicit coverage.

What was done
-------------
Five new integration tests were appended to integration_test.rs under the
section heading:
  // Issue StellarState#170: Multi-Currency Escrow Settlement Integration Tests

A private helper fn register_currency(...) was also added to reduce
boilerplate: it registers a new InvoiceToken + Stellar asset contract,
initializes the invoice token against the shared escrow, and returns the
clients for both.

26. test_integration_multi_currency_two_escrows_settle_independently
    Creates two escrows (inv_a / inv_b) backed by different Stellar asset
    contracts on a single InvoiceEscrow instance. Funds and settles each
    in sequence. Asserts both reach Settled status and both invoice tokens
    are unlocked without interfering with each other.

27. test_integration_multi_currency_settlement_token_isolation
    Settles escrow A (token A) and verifies token B balances are entirely
    unaffected. Then settles escrow B and checks token A balances remain
    unchanged. Escrow contract balance is verified to be zero in both
    currencies after settlement.

28. test_integration_multi_currency_refund_returns_correct_token
    Settles escrow A normally, then advances time past the due date of
    escrow B and calls refund. Asserts buyer_b receives the refund in
    token B (not token A), and that token A is unaffected by the refund.

29. test_integration_multi_currency_fee_collected_in_correct_token
    Uses a 5% fee. Settles two escrows of different sizes in different
    tokens and asserts the admin receives exactly 5% in token A from the
    A-settlement and exactly 5% in token B from the B-settlement. Cross-
    token fee leakage is verified to be zero.

30. test_integration_multi_currency_state_persists_independently
    Calls get_escrow on both escrows after creation, after funding only A,
    and after settling A. Verifies that face_value, purchase_price, status,
    token address, commitment, due_dt, funded_amt, funder, and paid_amt are
    stored and read back correctly for each escrow without cross-escrow
    contamination.

How it was implemented
----------------------
All tests use the existing setup() / create_and_fund() helpers where
possible and the new register_currency() helper for second-token setup.
Real InvoiceToken (invoice_token crate) and Stellar asset contracts are
used throughout so cross-contract calls (mint, transfer, set_transfer_locked,
decimals) execute as they would on-chain, matching the integration test
philosophy of the file. env.mock_all_auths() is used so auth is not the
variable under test.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Acceptance criteria satisfied
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 All implementation tasks completed (7 + 5 new test functions).
 Error code assertions on every failure path (SignatureExpired,
  NonceAlreadyUsed, EscrowStatus checks).
 State storage persistence verified after each operation in both files.
 Code follows project style (matches existing patterns in test.rs and
  integration_test.rs).
 PR targets the dev branch.
@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@Joyyyb 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

@chizzy192

Copy link
Copy Markdown
Contributor

Hi @Joyyyb, your PR merged cleanly with dev, but the CI pipeline failed on cargo test --all. Please inspect your new test cases for off-chain signature expiration and multi-currency edge cases in contracts/invoice-escrow/src/test.rs and verify that mock ledger timestamps pass locally using cargo test -p invoice-escrow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[TESTING] Write Integration Test for Multi-Currency Escrow Settlement [TESTING] Add Negative Tests for Expired Off-Chain Signature Submissions

2 participants