diff --git a/README.md b/README.md index b1b8a26..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,164 +0,0 @@ -# Callora Contracts - -Soroban smart contracts for the Callora API marketplace: prepaid vault (USDC) and balance deduction for pay-per-call settlement. - -[![CI](https://github.com/CalloraOrg/Callora-Contracts/actions/workflows/ci.yml/badge.svg)](https://github.com/CalloraOrg/Callora-Contracts/actions/workflows/ci.yml) -[![Coverage](https://github.com/CalloraOrg/Callora-Contracts/actions/workflows/coverage.yml/badge.svg)](https://github.com/CalloraOrg/Callora-Contracts/actions/workflows/coverage.yml) - -## Tech stack - -- **Rust** with **Soroban SDK** (Stellar) -- Contract compiles to WebAssembly and deploys to Stellar/Soroban -- Minimal WASM size (~17.5KB for vault) - -## What’s included - -### 1. `callora-vault` - -The primary storage and metering contract. - -- `init(owner, usdc_token, ..., authorized_caller, min_deposit, revenue_pool, max_deduct)` — Initialize with owner and optional configuration. -- `deposit(caller, amount)` — Owner or allowed depositor increases ledger balance. -- `deduct(caller, amount, request_id)` — Decrease balance for an API call; routes funds to settlement. -- `batch_deduct(caller, items)` — Atomically process multiple deductions. -- `set_allowed_depositor(caller, depositor)` — Owner-only; delegate deposit rights. -- `set_authorized_caller(caller)` — Owner-only; set the address permitted to trigger deductions. -- `get_price(api_id)` — returns `Option` with the configured price per call for `api_id`. - -## Architecture & Flow - -The following diagram illustrates the interaction between the backend, the user's vault, and the settlement contracts during an API call. - -```mermaid -sequenceDiagram - participant B as Backend/Metering - participant V as CalloraVault - participant S as Settlement/Pool - participant D as Developer Wallet - - Note over B,V: Pricing Resolution - B->>V: get_price(api_id) - V-->>B: price - - Note over B,V: Metering & Deduction - B->>V: deduct(caller, total_amount, request_id) - V->>V: validate balance & auth - - Note over V,S: Fund Movement - V->>S: USDC Transfer (via token contract) - - Note over S,D: Distribution - S->>D: distribute(to, amount) - D-->>S: Transaction Complete -``` - -- `get_meta()` / `balance()` — View configuration and current ledger balance. -- `set_metadata` / `get_metadata` — Attach off-chain metadata (IPFS/URI) to offerings. - -### 2. `callora-revenue-pool` - -A simple distribution contract for revenue. - -- `init(admin, usdc_token)` — Initialize with an admin and USDC token. -- `distribute(caller, to, amount)` — Admin sends USDC from this contract to a developer. -- `batch_distribute(caller, payments)` — Atomically distribute to multiple developers. -- `receive_payment(caller, amount, from_vault)` — Log payment receipt for indexers. - -### 3. `callora-settlement` - -Advanced settlement with individual developer balance tracking. - -- `init(admin, vault_address)` — Link to the vault and set admin. -- `receive_payment(caller, amount, to_pool, developer)` — Receive funds from vault; credit global pool or specific developer. -- `get_developer_balance(developer)` — Check tracked balance for a specific developer. -- `get_global_pool()` — View total accumulated pool balance. -- `set_vault(caller, new_vault)` — Admin-only; update the linked vault address. - -## Local setup - -1. **Prerequisites:** - - [Rust](https://rustup.rs/) (stable) - - [Stellar Soroban CLI](https://developers.stellar.org/docs/smart-contracts/getting-started/setup) (`cargo install soroban-cli`) - -2. **Build and test:** - - ```bash - cargo fmt --all - cargo clippy --all-targets --all-features -- -D warnings - cargo build - cargo test - ``` - -3. **Build WASM:** - - ```bash - # Build all publishable contract crates and verify their release WASM sizes - ./scripts/check-wasm-size.sh - - # Or build a specific contract manually - cargo build --target wasm32-unknown-unknown --release -p callora-vault - ``` - -## Development - -Use one branch per issue or feature. Run `cargo fmt --all`, `cargo clippy --all-targets --all-features -- -D warnings`, `cargo test`, and `./scripts/check-wasm-size.sh` before pushing so every publishable contract stays within Soroban's WASM size limit. - -### Test coverage - -The project enforces a **minimum of 95% line coverage** on every push via GitHub Actions. - -```bash -# Run coverage locally -./scripts/coverage.sh -``` - -## Project layout - -``` -callora-contracts/ -├── .github/workflows/ -│ ├── ci.yml # CI: fmt, clippy, test, WASM build -│ └── coverage.yml # CI: enforces 95% coverage on every push -├── contracts/ -│ ├── vault/ # Primary storage and metering -│ ├── revenue_pool/ # Simple revenue distribution -│ └── settlement/ # Advanced balance tracking -├── scripts/ -│ ├── coverage.sh # Local coverage runner -│ └── check-wasm-size.sh # WASM size verification -├── docs/ -│ ├── interfaces/ # JSON contract interface summaries (vault, settlement, revenue_pool) -│ └── ACCESS_CONTROL.md # Role-based access control overview -├── BENCHMARKS.md # Gas/cost notes -├── EVENT_SCHEMA.md # Event topics and payloads -├── UPGRADE.md # Upgrade and migration path -├── SECURITY.md # Security checklist -└── tarpaulin.toml # cargo-tarpaulin configuration -``` - -## Contract interface summaries - -Machine-readable JSON summaries of every public function and parameter for each contract are maintained under [`docs/interfaces/`](docs/interfaces/). They serve as the canonical reference for backend integrators using `@stellar/stellar-sdk`. - -| File | Contract | -|------|----------| -| [`docs/interfaces/vault.json`](docs/interfaces/vault.json) | `callora-vault` | -| [`docs/interfaces/settlement.json`](docs/interfaces/settlement.json) | `callora-settlement` | -| [`docs/interfaces/revenue_pool.json`](docs/interfaces/revenue_pool.json) | `callora-revenue-pool` | - -See [`docs/interfaces/README.md`](docs/interfaces/README.md) for the schema description and regeneration steps. - -## Security Notes - -- **Checked arithmetic**: All mutations use `checked_add` / `checked_sub`. -- **Input validation**: Enforced `amount > 0` for all deposits and deductions. -- **Overflow checks**: Enabled in both dev and release profiles. -- **Role-Based Access**: Documented in [docs/ACCESS_CONTROL.md](docs/ACCESS_CONTROL.md). - -## Security - -See [SECURITY.md](SECURITY.md) for the Vault Security Checklist and audit recommendations. - ---- - -Part of [Callora](https://github.com/CalloraOrg). diff --git a/contracts/revenue_pool/src/lib.rs b/contracts/revenue_pool/src/lib.rs index 1bc2fc2..e22c4ae 100644 --- a/contracts/revenue_pool/src/lib.rs +++ b/contracts/revenue_pool/src/lib.rs @@ -240,6 +240,7 @@ impl RevenuePool { /// /// # Panics /// * If the caller is not the current admin (`"unauthorized: caller is not admin"`). + /// * If `payments` is empty (`"batch_distribute requires at least one payment"`). /// * If any individual amount is zero or negative (`"amount must be positive"`). /// * If the revenue pool has not been initialized. /// * If the total amount exceeds the contract's available balance (`"insufficient USDC balance"`). @@ -252,6 +253,9 @@ impl RevenuePool { if caller != admin { panic!("{}", ERR_UNAUTHORIZED); } + if payments.is_empty() { + panic!("batch_distribute requires at least one payment"); + } let mut total_amount: i128 = 0; for payment in payments.iter() { diff --git a/contracts/revenue_pool/src/test.rs b/contracts/revenue_pool/src/test.rs index bff99e2..ac3ad36 100644 --- a/contracts/revenue_pool/src/test.rs +++ b/contracts/revenue_pool/src/test.rs @@ -320,7 +320,24 @@ fn batch_distribute_success_events() { } #[test] -fn receive_payment_emits_event_for_admin() { +#[should_panic(expected = "batch_distribute requires at least one payment")] +fn batch_distribute_empty_panics() { + let env = Env::default(); + env.mock_all_auths(); + let admin = Address::generate(&env); + let (pool_addr, client) = create_pool(&env); + let (usdc_address, _, usdc_admin) = create_usdc(&env, &admin); + + client.init(&admin, &usdc_address); + fund_pool(&usdc_admin, &pool_addr, 500); + + let payments: Vec<(Address, i128)> = Vec::new(&env); + client.batch_distribute(&admin, &payments); +} + +#[test] +#[should_panic(expected = "insufficient USDC balance")] +fn batch_distribute_insufficient_balance_panics() { let env = Env::default(); env.mock_all_auths(); let admin = Address::generate(&env); diff --git a/docs/revenue-pool-batch-distribute.md b/docs/revenue-pool-batch-distribute.md new file mode 100644 index 0000000..2d61e39 --- /dev/null +++ b/docs/revenue-pool-batch-distribute.md @@ -0,0 +1,9 @@ +# Revenue Pool Batch Distribute + +`callora-revenue-pool::batch_distribute` rejects an empty `payments` vector with +`"batch_distribute requires at least one payment"`. + +Rationale: +- This matches the vault contract's `batch_deduct` policy for empty batches. +- Admin tooling gets an explicit failure for malformed payout jobs instead of a silent no-op. +- Indexers and operators do not need to infer whether an empty successful transaction was intentional.