Skip to content
Closed
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
20 changes: 18 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,42 @@ jobs:
contracts/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('contracts/Cargo.lock') }}

- name: Install rustfmt and clippy
run: rustup component add rustfmt clippy

- name: Install System Dependencies
run: |
sudo apt-get update
sudo apt-get install -y libdbus-1-dev pkg-config libudev-dev
sudo apt-get install -y libdbus-1-dev pkg-config libudev-dev binaryen

- name: Install Soroban CLI
run: |
if ! command -v soroban &> /dev/null; then
cargo install --locked soroban-cli
fi

- name: Check formatting
run: |
cd contracts
cargo fmt --check

- name: Clippy (zero warnings)
run: |
cd contracts
cargo clippy -- -D warnings

- name: Contract Compilation
run: |
cd contracts
cargo build --target wasm32-unknown-unknown --release

- name: Optimize and check WASM binary sizes
run: make check-size

- name: Contract Tests
run: |
cd contracts
cargo test
cargo test --workspace

- name: Install cargo-llvm-cov
run: cargo install cargo-llvm-cov
Expand Down
87 changes: 87 additions & 0 deletions .github/workflows/deploy-testnet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: Deploy to Testnet

on:
push:
branches: [main]

jobs:
deploy:
name: Build, Optimize, and Deploy Contracts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: wasm32-unknown-unknown

- name: Cache Cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
contracts/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('contracts/Cargo.lock') }}

- name: Install System Dependencies
run: |
sudo apt-get update
sudo apt-get install -y libdbus-1-dev pkg-config libudev-dev binaryen

- name: Install Stellar CLI
run: cargo install --locked stellar-cli

