ARC-56 Storage Helpers
Description
Section titled “Description”This example demonstrates how to use ARC-56 storage helpers to inspect contract state key definitions and maps from an ARC-56 contract specification: Storage Key Properties (StorageKey):
- key: Base64-encoded key bytes
- key_type: The type of the key (ABIType or AVMType)
- value_type: The type of the value (ABIType or AVMType)
- desc: Optional description Storage Map Properties (StorageMap):
- key_type: The type of keys in the map
- value_type: The type of values in the map
- desc: Optional description
- prefix: Base64-encoded prefix for map keys In the Python SDK, access storage definitions via:
- contract.state.keys.global_state - Global state keys
- contract.state.keys.local_state - Local state keys
- contract.state.keys.box - Box storage keys
- contract.state.maps.global_state - Global state maps
- contract.state.maps.local_state - Local state maps
- contract.state.maps.box - Box storage maps
Prerequisites
Section titled “Prerequisites”No LocalNet required - demonstrates ARC-56 spec parsing
Run This Example
Section titled “Run This Example”From the repository’s examples directory:
cd examplesuv run python abi/15_arc56_storage.py# ruff: noqa: N999, C901, PLR0912, PLR0915"""Example: ARC-56 Storage Helpers
This example demonstrates how to use ARC-56 storage helpers to inspectcontract state key definitions and maps from an ARC-56 contract specification:
Storage Key Properties (StorageKey):- key: Base64-encoded key bytes- key_type: The type of the key (ABIType or AVMType)- value_type: The type of the value (ABIType or AVMType)- desc: Optional description
Storage Map Properties (StorageMap):- key_type: The type of keys in the map- value_type: The type of values in the map- desc: Optional description- prefix: Base64-encoded prefix for map keys
In the Python SDK, access storage definitions via:- contract.state.keys.global_state - Global state keys- contract.state.keys.local_state - Local state keys- contract.state.keys.box - Box storage keys- contract.state.maps.global_state - Global state maps- contract.state.maps.local_state - Local state maps- contract.state.maps.box - Box storage maps
No LocalNet required - demonstrates ARC-56 spec parsing"""
import base64from pathlib import Path
from shared import format_hex, print_header, print_info, print_step, print_success
from algokit_abi import abifrom algokit_abi.arc56 import Arc56Contract, AVMType, StorageKey, StorageMap
def format_key_or_value_type(type_val: abi.ABIType | AVMType) -> str: """Formats a storage key type for display (either ABI type or AVM type).""" if isinstance(type_val, AVMType): return type_val.value # AVM type (e.g., "AVMUint64", "AVMString", "AVMBytes") return str(type_val) # ABI type
def display_storage_key(name: str, storage_key: StorageKey) -> None: """Displays StorageKey properties.""" print_info(f" {name}:") print_info(f" key (base64): {storage_key.key}") try: decoded_key = base64.b64decode(storage_key.key).decode("utf-8") print_info(f' key (decoded): "{decoded_key}"') except (UnicodeDecodeError, ValueError): decoded_bytes = base64.b64decode(storage_key.key) print_info(f" key (decoded): {format_hex(decoded_bytes)}") print_info(f" key_type: {format_key_or_value_type(storage_key.key_type)}") print_info(f" value_type: {format_key_or_value_type(storage_key.value_type)}") if storage_key.desc: print_info(f" desc: {storage_key.desc}")
def display_storage_map(name: str, storage_map: StorageMap) -> None: """Displays StorageMap properties.""" print_info(f" {name}:") print_info(f" key_type: {format_key_or_value_type(storage_map.key_type)}") print_info(f" value_type: {format_key_or_value_type(storage_map.value_type)}") if storage_map.desc: print_info(f" desc: {storage_map.desc}") if storage_map.prefix is not None: print_info(f' prefix (base64): "{storage_map.prefix}"') if storage_map.prefix: try: decoded_prefix = base64.b64decode(storage_map.prefix).decode("utf-8") print_info(f' prefix (decoded): "{decoded_prefix}"') except (UnicodeDecodeError, ValueError): decoded_bytes = base64.b64decode(storage_map.prefix) print_info(f" prefix (decoded): {format_hex(decoded_bytes)}")
def main() -> None: print_header("ARC-56 Storage Helpers Example")
# Step 1: Load ARC-56 contract specification print_step(1, "Load ARC-56 Contract Specification")
# Load the State.arc56.json from test artifacts arc56_path = Path(__file__).parent.parent.parent / "tests" / "artifacts" / "state_contract" / "State.arc56.json" arc56_content = arc56_path.read_text() app_spec = Arc56Contract.from_json(arc56_content)
print_info(f"Loaded contract: {app_spec.name}") print_info(f"ARC standards supported: {', '.join(str(arc) for arc in app_spec.arcs)}") print_info("") print_info("State schema:") print_info( f" Global: {app_spec.state.schema.global_state.ints} ints, {app_spec.state.schema.global_state.bytes} bytes" ) print_info( f" Local: {app_spec.state.schema.local_state.ints} ints, {app_spec.state.schema.local_state.bytes} bytes" )
# Step 2: Demonstrate accessing global state keys print_step(2, "Get Global State Key Definitions")
print_info("Accessing contract.state.keys.global_state to get all global state keys:") print_info("")
global_keys = app_spec.state.keys.global_state global_key_names = list(global_keys.keys())
print_info(f"Found {len(global_key_names)} global state keys:") print_info("")
for name in global_key_names: display_storage_key(name, global_keys[name]) print_info("")
# Step 3: Demonstrate accessing local state keys print_step(3, "Get Local State Key Definitions")
print_info("Accessing contract.state.keys.local_state to get all local state keys:") print_info("")
local_keys = app_spec.state.keys.local_state local_key_names = list(local_keys.keys())
print_info(f"Found {len(local_key_names)} local state keys:") print_info("")
for name in local_key_names: display_storage_key(name, local_keys[name]) print_info("")
# Step 4: Demonstrate accessing box keys print_step(4, "Get Box Key Definitions")
print_info("Accessing contract.state.keys.box to get all box storage keys:") print_info("")
box_keys = app_spec.state.keys.box box_key_names = list(box_keys.keys())
if box_key_names: print_info(f"Found {len(box_key_names)} box storage keys:") print_info("") for name in box_key_names: display_storage_key(name, box_keys[name]) print_info("") else: print_info("No box storage keys defined in this contract.")
# Step 5: Demonstrate accessing box maps print_step(5, "Get Box Map Definitions")
print_info("Accessing contract.state.maps.box to get box map definitions:") print_info("")
box_maps = app_spec.state.maps.box box_map_names = list(box_maps.keys())
if box_map_names: print_info(f"Found {len(box_map_names)} box maps:") print_info("") for name in box_map_names: display_storage_map(name, box_maps[name]) print_info("") else: print_info("No box maps defined in this contract.")
# Step 6: Demonstrate decoding storage keys print_step(6, "Decoding Storage Key Names")
print_info("Storage keys are stored as base64-encoded bytes.") print_info("Decode them to see the actual key names used in the contract:") print_info("")
print_info("Global state keys decoded:") for name in global_key_names: key_base64 = global_keys[name].key try: key_decoded = base64.b64decode(key_base64).decode("utf-8") print_info(f" {name}: '{key_decoded}'") except UnicodeDecodeError: key_bytes = base64.b64decode(key_base64) print_info(f" {name}: {format_hex(key_bytes)} (binary)")
print_info("") print_info("Local state keys decoded:") for name in local_key_names: key_base64 = local_keys[name].key try: key_decoded = base64.b64decode(key_base64).decode("utf-8") print_info(f" {name}: '{key_decoded}'") except UnicodeDecodeError: key_bytes = base64.b64decode(key_base64) print_info(f" {name}: {format_hex(key_bytes)} (binary)")
# Step 7: Understanding type categories in storage print_step(7, "Understanding Type Categories in Storage")
print_info("Storage keys can have AVM types or ABI types for keys and values:") print_info("") print_info("AVM Types (native stack values, no length prefix):") print_info(" - AVMBytes: Raw bytes") print_info(" - AVMString: UTF-8 string") print_info(" - AVMUint64: 64-bit unsigned integer") print_info("") print_info("ABI Types (ARC-4 encoded with potential length prefixes):") print_info(" - string: 2-byte length prefix + UTF-8") print_info(" - uint64: 8 bytes big-endian") print_info(" - (tuple): Head/tail encoding") print_info(" - etc.") print_info("")
# Analyze types used in this contract print_info("Types used in this contract's storage:") print_info("")
avm_count = 0 abi_count = 0
for key in global_keys.values(): key_is_avm = isinstance(key.key_type, AVMType) val_is_avm = isinstance(key.value_type, AVMType) if key_is_avm: avm_count += 1 else: abi_count += 1 if val_is_avm: avm_count += 1 else: abi_count += 1
for key in local_keys.values(): key_is_avm = isinstance(key.key_type, AVMType) val_is_avm = isinstance(key.value_type, AVMType) if key_is_avm: avm_count += 1 else: abi_count += 1 if val_is_avm: avm_count += 1 else: abi_count += 1
print_info(f" AVM type usages: {avm_count}") print_info(f" ABI type usages: {abi_count}")
# Step 8: Creating a synthetic example with maps print_step(8, "Working with Storage Maps")
print_info("Storage maps define key-value relationships with typed keys and values.") print_info("")
# Create a synthetic example showing what a box map would look like synthetic_spec = { "name": "ExampleWithMaps", "arcs": [4, 56], "methods": [], "state": { "schema": {"global": {"ints": 0, "bytes": 0}, "local": {"ints": 0, "bytes": 0}}, "keys": {"global": {}, "local": {}, "box": {}}, "maps": { "global": {}, "local": {}, "box": { "userBalances": { "keyType": "address", "valueType": "uint64", "prefix": "dXNlcl8=", # base64("user_") "desc": "Maps user addresses to their balances", }, "orderData": { "keyType": "uint64", "valueType": "(address,uint64,string)", "desc": "Maps order IDs to order tuples", }, }, }, }, }
example_spec = Arc56Contract.from_dict(synthetic_spec)
print_info(f"Synthetic contract: {example_spec.name}") print_info("") print_info("Box maps defined:") print_info("")
for name, storage_map in example_spec.state.maps.box.items(): display_storage_map(name, storage_map) print_info("")
# Step 9: Practical use cases print_step(9, "Practical Use Cases")
print_info("ARC-56 storage inspection enables:") print_info("") print_info("1. Contract State Discovery") print_info(" - List all state keys without reading contract source") print_info(" - Understand data types for proper encoding/decoding") print_info("") print_info("2. Generic Contract Explorers") print_info(" - Build tools that can inspect any ARC-56 contract") print_info(" - Automatically decode state values using type info") print_info("") print_info("3. Runtime Type Validation") print_info(" - Validate state key/value types before transactions") print_info(" - Ensure proper encoding based on AVM vs ABI type") print_info("") print_info("4. Documentation Generation") print_info(" - Auto-generate state documentation from spec") print_info(" - Include type information and descriptions")
# Step 10: Summary print_step(10, "Summary")
print_info("ARC-56 Storage Classes:") print_info("") print_info("State (contract.state):") print_info(" keys: Keys - Container for storage key definitions") print_info(" maps: Maps - Container for storage map definitions") print_info(" schema: Schema - State schema (ints/bytes counts)") print_info("") print_info("Keys (contract.state.keys):") print_info(" global_state: dict[str, StorageKey] - Global state keys") print_info(" local_state: dict[str, StorageKey] - Local state keys") print_info(" box: dict[str, StorageKey] - Box storage keys") print_info("") print_info("Maps (contract.state.maps):") print_info(" global_state: dict[str, StorageMap] - Global state maps") print_info(" local_state: dict[str, StorageMap] - Local state maps") print_info(" box: dict[str, StorageMap] - Box storage maps") print_info("") print_info("StorageKey Properties:") print_info(" key - Base64-encoded key bytes") print_info(" key_type - Type of the key (ABIType or AVMType)") print_info(" value_type - Type of the value (ABIType or AVMType)") print_info(" desc - Optional description") print_info("") print_info("StorageMap Properties:") print_info(" key_type - Type of keys in the map") print_info(" value_type - Type of values in the map") print_info(" desc - Optional description") print_info(" prefix - Base64-encoded prefix for map keys") print_info("") print_info("Use Cases:") print_info(" - Inspect contract state schema from ARC-56 spec") print_info(" - Decode raw state bytes using typed definitions") print_info(" - Build generic contract explorers/tools") print_info(" - Validate state key/value types at runtime")
print_success("ARC-56 Storage Helpers example completed successfully!")
if __name__ == "__main__": main()