Encoding the Function Name

Work with multiple functions in a single Cross-Chain dApp

Great work. We can now pack multiple values into a message. In this section, we will learn how to encode the function name and, depending on it, its parameters in the message. Let's imagine a more advanced calculator that not only has a single add function but also a concatenate function, that lets you concatenate two strings.

For the add function we need to encode two numbers. For the concatenate function we need to encode two strings. How can we go about this? The concept is easy: It's like packing an envelope into another envelope:

Ecoding the Function Name and Parameters

The first step is to create a CalculatorAction enum that specifies the different functions that can be called on the calculator.

contracts/interchain-messaging/invoking-functions/CalculatorActions.sol
pragma solidity ^0.8.18;
 
enum CalculatorAction {
    add,
    concatenate
}

In the next step we can add this to our encode helpers in the sender contract:

contracts/interchain-messaging/invoking-functions/CalculatorSenderOnCChain.sol
pragma solidity ^0.8.18;
 
import "@teleporter/ITeleporterMessenger.sol";
import "./CalculatorActions.sol";
 
contract CalculatorSenderOnCChain {
    ITeleporterMessenger public immutable teleporterMessenger =
        ITeleporterMessenger(0x253b2784c75e510dD0fF1da844684a1aC0aa5fcf);
 
    function sendAddMessage(address destinationAddress, uint256 num1, uint256 num2) external {
        teleporterMessenger.sendCrossChainMessage(
            TeleporterMessageInput({
                // BlockchainID of Dispatch L1
                destinationBlockchainID: 0x9f3be606497285d0ffbb5ac9ba24aa60346a9b1812479ed66cb329f394a4b1c7,
                destinationAddress: destinationAddress,
                feeInfo: TeleporterFeeInfo({feeTokenAddress: address(0), amount: 0}),
                requiredGasLimit: 100000,
                allowedRelayerAddresses: new address[](0),
                message: encodeAddData(num1, num2)
            })
        );
    }
 
    function sendConcatenateMessage(address destinationAddress, string memory text1, string memory text2) external {
        teleporterMessenger.sendCrossChainMessage(
            TeleporterMessageInput({
                // BlockchainID of Dispatch L1
                destinationBlockchainID: 0x9f3be606497285d0ffbb5ac9ba24aa60346a9b1812479ed66cb329f394a4b1c7,
                destinationAddress: destinationAddress,
                feeInfo: TeleporterFeeInfo({feeTokenAddress: address(0), amount: 0}),
                requiredGasLimit: 100000,
                allowedRelayerAddresses: new address[](0),
                message: encodeConcatenateData(text1, text2)
            })
        );
    }
 
    // Encode helpers
    function encodeAddData(uint256 a, uint256 b) public pure returns (bytes memory) {
        bytes memory paramsData = abi.encode(a, b);
        return abi.encode(CalculatorAction.add, paramsData);
    }
 
    function encodeConcatenateData(string memory a, string memory b) public pure returns (bytes memory) {
        bytes memory paramsData = abi.encode(a, b);
        return abi.encode(CalculatorAction.concatenate, paramsData);
    }
}

As you can see here we are calling abi.encode twice in the encode helpers. The first time we encode the function parameters and the second time we encode the function name with the byte array containing parameters.

Decode the Function Name and Parameters

Let's now look at the receiver:

contracts/interchain-messaging/invoking-functions/CalculatorReceiverOnDispatch.sol
pragma solidity ^0.8.18;
 
import "@teleporter/ITeleporterMessenger.sol";
import "@teleporter/ITeleporterReceiver.sol";
import "./CalculatorActions.sol";
 
contract CalculatorReceiverOnDispatch is ITeleporterReceiver {
    ITeleporterMessenger public immutable teleporterMessenger =
        ITeleporterMessenger(0x253b2784c75e510dD0fF1da844684a1aC0aa5fcf);
    uint256 public result_num;
    string public result_string;
 
    function receiveTeleporterMessage(bytes32, address, bytes calldata message) external {
        // Only the Interchain Messaging receiver can deliver a message.
        require(
            msg.sender == address(teleporterMessenger), "CalculatorReceiverOnDispatch: unauthorized TeleporterMessenger"
        );
 
        // Decoding the Action type:
        (CalculatorAction actionType, bytes memory paramsData) = abi.decode(message, (CalculatorAction, bytes)); 
 
        // Route to the appropriate function.
        if (actionType == CalculatorAction.add) {
            (uint256 a, uint256 b) = abi.decode(paramsData, (uint256, uint256));
            _calculatorAdd(a, b);
        } else if (actionType == ...) {
            (string memory text1, string memory text2) = abi.decode(paramsData, (string, string));
            _calculatorConcatenateStrings(text1, text2);
        } else {
            revert("CalculatorReceiverOnDispatch: invalid action");
        }
    }
 
    function _calculatorAdd(uint256 _num1, uint256 _num2) internal {
        result_num = _num1 + _num2;
 
    }
 
    function _calculatorConcatenateStrings(string memory str1, string memory str2) internal {
        bytes memory str1Bytes = bytes(str1);
        bytes memory str2Bytes = bytes(str2);
 
        bytes memory combined = new bytes(str1Bytes.length + str2Bytes.length + 1);
 
        for (uint256 i = 0; i < str1Bytes.length; i++) {
            combined[i] = str1Bytes[i];
        }
        combined[str1Bytes.length] = " ";
        for (uint256 i = 0; i < str2Bytes.length; i++) {
            combined[str1Bytes.length + i + 1] = str2Bytes[i];
        }
 
        result_string = string(combined);
    }
}

You can see that we first decode the CalculatorAction enum:

// Decoding the Action type: 
(CalculatorAction actionType, bytes memory paramsData) = abi.decode(message, (CalculatorAction, bytes)); 

Then based on the function name we decide how to unpack the parameters

// Route to the appropriate function. 
if (actionType == CalculatorAction.add) {
    (uint256 a, uint256 b) = abi.decode(paramsData, (uint256, uint256));
    _calculatorAdd(a, b);
} else if (actionType == ...) {
    (string memory text1, string memory text2) = abi.decode(paramsData, (string, string));
    _calculatorConcatenateStrings(text1, text2);
} else {
    revert("CalculatorReceiverOnDispatch: invalid action");
}

For the add function we decode two numbers and for the concatenate function we decode two strings. After the decoding we call the appropriate internal function.

Try it Out

Deploy the sender and receiver contracts and try out the add and concatenate functions.

Deploy the Sender Contract

forge create --rpc-url fuji-c --private-key $PK contracts/interchain-messaging/invoking-functions/CalculatorSenderOnCChain.sol:CalculatorSenderOnCChain --broadcast
[⠃] Compiling...
[⠆] Compiling 2 files with Solc 0.8.18
[⠰] Solc 0.8.18 finished in 240.23ms
Compiler run successful!
Deployer: 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC // [$FUNDED_ADDRESS]
Deployed to: 0x8B3BC4270BE2abbB25BC04717830bd1Cc493a461 // [$SENDER_ADDRESS]
Transaction hash: 0xf9cce28a714764bb265bba7522bfd10d620fa0cb0f5dae26de2ac773b0a878ee

Save the Sender Address:

export SENDER_ADDRESS={your-sender-address}

Deploy the Receiver Contract:

forge create --rpc-url fuji-dispatch --private-key $PK contracts/interchain-messaging/invoking-functions/CalculatorReceiverOnDispatch.sol:CalculatorReceiverOnDispatch --broadcast
[⠊] Compiling...
[⠢] Compiling 1 files with Solc 0.8.18
[⠆] Solc 0.8.18 finished in 148.40ms
Compiler run successful!
Deployer: 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC // [$FUNDED_ADDRESS]
Deployed to: 0x5DB9A7629912EBF95876228C24A848de0bfB43A9 // [$RECEIVER_ADDRESS]
Transaction hash: 0xa8efb88abfef486d2caba30cb4146b1dc56a0ee88c7fb4c46adccdf1414ae39e

Save the Receiver address:

export RECEIVER_ADDRESS={your-receiver-address}

Call the Functions

Now you can call the sendAddMessage and sendConcatenateMessage functions on the sender contract and see the results on the receiver contract.

cast send --rpc-url fuji-c --private-key $PK $SENDER_ADDRESS "sendAddMessage(address, uint, uint)" $RECEIVER_ADDRESS 1 2

Verify the Result:

cast call --rpc-url fuji-dispatch $RECEIVER_ADDRESS "result_num()(uint)"
Loading...

Is this guide helpful?