0

A question in one sentence:

Regarding function call expressions in Solidity, can we always statically determine which functions will be invoked at compile time(not at runtime)?

Example Code:

contract A {
    uint256 public n;
    function set () public {
        n = 50;
    }
}

contract B is A {
    function set () public {
        n = 40;
    }
}

contract C is A,B {
    function test (uint n) public {
        A a;
        if (n==1){
            a = new A();
        } 
        else{
            a = B(new B());
        }
        a.set(); // member of A or B?
    }
}

Considering Java programs, it seems that, at compile time, we cannot determine whether the “set” function, invoked in the "test" function in contract C, is a member function of A contract or B contract. However, inspecting AST generated by a Solidity compiler (with an option solc --ast-compact-json), the invoked “set” function is referred as a member of A contract, not B contract. Is it an intentional feature of Solidity?

0

In your code you declare

A a;

And you invoke

a.set();

Your contract has no idea that a might be pointing to an instance of B.

Borrowing from you java statement: the call will resolved at "runtime".

Solidity compiles to: call function set() at address a. It doesn't care what contract is at that address, it might be a totally different contract.

For example the following will work as expected even when A and B and totally unrelated.

contract A {
    uint public n;
    function foo () public returns (uint) {
        n = 1234;
        return n;
    }
}

contract B {
    uint public n;
    function foo () public returns (uint) {
        n = 7777;
        return n;
    }
}

contract C {
    function bar(uint n) public returns (uint) {
        A a;
        if (n == 1) {
            a = new A();
        } else {
            a = A(address(new B()));
        }
        a.foo();
        return a.n();
    }
}
0

This depends on how do we call a function. There are several options:

Direct internal calls

function foo () public {
   ...
}

function bar () public {
   foo (); // Here call destination is statically determined
}

Indirect internal calls

function foo () public {
   ...    
}

function bar () public {
    ...
}

function zoo () public {
    function () func = block.timestamp & 1 == 0 ? foo : bar;
    func (); // Here call destination is determined at run time
}

Direct external calls

function foo (IERC20 _token) public {
    require (_token.transfer (msg.sender, 10)); // Here call destination is determined at run time
}

Indirect external calls

function foo (address _contract, bytes _data) {
    require (_contract.call (_data)); // Here called function is determined by first four bytes of _data
}
0

The rules are simple:

  • External calls are always dynamic and involve Solidity method dispatcher
  • Internal calls are always static and resolved in compile time

Example:

function foo() public {
    // ...
}

function bar() public {
    this.foo();  // external call
    foo();       // internal call
    super.bar(); // internal call
}

Solidity compiler transforms multiple inheritance into linear inheritance. So after compilation every smart contract will have a single parent, which can be accessed by super keyword. Maybe following example will help you to understand how C3 inheritance linearization works:

contract A { }
contract B { }
contract C is A, B { } // C(A,B) = ABC
contract D is C, A { } // D(C(A,B),A) = D(ABC,A) = ABCAD 💔 Error
contract E is A, C { } // E(A,C(A,B)) = E(A,ABC) = ABCE

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.