infrablockchain-docs
en
en
  • InfraBlockchain
    • Learn
      • Architecture
        • Architecture
        • Network Participants
        • Parachain
          • System Parachains
      • Protocol
        • System Token
        • Transaction Fee
        • Proof of Transaction
      • Substrate
        • Learn
          • Basic
            • Cryptography
            • Blockchain Basics
            • Consensus
            • Networks and Nodes
            • Blockchain Transaction
            • Transaction Life Cycle
            • Offchain Operations
            • Light Client
            • Rust for Substrate
            • Introduction to Library
            • Architecture and Rust Libraries
            • File Architecture
            • Accounts, Addresses, and Keys
            • Transaction Format
            • Blockchain Randomness
          • FRAME
            • FRAME Pallets
            • FRAME Macros
            • Custom Pallets
            • Pallet Coupling
            • Origin
            • Events and Erros
            • Runtime Storage
            • State Transitions and Storage
            • SCALE Encoding
            • Weight and Fee
            • Runtime API
            • Runtime Development
          • Account
          • Address Format
          • Glossary
          • CLI
            • Archive
            • Memory Profiler
            • Node Template
            • sidecar
            • srtool
            • Subkey
            • subxt
            • try-runtime
            • tx-wrapper
          • Runtime Development
            • Basics
              • Configure Genesis State
              • Configure Runtime Constants
              • Customize a Chain Spec
              • Import a Pallet
              • Use Helper Function
            • Consensus Model
              • PoW
              • Create a Hybrid Node
            • Offchain Worker
              • Request Offchain HTTP
              • Offchain Indexing
              • Offchain Local Storage
            • Pallet Design
              • Create a Storage Structure
              • Implement Lockable Currency
              • Incorporate Randomness
              • Loose Coupling
              • Tight Coupling
            • Parachain Development
              • Add HRMP Channel
              • Add Paranodes
              • Connect to a Local Relay Chain
              • Convert a Solo Chain
              • Prepare to Launch
              • Select Collator
              • Upgrade a Parachain
            • Storage Migration
              • Basics
              • Trigger Migration
            • Test
              • Basics
              • Test a Transfer Transaction
            • Tools
              • Create a TxWrapper
              • Use Sidecar
              • try-runtime
              • Verify WASM
            • Weigths
              • Benchmark
              • Calculate Fees
              • Use Conditional Weights
              • Use Custom Weights
        • Build
          • Decide What to Build
          • Build Process
          • Determinisitc Runtime
          • Chain Spec
          • Genesis Configuration
          • Application Development
          • RPC
          • Troubleshoot Your Code
        • Tutorials
          • Install
            • Developer Tools
            • Linux
            • macOS
            • Rust Toolchain
            • Issues
            • Windows
          • Quick Start
            • Explore the Code
            • Modify Runtime
            • Start a Node
            • Substrate Basics
          • Build a Blockchain
            • Add Trusted Nodes
            • Authorize Specific Nodes
            • Build a Local Blockchain
            • Simulate Network
            • Upgrade a Running Network
          • Build Application Logic
            • Add a Pallet
            • Add Offchasin Workers
            • Publish Custom Pallets
            • Specify Origin for a Call
            • Use Macros in a Custom Pallet
          • Integrate with Tools
            • Access EVM Accounts
            • EVM Integration
            • Explore Sidecar Endpoints
            • Integrate a Light Client Node
          • Smart Contracts
            • Strategy
            • Build a Token Contract
            • Develop a Smart Contract
            • Prepare Your First Contract
            • Troubleshoot Smart Contracts
            • Use Maps for Storing Values
      • XCM
        • XCM
        • XCM Format
    • Service Chains
      • InfraDID
      • InfraEVM
      • URAuth(Universal Resource Auth)
    • DevOps
      • Build
      • Deploy
      • Monitoring
      • Runtime Upgrade
    • Tutorials
      • Basic
        • How to Interact with System Token
        • How To Pay Transaction Fee
        • How To Vote with TaaV
        • Hot to Get Validator Reward
      • Build
        • Build InfraRelayChain
        • Build Parachain
        • Open Message Passing Channels
        • Transfer Assets with XCM
      • Test
        • Benchmark
        • Check Runtime
        • Debug
        • Simulate Parachains
        • Unit Testing
      • Service Chains
        • Play with InfraDID
          • Build
          • Add Keys
          • Add Service Endpoint
          • Create InfraDID
        • Play with InfraEVM
          • Build
          • Deposit and Withdraw Token
          • Deploy ERC20 Contract
          • Deploy ERC721 Contract
          • Deploy ERC1155 Contract
  • Newnal Data Market
