feat(types,core): typed errors for scenario follies - #765
Open
cds-amal wants to merge 9 commits into
Open
Conversation
`PdaSeed::to_bytes` and `AccountAddress::resolve` returned `Option`, collapsing seven distinct failures into `None`: invalid public keys, missing value maps, unknown properties, incorrect JSON types, out-of-range `u16` values, malformed hex, and nested derivation failures. Changes: * Introduce `SeedError` and `ScenarioError` using `thiserror`, and convert `PdaSeed::to_bytes`, `AccountAddress::resolve`, and `resolve_simple` to return `Result`. * Box the source of `SeedError::DerivedSeed` so nested `DerivedPda` failures preserve context such as the seed index and program at every level. * Replace the duplicated `filter_map`/length-check pattern in `resolve` and `DerivedPda` with `enumerate` and `collect::<Result<Vec<_>, _>>()`. * Move the value-map lookup shared by `PropertyRef`, `U16BeRef`, and `Bytes32Ref` into a `property_value` helper. Successful resolution is unchanged; only failures become more diagnostic.
`register_scenario` previously accepted every scenario and returned `Ok`, even if an enabled override referenced an address that could not be resolved. Those failures surfaced only during materialization as a server-side warning with no underlying cause. Changes: - Resolve every enabled override's address during registration and reject the scenario if any resolution fails. - Report each failing override by id together with its specific resolution error. - Skip disabled overrides during validation, matching materialization's behavior. - Preserve materialization's skip-and-continue policy, but include the underlying resolution error in its warning. - Add `From<ScenarioError>` for `SurfpoolError` and an `invalid_scenario` constructor for aggregated registration failures. Successful scenarios behave like before; invalid overrides are now rejected before any work is scheduled, with actionable diagnostics.
…rrides `surfnet_registerScenario` now rejects scenarios whose enabled overrides have unresolvable addresses. Update the documentation to describe the validation and the resulting error. Changes: - Document registration-time validation in the RPC rustdoc. - Update the scenarios README to describe validation and the shape of registration failures. - Document the same behavior in the MCP `create_scenario` tool description. - Add registration validation to the MCP tool's strict-rules list so LLM callers know the error is resolved by correcting the scenario and retrying. The docs now matches the registration behavior introduced by the validation changes.
Template addresses were converted from YAML with an infallible `From`, allowing malformed public keys and PDA definitions to survive until address resolution. Those failures were then reported without a clear connection to the template that introduced them. Changes: - Replace the infallible `From` conversion with `TryFrom<YamlAccountAddress>`. - Validate all information available at load time: provided public keys, PDA program ids, and literal public-key seeds, including nested `DerivedPda` values via `validate_literals`. - Leave reference seeds unvalidated until resolution, since they depend on instance values. - Preserve the existing empty-string placeholder for omitted public keys, which SPL Token templates use to indicate per-instance overrides. - Add `ScenarioError::Template` so conversion failures identify the template they originated from. - Share the wrapping logic across the three `Yaml`-to-`OverrideTemplate` conversions with a `template_address` helper. - Panic if an embedded template fails validation, matching the registry's existing policy for embedded assets. Successful templates behave as before; malformed templates now fail when loaded instead of during address resolution.
Scheduled override application expressed every failure as an inline warning and continued processing. Five distinct causes were reported only as log messages, making them difficult to test and impossible to handle as typed failures. Changes: - Introduce `OverrideError` in `surfpool-types` to represent address resolution failures, missing accounts, undersized accounts, missing IDLs, and forge failures. - Move override application into an `apply_override` helper returning `Result<(), OverrideError>`, leaving the existing skip-and-continue policy to its caller. - Define `Ok(())` to cover both a successfully applied override and the documented no-op case where every value is a PDA seed reference. - Check account size before looking up an IDL so undersized accounts consistently report `AccountTooSmall`. - Clarify `AccountNotFound` by documenting that overrides patch existing accounts rather than creating them, with `fetchBeforeUse` as the remedy. - Wrap core-side forge failures (IDL lookup, Borsh decode, and Borsh encode) at the crate boundary while preserving the existing discriminator mismatch behavior. Successful overrides behave as before; skipped overrides now carry typed, testable failure causes.
The existing tests verified typed errors structurally, but never exercised their `Display` implementations. A format-string change could therefore alter every user-facing error message without failing a test. Changes: * Add a test covering every variant of `SeedError`, `ScenarioError`, and `OverrideError`. * Assert the exact rendered `Display` message for each variant. * Print each message before asserting so `cargo test -- --nocapture` serves as a catalog of the error text users see. The test now guards both the structure of the error types and the user-facing messages they produce.
The u16 string path gained a WrongType { expected: "u16" } construction
when the decimal-string parse met the typed-error conversion during the
rebase; pin the exact error and its rendered message, and state the
range/type boundary: an overflowing decimal string reports the type,
because the parse rejects it before any range logic runs.
cds-amal
marked this pull request as ready for review
August 18, 2026 15:42
Contributor
Greptile SummaryThis PR replaces opaque scenario address-resolution failures with typed, contextual errors and validates enabled override addresses before scheduling.
Confidence Score: 5/5The PR appears safe to merge, with no concrete changed-code defect identified. The new typed-error paths consistently propagate address-resolution failures through template loading, scenario registration, and scheduled override handling, and repository call sites have been updated for the fallible APIs. Important Files Changed
Reviews (1): Last reviewed commit: "cargo: fmt" | Re-trigger Greptile |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was inspired by Stefan Baumgartner Rust talk. It was a fun exercise. Sharing it here in case anyone wants to get it over the line.
Disclaimer: most tests were written by AI.
Error fidelity: before and after
Registering a scenario whose PDA override references a property the values map
does not contain:
Before
{ "jsonrpc": "2.0", "id": 1, "result": { "context": { "slot": 1234 }, "value": null } }followed, one slot later, by the un-actionable warn above. The override never
applies.
After
{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32600, "message": "Cannot register scenario 'liquidation drill': override '2f6e0c0d': PDA for program CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK, seed 1: property 'poolId' not found in values" } }Nothing is scheduled; the caller learns the failing override, the seed position, and the cause in one message. Multiple bad overrides are all reported, joined with "; ", because validation collects every failure before rejecting (one round trip to fix everything, instead of one round trip per mistake).
A sampler of the causes that were previously indistinguishable:
invalid account address: 'not-a-pubkey' is not a valid pubkeyPDA for program CAMM..., seed 2: property 'configIndex' value 70000 does not fit in u16PDA for program pyth..., seed 1: 'ef0d8b' is not a 32-byte hex stringPDA for program CAMM..., seed 1: property 'poolId' is bool, expected string or u64PDA for program whirl..., seed 0: seed 1 of derived PDA for program whirl...: 'oops' is not a valid pubkey