Axelar Executable

Overview

The Axelar Executable Contract is a component of the Axelar General Message Passing (GMP) flow, allowing the execution of custom logic in response to messages from different blockchains. By simply inheriting from the Axelar Executable your contract can process and respond to incoming cross-chain GMP data.

Integration

  1. Import AxelarExecutable from the Axelar GMP SDK to enable cross-chain capabilities.

    import "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol";
  2. Inherit Axelar Executable Functions:

    contract MyContract is AxelarExecutable {}
  3. Implement the virtual functions defined in Axelar Executable on your own contracts. The functions you implement on your own contract will be automatically triggered by an Axelar relayer on the destination chain once the multichain transaction arrives on the destination chain

GMP Message vs. GMP Message With Token

There are two relevant functions that can be overriden from AxelarExecutable. The function you want to override depends on whether your sending a GMP message or a GMP message WITH a Gateway Token. These two functions are _execute() and _executeWithToken()

/*** For GMP Message ***/
/**
@param sourceChain The chain where the GMP message is sent from
@param sourceAddress The address on where the GMP msg is sent from
@param payload The GMP message being sent
**/
function _execute(string calldata sourceChain, string calldata sourceAddress, bytes calldata payload) internal override {
// Implement your logic on the destination chain
string memory myGmpMessage = abi.decode(payload, (string));
}
/*** For GMP Message + Token ***/
/**
@param sourceChain The chain where the GMP message is sent from
@param sourceAddress The address on where the GMP msg is sent from
@param payload The GMP message being sent
@param tokenSymbol The token being sent
@param amount The amount of the token being sent
**/
function _executeWithToken(string calldata sourceChain, string calldata sourceAddress, bytes calldata payload, string calldata tokenSymbol, uint256 amount) internal override {
address memory receiver = abi.decode(payload, (address));
address tokenAddress = gateway.tokenAddresses(tokenSymbol);
IERC20(tokenAddress).transfer(receiver, amount);
}

Incoming Message Validation

The AxelarExecutable contract allows developers to override the _execute and the _executeWithToken() function on your own contract. However, the function that is actually triggered by the Axelar relayers are external execute() and executeWithToken() functions. Since these contracts are external with no relevant modifiers they can in theory be triggered by anyone. This can be a security vulnerability as your own contract’s implementation is dependent on the data being passed into the execute() function (which triggers in the internal _execute() function you’re overriding).

To solve this problem, AxelarExecutable triggers the Axelar Gateway’s validateContractCall() function. This function will validate that the incoming message was approved by the Axelar validators and return true if it was. It will then mark the message as a valid to ensure that it is not called more than once. Only once the message is marked as valid will your _execute() function be triggered.

By simply inheriting AxelarExecutable and overriding the _execute() function defined their you can be confident that the message you’re receiving is an authentic message from the Axelar network.

Upgradeable Contracts

If your contract is upgradeable then you may be unable to inherit from AxelarExecutable as it implements a constructor . To get around this issue you can simply call the external execute() function (or executeWithToken()). By simply making the execute() function available the relayer will still trigger this function on your contract and it will run the same way the internal _execute() function would if you were overriding from AxelarExecutable. The catch is that the validateContractCall() function will not be automatically implemented the way it would be if you were inheriting from AxelarExecutable. To get around this problem simply make sure you implement the validateContractCall() functionality yourself in your execute() function to ensure the authenticity of the incoming message. Once implemented your execute() function should look like this

function execute(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload
) external {
bytes32 payloadHash = keccak256(payload);
if (
!gateway.validateContractCall(
commandId,
sourceChain,
sourceAddress,
payloadHash
)
) revert NotApprovedByGateway();
//The rest of your implementation
}

Edit on GitHub