Data Location Assignment

Nice solution @B_S :ok_hand:

Yes … that works :+1:

Yes, that also works, but as you would already have assigned the new balance to the local user variable’s balance property in what would be your line 18 …

user.balance = balance;

… you can just add …

users[id] = user;

to your line 19 instead. But whichever of these two versions you use, your chosen solution is better because …

  1. It is more concise and, in my opinion, clearer.
  2. It doesn’t create a separate copy of the User instance, which is stored in the mapping, and which we want to update with the new balance. Using a local memory variable creates a separate copy of the User instance and therefore consumes more gas. As well as being more concise, your chosen solution will also consume less gas.

Yes …basically, the mapping is being directly updated when you assign the new balance to the local storage variable user

user.balance = balance;

This is how it works …

In the first line of code in the function body, by assigning users[id] to a local storage variable, we create a local “pointer” called user. The important thing to understand here is that this “pointer” does not create an additional, separate state variable, and it also doesn’t create and store a copy of the User instance mapped to the id (key) input into the function .

Instead, the pointer only references this User instance in the mapping, creating a temporary “bridge” to the mapping during execution of the function.

Any value which is assigned to a property of the local “pointer” (user) …

user.balance = balance;

… is effectively updating 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.

So hopefully you can now see why the getBalance function successfully retrieves the new balance with this alternative solution as well.

In fact, even though the code is quite different, the low level operations performed by this method (using a local storage variable as a “pointer”) are effectively the same as those performed by your chosen solution …

users[id].balance = balance;

Hopefully, my explanation above enables you to understand why this is so. As a result, both of these solutions consume more or less the same amount of gas.

So, you may now be wondering, why bother to create a pointer with a local storage variable. From within a function, it is certainly simpler and more concise to just assign data to persistent storage by referencing a mapping directly, as we do with users[id].balance = balance;

However, sometimes using a local storage variable as a pointer can be a useful intermediate step when performing more complex operations involving multiple properties and their values: it can help us to set our code out into separate logical steps, making it easier to read and understand.

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

1 Like

Hi @spoonage,

The uint values input into the function as the parameters defined in the function header will only be stored temporarily while the function is executing. The data location of these parameters doesn’t have any influence on the data location of the local memory variable user, which is defined in the 1st line of the function body in the original code provided for the assignment…

User memory user = users[id];

This line of code does the following …

  • users[id] references the User instance in the users mapping which is mapped to the key with the same value as the id parameter input into the function.
  • Because this specific User instance is assigned to a local memory variable, a copy is made of it.
  • This copy is stored temporarily in memory in a variable called user

The balance property of this temporary copy of the User instance was originally updated with the new balance input into the function as the balance parameter…

user.balance = balance;

Your solution removes the temporary copy saved in memory, and directly updates the balance property of the User instance saved in persistent storage in the mapping …

users[id].balance

… with the new balance

users[id].balance = balance;

I hope this clarifies things. But just let me know if you have any questions :slight_smile:

very thorough: thank you!
you also anticipated my next question about the seeming redundancy of such a local pointer.

so maybe i missed this in one of the lectures: which types are mappable? are maps their own type? (just realized that was the first thing we did!)

and just to be sure: should i consider all local variables as pointers when they’re set to a state variable (such that i might be changing my state variable when i change the local variable) or is this only for mappings? if so, how would i create a copy of a state variable so that it stays untouched until i update it explicitly?

1 Like

These are still good questions …

As far as I’m aware, in a mapping, you can map all data types to mapping keys: including struct instances, arrays and mappings themselves (to create double mappings). But for the purposes of this introductory course, only elementary types (address, uint, int, string, bool, bytes) can be used as mapping keys; although for obvious reasons, I don’t think Booleans would prove very useful! :wink:

(The full story is that you can also use contract types and enums as mapping keys — but I would put those to one side for the time being.)

Yes


No … because to create a local storage pointer you have to explicitly define the local variable with the storage data location. The only local variables that can have their data location explicitly defined are strings, arrays and struct instances.

A local storage pointer could be used to modify properties of struct instances stored in storage arrays as well as mappings.

A local variable defined with an elementary data-type will always create a temporary local copy of any value (of an elementary data-type) stored in a state variable which is assigned to it.

A local variable defined as a string, array or struct instance, and with the keyword memory, will always create a temporary local copy of any string, array or struct instance stored in a state variable, storage array or mapping which is assigned to it.

Any modifications then made to these temporary local copies will only affect these copies. The values stored in the contract state will remain unchanged until referenced and updated directly.

1 Like

contract types and enums as mapping keys… :exploding_head:
noooice.

1 Like

Hi Jonathan

Thank you very much for the reply. I still need a bit of clarification because I am not entirely clear whether my answer just lack the “storage” keyword in replacement of the " memory" keyword or it’s more than that. Basically, if I changed to this code below, is it still workable or is it still fundamentally flawed?

user Storage newUser = users [ id ];

newUser.balance = balance;

I read your answers few times and unless I understood wrongly, I gathered only the missing " storage" keyword for complex data-type and struc being one of it was left out. Am I right or there’s more to it? I couldn’t quite understand your explanation on the pointer and wrong choice of variable name - newUser in the second line of code.

Apart from the above problem, I have two additional questions and I hope you can assist me with.

  1. For gas paid on the Ethereum Blockchain, who do we pay to? Miners? And if I maintain a Node ( a full wallet that synced up with the blockchain called a Node? ) am I be paid as well? And

