Data Location Assignment [OLD]

Screenshot (618)

2 Likes

Looks like the solution is to use storage instead of memory in the update balance function

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

contract MemoryAndStorageFIXED {

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

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

}

1 Like

Update variable user in updateBalance function to have storage, to have persistent memory storage. This makes sure it is accessible globally in the contract. Using memory, it will only be accessible within the updateBalance function when it is executing.

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

This is the best answer that I could find.

pragma solidity 0.5.1;
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;
    }

    functi
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 id = users[id];
     id.balance = balance;
}

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

}

1 Like

Hi @1984,

While your solution does work, you should be getting an orange compiler warning. Basically, this warning is telling you that you are using the same identifier/name (id) twice: for the (i) input parameter, and (ii) local variable. I’m sure you can understand why this is not good practice, and risks introducing bugs when developing the contract further. It also makes your updateBalance function confusing to read. I can understand why you don’t want to name your local variable user, because we already have the User struct and users mapping! However, even though these names are very similar, they are still different. If you would prefer something more distinct, then choose something like  userToUpdate or userInstance which are still meaningful.

1 Like

i couldn’t think it straight to find the solution so i watched the solution and as you did it my brain remind me of class and object from my c++ knowledge so i just call my object which is the balance into the class which is users[id]. below is my code :

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

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

}

1 Like

Hi @Phaxsam,

Your solution code is correct :ok_hand: but what the code is actually doing is…

…assigning your new balance value (2nd input parameter) 

    TO the balance property of the specific User instance WHICH IS

         mapped to the key with a value of id (1st input parameter)
         AND
         stored persistently in the users mapping  IN  the contract state


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

Apologies for my ignorance, but I don’t understand what this command is doing…

users[id] = User(id, balance);

why are we just specifying “id” and not “balance” in the left side of the equal sign?

1 Like

Hi @Yutaro_Shimizu,

Sorry for the delay in answering your question.

In our contract, the user ID is a property within each User instance (created from the struct) and it is also the key each User instance is mapped to in the mapping.

In the addUser() function, we create a new User instance (based on the struct) with…

User(id, balance);

This assigns the value input as the id argument to the id property, and the value input as the balance argument to the balance property.

We then need to assign this new User instance to the mapping, and we do that by mapping it to the key, which in this case is also the value input as the id argument …

users[id]

Once this user has been added to the mapping, their data can be retrieved, or referenced, in the same way that it was initially assigned…

users[id]

i.e. by using the key (id)


It may be easier to understand what’s happening if we do the same, but with a key which isn’t also one of the struct properties e.g.

pragma solidity 0.7.5;

contract Employees {

    struct Person {
        string name;
        bool isManager;
    }

    mapping(uint => Person) people;

    function addPerson(uint id, string memory name, bool isManager) public {
        people[id] = Person(name, isManager);
    }

}

Let me know if anything is still unclear, or if you have any further questions :slight_smile:

Jon,

Thank you so much ! Your example and explanation makes so much sense now. One last question, however. Why are we able to use the same variable for the key and for instance in struct? I was thinking this would cause a collision…

1 Like

Hey Yutaro,

I’m glad my explanation has helped make more sense of what’s happening. These coding structures can be difficult to fully understand at first, especially if you like to get to the bottom of things :wink:

The mapping only defines the data type of the keys, which in our example is uint (unsigned integer).

mapping(uint => User) users;

We have just choosen to use the same uint value, input into the function as a parameter with the name id, as both …
(i) the value assigned to the 1st struct property which also has the name id; and
(ii) the uint key which each User instance we create is mapped to when assigned to the mapping.

The id within the struct is a property name, and the id passed to the function is a parameter name. There is nothing to stop us from using the same name for both. The scope each is defined in is different, and so there is no “clash”.

You are right, though, that we shouldn’t use the same name for a variable and a parameter. If we did, this would generate a compiler warning.


Also, picking up on the other questions you’ve asked about this function in the Additional Solidity Concepts discussion topic …

users[id]   represents the “placement” of our new User instance in the mapping users . We create a new entry in a mapping by assigning it to a new key in that mapping. The keys in the users mapping are defined as having to be unsigned integers, and (as I’ve already mentioned above) for these keys, we are choosing to use the unsigned integers input into our function as the id parameter. The values mapped to each key are defined as having a custom data type User based on our User struct. The User struct has been defined with two properties, and that is the reason why each new value we assign to a new key in the mapping needs to be created with two property values: one for the id property, and the other for the balance property. For these two property values, we use the two values input into our function as the id and balance parameters.

The id in users[id] references the unsigned integer input into our function as the id parameter. It is placed within square brackets and appended to the mapping name users , because (as mentioned above) this is the syntax used to assign a new value to a mapping — achieved by mapping it to a key, which is the value placed within the square brackets. The same syntax is also used to reference an entry which is already stored in the mapping.

I hope this helps to answer some more of your questions. Just let me know if anything is still unclear, or if you have any more questions :slight_smile:

Jon,

Wow, great explanation. Now I fully get it. Thank you so much!

Yutaro

1 Like

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

It makes sense to store the new user data to override the old.

1 Like

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

The function updateBalance must include " users [_id]. balance = _balance " as shown above to make an update to the balance that refers to the specific user. Otherwise we can’t get the “getBalance function” to retrieve the rightful user updates

1 Like

You solution is correct @ecbwong :ok_hand:

The key thing here is that you are now updating the specific user’s balance in persistent storage in the mapping. Originally, we were still updating the specific user’s balance, but only in a temporary, local copy of their record, which was then lost when the function finished executing.

Let me know if you have any questions :slight_smile:

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