Disassembling the EVM Dispatcher

Disassembling the EVM Dispatcher

Have you ever wondered how a contract decides which function you actually called? Well, you are in luck as I'll show you how I compiled a simple Solidity contract and then I disassembled it with Binary Ninja to understand that.

We’ll use a tiny contract compiled with solc 0.8.4 that has five public functions:

  • function1(uint256) - Function selector c1994157
  • function2() - Function selector 78728c73
  • function3() - Function selector 2a64052b
  • function4() - Function selector 3a24555a
  • function5() - Function selector db70aca4

Reminder: a function selector is the first 4 bytes of keccak256("fnName(argTypes)").

Code below:

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

contract Example {

    function function1(uint256 param) public view {
    
    }

    function function2() public view {
    
    }

    function function3() public view {
    
    }

    function function4() public view {
    
    }

    function function5() public view {

    } 
}

After compiling the code above with the 0.8.4 solc compiler, the obtained runtime bytecode is 6080604052348015600f57600080fd5b506004361060505760003560e01c80632a64052b1460555780633a24555a14605d57806378728c73146065578063c199415714606d578063db70aca4146085575b600080fd5b605b608d565b005b6063608f565b005b606b6091565b005b60836004803603810190607f919060ab565b6093565b005b608b6096565b005b565b565b565b50565b565b60008135905060a58160db565b92915050565b60006020828403121560bc57600080fd5b600060c8848285016098565b91505092915050565b6000819050919050565b60e28160d1565b811460ec57600080fd5b5056fea26469706673582212209a7037725965c9d44cebc63cbae2ce40b57048967c0ce232716f49035cbdaaa464736f6c63430008040033.

After disassembling the compiled Smart Contract, we can see the _dispatcher located at the 0x00 address. This is the entry-point to any execution of the Smart Contract.

After executing the prolog, we can see the following instructions:

PUSH1 0x04
CALLDATASIZE
LT

The LT opcode is a comparison instruction. It takes the two previous values that were introduced in the stack (0x04 and the CALLDATASIZE value). Then pushes 1 into the stack if the first value if CALLDATASIZE is less than 0x04, and pushes 0 if it is equals or higher.

The purpose of checking if the CALLDATASIZE is bigger or equal than 4 bytes is to check if a function is being called (as functions have selectors of 4 bytes).

After that code, it pushes the value 0x50 into the stack, and executes the JUMPI opcode. That means that the execution flow will jump into the 0x50 address (fallback function) if CALLDATASIZE were less than 4 bytes.

In the other hand, if the CALLDATASIZE were equal or bigger than 4 bytes, it would have continue the execution to determine the flow to jump to the executed function.

The execution continues until it reaches the following code, which executes the EQ opcode. This opcode returns 1 if the DUP1 (which contains the function selector) is equals to 0x2a64052b.

...omitted for brevity...
DUP1
PUSH4   #2a64052b
EQ      
PUSH1   #55
JUMPI

As we know, the selector of the function3() is 2a64052b, which is located in the 0x55 address. That's why the code is followed by PUSH1 0x55 and JUMPI.

So, in case that the EQ execution returns true, the execution will jump into the address 0x55 (function3())

Otherwise, the execution flow will continue to the address 0x28.

Here, a similar comparation will happen. In this case, it compares the selector with the 0x3a24555a.

PUSH4 #3a24555a
EQ
PUSH1 #5d

As in the previous block, in case the EQ operator returns 1 it will jump into the 0x65 address, or otherwise will continue the execution in 0x3c

The same validation occurs for each one of the functions. In this case, the last selector to be validated was 0xdb70aca4 (function5()).

As observed, if none of the selectors matched with the available functions, the execution flow will jump into the fallback function.

And that’s the byte-sized tour! If you’ve got a spicy dispatcher or an odd compiler pattern, send it my way—I’ll happily dissect it in a future post.

Manuel