Data Location Assignment

My solution (played around and found the error report by copy/pasting to Remix):

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]; // switched memory to storage
     user.balance = balance;
}

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

}

1 Like

Here’s my solution. It is about data location, so basically I played around with data location and replace memory for storage and it seems working. So as I understood, there was memory in updateBalance, which is only within the function so then getBalance was not updating. Replacement memory for storage means that it is over time and it is updating or executing function in our contract so then getBalance is updating. Maybe I am wrong :smiley: I am very new to this.

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

Nice solution @martina :ok_hand:

Your explanation definitely shows that you are thinking along the right lines and have the right idea :muscle:

Yes … a value stored in a local variable (a variable defined within a function) is only saved temporarily until the function finishes executing, after which it is lost. Local variables which store complex values (strings, arrays and struct instances) must have their temporary data location defined as memory.

It was the user record (User struct instance) stored in the mapping that wasn’t having its balance property updated. The new balance was only being assigned to a temporary copy of the user record, which was then lost once the function finished executing. The consequence of this was that, when the getBalance() function was then called, it was still returning the user’s previous balance and not their new one, because getBalance() can only return the user balances which are stored persistently in the mapping.

However, the exception to the above is that, when defining a local variable as a data structure (an array, or a struct instance), we can also define the data location as storage. This creates what is known as a storage pointer.

When we define the data location of a local variable as memory, this creates a temporary copy of the complex value assigned to it. A storage pointer, however, doesn’t create or store a separate copy of the array or struct instance in persistent storage. Instead, it only references (“points to”) the value already saved in persistent storage which is assigned to it. In our case, this value is whichever specific user record (User struct instance in the mapping) is referenced by users[id]

A storage pointer is like a temporary “bridge” during execution of the function to wherever in the contract state (persistent storage) it points to. Any value which is assigned to a property of the local storage pointer (user) will effectively update that same property of the specific User instance in the mapping which is referenced by the pointer. This enables a specific user’s balance (stored persistently in the mapping) to be reassigned with the new balance (input into the function) before our “bridge” disappears when the function finishes executing …

I hope that helps you to understand in a bit more detail what the code is actually doing. Don’t worry if you don’t fully understand everything I’ve explained, because that’s normal when you’re just starting :sweat_smile: but do let me know if you have any questions :slight_smile:

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]; // change memory to storage
         user.balance = balance;
    }

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

}
1 Like

Thank you for your reply and explaining everything in details, appreciate! I will read it again to understand better and will let you know if I will need more assistance! :slight_smile:

1 Like
pragma solidity 0.8.13;
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 {
        // Place of refactoring
         users[id].balance += balance;
    }

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

There are two core things we should be aware about:

  1. memory variable in the updateBalance function. Any mutations to the User memory user variable will no have any effect on storage variables like mapping users in our case.
  2. user.balance = balance - this is not the balance update, we just overwrite the actual balance value with such approach. So += sign instead of just = should do the trick in cases like this.
1 Like

Nice solution @Voltu :ok_hand:

… and welcome to the forum! … I hope you’re enjoying the course :slight_smile:

Your notes/comments are also good.

The initial idea of this assignment is to update the existing balance by replacing it with a new one, and this is why we’ve named the parameter balance. But I do actually think that it makes more sense to do what you’ve done, and add an amount to the user’s existing balance using the addition assignment operator+= (instead of the assignment operator= ).

However, if we add to, instead of replacing, the existing balance, I think the code looks clearer and more readable if we also change the name of the balance parameter to amount, because we are adding an amount to the balance (to give a new total balance), rather than replacing it with a new balance.

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

Let me know if you have any questions.

1 Like
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;

    }

}
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 newBalance) public {
        users[id].balance = newBalance;
    }

    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 {

     User storage user = users[id];

     user.balance = balance;

}

function getBalance(uint id) view public returns (uint) {

    return users[id].balance;

}

}Preformatted text

2 Likes

Nice solution @Rebe :ok_hand:

… and welcome to the forum! I hope you’re enjoying the course :slight_smile:

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

Easiest solution imo.Worked on my first try.

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;
    }

}
3 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{

         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 {
         User storage user = users[id];
         user.balance = balance;
    }

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

}
2 Likes

Nice solution @Joe22 :ok_hand:

… and it’s good to see you back here in the forum! … I hope you’re enjoying the course :slightly_smiling_face:

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 {
         User storage user = users[id];
         uint newBalance = user.balance + balance;
         user.balance = newBalance;
    }

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

}
  1. we needed to change memory to storage so we are actually updating the users balance not just making a copy of it and
  2. we needed a way to actually update the balance first by getting the current balance and adding the inputted balance and then storing it back to the user balance in storage which requires an additional variable in memory to temporarily hold the calculated newBalance.
2 Likes

I see after watching the solution video that my attempt was very convoluted in comparison. I don’t understand how the balance will continue to update unless the inputted balance is actually added to the current balance? I did try to just change the storage location and my balance didn’t update so I have missed something somewhere…

I’d appreciate some clarity around that.

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 memory user = users[id];
        // user.balance = balance;

        uint balanceBeforeupdate = users[id].balance;

         users[id].balance += balance; 
         assert(users[id].balance = balanceBeforeupdate + balance);
    }

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

}
1 Like

Hi @sph73,

Your solution works :ok_hand: but you’re right that we can achieve the same result more concisely.

The initial idea of this assignment is to update the existing balance by replacing it with the new one, which is input into the function. This is the logic behind the suggested solutions shown in the video.

However, I do actually think that it makes more sense to approach things the way you have, and to add an amount (input into the function) to the user’s existing balance. But there is no need to use an additional local variable to do this. You can just add the input amount at the same time as assigning it …

User storage user = users[id];
user.balance = user.balance + balance;

We can also make this statement more concise by using an addition assignment operator+= (instead of the assignment operator= ) …

User storage user = users[id];
user.balance += balance;

Both of these alternatives perform exactly the same operation.

However, if we add to, instead of replacing, the existing balance, I think the code looks clearer and more readable if we also change the name of the balance parameter to amount, because we are adding an amount to the existing balance (to give a new total balance), rather than replacing it with a new balance.

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

There is always more than one solution to these assignments, and alternative interpretations are equally valid.


This is essentially correct …

By making this change, we are now directly updating the user’s balance stored persistently in the mapping. So, the change to the user’s balance is now a permanent one (at least until it is updated again).
Previously, we were only making a temporary copy of the struct instance (the user’s record stored in the mapping), and updating that with the new balance instead. This temporary copy (together with the new balance) was then lost when the function had finished executing.

Just let us know if anything is still unclear, or if you have any further questions :slight_smile: