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 Avalanche L1 (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({
                // Replace with chain id of your Avalanche L1 (see instructions in Readme)
                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 Interchain Messaging 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.

Try it Out

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

Deploy the Sender Contract

Don't forget to replace BOTH destinationBlockchainIDs in CalculatorSenderOnCChain with the Blockchain ID (HEX) from your Avalanche L1!
forge create --rpc-url local-c --private-key $PK src/2b-invoking-functions/CalculatorSenderOnCChain.sol:CalculatorSenderOnCChain
[⠃] Compiling...
[⠆] Compiling 2 files with Solc 0.8.18
[⠰] Solc 0.8.18 finished in 240.23ms
Compiler run successful!
Deployer: 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC
Deployed to: 0x8B3BC4270BE2abbB25BC04717830bd1Cc493a461
Transaction hash: 0xf9cce28a714764bb265bba7522bfd10d620fa0cb0f5dae26de2ac773b0a878ee

Save the Sender Address:

export SENDER_ADDRESS=0x8B3BC4270BE2abbB25BC04717830bd1Cc493a461

Deploy the Receiver Contract:

forge create --rpc-url myblockchain --private-key $PK src/2b-invoking-functions/CalculatorReceiverOnSubnet.sol:CalculatorReceiverOnSubnet
[⠊] Compiling...
[⠢] Compiling 1 files with Solc 0.8.18
[⠆] Solc 0.8.18 finished in 148.40ms
Compiler run successful!
Deployer: 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC
Deployed to: 0x5DB9A7629912EBF95876228C24A848de0bfB43A9
Transaction hash: 0xa8efb88abfef486d2caba30cb4146b1dc56a0ee88c7fb4c46adccdf1414ae39e

Save the Receiver address:

export RECEIVER_ADDRESS=0x5DB9A7629912EBF95876228C24A848de0bfB43A9

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(address, uint, uint)" $RECEIVER_ADDRESS 1 2

Verify the Result:

cast call --rpc-url myblockchain $RECEIVER_ADDRESS "result_num()(uint)"

On this page