function withdraw(amount) public returns (uint) {
msg.sender.transfer(amount);
returns balance[msg.sender];
}
function withdraw(amount) public returns (uint) {
msg.sender.transfer(amount);
returns balance[msg.sender];
}
Hello everyone
I just wanna make sure about a small detail that wasnât clearly stated in the video.
After succesfully using the transfer function, and after querying the same address the balance doesnât update automatically.
We have to add a line of code and substract the amount from the previous balance or am I missing something?
Hi @Meriem, and welcome to the forum!
Itâs great to see you here, and I hope youâre enjoying the course.
Correct
Thatâs all part of the assignment challenge. We want you to start noticing these kinds of issues yourself⌠for example âŚ
⌠and to come up with appropriate solutions. Developing this kind of awareness is an important part of learning to become a good programmer⌠so, youâre making good progress
Hi @Jacob_Pettit,
You have included nearly all of the additional lines of code needed to solve the problem with the withdraw function, but you need to have them in a different order:
In terms of the require statement, this needs to come first, because it is checking that the input is valid. While it is true that if require() is placed later in the function body all previous operations will still be reverted if it throws, this will end up costing more gas because only the remaining gas (after require has thrown) will be refunded, and not the gas consumed for executing require() itself and any code executed previously.
Have a look at this post which explains the importance of the order of the other statements within your withdraw function body.
Notice that the withdraw function header also includes returns(uint)
. This is not mandatory to include, and the function can still operate effectively without returning a value. But as itâs been included in the function header, you also need to include a return statement in the function body.
Let us know if you have any questions about these additional points
Hi @Andrewg,
Youâre missing a lot of code hereâŚ
Firstly, you should be getting compiler errors for:
returns
in the function header, but return
(without âs
â) in the function body. But well done for realising that you need to add a return statement You need to add a require statement to check that the caller has sufficient funds to cover their requested withdrawal amount.
Having made these changes, if you now deploy your contract and call the withdraw function, you will notice that the senderâs address receives the requested withdrawal amount, but their balance in the mapping is not reduced! Iâm sure you can see that this is another very serious bug!
Once youâve added all of this code, have a look at this post which explains the importance of the order of the statements within your withdraw function body.
If you find it hard to know where to start with any of these points, have a look at some of the other studentsâ code and feedback posted here in this discussion topic. And, of course, let us know if you have any questions, or if there is anything that you donât understand
Thanks for the assistance.
So this is what I came up with
pragma solidity 0.7.5;
contract bank {
mapping(address => uint) balance;
event withdrawal(address wallet, uint _amount);
function deposit() public payable{
balance[msg.sender] += msg.value;
}
function withdraw(uint amount) public{
require(balance[msg.sender] >= amount, "Insufficient balance !");
require(amount > 0, "There is no point in payaing gas fees withdrawing 0 !");
uint previousBalance = balance[msg.sender];
msg.sender.transfer(amount);
balance[msg.sender] -= amount;
assert(balance[msg.sender] == previousBalance - amount);
emit withdrawal(msg.sender, amount);
}
function getBalance() public view returns(uint){
return balance[msg.sender];
}
}
An excellent assignment solution @Meriem
You have added all of the additional lines of code needed to solve the problem with the withdraw function. Your additional assert statement is also appropriate and well coded
Now have a look at this post which explains an important security modification that should be made to the order of the statements within your withdraw function body. See if you can work out how to reorder your code for this, bearing in mind youâve got an additional 2 lines for the assert statement.
The additional event youâve added is also good. It emits and logs relevant information when a call to the withdraw function has executed successfully. Just remember that the convention is to start event names and contract names with a capital letterâ(e.g. event Withdrawal
â, â contract Bank
). Your code does still work starting these names with a lower case letter, but itâs standard practice to start contract, struct and event names with a capital letter.
pragma solidity 0.7.5;
contract Bank {
mapping(address => uint) balance;
address owner;
event fundsDeposited(uint amount, address indexed depositedTo);
modifier onlyOwner {
require(msg.sender==owner);
_; //Run the function
}
modifier enoughBalance (uint amount) {
require(balance[msg.sender]>= amount, "Not enough funds");
_; //Run the function
}
constructor(){
owner=msg.sender;
}
function deposit() public payable returns(uint) {
balance[msg.sender]+= msg.value;
emit fundsDeposited(msg.value, msg.sender);
return balance[msg.sender];
}
function withdraw (uint amount) enoughBalance(amount) public returns(uint){
msg.sender.transfer(amount);
balance[msg.sender]-=amount;
return balance[msg.sender];
}
function transfer (address recipient, uint amount) enoughBalance(amount) public {
//require(balance[msg.sender]>= amount, "Not enough funds");
require(msg.sender!= recipient, "Dont transfer to yourself");
uint previousSenderBalance=balance[msg.sender];
balance[recipient]+=amount;
balance[msg.sender]-=amount;
assert (balance[msg.sender]==previousSenderBalanceamount);
}
function getBalance () view public returns(uint){
return balance[msg.sender];
}
}
An excellent assignment solution @decrypter
You have added all of the additional code needed to solve the problem with the withdraw function. Your additional modifierâ enoughBalance
,âto provide the same require statement in both withdraw() and transfer() functions, is nicely done, appropriate, and shows a good understanding of how modifiers help us avoid code duplication, and keep our code concise
Now have a look at this post which explains an important security modification that should be made to the order of the statements within your withdraw function body.
Also, Iâm sure itâs just a copy-and-paste slip, but you are missing an arithmetic operator in the second operand in your assert statement condition:
You may find these types of errors are less likely to occur if you leave whitespace either side of your operators, as follows:
assert (balance[msg.sender] == previousSenderBalance - amount);
This is more standard practice, and also makes your code clearer, more readable, and developer-friendly
function withdraw(uint amount) public returns (uint){
require(balance[msg.sender] >= amount);
msg.sender.transfer(amount);
}
function withdraw(uint amount) public returns(uint) {
require(balance[msg.sender] >= amount);
msg.sender.transfer(amount);
balance[msg.sender] -= amount;
}
Thanks for your feedback. Of course the Assert was a copy past error (it was correct in Remix).
Regarding the order of statements. I agree that the reverse order looks cleaner. However isnât the function itself atomic? If this is not always the case even the different order will not guarantee correct functionality. The problem might be less severe but still there. In other words, I would expect Solidity to offer a foolproof way to guarantee that both statements or none are executed under any and all circumstances, no?
function withdraw(uint amount) public returns (uint) {
require(balance[msg.sender] >= amount);
balance[msg.sender] -= amount;
msg.sender.transfer(amount);
return balance[msg.sender];
}
Hi @Michael_Gerst,
The require statement youâve added is correct but you are missing 2 other important lines of code:
returns(uint)
. This is not mandatory to include, and the function can still operate effectively without returning a value. But as itâs been included in the function header, you also need to include a return statement in the function body.Once youâve had a go at making the above modifications, have a look at this post which explains the importance of the order of the statements within your withdraw function body.
Let us know if you have any questions about how to correct this, or if there is anything that you donât understand
Hi @santoretech,
Require statementâ
balance[msg.sender] -= amount;
â
Notice that the withdraw function header also includes returns(uint)
.âThis is not mandatory to include, and the function can still operate effectively without returning a value. But as itâs been included in the function header, you also need to include a return statement in the function body.
Then have a look at this post which explains the importance of the order of the statements within your withdraw function body.
Let us know if you have any questions about these additional points
Excellent solution @Bryan
You have included all of the additional functionality needed to solve the problem with the withdraw function, and you have the different lines in the correct order to reduce the security risk:
Itâs important to modify the contract state:
balance[msg.sender] -= amount;
before actually transferring the funds out of the contract:
msg.sender.transfer(amount);
⌠just in case there is an attack after the transfer, but before the state is modified to reflect this operation. Youâll learn about the type of attack this prevents, and how it does it, in later courses. We wouldnât expect you to know this at this early stage, though, so Iâm just telling you for extra information, in case you havenât already seen this explanation in some of the other posts in this discussion topic. Anyway, donât worry about the details for now, although itâs good to be aware of, and itâs great that youâre already getting into good habits
Hi @decrypter,
Great question!
Yes, Solidity functions are atomic. But this is not the same as being 100% secure. We still need to adopt best practices to reduce the risk presented by potential vulnerabilities. Here is an interesting article that opens with the fact that Solidity is atomic, but then goes on to discuss why we still need to address potential vulnerabilities in our smart contract code. Right at the very end it mentions the withdrawal pattern (order of statements) that I have highlighted, and the example given is essentially the same as the solution to this assignment in terms of optimal security.
https://blog.b9lab.com/the-solidity-withdrawal-pattern-1602cb32f1a5
This withdrawal pattern reduces the risk of re-entrancy attacks. If you are interested in learning about this type of issue in more detail, I would highly recommend that you take our Smart Contract Security course at some point. In that course we look at examples of different types of attacks (including re-entrancy) and the smart contract best practices we can adopt as developers to reduce the risk of these types of attack occuring.
To whet your appetite, here is another article that specifically addresses re-entrancy attacks, what they are, and how to protect Solidity smart contracts from them occuring.
https://medium.com/coinmonks/protect-your-solidity-smart-contracts-from-reentrancy-attacks-9972c3af7c21
I hope that goes some way towards answering your question
function withdraw(uint amount) public returns(uint) {
require(amount <= balance[msg.sender], "Insufficient balance");
uint initialBalance = balance[msg.sender];
balance[msg.sender] -= amount;
msg.sender.transfer(amount);
assert(balance[msg.sender] == initialBalance - amount);
return balance[msg.sender];
}
I added a require function to the withdraw function which checked that balance[msg.sender] >=
to the amount being withdrawn.
Also added balance[msg.sender] -= amount;
to update the balance of the person calling the function.
Also, for the fun of it I added an event to log the withdraw, similar to the event we have for the deposit.
Edit: Iâm not actually sure if I need to return the balance at the end of the withdraw function. Can somebody confirm this?
pragma solidity 0.7.5;
contract Bank {
mapping(address => uint) balance;
address owner;
event depositDone(uint amount, address indexed depositedTo);
event withdrawDone(uint amount, address indexed withdrawnTo);
modifier onlyOwner {
require(msg.sender == owner, "Invalid call address");
_;
}
constructor(){
owner = msg.sender;
}
//"payable" allows the function to receive money when people call it. Only for receiving.
function deposit() public payable returns (uint) {
balance[msg.sender] += msg.value;
emit depositDone(msg.value, msg.sender);
return balance[msg.sender];
}
function withdraw(uint amount) public returns (uint) {
require(balance[msg.sender] >= amount, "Balance too low");
msg.sender.transfer(amount);
balance[msg.sender] -= amount;
emit withdrawDone(amount, msg.sender);
return balance[msg.sender];
}
function getBalance() public view returns (uint){
return balance[msg.sender];
}
function transfer(address recipient, uint amount) public {
require(balance[msg.sender] >= amount, "Balance too low");
require(msg.sender != recipient, "Don't transfer money to yourself");
uint previousSenderBalance = balance[msg.sender];
_transfer(msg.sender, recipient, amount);
assert(balance[msg.sender] == previousSenderBalance - amount);
//event logs and further checks
}
function _transfer(address from, address to, uint amount) private {
balance[from] -= amount;
balance[to] += amount;
}
}
function withdraw(uint amount) public returns (uint){
require(balance[msg.sender] >= amount, "Not enough balance.");
require(msg.sender == owner, "You can only withdraw into your own account");
msg.sender.transfer(amount);
balance[owner] -= amount;
return amount;
}