Transfer Assignment

Nice solution @Adrian1 :ok_hand:

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.


Some additional comments …

(1)

No … this line of code doesn’t add Ether to the contract address balance.

A function is marked payable when it needs to receive Ether from an external address to add to the contract address balance . Including payable in a function header enables the Ether value sent to the function to be added to the contract address balance, without us having to add any further code for this. In the deposit() function, this happens automatically because we have marked the function as payable . The line …

… then adds the same value to the individual user’s balance in the mapping, in order to keep track of their share of the total funds held in the contract.

(2)

To clarify …

The _transfer() helper function is called with 3 arguments. These 3 values are input into the _transfer() function and assigned to 3 corresponding parameters:

The argument msg.sender (an address) is assigned to the from parameter.
The argument recipient (an address) is assigned to the to parameter.
The argument amount (an unsigned integer) is assigned to the _amount parameter.

msg.sender always references the address that has called the function in which it is used.

(3) You now have the emit statement for the DepositDone event in the correct position in the deposit function :ok_hand:

(4) Have a look at the names of all of your functions, and think about what I said in my feedback for your Events Assignment code regarding the importance of consistency with names …

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

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

An excellent solution @CryptoUsic :muscle:

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

  • Check inputs (require statements)
  • Effects (update the contract state)
  • External Interactions (e.g. transfer method)

Just as you have done, it’s important to modify the contract state for the reduction in the individual 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 what is known as a re-entrancy attack from occuring after the transfer, but before the user’s individual balance (effectively, the share of the total contract balance they are entitled to withdraw) 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:

Don’t forget to post your solution to the Events Assignment here. This is the assignment from the Events video lecture, which is near the end of the Additional Solidity Concepts section of the course.

Let me know if you have any questions :slight_smile:

1 Like
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.7.5;

contract Bank{

  mapping(address => uint) balance;
  // MAX 3 indexed params per event
  event depositDone(uint256 amount, address indexed depositedTo);
  event withdrawtDone(uint256 amount, address indexed depositedTo);
  event transferDone(address indexed fromAddress, address indexed toAddress, uint256 amount);

  function deposit() public payable returns(uint){
    balance[msg.sender] += msg.value;
    emit depositDone(msg.value, msg.sender);
    return balance[msg.sender];
  }

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

  function withdraw(uint amount) public payable{
    require(balance[msg.sender] >= amount, "You have not enought balance!");
    msg.sender.transfer(amount);
    balance[msg.sender] -= amount;
    emit withdrawtDone(msg.value, msg.sender);
  }

}

1 Like

Hi @tbem,

You have added the additional lines of code needed to solve the problem with the withdraw function :ok_hand:

You are also right that it’s not necessary to return the updated balance, and so returns(uint) can be removed from the function header. Besides, we do already have the getBalance() function which a user can call to consult their own individual updated balance after a withdrawal.

However, we should not mark the withdraw function as payable, because it doesn’t need to receive Ether from an external address in order to update the contract balance. If we make it payable, then, if the caller sends an Ether value to the withdraw function by mistake (as well as calling it with a withdrawal amount) then this Ether would be added to the contract balance and potentially lost to the user: this is because the withdraw function does not make an equivalent accounting adjustment to the mapping for such a deposit (only for withdrawals). However, the function is much securer if we leave it as non-payable, because then, if the caller makes the same mistake, the function will revert, and the Ether won’t be sent.

A function only needs to be marked payable when it receives ether from an external address to add to the contract address balance. It might help to think of payable as the code that enables msg.value to be added to the contract address balance, without us having to add any further code for this. In the deposit function, this happens automatically because we have marked the function as payable. The line   balance[msg.sender] += msg.value;   then adds the same value to the individual user’s balance in the mapping, in order to keep track of their share of the total funds held in the contract.

In contrast, the withdraw function is deducting ether from the contract balance and sending it to an external address. It does this using the address member transfer (which is like a method called on a payable address)…

<address payable>.transfer(uint256 amount);

