Priority: Medium · Area: Soroban contract / error handling · Est. effort: 5–8 h
📌 Problem
The crate has a dedicated src/error.rs, so typed contract errors are the established pattern. Despite that, 7 unwrap()/expect()/panic! sites remain in non-test contract source — lib.rs, storage.rs, events.rs, types.rs, error.rs.
(The crate has 234 such sites in total, but 227 are inside src/test.rs, where they are entirely appropriate. This issue is only the 7 in contract source — do not touch the test ones.)
When one of these fires, the contract does not return a typed error. It traps, and the client receives an undecodable host error with no indication of which precondition failed — "not authorised", "record does not exist" and "never initialised" become indistinguishable.
Seven is a small, tractable number. That is what makes this a good candidate for a complete fix rather than a partial one.
🎯 Design decision required
A blanket replacement with ? will be rejected. Classify each of the 7 and put the table in the PR:
- Recoverable precondition — a missing or invalid stored value a caller can act on. Must become a typed error from
src/error.rs.
- Genuine invariant — a state the contract's own logic makes unreachable. May remain a panic, but must carry a comment naming the invariant and why it holds.
- Misplaced test helper — code that belongs behind
#[cfg(test)]. Say so and move it.
You may need to add variants to the error enum. That is an ABI-visible change: state whether you appended (safe) or renumbered (breaking), and why.
🧩 Requirements and context
- New error variants must be appended with fresh discriminants — off-chain code decodes these by number. Do not renumber existing ones.
- Every new variant needs a test provoking it and asserting the exact error via a
try_* client method.
- Soroban caps type and function identifiers at 30 characters — check any new variant name.
- Report the wasm byte delta; the contract's size is currently unmeasured (tracked separately).
- Consider adding a CI guard — a grep, or a lint — so new unguarded unwraps cannot enter contract source. Say whether you did and why.
🛠️ Suggested execution
- List all 7 with file and line, and classify each. Put it in the PR before writing code.
- Add any needed error variants.
- Convert the recoverable sites; annotate the invariant ones.
- Add a test per new variant.
- Add the regression guard if you argued for one.
✅ Acceptance criteria
🚫 Out of scope
- The 227
unwrap()s in src/test.rs — appropriate as they are.
- Splitting
test.rs or lib.rs — separate issues.
- Adding clippy — separate issue.
🧪 Verification
cargo test
grep -n '\.unwrap()\|\.expect(\|panic!' src/lib.rs src/storage.rs src/events.rs src/types.rs src/error.rs
cargo build --target wasm32-unknown-unknown --release
cargo fmt --all -- --check
📤 What your PR must include
- The 7-site classification table.
- Your ABI-compatibility statement.
- Before/after wasm size.
- Whether you added a regression guard, and why.
Closes #<n>.
🔒 Security notes
Traps and typed errors are not equivalent failure modes. An integrator cannot distinguish an authorisation failure from a missing record from an uninitialised contract, which pushes clients toward blind retries against a contract whose state they cannot determine. On settlement and liquidity paths, an opaque failure is indistinguishable from a transient fault.
📋 Guidelines
- Minimum 95% test coverage on changed lines
- Clear documentation
- Timeframe: 96 hours from assignment
- One logical change per commit; no merge commits
💬 Join our community
Working on this, or want to sanity-check your approach before you start? Come and ask — the maintainers are there and happy to help.
Telegram: https://t.me/Grainlify
Priority: Medium · Area: Soroban contract / error handling · Est. effort: 5–8 h
📌 Problem
The crate has a dedicated
src/error.rs, so typed contract errors are the established pattern. Despite that, 7unwrap()/expect()/panic!sites remain in non-test contract source —lib.rs,storage.rs,events.rs,types.rs,error.rs.(The crate has 234 such sites in total, but 227 are inside
src/test.rs, where they are entirely appropriate. This issue is only the 7 in contract source — do not touch the test ones.)When one of these fires, the contract does not return a typed error. It traps, and the client receives an undecodable host error with no indication of which precondition failed — "not authorised", "record does not exist" and "never initialised" become indistinguishable.
Seven is a small, tractable number. That is what makes this a good candidate for a complete fix rather than a partial one.
🎯 Design decision required
A blanket replacement with
?will be rejected. Classify each of the 7 and put the table in the PR:src/error.rs.#[cfg(test)]. Say so and move it.You may need to add variants to the error enum. That is an ABI-visible change: state whether you appended (safe) or renumbered (breaking), and why.
🧩 Requirements and context
try_*client method.🛠️ Suggested execution
✅ Acceptance criteria
unwrap()was touched.cargo testpasses (305 tests); wasm byte delta reported.🚫 Out of scope
unwrap()s insrc/test.rs— appropriate as they are.test.rsorlib.rs— separate issues.🧪 Verification
📤 What your PR must include
Closes #<n>.🔒 Security notes
Traps and typed errors are not equivalent failure modes. An integrator cannot distinguish an authorisation failure from a missing record from an uninitialised contract, which pushes clients toward blind retries against a contract whose state they cannot determine. On settlement and liquidity paths, an opaque failure is indistinguishable from a transient fault.
📋 Guidelines
💬 Join our community
Working on this, or want to sanity-check your approach before you start? Come and ask — the maintainers are there and happy to help.
Telegram: https://t.me/Grainlify