Looks like the solution is to use storage instead of memory in the update balance function
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;
}
}
contract MemoryAndStorageFIXED {
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 {
uint previousBalance = users[id].balance;
User storage user = users[id];
user.balance += balance;
assert(user.balance == previousBalance + balance);
}
function getBalance(uint id) view public returns (uint) {
return users[id].balance;
}
}
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];
users[id].balance = balance;
}
function getBalance(uint id) view public returns (uint) {
return users[id].balance;
}
}
Update variable user in updateBalance function to have storage, to have persistent memory storage. This makes sure it is accessible globally in the contract. Using memory, it will only be accessible within the updateBalance function when it is executing.
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;
}
}
This is the best answer that I could find.
pragma solidity 0.5.1;
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;
}
functi
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;
}
}
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 id = users[id];
id.balance = balance;
}
function getBalance(uint id) view public returns (uint) {
return users[id].balance;
}
}
Hi @1984,
While your solution does work, you should be getting an orange compiler warning. Basically, this warning is telling you that you are using the same identifier/name (id
) twice: for the (i) input parameter, and (ii) local variable. I’m sure you can understand why this is not good practice, and risks introducing bugs when developing the contract further. It also makes your updateBalance function confusing to read. I can understand why you don’t want to name your local variable user
, because we already have the User
struct and users
mapping! However, even though these names are very similar, they are still different. If you would prefer something more distinct, then choose something like userToUpdate
or userInstance
which are still meaningful.
i couldn’t think it straight to find the solution so i watched the solution and as you did it my brain remind me of class and object from my c++ knowledge so i just call my object which is the balance into the class which is users[id]. below is my code :
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];
users[id].balance = balance;
}
function getBalance(uint id) view public returns (uint) {
return users[id].balance;
}
}
Hi @Phaxsam,
Your solution code is correct but what the code is actually doing is…
…assigning your new balance
value (2nd input parameter)
TO the balance
property of the specific User
instance WHICH IS
mapped to the key with a value of id
(1st input parameter)
AND
stored persistently in the users
mapping IN the contract state
Just let me know if you have any questions
Apologies for my ignorance, but I don’t understand what this command is doing…
users[id] = User(id, balance);
why are we just specifying “id” and not “balance” in the left side of the equal sign?
Hi @Yutaro_Shimizu,
Sorry for the delay in answering your question.
In our contract, the user ID is a property within each User
instance (created from the struct) and it is also the key each User
instance is mapped to in the mapping.
In the addUser() function, we create a new User
instance (based on the struct) with…
User(id, balance);
This assigns the value input as the id
argument to the id
property, and the value input as the balance
argument to the balance
property.
We then need to assign this new User
instance to the mapping, and we do that by mapping it to the key, which in this case is also the value input as the id
argument …
users[id]
Once this user has been added to the mapping, their data can be retrieved, or referenced, in the same way that it was initially assigned…
users[id]
i.e. by using the key (id
)
It may be easier to understand what’s happening if we do the same, but with a key which isn’t also one of the struct properties e.g.
pragma solidity 0.7.5;
contract Employees {
struct Person {
string name;
bool isManager;
}
mapping(uint => Person) people;
function addPerson(uint id, string memory name, bool isManager) public {
people[id] = Person(name, isManager);
}
}
Let me know if anything is still unclear, or if you have any further questions
Jon,
Thank you so much ! Your example and explanation makes so much sense now. One last question, however. Why are we able to use the same variable for the key and for instance in struct? I was thinking this would cause a collision…
Hey Yutaro,
I’m glad my explanation has helped make more sense of what’s happening. These coding structures can be difficult to fully understand at first, especially if you like to get to the bottom of things
The mapping only defines the data type of the keys, which in our example is uint
(unsigned integer).
mapping(
uint => User
) users;
We have just choosen to use the same uint value, input into the function as a parameter with the name id
, as both …
(i) the value assigned to the 1st struct property which also has the name id
; and
(ii) the uint
key which each User instance we create is mapped to when assigned to the mapping.
The id
within the struct is a property name, and the id
passed to the function is a parameter name. There is nothing to stop us from using the same name for both. The scope each is defined in is different, and so there is no “clash”.
You are right, though, that we shouldn’t use the same name for a variable and a parameter. If we did, this would generate a compiler warning.
Also, picking up on the other questions you’ve asked about this function in the Additional Solidity Concepts discussion topic …
users[id]
represents the “placement” of our new User instance in the mapping users
. We create a new entry in a mapping by assigning it to a new key in that mapping. The keys in the users
mapping are defined as having to be unsigned integers, and (as I’ve already mentioned above) for these keys, we are choosing to use the unsigned integers input into our function as the id
parameter. The values mapped to each key are defined as having a custom data type User
based on our User struct
. The User
struct has been defined with two properties, and that is the reason why each new value we assign to a new key in the mapping needs to be created with two property values: one for the id
property, and the other for the balance
property. For these two property values, we use the two values input into our function as the id
and balance
parameters.
The id
in users[id]
references the unsigned integer input into our function as the id
parameter. It is placed within square brackets and appended to the mapping name users
, because (as mentioned above) this is the syntax used to assign a new value to a mapping — achieved by mapping it to a key, which is the value placed within the square brackets. The same syntax is also used to reference an entry which is already stored in the mapping.
I hope this helps to answer some more of your questions. Just let me know if anything is still unclear, or if you have any more questions
Jon,
Wow, great explanation. Now I fully get it. Thank you so much!
Yutaro
In this section:
function updateBalance(uint id, uint balance) public {
User storage user = users[id];
user.balance = balance;
It makes sense to store the new user data to override the old.
function updateBalance (uint _id , uint _balance) public { users [_id].balance=_balance; }
The function updateBalance must include " users [_id]. balance = _balance " as shown above to make an update to the balance that refers to the specific user. Otherwise we can’t get the “getBalance function” to retrieve the rightful user updates
You solution is correct @ecbwong
The key thing here is that you are now updating the specific user’s balance in persistent storage in the mapping. Originally, we were still updating the specific user’s balance, but only in a temporary, local copy of their record, which was then lost when the function finished executing.
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 {
users[id].balance = balance;
}
function getBalance(uint id) view public returns (uint) {
return users[id].balance;
}
}