Skip to content
 
 

Repository files navigation

soroban-guard-contracts

CI codecov

A library of sample Soroban smart contracts — both vulnerable and secure — used for testing the Soroban Guard scanner, plus an on-chain scan result registry.

Part of the Veritas Vaults Network org.


Sister repos

Repo Purpose
soroban-guard-core CLI scanner
soroban-guard-web Web dashboard

Project structure

soroban-guard-contracts/
├── vulnerable/
│   ├── missing_auth/       # transfer() with no require_auth()
│   ├── unchecked_math/     # staking rewards with raw u64 arithmetic
│   ├── missing_ttl/        # persistent balances expire because TTL is never renewed
│   ├── unprotected_admin/  # set_admin() / upgrade() open to anyone
│   └── unsafe_storage/     # public writes to any account's storage slot
├── secure/
│   ├── secure_vault/       # fixed token: auth + checked math
│   └── protected_admin/    # fixed admin + profile registry
├── registry/               # on-chain scan result registry contract
├── docs/
│   └── vulnerabilities.md  # explains each vulnerability with examples
├── CONTRIBUTING.md
└── Cargo.toml

Contracts

Vulnerable

Crate Context Vulnerability
accept_admin_missing_auth Admin contract accept_admin() never calls require_auth — anyone can finalise the transfer
missing_auth Token contract transfer() mutates balances without require_auth()
missing_ttl Token contract Persistent balances expire because the contract never calls extend_ttl()
unchecked_math Staking contract Reward calc uses raw * on u64 — overflows silently
unprotected_admin Escrow contract set_admin() and upgrade() have no caller check
unsafe_storage KYC registry Any caller can write to any account's storage slot
admin_rugpull Admin contract Single-step admin transfer with no acceptance confirmation
allowance_not_decremented Token contract Allowance not reduced after transfer_from
call_depth Cross-contract Unbounded recursive calls exhaust the call stack
div_by_zero Fee contract Division by zero when pool or supply is empty
double_claim Staking contract Reward window never resets — same period claimed repeatedly
dust_griefing Vault contract No minimum deposit — storage bloated with dust entries
flash_loan_no_check Lending contract Flash loan repayment not verified before returning
instant_oracle DEX contract Single-block oracle price is manipulable via flash loan
key_collision Token contract Different data types share the same storage key
leaky_events Registry contract Sensitive data emitted in public contract events
missing_events Token contract No events emitted — off-chain indexers are blind
negative_transfer Token contract Negative amount reverses transfer direction
no_slippage DEX contract No min_out guard — sandwich attacks extract value
reentrancy Vault contract External call before state update enables re-entrancy
reinit_attack Any contract initialize() callable multiple times — admin replaced
replay_attack Signature contract Signed messages not invalidated — replayable indefinitely
scanner_impersonation Registry contract Scanner address not verified against approved list
self_transfer Token contract from == to corrupts balance accounting
sensitive_storage Registry contract Secrets stored in publicly readable contract storage
stale_oracle DEX contract Oracle price used without staleness check
stale_pending_admin Admin contract Two-step admin: cancellation does not clear pending admin — stale address can still accept
string_admin Admin contract Admin stored as String — bypasses require_auth
timestamp_lock Vault contract Time-lock uses manipulable ledger().timestamp()
unbounded_storage Registry contract Unbounded collection growth exhausts storage
uncapped_rate Staking contract Reward rate has no upper bound — pool drainable
unchecked_math Staking contract Raw arithmetic overflows silently
underflow_transfer Token contract Unchecked subtraction wraps balance to u64::MAX
unprotected_burn Token contract burn() callable by anyone — destroys any account's tokens
unprotected_delete Any contract Storage wipe callable without admin auth
unprotected_emergency_withdraw Vault contract Emergency drain callable by any address
unprotected_fee_withdraw DEX contract Fee withdrawal open to any caller
unprotected_mint Token contract mint() callable by anyone — unlimited supply inflation
unsafe_cast Token contract Integer cast truncates or wraps silently
zero_admin Admin contract Admin set to zero address — contract permanently locked
zero_deposit Vault contract Zero-value deposit accepted — storage griefing
zero_stake Staking contract Zero-value stake accepted — division-by-zero risk
reward_debt_not_updated Staking contract claim_rewards never updates reward debt — same rewards claimable repeatedly
reward_checkpoint_missing Staking contract stake omits reward checkpoint — late depositors steal historical rewards

Secure

Crate Fixes
secure_vault require_auth on transfer + checked_sub/checked_add
protected_admin Admin auth on set_admin/upgrade + account auth on profile writes

Registry

registry — an on-chain contract that stores scan findings keyed by contract address. Only verified scanners (managed by the admin) can submit results. Supports full scan history per contract.

submit_scan(scanner, contract_address, findings_hash, severity_counts)
get_scan(contract_address) -> Option<ScanResult>
get_history_page(contract_address, offset, limit) -> Vec<ScanResult>  // limit capped at 50
get_history_len(contract_address) -> u32

Quick start

# Build all contracts
cargo build

# Run all tests
cargo test

# Run tests for a single contract
cargo test -p missing-auth
cargo test -p registry

See CONTRIBUTING.md for full setup instructions and how to add new vulnerable contract examples.


Stellar blockchain integration

These contracts run on Stellar via the Soroban smart contract platform. Below is the full scaffold for deploying and interacting with them on Stellar Testnet.

Network overview

Stellar Testnet
  RPC endpoint : https://soroban-testnet.stellar.org
  Network pass : Test SDF Network ; September 2015
  Explorer     : https://stellar.expert/explorer/testnet

Stellar Mainnet
  RPC endpoint : https://soroban-mainnet.stellar.org
  Network pass : Public Global Stellar Network ; September 2015
  Explorer     : https://stellar.expert/explorer/public

1. Prerequisites

# Rust + WASM target
rustup target add wasm32-unknown-unknown

# Stellar CLI
cargo install --locked stellar-cli --features opt

# Fund a testnet account (Friendbot)
stellar keys generate --global deployer --network testnet
stellar keys fund deployer --network testnet

2. Build optimised WASM

cargo build --release --target wasm32-unknown-unknown

# Compiled artefacts land at:
# target/wasm32-unknown-unknown/release/missing_auth.wasm
# target/wasm32-unknown-unknown/release/registry.wasm
# ... etc

3. Deploy a contract

# Deploy the scan result registry
stellar contract deploy \
  --wasm target/wasm32-unknown-unknown/release/registry.wasm \
  --source deployer \
  --network testnet

# Returns a contract address, e.g.:
# CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
export REGISTRY_ID=<contract-address>

4. Initialise the registry

stellar contract invoke \
  --id $REGISTRY_ID \
  --source deployer \
  --network testnet \
  -- initialize \
  --admin $(stellar keys address deployer)

5. Register a scanner

export SCANNER=$(stellar keys address deployer)

stellar contract invoke \
  --id $REGISTRY_ID \
  --source deployer \
  --network testnet \
  -- add_scanner \
  --scanner $SCANNER

6. Submit a scan result

stellar contract invoke \
  --id $REGISTRY_ID \
  --source deployer \
  --network testnet \
  -- submit_scan \
  --scanner $SCANNER \
  --contract_address <scanned-contract-address> \
  --findings_hash "e3b0c44298fc1c149afb" \
  --severity_counts '{"critical":1,"high":2,"medium":0,"low":3}'

7. Query scan results

# Latest result
stellar contract invoke \
  --id $REGISTRY_ID \
  --network testnet \
  -- get_scan \
  --contract_address <scanned-contract-address>

# History page (offset=0, limit=50)
stellar contract invoke \
  --id $REGISTRY_ID \
  --network testnet \
  -- get_history_page \
  --contract_address <scanned-contract-address> \
  --offset 0 \
  --limit 50

8. Deploy a vulnerable contract (for scanner testing)

stellar contract deploy \
  --wasm target/wasm32-unknown-unknown/release/missing_auth.wasm \
  --source deployer \
  --network testnet

export VULN_ID=<contract-address>

# Mint some tokens
stellar contract invoke \
  --id $VULN_ID \
  --source deployer \
  --network testnet \
  -- mint \
  --to $(stellar keys address deployer) \
  --amount 1000000

# Demonstrate the vulnerability — transfer without auth
stellar contract invoke \
  --id $VULN_ID \
  --source deployer \
  --network testnet \
  -- transfer \
  --from $(stellar keys address deployer) \
  --to <any-address> \
  --amount 1000000

Architecture diagram

flowchart TD
    subgraph Stellar["Stellar Network (on-chain)"]
        subgraph vuln["vulnerable/*"]
            V1[missing_auth]
            V2[unchecked_math]
            V3[missing_ttl]
            V4[unprotected_admin]
            V5[unsafe_storage]
        end

        subgraph sec["secure/*"]
            S1[secure_vault]
            S2[protected_admin]
        end

        REG["registry\nsubmit_scan()\nget_scan()\nget_history_page()"]
    end

    CLI["soroban-guard-core\n(off-chain scanner CLI)"]

    V1 -- mirrors --> S1
    V2 -- mirrors --> S1
    V4 -- mirrors --> S2
    V5 -- mirrors --> S2

    CLI -- "1. deploy & scan" --> vuln
    CLI -- "2. submit_scan()\n(verified scanner only)" --> REG
    CLI -- "3. get_scan() / get_history()" --> REG
Loading

Vulnerability ↔ secure-crate pairing

Vulnerability crate Class Secure mirror Fix applied
missing_auth Missing authorisation secure_vault require_auth() on transfer
unchecked_math Integer overflow secure_vault checked_mul / checked_add
missing_ttl Storage expiry (inline secure.rs) extend_ttl() on every write
unprotected_admin Privilege escalation protected_admin Admin auth on set_admin / upgrade
unsafe_storage Unauthorised writes protected_admin Account auth on profile writes
key_collision Storage key clash (inline secure.rs) Namespaced storage keys
admin_rugpull Admin rug-pull (inline secure.rs) Two-step admin transfer
zero_deposit Zero-value deposit (inline secure.rs) Guard amount > 0
dust_griefing Dust griefing secure/dust_griefing Minimum deposit threshold
instant_oracle Oracle manipulation (inline secure.rs) TWAP / multi-source oracle
no_slippage Slippage (inline secure.rs) min_out slippage guard
flash_loan_no_check Flash-loan re-entry (inline secure.rs) Repayment check before return
leaky_events Sensitive data in events (inline secure.rs) Emit only non-sensitive fields
scanner_impersonation Scanner spoofing (inline secure.rs) On-chain scanner registry check
allowance_not_decremented Allowance bug (inline secure.rs) Decrement allowance after spend
double_claim Double-claim Claim flag in storage
div_by_zero Division by zero Guard divisor > 0
negative_transfer Negative amount Reject amount < 0
underflow_transfer Underflow checked_sub
unprotected_burn Unprotected burn secure/secure_burn require_auth() on burn
unprotected_fee_withdraw Fee drain secure/protected_fee_withdraw Admin auth on fee withdrawal
unprotected_delete Storage wipe Admin auth on delete
unprotected_emergency_withdraw Emergency drain (inline secure.rs) Auth + time-lock
self_transfer Self-transfer Reject from == to
reinit_attack Re-initialisation Initialised flag guard
reentrancy Re-entrancy (inline secure.rs) Checks-effects-interactions
zero_admin Zero address admin Reject zero address
string_admin String-typed admin Use Address type
zero_stake Zero-value stake (inline secure.rs) Guard amount > 0
timestamp_lock Timestamp manipulation secure/sequence_lock Ledger sequence instead of timestamp
missing_events No events emitted Emit structured events
reward_debt_not_updated Reward debt not updated (inline secure.rs) Update debt after payout
reward_checkpoint_missing Reward checkpoint missing (inline secure.rs) Snapshot accumulator on deposit

Useful links


Vulnerability reference

See docs/vulnerabilities.md for a detailed explanation of each vulnerability class with code examples and fixes.

About

Sample vulnerable and secure Soroban contracts for testing, plus an on-chain scan registry

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages