Agent skill

temporal-parameter-staleness

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

Stars 215
Forks 33

Install this agent skill to your Project

npx add-skill https://github.com/PlamenTSV/plamen/tree/main/agents/skills/soroban/temporal-parameter-staleness

SKILL.md

TEMPORAL_PARAMETER_STALENESS Skill (Soroban)

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 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.


Step 1: Enumerate Multi-Step Operations

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:

  • What parameters are read/cached at Step 1 (stored in Persistent/Instance storage)?
  • What parameters are re-read at Step N?
  • What parameters are used but NOT re-read at Step N?
  • Which clock source is used? (env.ledger().timestamp() vs env.ledger().sequence_number())

Soroban Clock Semantics

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 slowdowns
  • sequence_number is exact but requires converting to human time (~5s per ledger, subject to change if Stellar network parameters change)
  • Hardcoded sequence_number durations break if Stellar's ledger close time changes (currently ~5s, configurable by validators)

Step 2: Identify Cached Parameters

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:

  • Instance storage cache: User's position struct stores a snapshot of config params at initiation time (e.g., position.fee_rate = config.fee_rate at deposit time)
  • Persistent storage cache: Per-user Persistent entry stores the start ledger or timestamp (e.g., user_position.start_ledger = env.ledger().sequence_number())
  • Re-read pattern: Step N re-reads from Instance storage directly — no staleness for that param
  • Allowance TTL as cached permission: An allowance stored in Temporary storage expires by expiration_ledger — if the operation takes longer than expected, the allowance expires and Step N fails

Red 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.


Step 3: Model Staleness Impact

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.

Soroban-Specific Staleness Vectors

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

Step 3b: Update Source Audit

For each parameter updated from an external source (oracle contract, price feed):

  • Is the source the correct representation of what this parameter tracks?
  • Is the source contract address validated? Can a fake oracle be substituted via a user-supplied address?
  • Should this parameter be fixed for a period (e.g., per epoch cycle) rather than continuously refreshed?
  • Which functions update it? Which SHOULD? Any mismatch?
  • Is there a refresh or crank function? Who calls it and when? What is the protocol state if it is never called?

Step 4: Retroactive Application Analysis

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:

  • Instance config update: Admin changes 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 ledger update: Admin changes 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.
  • Rate-at-close pattern: If the protocol reads the current config rate at Step N (not the cached rate from Step 1), any admin change between steps applies retroactively to all pending operations.

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?


Step 5: Assess Severity

For each staleness issue:

  • Who is affected? (single user, all users with pending operations, protocol)
  • Is the impact bounded? (capped by parameter range, max delay, etc.)
  • Can it be exploited intentionally? (admin or operator timing changes to specific ledgers)
  • Is there a recovery path? (cancel and re-initiate, admin override)
  • Ledger precision: Given ~5s ledger close time, how precisely can an attacker time the exploitation?

Severity Assessment (Rule 10 — Worst-State)

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

Key Questions (must answer all)

  1. What multi-step operations exist? (initiate/claim, deposit/cooldown/withdraw, propose/execute)
  2. For each cached parameter: can admin change it between steps?
  3. What happens if a delay DECREASES after initiation? (users can complete too early relative to new policy)
  4. What happens if a delay INCREASES after initiation? (users locked longer than originally expected)
  5. Are fees applied retroactively to existing positions or only to new ones?
  6. Is there a maximum parameter range that bounds the staleness impact?
  7. Soroban-specific: Does the protocol use sequence_number or timestamp for timing? If timestamp, is potential lag handled?
  8. Soroban-specific: Does any Persistent storage entry have a TTL shorter than the longest expected operation? Can it expire mid-operation?
  9. Soroban-specific: Is there a crank/refresh function? What happens if it is never called?
  10. Soroban-specific: If the contract is upgraded between Step 1 and Step N, does the new wasm correctly interpret cached values from Persistent storage?

Common False Positives

  • Immutable config: If Instance storage has no update function or admin is revoked, no staleness
  • Bounded ranges: If min/max bounds limit the change magnitude (enforced on-chain), impact may be Low
  • User can cancel: If users can cancel pending operations and re-initiate, reduced severity
  • Timelock protection: If parameter changes require a proposal+delay pattern, users have time to react
  • Same-transaction operations: Operations completing within a single invocation tree cannot have clock staleness between steps
  • Re-read at completion: If Step N re-reads directly from Instance storage (not a cached copy in Persistent), no staleness for that parameter

Instantiation Parameters

{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)

Output Schema

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 Execution Checklist (MANDATORY)

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

Cross-Reference Markers

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).

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results