logoAcademy

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 that 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.

src/2b-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:

src/2b-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({
                // Replace with chain id of your Subnet (see instructions in Readme)
                destinationBlockchainID: 0x92756d698399805f0088fc07fc42af47c67e1d38c576667ac6c7031b8df05293,
                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({
                destinationBlockchainID: 0x382d2a20c299b03b638dd4d42b96e7401f6c3e88209b764abce83cf71c0c30cd,
                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 paramters and the second time we encode the function name with the byte array containing paramters.

Decode the Function Name and Parameters

Let's now look at the receiver:

src/2b-invoking-functions/CalculatorReceiverOnSubnet.sol
pragma solidity ^0.8.18;
 
import "@teleporter/ITeleporterMessenger.sol";
import "@teleporter/ITeleporterReceiver.sol";
import "./CalculatorActions.sol";
 
contract CalculatorReceiverOnSubnet 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 Teleporter receiver can deliver a message.
        require(
            msg.sender == address(teleporterMessenger), "CalculatorReceiverOnSubnet: 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("CalculatorReceiverOnSubnet: 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 paramters

// 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("CalculatorReceiverOnSubnet: 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.

Deploy the Contracts

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

forge create --rpc-url local-c --private-key $PK src/2b-invoking-functions/CalculatorSenderOnCChain.sol:CalculatorSenderOnCChain

Save the sender address:

export SENDER_ADDRESS=0x123...

Deploy the receiver contract:

forge create --rpc-url mysubnet --private-key $PK src/2b-invoking-functions/CalculatorReceiverOnSubnet.sol:CalculatorReceiverOnSubnet

Save the receiver address:

export RECEIVER_ADDRESS=0x123...

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 local-c --private-key $PK $SENDER_ADDRESS "sendAddMessage(uint, uint)" 1 2

And verify the result:

cast call --rpc-url mysubnet $RECEIVER_ADDRESS "result_num()(uint)"
Updated:

On this page

Edit on Github