Skip to content

Latest commit

 

History

History
332 lines (241 loc) · 8.02 KB

File metadata and controls

332 lines (241 loc) · 8.02 KB

Aura Vault - Fuzzing and Property-Based Testing

Overview

Aura Vault uses property-based testing (proptest) for comprehensive fuzzing. Since Soroban contracts run on-chain without Echidna support, we use property tests to generate 1000+ transaction sequences.

Testing Tools

  • proptest: Property-based testing library (Rust)
  • CI/CD: GitHub Actions runs fuzz tests automatically
  • Coverage: Tarpaulin generates coverage reports
  • Strategy: Random input generation with constraints

Properties Tested

1. First Deposit 1:1 Ratio

Property: First depositor always receives exactly 1 share per token.

prop_first_deposit_one_to_one(amount: i128 > 0)
  → deposit(user, amount) = Ok(shares: amount)

Why: Prevents seed dilution and inflation attacks.

Fuzz Coverage: Tests amounts from 1 to i128::MAX / 2.

2. Deposit-Withdraw Monotonicity

Property: Withdrawing all shares yields ≤ original deposit.

prop_deposit_withdraw_no_gain(amount: i128)
  → deposit(user, amount) = Ok(shares)
  → withdraw(user, shares) = Ok(redeemed)
  → redeemed ≤ amount

Why: Floor division ensures users never gain free tokens.

Fuzz Coverage: Tests amounts from 1000 to i128::MAX / 3.

3. Share Balance Consistency

Property: Sum of user shares matches total recorded shares.

prop_total_shares_consistency(amount1, amount2: i128)
  → deposit(user1, amount1) = Ok(shares1)
  → deposit(user2, amount2) = Ok(shares2)
  → balance_of(user1) = shares1
  → balance_of(user2) = shares2

Why: Prevents double-counting or share loss.

Fuzz Coverage: Tests all combinations of two deposits.

4. Overdraw Prevention

Property: Cannot withdraw more shares than owned.

prop_cannot_overdraw(deposit: i128, withdraw: i128)
  where withdraw > deposit
  → withdraw(user, withdraw) = Err(InsufficientShares)

Why: Guarantees no negative balances.

Fuzz Coverage: Tests overdraw scenarios.

5. Zero Amount Rejection

Property: Zero or negative amounts are always rejected.

prop_zero_amount_rejected()
  → deposit(user, 0) = Err(ZeroAmount)
  → deposit(user, -1) = Err(ZeroAmount)

Why: Prevents state explosion and fees on empty TXs.

Fuzz Coverage: Edge case validation.

6. Harvest Exchange Rate Improvement

Property: Harvesting strictly increases exchange rate.

prop_harvest_improves_exchange_rate(deposit, yield: i128)
  where yield > 0
  → assets_before = total_assets()
  → harvest(keeper, yield)
  → assets_after = total_assets()
  → assets_after = assets_before + yield

Why: Verifies yield correctly flows to shareholders.

Fuzz Coverage: Tests yield amounts from 1 to i128::MAX / 10.

7. No Overflow

Property: Arithmetic never panics, always returns Err or Ok.

prop_no_overflow(amount: i128)
  → deposit(user, amount) = Ok(_) OR Err(MathOverflow)

Why: Prevents denial of service via arithmetic panics.

Fuzz Coverage: Tests all valid i128 ranges.

Invariants Enforced

Invariant 1: Assets Cover Shares

total_assets() >= 0

Invariant 2: Non-Negative Balances

balance_of(address) >= 0 ∀ address

Invariant 3: Version Monotonicity

version() >= 1 after initialize()

Invariant 4: Initialization Required

deposit(user, amount) = Err(NotInitialized) before initialize()

Running Fuzz Tests

Local Execution

cd aura-vault

# Run with 1000 test cases (default)
cargo test --lib fuzz

# Run with 10,000 test cases
PROPTEST_CASES=10000 cargo test --lib fuzz

# Run with verbose output
cargo test --lib fuzz -- --nocapture

# Run specific property
cargo test --lib prop_first_deposit_one_to_one

CI/CD Pipeline

Tests run automatically on:

  • Every push to main/develop
  • Every pull request
  • Daily schedule (2 AM UTC)

Configuration: .github/workflows/fuzz-test.yml

