Understanding the Function Selector in Solidity

The function selector is a 4 byte id that Solidity uses to identify functions under the hood.

The function selector is how a Solidity contract knows which function you are trying to call in the transaction.

You can see the 4 byte id using the .selector method:

pragma solidity 0.8.25;

contract SelectorTest{

    function foo() public {}

    function getSelectorOfFoo() external pure returns (bytes4) {
        return this.foo.selector; // 0xc2985578
    }
}

If a function takes no arguments, you can invoke that function by sending those 4 bytes (the selector) as data to the contract with a low level call.

In the following example, the CallFoo contract calls the foo function defined in the FooContract by making a call to FooContract with the appropriate four-byte function selector

pragma solidity 0.8.25;

contract CallFoo {

    function callFooLowLevel(address _contract) external {
        bytes4 fooSelector = 0xc2985578;

        (bool ok, ) = _contract.call(abi.encodePacked(fooSelector)); 
        require(ok, "call failed");
    }

}

contract FooContract {

    uint256 public x;

    function foo() public {
        x = 1;
    }
}

If FooContract and CallFoo are deployed, and the callFooLowLevel() function is executed with the address of FooContract, then the value of x within FooContract will be set to 1. This indicates a successful call to the foo function.

Identifying function calls and selector in Solidity with msg.sig

msg.sig is a global variable that returns the first four bytes of the transaction data, which is how the Solidity contract knows which function to invoke.

msg.sig is preserved across the entire transaction, it does not changed depending on which function is currently active. So even if a public function calls another public function during a call, msg.sig will be the selector of the function that was initially called.

In the code below, foo calls bar to get the msg.sig, but the selector that gets returned when foo is called is the function selector of foo, not bar:

You can test the code in Remix here:

//SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

contract SelectorTest {

    // returns function selector of `bar()` 0xfebb0f7e
    function bar() public pure returns (bytes4) {
        return msg.sig;
    }

    // returns function selector of `foo()` 0xc2985578
    function foo() public pure returns (bytes4) {
        return bar();
    }

    function testSelectors() external pure returns (bool) {
        
        assert(this.foo.selector == 0xc2985578);
        assert(this.bar.selector == 0xfebb0f7e);
        
        return true;
    }
}

Solidity function signature

The function signature in Solidity is a string holding the name of the contract followed by the types of the arguments it accepts. The variable names are removed from the arguments.

In the following snippet, the function is on the left, and the function signature is on the right:

function setPoint(uint256 x, uint256 y) --> "setPoint(uint256,uint256)"
function setName(string memory name) --> "setName(string)"
function addValue(uint v) --> "addValue(uint256)"

There are no spaces in the function selector. All uint types must include their size explicitly (uint256, uint40, uint8, etc). The calldata and memory types are not included. For example, getBalanceById(uint) is an invalid signature.

How the function selector is computed from the function signature

The function selector is the first four bytes of the keccak256 hash of the function signature.

function returnFunctionSelectorFromSignature(
    string calldata functionName
) public pure returns(bytes4) {
    bytes4 functionSelector = bytes4(keccak256(abi.encodePacked(functionName)));
    return(functionSelector);
}

Putting foo() into the function above will return 0xc2985578.

The code below demonstrates computing the function selector, given the function signature.

//SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

contract FunctionSignatureTest {

    function foo() external {}
    
    function point(uint256 x, uint256 y) external {}
    
    function setName(string memory name) external {}
    
    function testSignatures() external pure returns (bool) {
    
    // NOTE: Casting to bytes4 takes the first 4 bytes
    // and removes the rest

    assert(bytes4(keccak256("foo()")) == this.foo.selector);
        assert(bytes4(keccak256("point(uint256,uint256)")) == this.point.selector);
        assert(bytes4(keccak256("setName(string)")) == this.setName.selector);
    
    return true;

    }
}

Interfaces are encoded as addresses. For example f(IERC20 token) is encoded as f(address token). Making an address payable does not change the signature.