Your withdraw event is a nice thing to add. The event declaration is well coded, but from what I’ve just explained about the function type payable, you can probably now see why referencing msg.value in the corresponding emit statement will not log the withdrawal amount: msg.value will only log a value if Ether is also sent to the function and, as I’ve mentioned above, we want to prevent that from happening by leaving the function type as undefined (i.e. non-payable). Instead, the emit statement needs to reference the amount parameter. You correctly deducted amount (and not msg.value) from the individual user’s balance in the mapping, so that it continues to accurately reflect their share of the total amount of Ether held in the contract…

balance[msg.sender] -= amount;

This amount is the same value you want your withdraw event to log for the withdrawal amount, together with the withdrawer’s address (msg.sender).

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.

Can you also add your transfer() function? That way we can see your transfer event’s corresponding emit statement, and your full solution to the Events Assignment from the Events video lecture.

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

    function withdraw(uint amount) public {
        require(balance[msg.sender] >= amount, "Insufficient Balance to Withdraw.");
        balance[msg.sender] -= amount;
        msg.sender.transfer(amount);
    } 

I get a TypeError.

from solidity:
TypeError: "send" and "transfer" are only available for objects of type "address payable", not "address".
  --> helloworld.sol:27:9:
   |
27 |         msg.sender.transfer(amount);
   |         ^^^^^^^^^^^^^^^^^^^

I thought msg.sender is already a payable object?

edit: Error only comes up with the latest compiler 0.8.7. I switched to 0.7.5 and it works.
From 0.8.0, using

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

seems to work.

1 Like

Great solution @RyanYeap :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 within the function body to reduce security risk:

  • Check inputs (require statements)
  • Effects (update the contract state)
  • External Interactions (e.g. transfer method)

Just as you have done, it’s important to modify the contract state for the reduction in the individual 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 what is known as a re-entrancy attack from occuring after the transfer, but before the user’s individual balance (effectively, the share of the total contract balance they are entitled to withdraw) 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:

You are also right that it’s not necessary to return the updated balance, and so returns(uint) can be removed from the function header. Besides, we do already have the getBalance() function which can be called separately to consult the individual account holder’s updated balance after the withdrawal.

Yes … well done for resolving this yourself :+1:

msg.sender is payable by default in versions of Solidity prior to v0.8, where it doesn’t need to be explicitly converted (which is what you were expecting). However, from v0.8 msg.sender is non-payable by default, and so when it needs to refer to a payable address — such as here, when the transfer method is called on it — it needs to be explicitly converted using payable()

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

2 Likes
    function withdrawl(uint amount) public returns (uint) {
        require(balance[msg.sender] >= amount);
        balance[msg.sender] -= amount;
        msg.sender.transfer(amount);
        return amount;
    }

I found out that the
balance[msg.sender] -= amount;
was needed by messing around and to my shock being able to take out more. You can have the require all you want but if you never actually update their balance in the balance mapping :stuck_out_tongue:

I see I am not the only one to have found that out and that luckily I put it in the correct spot to stop a re-entrancy attack as mentioned by jon_m above. Makes sense and really enjoyed the extra reasoning.

1 Like
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

Great solution, testing, reasoning, research and analysis @ekr990011 :muscle: :slight_smile:

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

An excellent solution @Scott815 :muscle:

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

  • Check inputs (require statements)
  • Effects (update the contract state)
  • External Interactions (e.g. transfer method)

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

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

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, the share of the total contract balance they are entitled to withdraw) 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:

Don’t forget to post your solution to the Events Assignment here. This is the assignment from the Events video lecture, which is near the end of the Additional Solidity Concepts section of the course.

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

I used the require to make sure the balance of the msg.sender was greater than or equal to the amount he wanted to send

function withdraw(uint amount) public returns (uint) {
    require(balance[msg.sender] >= amount,'');
    msg.sender.transfer(amount);
}

Hi @OzzieAl ,

The require statement you’ve added is correct, although it would be better to add an error message to the second argument, rather than leave it as an empty string. You will see in the Remix terminal that having an empty string results in a confusing error message …

revert
        The transaction has been reverted to the initial state.
Reason provided by the contract: "".

You should include a reason, or not add a second argument to the require statement at all.


However, your withdraw function is also missing an essential line of code …

If you deploy your contract, make a deposit, and then call your withdraw function as it is, 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 — each one no greater than their initial balance, which isn’t being reduced — 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, we need to do the opposite whenever they make a withdrawal.


Also, notice that the withdraw function header 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. The compiler will have given you an orange warning about this.


