Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: addressed ackee's report #312

Merged
merged 16 commits into from
Jan 6, 2025
Merged
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 @@ -395,6 +395,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 @@ -646,26 +666,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 { IERC20BurnableFrom } from './interfaces/IERC20BurnableFrom.sol';

Foivos marked this conversation as resolved.
Show resolved Hide resolved
/**
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
* @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