We welcome contributions! Please follow these guidelines to help us maintain project quality.
- Fork the repo and create your branch: git checkout -b feature/your-feature-name.
- Formatting: We use rustfmt. Please run the following command before committing:
just fmt # or without just: cargo fmt - Testing: Run the test suite before submitting:
just test # or without just: ./scripts/test.sh
- Pre-PR check: Run the full CI suite locally before opening a PR:
just ci
- Pull Requests: Open a PR against main. Ensure your PR description clearly outlines the changes and links to the relevant issue.
The CI pipeline includes three layers of automated security scanning that run on every push and pull request:
Gitleaks scans for accidentally committed secrets (API keys, tokens, credentials, etc.) in the git history and working directory.
- Configuration:
.gitleaks.tomlcontains the detection rules and allowlist for test fixtures - False positives: If legitimate test credentials or documentation examples trigger a false positive, add them to the
allowlist.regexesorallowlist.pathsin.gitleaks.toml - Local testing: Run gitleaks locally before pushing:
gitleaks detect --source . --config .gitleaks.toml --redact --fail
Cargo audit checks Rust dependencies against a database of known security vulnerabilities.
- Runs:
cargo audit --deny warnings - What it catches: Known CVEs in transitive dependencies
- If audit fails: Update the vulnerable dependency to a patched version, or if no fix is available, document the exception and work with the security team
- Local testing:
cargo audit --deny warnings
Clippy enforces lint warnings as hard errors to catch common mistakes and anti-patterns.
- Runs:
cargo clippy --package ttl-vault -- -D warnings - Warnings treated as errors: All clippy warnings must be fixed before merging
- Suppress spurious warnings: Use
#[allow(clippy::rule_name)]on specific code with a comment explaining why - Local testing:
cargo clippy --package ttl-vault -- -D warnings
Before opening a PR, run all security checks locally:
# Run the full CI suite including all security scans
just ci
# Or manually:
cargo fmt --all -- --check
cargo clippy --package ttl-vault -- -D warnings
cargo audit --deny warnings
gitleaks detect --source . --config .gitleaks.toml --redact --failIf any check fails, fix it before pushing. The CI pipeline will enforce the same checks.
Install just, then run just --list from the repo root:
Available recipes:
audit # Run cargo-audit (install with: cargo install cargo-audit)
build # Build both Soroban contracts for wasm32 release
ci # Run build + test + clippy in one shot (useful before opening a PR)
clippy # Run clippy (warnings treated as errors, matching CI)
deploy-mainnet # Deploy to Stellar mainnet (requires STELLAR_MAINNET_RPC_URL; prompts for confirmation)
deploy-mainnet-force# Force-redeploy to mainnet without the existing-contract prompt
deploy-testnet # Deploy to Stellar testnet (prompts if a contract already exists)
deploy-testnet-force# Force-redeploy to testnet without confirmation prompt
docker-down # Stop and remove local dev stack containers
docker-up # Start local dev stack (PostgreSQL, backend, Stellar Quickstart)
env-setup # Copy .env.example to .env (skips if .env already exists)
fmt # Auto-format all code
fmt-check # Check code formatting
test # Run the full ttl_vault test suite
The TTL Vault contract includes comprehensive fuzz testing to catch panics, unexpected errors, and boundary condition violations. Fuzz tests are located in contracts/ttl_vault/fuzz/.
Fuzz testing requires the nightly Rust toolchain:
# Install nightly if you haven't already
rustup install nightly
rustup component add rust-src --toolchain nightlyRun a single fuzz target:
cd contracts/ttl_vault/fuzz
# Fuzz for 10 minutes (600 seconds)
cargo +nightly fuzz run fuzz_create_vault -- -max_total_time=600
# Fuzz with corpus (if available)
cargo +nightly fuzz run fuzz_deposit corpus/fuzz_deposit -- -max_total_time=600Run all fuzz targets:
# Run each target for 10 minutes
for target in fuzz_vesting fuzz_create_vault fuzz_deposit fuzz_withdraw fuzz_check_in; do
cargo +nightly fuzz run $target -- -max_total_time=600
doneFour primary contract entry points have fuzz targets:
fuzz_create_vault- Tests vault creation with arbitrary addresses and intervalsfuzz_deposit- Tests deposits with arbitrary amounts and limitsfuzz_withdraw- Tests withdrawals with approval thresholds and guardsfuzz_check_in- Tests check-ins with TTL caps and inactivity penaltiesfuzz_vesting- Tests vesting schedule calculations (existing)
If the fuzzer finds a crash or panic:
- Reproduce locally: The fuzzer creates a corpus file with the failing input
- Minimize: Use
cargo +nightly fuzz cmin <target>to reduce input size - Fix: Address the underlying issue in the contract code
- Verify: Re-run the fuzzer to confirm the fix
- Commit: Add the test case to prevent regressions
Example:
# If fuzz_create_vault crashes
cd contracts/ttl_vault/fuzz
# Reproduce the crash
cargo +nightly fuzz run fuzz_create_vault corpus/fuzz_create_vault/crash-xxx
# Minimize the input
cargo +nightly fuzz cmin fuzz_create_vault corpus/fuzz_create_vault/crash-xxxFuzz tests run nightly via .github/workflows/nightly-fuzz.yml:
- Schedule: Daily at 2 AM UTC
- Duration: 10 minutes per target (configurable)
- Failure Mode: Any crash/panic causes CI failure
- Artifacts: Corpus and crash artifacts uploaded for 30 days
Manual trigger:
# Trigger via GitHub CLI with custom parameters
gh workflow run nightly-fuzz.yml -f fuzz_time=1800 -f max_len=2048Seed corpus files are stored in contracts/ttl_vault/fuzz/corpus/:
fuzz_create_vault/- Seeds for vault creation testingfuzz_deposit/- Seeds for deposit testingfuzz_withdraw/- Seeds for withdrawal testingfuzz_check_in/- Seeds for check-in testing
Generate or regenerate corpus:
cd contracts/ttl_vault/fuzz
python3 generate_corpus.pyAfter fuzzing discovers new interesting inputs, commit them:
git add contracts/ttl_vault/fuzz/corpus/
git commit -m "Update fuzz corpus after extended fuzzing run"- Run fuzz tests before major releases - Extended runs (hours/days) catch subtle bugs
- Commit crash inputs - Prevents regressions
- Review fuzzer findings - Understand why an input triggered coverage
- Maintain corpus - Keep seed inputs diverse and minimal
For more details, see contracts/ttl_vault/fuzz/README.md.
- Follow standard Rust idiomatic practices.
- Use /// for all public function documentation.
- Maintain consistency with the existing project structure.