Data Location Assignment

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

// I used storage instead of memory - it seemed to have worked

1 Like

My solution to the Data Location assignment

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

}

seems to be working the way it should now.

Ben

1 Like

I may be wrong, so please correct me if so, however I think that the “User” in the mapping is just the value Type. It is just stating the type of value that will be in there, not actually declaring/defining anything yet.

Ben

1 Like

Yes, my bad, I got it backward from my notes, fixed now.

Indeed it is the most hard part, but the more you program, the more easiest will be for you to understand code quickly.

Carlos Z

Remember that all code from your contract will be compiled into byte code at the end, this means that all the contract logic will be available at the execution time no matter the order.

But it is a good practice to keep your syntax easy to read and understand has possible.

Carlos Z

1 Like

Hi Ben, thanks for the reply. Yes, “User” is a value type but a user-defined one. Therefore, I didn’t expect the compiler to know what this is before we tell him what it is beforehand. In C++ this would produce a compiler error.

1 Like

Hi Carlos, thank you for your answer. What I expected from the matter was a compiler error, not a runtime error. In what order does the compiler compile the solidity code? If it goes from top to bottom, how does it know what “User” is when this word appears for the first time without a declaration or definition beforehand?

Hi @cincinnato,

You’ve nearly got it:

Yes - this happens in the addUser function with this code:

users[id] = User(id, balance);
/* A User struct instance is created and assigned to the mapping users.
   A mapping is always stored persistently in storage (no need to state
   the data location with the keyword storage).
   The id parameter is used as the mapping key */

We aren’t sending anything in this contract, but we are replacing the initial balance (saved in the mapping by calling the addUser function) with a new balance i.e. updating it by calling the updateBalance function. You are correct that we can first save this new amount (the new balance) in memory, by assigning it to a local variable, as follows:

//'local variable declared (data type = User struct instances) 
User memory user;

// local variable given a balance property, and new balance assigned to it   
user.balance = balance;

However, we don’t have to use this inefficient intermediary step. We can just assign the new balance directly to the mapping (to persistent storage): see Step 4 below.


This isn’t happening in this contract. The new balance is passed into the updateBalance function as an argument/parameter.


Yes, that’s correct. We can either assign the new balance already saved in memory (in the user variable in Step 2) to the users mapping using the key id, as follows (3rd line):

User memory user;          // Step 2
user.balance = balance;    // Step 2

// replaces the existing User instance in the mapping 
users[id] = user;           

… or we can miss out Step 2 altogether and just directly assign the new balance to the mapping (save it directly to persistent storage) using either of the following 2 alternatives:

// ALTERNATIVE 1
/* sets the balance property of the User instance (with key'id) in the mapping
to the new balance */
users[id].balance = balance;

// ALTERNATIVE 2
// creates a pointer called 'user' (direct reference) to users[id] in the mapping
User storage user = users[id];

/* sets the balance property of the User instance in the mapping, which is 
referenced by the pointer, to the new balance */
user.balance = balance;  

I hope that helps, and makes things (a bit) clearer :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 {
        users[id].balance = balance;
    }

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

}
1 Like

Thank you very much!

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

Now i think you have the same confusion that other students have.

why you can declare a operation and then declare the variable that will be operated?
Like the mapping, you first declare the mapping to operate on User struct and then define the struct.

At the compile time, it does not matter the order for global variables declaration (for solidity), because at the end the compiler will take all your code and converted to ByteCode, so the compiler will take care of the details like declaration of the variables.

Now it is a little bit confusing, but remember that EVM does not read solidity, so when you compile it to bytecode, the order could be completely different and does not care about the syntax in soldiity.

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

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

My solution was to simply update the balance of the user directly using the users mapping declared as a state variable, since this reduced the use of memory in the function and should lead to lower gas cost for the function.

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

Thinking of why the getBalance function didn’t show the updated balance, I understand it is because of the memory data storage in updateBalance function.
I just used the same structure as in the addUser function and so if I click getBalance it gives me 200 instead of 100. However I may be missing something.

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] = User(id, balance);
}

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

}Preformatted text

2 Likes

Hi @makinitnow,

While your solution does work, it only does by accident, because the code for this assignment is more flexible than it should be. You need to think about the job each function should be performing. Even though you’ve discovered that in this contract the same function (addUser) could in fact be used to add a new user and then to also update their balance, I’m sure you can see that this shouldn’t really be possible.

What your code is doing is replacing the whole existing User instance in the mapping with a complete new user who just happens to have the same ID, so it appears as though you are updating an existing user’s balance. In reality we would have additional lines of code which would prevent this from happening, keeping the functionality of both functions mutually exclusive.

What you have got right, is the need to assign your input to persistent storage in the mapping, instead of only to a temporary variable stored in memory.
In order to assign the new balance to the mapping you are also right that we need users[id]. However we need to go one level deeper by assigning it specifically to the balance property as follows:
users[id].balance
All that remains now is to assign the new balance (input into the function) to this property of a specific User instance in the mapping:
users[id].balance = balance;

In summary, the value we assign and update in the mapping is only the balance. The other function parameter (id) is used as the key to locate the specific User instance within the mapping (like a specific user account or record) whose balance we want to update.

I hope that makes things clearer, and hasn’t confused you further! :sweat_smile:
Just let us know if you have any questions :slight_smile:

2 Likes

Happy Coding everyone. Pls find my solution below. I simply replaced the keyword memory with storage in the updateBalance function. Seems to have worked?

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.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

Yes, thank you, the last paragraph starting with In summary,… makes it clearer.
I had to rewatch the video on structs today, because I was missing something around this userd[id]. I have a long way to go…

1 Like