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
48 changes: 48 additions & 0 deletions docs/issue-813-resource-hotspot-detector.md
Original file line number Diff line number Diff line change
@@ -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
44 changes: 44 additions & 0 deletions docs/issue-814-transaction-simulation-adapter.md
Original file line number Diff line number Diff line change
@@ -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<SimulationResult | SimulationFailure>;
}
```

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
47 changes: 47 additions & 0 deletions docs/issue-815-static-to-simulation-cost-comparison.md
Original file line number Diff line number Diff line change
@@ -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
48 changes: 48 additions & 0 deletions docs/issue-816-simulation-failure-classifier.md
Original file line number Diff line number Diff line change
@@ -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
Loading