Hey @seybastiens,
Your solution meets all of the assignment objectives, and your inheritance structure works
In fact, Bank only needs to explicitly inherit Destroyable, because it will inherit Ownable implicitly via Destroyable. This will give you a multi-level inheritance structure.
This line of code in your SelfDestructContract() function achieves the desired result, and from a technical point of view itâs well coded. However, as well as destroying the contract, selfdestruct
also automatically transfers the remaining contract balanceâ (address(this).balance
) âto the payable address argument it is called with (in our case, the contract ownerâs address). This is all part of the pre-defined selfdestruct
functionality, and is why there is no need to include your extra line of code.
Itâs transferred out of the contract to the contract ownerâs external address balance (in the dropdown list of addresses in the Account field near the top of the Deploy & Run Transactions panel in Remix). Only this address can call SelfDestructContract() and trigger selfdestruct
, so this should already be the address showing in the Account field when this transfer is made, anyway. We obviously canât assign these funds to the contract ownerâs individual user balance in the mapping, because that only records each individual bank account holderâs share of the total contract balance and, after the contract has been destroyed, neither the contractâs funds nor the mapping (which tracks and records the allocation of these funds) will exist anymore.
If you perform your prior transactions (before calling selfdestruct)
using whole amounts of Ether, rather than small amounts of Wei, it will be easier to see the movement of funds e.g.
If address A is the contract owner (contract deployer), and addresses B and C are two other usersâŚ
- Add the following function to Bank, if you donât already have it âŚ
function getContractBalance() external view returns(uint) {
return address(this).balance;
}
Only one contract has been deployed at a single Ethereum address: Bank.
When Bank is compiled, the result is a single set of bytecode which incorporates the inherited functionality from the parent contract(s).
So, whetherâaddress(this)
âis in Bank, Destroyable or Ownable, if we deploy Bank, it will always reference whichever address Bankâs compiled bytecode (which address(this)
will have been included in) is deployed at.
-
Address A deploys contract (make sure Bank is selected from the 3 contracts available in the Contract fieldâs dropdown, and is showing in the Contract field, which is just above the orange Deploy button):
Aâs external Ether balance is still approx. 100 (slightly less due to gas cost: 99.99999999)
-
Address B deposits 8 Ether:
Bâs external Ether balance is now approx. 92 (100 - 8)
-
B calls getBalance:
Bâs individual share of contract balance is 8 Ether (displayed in Wei, 8 + 18 zeros)
-
Any address calls getContractBalance:
Total contract balance is also 8 Ether (displayed in Wei)
-
B transfers 4000000000000000000 Wei (4 Ether) to C, and then transfers another 100000000000000000 Wei (1 Ether) to Aâââthese are both internal bank transfers and do not affect these usersâ external address balances.
-
All 3 address separately call getBalance:
Aâs individual share of contract balance is 1 Ether
Bâs individual share of contract balance is now 3 Ether (8 - 4 - 1)
Câs individual share of contract balance is 4 Ether
-
Any address calls getContractBalance:
Total contract balance is still 8 Ether (1 + 3 + 4)
-
C withdraws 2000000000000000000 Wei (2 Ether)
Câs external Ether balance is now approx. 102 (100 + 2)
-
C calls getBalance:âCâs individual share of contract balance is now 2 Ether (4 - 2)
Any address calls getContractBalance:âtotal contract balance is now 6 Ether (1 + 3 + 2)
-
The contract owner (A) calls SelfDestructContract:
Aâs external Ether balance is now approx. 106 (100 + 6)
Remaining contract balance (6 Ether) has been successfuly transferred out of the smart contract (just before itâs destroyed) to contract ownerâs external address.
-
A, B or C calls getBalance() and getContractBalance() functions:
Both return zero, because the contract has been destroyed.
We want the selfdestruct
methodâs payable-address argument to reference the contract ownerâs address, because this is the address the Ether remaining in the contract will be sent to, when the contract is destroyed. To achieve this, we can either âŚ
-
reference the owner
state variable, which is inherited from Ownable (see B below); or
-
useâmsg.sender
â(see A below)âââif we add the onlyOwner modifier to the header of the function containing selfdestruct
, this restricts access to this function to the contract ownerâs address, which in turn means that the only address msg.sender
can reference in this function is the contract ownerâs address.
So, you are right when you say âŚ
As the selfdestruct
method requires a payable address argument âŚ
(A)âmsg.sender
If we use msg.sender
then how we code this depends on whether we are using Solidity v0.7 or Solidity v0.8 âŚ
(A.1)âPrior to Solidity v0.8, msg.sender
is already a payable address by default, and so doesnât need to be explicitly converted whenever the syntax requires it to reference a payable addressâŚ
selfdestruct(msg.sender);
(A.2)âFrom Solidity v0.8, msg.sender
is non-payable by default, which means having to explicity convert it to a payable address when necessaryâŚ
selfdestruct(payable(msg.sender));
(B)âowner
If we do what youâve done, and reference the owner
state variable as the argument, this needs to be explicity converted to a payable address type, because originally it was defined as non-payable in Ownable âŚ
address owner // internal visibility by default
//or
address public owner // public visibility (what you've used in your code)
We can do this in 2 different waysâŚ
(B.1) If we only need the contract ownerâs address to be payable for the purposes of executing selfdestruct
, we can do what youâve done, and leave the owner
state variable in Ownable as a non-payable address type, and explicitly convert the address to payable locally, within the function where we actually need to use it as payable. As youâve done, we can perform this explicit conversion directly within the selfdestruct() function call itself, as follows:
This is the syntax you need whether you use Solidity v0.7 or v0.8
(B.2) Or we can define the owner
address state variable as a payable address type by adding the payable
keyword âŚ
contract Ownable
address payable owner; // internal visibility by default
//or
address payable public owner // public visibility
contract Destroyable
function SelfDestructContract () public onlyOwner {
selfdestruct(owner);
}
Again, this is the syntax you need whether you use Solidity v.0.7 or v0.8. The difference with v0.8 is that you need to make an additional modification in your Ownable contract. In the constructor msg.sender
is the address that deploys the contract (the contract owner) and it needs to be explicity converted to a payable address using payable()
so that the constructor can assign it to the owner
state variable, where this is defined as a payable address typeâŚ
constructor() {
owner = payable(msg.sender);
}
But with v0.7, msg.sender
is already a payable address by default, and so it can be assigned to a payable address variable (e.g. owner
) without having to explicitly convert it to a payable address âŚ
constructor() {
owner = msg.sender;
}
Yes, but for the reasons Iâve just explained above regarding the constructor in Ownable, because you are using Solidity v0.8, if you wanted to implement the code from the model solution, youâd need to explicity convert msg.sender
to a payable address âŚ
address payable receiver = payable(msg.sender);
selfdestruct(receiver);
However, using âŚ
⌠is a more concise alternative to using the additional receiver
variable.
Have a look at this post for further details about the use of this additional local variable (receiver
) in the model solution for this assignment.
I hope Iâve managed to answer your questions, but let me know if anything is still unclear, or if you have any further questions after reviewing this feedback
Youâre doing a great job analysing and really thinking about what the code is doing, and this will really help you continue to make good, solid progress, and to make the most of the more advanced Solidity courses which follow this one. Keep up the great work!