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
23 changes: 17 additions & 6 deletions .github/workflows/soroban-contracts-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ env:
RUN_TESTNET_IT: ${{ secrets.RUN_TESTNET_IT || 'false' }}

jobs:
commitment-interface-drift:
name: Commitment Interface Drift Check
runs-on: ubuntu-latest

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

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Run commitment interface drift tests
run: |
cargo test -p commitment_interface
echo "✓ Commitment interface drift checks passed"

build-and-test:
name: Build and Test Soroban Contracts
runs-on: macos-latest
Expand All @@ -35,9 +51,9 @@ jobs:
run: |
brew tap stellar/stellar-cli 2>/dev/null || true
brew install stellar-cli 2>/dev/null || brew install stellar 2>/dev/null || true
continue-on-error: true
brew tap stellar/tap || true
brew install stellar-cli || brew install stellar
continue-on-error: true

- name: Verify Stellar CLI installation
run: |
Expand Down Expand Up @@ -100,11 +116,6 @@ jobs:
cargo test --workspace
echo "✓ Unit tests passed"

- name: Drift check commitment interface
run: |
cargo test -p commitment_interface
echo "✓ Commitment interface drift checks passed"

- name: Run integration tests
run: |
# Run integration tests from their own directory (separate from workspace)
Expand Down
30 changes: 29 additions & 1 deletion contracts/commitment_interface/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

This guide documents the interface-only ABI exported by `commitment_interface`.
As of interface version `2`, it mirrors the live `commitment_core` commitment
schema so downstream bindings can detect drift before deployment.
schema, key read-only getters, and event payload types so downstream bindings
can detect drift before deployment.

---

Expand All @@ -23,8 +24,13 @@ commitment contracts on the Soroban network.
| `initialize` | `env: Env, admin: Address, nft_contract: Address` | `Result<(), Error>` | Initializes admin and linked NFT contract. |
| `create_commitment` | `env: Env, owner: Address, amount: i128, asset_address: Address, rules: CommitmentRules` | `Result<String, Error>` | Creates a commitment and returns its string id. |
| `get_commitment` | `env: Env, commitment_id: String` | `Result<Commitment, Error>` | Fetches the full live commitment record. |
| `list_commitments_by_owner` | `env: Env, owner: Address` | `Result<Vec<String>, Error>` | Alias for owner-indexed commitment lookup used by UIs and indexers. |
| `get_owner_commitments` | `env: Env, owner: Address` | `Result<Vec<String>, Error>` | Lists commitment ids owned by an address. |
| `get_total_commitments` | `env: Env` | `Result<u64, Error>` | Reads the global commitment counter. |
| `get_total_value_locked` | `env: Env` | `Result<i128, Error>` | Reads total value locked across active commitments. |
| `get_commitments_created_between` | `env: Env, from_ts: u64, to_ts: u64` | `Result<Vec<String>, Error>` | Reads commitment ids created in an inclusive time range. |
| `get_admin` | `env: Env` | `Result<Address, Error>` | Reads the configured core-contract admin. |
| `get_nft_contract` | `env: Env` | `Result<Address, Error>` | Reads the linked NFT contract address. |
| `settle` | `env: Env, commitment_id: String` | `Result<(), Error>` | Settles an expired commitment. |
| `early_exit` | `env: Env, commitment_id: String, caller: Address` | `Result<(), Error>` | Exits an active commitment early. |

Expand Down Expand Up @@ -52,6 +58,23 @@ pub struct Commitment {
pub current_value: i128,
pub status: String,
}

pub struct CommitmentCreatedEvent {
pub commitment_id: String,
pub owner: Address,
pub amount: i128,
pub asset_address: Address,
pub nft_token_id: u32,
pub rules: CommitmentRules,
pub timestamp: u64,
}

pub struct CommitmentSettledEvent {
pub commitment_id: String,
pub owner: Address,
pub settlement_amount: i128,
pub timestamp: u64,
}
```

---
Expand Down Expand Up @@ -117,6 +140,11 @@ To keep the interface aligned with live contracts:
cargo test -p commitment_interface
```

These tests compare source-defined `CommitmentRules`, `Commitment`,
`CommitmentCreatedEvent`, and `CommitmentSettledEvent` structs against
`commitment_core`, and verify the live core contract still exports the
expected public signatures mirrored by this ABI crate.

