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

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

}

1 Like

Hi @youngruz,

Your solution works, and you certainly have the right idea in terms of saving the new data to persistent storage :ok_hand: but the first line in your updateBalance function body is redundant …

The id is both the key to each User instance in the mapping, and also a property within each instance. When we update a user’s balance, we are using their id as the key, in order to “look up” their instance in the mapping. Because the id is the unique key to each User instance, a user’s id value itself cannot change. All your additional line is doing is replacing a user’s exisiting id property with the same value.

Entering a different id will either update a different user’s balance (the user whose instance is mapped to that id key), or it will add a completely new User instance to the mapping (duplicating what the addUser function already does).

We should aim to avoid adding unnecessary code, so that gas isn’t wasted.

I hope that makes sense, but let me know if anything is unclear, or if you have any questions :slight_smile:

1 Like

Thanks man. I am still getting the grips of the language. Thanks a lot.

1 Like

I tried to use structs at first to push into an array, but realised the code felt very clunky and hard to read. I then remembered the mapping tutorial, and used a more in depth Youtube tutorial on mapping to complete the mapping for this. Definitely had a realisation moment of how mapping worked while I was completing this assignment.

pragma solidity 0.7.5;

contract MemoryAndStorage {

// the contract does not properly update the user balance

    mapping(uint => User) users;

    struct User{

        uint id;

        uint balance;

    }

// create an array put in storage

// update storage with the balance associated with the userid

    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
    function updateBalance(uint id, uint balance) public {
         users[id].balance =  balance;
    }
1 Like

Nice solution @AU-Stephen,

In fact, you can easily use an array, here, instead of a mapping, without it causing any look-up issues. That’s because we are using unsigned integers as IDs and not addresses. The constraint with arrays is that, in order to find a value without using iteration, we need to know its index number in the array. When we push a new User instance (based on the User struct) to the array, if we assign it a unit ID equivalent to its index number within the array, then we can easily look it up afterwards e.g.

pragma solidity 0.7.5;

contract MemoryAndStorage {

    User[] users;   // array instead of mapping

    struct User{
        uint id;
        uint balance;
    }

    // The 1st User instance pushed to the array will be at index 0
    // It will therefore have an ID of 0
    function addUser(uint _balance) public {
        users.push(User(users.length, _balance));
    }

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

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

}

Let me know if you have any questions :slight_smile:

1 Like

Ok, I likely over simplified the problem, but I thought to take out the user ID since we can use the msg.sender address as the transaction ID. So I setup a mapping of the address to the balance.

pragma solidity 0.5.12;

contract MemoryAndStorage {
    
    mapping(address => uint) balance;
    
    
    function addBalance(uint _toAdd) public returns (uint) {
        balance[msg.sender] += _toAdd;
        return balance[msg.sender];
    }
    
    
    function getBalance() view public returns (uint) {
        return balance[msg.sender];
    }
}
1 Like

Thank you for the example, I understand it much better now that we can use both. While I was doing the assignment I did read somewhere that there were the constraints that you have mentioned with arrays.

1 Like

Glad the example was helpful @AU-Stephen,

Now that you’ve moved on with the Bank contract, I’m sure you can see why a mapping is more suitable than an array when we want to reference, or look up, stored data using addresses, instead of integers…

Hi @toddabraham,

Your complete re-write of the original contract is also a perfectly valid solution :ok_hand:

Mapping user data to user addresses, instead of integers, is actually more common practice.

Even though the initial idea of this assignment is to update a user’s existing balance by replacing it with the new one, I also think that it makes more sense to do what you have done, and add an amount to the user’s existing balance — or add it as a new balance value in the mapping for new users — using the addition assignment operator += (instead of the assignment operator =).


Are you following the old Ethereum Smart Contract Programming 101 course (based on Solidity v0.5.12) or the new one, which uses v0.7.5?

If you had already started the old course over a year ago, then I guess it makes sense to finish it first; but, if not, then you should be following the course based on the more up-to-date syntax (Solidity v0.7).

Your solution is actually more or less the same as the code used in the most recent course’s Bank contract… but that would be an amazing coincidence if you are following the older course :smile:

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

The above local variable “user” has been coded with a memory data location. Since memory is stored temporary
and cleared once the updateBalance function is done, whatever arguments placed in the function will not be stored, let alone to the Struct Storage data location. So in order to update the State variables, we must clear the keyword " memory " and replaced with the following code instead

User newUser = users [ id ];
newUser. balance = balance ; }

Hi @ecbwong,

You are right to remove the keyword memory from the local variable definition, but you need to replace it with storage. If you don’t add storage, you will get a red compiler error highlighting this.

