Search code examples
ethereumsolidityweb3js

How is a contract defined by an interface without implementing all functions?


In the following code the "MainContract" contract is constructed by passing the address of a "MyInterface" Object. I'm able to construct a "MainContract" contract by by passing the address of a "ContractInherits" contract. How is this possible when "ContractInherits" doesn't implement the "randomFunction()" function? Is ContractInherits actually inheriting from MyInterface?

pragma solidity 0.8.19;

interface MyInterface{
    function changeVariable() external;
    function randomFunction() external;
}

contract ContractInherits {
    uint256 public a_variable;

    function changeVariable() public {
        a_variable = a_variable++;
    }  
}

contract MainContract {
    MyInterface public immutable ContractInherits;

    constructor(MyInterface _ContractInherits) {
        ContractInherits = _ContractInherits;
    }
    
    
}

Solution

  • I'll demo an example of my comment above, that you'll get a runtime error.

    Here is your original code, with a couple of modifications:

    • Changed the ContractInherits variable name within MainContract to instanceOfContractInherits to clarify what it really is, plus avoid variable shadowing.
    • Added a doStuff() function for the runtime error will be produced.
    // SPDX-License-Identifier: GPL-3.0
    pragma solidity 0.8.19;
    
    interface MyInterface{
        function changeVariable() external;
        function randomFunction() external;
    }
    
    contract ContractInherits {
        uint256 public a_variable;
    
        function changeVariable() public {
            a_variable = a_variable++;
        }  
    }
    
    contract MainContract {
        MyInterface public immutable instanceOfContractInherits;
    
        constructor(MyInterface _ContractInherits) {
            instanceOfContractInherits = _ContractInherits;
        }
    
        function doStuff() public {
            instanceOfContractInherits.changeVariable();
            instanceOfContractInherits.randomFunction();
        }
    }
    
    

    Then I've done the following in Remix:

    (1) Deploy ContractInherits

    (2) Copy its deployed address

    (3) Deploy MainContract, passing in the just copied address into its constructor

    (4) Invoke the instanceOfContractInherits accessor function

    At this point, the "Deployed Contracts" section looks like this:

    deployed-contracts-remix

    Now for the runtime error as promised:

    (5) Invoke the doStuff transaction function

    (6) Observe the transaction status of revert in the logs

    transact to MainContract.doStuff errored: VM error: revert.
    
    revert
    

    (7) Expand the lines in the logs that started with [vm] to the the reason, which is:

    status  false Transaction mined but execution failed
    

    transaction-error


    So, why does this happen?

    The solidity compiler solc does not (and cannot) know what the interface is at compile time, because

    1. You are only passing in the deployed address for the implementation of that interface at run time (the constructor argument).
    2. Even if the above were not true (let's say you hardcoded the address instead of accepting it as a constructor argument), it still would not know, because
    • (a) solc does not access the network during compilation,
    • (b) solc cannot infer ABI from bytecode (reverse engineering is not one of the things it does), and
    • (c) solc should produce bytecode that is agnostic to which EVM-compatible network it is deployed to (e.g. it could be deployed to another network where the address referenced does indeed have a working implementation for this interface)