Skip to content

Commit

Permalink
Merge branch 'feat/token-manager-mint-interchain-tokens-2' into feat/…
Browse files Browse the repository at this point in the history
…manual-migrate-tokens
  • Loading branch information
milapsheth authored Jan 7, 2025
2 parents 8e0c290 + af995b8 commit 4c7e551
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 45 deletions.
44 changes: 41 additions & 3 deletions contracts/InterchainTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
* @notice Deploys a new interchain token with specified parameters.
* @dev Creates a new token and optionally mints an initial amount to a specified minter.
* This function is `payable` because non-payable functions cannot be called in a multicall that calls other `payable` functions.
* Cannot deploy tokens with empty supply and no minter.
* @param salt The unique salt for deploying the token.
* @param name The name of the token.
* @param symbol The symbol of the token.
Expand Down Expand Up @@ -149,6 +150,8 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
if (minter == address(interchainTokenService)) revert InvalidMinter(minter);

minterBytes = minter.toBytes();
} else {
revert ZeroSupplyToken();
}

tokenId = _deployInterchainToken(deploySalt, currentChain, name, symbol, decimals, minterBytes, gasValue);
Expand Down Expand Up @@ -381,12 +384,14 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
bytes memory minter,
uint256 gasValue
) internal returns (bytes32 tokenId) {
// Ensure that a token is registered locally for the tokenId before allowing a remote deployment
bytes32 expectedTokenId = _interchainTokenId(deploySalt);
// Ensure that a local token has been registered for the tokenId
IERC20Named token = IERC20Named(interchainTokenService.registeredTokenAddress(expectedTokenId));
address tokenAddress = interchainTokenService.registeredTokenAddress(expectedTokenId);

// The local token must expose the name, symbol, and decimals metadata
tokenId = _deployInterchainToken(deploySalt, destinationChain, token.name(), token.symbol(), token.decimals(), minter, gasValue);
(string memory name, string memory symbol, uint8 decimals) = _getTokenMetadata(tokenAddress);

tokenId = _deployInterchainToken(deploySalt, destinationChain, name, symbol, decimals, minter, gasValue);
if (tokenId != expectedTokenId) revert InvalidTokenId(tokenId, expectedTokenId);
}

Expand All @@ -402,9 +407,42 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
string memory currentChain = '';
uint256 gasValue = 0;

// Ensure that the ERC20 token has metadata before registering it
// slither-disable-next-line unused-return
_getTokenMetadata(tokenAddress);

tokenId = interchainTokenService.deployTokenManager(deploySalt, currentChain, TokenManagerType.LOCK_UNLOCK, params, gasValue);
}

/**
* @notice Retrieves the metadata of an ERC20 token. Reverts with `NotToken` error if metadata is not available.
* @param tokenAddress The address of the token.
* @return name The name of the token.
* @return symbol The symbol of the token.
* @return decimals The number of decimals for the token.
*/
function _getTokenMetadata(address tokenAddress) internal view returns (string memory name, string memory symbol, uint8 decimals) {
IERC20Named token = IERC20Named(tokenAddress);

try token.name() returns (string memory name_) {
name = name_;
} catch {
revert NotToken(tokenAddress);
}

try token.symbol() returns (string memory symbol_) {
symbol = symbol_;
} catch {
revert NotToken(tokenAddress);
}

try token.decimals() returns (uint8 decimals_) {
decimals = decimals_;
} catch {
revert NotToken(tokenAddress);
}
}

/**
* @notice Deploys a canonical interchain token on a remote chain.
* @param originalTokenAddress The address of the original token on the original chain.
Expand Down
40 changes: 20 additions & 20 deletions contracts/InterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,26 @@ contract InterchainTokenService is
return _contractCallValue(payload);
}

/**
* @notice Executes the cross-chain ITS message.
* @param commandId The unique message id.
* @param sourceChain The chain where the transaction originates from.
* @param sourceAddress The address of the remote ITS where the transaction originates from.
* @param payload The encoded data payload for the transaction.
*/
function execute(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload
) external onlyRemoteService(sourceChain, sourceAddress) whenNotPaused {
bytes32 payloadHash = keccak256(payload);

if (!gateway.validateContractCall(commandId, sourceChain, sourceAddress, payloadHash)) revert NotApprovedByGateway();

_execute(commandId, sourceChain, sourceAddress, payload, payloadHash);
}

/**
* @notice Express executes operations based on the payload and selector.
* @dev This function is `payable` because non-payable functions cannot be called in a multicall that calls other `payable` functions.
Expand Down Expand Up @@ -670,26 +690,6 @@ contract InterchainTokenService is
}
}

/**
* @notice Executes operations based on the payload and selector.
* @param commandId The unique message id.
* @param sourceChain The chain where the transaction originates from.
* @param sourceAddress The address of the remote ITS where the transaction originates from.
* @param payload The encoded data payload for the transaction.
*/
function execute(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload
) external onlyRemoteService(sourceChain, sourceAddress) whenNotPaused {
bytes32 payloadHash = keccak256(payload);

if (!gateway.validateContractCall(commandId, sourceChain, sourceAddress, payloadHash)) revert NotApprovedByGateway();

_execute(commandId, sourceChain, sourceAddress, payload, payloadHash);
}

