Data Location Assignment

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

The only thing that required changing was to modify memory reference to storage reference. This is because it is referencing and modifying something outside the contract so needs to persist beyond execution.

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 @leolukaz :ok_hand:

It’s because it’s referencing and modifying the contract state which is stored persistently beyond execution,

as opposed to…

just being a local variable which is only stores its data temporarily until the function has finished executing.

Just let me know if you have any questions.

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]; //Instead on memory, save the user information in the storage. 
         user.balance = balance;
    }

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

}

1 Like

Needed to add “storage” instead of “memory” for the user struct…

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 @conrarob :ok_hand:
… and welcome to the forum! I hope you’re enjoying the course :slight_smile:

Just to clarify…
The User struct is already saved in persistent storage. It acts like a template which new User instances are based on. When new User instances are created, they are assigned to the users mapping, where they are also saved in persistent storage.

By assigning users[id] to a local storage variable, we create a local “pointer” called user. This points to (references) the User instance in the mapping which is mapped to the id parameter input into the function. Any value assigned to this local “pointer” variable is effectively updating the instance stored persistently in the mapping.

The local variable user creates a temporary “bridge” to the mapping during execution of the function, so that the amount (balance) can be assigned to a specific user’s balance stored persistently in the mapping, before this “bridge” is lost when the function finishes executing.

It is complex, and not easy to grasp at first, but hopefully that now makes things clearer. Just let me know if you have any questions.

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

}

// Changing the update function from memory to storage is the simplest way
// to amened the contract and avoid errors in the contract as memory is READ-ONLY
// and storage stores the data in 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 {
     users[id].balance = balance;
}

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

}
took me about an hour as I had user[id].balance = balance; instead of userS[id].balance = balance; …:crazy_face:

1 Like