- Docs
- Smart Contracts
- Simple state
Simple state
The state is where the smart contract saves and retrieves data. In a sense, the smart contract state functions similarly to a database interaction layer in a traditional application.
The simplest method of representing state involves storing a single item. For instance, in the cw20 contract, the TokenInfo is recorded during the contract's initialization.
First, a TokenInfo type is declared in state.rs:
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]#[serde(rename_all = "snake_case")]pub struct TokenInfo { pub name: String, pub symbol: String, pub decimals: u8, pub total_supply: Uint128, pub mint: Option<MinterData>,}
Then the storage is initialized:
pub const TOKEN_INFO: Item<TokenInfo> = Item::new("token_info");
In the contract, we see in the instantiate function how data can be saved to this:
let data = TokenInfo { name: msg.name, symbol: msg.symbol, decimals: msg.decimals, total_supply, mint,};TOKEN_INFO.save(deps.storage, & data) ?;
You can get the cw20 base contract by choosing it as the starter template when setting up the project via the archway CLI
Simple state management
State management is a core component of any smart contract in CosmWasm, serving as the place where the contract saves and retrieves data. The state functions similarly to a database in a traditional application, allowing your smart contract to persist information across different transactions.
This guide will walk you through the basics of managing simple state in a CosmWasm contract, using the example of a cw20 token contract.
Declaring the state structure
The first step in managing state is to define the data structure that will be stored. In the cw20 token contract, the TokenInfo type is used to represent essential information about the token, such as its name, symbol, decimals, total supply, and minting details.
Here's how you might define the TokenInfo structure in the state.rs file:
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]#[serde(rename_all = "snake_case")]pub struct TokenInfo { pub name: String, pub symbol: String, pub decimals: u8, pub total_supply: Uint128, pub mint: Option<MinterData>,}
Explanation of attributes
- Serialize, Deserialize: These derive macros allow the structure to be converted to and from JSON or binary formats, which is necessary for saving and retrieving the state.
- Clone, PartialEq, Debug: These traits make the structure easier to work with in Rust, enabling cloning, comparison, and debugging.
- JsonSchema: This derive macro generates a JSON schema for the structure, which can be useful for contract schema generation tools.
- serde(rename_all = "snake_case"): This attribute ensures that all fields in the structure are serialized in snake_case, which is a common convention in JSON APIs.
Initializing the storage
Once the data structure is defined, you need to initialize the storage for it. In CosmWasm, the cw-storage-plus
crate provides a simple way to manage state using types like Item
and Map
.
For a single piece of data, such as TokenInfo, you use the Item
type:
use cw_storage_plus::Item;pub const TOKEN_INFO: Item<TokenInfo> = Item::new("token_info");
Key points
- Item: This is a generic type provided by
cw-storage-plus
that represents a single value stored in the contract’s state. - "token_info": This is the key under which the TokenInfo object will be stored. The key is stored as a string and should be unique within the contract’s state.
Saving data to the state
When your contract is instantiated, you typically save initial data to the state. In the instantiate function, you would create an instance of TokenInfo and save it to the TOKEN_INFO
storage:
let data = TokenInfo { name: msg.name, symbol: msg.symbol, decimals: msg.decimals, total_supply, mint,};TOKEN_INFO.save(deps.storage, &data)?;
Explanation of the Code
- msg: This is the
InstantiateMsg
that contains the parameters provided during contract instantiation. - deps.storage: This is the storage interface provided by CosmWasm, allowing you to save and retrieve data.
- save: The
save
method persists the data to the blockchain state under the specified key ("token_info"). The?
operator handles any potential errors, returning them to the caller if they occur.
Retrieving data from the state
To interact with the stored data, you need to retrieve it from the state. This is typically done in the execute
or query
functions when the contract needs to read the current state.
Here's how you can load the TokenInfo from storage:
let token_info = TOKEN_INFO.load(deps.storage)?;
Explanation of the code
- load: The
load
method retrieves the data associated with the "token_info" key from the storage. If the key doesn’t exist, it returns an error.
Once you have the token_info
object, you can use it within your contract logic, such as verifying the token’s properties or performing state-dependent operations.
Example usage in a CW20 contract
In a typical cw20 contract, you might want to update the total supply when minting new tokens. Here’s how you could do that:
let mut token_info = TOKEN_INFO.load(deps.storage)?;token_info.total_supply += amount;TOKEN_INFO.save(deps.storage, &token_info)?;
Key takeaways
- Mutating the State: By loading the data, modifying it, and then saving it back, you can safely mutate the contract’s state.
- Error Handling: Always ensure you handle potential errors when saving and loading data to avoid runtime panics.
Practical tips for developers
- Use Clear and Descriptive Keys: When defining keys for your state, use clear and descriptive names that reflect the data they represent. This helps maintain clarity in your code, especially as your contract grows in complexity.
- Handle Errors Gracefully: Always anticipate and handle errors when interacting with the state. This will make your contract more robust and easier to debug.
- Leverage the
cw-storage-plus
Crate: Thecw-storage-plus
crate provides powerful abstractions likeItem
andMap
to manage state. Use these tools to simplify your contract’s storage logic. - Schema Generation: Utilize the
JsonSchema
derive macro to automatically generate schemas for your contract’s data structures. This is particularly useful when integrating with frontend applications or other services that need to interact with your contract. - Modularize State Management: Consider separating state management logic into its own module (e.g.,
state.rs
). This keeps your contract code organized and easier to maintain.