Inheritance & External Contracts - Discussion

In the quiz about inheritance there is this question:

What are some benefits of using inheritance as part of your code?

Your Answer

check_box Reduces code duplication

Correct Answer

check_box Reduces code duplication

check_box Creates a clear code structure

I disagree that inheritance creates a clear code structure. In a lot of cases (not all) it makes it harder to read and easier to miss things because you have to dig into the parent contracts. I come from C# for

1 Like

Thanks for the explanation @gabba !

1 Like

Hey Iris,
I had the same problem and solved the issue by creating a new account/wallet by clicking on the ‘plus icon’ next to the “account” Title.

1 Like

Hi @chadrickm,

Sorry for the delay in responding to you on this.

There are always many aspects in any field of work that are subjective and experienced differently by different individuals — after all, we all work, organise our thoughts, and structure processes in different ways. So, I can appreciate what you’re saying here. I guess the whole question comes down to whether you as an individual find it easier to process a large amount of detailed information in less separate and compartmentalised locations, or prefer more “bite-sized” packages of information (i.e. less detail in just one place) but with a higher number of different (yet interconnected) locations where you need to go to access that information. Do you prefer a bag with lots of different pockets, compartments, zips and fastenings, to carry around your personal items; or do you prefer one with just one large internal space to hold everything all together (without having to deal with all the zips and fastenings)?

If, as individual programmers, we were the only people who ever had to read, modify, debug and continue developing the code that we ourselves have written, then I think you could argue that each person could structure their code in either of the two ways I’ve described above, depending on our own personal view of what constitutes clarity. However, this isn’t the case, and we always need to structure our code in a way that is more developer-friendly in terms of the general consensus across the whole community of developers using a particular programming language.

In terms of Solidity (and indeed other programming languages as well) it is more widely considered that, by using inheritance, we can create smart contracts that are structured, compartmentalised and interconnected in a way that makes them easier to read, build, adapt, manage and debug by the wide majority of developers, and in the wide majority of use cases.

Obviously, each particular project is different, and so either approach could be considered as providing more clarity on an individual case-by-case basis. However, in terms of this quiz question, the answer has been based on what is more generally considered best practice.

2 Likes

Filip,

I noticed that the modifier in the parent contract was not declared to be public or internal. Does that mean that all modifiers are always inherited by the child contacts as long as they are not marked as ‘private’?

1 Like

Hi @FrankB,

Apologies for the late reponse, and for leaving you hanging on this one!

YES

Modifiers are always visible/available to derived contracts, and in fact their syntax does not allow visibility to be set anyway i.e. they cannot be marked private, public, internal or external.

Thank you, Jonathan, for the clarification. I appreciate it.

Frank

1 Like

For the below code after deploying and copying the address in ExternalContract.sol file, I get below error. Also attached the image for your reference.
browser/External.sol 5:1 ParseError: Function, variable, struct, or modifier declaration expected.!

