This document describes the comprehensive unit test suite for escrow logic in the Stellar payroll system. The tests cover two main escrow implementations:
- Vesting Escrow Contract - Time-based token vesting with cliff periods and clawback
- Cross-Asset Payment Contract - Payment escrow for SEP-31 cross-border transactions
contracts/vesting_escrow/src/test_escrow_logic.rs- Vesting escrow testscontracts/cross_asset_payment/src/test_escrow.rs- Payment escrow tests
Tests that verify funds are properly locked in the escrow contract.
Tests:
test_escrow_locks_funds_on_initialization- Verifies tokens transfer from funder to contracttest_escrow_holds_funds_during_cliff_period- Ensures funds remain locked before clifftest_escrow_prevents_unauthorized_withdrawal- Security test for unauthorized accesstest_escrow_multiple_schedules_independent- Tests isolation between multiple escrows
Key Assertions:
- Funder balance decreases by escrow amount
- Contract balance equals escrow amount
- Beneficiary balance remains zero during lock period
- Multiple escrows don't interfere with each other
Tests the linear vesting formula and time-based calculations.
Tests:
test_linear_vesting_calculation- Validates vesting at various time points (0%, 10%, 25%, 50%, 75%, 100%)test_vesting_with_cliff_calculation- Tests cliff period behaviortest_claimable_amount_calculation- Verifies claimable = vested - claimedtest_vesting_precision_no_rounding_errors- Tests with prime numbers to catch precision issues
Formula Tested:
vested = total_amount * (time_elapsed / duration_seconds)Edge Cases:
- Before cliff: vested = 0
- After duration: vested = total_amount (capped)
- During vesting: linear interpolation
Tests incremental token releases as vesting progresses.
Tests:
test_partial_claim_releases_correct_amount- Multiple claims at different percentagestest_multiple_small_claims- Frequent small claims (every 10%)test_claim_after_full_vesting_releases_all- Full release after vesting complete
Scenarios:
- Claim at 25%, then 75% - verify correct deltas
- 10 consecutive 10% claims - verify accumulation
- Single claim after 100% vesting - verify full release
Tests early termination and unvested token recovery.
Tests:
test_clawback_returns_unvested_to_admin- Admin receives unvested portiontest_clawback_before_cliff_returns_all- 100% return before any vestingtest_clawback_after_partial_claim- Clawback with existing claimstest_clawback_deactivates_future_vesting- Prevents further vestingtest_clawback_twice_panics- Security: prevent double clawback
Clawback Logic:
unvested = total_amount - vested_at_clawback_time
// Admin receives: unvested
// Beneficiary can still claim: vested - already_claimedTests that ensure no tokens are created or destroyed.
Tests:
test_total_supply_conservation- Total tokens remain constanttest_escrow_balance_equals_unclaimed_vested- Contract balance = total - claimedtest_no_token_loss_after_clawback_and_full_claim- All tokens accounted for
Invariants:
initial_supply = funder + contract + beneficiary + admin (constant)
contract_balance = total_amount - claimed_amount
Tests boundary conditions and attack vectors.
Tests:
test_zero_amount_escrow_panics- Reject zero amounttest_negative_amount_escrow_panics- Reject negative amounttest_very_large_escrow_amount- Handle i128::MAX / 2test_very_long_vesting_duration- 10-year vesting periodtest_claim_with_no_vested_amount_is_noop- Graceful handling of early claimstest_concurrent_escrows_same_beneficiary- Multiple escrows for same user
Tests payment fund locking during processing.
Tests:
test_payment_escrow_locks_funds- Funds transfer to contract on initiationtest_multiple_payments_accumulate_in_escrow- Multiple payments sum correctlytest_escrow_holds_funds_until_completion- Funds locked until status change
Tests successful payment processing and fund release.
Tests:
test_complete_payment_releases_funds- Funds transfer to recipienttest_multiple_payments_released_independently- Independent payment processing
Flow:
initiate_payment() -> funds locked in contract
complete_payment() -> funds released to recipient
Tests refund mechanisms for failed payments.
Tests:
test_fail_payment_refunds_sender- Full refund on failuretest_partial_refund_scenario- Mixed success/failure handling
Flow:
initiate_payment() -> funds locked in contract
fail_payment() -> funds refunded to sender
Tests replay protection and authorization.
Tests:
test_duplicate_payment_same_ledger_panics- Prevent replay attackstest_payments_allowed_different_ledgers- Allow legitimate retries
Security Mechanism:
// Tracks last ledger per sender to prevent duplicates
LastPaymentLedger(Address) -> u32Tests system invariants and boundary conditions.
Tests:
test_escrow_balance_invariant- Contract balance matches pending paymentstest_large_payment_amount- Handle large amounts (500M)test_payment_count_accuracy- Counter increments correctlytest_zero_balance_after_all_payments_processed- Clean state after processing
Invariant:
contract_balance = sum(pending_payment_amounts)
# Vesting escrow tests
cd contracts/vesting_escrow
cargo test test_escrow_logic
# Cross-asset payment escrow tests
cd contracts/cross_asset_payment
cargo test test_escrow
# Run all contract tests
cargo test --all# Run single test
cargo test test_escrow_locks_funds_on_initialization
# Run test category
cargo test test_escrow_locks -- --nocapture
# Run with output
cargo test -- --nocapture --test-threads=1# Install tarpaulin
cargo install cargo-tarpaulin
# Generate coverage report
cargo tarpaulin --out Html --output-dir coverageEach test uses a setup function that:
- Creates a fresh test environment
- Generates test addresses
- Registers contracts
- Mints initial tokens
- Returns all necessary clients
let (e, funder, beneficiary, admin, token, token_client, _, client, contract_addr) = setup_escrow();Tests control time and sequence:
e.ledger().set_timestamp(start + 500); // Advance time
e.ledger().set_sequence_number(10); // Set ledger sequenceAlways verify all relevant balances:
assert_eq!(token_client.balance(&sender), expected_sender);
assert_eq!(token_client.balance(&contract), expected_contract);
assert_eq!(token_client.balance(&recipient), expected_recipient);Use #[should_panic] for expected failures:
#[test]
#[should_panic(expected = "Already initialized")]
fn test_double_init_panics() {
// Test code that should panic
}- Initialization: 100% (all paths tested)
- Vesting Calculation: 100% (all time ranges)
- Claim Logic: 100% (all scenarios)
- Clawback Logic: 100% (all scenarios)
- Edge Cases: 95% (most boundary conditions)
- Payment Initiation: 100%
- Payment Completion: 100%
- Payment Failure: 100%
- Replay Protection: 100%
- Edge Cases: 90%
- Time Precision: Tests use second-level precision; sub-second vesting not tested
- Gas Costs: Tests don't verify gas optimization
- Concurrent Access: Limited testing of high-concurrency scenarios
- Network Conditions: Tests run in isolated environment, not on actual network
- Property-Based Testing: Use quickcheck/proptest for fuzzing
- Integration Tests: Test contract interactions with real Stellar network
- Performance Tests: Benchmark gas costs and execution time
- Stress Tests: Test with thousands of concurrent escrows
- Upgrade Tests: Test contract upgrade scenarios
-
Balance Mismatch
- Check initial token minting
- Verify all transfers are accounted for
- Check for integer overflow
-
Timing Issues
- Ensure ledger timestamp is set correctly
- Verify cliff and duration calculations
- Check for off-by-one errors
-
Panic Messages
- Read panic message carefully
- Check authorization requirements
- Verify ledger sequence uniqueness
# Run with backtrace
RUST_BACKTRACE=1 cargo test test_name
# Run with logging
RUST_LOG=debug cargo test test_name
# Run single test with output
cargo test test_name -- --nocapture --test-threads=1When adding new escrow tests:
- Follow existing naming conventions
- Add tests to appropriate category
- Include descriptive comments
- Test both success and failure paths
- Verify all balance invariants
- Update this documentation