Skip to content

Latest commit

 

History

History
155 lines (119 loc) · 9.34 KB

File metadata and controls

155 lines (119 loc) · 9.34 KB

Contributing to Lafiya Smart Contracts

Thank you for your interest in contributing to Lafiya! Lafiya is an open-source Digital Public Good (DPG) aiming to bring verified, patient-controlled emergency health cards to the last mile. Your contributions help make this trust layer more robust, secure, and accessible.

This repository holds the Soroban (Stellar) smart contracts. Because Lafiya is a multi-repo ecosystem, contributions here often have ripple effects across other repositories. This guide outlines the setup, conventions, and workflows required to contribute safely.

  • Rust (stable), installed via rustup
  • The wasm32v1-none target: rustup target add wasm32v1-none
  • pre-commit (required for local git hooks): Install via pip install pre-commit or brew install pre-commit, then run pre-commit install in the repository root.

Table of Contents


Local Setup

The core development environment requires Rust and the Soroban SDK. For the quick-start commands, see the Getting Started section in the README.md.

Prerequisites

  • Rust (stable): Install via rustup.
  • Wasm target: rustup target add wasm32v1-none (pinned via rust-toolchain.toml).
  • Stellar CLI: Needed for deploying and interacting with the testnet. Install it via Cargo:
    cargo install --locked stellar-cli --features opt

Branching & Commit Conventions

To maintain a clean, navigable history for auditability and open-source collaboration, we follow these conventions:

Branch Naming

  • feature/short-description for new features or smart contract functions.
  • bugfix/short-description for bug fixes.
  • docs/short-description for documentation-only changes.
  • chore/short-description for build tasks, dependencies, etc.

Commit Messages

We encourage Conventional Commits:

  • feat(registry): add batch attestation support
  • fix(allowlist): correct signature verification check
  • test(contracts): add tests for admin transfer
  • docs: update contributing guide for cross-repo changes

Cross-Repo Coordination & Shared Contracts

Lafiya is composed of five distinct repositories in the Lafiya-xyz organization:

  1. lafiya-web: Next.js web application (patient records, QR, allowlist management interface).
  2. lafiya-contracts (this repo): Soroban smart contracts (attester allowlist, attestation registry).
  3. lafiya-docs: Architectural documentation, threat model, and references.
  4. .github: Organization-level files.
  5. lafiya-verifier: Standalone verification tool.

Shared Contracts Constraint

The on-chain attestation schema (a 32-byte record hash, attester Address, and timestamp) acts as a shared contract between lafiya-contracts and lafiya-web.

Important

If you modify a smart contract function signature, event payload, or the return shape of get_attestation, you must flag this change. It will break the off-chain patient profile and verification displays in lafiya-web.

How to flag cross-repo changes:

  1. Check the Cross-Repo Impact section in the PR template.
  2. Link the corresponding issue/PR in the lafiya-web repository.
  3. Coordinate with maintainers to ensure both repositories are updated and deployed in tandem.

Database & Supabase Migrations

While lafiya-contracts is a Rust smart contract repository and contains no database code:

  • The main web application lafiya-web uses Supabase for its encrypted off-chain storage.
  • If your contribution spans both the smart contracts and the database schema (e.g., adding field tracking for attestation IDs off-chain):

Migration Guidelines (in lafiya-web)

  1. Supabase CLI: Use the Supabase CLI to generate a new migration:
    supabase migration new your_migration_name
  2. Hand-Authored Types: We use hand-authored types for database safety and strict runtime boundaries. The types are documented and maintained in: lafiya-web/lib/supabase/types.ts

    [!WARNING] Do not auto-generate database types and overwrite lib/supabase/types.ts blindly. Any schema change must have its typescript types updated by hand following the existing patterns to preserve custom wrappers, type guards, and safety boundaries.


Smart Contract Development & Quality Standards

To maintain high security and minimize gas/storage costs on Soroban, all contract code must adhere to:

Cross-Contract Calls

  • Interact with other contracts (e.g., attestation-registry calling attester-registry) through a client trait interface using the #[contractclient] macro.
  • Do not add direct crate dependencies between contracts to prevent linking duplicate symbols and bloating WASM binary sizes.

Testing Requirements

  • Every public contract function must have accompanying unit tests in its crate's src/test.rs.
  • Tests must cover:
    • Success paths: Standard execution flow.
    • Authorization paths: Proper validation of admin or user signatures (require_auth()).
    • Failure paths: Rejection of double-initialization, invalid inputs, and unauthorized calls.
    • Events: Verify that correct events (like AttesterAdded, AttestationRecorded) are emitted.

Fuzz / Property-Based Testing

attester-registry and attestation-registry each have a fuzz_test.rs module (built with proptest, already a dev-dependency in both crates) alongside their regular test.rs:

  • attester-registry::fuzz_test generates arbitrary sequences of add_attester/remove_attester/suspend_attester/reinstate_attester calls over a small pool of addresses and checks, after every step, that is_attester agrees with a plain-Rust model — i.e. it never observes an address as simultaneously allowlisted and not.
  • attestation-registry::fuzz_test calls attest with arbitrary/adversarial 32-byte record_hash values (including all-zero and all-0xFF) and in unusual orderings relative to initialize, asserting it only ever returns a typed Result and never panics.

CI runs these with a small, time-bounded case count (PROPTEST_CASES=256, see .github/workflows/ci.yml) as a non-blocking job — a regression there is a signal to investigate, not a merge blocker, since proptest's case count/seed is inherently variable run to run.

To fuzz much harder locally (uncapped, until you stop it or hit a shrink-and-report), raise the case count and optionally the max shrink iterations:

PROPTEST_CASES=100000 cargo test -p attester-registry fuzz_test -- --nocapture
PROPTEST_CASES=100000 cargo test -p attestation-registry fuzz_test -- --nocapture

If proptest finds a failing case, it shrinks it to a minimal repro and writes it to contracts/<crate>/proptest-regressions/fuzz_test.txt; commit that file alongside your fix so the minimal input becomes a permanent regression test (proptest replays entries from that file automatically on every run).

If a crash or invariant violation surfaces while working on either fuzz target, file it as its own bug report and fix it — don't fold an unrelated fix into a feature PR.

Local Quality Gate

Always run the validation suite locally before committing:

make check

This runs:

  1. make fmt (code formatting verification)
  2. make clippy (linter checks; warnings are treated as errors)
  3. make test (all cargo tests)
  4. make wasm (building target WASM binaries)
  • Every new contract function needs unit tests covering both the success path and the failure/authorization paths (see contracts/*/src/test.rs for existing patterns using soroban_sdk::testutils).
  • Cross-contract calls should go through a #[contractclient] trait interface (see attestation-registry's AttesterRegistryInterface), not a direct crate dependency on the callee — depending on the whole crate links its contract implementation into your wasm build too.
  • Any pull request (PR) that changes contract behavior, storage schemas, or public function signatures must include a corresponding entry in CHANGELOG.md under the [Unreleased] section. Refer to releasing.md for details.
  • Run make check locally before pushing; it's the same set of checks CI runs.
  • Keep Cargo.lock committed and up to date so builds are reproducible.

Pull Request Process

  1. Fork the repository and create your branch from main.
  2. Ensure your changes compile and pass all quality checks locally (make check).
  3. Fill out the Pull Request Template completely, paying extra attention to the Cross-Repo Impact section if your changes touch shared interfaces.
  4. An admin will review your PR. All checks in CI must pass before merging.