Data Location Assignment

Simple change of updating getBalance to update users[id].balance:

pragma solidity 0.7.5;
contract MemoryAndStorage {

    mapping(uint => User) users;

    struct User{
        uint id;
        uint balance;
    }

    function addUser(uint id, uint balance) public {
        users[id] = User(id, balance);
    }

    function updateBalance(uint id, uint balance) public {
         //User memory user = users[id];
         //user.balance = balance;
         users[id].balance = balance; 
    }

    function getBalance(uint id) view public returns (uint) {
        return users[id].balance;
    }

}
1 Like

pragma solidity 0.7.5;
contract MemoryAndStorage {

mapping(uint => User) users;

struct User{
    uint id;
    uint balance;
}

function addUser(uint id, uint balance) public {
    users[id] = User(id, balance);
}

function updateBalance(uint id, uint balance) public {
     User storage user = users[id];
     user.balance = balance;
}

function getBalance(uint id) view public returns (uint) {
    return users[id].balance;
}

}

2 Likes

pragma solidity 0.7.5;

contract MemoryAndStorage {

mapping(uint => User) users;

struct User{
    uint id;
    uint balance;
}

function addUser(uint id, uint balance) public {
    users[id] = User(id, balance);
}

function updateBalance(uint id, uint balance) public {
     users[id].balance = balance;
}

function getBalance(uint id) view public returns (uint) {
    return users[id].balance;
}

}

1 Like

I’ve found a simple fix in changing the Data Location of the variable “user” in the updateBalance function from memory to storage.

from:

 function updateBalance(uint id, uint balance) public {
         User memory user = users[id];
         user.balance = balance;
    }

to:

 function updateBalance(uint id, uint balance) public {
         User storage user = users[id];
         user.balance = balance;
    }
1 Like

One of the plenty solutions of this assignment:

function updateBalance(uint id, uint balance) public {
     //User memory user = users[id];
     //user.balance = balance;
     users[id].balance = balance;
}
1 Like

Hi @Saula_Atama,

Your solution is correct :ok_hand: … but in terms of your explanation…

memory is temporary storage, which lasts until the function has finished executing. It doesn’t refer to being “read-only”, because while the function is executing the data stored temprorarily in a memory variable can be modified as well as read/referenced. The key point here is that if we want any data within the function to be stored persistently in the contract state, we need to assign it to a storage data location before the function finishes executing and all data stored in memory is lost.

By assigning the new balance to a local storage variable, we aren’t storing it in the function. Anything stored locally within a function’s scope will only be saved temporarily during function execution. Instead, when we assign users[id] to a local storage variable, we create a local “pointer” called user . This points to (references) the User instance in the mapping which is mapped to the id parameter input into the function. Therefore, any value assigned to the local “pointer” variable is effectively updating a specific instance in the mapping; and because all data held in the mapping is located in storage and part of the contract state, the new balance will now be stored persistently.

To put it another way, the local variable user creates a temporary “bridge” to the mapping during execution of the function, so that the new balance can be assigned to a specific user’s balance property in the mapping, before this “bridge” is lost when the function finishes executing.

It is complex, and not easy to grasp at first, but hopefully that makes things clearer. Just let me know if you have any questions :slight_smile:

Nice solution @Naoki_Takahashi :ok_hand:
… and welcome to the forum! I hope you’re enjoying the course :slight_smile:

Replace:

    function updateBalance(uint id, uint balance) public {
         User memory user = users[id];
         user.balance = balance;
    }

With:

    function updateBalance(uint id, uint balance) public {
         User storage user = users[id];
         user.balance = balance;
    }
1 Like

First I added a line users[id]=user; to the end of updateBalance however I I’m not sure if that might be more computationally expensive than changing the first line to User storage user = users[id]; as that would then do two storage writes instead of two memory writes and a storage write. There’s a tool on remix somewhere to find the estimated cost of a transaction, let’s see if I can find it. Looks like the way I did it originally(below) is less expensive by almost 3000 gas. Doing it the other way would probably be more in the spirit of the assignment given the subject though.

pragma solidity 0.7.5;
contract MemoryAndStorage {
    mapping(uint => User) users;
    struct User{
        uint id;
        uint balance;
    }
    function addUser(uint id, uint balance) public {
        users[id] = User(id, balance);
    }
    function updateBalance(uint id, uint balance) public {
         User memory user = users[id];
         user.balance = balance;
         users[id]=user;
    }
    function getBalance(uint id) view public returns (uint) {
        return users[id].balance;
    }
}

Edit after watching next video: and this is why I don’t do coding competitions.

1 Like

Welcome to the forum @BTim… I hope you’re enjoy the course! :slight_smile:

Your solution is correct, and really well done for considering the gas consumption, and attempting to calculate the difference in gas consumption between the difference solutions :muscle:

As you have discovered, Remix generates the gas consumption for each transaction (including deployment) within the transaction receipts in the Remix console. However, I’m aware that they may not be entirely accurate. The gas consumption in Remix is also split between transaction and execution costs, and I still haven’t entirely got to the bottom of how this split is arrived at in Remix, and whether there is any double counting or other aspects that need to be taken into consideration. It’s also possible to click on “Debug” next to a transaction receipt, which will take you to the Debugger in the panel on the left. Here you can click through each of the separate operations performed in that transaction and see the gas consumed for each one. I’ve used that before to identify the gas consumption for each individual, low-level operation performed for a specific chunk of code. By adding those up, I’ve tried to arrive at the total gas consumed for alternative chunks of code, in order to compare how much gas they use. But, as I’m sure you can imagine, this gets quite time consuming!

A more straight forward way to arrive at the gas consumption is to deploy the contract using Truffle and Ganache, and then perform various transactions using the Truffle console. You will learn how to use these tools in the 201 course, which follows this one.

In fact, your solution should consume more gas, because storing the User instance temporarily in memory creates a copy of the data. However, when we assign users[id] to a local storage variable, we create a local “pointer”, which points to (references) the User instance in the mapping, but doesn’t create a copy of it. Any value assigned to the local “pointer” variable is effectively updating a specific instance in the mapping directly. To put it another way, the local storage variable user creates a temporary “bridge” to the mapping during execution of the function, so that the new balance can be assigned to a specific user’s balance property in the mapping, before this “bridge” is lost when the function finishes executing.

So, expecting a different result to yours, I’ve used Truffle and Ganache to test your assignment solution and the 2 others demonstrated in the solution video, and here are my findings:

Results:

// Test 1 - Code in updateBalance function body (your solution)
     User memory user = users[id];
     user.balance = balance;
     users[id] = user;
/* Gas used to deploy contract: 170005
   Gas used to execute updateBalance function: 29406  */

// Test 2 - Code in updateBalance function body
     User storage user = users[id];
     user.balance = balance;
/* Gas used to deploy contract: 147163
   Gas used to execute updateBalance function: 26712  */

// Test 3 - Code in updateBalance function body
      users[id].balance = balance;
/* Gas used to deploy contract: 145879
   Gas used to execute updateBalance function: 26699  */

