Transfer Assignment

Nice solution @Obi :muscle:

You have added all of the additional lines of code needed to solve the problem with the withdraw function.

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.

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

1 Like

Hi @tnc,

Could I ask what you mean by “an error in hour code”, please?

Oops, It’s a typo. “an error in our code” should be. Sorry for the confusion.

1 Like

Jon,
thanks again for more comments. -
I don’t quite understand your point: I have adjusted the balance amount BEFORE making the transfer, so that should avoid re-entry attack, no? Isn’t that the correct way to do it?
(BTW I’m completing the bootcamp and am just refreshing as I have a lot happening and some concepts have been slipping my mind hence the refresher)

Another point: you mention the “orange warning” - yes I get the warning, but for some reason, only that little orange warning appears in remix but without stating the actual text of the warning in the yellowish boxes below the left panel (as I’m used to seeing normally) Any clues from yur end why these comments are missing?
Screenshot 2021-05-27 at 19.09.23|654x500

thank you.

1 Like

Hey Ernest,

The require statement you’ve added is correct, :ok_hand:

You are also right that returns(uint) can be removed from the function header. It isn’t mandatory to include, and the function can still operate effectively without returning a value.

But you are missing one very important line of code

If you deploy your contract, make a deposit, and then call your withdraw function (without the additional assert statement), you will notice that the caller’s address receives the requested withdrawal amount, but their balance in the mapping is not reduced (call getBalance to check this). I’m sure you can see that this is a very serious bug because, even though there is a limit on each separate withdrawal, this limit is never reduced to reflect each withdrawal.
msg.sender.transfer(amount)  transfers ether from the contract address balance to the caller’s external wallet address, but it doesn’t adjust the individual user balances in the mapping. These balances perform an internal accounting role, and record each user’s share of the contract’s total ether balance. So, just as we increase a user’s individual balance when they deposit ether in the contract, we need to do the opposite whenever they make a withdrawal.

Your assert statement is incorrect and so it’s always failing whenever require evaluates to true i.e. when the amount requested for withdrawal is less than or equal to the user’s original balance (not their actual balance after having already withdrawn some ether because, as I’ve mentioned above, this isn’t being reduced). Maybe this is why your require statement isn’t failing and generating the error message you expect. However, even with your current incorrect assert statement, require should still trigger revert first whenever the amount requested for withdrawal is greater than the user’s original balance.

Until you add the additional line of code to adjust the user’s individual balance in the mapping, your assert statement is actually meaningless, because balance[msg.sender] is the same before and after the withdrawal. But even when you have added the additional line code, your assert statement will still be incorrect. By using an assignment operator the assert statement is changing the balance in the mapping as well as checking it. It must only check it. You also need to think carefully about which operand (which side of the comparison operator) to add or subtract the withdrawal amount from.

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 me know if you have any questions about how to correct this, or if there is anything that you don’t understand :slight_smile:

1 Like

@tnc,
Thanks for clarifying. I am rather new to programming and I would not have known any better man. Thank you very much. I spend some time reading other posts here to learn from others’ mistakes and the feedback that is given to others, too. Any who, thanks again!

1 Like

Hey Andreas,

Yes, that’s correct. I was actually confirming that what you’ve done is correct and providing the reason. Sorry, I probably confused you because you already know the reason why (to prevent re-entrancy). I’m explaining the reason to everyone taking this new Solidity course, because it’s not covered in the lectures — if I’d realised you were revising the material I wouldn’t have added the explanation to your feedback :sweat_smile:

Yes
 looking at your screenshot, you’ve got the Hide Warnings box checked. If you uncheck that you should get the warning messages, and also the line with the issue highlighted in yellow (if you hover over that you’ll get the error message directly in the text editor) :slight_smile:

:upside_down_face: :relaxed: :face_with_monocle: :flushed:

That was like a challenge all in itself, lol! Wasn’t it @tom88norman

1 Like

Well, it looks like I got it. Jon, I was reading in another of your posts that you cued us to add the additional lines of code to prevent reentry attacks. Could you tell me if I did it right, please? I added it in the the withdraw function.

pragma solidity 0.7.5;

contract Bank{
    
    mapping (address => uint) balance; 
    address owner; 
    
    event depositDone(uint amount, address indexed depositedTo); 
    
    modifier onlyOwner{
        require(msg.sender == owner); 
        _; // run the function 
    }
    
    
    constructor(){
        owner = msg.sender; 
    }
    
    
    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) {
        msg.sender.transfer(amount); 
        require(balance[msg.sender] >= amount); 
        return balance[msg.sender]; 
        balance[msg.sender] -= amount; 
       msg.sender.transfer(amount); 

    }
    
    
    function getBalance() public view returns (uint) {
        return balance[msg.sender];
    }
    
    function transfer(address recipient, uint amount) public {
        require(balance[msg.sender] >= amount, "Balance not sufficient"); 
        require(msg.sender != recipient, "You may not transfer funds to yourself"); 
        
        uint previousSenderBalance = balance[msg.sender]; 
        _transfer(msg.sender, recipient, amount); 
        assert(balance[msg.sender] == previousSenderBalance - amount); 
    }
    
    
    function _transfer(address from, address to, uint amount) private {
        balance[from] -= amount; 
        balance[to] += amount; 
    }
    
    
}