Internal functions do not have a function selector

Public and external functions have a selector but internal and private functions do not. The following code does not compile:

contract Foo {

    function bar() internal {
    }

    // does not compile
    function barSelector() external pure returns (bytes4) {
        return this.bar.selector;
    }
}

If bar is changed to public or external, the code will compile.

Internal functions do not need function selectors because function selectors are intended for use by external contracts. That is, the function selector is how an external user specifies which public or external function they are trying to call.

Why use function selectors instead of the name of the function?

Solidity function names can be arbitrarily long, and if the function name is long, it will increase the size and cost of the transaction. Transactions are generally smaller in size if a function selector is provided instead of the name.

Does the fallback function have a function selector?

The fallback function does not have a function selector.

If msg.sig is logged inside the fallback function, it will simply log the first four bytes of the transaction data. If there is no transaction data, then msg.sig will return four bytes of zero. This does not mean that the function selector of the fallback is all zeros, it means msg.sig tried to read data that wasn’t there.

contract FunctionSignatureTest {

    event LogSelector(bytes4);

    fallback() external payable {
        emit LogSelector(msg.sig);
    }
}

Here is the code on Remix to test the above contract. Below is a video showing how to trigger the fallback function on Remix:

Probability of a function selector collision

A function selector can hold up to 2**32 – 1 (approximately 4.2 billion) possible values. As such, the odds of two functions having the same selector is small but realistic.

contract WontCompile {

    function collate_propagate_storage(bytes16 x) external {}

    function burn(uint256 amount) external {}
}

The example above was taken from this forum post. Both of these functions have a function selector of 0x42966c68.

Useful resources for function selectors

Function selector calculator. Be sure to add the keyword function. The calculator expects a format like function burn(uint256).

Function selector database. Put the 4 bytes into the search and it will turn up the known functions that match it.

Function selectors and the EVM — Ethereum Virtual Machine

For readers familiar with the EVM, there is a common source of confusion. The four byte function selector happens at the Solidity application level, not the EVM level. Nothing in Ethereum’s specification states that functions must be identified with 4 bytes. This is a strong convention, but it is not required.

In fact, a common optimization for layer 2 applications is to identify the function with a 1 byte function selector. That is, all function calls go to the fallback function, then the fallback function looks at the first byte in the transaction to determine which function to invoke.

Learn more with RareSkills

See our Solidity bootcamp for more advanced topics in smart contract development.

Originally Published March 30, 2024

20 Common Solidity Beginner Mistakes

20 Common Solidity Beginner Mistakes Our intent is not to be patronizing towards developers early in their journey with this article. Having reviewed code from numerous Solidity developers, we’ve seen some mistakes occur more frequently and we list those here. By no means is this an exhaustive list of mistakes a Solidity developer can make. […]

Smart Contract Foundry Upgrades with the OpenZeppelin Plugin

Smart Contract Foundry Upgrades with the OpenZeppelin Plugin Upgrading a smart contract is a multistep and error-prone process, so to minimize the chances of human error, it is desirable to use a tool that automates the procedure as much as possible. Therefore, the OpenZeppelin Upgrade Plugin streamlines deploying, upgrading and managing smart contracts built with Foundry or […]

UUPS: Universal Upgradeable Proxy Standard (ERC-1822)

UUPS: Universal Upgradeable Proxy Standard (ERC-1822) The UUPS pattern is a proxy pattern where the upgrade function resides in the implementation contract, but changes the implementation address stored in the proxy contract via a delegatecall from the proxy. The high level mechanism is shown in the animation below: Similar to the Transparent Upgradeable Proxy, the […]

Try Catch and all the ways Solidity can revert

Try Catch and all the ways Solidity can revert This article describes all the kinds of errors that can happen when a smart contract is called, and how the Solidity Try / Catch block responds (or fails to respond) to each of them. To understand how Try / Catch works in Solidity, we must understand […]