Powered by GitBook
On this page
  • Initialize a Mapping
  • Add mapping to the smart contract
  • Insert, update, or remove values
  • Next steps
  1. InfraBlockchain
  2. Learn
  3. Substrate
  4. Tutorials
  5. Smart Contracts

Use Maps for Storing Values

Use mapping to store data as key-value pairs.

PreviousTroubleshoot Smart ContractsNextXCM

Last updated 1 year ago

In , you developed a smart contract for storing and retrieving a single numeric value.

This tutorial illustrates how you can extend the functionality of your smart contract to manage one number per user. To add this functionality, you'll use the type.

The ink! language provides the Mapping type to enable you to store data as key-value pairs. For example, the following code illustrates mapping a user to a number:

// 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>,
}

With the Mapping data type, you can store a unique instance of the storage value for each key.

For this tutorial, each AccountId represents a key that maps to one and only one stored numeric my_map.

Each user can only store, increment, and retrieve the value associated with their own AccountId.

Initialize a Mapping

The first step is to initialize the mapping between an AccountId and a stored value.

  • Specify the mapping key and the value mapped to it.

The following example illustrates how to initialize a Mapping and retrieve a value:

#![cfg_attr(not(feature = "std"), no_std)]

#[ink::contract]
mod mycontract {
    use ink::storage::Mapping;

    #[ink(storage)]
    pub struct MyContract {
        // Store a mapping from AccountIds to a u32
        my_map: Mapping<AccountId, u32>,
    }

    impl MyContract {
        #[ink(constructor)]
        pub fn new(count: u32) -> Self {
            let mut my_map = Mapping::default();
            let caller = Self::env().caller();
            my_map.insert(&caller, &count);

            Self { my_map }
        }

        // Get the number associated with the caller's AccountId, if it exists
        #[ink(message)]
        pub fn get(&self) -> u32 {
            let caller = Self::env().caller();
            self.my_map.get(&caller).unwrap_or_default()
        }
    }
}

Identifying the contract caller

In the preceding example, you might have noticed the Self::env().caller() function call.

This function is available throughout the contract logic and always returns the contract caller.

It is important to note that the contract caller is not the same as the origin caller.

If a user accesses a contract that then calls a subsequent contract, the Self::env().caller() in the second contract is the address of the first contract, not the original user.

Using the contract caller

There are many scenarios where having the contract caller available is useful.

For example, you can use Self::env().caller() to create an access control layer that only allows users to access their own values.

You can also use Self::env().caller() to save the contract owner during contract deployment.

For example:

#![cfg_attr(not(feature = "std"), no_std)]

#[ink::contract]
mod my_contract {

    #[ink(storage)]
    pub struct MyContract {
        // Store a contract owner
        owner: AccountId,
    }

    impl MyContract {
        #[ink(constructor)]
        pub fn new() -> Self {
            Self {
                owner: Self::env().caller(),
            }
        }
        /* --snip-- */
    }
}