- name: Build optimized WASM binaries
run: |
cd contracts
cargo build --target wasm32-unknown-unknown --release
for wasm in target/wasm32-unknown-unknown/release/*.wasm; do
wasm-opt -O4 "$wasm" -o "$wasm"
done

- name: Deploy changed contracts to Stellar testnet
env:
STELLAR_SECRET_KEY: ${{ secrets.STELLAR_SECRET_KEY }}
NETWORK: testnet
RPC_URL: https://soroban-testnet.stellar.org:443
NETWORK_PASSPHRASE: "Test SDF Network ; September 2015"
run: |
stellar network add \
--rpc-url "$RPC_URL" \
--network-passphrase "$NETWORK_PASSPHRASE" \
"$NETWORK"

printf '%s' "$STELLAR_SECRET_KEY" | stellar keys generate deployer --secret-key

SUMMARY="## Testnet Deployment Summary\n\n| Contract | Contract ID |\n|---|---|\n"
for wasm in contracts/target/wasm32-unknown-unknown/release/*.wasm; do
name=$(basename "$wasm" .wasm)
contract_id=$(stellar contract deploy \
--wasm "$wasm" \
--source deployer \
--network "$NETWORK" 2>&1 | tail -1)
echo "Deployed $name: $contract_id"
SUMMARY="$SUMMARY| $name | $contract_id |\n"
done
echo -e "$SUMMARY" >> "$GITHUB_STEP_SUMMARY"

- name: Post deployment summary as PR comment
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const summary = process.env.GITHUB_STEP_SUMMARY
? fs.readFileSync(process.env.GITHUB_STEP_SUMMARY, 'utf8')
: 'Deployment complete.';
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: summary,
});
21 changes: 21 additions & 0 deletions .github/workflows/security-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Security Audit

on:
schedule:
- cron: "0 6 * * 1" # Weekly on Monday at 06:00 UTC
workflow_dispatch:

jobs:
cargo-audit:
name: cargo audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install cargo-audit
run: cargo install --locked cargo-audit

- name: Run cargo audit
run: |
cd contracts
cargo audit
43 changes: 43 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
WASM_DIR := contracts/target/wasm32-unknown-unknown/release
# Maximum allowed WASM binary size in bytes (500 KB)
MAX_WASM_SIZE := 512000

.PHONY: build optimize check-size test fmt clippy audit

build:
cd contracts && cargo build --target wasm32-unknown-unknown --release

optimize: build
@command -v wasm-opt >/dev/null 2>&1 || { echo "wasm-opt not found. Install binaryen: https://github.com/WebAssembly/binaryen"; exit 1; }
@for wasm in $(WASM_DIR)/*.wasm; do \
echo "Optimizing $$wasm ..."; \
wasm-opt -O4 "$$wasm" -o "$$wasm"; \
done
@echo "Optimization complete."

check-size: optimize
@echo "Checking WASM binary sizes (limit: $(MAX_WASM_SIZE) bytes)..."
@failed=0; \
for wasm in $(WASM_DIR)/*.wasm; do \
size=$$(wc -c < "$$wasm"); \
name=$$(basename "$$wasm"); \
echo " $$name: $$size bytes"; \
if [ "$$size" -gt "$(MAX_WASM_SIZE)" ]; then \
echo " FAIL: $$name exceeds limit ($$size > $(MAX_WASM_SIZE))"; \
failed=1; \
fi; \
done; \
if [ "$$failed" -eq 1 ]; then exit 1; fi; \
echo "All binaries within size limit."

test:
cd contracts && cargo test --workspace

fmt:
cd contracts && cargo fmt --check

clippy:
cd contracts && cargo clippy -- -D warnings

audit:
cd contracts && cargo audit
97 changes: 97 additions & 0 deletions contracts/src/financial_records/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Address, Env, Symbol};

/// A payment record stored in the financial ledger.
#[contracttype]
#[derive(Clone)]
pub struct PaymentRecord {
pub payment_id: u64,
pub payer: Address,
pub payee: Address,
pub amount: i128,
pub recorded_at: u64,
}

// ── Storage helpers ──────────────────────────────────────────────────────────

fn payment_key(payment_id: u64) -> (Symbol, u64) {
(symbol_short!("payment"), payment_id)
}

fn next_payment_key() -> Symbol {
symbol_short!("nxt_pay")
}

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

#[contract]
pub struct FinancialRecordsContract;

#[contractimpl]
impl FinancialRecordsContract {
/// Record a new payment. Returns the assigned payment_id.
pub fn record_payment(
env: Env,
payer: Address,
payee: Address,
amount: i128,
recorded_at: u64,
) -> u64 {
payer.require_auth();
assert!(amount > 0, "amount must be positive");

let payment_id: u64 = env
.storage()
.instance()
.get(&next_payment_key())
.unwrap_or(1u64);
env.storage()
.instance()
.set(&next_payment_key(), &(payment_id + 1));

let record = PaymentRecord {
payment_id,
payer,
payee,
amount,
recorded_at,
};
env.storage()
.persistent()
.set(&payment_key(payment_id), &record);
payment_id
}

/// Retrieve a payment record by id.
pub fn get_payment(env: Env, payment_id: u64) -> PaymentRecord {
env.storage()
.persistent()
.get(&payment_key(payment_id))
.expect("payment not found")
}
}

// ── Tests ─────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::{testutils::Address as _, Env};

#[test]
fn test_record_and_retrieve_payment() {
let env = Env::default();
env.mock_all_auths();
let id = env.register_contract(None, FinancialRecordsContract);
let client = FinancialRecordsContractClient::new(&env, &id);

let payer = Address::generate(&env);
let payee = Address::generate(&env);

let payment_id = client.record_payment(&payer, &payee, &500i128, &1000u64);
assert_eq!(payment_id, 1);

let record = client.get_payment(&payment_id);
assert_eq!(record.amount, 500);
assert_eq!(record.payer, payer);
}
}
9 changes: 9 additions & 0 deletions contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,12 @@ pub mod admin;

#[path = "booking_receipt/lib.rs"]
pub mod booking_receipt;

#[path = "nutrition_care/lib.rs"]
pub mod nutrition_care;

#[path = "medical_claims/lib.rs"]
pub mod medical_claims;

#[path = "financial_records/lib.rs"]
pub mod financial_records;
Loading
Loading