Data Location Assignment

Hi Jonathan

Sorry to respond so late because I had to travel out-station to attend a wedding for the weekend. I went through your answers and definitely much clearer now. I really appreciate your meticulous and systematic method of highlighting the answers to my questions. It really brings out a better understanding on a subject which I find sometimes even difficult to put into a meaningful question statement to ask! Thanks.

Eric

1 Like

I simply removed the unnecessary User struct array and updated the state mapping directly in the function.

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

Yes Jonathan,

I received all the answers under different discussion topics. I really appreciate you responding to all my questions even during weekends.

Thanks
Eric

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 Eric,

Really glad you’re finding the answers and explanations so helpful :slight_smile:

I know it can often be quite a challenge to put questions about these topics into words, but you’re doing a good job at expressing yourself. And I always try to consider and reply with what I think the student needs to understand.

I hope you had an enjoyable weekend at the wedding!

Jon

1 Like

Nice solution @jeffalomaniac :ok_hand:
… and good to see you back here in the forum :slight_smile:

Just a quick comment about this part of your explanation …

User memory user = users[id];

This line, which you removed, doesn’t create a copy of an array. You are right that users[id] is created from the User struct; but the correct term is struct instance. It’s based on the “template” provided by the struct, with values assigned to the pre-defined properties. If you want to refer to an instance based on a specific struct, you can refer to the name of the struct e.g. User instance

Let me know if you have any questions.

1 Like

The struct instance makes sense now, thank you for clearing that up. Good to be back. These courses are way more helpful than the udemy courses I was trying.

1 Like

Good to hear @jeffalomaniac :grinning:

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

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

}`

I just tweaked the code by changing memory to storage but I honestly got lost on the mapping.

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

}

Hum, now I know the solution ; in my mind “updateBalance” could be compared as an update. That’s why I put this “+=” in updateBalance function.

But, as the given solution is “= balance”, in my mind I think more about a “resetBalance” name function, instead of “updateBalance” name function.
But, it’s just a different point of view and it was not the most important here :stuck_out_tongue:

Thanks for these perfect courses and this forum.

1 Like

Nice solution @Talk2coded,

You didn’t need to change the mapping. Do you mean that you don’t understand how the mapping works? …or how the local storage variable is able to update the mapping directly?

If you explain a bit more about what it is you haven’t understood, then we can help you :slight_smile:

You’ll also find a lot of useful explanations about the more challenging aspects of this assignment in other posts in this discussion topic. It’s worth having a browse.

Nice solution @_Vincent :ok_hand:

… and great analysis, explaining why you chose to use += instead of =
This is exactly the kind of reflection we want to encourage :muscle:

Even though the initial idea of this assignment is to update the existing balance by replacing it with the new one, I actually think that it does make 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, rather than replacing it with a new balance.

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

Glad to hear you’re enjoying the courses! Just let us know if you have any questions.

1 Like

the mapping syntax confuses me a lot, especially when I’m not finding the balance of an address.
also arrays are a little bit confusing because it seems like you can declare it with anything without using any keyword.

Problem with function updateBalance() : It was storing the new balance in memory. Problem with memory is that value is only stored while function is being called and when function is completed the value is gone. So memory operates as a temporary storage and is not permanent after function execution. Storage operates differently and it stores the value even between function calls, so the storage of value is persistent.

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 and explanation @Jaka :ok_hand:

The important point to add is that when we define the data location of a local variable as storage, this creates a storage pointer. This only points to (references) the value already saved in persistent storage which is assigned to it (in our case users[id]); it doesn’t create or store a separate copy of it. It’s like a temporary “bridge” during execution of the function to wherever in the contract state it points to (in our case a specific struct instance in the mapping).

In this 2nd line of code, any value which is assigned to a property of the local “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” is lost when the function finishes executing.

Just let me know if you have any questions.

1 Like

All clear on your answer. Learning smart contract programming is quite hard actually, it’s really awsome to have help. If you are so kind, I would like to understand this. In the previus videos, the functions had a different code structure compared to this assignment. As:

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

msg.sender is a bit tricky to understand for me, because I though it was important to include, but this code doesnt include it. If I understand correctly when we defined the User in:

    struct User{
        uint id;
        uint balance;
    }

We don’t have to include msg.sender, because users[id].balance serves the same role?

1 Like

Hi @Jaka,

Glad you found the feedback helpful :slight_smile:

msg.sender

Interacting with a smart contract requires calling a function. If a function takes parameters, it is called with arguments. The address which calls a function is like an additional default argument i.e. this value is automatically fed into the function for us, and Solidity allows us to reference this calling address with msg.sender . Like other parameter names, msg.sender has local, function scope i.e. it only references the calling address within the function it is used in, and only temporarily, until the function has finished executing. Each time a function is called with a different address, msg.sender will always reference whatever that address is, whilst that specific function’s code is executing. However, whenever we assign msg.sender to a state variable (including storage arrays and mappings), the temporary value that msg.sender represents within a function, is then stored persistently until it is next modified, either by the same function or a different one.

In any function, the caller’s address can always be referenced by msg.sender, but you don’t have to. Whether you need to or not depends on whether addresses are stored elsewhere in the smart contract (e.g. as mapping keys, or authorised addresses etc.) and need to be referenced within the code of a particular function to perform a particular operation.

Amongst other things, addresses are commonly used as mapping keys. That’s why we use msg.sender in this function …

… to retrieve the balance (stored in the mapping) for the user who calls the function. Because each user’s balance is mapped to their personal, unique address, we need to use their address to look up their specific balance.

mapping(address => uint) balance;

However, in this assignment, the mapping keys are uint values, not addresses…

This time, each user has it’s own data structure (a struct instance, based on the User struct) containing two pieces of data (two properties): a uint value called id, and another uint value called balance

In order to add a new User instance to the mapping in the addUser() function, we need to map it to a unique uint value, not an address. The way we’ve done it in this contract is to use the same id value for (i) a user’s id property value (stored in their User instance), as well as for (ii) the key which their User instance is mapped to. So, when we update and retrieve a user’s balance, we access their User instance in the mapping using the mapping name (users) together with their unique key (id) …

users[id]

… and access the balance property within their User instance using dot notation…

In summary … “We don’t have to include msg.sender, because [id] serves the same role”

Just let me know if anything is still unclear, or if you have any further questions.

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

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

}```

