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
41 changes: 41 additions & 0 deletions .github/workflows/contracts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Contracts CI

on:
push:
branches: [main]
paths:
- 'contracts/**'
- '.github/workflows/contracts.yml'
pull_request:
branches: [main]
paths:
- 'contracts/**'
- '.github/workflows/contracts.yml'

jobs:
test-and-build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./contracts

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown

- name: Install cargo-binstall
uses: cargo-bins/cargo-binstall@main

- name: Install stellar-cli
run: cargo binstall -y stellar-cli

- name: Test Contracts
run: cargo test

- name: Build & Check Wasm Size Budget
run: bash build-size-check.sh
197 changes: 53 additions & 144 deletions contracts/README.md
Original file line number Diff line number Diff line change
@@ -1,160 +1,69 @@
# Disciplr Smart Contracts

This directory contains Soroban smart contracts for the Disciplr platform.

## Accountability Vault

The `accountability_vault` contract implements time-locked capital vaults on Stellar with milestone-based release conditions.

### Overview

The accountability vault allows users to:
- Lock funds in a vault with a total amount
- Define milestones with individual amounts that must sum to the total
- Specify a verifier authorized to validate milestone completion
- Set success and failure destinations for fund release
- Allow reclaiming residual (dust) token balances to the creator after settlement

### Arithmetic Safety

**Critical Security Feature: Overflow-Safe Amount Summation**

The `create_vault` function implements overflow-safe arithmetic for milestone amount summation to prevent integer overflow attacks and unexpected panics.

#### Implementation Details

- **Location**: `accountability_vault/src/lib.rs` in the `create_vault` function
- **Method**: Uses `checked_add` instead of `+=` for all i128 arithmetic operations
- **Error Handling**: Returns `Error::Overflow` on overflow instead of panicking
- **Invariant**: Maintains `sum == amount` invariant after successful validation

#### Code Example

