Skip to content
Merged
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
11 changes: 2 additions & 9 deletions contracts/.gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,2 @@
# Rust's output directory
target

# Local settings
.soroban
.stellar

# Generated test snapshots
test_snapshots
/target/
Cargo.lock
163 changes: 106 additions & 57 deletions contracts/README.md
Original file line number Diff line number Diff line change
@@ -1,75 +1,124 @@
# 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
├── Makefile
└── src/
├── lib.rs # AccountabilityVault contract
└── test.rs # unit tests (testutils)
# 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

### 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);
}
};
}
```

## accountability_vault
#### 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

Implements the vault lifecycle that the backend models off-chain in
`src/services/vaultTransitions.ts` and parses events for in
`src/services/eventParser.ts`:
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

| 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. |
All tests ensure that:
- Overflow returns `Error::Overflow` instead of panicking
- Valid large amounts are processed correctly
- The `sum == amount` invariant is maintained

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.
### Error Types

## Build & test
The contract defines the following error types:

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

### Building and Testing

#### Prerequisites

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

#### Build

```bash
make build
make test
make fmt
make clippy
cd contracts/accountability_vault
cargo build --release --target wasm32-unknown-unknown
```

Available targets:
#### Test

| Target | Command |
|---|---|
| `make build` | `stellar contract build` |
| `make test` | `cargo test` |
| `make fmt` | `cargo fmt -- --check` |
| `make clippy` | `cargo clippy -- -D warnings` |
```bash
cd contracts/accountability_vault
cargo test
```

You can also run the commands manually from the `contracts/` directory:
#### 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
stellar contract build
cargo test
soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/accountability_vault.wasm \
--source <your-secret-key> \
--network <network-passphrase>
```

## Backend integration
### 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

### License

`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.
See main repository license file.
16 changes: 10 additions & 6 deletions contracts/accountability_vault/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
[package]
name = "accountability_vault"
version = "0.0.0"
version = "0.1.0"
edition = "2021"
publish = false

[lib]
crate-type = ["lib", "cdylib"]
doctest = false
crate-type = ["cdylib", "rlib"]

[dependencies]
soroban-sdk = { workspace = true }
soroban-sdk = "21.0.0"

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
soroban-sdk = { version = "21.0.0", features = ["testutils"] }

[profile.release]
opt-level = "z"
overflow-checks = true
lto = true
codegen-units = 1
Loading