Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 0 additions & 164 deletions README.md
Original file line number Diff line number Diff line change
@@ -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<i128>` 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).
4 changes: 4 additions & 0 deletions contracts/revenue_pool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"`).
Expand All @@ -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() {
Expand Down
19 changes: 18 additions & 1 deletion contracts/revenue_pool/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions docs/revenue-pool-batch-distribute.md
Original file line number Diff line number Diff line change
@@ -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.