Mapping & Nested Mapping – Solidity [Part #5]

In the previous post, we covered memory, calldata, and storage in Solidity, which are essential concepts for managing data in smart contracts. In this post, we’ll build on our simple bank setup example and explore mapping and nested mapping, key concepts for storing and retrieving data efficiently in Solidity.

Current State of the our Banking Smart Contract

So far, we have the following Solidity contract where we can:

  • Add users by executing the createUserAccount function and submitting values like 800000, princechaddha or 200, pwnmachine.
  • Access user balances using the userAccounts array by entering an index value like 01, and so on.

However, this setup works only if you know the exact index of the user. Imagine having hundreds of users in the contract, finding a specific user’s balance would be extremely inefficient.

What Is Mapping in Solidity?

A mapping in Solidity is a data structure that acts like a key-value store. It allows us to map keys to corresponding values, offering efficient data storage and lookup.

Why Use Mapping?

In our previous example, we used an array of user accounts. While this works for small-scale projects, it can be inefficient when dealing with hundreds or thousands of users. Accessing a specific user’s balance using an array requires knowing the exact index, which isn’t practical.

To solve this, we use mapping, which allows us to link user data directly using a unique key, similar to a dictionary or hashmap in other languages. In simple words if we search for a user we will get their balance directly instead of like preivlege we have to

Here’s the basic syntax for a mapping:

mapping (string => uint256) public UserAccountBalance;

This creates a mapping where:

  • Key: A string (user’s name)
  • Value: A uint256 (user’s balance)

Now updating our Simple bank contract with Mapping

Let’s create a Mapping to store balances by user name and update the createUserAccount function to include mapping:

pragma solidity 0.8.26;

contract FirstContract {
    uint public balance;
    uint256[] public accountBalances;

    // Struct for user account
    struct Username {
        uint256 balance;
        string name;
    }

    // Array to store users
    Username[] public userAccounts;

    // Mapping to store balances by user name
<strong>    mapping (string => uint256) public UserAccountBalance;
</strong>
    // Function to create a new user account
    function createUserAccount(uint256 _balance, string memory _name) public {
        userAccounts.push(Username(_balance, _name));
<strong>        UserAccountBalance[_name] = _balance;  // Update mapping
</strong>    }

    // Deposit function
    function deposit(uint amount) public {
        balance += amount;
    }

    // Withdraw function
    function withdraw(uint amount) public {
        require(amount <= balance, "Insufficient balance");
        balance -= amount;
    }

    // Fallback functions
    receive() external payable {
        balance += msg.value;
    }

    fallback() external payable {}
}

Now let’s deploy the updated code on Remix IDE

  1. Deploy the updated contract in Remix IDE.
  2. Use the createUserAccount function to add users like this:
  • 1000000, princechaddha
  • 100000, pwnmachine
  • <code>10000, testuser

Once users are added, use the UserAccountBalance public getter to search for their balances.

Example Queries:

  • Enter princechaddha → Returns <code>1000000
  • Enter testuser → Returns <code>10000
  • Enter an invalid value like randomuser → Returns 0 (default value)

Important Things to Know About Mappings

  1. Mappings Can Only Be Declared in Storage
    Mappings cannot be declared inside functions. They must be defined at the contract level because mappings work directly with blockchain storage. Invalid Example:
   function invalidMapping() public {
       mapping (string => uint256) tempMapping;  // Not allowed
   }
  1. Mappings Cannot Be Iterated Over
    There’s no way to loop through keys in a mapping because mappings don’t keep a list of keys. Every possible key exists, and its value defaults to 0 unless explicitly set.
  2. Mappings Cannot Be Returned
    You cannot return entire mappings from a function. Solidity doesn’t support this because mappings don’t track keys internally.

What Is Nested Mapping?

A nested mapping is a mapping inside another mapping. It’s useful when you need to link multiple keys to a value, like tracking balances across different tokens for the same user.

Example of Nested Mapping

Here’s how we can track balances for users across different tokens:

pragma solidity 0.8.26;

contract NestedMappingExample {

    // Nested mapping to track token balances by user
    mapping (string => mapping (string => uint256)) public tokenBalances;

    // Function to set balances
    function setBalance(string memory _user, string memory _token, uint256 _balance) public {
        tokenBalances[_user][_token] = _balance;
    }

    // Function to get balance
    function getBalance(string memory _user, string memory _token) public view returns (uint256) {
        return tokenBalances[_user][_token];
    }
}

Example Queries:

  • Set balance: setBalance("princechaddha", "ETH", 5000)
  • Check balance: getBalance("princechaddha", "ETH") → Returns 5000

Why Use Nested Mapping?

Nested mappings allow you to create a more organized and scalable way to store related data. In a banking example, this could track balances for different tokens, accounts, or even account permissions.

Conclusion

We’ve covered mappings and nested mappings in Solidity, showing how to efficiently manage user data. Mappings offer a simple way to store and retrieve values by keys, while nested mappings take things further by supporting more complex relationships.

Leave a Reply