Transfer Assignment

This is a perfect solution @Gos :muscle:

Are you maybe only inputting a value of, say 5 for the amount argument, instead of 5000000000000000000 ? Solidity handles values in units of wei, not ether. In Remix, when sending a value to a contract, we have the dropdown to allow us to input the value in units of ether, and it will be converted to wei for us. Unfortunately, we don’t have that option when calling a function with a uint value we want to be processed as an amount in ether — we have to input our ether amount in uints of wei, with all the zeros!

If that is the issue, then the balance will be being updated, but only for a very small amount, and so you may not have noticed it if you are looking for a change in units of ether.

If that isn’t the issue, then post your full contract code, and I’ll take a look.


You have added all of the additional lines of code needed to solve the problem with the withdraw function, and they 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 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 the courses which follow this one. But it’s great you’re already getting into good habits in terms of smart contract security :+1:

1 Like

Hi @Ruzz,

Apologies for the delay in answering your question


The order of the statements does matter, yes, although we wouldn’t expect you to know about this yet. But seeing as you’ve asked


In order to reduce security risk, we should apply the following order to the statements we place within the function body:

  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 the courses which follow this one. But it’s great you’re already noticing these kinds of details and wanting to find out why they are as they are :+1:

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

1 Like

Hi @cgironda,

Just be careful not to mix up your assignment (=) and equality (==) operators


This makes a huge difference, because your code (although it does still compile) does not reduce the user’s balance in the mapping by the withdrawal amount. I’m sure you can see why this is a very serious bug: even though the require statement places a limit on each separate withdrawal, this limit is never reduced to reflect each withdrawal, meaning that the user can repeatedly make withdrawals of amounts up to their initial balance, until the contract balance (which includes all user account balances) is reduced to zero!

Apart from this, your solution is excellent.

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

Hi @Cero,

Just be careful with your assignment operators


In this line of code you need


  • the subtraction assignment operator, which has the minus sign before the equals sign ( -= ). This will deduct the value on the right (the withdrawal amount) from the user’s current balance stored in the mapping.

and not


  • The “standard” assignment operator (=) followed by the unary minus operator ( - ). This negates the value on the right of the assignment operator (the withdrawal amount) and then assigns the resulting value as the user’s balance in the mapping (replacing their current balance). As the mapping is defined as unsigned integers mapped to addresses (address => uint) , it cannot store negative balances. So, if you input a positive withdrawal amount (as expected), the unary minus operator will convert this to a negative amount, and when this is assigned to the mapping it will be expressed as an extremely large positive balance — the largest integer which can be stored with 256 bits (a binary number with 256 1’s) less the withdrawal amount. This happens as a result of something called underflow.

I’m sure you can see why this is a very serious bug, because the limit on withdrawals is so high, that it effectively allows the user to withdraw all of the funds held in the contract (which includes all user account balances).

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

Hi @benj,

You’ve done a great job at explaining these concepts to @Flippy :+1:

Your terminology is actually very good. The term used in the Solidity documentation for attributes or properties is members, but it is clear what you mean by attributes, and that’s the main thing.

Just a couple of observations 


This is correct if our mapping is defined with an address data type as the key


mapping(address => User) users;


 and not with an unsigned integer data type 


From your explanation, I think this is actually how you meant to define your mapping, but I just wanted to clarify this.

With the mapping defined as User instances (created from the struct) mapped to addresses (and not unsigned integers), users[msg.sender] will reference a User instance, and not an address, and so instead of



 the local activeUser variable should be defined with the custom data type User (and not as an address)


User activeUser = users[msg.sender];
uint activeUserBalance = activeUser.balance;

I think it’s important to make clear that, when we are referencing a specific “entry” in a mapping, the value within the square brackets is the key the entry is mapped to, and the data type of the key (the data type declared before the arrow in the mapping definition) is usually not the same as the data type of the value that is mapped to it (the data type declared after the arrow in the mapping definition).


