agents/skills/soroban/temporal-parameter-staleness/SKILL.md
Trigger Pattern interval|period|duration|delay|cooldown|lock_period|timelock|unbonding|claim_delay|withdraw_delay|maturity|ledger_sequence|timestamp - Inject Into Breadth agents, depth-state-trace
npx skillsauth add plamentsv/plamen temporal-parameter-stalenessInstall 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:
interval|period|duration|delay|cooldown|lock_period|timelock|unbonding|claim_delay|withdraw_delay|maturity|ledger_sequence|timestampInject Into: Breadth agents, depth-state-trace Finding prefix:[TPS-N]Rules referenced: R2, R8, R10, R13, R14
Cached parameters in multi-step operations become stale when authority changes them between steps. On Soroban, timing has unique properties: env.ledger().timestamp() provides Unix seconds (estimated, can lag wall clock), env.ledger().sequence_number() provides the exact ledger number (strictly monotonic), SCP consensus closes ledgers approximately every 5 seconds, and there is no concept of slots, epochs, or a clock sysvar. TTL (time-to-live) is measured in ledger numbers and can itself act as a timing mechanism.
Find all operations that span multiple transactions (ledgers):
| Operation | Step 1 (Initiate) | Wait Condition | Step N (Complete) | Clock Source | |-----------|-------------------|----------------|-------------------|-------------| | {op_name} | {initiate_fn}() | {condition} | {complete_fn}() | ledger_sequence / timestamp |
For each multi-step operation:
env.ledger().timestamp() vs env.ledger().sequence_number())| Clock Source | Type | Resolution | Monotonicity | Accuracy | Typical Use Case |
|-------------|------|-----------|-------------|----------|-----------------|
| env.ledger().sequence_number() | u32 | ~5s per ledger | Strictly increasing | Exact (validator-produced) | Cooldowns, lock periods, rate-limiting |
| env.ledger().timestamp() | u64 (Unix seconds) | ~5s | Mostly increasing (can lag) | Estimated by SCP; may lag wall clock by seconds to minutes during network stress | Human-readable delays, longer durations |
Critical property: Within a single transaction (a single invocation tree, including sub-invocations via invoke_contract), ALL calls see the same env.ledger() values — there is no intra-transaction timing variation. Multi-step timing attacks require separate ledgers (separate transactions).
Timestamp vs sequence tradeoff:
timestamp is human-intuitive (seconds) but can lag real time during validator slowdownssequence_number is exact but requires converting to human time (~5s per ledger, subject to change if Stellar network parameters change)sequence_number durations break if Stellar's ledger close time changes (currently ~5s, configurable by validators)For each parameter used across steps:
| Parameter | Storage Type + Key | Read At Step | Cached in User State? | Admin-Changeable? | Re-Validated At Completion? | |-----------|-------------------|-------------|----------------------|------------------|-----------------------------| | {param} | {Instance/Persistent, DataKey::Foo} | initiate() | YES/NO | YES/NO (which function) | YES/NO |
Soroban caching patterns:
position.fee_rate = config.fee_rate at deposit time)user_position.start_ledger = env.ledger().sequence_number())expiration_ledger — if the operation takes longer than expected, the allowance expires and Step N failsRed flags: Parameter is cached in user's Persistent entry at Step 1 AND admin can change the source config AND Step N does NOT re-read from source.
For each cached parameter that can become stale:
Scenario A: Parameter INCREASES between steps
1. User initiates at Step 1 — user Persistent entry stores param = X
2. Admin updates Instance config: param = X + delta
3. User completes at Step N — uses cached X from Persistent entry
4. Impact: {what happens with stale X when current config is X + delta}
Scenario B: Parameter DECREASES between steps
1. User initiates at Step 1 — user Persistent entry stores param = X
2. Admin updates Instance config: param = X - delta
3. User completes at Step N — uses cached X
4. Impact: {what happens with stale X when current config is X - delta}
BOTH directions are mandatory — increase and decrease often have different impacts.
| Vector | Description | Severity Modifier |
|--------|-------------|-------------------|
| TTL expiry window | User position's Persistent entry expires (archived) during a long multi-step operation. Step N read panics or returns stale default. | HIGH if position data lost; MEDIUM if restorable |
| Ledger sequence drift | Protocol uses hardcoded ledger counts for delays; Stellar network changes average ledger time. Delays become longer or shorter than intended in wall-clock seconds. | Medium if delay is safety-critical (e.g., withdrawal delay shorter than market manipulation window) |
| Timestamp lag | env.ledger().timestamp() lags wall clock during network stress. Tight timestamp comparisons (<60s) may behave unexpectedly. | Medium if tight comparisons used |
| Config upgraded mid-operation | Admin upgrades contract wasm between Step 1 and Step N. New wasm reads cached params differently. | High — storage layout changes can corrupt cached position |
| Instance TTL expiry | Instance storage (holding config) expires during a long operation. Step N reads config and panics. | MEDIUM — affects all users simultaneously |
For each parameter updated from an external source (oracle contract, price feed):
refresh or crank function? Who calls it and when? What is the protocol state if it is never called?For fee/rate parameters that apply to existing state:
| Parameter | Storage Key | Applies To | Retroactive? | Impact | |-----------|------------|-----------|--------------|--------| | {fee_param} | {DataKey::FeeBps} | {what it affects} | YES/NO | {if retroactive: who is harmed} |
Soroban retroactive patterns:
fee_bps in Instance storage. All pending claims calculated at completion time using new rate — retroactively changes expected returns for users who initiated under old rate.cooldown_ledgers in config. Users who initiated cooldown under old value may now need to wait longer or shorter — retroactive effect on in-flight positions.Rule 2 direction check: Can the admin's parameter change make a user-facing function behave unexpectedly? (e.g., setting cooldown_ledgers = 0 removes withdrawal protection entirely; setting max_withdrawal = 0 blocks all withdrawals). Does the change retroactively affect users in active positions?
For each staleness issue:
Use worst realistic operational state, not current on-chain snapshot:
Severity assessed at: pending_claims=MAX_USERS, fee_delta=MAX_FEE-MIN_FEE, tvl=$XXM
Rationale: Protocol designed for up to {N} concurrent pending operations per documentation
sequence_number or timestamp for timing? If timestamp, is potential lag handled?{CONTRACTS} - Contracts to analyze
{MULTI_STEP_OPS} - Identified multi-step operations
{CACHED_PARAMS} - Parameters cached at initiation (stored in user Persistent entries)
{ADMIN_PARAMS} - Admin-changeable parameters in Instance storage
{DELAY_PARAMS} - Delay/cooldown parameters (in ledger_sequence counts or timestamp seconds)
{FEE_PARAMS} - Fee/rate parameters that may apply retroactively
{CLOCK_SOURCE} - Clock source used (sequence_number / timestamp)
| Field | Required | Description | |-------|----------|-------------| | multi_step_ops | yes | List of multi-step operations found | | cached_params | yes | Parameters cached across steps (stored in which storage type + key) | | staleness_vectors | yes | How cached params can become stale | | retroactive_fees | yes | Fees applied retroactively | | clock_source_audit | yes | Which clock source is used and whether appropriate | | ttl_expiry_risks | yes | Storage entries that could expire mid-operation | | finding | yes | CONFIRMED / REFUTED / CONTESTED | | evidence | yes | Code locations with line numbers | | step_execution | yes | Status for each step |
| Step | Required | Completed? | Notes | |------|----------|------------|-------| | 1. Enumerate Multi-Step Operations | YES | | | | 2. Identify Cached Parameters | YES | | | | 3. Model Staleness Impact (both directions) | YES | | | | 3b. Update Source Audit | YES | | | | 4. Retroactive Application Analysis | YES | | | | 5. Assess Severity | YES | | |
After Step 2: If cached parameters are admin-changeable → MUST complete Step 3 with BOTH increase and decrease scenarios.
After Step 4: Cross-reference with SEMI_TRUSTED_ROLES for admin functions that change these parameters.
After Step 3: If protocol uses timestamp for comparisons tighter than 60 seconds → FLAG clock lag concern.
After Step 1: If any Persistent storage entry has a TTL shorter than the longest expected operation → cross-reference with TTL expiry risk (Step 3 vector: TTL expiry window).
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