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