Because you have saved the contract caller using the owner identifier, you can later write functions that check whether the current contract caller is the owner of the contract.

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:

  1. Open a terminal shell on your computer, if needed.

  2. Verify that you are in the incrementer project folder.

  3. Open the lib.rs file in a text editor.

  4. Import the Mapping type.

    #[ink::contract
    mod incrementer {
        use ink::storage::Mapping;
  5. Add the mapping key from AccountId to the i32 data type stored as my_map.

    pub struct Incrementer {
        value: i32,
        my_map: Mapping<AccountId, i32>,
    }
  6. In the new constructor create a new Mapping and use that to initialize your contract.

    #[ink(constructor)]
    pub fn new(init_value: i32) -> Self {
        let mut my_map = Mapping::default();
        let caller = Self::env().caller();
        my_map.insert(&caller, &0);
    
        Self {
            value: init_value,
            my_map,
        }
    }
  7. In the default constructor add a new default Mapping along with the already defined default value.

    #[ink(constructor)]
    pub fn default() -> Self {
    Self {
            value: 0,
            my_map: Mapping::default(),
        }
    }
  8. Add a get_mine() function to read my_map using the Mapping API's get() method and return my_map for the contract caller.

    #[ink(message)]
    pub fn get_mine(&self) -> i32 {
         let caller = self.env().caller();
         self.my_map.get(&caller).unwrap_or_default()
    }
  9. Add a new test to the initialize accounts.

     #[ink::test]
    fn my_map_works() {
        let contract = Incrementer::new(11);
        assert_eq!(contract.get(), 11);
        assert_eq!(contract.get_mine(), 0);
    }
  10. Save your changes and close the file.

  11. Use the test subcommand and nightly toolchain to test your work by running the following command:

    cargo test

    The command should display output similar to the following to indicate successful test completion:

    running 3 tests
    test incrementer::tests::default_works ... ok
    test incrementer::tests::it_works ... ok
    test incrementer::tests::my_map_works ... ok
    
    test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Insert, update, or remove values

The final step in the Incrementer contract is to allow users to update their own values.

You can use calls to the Mapping API to provide this functionality in the smart contract.

The Mapping provides direct access to storage items.

For example, you can replace a previous value held for a storage item by calling Mapping::insert() with an existing key.

You can also update values by first reading them from storage using Mapping::get(), then update the value with Mapping::insert().

If there is no existing value at a given key, Mapping::get() returns None.

Because the Mapping API provides direct access to storage, you can use the Mapping::remove() method to remove the value at a given key from storage.

To add insert and remove functions to the contract:

  1. Open a terminal shell on your computer, if needed.

  2. Verify that you are in the incrementer project folder.

  3. Open the lib.rs file in a text editor.

  4. Add an inc_mine() function that allows the contract caller to get the my_map storage item and insert an incremented value into the mapping.

    #[ink(message)]
    pub fn inc_mine(&mut self, by: i32) {
        let caller = self.env().caller();
        let my_value = self.get_mine();
        self.my_map.insert(caller, &(my_value + by));
    }
  5. Add a remove_mine() function that allows the contract caller to clear the my_map storage item from storage.

    #[ink(message)]
    pub fn remove_mine(&self) {
        let caller = self.env().caller();
        self.my_map.remove(&caller)
    }
  6. Add a new test to verify that the inc_mine() functions works as expected.

    #[ink::test]
    fn inc_mine_works() {
        let mut contract = Incrementer::new(11);
        assert_eq!(contract.get_mine(), 0);
        contract.inc_mine(5);
        assert_eq!(contract.get_mine(), 5);
        contract.inc_mine(5);
        assert_eq!(contract.get_mine(), 10);
    }
  7. Add a new test to verify that the remove_mine() functions works as expected.

    #[ink::test]
    fn remove_mine_works() {
        let mut contract = Incrementer::new(11);
        assert_eq!(contract.get_mine(), 0);
        contract.inc_mine(5);
        assert_eq!(contract.get_mine(), 5);
        contract.remove_mine();
        assert_eq!(contract.get_mine(), 0);
    }
  8. Check your work using the test subcommand:

    cargo test

    The command should display output similar to the following to indicate successful test completion:

    running 5 tests
    test incrementer::tests::default_works ... ok
    test incrementer::tests::it_works ... ok
    test incrementer::tests::remove_mine_works ... ok
    test incrementer::tests::inc_mine_works ... ok
    test incrementer::tests::my_map_works ... ok
    
    test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Next steps

In this tutorial, you learned how to use the ink::storage::Mapping type and Mapping API in a smart contract. For example, this tutorial illustrated:

  • How to initialize a mapping for storing key-value pairs.

  • How to identify and use the contract caller in a smart contract.

  • How to add functions that enable users to insert and remove the values stored for them in a map using a smart contract.

You can learn more about smart contract development in the following topics:

You can find an example of the final code for this tutorial in the assets for the .

Develop a smart contract
Mapping
smart-contracts
Build an ERC20 token contract
Troubleshoot smart contracts