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
259 changes: 259 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
name: Release WASM Artifacts

on:
push:
tags:
- v*

permissions:
contents: write

env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1

jobs:
build:
name: Build & Release WASM
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

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

- name: Install WASM tools
run: |
set -euo pipefail
rustup component add rust-src
cargo install wasm-tools binaryen
echo "βœ“ WASM tools installed"

- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: "."
cache-targets: "true"
cache-all-crates: "true"

- name: Verify Cargo.lock is present
run: |
set -euo pipefail
if [ ! -f "Cargo.lock" ]; then
echo "ERROR: Cargo.lock not found"
exit 1
fi
echo "βœ“ Cargo.lock verified"

- name: Build WASM artifact (deterministic)
run: |
set -euo pipefail
cargo build \
--package stellaraid-core \
--target wasm32-unknown-unknown \
--release \
--locked \
--verbose
echo "βœ“ WASM artifact built successfully"

- name: Locate built artifact
id: locate
run: |
set -euo pipefail
WASM_FILE="target/wasm32-unknown-unknown/release/stellaraid_core.wasm"
if [ ! -f "$WASM_FILE" ]; then
echo "ERROR: WASM file not found at $WASM_FILE"
exit 1
fi
echo "wasm_file=$WASM_FILE" >> "$GITHUB_OUTPUT"
ls -lh "$WASM_FILE"
echo "βœ“ WASM artifact located"

- name: Strip WASM binary
run: |
set -euo pipefail
wasm-strip "${{ steps.locate.outputs.wasm_file }}"
echo "βœ“ WASM binary stripped"

- name: Optimize WASM with wasm-opt
run: |
set -euo pipefail
wasm-opt \
-Oz \
"${{ steps.locate.outputs.wasm_file }}" \
-o "${{ steps.locate.outputs.wasm_file }}.tmp"
mv "${{ steps.locate.outputs.wasm_file }}.tmp" \
"${{ steps.locate.outputs.wasm_file }}"
echo "βœ“ WASM optimized with -Oz"
ls -lh "${{ steps.locate.outputs.wasm_file }}"

- name: Generate checksums
id: checksums
run: |
set -euo pipefail
cd "${{ runner.temp }}"
cp "${{ github.workspace }}/${{ steps.locate.outputs.wasm_file }}" \
./core-${{ github.ref_name }}.wasm
sha256sum core-${{ github.ref_name }}.wasm > checksums.txt
cat checksums.txt
echo "βœ“ Checksums generated"
echo "checksum_file=${{ runner.temp }}/checksums.txt" >> \
"$GITHUB_OUTPUT"
echo "artifact_dir=${{ runner.temp }}" >> "$GITHUB_OUTPUT"

- name: Verify artifact integrity
run: |
set -euo pipefail
cd "${{ steps.checksums.outputs.artifact_dir }}"
echo "Computing verification checksum..."
VERIFY_CHECKSUM=$(sha256sum core-${{ github.ref_name }}.wasm \
| awk '{print $1}')
ORIGINAL_CHECKSUM=$(awk '{print $1}' checksums.txt)
echo "Original: $ORIGINAL_CHECKSUM"
echo "Verification: $VERIFY_CHECKSUM"
if [ "$VERIFY_CHECKSUM" != "$ORIGINAL_CHECKSUM" ]; then
echo "ERROR: Checksum mismatch!"
exit 1
fi
echo "βœ“ Artifact integrity verified"

- name: Prepare release artifacts
id: prepare
run: |
set -euo pipefail
mkdir -p release-artifacts
cp "${{ steps.checksums.outputs.artifact_dir }}/core-${{ github.ref_name }}.wasm" \
release-artifacts/
cp "${{ steps.checksums.outputs.checksum_file }}" \
release-artifacts/
ls -lh release-artifacts/
echo "βœ“ Release artifacts prepared"

- name: Display release notes
run: |
set -euo pipefail
echo "════════════════════════════════════════"
echo "Release: ${{ github.ref_name }}"
echo "════════════════════════════════════════"
echo ""
echo "Artifacts:"
echo " - core-${{ github.ref_name }}.wasm"
echo " - checksums.txt"
echo ""
echo "Checksums:"
cat "${{ steps.checksums.outputs.checksum_file }}"
echo ""

- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
draft: false
prerelease: false
files: |
release-artifacts/core-${{ github.ref_name }}.wasm
release-artifacts/checksums.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

verify:
name: Verify Build Reproducibility
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

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

