Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
92 changes: 92 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,33 @@ jobs:
working-directory: apexchainx_calculator
run: cargo test --lib

# Issue #90 — compile and run tests under the `--release` profile.
# Catches bugs that only appear under release inlining/DCE (e.g., UB
# detection, expression reordering, dead-store elimination). Runs in
# parallel to `e2e-tests` so the added wall-clock cost is bounded.
e2e-tests-release:
name: E2E Tests (Release Profile)
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Cache cargo target (release profile)
uses: actions/cache@v4
with:
path: apexchainx_calculator/target
key: e2e-release-${{ runner.os }}-${{ hashFiles('apexchainx_calculator/Cargo.lock', 'apexchainx_calculator/Cargo.toml') }}
restore-keys: |
e2e-release-${{ runner.os }}-

- name: Run tests in release mode
working-directory: apexchainx_calculator
run: cargo test --release --lib

fuzz-tests:
name: Fuzz Tests (proptest)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -127,6 +154,71 @@ jobs:
run: cargo test --lib fuzz_tests::


# Issue #82 — Track WASM size history across every CI run.
# Builds the release WASM, runs `tools/wasm-size.ts` to enforce the
# 100 KB budget (SC-042) and to append a TSV row to
# `apexchainx_calculator/.wasm-size.history.txt`, then renders a
# markdown summary that is uploaded as an artifact and posted as a
# sticky PR comment (only on pull_request runs).
wasm-size-history:
name: WASM Size History
runs-on: ubuntu-latest

# `pull-requests: write` is required by `marocchino/sticky-pull-request-comment`
# to (re)post the wasm-size-history header on the originating PR.
permissions:
contents: read
pull-requests: write

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Rust + wasm32 target
uses: dtolnay/rust-toolchain@1.94.1
with:
targets: wasm32-unknown-unknown

- name: Build WASM release
working-directory: apexchainx_calculator
run: cargo build --target wasm32-unknown-unknown --release

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Enforce budget + persist history
env:
GITHUB_SHA: ${{ github.sha }}
GITHUB_REF_NAME: ${{ github.ref_name }}
GITHUB_EVENT_NAME: ${{ github.event_name }}
run: npx --yes tsx tools/wasm-size.ts

- name: Render markdown summary
run: npx --yes tsx tools/wasm-size.ts summary > wasm-size-summary.md
shell: bash

- name: Show summary
run: cat wasm-size-summary.md

- name: Upload trend artifact
uses: actions/upload-artifact@v4
with:
name: wasm-size-history
path: |
wasm-size-summary.md
apexchainx_calculator/.wasm-size.baseline.txt
apexchainx_calculator/.wasm-size.history.txt
retention-days: 365

- name: Post summary as PR comment
if: github.event_name == 'pull_request'
uses: marocchino/sticky-pull-request-comment@v2
with:
header: wasm-size-history
path: wasm-size-summary.md

provenance-hashes:
name: Provenance & Hashes
runs-on: ubuntu-latest
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,40 @@ The `release-hash` workflow (`.github/workflows/release-hash.yml`) runs
automatically on every push to `main`, every PR, and every `v*` tag. On tag
pushes the manifest and WASM are attached to the GitHub Release.

## Build Size Trend

The contract WASM is enforced against a hard budget of **100 KB** (SC-042).
Every release WASM build also appends a row to a persistent history file so
maintainers can spot regressions before they land.

| File | Purpose |
| --- | --- |
| `apexchainx_calculator/.wasm-size.baseline.txt` | Single-line current size; updated by the CI job after every successful release build. |
| `apexchainx_calculator/.wasm-size.history.txt` | TSV log. Columns: `sha`, `ts`, `size_bytes`, `source`, `event`, `iso_date`. Header comment describes the columns. |

The `wasm-size-history` CI job (`.github/workflows/ci.yml`) runs on every
push and PR:

1. Builds the release WASM (`cargo build --target wasm32-unknown-unknown --release`).
2. Calls `npx tsx tools/wasm-size.ts` — enforces the 100 KB budget, updates
the baseline, and appends a row to the history file.
3. Emits a markdown summary via `npx tsx tools/wasm-size.ts summary` and:
- uploads it as the `wasm-size-history` artifact, and
- posts it as a sticky comment on the originating PR.

Inspect the history locally:

```bash
# Show last 5 release builds
tail -n 8 apexchainx_calculator/.wasm-size.history.txt

# Print a markdown summary to stdout
npx tsx tools/wasm-size.ts summary
```

The script honours `GITHUB_SHA`, `GITHUB_REF_NAME`, and `GITHUB_EVENT_NAME`
when invoked from CI so history rows are tagged accurately.

## Build Verification