3. Build WASM if bindings need regeneration:
```bash
stellar contract build
Expand Down
99 changes: 93 additions & 6 deletions contracts/commitment_interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ pub mod types;
use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env, String, Symbol, Vec};

use crate::error::Error;
use crate::types::{Commitment, CommitmentRules};
pub use crate::types::{
Commitment, CommitmentCreatedEvent, CommitmentRules, CommitmentSettledEvent,
};

/// =======================
/// Interface Metadata
Expand Down Expand Up @@ -86,6 +88,15 @@ impl CommitmentInterface {
unimplemented!("interface only")
}

/// List commitment ids owned by the supplied address.
///
/// # Security
/// This is a read-only view into ownership-indexed storage in the live
/// contract. No authorization is required because it does not mutate state.
pub fn list_commitments_by_owner(_env: Env, _owner: Address) -> Result<Vec<String>, Error> {
unimplemented!("interface only")
}

/// List commitment ids owned by the supplied address.
pub fn get_owner_commitments(_env: Env, _owner: Address) -> Result<Vec<String>, Error> {
unimplemented!("interface only")
Expand All @@ -96,6 +107,34 @@ impl CommitmentInterface {
unimplemented!("interface only")
}

/// Return the aggregate value locked across active commitments.
///
/// # Security
/// Live implementations derive this from mutable storage updated during
/// create, value-update, settle, and early-exit flows.
pub fn get_total_value_locked(_env: Env) -> Result<i128, Error> {
unimplemented!("interface only")
}

/// Return commitment ids created between two timestamps, inclusive.
pub fn get_commitments_created_between(
_env: Env,
_from_ts: u64,
_to_ts: u64,
) -> Result<Vec<String>, Error> {
unimplemented!("interface only")
}

/// Return the configured admin for the live commitment core contract.
pub fn get_admin(_env: Env) -> Result<Address, Error> {
unimplemented!("interface only")
}

/// Return the linked commitment NFT contract address.
pub fn get_nft_contract(_env: Env) -> Result<Address, Error> {
unimplemented!("interface only")
}

/// Settle an expired commitment.
///
/// # Security
Expand All @@ -118,7 +157,10 @@ impl CommitmentInterface {
#[cfg(test)]
mod tests {
use super::INTERFACE_VERSION;
use alloc::{string::{String, ToString}, vec::Vec};
use alloc::{
string::{String, ToString},
vec::Vec,
};

const INTERFACE_TYPES: &str = include_str!("types.rs");
const CORE_SOURCE: &str = include_str!("../../commitment_core/src/lib.rs");
Expand Down Expand Up @@ -173,16 +215,25 @@ mod tests {
#[test]
fn commitment_rules_source_matches_commitment_core() {
assert_eq!(
normalize(&extract_block(INTERFACE_TYPES, "pub struct CommitmentRules {")),
normalize(&extract_block(
INTERFACE_TYPES,
"pub struct CommitmentRules {"
)),
normalize(&extract_block(CORE_SOURCE, "pub struct CommitmentRules {"))
);
}

#[test]
fn commitment_rules_source_matches_attestation_engine() {
assert_eq!(
normalize(&extract_block(INTERFACE_TYPES, "pub struct CommitmentRules {")),
normalize(&extract_block(ATTESTATION_SOURCE, "pub struct CommitmentRules {"))
normalize(&extract_block(
INTERFACE_TYPES,
"pub struct CommitmentRules {"
)),
normalize(&extract_block(
ATTESTATION_SOURCE,
"pub struct CommitmentRules {"
))
);
}

Expand All @@ -198,7 +249,38 @@ mod tests {
fn commitment_source_matches_attestation_engine() {
assert_eq!(
normalize(&extract_block(INTERFACE_TYPES, "pub struct Commitment {")),
normalize(&extract_block(ATTESTATION_SOURCE, "pub struct Commitment {"))
normalize(&extract_block(
ATTESTATION_SOURCE,
"pub struct Commitment {"
))
);
}

#[test]
fn created_event_source_matches_commitment_core() {
assert_eq!(
normalize(&extract_block(
INTERFACE_TYPES,
"pub struct CommitmentCreatedEvent {"
)),
normalize(&extract_block(
CORE_SOURCE,
"pub struct CommitmentCreatedEvent {"
))
);
}

#[test]
fn settled_event_source_matches_commitment_core() {
assert_eq!(
normalize(&extract_block(
INTERFACE_TYPES,
"pub struct CommitmentSettledEvent {"
)),
normalize(&extract_block(
CORE_SOURCE,
"pub struct CommitmentSettledEvent {"
))
);
}

Expand All @@ -210,8 +292,13 @@ mod tests {
"pub fn initialize(e: Env, admin: Address, nft_contract: Address)",
"pub fn create_commitment( e: Env, owner: Address, amount: i128, asset_address: Address, rules: CommitmentRules, ) -> String",
"pub fn get_commitment(e: Env, commitment_id: String) -> Commitment",
"pub fn list_commitments_by_owner(e: Env, owner: Address) -> Vec<String>",
"pub fn get_owner_commitments(e: Env, owner: Address) -> Vec<String>",
"pub fn get_total_commitments(e: Env) -> u64",
"pub fn get_total_value_locked(e: Env) -> i128",
"pub fn get_commitments_created_between(e: Env, from_ts: u64, to_ts: u64) -> Vec<String>",
"pub fn get_admin(e: Env) -> Address",
"pub fn get_nft_contract(e: Env) -> Address",
"pub fn settle(e: Env, commitment_id: String)",
"pub fn early_exit(e: Env, commitment_id: String, caller: Address)",
] {
Expand Down
43 changes: 43 additions & 0 deletions contracts/commitment_interface/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,46 @@ pub struct Commitment {
/// Lifecycle status such as `active`, `settled`, `violated`, or `early_exit`.
pub status: String,
}

/// Event payload emitted by the live core contract when a commitment is created.
///
/// # Security
/// Consumers should treat this as observational data only. The authoritative
/// state still lives in `get_commitment`, because later lifecycle transitions
/// may change `current_value` or `status`.
#[derive(Clone, Debug, Eq, PartialEq)]
#[contracttype]
pub struct CommitmentCreatedEvent {
/// Unique on-chain identifier such as `c_0`.
pub commitment_id: String,
/// Commitment owner.
pub owner: Address,
/// Initial committed amount.
pub amount: i128,
/// Token contract address for the committed asset.
pub asset_address: Address,
/// Associated NFT token id minted by `commitment_nft`.
pub nft_token_id: u32,
/// Policy and risk settings fixed at creation time.
pub rules: CommitmentRules,
/// Ledger timestamp when the event was emitted.
pub timestamp: u64,
}

/// Event payload emitted by the live core contract when a commitment is settled.
///
/// # Security
/// This payload reflects a completed state transition that also performs
/// token/NFT cross-contract interactions in the live contract.
#[derive(Clone, Debug, Eq, PartialEq)]
#[contracttype]
pub struct CommitmentSettledEvent {
/// Unique on-chain identifier such as `c_0`.
pub commitment_id: String,
/// Commitment owner receiving the settlement.
pub owner: Address,
/// Amount returned to the owner on settlement.
pub settlement_amount: i128,
/// Ledger timestamp when the event was emitted.
pub timestamp: u64,
}
14 changes: 10 additions & 4 deletions docs/CONTRACT_FUNCTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,24 @@ This document summarizes public entry points for each contract and their access

## commitment_interface

`commitment_interface` is an ABI-only crate. It should mirror the live
`commitment_core` commitment schema and a narrow set of production entrypoints.
CI drift tests compare its source-defined types and expected signatures against
`commitment_core` and `attestation_engine`.
`commitment_interface` is an ABI-only crate. It mirrors the live
`commitment_core` commitment schema, event payloads, and the core read-only
entrypoints that downstream bindings commonly consume. CI drift checks compare
its source-defined types and expected signatures against `commitment_core` and
`attestation_engine`.

| Function | Summary | Access control | Notes |
| ------------------------------------------------------------------- | -------------------------------------------- | ------------------------- | ------------------------------------------------------------------------ |
| initialize(admin, nft_contract) -> Result | Initialize admin and linked NFT contract. | Interface only. | Live core contract is single-use; no state exists in this crate. |
| create_commitment(owner, amount, asset_address, rules) -> Result<String> | Create a commitment and return string id. | Interface only. | Mirrors live `commitment_core` types, including `CommitmentRules`. |
| get_commitment(commitment_id) -> Result<Commitment> | Fetch the canonical commitment record. | View in live contract. | `Commitment` shape is drift-checked against `commitment_core`. |
| list_commitments_by_owner(owner) -> Result<Vec<String>> | Alias for owner-indexed commitment lookup. | View in live contract. | Mirrors the live helper exposed by `commitment_core`. |
| get_owner_commitments(owner) -> Result<Vec<String>> | List commitment ids owned by an address. | View in live contract. | Used by UIs and indexers. |
| get_total_commitments() -> Result<u64> | Read the total commitment counter. | View in live contract. | Counter is stored by the live core contract. |
| get_total_value_locked() -> Result<i128> | Read aggregate active TVL. | View in live contract. | Derived from mutable storage in create/update/settle/exit paths. |
| get_commitments_created_between(from_ts, to_ts) -> Result<Vec<String>> | Read commitment ids created in a time range. | View in live contract. | Useful for analytics and indexers. |
| get_admin() -> Result<Address> | Read configured admin. | View in live contract. | Panics in live core if contract is not initialized. |
| get_nft_contract() -> Result<Address> | Read linked NFT contract address. | View in live contract. | Panics in live core if contract is not initialized. |
| settle(commitment_id) -> Result | Settle an expired commitment. | Mutating in live contract | Live implementation performs token and NFT cross-contract interactions. |
| early_exit(commitment_id, caller) -> Result | Exit a commitment early with penalty logic. | Mutating in live contract | Live implementation must enforce caller auth and overflow-safe math. |

Expand Down
Loading