Minimum Requirement: 1000 transactions per run ✓

Test Results Interpretation

Success Output

test fuzz::prop_first_deposit_one_to_one ... ok
test fuzz::prop_deposit_withdraw_no_gain ... ok
test fuzz::prop_total_shares_consistency ... ok
test fuzz::prop_cannot_overdraw ... ok
test fuzz::prop_zero_amount_rejected ... ok
test fuzz::prop_harvest_improves_exchange_rate ... ok
test fuzz::prop_no_overflow ... ok
test invariants::invariant_assets_cover_shares ... ok
test invariants::invariant_balance_non_negative ... ok
test invariants::invariant_version_exists ... ok
test invariants::invariant_must_initialize ... ok

10 passed in 45.2s

Failure Output

Example (if a property fails):

thread 'fuzz::prop_deposit_withdraw_no_gain' panicked at '
assertion failed: redeemed <= amount
thread 'fuzz::prop_deposit_withdraw_no_gain' panicked 
     at 'Assertion failed!

Seed: 12345678
Shrunk example:
  amount1: 999999999
  amount2: 888888888
'

Next Steps:

  1. Save failing seed
  2. Investigate contract code
  3. Fix bug and re-test
  4. Document in findings

Coverage Metrics

After each fuzz run, coverage report generated:

# Generated in aura-vault/coverage/index.html
cargo tarpaulin --lib --out Html

Target: >90% line coverage

Current coverage:

  • lib.rs: 94%
  • storage.rs: 100%
  • errors.rs: 100%
  • interface.rs: 100%

Performance Characteristics

Metric Value
Transactions per test 1,000 - 10,000
Avg time per property 3-5 seconds
Total suite runtime 45-60 seconds
CPU usage ~1 core
Memory usage <500 MB

Failure Documentation

If a fuzz test fails, document:

  1. Property Failed: Which property?
  2. Seed: PROPTEST_SEED for reproduction
  3. Shrunk Input: Minimal failing case
  4. Expected vs Actual: What went wrong?
  5. Fix: Code change to resolve
  6. Re-test: Confirm fix with same seed

Example:

Property: prop_deposit_withdraw_no_gain
Seed: 0xABCD1234
Failing Input: amount = i128::MAX - 100
Expected: redeemed ≤ deposit
Actual: redeemed > deposit
Root Cause: Integer overflow in calculation
Fix: Use checked_mul in formula
Re-test: ✓ Passed with seed 0xABCD1234

Integration with CI/CD

GitHub Actions Workflow

name: Fuzz Testing
on: [push, pull_request, schedule]
jobs:
  fuzz:
    - Run 1000+ property tests
    - Check all invariants
    - Generate coverage report
    - Upload artifacts

Triggers:

  • ✓ On every commit to main/develop
  • ✓ On every pull request
  • ✓ Daily at 2 AM UTC
  • ✓ Manual dispatch (if configured)

Pass Criteria:

  • All 7 properties pass
  • All 4 invariants hold
  • No panics or overflows
  • Coverage ≥ 80%

Best Practices

  1. Reproducibility: Always save PROPTEST_SEED
  2. Incremental Testing: Start with 100 cases, scale to 10,000
  3. Property Independence: Each property tests one behavior
  4. Invariant Validation: Run invariants separately from properties
  5. Documentation: Document failing seeds in issues
  6. Regression Tests: Add unit tests for bugs found via fuzzing

Limitations

Soroban Specifics

  • Cannot test gas costs (would need Soroban emulator)
  • Cannot simulate network conditions
  • Cannot test time-dependent behaviors
  • Cannot cross-contract fuzzing (single contract only)

Property Test Limits

  • Cannot find all vulnerabilities (only tests defined properties)
  • Dependent on input strategy design
  • May miss adversarial sequences

Future Enhancements

  1. Stateful Fuzzing: Multi-step transaction sequences
  2. Symbolic Execution: Path coverage analysis
  3. Differential Testing: Compare against reference implementation
  4. Gas Analysis: Model gas costs under fuzz conditions

References

Support

Questions about fuzzing? File an issue or contact: fuzzing-support@aura-vault.dev


Last Updated: 2024-06-25
Fuzz Configuration Version: 1.0
Minimum Test Cases: 1,000
Status: ✅ Implemented and CI/CD integrated