Transfer Assignment

I got the " balance[msg.sender] -= amount;" part ok but realised I also needed the require part of the code when looking through the forum.

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

Nice solution @Douglas :ok_hand:

… and nice research :muscle: After you’ve done the assignment yourself, it’s really good to look at some of the other students’ solutions posted here in the forum, and the comments and feedback they’ve received: that way, you can check your solution and any points you were unsure about, and often learn some extra things.

You have added both of the essential lines of code needed to solve the problem with the withdraw function, and your statements are also in the correct order to reduce security risk:

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

Just as you have done, 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 address…

msg.sender.transfer(amount);

This is to prevent what is known as a re-entrancy attack from occuring after the transfer, but before the user’s individual balance (effectively, their entitlement) is reduced to reflect this operation. You’ll learn more about this type of attack, and how the above order of operations helps to prevent it, in the courses which follow this one. But it’s great you’re already getting into good habits in terms of smart contract security :+1:

Hi, there is my solution for transfer. but it seems with recent version of the compiler we must implement a receive function to send money an do transfer.

pragma solidity ^0.8.7;
contract Bank{
    mapping(address=>uint) balance;
    event depositDone(address sender,uint _amount);
    function deposit()public payable {
        balance[msg.sender]+=msg.value;
//emit an event when the deposit function is call
        emit depositDone(msg.sender,msg.value);
    }
    
    function withdraw(uint _amount) public returns(uint){
        //Check the address that is calling this function have enough money in his balance
        require(balance[msg.sender]>=_amount,"No enough funds");
        balance[msg.sender]-=_amount;
        payable(msg.sender).transfer(_amount);
        return balance[msg.sender];
    }
    
    function getBalance()public view returns(uint){
        return balance[msg.sender];
    }
}
1 Like

Hi @michael356,

The return statement you’ve added to your withdraw function is correct :ok_hand:

You are right that you need a require() statement in the withdraw function to check this, but there isn’t one in the code you’ve posted. Can you add it, so we can see it?

That’s because you need to add an extra line of code …

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, with this line …

… we need to do the opposite whenever they make a withdrawal.

Once you’ve had a go at adding both of these essential lines of code, 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:

pragma solidity 0.7.5;
contract Bank {
  
     mapping(address => uint) balance;
     address owner;
     
     event transferComplete(address sender, address indexed recipient, uint amount);
     event depositComplete(uint amount, address indexed sender);
     
     modifier onlyOwner {
         require(msg.sender == owner);
         _;// underscore means run the function
     }
     constructor(){
         owner = msg.sender;
         
     }
    function deposit() public payable returns (uint) {
        balance[msg.sender] += msg.value;
        emit depositComplete(msg.value, msg.sender);
        return balance[msg.sender];
    }
    function withdraw(uint amount) public returns (uint){
            
        require(balance[msg.sender] >= amount, "Balance not sufficient.");
        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, "Transferring funds to yourself is prohibited.");
       
        uint previousSenderBalance = balance[msg.sender];
        
    
        _transfer(msg.sender, recipient, amount);
        
        
        assert(balance[msg.sender] == previousSenderBalance - amount);
        
        emit transferComplete(msg.sender, recipient, amount);
        // event logs and further checks
    }
    
    function _transfer(address from, address to, uint amount) private {
                balance[from] -= amount;
                balance[to] += amount;
    }
}
1 Like

This is my solution.

Is using “assert” in this case an unnecessary second check? Is there some rule of thumb as to decide when to use “assert” (or in other words, when is this check unnecessary)?

function withdraw(uint amount) public returns(uint){
       
        require(amount <= balance[msg.sender], "Insufficient balance");

        uint previousSenderBalance = balance[msg.sender];

        balance[msg.sender] -= amount;

        msg.sender.transfer(amount); 

        assert(balance[msg.sender] == previousSenderBalance - amount);
        
        return balance[msg.sender];

}
1 Like

Nice solution @moise :ok_hand:

You have added both of the essential lines of code needed to solve the problem with the withdraw function, and your statements are also in the correct order to reduce security risk:

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

Just as you have done, 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 address…

payable(msg.sender).transfer(amount);

This is to prevent a re-entrancy attack from occuring after the transfer, but before the user’s individual balance (effectively, their entitlement) is reduced to reflect this operation. As I mentioned to you in my feedback for your Events Assignment, you’ll learn more about this type of attack, and how the above order of operations helps to prevent it, in the courses which follow this one. But it’s great you’re already getting into good habits in terms of smart contract security :muscle:


Not in the contract sending the Ether… A receive Ether function would be needed in an external contract calling the withdraw() function, in order for it to be able to receive any Ether it had deposited in the Bank contract, when this Ether is sent to it by the transfer method. But this isn’t something new with Solidity v0.8. Your contract compiles without any errors, so I’m not sure what has made you think that you need a receive Ether function …

Let me know if you have any questions.

You are right that you need a require() statement in the withdraw function to check this, but there isn’t one in the code you’ve posted. Can you add it, so we can see it?

Blockquote
what confused me is in the withdraw function , it called the transfer function which include the the require() statement.
“require (balance[msg.sender] >= amount,“Balance not sufficient.”);” is it need to be write again?

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, with this line …

Blockquote

also this confused me , the statment is already in _transferfunction

function _transfer(address from, address to ,uint amount) private{

    balance[from] -= amount;
    balance[to] += amount;

within withdraw function called transfer function ,within transfer function called _transfer function.

I think “balance[from] -= amount” means “balance[msg.sender] -= amount”

so i didn’t write the same statement twice,obivously i am not right, but don’t get why.

An excellent solution and, overall, a very well-coded contract @Gry :muscle:

You have added both of the essential lines of code needed to solve the problem with the withdraw function, and your statements are also in the correct order to reduce security risk:

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

Just as you have done, 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 address…

msg.sender.transfer(amount);

This is to prevent a re-entrancy attack from occuring after the transfer, but before the user’s individual balance (effectively, their entitlement) is reduced to reflect this operation. You’ll learn more about this type of attack, and how the above order of operations helps to prevent it, in the courses which follow this one. But it’s great you’re already getting into good habits in terms of smart contract security :ok_hand:

Greetings, @jon_m, i understand it know. i had also read the docs. thank you

1 Like

Nice solution @Alex_13 :ok_hand:

You have added both of the essential lines of code needed to solve the problem with the withdraw function, and your statements are also in the correct order to reduce security risk:

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

Just as you have done, it’s important to modify the contract state for the reduction in the user’s balance…

balance[msg.sender] -= amount;

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

msg.sender.transfer(amount);

This is to prevent a re-entrancy attack from occuring after the transfer, but before the user’s individual balance (effectively, their entitlement) is reduced to reflect this operation. You’ll learn more about this type of attack, and how the above order of operations helps to prevent it, in the courses which follow this one. But it’s great you’re already getting into good habits in terms of smart contract security :muscle:


This is a good question… but I have to say, I’ve never seen any documentation discussing any guidelines in terms of when invariances should be checked and when it’s unnecessary. My own conclusion is that a developer needs to weigh up several factors on a case-by-case basis and perform their own risk assessment and cost-benefit analysis:

  • How serious would the negative consequences be if the function’s code failed to perform a computation as it should (bearing in mind the improbability of this actually happening)? For example, the extent of any financial impact or impact to people’s health and safety.
  • How much additional gas is consumed to execute the assert() statement, and any associated lines of code which also need to be added with it, each time the function is called and assert() eveluates to true (effectively, always)?
  • Does any extra security and reduction in risk, gained by adding assert(), justify the additional gas cost?

Depending on each particular function and smart-contract use-case, there will be other factors to consider too.

In this particular Bank contract, your use of assert() in the withdraw function certainly isn’t wrong, and it isn’t unnecessary either.

Let me know if you have any further questions :slight_smile:

1 Like

Hi @michael356,

This is what you haven’t understood and what’s caused your confusion.

msg.sender.transfer(amount);

This line of code isn’t calling our smart contract’s transfer() function. To do that we wouldn’t call it on an address, and we would need to call it with two arguments (a recipient address as well as the uint amount) e.g.

transfer(recipient, amount);

Instead, what we have in the withdraw function is an address member. This particular address member is a special type of function. It is pre-defined within Solidity’s syntax, and is similar to a method in JavaScript. This is the syntax…

<address payable>.transfer(uint amount)

…meaning that transfer must be called on a payable address with a single uint argument. The uint value will always be the amount of wei which is deducted from the smart contract’s ether balance, and transferred to the external address which transfer is called on.

This what I was explaining here …

The caller of the withdraw function is msg.sender, so because the transfer method (address member) is called on msg.sender using dot notation, that’s why it will transfer the withdrawal amount to the caller’s external address.

As the withdraw function isn’t calling the smart contract’s transfer() function, this is why you need to also add a require() statement to the withdraw function, in order to perform the same check.

And because the withdraw function doesn’t call the transfer() function, this then doesn’t call the _transfer() helper function. So, the code in the _transfer() function body …

… also isn’t executed. This is why you also need to add the additional line of code to your withdraw() function which adjusts the user’s individual balance in the mapping for the withdrawal amount.

Then you need to consider the order of your statements …

Let me know if anything is still unclear.

Answer to ensure transfer not more than owner’s balance in the smart contract

function withdraw ( uint amount ) public returns ( uint ) {
   require amount [ msg.sender ] <= msg.value;
   msg.sender.transfer ( amount );
}

I posted a screenshot to see you the error message with.
Don’t understand error message, I’ve got some trouble when playing with Wei instead of playing with Eth.
Better when I know debug that, later.

Hi @_Vincent,

The error you are getting is because your assert statement is failing and reverting the transaction.

Your assert statement is failing because you are missing an essential line of code…

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 have to increase a user’s individual balance when they deposit ether in the contract, with this line …

balance[msg.sender] += msg.value;

… we need to do the opposite whenever they make a withdrawal.

Hopefully you can see that, in your code, balance[msg.sender] in the assert statement is still the same value as previousBalance saved before the withdrawl of funds. So the condition will always evaluate to false.

Let me know if anything is still unclear, and if you have any further questions about how to correct this.

1 Like

Hi @ecbwong,

Quite a few problems here …

This line should have generated a red compiler error…

  • A require statement is a function, and the condition is an argument, so you need parentheses.

  • msg.value references an amount of wei received by a function in order to add it to the contract balance. This happens in the deposit() function when we call it with an ether value. A function has to be payable to receive ether.
    In contrast, the withdraw function is sending (not receiving) ether from the contract (deducting it from the contract balance) to an external address. That is why this function doesn’t need to be payable.
    msg.value will only reference an amount of wei temporarily, and within the local scope of the payable function it’s input into. It cannot be used in another function to reference this same value. Besides, in the withdraw function we need to reference the user’s total current balance, which is stored persistently in the mapping.

  • To reference a value stored in a mapping (in our case a uint balance) you need to use the syntax mappingName[key]
    In our mapping the key to each balance is an address, so your key is correct [msg.sender] , because we want to reference the current balance of the caller of the function. But the name of the mapping isn’t amount


Your withdraw function is also missing an essential line of code …

Once you’ve corrected your require() statement, if you deploy your contract, make a deposit, and then call your withdraw function with only these 2 lines in the function body, you will notice that the caller’s external address receives the requested withdrawal amount, but their balance in the mapping is not reduced! This means that they can make repeat withdrawals up to the total amount of ether held in the contract (the sum of all of the individual account holders’ balances). So, I’m sure you can see why this is a very serious bug.

msg.sender.transfer(amount) transfers ether from the contract address balance to the caller’s external 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 …

balance[msg.sender] += msg.value;

… we need to do the opposite whenever they make a withdrawal (but not using msg.value).


Finally, 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 if you include it in the function header, you should also include a return statement in the function body. Once any remaining compiler errors (highlighted in red) are resolved, the compiler would generate a yellow/orange warning about this missing return statement.

Let me know if you have any questions about any of these points, or need any help making the necessary modifications to your code.

Thanks Jon.
In my mind, the standard function “Transfer()” adjust too the individual user balance in the mapping.
So, thanks for your light.

1 Like

Hi Jonathan

I must confess that I do have problems with my ReMix. Firstly, I struggle with it because not sure whether it’s me, I don’t seem to code with ease using it. Sometimes I tried deleting old files or creating new files and I messed the application up. Uninstalled and tried installing again, the files appeared again.

Secondly, having gone through your answers, I think I needed more time to figure out many things - didn’t realize msg.value is used in conjunction with payable ( crediting ) smart contract only and not the other way around. Is it possible you recommend me a good book that teaches on Solidity syntax? There are still many points in your answers which I am not sure whether I am understanding them properly unless I can see the corrected format to vouch my understanding.

Thanks
Eric

Hi Eric,

Sorry to hear you’ve been having problems with Remix :expressionless:

You should be able to just right click on a file and select delete to delete it. You can also rename a file that way too.
You can create a new file by clicking on the icon on the left of the row of icons at the the top of the list of files. To make sure you’re in the right folder you can click on the folder you want the new file to be created in, before you create it.

The files you create in Remix are stored in a local cache folder. This means that if you clear your browser data and local cache you can lose your files. So, it’s always good to make a backup copy of your files somewhere else. This can be as simple as copying and pasting a file’s code into your text editor (Atom for example) and then saving it as a .sol file wherever is most convenient for you.

Some other students have had problems with Remix, so it’s not just you. If you’re using Brave then that might be the issue. I’m not sure of the exact problem, because I don’t use Brave myself, but it’s something to do with the Shields settings. Try using Chrome or Firefox, if you’re not already. Remix normally works much better for students on either of those browsers if they’ve had problems with others such as Safari or Opera. I use Remix in Chrome and it works absolutely fine for me.

It seems to me from some of the code you’ve posted that you haven’t been compiling your files effectively. I would make sure you’ve got your Auto compile box checked in your Compiler Settings on the Solidity Compiler panel. This will compile your code as you go, and will immediately highlight any errors in red, and warnings in yellow/orange. Often just seeing that a line has an error or warning is enough to prompt you to check for any errors and correct them yourself. When you can’t see what the problem is, then you can read the actual error or warning message by either hovering over the red or yellow/orange indicator, or by navigating to the bottom of the Solidity Compiler panel.


I have no knowledge of any books, but 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.

https://www.youtube.com/playlist?list=PLbbtODcOYIoE0D6fschNU4rqtGFRpk3ea

Also, it’s well worth spending your time browsing and reading other posts in these forum discussion topics, which include other students’ attempts and solutions, and the help, comments and feedback posted as replies. This is a valuable resource because it relates directly to the course material. You can use it 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, and additional information.

Have you not seen the model assignment solutions that are provided? There should be links to them from the relevant lessons. Here is a link to the solution for this assignment:

https://github.com/filipmartinsson/solidity-0.7.5/blob/main/transfer-assignment-solution.sol

You can now read through my comments again, using the withdraw() function in this solution code as a reference.
You’ll find all the other solutions within the same repository. But remember, there are always alternative solutions, and so that’s why it’s also good to have a good look at some of what’s been posted here in the forum.

Anyway, I hope that gives you some ideas about how to tackle some of these difficulties :slight_smile:

Thanks Jonathan

I don’t use Brave browser although I have it installed in my laptop. I do have Chrome though and do use it frequently. My problem with Remix is I find it very clunky and whenever I wanted to clear something, I find it not as simple or friendly thus remnant issues keep popping up despite I thought I cleared it.

Thanks for the YouTube link. I actually downloaded something like a “White Paper” on Solidity before and tried reading it, but I found it so confusing maybe because I don’t have a strong programming background.

As for the link to the solution, I could have missed this one. Yes, in Ethereum Smart Contract Programming I actually completed the older version and there was a solution video on every assignment. I could have missed this one in the newer version. Anyway, thanks for the solution link.

Eric

1 Like