pragma solidity 0.5.12;
contract structureWorld{
function createPerson(string memory name, uint age, uint height) public payable {
}
contract ExternalContract{
structureWorld instance = structureWorld(0x315a5Ee8AD86C88BD83611D50dB62D96948A9198);

function externalCreatePerson(string memory name, uint age, uint height) public payable{
instance.createPerson.value(msg.value)(name,age,height);
    
}

}

1 Like

Hi @Nityam_Jigyasu,

Sorry for the delay in replying.

It looks like it’s as simple as the extra opening curly brace you have at the end of your interface with the createPerson function. It should be a semi colon instead, as you are not adding a function body:

function createPerson(string memory name, uint age, uint height) public payable {

// change to

function createPerson(string memory name, uint age, uint height) public payable;

I think the error you are getting is because the compiler is expecting another closing curly brace (to close the whole interface body) due to the additional opening curly brace you added in.

Let us know if this resolved your issue, or whether you have any further questions.

1 Like

It worked, Thanks a lot, Jon.

1 Like

hi @jon_m,

I made “kill” function in my Destroyable contract “internal” to Ownable contract (its parent contract), which is a parent contract of “hello world”. So, because Destoryable is a child of Ownable, and Ownable is a child of “Hello World”, why doesn’t the “internal” kill function show up when I deploy Hello World??? Wouldn’t an internal function of a parent contract show up in it’s child’s child contract?

// Destroyable Contract

import “./Ownable.sol”;
pragma solidity 0.5.12;

contract Destroyable is Ownable{

function kill() internal onlyOwner{
    selfdestruct(address(uint160(owner)));
}

}

// Ownable Contract

pragma solidity 0.5.12;

contract Ownable{
address internal owner;

modifier onlyOwner(){
    require(msg.sender == owner);
    _; // underscore just means continue the execution
}

constructor() public{
    owner = msg.sender;
}

}

//HelloWorld Contract

import “./Ownable.sol”;
import “./Destroyable.sol”;
pragma solidity 0.5.12;

// Inheritance in Solidity

contract HelloWorld is Ownable, Destroyable{ //Ownable is the Parent
struct Person {
string name;
uint age;
uint height;
bool senior;
uint balance;

} // Struct is like an object that has data structure 

event personCreated(string name, bool senior);
event personDeleted(string name, bool senior, address deletedBy);


uint public totalBalance;



modifier costs(uint cost){
    require(msg.value >= cost);
    _;
}


mapping(address => Person) private people; // Need review on Mapping
address [] creators;


function createPerson(string memory name, uint age, uint height) public payable costs(1 ether){
    
    require(age <= 150, "Age needs to be below 150");
    // require(msg.value >= 1);
    totalBalance += msg.value;
    
    
    // people.push(Person(people.length, name, age, height));
    Person memory newPerson;
    
    newPerson.name = name;
    newPerson.age = age;
    newPerson.height = height;
    newPerson.balance = msg.value;
    
    if(age >= 65) {
        newPerson.senior = true;
    }
    else {
        newPerson.senior = false;
    }
    
    insertPerson(newPerson); 
    creators.push(msg.sender);
    assert(
        keccak256(
            abi.encodePacked(
                people[msg.sender].name, 
                people[msg.sender].age, 
                people[msg.sender].height, 
                people[msg.sender].senior
                )
            ) == 
        keccak256(
            abi.encodePacked(
                newPerson.name, 
                newPerson.age, 
                newPerson.height, 
                newPerson.senior
            )
        )
    );
    emit personCreated(newPerson.name, newPerson.senior);
}

function getPerson() public view returns(string memory name, uint age, uint height, bool senior, uint balance) {
    address creator = msg.sender;
    return (people[creator].name, people[creator].age, people[creator].height, people[creator].senior, people[creator].balance);
}

function insertPerson(Person memory newPerson) private {
    address creator = msg.sender; // "msg.sender" will give the address of the sender in solidity, this is highly used
   // pay attention to payable address vs. normal address
   
   //convert from normal to payable address
    //  address payable test = address(uint160(creator)); // wrap with uint160() and then address()
   
   
    people[creator] = newPerson; 
    
}

function deletePerson(address creator) public onlyOwner {
    // require(msg.sender == owner); // removed for Modifier
    string memory name = people[creator].name;
    bool senior = people[creator].senior;
    
    delete people[creator];
    assert(people[creator].age == 0);
    emit personDeleted(name, senior, msg.sender);
}

function getCreator (uint index) public view onlyOwner returns(address) {
    // require(msg.sender == owner, "caller needs to be owner");
    return creators[index];
}

function withdrawAll () public onlyOwner returns(uint) {
    //VERY IMPORTANT, WE ALWAYS NEED TO MAKE CHANGES TO THE STATE 'BEFORE' TRANSFER
    // TRANSFER IS A BETTER USED FUNCTION BECAUSE IT WILL AUTOMATICALLY REVERT IF FAILED, USE "SEND" FUNCTION ONLY IF YOU WANT CUSTOM ERROR MESSSAGE
    
    // uint toTransfer = totalBalance;
    
    // totalBalance = 0;
    // msg.sender.transfer(toTransfer); // Automatic Revert
    // return toTransfer; //WHY DO YOU NEED TO RETURN?
    
    uint toTransfer = totalBalance;
    
    totalBalance = 0;
    
    if(msg.sender.send(toTransfer)){
        return toTransfer;
    }
    else {
        totalBalance = toTransfer;
        return 0;
    } //return false instead of reverse
}

}

Hi Li_Sun,

Child contract can access to the parent internal function, but as an external account you can’t access to the kill function.
As an external account only public and external functions are accessible.

To access to the kill function inside the HelloWorld contract you should create a function, for instance accessToKill which will call the kill function of Destroyable.

function accessToKill() public onlyOwner {
        kill();
    }

And this function needs to be public or external to be accessible from an external account.
See below the entire code

pragma solidity 0.5.12;

contract Ownable {
    address internal owner;

    modifier onlyOwner(){
        require(msg.sender == owner);
        _; // underscore just means continue the execution
    }

    constructor() public{
        owner = msg.sender;
    }
}

contract Destroyable is Ownable{
    function kill() internal onlyOwner{
        selfdestruct(address(uint160(owner)));
    }
}

contract HelloWorld is Ownable, Destroyable{ //Ownable is the Parent
    struct Person {
        string name;
        uint age;
        uint height;
        bool senior;
        uint balance;
    
    } // Struct is like an object that has data structure 

    event personCreated(string name, bool senior);
    event personDeleted(string name, bool senior, address deletedBy);
    
    
    uint public totalBalance;



    modifier costs(uint cost){
        require(msg.value >= cost);
        _;
    }


    mapping(address => Person) private people; // Need review on Mapping
    address [] creators;

    function accessToKill() public onlyOwner {
        kill();
    }

    function createPerson(string memory name, uint age, uint height) public payable costs(1 ether){
        
        require(age <= 150, "Age needs to be below 150");
        // require(msg.value >= 1);
        totalBalance += msg.value;
        
        
        // people.push(Person(people.length, name, age, height));
        Person memory newPerson;
        
        newPerson.name = name;
        newPerson.age = age;
        newPerson.height = height;
        newPerson.balance = msg.value;
        
        if(age >= 65) {
            newPerson.senior = true;
        }
        else {
            newPerson.senior = false;
        }
        
        insertPerson(newPerson); 
        creators.push(msg.sender);
        assert(
            keccak256(
                abi.encodePacked(
                    people[msg.sender].name, 
                    people[msg.sender].age, 
                    people[msg.sender].height, 
                    people[msg.sender].senior
                    )
                ) == 
            keccak256(
                abi.encodePacked(
                    newPerson.name, 
                    newPerson.age, 
                    newPerson.height, 
                    newPerson.senior
                )
            )
        );
        emit personCreated(newPerson.name, newPerson.senior);
    }

    function getPerson() public view returns(string memory name, uint age, uint height, bool senior, uint balance) {
        address creator = msg.sender;
        return (people[creator].name, people[creator].age, people[creator].height, people[creator].senior, people[creator].balance);
    }

    function insertPerson(Person memory newPerson) private {
        address creator = msg.sender; // "msg.sender" will give the address of the sender in solidity, this is highly used
       // pay attention to payable address vs. normal address
       
       //convert from normal to payable address
        //  address payable test = address(uint160(creator)); // wrap with uint160() and then address()
       
       
        people[creator] = newPerson; 
        
    }

    function deletePerson(address creator) public onlyOwner {
        // require(msg.sender == owner); // removed for Modifier
        string memory name = people[creator].name;
        bool senior = people[creator].senior;
        
        delete people[creator];
        assert(people[creator].age == 0);
        emit personDeleted(name, senior, msg.sender);
    }

    function getCreator (uint index) public view onlyOwner returns(address) {
        // require(msg.sender == owner, "caller needs to be owner");
        return creators[index];
    }

    function withdrawAll () public onlyOwner returns(uint) {
        //VERY IMPORTANT, WE ALWAYS NEED TO MAKE CHANGES TO THE STATE 'BEFORE' TRANSFER
        // TRANSFER IS A BETTER USED FUNCTION BECAUSE IT WILL AUTOMATICALLY REVERT IF FAILED, USE "SEND" FUNCTION ONLY IF YOU WANT CUSTOM ERROR MESSSAGE
        
        // uint toTransfer = totalBalance;
        
        // totalBalance = 0;
        // msg.sender.transfer(toTransfer); // Automatic Revert
        // return toTransfer; //WHY DO YOU NEED TO RETURN?
        
        uint toTransfer = totalBalance;
        
        totalBalance = 0;
        
        if(msg.sender.send(toTransfer)){
            return toTransfer;
        }
        else {
            totalBalance = toTransfer;
            return 0;
        } //return false instead of reverse
    }
}

Hello,

I’m getting this error below once I input and follow Filip’s code in this lesson. What am I doing wrong?

transact to ExternalContract.ExternalCreatePerson errored: VM error: revert.
revert The transaction has been reverted to the initial state.
Note: The called function should be payable if you send value and the value you send should be less than your current balance. Debug the transaction to get more information.

//Interface
contract HelloWorld{
function createPerson(string memory name, uint age, uint height) public payable;
}

contract ExternalContract{

HelloWorld instance = HelloWorld(0x5fbb080edd8Ef5718b052862b928879293d37A20); 

function ExternalCreatePerson(string memory name, uint age, uint height) public payable{
    //Call createPerson in HelloWorld contract
    //Forward any ether to HelloWorld 
    instance.createPerson.value(msg.value)(name, age, height);
    
}

}

function createPerson(string memory name, uint age, uint height) public payable costs(1 ether){
    
    require(age <= 150, "Age needs to be below 150");
    // require(msg.value >= 1);
    totalBalance += msg.value;
    
    
    // people.push(Person(people.length, name, age, height));
    Person memory newPerson;
    
    newPerson.name = name;
    newPerson.age = age;
    newPerson.height = height;
    newPerson.balance = msg.value;
    
    if(age >= 65) {
        newPerson.senior = true;
    }
    else {
        newPerson.senior = false;
    }
    
    insertPerson(newPerson); 
    creators.push(msg.sender);
    assert(
        keccak256(
            abi.encodePacked(
                people[msg.sender].name, 
                people[msg.sender].age, 
                people[msg.sender].height, 
                people[msg.sender].senior
                )
            ) == 
        keccak256(
            abi.encodePacked(
                newPerson.name, 
                newPerson.age, 
                newPerson.height, 
                newPerson.senior
            )
        )
    );
    emit personCreated(newPerson.name, newPerson.senior);
}

Are you sending 1 ether when you are calling ExternalCreatePerson function?

I get the value of child inheritance of the parent file. That does make for clean code.

However when inheriting from parent that itself inherited it becomes a big can of worms again…like my second cousin’s cousin first removed (nod head and fake look of understanding relationship for ‘auntie mildred’ but you are clueless really who she is…)

in your solidity file folder is there something like heirarchical file that maps this out so you can really know who ‘milley’ really is…or just continue on in ignorance? (BTW, I have no clue really of my relatives as my brain just doesn’t get heirarchies…family tree would help but don’t have one)

1 Like

@John_Okoye
Keep the parent and child contract in separate files, no need to make cousins :stuck_out_tongue_closed_eyes:

1 Like

Why does the sample code cause errors on solidity 0.6.6?

Errors say HelloWorld should be marked as abstract and is missing implementation
second error says because missing implementation should be marked virtual

Will this be discussed in the future course 201? How do we know which version of solidity we want to be using as by default one would think you always want to be on the latest version.

thanks in advance for your help

Hi @filip - loving the course, thanks for making it all so clear.
I have a question in relation to the video about External Contracts.

I have implemented the code as you have and it works fine and takes Ether from the address of the user creating a new person through the external contract, but if I go to my other contract and try to withdraw the funds, nothing happens.
My understanding was that the function was sending those funds to the smart contract that it is calling the function from…
Am I missing something? misunderstanding a concept? or is this something you have not yet gotten to in the lectures?
Cheers, Justin

@cryptocrom
Check the balance of the contract, things will become more clear if the smart contract is receiving funds or not :slight_smile:

1 Like

Hi Filip and everyone, @filip @ivga80 @jon_m
the code does not work for compilers 0.6.0. above. I get errors that I do not understand.

The code:

pragma solidity 0.5.12;


//Interface
contract HelloWorld{
    function createPerson(address creator, string memory name, uint age, uint height) public payable;
}

contract ExternalContract{

    HelloWorld instance = HelloWorld(<ADDRESS>);

    function externalCreatePerson(string memory name, uint age, uint height) public payable{
        //CALL createPerson in HelloWorld contract
        //Forward any ether to HelloWorld
        instance.createPerson.value(msg.value)(msg.sender, name, age, height);
    }
}

Please help how to make it work for compilers above 0.6.0.
Thanks