Hey Cesc,
That photo-gif makes me smile — is it a selfie?
Before I answer your question …
Your inheritance structure is well coded, and your solution will meet all of the assignment objectives, as long as when you call your deposit() function you input the same uint
value in units of Wei (for the qty
argument, next to the red deposit function-call button) as the Ether value you enter in the Value field (just below the Gas Limit in the same Deploy & Run Transactions panel in Remix) e.g.
Value field & adjacent dropdown:
5 Ether or 5000000000 Gwei (9 x 0’s) or 5000000000000000000 Wei (18 x 0’s)
uint qty
argument:
5000000000000000000 (18 x 0’s)
That way, the increase in the user’s individual balance in the mapping (recording their share of the total contract balance) will be the same as the actual amount of Ether (in units of Wei) added to the contract balance.
But this is a very long-winded, and unnecessary, way of doing this (see my answer to your question below).
If you only call the deposit() function with the uint qty
argument (without an Ether value), then the mapping will record an amount of Ether which that user is entitled to withdraw from the contract (their share of the total contract balance), but without actually transferring any Ether from the user’s external address balance (shown in the Account field, near the top of the Deploy & Run Transactions panel) to the contract address balance. In other words, the mapping will record individual user balances (amounts each user is entitled to withdraw from the contract) but the contract itself won’t actually hold any Ether for these users to withdraw.
The first thing to understand is that we only mark a function as payable
when it needs to receive Ether from an external address to add to the contract address balance e.g. the deposit() function. In Remix, the Value field (and adjacent dropdown) makes it easy to send Ether from an external address to a deployed contract. Whatever value we enter here will be sent to the payable function we call with a red button, and automatically added to the contract address balance. The contract address is the address shown next to the name of the deployed contract below where it says Deployed Contracts at the bottom of the Deploy & Run Transactions panel. When we mark a function as payable, Remix will highlight this by giving us a red button to call that function (highlighting that the function expects to receive an Ether value which has been input in the Value field).
In our function body, in the same way that msg.sender
will always reference the address that calls the function (the address showing in the Account field when the function-call button is clicked), msg.value
will always reference the Ether value (in the Value field and adjacent dropdown) sent to the function from the caller’s (msg.sender
's) external address balance.
In the same way that argument values input into a function can be referenced within the function body using the names of their parameters (defined between the parentheses after the function name in the function header), both the calling address and any Ether value sent to a payable function can also be referenced, but without the need to explicity define them as parameters. They are like in-built parameters (pre-defined in Solidity’s syntax) which we can reference using msg.sender
and msg.value
.
It might help to think of payable
in a function header 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 this same Ether value (always in units of Wei) 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. By using msg.value
, instead of a separate uint qty
parameter, we ensure that whatever units the Ether value has been expressed in (Ether, Gwei etc.) in the Value field, the same uint
equivalent in Wei will always be added to the individual user’s balance in the mapping.
So that’s why the deposit() function doesn’t need any explicit parameters, and doesn’t need to be called with any arguments, because the only values it needs to reference in the code in its function body are the caller’s address (msg.sender
) and the Ether value sent to it (msg.value
).
Calling getBalance() will retrieve the caller’s individual share (their entitlement) of the total amount of Ether held in the contract. If you also add the following function to your contract, you can call it to retrieve the current total contract address balance (total amount of Ether held in the contract).
function getContractBalance() public view returns(uint) {
return address(this).balance;
}
Any address can call this function and it will always return the total contract balance (not the caller’s individual share of that total balance). In our Bank contract, before we add the external value call to the Government contract (in the last course video before the final Multisig project), the amount returned by getContractBalance() should always be equal to the sum of all the values returned by getBalance() for each individual user with funds in the contract.
We don’t (and shouldn’t) mark the withdraw() function as payable
, because it doesn’t need to receive any Ether from an external address and add it to the contract balance. Effectively, the withdraw() function does the opposite of what the deposit() function does: it deducts Ether from the contract balance and transfers it to the caller’s external address balance. It does this using the in-built address member transfer
(which is like a method called on a payable address)…
<address payable>.transfer(uint amount);
Notice, however, that, unlike the deposit() function, the withdraw() function does need to include an explicit uint
parameter for the withdrawal amount, because, unlike with payable functions, there is no in-built msg.value
equivalent available for us to use. In any case, the set syntax of the transfer
method requires it to be called with a uint
value. This uint
value needs to come from somewhere, and in the withdraw() function it comes from the requested withdrawal amount input by the caller.
The separate transfer() function (not the special transfer
method in the withdraw function) shouldn’t be marked payable
either. In fact, calling the transfer() function doesn’t involve any Ether entering or leaving the Bank contract at all. Instead, it performs an internal transfer of funds (effectively, a reallocation of funds) between two individual users of the Bank contract. The net effect to the contract balance is zero, and the only change is to the individual user balances in the mapping, in order to adjust the amount each of the parties involved in the internal transfer is entitled to withdraw from the contract (their share of the total contract balance) i.e. the recipient’s entitlement (balance) is increased by the same amount the sender’s entitlement is reduced.
I hope that’s helped to fill in some missing links in your understanding. You may also find this post helpful. It lays out step-by-step instructions of how to properly check that all of your functions are working correctly, and it details what you should see happening, and where, in Remix, if it is working correctly.
There’s probably a lot of information here to absorb, so take your time to think it all through and do some of your own testing, and when you’re ready, just let me know if anything is still unclear, or if you have any further questions