Skip to content

Commit

Permalink
fix(its)!: add more checks for its hub routing (#268)
Browse files Browse the repository at this point in the history
  • Loading branch information
milapsheth authored Aug 7, 2024
1 parent 124db7e commit 6e99005
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 68 deletions.
32 changes: 26 additions & 6 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ The messages going through the Axelar Network between `InterchainTokenServices`

### `INTERCHAIN_TRANSFER`

This message has the following data encoded and should only be sent after the proper tokens have been procured by the service. It should result in the proper funds being transferred to the user at the destionation chain.
This message is used to transfer tokens between chains. The tokens are handled appropriately on the source chain (lock/burn etc.), and then this message is sent to the ITS contract on the remote chain which sends the tokens to the destination address.

| Name | Type | Description |
| ------------------- | --------- | --------------------------------------------------------------------------------------------------------- |
| selector | `uint256` | Will always have a value of `0` |
| messageType | `uint256` | Will always have a value of `0` |
| tokenId | `bytes32` | The `interchainTokenId` of the token being transferred |
| source address | `bytes` | The address of the sender, encoded as bytes to account for different chain architectures |
| destination address | `bytes` | The address of the recipient, encoded as bytes as well |
Expand All @@ -60,11 +60,11 @@ This message has the following data encoded and should only be sent after the pr

### `DEPLOY_INTERCHAIN_TOKEN`

This message has the following data encoded and should only be sent after the `interchainTokenId` has been properly generated (a user should not be able to claim just any `interchainTokenId`)
This message is used to deploy an `InterchainToken` on a remote chain, that corresponds to the `tokenId` of a local ERC-20 token registered in ITS. This allows a user to deploy tokens to remote chains from a single source chain, instead of having to make a tx on each chain. It also allows the implementation on each chain to be flexible (e.g. `tokenId` derivation can be different on remote chains).

| Name | Type | Description |
| -------- | --------- | ----------------------------------------------------------------------------------------------------------------------- |
| selector | `uint256` | Will always have a value of `1` |
| messageType | `uint256` | Will always have a value of `1` |
| tokenId | `bytes32` | The `interchainTokenId` of the token being deployed |
| name | `string` | The name for the token |
| symbol | `string` | The symbol for the token |
Expand All @@ -73,11 +73,31 @@ This message has the following data encoded and should only be sent after the `i

### `DEPLOY_TOKEN_MANAGER`

This message has the following data encoded and should only be sent after the proper tokens have been procured by the service. It should result in the proper funds being transferred to the user at the destination chain.
This message is used to deploy a token manager on a remote chain, that corresponds to a local token manager. This is useful to link custom tokens via ITS with the same tokenId.

| Name | Type | Description |
| ------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| selector | `uint256` | Will always have a value of `2` |
| messageType | `uint256` | Will always have a value of `2` |
| tokenId | `bytes32` | The `interchainTokenId` of the token being deployed |
| token manager type | `uint256` | The type of the token manager, look at the [code](./contracts/interfaces/ITokenManagerType.sol) for details on EVM, but it could be different for different architectures |
| params | `bytes` | The parameters for the token manager deployments, look [here](./contracts/token-manager/TokenManager.sol#L179) for details on EVM chain parameters |

### `SEND_TO_HUB`

This message is used to route an ITS message via the ITS Hub. The ITS Hub applies certain security checks, and then routes it to the true destination chain. This mode is enabled if the trusted address corresponding to the destination chain is set to the ITS Hub identifier.

| Name | Type | Description |
| ------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| messageType | `uint256` | Will always have a value of `2` |
| destinationChain | `string` | The true destination chain for the ITS call |
| payload | `bytes` | The actual ITS message that's being routed through ITS Hub

### `RECEIVE_FROM_HUB`

This message is used to receive an ITS message from the ITS Hub. The ITS Hub applies certain security checks, and then routes it to the ITS contract. The message is accepted if the trusted address corresponding to the original source chain is set to the ITS Hub identifier.

| Name | Type | Description |
| ------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| messageType | `uint256` | Will always have a value of `2` |
| sourceChain | `string` | The original source chain for the ITS call |
| payload | `bytes` | The actual ITS message that's being routed through ITS Hub
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,31 @@ To build and run tests, do the following
npm ci

npm run build
```

## Test

```bash
npm run test
```

## Test Coverage Report
### Gas Report

Generate gas report

```bash
REPORT_GAS=true npm run test
```

### Coverage

Generate test coverage report

```bash
COVERAGE=true npm run test
```

For the most recent test coverage report of the `main` branch, please visit the following [page](https://axelarnetwork.github.io/interchain-token-service/).
Test coverage reports of the `main` branch can also be found on [Codecov](https://app.codecov.io/gh/axelarnetwork/interchain-token-service).

## Deployment Guide

Expand Down
134 changes: 79 additions & 55 deletions contracts/InterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,18 @@ contract InterchainTokenService is
uint32 internal constant LATEST_METADATA_VERSION = 1;

/**
* @dev Chain name for Axelar. This is used for routing ITS calls via ITS hub on Axelar.
* @dev Chain name where ITS Hub exists. This is used for routing ITS calls via ITS hub.
* This is set as a constant, since the ITS Hub will exist on Axelar.
*/
string internal constant AXELAR_CHAIN_NAME = 'Axelarnet';
bytes32 internal constant AXELAR_CHAIN_NAME_HASH = keccak256(abi.encodePacked(AXELAR_CHAIN_NAME));
string internal constant ITS_HUB_CHAIN_NAME = 'Axelarnet';
bytes32 internal constant ITS_HUB_CHAIN_NAME_HASH = keccak256(abi.encodePacked(ITS_HUB_CHAIN_NAME));

/**
* @dev Special trusted address value that indicates that the ITS call
* for that destination chain should be routed via the ITS hub.
* @dev Special identifier that the trusted address for a chain should be set to, which indicates if the ITS call
* for that chain should be routed via the ITS hub.
*/
bytes32 internal constant ITS_HUB_TRUSTED_ADDRESS_HASH = keccak256('hub');
string internal constant ITS_HUB_ROUTING_IDENTIFIER = 'hub';
bytes32 internal constant ITS_HUB_ROUTING_IDENTIFIER_HASH = keccak256(abi.encodePacked(ITS_HUB_ROUTING_IDENTIFIER));

/**
* @notice Constructor for the Interchain Token Service.
Expand Down Expand Up @@ -805,6 +807,8 @@ contract InterchainTokenService is

/**
* @notice Calls a contract on a specific destination chain with the given payload
* @dev This method also determines whether the ITS call should be routed via the ITS Hub.
* If the `trustedAddress(destinationChain) == 'hub'`, then the call is wrapped and routed to the ITS Hub destination.
* @param destinationChain The target chain where the contract will be called.
* @param payload The data payload for the transaction.
* @param gasValue The amount of gas to be paid for the transaction.
Expand All @@ -815,17 +819,9 @@ contract InterchainTokenService is
IGatewayCaller.MetadataVersion metadataVersion,
uint256 gasValue
) internal {
string memory destinationAddress = trustedAddress(destinationChain);

// Check if the ITS call should be routed via ITS hub for this destination chain
if (keccak256(abi.encodePacked(destinationAddress)) == ITS_HUB_TRUSTED_ADDRESS_HASH) {
// Wrap ITS message in an ITS Hub message
payload = abi.encode(MESSAGE_TYPE_SEND_TO_HUB, destinationChain, payload);
destinationChain = AXELAR_CHAIN_NAME;
destinationAddress = trustedAddress(AXELAR_CHAIN_NAME);
}
string memory destinationAddress;

if (bytes(destinationAddress).length == 0) revert UntrustedChain();
(destinationChain, destinationAddress, payload) = _getCallParams(destinationChain, payload);

(bool success, bytes memory returnData) = gatewayCaller.delegatecall(
abi.encodeWithSelector(
Expand Down Expand Up @@ -855,17 +851,9 @@ contract InterchainTokenService is
IGatewayCaller.MetadataVersion metadataVersion,
uint256 gasValue
) internal {
string memory destinationAddress = trustedAddress(destinationChain);
string memory destinationAddress;

// Check if the ITS call should be routed via ITS hub for this destination chain
if (keccak256(abi.encodePacked(destinationAddress)) == ITS_HUB_TRUSTED_ADDRESS_HASH) {
// Wrap ITS message in an ITS Hub message
payload = abi.encode(MESSAGE_TYPE_SEND_TO_HUB, destinationChain, payload);
destinationChain = AXELAR_CHAIN_NAME;
destinationAddress = trustedAddress(AXELAR_CHAIN_NAME);
}

if (bytes(destinationAddress).length == 0) revert UntrustedChain();
(destinationChain, destinationAddress, payload) = _getCallParams(destinationChain, payload);

(bool success, bytes memory returnData) = gatewayCaller.delegatecall(
abi.encodeWithSelector(
Expand All @@ -883,27 +871,42 @@ contract InterchainTokenService is
if (!success) revert GatewayCallFailed(returnData);
}

/**
* @dev Get the params for the cross-chain message, taking routing via ITS Hub into account.
*/
function _getCallParams(
string memory destinationChain,
bytes memory payload
) internal view returns (string memory, string memory, bytes memory) {
string memory destinationAddress = trustedAddress(destinationChain);

// Prevent sending directly to the ITS Hub chain. This is not supported yet, so fail early to prevent the user from having their funds stuck.
if (keccak256(abi.encodePacked(destinationChain)) == ITS_HUB_CHAIN_NAME_HASH) revert UntrustedChain();

// Check whether the ITS call should be routed via ITS hub for this destination chain
if (keccak256(abi.encodePacked(destinationAddress)) == ITS_HUB_ROUTING_IDENTIFIER_HASH) {
// Wrap ITS message in an ITS Hub message
payload = abi.encode(MESSAGE_TYPE_SEND_TO_HUB, destinationChain, payload);
destinationChain = ITS_HUB_CHAIN_NAME;
destinationAddress = trustedAddress(ITS_HUB_CHAIN_NAME);
}

// Check whether no trusted address was set for the destination chain
if (bytes(destinationAddress).length == 0) revert UntrustedChain();

return (destinationChain, destinationAddress, payload);
}

function _execute(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes memory payload,
bytes32 payloadHash
) internal {
// Read the first 32 bytes of the payload to determine the message type
uint256 messageType = _getMessageType(payload);
// True source chain, this is overridden if the ITS call is coming via the ITS hub
string memory originalSourceChain = sourceChain;

// Only allow routed call wrapping once
if (messageType == MESSAGE_TYPE_RECEIVE_FROM_HUB) {
if (keccak256(abi.encodePacked(sourceChain)) != AXELAR_CHAIN_NAME_HASH) revert UntrustedChain();

(, originalSourceChain, payload) = abi.decode(payload, (uint256, string, bytes));

// Get message type of the inner ITS message
messageType = _getMessageType(payload);
}
uint256 messageType;
string memory originalSourceChain;
(messageType, originalSourceChain, payload) = _getExecuteParams(sourceChain, payload);

if (messageType == MESSAGE_TYPE_INTERCHAIN_TRANSFER) {
address expressExecutor = _getExpressExecutorAndEmitEvent(commandId, sourceChain, sourceAddress, payloadHash);
Expand All @@ -930,20 +933,9 @@ contract InterchainTokenService is
if (!gateway.validateContractCallAndMint(commandId, sourceChain, sourceAddress, payloadHash, tokenSymbol, amount))
revert NotApprovedByGateway();

// Read the first 32 bytes of the payload to determine the message type
uint256 messageType = _getMessageType(payload);
// True source chain, this is overridden if the ITS call is coming via the ITS hub
string memory originalSourceChain = sourceChain;

// Unwrap ITS message if coming from ITS hub
if (messageType == MESSAGE_TYPE_RECEIVE_FROM_HUB) {
if (keccak256(abi.encodePacked(sourceChain)) != AXELAR_CHAIN_NAME_HASH) revert UntrustedChain();

(, originalSourceChain, payload) = abi.decode(payload, (uint256, string, bytes));

// Get message type of the inner ITS message
messageType = _getMessageType(payload);
}
uint256 messageType;
string memory originalSourceChain;
(messageType, originalSourceChain, payload) = _getExecuteParams(sourceChain, payload);

if (messageType != MESSAGE_TYPE_INTERCHAIN_TRANSFER) {
revert InvalidMessageType(messageType);
Expand All @@ -966,6 +958,38 @@ contract InterchainTokenService is
}
}

/**
* @dev Return the parameters for the execute call, taking routing via ITS Hub into account.
*/
function _getExecuteParams(
string calldata sourceChain,
bytes memory payload
) internal view returns (uint256, string memory, bytes memory) {
// Read the first 32 bytes of the payload to determine the message type
uint256 messageType = _getMessageType(payload);

// True source chain, this is overridden if the ITS call is coming via the ITS hub
string memory originalSourceChain = sourceChain;

// Unwrap ITS message if coming from ITS hub
if (messageType == MESSAGE_TYPE_RECEIVE_FROM_HUB) {
if (keccak256(abi.encodePacked(sourceChain)) != ITS_HUB_CHAIN_NAME_HASH) revert UntrustedChain();

(, originalSourceChain, payload) = abi.decode(payload, (uint256, string, bytes));

// Check whether the original source chain is expected to be routed via the ITS Hub
if (trustedAddressHash(originalSourceChain) != ITS_HUB_ROUTING_IDENTIFIER_HASH) revert UntrustedChain();

// Get message type of the inner ITS message
messageType = _getMessageType(payload);
} else {
// Prevent receiving a direct message from the ITS Hub. This is not supported yet.
if (keccak256(abi.encodePacked(sourceChain)) == ITS_HUB_CHAIN_NAME_HASH) revert UntrustedChain();
}

return (messageType, originalSourceChain, payload);
}

/**
* @notice Deploys a token manager on a destination chain.
* @param tokenId The ID of the token.
Expand All @@ -979,7 +1003,7 @@ contract InterchainTokenService is
string calldata destinationChain,
uint256 gasValue,
TokenManagerType tokenManagerType,
bytes memory params
bytes calldata params
) internal {
// slither-disable-next-line unused-return
validTokenManagerAddress(tokenId);
Expand Down
4 changes: 2 additions & 2 deletions contracts/interfaces/IGatewayCaller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface IGatewayCaller {
* @param destinationAddress The address of the contract to be called on the destination chain
* @param payload The data payload for the transaction
* @param metadataVersion The version of metadata to be used
* @param gasValue The amount of gas to be paid for the transaction
* @param gasValue The amount of gas to be paid for the cross-chain message. If this is 0, then gas payment is skipped. `msg.value` must be at least gasValue.
*/
function callContract(
string calldata destinationChain,
Expand All @@ -44,7 +44,7 @@ interface IGatewayCaller {
* @param symbol The symbol of the token to be sent
* @param amount The amount of tokens to be sent
* @param metadataVersion The version of metadata to be used
* @param gasValue The amount of gas to be paid for the transaction
* @param gasValue The amount of gas to be paid for the cross-chain message. If this is 0, then gas payment is skipped. `msg.value` must be at least gasValue.
*/
function callContractWithToken(
string calldata destinationChain,
Expand Down
Loading

0 comments on commit 6e99005

Please sign in to comment.