2)Are Bitcoin or Ethereum “receiving” addresses unique? Why do I see in certain wallets, new “receiving” addresses are generated that’s totally different from the original receiving address? How do they account for it to make sure tokens are received by the same wallet address?

Thanks

Eric

1 Like

just change MEMORY to STORAGE in getBalance fx

Hi, here is my solution to data location assignement

pragma solidity ^0.8.7;
contract MemoryAndStorage {
    //Data location is storage
    mapping(uint => User) users;

    struct User{
        uint id;
        uint balance;
    }

    function addUser(uint id, uint balance) public {
        //the right hand create a user with data location memory
        // the user is add to the mapping users
        users[id] = User(id, balance);
    }

    //remove view from function header since it modifies a state variable
    function updateBalance(uint id, uint balance)  public {
         
         /*User memory user = users[id];
            the mapping users is from datalocation storage thus it send only a copies to 
            left hand variable user with data location memory 
            to modify the mapping we must send the reference by setting data location of 
            the newly created user to local storage
         */
         User storage user =users[id]; 
         user.balance = balance;
    }

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

I hope you mean the updateBalance function! :wink:

Nice solution @moise :ok_hand:
… and you’ve made a good attempt at explaining what the code is doing… well done, because it’s difficult to put it into words :muscle:

A few comments …

Good :ok_hand: … this line creates a copy of the User instance (the user’s record) from the mapping, and stores it temporarily in the memory data location.

The important thing to understand here is that this line of code doesn’t create or store a copy of the User instance. Instead, by assigning users[id] to a local storage variable, we create a local “pointer” called user. This only points to (references) users[id] saved in persistent storage in the mapping (the struct instance mapped to the id input into the function). It’s like a temporary “bridge” to the mapping during execution of the function.

Then, in the 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.

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

pragma solidity 0.7.5;

contract MemoryAndStorage {
// users[id] = > User(id,balance)
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;// add this line for the solution
}

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

}

1 Like

Hi Eric,

Yes, all that is missing is the storage keyword.

Yes, this will now work, as long as User is written with a capital “U”, and storage with a lower case “s

You are right :ok_hand: … because our local variable is defined with a complex data-type (our User blueprint) the syntax requires data location to be explicity defined as either memory or storage. We don’t have the option of leaving this out. This is the same with strings and arrays. With the elementary data-types the syntax doesn’t allow us to explicity define the data location. This decision is made for us.

There isn’t any more to it. But how it works as a storage pointer is quite complicated, which is why you may not have fully understood my explanation. Don’t worry if you don’t fully understand yet. It often takes a while before the more complex concepts “click into place”. I would advise bookmarking my post and revisiting it at a later date after you’ve progressed further with your studies and gained more Solidity coding experience.

Having said that… here’s a reworded version of my explanation, just to see if it’s easier to understand…

User storage user = users[id];

The important thing to understand is that this first line of code doesn’t create or store a copy of the User instance. Instead, by assigning users[id] to a local storage variable, we create a local “pointer” called user. This only points to (references) the users[id] already saved in persistent storage in the mapping (the struct instance which is mapped to the id input into the updateBalance function). It’s like a temporary “bridge” to the mapping during execution of the function.

Because the pointer is only referencing an existing User instance already stored in the mapping, and not creating or storing a new one, this is why I don’t think newUser is an appropriate name for it. The new User instances are created and added to the mapping in the addUser() function.

Your code will still work if you name the pointer newUser; it’s just an issue of clarity and readability.

user.balance = balance;

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.

I’ll answer your other questions in a separate post over the weekend :slight_smile:

Thanks for the detailed explanation.

1 Like

sure it was just a mistake, thanks though :slight_smile:

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

OR

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

I think that with the second solution I will use more gas because there will be another struct stored into the blockchain. :thinking: But this is a simple smart contract. Probably for a complex one, creating a new struct is necessary.

1 Like

Nice alternative solutions @ballack13r :ok_hand:

In actual fact, even though the code is quite different, the low level operations performed by your second solution (using a local storage variable as a “pointer”) are effectively the same as those performed by your first solution …

If I explain how the second solution works, then, hopefully, you’ll see why …

The important thing to understand is that this first line of code doesn’t create or store a copy of the User instance. Instead, by assigning users[id] to a local storage variable, we create a local “pointer” called user. This only points to (references) the users[id] already saved in persistent storage in the mapping (the struct instance which is mapped to the id input into the updateBalance function). It’s like a temporary “bridge” to the mapping during execution of the function.

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.

As I’ve explained above, the local storage pointer doesn’t create a separate copy of the struct instance. However, you are thinking along the right lines by suggesting that the second solution could be useful in a more complex contract…

From within a function, it is certainly simpler and more concise to just assign data to persistent storage by referencing a mapping directly, as you do in your first solution …

But, sometimes, using a local storage variable as a pointer can be a useful intermediate step when performing more complex operations involving multiple properties and their values: it can help us to set our code out into separate logical steps, making it easier to read and understand.

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

1 Like

It is very clear now. Thanks a lot for the explanations! :smiley:

1 Like

Hi Eric,

I’ve answered your additional questions in different discussion topics, which are more suited to their subject matter. You should have received notifications from those particular discussion topics, but I’ve also included links to my replies below.

https://studygroup.moralis.io/t/ethereum-advanced/8420/407?u=jon_m

https://studygroup.moralis.io/t/bitcoin-basics-discussion/8417/683?u=jon_m

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