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
Scanned 9/6/2026
Install to Claude Code
npx -y skills add PlamenTSV/plamen --skill temporal-parameter-staleness --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Temporal Parameter Staleness?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/plamentsv-temporal-parameter-staleness-76ad2f8d)More formats (shields.io, HTML) on the badges page.
---
name: "temporal-parameter-staleness"
description: "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"
---
# 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).
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!