Store & Retrieve values with Maps
Prerequisites
Before getting started, make sure you have the following ready:
You are generally familiar with command-line interfaces (CLI).
You have installed Rust and set up your development environment as described in one of the sources below:
In the Develop the Counter smart contract section, you created a smart contract designed for the storage and retrieval of a singular numerical value. This tutorial demonstrates how to enhance your smart contract to handle a distinct number for each user by employing the Mapping type. The ink! language offers the Mapping type as a means to organize data in key-value pairs. For instance, the code below shows how to map a number to a specific user:
// Import the `Mapping` type
use ink::storage::Mapping;
#[ink(storage)]
pub struct MyContract {
// Store a mapping from AccountIds to a u32
my_map: Mapping<AccountId, u32>,
}Using the Mapping data type allows you to maintain a distinct storage value for every key. In this tutorial, every AccountId serves as a unique key that is linked to a singular stored numeric value within my_map.
Consequently, each user is limited to storing, increasing, and accessing the value tied to their specific AccountId.
Initialize a Mapping
MappingThe initial step involves setting up the mapping between an AccountId and its corresponding stored value. This requires specifying both the mapping key and the value it is associated with.
The example below demonstrates how to establish a Mapping and accessing a value:
Setting the Caller of the Contract
In the example provided earlier, you may have observed the usage of the Self::env().caller() function. This function, which can be invoked at any point within the contract's logic, consistently identifies the entity calling the contract. It's crucial to distinguish between the contract caller and the origin caller. If a contract invoked by a user subsequently triggers another contract, the Self::env().caller() in this secondary contract refers to the first contract's address, not the initiating user's.
Applying the Contract Caller Information
Understanding the identity of the contract caller is beneficial in various contexts. For instance, Self::env().caller() can be leveraged to establish an access control mechanism that restricts users from interacting only with their data. Additionally, it can be utilized to record the contract's owner at the time of its deployment, as shown in the following example:
By storing the contract caller under the owner identifier, you can subsequently develop functions to verify if the present contract caller is the contract's owner.
Add mapping to the smart contract
You are now ready to introduce a storage map to the incrementer contract.
To add a storage map to the incrementer contract:
Open a terminal shell on your computer, if needed.
Verify that you are in the
incrementerproject folder.Open the
lib.rsfile in a text editor.Import the
Mappingtype.Add the mapping key from
AccountIdto thei32data type stored asmy_map.In the
newconstructor create a newMappingand use that to initialize your contract.In the
defaultconstructor add a new defaultMappingalong with the already defined defaultvalue.Add a
get_mine()function to readmy_mapusing the Mapping API'sget()method and returnmy_mapfor the contract caller.Add a new test to the initialize accounts.
Save your changes and close the file.
Use the
testsubcommand andnightlytoolchain to test your work by running the following command:The command should display output similar to the following to indicate successful test completion:
Enabling Values Modification
The final step of the Incrementer contract involves empowering users to modify their specific values.
This capability can be facilitated through the use of the Mapping API within the smart contract, which grants straightforward access to storage items.
For instance, to replace an existing value for a storage item, you can utilize the Mapping::insert() function with a previously used key. Moreover, values can be updated by initially retrieving them from storage with Mapping::get(), followed by applying Mapping::insert() to input the new value. Should Mapping::get() find no current value for a specified key, it will return None.
Given that the Mapping API offers unmediated access to storage, the Mapping::remove() function can be employed to eliminate the value associated with a particular key from storage.
To add functions for inserting and removing values into the contract:
Open a terminal shell on your computer, if needed.
Verify that you are in the
incrementerproject folder.Open the
lib.rsfile in a text editor.Add an
inc_mine()function that allows the contract caller to get themy_mapstorage item and insert an incrementedvalueinto the mapping.Add a
remove_mine()function that allows the contract caller to clear themy_mapstorage item from storage.Add a new test to verify that the
inc_mine()functions works as expected.Add a new test to verify that the
remove_mine()functions works as expected.Check your work using the
testsubcommand:The command should display output similar to the following to indicate successful test completion:
Last updated