a quick one.
1 Like

Hi @Talk2coded,

Sorry for the delay in getting back to you.

The values stored in an array are ordered according to a fixed sequence of indices (0,1, 2, 3 etc.). Each index number and its associated value is a key/value pair (each unique key being an index number). This means that, as well as being able to reference or retrieve values by their specific index number (if we know it), arrays can also be iterated over. The ability to iterate over an array, means we can search for specifc data stored within it, without needing to know specific index numbers (keys).

The reason why the syntax for an array declaration is simpler than that of a mapping is because we don’t need to define the data type of the keys: these are always a sequence of ascending uint values starting with 0 (and automatically set for us). The data type of the values to be stored in the array at each index is declared immediately preceding the square brackets e.g.

uint[] arrayOfUnsignedIntegers;
string[] arrayOfStrings;

Person[] arrayOfStructInstances;
/* This will hold struct instances (similar to objects) which are created
   based on the template of properties defined in the Person struct */

The square brackets declare the state variable as an array (in the same way that the keyword mapping declares those as mappings). Then, apart from the name/identifier (which is placed at the end of the declaration, the only other keywords that might be needed to define a storage array are those marking the visibility as public or private. Unlike functions (where a visibility keyword must be included in the declaration) state variables have a default visibility of internal, which means that if the visibility is not explicitly stated (as in the 3 examples above) it will be internal by default.


Mappings are also data structures which store key/value pairs. However, unlike arrays, values can be mapped to unique keys of data types other than uint e.g. addresses or strings. There is, therefore, no need for values to be added and stored in a mapping according to a fixed, sequential order of integers which must begin with 0.

The syntax for looking up values in a mapping which are mapped to data types other than addresses is exactly the same as the syntax used with addresses. As with arrays, we reference a value stored in a mapping with the mapping name followed by the key in square brackets e.g.

uint[] numbers;
mapping(uint => uint) balances;
mapping(address => string) names; 

// Assuming that these 3 data structures already contain values...
function getNumber(uint index) view public returns(uint) {
    return numbers[index];
}

function getBalance(uint id) view public returns(uint) {
    return balances[id];
}

function getName() view public returns(string memory) {
    return names[msg.sender];
}

Unless a mapping is defined with uint keys, and the values are added and mapped according to a fixed sequence of uint keys which mirror an array’s index values, mappings cannot be iterated over. In Solidity, mappings are commonly used to map values to Ethereum addresses. In order to add, modify, reference or retrieve a value mapped to an address, this unique address needs to be known. This gives mappings the ability to provide more in-built data privacy than arrays.

Generally speaking, if you need to store and look up large amounts of data, using mappings will be more efficient than using arrays, and will save you a noticeable amount of gas. With large amounts of data, using arrays, where mappings could provide the same results, runs the risk of transactions running out of gas and failing, because the gas limit has been reached.

I hope this gives you a clearer picture of mappings and arrays in Solidity.

Here’s my solution:

pragma solidity 0.8.9;
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