Data Location Assignment

I hope this is correct it seemed like the most logical and the contract worked. Is there a better way to do this or am i just not grasping?

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

ok i just watched the video thank you!

1 Like

Nice solution @WMXgTW6wh :ok_hand:
… and welcome to the forum! It’s good to see you here, and I hope you’re enjoying the course :slightly_smiling_face:

Hi @Chazzman22,

So, I’m sure you’ve already seen the alternative solution in the video…

users[id].balance = balance;

It’s not that one solution is better than the other — they are just alternatives. It was probably easier to come up with your solution, because it only involved changing the data location of the local variable from memory to storage. However, the actual code with the local storage variable is actually quite complex to understand. It works because, instead of a local copy of the user’s record, we create a local “pointer” which directly references the user’s record in the mapping — a temporary “bridge” to the mapping during execution of the function. Any value assigned to this local “pointer” variable, before the “bridge” disappears when the function finishes executing, is effectively updating the user’s record stored persistently in the mapping.

The solution I prefer, because it’s more concise, and easier to understand conceptually, I think, is the one-liner I’ve included above. This doesn’t involve any local variables, or having to state the data location. All we do is assign the new balance to the balance property of the user’s record (instance) in the mapping (the record of the user with the ID input into the mapping together with the new balance). We are still using the concept of data location to ensure the new balance is saved persistently and not lost when the function finishes executing, because the mapping is a state variable and saved in storage. All state variables are saved in storage, so we don’t have to explicity state their data location.

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

1 Like

I used the mapping storage location directly instead of creating additional storage references. This turned into a single line of code in the function to execute. Although using memory is lower in gas cost it’s the reason for the issue at hand.

2021-07-16_22-36-48

Actual Code:

// SPDX-License-Identifier: MIT
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

Nice solution @jCA :ok_hand:

In actual fact, defining a local variable in memory consumes more gas because it creates a separate copy of the User instance. Your solution, and also the alternative which turns the local variable into a “pointer” by changing memory to storage, are both cheaper in terms of the gas cost, and both consume more or less the same amount of gas, because they effectively perform the same low level operations.

Also, for your next assignments, please don’t post screen shots, because we can’t copy and paste your code in order to execute and test it, if we need to. Instead, follow the instructions in this FAQ: How to post code in the forum .

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

1 Like

Thank you for clarifying that point about memory. I would like to learn more about how to correctly apply memory and storage in terms of gas efficiency.
Thanks for your help!

Thanks for pointing out the screenshot vs code only.

Joe

1 Like

Okay, Here is my solution:

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 {
        require( balance > 0, "Balance not sufficient");
         users[id] = User(id, balance);
         
    }

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

}
1 Like

my solution of 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;
    }

}

1 Like

Hi @mohkanaan,

Your solution works, but, apart from the require() statement, your updateBalance function is performing the same operation as the addUser function: it’s creating a whole new User instance. We only need to do that when we add a new user, because once added, their user ID won’t need to change (at least not at the same time as their balance).

So what you want to aim for with the updateBalance function, is code that only updates the balance property for a particular user ID, and not the whole User instance.

In terms of your require() statement, preventing zero amounts being input would certainly make sense if the updateBalance function was adding these amounts to the user’s existing balance. However, instead, the function is replacing the user’s existing balance with a new one. The addUser function doesn’t contain the same require() statement, so do you really want to allow new users to be added with zero balances, but prevent existing balances being reduced to zero?

I assume you’ve already seen the 2 alternative solutions suggested in the follow-up video. Can you see how they are more suitable?

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

Thanks @jon_m I posted this solution before watching the 2nd video about the location solution.

Regarding your notes, It’s really interesting to know the cost of this solution, of creating a new instance and replace the existing balance struct with it. Do you know more about the cost? Does it cost more in terms of gas? do you have any idea about the internals of replacing the value with a new instance versus updating the balance property directly?

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

Hi @mohkanaan