```rust
// Sum milestone amounts using checked_add to prevent overflow
let mut sum: i128 = 0;
for milestone in milestones.iter() {
// Use checked_add to detect overflow and return typed error instead of panicking
sum = match sum.checked_add(milestone.amount) {
Some(result) => result,
None => {
// Overflow occurred - return typed error instead of panicking
return Err(Error::Overflow);
}
};
}
# Disciplr Soroban Contracts

On-chain programmable, time-locked capital vaults for accountability staking,
the chain-side counterpart to the `disciplr-backend` API and Horizon listener.

## Workspace layout

```text
contracts/
├── Cargo.toml # workspace manifest (soroban-sdk = "23")
├── README.md
└── accountability_vault/
├── Cargo.toml
└── src/
├── lib.rs # AccountabilityVault contract
└── test.rs # unit tests (testutils)
```

#### Why This Matters

1. **Security**: Prevents integer overflow attacks that could bypass amount validation
2. **Reliability**: Returns typed errors instead of panicking, allowing graceful error handling
3. **Predictability**: Ensures the contract behaves consistently even with extreme input values
4. **Auditability**: Clear error types make security reviews easier

#### Test Coverage

The contract includes comprehensive tests for overflow scenarios:
- `test_create_vault_overflow_extreme_amounts`: Tests overflow with multiple large milestones
- `test_create_vault_overflow_single_large_milestone`: Tests overflow with two large milestones
- `test_create_vault_large_valid_amounts`: Verifies large but valid amounts work correctly

All tests ensure that:
- Overflow returns `Error::Overflow` instead of panicking
- Valid large amounts are processed correctly
- The `sum == amount` invariant is maintained

### Error Types

The contract defines the following error types:

- `InvalidAmount`: Negative or zero amounts provided
- `AmountMismatch`: Milestone amounts don't sum to total vault amount
- `Overflow`: Integer overflow occurred during amount summation

### Performance & Gas Benchmarks
## accountability_vault

To ensure predictable scaling and prevent out-of-gas exploits or transaction failures, the contract has built-in performance bounds.
Implements the vault lifecycle that the backend models off-chain in
`src/services/vaultTransitions.ts` and parses events for in
`src/services/eventParser.ts`:

#### Storage Reads & Complexity Analysis
- **Milestone Iteration**: Functions like `claim` and `slash_on_miss` iterate over the `milestones` vector to sum release amounts and check status. CPU and Memory usage scale linearly ($O(N)$) with the milestone count $N$.
- **Flat Storage Access**: The storage layout guarantees flat ($O(1)$) read footprint. There are no redundant storage reads or nested lookups within loops.
- **Gas Bounded Growth**: The CPU and Memory bounds are actively asserted in test suites to catch regressions before deployment.
| Function | Purpose |
|---|---|
| `create_vault` | Create a `Draft` vault with milestones, verifier, and success/failure destinations. Validates amount, deadline, and that milestone amounts sum to the total. |
| `stake` | Creator transfers the SEP-41 token into the contract; `Draft` -> `Active`. |
| `check_in` | Designated verifier confirms a milestone before its `due_date`. |
| `slash_on_miss` | After the deadline with unverified milestones, slash funds to `failure_destination`; `Active` -> `Failed`. |
| `claim` | When all milestones are verified, release funds to `success_destination`; `Active` -> `Completed`. |
| `withdraw` | Cancel/refund an unfunded or unstarted vault to the creator; -> `Cancelled`. |
| `get_vault` | Read-only accessor for the current vault record. |

#### Documented Footprint Thresholds (10 Milestones Baseline)
Using Soroban's native budget tracking (`Env::budget()`), the performance metrics for a representative 10-milestone vault are capped as follows:
The `VaultStatus` enum (`Draft`/`Active`/`Completed`/`Failed`/`Cancelled`)
mirrors `PersistedVault.status` in `src/types/vaults.ts`. Emitted events
(`vault_created`, `vault_staked`, `milestone_checked_in`, `vault_slashed`,
`vault_completed`, `vault_cancelled`, `vault_withdrawn`) align with the topics
consumed by the backend event parser.

| Function | CPU Cost Threshold (Instructions) | Memory Cost Threshold (Bytes) | Storage Read Footprint |
|----------|----------------------------------|-------------------------------|------------------------|
| `create_vault` | < 600,000 | < 200,000 | $O(1)$ Flat |
| `stake` | < 700,000 | < 200,000 | $O(1)$ Flat |
| `check_in` | < 300,000 | < 100,000 | $O(1)$ Flat |
| `claim` | < 900,000 | < 250,000 | $O(1)$ Flat |
| `slash_on_miss`| < 900,000 | < 250,000 | $O(1)$ Flat |

### Building and Testing


#### Prerequisites

- Rust 1.70+ with `wasm32-unknown-unknown` target
- Soroban CLI tools

#### Build
## Build & test

```bash
cd contracts/accountability_vault
cargo build --release --target wasm32-unknown-unknown
```

#### Test

```bash
cd contracts/accountability_vault
# from the contracts/ directory
stellar contract build
cargo test
```

#### Test Coverage

The contract maintains >95% test coverage including:
- Normal vault creation
- Invalid amount validation
- Amount mismatch detection
- Overflow scenarios with extreme values
- Edge cases (empty milestones, zero amounts, negative amounts)

### Deployment

Deploy the contract to Soroban testnet or mainnet using the Soroban CLI:

```bash
soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/accountability_vault.wasm \
--source <your-secret-key> \
--network <network-passphrase>
# Check that the compiled contract stays within the allowed size budget
# Fails if the .wasm artifact exceeds the 100KB budget (configurable via MAX_WASM_SIZE)
bash build-size-check.sh
```

### Security Considerations

1. **Overflow Protection**: All arithmetic operations use checked arithmetic
2. **Input Validation**: All amounts are validated for positivity
3. **Invariant Enforcement**: Milestone amounts must exactly sum to total vault amount
4. **Error Handling**: Typed errors prevent information leakage through panics

### Residual Sweep (reclaim_after_settlement)

The contract exposes `reclaim_after_settlement` to sweep any residual token
balance (dust or rounding remainders) held by the contract back to the vault
creator. Requirements:

- Caller must be the vault `creator` (authorization enforced via `Address::require_auth`).
- The vault must have no staked funds remaining (`amount == 0`).
### Wasm Size Budget Configuration

The function queries the contract's token balance via `TokenClient::balance`
and performs a `TokenClient::transfer` of the full balance to the creator.
To prevent accidental bloat in the smart contract, the `accountability_vault` includes a size budget check (`build-size-check.sh`) integrated into the CI pipeline.
The default limit is set to **100,000 bytes** (~100KB).

Location: `accountability_vault/src/lib.rs` — `Contract::reclaim_after_settlement`
If you need to update this budget as the contract grows:
1. Temporarily increase the budget locally by exporting the variable: `export MAX_WASM_SIZE=150000`
2. Update the default value in `contracts/build-size-check.sh`
3. Push the changes to update the CI limit.

### License
## Backend integration

See main repository license file.
`src/services/soroban.ts` calls `create_vault` via the Stellar SDK
(`@stellar/stellar-sdk` v14). The Horizon listener
(`src/services/horizonListener.ts`) and `src/services/eventParser.ts`
ingest the events emitted by these functions to keep the off-chain vault state
in sync.
Loading
Loading