Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions examples/user_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# =============================================================================
# MiniChain Storage Paradigms Example: Grouped Values vs Composite Keys
# =============================================================================
#
# Problem Statement:
# Unlike Solidity, which allows multiple distinct `mapping` variables,
# MiniChain provides a single global `storage` dictionary per contract.
# If you naively try to save an age and a name to the same address key:
# storage[sender] = age
# storage[sender] = name <-- This overwrites the age!
#
# This contract demonstrates two paradigms to solve this key collision.
#
# Valid Payloads:
# - 'set_grouped:<age>:<name>'
# - 'update_age_grouped:<new_age>'
#
# - 'set_composite:<age>:<name>'
# - 'update_age_composite:<new_age>'
# =============================================================================

payload = msg['data']
sender = msg['sender']

# -----------------------------------------------------------------------------
# Paradigm 1: Grouped Values
# -----------------------------------------------------------------------------
# We store a Python dictionary as the value for the sender's address.
# Pros: Keeps all user data neatly organized in one object.
# Cons: To update a single field, we must read the whole dict and write it back.

if payload.startswith('set_grouped:'):
parts = payload.split(':')
if len(parts) != 3:
raise Exception("Invalid format. Use set_grouped:<age>:<name>")

age = int(parts[1])
name = parts[2]
Comment on lines +32 to +38

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Names containing colons are rejected by the parser.

payload.split(':') without a maxsplit limit produces more than 3 parts when the name itself contains a colon (e.g., set_grouped:25:John:Doe → 4 parts → "Invalid format"). Since <name> is the last field, use split(':', 2) so the name captures any remaining colons.

🐛 Proposed fix for both set commands
 if payload.startswith('set_grouped:'):
-    parts = payload.split(':')
+    parts = payload.split(':', 2)
     if len(parts) != 3:
 elif payload.startswith('set_composite:'):
-    parts = payload.split(':')
+    parts = payload.split(':', 2)
     if len(parts) != 3:

Also applies to: 69-75

🧰 Tools
🪛 Ruff (0.15.21)

[warning] 35-35: Create your own exception

(TRY002)


[warning] 35-35: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/user_profile.py` around lines 32 - 38, Update the payload parsing
for both set_grouped and the other set command around their respective split
calls to use a maximum of two delimiters, preserving any remaining colons in the
name field. Keep the existing three-part validation and age/name assignment
behavior unchanged.


# Store the entire profile as a single dictionary
storage[sender] = {
"age": age,
"name": name
}

elif payload.startswith('update_age_grouped:'):
parts = payload.split(':')
if len(parts) != 2:
raise Exception("Invalid format. Use update_age_grouped:<new_age>")

new_age = int(parts[1])

# Notice the inefficiency here: we must load the entire dict,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are not loading the entire dict. We are loading the entire profile.

# modify one field, and then re-save the entire dict to storage.
profile = storage.get(sender, {})
if not profile:
raise Exception("Profile not found")

profile["age"] = new_age
storage[sender] = profile # Re-saving the whole dictionary

# -----------------------------------------------------------------------------
# Paradigm 2: Composite Keys
# -----------------------------------------------------------------------------
# We namespace the keys using a string prefix (e.g., "age:0x123").
# Pros: We can update individual fields independently without fetching everything.
# Cons: Data is slightly fragmented across the storage dictionary.

elif payload.startswith('set_composite:'):
parts = payload.split(':')
if len(parts) != 3:
raise Exception("Invalid format. Use set_composite:<age>:<name>")

age = int(parts[1])
name = parts[2]

# Store each field independently using namespaced keys
storage[f"age:{sender}"] = age
storage[f"name:{sender}"] = name

elif payload.startswith('update_age_composite:'):
parts = payload.split(':')
if len(parts) != 2:
raise Exception("Invalid format. Use update_age_composite:<new_age>")

new_age = int(parts[1])

# Notice the efficiency here: we only interact with the exact
# piece of data we want to change, without touching the name at all.
if f"age:{sender}" not in storage:
raise Exception("Profile not found")

storage[f"age:{sender}"] = new_age

else:
raise Exception("Unknown command. Valid commands: set_grouped, update_age_grouped, set_composite, update_age_composite")
Loading