Your explanation breaking down   msg.sender.transfer(amount);   is really quite excellent :muscle:

Strictly speaking, msg is not a data type, but instead we can consider msg.sender as representing one value with an address data type (the address calling the function). Address data types in Solidity have many available members, some of which are what you have described as methods — which I think is a really good term to use to describe how these particular members operate.

Here is a link to the full list of Members of Address Types in the documentation:

https://docs.soliditylang.org/en/latest/units-and-global-variables.html#members-of-address-types

Just let me know if either of you have any further questions :slight_smile:

Hi @FerfiC,

Apologies for the delay in giving you feedback on your solution to this assignment, and in answering your question.

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

In order to receive ether, an address needs to be payable . From Solidity v0.8 msg.sender is non-payable by default, and so when it needs to refer to a payable address, it needs to be explicitly converted. One of the ways to do this is using payable() . If you are using v0.8, then this is why the compiler was generating an error when you had


msg.sender.transfer(amount);
// instead of
payable(msg.sender).transfer(amount);

You need to bear in mind that this course material is based on v0.7 syntax. In versions prior to v0.8 msg.sender is payable by default and so doesn’t need to be explicitly converted. That’s why the course video uses  msg.sender.transfer(amount);  without the compiler throwing an error.


You’ve modified the withdraw function with the onlyOwner modifier, but this means that only the contract owner can withdraw funds up to their own individual balance. But the contract allows multiple addresses to deposit funds (we are simulating a bank with bank account holders) and so it only seems fair that all users should be able to withdraw their funds as well, don’t you think? :sweat_smile:

Also, have a look at this post which explains the importance of the order of the statements within your withdraw function body.

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

Hi @TuomoP,

Apologies for the delay in giving you feedback on your solution to this assignment.

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

However, you’ve modified the withdraw function with the onlyOwner modifier, but this means that only the contract owner can withdraw funds up to their own individual balance. But the contract allows multiple addresses to deposit funds (we are simulating a bank with bank account holders) and so it only seems fair that all users should be able to withdraw their funds as well, don’t you think? :sweat_smile:

I’m sure this was just a slip, but if you don’t include solidity , and the semi colon at the end of the statement, the compiler will throw an error:

pragma solidity 0.8.4;

Also, have a look at this post which explains the importance of the order of the statements within your withdraw function body.

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

Well, it’s definitely right now @benj :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.

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

1 Like

An excellent assignment solutuion @Nathan_Burkiewicz and, overall, a very well-coded contract :muscle:

You have added all of the additional lines of code needed to solve the problem with the withdraw function, and they 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 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 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’re making great progress!

1 Like

Nice solution @Julian97 and, overall, a very well-coded contract :ok_hand:

You have added all of the necessary code needed to solve the problem with the withdraw function. Your emit statement for the transfer event is also now correct.

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 me know if you have any questions :slight_smile:

You’re making great progress!

1 Like

Thanks for recognizing this bug. It was made by mistake. You clearly explained the problem.

1 Like

Hi Jon,

Thanks for coming back on this, very helpful.

From your explanation, I think this is actually how you meant to define your mapping, but I just wanted to clarify this.

Yeah, I did - forgot to swap it out having taken some code from the data location assignment.

And this bit:

User activeUser = users[msg.sender];
uint activeUserBalance = activeUser.balance;


makes total sense. This is what I get for not testing my code before posting!

And agreed on highlighting key vs value. Appreciate it just a particular case in this example that key is the address and the value being the custom User data type also happens to contain the address. Appreciate that won’t always be the case and makes sense to call it out.

Your explanation breaking down msg.sender.transfer(amount); is really quite excellent :muscle:

Thanks, glad you think so!

Strictly speaking, msg is not a data type,

I had a sense that this must be the case, think I was trying to oversimplify and find a way to relate the concepts somehow though I have a question on this - is the description of msg as an object accurate and how does that differ from a custom data type like User?