I’ve tested this using Truffle & Ganache — you will learn how to use these tools in the 201 course.

Results:

// Test 1 - Code in updateBalance function body
users[id].balance = balance;
/* Gas used to deploy contract: 145879
   Gas used to execute updateBalance function: 26699  */

// Test 2 - Code in updateBalance function body (same as in addUser)
users[id] = User(id, balance);
/* Gas used to deploy contract: 153637
   Gas used to execute updateBalance function: 27590  */

// Test 3 - Code in updateBalance function body (your solution)
require(balance > 0, "Balance not sufficient");
users[id] = User(id, balance);
/* Gas used to deploy contract: 178669
   Gas used to execute updateBalance function: 27613  */

/* Gas used to execute addUser function to create new User instance
   is the same in each (function code doesn't change): 61812

It’s interesting to note that, while reassigning the whole user instance (Test 2) does use more gas than just assigning the balance property (Test 1), it still uses much less gas than using the same code to create a new user instance with addUser().

Even though the same code is used, I would assume that less gas is consumed to reassign an existing instance in the mapping than to create a new instance, due to efficiencies from not having to create additional storage space and/or the ID value not actually changing. But as I’m no expert on the resulting bytecode, these are only my own logical deductions, and so it could also be down to something else entirely.

Do you have anything to add @dan-i, @thecil?

1 Like

@jon_m Jontathan Thanks a lot man for doing that. I am about to finish Smart Contracts programming 101 and to move to 201, I cannot wait to use the tools and estimate the gas cost of code since Smart contracts programming requires this economic attention from the developer; every single gas cost increment does matter here.

Is there a lesson about gas cost optimization across the courses?

1 Like

Hi Mohamed,

I’ve added some further comments to the end of my last post about the gas costs … about possible reasons for the difference.

I don’t think there is a specific lesson on gas cost optimization, although I’m sure it’s mentioned as an important consideration. @dan-i, @thecil Can you confirm this in terms of the other courses (201 etc.?)

Remix also generates gas costs for each transaction (including deployment) within the transaction receipts in the Remix console. However, I’m aware that they may not be entirely accurate. The gas costs in Remix are also split between transaction and execution costs, and I still haven’t entirely got to the bottom of how this split is arrived at in Remix. It’s also possible to click on “Debug” next to a transaction receipt, which will take you to the Debugger in the panel on the left. Here you can click through each of the separate operations performed in that transaction and see the gas consumed for each one. I’ve used that before to identify the gas consumption for each individual, low-level operation performed for a specific chunk of code. By adding those up, I’ve tried to arrive at the total gas consumed for alternative chunks of code, in order to compare how much gas they use. This can get quite time consuming!

Yes, you are right, as long as this is placed within the context of a wider cost v benefit analysis. Other factors such as security, code management, and the type of end user will also need to be considered.

1 Like

Thank you Jonathan, This is really helpful.

1 Like

Indeed master @jon_m, there are at least 2 lessons in Ethereum Smart Contract Programming 201, is not exactly about gas cost optimization, its more about the difference on gas cost between different storage methods, so one way or another you will know which method is more efficient in terms gas cost.

Carlos Z

1 Like

Hi @mohkanaan, see reply below…

1 Like

@mohkanaan

Some good stuff I read years ago :slight_smile:
https://medium.com/joyso/solidity-how-does-function-name-affect-gas-consumption-in-smart-contract-47d270d8ac92

https://medium.com/layerx/how-to-reduce-gas-cost-in-solidity-f2e5321e0395

Have fun,
Dani

3 Likes

Thanks a lot, @dan-i Daniele, Amazing content! it’s really interesting to know the gas consumption optimization techniques, I just read the first article about the naming and the order of functions and how it affects the gas consumption! there should be a micro-course about gas optimization, I have a project right now, and it’s an ERC777 utility token, and we do care a lot about gas optimization, this is a very very useful content, thanks again for sharing this.

1 Like