agents/skills/soroban/custom-type-safety/SKILL.md
Trigger Pattern contractimport! or contracttype detected - Inject Into Breadth agents, depth-external
npx skillsauth add plamentsv/plamen custom-type-safetyInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Trigger Pattern:
contractimport!or#[contracttype]detected in codebase Inject Into: Breadth agents, depth-external Finding prefix:[CT-N]Rules referenced: R4, R8, R10
Soroban contracts interact with external contracts through generated client bindings (contractimport!) and pass structured data across boundaries using #[contracttype] types. Both mechanisms carry security-relevant assumptions about versioning, type stability, and deserialization behavior that must be verified.
For every contractimport! macro invocation, identify what is being imported and whether it is pinned:
| Import Target | File/Path or Wasm Hash | Version Pinned? | Pinning Method | Stable? |
|--------------|----------------------|----------------|---------------|--------|
| contractimport!("{target}") | {resolved path or hash} | YES/NO | Hash / Path / None | YES/NO |
Pinning methods (strongest to weakest):
contractimport!(file = "...", sha256 = "0xabc...") — exact bytecode pinning, most secureChecks:
When values of #[contracttype] types cross contract boundaries via invoke_contract, the receiving contract must be able to deserialize them. Verify semantic meaning is preserved:
| Type | Crosses Boundary? | Sending Contract Version | Receiving Contract Version | Compatibility? |
|------|------------------|------------------------|--------------------------|---------------|
| {StructName} | YES/NO | {version or hash} | {version or hash} | YES/NO |
Safety rules:
#[contracttype] types serialize to ScVal using field names as keys (for structs) or variant names (for enums). Adding fields with defaults is safe; removing fields or renaming them breaks existing serialized data.Type confusion risk: A #[contracttype] enum variant that serializes to the same ScVal representation as a variant in a different enum is a type confusion vector. This is rare but check when two enums use identical variant names or when raw Val conversions are used.
An imported contract may have been upgraded since the contractimport! snapshot was taken. If the imported ABI has changed, calling the contract with the old-generated client will fail at runtime:
| Imported Contract | Import Snapshot Date / Hash | Currently Deployed Hash | ABI Drift? | Breaking Changes? |
|------------------|-----------------------------|------------------------|-----------|-----------------|
| {contract name} | {date or hash from file} | {check stellar explorer or build_status} | YES/NO/UNKNOWN | YES/NO/UNKNOWN |
How to detect:
contractimport! statement against the currently deployed contract's WASM hash via the Stellar networkImpact of stale imports: Runtime deserialization errors (InvalidAction / WasmError) when calling functions whose signatures have changed. The contract compiles successfully but fails at runtime, potentially during critical operations.
#[contracttype] enums and structs must handle all serialization edge cases. Verify correct handling:
| Type | All Enum Variants Handled in Match? | Default/Fallback for Unknown Variants? | Deserialization Panic on Unknown? |
|------|-------------------------------------|---------------------------------------|----------------------------------|
| {EnumName} | YES/NO | YES/NO | YES/NO → FLAG if YES |
Enum exhaustiveness:
#[contracttype]
pub enum Status {
Active,
Paused,
Closed,
}
// SAFE: all variants covered
match status {
Status::Active => ...,
Status::Paused => ...,
Status::Closed => ...,
}
// RISKY: if a new variant is added to the external contract's Status,
// deserialization succeeds but the match panics
match status {
Status::Active => ...,
Status::Paused => ...,
// Missing: Status::Closed → panic at runtime
}
Struct field additions: If an external contract's #[contracttype] struct gains new fields, the importing contract's deserialization will fail with a type mismatch unless it uses versioned types or handles extra fields gracefully.
For each #[contracttype] enum used in deserialization:
Direct Val conversions (e.g., Val::from_val, TryFromVal, raw RawVal casts) bypass the typed #[contracttype] system. These must be handled with explicit error checking:
| Location | Val Conversion | Error Handled? | Type Confusion Risk? |
|----------|---------------|---------------|---------------------|
| {file:line} | {conversion expression} | YES/NO | YES/NO |
Unsafe patterns:
val.unchecked_into::<i128>() — no type check, interprets raw bits as i128 regardless of actual typeTryFromVal::try_from_val(&env, val).unwrap() — panics on type mismatch instead of returning an errorVal::from_bool / Val::from_i32 on unverified external inputSafe patterns:
TryFromVal::try_from_val(&env, val).map_err(|_| Error::InvalidInput)? — handles type mismatch gracefully#[contracttype] types for all cross-boundary data (avoids raw Val entirely)Val tag before conversion: val.is_i32 / val.get_tag() == Tag::I32ValType confusion attacks: If an attacker can influence the Val type tag (e.g., by passing an Address where an i128 is expected), raw conversion will interpret the Address bits as an integer, producing arbitrary numeric values — potential for balance manipulation, permission bypass, or incorrect calculation results.
**ID**: [CT-N]
**Severity**: [High if type confusion enables fund theft or auth bypass, Medium if stale import causes DoS, Low if missing variant or pinning only]
**Step Execution**: ✓1,2,3,4,5 | ✗(reasons) | ?(uncertain)
**Rules Applied**: [R4:✓/✗, R8:✓/✗, R10:✓/✗]
**Location**: src/{contract}.rs:LineN (or Cargo.toml/build.rs for Section 1)
**Title**: {Unpinned import / stale dependency / Val type confusion / missing variant} in `{context}`
**Description**: [Specific type safety issue with import target, type name, or conversion expression]
**Impact**: [Runtime deserialization panic / incorrect value interpretation / type confusion enabling exploit]
| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Import Dependency Audit | IF contractimport! present | ✓/✗(N/A)/? | All import targets, pinning method |
| 2. Type Boundary Safety | IF types cross contract boundaries | ✓/✗(N/A)/? | All #[contracttype] types in cross-boundary calls |
| 3. Stale Dependency Detection | IF contractimport! present | ✓/✗(N/A)/? | Snapshot hash vs deployed hash |
| 4. Custom Type Validation | IF #[contracttype] enums used in match | ✓/✗(N/A)/? | All match arms exhaustive |
| 5. Val Conversion Safety | IF raw Val conversions present | ✓/✗(N/A)/? | All unchecked_into / try_from_val calls |
development
Prepare Solidity projects for a security audit — test coverage, test quality, NatSpec docs, code hygiene, dependency health, best-practice enforcement, deployment readiness, and project documentation checks. Generates a scored Audit Readiness Report and optionally runs static analysis. Trigger on: "prepare for audit", "audit readiness", "pre-audit check", "audit prep", "NatSpec check", or any request to review a Solidity codebase before a security review.
development
Launch the Plamen deterministic Web3 security audit pipeline
development
Run the Plamen smart-contract audit wizard in Codex
testing
Launch the Plamen deterministic L1 infrastructure audit pipeline