Skip to content

[Bug]: listStorageKeys() returns the durability instead of the storage key (off-by-one) #87

Closed

Description

@ykargeee-bit

Category: Bug | Difficulty: advanced

Suggested labels: bug, advanced, help wanted


Problem

ContractStateService.listStorageKeys() parses a Redis cache key back into its storage-key component but indexes the wrong array slot, so it returns the durability of every entry rather than the key.

src/modules/stellar/soroban/contract-state.service.ts:109-113:

// Extract just the storage key portion from the full cache key
return keys.map((key) => {
  const parts = key.split(':');
  return parts.length >= 4 ? parts[3] : key;
});

The key format built by cacheKey() is soroban:state:{contractId}:{durability}:{keyPart} (see contract-state.service.ts:225), which splits to:

index value
0 soroban
1 state
2 contractId
3 durability
4 keyPart (the actual storage key)

So parts[3] is the durability string (persistent, temporary, …), not the key.

Why it matters

This corrupts two consumers:

  • exportContractState() (contract-state.service.ts:119-135) calls getState(contractId, key) with the durability, so it reads the wrong entries and produces a garbage export.
  • ContractUpgradeManager.checkStateCompatibility() (contract-upgrade.manager.ts:196-239) diffs these "keys", so every real key looks removed and stateCompatible is effectively always false, which blocks legitimate upgrades.

It also silently discards the durability information that downstream consumers may need.

Suggested direction

  1. Return both key and durability ΓÇö e.g. a StorageKeyRef { key: string; durability: string }[] ΓÇö or at minimum index parts[4].
  2. Guard on parts.length >= 5 (not >= 4) and fall back to the full key when the shape is unexpected.
  3. Prefer encoding the cache key with an unambiguous separator (or store a small JSON envelope) so parsing is not positional. Remember that listStorageKeys and invalidateContract both feed the keyPart into a Redis SCAN MATCH glob, so a : or * inside a key currently collides with other entries ΓÇö see contract-state.service.ts:154.
  4. exportContractState() currently swallows errors with an empty catch { // Skip keys that fail to load } (:129-131). Log each failure at warn with the key so a partial export is diagnosable, and add a SCAN COUNT cap.
  5. contract-state.service.spec.ts:96-103 only tests invalidateContract ΓÇö add coverage for listStorageKeys and exportContractState.

Acceptance criteria

  • listStorageKeys() returns the actual storage key part (parts[4]) and the guard checks parts.length >= 5
  • The returned type carries durability explicitly if that information is needed by callers
  • exportContractState() returns keys that getState() can actually re-read
  • Individual key-read failures are logged rather than silently skipped
  • ContractUpgradeManager.checkStateCompatibility() reports stateCompatible: true for a contract whose key set is unchanged
  • Unit tests cover listStorageKeys, exportContractState, and keys containing : / *
  • Tests added/updated and passing
  • Docs updated if behavior changes

Activity

  1. shobhamerabacha-star commented on Sep 30, 2026

    @shobhamerabacha-star
    Contributor

    Hello team,

    I would like to resolve this issue. Here is my proposed execution plan:

    • Implementation: Fix [Bug]: listStorageKeys() returns the durability instead of the storage key (off-by-one) adhering strictly to the repository architecture and conventions.
    • Testing: Add a regression reproduction test case confirming the bug is permanently resolved.

    I am ready to start immediately—please feel free to assign this to me!

  2. grantfox-oss commented on Oct 2, 2026

    @grantfox-oss

    🎉 This issue has been marked as completed on GrantFox!

    @shobhamerabacha-star's PR #276 was approved and merged by @akargi.

    🏆 @shobhamerabacha-star: You earned 35 FoxPoints for this contribution! Your current tier: Explorer (385 total points). Track your full progress on GrantFox.

    👏 Great work, @shobhamerabacha-star! Keep contributing to Chulilee.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions