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.
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:
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:
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)"
Is this guide helpful?