Repository navigation
[Bug]: listStorageKeys() returns the durability instead of the storage key (off-by-one) #87
Copy link
Copy link
Closed
Description
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) callsgetState(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 andstateCompatibleis effectively alwaysfalse, which blocks legitimate upgrades.
It also silently discards the durability information that downstream consumers may need.
Suggested direction
- Return both
keyanddurabilityΓÇö e.g. aStorageKeyRef { key: string; durability: string }[]ΓÇö or at minimum indexparts[4]. - Guard on
parts.length >= 5(not>= 4) and fall back to the full key when the shape is unexpected. - Prefer encoding the cache key with an unambiguous separator (or store a small JSON envelope) so parsing is not positional. Remember that
listStorageKeysandinvalidateContractboth feed thekeyPartinto a RedisSCAN MATCHglob, so a:or*inside a key currently collides with other entries ΓÇö seecontract-state.service.ts:154. exportContractState()currently swallows errors with an emptycatch { // Skip keys that fail to load }(:129-131). Log each failure atwarnwith the key so a partial export is diagnosable, and add aSCAN COUNTcap.contract-state.service.spec.ts:96-103only testsinvalidateContractΓÇö add coverage forlistStorageKeysandexportContractState.
Acceptance criteria
-
listStorageKeys()returns the actual storage key part (parts[4]) and the guard checksparts.length >= 5 - The returned type carries durability explicitly if that information is needed by callers
-
exportContractState()returns keys thatgetState()can actually re-read - Individual key-read failures are logged rather than silently skipped
-
ContractUpgradeManager.checkStateCompatibility()reportsstateCompatible: truefor 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
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
shobhamerabacha-star commentedon Sep 30, 2026
Hello team,
I would like to resolve this issue. Here is my proposed execution plan:
[Bug]:listStorageKeys()returns the durability instead of the storage key (off-by-one)adhering strictly to the repository architecture and conventions.I am ready to start immediately—please feel free to assign this to me!
grantfox-oss commentedon Oct 2, 2026
🎉 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.