Once you’ve made the above modifications to your code, have a look at this post which explains the importance of the order of the statements within your withdraw function body.

And don’t forget to post your solution to the Events Assignment here. This is the assignment from the Events video lecture, which is near the end of the Additional Solidity Concepts section of the course.

Let me know if anything is unclear, if you have any questions about any of these points, or if you need any further help correcting your solution :slight_smile:

//SPDX-License-Identifier:

pragma solidity 0.8.7;

contract myFirstContract {

mapping (address => uint) balance;
address owner;

event depositDone (uint amount, address depositedTo);
event balanceTransfered (uint amountTransfered, address transferedTo);

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 not sufficient!");

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

balance[msg.sender] -= 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, "Don't send money to yourself!");

uint previousSenderBalance = balance[msg.sender];

_transfer(msg.sender, recipient, amount);
emit balanceTransfered (amount, recipient);

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

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

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

}

}

1 Like

why is it that when you deposit Ether into a payable function, it sometimes the funds dont get subtracted from your address and added to the contract, but decides to work when you try it again a few times at random. Also, when depositing say 20 ether into a contract, sometimes less than 20 ether ends up being deducted from my address and added to the smart contract. Is this a feature within remix?

Hi @Marta_Tofan,

(1) Withdraw function

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

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.

(2) Transfer Event (from the Events Assignment)

Your transfer event and corresponding emit statement are both accurately coded, and the emit statement will log the data when the transfer() function is successfully executed.

A couple of observations…

  • An emit statement should be placed at the end of a function, after the event itself has occurred, but before a return statement if there is one. Generally speaking, it’s probably also better to place an emit statement after an assert statement if there is one.

  • Where the deposit() function executes a transaction that only involves 1 user address (the depositor), the transfer() function executes a transaction that involves 2 user addresses (the sender and the recipient). So, it would be useful to include the sender’s address, as well as the recipient’s address, within the data emitted for the transfer event.


Please, format all your code when posting it, instead of just parts of it. This will make it clearer and easier to read. It will also make it easier to copy and paste if we need to deploy and test it. You just need to make sure that all your code is placed between the two sets of 3 back ticks i.e.

```
all your code
```
Follow the instructions in this FAQ: How to post code in the forum

Let me know if you have any questions :slight_smile:

1 Like

Hi @Adrian1,

The same amount of Ether, Finney, Gwei or Wei that you enter into the Value field will always be deducted from the sender’s address balance that is showing in the Account field, near the top of the Deploy & Run Transactions panel in Remix. Make sure you set the dropdown next to the Value input box to the appropriate currency unit for the amount you want to send e.g.

20 Ether … will deduct 20.0 Ether from the sender’s address balance
2 Finney … will deduct 0.002 Ether from the sender’s address balance
25 Gwei … will deduct 0.000000025 Ether from the sender’s address balance
275 Wei … will deduct 0.000000000000000275 Ether from the sender’s address balance

If you are sending units of Wei instead of Ether, this could be why you aren’t noticing the change in the address balance, because it’s only affecting the last decimal places.

When you call the deposit() function, the amount of Ether that is sent to the contract will be added to the contract address balance. If you add the following function to your contract, you can call it to retrieve the current contract address balance (instead of the individual user balances stored in the mapping).

function getContractBalance() public view returns(uint) {
    return address(this).balance;
}

The value returned from this function, and also from the getBalance() function, will always be in units of Wei e.g.

Return value of …

20000000000000000000  =  20 Ether
   2000000000000000  =   2 Finney
        25000000000  =  25 Gwei
               275  =  275 Wei

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

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

    return balance[msg.sender];
}
1 Like
pragma solidity 0.8.7;

contract Bank {

    mapping(address => uint)balance;

    address owner;

    event depositDone (uint _amount, address _depositedTo);
    event amountWithdrawn (uint _amount, address _withdrawnFrom);

    constructor(){
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    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, "You cannot withdraw more than what you have!");
        
        balance[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);

        emit amountWithdrawn(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(msg.sender != recipient, "You cannot transfer to yourself!");
        require(balance[msg.sender] >= amount, "Balance insufficient.");

        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] = balance[from] - amount;
        balance[to] = balance[to] + amount;
    }

}
1 Like