### Current Status
Expand Down
1 change: 1 addition & 0 deletions apexchainx_calculator/.wasm-size.baseline.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24576
2 changes: 2 additions & 0 deletions apexchainx_calculator/.wasm-size.history.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# sha ts size_bytes source event iso_date
feedf00 1784541259483 24576 micd746 pull_request 2026-07-20T09:54:19.483Z
5 changes: 5 additions & 0 deletions apexchainx_calculator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ crate-type = ["cdylib"]

[features]
default = []
# Issue #98: opt-in structured tracing for the test harness. Builds the
# `trace` module (stdlib-backed, host-only) and wires `trace::record_*`
# calls into the contract public methods. Enable with
# `cargo test --features debug-trace` to exercise the trace API.
debug-trace = []

[dependencies]
soroban-sdk = { version = "21.1.0", features = ["alloc"] }
Expand Down
79 changes: 79 additions & 0 deletions apexchainx_calculator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ mod tests;
#[cfg(test)]
mod fuzz_tests;

#[cfg(test)]
#[cfg(feature = "debug-trace")]
mod trace_tests;

#[cfg(all(feature = "debug-trace", not(target_family = "wasm")))]
pub mod trace;

pub mod config_bundle;
pub mod config_freeze;
pub mod config_metadata;
Expand Down Expand Up @@ -555,6 +562,12 @@ impl SLACalculatorContract {

env.storage().instance().set(&CONFIG_KEY, &configs);
Self::write_version(&env);

#[cfg(all(feature = "debug-trace", not(target_family = "wasm")))]
{
trace::record_initialize("admin", "operator");
}

Ok(())
}

Expand Down Expand Up @@ -732,6 +745,11 @@ impl SLACalculatorContract {
(new_operator.clone(),),
);

#[cfg(all(feature = "debug-trace", not(target_family = "wasm")))]
{
trace::record_set_operator();
}

Ok(())
}

Expand Down Expand Up @@ -886,6 +904,22 @@ impl SLACalculatorContract {
);
env.events()
.publish((EVENT_PAUSED, EVENT_VERSION, caller), (true,));

#[cfg(all(feature = "debug-trace", not(target_family = "wasm")))]
{
// Copy into an owned String for the trace (Soroban String is
// bounded; using a separate local keeps the contract API
// unchanged and avoids referencing the consumed Soroban String).
let reason_owned: String = {
let mut buf = alloc::string::String::new();
for c in reason.iter() {
buf.push(c);
}
buf
};
trace::record_pause(&reason_owned);
}

Ok(())
}

Expand All @@ -899,6 +933,12 @@ impl SLACalculatorContract {
env.storage().instance().remove(&PAUSE_INFO_KEY);
env.events()
.publish((EVENT_UNPAUSED, EVENT_VERSION, caller), (false,));

#[cfg(all(feature = "debug-trace", not(target_family = "wasm")))]
{
trace::record_unpause();
}

Ok(())
}

Expand Down Expand Up @@ -926,6 +966,12 @@ impl SLACalculatorContract {
config_freeze::freeze_config(&env);
env.events()
.publish((EVENT_CONFIG_FREEZE, EVENT_VERSION, caller), ());

#[cfg(all(feature = "debug-trace", not(target_family = "wasm")))]
{
trace::record_freeze_config();
}

Ok(())
}

Expand All @@ -937,6 +983,12 @@ impl SLACalculatorContract {
config_freeze::unfreeze_config(&env);
env.events()
.publish((EVENT_CONFIG_UNFREEZE, EVENT_VERSION, caller), ());

#[cfg(all(feature = "debug-trace", not(target_family = "wasm")))]
{
trace::record_unfreeze_config();
}

Ok(())
}

Expand Down Expand Up @@ -996,6 +1048,17 @@ impl SLACalculatorContract {
(EVENT_CONFIG_UPD, EVENT_VERSION, severity),
(threshold_minutes, penalty_per_minute, reward_base),
);

#[cfg(all(feature = "debug-trace", not(target_family = "wasm")))]
{
trace::record_set_config(
&severity.to_string(),
threshold_minutes,
penalty_per_minute,
reward_base,
);
}

Ok(())
}

Expand Down Expand Up @@ -1301,9 +1364,25 @@ impl SLACalculatorContract {
Self::increment_stats(&env, true, result.amount, 0);
}

#[cfg(all(feature = "debug-trace", not(target_family = "wasm")))]
let severity_str = severity.to_string();

Self::publish_sla_event(&env, severity.clone(), &result);
Self::publish_settlement_intent_event(&env, severity, &result);

#[cfg(all(feature = "debug-trace", not(target_family = "wasm")))]
{
trace::record_calculate_sla(
&severity_str,
&result.status.to_string(),
&result.payment_type.to_string(),
&result.rating.to_string(),
result.amount,
result.mttr_minutes,
result.threshold_minutes,
);
}

Ok(result)
}

Expand Down
Loading
Loading