I appreciate that msg is the message being sent and I was presuming it must be a standardised object. I’m thinking in terms of my understanding of Python classes here:

class message:
  def __init__(self, address, data, value, sig):
    self.sender = address
    self.data = data
    self.value = value
    self.sig = sig

  def printSender(self):
    print("The sender's address is " + self.sender)

msg = message(0x123456..., 78, 90, "xyz")
msg.sender # call attribute
msg.printSender() # perform function
>> 

In Python, the User data type would be defined in a very similar way which is why I referred to msg as a data type.

Another question on this - in Solidity, is there no word other than ‘member’ to differentiate between what I consider a static attribute (ie an unchanging piece of data extracted from an instance such as msg.sender) and an active method that applies a function (such as msg.sender.transfer(amount) )?

1 Like

Great, thanks for that and for the post link. Where can I find more details on the attack that code in this order is vulnerable to?

1 Like

Hey Ben,

The attack I’m referring to is called a re-entrancy attack. Check out this article:

https://medium.com/coinmonks/protect-your-solidity-smart-contracts-from-reentrancy-attacks-9972c3af7c21

If you are interested in these sorts of security topics then I highly recommend you take the Smart Contract Security course, either after this one or after Ethereum Smart Contract Programming 201. It’s a really good course as it covers real case studies of famous attacks that have happened, looks at the code that caused the vulnerabilities, and then how these attacks can be prevented (or the risk posed by them seriously mitigated) by adopting specific best practices in how we code certain aspects of our smart contracts.


PS I’m saving my reply to your other post for after a strong cup of coffee some time tomorrow, so I can give it the attention it deserves :wink:

1 Like

Hey all. Banged my head against this one for quite some time but eventually figured it out. I think.

My code and notes:

pragma solidity >= 0.8.4;

contract metacontract{

constructor(){
    owner = msg.sender;
}

mapping(address => uint) balance; //key value pair, address returns balance.
mapping(address => uint) ethBalance; // second KvP for tracking Eth deposits.

address owner;//if i comment this out it breaks. why?

modifier reqAdmin{ //modifier req admin priv to execute function.
    require(msg.sender == owner);
    _; //run the function.
}
 //event listeners.
event eventTransfer(uint amount, address indexed depositedTo, address indexed sentFrom); //declares event to track with emit
event eventDeposit(uint amount, address indexed sentFrom);
event eventEthDeposit(uint amount, address indexed sentFrom);

function adminSetBalance(address target, uint value) public reqAdmin { //admin tool: set balance.
            balance[target]=value;
}

function getBalance(address target) public view returns(uint) { // return address balance.
    return balance[target];
}
//----------------------------------------------------
// Eth related functions
function getEthBalance() public view returns(uint){
    return ethBalance[msg.sender];
}

function depositEth() public payable returns(uint){ //deposit ETH to contract.
        ethBalance[msg.sender]+= msg.value;//msg.value replaces need to include function argument.
        emit eventEthDeposit(msg.value,msg.sender);
        return balance[msg.sender];
        //assert
}


function withdrawlEth(uint amount) public payable returns(uint){
    require(ethBalance[msg.sender] >= amount);//amount is used instead of msg.value 
    ethBalance[msg.sender]= ethBalance[msg.sender]-amount;//this line adjusts acc balance before transfer &protect against rentrancy;
    (bool success, ) = msg.sender.call{value: amount} ('');
    require(success, "transfer failed.");
    return ethBalance[msg.sender];
}

}   

I was having trouble with the transfer function and in my googling i stumbled across a few articles Id like to share and get some feedback on.
https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now
which also led me to:
https://quantstamp.com/blog/what-is-a-re-entrancy-attack

both are good reads. Does anyone have any further insight to this? Is it true that .send and .transfer are depreciated?

PS: just realized i never implemented a withdrawl event listener/emitter. gunna go do that now.

Thanks all,

Ben

1 Like

Thanks for that detailed reply Jon!

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

Hi @ImmMeta,

