This document describes the property-based tests added to src/test_transitions.rs to verify state machine transition invariants across arbitrary sequences of operations.
While unit tests verify specific scenarios, property-based tests use randomized input generation to discover edge cases and verify that invariants hold universally. This approach is particularly valuable for state machines where the number of possible transition sequences grows exponentially.
Tests use proptest (v1.4), a Rust property-based testing framework that:
- Generates arbitrary test inputs according to defined strategies
- Shrinks failing cases to minimal reproducers
- Provides deterministic replay via seed values
Invariant: Completed and Cancelled states cannot transition to any other state.
prop_terminal_states_are_immutableWhy it matters: Ensures finality — once a remittance is settled or cancelled, its state is locked.
Invariant: All transitions in the state machine graph are explicitly allowed by can_transition_to().
prop_valid_transitions_allowedValid transitions:
Pending→Processing,Cancelled,FailedProcessing→Completed,Cancelled,FailedFailed→Disputed- Any state → itself (idempotent)
Invariant: Transitions not in the state machine graph are explicitly rejected.
prop_invalid_transitions_rejectedExamples of invalid transitions:
Pending→Completed(must go throughProcessing)Completed→Pending(terminal state cannot transition)Processing→Pending(no backward transitions)
Invariant: Transitioning to the same state is always allowed (safe for retries).
prop_idempotent_transitions_allowedWhy it matters: Enables safe retry logic without state corruption.
Invariant: If a valid transition leads to a terminal state, that terminal state cannot transition further.
prop_terminal_states_block_further_transitionsWhy it matters: Prevents accidental state corruption after settlement.
Invariant: No cycles exist in the state machine (except self-loops).
prop_no_cycles_in_state_graphWhy it matters: Ensures deterministic progression toward terminal states; prevents infinite loops.
Invariant: Disputed state can only be reached from Failed state.
prop_disputed_only_from_failedWhy it matters: Enforces the dispute resolution workflow — disputes only arise from failed payouts.
Invariant: Pending is the only initial state; no other state transitions to Pending.
prop_pending_is_initial_onlyWhy it matters: Prevents accidental re-initialization of settled remittances.
Invariant: Every non-terminal state has at least one valid outgoing transition.
prop_non_terminal_states_have_exitsWhy it matters: Ensures no "stuck" states where remittances cannot progress.
Invariant: Calling can_transition_to() multiple times with the same inputs always returns the same result.
prop_transition_validation_is_deterministicWhy it matters: Ensures predictable, reproducible behavior for contract operations.
Generates arbitrary RemittanceStatus values:
Pending,Processing,Completed,Cancelled,Failed,Disputed
Generates valid (from, to) transition pairs:
- All edges in the state machine graph
- Idempotent transitions (same state)
Generates invalid (from, to) transition pairs:
- Terminal state transitions
- Invalid forward transitions
- Backward transitions
In addition to property-based tests, two deterministic tests verify:
Explicitly verifies all expected transitions exist:
Pending → Processing, Cancelled, Failed
Processing → Completed, Cancelled, Failed
Failed → Disputed
Verifies that Completed and Cancelled cannot transition to any other state.
# Run all transition tests
cargo test --lib test_transitions
# Run only property-based tests
cargo test --lib test_transitions prop_
# Run with verbose output
cargo test --lib test_transitions -- --nocapture
# Run with custom seed for reproducibility
PROPTEST_REGRESSIONS=src/test_transitions.rs cargo test --lib test_transitionsIf a property test fails, proptest automatically:
- Shrinks the failing case to a minimal reproducer
- Saves the seed to
proptest/regressions/src_test_transitions_rs.txt - Replays the same seed on subsequent runs
To replay a specific failure:
PROPTEST_REGRESSIONS=src/test_transitions.rs cargo test --lib test_transitionsThe property-based tests cover:
- ✅ All 6 states in the state machine
- ✅ All valid transitions (7 edges + idempotent)
- ✅ All invalid transitions (20+ combinations)
- ✅ Terminal state immutability
- ✅ Acyclicity of the state graph
- ✅ Determinism of transition validation
- ✅ Reachability constraints (e.g., Disputed only from Failed)
These tests run automatically in CI as part of:
cargo test --libNo additional configuration is required. The tests are gated by #[cfg(test)] and only compile in test mode.
Property-based tests run quickly because they only test the state machine logic (no contract invocation):
- ~100 test cases per property (configurable)
- Total runtime: <1 second for all property tests
- No external dependencies or network calls
Potential extensions:
- Sequence-based properties: Generate arbitrary sequences of transitions and verify invariants hold
- Concurrency properties: Verify state machine safety under concurrent access
- Regression tests: Add failing cases discovered in production to the test suite
- Fuzzing: Integrate with libFuzzer for continuous fuzzing of transition logic
- proptest documentation
- Property-based testing guide
- State machine design:
src/transitions.rs,src/types.rs