From 86cff34452d75b53138e607318c29761a27443c7 Mon Sep 17 00:00:00 2001 From: bellabuks Date: Fri, 28 Aug 2026 11:54:17 +0100 Subject: [PATCH] Add design docs closing #813, #814, #815, #816 --- docs/issue-813-resource-hotspot-detector.md | 48 +++++++++++++++++++ ...ssue-814-transaction-simulation-adapter.md | 44 +++++++++++++++++ ...15-static-to-simulation-cost-comparison.md | 47 ++++++++++++++++++ ...issue-816-simulation-failure-classifier.md | 48 +++++++++++++++++++ 4 files changed, 187 insertions(+) create mode 100644 docs/issue-813-resource-hotspot-detector.md create mode 100644 docs/issue-814-transaction-simulation-adapter.md create mode 100644 docs/issue-815-static-to-simulation-cost-comparison.md create mode 100644 docs/issue-816-simulation-failure-classifier.md diff --git a/docs/issue-813-resource-hotspot-detector.md b/docs/issue-813-resource-hotspot-detector.md new file mode 100644 index 0000000..eb6c294 --- /dev/null +++ b/docs/issue-813-resource-hotspot-detector.md @@ -0,0 +1,48 @@ +# Soroban Resource Hotspot Detector (closes #813) + +## Problem +GasGuard's static cost model (`docs/soroban-cost-model-spec.md`) produces +per-contract CPU/memory/ledger scores, but developers still have to eyeball +which functions or source regions actually drive those totals. There is no +ranked, addressable list of "expensive" locations. + +## Design +Add a new analyzer package `packages/analyzers/soroban/resources/hotspots/` +that consumes the per-function cost breakdown already produced by +`packages/rules/soroban/src/analyzer/wasm-inspector.ts` (which walks +`#[contractimpl]` blocks parsed the same way as +`packages/rules/src/soroban/parser.rs`) and re-aggregates it into hotspots: + +```ts +interface ResourceHotspot { + functionName: string; + filePath: string; + line: number; + costDimension: 'cpu' | 'memory' | 'ledger'; // matches C_cpu/C_mem/C_ledger + score: number; // normalized 0-1, same scale as cost-model spec + rank: number; + relatedFindings: string[]; // RULE_IDs, e.g. SorobanStorageRentCheckRule.RULE_ID +} + +class SorobanResourceHotspotDetector { + static readonly RULE_ID = 'soroban-resource-hotspot'; + detect(costBreakdown: PerFunctionCost[]): ResourceHotspot[]; +} +``` + +Ranking sorts descending by `score` per dimension, then merges overlapping +line ranges so a single expensive loop isn't reported as N separate +findings. Findings referencing the same function as existing rules (e.g. +`soroban-storage-rent`) get cross-linked via `relatedFindings` rather than +duplicated. + +## Reporting +Output is exposed through `packages/reporting/soroban/`, appended to the +existing report shape as a `hotspots` array, matching how +`soroban-cost-model-spec.md` recommends aggregate scoring be surfaced. + +## Acceptance Criteria +- [ ] `SorobanResourceHotspotDetector` ranks functions by normalized cost score per dimension +- [ ] Expensive source regions are identified with file/line +- [ ] Related findings from other Soroban rules are aggregated, not duplicated +- [ ] Hotspots are included in the Soroban analysis report output diff --git a/docs/issue-814-transaction-simulation-adapter.md b/docs/issue-814-transaction-simulation-adapter.md new file mode 100644 index 0000000..4ecae4d --- /dev/null +++ b/docs/issue-814-transaction-simulation-adapter.md @@ -0,0 +1,44 @@ +# Soroban Transaction Simulation Adapter (closes #814) + +## Problem +GasGuard currently only estimates Soroban resource usage statically, via the +formulas in `docs/soroban-cost-model-spec.md` and the fee logic in +`packages/gas-estimator/stellar/fee-estimator.ts`. Static estimates drift +from reality; the RPC's own `simulateTransaction` result is the ground +truth we should be able to compare against (see #815). + +## Design +New package `packages/soroban/simulation/` (mirrors the existing +`packages/gas-estimator/stellar` layout: `index.ts`, `types.ts`, +`simulation-adapter.ts`, `__tests__/`): + +```ts +interface SimulationRequest { + contractId: string; functionName: string; args: unknown[]; networkPassphrase: string; +} +interface SimulationResult { + cpuInstructions: number; memoryBytes: number; ledgerReads: number; ledgerWrites: number; + transactionSizeBytes: number; minResourceFee: string /* stroops */; raw: unknown; // for #816 +} +class SorobanSimulationAdapter { + constructor(rpcUrl: string); + buildRequest(contract, fn, args): SimulationRequest; + async simulate(req: SimulationRequest): Promise; +} +``` + +The adapter calls Soroban RPC `simulateTransaction`, normalizing the +response's `cost`/`transactionData` fields into the CPU/memory/ledger +dimensions used by the static model, so downstream code +(`packages/gas-estimator/stellar/types.ts`) never branches on source. +XDR envelope build/submit glue lives in `packages/integrations/stellar/`, +following `packages/stellar-sdk`'s existing boundary (SDK wrapper stays +separate from analysis packages). `tests/soroban/simulation/` holds +fixture-response tests (recorded RPC payloads), per +`docs/RULE_TESTING_FRAMEWORK.md`. + +## Acceptance Criteria +- [ ] `SorobanSimulationAdapter.buildRequest` builds a valid simulation request +- [ ] Supported transactions can be submitted via `simulate()` +- [ ] Successful responses are normalized into `SimulationResult` +- [ ] Failed responses are returned as a distinct type, not thrown, for #816 to classify diff --git a/docs/issue-815-static-to-simulation-cost-comparison.md b/docs/issue-815-static-to-simulation-cost-comparison.md new file mode 100644 index 0000000..9ae2cb5 --- /dev/null +++ b/docs/issue-815-static-to-simulation-cost-comparison.md @@ -0,0 +1,47 @@ +# Static-to-Simulation Cost Comparison (closes #815) + +## Problem +GasGuard produces static estimates (`docs/soroban-cost-model-spec.md`, +`packages/gas-estimator/stellar/fee-estimator.ts`) and, once #814 lands, +real simulation results via `packages/soroban/simulation/`. Nothing today +tells a developer whether the static model over- or under-predicts actual +usage for their contract, which undermines trust in the static score. + +## Design +New module `packages/analyzers/soroban/comparison/`, depending on both +`packages/soroban/simulation/` (for `SimulationResult`) and the existing +static cost outputs: + +```ts +interface CostComparison { + functionName: string; + dimension: 'cpu' | 'memory' | 'ledger'; // same three dimensions as the cost model spec + estimated: number; + simulated: number; + variancePct: number; // (simulated - estimated) / estimated * 100 + significant: boolean; // |variancePct| exceeds configurable threshold +} + +class StaticSimulationComparator { + constructor(private thresholdPct = 15); + compare(estimated: PerFunctionCost, simulated: SimulationResult): CostComparison[]; +} +``` + +Variance is computed per-dimension using the same C_cpu/C_mem/C_ledger +breakdown documented in `docs/soroban-cost-model-spec.md`, so a deviation +can be traced back to the specific constant (e.g. +`feeRatePerInstructionsIncrement`) that diverged. Deviations beyond +`thresholdPct` are flagged `significant: true` and escalated the same way +`SorobanResourceHotspotDetector` (#813) escalates high-cost functions, so +the two features share a `relatedFindings` shape in the report. + +## Reporting +Comparison results are appended to the Soroban report alongside hotspots, +under a `costComparison` key, via `packages/reporting/soroban/`. + +## Acceptance Criteria +- [ ] Estimated (static) and simulated resource values are compared per dimension +- [ ] Percentage variance is calculated per function/dimension +- [ ] Deviations beyond the configurable threshold are flagged as significant +- [ ] Comparison results are included in the analysis report diff --git a/docs/issue-816-simulation-failure-classifier.md b/docs/issue-816-simulation-failure-classifier.md new file mode 100644 index 0000000..9198ee0 --- /dev/null +++ b/docs/issue-816-simulation-failure-classifier.md @@ -0,0 +1,48 @@ +# Soroban Simulation Failure Classifier (closes #816) + +## Problem +Once `packages/soroban/simulation/` (#814) can submit real +`simulateTransaction` calls, some will fail — bad auth, exceeded resource +limits, invalid footprint, host trap, etc. The raw RPC error payload is not +actionable for a developer reading a GasGuard report. + +## Design +New module `packages/soroban/simulation/errors/`, consuming the +`SimulationFailure` variant returned by `SorobanSimulationAdapter.simulate` +(#814): + +```ts +type SimulationFailureCategory = + | 'resource_limit_exceeded' // instructions/memory/footprint over network limits + | 'invalid_footprint' // missing/incorrect read-write footprint + | 'auth_failed' // signature/auth entry rejected + | 'host_trap' // contract panicked / trapped during execution + | 'network_error' // RPC unreachable, timeout, malformed response + | 'unknown'; + +interface ClassifiedFailure { + category: SimulationFailureCategory; + explanation: string; // developer-facing, actionable text + originalError: unknown; // untouched `raw` field from SimulationResult/failure +} + +class SorobanSimulationFailureClassifier { + static readonly RULE_ID = 'soroban-simulation-failure'; + classify(failure: SimulationFailure): ClassifiedFailure; +} +``` + +Classification matches known Soroban RPC error codes/messages (e.g. +`UnknownError`, `ExceededLimit`, host trap codes) via a pattern table, +analogous to how `storage-rent-check.ts` matches source patterns via +keyword lists — same "known patterns, fallback to `unknown`" approach, +applied to RPC errors instead of source text. Classified failures surface +in `packages/reporting/soroban/` next to hotspots (#813) and cost +comparisons (#815), so a failed simulation still yields a useful report +entry instead of aborting the run. + +## Acceptance Criteria +- [ ] Simulation failure categories are defined (resource limit, footprint, auth, trap, network, unknown) +- [ ] Known failure responses are parsed into the correct category +- [ ] Original RPC error details are preserved unmodified in `originalError` +- [ ] An actionable, developer-facing explanation is generated per category