XION
DiscordGithub
  • Welcome to XION
  • XION'S Core
    • Concepts
      • Generalized Chain Abstraction
      • Intro to Account Abstraction
      • XION's Meta Accounts
      • Meta Accounts Design
      • Architecture & Tech Glossary
      • Use Cases
  • developers
    • Xion Quick Start
      • Zero to Dapp in 5 Minutes
        • Launch a User Map Dapp on XION in 5 Minutes
        • React Native Mobile Dapp on XION in 5 Minutes
      • Set Up Local Environment
        • Setting up your Local Smart Contract Development Environment for XION
          • Setting up your XION Smart Contract Development Environment on Windows (WSL2 + Ubuntu)
        • Set Up an Integrated Development Environment (IDE)
        • Interacting with Xion Chain using Xion Daemon
      • Your First Contract
        • Deploying Your First Smart Contract on XION
      • Gasless UX & Permission Grants
        • Enabling Gasless Transactions with Treasury Contracts
      • Your First DApp
        • Account Abstraction with Gasless Transactions
        • Interact with XION via your Backend Service
    • Web3 for Web2 Developers
      • Web2 vs Web3 App Architecture: A Comparison
      • Misconceptions and Misapplied Web2 Patterns
      • Recommended Architecture for Dapps on XION
    • Building for Mainnet
      • Xion Testnet: Your Development Playground
      • Building with Audited & Battle-Tested Contracts
      • Community Engagement: Building Support for Your dApp
      • Deploying to Xion Mainnet
        • Smart Contract Audits: Process, Costs & Support
        • Governance Process to Deploying Smart Contracts to Mainnet
    • Learn & Build
      • Token Factory
        • Creating, Minting, and Interacting with a Token Factory Token
        • Building a React dApp to Interact with Token Factory Tokens
        • Integrating a Token Factory Token in a Smart Contract
      • Websockets
        • WebSockets with Xion: Real-Time Communication
      • Mobile Development
        • Building a React Native Mobile App with Abstraxion (Xion.js)
      • Oracles
        • Creating a Smart Contract with Pyth Oracle Integration
      • Indexers: Optimized Data Retrieval
        • SubQuery
      • Use Cases
        • Building a Per-User Data Storage Dapp
    • Reference and Resources
      • Requesting XION Testnet Tokens
      • Public Endpoints & Resources
      • Block Explorers
      • Governance
        • Deploying Smart Contracts to Mainnet
      • Developer Tools: Abstract
      • IBC Denoms on XION Networks
      • Frequently Asked Questions
      • XION Token Contract Addresses on EVM Chains
  • Nodes & Validators
    • Run a Node
      • System Specifications
      • Build the Xion Daemon
      • Download the Xion Daemon
      • Configure the Xion Daemon
        • app.toml
        • client.toml
        • config.toml
      • Join the XION Network
        • xion-testnet-1
      • Confirm node is running
    • Become a Validator
      • Initial Setup
      • Obtain a XION Wallet Address
      • Obtain Funds
        • Testnet
      • Create Validator
    • IBC Relayers and Tokens
  • Others
    • Resources
Powered by GitBook
On this page
  • Naive Implementation
  • Optimized Implementation
  • Now for the Group
  • Establishing Relations and Defining Roles

Was this helpful?

Edit on GitHub
  1. developers
  2. Reference and Resources
  3. CosmWasm Resources
  4. Tutorials
  5. Key Value Storage

State Modeling

The design of key-value storage can appear challenging to individuals with a background in SQL. Even though databases like MongoDB or other streamlined systems incorporate key-value storage, their libraries shield developers from the underlying intricacies.

This complexity is why comprehending the storage system in Cosmos-SDK might not come easily initially. Nevertheless, once you grasp the concept, it becomes straightforward.

When implementing a state model, it's essential to pause and consider some questions before commencing the implementation. For instance:

Do you truly need to persist that data in the blockchain state?

Is the connection genuinely indispensable, or could it be handled by an off-chain database collector for the UI?

By posing these inquiries, you can sidestep unnecessary data writes to the state and prevent excessive storage utilization, ultimately resulting in more cost-effective execution.

Tutorial Introduction

This tutorial guides you through the process of crafting a state model to address a specific business scenario.

The use case is outlined as follows:

  • The system encompasses individuals.

  • Individuals possess the capability to join various groups.

  • Groups have the capacity to encompass multiple individuals.

  • Members within these groups can assume distinct roles, including but not limited to admin, super-admin, and regular members, among others.

Naive Implementation

Below is a design for an any-to-any relation to store data utilizing IDs. Initially, individual data is organized and indexed through an auto-incremented ID:

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct Person {
    pub name: String,
    pub age: i32,
    pub membership_ids: Vec<String>
}
pub const PEOPLE: Map<&[u8], Person> = Map::new("people");

Within this design, groups are similarly organized and identified through the use of an ID.

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct Group {
    pub name: String,
    pub membership_ids: Vec<String>
}
pub const GROUPS: Map<&[u8], Group> = Map::new("groups");

The connection between groups and individuals is established through a membership structure.

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct Membership {
  pub person_id: String,
  pub group_id: String,
  pub membership_status_id: String
}
pub const MEMBERSHIPS: Map<&[u8], Membership> = Map::new("memberships");

The membership status is determined through the utilization of a "status" string field.

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct MembershipStatus {
  pub status: String,
  pub membership_ids: Vec<String>
}
pub const MEMBERSHIP_STATUSES: Map<&[u8], MembershipStatus> = Map::new("membership_statuses");

Optimized Implementation

Although utilizing an ID for person identification might initially appear intuitive, it introduces redundancy. IDs essentially serve as values for user identification, yet users are already uniquely identified by their Address. Consequently, it is more advisable to index individuals based on their Address instead of relying on auto-incremented integers.

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct Person {
    pub name: String,
    pub age: u8, // changed to u8 since ages are unsigned and 100 years max.
}
// Addr -> Person
pub const PEOPLE: Map<&[u8], Person> = Map::new("people");

The "membership_id" has been eliminated, and the data type "i32" has been replaced with "u8." Optimizing variable types enhances gas efficiency, leading to reduced transaction fees.

Now for the Group

Since groups lack an address, it is logical to distinguish them through auto-incremented IDs. If uniqueness in group names is desired, it's more suitable to employ the group name as the indexing mechanism.

pub const GROUP_COUNTER: Item<u64> = Item::new("group_counter");
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct Group {
  pub name: String,
}
// u64 ID -> Group
pub const GROUPS: Map<U64Key, Group> = Map::new("groups");

When saving a group, it's imperative to obtain and store the auto-incremented ID in the GROUP_COUNTER item. To execute this logic, it is advisable to encapsulate it within a dedicated function:

pub fn next_group_counter(store: &mut dyn Storage) -> StdResult<u64> {
  let id: u64 = GROUP_COUNTER.may_load(store)?.unwrap_or_default() + 1;
  GROUP_COUNTER.save(store, &id)?;
  Ok(id)
}
pub fn save_group(store: &mut dyn Storage, group: &Group) -> StdResult<()> {
  let id = next_group_counter(store)?;
  let key = U64Key::new(id);
  NEW_GROUPS.save(store, key, group)
}

Establishing Relations and Defining Roles

To establish a relationship between groups and individuals, as well as define an individual's role, the following steps are necessary:

  1. Create a list of users associated with a specific group.

  2. Develop a list of groups to which a particular user belongs.

These tasks can be achieved by constructing secondary indexes.

It is strongly encouraged to undertake the remaining implementation as a personal exercise.

Last updated 1 year ago

Was this helpful?