Hi Joseph,

Your require statement and return statement are both correct. But the withdraw() function will exit after executing the return statement, and so your last two lines are unreachable. The compiler should have given you a yellow warning for this.

balance[msg.sender] -= amount;  is correct but because it is currently unreachable, the caller’s balance will not be updated in the mapping. If you deploy your contract, make a deposit, and then call your withdraw function, you will notice that the caller’s address receives the requested withdrawal amount, but their balance in the mapping is not reduced (call getBalance to check this). I’m sure you can see that this is a very serious bug because, even though there is a limit on each separate withdrawal, this limit is never reduced to reflect each withdrawal.

You’ve also included   msg.sender.transfer(amount);  twice! The second one is currently unreachable, and so doesn’t affect anything, but the first one should not come before the require statement. Require statements validate inputs or restrict access to the function. Therefore, it’s important that they are placed as near to the beginning of the function body as possible. In addition, if the withdrawal amount isn’t checked until after the ether is transferred from the contract address to the caller’s external wallet address, less gas will be refunded if require fails and reverts the transaction, than if the check was performed at the very beginning. This is because only the gas cost associated with the unexecuted operations after require() is saved; any gas already consumed executing code for the operations before and including require() is not refunded.

This concerns the order of the statements in the solution (it doesn’t require adding any additional lines of code). But you need to resolve the other issues I’ve mentioned first, before the security of your contract against attacks can be checked.

So, basically, you’ve got the right code, but you need to remove the duplicated statement and modify the order of the other statements to address the issues I’ve mentioned above.

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

Re-entrancy attack: ok - I have full empathy for the educational goal of your comments, perfectly fine. Thanks also for the minor correction of terminology re-entry / re-entrancy.
And thanks for the hint with the checkbox - that works now once again :slight_smile:

1 Like

pragma solidity 0.7.5;
contract Bank {

mapping(address => uint) balance;

//Storage - Permanent storage of data (State variables)
//Memory - Temporary storage used in function execution
//Calldata - Save arguments/inputs to our functions
//strings, arrays, mappings, structs

address owner;

event depositDone(uint amount, address indexed depositedTo);

modifier onlyOwner{
    require(msg.sender == owner);
    _; // Run the function
    
}
    
    constructor(){
        owner = msg.sender;
    }


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[msg.sender] -= amount;
    msg.sender.transfer(amount);
    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 not sufficient");
    require(msg.sender != recipient, "Dont transfer money to yourself");
    
    uint previousSenderBalance = balance[msg.sender];
    
    _transfer(msg.sender, recipient, amount);
    
    assert(balance[msg.sender] == previousSenderBalance - amount);
    
     }

function _transfer(address from, address to, uint amount) private{
    balance[from] -= amount;
    balance [to] += amount;
}

}

1 Like

An excellent solution @PoeAslan :muscle:

You have added all of the additional lines of code needed to solve the problem with the withdraw function, and all of the statements in the function body are in the correct order to reduce security risk:

  1. check inputs (require statements)
  2. effects (update the contract state)
  3. external interactions

It’s important to modify the contract state for the reduction in the balance


balance[msg.sender] -= amount;

before actually transferring the funds out of the contract to the external wallet address


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. But it’s great that you’re already getting into good habits in terms of smart contract security :+1:

Let us know if you have any questions :slight_smile:

1 Like

Thanks, but I can®t take credit for this. I tried my best to understand the syntax but nothing worked. I watched the “require” and “assert” videos several times but was unable to resolve the issue so I looked at the solution and added it. I guess I have to repeat the course a few times from start to finish until I understand the logic. :slight_smile:

1 Like

function withdraw (uint amount) public returns (uint) {
//address payable toSend = 0x17F6AD8Ef982297579C203069C1DbfFE4348c372;
//toSend.transfer(amount);
require(balance[msg.sender] >= amount);
uint previousBalance = balance[msg.sender]
msg.sender.transfer(amount);
assert(balance[msg.sender] = previousBalance - amount);
}

@jon_m

Hello again, Jon. I believe I understand what you are telling me concerning the order of the statements that I add to the function transfer. When the “require” statement is used, as it is a validator/error handling code we need to use it as close to the function body so that it can be read first. Solidity is a statically read language so it is read first and validates specific data.

Also, I added the msg.sender.transfer(amount) statement before the balance[msg.sender] per your feedback to another of the guys here at the academy. I no longer got any error messages except for a “number 1” next to the compiler icon but when I refer to the code to see an error message, I see no error messages so I am not sure what that “number 1” might refer to. Please let me know if I got it right this time, please big guy! Thanks in advance!

pay.transfer

1 Like

Hi @PoeAslan,

Well that’s good that you are being realistic about what you still need to work at understanding :+1:

Here is a list of some learning and study activities, which you may find useful when repeating the course and the assignments. I think they could help you get more out of the course, and will also make the repetition more varied and interesting.


  • Have a look at other students’ posts in the relevant forum discussion topic, with their attempts, solutions, and the help, comments and feedback posted as replies. There are a lot of comments and explanations posted here. It’s well worth spending your time browsing and reading, not only to get help with the assignments, but also after finishing a section or particular assignment as a way to review what you’ve learnt, and to discover alternative coding solutions or answers.
  • When you look at the assignment solutions, if they are different to your code, you should spend some time analysing and thinking about them. This is a really important stage in the learning process. You can learn a lot by working out what the solution code does and how it does it, and also how to go about “bridging the gap” from what you managed to do (or not) on your own. However, it’s important to remember that there are usually several different alternative approaches and solutions to these assignments, so if you’ve coded yours differently, but it still works, then it may well be valid. If you’re not sure, then post it here in the forum, and we’ll review it for you.
  • Another good learning technique to use (after having already done the tasks described above) is to then try the assignment again from scratch without looking back at the solution (like a memory test). You can also try to do this after having left it for a certain period of time (say, the next day, then after a few days, then a week etc. etc.) This builds up longer-term retention. This is a good approach to take when reviewing a course you’ve already done, or earlier parts of the course you’re currently doing. Instead of just looking back at your solutions to the assignments, test yourself to see how many of them you can redo from scratch — only checking your previous code and notes, and rewatching the videos, if you need to remind yourself about certain things.
  • Another important point to remember is that it can take some time before you fully understand all of the code in an assignment, so another good strategy is to come back and revisit an assignment, which you didn’t fully understand, after you’ve progressed a bit more through the course. You may well find that it’s much easier then.
  • Don’t forget to do some research yourself on the Internet. Here is a link to a playlist from the YouTube channel Eat The Blocks. I’ve seen some of this guy’s videos myself and I think they are particularly helpful for learning Solidity. The videos are quite short and the explanations clearly explained and well demonstrated with clear examples. You’ll find that several of the videos in this playlist correspond to specific sections of this course, and so serve as an ideal starting point for your own further research.
    https://www.youtube.com/playlist?list=PLbbtODcOYIoE0D6fschNU4rqtGFRpk3ea
  • Finally 
 play around with the code, experiment, and try to come up with your own examples, no matter how basic, short or simple. Even by just making a few small changes and adaptations to the code presented in the course, any amount of personalisation that you are able to add to your code will help it to mean more to you, and will therefore help you to internalise it better.

Anyway, I hope you find some of these ideas helpful. Just let me know if you have any questions :slight_smile:

1 Like

Hi @vili,

The require statement you’ve added is correct :ok_hand: but you are missing 2 other important lines of code, and there are couple of other mistakes which prevent your code from compiling:

  • You are missing a semi colon at the end of one of your statements.
    If you format your code before posting it, as well as making it more readable, this will also make it easier to spot these kinds of mistakes.
    Follow the instructions in this FAQ: How to post code in the forum

  • You have used an assignment operator (=) instead of an equality operator (==) in your assert statement.

  • Once you have corrected the above 2 syntax errors, if you deploy your contract, make a deposit, and then call your withdraw function as it is, your assert statement will always fail. This is because, although the requested withdrawal amount is transferred from the contract address to the caller’s external wallet address, the caller’s balance in the mapping is not reduced.
    msg.sender.transfer(amount) transfers ether from the contract address balance to the caller’s external wallet address, but it doesn’t adjust the individual user balances in the mapping. These balances perform an internal accounting role, and record each user’s share of the contract’s total ether balance. So, just as we increase a user’s individual balance when they deposit ether in the contract, we need to do the opposite whenever they make a withdrawal.
    When you’ve corrected your code for this, the invariance your assert statement is checking for will always be true.

  • 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 should also include a return statement in the function body. You should have got a yellow/orange compiler warning for this.

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 me know if anything is unclear, or if you have any questions :slight_smile:

I have just modified this function as requested:I have added a requirement and an assert function. The require function makes sure that there is enough balance in the address and the assert function to secure.
I’m not too sure about the syntax of the assert function tho. The idea behind it is to make sure that the new value of the sender is that of the previous value - the transferred amount. Hopefully this is not too far off.

mapping (address => uint) balance;

function withdraw(uint amount) public returns(uint){
require (balance[msg.sender]>=msg.value);
msg.sender.transfer(amount);
return msg.value;
assert(msg.value=msg.value-amount);
}

    function withdraw(uint amount) public returns(uint){
        //check balance of msg.sender
        require(balance[msg.sender] >= amount, "Balance not sufficient");
        uint iniitialBalance = balance[msg.sender];
        
        //commit withdrawal and update balance
        msg.sender.transfer(amount);
        balance[msg.sender] -= amount;
        
        //check balance after withdrawal
        assert(balance[msg.sender] == iniitialBalance - amount);
        return balance[msg.sender];
    }
1 Like