Communication between multiple chains has become more and more important. Although the major security responsibility and trust are on the selected bridge protocol, bad cross-chain app implementation on top of these bridges can lead to catastrophic consequences. This guide covers the main threats, mistakes, and best practices for developing secure cross-chain apps on the Axelar protocol.
We will focus mainly on the application layer and gateway, but it is good to have basic knowledge of the whole architecture.
Axelar has deployed
AxelarGateway contracts on every chain, which acts as a middle layer between applications and Axelar network and provides communication between these layers. On the source chain, the gateway sends messages (as events) to validators and eventually burns/locks tokens. On the destination chain, the gateway is responsible for received message validations and eventually minting/unlocking tokens. Axelar network consensus is achieved by a set of validators using delegated proof-of-stake.
General Message Passing protocol
Axelar GMP is the fundamental feature of Axelar network, which provides developers an easy way to send tokens and call functions across supported chains. What does the message flow look like?
- Application on the source chain calls one of these gateway functions:
sendTokento send only tokens,
callContractto execute a
payloadon the destination chain,
callContractWithTokento send tokens and execute a call.
- Gateway emits an event with all parameters.
- Validators validate the message and notify the destination gateway.
- The execute function is called in the app on the destination chain.
Privileged clients (protocols) can be allowed by Axelar to use the Express version of the messaging protocol. A standard message can take several minutes for the Axelar network to fully approve the message and pass it to the final destination. The express message will still go through the Axelar network, but GMP Express Service will lend any sent tokens to the destination address while approval is happening. Once the complete approval is done, tokens are paid back to the GMP Express service. This process can speed up the cross-chain transfer more than ten times. The crucial part of GMP Express is complete trust between the privileged protocol and Axelar. The privileged protocol MUST implement the logic for token returns when a standard message is received. Otherwise, the privileged protocol will receive 2x more tokens. See
ExpressExecutable for more information.
How to start
Developing apps on Axelar is simple and straightforward, using Axelar GMP SDK. Just add @axelar-network/axelar-gmp-sdk-solidity dependency to your Solidity project, and you are good to go.
Application is AxelarExecutable
Every application built on Axelar Network has to inherit from
AxelarExecutable contract, which implements
IAxelarExecutable interface and handles received messages.
The following internal functions are intended to be overridden in the application to handle received messages or/and tokens. This is also a critical part of application security. Pay maximum attention to avoid any reentrancies, logic bugs, or miscalculations.
If the source chain calls
callContractWithToken function on the gateway, then in the app on the destination chain
_executeWithToken function gets called. Minting/unlocking tokens is handled automatically by
For sending messages, there is
IAxelarGateway the interface of Axelar gateway, which is used to send tokens and messages.
sendToken provides multichain token transfer. The destination chain is identified by string id, the destination address is also a string. Depending on the type of token (internal/external), it gets burned/locked on the source chain and minted/unlocked on the destination chain.
For sending the cross-chain message (payload), use
And if you need to send both (message and tokens), there is a
How to pay gas?
There are two ways to pay gas for cross-chain calls. The preferred one is
AxelarGasService contract, which works as a prepay on the source chain for transactions on the destination chain.
- Using AxelarJS SDK call
estimateGasFeefunction on the destination chain to calculate the gas fee.
- Pass the calculated gas fee as
msg.valuefrom the smart contract to
AxelarGasServicecontract on the source chain using one of these functions:
The user/app on the destination chain can also pay gas fees manually. For detailed information about paying gas fees, see this article in Axelar documentation.
Example cross-chain app
We’ve prepared a sample cross-chain app for sending messages and tokens through the Axelar network here on GitHub. The project also includes Woke tests.
For upgradable contracts, it’s recommended to inherit from Axelar’s
Upgradable contract, which implements
Then define a unique
contractId constant, which needs to stay the same across the contract versions. The
contractId is a hash of app/component name e.g.
keccak256("your-app"). During every upgrade, the
Upgradable implementation logic checks if the
contractId of the new implementation matches the
contractId of the Proxy. If not, the upgrade fails.
We implemented a detector for Woke static analyzer, which checks
contractId constant. It warns if a proxy for implementation is missing or if more than one proxy contract with the same
Things to be aware of
Besides the awareness of common vulnerabilities of Solidity/EVM, cross-chain app development brings more topics to be cautious about. Let’s dive deeper into the six most important ones.
Inherit from AxelarExecutable
AxelarExecutable contract contains important checks against the Axelar Gateway to validate contract calls. Bypassing these checks (e.g. implementing
IAxelarExecutable interface directly) could lead to critical vulnerabilities. Since the execute function is not protected by any modifier, everyone would be able to call it with any payload and execute any function on the destination chain.
So, ensure that your execute function validates the calls using this gateway function
validateContractCall, which checks that Axelar validators confirmed the message’s source and payload are valid.
A similar approach applies to
executeWithToken function, but using
Axelar components’ addresses
Axelar gateway is a fundamental part of the protocol security, so it’s very important to double-check the gateway address passed to the app during the deployment. A malicious gateway would be able to manipulate message checks. This is also important from the user’s perspective. Always check that the application uses the official Axelar gateway and Gas Service addresses.
Also, the application should not contain a gateway address setter. This would decrease the trust in the application. If the owner can control the gateway address, he also would be able to manipulate cross-chain messages in many ways.
Destination chain and address validations
Before sending the message to another chain, do data validations of
destinationAddress. Sending tokens to a non-existent chain or address means a loss of funds.
Validate the source address on the destination chain
It is important to avoid receiving messages from any source address. For this purpose, deploying the app to the same address on multiple chains is a good practice. Axelar provides for this purpose utils
Create3Deployer. Then, you can simply validate the source address in your
_executeWithToken functions using this condition.
Express GMP data validations
If you decide to use
ExpressExecutable contract, be extra careful of the receiving messages data validations. Functions
executeWithToken are external and aren’t protected by any modifier, so anyone can call them and potentially exploit the contract in many ways using malicious payload. Message validations using the Axelar gateway are also missing in the
ExpressExecutable. Keep this in mind and implement robust data validations here.
When implementing NFT cross-chain transfers, ensure the user’s NFT is properly locked in the contract and the user cannot own it on both chains simultaneously.
Static analysis, unit testing, and fuzzing
Contract development doesn’t end with programming itself. Quality assurance has to be your top priority when protocols operate with users’ funds or other valuables. There steps that development teams can and should perform internally before assigning a security audit.
Detecting possible vulnerabilities during the development using classic static analysis tools like Slither, MythX, Mythrill… can be helpful and also painful because of many false positive findings. Our Woke static analyzer goes deeper into this rabbit hole. Our target is to minimize false positive detections and achieve high precision while we accept a lower recall. Check out Tools For Solidity extension that shows the static analysis results directly in VSCode.
It is very important to test the application properly. Unit tests are useful for testing all use cases, and high test coverage is essential for basic security.
Since apps on Axelar operate on multiple chains, it brings additional complexity to the system, and cross-chain testing definitely should be part of a development pipeline. We recommend our easy-to-use testing framework Woke, for this purpose. It provides a complete set of tools for cross-chain testing and even fuzzing. See the documentation for more information. Also, don’t miss the article Testing Axelar contracts using open-source tools.
Unit and fuzz test coverage is also calculated by Woke and is displayed in the Tools For Solidity extension as well. In the case of fuzzing, the coverage is calculated realtime.
Fuzzing is a technique for testing software that involves providing invalid, unexpected, or random data as inputs to a computer program.
Unit tests mostly cover the system’s intended behavior and strictly defined use cases. On the other hand, fuzz tests can test a wide range of unpredictable, random scenarios and discover even hidden vulnerabilities like inconsistent calculations.
Axelar provides developers with a powerful platform and tools to develop cross-chain applications. Axelar cross-chain applications inherit the bridge’s security, but it doesn’t mean the applications are safe. There are still potential pitfalls during the implementation of the app logic. So even if the bridge is bulletproof, it doesn’t mean that the application is and that the funds of users are safe. Always do intense internal testing and independent external security audits to achieve high-security standards and one of the most important things in Web3 — community trust.