You have done some excellent research to arrive at a robust solution for the withdraw function, which not only prevents users from withdrawing more than their individual account balances, but also:

  • uses the address member call() to perform the transfer of ether from the contract address balance to the external address, in order to take into consideration the latest advice not to use the address members transfer() or send() after the Istanbul hard fork;
    and
  • guards against the risk of re-entrancy attacks using the Checks-Effects-Interactions Pattern.

As you can see from the documentation for the latest version of Solidity (link below), the address members transfer() and send() are not deprecated and still available to use.

https://docs.soliditylang.org/en/latest/units-and-global-variables.html#members-of-address-types

However, it is true that the latest advice is not to use them. This is because the fixed stipend of 2300 gas, which they forward to a calling contract’s fallback function, used to be enough gas for the fallback function to execute successfully, but not enough gas to allow a re-entrant call (thereby reducing the risk of re-entrancy attacks). But since the Istanbul hard fork changed the gas charges for certain operations, this fixed gas stipend is no longer guaranteed to be sufficient.

Your implementation of  call()  is correct. And the order you have placed each statement within the withdraw() function body is correct to guard against re-entrancy attacks, which would otherwise be more of risk as a result of using call() instead of transfer() 


  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 individual user’s balance


balance[msg.sender] -= amount;

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


(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");


 to prevent a malicious fallback function making successful re-entrant calls to the withdraw() function, and executing multiple withdrawals before the individual account balance is modified in the contract state to accurately reflect them, which would otherwise enable the initial require statement to fail and trigger revert.

You’ll learn more about these kinds of smart-contract security issues in the courses which follow this one. But it’s great you’re already doing your own research on the subject, and with some impressive results to show for it :muscle:



 because this state variable owner is referenced in two places:

  1. In the constructor — to set the contract owner’s address on deployment
  2. In the onlyOwner modifier’s require() statement — to check whether the function caller is the contract owner

If you remove the owner state variable, the compiler will throw errors in both of these places where it is referenced. To be able to remove it, you would have to also remove the constructor and the modifier, which, as far as the deposit(), withdraw() and transfer() functions are concerned, you could do.

Is this, perhaps, because you hadn’t converted msg.sender to a payable address, as follows?

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

In order to receive ether, an address needs to be payable . You need to bear in mind that this course material is based on Solidity v0.7 syntax. In versions prior to v0.8 msg.sender is payable by default and so doesn’t need to be explicitly converted when transfer() is called on it. That’s why the course video uses   msg.sender.transfer(amount);   without the compiler throwing an error. However, you have chosen to use Solidity v0.8, and from v0.8 msg.sender is non-payable by default, and so when it needs to refer to a payable address, it needs to be explicitly converted. One of the ways to do this is with payable()


What follows is a list of issues which need addressing:

(1) I’m not really sure why you’ve added the extra mapping ethBalance. When users deposit ether into, or withdraw ether from, the contract, the ether itself is added to, or deducted from, the contract address balance. This contract balance represents the total ether belonging all account holders. The individual account balances in the mapping perform an internal accounting role, by keeping track of each individual account holder’s share of the total pooled funds. As a result of this, we can continue to use the same balance mapping for this purpose, and we can also leave the function names as deposit() withdraw() and transfer().

(2) I also don’t understand the purpose of your adminSetBalance() function or your modified getBalance() function. The only balances we need to manipulate within the contract are the individual account balances. Could you explain your thinking here?

(3) You have two deposit event declarations, but you’re only emitting one.

(4)

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

Hey Jon,

I had some leftover code from previous exercices in here so the contract as is handles both an ethBalance tracker and a Balance tracker which isnt used in this instance of value transfering. going forward ill try to keep the code cleaner/more organized.

Thanks for the insight. :slight_smile:

1 Like
function deposit() public payable returns (uint) {
    balances[msg.sender] += msg.value;
    
    emit depositedAmount(msg.sender, msg.value);
    
    return balances[msg.sender];
}

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