1

The following code would revert in high-level Solidity if the provided addr is an EOA or a contract that does not implement MyInterface:

interface MyInterface {
    function myFunction() external;
}

function foo(address addr) {
    MyInterface(addr).myFunction();
}

But what if we used a low-level call instead? My understanding is that in that case, the call would be successful. But why is that? Can someone point me to a resource in the EVM spec where this behavior is documented?

2 Answers 2

2

Calls to non-contract addresses (that is, EOAs or inactive accounts) always set the success flag to true and leave the return data field empty, so the right question isnt "Why lows level calls to EOAs don't revert", but rather "Why high level calls to EOAs revert?".

And the reason why MyInterface(addr).myFunction(); reverts is because the compiler adds a check to addr's EXTCODESIZE before the call is made when you call through an interface

2
  • Can someone point me to a resource in the EVM spec where this behavior is documented? Can you add a resource link to this question? @Foxxxey
    – BonisTech
    Mar 17, 2023 at 15:50
  • I'm not sure it's explicitely specified in the yellow paper or another related ressource (and frankly, don't wanna check ^^') but the idea is that trying to read the code stored at a non-contract address returns 0, which is the STOP opcode, which just sucessfully returns to the previous execution context without doing anything else
    – Foxxxey
    Mar 17, 2023 at 15:55
1

A call, any call, has a caller, can have a value and can have some data. The value can be zero, the data can be empty, doesn't matter. By default as explained in the chosen answer, all calls are valid.

Now if a call is received by a smart contract, there are multiple possible scenarios:

  1. The message data is empty: The execution of the transaction continues at the receive() function or the fallback() function if the former is not implemented. If neither of them is available, the transaction will revert.

  2. The call has data: the contract will look at the 4 first bytes and see if it matches one of the registered function selectors (functions marked as public or external. If it finds a match, the transaction execution will continue at that function's location in the bytecode, if not, same as for case (1): it will look for the fallback or revert.

  3. The message data's first 4 bytes correspond to a function selector that exists on the receiving contract: the transaction's execution will continue by jumping to that part in the contract's code that contains the recognized function selector.

    3a. If the message also had value and the function wasn't marked as payable in the solidity code, the function will revert because the Solidity compiler adds that condition explicitly

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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