Hi @Xabier22,
Your implementation of a multi-level inheritance structure is the best approach. Bank only needs to explicitly inherit Destroyable, because it will implicitly inherit Ownable via Destroyable
Your inheritance structure works if all 3 contracts are in the same file. However, better practice is to place each contract in its own separate file, so that Ownable and Destroyable can be re-used as independent modules for easy implementation of the contract-ownership and contract-destruction functionalities. That would also mean adding import statements.
Increasing code re-usability is an important reason for using inheritance.
You’ve made a great attempt at introducing some of your own functionality into your Bank contract, in order to enable users to allow other addresses to access a specific amount of their funds/tokens and make transfers up to this amount, or “allowance”, to addresses of their choosing (either to their own, or to other user addresses). Your implementation of a double mapping to manage these “allowances” is particularly impressive
Your code works, but when an address with an “allowance” calls the transferFrom() function:
- if the balance of the sender address is already lower than the “allowance” of the calling address, for example if the sender has already made some transfers after setting an “allowance” for the calling address; and
- if the calling address tries to transfer an amount lower or equal to their “allowance”, but greater than the balance of the sender address; then …
… we shouldn’t rely on the following line of code in the private function …
… to trigger revert, because of the resulting underflow in the sender’s balance in the mapping. An underflow would occur here because an unsigned integer cannot be a negative value, and so when the value reaches zero the subtraction continues from the highest integer available for the number of bits the uint
value is defined with (in your case 256). Underflows and overflows (an overflow is when the opposite of an underflow occurs) only automatically trigger revert in Solidity from v0.8. To see the effect this would have caused using an earlier version of Solidity, change your pragma statement to …
pragma solidity 0.7.5;
… and see what the sender’s balance is after performing the transfer I’ve outlined above!
Instead, best practice is to ensure that such an input amount also causes a require statement to fail and revert the transaction at the earliest opportunity. For example, you could do this by adding an additional require statement at the beginning of your transferFrom() function …
require(balances[_sender] >= _amount, "Insufficient balance");
require(
allowances[_sender][msg.sender] >= _amount,
"You are not allowed to spend that amount"
);
… or by combining both require statements as follows …
require(
balances[_sender] >= _amount &&
allowances[_sender][msg.sender] >= _amount,
"Invalid amount"
);
It’s also important to highlight that, as well as the contract owner being able to destroy the contract, another assignment objective is to ensure that, when the contract is destroyed, all remaining funds in the contract are transferred out of the contract to the contract owner. Your solution enables the contract owner, and only the contract owner, to destroy the contract, but all funds/tokens held in the contract are then lost and cannot be retrieved.
The reason why selfdestruct
takes a payable address argument is because, as well as destroying the contract, selfdestruct
also automatically transfers the remaining contract Ether balance to the address argument it is called with (in our case, the contract owner’s address). This is all part of the pre-defined selfdestruct
functionality. However, in order to demonstrate this, you need to have a Bank contract which allows deposits and transactions of Ether, rather than just some kind of token i.e. something like the one we’ve been developing during the second half of this course with a payable deposit() function, withdraw() function etc. You can still add your additional functionality to this kind of contract as well.
(See the footnote about the Events and Transfer assignments)
In fact, the addBalance() function that you currently have in your contract doesn’t actually make any practical sense, because any address can just create as many tokens as it wants out of thin air and add them to its individual address balance in the mapping. In reality, such tokens would be minted according to a set protocol, hard-coded into the smart contract. Having users deposit Ether in the contract (by way of a transfer from their external addresses to the smart contract address balance) means that our contract is managing Ether which already pre-exists within the Ethereum ecosystem (or at least that is what Remix’s JavaScript Virtual Machine is simulating).
The addBalance() function was only used earlier on in the course as a way to introduce and demonstrate some of the fundamental concepts and syntax of Solidity, and serves more of a convenient building block rather than something that would be used in practice. You will learn how to code and implement smart contracts in accordance with the accepted token standards in the 201 course which follows this one.
A couple of other comments about your Bank contract …
-
Users can retrieve individual balances, but they also need to be able to retrieve their “allowances”, otherwise they won’t know how much of another user’s balance they are entitled to transfer.
-
You are missing an additional require statement in the transfer() and transferFrom() functions to prevent transferring funds/tokens to the same address that is sending them. Such a transaction would waste gas for no reason.
Let me know if you have any questions about any of these points
Don’t forget to also post your solutions to these earlier assignments …
Events Assignment https://studygroup.moralis.io/t/events-assignment/28040?u=jon_m
This is the assignment from the Events video lecture, which is near the end of the Additional Solidity Concepts section of the course.
Transfer Assignment https://studygroup.moralis.io/t/transfer-assignment/27368?u=jon_m
This is the assignment from the Payable Functions section of the course, where you have the task of completing the withdraw() function.