/**
* @notice Processes the payload data for a send token call.
* @param commandId The unique message id.
Expand Down
5 changes: 2 additions & 3 deletions contracts/TokenHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.0;

import { ITokenHandler } from './interfaces/ITokenHandler.sol';
import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol';
import { SafeTokenTransfer, SafeTokenTransferFrom, SafeTokenCall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol';
import { SafeTokenTransferFrom, SafeTokenCall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol';
import { ReentrancyGuard } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/ReentrancyGuard.sol';
import { Create3AddressFixed } from './utils/Create3AddressFixed.sol';

Expand All @@ -16,12 +16,11 @@ import { IMinter } from './interfaces/IMinter.sol';

/**
* @title TokenHandler
* @notice This interface is responsible for handling tokens before initiating an interchain token transfer, or after receiving one.
* @notice This contract is responsible for handling tokens before initiating an interchain token transfer, or after receiving one.
*/
contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Create3AddressFixed {
using SafeTokenTransferFrom for IERC20;
using SafeTokenCall for IERC20;
using SafeTokenTransfer for IERC20;

/**
* @notice This function gives token to a specified address from the token manager.
Expand Down
4 changes: 2 additions & 2 deletions contracts/interfaces/IInterchainTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ interface IInterchainTokenFactory is IUpgradable, IMulticall {
error InvalidChainName();
error InvalidMinter(address minter);
error NotMinter(address minter);
error NotOperator(address operator);
error NotServiceOwner(address sender);
error NotSupported();
error RemoteDeploymentNotApproved();
error InvalidTokenId(bytes32 tokenId, bytes32 expectedTokenId);
error ZeroSupplyToken();
error NotToken(address tokenAddress);

/// @notice Emitted when a minter approves a deployer for a remote interchain token deployment that uses a custom destinationMinter address.
event DeployRemoteInterchainTokenApproval(
Expand Down
1 change: 0 additions & 1 deletion contracts/interfaces/IInterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ interface IInterchainTokenService is
IAddressTracker,
IUpgradable
{
error InvalidTokenManagerImplementationType(address implementation);
error InvalidChainName();
error NotRemoteService();
error TokenManagerDoesNotExist(bytes32 tokenId);
Expand Down
1 change: 0 additions & 1 deletion contracts/interfaces/ITokenManagerDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ pragma solidity ^0.8.0;
* @notice This interface is used to deploy new instances of the TokenManagerProxy contract.
*/
interface ITokenManagerDeployer {
error AddressZero();
error TokenManagerDeploymentFailed();

/**
Expand Down
4 changes: 3 additions & 1 deletion contracts/types/InterchainTokenServiceTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ pragma solidity ^0.8.0;
enum MessageType {
INTERCHAIN_TRANSFER,
DEPLOY_INTERCHAIN_TOKEN,
DEPLOY_TOKEN_MANAGER
DEPLOY_TOKEN_MANAGER,
SEND_TO_HUB,
RECEIVE_FROM_HUB
}

struct InterchainTransfer {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 16 additions & 12 deletions test/InterchainTokenFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ describe('InterchainTokenFactory', () => {
.withArgs(tokenId, tokenManagerAddress, LOCK_UNLOCK, params);
});

it('Should not register a non-existing token', async () => {
await expectRevert(
(gasOptions) => tokenFactory.registerCanonicalInterchainToken(tokenFactory.address, { gasOptions }),
tokenFactory,
'NotToken',
[tokenFactory.address],
);
});

it('Should initiate a remote interchain token deployment with no original chain name provided', async () => {
const gasValue = 1234;
const payload = defaultAbiCoder.encode(
Expand Down Expand Up @@ -232,21 +241,16 @@ describe('InterchainTokenFactory', () => {
await checkRoles(tokenManager, minter);
});

it('Should register a token if the mint amount is zero and minter is the zero address', async () => {
it('Should revert when trying to register a token if the mint amount is zero and minter is the zero address', async () => {
const salt = keccak256('0x123456');
tokenId = await tokenFactory.interchainTokenId(wallet.address, salt);
const tokenAddress = await service.interchainTokenAddress(tokenId);
const minterBytes = new Uint8Array();
const params = defaultAbiCoder.encode(['bytes', 'address'], [minterBytes, tokenAddress]);
const tokenManager = await getContractAt('TokenManager', await service.tokenManagerAddress(tokenId), wallet);

await expect(tokenFactory.deployInterchainToken(salt, name, symbol, decimals, 0, AddressZero))
.to.emit(service, 'InterchainTokenDeployed')
.withArgs(tokenId, tokenAddress, AddressZero, name, symbol, decimals)
.and.to.emit(service, 'TokenManagerDeployed')
.withArgs(tokenId, tokenManager.address, NATIVE_INTERCHAIN_TOKEN, params);

await checkRoles(tokenManager, AddressZero);
await expectRevert(
(gasOptions) => tokenFactory.deployInterchainToken(salt, name, symbol, decimals, 0, AddressZero, { gasOptions }),
tokenFactory,
'ZeroSupplyToken',
[],
);
});

it('Should register a token if the mint amount is greater than zero and the minter is the zero address', async () => {
Expand Down

0 comments on commit 4c7e551

Please sign in to comment.