This document describes the high-level architecture of the Crucible project, clarifying crate ownership, module boundaries, and dependency directions to help new contributors understand where functionality belongs.
Crucible is a batteries-included testing toolkit for Soroban smart contracts, analogous to Jest (JavaScript) or Hardhat (Solidity). It combines:
- A core Soroban testing toolkit with builders, helpers, assertion macros, and fixtures
- A proc-macro crate providing derive support
- Example contracts demonstrating testing patterns
- Application contracts (treasury, governance, etc.)
- A backend platform providing performance profiling, analytics, and deployment infrastructure
crucible/
├── contracts/
│ ├── crucible/ [CORE TOOLKIT - Testing Framework]
│ ├── treasury/ [Application Contract - Treasury]
│ ├── governance/ [Application Contract - Governance]
│ ├── insurance/ [Application Contract - Insurance]
│ ├── oracle/ [Application Contract - Oracle]
│ └── supply_chain/ [Application Contract - Supply Chain]
├── crucible-macros/ [Proc-Macro Crate - Derives for toolkit]
├── examples/ [Example Contracts - Demonstrate testing patterns]
│ ├── counter/
│ ├── token/
│ ├── vesting/
│ ├── staking/
│ ├── cross-contract/
│ ├── lending/
│ └── [other examples...]
├── backend/ [API Platform - Analytics & Profiling]
│ ├── src/
│ │ ├── main.rs [Axum HTTP server entry point]
│ │ ├── api/ [HTTP handlers & routes]
│ │ ├── db/ [Database access layer]
│ │ ├── services/ [Business logic]
│ │ ├── workers/ [Background jobs]
│ │ ├── config/ [Configuration management]
│ │ └── utils/ [Utilities & helpers]
│ ├── migrations/ [SQL schema migrations]
│ ├── benches/ [Performance benchmarks]
│ └── tests/ [Integration & E2E tests]
├── src/ [Workspace root (unused)]
├── Cargo.toml [Workspace manifest]
├── CONTRIBUTING.md [Contribution guidelines]
├── ARCHITECTURE.md [This file]
└── README.md [Project overview]
Location: contracts/crucible/
Purpose: A Rust testing library for Soroban smart contracts
Key Concepts:
MockEnv— Fluent builder for the SorobanEnvtest objectMockToken— Pre-built mock Stellar Asset Contract (SAC) tokensAccountBuilder— Helpers for creating pre-funded test accountsassert_emitted!/assert_not_emitted!— Event assertion macros- Fixtures — Re-usable test setup components
- Snapshot testing — State serialization and diffing
Dependencies:
soroban-sdk(withtestutilsfeature) — Official Soroban SDKcrucible-macros— Proc-macro support (via optionalderivefeature)serde/serde_json— Serialization (for snapshots feature)
Public API Entry Point:
pub use crucible::prelude::*;Guidelines:
- ✅ Should remain independent of the backend
- ✅ Can depend on
soroban-sdkand community crates - ❌ Should NOT import from
backendorexamples - ✅ Focuses on test utilities and builders
- ✅ All public APIs exported via
preludemodule
Example Usage:
#[test]
fn test_contract() {
let env = MockEnv::builder()
.with_contract::<MyContract>()
.with_account("alice", Stroops::xlm(1_000))
.build();
let token = MockToken::usdc(&env);
// ... test code
}Location: crucible-macros/
Purpose: Provides derive macros and code generation for the toolkit
Key Features:
- Derive support for fixtures and test helpers
- Code generation for common testing patterns
- Compile-time validation of test structures
Dependencies:
proc-macro2,quote,syn— Standard macro/AST libraries
Guidelines:
- ✅ Only used by crucible and tests
- ✅ Implements code generation for the testing framework
- ❌ Should NOT contain runtime logic
- ❌ Should NOT depend on the backend
Example Usage:
#[derive(Fixture)]
struct MyTestSetup {
env: MockEnv,
contract_id: ContractId,
}Location: backend/
Purpose: High-performance Rust API server providing:
- Performance profiling and analytics dashboards
- Build error analytics
- Audit logging and security monitoring
- Contract deployment tracking
- Background job processing via Redis
Architecture:
Clients (HTTP)
↓
Axum HTTP Server (async/await)
├→ Middleware: CORS, Tracing, Compression, Request ID
├→ API Routes: /api/v1/*, /health/*, /metrics
├→ Error Handling: Structured AppError responses
└→ Database & Cache Layers
├→ PostgreSQL (via SQLx, compile-time checked)
├→ Redis (via redis-rs)
└→ Worker Queue (via Apalis)
Key Modules:
api/handlers/— HTTP endpoint handlersdb/— Database queries and modelsservices/— Business logic (alerts, error analytics, audit logs)workers/— Background job handlersconfig/— Configuration and environment managementtelemetry/— Tracing and observability
API Endpoints:
- Health checks:
GET /health/live,GET /health/ready - Metrics:
GET /metrics(Prometheus format) - Alerts:
GET|POST /api/alerts/rules,POST /api/alerts/ingest - Audit:
GET|POST /api/v1/audit/* - Errors:
GET /api/v1/errors/dashboard/*
Dependencies:
axum— Web frameworktokio— Async runtimesqlx— PostgreSQL driver (compile-time checked queries)redis— Caching and job queuesoroban-sdk— For contract type definitions and serializationtracing+opentelemetry— Observability
Guidelines:
- ✅ Independent service with its own database and API
- ✅ Can use
soroban-sdkfor contract types (not testing) - ❌ Should NOT depend on crucible or examples
- ✅ Focuses on operational metrics and analytics
- ✅ Can be deployed and scaled independently
Example Handler:
#[get("/api/v1/errors/dashboard/build-errors")]
async fn get_build_error_analytics(
State(db): State<PgPool>,
State(redis): State<RedisPool>,
) -> Result<Json<BuildErrorMetrics>> {
// Fetch from cache or database
}Location: examples/*/
Purpose: Reference implementations of Soroban contracts with comprehensive tests
Key Examples:
counter/— Simple counter contracttoken/— Token contract with transfer logicvesting/— Time-locked fund releasestaking/— Staking and rewards mechanismscross-contract/— Inter-contract communication patternslending/— DeFi lending protocolmultisig/— Multi-signature walletnft-marketplace/— NFT tradingprediction-market/— Prediction marketdex/— Decentralized exchange
Test Pattern:
#[cfg(test)]
mod tests {
use crucible::prelude::*;
use crate::{MyContract, MyContractClient};
#[test]
fn test_example() {
let env = MockEnv::builder()
.with_contract::<MyContract>()
.build();
let client = MyContractClient::new(&env.inner(), &env.contract_id::<MyContract>());
// ... test assertions
}
}Guidelines:
- ✅ Only depend on crucible (for testing)
- ✅ Demonstrate testing best practices
- ✅ Serve as templates for new contracts
- ❌ Should NOT depend on backend
- ✅ Each example is self-contained
Dependencies:
soroban-sdk— Soroban programming frameworkcrucible(dev-dependency) — Testing framework
Location: contracts/[treasury|governance|insurance|oracle|supply_chain]/
Purpose: Production smart contracts using the Soroban SDK
Examples:
treasury/— Treasury management and fund allocationgovernance/— Governance and voting mechanismsinsurance/— Insurance fund and claim processingoracle/— Price feeds and data oraclesupply_chain/— Supply chain tracking
Testing Pattern:
#[cfg(test)]
mod tests {
use crucible::prelude::*;
use crate::{TreasuryContract, TreasuryContractClient};
#[test]
fn test_allocation() {
let env = MockEnv::builder()
.with_contract::<TreasuryContract>()
.with_token("USDC", 6)
.build();
// ... test
}
}Guidelines:
- ✅ Use crucible for comprehensive testing
- ✅ Follow testing patterns from examples
- ✅ Can have integration with other contracts
- ❌ Should NOT depend on backend (backend may query them)
┌─────────────────────────────────────────────────────────────────┐
│ soroban-sdk (base) │
│ (official Stellar library) │
└─────────────────────────────────────────────────────────────────┘
↓ ↓
┌──────────────────────┐ ┌──────────────────────┐
│ crucible-macros │ │ Examples (Rust) │
│ (proc macros only) │ │ Example Contracts │
└──────────────────────┘ └──────────────────────┘
↓ ↓
┌──────────────────────┐ ┌──────────────────────┐
│ crucible (toolkit) │←───│ Example Tests ✓ │
│ ├─ MockEnv │ │ Use MockEnv, etc. │
│ ├─ MockToken │ └──────────────────────┘
│ ├─ Fixtures │
│ └─ Assertions │
└──────────────────────┘
↓
┌──────────────────────┐
│ App Contracts (Rust) │ ┌──────────────────────┐
│ ├─ Treasury │ │ backend (Axum) │
│ ├─ Governance │ │ ├─ PostgreSQL │
│ ├─ Insurance │ │ ├─ Redis │
│ ├─ Oracle │ │ ├─ API Handlers │
│ └─ Supply Chain │ │ ├─ Workers │
│ ↓ │ │ └─ Telemetry │
│ Contract Tests ✓ │ └──────────────────────┘
│ Use crucible │
└──────────────────────┘
| From | To | Allowed? | Reason |
|---|---|---|---|
examples |
crucible |
✅ Yes | Examples demonstrate toolkit usage |
crucible |
examples |
❌ No | Would create circular dependency |
app-contracts |
crucible |
✅ Yes (test only) | Dev-dependency for testing |
crucible |
backend |
❌ No | Toolkit must be independent |
backend |
crucible |
❌ No | Backend is separate service |
examples |
backend |
❌ No | Examples are standalone |
crucible-macros |
crucible |
✅ Yes | Macros provide derives for toolkit |
crucible |
crucible-macros |
✅ Yes (optional) | Optional feature for derives |
backend |
soroban-sdk |
✅ Yes | For contract types (not testing) |
┌──────────────────────────────────────────────────────────┐
│ Test Execution Flow │
├──────────────────────────────────────────────────────────┤
│ │
│ 1. Setup Phase │
│ ├─ MockEnv::builder() │
│ │ └─ Creates test Soroban Env with defaults │
│ ├─ .with_contract::<MyContract>() │
│ │ └─ Registers contract in test environment │
│ ├─ .with_account("alice", Stroops::xlm(1_000)) │
│ │ └─ Creates funded test account │
│ ├─ .with_token("USDC", 6) │
│ │ └─ Deploys MockToken SAC │
│ └─ .build() │
│ └─ Returns MockEnv instance ready for tests │
│ │
│ 2. Fixture Assembly (Optional) │
│ ├─ Define reusable test setup struct: │
│ │ struct ContractFixture { │
│ │ env: MockEnv, │
│ │ contract_id: ContractId, │
│ │ alice_account: Address, │
│ │ } │
│ └─ Instantiate with builder output │
│ │
│ 3. Execution Phase │
│ ├─ Create contract client: │
│ │ let client = MyContractClient::new( │
│ │ &env.inner(), &contract_id │
│ │ ) │
│ ├─ Invoke contract methods: │
│ │ let result = client.transfer( │
│ │ &from, &to, &amount │
│ │ ) │
│ └─ Contract executes in test env │
│ │
│ 4. Assertion Phase │
│ ├─ assert_emitted!(env, │
│ │ topics: ("transfer"), │
│ │ data: (from, to, amount) │
│ │ ) — Pattern-match emitted events │
│ ├─ assert_not_emitted!(env, ...) │
│ │ — Verify events that must NOT occur │
│ ├─ assert_eq!(result, expected) │
│ │ — Standard Rust assertions │
│ └─ Assertions fail fast on mismatch │
│ │
│ 5. Cleanup & State Restoration │
│ └─ Test isolation: each test gets fresh MockEnv │
│ No shared state between tests │
│ │
└──────────────────────────────────────────────────────────┘
let env = MockEnv::builder() // 1. Create builder
.at_sequence(1_000) // 2. Set ledger height
.at_timestamp(1_700_000_000) // 3. Set ledger time
.with_contract::<MyContract>() // 4. Register contract
.with_account("alice", stroops) // 5. Create account
.with_token("USDC", 6) // 6. Deploy SAC token
.track_costs() // 7. Enable cost tracking
.build(); // 8. Finalize → MockEnv
// Then use:
env.contract_id::<MyContract>() // Get registered contract ID
env.account("alice") // Get account by name
env.advance_time(Duration::days(7)) // Jump forward in time
env.measure(|| { /* invocation */ }) // Measure instruction cost
assert_emitted!(env, ...) // Assert events// XLM token (native)
let xlm = MockToken::xlm(&env);
// Standard issued assets
let usdc = MockToken::issued_asset(&env, "USDC", 6);
let eurc = MockToken::issued_asset(&env, "EURC", 6);
// Token operations
xlm.mint(&alice, 1_000_000); // Mint tokens to account
xlm.transfer(&alice, &bob, 50_000); // Transfer between accounts
xlm.approve(&alice, &contract, 100_000); // Approve contract spend
let balance = xlm.balance(&alice); // Check account balance// Define once
#[derive(Fixture)]
struct DexFixture {
env: MockEnv,
pool_id: ContractId,
token_a: MockToken,
token_b: MockToken,
alice: Address,
bob: Address,
}
// Use in multiple tests
#[test]
fn test_swap() {
let fix = DexFixture::setup();
// execute swap
}
#[test]
fn test_liquidity_provision() {
let fix = DexFixture::setup();
// add liquidity
}
#[test]
fn test_fee_accrual() {
let fix = DexFixture::setup();
// verify fees
}Is this code about testing Soroban contracts?
├─ YES → Does it help write cleaner tests?
│ ├─ YES → Is it a builder, assertion macro, or fixture?
│ │ └─ YES → Add to `crucible/src/lib.rs` 📍
│ │ └─ NO → Add to `examples/` as pattern 📍
│ └─ NO → This is a contract example
│ └─ Add to `examples/name/src/lib.rs` 📍
└─ NO → Is this business logic for a Soroban contract?
├─ YES → Add to `contracts/[app]/src/lib.rs` 📍
└─ NO → Is this backend API/analytics functionality?
└─ YES → Add to `backend/src/` 📍
| Scenario | Location | Reason |
|---|---|---|
| New assertion macro for contract events | crucible/src/assertions.rs |
Toolkit feature; used by all tests |
| Example: Voting contract | examples/voting/src/lib.rs |
Demonstrates testing patterns |
| Tests for voting example | examples/voting/src/tests.rs |
Use crucible's MockEnv, MockToken, etc. |
| Treasury contract tests | contracts/treasury/src/tests.rs |
Use crucible (dev-dependency) |
| API endpoint for build stats | backend/src/api/handlers/metrics.rs |
Operational analytics |
| New derive macro for fixtures | crucible-macros/src/lib.rs |
Proc-macro support for toolkit |
| Ledger time manipulation helper | crucible/src/time_helpers.rs |
Test utility for all contracts |
Public Modules:
pub mod prelude; // Re-exports all common types/macros
pub mod builders; // MockEnvBuilder, AccountBuilder
pub mod tokens; // MockToken, TokenOperations
pub mod fixtures; // Fixture trait and derives
pub mod assertions; // assert_emitted!, assert_not_emitted!
pub mod time; // Time manipulation helpers
pub mod cost; // Instruction/gas measurement
pub mod snapshots; // Snapshot testing (feature-gated)Private Modules:
mod utils; // Internal helpers (not re-exported)
mod errors; // Internal error handlingPrelude (Primary Entry Point):
pub use crate::builders::{MockEnv, MockEnvBuilder, AccountBuilder};
pub use crate::tokens::MockToken;
pub use crate::fixtures::Fixture;
pub use crate::assertions::{assert_emitted, assert_not_emitted};
pub use crate::time::*;
pub use crate::cost::*;Public API Modules:
pub mod api; // HTTP handlers
pub mod db; // Database layer
pub mod config; // Configuration
pub mod telemetry; // Tracing setupImplementation Modules:
mod services; // Business logic
mod workers; // Job handlers
mod error; // Error types
mod utils; // Helpers-
Import from
preludeonly in test filesuse crucible::prelude::*; // ✅ Stable imports
-
Don't import internal modules in production code
use crucible::utils::internal_helper; // ❌ Avoid this
-
Use feature flags for optional dependencies
[features] snapshots = ["serde", "serde_json"]
-
Backend should not know about test utilities
// backend/src/lib.rs use soroban_sdk; // ✅ Types only use crucible; // ❌ Never do this
-
Examples are templates, not library code
// examples/ can depend on crucible (for testing) // but examples should NOT be imported by other crates
-
Contract tests use dev-dependencies
[dev-dependencies] crucible = { path = "../../contracts/crucible" }
| Anti-Pattern | Problem | Solution |
|---|---|---|
use crucible::utils::* in app code |
Relies on internals; breaks on refactor | Use prelude only |
backend depends on examples |
Tight coupling; examples change frequently | Backend should be independent |
crucible depends on backend |
Circular; toolkit can't be used standalone | One-way dependency only |
| Examples import from other examples | Code duplication; hard to maintain | Share via crucible toolkit |
Contract tests use backend utilities |
Tests depend on infrastructure | Use only crucible for testing |
When adding new code, verify:
- Correct location? Used decision tree in "Where New Functionality Belongs"
- No accidental coupling? Checked dependency rules in "Dependency Graph"
- Using public APIs? Imported from
prelude, not internal modules - Test coverage? Added tests in appropriate location
- Documentation? Added module-level docs explaining the feature
- Backwards compatible? Won't break existing code
- Feature-gated? Optional dependencies use Cargo features
- No circular imports? Verified with
cargo check
| Code Type | Location | Example |
|---|---|---|
| Test framework helpers | crucible/src/ |
builders.rs, tokens.rs |
| Assertion macros | crucible/src/assertions.rs |
assert_emitted! |
| Derive macros | crucible-macros/src/ |
#[derive(Fixture)] |
| Example contract | examples/name/src/lib.rs |
Counter, Token |
| Example tests | examples/name/src/tests.rs |
Uses MockEnv from crucible |
| App contract | contracts/app/src/lib.rs |
Treasury, Governance |
| App contract tests | contracts/app/src/tests.rs |
Uses MockEnv from crucible |
| API endpoint | backend/src/api/handlers/ |
/api/v1/metrics, /health |
| Database queries | backend/src/db/ |
Query builders, migrations |
| Background jobs | backend/src/workers/ |
Async task handlers |
| Configuration | backend/src/config/ |
Environment, settings |
# Run all toolkit tests
cargo test -p crucible
# Run specific example tests
cargo test -p crucible-example-counter
# Run app contract tests
cargo test -p treasury
# Run backend tests
cargo test -p backend- Determine ownership using decision tree above
- Add implementation in correct location
- Add tests (unit or integration as appropriate)
- Update docs (module comments, examples)
- Verify dependencies with
cargo check - Test in examples to ensure no coupling
Verify:
- Code is in the right location
- No accidental cross-crate dependencies
- Tests are comprehensive
- Public API additions are in
prelude - No breaking changes to existing interfaces
| Term | Definition |
|---|---|
| MockEnv | Test environment builder that wraps soroban_sdk::Env |
| MockToken | Pre-built Stellar Asset Contract for testing |
| Fixture | Reusable test setup; can be derived with #[derive(Fixture)] |
| assert_emitted! | Macro to verify contract events were emitted |
| SAC | Stellar Asset Contract; standardized contract for managing assets |
| Proc-macro | Compile-time code generation (in crucible-macros) |
| prelude | Re-export module with commonly used types and macros |
| Stroops | Smallest unit of XLM (1 XLM = 10,000,000 stroops) |
| Ledger | Soroban's persistent storage; includes time, sequence number, state |
For questions about this architecture:
- Check this document and the decision trees
- Ask in project discussions
- Review existing examples (
examples/*/) - Refer to the Soroban SDK documentation
Last Updated: June 2026