/* Gas used to execute addUser function to create new User instance
   is the same in each (function code doesn't change): 61812

This is in line with what I would expect. Your solution consumes approx. 2700 more units of gas than the other two. The two from the solution video consume more or less the same amount of gas, which again is to be expected, because despite the code being different, the actual operations being performed are the same: they both update the mapping directly. This is cheaper, because we are not creating any additional storage — just updating the existing storage. However, it’s more expensive to include the extra step of updating a local copy of the User instance (stored in memory) with the new balance, before then assigning this updated copy to the mapping.

The following article is an easy read about gas consumption, which you may find helpful…

https://medium.com/coinmonks/understanding-gas-in-ethereum-53ad816f79ae

Just let me know if you have any questions :slight_smile:

2 Likes

We want the change to exist after the function has been called so we need to use the keyword storage in place of memory

pragma solidity 0.7.5;
contract MemoryAndStorage {

mapping(uint => User) users;

struct User{
    uint id;
    uint balance;
}

function addUser(uint id, uint balance) public {
    users[id] = User(id, balance);
}

function updateBalance(uint id, uint _balance) public {
     User storage user = users[id];
     user.balance = _balance;
}

function getBalance(uint id) view public returns (uint) {
    return users[id].balance;
}

}

1 Like
pragma solidity 0.8.4;
contract MemoryAndStorage {

    mapping(uint => User) users;

    struct User{
        uint id;
        uint balance;
    }

    function addUser(uint id, uint balance) public {
        users[id] = User(id, balance);
    }

    function updateBalance(uint id, uint balance) public {
       users[id].balance = balance;
    }

    function getBalance(uint id) view public returns (uint) {
        return users[id].balance;
    }
1 Like

image

1 Like
pragma solidity 0.7.5;

contract MemoryAndStorage {
    
    mapping(uint => Users) users;
    
    struct Users {
        uint id;
        uint balance;
    }
    
    function addUser (uint id, uint balance) public {
        users[id] = Users(id, balance);
    }
    
 
    function updateBalance(uint id, uint _balance) public returns (uint) {
        users[id].balance += _balance; 
        return users[id].balance;
    }
    
    function getBalance(uint id) view public returns (uint) {
        return users[id].balance;
    }
    
}
1 Like

Hi Guys,
I post my solution below:
// SPDX-License-Identifier: MIT
pragma solidity 0.7.5;
contract MemoryAndStorage {

mapping(uint => User) users;

struct User{
    uint id;
    uint balance;
}

function addUser(uint id, uint balance) public {
    users[id] = User(id, balance);
}

function updateBalance(uint id, uint balance) public {

     users[id].balance = balance;
}

function getBalance(uint id) view public returns (uint) {
    return users[id].balance;
}

}

1 Like
pragma solidity 0.7.5;
contract MemoryAndStorage {
mapping(uint => User) users;

struct User{
    uint id;
    uint balance;
}

function addUser(uint id, uint balance) public {
    users[id] = User(id, balance);
}

function updateBalance(uint id, uint balance) public {
    User storage user = users[id];
     user.balance = balance;

        }

function getBalance(uint id) view public returns (uint) {
    return users[id].balance;
}

}

1 Like

Hi! I’m not sure if this is correct but at least the contract is running correctly =D
I’ll just post the solution and read all the thread messages!

All I just did was changing the ‘memory’ for ‘storage’. The reason why I did this was because by stating the variable as ‘memory’, the data is cleaned up after the function runs and the contract kind of “forgets” what the actual balance is. By putting the balance on permanent storage (‘storage’ data location) it remembers the balance I input.

pragma solidity 0.7.5;
contract MemoryAndStorage {

mapping(uint => User) users;

struct User{
    uint id;
    uint balance;
}

function addUser(uint id, uint balance) public {
    users[id] = User(id, balance);
}

function updateBalance(uint id, uint balance) public {
     User storage user = users[id];
     user.balance = balance;
}

function getBalance(uint id) view public returns (uint) {
    return users[id].balance;
}

}

1 Like
// SPDX-License-Identifier: MIT
pragma solidity 0.7.5;
contract MemoryAndStorage {

    struct User{
        uint id;
        uint balance;
    }

    mapping(uint => User) users;

    function addUser(uint id, uint balance) public {
        users[id] = User(id, balance);
    }

    function updateBalance(uint id, uint balance) public {
         User storage user = users[id];
         user.balance = balance;
    }

    function getBalance(uint id) view public returns (uint) {
        return users[id].balance;
    }

}
1 Like
pragma solidity 0.7.5;
contract MemoryAndStorage {

    mapping(uint => User) users;

    struct User{
        uint id;
        uint balance;
    }

    function addUser(uint id, uint balance) public {
        users[id] = User(id, balance);
    }

    function updateBalance(uint id, uint balance) public {
         // Change to storage, memory is temporary within the scope of the function
         // User memory user = users[id];
         User storage user = users[id];
         user.balance = balance;
    }

    function getBalance(uint id) view public returns (uint) {
        return users[id].balance;
    }

}
1 Like

Hi, my solution is:
function updateBalance(uint id, uint balance) public {
// User memory user = users[id];
// user.balance = balance;
users[id].balance = balance;
}