Can anyone explain to me what the _method does?

I’m trying to understand a smart contract, but I’m struggling with this variable _method.

Can anyone explain to me please?

function _executeCrossChainTx(address _toContract, bytes memory _method, bytes memory _args, bytes memory _fromContractAddr, uint64 _fromChainId) internal returns (bool){
        // Ensure the targeting contract gonna be invoked is indeed a contract rather than a normal account address
        require(Utils.isContract(_toContract), "The passed in address is not a contract!");
        bytes memory returnData;
        bool success;
        
        // The returnData will be bytes32, the last byte must be 01;
        (success, returnData) = _toContract.call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))), abi.encode(_args, _fromContractAddr, _fromChainId)));

Hi @filipaze,

The function is first making sure that _toContract is actually a contract (an account with code in it). Then in general lines it calls _toContract method _method with parameters _args, _fromContractAddr and _fromChainId. In particular the _method parameters is a string (encoded in bytes) with the name of the function to be called.

If _method has, for example, the value “doSomething”, the code above would be executing:

_toContract.doSomething(_args, _fromContractAddr, _fromChainId)

The way it does this call is by first calculating the function selector for the _method parameters. This is done by hashing the signature of the method “doSomething(bytes,bytes,uint64)” and getting the first 4 bytes. This is what the first part of the last line is doing. It first concatenates the _method name to the rest of the signature:

abi.encodePacked(_method, "(bytes,bytes,uint64)")

Then it hashes it:

keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))

And finally gets the first 4 bytes:

bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)")))

That will yield a 4 bytes value with the function selector for the function name in _method. Let’s call this value _selector.

Finally it appends to this selector the required parameters in order to call that function in the other contract:

abi.encodePacked(_selector, abi.encode(_args, _fromContractAddr, _fromChainId))

This yields a sequence of bytes that encode a call to _selector with the rest of the parameters as arguments. Let’s call this sequence of bytes _calldata. Notice that _args, _fromContractAddr and _fromChaind parameters follow the signature used to calculate the selector (bytes,bytes,uint64).

Then it goes into calling that function in _toContract:

_toContract.call(_calldata)

Hope this helps!

2 Likes

Hi @robercano

Thank you so much!

I have one more question. Why use the first 4 bytes? This opens an attack surface, right? As the attacker just needs to brute-force the first 4 bytes, in a scenario where the attacker controls the _method. Why don’t use the complete hash?

Thanks again for the help :smiley:

Hey @filipaze,

You’re welcome! So the 4 bytes for the selector is actually a standard defined by Solidity to encode calls to a function in a contract.

You are right about the surface attack: if _executeCrossChain would be accessible from an external call (notice that the function is internal), then it could enable an attack vector.

However the attacker does not need to brute force the selector: the selector is completely deterministic if you know which function you want to call, as it is the hash of the prototype for the function. The idea of a selector is not to conceal the method from attackers, but rather to provide a way to encode a function prototype as something that can be used in the EVM to jump to that function.

The opposite is more difficult: knowing the prototype behind a selector is not trivial, so it is more difficult to reverse engineer which prototype belongs to a selector (maybe this is what you had in mind). In that case the attacker would need to brute force the selector to know which function it will call, but there is not a 1 to 1 correspondence from the selector to the prototype, as many prototypes could yield the same selector.

In any case if you are interested, there is an effort on reverse engineering selectors by creating a public database of correspondence: https://www.4byte.directory/

1 Like

Hi

Thank you again @robercano that really helped a lot! :slight_smile:

Even if the attacker knows the selector he wants to call, he would need to ensure the first 4 bytes of the selector correspond to a valid one right? So we would need to brute force a string that matches the 4 first bytes.

Thank you,

So the thing is that if the attacker knows the selector, and the type of parameters that the associated function receives (for example the attacker could gain this information by looking at transactions using that selector), then the attacker could directly call the function without knowing the string. You just have to generate the calldata for that call. If you know the selector is 0x12446587 and that it receives an address as parameter, and you want to call it with the address 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 you can compose your calldata as:

0x12446587000000000000000000000000C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2

and send it to the contract. Notice that the address is padded with zeroes to the left to make it fill 32 bytes, which is the word size of the EVM. The contract will execute the function passing the given address. You don’t really need to reverse engineer the string to call the contract.

Hope this answers the question!