This is because your local variable newUser is defined to hold struct instances of type User (i.e. struct instances based on the User struct). Local variables, parameters, and return values which are defined as complex data types (i.e. strings, arrays and struct instances) must have their data location explicity defined. There is no default data location in these cases.

By assigning users[id] to a local storage variable, we create a local “pointer” (which in your case would be called newUser).

This points to (references) users[id]===>   the struct instance in the mapping (users) which is mapped to the id input into the function.

Then, in the second line of code, any value which is assigned to a property of the local “pointer” (newUser) will effectively be updating that same property of a specific struct instance in the mapping …

Your local storage variable newUser will create a temporary “bridge” to the mapping during execution of the function. This enables a specific user’s balance (stored persistently in the mapping) to be reassigned with the new balance (input into the function), before this “bridge” is lost when the function finishes executing.

Because the pointer is updating an existing user’s balance, you can probably also see that newUser isn’t really an appropriate name for it. The new user instances are created and added to the mapping in the addUser() function.


Just one further observation …

This is mostly correct, apart from the final part, which I’ve highlighted in bold …

The argument values can be stored, but only temporarily. They won’t be stored persistently in a specific User instance (struct instance) in the mapping.

It’s the mapping which stores the struct instances which are created for each user. As we discussed a while ago, the struct itself is like a “blueprint” or a “template” used to create the individual User instances.

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

My solution was to change memory -> storage and added a check to see whether or not user exist. Mostly to try the require functionality :slight_smile:

pragma solidity >=0.7.0 <0.9.0;

contract MemoryAndStorage {

mapping(uint => User) users;

struct User{
    uint id;
    uint balance;
    bool exists;
}

function addUser(uint id, uint balance) public {
    users[id] = User(id, balance, true);
}

function updateBalance(uint id, uint balance) public {
     require (users[id].exists != false, "no user");
     User storage user = users[id];
     user.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 {
         // Before it was storing to a memory
         // User memory user = users[id];
         // user.balance = balance;
         // Now we are explicitly updating the balance against the stored value
         users[id].balance = balance;
    }

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

}
1 Like

I changed the memory to storage in the updateBalance function, but I was expecting the update to add to the initial balance created with the addUser function and spent the past few days unsuccessfully trying to achieve this. Anyway my code is shown below.

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

An excellent solution @antonfriedmann :muscle:

Your require() statement is a nice addition, it is well coded, and I think it improves the overall functionality and usefulness of the contract. The Boolean property you’ve added to the struct — assigned true when a new User instance is created — provides an appropriate marker with which to check if an id corresponds to an existing user or not.

In fact, this same require() statement could also be added to the getBalance() function. And to avoid code duplication, the require() statement can be moved to a modifier, and the modifier applied to both functions.

You can also make the Boolean expression (condition) more concise…

users[id].exists != false
users[id].exists

Both of these will evaluate to …

  • true when the exists property is true  ==>  the function continues executing
  • false when the exists property is as yet unassigned  ==>  revert is triggered

undefined and null do not exist as value types in Solidity. Any undefined variable will default to the equivalent zero-value for the data type it is defined with i.e.

uint id;         //==> 0
string message;  //==> ""
bool exists;     //==> false
address owner;   //==> 0x000...000

Let me know if you have any questions.

Nice solution @kmichie32 :ok_hand:
… and welcome to the forum! … I hope you are enjoying the course :slight_smile:

Yes … we are saving the new balance to persistent storage (changing the contract state).

If you have any questions about anything in the course, or about any of the assignments, just post them in the corresponding discussion topic (links are always provided in the lectures).

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

Even though the initial idea of this assignment is to update the existing balance by replacing it with the new one (what your posted solution does), I actually think that adding an amount to the existing balance (what you’ve been trying to do) makes more sense.

You can achieve this by replacing the assignment operator= with an addition assignment operator+= … easy when you know how, eh? :wink:

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

function updateBalance(uint id, uint amount) public {
    User storage user = users[id];
    user.balance += amount;
}
// using  += 
user.balance += amount;
// is a more concise way of coding ...
user.balance = user.balance + amount;
// Both perform the same operation

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 memory user = users[id]; this line was linking the change to temporary memory and not the storage value as the function header had setup. Also it's unnecessary code.
         users[id].balance = balance; //changed slightly to adjust for the elimination of the previous line.
    }

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

}
1 Like

Nice solution @spoonage :ok_hand:

I’m just not sure what you mean by the bit I’ve highlighted in bold in this comment …

Instead of "function header", do you mean "mapping" ?