From a1720a981ba251bff4a9c7954321c9e268b9efed Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Sun, 17 Dec 2023 19:00:37 -0500 Subject: [PATCH 01/16] fix: add missing imports to factory interface (#240) (#243) * fix: add missing imports to factory interface * update docs --- contracts/InterchainTokenFactory.sol | 40 +++++++------ .../interfaces/IInterchainTokenFactory.sol | 13 ++++- .../interfaces/IInterchainTokenService.sol | 8 ++- docs/index.md | 56 +++++++++++++++++-- package-lock.json | 4 +- package.json | 2 +- 6 files changed, 95 insertions(+), 28 deletions(-) diff --git a/contracts/InterchainTokenFactory.sol b/contracts/InterchainTokenFactory.sol index cb9f2e19..23106863 100644 --- a/contracts/InterchainTokenFactory.sol +++ b/contracts/InterchainTokenFactory.sol @@ -25,7 +25,7 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M using SafeTokenTransferFrom for IInterchainToken; using SafeTokenCall for IInterchainToken; - IInterchainTokenService public immutable service; + IInterchainTokenService public immutable interchainTokenService; bytes32 public immutable chainNameHash; IAxelarGateway public immutable gateway; @@ -36,14 +36,15 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M /** * @notice Constructs the InterchainTokenFactory contract. - * @param interchainTokenServiceAddress The address of the interchain token service. + * @param interchainTokenService_ The address of the interchain token service. */ - constructor(address interchainTokenServiceAddress) { - if (interchainTokenServiceAddress == address(0)) revert ZeroAddress(); - service = IInterchainTokenService(interchainTokenServiceAddress); + constructor(address interchainTokenService_) { + if (interchainTokenService_ == address(0)) revert ZeroAddress(); - chainNameHash = service.chainNameHash(); - gateway = service.gateway(); + interchainTokenService = IInterchainTokenService(interchainTokenService_); + + chainNameHash = interchainTokenService.chainNameHash(); + gateway = interchainTokenService.gateway(); } /** @@ -82,7 +83,7 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M * @return tokenId The ID of the interchain token. */ function interchainTokenId(address deployer, bytes32 salt) public view returns (bytes32 tokenId) { - tokenId = service.interchainTokenId(TOKEN_FACTORY_DEPLOYER, interchainTokenSalt(chainNameHash, deployer, salt)); + tokenId = interchainTokenService.interchainTokenId(TOKEN_FACTORY_DEPLOYER, interchainTokenSalt(chainNameHash, deployer, salt)); } /** @@ -91,7 +92,10 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M * @return tokenId The ID of the canonical interchain token. */ function canonicalInterchainTokenId(address tokenAddress) public view returns (bytes32 tokenId) { - tokenId = service.interchainTokenId(TOKEN_FACTORY_DEPLOYER, canonicalInterchainTokenSalt(chainNameHash, tokenAddress)); + tokenId = interchainTokenService.interchainTokenId( + TOKEN_FACTORY_DEPLOYER, + canonicalInterchainTokenSalt(chainNameHash, tokenAddress) + ); } /** @@ -101,7 +105,7 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M * @return tokenAddress The address of the interchain token. */ function interchainTokenAddress(address deployer, bytes32 salt) public view returns (address tokenAddress) { - tokenAddress = service.interchainTokenAddress(interchainTokenId(deployer, salt)); + tokenAddress = interchainTokenService.interchainTokenAddress(interchainTokenId(deployer, salt)); } /** @@ -136,8 +140,8 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M tokenId = _deployInterchainToken(salt, '', name, symbol, decimals, minterBytes, 0); if (initialSupply > 0) { - IInterchainToken token = IInterchainToken(service.interchainTokenAddress(tokenId)); - ITokenManager tokenManager = ITokenManager(service.tokenManagerAddress(tokenId)); + IInterchainToken token = IInterchainToken(interchainTokenService.interchainTokenAddress(tokenId)); + ITokenManager tokenManager = ITokenManager(interchainTokenService.tokenManagerAddress(tokenId)); token.mint(sender, initialSupply); @@ -182,9 +186,9 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M address sender = msg.sender; salt = interchainTokenSalt(chainNameHash_, sender, salt); - tokenId = service.interchainTokenId(TOKEN_FACTORY_DEPLOYER, salt); + tokenId = interchainTokenService.interchainTokenId(TOKEN_FACTORY_DEPLOYER, salt); - IInterchainToken token = IInterchainToken(service.interchainTokenAddress(tokenId)); + IInterchainToken token = IInterchainToken(interchainTokenService.interchainTokenAddress(tokenId)); tokenName = token.name(); tokenSymbol = token.symbol(); @@ -221,7 +225,7 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M uint256 gasValue ) internal returns (bytes32 tokenId) { // slither-disable-next-line arbitrary-send-eth - tokenId = service.deployInterchainToken{ value: gasValue }( + tokenId = interchainTokenService.deployInterchainToken{ value: gasValue }( salt, destinationChain, tokenName, @@ -243,7 +247,7 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M if (_isGatewayToken(tokenAddress)) revert GatewayToken(tokenAddress); bytes32 salt = canonicalInterchainTokenSalt(chainNameHash, tokenAddress); - tokenId = service.deployTokenManager(salt, '', TokenManagerType.LOCK_UNLOCK, params, 0); + tokenId = interchainTokenService.deployTokenManager(salt, '', TokenManagerType.LOCK_UNLOCK, params, 0); } /** @@ -272,8 +276,8 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M } // This ensures that the token manager has been deployed by this address, so it's safe to trust it. salt = canonicalInterchainTokenSalt(chainNameHash_, originalTokenAddress); - tokenId = service.interchainTokenId(TOKEN_FACTORY_DEPLOYER, salt); - token = IInterchainToken(service.validTokenAddress(tokenId)); + tokenId = interchainTokenService.interchainTokenId(TOKEN_FACTORY_DEPLOYER, salt); + token = IInterchainToken(interchainTokenService.validTokenAddress(tokenId)); } // The 3 lines below will revert if the token does not exist. diff --git a/contracts/interfaces/IInterchainTokenFactory.sol b/contracts/interfaces/IInterchainTokenFactory.sol index 6fc48eb7..3e792036 100644 --- a/contracts/interfaces/IInterchainTokenFactory.sol +++ b/contracts/interfaces/IInterchainTokenFactory.sol @@ -2,17 +2,28 @@ pragma solidity ^0.8.0; +import { IMulticall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IMulticall.sol'; +import { IUpgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IUpgradable.sol'; + +import { IInterchainTokenService } from './IInterchainTokenService.sol'; + /** * @title IInterchainTokenFactory Interface * @notice This interface defines functions for deploying new interchain tokens and managing their token managers. */ -interface IInterchainTokenFactory { +interface IInterchainTokenFactory is IUpgradable, IMulticall { error ZeroAddress(); error InvalidChainName(); error NotMinter(address minter); error NotOperator(address operator); error GatewayToken(address tokenAddress); + /** + * @notice Returns the address of the interchain token service. + * @return IInterchainTokenService The address of the interchain token service. + */ + function interchainTokenService() external view returns (IInterchainTokenService); + /** * @notice Returns the hash of the chain name. * @return bytes32 The hash of the chain name. diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index d3199f62..1e29b194 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.0; import { IAxelarValuedExpressExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarValuedExpressExecutable.sol'; -import { IContractIdentifier } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IContractIdentifier.sol'; import { IMulticall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IMulticall.sol'; import { IPausable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IPausable.sol'; import { IUpgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IUpgradable.sol'; @@ -26,7 +25,6 @@ interface IInterchainTokenService is IOperator, IPausable, IMulticall, - IContractIdentifier, IAddressTracker, IUpgradable { @@ -115,6 +113,12 @@ interface IInterchainTokenService is */ function tokenHandler() external view returns (address tokenHandlerAddress); + /** + * @notice Returns the address of the interchain token factory. + * @return address The address of the interchain token factory. + */ + function interchainTokenFactory() external view returns (address); + /** * @notice Returns the hash of the chain name. * @return bytes32 The hash of the chain name. diff --git a/docs/index.md b/docs/index.md index 747bbf51..28d3a181 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,12 +4,19 @@ This contract is responsible for deploying new interchain tokens and managing their token managers. -### service +### interchainTokenService ```solidity -contract IInterchainTokenService service +contract IInterchainTokenService interchainTokenService ``` +Returns the address of the interchain token service. + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | + ### chainNameHash ```solidity @@ -44,7 +51,7 @@ bytes32 PREFIX_INTERCHAIN_TOKEN_SALT ### constructor ```solidity -constructor(address interchainTokenServiceAddress) public +constructor(address interchainTokenService_) public ``` Constructs the InterchainTokenFactory contract. @@ -53,7 +60,7 @@ Constructs the InterchainTokenFactory contract. | Name | Type | Description | | ---- | ---- | ----------- | -| interchainTokenServiceAddress | address | The address of the interchain token service. | +| interchainTokenService_ | address | The address of the interchain token service. | ### contractId @@ -340,6 +347,13 @@ contract IAxelarGasService gasService address interchainTokenFactory ``` +Returns the address of the interchain token factory. + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | + ### chainNameHash ```solidity @@ -2270,6 +2284,12 @@ error TokenIdZero() error TokenNameEmpty() ``` +### TokenSymbolEmpty + +```solidity +error TokenSymbolEmpty() +``` + ### AlreadyInitialized ```solidity @@ -2499,6 +2519,20 @@ error NotOperator(address operator) error GatewayToken(address tokenAddress) ``` +### interchainTokenService + +```solidity +function interchainTokenService() external view returns (contract IInterchainTokenService) +``` + +Returns the address of the interchain token service. + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | contract IInterchainTokenService | IInterchainTokenService The address of the interchain token service. | + ### chainNameHash ```solidity @@ -2920,6 +2954,20 @@ Returns the address of TokenHandler implementation. | ---- | ---- | ----------- | | tokenHandlerAddress | address | The address of the token handler contract. | +### interchainTokenFactory + +```solidity +function interchainTokenFactory() external view returns (address) +``` + +Returns the address of the interchain token factory. + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | address | address The address of the interchain token factory. | + ### chainNameHash ```solidity diff --git a/package-lock.json b/package-lock.json index 3a18d0ac..8ccef5b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@axelar-network/interchain-token-service", - "version": "1.2.0", + "version": "1.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@axelar-network/interchain-token-service", - "version": "1.2.0", + "version": "1.2.1", "license": "MIT", "dependencies": { "@axelar-network/axelar-cgp-solidity": "6.2.1", diff --git a/package.json b/package.json index 79a1b2b2..0a3ba423 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@axelar-network/interchain-token-service", - "version": "1.2.0", + "version": "1.2.1", "repository": { "type": "git", "url": "https://github.com/axelarnetwork/interchain-token-service" From 50f946775b37b93f05bdc19a6e7354d02e78f766 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Sun, 17 Dec 2023 19:01:47 -0500 Subject: [PATCH 02/16] ci: backport ci fixes (#244) * ci: use env var setter in gh actions (#241) * fix: add missing imports to factory interface * ci: use env var setter in gh actions * ci: improve var reference in workflow (#242) * test: action * direct trigger * rename * improve var reference --- .github/workflows/publish-bytecode.yaml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-bytecode.yaml b/.github/workflows/publish-bytecode.yaml index f9c51bb3..cff05355 100644 --- a/.github/workflows/publish-bytecode.yaml +++ b/.github/workflows/publish-bytecode.yaml @@ -1,4 +1,4 @@ -name: Publish Compiled Bytecode +name: 'Publish Bytecode' on: release: @@ -35,15 +35,20 @@ jobs: - name: Clean artifacts run: npm run clean:artifacts + - name: Get release tag + run: | + TAG=${{ github.ref }} + echo "release_tag=${TAG#refs/tags/}" >> "$GITHUB_ENV" + - name: Create zip file working-directory: ./artifacts run: | - zip -r Bytecode-${GITHUB_REF#refs/tags/}.zip * + zip -r Bytecode-${{ release_tag }}.zip * - name: Upload Bytecode to release uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ./artifacts/Bytecode-${GITHUB_REF#refs/tags/}.zip - tag: ${GITHUB_REF#refs/tags/} + file: ./artifacts/Bytecode-${{ release_tag }}.zip + tag: ${{ release_tag }} overwrite: true From 471d60de22ca2687b414969e1b7a43a79d245f65 Mon Sep 17 00:00:00 2001 From: axelar-cicd-bot Date: Mon, 18 Dec 2023 00:03:08 +0000 Subject: [PATCH 03/16] v1.2.1 From 6add3e85636d8800082ff3740f3151524f151e9b Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 15 Mar 2024 01:26:47 -0400 Subject: [PATCH 04/16] chore: restrict custom token manager deployments (#2) --- contracts/InterchainTokenService.sol | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 4890ff16..08d08671 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -282,7 +282,11 @@ contract InterchainTokenService is ) external payable whenNotPaused returns (bytes32 tokenId) { address deployer = msg.sender; - if (deployer == interchainTokenFactory) deployer = TOKEN_FACTORY_DEPLOYER; + if (deployer == interchainTokenFactory) { + deployer = TOKEN_FACTORY_DEPLOYER; + } else { + revert(''); + } tokenId = interchainTokenId(deployer, salt); @@ -733,15 +737,9 @@ contract InterchainTokenService is /** * @notice Processes a deploy token manager payload. - * @param payload The encoded data payload to be processed */ - function _processDeployTokenManagerPayload(bytes calldata payload) internal { - (, bytes32 tokenId, TokenManagerType tokenManagerType, bytes memory params) = abi.decode( - payload, - (uint256, bytes32, TokenManagerType, bytes) - ); - - _deployTokenManager(tokenId, tokenManagerType, params); + function _processDeployTokenManagerPayload(bytes calldata) internal pure { + revert(''); } /** From 68c7793e204f8c52ba49160d60b81dd3a69d563a Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Sun, 24 Mar 2024 02:30:55 -0400 Subject: [PATCH 05/16] chore: bump version to v1.2.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ccef5b3..78d9b7e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@axelar-network/interchain-token-service", - "version": "1.2.1", + "version": "1.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@axelar-network/interchain-token-service", - "version": "1.2.1", + "version": "1.2.2", "license": "MIT", "dependencies": { "@axelar-network/axelar-cgp-solidity": "6.2.1", diff --git a/package.json b/package.json index 0a3ba423..d7d3067b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@axelar-network/interchain-token-service", - "version": "1.2.1", + "version": "1.2.2", "repository": { "type": "git", "url": "https://github.com/axelarnetwork/interchain-token-service" From 608c126b94caf91374a02ea6eddea9cf011933c9 Mon Sep 17 00:00:00 2001 From: axelar-cicd-bot Date: Sun, 24 Mar 2024 06:36:27 +0000 Subject: [PATCH 06/16] v1.2.2 From 8342aaf07d81f18f316e82be662aaf9fefab9e6c Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Sun, 24 Mar 2024 02:36:29 -0400 Subject: [PATCH 07/16] chore: bump version to v1.2.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 78d9b7e1..618dd2fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@axelar-network/interchain-token-service", - "version": "1.2.2", + "version": "1.2.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@axelar-network/interchain-token-service", - "version": "1.2.2", + "version": "1.2.3", "license": "MIT", "dependencies": { "@axelar-network/axelar-cgp-solidity": "6.2.1", diff --git a/package.json b/package.json index d7d3067b..1b66d70f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@axelar-network/interchain-token-service", - "version": "1.2.2", + "version": "1.2.3", "repository": { "type": "git", "url": "https://github.com/axelarnetwork/interchain-token-service" From 78173e3cc8fe33170a1ee15d56efda43abb6b00c Mon Sep 17 00:00:00 2001 From: axelar-cicd-bot Date: Sun, 24 Mar 2024 06:38:42 +0000 Subject: [PATCH 08/16] v1.2.3 From 023083e0ff6dfa498284b77c94958376ce4f5811 Mon Sep 17 00:00:00 2001 From: Foivos Date: Sun, 24 Mar 2024 08:42:59 +0200 Subject: [PATCH 09/16] feat: added new token managers that have the mint/burn permissions. (#1) * added new token managers that have the mint/burn permissions. * added a few tests * a few more tests * removed mint/burn from legacy * rename INTERCHAIN_TOKEN to INTERCHAIN_TOKEN_MINT_BURN * remove unused import and prettier * some more renaming * rever * added some docstrings * prettier * chore: restrict custom token manager deployments (#3) * some styling * fixed revert * fixed some docs * bump package version * restrict remote token manager deployment * rename MINT_BURN_FROM to CUSTOM_MINT_BURN_FROM * prettier * add comments to TokenManagerType * rename token manager types * update docs * refactor(TokenManager): simplification (#4) * refactor(TokenManager): simplification * refactor(TokenManager): unused import * update doc --------- Co-authored-by: Milap Sheth Co-authored-by: re1ro --- contracts/InterchainTokenService.sol | 22 ++- contracts/TokenHandler.sol | 32 +++- .../interfaces/IInterchainTokenService.sol | 3 +- contracts/interfaces/ITokenManager.sol | 18 +++ contracts/interfaces/ITokenManagerType.sol | 9 +- .../test/TestInterchainTokenStandard.sol | 2 +- contracts/token-manager/TokenManager.sol | 23 +++ docs/index.md | 117 +++++++++++++-- package-lock.json | 16 +- package.json | 2 +- test/InterchainTokenFactory.js | 14 +- test/InterchainTokenService.js | 139 ++++++++++++++---- test/InterchainTokenServiceFullFlow.js | 14 +- test/InterchainTokenServiceUpgradeFlow.js | 6 +- test/TokenManager.js | 18 +++ 15 files changed, 344 insertions(+), 91 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 08d08671..905a633d 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -268,7 +268,7 @@ contract InterchainTokenService is * part of a multicall involving multiple functions that could make remote contract calls. * @param salt The salt to be used during deployment. * @param destinationChain The name of the chain to deploy the TokenManager and standardized token to. - * @param tokenManagerType The type of TokenManager to be deployed. + * @param tokenManagerType The type of token manager to be deployed. Cannot be NATIVE_INTERCHAIN_TOKEN. * @param params The params that will be used to initialize the TokenManager. * @param gasValue The amount of native tokens to be used to pay for gas for the remote deployment. * @return tokenId The tokenId corresponding to the deployed TokenManager. @@ -280,12 +280,13 @@ contract InterchainTokenService is bytes calldata params, uint256 gasValue ) external payable whenNotPaused returns (bytes32 tokenId) { + // Custom token managers can't be deployed with Interchain token mint burn type, which is reserved for interchain tokens + if (tokenManagerType == TokenManagerType.NATIVE_INTERCHAIN_TOKEN) revert CannotDeploy(tokenManagerType); + address deployer = msg.sender; if (deployer == interchainTokenFactory) { deployer = TOKEN_FACTORY_DEPLOYER; - } else { - revert(''); } tokenId = interchainTokenId(deployer, salt); @@ -331,7 +332,7 @@ contract InterchainTokenService is if (bytes(destinationChain).length == 0) { address tokenAddress = _deployInterchainToken(tokenId, minter, name, symbol, decimals); - _deployTokenManager(tokenId, TokenManagerType.MINT_BURN, abi.encode(minter, tokenAddress)); + _deployTokenManager(tokenId, TokenManagerType.NATIVE_INTERCHAIN_TOKEN, abi.encode(minter, tokenAddress)); } else { _deployRemoteInterchainToken(tokenId, name, symbol, decimals, minter, destinationChain, gasValue); } @@ -738,8 +739,15 @@ contract InterchainTokenService is /** * @notice Processes a deploy token manager payload. */ - function _processDeployTokenManagerPayload(bytes calldata) internal pure { - revert(''); + function _processDeployTokenManagerPayload(bytes calldata payload) internal { + (, bytes32 tokenId, TokenManagerType tokenManagerType, bytes memory params) = abi.decode( + payload, + (uint256, bytes32, TokenManagerType, bytes) + ); + + if (tokenManagerType == TokenManagerType.NATIVE_INTERCHAIN_TOKEN) revert CannotDeploy(tokenManagerType); + + _deployTokenManager(tokenId, tokenManagerType, params); } /** @@ -755,7 +763,7 @@ contract InterchainTokenService is tokenAddress = _deployInterchainToken(tokenId, minterBytes, name, symbol, decimals); - _deployTokenManager(tokenId, TokenManagerType.MINT_BURN, abi.encode(minterBytes, tokenAddress)); + _deployTokenManager(tokenId, TokenManagerType.NATIVE_INTERCHAIN_TOKEN, abi.encode(minterBytes, tokenAddress)); } /** diff --git a/contracts/TokenHandler.sol b/contracts/TokenHandler.sol index 0eee208f..7f9da032 100644 --- a/contracts/TokenHandler.sol +++ b/contracts/TokenHandler.sol @@ -8,6 +8,7 @@ import { SafeTokenTransferFrom, SafeTokenCall } from '@axelar-network/axelar-gmp import { ReentrancyGuard } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/ReentrancyGuard.sol'; import { ITokenManagerType } from './interfaces/ITokenManagerType.sol'; +import { ITokenManager } from './interfaces/ITokenManager.sol'; import { IERC20MintableBurnable } from './interfaces/IERC20MintableBurnable.sol'; import { IERC20BurnableFrom } from './interfaces/IERC20BurnableFrom.sol'; @@ -36,8 +37,13 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard { address to, uint256 amount ) external payable returns (uint256) { + if (tokenManagerType == uint256(TokenManagerType.NATIVE_INTERCHAIN_TOKEN)) { + _giveInterchainToken(tokenAddress, to, amount); + return amount; + } + if (tokenManagerType == uint256(TokenManagerType.MINT_BURN) || tokenManagerType == uint256(TokenManagerType.MINT_BURN_FROM)) { - _giveTokenMintBurn(tokenAddress, to, amount); + _mintToken(tokenManager, tokenAddress, to, amount); return amount; } @@ -71,13 +77,18 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard { address from, uint256 amount ) external payable returns (uint256) { + if (tokenManagerType == uint256(TokenManagerType.NATIVE_INTERCHAIN_TOKEN)) { + _takeInterchainToken(tokenAddress, from, amount); + return amount; + } + if (tokenManagerType == uint256(TokenManagerType.MINT_BURN)) { - _takeTokenMintBurn(tokenAddress, from, amount); + _burnToken(tokenManager, tokenAddress, from, amount); return amount; } if (tokenManagerType == uint256(TokenManagerType.MINT_BURN_FROM)) { - _takeTokenMintBurnFrom(tokenAddress, from, amount); + _burnTokenFrom(tokenAddress, from, amount); return amount; } @@ -112,6 +123,7 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard { uint256 amount ) external payable returns (uint256) { if ( + tokenManagerType == uint256(TokenManagerType.NATIVE_INTERCHAIN_TOKEN) || tokenManagerType == uint256(TokenManagerType.LOCK_UNLOCK) || tokenManagerType == uint256(TokenManagerType.MINT_BURN) || tokenManagerType == uint256(TokenManagerType.MINT_BURN_FROM) @@ -151,15 +163,23 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard { return amount; } - function _giveTokenMintBurn(address tokenAddress, address to, uint256 amount) internal { + function _giveInterchainToken(address tokenAddress, address to, uint256 amount) internal { IERC20(tokenAddress).safeCall(abi.encodeWithSelector(IERC20MintableBurnable.mint.selector, to, amount)); } - function _takeTokenMintBurn(address tokenAddress, address from, uint256 amount) internal { + function _takeInterchainToken(address tokenAddress, address from, uint256 amount) internal { IERC20(tokenAddress).safeCall(abi.encodeWithSelector(IERC20MintableBurnable.burn.selector, from, amount)); } - function _takeTokenMintBurnFrom(address tokenAddress, address from, uint256 amount) internal { + function _mintToken(address tokenManager, address tokenAddress, address to, uint256 amount) internal { + ITokenManager(tokenManager).mintToken(tokenAddress, to, amount); + } + + function _burnToken(address tokenManager, address tokenAddress, address from, uint256 amount) internal { + ITokenManager(tokenManager).burnToken(tokenAddress, from, amount); + } + + function _burnTokenFrom(address tokenAddress, address from, uint256 amount) internal { IERC20(tokenAddress).safeCall(abi.encodeWithSelector(IERC20BurnableFrom.burnFrom.selector, from, amount)); } } diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index 1e29b194..88df83b6 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -46,6 +46,7 @@ interface IInterchainTokenService is error GiveTokenFailed(bytes data); error TokenHandlerFailed(bytes data); error EmptyData(); + error CannotDeploy(TokenManagerType); event InterchainTransfer( bytes32 indexed tokenId, @@ -165,7 +166,7 @@ interface IInterchainTokenService is * @notice Deploys a custom token manager contract on a remote chain. * @param salt The salt used for token manager deployment. * @param destinationChain The name of the destination chain. - * @param tokenManagerType The type of token manager. + * @param tokenManagerType The type of token manager. Cannot be NATIVE_INTERCHAIN_TOKEN. * @param params The deployment parameters. * @param gasValue The gas value for deployment. * @return tokenId The tokenId associated with the token manager. diff --git a/contracts/interfaces/ITokenManager.sol b/contracts/interfaces/ITokenManager.sol index 435570a9..50c582a9 100644 --- a/contracts/interfaces/ITokenManager.sol +++ b/contracts/interfaces/ITokenManager.sol @@ -74,4 +74,22 @@ interface ITokenManager is IBaseTokenManager, IOperator, IFlowLimit, IImplementa * @return params_ The resulting params to be passed to custom TokenManager deployments. */ function params(bytes calldata operator_, address tokenAddress_) external pure returns (bytes memory params_); + + /** + * @notice External function to allow the service to mint tokens through the tokenManager + * @dev This function should revert if called by anyone but the service. + * @param tokenAddress_ The address of the token, since its cheaper to pass it in instead of reading it as the token manager. + * @param to The recipient. + * @param amount The amount to mint. + */ + function mintToken(address tokenAddress_, address to, uint256 amount) external; + + /** + * @notice External function to allow the service to burn tokens through the tokenManager + * @dev This function should revert if called by anyone but the service. + * @param tokenAddress_ The address of the token, since its cheaper to pass it in instead of reading it as the token manager. + * @param from The address to burn the token from. + * @param amount The amount to burn. + */ + function burnToken(address tokenAddress_, address from, uint256 amount) external; } diff --git a/contracts/interfaces/ITokenManagerType.sol b/contracts/interfaces/ITokenManagerType.sol index 2d5a3a7d..d03db6c7 100644 --- a/contracts/interfaces/ITokenManagerType.sol +++ b/contracts/interfaces/ITokenManagerType.sol @@ -8,9 +8,10 @@ pragma solidity ^0.8.0; */ interface ITokenManagerType { enum TokenManagerType { - MINT_BURN, - MINT_BURN_FROM, - LOCK_UNLOCK, - LOCK_UNLOCK_FEE + NATIVE_INTERCHAIN_TOKEN, // This type is reserved for interchain tokens deployed by ITS, and can't be used by custom token managers. + MINT_BURN_FROM, // The token will be minted/burned on transfers. The token needs to give mint permission to the token manager, but burning happens via an approval. + LOCK_UNLOCK, // The token will be locked/unlocked at the token manager. + LOCK_UNLOCK_FEE, // The token will be locked/unlocked at the token manager, which will account for any fee-on-transfer behaviour. + MINT_BURN // The token will be minted/burned on transfers. The token needs to give mint and burn permission to the token manager. } } diff --git a/contracts/test/TestInterchainTokenStandard.sol b/contracts/test/TestInterchainTokenStandard.sol index 4599820a..c9d39b10 100644 --- a/contracts/test/TestInterchainTokenStandard.sol +++ b/contracts/test/TestInterchainTokenStandard.sol @@ -73,7 +73,7 @@ contract TestInterchainTokenStandard is InterchainTokenStandard, Minter, ERC20, _burn(account, amount); } - function burnFrom(address account, uint256 amount) external onlyRole(uint8(Roles.MINTER)) { + function burnFrom(address account, uint256 amount) external { uint256 currentAllowance = allowance[account][msg.sender]; if (currentAllowance < amount) revert AllowanceExceeded(); _approve(account, msg.sender, currentAllowance - amount); diff --git a/contracts/token-manager/TokenManager.sol b/contracts/token-manager/TokenManager.sol index 2db0eaa4..6c31531b 100644 --- a/contracts/token-manager/TokenManager.sol +++ b/contracts/token-manager/TokenManager.sol @@ -9,6 +9,7 @@ import { Implementation } from '@axelar-network/axelar-gmp-sdk-solidity/contract import { SafeTokenCall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol'; import { ITokenManager } from '../interfaces/ITokenManager.sol'; +import { IERC20MintableBurnable } from '../interfaces/IERC20MintableBurnable.sol'; import { Operator } from '../utils/Operator.sol'; import { FlowLimit } from '../utils/FlowLimit.sol'; @@ -179,4 +180,26 @@ contract TokenManager is ITokenManager, Operator, FlowLimit, Implementation { function params(bytes calldata operator_, address tokenAddress_) external pure returns (bytes memory params_) { params_ = abi.encode(operator_, tokenAddress_); } + + /** + * @notice External function to allow the service to mint tokens through the tokenManager + * @dev This function should revert if called by anyone but the service. + * @param tokenAddress_ The address of the token, since its cheaper to pass it in instead of reading it as the token manager. + * @param to The recipient. + * @param amount The amount to mint. + */ + function mintToken(address tokenAddress_, address to, uint256 amount) external onlyService { + IERC20(tokenAddress_).safeCall(abi.encodeWithSelector(IERC20MintableBurnable.mint.selector, to, amount)); + } + + /** + * @notice External function to allow the service to burn tokens through the tokenManager + * @dev This function should revert if called by anyone but the service. + * @param tokenAddress_ The address of the token, since its cheaper to pass it in instead of reading it as the token manager. + * @param from The address to burn the token from. + * @param amount The amount to burn. + */ + function burnToken(address tokenAddress_, address from, uint256 amount) external onlyService { + IERC20(tokenAddress_).safeCall(abi.encodeWithSelector(IERC20MintableBurnable.burn.selector, from, amount)); + } } diff --git a/docs/index.md b/docs/index.md index 28d3a181..a8cefe68 100644 --- a/docs/index.md +++ b/docs/index.md @@ -703,7 +703,7 @@ part of a multicall involving multiple functions that could make remote contract | ---- | ---- | ----------- | | salt | bytes32 | The salt to be used during deployment. | | destinationChain | string | The name of the chain to deploy the TokenManager and standardized token to. | -| tokenManagerType | enum ITokenManagerType.TokenManagerType | The type of TokenManager to be deployed. | +| tokenManagerType | enum ITokenManagerType.TokenManagerType | The type of token manager to be deployed. Cannot be NATIVE_INTERCHAIN_TOKEN. | | params | bytes | The params that will be used to initialize the TokenManager. | | gasValue | uint256 | The amount of native tokens to be used to pay for gas for the remote deployment. | @@ -987,12 +987,6 @@ function _processDeployTokenManagerPayload(bytes payload) internal Processes a deploy token manager payload. -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| payload | bytes | The encoded data payload to be processed | - ### _processDeployInterchainTokenPayload ```solidity @@ -1264,22 +1258,34 @@ function _transferTokenFrom(address tokenAddress, address from, address to, uint function _transferTokenFromWithFee(address tokenAddress, address from, address to, uint256 amount) internal returns (uint256) ``` -### _giveTokenMintBurn +### _giveInterchainToken + +```solidity +function _giveInterchainToken(address tokenAddress, address to, uint256 amount) internal +``` + +### _takeInterchainToken + +```solidity +function _takeInterchainToken(address tokenAddress, address from, uint256 amount) internal +``` + +### _mintToken ```solidity -function _giveTokenMintBurn(address tokenAddress, address to, uint256 amount) internal +function _mintToken(address tokenManager, address tokenAddress, address to, uint256 amount) internal ``` -### _takeTokenMintBurn +### _burnToken ```solidity -function _takeTokenMintBurn(address tokenAddress, address from, uint256 amount) internal +function _burnToken(address tokenManager, address tokenAddress, address from, uint256 amount) internal ``` -### _takeTokenMintBurnFrom +### _burnTokenFrom ```solidity -function _takeTokenMintBurnFrom(address tokenAddress, address from, uint256 amount) internal +function _burnTokenFrom(address tokenAddress, address from, uint256 amount) internal ``` ## InterchainTokenExecutable @@ -2856,6 +2862,12 @@ error TokenHandlerFailed(bytes data) error EmptyData() ``` +### CannotDeploy + +```solidity +error CannotDeploy(enum ITokenManagerType.TokenManagerType) +``` + ### InterchainTransfer ```solidity @@ -3097,7 +3109,7 @@ Deploys a custom token manager contract on a remote chain. | ---- | ---- | ----------- | | salt | bytes32 | The salt used for token manager deployment. | | destinationChain | string | The name of the destination chain. | -| tokenManagerType | enum ITokenManagerType.TokenManagerType | The type of token manager. | +| tokenManagerType | enum ITokenManagerType.TokenManagerType | The type of token manager. Cannot be NATIVE_INTERCHAIN_TOKEN. | | params | bytes | The deployment parameters. | | gasValue | uint256 | The gas value for deployment. | @@ -3716,6 +3728,42 @@ _This function will be mainly used by frontends._ | ---- | ---- | ----------- | | params_ | bytes | The resulting params to be passed to custom TokenManager deployments. | +### mintToken + +```solidity +function mintToken(address tokenAddress_, address to, uint256 amount) external +``` + +External function to allow the service to mint tokens through the tokenManager + +_This function should revert if called by anyone but the service._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| tokenAddress_ | address | The address of the token, since its cheaper to pass it in instead of reading it as the token manager. | +| to | address | The recipient. | +| amount | uint256 | The amount to mint. | + +### burnToken + +```solidity +function burnToken(address tokenAddress_, address from, uint256 amount) external +``` + +External function to allow the service to burn tokens through the tokenManager + +_This function should revert if called by anyone but the service._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| tokenAddress_ | address | The address of the token, since its cheaper to pass it in instead of reading it as the token manager. | +| from | address | The address to burn the token from. | +| amount | uint256 | The amount to burn. | + ## ITokenManagerDeployer This interface is used to deploy new instances of the TokenManagerProxy contract. @@ -3853,10 +3901,11 @@ A simple interface that defines all the token manager types. ```solidity enum TokenManagerType { - MINT_BURN, + NATIVE_INTERCHAIN_TOKEN, MINT_BURN_FROM, LOCK_UNLOCK, - LOCK_UNLOCK_FEE + LOCK_UNLOCK_FEE, + MINT_BURN } ``` @@ -5069,6 +5118,42 @@ _This function will be mainly used by frontends._ | ---- | ---- | ----------- | | params_ | bytes | The resulting params to be passed to custom TokenManager deployments. | +### mintToken + +```solidity +function mintToken(address tokenAddress_, address to, uint256 amount) external +``` + +External function to allow the service to mint tokens through the tokenManager + +_This function should revert if called by anyone but the service._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| tokenAddress_ | address | The address of the token, since its cheaper to pass it in instead of reading it as the token manager. | +| to | address | The recipient. | +| amount | uint256 | The amount to mint. | + +### burnToken + +```solidity +function burnToken(address tokenAddress_, address from, uint256 amount) external +``` + +External function to allow the service to burn tokens through the tokenManager + +_This function should revert if called by anyone but the service._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| tokenAddress_ | address | The address of the token, since its cheaper to pass it in instead of reading it as the token manager. | +| from | address | The address to burn the token from. | +| amount | uint256 | The amount to burn. | + ## FlowLimit Implements flow limit logic for interchain token transfers. diff --git a/package-lock.json b/package-lock.json index 618dd2fb..b390142e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@axelar-network/interchain-token-service", - "version": "1.2.3", + "version": "1.2.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@axelar-network/interchain-token-service", - "version": "1.2.3", + "version": "1.2.4", "license": "MIT", "dependencies": { "@axelar-network/axelar-cgp-solidity": "6.2.1", @@ -5351,9 +5351,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -10363,9 +10363,9 @@ } }, "node_modules/undici": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.27.0.tgz", - "integrity": "sha512-l3ydWhlhOJzMVOYkymLykcRRXqbUaQriERtR70B9LzNkZ4bX52Fc8wbTDneMiwo8T+AemZXvXaTx+9o5ROxrXg==", + "version": "5.28.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz", + "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==", "dev": true, "dependencies": { "@fastify/busboy": "^2.0.0" diff --git a/package.json b/package.json index 1b66d70f..a894fe16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@axelar-network/interchain-token-service", - "version": "1.2.3", + "version": "1.2.4", "repository": { "type": "git", "url": "https://github.com/axelarnetwork/interchain-token-service" diff --git a/test/InterchainTokenFactory.js b/test/InterchainTokenFactory.js index a74fce31..425471e3 100644 --- a/test/InterchainTokenFactory.js +++ b/test/InterchainTokenFactory.js @@ -15,7 +15,7 @@ const { getRandomBytes32, expectRevert } = require('./utils'); const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN = 1; const LOCK_UNLOCK = 2; -const MINT_BURN = 0; +const NATIVE_INTERCHAIN_TOKEN = 0; const MINTER_ROLE = 0; const OPERATOR_ROLE = 1; @@ -190,7 +190,7 @@ describe('InterchainTokenFactory', () => { .to.emit(service, 'InterchainTokenDeployed') .withArgs(tokenId, tokenAddress, minter, name, symbol, decimals) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, tokenManager.address, MINT_BURN, params); + .withArgs(tokenId, tokenManager.address, NATIVE_INTERCHAIN_TOKEN, params); await checkRoles(tokenManager, minter); }); @@ -207,7 +207,7 @@ describe('InterchainTokenFactory', () => { .to.emit(service, 'InterchainTokenDeployed') .withArgs(tokenId, tokenAddress, AddressZero, name, symbol, decimals) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, tokenManager.address, MINT_BURN, params); + .withArgs(tokenId, tokenManager.address, NATIVE_INTERCHAIN_TOKEN, params); await checkRoles(tokenManager, AddressZero); }); @@ -223,7 +223,7 @@ describe('InterchainTokenFactory', () => { .to.emit(service, 'InterchainTokenDeployed') .withArgs(tokenId, tokenAddress, tokenFactory.address, name, symbol, decimals) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, tokenManager.address, MINT_BURN, params); + .withArgs(tokenId, tokenManager.address, NATIVE_INTERCHAIN_TOKEN, params); await checkRoles(tokenManager, AddressZero); }); @@ -240,7 +240,7 @@ describe('InterchainTokenFactory', () => { .to.emit(service, 'InterchainTokenDeployed') .withArgs(tokenId, tokenAddress, tokenFactory.address, name, symbol, decimals) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, tokenManager.address, MINT_BURN, params) + .withArgs(tokenId, tokenManager.address, NATIVE_INTERCHAIN_TOKEN, params) .and.to.emit(token, 'Transfer') .withArgs(AddressZero, wallet.address, mintAmount) .and.to.emit(tokenManager, 'RolesAdded') @@ -277,7 +277,7 @@ describe('InterchainTokenFactory', () => { .to.emit(service, 'InterchainTokenDeployed') .withArgs(tokenId, tokenAddress, tokenFactory.address, name, symbol, decimals) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, tokenManager.address, MINT_BURN, params) + .withArgs(tokenId, tokenManager.address, NATIVE_INTERCHAIN_TOKEN, params) .and.to.emit(token, 'Transfer') .withArgs(AddressZero, wallet.address, mintAmount) .and.to.emit(token, 'RolesAdded') @@ -348,7 +348,7 @@ describe('InterchainTokenFactory', () => { .to.emit(service, 'InterchainTokenDeployed') .withArgs(tokenId, tokenAddress, tokenFactory.address, name, symbol, decimals) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, tokenManager.address, MINT_BURN, params) + .withArgs(tokenId, tokenManager.address, NATIVE_INTERCHAIN_TOKEN, params) .and.to.emit(token, 'Transfer') .withArgs(AddressZero, wallet.address, mintAmount) .and.to.emit(token, 'RolesAdded') diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 8a2a4e7e..7842dff7 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -19,10 +19,11 @@ const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN = 1; const MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER = 2; const INVALID_MESSAGE_TYPE = 3; -const MINT_BURN = 0; +const NATIVE_INTERCHAIN_TOKEN = 0; const MINT_BURN_FROM = 1; const LOCK_UNLOCK = 2; const LOCK_UNLOCK_FEE_ON_TRANSFER = 3; +const MINT_BURN = 4; const OPERATOR_ROLE = 1; const FLOW_LIMITER_ROLE = 2; @@ -154,7 +155,7 @@ describe('Interchain Token Service', () => { await (await token.mint(wallet.address, mintAmount)).wait(); } - await (await token.transferMintership(service.address)).wait(); + await (await token.transferMintership(tokenManager.address)).wait(); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); await (await service.deployTokenManager(salt, '', type, params, 0)).wait(); @@ -508,7 +509,7 @@ describe('Interchain Token Service', () => { }); describe('Token Handler', () => { - const tokenManagerType = 4; + const tokenManagerType = 6; const amount = 1234; it('Should revert on give token with unsupported token type', async () => { @@ -585,7 +586,7 @@ describe('Interchain Token Service', () => { .to.emit(service, 'InterchainTokenDeployed') .withArgs(tokenId, tokenAddress, wallet.address, tokenName, tokenSymbol, tokenDecimals) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params); + .withArgs(tokenId, expectedTokenManagerAddress, NATIVE_INTERCHAIN_TOKEN, params); const tokenManagerAddress = await service.validTokenManagerAddress(tokenId); expect(tokenManagerAddress).to.not.equal(AddressZero); @@ -721,32 +722,6 @@ describe('Interchain Token Service', () => { ); }); - it('Should be able to receive a remote interchain token deployment with a mint/burn token manager', async () => { - const tokenId = getRandomBytes32(); - const minter = wallet.address; - const operator = wallet.address; - - const tokenManagerAddress = await service.tokenManagerAddress(tokenId); - const tokenAddress = await service.interchainTokenAddress(tokenId); - const params = defaultAbiCoder.encode(['bytes', 'address'], [operator, tokenAddress]); - const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes'], - [MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN, tokenId, tokenName, tokenSymbol, tokenDecimals, minter], - ); - const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - - await expect(reportGas(service.execute(commandId, sourceChain, sourceAddress, payload), 'Receive GMP DEPLOY_INTERCHAIN_TOKEN')) - .to.emit(service, 'InterchainTokenDeployed') - .withArgs(tokenId, tokenAddress, minter, tokenName, tokenSymbol, tokenDecimals) - .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, tokenManagerAddress, MINT_BURN, params); - - const tokenManager = await getContractAt('TokenManager', tokenManagerAddress, wallet); - - expect(await tokenManager.tokenAddress()).to.equal(tokenAddress); - expect(await tokenManager.hasRole(operator, OPERATOR_ROLE)).to.be.true; - }); - it('Should be able to receive a remote interchain token deployment with a mint/burn token manager with empty minter and operator', async () => { const tokenId = getRandomBytes32(); const tokenManagerAddress = await service.tokenManagerAddress(tokenId); @@ -764,7 +739,7 @@ describe('Interchain Token Service', () => { .to.emit(service, 'InterchainTokenDeployed') .withArgs(tokenId, tokenAddress, AddressZero, tokenName, tokenSymbol, tokenDecimals) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, tokenManagerAddress, MINT_BURN, params); + .withArgs(tokenId, tokenManagerAddress, NATIVE_INTERCHAIN_TOKEN, params); const tokenManager = await getContractAt('TokenManager', tokenManagerAddress, wallet); expect(await tokenManager.tokenAddress()).to.equal(tokenAddress); expect(await tokenManager.hasRole(service.address, OPERATOR_ROLE)).to.be.true; @@ -793,7 +768,29 @@ describe('Interchain Token Service', () => { it('Should revert on deploying an invalid token manager', async () => { const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - await expectRevert((gasOptions) => service.deployTokenManager(salt, '', 5, params, 0, gasOptions)); + await expectRevert((gasOptions) => service.deployTokenManager(salt, '', 6, params, 0, gasOptions)); + }); + + it('Should revert on deploying a local token manager with interchain token manager type', async () => { + const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); + + await expectRevert( + (gasOptions) => service.deployTokenManager(salt, '', NATIVE_INTERCHAIN_TOKEN, params, 0, gasOptions), + service, + 'CannotDeploy', + [NATIVE_INTERCHAIN_TOKEN], + ); + }); + + it('Should revert on deploying a remote token manager with interchain token manager type', async () => { + const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); + + await expectRevert( + (gasOptions) => service.deployTokenManager(salt, destinationChain, NATIVE_INTERCHAIN_TOKEN, params, 0, gasOptions), + service, + 'CannotDeploy', + [NATIVE_INTERCHAIN_TOKEN], + ); }); it('Should deploy a lock/unlock token manager', async () => { @@ -1107,6 +1104,31 @@ describe('Interchain Token Service', () => { expect(await tokenManager.tokenAddress()).to.equal(token.address); expect(await tokenManager.hasRole(wallet.address, OPERATOR_ROLE)).to.be.true; }); + + it('Should not be able to receive a remote interchain token manager deployment', async () => { + const tokenId = getRandomBytes32(); + const token = await deployContract(wallet, 'TestInterchainTokenStandard', [ + tokenName, + tokenSymbol, + tokenDecimals, + service.address, + tokenId, + ]); + + const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'uint256', 'bytes'], + [MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER, tokenId, NATIVE_INTERCHAIN_TOKEN, params], + ); + const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); + + await expectRevert( + (gasOptions) => service.execute(commandId, sourceChain, sourceAddress, payload, gasOptions), + service, + 'CannotDeploy', + [NATIVE_INTERCHAIN_TOKEN], + ); + }); }); describe('Send Token', () => { @@ -2066,6 +2088,31 @@ describe('Interchain Token Service', () => { .withArgs(commandId, sourceChain, sourceAddress, keccak256(payload), wallet.address); }); + it('Should be able to receive interchain mint/burn token', async () => { + const salt = getRandomBytes32(); + await (await service.deployInterchainToken(salt, '', `Test Token Mint Burn`, 'TT', 12, wallet.address, 0)).wait(); + const tokenId = await service.interchainTokenId(wallet.address, salt); + const token = await getContractAt('InterchainToken', await service.interchainTokenAddress(tokenId), wallet); + + await (await token.mint(wallet.address, amount)).wait(); + await (await token.approve(service.address, amount)).wait(); + + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [MESSAGE_TYPE_INTERCHAIN_TRANSFER, tokenId, hexlify(wallet.address), destAddress, amount, '0x'], + ); + + const commandId = getRandomBytes32(); + await (await service.expressExecute(commandId, sourceChain, sourceAddress, payload)).wait(); + await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload, getRandomBytes32(), 0, commandId); + + await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) + .to.emit(token, 'Transfer') + .withArgs(AddressZero, wallet.address, amount) + .and.to.emit(service, 'ExpressExecutionFulfilled') + .withArgs(commandId, sourceChain, sourceAddress, keccak256(payload), wallet.address); + }); + it('Should be able to receive mint/burn token', async () => { const [token, , tokenId] = await deployFunctions.mintBurn(`Test Token Mint Burn`, 'TT', 12, amount); @@ -2197,6 +2244,34 @@ describe('Interchain Token Service', () => { expect(await executable.lastMessage()).to.equal(msg); }); + it('Should be able to receive interchain mint/burn token', async () => { + const salt = getRandomBytes32(); + await (await service.deployInterchainToken(salt, '', `Test Token Mint Burn`, 'TT', 12, wallet.address, 0)).wait(); + const tokenId = await service.interchainTokenId(wallet.address, salt); + const token = await getContractAt('InterchainToken', await service.interchainTokenAddress(tokenId), wallet); + + await (await token.mint(wallet.address, amount)).wait(); + await (await token.approve(service.address, amount)).wait(); + + const msg = `mint/burn`; + const data = defaultAbiCoder.encode(['address', 'string'], [wallet.address, msg]); + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [MESSAGE_TYPE_INTERCHAIN_TRANSFER, tokenId, sourceAddressForService, destAddress, amount, data], + ); + + const commandId = getRandomBytes32(); + await (await service.expressExecute(commandId, sourceChain, sourceAddress, payload)).wait(); + await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload, getRandomBytes32(), 0, commandId); + + await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) + .to.emit(token, 'Transfer') + .withArgs(AddressZero, wallet.address, amount) + .and.to.emit(service, 'ExpressExecutionFulfilled') + .withArgs(commandId, sourceChain, sourceAddress, keccak256(payload), wallet.address); + + expect(await executable.lastMessage()).to.equal(msg); + }); it('Should be able to receive mint/burn token', async () => { const [token, , tokenId] = await deployFunctions.mintBurn(`Test Token Mint Burn`, 'TT', 12, amount); await (await token.approve(service.address, amount)).wait(); diff --git a/test/InterchainTokenServiceFullFlow.js b/test/InterchainTokenServiceFullFlow.js index c3e50ba2..b818e8bc 100644 --- a/test/InterchainTokenServiceFullFlow.js +++ b/test/InterchainTokenServiceFullFlow.js @@ -22,7 +22,8 @@ const MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER = 2; const MINTER_ROLE = 0; -const MINT_BURN = 0; +const NATIVE_INTERCHAIN_TOKEN = 0; +const MINT_BURN = 4; const LOCK_UNLOCK = 2; describe('Interchain Token Service Full Flow', () => { @@ -196,7 +197,7 @@ describe('Interchain Token Service Full Flow', () => { .to.emit(service, 'InterchainTokenDeployed') .withArgs(tokenId, expectedTokenAddress, tokenFactory.address, name, symbol, decimals) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params) + .withArgs(tokenId, expectedTokenManagerAddress, NATIVE_INTERCHAIN_TOKEN, params) .and.to.emit(service, 'InterchainTokenDeploymentStarted') .withArgs(tokenId, name, symbol, decimals, wallet.address.toLowerCase(), otherChains[0]) .and.to.emit(gasService, 'NativeGasPaidForContractCall') @@ -393,17 +394,18 @@ describe('Interchain Token Service Full Flow', () => { * Transfer the minter to ITS on all chains to allow it to mint/burn */ it('Should be able to change the token minter', async () => { + const tokenManagerAddress = await service.tokenManagerAddress(tokenId); const newAddress = new Wallet(getRandomBytes32()).address; const amount = 1234; await expect(token.mint(newAddress, amount)).to.emit(token, 'Transfer').withArgs(AddressZero, newAddress, amount); await expect(token.burn(newAddress, amount)).to.emit(token, 'Transfer').withArgs(newAddress, AddressZero, amount); - await expect(token.transferMintership(service.address)) + await expect(token.transferMintership(tokenManagerAddress)) .to.emit(token, 'RolesRemoved') .withArgs(wallet.address, 1 << MINTER_ROLE) .to.emit(token, 'RolesAdded') - .withArgs(service.address, 1 << MINTER_ROLE); + .withArgs(tokenManagerAddress, 1 << MINTER_ROLE); await expectRevert((gasOptions) => token.mint(newAddress, amount, gasOptions), token, 'MissingRole', [ wallet.address, @@ -503,7 +505,7 @@ describe('Interchain Token Service Full Flow', () => { .to.emit(service, 'InterchainTokenDeployed') .withArgs(tokenId, expectedTokenAddress, tokenFactory.address, name, symbol, decimals) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params) + .withArgs(tokenId, expectedTokenManagerAddress, NATIVE_INTERCHAIN_TOKEN, params) .and.to.emit(service, 'InterchainTokenDeploymentStarted') .withArgs(tokenId, name, symbol, decimals, '0x', otherChains[0]) .and.to.emit(gasService, 'NativeGasPaidForContractCall') @@ -592,7 +594,7 @@ describe('Interchain Token Service Full Flow', () => { .to.emit(service, 'InterchainTokenDeployed') .withArgs(tokenId, tokenAddress, tokenFactory.address, name, symbol, decimals) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params); + .withArgs(tokenId, expectedTokenManagerAddress, NATIVE_INTERCHAIN_TOKEN, params); token = await getContractAt('InterchainToken', tokenAddress, wallet); executable = await deployContract(wallet, 'TestInterchainExecutable', [service.address]); diff --git a/test/InterchainTokenServiceUpgradeFlow.js b/test/InterchainTokenServiceUpgradeFlow.js index c66a5c16..5cb9285f 100644 --- a/test/InterchainTokenServiceUpgradeFlow.js +++ b/test/InterchainTokenServiceUpgradeFlow.js @@ -16,6 +16,8 @@ const { getBytecodeHash } = require('@axelar-network/axelar-chains-config'); const AxelarServiceGovernance = getContractJSON('AxelarServiceGovernance'); const Create3Deployer = getContractJSON('Create3Deployer'); +const MINT_BURN = 4; + describe('Interchain Token Service Upgrade Flow', () => { let wallet, otherWallet, signer; let service, gateway, gasService; @@ -51,9 +53,9 @@ describe('Interchain Token Service Upgrade Flow', () => { ]); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - await expect(service.deployTokenManager(salt, '', 0, params, 0)) + await expect(service.deployTokenManager(salt, '', MINT_BURN, params, 0)) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, tokenManager.address, 0, params); + .withArgs(tokenId, tokenManager.address, MINT_BURN, params); } before(async () => { diff --git a/test/TokenManager.js b/test/TokenManager.js index e62e325e..18fd33de 100644 --- a/test/TokenManager.js +++ b/test/TokenManager.js @@ -61,6 +61,24 @@ describe('Token Manager', () => { await expectRevert((gasOptions) => TestTokenManager.approveService(gasOptions), TestTokenManager, 'NotService', [owner.address]); }); + it('Should revert on mintToken when calling directly', async () => { + await expectRevert( + (gasOptions) => TestTokenManager.mintToken(other.address, owner.address, 1234, gasOptions), + TestTokenManager, + 'NotService', + [owner.address], + ); + }); + + it('Should revert on burnToken when calling directly', async () => { + await expectRevert( + (gasOptions) => TestTokenManager.burnToken(other.address, owner.address, 1234, gasOptions), + TestTokenManager, + 'NotService', + [owner.address], + ); + }); + it('Should return the correct parameters for a token manager', async () => { const expectedParams = defaultAbiCoder.encode(['bytes', 'address'], [toUtf8Bytes(owner.address), other.address]); const params = await TestTokenManager.params(toUtf8Bytes(owner.address), other.address); From cd8f7be582eaa86a7de9a098fcfc5121e3a39af1 Mon Sep 17 00:00:00 2001 From: axelar-cicd-bot Date: Sun, 24 Mar 2024 06:44:04 +0000 Subject: [PATCH 10/16] v1.2.4 From ce3cf52235f2d50f5f545da9d1aa87693676c7a4 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 25 Mar 2024 16:52:34 +0200 Subject: [PATCH 11/16] fixed tests --- contracts/interfaces/IInterchainTokenFactory.sol | 6 ------ 1 file changed, 6 deletions(-) diff --git a/contracts/interfaces/IInterchainTokenFactory.sol b/contracts/interfaces/IInterchainTokenFactory.sol index e11f9d8d..7e55efbf 100644 --- a/contracts/interfaces/IInterchainTokenFactory.sol +++ b/contracts/interfaces/IInterchainTokenFactory.sol @@ -26,12 +26,6 @@ interface IInterchainTokenFactory is IUpgradable, IMulticall { */ function interchainTokenService() external view returns (IInterchainTokenService); - /** - * @notice Returns the address of the interchain token service. - * @return IInterchainTokenService The address of the interchain token service. - */ - function interchainTokenService() external view returns (IInterchainTokenService); - /** * @notice Returns the hash of the chain name. * @return bytes32 The hash of the chain name. From 30a919e7d9c06f8bc7974d106e68a2790f7805f8 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 27 Mar 2024 21:32:14 +0200 Subject: [PATCH 12/16] fixed tests --- contracts/interfaces/ITokenManagerType.sol | 3 ++- test/InterchainTokenFactory.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/interfaces/ITokenManagerType.sol b/contracts/interfaces/ITokenManagerType.sol index d03db6c7..c8d361c9 100644 --- a/contracts/interfaces/ITokenManagerType.sol +++ b/contracts/interfaces/ITokenManagerType.sol @@ -12,6 +12,7 @@ interface ITokenManagerType { MINT_BURN_FROM, // The token will be minted/burned on transfers. The token needs to give mint permission to the token manager, but burning happens via an approval. LOCK_UNLOCK, // The token will be locked/unlocked at the token manager. LOCK_UNLOCK_FEE, // The token will be locked/unlocked at the token manager, which will account for any fee-on-transfer behaviour. - MINT_BURN // The token will be minted/burned on transfers. The token needs to give mint and burn permission to the token manager. + MINT_BURN, // The token will be minted/burned on transfers. The token needs to give mint and burn permission to the token manager. + GATEWAY // The token will be moved throught the AxelarGateway via callContractWithToken } } diff --git a/test/InterchainTokenFactory.js b/test/InterchainTokenFactory.js index 1c481f14..ffdbeb06 100644 --- a/test/InterchainTokenFactory.js +++ b/test/InterchainTokenFactory.js @@ -15,7 +15,7 @@ const { getRandomBytes32, expectRevert } = require('./utils'); const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN = 1; const LOCK_UNLOCK = 2; -const GATEWAY = 4; +const GATEWAY = 5; const NATIVE_INTERCHAIN_TOKEN = 0; const MINTER_ROLE = 0; From d48a8a922af300871e57b9e4d4871b1276ce0dbd Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 28 Mar 2024 14:01:15 +0200 Subject: [PATCH 13/16] reduce code size further --- contracts/InterchainTokenService.sol | 29 ++------ contracts/TokenHandler.sol | 71 ++++++++++++------- .../interfaces/IInterchainTokenService.sol | 1 - contracts/interfaces/ITokenHandler.sol | 26 ++++--- test/InterchainTokenService.js | 2 +- 5 files changed, 63 insertions(+), 66 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index f2d3fe56..5341ee22 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -1077,44 +1077,25 @@ contract InterchainTokenService is * @dev Takes token from a sender via the token service. `tokenOnly` indicates if the caller should be restricted to the token only. */ function _takeToken(bytes32 tokenId, address from, uint256 amount, bool tokenOnly) internal returns (uint256, string memory symbol) { - address tokenManager_ = tokenManagerAddress(tokenId); - uint256 tokenManagerType; - address tokenAddress; - - (tokenManagerType, tokenAddress) = ITokenManagerProxy(tokenManager_).getImplementationTypeAndTokenAddress(); - - if (tokenOnly && msg.sender != tokenAddress) revert NotToken(msg.sender, tokenAddress); (bool success, bytes memory data) = tokenHandler.delegatecall( - abi.encodeWithSelector(ITokenHandler.takeToken.selector, tokenManagerType, tokenAddress, tokenManager_, from, amount) + abi.encodeWithSelector(ITokenHandler.takeToken.selector, tokenId, tokenOnly, from, amount) ); if (!success) revert TakeTokenFailed(data); - amount = abi.decode(data, (uint256)); + (amount, symbol) = abi.decode(data, (uint256, string)); - /// @dev Track the flow amount being sent out as a message - ITokenManager(tokenManager_).addFlowOut(amount); - if (tokenManagerType == uint256(TokenManagerType.GATEWAY)) { - symbol = IERC20Named(tokenAddress).symbol(); - } return (amount, symbol); } /** * @dev Gives token to recipient via the token service. */ - function _giveToken(bytes32 tokenId, address to, uint256 amount) internal returns (uint256, address) { - address tokenManager_ = tokenManagerAddress(tokenId); - - (uint256 tokenManagerType, address tokenAddress) = ITokenManagerProxy(tokenManager_).getImplementationTypeAndTokenAddress(); - - /// @dev Track the flow amount being received via the message - ITokenManager(tokenManager_).addFlowIn(amount); - + function _giveToken(bytes32 tokenId, address to, uint256 amount) internal returns (uint256, address tokenAddress) { (bool success, bytes memory data) = tokenHandler.delegatecall( - abi.encodeWithSelector(ITokenHandler.giveToken.selector, tokenManagerType, tokenAddress, tokenManager_, to, amount) + abi.encodeWithSelector(ITokenHandler.giveToken.selector, tokenId, to, amount) ); if (!success) revert GiveTokenFailed(data); - amount = abi.decode(data, (uint256)); + (amount, tokenAddress) = abi.decode(data, (uint256, address)); return (amount, tokenAddress); } diff --git a/contracts/TokenHandler.sol b/contracts/TokenHandler.sol index dbe1fd7d..cc803a03 100644 --- a/contracts/TokenHandler.sol +++ b/contracts/TokenHandler.sol @@ -6,17 +6,21 @@ 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 { ReentrancyGuard } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/ReentrancyGuard.sol'; +import { Create3Address } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3Address.sol'; import { ITokenManagerType } from './interfaces/ITokenManagerType.sol'; import { ITokenManager } from './interfaces/ITokenManager.sol'; +import { ITokenManagerProxy } from './interfaces/ITokenManagerProxy.sol'; import { IERC20MintableBurnable } from './interfaces/IERC20MintableBurnable.sol'; import { IERC20BurnableFrom } from './interfaces/IERC20BurnableFrom.sol'; +import { IERC20Named } from './interfaces/IERC20Named.sol'; + /** * @title TokenHandler * @notice This interface is responsible for handling tokens before initiating an interchain token transfer, or after receiving one. */ -contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard { +contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Create3Address { using SafeTokenTransferFrom for IERC20; using SafeTokenCall for IERC20; using SafeTokenTransfer for IERC20; @@ -32,44 +36,48 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard { /** * @notice This function gives token to a specified address from the token manager. - * @param tokenManagerType The token manager type. - * @param tokenAddress The address of the token to give. - * @param tokenManager The address of the token manager. + * @param tokenId The token id of the tokenManager. * @param to The address to give tokens to. * @param amount The amount of tokens to give. * @return uint256 The amount of token actually given, which could be different for certain token type. + * @return address the address of the token. */ // slither-disable-next-line locked-ether function giveToken( - uint256 tokenManagerType, - address tokenAddress, - address tokenManager, + bytes32 tokenId, address to, uint256 amount - ) external payable returns (uint256) { + ) external payable returns (uint256, address) { + address tokenManager = _create3Address(tokenId); + + (uint256 tokenManagerType, address tokenAddress) = ITokenManagerProxy(tokenManager).getImplementationTypeAndTokenAddress(); + + /// @dev Track the flow amount being received via the message + ITokenManager(tokenManager).addFlowIn(amount); + if (tokenManagerType == uint256(TokenManagerType.NATIVE_INTERCHAIN_TOKEN)) { _giveInterchainToken(tokenAddress, to, amount); - return amount; + return (amount, tokenAddress); } if (tokenManagerType == uint256(TokenManagerType.MINT_BURN) || tokenManagerType == uint256(TokenManagerType.MINT_BURN_FROM)) { _mintToken(tokenManager, tokenAddress, to, amount); - return amount; + return (amount, tokenAddress); } if (tokenManagerType == uint256(TokenManagerType.LOCK_UNLOCK)) { _transferTokenFrom(tokenAddress, tokenManager, to, amount); - return amount; + return (amount, tokenAddress); } if (tokenManagerType == uint256(TokenManagerType.LOCK_UNLOCK_FEE)) { amount = _transferTokenFromWithFee(tokenAddress, tokenManager, to, amount); - return amount; + return (amount, tokenAddress); } if (tokenManagerType == uint256(TokenManagerType.GATEWAY)) { _transferToken(tokenAddress, to, amount); - return amount; + return (amount, tokenAddress); } revert UnsupportedTokenManagerType(tokenManagerType); @@ -77,49 +85,60 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard { /** * @notice This function takes token from a specified address to the token manager. - * @param tokenManagerType The token manager type. - * @param tokenAddress The address of the token to give. - * @param tokenManager The address of the token manager. + * @param tokenId The tokenId for the token. + * @param tokenOnly can onky be called from the token. * @param from The address to take tokens from. * @param amount The amount of token to take. * @return uint256 The amount of token actually taken, which could be different for certain token type. + * @return symbol The symbol for the token, if not empty the token is a gateway token and a callContractWith token has to be made. */ // slither-disable-next-line locked-ether function takeToken( - uint256 tokenManagerType, - address tokenAddress, - address tokenManager, + bytes32 tokenId, + bool tokenOnly, address from, uint256 amount - ) external payable returns (uint256) { + ) external payable returns (uint256, string memory symbol) { + + address tokenManager = _create3Address(tokenId); + (uint256 tokenManagerType, address tokenAddress) = ITokenManagerProxy(tokenManager).getImplementationTypeAndTokenAddress(); + + if (tokenOnly && msg.sender != tokenAddress) revert NotToken(msg.sender, tokenAddress); + + /// @dev Track the flow amount being sent out as a message + ITokenManager(tokenManager).addFlowOut(amount); + if (tokenManagerType == uint256(TokenManagerType.GATEWAY)) { + symbol = IERC20Named(tokenAddress).symbol(); + } + if (tokenManagerType == uint256(TokenManagerType.NATIVE_INTERCHAIN_TOKEN)) { _takeInterchainToken(tokenAddress, from, amount); - return amount; + return (amount, symbol); } if (tokenManagerType == uint256(TokenManagerType.MINT_BURN)) { _burnToken(tokenManager, tokenAddress, from, amount); - return amount; + return (amount, symbol); } if (tokenManagerType == uint256(TokenManagerType.MINT_BURN_FROM)) { _burnTokenFrom(tokenAddress, from, amount); - return amount; + return (amount, symbol); } if (tokenManagerType == uint256(TokenManagerType.LOCK_UNLOCK)) { _transferTokenFrom(tokenAddress, from, tokenManager, amount); - return amount; + return (amount, symbol); } if (tokenManagerType == uint256(TokenManagerType.LOCK_UNLOCK_FEE)) { amount = _transferTokenFromWithFee(tokenAddress, from, tokenManager, amount); - return amount; + return (amount, symbol); } if (tokenManagerType == uint256(TokenManagerType.GATEWAY)) { _transferTokenFrom(tokenAddress, from, address(this), amount); - return amount; + return (amount, symbol); } revert UnsupportedTokenManagerType(tokenManagerType); diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index 19761829..d7447312 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -32,7 +32,6 @@ interface IInterchainTokenService is error InvalidChainName(); error NotRemoteService(); error TokenManagerDoesNotExist(bytes32 tokenId); - error NotToken(address caller, address token); error ExecuteWithInterchainTokenFailed(address contractAddress); error ExpressExecuteWithInterchainTokenFailed(address contractAddress); error GatewayToken(); diff --git a/contracts/interfaces/ITokenHandler.sol b/contracts/interfaces/ITokenHandler.sol index b2167cf5..34bb4d27 100644 --- a/contracts/interfaces/ITokenHandler.sol +++ b/contracts/interfaces/ITokenHandler.sol @@ -9,6 +9,7 @@ pragma solidity ^0.8.0; interface ITokenHandler { error UnsupportedTokenManagerType(uint256 tokenManagerType); error AddressZero(); + error NotToken(address caller, address token); /** * @notice Returns the address of the axelar gateway on this chain. @@ -18,37 +19,34 @@ interface ITokenHandler { /** * @notice This function gives token to a specified address from the token manager. - * @param tokenManagerType The token manager type. - * @param tokenAddress The address of the token to give. - * @param tokenManager The address of the token manager. + * @param tokenId The token id of the tokenManager. * @param to The address to give tokens to. * @param amount The amount of tokens to give. * @return uint256 The amount of token actually given, which could be different for certain token type. + * @return address the address of the token. */ function giveToken( - uint256 tokenManagerType, - address tokenAddress, - address tokenManager, + bytes32 tokenId, address to, uint256 amount - ) external payable returns (uint256); + ) external payable returns (uint256, address); /** * @notice This function takes token from a specified address to the token manager. - * @param tokenManagerType The token manager type. - * @param tokenAddress The address of the token to give. - * @param tokenManager The address of the token manager. + * @param tokenId The tokenId for the token. + * @param tokenOnly can onky be called from the token. * @param from The address to take tokens from. * @param amount The amount of token to take. * @return uint256 The amount of token actually taken, which could be different for certain token type. + * @return symbol The symbol for the token, if not empty the token is a gateway token and a callContractWith token has to be made. */ + // slither-disable-next-line locked-ether function takeToken( - uint256 tokenManagerType, - address tokenAddress, - address tokenManager, + bytes32 tokenId, + bool tokenOnly, address from, uint256 amount - ) external payable returns (uint256); + ) external payable returns (uint256, string memory symbol); /** * @notice This function transfers token from and to a specified address. diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index c1651a9e..b4acce91 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -192,7 +192,7 @@ describe('Interchain Token Service', () => { await token.mint(wallet.address, mintAmount).then((tx) => tx.wait); } - await token.transferMintership(service.address).then((tx) => tx.wait); + await token.transferMintership(tokenManager.address).then((tx) => tx.wait); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); await service.deployTokenManager(salt, '', type, params, 0).then((tx) => tx.wait); From ce9f00108c8900c0df8c5b693af94ed2bd7adb82 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 28 Mar 2024 17:09:04 +0200 Subject: [PATCH 14/16] Moving some functionality out of the service and into the token handler --- contracts/InterchainTokenService.sol | 8 +-- contracts/TokenHandler.sol | 15 ++--- contracts/interfaces/ITokenHandler.sol | 11 ++-- test/InterchainTokenService.js | 84 +++++++------------------- 4 files changed, 36 insertions(+), 82 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 5341ee22..1a35b767 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -401,21 +401,17 @@ contract InterchainTokenService is IERC20 token; { - ITokenManager tokenManager_ = ITokenManager(tokenManagerAddress(tokenId)); - token = IERC20(tokenManager_.tokenAddress()); - (bool success, bytes memory returnData) = tokenHandler.delegatecall( abi.encodeWithSelector( ITokenHandler.transferTokenFrom.selector, - tokenManager_.implementationType(), - address(token), + tokenId, msg.sender, destinationAddress, amount ) ); if (!success) revert TokenHandlerFailed(returnData); - amount = abi.decode(returnData, (uint256)); + (amount, token) = abi.decode(returnData, (uint256, IERC20)); } // slither-disable-next-line reentrancy-events diff --git a/contracts/TokenHandler.sol b/contracts/TokenHandler.sol index cc803a03..12224f21 100644 --- a/contracts/TokenHandler.sol +++ b/contracts/TokenHandler.sol @@ -146,21 +146,22 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Crea /** * @notice This function transfers token from and to a specified address. - * @param tokenManagerType The token manager type. - * @param tokenAddress the address of the token to give. + * @param tokenId The token id of the token manager. * @param from The address to transfer tokens from. * @param to The address to transfer tokens to. * @param amount The amount of token to transfer. * @return uint256 The amount of token actually transferred, which could be different for certain token type. + * @return address The address of the token corresponding to the input tokenId. */ // slither-disable-next-line locked-ether function transferTokenFrom( - uint256 tokenManagerType, - address tokenAddress, + bytes32 tokenId, address from, address to, uint256 amount - ) external payable returns (uint256) { + ) external payable returns (uint256, address) { + address tokenManager = _create3Address(tokenId); + (uint256 tokenManagerType, address tokenAddress) = ITokenManagerProxy(tokenManager).getImplementationTypeAndTokenAddress(); if ( tokenManagerType == uint256(TokenManagerType.NATIVE_INTERCHAIN_TOKEN) || tokenManagerType == uint256(TokenManagerType.LOCK_UNLOCK) || @@ -169,12 +170,12 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Crea tokenManagerType == uint256(TokenManagerType.GATEWAY) ) { _transferTokenFrom(tokenAddress, from, to, amount); - return amount; + return (amount, tokenAddress); } if (tokenManagerType == uint256(TokenManagerType.LOCK_UNLOCK_FEE)) { amount = _transferTokenFromWithFee(tokenAddress, from, to, amount); - return amount; + return (amount, tokenAddress); } revert UnsupportedTokenManagerType(tokenManagerType); diff --git a/contracts/interfaces/ITokenHandler.sol b/contracts/interfaces/ITokenHandler.sol index 34bb4d27..afdfeee9 100644 --- a/contracts/interfaces/ITokenHandler.sol +++ b/contracts/interfaces/ITokenHandler.sol @@ -50,21 +50,20 @@ interface ITokenHandler { /** * @notice This function transfers token from and to a specified address. - * @param tokenManagerType The token manager type. - * @param tokenAddress the address of the token to give. + * @param tokenId The token id of the token manager. * @param from The address to transfer tokens from. * @param to The address to transfer tokens to. * @param amount The amount of token to transfer. * @return uint256 The amount of token actually transferred, which could be different for certain token type. + * @return address The address of the token corresponding to the input tokenId. */ + // slither-disable-next-line locked-ether function transferTokenFrom( - uint256 tokenManagerType, - address tokenAddress, + bytes32 tokenId, address from, address to, uint256 amount - ) external payable returns (uint256); - + ) external payable returns (uint256, address); /** * @notice This function prepares a token manager after it is deployed * @param tokenManagerType The token manager type. diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index b4acce91..da692edd 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -553,62 +553,6 @@ describe('Interchain Token Service', () => { }); }); - describe('Token Handler', () => { - const tokenManagerType = 6; - const amount = 1234; - - it('Should revert on give token with unsupported token type', async () => { - await expectRevert( - (gasOptions) => - tokenHandler.giveToken( - tokenManagerType, - otherWallet.address, - otherWallet.address, - otherWallet.address, - amount, - gasOptions, - ), - tokenHandler, - 'UnsupportedTokenManagerType', - [tokenManagerType], - ); - }); - - it('Should revert on take token with unsupported token type', async () => { - await expectRevert( - (gasOptions) => - tokenHandler.takeToken( - tokenManagerType, - otherWallet.address, - otherWallet.address, - otherWallet.address, - amount, - gasOptions, - ), - tokenHandler, - 'UnsupportedTokenManagerType', - [tokenManagerType], - ); - }); - - it('Should revert on transfer token from with unsupported token type', async () => { - await expectRevert( - (gasOptions) => - tokenHandler.transferTokenFrom( - tokenManagerType, - otherWallet.address, - otherWallet.address, - otherWallet.address, - amount, - gasOptions, - ), - tokenHandler, - 'UnsupportedTokenManagerType', - [tokenManagerType], - ); - }); - }); - describe('Deploy and Register Interchain Token', () => { const tokenName = 'Token Name'; const tokenSymbol = 'TN'; @@ -1309,6 +1253,10 @@ describe('Interchain Token Service', () => { }); it(`Should revert on transmit send token when not called by interchain token`, async () => { + const errorSignatureHash = id('NotToken(address,address)'); + const selector = errorSignatureHash.substring(0, 10); + const errorData = defaultAbiCoder.encode(['address', 'address'], [wallet.address, token.address]) + await expectRevert( (gasOptions) => service.transmitInterchainTransfer(tokenId, wallet.address, destinationChain, destAddress, amount, '0x', { @@ -1316,8 +1264,8 @@ describe('Interchain Token Service', () => { value: gasValue, }), service, - 'NotToken', - [wallet.address, token.address], + 'TakeTokenFailed', + [selector + errorData.substring(2)], ); }); }); @@ -2640,11 +2588,16 @@ describe('Interchain Token Service', () => { it('Should be able to send token only if it does not trigger the mint limit', async () => { await service.interchainTransfer(tokenId, destinationChain, destinationAddress, sendAmount, '0x', 0).then((tx) => tx.wait); + + const errorSignatureHash = id('FlowLimitExceeded(uint256,uint256,address)'); + const selector = errorSignatureHash.substring(0, 10); + const errorData = defaultAbiCoder.encode(['uint256', 'uint256', 'address'], [flowLimit, 2 * sendAmount, tokenManager.address]) + await expectRevert( (gasOptions) => service.interchainTransfer(tokenId, destinationChain, destinationAddress, sendAmount, '0x', 0, gasOptions), - tokenManager, - 'FlowLimitExceeded', - [flowLimit, 2 * sendAmount, tokenManager.address], + service, + 'TakeTokenFailed', + [selector + errorData.substring(2)], ); }); @@ -2676,11 +2629,16 @@ describe('Interchain Token Service', () => { expect(flowIn).to.eq(sendAmount); expect(flowOut).to.eq(sendAmount); - await expectRevert((gasOptions) => receiveToken(2 * sendAmount, gasOptions), tokenManager, 'FlowLimitExceeded', [ + const errorSignatureHash = id('FlowLimitExceeded(uint256,uint256,address)'); + const selector = errorSignatureHash.substring(0, 10); + const errorData = defaultAbiCoder.encode(['uint256', 'uint256', 'address'], [ (5 * sendAmount) / 2, 3 * sendAmount, tokenManager.address, - ]); + ]) + + + await expectRevert((gasOptions) => receiveToken(2 * sendAmount, gasOptions), service, 'GiveTokenFailed', [selector + errorData.substring(2)]); }); it('Should be able to set flow limits for each token manager', async () => { From cde0be6774d14110d9f0bff1241a56c6bdbb4529 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 28 Mar 2024 17:09:57 +0200 Subject: [PATCH 15/16] lint and prettier --- contracts/InterchainTokenService.sol | 11 +---------- contracts/TokenHandler.sol | 15 ++------------- contracts/interfaces/ITokenHandler.sol | 14 +++----------- test/InterchainTokenService.js | 20 ++++++++++---------- 4 files changed, 16 insertions(+), 44 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 1a35b767..32069a62 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -14,14 +14,12 @@ import { Pausable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/util import { InterchainAddressTracker } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/InterchainAddressTracker.sol'; import { IInterchainTokenService } from './interfaces/IInterchainTokenService.sol'; -import { ITokenManagerProxy } from './interfaces/ITokenManagerProxy.sol'; import { ITokenHandler } from './interfaces/ITokenHandler.sol'; import { ITokenManagerDeployer } from './interfaces/ITokenManagerDeployer.sol'; import { IInterchainTokenDeployer } from './interfaces/IInterchainTokenDeployer.sol'; import { IInterchainTokenExecutable } from './interfaces/IInterchainTokenExecutable.sol'; import { IInterchainTokenExpressExecutable } from './interfaces/IInterchainTokenExpressExecutable.sol'; import { ITokenManager } from './interfaces/ITokenManager.sol'; -import { IERC20Named } from './interfaces/IERC20Named.sol'; import { Operator } from './utils/Operator.sol'; @@ -402,13 +400,7 @@ contract InterchainTokenService is IERC20 token; { (bool success, bytes memory returnData) = tokenHandler.delegatecall( - abi.encodeWithSelector( - ITokenHandler.transferTokenFrom.selector, - tokenId, - msg.sender, - destinationAddress, - amount - ) + abi.encodeWithSelector(ITokenHandler.transferTokenFrom.selector, tokenId, msg.sender, destinationAddress, amount) ); if (!success) revert TokenHandlerFailed(returnData); (amount, token) = abi.decode(returnData, (uint256, IERC20)); @@ -1073,7 +1065,6 @@ contract InterchainTokenService is * @dev Takes token from a sender via the token service. `tokenOnly` indicates if the caller should be restricted to the token only. */ function _takeToken(bytes32 tokenId, address from, uint256 amount, bool tokenOnly) internal returns (uint256, string memory symbol) { - (bool success, bytes memory data) = tokenHandler.delegatecall( abi.encodeWithSelector(ITokenHandler.takeToken.selector, tokenId, tokenOnly, from, amount) ); diff --git a/contracts/TokenHandler.sol b/contracts/TokenHandler.sol index 12224f21..4f55cf91 100644 --- a/contracts/TokenHandler.sol +++ b/contracts/TokenHandler.sol @@ -15,7 +15,6 @@ import { IERC20MintableBurnable } from './interfaces/IERC20MintableBurnable.sol' import { IERC20BurnableFrom } from './interfaces/IERC20BurnableFrom.sol'; import { IERC20Named } from './interfaces/IERC20Named.sol'; - /** * @title TokenHandler * @notice This interface is responsible for handling tokens before initiating an interchain token transfer, or after receiving one. @@ -43,11 +42,7 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Crea * @return address the address of the token. */ // slither-disable-next-line locked-ether - function giveToken( - bytes32 tokenId, - address to, - uint256 amount - ) external payable returns (uint256, address) { + function giveToken(bytes32 tokenId, address to, uint256 amount) external payable returns (uint256, address) { address tokenManager = _create3Address(tokenId); (uint256 tokenManagerType, address tokenAddress) = ITokenManagerProxy(tokenManager).getImplementationTypeAndTokenAddress(); @@ -99,7 +94,6 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Crea address from, uint256 amount ) external payable returns (uint256, string memory symbol) { - address tokenManager = _create3Address(tokenId); (uint256 tokenManagerType, address tokenAddress) = ITokenManagerProxy(tokenManager).getImplementationTypeAndTokenAddress(); @@ -154,12 +148,7 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Crea * @return address The address of the token corresponding to the input tokenId. */ // slither-disable-next-line locked-ether - function transferTokenFrom( - bytes32 tokenId, - address from, - address to, - uint256 amount - ) external payable returns (uint256, address) { + function transferTokenFrom(bytes32 tokenId, address from, address to, uint256 amount) external payable returns (uint256, address) { address tokenManager = _create3Address(tokenId); (uint256 tokenManagerType, address tokenAddress) = ITokenManagerProxy(tokenManager).getImplementationTypeAndTokenAddress(); if ( diff --git a/contracts/interfaces/ITokenHandler.sol b/contracts/interfaces/ITokenHandler.sol index afdfeee9..2bc8da8a 100644 --- a/contracts/interfaces/ITokenHandler.sol +++ b/contracts/interfaces/ITokenHandler.sol @@ -25,11 +25,7 @@ interface ITokenHandler { * @return uint256 The amount of token actually given, which could be different for certain token type. * @return address the address of the token. */ - function giveToken( - bytes32 tokenId, - address to, - uint256 amount - ) external payable returns (uint256, address); + function giveToken(bytes32 tokenId, address to, uint256 amount) external payable returns (uint256, address); /** * @notice This function takes token from a specified address to the token manager. @@ -58,12 +54,8 @@ interface ITokenHandler { * @return address The address of the token corresponding to the input tokenId. */ // slither-disable-next-line locked-ether - function transferTokenFrom( - bytes32 tokenId, - address from, - address to, - uint256 amount - ) external payable returns (uint256, address); + function transferTokenFrom(bytes32 tokenId, address from, address to, uint256 amount) external payable returns (uint256, address); + /** * @notice This function prepares a token manager after it is deployed * @param tokenManagerType The token manager type. diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index da692edd..23151841 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -1255,7 +1255,7 @@ describe('Interchain Token Service', () => { it(`Should revert on transmit send token when not called by interchain token`, async () => { const errorSignatureHash = id('NotToken(address,address)'); const selector = errorSignatureHash.substring(0, 10); - const errorData = defaultAbiCoder.encode(['address', 'address'], [wallet.address, token.address]) + const errorData = defaultAbiCoder.encode(['address', 'address'], [wallet.address, token.address]); await expectRevert( (gasOptions) => @@ -2588,10 +2588,10 @@ describe('Interchain Token Service', () => { it('Should be able to send token only if it does not trigger the mint limit', async () => { await service.interchainTransfer(tokenId, destinationChain, destinationAddress, sendAmount, '0x', 0).then((tx) => tx.wait); - + const errorSignatureHash = id('FlowLimitExceeded(uint256,uint256,address)'); const selector = errorSignatureHash.substring(0, 10); - const errorData = defaultAbiCoder.encode(['uint256', 'uint256', 'address'], [flowLimit, 2 * sendAmount, tokenManager.address]) + const errorData = defaultAbiCoder.encode(['uint256', 'uint256', 'address'], [flowLimit, 2 * sendAmount, tokenManager.address]); await expectRevert( (gasOptions) => service.interchainTransfer(tokenId, destinationChain, destinationAddress, sendAmount, '0x', 0, gasOptions), @@ -2631,14 +2631,14 @@ describe('Interchain Token Service', () => { const errorSignatureHash = id('FlowLimitExceeded(uint256,uint256,address)'); const selector = errorSignatureHash.substring(0, 10); - const errorData = defaultAbiCoder.encode(['uint256', 'uint256', 'address'], [ - (5 * sendAmount) / 2, - 3 * sendAmount, - tokenManager.address, - ]) - + const errorData = defaultAbiCoder.encode( + ['uint256', 'uint256', 'address'], + [(5 * sendAmount) / 2, 3 * sendAmount, tokenManager.address], + ); - await expectRevert((gasOptions) => receiveToken(2 * sendAmount, gasOptions), service, 'GiveTokenFailed', [selector + errorData.substring(2)]); + await expectRevert((gasOptions) => receiveToken(2 * sendAmount, gasOptions), service, 'GiveTokenFailed', [ + selector + errorData.substring(2), + ]); }); it('Should be able to set flow limits for each token manager', async () => { From 05226cbe20f2b5f5824d420ec086e75abad6bcad Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 1 Apr 2024 11:55:35 +0300 Subject: [PATCH 16/16] Update contracts/interfaces/ITokenManagerType.sol Co-authored-by: Milap Sheth --- contracts/interfaces/ITokenManagerType.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/interfaces/ITokenManagerType.sol b/contracts/interfaces/ITokenManagerType.sol index c8d361c9..9c8406be 100644 --- a/contracts/interfaces/ITokenManagerType.sol +++ b/contracts/interfaces/ITokenManagerType.sol @@ -13,6 +13,6 @@ interface ITokenManagerType { LOCK_UNLOCK, // The token will be locked/unlocked at the token manager. LOCK_UNLOCK_FEE, // The token will be locked/unlocked at the token manager, which will account for any fee-on-transfer behaviour. MINT_BURN, // The token will be minted/burned on transfers. The token needs to give mint and burn permission to the token manager. - GATEWAY // The token will be moved throught the AxelarGateway via callContractWithToken + GATEWAY // The token will be transferred through the AxelarGateway via callContractWithToken } }