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
15 changes: 15 additions & 0 deletions DOCUMENTATION_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@

## 🛠️ Component Documentation

### sanctifier-core: Parser + Contract Discovery

**[tooling/sanctifier-core/src/parser.rs](tooling/sanctifier-core/src/parser.rs)** — shared source parsing with input validation
- `parse_source(source: &str) -> Result<ParsedSource, ParseError>` — single canonical entry point for all rules
- Runs all guards from `input_validation` (size, null bytes) before any AST work
- Returns `ParseError::Validation` or `ParseError::Syntax` on failure

**[tooling/sanctifier-core/src/contract_discovery.rs](tooling/sanctifier-core/src/contract_discovery.rs)** — Soroban contract discovery
- `discover_contracts(file: &syn::File) -> Vec<DiscoveredContract>` — maps `#[contract]` structs to their `#[contractimpl]` blocks
- Collects public functions per contract and `#[contracttype]` storage types
- `DiscoveredFunction.is_reserved` flags `__constructor` / `__check_auth` entry-points
- `DiscoveredContract::public_functions()` iterator excludes reserved entry-points
- Unit tests in each module; integration tests in `tests/parser_contract_discovery_test.rs`
- Fixtures: `tests/fixtures/minimal_contract.rs`, `multi_contract.rs`, `contract_with_storage_types.rs`

### Contract ABI / Interface Reference
**[docs/contract-interfaces.md](docs/contract-interfaces.md)** - Public ABI for all contracts in `contracts/*`
- Function signatures and descriptions for every contract
Expand Down
55 changes: 55 additions & 0 deletions tests/fixtures/contract_with_storage_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#![no_std]

use soroban_sdk::{contract, contractimpl, contracttype, Address, Env};

// ── Storage layout ─────────────────────────────────────────────────────────────

#[contracttype]
pub enum DataKey {
Admin,
Counter,
User(Address),
}

#[contracttype]
pub struct Config {
pub max_value: u64,
pub owner: Address,
}

// ── Contract ───────────────────────────────────────────────────────────────────

#[contract]
pub struct Registry;

#[contractimpl]
impl Registry {
/// Reserved constructor — sets up initial state.
pub fn __constructor(env: Env, admin: Address, max_value: u64) {
env.storage().instance().set(&DataKey::Admin, &admin);
env.storage().instance().set(&DataKey::Counter, &0u64);
env.storage().instance().set(
&DataKey::Admin,
&Config {
max_value,
owner: admin,
},
);
}

pub fn increment(env: Env, caller: Address) -> u64 {
caller.require_auth();
let count: u64 = env.storage().instance().get(&DataKey::Counter).unwrap_or(0);
let new_count = count.checked_add(1).expect("counter overflow");
env.storage().instance().set(&DataKey::Counter, &new_count);
new_count
}

pub fn get_count(env: Env) -> u64 {
env.storage().instance().get(&DataKey::Counter).unwrap_or(0)
}

pub fn admin(env: Env) -> Address {
env.storage().instance().get(&DataKey::Admin).unwrap()
}
}
14 changes: 14 additions & 0 deletions tests/fixtures/minimal_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#![no_std]

use soroban_sdk::{contract, contractimpl, Env};

/// The smallest valid Soroban contract — one public function, no storage, no auth.
#[contract]
pub struct MinimalContract;

#[contractimpl]
impl MinimalContract {
pub fn ping(_env: Env) -> u32 {
42
}
}
68 changes: 68 additions & 0 deletions tests/fixtures/multi_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#![no_std]

use soroban_sdk::{contract, contractimpl, contracttype, Address, Env};

// ── Shared storage types ───────────────────────────────────────────────────────

#[contracttype]
pub enum TokenKey {
Balance(Address),
Admin,
}

#[contracttype]
pub enum VaultKey {
Depositor(Address),
TotalDeposits,
}

// ── Contract A: simple token ──────────────────────────────────────────────────

#[contract]
pub struct TokenA;

#[contractimpl]
impl TokenA {
pub fn initialize(env: Env, admin: Address) {
admin.require_auth();
env.storage().instance().set(&TokenKey::Admin, &admin);
}

pub fn balance(env: Env, user: Address) -> i128 {
env.storage().persistent().get(&TokenKey::Balance(user)).unwrap_or(0)
}

pub fn transfer(env: Env, from: Address, to: Address, amount: i128) {
from.require_auth();
let from_bal: i128 =
env.storage().persistent().get(&TokenKey::Balance(from.clone())).unwrap_or(0);
let to_bal: i128 =
env.storage().persistent().get(&TokenKey::Balance(to.clone())).unwrap_or(0);
env.storage().persistent().set(&TokenKey::Balance(from), &(from_bal - amount));
env.storage().persistent().set(&TokenKey::Balance(to), &(to_bal + amount));
}
}

// ── Contract B: vault ─────────────────────────────────────────────────────────

#[contract]
pub struct VaultB;

#[contractimpl]
impl VaultB {
pub fn deposit(env: Env, user: Address, amount: i128) {
user.require_auth();
let current: i128 = env
.storage()
.persistent()
.get(&VaultKey::Depositor(user.clone()))
.unwrap_or(0);
env.storage()
.persistent()
.set(&VaultKey::Depositor(user), &(current + amount));
}

pub fn total(env: Env) -> i128 {
env.storage().persistent().get(&VaultKey::TotalDeposits).unwrap_or(0)
}
}
Loading
Loading