|
| 1 | +name: Contract WASM Size Check |
| 2 | + |
| 3 | +# Runs on every PR that touches contracts/ or this workflow file. |
| 4 | +# Also runs on pushes to main so the baseline stays auditable. |
| 5 | + |
| 6 | +on: |
| 7 | + pull_request: |
| 8 | + paths: |
| 9 | + - "contracts/**" |
| 10 | + - ".github/workflows/contract-wasm-size.yml" |
| 11 | + push: |
| 12 | + branches: |
| 13 | + - main |
| 14 | + paths: |
| 15 | + - "contracts/**" |
| 16 | + - ".github/workflows/contract-wasm-size.yml" |
| 17 | + |
| 18 | +# ── Tunable knobs ───────────────────────────────────────────────────────────── |
| 19 | +env: |
| 20 | + # Build artefact path (relative to contracts/) |
| 21 | + WASM_PATH: target/wasm32-unknown-unknown/release/portfolio_rebalancer.wasm |
| 22 | + # Baseline file path (relative to contracts/) |
| 23 | + BASELINE_FILE: wasm-size-baseline.txt |
| 24 | + # Percentage growth that triggers a failure (integer, e.g. 10 = 10 %) |
| 25 | + WASM_SIZE_THRESHOLD_PCT: "10" |
| 26 | + |
| 27 | +jobs: |
| 28 | + wasm-size: |
| 29 | + name: WASM size regression check |
| 30 | + runs-on: ubuntu-latest |
| 31 | + permissions: |
| 32 | + pull-requests: write # needed to post a PR comment with the size delta |
| 33 | + contents: read |
| 34 | + |
| 35 | + steps: |
| 36 | + - uses: actions/checkout@v4 |
| 37 | + |
| 38 | + - name: Setup Rust (wasm32 target) |
| 39 | + uses: dtolnay/rust-toolchain@stable |
| 40 | + with: |
| 41 | + targets: wasm32-unknown-unknown |
| 42 | + |
| 43 | + - name: Cache Cargo registry + build artefacts |
| 44 | + uses: actions/cache@v4 |
| 45 | + with: |
| 46 | + path: | |
| 47 | + ~/.cargo/registry |
| 48 | + ~/.cargo/git |
| 49 | + contracts/target |
| 50 | + key: wasm-size-${{ runner.os }}-${{ hashFiles('contracts/Cargo.lock', 'contracts/Cargo.toml') }} |
| 51 | + restore-keys: | |
| 52 | + wasm-size-${{ runner.os }}- |
| 53 | +
|
| 54 | + - name: Build contract (release WASM) |
| 55 | + working-directory: contracts |
| 56 | + run: cargo build --target wasm32-unknown-unknown --release |
| 57 | + |
| 58 | + - name: Measure WASM size and compare against baseline |
| 59 | + id: size_check |
| 60 | + working-directory: contracts |
| 61 | + run: | |
| 62 | + set -euo pipefail |
| 63 | +
|
| 64 | + # ── Read baseline ────────────────────────────────────────────────── |
| 65 | + if [ ! -f "$BASELINE_FILE" ]; then |
| 66 | + echo "::error::Baseline file '$BASELINE_FILE' not found in contracts/." |
| 67 | + echo "::error::Run 'wc -c < $WASM_PATH' after a release build and add the" |
| 68 | + echo "::error::result to contracts/$BASELINE_FILE (see file for format)." |
| 69 | + exit 1 |
| 70 | + fi |
| 71 | +
|
| 72 | + # Extract the numeric value from BASELINE= line, ignoring comment lines. |
| 73 | + BASELINE=$(grep -E '^BASELINE=[0-9]+' "$BASELINE_FILE" | head -1 | cut -d= -f2) |
| 74 | + if [ -z "$BASELINE" ] || [ "$BASELINE" -le 0 ] 2>/dev/null; then |
| 75 | + echo "::error::Could not parse a positive integer from BASELINE= in $BASELINE_FILE" |
| 76 | + exit 1 |
| 77 | + fi |
| 78 | +
|
| 79 | + # ── Measure actual size ──────────────────────────────────────────── |
| 80 | + if [ ! -f "$WASM_PATH" ]; then |
| 81 | + echo "::error::WASM artefact not found at contracts/$WASM_PATH" |
| 82 | + exit 1 |
| 83 | + fi |
| 84 | + ACTUAL=$(wc -c < "$WASM_PATH") |
| 85 | +
|
| 86 | + # ── Compute delta ────────────────────────────────────────────────── |
| 87 | + # Use awk for floating-point arithmetic; bash only does integer math. |
| 88 | + DELTA=$((ACTUAL - BASELINE)) |
| 89 | + PCT=$(awk "BEGIN { printf \"%.2f\", ($ACTUAL - $BASELINE) / $BASELINE * 100 }") |
| 90 | + ABS_PCT=$(awk "BEGIN { printf \"%.2f\", ($ACTUAL - $BASELINE < 0 ? $BASELINE - $ACTUAL : $ACTUAL - $BASELINE) / $BASELINE * 100 }") |
| 91 | +
|
| 92 | + # ── Emit step summary ────────────────────────────────────────────── |
| 93 | + { |
| 94 | + echo "## Contract WASM Size Report" |
| 95 | + echo "" |
| 96 | + echo "| Metric | Value |" |
| 97 | + echo "|--------|-------|" |
| 98 | + echo "| Baseline | $(numfmt --grouping "$BASELINE") bytes |" |
| 99 | + echo "| Actual | $(numfmt --grouping "$ACTUAL") bytes |" |
| 100 | + echo "| Delta | $([ $DELTA -ge 0 ] && echo "+")${DELTA} bytes (${PCT}%) |" |
| 101 | + echo "| Threshold | ±${WASM_SIZE_THRESHOLD_PCT}% |" |
| 102 | + } >> "$GITHUB_STEP_SUMMARY" |
| 103 | +
|
| 104 | + # ── Expose outputs for downstream use (e.g. PR comments) ────────── |
| 105 | + echo "baseline=$BASELINE" >> "$GITHUB_OUTPUT" |
| 106 | + echo "actual=$ACTUAL" >> "$GITHUB_OUTPUT" |
| 107 | + echo "delta=$DELTA" >> "$GITHUB_OUTPUT" |
| 108 | + echo "pct=$PCT" >> "$GITHUB_OUTPUT" |
| 109 | +
|
| 110 | + # ── Enforce threshold (growth only; shrinkage is always fine) ────── |
| 111 | + OVER_THRESHOLD=$(awk "BEGIN { print ($ACTUAL > $BASELINE && ($ACTUAL - $BASELINE) / $BASELINE * 100 > $WASM_SIZE_THRESHOLD_PCT) ? 1 : 0 }") |
| 112 | + if [ "$OVER_THRESHOLD" = "1" ]; then |
| 113 | + echo "failed=true" >> "$GITHUB_OUTPUT" |
| 114 | + echo "::error::WASM size regression: ${ACTUAL} bytes is ${PCT}% larger than baseline ${BASELINE} bytes (threshold: +${WASM_SIZE_THRESHOLD_PCT}%)." |
| 115 | + echo "::error::To accept this growth, update BASELINE in contracts/${BASELINE_FILE} and commit the change alongside your feature." |
| 116 | + exit 1 |
| 117 | + fi |
| 118 | +
|
| 119 | + echo "failed=false" >> "$GITHUB_OUTPUT" |
| 120 | +
|
| 121 | + if [ "$DELTA" -lt 0 ]; then |
| 122 | + echo "::notice::WASM shrank by ${ABS_PCT}% (${ACTUAL} vs baseline ${BASELINE} bytes). Consider updating the baseline." |
| 123 | + else |
| 124 | + echo "::notice::WASM size OK: ${ACTUAL} bytes (+${PCT}% within ${WASM_SIZE_THRESHOLD_PCT}% threshold)." |
| 125 | + fi |
| 126 | +
|
| 127 | + - name: Post PR comment with size delta |
| 128 | + if: github.event_name == 'pull_request' |
| 129 | + uses: actions/github-script@v7 |
| 130 | + with: |
| 131 | + script: | |
| 132 | + const baseline = '${{ steps.size_check.outputs.baseline }}'; |
| 133 | + const actual = '${{ steps.size_check.outputs.actual }}'; |
| 134 | + const delta = '${{ steps.size_check.outputs.delta }}'; |
| 135 | + const pct = '${{ steps.size_check.outputs.pct }}'; |
| 136 | + const failed = '${{ steps.size_check.outputs.failed }}' === 'true'; |
| 137 | + const threshold = process.env.WASM_SIZE_THRESHOLD_PCT; |
| 138 | +
|
| 139 | + const sign = delta >= 0 ? '+' : ''; |
| 140 | + const icon = failed ? '❌' : (delta < 0 ? '✅' : '✅'); |
| 141 | + const status = failed |
| 142 | + ? `**Regression detected** — size grew by ${sign}${delta} bytes (${sign}${pct}%), exceeding the ±${threshold}% threshold.` |
| 143 | + : `Size change is within the ±${threshold}% threshold.`; |
| 144 | +
|
| 145 | + const body = [ |
| 146 | + `### ${icon} Contract WASM Size Check`, |
| 147 | + '', |
| 148 | + `| | Bytes |`, |
| 149 | + `|---|---|`, |
| 150 | + `| Baseline | ${Number(baseline).toLocaleString()} |`, |
| 151 | + `| This PR | ${Number(actual).toLocaleString()} |`, |
| 152 | + `| Delta | ${sign}${Number(delta).toLocaleString()} (${sign}${pct}%) |`, |
| 153 | + '', |
| 154 | + status, |
| 155 | + '', |
| 156 | + failed |
| 157 | + ? `To accept this size increase, update \`BASELINE\` in \`contracts/wasm-size-baseline.txt\` to \`${actual}\` in this PR.` |
| 158 | + : '', |
| 159 | + ].join('\n'); |
| 160 | +
|
| 161 | + // Upsert: replace an existing comment if re-running. |
| 162 | + const { data: comments } = await github.rest.issues.listComments({ |
| 163 | + owner: context.repo.owner, |
| 164 | + repo: context.repo.repo, |
| 165 | + issue_number: context.issue.number, |
| 166 | + }); |
| 167 | + const existing = comments.find(c => |
| 168 | + c.user.type === 'Bot' && c.body.includes('Contract WASM Size Check') |
| 169 | + ); |
| 170 | + if (existing) { |
| 171 | + await github.rest.issues.updateComment({ |
| 172 | + owner: context.repo.owner, |
| 173 | + repo: context.repo.repo, |
| 174 | + comment_id: existing.id, |
| 175 | + body, |
| 176 | + }); |
| 177 | + } else { |
| 178 | + await github.rest.issues.createComment({ |
| 179 | + owner: context.repo.owner, |
| 180 | + repo: context.repo.repo, |
| 181 | + issue_number: context.issue.number, |
| 182 | + body, |
| 183 | + }); |
| 184 | + } |
0 commit comments