Bytes[] memory conversion to bytes[] calldata

Hi there, it looks like the use case of Multicall is out of solidity environment. As I was trying to create a solidity example in Solidity as shown below, it seems impossible to convert bytes[] memory to bytes[] calldata to be passed into Multicall.multicall.

Please help clarify if this is the right way of using Multicall in Solidity and how I can create a bytes[] calldata array. Thanks.

// SPDX-License-Identifier: MIT

pragma solidity 0.8.4;

import "@openzeppelin/contracts/utils/Address.sol";

abstract contract Multicall {
    function multicall(bytes[] calldata data) external returns (bytes[] memory results) {
        results = new bytes[](data.length);
        for (uint i = 0; i < data.length; i++) {
            results[i] = Address.functionDelegateCall(address(this), data[i]);
        }
        return results;
    }
}

contract testMultiCall is Multicall {
    
    bytes4 private constant FUNC_SELECTOR = bytes4(keccak256("transferFrom(address,address,uint256)"));
    
    function createMulticallData(address[] memory from, address[] memory to, uint256[] memory amount) public pure returns(bytes[] calldata data) {
        uint256 length = from.length;

        bytes[] memory temp;

        if (length != to.length || length != amount.length) revert();

        for (uint256 i = 0; i < length; i++) {
            temp[i] = abi.encodeWithSelector(FUNC_SELECTOR, from[i], to[i], amount[i]);
        }

        return temp;
    }
}

Hey @maxareo! The idea behind multicall is allowing an EOA (externally owned account) caller to bundle together several function calls that can be executed in a single tx to the target contract. So, if you wanted to do an approve + transfer on an erc20 (for instance), instead of sending two separate txs, you can just encode both calls and send them together to the multicall method in a single tx (which is cheaper!).

If you are calling your contract from another contract in Solidity, then you don’t need to use multicall since you are already in the context of a single transaction. There is no benefit in calling multicall from within Solidity code.

1 Like

@spalladino Thanks for the answer. Here are two other posts that can show where I came from.

Basically, what we would like to achieve is an automatic batch minting mechanism that is invoked not by the contract or project owner but by any contract calller, i.e., anyone could call a function in a smart contract and invoke a batch minting of ERC721 tokens.

We are currently using a for loop, which is linear and sequential in a single transaction. The name multicall gives me a sense that it has a parallel mechanism built in, sort of like multi-processing in Python. It looks like it is not. multicall is also sending calling messages in a linear fashion, correct? I would also assume it takes similar amount of gas to a function with a for loop and high-level calls, for a task like making a transfer, correct?

Now, coming back to the way of using multicall, is it possible to create an array of calldata and get it passed to this function in Solidity, as described in the original question?

Thanks.

The name multicall gives me a sense that it has a parallel mechanism built in, sort of like multi-processing in Python. It looks like it is not. multicall is also sending calling messages in a linear fashion, correct?

Correct. There is no multiprocessing in EVM at all. The transactions are always executed sequentially and all instructions within a transaction are sequential too.

I would also assume it takes similar amount of gas to a function with a for loop and high-level calls, for a task like making a transfer, correct?

Not quite. Note that multicall() first executes an external function from your own contract using delegatecall and it's that function that you would call transfer() from. Compared to a for loop that can just call transfer() directly it's going to be more expensive. An external call to the same contract is much cheaper than one to a different contract but it's still just unnecessary overhead for you.

Now, coming back to the way of using multicall , is it possible to create an array of calldata and get it passed to this function in Solidity, as described in the original question?

No. calldata is a read-only area so you cannot add anything to it. You can only use data that's already there.

This function could have been written to take memory argument which would allow you to pass both memory and calldata arguments to it (because stuff from calldata can always be decoded into memory and the compiler can do that for you automatically; decoding costs gas though) but as @spalladino has already pointed out, it would be pointless because calling multicall() from within a contract defeats its whole purpose. It's more expensive than on-chain calls and the cost is justified only when issuing a transaction from an EOA because separate transactions would be even more expensive.

Given how short it is, the easiest way to work around the calldata parameter would be to just copy it to your own code and change it to memory. But you're really much better off keeping your for loop.

2 Likes

Thanks so much @cameel for jumping in! I don’t have anything to add on top of what you shared :slight_smile: