This directory contains automated tests that validate storage key naming conventions across all Remitwise smart contracts. These tests ensure that all storage keys comply with Soroban's symbol_short! constraints and maintain consistency across the codebase.
storage_key_naming_test.rs- Comprehensive validation suite for a hand-maintained catalogue of documented storage keysstorage_key_source_scan_test.rs- Parses each contract crate'ssrc/lib.rsdirectly, extracts every storage-key literal actually passed to a Soroban storage accessor (.get(),.set(),.has(),.remove()), and re-validates those against the same conventions. This catches drift that the hand-maintained catalogue can miss: a key can be added or renamed in source without anyone remembering to updatestorage_key_naming_test.rs, and this scan will still catch a convention violation because it reads the real code, not a snapshot of it.reserved_storage_keys_test.rs- Parses the reserved-key table out ofdocs/RESERVED_STORAGE_KEYS.mdand cross-checks it against the same live source scan used above, failing if any contract stores data under a key set aside for a future roadmap feature (yield generation, staking, the V2 migration path, etc.).
Ensures all storage keys fit within the symbol_short! macro's 9-character limit. Keys exceeding this limit must use Symbol::new() at runtime, which is less efficient.
Validates that all keys use uppercase letters with underscores as separators, following Rust constant naming conventions.
Ensures keys only contain valid characters: A-Z, 0-9, and _.
Checks that each contract doesn't have duplicate storage key definitions.
Validates that all keys are non-empty strings.
Ensures keys don't start or end with underscores.
Validates that keys don't contain multiple consecutive underscores.
Ensures every storage key has a description explaining its purpose.
Identifies common keys used across multiple contracts and validates they're used consistently.
storage_key_source_scan_test.rs runs the same length/format/underscore
checks (items 1, 2, 6, 7 above) directly against every storage-key literal
it finds in each contract's src/lib.rs, plus a scanner self-check that a
fixed set of well-known keys are still found (guards against the parsing
heuristic silently regressing). It recognizes two shapes:
- An inline literal:
env.storage().instance().get(&symbol_short!("KEY")) - A local named constant used at a storage call site:
const KEY: Symbol = symbol_short!("KEY");....set(&KEY, &v)
savings_goals and insurance mostly use a composite DataKey enum
instead of literal keys, so the scan naturally finds fewer entries for
those two crates - see STORAGE_LAYOUT.md.
reserved_storage_keys_test.rs reuses the same key-extraction approach as
the source scan, but checks against a different list: the "reserved for a
future feature" keys documented in
docs/RESERVED_STORAGE_KEYS.md
(e.g. YIELD_CFG, STAKE_POL, REWARD_CF). It parses that document's
table directly, so the doc itself stays the single source of truth - remove
a row there once the feature ships, and the test stops treating that key as
reserved without any test-code change.
Three checks:
- Parser sanity - the expected reserved keys are actually found in the doc's table (guards against a markdown format change silently emptying the reserved set).
- Enforcement (happy path) - no crate in
SCANNED_CRATEScurrently uses a reserved key. - Detector proof (failure mode) - runs the same extraction + check against a synthetic snippet that intentionally reuses a reserved key, proving the detector actually flags it.
cargo test --package testutils storage_key_naming_test -- --nocapture# Test length constraints
cargo test --package testutils test_all_keys_within_max_length -- --nocapture
# Test format validation
cargo test --package testutils test_all_keys_uppercase_with_underscores -- --nocapture
# Test for duplicates
cargo test --package testutils test_no_duplicate_keys_within_contract -- --nocapture
# View storage key summary
cargo test --package testutils test_print_storage_key_summary -- --nocapturecargo test --package testutils --test storage_key_source_scan_test -- --nocapturecargo test --package testutils --test reserved_storage_keys_test -- --nocapture# From the repository root
cargo test --package testutils -- --nocapturerunning 10 tests
✅ All 74 storage keys are within 9 character limit
✅ All 74 storage keys use UPPERCASE_WITH_UNDERSCORES format
✅ No duplicate keys within any contract
✅ All storage keys are non-empty
✅ No storage keys start with underscore
✅ No storage keys end with underscore
✅ No storage keys have consecutive underscores
✅ All 74 storage keys have descriptions
✅ Common key 'PAUSE_ADM' used consistently across 5 contracts
✅ Common key 'VERSION' used consistently across 5 contracts
📊 Storage Key Summary:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total storage keys: 74
Keys per contract:
• bill_payments: 11 keys
• family_wallet: 23 keys
• insurance: 11 keys
• orchestrator: 2 keys
• remittance_split: 10 keys
• reporting: 5 keys
• savings_goals: 12 keys
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
running 10 tests
Storage key length violations found:
❌ remittance_split.NEXT_RSCH_ID: 'NEXT_RSCH_ID' exceeds max length 9 (actual: 12)
❌ bill_payments.ARCHIVED_BILLS: 'ARCHIVED_BILLS' exceeds max length 9 (actual: 14)
test test_all_keys_within_max_length ... FAILED
When adding a new storage key to any contract:
-
Add the key definition to the contract
const KEY_NEW_KEY: Symbol = symbol_short!("NEW_KEY");
-
Update the test file (
storage_key_naming_test.rs)Add your key to the
get_all_storage_keys()function:StorageKey { key: "NEW_KEY", contract: "your_contract", description: "Description of what this key stores", },
-
Run the tests
cargo test --package testutils storage_key_naming_test -- --nocapture -
Fix any violations reported by the tests
-
Update documentation
- Add the key to
STORAGE_LAYOUT.md - Add the key to
docs/storage-key-naming-conventions.md
- Add the key to
Note: storage_key_source_scan_test.rs validates length/format automatically
against the real source, so a non-compliant key literal fails CI even if step
2 (updating the catalogue) is forgotten. The catalogue update in step 2 is
still required for documentation coverage and consistency checks, which only
run against the curated list.
These tests run automatically in CI through the GitHub Actions workflow (.github/workflows/ci.yml). The storage-key-validation job will fail if any naming convention violations are detected, either in the hand-maintained catalogue or in the live source scan.
storage-key-validation:
name: Storage Key Naming Convention Validation
runs-on: macos-latest
timeout-minutes: 5
steps:
- name: Run storage key naming convention tests
run: |
cargo test --package testutils storage_key_naming_test -- --nocapture
continue-on-error: false
- name: Run storage key source scan (drift detection)
run: |
cargo test --package testutils --test storage_key_source_scan_test -- --nocapture
continue-on-error: false
- name: Run reserved storage key enforcement check
run: |
cargo test --package testutils --test reserved_storage_keys_test -- --nocapture
continue-on-error: falseThe test suite should be updated when:
- New contracts are added - Add all storage keys from the new contract
- New keys are added to existing contracts - Add the new keys to the test
- Keys are renamed - Update the key name in the test
- Keys are removed - Remove the key from the test
- Conventions change - Update validation logic and documentation
Storage keys are defined using the StorageKey struct:
struct StorageKey {
key: &'static str, // The actual key string (e.g., "PAUSE_ADM")
contract: &'static str, // Contract name (e.g., "bill_payments")
description: &'static str, // Human-readable description
}All keys are centralized in the get_all_storage_keys() function for easy maintenance.
Problem: A storage key is longer than 9 characters.
Solution: Shorten the key using abbreviations. See docs/storage-key-naming-conventions.md for common abbreviations.
Problem: A storage key contains lowercase letters or invalid characters.
Solution: Convert to UPPERCASE and replace invalid characters with underscores.
Problem: The same key is defined multiple times in a contract.
Solution: Remove the duplicate definition or rename one of the keys.
Problem: A storage key in the test doesn't have a description.
Solution: Add a meaningful description explaining what the key stores.
- Storage Key Naming Conventions
- Reserved Storage Keys
- Storage Layout Documentation
- Soroban Symbol Documentation
- Soroban Storage Example
For questions or issues with the storage key validation tests, please:
- Review the documentation in
docs/storage-key-naming-conventions.md - Check existing test output for guidance
- Open an issue in the repository with the
testinglabel