- name: Install WASM tools
run: |
set -euo pipefail
rustup component add rust-src
cargo install wasm-tools binaryen
echo "βœ“ WASM tools installed for verification"

- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: "."
cache-targets: "true"
cache-all-crates: "true"

- name: Download artifacts from release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
mkdir -p verify-artifacts
gh release download ${{ github.ref_name }} \
--pattern "core-*" \
--pattern "checksums.txt" \
--dir verify-artifacts
ls -lh verify-artifacts/
echo "βœ“ Artifacts downloaded"

- name: Rebuild WASM locally
run: |
set -euo pipefail
cargo build \
--package stellaraid-core \
--target wasm32-unknown-unknown \
--release \
--locked \
--verbose
echo "βœ“ Local rebuild completed"

- name: Apply optimizations to rebuilt artifact
run: |
set -euo pipefail
REBUILT_WASM="target/wasm32-unknown-unknown/release/stellaraid_core.wasm"
wasm-strip "$REBUILT_WASM"
wasm-opt -Oz "$REBUILT_WASM" -o "${REBUILT_WASM}.tmp"
mv "${REBUILT_WASM}.tmp" "$REBUILT_WASM"
echo "βœ“ Rebuilt artifact optimized"

- name: Verify checksums match
run: |
set -euo pipefail
RELEASED_ARTIFACT="verify-artifacts/core-${{ github.ref_name }}.wasm"
REBUILT_WASM="target/wasm32-unknown-unknown/release/stellaraid_core.wasm"
RELEASED_SUM=$(sha256sum "$RELEASED_ARTIFACT" | awk '{print $1}')
REBUILT_SUM=$(sha256sum "$REBUILT_WASM" | awk '{print $1}')
ORIGINAL_SUM=$(awk '{print $1}' verify-artifacts/checksums.txt)
echo "════════════════════════════════════════"
echo "Checksum Verification Report"
echo "════════════════════════════════════════"
echo "Released artifact: $RELEASED_SUM"
echo "Rebuilt artifact: $REBUILT_SUM"
echo "Original checksum: $ORIGINAL_SUM"
echo ""
if [ "$RELEASED_SUM" != "$ORIGINAL_SUM" ]; then
echo "❌ ERROR: Released artifact doesn't match original!"
exit 1
fi
if [ "$REBUILT_SUM" != "$RELEASED_SUM" ]; then
echo "⚠️ WARNING: Rebuilt artifact differs from released"
echo "This could indicate non-deterministic builds"
fi
echo "βœ“ Build reproducibility check passed"

- name: Report verification status
run: |
set -euo pipefail
echo "════════════════════════════════════════"
echo "βœ“ Build verification successful"
echo "βœ“ Artifacts are intact and verified"
echo "════════════════════════════════════════"
7 changes: 7 additions & 0 deletions crates/contracts/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,10 @@ soroban-sdk = { workspace = true }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }

[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
strip = true

37 changes: 14 additions & 23 deletions crates/contracts/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,33 @@
#![no_std]
use soroban_sdk::{contract, contractimpl, Env, Symbol};
use soroban_sdk::{contract, contractimpl, Address, Env};

#[contract]
pub struct StellarAidContract;
pub struct CoreContract;

#[contractimpl]
impl StellarAidContract {
pub fn hello(_env: Env, _to: Symbol) -> Symbol {
soroban_sdk::symbol_short!("Hello")
}
impl CoreContract {
pub fn init(_env: Env, _admin: Address) {}

pub fn get_greeting(_env: Env) -> Symbol {
soroban_sdk::symbol_short!("Hi")
pub fn ping(_env: Env) -> u32 {
1
}
}

#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::{symbol_short, Env};
use soroban_sdk::Env;

#[test]
fn test_hello() {
fn test_init_and_ping() {
let env = Env::default();
let contract_id = env.register_contract(None, StellarAidContract);
let client = StellarAidContractClient::new(&env, &contract_id);

let result = client.hello(&symbol_short!("World"));
assert_eq!(result, symbol_short!("Hello"));
}
let contract_id = env.register_contract(None, CoreContract);
let client = CoreContractClient::new(&env, &contract_id);

#[test]
fn test_get_greeting() {
let env = Env::default();
let contract_id = env.register_contract(None, StellarAidContract);
let client = StellarAidContractClient::new(&env, &contract_id);
let admin = env.current_contract_address();
client.init(&admin);

let result = client.get_greeting();
assert_eq!(result, symbol_short!("Hi"));
let result = client.ping();
assert_eq!(result, 1);
}
}
Loading