Skip to content

feat: harden family wallet initialization invariants #951

feat: harden family wallet initialization invariants

feat: harden family wallet initialization invariants #951

Workflow file for this run

name: Soroban Smart Contracts CI
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
permissions:
contents: read
pull-requests: write
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
build:
name: Build and Test
runs-on: macos-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
components: rustfmt, clippy
- name: Cache Cargo dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Install Soroban CLI
run: |
if ! command -v soroban &> /dev/null; then
echo "Attempting to install via Homebrew..."
if brew list stellar-cli &>/dev/null || brew install stellar-cli 2>/dev/null; then
echo "Installed via Homebrew"
else
echo "Homebrew install failed, installing via cargo..."
cargo install --locked --version 21.0.0 soroban-cli
fi
fi
# Try both commands in case Homebrew installs it as 'stellar'
if command -v soroban &> /dev/null; then
soroban --version
elif command -v stellar &> /dev/null; then
stellar --version
# Create alias for consistency
ln -s $(which stellar) /usr/local/bin/soroban || true
else
echo "Error: Neither soroban nor stellar CLI found"
exit 1
fi
- name: Verify Rust installation
run: |
rustc --version
cargo --version
rustup target list --installed | grep wasm32-unknown-unknown || rustup target add wasm32-unknown-unknown
- name: Build workspace (WASM)
run: |
cargo build --release --target wasm32-unknown-unknown --workspace \
--exclude remitwise-cli \
--exclude scenarios \
--exclude integration_tests \
--exclude testutils \
--verbose
continue-on-error: false
- name: Build Soroban contracts
run: |
for contract in remittance_split savings_goals bill_payments insurance; do
echo "Building contract: $contract"
# Build from workspace root to ensure WASM files go to target/wasm32-unknown-unknown/release/
cargo build --release --target wasm32-unknown-unknown --package $contract --verbose
done
continue-on-error: false
- name: Verify WASM files exist
run: |
for contract in remittance_split savings_goals bill_payments insurance; do
wasm_file="target/wasm32-unknown-unknown/release/${contract}.wasm"
if [ ! -f "$wasm_file" ]; then
echo "Error: WASM file not found: $wasm_file"
exit 1
fi
echo "✓ Found: $wasm_file ($(du -h $wasm_file | cut -f1))"
done
- name: Run Cargo tests
run: |
cargo test -p orchestrator --verbose
continue-on-error: false
- name: Run Clippy
run: |
cargo clippy -p orchestrator --all-targets -- -D warnings
continue-on-error: false
- name: Check formatting
run: |
cargo fmt --all -- --check
continue-on-error: false
- name: Upload WASM artifacts
uses: actions/upload-artifact@v4
if: always()
with:
name: wasm-contracts
path: |
target/wasm32-unknown-unknown/release/*.wasm
retention-days: 7
compression-level: 9
- name: Upload build logs
uses: actions/upload-artifact@v4
if: failure()
with:
name: build-logs
path: |
target/**/*.log
retention-days: 3
security:
name: Security Checks
runs-on: macos-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache Cargo dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Install cargo-audit
run: |
if ! command -v cargo-audit &> /dev/null; then
cargo install cargo-audit --locked
fi
- name: Run cargo audit
run: |
cargo audit --deny warnings
continue-on-error: true
gas-benchmarks:
name: Gas Benchmarks
runs-on: macos-latest
timeout-minutes: 15
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Cache Cargo dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Install jq
run: |
if ! command -v jq &> /dev/null; then
brew install jq
fi
- name: Run gas benchmarks
run: |
./scripts/run_gas_benchmarks.sh
continue-on-error: false
- name: Check for gas regressions
run: |
if [ -f benchmarks/baseline.json ]; then
echo "Comparing against baseline..."
./scripts/compare_gas_results.sh benchmarks/baseline.json gas_results.json
else
echo "⚠️ No baseline found. Skipping regression check."
echo "Run './scripts/update_baseline.sh' locally to create initial baseline."
fi
continue-on-error: false
- name: Upload gas benchmark results
uses: actions/upload-artifact@v4
if: always()
with:
name: gas-benchmarks
path: gas_results.json
retention-days: 30
- name: Comment PR with gas results
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
if (!fs.existsSync('gas_results.json')) {
console.log('No gas results found');
return;
}
const results = JSON.parse(fs.readFileSync('gas_results.json', 'utf8'));
let comment = '## ⛽ Gas Benchmark Results\n\n';
comment += '| Contract | Method | Scenario | CPU | Memory |\n';
comment += '|----------|--------|----------|-----|--------|\n';
results.forEach(r => {
comment += `| ${r.contract} | ${r.method} | ${r.scenario} | ${r.cpu.toLocaleString()} | ${r.mem.toLocaleString()} |\n`;
});
comment += '\n*Results are in instruction/byte counts. Lower is better.*\n';
// Check if baseline exists and add comparison
if (fs.existsSync('benchmarks/baseline.json')) {
const baseline = JSON.parse(fs.readFileSync('benchmarks/baseline.json', 'utf8'));
comment += '\n### Comparison vs Baseline\n\n';
results.forEach(curr => {
const base = baseline.find(b =>
b.contract === curr.contract &&
b.method === curr.method &&
b.scenario === curr.scenario
);
if (base && base.cpu > 0) {
const cpuChange = ((curr.cpu - base.cpu) / base.cpu * 100).toFixed(1);
const memChange = ((curr.mem - base.mem) / base.mem * 100).toFixed(1);
const cpuIcon = cpuChange > 10 ? '⚠️' : cpuChange < -5 ? '✨' : '✅';
const memIcon = memChange > 10 ? '⚠️' : memChange < -5 ? '✨' : '✅';
comment += `- **${curr.contract}::${curr.method}**: CPU ${cpuIcon} ${cpuChange > 0 ? '+' : ''}${cpuChange}%, Memory ${memIcon} ${memChange > 0 ? '+' : ''}${memChange}%\n`;
}
});
}
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});