The bytes
type is like an array, where each element in the array is 1 byte.
The getSelectorTwo()
function is encoding the transfer(address,uint256)
function selector with the values of the parameters it expects (address
and uint256
).
If you printed functionCallData
you would see the following bytes:
0xa9059cbb00000000000000000000000059235ef80c8c63b480734c6888754f464fdb212d000000000000000000000000000000000000000000000000000000000000007b
Notice how the first four bytes are the function selector a9059cbb
.
Then the following 32 bytes are the address of that contract (left-padded with zeros):
00000000000000000000000059235ef80c8c63b480734c6888754f464fdb212d
Then the following 32 bytes are the 123
number in hex (left-padded with zeros):
000000000000000000000000000000000000000000000000000000000000007b
7b
hex is 123
in decimal.
So, the reason why that code is accessing the functionCallData
bytes array 4 times is because it is copying the first 4 bytes of that data, which we know it's the function selector:
functionCallData[0] = a9 functionCallData[1] = 05 functionCallData[2] = 9c functionCallData[3] = bb
It concatenates it and returns it.
It cannot do something like functionCallData[0:4]
to copy a range from the functionCallData
bytes array because the slice operator is only available in calldata
bytes array, not on local or storage arrays (yet). So it has to copy it manually, whether in a loop or index by index like it's doing.
Check and try my getTransferFunctionSelectorWithParams
function below:
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
contract Contract {
function getTransferFunctionSelectorWithParams() public view returns(bytes memory) {
bytes memory functionCallData = abi.encodeWithSignature(
"transfer(address,uint256)",
address(this),
123
);
return functionCallData;
}
function getSelectorTwo() public view returns (bytes4 selector) {
bytes memory functionCallData = abi.encodeWithSignature(
"transfer(address,uint256)",
address(this),
123
);
selector = bytes4(
bytes.concat(
functionCallData[0],
functionCallData[1],
functionCallData[2],
functionCallData[3]
)
);
}
}
Best Answer
There is no way to directly call an internal function through function selector.
The entry point of a contract is basically a jump table with additional checks, looking for a matching function selector between the first 4 bytes of calldata and the exposed functions of the contract.
Marking your function internal excludes it from that jump table, making it inaccessible from an external call.
Take the following contract :
I deployed it here. You can check the decompiled bytecode here.
With an explicit check for the function selector in the main function body (As a reminder:
keccak256("otherFunc(bool)")
==0x64feed76...
) :By opposition, this contract (with internal visibility) :
Deployed here, and decompiled here presents no such check. Meaning that there is no path leading to
otherFunc
from the main entry point of the smart contract.Only internal calls to it will be allowed, external ones will not find a matching entry and therefore revert (or won't even compile like in your example).
EDIT : If from the decompiled bytecode you are wondering what the check for
0x50f9b6cd
are : those are just the same principle applied to the auto generated getter for :bool public called
,keccak256("called()")
=0x50f9b6cd....
EDIT 2:
Only external (or public) functions have the following members:
.address
: returns the address of the contract of the function..selector
: returns the ABI function selectorSo by using either of these, you are calling the functions "externally".