From fbaded0177fe62a006b6a0afba8346f63243755f Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 6 Jul 2023 14:35:21 +0300 Subject: [PATCH 01/54] renamed folder and changed version --- .../RemoteAddressValidator.sol | 0 package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename contracts/{linker-router => remote-address-validator}/RemoteAddressValidator.sol (100%) diff --git a/contracts/linker-router/RemoteAddressValidator.sol b/contracts/remote-address-validator/RemoteAddressValidator.sol similarity index 100% rename from contracts/linker-router/RemoteAddressValidator.sol rename to contracts/remote-address-validator/RemoteAddressValidator.sol diff --git a/package.json b/package.json index 09b43e6a..a0d543db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@axelar-network/interchain-token-service", - "version": "0.1.0", + "version": "1.0.0", "main": "index.js", "scripts": { "test": "npx hardhat test", From 8a6d845a66cdcbf12852247f0db3b8b468ab7c91 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 6 Jul 2023 19:25:59 +0300 Subject: [PATCH 02/54] npmignore --- .github/workflows/publish-to-npm.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/publish-to-npm.yaml b/.github/workflows/publish-to-npm.yaml index 1a2fd77e..e4c35868 100644 --- a/.github/workflows/publish-to-npm.yaml +++ b/.github/workflows/publish-to-npm.yaml @@ -17,7 +17,6 @@ jobs: registry-url: 'https://registry.npmjs.org' - run: npm install - run: npm run build - - run: cp -r artifacts/contracts/interfaces . - run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 607de5ceaa552c3f4c15c9720ee7a397865bc64a Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 6 Jul 2023 19:26:04 +0300 Subject: [PATCH 03/54] npmignore --- .npmignore | 26 ++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..4c95de20 --- /dev/null +++ b/.npmignore @@ -0,0 +1,26 @@ +# Modules +node_modules + +# dotenv environment variables file +.env +.env.test +env +.credentials +credentials + +# Logs +logs +*.log + +# Optional npm cache directory +.npm + +# builder cache +.cache +cache + +# workflows +.github + +# artifacts +artifacts \ No newline at end of file diff --git a/package.json b/package.json index a0d543db..4a02872d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "main": "index.js", "scripts": { "test": "npx hardhat test", - "build": "rm -rf cache artifacts && npx hardhat compile", + "build": "rm -rf cache artifacts dist && npx hardhat compile && mkdir dist && cp -r artifacts/contracts/** dist", "lint": "solhint 'contracts/**/*.sol' && eslint 'scripts/**/*.js' && eslint 'test/*.js'", "prettier": "prettier --write 'contracts/**/*.sol' 'scripts/**/*.js' 'test/*.js' '*.js' 'package.json' '.solhint.json' '.prettierrc'", "flatten": "sh scripts/flatten-contracts.sh", From f5b5f56cbc51448a46ecad9225b952d8744ec2c1 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 6 Jul 2023 19:27:54 +0300 Subject: [PATCH 04/54] change version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4a02872d..ce2ddf17 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@axelar-network/interchain-token-service", - "version": "1.0.0", + "version": "0.1.0", "main": "index.js", "scripts": { "test": "npx hardhat test", From 4c5738d855deab0de79b39308e4cefea0ad48c75 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 6 Jul 2023 19:42:17 +0300 Subject: [PATCH 05/54] using include pattern instead. --- .npmignore | 26 -------------------------- package.json | 11 ++++++++++- 2 files changed, 10 insertions(+), 27 deletions(-) delete mode 100644 .npmignore diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 4c95de20..00000000 --- a/.npmignore +++ /dev/null @@ -1,26 +0,0 @@ -# Modules -node_modules - -# dotenv environment variables file -.env -.env.test -env -.credentials -credentials - -# Logs -logs -*.log - -# Optional npm cache directory -.npm - -# builder cache -.cache -cache - -# workflows -.github - -# artifacts -artifacts \ No newline at end of file diff --git a/package.json b/package.json index ce2ddf17..670c10ee 100644 --- a/package.json +++ b/package.json @@ -34,5 +34,14 @@ "solhint": "^3.4.1", "solidity-docgen": "^0.6.0-beta.35" }, - "description": "" + "description": "", + "files": [ + "contracts", + "dist", + "docs", + "scripts", + "DESIGN.md", + "README.md", + "hardhat.config.js" + ] } From 0548a6ec0381573b033e914ae5ca0bc52a4b1e69 Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 11 Jul 2023 17:50:19 +0300 Subject: [PATCH 06/54] Fixed most of the things least auhority suggested. --- .../InterchainTokenService.sol | 4 +-- contracts/interfaces/IDistributable.sol | 14 ++++++++++ contracts/interfaces/IOperatable.sol | 14 ++++++++++ contracts/utils/Distributable.sol | 27 +++++++++++++++++++ contracts/utils/Operatable.sol | 26 ++++++++++++++++++ 5 files changed, 83 insertions(+), 2 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index b85b113a..e610c2e3 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -221,8 +221,7 @@ contract InterchainTokenService is * @return tokenManagerAddress the address of the TokenManagerImplementation. */ function getImplementation(uint256 tokenManagerType) external view returns (address tokenManagerAddress) { - // There could be a way to rewrite the following using assembly switch statements, which would be more gas efficient, - // but accessing immutable variables and/or enum values seems to be tricky, and would reduce code readability. + if(tokenManagerType > uint256(type(TokenManagerType).max)) revert InvalidImplementation(); if (TokenManagerType(tokenManagerType) == TokenManagerType.LOCK_UNLOCK) { return implementationLockUnlock; } else if (TokenManagerType(tokenManagerType) == TokenManagerType.MINT_BURN) { @@ -678,6 +677,7 @@ contract InterchainTokenService is address tokenAddress = getStandardizedTokenAddress(tokenId); address tokenManagerAddress = getTokenManagerAddress(tokenId); address distributor = distributorBytes.length > 0 ? distributorBytes.toAddress() : tokenManagerAddress; + if(distributor == address(0)) revert ZeroAddress(); _deployStandardizedToken(tokenId, distributor, name, symbol, decimals, 0, distributor); TokenManagerType tokenManagerType = distributor == tokenManagerAddress ? TokenManagerType.MINT_BURN : TokenManagerType.LOCK_UNLOCK; _deployTokenManager( diff --git a/contracts/interfaces/IDistributable.sol b/contracts/interfaces/IDistributable.sol index 3bba8e28..fbd32bbb 100644 --- a/contracts/interfaces/IDistributable.sol +++ b/contracts/interfaces/IDistributable.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; interface IDistributable { error NotDistributor(); + error NotProposedDistributor(); event DistributorChanged(address distributor); @@ -19,4 +20,17 @@ interface IDistributable { * @param distributor The address of the new distributor */ function setDistributor(address distributor) external; + + /** + * @notice Proposed a change of the distributor of the contract + * @dev Can only be called by the current distributor + * @param distr The address of the new distributor + */ + function proposeDistributorChange(address distr) external; + + /** + * @notice Accept a change of the distributor of the contract + * @dev Can only be called by the proposed distributor + */ + function acceptDistributorChange() external; } diff --git a/contracts/interfaces/IOperatable.sol b/contracts/interfaces/IOperatable.sol index ac40737b..f6c3c778 100644 --- a/contracts/interfaces/IOperatable.sol +++ b/contracts/interfaces/IOperatable.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; interface IOperatable { error NotOperator(); + error NotProposedOperator(); event OperatorChanged(address operator); @@ -19,4 +20,17 @@ interface IOperatable { * @param operator_ The address of the new operator */ function setOperator(address operator_) external; + + /** + * @notice Proposed a change of the operator of the contract + * @dev Can only be called by the current operator + * @param operator_ The address of the new operator + */ + function proposeOperatorChange(address operator_) external; + + /** + * @notice Accept a proposed change of operatorship + * @dev Can only be called by the proposed operator + */ + function acceptOperatorChange() external; } diff --git a/contracts/utils/Distributable.sol b/contracts/utils/Distributable.sol index 8cfd4693..0290ea3a 100644 --- a/contracts/utils/Distributable.sol +++ b/contracts/utils/Distributable.sol @@ -14,6 +14,9 @@ contract Distributable is IDistributable { // uint256(keccak256('distributor')) - 1 uint256 internal constant DISTRIBUTOR_SLOT = 0x71c5a35e45a25c49e8f747acd4bcb869814b3d104c492d2554f4c46e12371f56; + // uint256(keccak256('proposed-distributor')) - 1 + uint256 internal constant PROPOSED_DISTRIBUTOR_SLOT = 0xbb1aa7d30971a97896e14e460c5ace030e39b624cf8f7c1ce200eeb378d7dcf1; + /** * @dev Throws a NotDistributor custom eror if called by any account other than the distributor. */ @@ -51,4 +54,28 @@ contract Distributable is IDistributable { function setDistributor(address distr) external onlyDistributor { _setDistributor(distr); } + + /** + * @notice Proposed a change of the distributor of the contract + * @dev Can only be called by the current distributor + * @param distr The address of the new distributor + */ + function proposeDistributorChange(address distr) external onlyDistributor { + assembly { + sstore(PROPOSED_DISTRIBUTOR_SLOT, distr) + } + } + + /** + * @notice Accept a change of the distributor of the contract + * @dev Can only be called by the proposed distributor + */ + function acceptDistributorChange() external { + address proposedDistributor; + assembly { + proposedDistributor := sload(PROPOSED_DISTRIBUTOR_SLOT) + } + if(msg.sender != proposedDistributor) revert NotProposedDistributor(); + _setDistributor(proposedDistributor); + } } diff --git a/contracts/utils/Operatable.sol b/contracts/utils/Operatable.sol index d7f1e1f5..3b4f7660 100644 --- a/contracts/utils/Operatable.sol +++ b/contracts/utils/Operatable.sol @@ -13,6 +13,8 @@ import { IOperatable } from '../interfaces/IOperatable.sol'; contract Operatable is IOperatable { // uint256(keccak256('operator')) - 1 uint256 internal constant OPERATOR_SLOT = 0xf23ec0bb4210edd5cba85afd05127efcd2fc6a781bfed49188da1081670b22d7; + // uint256(keccak256('proposed-operator')) - 1 + uint256 internal constant PROPOSED_OPERATOR_SLOT = 0x18dd7104fe20f6107b1523000995e8f87ac02b734a65cf0a45fafa7635a2c526; /** * @dev Throws a NotOperator custom error if called by any account other than the operator. @@ -51,4 +53,28 @@ contract Operatable is IOperatable { function setOperator(address operator_) external onlyOperator { _setOperator(operator_); } + + /** + * @notice Proposed a change of the operator of the contract + * @dev Can only be called by the current operator + * @param operator_ The address of the new operator + */ + function proposeOperatorChange(address operator_) external onlyOperator { + assembly { + sstore(PROPOSED_OPERATOR_SLOT, operator_) + } + } + + /** + * @notice Accept a proposed change of operatorship + * @dev Can only be called by the proposed operator + */ + function acceptOperatorChange() external { + address proposedOperator; + assembly { + proposedOperator := sload(PROPOSED_OPERATOR_SLOT) + } + if(msg.sender != proposedOperator) revert NotProposedOperator(); + _setOperator(proposedOperator); + } } From 8397b0723373bb4f098d6d4410b1a3b61fcadc53 Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 11 Jul 2023 17:50:50 +0300 Subject: [PATCH 07/54] made lint happy --- .../interchain-token-service/InterchainTokenService.sol | 4 ++-- contracts/utils/Distributable.sol | 2 +- contracts/utils/Operatable.sol | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index e610c2e3..0ac2faff 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -221,7 +221,7 @@ contract InterchainTokenService is * @return tokenManagerAddress the address of the TokenManagerImplementation. */ function getImplementation(uint256 tokenManagerType) external view returns (address tokenManagerAddress) { - if(tokenManagerType > uint256(type(TokenManagerType).max)) revert InvalidImplementation(); + if (tokenManagerType > uint256(type(TokenManagerType).max)) revert InvalidImplementation(); if (TokenManagerType(tokenManagerType) == TokenManagerType.LOCK_UNLOCK) { return implementationLockUnlock; } else if (TokenManagerType(tokenManagerType) == TokenManagerType.MINT_BURN) { @@ -677,7 +677,7 @@ contract InterchainTokenService is address tokenAddress = getStandardizedTokenAddress(tokenId); address tokenManagerAddress = getTokenManagerAddress(tokenId); address distributor = distributorBytes.length > 0 ? distributorBytes.toAddress() : tokenManagerAddress; - if(distributor == address(0)) revert ZeroAddress(); + if (distributor == address(0)) revert ZeroAddress(); _deployStandardizedToken(tokenId, distributor, name, symbol, decimals, 0, distributor); TokenManagerType tokenManagerType = distributor == tokenManagerAddress ? TokenManagerType.MINT_BURN : TokenManagerType.LOCK_UNLOCK; _deployTokenManager( diff --git a/contracts/utils/Distributable.sol b/contracts/utils/Distributable.sol index 0290ea3a..dbe193f4 100644 --- a/contracts/utils/Distributable.sol +++ b/contracts/utils/Distributable.sol @@ -75,7 +75,7 @@ contract Distributable is IDistributable { assembly { proposedDistributor := sload(PROPOSED_DISTRIBUTOR_SLOT) } - if(msg.sender != proposedDistributor) revert NotProposedDistributor(); + if (msg.sender != proposedDistributor) revert NotProposedDistributor(); _setDistributor(proposedDistributor); } } diff --git a/contracts/utils/Operatable.sol b/contracts/utils/Operatable.sol index 3b4f7660..f33413c5 100644 --- a/contracts/utils/Operatable.sol +++ b/contracts/utils/Operatable.sol @@ -53,7 +53,7 @@ contract Operatable is IOperatable { function setOperator(address operator_) external onlyOperator { _setOperator(operator_); } - + /** * @notice Proposed a change of the operator of the contract * @dev Can only be called by the current operator @@ -64,7 +64,7 @@ contract Operatable is IOperatable { sstore(PROPOSED_OPERATOR_SLOT, operator_) } } - + /** * @notice Accept a proposed change of operatorship * @dev Can only be called by the proposed operator @@ -74,7 +74,7 @@ contract Operatable is IOperatable { assembly { proposedOperator := sload(PROPOSED_OPERATOR_SLOT) } - if(msg.sender != proposedOperator) revert NotProposedOperator(); + if (msg.sender != proposedOperator) revert NotProposedOperator(); _setOperator(proposedOperator); } } From f2e5ea86c72616cd9173c6e3f1f46caca30f93d6 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 12 Jul 2023 05:18:23 -0400 Subject: [PATCH 08/54] Apply suggestions from code review --- contracts/interfaces/IDistributable.sol | 2 +- contracts/utils/Distributable.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/interfaces/IDistributable.sol b/contracts/interfaces/IDistributable.sol index fbd32bbb..61495842 100644 --- a/contracts/interfaces/IDistributable.sol +++ b/contracts/interfaces/IDistributable.sol @@ -26,7 +26,7 @@ interface IDistributable { * @dev Can only be called by the current distributor * @param distr The address of the new distributor */ - function proposeDistributorChange(address distr) external; + function proposeDistributorChange(address distributor_) external; /** * @notice Accept a change of the distributor of the contract diff --git a/contracts/utils/Distributable.sol b/contracts/utils/Distributable.sol index dbe193f4..661443a5 100644 --- a/contracts/utils/Distributable.sol +++ b/contracts/utils/Distributable.sol @@ -60,9 +60,9 @@ contract Distributable is IDistributable { * @dev Can only be called by the current distributor * @param distr The address of the new distributor */ - function proposeDistributorChange(address distr) external onlyDistributor { + function proposeDistributorChange(address distributor_) external onlyDistributor { assembly { - sstore(PROPOSED_DISTRIBUTOR_SLOT, distr) + sstore(PROPOSED_DISTRIBUTOR_SLOT, distributor_) } } From 32ce490b5206c4f194212a300539b687debd4363 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 19 Jul 2023 18:30:06 +0300 Subject: [PATCH 09/54] fixed some bugs --- contracts/interfaces/IDistributable.sol | 2 +- contracts/utils/Distributable.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/interfaces/IDistributable.sol b/contracts/interfaces/IDistributable.sol index 61495842..3c5ecbc3 100644 --- a/contracts/interfaces/IDistributable.sol +++ b/contracts/interfaces/IDistributable.sol @@ -24,7 +24,7 @@ interface IDistributable { /** * @notice Proposed a change of the distributor of the contract * @dev Can only be called by the current distributor - * @param distr The address of the new distributor + * @param distributor_ The address of the new distributor */ function proposeDistributorChange(address distributor_) external; diff --git a/contracts/utils/Distributable.sol b/contracts/utils/Distributable.sol index 661443a5..44c7f1db 100644 --- a/contracts/utils/Distributable.sol +++ b/contracts/utils/Distributable.sol @@ -58,7 +58,7 @@ contract Distributable is IDistributable { /** * @notice Proposed a change of the distributor of the contract * @dev Can only be called by the current distributor - * @param distr The address of the new distributor + * @param distributor_ The address of the new distributor */ function proposeDistributorChange(address distributor_) external onlyDistributor { assembly { From dc516fe4df8c8ed3cf18281b05fa893b72889e83 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 19 Jul 2023 18:59:53 +0300 Subject: [PATCH 10/54] added events --- contracts/interfaces/IDistributable.sol | 3 ++- contracts/interfaces/IOperatable.sol | 3 ++- contracts/utils/Distributable.sol | 1 + contracts/utils/Operatable.sol | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/contracts/interfaces/IDistributable.sol b/contracts/interfaces/IDistributable.sol index 3c5ecbc3..08d7c7a7 100644 --- a/contracts/interfaces/IDistributable.sol +++ b/contracts/interfaces/IDistributable.sol @@ -6,7 +6,8 @@ interface IDistributable { error NotDistributor(); error NotProposedDistributor(); - event DistributorChanged(address distributor); + event DistributorChanged(address indexed distributor); + event DistributorChangeProposed(address indexed distributor); /** * @notice Get the address of the distributor diff --git a/contracts/interfaces/IOperatable.sol b/contracts/interfaces/IOperatable.sol index f6c3c778..9266c52d 100644 --- a/contracts/interfaces/IOperatable.sol +++ b/contracts/interfaces/IOperatable.sol @@ -6,7 +6,8 @@ interface IOperatable { error NotOperator(); error NotProposedOperator(); - event OperatorChanged(address operator); + event OperatorChanged(address indexed operator); + event OperatorChangeProposed(address indexed operator); /** * @notice Get the address of the operator diff --git a/contracts/utils/Distributable.sol b/contracts/utils/Distributable.sol index 44c7f1db..196fb04f 100644 --- a/contracts/utils/Distributable.sol +++ b/contracts/utils/Distributable.sol @@ -64,6 +64,7 @@ contract Distributable is IDistributable { assembly { sstore(PROPOSED_DISTRIBUTOR_SLOT, distributor_) } + emit DistributorChangeProposed(distributor_); } /** diff --git a/contracts/utils/Operatable.sol b/contracts/utils/Operatable.sol index f33413c5..cb6232ed 100644 --- a/contracts/utils/Operatable.sol +++ b/contracts/utils/Operatable.sol @@ -63,6 +63,7 @@ contract Operatable is IOperatable { assembly { sstore(PROPOSED_OPERATOR_SLOT, operator_) } + emit OperatorChangeProposed(operator_); } /** From d6fa384328152974b4cf70ea0c7de1da83f77c9e Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 24 Jul 2023 16:36:54 +0300 Subject: [PATCH 11/54] rename set to transfer for distributor and operator --- contracts/examples/CustomTokenExample.sol | 28 +++++++++++++++++++++++ contracts/interfaces/IDistributable.sol | 2 +- contracts/interfaces/IOperatable.sol | 2 +- contracts/utils/Distributable.sol | 2 +- contracts/utils/Operatable.sol | 2 +- docs/index.md | 16 ++++++------- test/tokenService.js | 2 +- test/tokenServiceFullFlow.js | 4 ++-- test/utils.js | 8 +++---- 9 files changed, 47 insertions(+), 19 deletions(-) create mode 100644 contracts/examples/CustomTokenExample.sol diff --git a/contracts/examples/CustomTokenExample.sol b/contracts/examples/CustomTokenExample.sol new file mode 100644 index 00000000..e9c92675 --- /dev/null +++ b/contracts/examples/CustomTokenExample.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { IInterchainTokenService } from '../interfaces/IInterchainTokenService.sol'; +import { CustomERC20 } from './CustomERC20.sol'; +import { AddressBytesUtils } from '../libraries/AddressBytesUtils.sol'; +import { ITokenManagerType } from '../interfaces/ITokenManagerType.sol'; + +// This will deploy standardized tokens everywhere with mint/burn token managers, and will have an initial supploy +contract CustomTokenExample is ITokenManagerType { + using AddressBytesUtils for address; + + address public immutable tokenAddress; + IInterchainTokenService public immutable service; + + constructor(string memory name, string memory symbol, uint8 decimals, address service_, uint256 tokenCap) { + service = IInterchainTokenService(service_); + tokenAddress = address(new CustomERC20(name, symbol, decimals, address(this), tokenCap)); + } + + function deployTokenManager(bytes32 salt) external { + bytes memory params = service.getParamsMintBurn(address(this).toBytes(), tokenAddress); + bytes32 tokenId = service.deployCustomTokenManager(salt, TokenManagerType.MINT_BURN, params); + address tokenManager = service.getTokenManagerAddress(tokenId); + CustomERC20(tokenAddress).transferDistributorship(tokenManager); + } +} \ No newline at end of file diff --git a/contracts/interfaces/IDistributable.sol b/contracts/interfaces/IDistributable.sol index 08d7c7a7..cf765b70 100644 --- a/contracts/interfaces/IDistributable.sol +++ b/contracts/interfaces/IDistributable.sol @@ -20,7 +20,7 @@ interface IDistributable { * @dev Can only be called by the current distributor * @param distributor The address of the new distributor */ - function setDistributor(address distributor) external; + function transferDistributorship(address distributor) external; /** * @notice Proposed a change of the distributor of the contract diff --git a/contracts/interfaces/IOperatable.sol b/contracts/interfaces/IOperatable.sol index 9266c52d..7b7dd22c 100644 --- a/contracts/interfaces/IOperatable.sol +++ b/contracts/interfaces/IOperatable.sol @@ -20,7 +20,7 @@ interface IOperatable { * @dev Can only be called by the current operator * @param operator_ The address of the new operator */ - function setOperator(address operator_) external; + function transferOperatorship(address operator_) external; /** * @notice Proposed a change of the operator of the contract diff --git a/contracts/utils/Distributable.sol b/contracts/utils/Distributable.sol index 196fb04f..3a0c1d88 100644 --- a/contracts/utils/Distributable.sol +++ b/contracts/utils/Distributable.sol @@ -51,7 +51,7 @@ contract Distributable is IDistributable { * @dev Can only be called by the current distributor * @param distr The address of the new distributor */ - function setDistributor(address distr) external onlyDistributor { + function transferDistributorship(address distr) external onlyDistributor { _setDistributor(distr); } diff --git a/contracts/utils/Operatable.sol b/contracts/utils/Operatable.sol index cb6232ed..2090cde5 100644 --- a/contracts/utils/Operatable.sol +++ b/contracts/utils/Operatable.sol @@ -50,7 +50,7 @@ contract Operatable is IOperatable { * @dev Can only be called by the current operator * @param operator_ The address of the new operator */ - function setOperator(address operator_) external onlyOperator { + function transferOperatorship(address operator_) external onlyOperator { _setOperator(operator_); } diff --git a/docs/index.md b/docs/index.md index 8de2e1b2..a54c9cfd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1799,10 +1799,10 @@ Get the address of the operator | ---- | ---- | ----------- | | operator_ | address | of the operator | -### setOperator +### transferOperatorship ```solidity -function setOperator(address operator_) external +function transferOperatorship(address operator_) external ``` Change the operator of the contract @@ -2851,10 +2851,10 @@ _Internal function that stores the new operator address in the operator storage | ---- | ---- | ----------- | | operator_ | address | The address of the new operator | -### setOperator +### transferOperatorship ```solidity -function setOperator(address operator_) external +function transferOperatorship(address operator_) external ``` Change the operator of the contract @@ -3111,10 +3111,10 @@ Get the address of the distributor | ---- | ---- | ----------- | | distributor | address | of the distributor | -### setDistributor +### transferDistributorship ```solidity -function setDistributor(address distributor) external +function transferDistributorship(address distributor) external ``` Change the distributor of the contract @@ -4722,10 +4722,10 @@ _Internal function that stores the new distributor address in the correct storag | ---- | ---- | ----------- | | distributor_ | address | The address of the new distributor | -### setDistributor +### transferDistributorship ```solidity -function setDistributor(address distr) external +function transferDistributorship(address distr) external ``` Change the distributor of the contract diff --git a/test/tokenService.js b/test/tokenService.js index 14e92cf6..f62917c0 100644 --- a/test/tokenService.js +++ b/test/tokenService.js @@ -64,7 +64,7 @@ describe('Interchain Token Service', () => { await (await token.mint(wallet.address, mintAmount)).wait(); } - await (await token.setDistributor(tokenManagerAddress)).wait(); + await (await token.transferDistributorship(tokenManagerAddress)).wait(); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); await (await service.deployCustomTokenManager(salt, MINT_BURN, params)).wait(); diff --git a/test/tokenServiceFullFlow.js b/test/tokenServiceFullFlow.js index fcd29264..aae64f98 100644 --- a/test/tokenServiceFullFlow.js +++ b/test/tokenServiceFullFlow.js @@ -144,7 +144,7 @@ describe('Interchain Token Service', () => { 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.setDistributor(newAddress)).to.emit(token, 'DistributorChanged').withArgs(newAddress); + await expect(token.transferDistributorship(newAddress)).to.emit(token, 'DistributorChanged').withArgs(newAddress); await expect(token.mint(newAddress, amount)).to.be.revertedWithCustomError(token, 'NotDistributor'); await expect(token.burn(newAddress, amount)).to.be.revertedWithCustomError(token, 'NotDistributor'); @@ -251,7 +251,7 @@ describe('Interchain Token Service', () => { 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.setDistributor(newAddress)).to.emit(token, 'DistributorChanged').withArgs(newAddress); + await expect(token.transferDistributorship(newAddress)).to.emit(token, 'DistributorChanged').withArgs(newAddress); await expect(token.mint(newAddress, amount)).to.be.revertedWithCustomError(token, 'NotDistributor'); await expect(token.burn(newAddress, amount)).to.be.revertedWithCustomError(token, 'NotDistributor'); diff --git a/test/utils.js b/test/utils.js index 41050122..207fc25f 100644 --- a/test/utils.js +++ b/test/utils.js @@ -39,9 +39,9 @@ describe('Operatable', () => { it('Should be able to change the operator only as the operator', async () => { expect(await test.operator()).to.equal(ownerWallet.address); - await expect(test.setOperator(otherWallet.address)).to.emit(test, 'OperatorChanged').withArgs(otherWallet.address); + await expect(test.transferOperatorship(otherWallet.address)).to.emit(test, 'OperatorChanged').withArgs(otherWallet.address); expect(await test.operator()).to.equal(otherWallet.address); - await expect(test.setOperator(otherWallet.address)).to.be.revertedWithCustomError(test, 'NotOperator'); + await expect(test.transferOperatorship(otherWallet.address)).to.be.revertedWithCustomError(test, 'NotOperator'); }); }); @@ -62,9 +62,9 @@ describe('Distributable', () => { it('Should be able to change the distributor only as the distributor', async () => { expect(await test.distributor()).to.equal(ownerWallet.address); - await expect(test.setDistributor(otherWallet.address)).to.emit(test, 'DistributorChanged').withArgs(otherWallet.address); + await expect(test.transferDistributorship(otherWallet.address)).to.emit(test, 'DistributorChanged').withArgs(otherWallet.address); expect(await test.distributor()).to.equal(otherWallet.address); - await expect(test.setDistributor(otherWallet.address)).to.be.revertedWithCustomError(test, 'NotDistributor'); + await expect(test.transferDistributorship(otherWallet.address)).to.be.revertedWithCustomError(test, 'NotDistributor'); }); }); From 5cc8fbbb9158c28519c32f623b677fca6d7ef3bc Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 24 Jul 2023 18:01:44 +0300 Subject: [PATCH 12/54] changed standardized token to always allow token managers to mint/burn it. --- contracts/examples/CustomTokenExample.sol | 28 ---------- .../InterchainTokenService.sol | 6 +-- .../interchain-token/InterchainToken.sol | 52 ++++++------------- .../interfaces/IERC20BurnableMintable.sol | 4 +- contracts/interfaces/IInterchainToken.sol | 12 ++++- contracts/interfaces/IStandardizedToken.sol | 4 +- contracts/interfaces/ITokenManager.sol | 5 ++ contracts/test/InterchainTokenTest.sol | 12 ++++- .../StandardizedToken.sol | 13 +++-- .../StandardizedTokenLockUnlock.sol | 11 ---- .../StandardizedTokenMintBurn.sol | 11 ---- contracts/token-manager/TokenManager.sol | 7 +++ contracts/utils/StandardizedTokenDeployer.sol | 14 ++--- scripts/deploy.js | 6 +-- test/tokenService.js | 49 ++--------------- test/tokenServiceFullFlow.js | 14 +++-- test/utils.js | 38 ++------------ 17 files changed, 85 insertions(+), 201 deletions(-) delete mode 100644 contracts/examples/CustomTokenExample.sol delete mode 100644 contracts/token-implementations/StandardizedTokenLockUnlock.sol delete mode 100644 contracts/token-implementations/StandardizedTokenMintBurn.sol diff --git a/contracts/examples/CustomTokenExample.sol b/contracts/examples/CustomTokenExample.sol deleted file mode 100644 index e9c92675..00000000 --- a/contracts/examples/CustomTokenExample.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import { IInterchainTokenService } from '../interfaces/IInterchainTokenService.sol'; -import { CustomERC20 } from './CustomERC20.sol'; -import { AddressBytesUtils } from '../libraries/AddressBytesUtils.sol'; -import { ITokenManagerType } from '../interfaces/ITokenManagerType.sol'; - -// This will deploy standardized tokens everywhere with mint/burn token managers, and will have an initial supploy -contract CustomTokenExample is ITokenManagerType { - using AddressBytesUtils for address; - - address public immutable tokenAddress; - IInterchainTokenService public immutable service; - - constructor(string memory name, string memory symbol, uint8 decimals, address service_, uint256 tokenCap) { - service = IInterchainTokenService(service_); - tokenAddress = address(new CustomERC20(name, symbol, decimals, address(this), tokenCap)); - } - - function deployTokenManager(bytes32 salt) external { - bytes memory params = service.getParamsMintBurn(address(this).toBytes(), tokenAddress); - bytes32 tokenId = service.deployCustomTokenManager(salt, TokenManagerType.MINT_BURN, params); - address tokenManager = service.getTokenManagerAddress(tokenId); - CustomERC20(tokenAddress).transferDistributorship(tokenManager); - } -} \ No newline at end of file diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 0ac2faff..eff33adf 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -391,9 +391,8 @@ contract InterchainTokenService is bytes32 tokenId = getCustomTokenId(msg.sender, salt); _deployStandardizedToken(tokenId, distributor, name, symbol, decimals, mintAmount, msg.sender); address tokenManagerAddress = getTokenManagerAddress(tokenId); - TokenManagerType tokenManagerType = distributor == tokenManagerAddress ? TokenManagerType.MINT_BURN : TokenManagerType.LOCK_UNLOCK; address tokenAddress = getStandardizedTokenAddress(tokenId); - _deployTokenManager(tokenId, tokenManagerType, abi.encode(msg.sender.toBytes(), tokenAddress)); + _deployTokenManager(tokenId, TokenManagerType.MINT_BURN, abi.encode(msg.sender.toBytes(), tokenAddress)); } /** @@ -679,10 +678,9 @@ contract InterchainTokenService is address distributor = distributorBytes.length > 0 ? distributorBytes.toAddress() : tokenManagerAddress; if (distributor == address(0)) revert ZeroAddress(); _deployStandardizedToken(tokenId, distributor, name, symbol, decimals, 0, distributor); - TokenManagerType tokenManagerType = distributor == tokenManagerAddress ? TokenManagerType.MINT_BURN : TokenManagerType.LOCK_UNLOCK; _deployTokenManager( tokenId, - tokenManagerType, + TokenManagerType.MINT_BURN, abi.encode(operatorBytes.length == 0 ? address(this).toBytes() : operatorBytes, tokenAddress) ); } diff --git a/contracts/interchain-token/InterchainToken.sol b/contracts/interchain-token/InterchainToken.sol index 5fe61aa8..37d9ebd1 100644 --- a/contracts/interchain-token/InterchainToken.sol +++ b/contracts/interchain-token/InterchainToken.sol @@ -20,17 +20,6 @@ abstract contract InterchainToken is IInterchainToken, ERC20 { */ function getTokenManager() public view virtual returns (ITokenManager tokenManager); - /** - * @notice Getter function specifiying if the tokenManager requires approval to facilitate cross-chain transfers. - * Usually, only mint/burn tokenManagers do not need approval. - * @dev The return value depends on the implementation of ERC20. - * In case of lock/unlock and liquidity pool TokenManagers it is possible to implement transferFrom to allow the - * TokenManager specifically to do it permissionlesly. - * On the other hand you can implement burn in a way that requires approval for a mint/burn TokenManager - * @return tokenManager the TokenManager called to facilitate cross chain transfers. - */ - function tokenManagerRequiresApproval() public view virtual returns (bool); - /** * @notice Implementation of the interchainTransfer method * @dev We chose to either pass `metadata` as raw data on a remote contract call, or, if no data is passed, just do a transfer. @@ -47,22 +36,10 @@ abstract contract InterchainToken is IInterchainToken, ERC20 { bytes calldata metadata ) external payable { address sender = msg.sender; - ITokenManager tokenManager = getTokenManager(); - /** - * @dev if you know the value of `tokenManagerRequiresApproval()` you can just skip the if statement and just do nothing or _approve. - */ - if (tokenManagerRequiresApproval()) { - uint256 allowance_ = allowance[sender][address(tokenManager)]; - if (allowance_ != type(uint256).max) { - if (allowance_ > type(uint256).max - amount) { - allowance_ = type(uint256).max - amount; - } + + _beforeInterchainTransfer(msg.sender, destinationChain, recipient, amount, metadata); - _approve(sender, address(tokenManager), allowance_ + amount); - } - } - - // Metadata semantics are defined by the token service and thus should be passed as-is. + ITokenManager tokenManager = getTokenManager(); tokenManager.transmitInterchainTransfer{ value: msg.value }(sender, destinationChain, recipient, amount, metadata); } @@ -88,19 +65,20 @@ abstract contract InterchainToken is IInterchainToken, ERC20 { if (_allowance != type(uint256).max) { _approve(sender, msg.sender, _allowance - amount); } + + _beforeInterchainTransfer(msg.sender, destinationChain, recipient, amount, metadata); ITokenManager tokenManager = getTokenManager(); - if (tokenManagerRequiresApproval()) { - uint256 allowance_ = allowance[sender][address(tokenManager)]; - if (allowance_ != type(uint256).max) { - if (allowance_ > type(uint256).max - amount) { - allowance_ = type(uint256).max - amount; - } - - _approve(sender, address(tokenManager), allowance_ + amount); - } - } - tokenManager.transmitInterchainTransfer{ value: msg.value }(sender, destinationChain, recipient, amount, metadata); } + + /** + * @notice A method to be overwritten that will be called before an interchain transfer. You can approve the tokenManager here if you need and want to, to allow users for a 1-call transfer in case of a lock-unlock token manager. + * @param from the sender of the tokens. They need to have approved `msg.sender` before this is called. + * @param destinationChain the string representation of the destination chain. + * @param destinationAddress the bytes representation of the address of the recipient. + * @param amount the amount of token to be transfered. + * @param metadata either empty, to just facilitate a cross-chain transfer, or the data to be passed to a cross-chain contract call and transfer. + */ + function _beforeInterchainTransfer(address from, string calldata destinationChain, bytes calldata destinationAddress, uint256 amount, bytes calldata metadata) internal virtual {} } diff --git a/contracts/interfaces/IERC20BurnableMintable.sol b/contracts/interfaces/IERC20BurnableMintable.sol index 8c18a2a9..12925185 100644 --- a/contracts/interfaces/IERC20BurnableMintable.sol +++ b/contracts/interfaces/IERC20BurnableMintable.sol @@ -2,12 +2,10 @@ pragma solidity ^0.8.0; -import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; - /** * @dev Interface of the ERC20 standard as defined in the EIP. */ -interface IERC20BurnableMintable is IERC20 { +interface IERC20BurnableMintable { /** * @notice Function to mint new tokens * Can only be called by the distributor address. diff --git a/contracts/interfaces/IInterchainToken.sol b/contracts/interfaces/IInterchainToken.sol index 3e2563c3..d3a5fa56 100644 --- a/contracts/interfaces/IInterchainToken.sol +++ b/contracts/interfaces/IInterchainToken.sol @@ -2,12 +2,20 @@ pragma solidity ^0.8.0; -import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; +import { ITokenManager } from './ITokenManager.sol'; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ -interface IInterchainToken is IERC20 { +interface IInterchainToken { + + /** + * @notice Getter for the tokenManager used for this token. + * @dev Needs to be overwitten. + * @return tokenManager the TokenManager called to facilitate cross chain transfers. + */ + function getTokenManager() external view returns (ITokenManager tokenManager); + /** * @notice Implementation of the interchainTransfer method * @dev We chose to either pass `metadata` as raw data on a remote contract call, or, if no data is passed, just do a transfer. diff --git a/contracts/interfaces/IStandardizedToken.sol b/contracts/interfaces/IStandardizedToken.sol index 13b6c247..bb49c28e 100644 --- a/contracts/interfaces/IStandardizedToken.sol +++ b/contracts/interfaces/IStandardizedToken.sol @@ -5,13 +5,15 @@ pragma solidity ^0.8.0; import { IInterchainToken } from './IInterchainToken.sol'; import { IDistributable } from './IDistributable.sol'; import { IERC20BurnableMintable } from './IERC20BurnableMintable.sol'; +import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; + /** * @title StandardizedToken * @notice This contract implements a standardized token which extends InterchainToken functionality. * This contract also inherits Distributable and Implementation logic. */ -interface IStandardizedToken is IInterchainToken, IDistributable, IERC20BurnableMintable { +interface IStandardizedToken is IInterchainToken, IDistributable, IERC20BurnableMintable, IERC20 { /** * @notice Returns the contract id, which a proxy can check to ensure no false implementation was used. */ diff --git a/contracts/interfaces/ITokenManager.sol b/contracts/interfaces/ITokenManager.sol index 30d35f9b..832b1473 100644 --- a/contracts/interfaces/ITokenManager.sol +++ b/contracts/interfaces/ITokenManager.sol @@ -18,6 +18,11 @@ interface ITokenManager is ITokenManagerType, IOperatable, IFlowLimit, IImplemen error GiveTokenFailed(); error NotToken(); + /** + * @notice A function that returns the token id. + */ + function tokenId() external view returns (bytes32); + /** * @notice A function that should return the address of the token. * Must be overridden in the inheriting contract. diff --git a/contracts/test/InterchainTokenTest.sol b/contracts/test/InterchainTokenTest.sol index a0dfb6d4..3724f7b1 100644 --- a/contracts/test/InterchainTokenTest.sol +++ b/contracts/test/InterchainTokenTest.sol @@ -26,8 +26,16 @@ contract InterchainTokenTest is InterchainToken, Distributable, IERC20BurnableMi return tokenManager; } - function tokenManagerRequiresApproval() public view override returns (bool) { - return tokenManagerRequiresApproval_; + function _beforeInterchainTransfer(address sender, string calldata /*destinationChain*/, bytes calldata /*destinationAddress*/, uint256 amount, bytes calldata /*metadata*/) internal override { + if(!tokenManagerRequiresApproval_) return; + uint256 allowance_ = allowance[sender][address(tokenManager)]; + if (allowance_ != type(uint256).max) { + if (allowance_ > type(uint256).max - amount) { + allowance_ = type(uint256).max - amount; + } + + _approve(sender, address(tokenManager), allowance_ + amount); + } } function setTokenManagerRequiresApproval(bool requiresApproval) public { diff --git a/contracts/token-implementations/StandardizedToken.sol b/contracts/token-implementations/StandardizedToken.sol index 82eb3151..830a49af 100644 --- a/contracts/token-implementations/StandardizedToken.sol +++ b/contracts/token-implementations/StandardizedToken.sol @@ -16,7 +16,7 @@ import { Distributable } from '../utils/Distributable.sol'; * @notice This contract implements a standardized token which extends InterchainToken functionality. * This contract also inherits Distributable and Implementation logic. */ -abstract contract StandardizedToken is InterchainToken, ERC20Permit, Implementation, Distributable { +contract StandardizedToken is InterchainToken, ERC20Permit, Implementation, Distributable { using AddressBytesUtils for bytes; address public tokenManager; @@ -26,6 +26,13 @@ abstract contract StandardizedToken is InterchainToken, ERC20Permit, Implementat bytes32 private constant CONTRACT_ID = keccak256('standardized-token'); + modifier onlyDistributorOrTokenManager { + if(msg.sender != tokenManager) { + if(msg.sender != distributor()) revert NotDistributor(); + } + _; + } + /** * @notice Getter for the contract id. */ @@ -73,7 +80,7 @@ abstract contract StandardizedToken is InterchainToken, ERC20Permit, Implementat * @param account The address that will receive the minted tokens * @param amount The amount of tokens to mint */ - function mint(address account, uint256 amount) external onlyDistributor { + function mint(address account, uint256 amount) external onlyDistributorOrTokenManager { _mint(account, amount); } @@ -83,7 +90,7 @@ abstract contract StandardizedToken is InterchainToken, ERC20Permit, Implementat * @param account The address that will have its tokens burnt * @param amount The amount of tokens to burn */ - function burn(address account, uint256 amount) external onlyDistributor { + function burn(address account, uint256 amount) external onlyDistributorOrTokenManager { _burn(account, amount); } } diff --git a/contracts/token-implementations/StandardizedTokenLockUnlock.sol b/contracts/token-implementations/StandardizedTokenLockUnlock.sol deleted file mode 100644 index bc403f8c..00000000 --- a/contracts/token-implementations/StandardizedTokenLockUnlock.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import { StandardizedToken } from './StandardizedToken.sol'; - -contract StandardizedTokenLockUnlock is StandardizedToken { - function tokenManagerRequiresApproval() public pure override returns (bool) { - return true; - } -} diff --git a/contracts/token-implementations/StandardizedTokenMintBurn.sol b/contracts/token-implementations/StandardizedTokenMintBurn.sol deleted file mode 100644 index 7b78a5ea..00000000 --- a/contracts/token-implementations/StandardizedTokenMintBurn.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import { StandardizedToken } from './StandardizedToken.sol'; - -contract StandardizedTokenMintBurn is StandardizedToken { - function tokenManagerRequiresApproval() public pure override returns (bool) { - return false; - } -} diff --git a/contracts/token-manager/TokenManager.sol b/contracts/token-manager/TokenManager.sol index 15ec8ffe..24fcff1c 100644 --- a/contracts/token-manager/TokenManager.sol +++ b/contracts/token-manager/TokenManager.sol @@ -52,6 +52,13 @@ abstract contract TokenManager is ITokenManager, Operatable, FlowLimit, Implemen */ function tokenAddress() public view virtual returns (address); + /** + * @notice A function that returns the token id. + */ + function tokenId() external view returns (bytes32) { + return this.tokenId(); + } + /** * @dev This function should only be called by the proxy, and only once from the proxy constructor * @param params the parameters to be used to initialize the TokenManager. The exact format depends diff --git a/contracts/utils/StandardizedTokenDeployer.sol b/contracts/utils/StandardizedTokenDeployer.sol index b91f2d5b..32e02e75 100644 --- a/contracts/utils/StandardizedTokenDeployer.sol +++ b/contracts/utils/StandardizedTokenDeployer.sol @@ -14,21 +14,18 @@ import { StandardizedTokenProxy } from '../proxies/StandardizedTokenProxy.sol'; */ contract StandardizedTokenDeployer is IStandardizedTokenDeployer { Create3Deployer public immutable deployer; - address public immutable implementationMintBurnAddress; - address public immutable implementationLockUnlockAddress; + address public immutable implementationAddress; /** * @notice Constructor for the StandardizedTokenDeployer contract * @param deployer_ Address of the Create3Deployer contract - * @param implementationLockUnlockAddress_ Address of the StandardizedTokenLockUnlock contract - * @param implementationMintBurnAddress_ Address of the StandardizedTokenMintBurn contract + * @param implementationAddress_ Address of the StandardizedToken contract */ - constructor(address deployer_, address implementationLockUnlockAddress_, address implementationMintBurnAddress_) { - if (deployer_ == address(0) || implementationLockUnlockAddress_ == address(0) || implementationMintBurnAddress_ == address(0)) + constructor(address deployer_, address implementationAddress_) { + if (deployer_ == address(0) || implementationAddress_ == address(0)) revert AddressZero(); deployer = Create3Deployer(deployer_); - implementationLockUnlockAddress = implementationLockUnlockAddress_; - implementationMintBurnAddress = implementationMintBurnAddress_; + implementationAddress = implementationAddress_; } /** @@ -53,7 +50,6 @@ contract StandardizedTokenDeployer is IStandardizedTokenDeployer { address mintTo ) external payable { bytes memory bytecode; - address implementationAddress = distributor == tokenManager ? implementationMintBurnAddress : implementationLockUnlockAddress; { bytes memory params = abi.encode(tokenManager, distributor, name, symbol, decimals, mintAmount, mintTo); bytecode = abi.encodePacked(type(StandardizedTokenProxy).creationCode, abi.encode(implementationAddress, params)); diff --git a/scripts/deploy.js b/scripts/deploy.js index 42d28515..d23320a4 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -81,12 +81,10 @@ async function deployAll(wallet, chainName, deploymentKey = 'interchainTokenServ const gateway = await deployMockGateway(wallet); const gasService = await deployGasService(wallet); const tokenManagerDeployer = await deployContract(wallet, 'TokenManagerDeployer', [create3Deployer.address]); - const standardizedTokenLockUnlock = await deployContract(wallet, 'StandardizedTokenLockUnlock'); - const standardizedTokenMintBurn = await deployContract(wallet, 'StandardizedTokenMintBurn'); + const standardizedToken = await deployContract(wallet, 'StandardizedToken'); const standardizedTokenDeployer = await deployContract(wallet, 'StandardizedTokenDeployer', [ create3Deployer.address, - standardizedTokenLockUnlock.address, - standardizedTokenMintBurn.address, + standardizedToken.address, ]); const interchainTokenServiceAddress = await getCreate3Address(create3Deployer.address, wallet, deploymentKey); const remoteAddressValidator = await deployRemoteAddressValidator(wallet, interchainTokenServiceAddress); diff --git a/test/tokenService.js b/test/tokenService.js index f62917c0..3d3a32e5 100644 --- a/test/tokenService.js +++ b/test/tokenService.js @@ -211,7 +211,7 @@ describe('Interchain Token Service', () => { service.deployAndRegisterStandardizedToken(salt, tokenName, tokenSymbol, tokenDecimals, mintAmount, wallet.address), ) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, LOCK_UNLOCK, params); + .withArgs(tokenId, MINT_BURN, params); const tokenManagerAddress = await service.getValidTokenManagerAddress(tokenId); expect(tokenManagerAddress).to.not.equal(AddressZero); @@ -253,7 +253,7 @@ describe('Interchain Token Service', () => { await txPaused.wait(); }); - it('Should register a standardized token as a lock/unlock', async () => { + it('Should register a standardized token', async () => { const salt = getRandomBytes32(); const tokenId = await service.getCustomTokenId(wallet.address, salt); const tokenAddress = await service.getStandardizedTokenAddress(tokenId); @@ -262,7 +262,7 @@ describe('Interchain Token Service', () => { service.deployAndRegisterStandardizedToken(salt, tokenName, tokenSymbol, tokenDecimals, mintAmount, wallet.address), ) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, LOCK_UNLOCK, params); + .withArgs(tokenId, MINT_BURN, params); const tokenManagerAddress = await service.getValidTokenManagerAddress(tokenId); expect(tokenManagerAddress).to.not.equal(AddressZero); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); @@ -279,7 +279,7 @@ describe('Interchain Token Service', () => { service.deployAndRegisterStandardizedToken(salt, tokenName, tokenSymbol, tokenDecimals, mintAmount, wallet.address), ) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, LOCK_UNLOCK, params); + .withArgs(tokenId, MINT_BURN, params); const tokenManagerAddress = await service.getValidTokenManagerAddress(tokenId); expect(tokenManagerAddress).to.not.equal(AddressZero); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); @@ -291,45 +291,6 @@ describe('Interchain Token Service', () => { service.deployAndRegisterStandardizedToken(salt, tokenName, tokenSymbol, tokenDecimals, mintAmount, wallet.address), ).to.be.revertedWithCustomError(service, 'StandardizedTokenDeploymentFailed'); }); - - it('Should register a standardized token as a mint/burn', async () => { - const salt = getRandomBytes32(); - const tokenId = await service.getCustomTokenId(wallet.address, salt); - const tokenAddress = await service.getStandardizedTokenAddress(tokenId); - const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, tokenAddress]); - const tokenManagerAddress = await service.getTokenManagerAddress(tokenId); - await expect( - service.deployAndRegisterStandardizedToken(salt, tokenName, tokenSymbol, tokenDecimals, mintAmount, tokenManagerAddress), - ) - .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, MINT_BURN, params); - expect(tokenManagerAddress).to.not.equal(AddressZero); - const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); - - expect(await tokenManager.operator()).to.equal(wallet.address); - }); - - it('Should revert when registering a standardized token for a second time', async () => { - const salt = getRandomBytes32(); - const tokenId = await service.getCustomTokenId(wallet.address, salt); - const tokenAddress = await service.getStandardizedTokenAddress(tokenId); - const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, tokenAddress]); - const tokenManagerAddress = await service.getTokenManagerAddress(tokenId); - await expect( - service.deployAndRegisterStandardizedToken(salt, tokenName, tokenSymbol, tokenDecimals, mintAmount, tokenManagerAddress), - ) - .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, MINT_BURN, params); - expect(tokenManagerAddress).to.not.equal(AddressZero); - const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); - - expect(await tokenManager.operator()).to.equal(wallet.address); - - // Register same token again - await expect( - service.deployAndRegisterStandardizedToken(salt, tokenName, tokenSymbol, tokenDecimals, mintAmount, tokenManagerAddress), - ).to.be.revertedWithCustomError(service, 'StandardizedTokenDeploymentFailed'); - }); }); describe('Deploy and Register remote Standardized Token', () => { @@ -432,7 +393,7 @@ describe('Interchain Token Service', () => { .to.emit(service, 'StandardizedTokenDeployed') .withArgs(tokenId, tokenName, tokenSymbol, tokenDecimals, 0, distributor) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, LOCK_UNLOCK, params); + .withArgs(tokenId, MINT_BURN, params); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); expect(await tokenManager.tokenAddress()).to.equal(tokenAddress); expect(await tokenManager.operator()).to.equal(wallet.address); diff --git a/test/tokenServiceFullFlow.js b/test/tokenServiceFullFlow.js index aae64f98..35e374f7 100644 --- a/test/tokenServiceFullFlow.js +++ b/test/tokenServiceFullFlow.js @@ -22,7 +22,7 @@ const SELECTOR_SEND_TOKEN = 1; const SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN = 4; const LOCK_UNLOCK = 0; -// const MINT_BURN = 1; +const MINT_BURN = 1; // const LIQUIDITY_POOL = 2; describe('Interchain Token Service', () => { @@ -200,8 +200,10 @@ describe('Interchain Token Service', () => { [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, name, symbol, decimals, '0x', wallet.address], ); await expect(service.multicall(data, { value })) - .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, LOCK_UNLOCK, params) + .to.emit(service, 'StandardizedTokenDeployed') + .withArgs(tokenId, name, symbol, decimals, tokenCap, wallet.address) + .and.to.emit(service, 'TokenManagerDeployed') + .withArgs(tokenId, MINT_BURN, params) .and.to.emit(service, 'RemoteStandardizedTokenAndManagerDeploymentInitialized') .withArgs(tokenId, name, symbol, decimals, '0x', wallet.address, otherChains[0], gasValues[0]) .and.to.emit(gasService, 'NativeGasPaidForContractCall') @@ -228,13 +230,9 @@ describe('Interchain Token Service', () => { ); const payloadHash = keccak256(payload); - await expect(token.approve(tokenManager.address, amount)) - .to.emit(token, 'Approval') - .withArgs(wallet.address, tokenManager.address, amount); - await expect(tokenManager.sendToken(destChain, destAddress, amount, '0x', { value: gasValue })) .and.to.emit(token, 'Transfer') - .withArgs(wallet.address, tokenManager.address, amount) + .withArgs(wallet.address, AddressZero, amount) .and.to.emit(gateway, 'ContractCall') .withArgs(service.address, destChain, service.address.toLowerCase(), payloadHash, payload) .and.to.emit(gasService, 'NativeGasPaidForContractCall') diff --git a/test/utils.js b/test/utils.js index 207fc25f..f6e62b47 100644 --- a/test/utils.js +++ b/test/utils.js @@ -342,7 +342,7 @@ describe('Pausable', () => { }); describe('StandardizedTokenDeployer', () => { - let create3Deployer, standardizedTokenLockUnlock, standardizedTokenDeployer, standardizedTokenMintBurn; + let create3Deployer, standardizedToken, standardizedTokenDeployer; const tokenManager = new Wallet(getRandomBytes32()).address; const distributor = new Wallet(getRandomBytes32()).address; const mintTo = new Wallet(getRandomBytes32()).address; @@ -353,43 +353,13 @@ describe('StandardizedTokenDeployer', () => { before(async () => { create3Deployer = await deployContract(ownerWallet, 'Create3Deployer'); - standardizedTokenLockUnlock = await deployContract(ownerWallet, 'StandardizedTokenLockUnlock'); - standardizedTokenMintBurn = await deployContract(ownerWallet, 'StandardizedTokenMintBurn'); + standardizedToken = await deployContract(ownerWallet, 'StandardizedToken'); standardizedTokenDeployer = await deployContract(ownerWallet, 'StandardizedTokenDeployer', [ create3Deployer.address, - standardizedTokenLockUnlock.address, - standardizedTokenMintBurn.address, + standardizedToken.address, ]); }); - it('Should deploy a lock unlock token only once', async () => { - const salt = getRandomBytes32(); - - const tokenAddress = await create3Deployer.deployedAddress(standardizedTokenDeployer.address, salt); - - const token = new Contract(tokenAddress, StandardizedToken.abi, ownerWallet); - const tokenProxy = new Contract(tokenAddress, StandardizedTokenProxy.abi, ownerWallet); - - await expect( - standardizedTokenDeployer.deployStandardizedToken(salt, tokenManager, distributor, name, symbol, decimals, mintAmount, mintTo), - ) - .to.emit(token, 'Transfer') - .withArgs(AddressZero, mintTo, mintAmount) - .and.to.emit(token, 'DistributorChanged') - .withArgs(distributor); - - expect(await tokenProxy.implementation()).to.equal(standardizedTokenLockUnlock.address); - expect(await token.name()).to.equal(name); - expect(await token.symbol()).to.equal(symbol); - expect(await token.decimals()).to.equal(decimals); - expect(await token.balanceOf(mintTo)).to.equal(mintAmount); - expect(await token.distributor()).to.equal(distributor); - expect(await token.tokenManager()).to.equal(tokenManager); - await expect( - standardizedTokenDeployer.deployStandardizedToken(salt, tokenManager, distributor, name, symbol, decimals, mintAmount, mintTo), - ).to.be.revertedWithCustomError(create3Deployer, 'AlreadyDeployed'); - }); - it('Should deploy a mint burn token only once', async () => { const salt = getRandomBytes32(); @@ -406,7 +376,7 @@ describe('StandardizedTokenDeployer', () => { .and.to.emit(token, 'DistributorChanged') .withArgs(tokenManager); - expect(await tokenProxy.implementation()).to.equal(standardizedTokenMintBurn.address); + expect(await tokenProxy.implementation()).to.equal(standardizedToken.address); expect(await token.name()).to.equal(name); expect(await token.symbol()).to.equal(symbol); expect(await token.decimals()).to.equal(decimals); From ea255e0850a934394196aa59e99bafcdc7ab3d65 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 24 Jul 2023 18:10:14 +0300 Subject: [PATCH 13/54] using immutable storage for remoteAddressValidator address to save gas --- .../RemoteAddressValidator.sol | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/contracts/remote-address-validator/RemoteAddressValidator.sol b/contracts/remote-address-validator/RemoteAddressValidator.sol index b07c2225..5d1a0814 100644 --- a/contracts/remote-address-validator/RemoteAddressValidator.sol +++ b/contracts/remote-address-validator/RemoteAddressValidator.sol @@ -16,8 +16,12 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { mapping(string => string) public remoteAddresses; address public immutable interchainTokenServiceAddress; bytes32 public immutable interchainTokenServiceAddressHash; + uint256 private immutable interchainTokenServiceAddress1; + uint256 private immutable interchainTokenServiceAddress2; mapping(string => bool) public supportedByGateway; + + bytes32 private constant CONTRACT_ID = keccak256('remote-address-validator'); /** @@ -27,7 +31,17 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { constructor(address _interchainTokenServiceAddress) { if (_interchainTokenServiceAddress == address(0)) revert ZeroAddress(); interchainTokenServiceAddress = _interchainTokenServiceAddress; - interchainTokenServiceAddressHash = keccak256(bytes(_lowerCase(interchainTokenServiceAddress.toString()))); + string memory interchainTokenServiceAddressString = interchainTokenServiceAddress.toString(); + interchainTokenServiceAddressHash = keccak256(bytes(_lowerCase(interchainTokenServiceAddressString))); + uint256 p1; + uint256 p2; + assembly { + p1 := mload(add(interchainTokenServiceAddressString, 32)) + p2 := mload(add(interchainTokenServiceAddressString, 64)) + } + interchainTokenServiceAddress1 = p1; + interchainTokenServiceAddress2 = p2; + } /** @@ -60,6 +74,16 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { return s; } + function _interchainTokenServiceAddressString() internal view returns (string memory interchainTokenServiceAddressString) { + interchainTokenServiceAddressString = new string(42); + uint256 p1 = interchainTokenServiceAddress1; + uint256 p2 = interchainTokenServiceAddress2; + assembly{ + mstore(add(interchainTokenServiceAddressString, 32), p1) + mstore(add(interchainTokenServiceAddressString, 64), p2) + } + } + /** * @dev Validates that the sender is a valid interchain token service address * @param sourceChain Source chain of the transaction @@ -133,7 +157,7 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { function getRemoteAddress(string calldata chainName) external view returns (string memory remoteAddress) { remoteAddress = remoteAddresses[chainName]; if (bytes(remoteAddress).length == 0) { - remoteAddress = interchainTokenServiceAddress.toString(); + remoteAddress = _interchainTokenServiceAddressString(); } } } From 650ca71eb752513dc16f8fb50364805225b15662 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 26 Jul 2023 17:39:33 +0300 Subject: [PATCH 14/54] Added some recommended changes --- .../InterchainTokenService.sol | 26 +++++++--- contracts/interfaces/IDistributable.sol | 8 +-- .../interfaces/IInterchainTokenService.sol | 11 ++-- contracts/interfaces/IOperatable.sol | 2 +- contracts/token-manager/TokenManager.sol | 16 ++---- contracts/utils/Distributable.sol | 20 ++++---- contracts/utils/Operatable.sol | 2 +- docs/index.md | 16 +++--- test/tokenService.js | 51 +++++++++++++------ test/tokenServiceFullFlow.js | 30 ++++++----- test/utils.js | 6 +-- 11 files changed, 109 insertions(+), 79 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index eff33adf..4a171464 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -326,7 +326,7 @@ contract InterchainTokenService is tokenAddress = ITokenManager(tokenAddress).tokenAddress(); if (getCanonicalTokenId(tokenAddress) != tokenId) revert NotCanonicalTokenManager(); (string memory tokenName, string memory tokenSymbol, uint8 tokenDecimals) = _validateToken(tokenAddress); - _deployRemoteStandardizedToken(tokenId, tokenName, tokenSymbol, tokenDecimals, '', '', destinationChain, gasValue); + _deployRemoteStandardizedToken(tokenId, tokenName, tokenSymbol, tokenDecimals, '', '', 0, '', destinationChain, gasValue); } /** @@ -390,7 +390,6 @@ contract InterchainTokenService is ) external payable notPaused { bytes32 tokenId = getCustomTokenId(msg.sender, salt); _deployStandardizedToken(tokenId, distributor, name, symbol, decimals, mintAmount, msg.sender); - address tokenManagerAddress = getTokenManagerAddress(tokenId); address tokenAddress = getStandardizedTokenAddress(tokenId); _deployTokenManager(tokenId, TokenManagerType.MINT_BURN, abi.encode(msg.sender.toBytes(), tokenAddress)); } @@ -410,16 +409,18 @@ contract InterchainTokenService is */ function deployAndRegisterRemoteStandardizedToken( bytes32 salt, - string calldata name, - string calldata symbol, + string memory name, + string memory symbol, uint8 decimals, bytes memory distributor, + bytes memory mintTo, + uint256 mintAmount, bytes memory operator, string calldata destinationChain, uint256 gasValue ) external payable notPaused { bytes32 tokenId = getCustomTokenId(msg.sender, salt); - _deployRemoteStandardizedToken(tokenId, name, symbol, decimals, distributor, operator, destinationChain, gasValue); + _deployRemoteStandardizedToken(tokenId, name, symbol, decimals, distributor, mintTo, mintAmount, operator, destinationChain, gasValue); } /** @@ -671,13 +672,16 @@ contract InterchainTokenService is string memory symbol, uint8 decimals, bytes memory distributorBytes, + bytes memory mintToBytes, + uint256 mintAmount, bytes memory operatorBytes - ) = abi.decode(payload, (uint256, bytes32, string, string, uint8, bytes, bytes)); + ) = abi.decode(payload, (uint256, bytes32, string, string, uint8, bytes, bytes, uint256, bytes)); address tokenAddress = getStandardizedTokenAddress(tokenId); address tokenManagerAddress = getTokenManagerAddress(tokenId); address distributor = distributorBytes.length > 0 ? distributorBytes.toAddress() : tokenManagerAddress; if (distributor == address(0)) revert ZeroAddress(); - _deployStandardizedToken(tokenId, distributor, name, symbol, decimals, 0, distributor); + address mintTo = mintToBytes.length > 0 ? mintToBytes.toAddress() : distributor; + _deployStandardizedToken(tokenId, distributor, name, symbol, decimals, mintAmount, mintTo); _deployTokenManager( tokenId, TokenManagerType.MINT_BURN, @@ -749,6 +753,8 @@ contract InterchainTokenService is string memory symbol, uint8 decimals, bytes memory distributor, + bytes memory mintTo, + uint256 mintAmount, bytes memory operator, string calldata destinationChain, uint256 gasValue @@ -760,6 +766,8 @@ contract InterchainTokenService is symbol, decimals, distributor, + mintTo, + mintAmount, operator ); _callContract(destinationChain, payload, gasValue, msg.sender); @@ -769,6 +777,8 @@ contract InterchainTokenService is symbol, decimals, distributor, + mintTo, + mintAmount, operator, destinationChain, gasValue @@ -838,7 +848,7 @@ contract InterchainTokenService is if (!success) { revert StandardizedTokenDeploymentFailed(); } - emit StandardizedTokenDeployed(tokenId, name, symbol, decimals, mintAmount, mintTo); + emit StandardizedTokenDeployed(tokenId, distributor, name, symbol, decimals, mintAmount, mintTo); } function _decodeMetadata(bytes calldata metadata) internal pure returns (uint32 version, bytes calldata data) { diff --git a/contracts/interfaces/IDistributable.sol b/contracts/interfaces/IDistributable.sol index cf765b70..7605eccd 100644 --- a/contracts/interfaces/IDistributable.sol +++ b/contracts/interfaces/IDistributable.sol @@ -6,8 +6,8 @@ interface IDistributable { error NotDistributor(); error NotProposedDistributor(); - event DistributorChanged(address indexed distributor); - event DistributorChangeProposed(address indexed distributor); + event DistributorshipTransferred(address indexed distributor); + event DistributorshipTransferStarted(address indexed distributor); /** * @notice Get the address of the distributor @@ -27,11 +27,11 @@ interface IDistributable { * @dev Can only be called by the current distributor * @param distributor_ The address of the new distributor */ - function proposeDistributorChange(address distributor_) external; + function proposeDistributorship(address distributor_) external; /** * @notice Accept a change of the distributor of the contract * @dev Can only be called by the proposed distributor */ - function acceptDistributorChange() external; + function acceptDistributorship() external; } diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index d4f98c3b..a04fcaab 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -60,13 +60,16 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx string tokenSymbol, uint8 tokenDecimals, bytes distributor, - bytes indexed operator, + bytes mintTo, + uint256 mintAmount, + bytes operator, string destinationChain, uint256 indexed gasValue ); event TokenManagerDeployed(bytes32 indexed tokenId, TokenManagerType indexed tokenManagerType, bytes params); event StandardizedTokenDeployed( bytes32 indexed tokenId, + address distributor, string name, string symbol, uint8 decimals, @@ -240,10 +243,12 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx */ function deployAndRegisterRemoteStandardizedToken( bytes32 salt, - string calldata name, - string calldata symbol, + string memory name, + string memory symbol, uint8 decimals, bytes memory distributor, + bytes memory mintTo, + uint256 mintAmount, bytes memory operator, string calldata destinationChain, uint256 gasValue diff --git a/contracts/interfaces/IOperatable.sol b/contracts/interfaces/IOperatable.sol index 7b7dd22c..4c7d7911 100644 --- a/contracts/interfaces/IOperatable.sol +++ b/contracts/interfaces/IOperatable.sol @@ -6,7 +6,7 @@ interface IOperatable { error NotOperator(); error NotProposedOperator(); - event OperatorChanged(address indexed operator); + event OperatorshipTransferred(address indexed operator); event OperatorChangeProposed(address indexed operator); /** diff --git a/contracts/token-manager/TokenManager.sol b/contracts/token-manager/TokenManager.sol index 24fcff1c..37ae8a6e 100644 --- a/contracts/token-manager/TokenManager.sol +++ b/contracts/token-manager/TokenManager.sol @@ -55,7 +55,7 @@ abstract contract TokenManager is ITokenManager, Operatable, FlowLimit, Implemen /** * @notice A function that returns the token id. */ - function tokenId() external view returns (bytes32) { + function tokenId() public view returns (bytes32) { return this.tokenId(); } @@ -97,7 +97,7 @@ abstract contract TokenManager is ITokenManager, Operatable, FlowLimit, Implemen amount = _takeToken(sender, amount); _addFlowOut(amount); interchainTokenService.transmitSendToken{ value: msg.value }( - _getTokenId(), + this.tokenId(), sender, destinationChain, destinationAddress, @@ -124,7 +124,7 @@ abstract contract TokenManager is ITokenManager, Operatable, FlowLimit, Implemen _addFlowOut(amount); uint32 version = 0; interchainTokenService.transmitSendToken{ value: msg.value }( - _getTokenId(), + this.tokenId(), sender, destinationChain, destinationAddress, @@ -150,7 +150,7 @@ abstract contract TokenManager is ITokenManager, Operatable, FlowLimit, Implemen amount = _takeToken(sender, amount); _addFlowOut(amount); interchainTokenService.transmitSendToken{ value: msg.value }( - _getTokenId(), + this.tokenId(), sender, destinationChain, destinationAddress, @@ -203,12 +203,4 @@ abstract contract TokenManager is ITokenManager, Operatable, FlowLimit, Implemen * @param params The setup parameters */ function _setup(bytes calldata params) internal virtual; - - /** - * @notice Gets the token ID from the token manager proxy. - * @return tokenId The ID of the token - */ - function _getTokenId() internal view returns (bytes32 tokenId) { - tokenId = ITokenManagerProxy(address(this)).tokenId(); - } } diff --git a/contracts/utils/Distributable.sol b/contracts/utils/Distributable.sol index 3a0c1d88..4f930f29 100644 --- a/contracts/utils/Distributable.sol +++ b/contracts/utils/Distributable.sol @@ -27,11 +27,11 @@ contract Distributable is IDistributable { /** * @notice Get the address of the distributor - * @return distr of the distributor + * @return distributor_ of the distributor */ - function distributor() public view returns (address distr) { + function distributor() public view returns (address distributor_) { assembly { - distr := sload(DISTRIBUTOR_SLOT) + distributor_ := sload(DISTRIBUTOR_SLOT) } } @@ -43,16 +43,16 @@ contract Distributable is IDistributable { assembly { sstore(DISTRIBUTOR_SLOT, distributor_) } - emit DistributorChanged(distributor_); + emit DistributorshipTransferred(distributor_); } /** * @notice Change the distributor of the contract * @dev Can only be called by the current distributor - * @param distr The address of the new distributor + * @param distributor_ The address of the new distributor */ - function transferDistributorship(address distr) external onlyDistributor { - _setDistributor(distr); + function transferDistributorship(address distributor_) external onlyDistributor { + _setDistributor(distributor_); } /** @@ -60,18 +60,18 @@ contract Distributable is IDistributable { * @dev Can only be called by the current distributor * @param distributor_ The address of the new distributor */ - function proposeDistributorChange(address distributor_) external onlyDistributor { + function proposeDistributorship(address distributor_) external onlyDistributor { assembly { sstore(PROPOSED_DISTRIBUTOR_SLOT, distributor_) } - emit DistributorChangeProposed(distributor_); + emit DistributorshipTransferStarted(distributor_); } /** * @notice Accept a change of the distributor of the contract * @dev Can only be called by the proposed distributor */ - function acceptDistributorChange() external { + function acceptDistributorship() external { address proposedDistributor; assembly { proposedDistributor := sload(PROPOSED_DISTRIBUTOR_SLOT) diff --git a/contracts/utils/Operatable.sol b/contracts/utils/Operatable.sol index 2090cde5..6f567744 100644 --- a/contracts/utils/Operatable.sol +++ b/contracts/utils/Operatable.sol @@ -42,7 +42,7 @@ contract Operatable is IOperatable { assembly { sstore(OPERATOR_SLOT, operator_) } - emit OperatorChanged(operator_); + emit OperatorshipTransferred(operator_); } /** diff --git a/docs/index.md b/docs/index.md index a54c9cfd..3131a399 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1779,10 +1779,10 @@ If any of the calls fail, the function will revert with the failure message._ error NotOperator() ``` -### OperatorChanged +### OperatorshipTransferred ```solidity -event OperatorChanged(address operator) +event OperatorshipTransferred(address operator) ``` ### operator @@ -3091,10 +3091,10 @@ A different implementation could have `metadata` that tells this function which error NotDistributor() ``` -### DistributorChanged +### DistributorshipTransferred ```solidity -event DistributorChanged(address distributor) +event DistributorshipTransferred(address distributor) ``` ### distributor @@ -4697,7 +4697,7 @@ _Throws a NotDistributor custom eror if called by any account other than the dis ### distributor ```solidity -function distributor() public view returns (address distr) +function distributor() public view returns (address distributor_) ``` Get the address of the distributor @@ -4706,7 +4706,7 @@ Get the address of the distributor | Name | Type | Description | | ---- | ---- | ----------- | -| distr | address | of the distributor | +| distributor_ | address | of the distributor | ### _setDistributor @@ -4725,7 +4725,7 @@ _Internal function that stores the new distributor address in the correct storag ### transferDistributorship ```solidity -function transferDistributorship(address distr) external +function transferDistributorship(address distributor_) external ``` Change the distributor of the contract @@ -4736,7 +4736,7 @@ _Can only be called by the current distributor_ | Name | Type | Description | | ---- | ---- | ----------- | -| distr | address | The address of the new distributor | +| distributor_ | address | The address of the new distributor | ## FlowLimit diff --git a/test/tokenService.js b/test/tokenService.js index 3d3a32e5..a727cbc7 100644 --- a/test/tokenService.js +++ b/test/tokenService.js @@ -9,6 +9,7 @@ const { defaultAbiCoder, solidityPack, keccak256 } = ethers.utils; const { Contract, Wallet } = ethers; const TokenManager = require('../artifacts/contracts/token-manager/TokenManager.sol/TokenManager.json'); +const Token = require('../artifacts/contracts/interfaces/IStandardizedToken.sol/IStandardizedToken.json'); const { approveContractCall, getRandomBytes32 } = require('../scripts/utils'); const { deployAll, deployContract } = require('../scripts/deploy'); @@ -183,12 +184,12 @@ describe('Interchain Token Service', () => { const chain = 'chain1'; const gasValue = 1e6; const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes'], - [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, tokenName, tokenSymbol, tokenDecimals, '0x', '0x'], + ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, tokenName, tokenSymbol, tokenDecimals, '0x', '0x', 0, '0x'], ); await expect(service.deployRemoteCanonicalToken(tokenId, chain, gasValue, { value: gasValue })) .to.emit(service, 'RemoteStandardizedTokenAndManagerDeploymentInitialized') - .withArgs(tokenId, tokenName, tokenSymbol, tokenDecimals, '0x', '0x', chain, gasValue) + .withArgs(tokenId, tokenName, tokenSymbol, tokenDecimals, '0x', '0x', 0, '0x', chain, gasValue) .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, chain, service.address.toLowerCase(), keccak256(payload), gasValue, wallet.address) .and.to.emit(gateway, 'ContractCall') @@ -299,6 +300,8 @@ describe('Interchain Token Service', () => { const tokenDecimals = 13; const distributor = '0x12345678'; const destinationChain = 'dest'; + const mintTo = '0x0abc'; + const mintAmount = 123456; const operator = '0x5678'; const gasValue = 1234; const salt = getRandomBytes32(); @@ -312,8 +315,8 @@ describe('Interchain Token Service', () => { it('Should initialize a remote standardized token deployment', async () => { const tokenId = await service.getCustomTokenId(wallet.address, salt); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes'], - [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, tokenName, tokenSymbol, tokenDecimals, distributor, operator], + ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, tokenName, tokenSymbol, tokenDecimals, distributor, mintTo, mintAmount, operator], ); await expect( service.deployAndRegisterRemoteStandardizedToken( @@ -322,14 +325,16 @@ describe('Interchain Token Service', () => { tokenSymbol, tokenDecimals, distributor, + mintTo, + mintAmount, operator, destinationChain, gasValue, { value: gasValue }, ), - ) + ) .to.emit(service, 'RemoteStandardizedTokenAndManagerDeploymentInitialized') - .withArgs(tokenId, tokenName, tokenSymbol, tokenDecimals, distributor, operator, destinationChain, gasValue) + .withArgs(tokenId, tokenName, tokenSymbol, tokenDecimals, distributor, mintTo, mintAmount, operator, destinationChain, gasValue) .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, destinationChain, service.address.toLowerCase(), keccak256(payload), gasValue, wallet.address) .and.to.emit(gateway, 'ContractCall') @@ -347,6 +352,8 @@ describe('Interchain Token Service', () => { tokenSymbol, tokenDecimals, distributor, + mintTo, + mintAmount, operator, destinationChain, gasValue, @@ -380,18 +387,23 @@ describe('Interchain Token Service', () => { const tokenId = getRandomBytes32(); const distributor = wallet.address; const operator = wallet.address; + const mintTo = '0x'; + const mintAmount = 1234; const tokenManagerAddress = await service.getTokenManagerAddress(tokenId); const tokenAddress = await service.getStandardizedTokenAddress(tokenId); const params = defaultAbiCoder.encode(['bytes', 'address'], [distributor, tokenAddress]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes'], - [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, tokenName, tokenSymbol, tokenDecimals, distributor, operator], + ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, tokenName, tokenSymbol, tokenDecimals, distributor, mintTo, mintAmount, operator], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); + const token = new Contract(tokenAddress, Token.abi, wallet); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(service, 'StandardizedTokenDeployed') - .withArgs(tokenId, tokenName, tokenSymbol, tokenDecimals, 0, distributor) + .withArgs(tokenId, distributor, tokenName, tokenSymbol, tokenDecimals, mintAmount, distributor) + .and.to.emit(token, 'Transfer') + .withArgs(AddressZero, wallet.address, mintAmount) .and.to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, MINT_BURN, params); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); @@ -404,17 +416,19 @@ describe('Interchain Token Service', () => { const tokenManagerAddress = await service.getTokenManagerAddress(tokenId); const distributor = tokenManagerAddress; const operator = wallet.address; + const mintTo = '0x'; + const mintAmount = 0; const tokenAddress = await service.getStandardizedTokenAddress(tokenId); const params = defaultAbiCoder.encode(['bytes', 'address'], [operator, tokenAddress]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes'], - [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, tokenName, tokenSymbol, tokenDecimals, distributor, operator], + ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, tokenName, tokenSymbol, tokenDecimals, distributor, mintTo, mintAmount, operator], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(service, 'StandardizedTokenDeployed') - .withArgs(tokenId, tokenName, tokenSymbol, tokenDecimals, 0, distributor) + .withArgs(tokenId, distributor, tokenName, tokenSymbol, tokenDecimals, mintAmount, distributor) .and.to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, MINT_BURN, params); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); @@ -426,18 +440,23 @@ describe('Interchain Token Service', () => { const tokenId = getRandomBytes32(); const tokenManagerAddress = await service.getTokenManagerAddress(tokenId); const distributor = '0x'; + const mintTo = '0x'; + const mintAmount = 1234; const operator = '0x'; const tokenAddress = await service.getStandardizedTokenAddress(tokenId); const params = defaultAbiCoder.encode(['bytes', 'address'], [service.address, tokenAddress]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes'], - [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, tokenName, tokenSymbol, tokenDecimals, distributor, operator], + ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, tokenName, tokenSymbol, tokenDecimals, distributor, mintTo, mintAmount, operator], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); + const token = new Contract(tokenAddress, Token.abi, wallet); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(service, 'StandardizedTokenDeployed') - .withArgs(tokenId, tokenName, tokenSymbol, tokenDecimals, 0, tokenManagerAddress) + .withArgs(tokenId, tokenManagerAddress, tokenName, tokenSymbol, tokenDecimals, mintAmount, tokenManagerAddress) + .and.to.emit(token, 'Transfer') + .withArgs(AddressZero, tokenManagerAddress, mintAmount) .and.to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, MINT_BURN, params); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); diff --git a/test/tokenServiceFullFlow.js b/test/tokenServiceFullFlow.js index 35e374f7..2cfa050a 100644 --- a/test/tokenServiceFullFlow.js +++ b/test/tokenServiceFullFlow.js @@ -70,7 +70,7 @@ describe('Interchain Token Service', () => { wallet.address, ), ) - .to.emit(token, 'DistributorChanged') + .to.emit(token, 'DistributorshipTransferred') .withArgs(wallet.address) .and.to.emit(token, 'Transfer') .withArgs(AddressZero, wallet.address, tokenCap); @@ -89,20 +89,20 @@ describe('Interchain Token Service', () => { const params = defaultAbiCoder.encode(['bytes', 'address'], [service.address, token.address]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes'], - [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, name, symbol, decimals, '0x', '0x'], + ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, name, symbol, decimals, '0x', '0x', 0, '0x'], ); await expect(service.multicall(data, { value })) .to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, LOCK_UNLOCK, params) .and.to.emit(service, 'RemoteStandardizedTokenAndManagerDeploymentInitialized') - .withArgs(tokenId, name, symbol, decimals, '0x', '0x', otherChains[0], gasValues[0]) + .withArgs(tokenId, name, symbol, decimals, '0x', '0x', 0, '0x', otherChains[0], gasValues[0]) .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, otherChains[0], service.address.toLowerCase(), keccak256(payload), gasValues[0], wallet.address) .and.to.emit(gateway, 'ContractCall') .withArgs(service.address, otherChains[0], service.address.toLowerCase(), keccak256(payload), payload) .and.to.emit(service, 'RemoteStandardizedTokenAndManagerDeploymentInitialized') - .withArgs(tokenId, name, symbol, decimals, '0x', '0x', otherChains[1], gasValues[1]) + .withArgs(tokenId, name, symbol, decimals, '0x', '0x', 0, '0x', otherChains[1], gasValues[1]) .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, otherChains[1], service.address.toLowerCase(), keccak256(payload), gasValues[1], wallet.address) .and.to.emit(gateway, 'ContractCall') @@ -144,7 +144,7 @@ describe('Interchain Token Service', () => { 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.transferDistributorship(newAddress)).to.emit(token, 'DistributorChanged').withArgs(newAddress); + await expect(token.transferDistributorship(newAddress)).to.emit(token, 'DistributorshipTransferred').withArgs(newAddress); await expect(token.mint(newAddress, amount)).to.be.revertedWithCustomError(token, 'NotDistributor'); await expect(token.burn(newAddress, amount)).to.be.revertedWithCustomError(token, 'NotDistributor'); @@ -186,6 +186,8 @@ describe('Interchain Token Service', () => { symbol, decimals, '0x', + '0x', + 0, wallet.address, otherChains[i], gasValues[i], @@ -196,22 +198,24 @@ describe('Interchain Token Service', () => { const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes'], - [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, name, symbol, decimals, '0x', wallet.address], + ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, name, symbol, decimals, '0x', '0x', 0, wallet.address], ); - await expect(service.multicall(data, { value })) + const tx = service.multicall(data, { value }); + + await expect(tx) .to.emit(service, 'StandardizedTokenDeployed') - .withArgs(tokenId, name, symbol, decimals, tokenCap, wallet.address) + .withArgs(tokenId, wallet.address, name, symbol, decimals, tokenCap, wallet.address) .and.to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, MINT_BURN, params) .and.to.emit(service, 'RemoteStandardizedTokenAndManagerDeploymentInitialized') - .withArgs(tokenId, name, symbol, decimals, '0x', wallet.address, otherChains[0], gasValues[0]) + .withArgs(tokenId, name, symbol, decimals, '0x', '0x', 0, wallet.address.toLowerCase(), otherChains[0], gasValues[0]) .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, otherChains[0], service.address.toLowerCase(), keccak256(payload), gasValues[0], wallet.address) .and.to.emit(gateway, 'ContractCall') .withArgs(service.address, otherChains[0], service.address.toLowerCase(), keccak256(payload), payload) .and.to.emit(service, 'RemoteStandardizedTokenAndManagerDeploymentInitialized') - .withArgs(tokenId, name, symbol, decimals, '0x', wallet.address, otherChains[1], gasValues[1]) + .withArgs(tokenId, name, symbol, decimals, '0x', '0x', 0, wallet.address.toLowerCase(), otherChains[1], gasValues[1]) .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, otherChains[1], service.address.toLowerCase(), keccak256(payload), gasValues[1], wallet.address) .and.to.emit(gateway, 'ContractCall') @@ -249,7 +253,7 @@ describe('Interchain Token Service', () => { 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.transferDistributorship(newAddress)).to.emit(token, 'DistributorChanged').withArgs(newAddress); + await expect(token.transferDistributorship(newAddress)).to.emit(token, 'DistributorshipTransferred').withArgs(newAddress); await expect(token.mint(newAddress, amount)).to.be.revertedWithCustomError(token, 'NotDistributor'); await expect(token.burn(newAddress, amount)).to.be.revertedWithCustomError(token, 'NotDistributor'); diff --git a/test/utils.js b/test/utils.js index f6e62b47..544ae8e0 100644 --- a/test/utils.js +++ b/test/utils.js @@ -39,7 +39,7 @@ describe('Operatable', () => { it('Should be able to change the operator only as the operator', async () => { expect(await test.operator()).to.equal(ownerWallet.address); - await expect(test.transferOperatorship(otherWallet.address)).to.emit(test, 'OperatorChanged').withArgs(otherWallet.address); + await expect(test.transferOperatorship(otherWallet.address)).to.emit(test, 'OperatorshipTransferred').withArgs(otherWallet.address); expect(await test.operator()).to.equal(otherWallet.address); await expect(test.transferOperatorship(otherWallet.address)).to.be.revertedWithCustomError(test, 'NotOperator'); }); @@ -62,7 +62,7 @@ describe('Distributable', () => { it('Should be able to change the distributor only as the distributor', async () => { expect(await test.distributor()).to.equal(ownerWallet.address); - await expect(test.transferDistributorship(otherWallet.address)).to.emit(test, 'DistributorChanged').withArgs(otherWallet.address); + await expect(test.transferDistributorship(otherWallet.address)).to.emit(test, 'DistributorshipTransferred').withArgs(otherWallet.address); expect(await test.distributor()).to.equal(otherWallet.address); await expect(test.transferDistributorship(otherWallet.address)).to.be.revertedWithCustomError(test, 'NotDistributor'); }); @@ -373,7 +373,7 @@ describe('StandardizedTokenDeployer', () => { ) .to.emit(token, 'Transfer') .withArgs(AddressZero, mintTo, mintAmount) - .and.to.emit(token, 'DistributorChanged') + .and.to.emit(token, 'DistributorshipTransferred') .withArgs(tokenManager); expect(await tokenProxy.implementation()).to.equal(standardizedToken.address); From c69e0a4f2a0128da125151f4080f5ec125bc51c2 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 26 Jul 2023 17:51:17 +0300 Subject: [PATCH 15/54] added milap's suggested changes --- .../interchain-token/InterchainToken.sol | 12 ++++++------ contracts/interfaces/IInterchainToken.sol | 9 --------- contracts/interfaces/IStandardizedToken.sol | 9 +++++++++ contracts/test/InterchainTokenTest.sol | 19 ++++++++++--------- .../StandardizedToken.sol | 14 +++++++------- 5 files changed, 32 insertions(+), 31 deletions(-) diff --git a/contracts/interchain-token/InterchainToken.sol b/contracts/interchain-token/InterchainToken.sol index 37d9ebd1..4d1f4961 100644 --- a/contracts/interchain-token/InterchainToken.sol +++ b/contracts/interchain-token/InterchainToken.sol @@ -16,9 +16,9 @@ abstract contract InterchainToken is IInterchainToken, ERC20 { /** * @notice Getter for the tokenManager used for this token. * @dev Needs to be overwitten. - * @return tokenManager the TokenManager called to facilitate cross chain transfers. + * @return tokenManager_ the TokenManager called to facilitate cross chain transfers. */ - function getTokenManager() public view virtual returns (ITokenManager tokenManager); + function tokenManager() public view virtual returns (ITokenManager tokenManager_); /** * @notice Implementation of the interchainTransfer method @@ -39,8 +39,8 @@ abstract contract InterchainToken is IInterchainToken, ERC20 { _beforeInterchainTransfer(msg.sender, destinationChain, recipient, amount, metadata); - ITokenManager tokenManager = getTokenManager(); - tokenManager.transmitInterchainTransfer{ value: msg.value }(sender, destinationChain, recipient, amount, metadata); + ITokenManager tokenManager_ = tokenManager(); + tokenManager_.transmitInterchainTransfer{ value: msg.value }(sender, destinationChain, recipient, amount, metadata); } /** @@ -68,8 +68,8 @@ abstract contract InterchainToken is IInterchainToken, ERC20 { _beforeInterchainTransfer(msg.sender, destinationChain, recipient, amount, metadata); - ITokenManager tokenManager = getTokenManager(); - tokenManager.transmitInterchainTransfer{ value: msg.value }(sender, destinationChain, recipient, amount, metadata); + ITokenManager tokenManager_ = tokenManager(); + tokenManager_.transmitInterchainTransfer{ value: msg.value }(sender, destinationChain, recipient, amount, metadata); } /** diff --git a/contracts/interfaces/IInterchainToken.sol b/contracts/interfaces/IInterchainToken.sol index d3a5fa56..4bc57fd7 100644 --- a/contracts/interfaces/IInterchainToken.sol +++ b/contracts/interfaces/IInterchainToken.sol @@ -2,20 +2,11 @@ pragma solidity ^0.8.0; -import { ITokenManager } from './ITokenManager.sol'; - /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IInterchainToken { - /** - * @notice Getter for the tokenManager used for this token. - * @dev Needs to be overwitten. - * @return tokenManager the TokenManager called to facilitate cross chain transfers. - */ - function getTokenManager() external view returns (ITokenManager tokenManager); - /** * @notice Implementation of the interchainTransfer method * @dev We chose to either pass `metadata` as raw data on a remote contract call, or, if no data is passed, just do a transfer. diff --git a/contracts/interfaces/IStandardizedToken.sol b/contracts/interfaces/IStandardizedToken.sol index bb49c28e..61db1fb1 100644 --- a/contracts/interfaces/IStandardizedToken.sol +++ b/contracts/interfaces/IStandardizedToken.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.0; import { IInterchainToken } from './IInterchainToken.sol'; import { IDistributable } from './IDistributable.sol'; import { IERC20BurnableMintable } from './IERC20BurnableMintable.sol'; +import { ITokenManager } from './ITokenManager.sol'; import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; @@ -25,4 +26,12 @@ interface IStandardizedToken is IInterchainToken, IDistributable, IERC20Burnable * @param params the data to be used for the initialization. */ function setup(bytes calldata params) external; + + /** + * @notice Getter for the tokenManager used for this token. + * @dev Needs to be overwitten. + * @return tokenManager_ the TokenManager called to facilitate cross chain transfers. + */ + function tokenManager() external view returns (ITokenManager tokenManager_); + } diff --git a/contracts/test/InterchainTokenTest.sol b/contracts/test/InterchainTokenTest.sol index 3724f7b1..1371c4e2 100644 --- a/contracts/test/InterchainTokenTest.sol +++ b/contracts/test/InterchainTokenTest.sol @@ -8,33 +8,34 @@ import { ITokenManager } from '../interfaces/ITokenManager.sol'; import { IERC20BurnableMintable } from '../interfaces/IERC20BurnableMintable.sol'; contract InterchainTokenTest is InterchainToken, Distributable, IERC20BurnableMintable { - ITokenManager internal tokenManager; + ITokenManager public tokenManager_; bool internal tokenManagerRequiresApproval_ = true; string public name; string public symbol; uint8 public decimals; - constructor(string memory name_, string memory symbol_, uint8 decimals_, address tokenManager_) { + constructor(string memory name_, string memory symbol_, uint8 decimals_, address tokenManagerAddress) { name = name_; symbol = symbol_; decimals = decimals_; _setDistributor(msg.sender); - tokenManager = ITokenManager(tokenManager_); + tokenManager_ = ITokenManager(tokenManagerAddress); } - function getTokenManager() public view override returns (ITokenManager) { - return tokenManager; + function tokenManager() public view override returns (ITokenManager) { + return tokenManager_; } function _beforeInterchainTransfer(address sender, string calldata /*destinationChain*/, bytes calldata /*destinationAddress*/, uint256 amount, bytes calldata /*metadata*/) internal override { if(!tokenManagerRequiresApproval_) return; - uint256 allowance_ = allowance[sender][address(tokenManager)]; + address tokenManagerAddress = address(tokenManager_); + uint256 allowance_ = allowance[sender][tokenManagerAddress]; if (allowance_ != type(uint256).max) { if (allowance_ > type(uint256).max - amount) { allowance_ = type(uint256).max - amount; } - _approve(sender, address(tokenManager), allowance_ + amount); + _approve(sender, tokenManagerAddress, allowance_ + amount); } } @@ -50,7 +51,7 @@ contract InterchainTokenTest is InterchainToken, Distributable, IERC20BurnableMi _burn(account, amount); } - function setTokenManager(ITokenManager tokenManager_) external { - tokenManager = tokenManager_; + function setTokenManager(ITokenManager tokenManagerAddress) external { + tokenManager_ = tokenManagerAddress; } } diff --git a/contracts/token-implementations/StandardizedToken.sol b/contracts/token-implementations/StandardizedToken.sol index 830a49af..67a8abc8 100644 --- a/contracts/token-implementations/StandardizedToken.sol +++ b/contracts/token-implementations/StandardizedToken.sol @@ -19,7 +19,7 @@ import { Distributable } from '../utils/Distributable.sol'; contract StandardizedToken is InterchainToken, ERC20Permit, Implementation, Distributable { using AddressBytesUtils for bytes; - address public tokenManager; + address internal tokenManager_; string public name; string public symbol; uint8 public decimals; @@ -27,7 +27,7 @@ contract StandardizedToken is InterchainToken, ERC20Permit, Implementation, Dist bytes32 private constant CONTRACT_ID = keccak256('standardized-token'); modifier onlyDistributorOrTokenManager { - if(msg.sender != tokenManager) { + if(msg.sender != tokenManager_) { if(msg.sender != distributor()) revert NotDistributor(); } _; @@ -44,8 +44,8 @@ contract StandardizedToken is InterchainToken, ERC20Permit, Implementation, Dist * @notice Returns the token manager for this token * @return ITokenManager The token manager contract */ - function getTokenManager() public view override returns (ITokenManager) { - return ITokenManager(tokenManager); + function tokenManager() public view override returns (ITokenManager) { + return ITokenManager(tokenManager_); } /** @@ -56,11 +56,11 @@ contract StandardizedToken is InterchainToken, ERC20Permit, Implementation, Dist function setup(bytes calldata params) external override onlyProxy { { address distributor_; - address tokenManager_; + address tokenManagerAddress; string memory tokenName; - (tokenManager_, distributor_, tokenName, symbol, decimals) = abi.decode(params, (address, address, string, string, uint8)); + (tokenManagerAddress, distributor_, tokenName, symbol, decimals) = abi.decode(params, (address, address, string, string, uint8)); _setDistributor(distributor_); - tokenManager = tokenManager_; + tokenManager_ = tokenManagerAddress; _setDomainTypeSignatureHash(tokenName); name = tokenName; } From 29939308bd43547102b6f359f94391e4e191877d Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 26 Jul 2023 19:08:43 +0300 Subject: [PATCH 16/54] Fixed some names and some minor gas optimizations --- .../InterchainTokenService.sol | 5 ++-- contracts/interfaces/IOperatable.sol | 4 +-- contracts/libraries/AddressBytesUtils.sol | 2 +- contracts/token-manager/TokenManager.sol | 1 + contracts/utils/Distributable.sol | 25 ------------------- contracts/utils/Operatable.sol | 4 +-- test/tokenService.js | 4 +-- test/tokenServiceFullFlow.js | 2 +- 8 files changed, 12 insertions(+), 35 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 4a171464..7c473ec4 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -158,6 +158,7 @@ contract InterchainTokenService is * @param tokenId the tokenId. * @return tokenManagerAddress deployement address of the TokenManager. */ + // TODO: Maybe copy the code of the create3Deployer to save gas, but would introduce duplicate code problems. function getTokenManagerAddress(bytes32 tokenId) public view returns (address tokenManagerAddress) { tokenManagerAddress = deployer.deployedAddress(address(this), tokenId); } @@ -169,7 +170,7 @@ contract InterchainTokenService is */ function getValidTokenManagerAddress(bytes32 tokenId) public view returns (address tokenManagerAddress) { tokenManagerAddress = getTokenManagerAddress(tokenId); - if (ITokenManagerProxy(tokenManagerAddress).tokenId() != tokenId) revert TokenManagerDoesNotExist(tokenId); + if (tokenManagerAddress.code.length == 0) revert TokenManagerDoesNotExist(tokenId); } /** @@ -309,7 +310,7 @@ contract InterchainTokenService is (, string memory tokenSymbol, ) = _validateToken(tokenAddress); if (gateway.tokenAddresses(tokenSymbol) == tokenAddress) revert GatewayToken(); tokenId = getCanonicalTokenId(tokenAddress); - _deployTokenManager(tokenId, TokenManagerType.LOCK_UNLOCK, abi.encode(address(this).toBytes(), tokenAddress)); + _deployTokenManager(tokenId, TokenManagerType.LOCK_UNLOCK, abi.encode('', tokenAddress)); } /** diff --git a/contracts/interfaces/IOperatable.sol b/contracts/interfaces/IOperatable.sol index 4c7d7911..861a84b6 100644 --- a/contracts/interfaces/IOperatable.sol +++ b/contracts/interfaces/IOperatable.sol @@ -27,11 +27,11 @@ interface IOperatable { * @dev Can only be called by the current operator * @param operator_ The address of the new operator */ - function proposeOperatorChange(address operator_) external; + function proposeOperatorship(address operator_) external; /** * @notice Accept a proposed change of operatorship * @dev Can only be called by the proposed operator */ - function acceptOperatorChange() external; + function acceptOperatorship() external; } diff --git a/contracts/libraries/AddressBytesUtils.sol b/contracts/libraries/AddressBytesUtils.sol index 449ae4ae..f02570fe 100644 --- a/contracts/libraries/AddressBytesUtils.sol +++ b/contracts/libraries/AddressBytesUtils.sol @@ -29,7 +29,7 @@ library AddressBytesUtils { */ function toBytes(address addr) internal pure returns (bytes memory bytesAddress) { bytesAddress = new bytes(20); - + // we can test if using a single 32 byte variable that is the address with the length together and using one mstore would be slightly cheaper. assembly { mstore(add(bytesAddress, 20), addr) mstore(bytesAddress, 20) diff --git a/contracts/token-manager/TokenManager.sol b/contracts/token-manager/TokenManager.sol index 37ae8a6e..44788454 100644 --- a/contracts/token-manager/TokenManager.sol +++ b/contracts/token-manager/TokenManager.sol @@ -54,6 +54,7 @@ abstract contract TokenManager is ITokenManager, Operatable, FlowLimit, Implemen /** * @notice A function that returns the token id. + * @dev This will only work when called by a proxy, which hides this and returns the correct value. */ function tokenId() public view returns (bytes32) { return this.tokenId(); diff --git a/contracts/utils/Distributable.sol b/contracts/utils/Distributable.sol index cc068164..4f930f29 100644 --- a/contracts/utils/Distributable.sol +++ b/contracts/utils/Distributable.sol @@ -79,29 +79,4 @@ contract Distributable is IDistributable { if (msg.sender != proposedDistributor) revert NotProposedDistributor(); _setDistributor(proposedDistributor); } - - /** - * @notice Proposed a change of the distributor of the contract - * @dev Can only be called by the current distributor - * @param distributor_ The address of the new distributor - */ - function proposeDistributorChange(address distributor_) external onlyDistributor { - assembly { - sstore(PROPOSED_DISTRIBUTOR_SLOT, distributor_) - } - emit DistributorChangeProposed(distributor_); - } - - /** - * @notice Accept a change of the distributor of the contract - * @dev Can only be called by the proposed distributor - */ - function acceptDistributorChange() external { - address proposedDistributor; - assembly { - proposedDistributor := sload(PROPOSED_DISTRIBUTOR_SLOT) - } - if (msg.sender != proposedDistributor) revert NotProposedDistributor(); - _setDistributor(proposedDistributor); - } } diff --git a/contracts/utils/Operatable.sol b/contracts/utils/Operatable.sol index 6f567744..5ca7537e 100644 --- a/contracts/utils/Operatable.sol +++ b/contracts/utils/Operatable.sol @@ -59,7 +59,7 @@ contract Operatable is IOperatable { * @dev Can only be called by the current operator * @param operator_ The address of the new operator */ - function proposeOperatorChange(address operator_) external onlyOperator { + function proposeOperatorship(address operator_) external onlyOperator { assembly { sstore(PROPOSED_OPERATOR_SLOT, operator_) } @@ -70,7 +70,7 @@ contract Operatable is IOperatable { * @notice Accept a proposed change of operatorship * @dev Can only be called by the proposed operator */ - function acceptOperatorChange() external { + function acceptOperatorship() external { address proposedOperator; assembly { proposedOperator := sload(PROPOSED_OPERATOR_SLOT) diff --git a/test/tokenService.js b/test/tokenService.js index a727cbc7..11844e92 100644 --- a/test/tokenService.js +++ b/test/tokenService.js @@ -123,7 +123,7 @@ describe('Interchain Token Service', () => { }); it('Should register a canonical token', async () => { - const params = defaultAbiCoder.encode(['bytes', 'address'], [service.address, token.address]); + const params = defaultAbiCoder.encode(['bytes', 'address'], ['0x', token.address]); await expect(service.registerCanonicalToken(token.address)) .to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, LOCK_UNLOCK, params); @@ -135,7 +135,7 @@ describe('Interchain Token Service', () => { }); it('Should revert if canonical token has already been registered', async () => { - const params = defaultAbiCoder.encode(['bytes', 'address'], [service.address, token.address]); + const params = defaultAbiCoder.encode(['bytes', 'address'], ['0x', token.address]); await expect(service.registerCanonicalToken(token.address)) .to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, LOCK_UNLOCK, params); diff --git a/test/tokenServiceFullFlow.js b/test/tokenServiceFullFlow.js index 2cfa050a..23376953 100644 --- a/test/tokenServiceFullFlow.js +++ b/test/tokenServiceFullFlow.js @@ -87,7 +87,7 @@ describe('Interchain Token Service', () => { value += gasValues[i]; } - const params = defaultAbiCoder.encode(['bytes', 'address'], [service.address, token.address]); + const params = defaultAbiCoder.encode(['bytes', 'address'], ['0x', token.address]); const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes', 'uint256', 'bytes'], [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, name, symbol, decimals, '0x', '0x', 0, '0x'], From 9074ed1ed0228e96a4551be68d7242b80c83e7a8 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 26 Jul 2023 19:12:13 +0300 Subject: [PATCH 17/54] prettier and lint --- .../InterchainTokenService.sol | 13 +++- .../interchain-token/InterchainToken.sol | 12 +++- contracts/interfaces/IInterchainToken.sol | 1 - contracts/interfaces/IStandardizedToken.sol | 2 - .../RemoteAddressValidator.sol | 5 +- contracts/test/InterchainTokenTest.sol | 10 ++- .../StandardizedToken.sol | 11 ++-- contracts/utils/StandardizedTokenDeployer.sol | 3 +- test/tokenService.js | 63 +++++++++++++++++-- test/utils.js | 5 +- 10 files changed, 98 insertions(+), 27 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 7c473ec4..08b6be68 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -421,7 +421,18 @@ contract InterchainTokenService is uint256 gasValue ) external payable notPaused { bytes32 tokenId = getCustomTokenId(msg.sender, salt); - _deployRemoteStandardizedToken(tokenId, name, symbol, decimals, distributor, mintTo, mintAmount, operator, destinationChain, gasValue); + _deployRemoteStandardizedToken( + tokenId, + name, + symbol, + decimals, + distributor, + mintTo, + mintAmount, + operator, + destinationChain, + gasValue + ); } /** diff --git a/contracts/interchain-token/InterchainToken.sol b/contracts/interchain-token/InterchainToken.sol index 4d1f4961..3da7f8dd 100644 --- a/contracts/interchain-token/InterchainToken.sol +++ b/contracts/interchain-token/InterchainToken.sol @@ -36,7 +36,7 @@ abstract contract InterchainToken is IInterchainToken, ERC20 { bytes calldata metadata ) external payable { address sender = msg.sender; - + _beforeInterchainTransfer(msg.sender, destinationChain, recipient, amount, metadata); ITokenManager tokenManager_ = tokenManager(); @@ -65,7 +65,7 @@ abstract contract InterchainToken is IInterchainToken, ERC20 { if (_allowance != type(uint256).max) { _approve(sender, msg.sender, _allowance - amount); } - + _beforeInterchainTransfer(msg.sender, destinationChain, recipient, amount, metadata); ITokenManager tokenManager_ = tokenManager(); @@ -80,5 +80,11 @@ abstract contract InterchainToken is IInterchainToken, ERC20 { * @param amount the amount of token to be transfered. * @param metadata either empty, to just facilitate a cross-chain transfer, or the data to be passed to a cross-chain contract call and transfer. */ - function _beforeInterchainTransfer(address from, string calldata destinationChain, bytes calldata destinationAddress, uint256 amount, bytes calldata metadata) internal virtual {} + function _beforeInterchainTransfer( + address from, + string calldata destinationChain, + bytes calldata destinationAddress, + uint256 amount, + bytes calldata metadata + ) internal virtual {} } diff --git a/contracts/interfaces/IInterchainToken.sol b/contracts/interfaces/IInterchainToken.sol index 4bc57fd7..567519c4 100644 --- a/contracts/interfaces/IInterchainToken.sol +++ b/contracts/interfaces/IInterchainToken.sol @@ -6,7 +6,6 @@ pragma solidity ^0.8.0; * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IInterchainToken { - /** * @notice Implementation of the interchainTransfer method * @dev We chose to either pass `metadata` as raw data on a remote contract call, or, if no data is passed, just do a transfer. diff --git a/contracts/interfaces/IStandardizedToken.sol b/contracts/interfaces/IStandardizedToken.sol index 61db1fb1..79b61315 100644 --- a/contracts/interfaces/IStandardizedToken.sol +++ b/contracts/interfaces/IStandardizedToken.sol @@ -8,7 +8,6 @@ import { IERC20BurnableMintable } from './IERC20BurnableMintable.sol'; import { ITokenManager } from './ITokenManager.sol'; import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; - /** * @title StandardizedToken * @notice This contract implements a standardized token which extends InterchainToken functionality. @@ -33,5 +32,4 @@ interface IStandardizedToken is IInterchainToken, IDistributable, IERC20Burnable * @return tokenManager_ the TokenManager called to facilitate cross chain transfers. */ function tokenManager() external view returns (ITokenManager tokenManager_); - } diff --git a/contracts/remote-address-validator/RemoteAddressValidator.sol b/contracts/remote-address-validator/RemoteAddressValidator.sol index 5d1a0814..55c4c7ef 100644 --- a/contracts/remote-address-validator/RemoteAddressValidator.sol +++ b/contracts/remote-address-validator/RemoteAddressValidator.sol @@ -20,8 +20,6 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { uint256 private immutable interchainTokenServiceAddress2; mapping(string => bool) public supportedByGateway; - - bytes32 private constant CONTRACT_ID = keccak256('remote-address-validator'); /** @@ -41,7 +39,6 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { } interchainTokenServiceAddress1 = p1; interchainTokenServiceAddress2 = p2; - } /** @@ -78,7 +75,7 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { interchainTokenServiceAddressString = new string(42); uint256 p1 = interchainTokenServiceAddress1; uint256 p2 = interchainTokenServiceAddress2; - assembly{ + assembly { mstore(add(interchainTokenServiceAddressString, 32), p1) mstore(add(interchainTokenServiceAddressString, 64), p2) } diff --git a/contracts/test/InterchainTokenTest.sol b/contracts/test/InterchainTokenTest.sol index 1371c4e2..53c369c2 100644 --- a/contracts/test/InterchainTokenTest.sol +++ b/contracts/test/InterchainTokenTest.sol @@ -26,8 +26,14 @@ contract InterchainTokenTest is InterchainToken, Distributable, IERC20BurnableMi return tokenManager_; } - function _beforeInterchainTransfer(address sender, string calldata /*destinationChain*/, bytes calldata /*destinationAddress*/, uint256 amount, bytes calldata /*metadata*/) internal override { - if(!tokenManagerRequiresApproval_) return; + function _beforeInterchainTransfer( + address sender, + string calldata /*destinationChain*/, + bytes calldata /*destinationAddress*/, + uint256 amount, + bytes calldata /*metadata*/ + ) internal override { + if (!tokenManagerRequiresApproval_) return; address tokenManagerAddress = address(tokenManager_); uint256 allowance_ = allowance[sender][tokenManagerAddress]; if (allowance_ != type(uint256).max) { diff --git a/contracts/token-implementations/StandardizedToken.sol b/contracts/token-implementations/StandardizedToken.sol index 67a8abc8..b27b8c96 100644 --- a/contracts/token-implementations/StandardizedToken.sol +++ b/contracts/token-implementations/StandardizedToken.sol @@ -26,9 +26,9 @@ contract StandardizedToken is InterchainToken, ERC20Permit, Implementation, Dist bytes32 private constant CONTRACT_ID = keccak256('standardized-token'); - modifier onlyDistributorOrTokenManager { - if(msg.sender != tokenManager_) { - if(msg.sender != distributor()) revert NotDistributor(); + modifier onlyDistributorOrTokenManager() { + if (msg.sender != tokenManager_) { + if (msg.sender != distributor()) revert NotDistributor(); } _; } @@ -58,7 +58,10 @@ contract StandardizedToken is InterchainToken, ERC20Permit, Implementation, Dist address distributor_; address tokenManagerAddress; string memory tokenName; - (tokenManagerAddress, distributor_, tokenName, symbol, decimals) = abi.decode(params, (address, address, string, string, uint8)); + (tokenManagerAddress, distributor_, tokenName, symbol, decimals) = abi.decode( + params, + (address, address, string, string, uint8) + ); _setDistributor(distributor_); tokenManager_ = tokenManagerAddress; _setDomainTypeSignatureHash(tokenName); diff --git a/contracts/utils/StandardizedTokenDeployer.sol b/contracts/utils/StandardizedTokenDeployer.sol index 32e02e75..5c2a2ca8 100644 --- a/contracts/utils/StandardizedTokenDeployer.sol +++ b/contracts/utils/StandardizedTokenDeployer.sol @@ -22,8 +22,7 @@ contract StandardizedTokenDeployer is IStandardizedTokenDeployer { * @param implementationAddress_ Address of the StandardizedToken contract */ constructor(address deployer_, address implementationAddress_) { - if (deployer_ == address(0) || implementationAddress_ == address(0)) - revert AddressZero(); + if (deployer_ == address(0) || implementationAddress_ == address(0)) revert AddressZero(); deployer = Create3Deployer(deployer_); implementationAddress = implementationAddress_; } diff --git a/test/tokenService.js b/test/tokenService.js index 11844e92..ec201748 100644 --- a/test/tokenService.js +++ b/test/tokenService.js @@ -316,7 +316,17 @@ describe('Interchain Token Service', () => { const tokenId = await service.getCustomTokenId(wallet.address, salt); const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes', 'uint256', 'bytes'], - [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, tokenName, tokenSymbol, tokenDecimals, distributor, mintTo, mintAmount, operator], + [ + SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, + tokenId, + tokenName, + tokenSymbol, + tokenDecimals, + distributor, + mintTo, + mintAmount, + operator, + ], ); await expect( service.deployAndRegisterRemoteStandardizedToken( @@ -332,9 +342,20 @@ describe('Interchain Token Service', () => { gasValue, { value: gasValue }, ), - ) + ) .to.emit(service, 'RemoteStandardizedTokenAndManagerDeploymentInitialized') - .withArgs(tokenId, tokenName, tokenSymbol, tokenDecimals, distributor, mintTo, mintAmount, operator, destinationChain, gasValue) + .withArgs( + tokenId, + tokenName, + tokenSymbol, + tokenDecimals, + distributor, + mintTo, + mintAmount, + operator, + destinationChain, + gasValue, + ) .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, destinationChain, service.address.toLowerCase(), keccak256(payload), gasValue, wallet.address) .and.to.emit(gateway, 'ContractCall') @@ -394,7 +415,17 @@ describe('Interchain Token Service', () => { const params = defaultAbiCoder.encode(['bytes', 'address'], [distributor, tokenAddress]); const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes', 'uint256', 'bytes'], - [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, tokenName, tokenSymbol, tokenDecimals, distributor, mintTo, mintAmount, operator], + [ + SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, + tokenId, + tokenName, + tokenSymbol, + tokenDecimals, + distributor, + mintTo, + mintAmount, + operator, + ], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); const token = new Contract(tokenAddress, Token.abi, wallet); @@ -422,7 +453,17 @@ describe('Interchain Token Service', () => { const params = defaultAbiCoder.encode(['bytes', 'address'], [operator, tokenAddress]); const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes', 'uint256', 'bytes'], - [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, tokenName, tokenSymbol, tokenDecimals, distributor, mintTo, mintAmount, operator], + [ + SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, + tokenId, + tokenName, + tokenSymbol, + tokenDecimals, + distributor, + mintTo, + mintAmount, + operator, + ], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); @@ -447,7 +488,17 @@ describe('Interchain Token Service', () => { const params = defaultAbiCoder.encode(['bytes', 'address'], [service.address, tokenAddress]); const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes', 'uint256', 'bytes'], - [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, tokenName, tokenSymbol, tokenDecimals, distributor, mintTo, mintAmount, operator], + [ + SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, + tokenId, + tokenName, + tokenSymbol, + tokenDecimals, + distributor, + mintTo, + mintAmount, + operator, + ], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); const token = new Contract(tokenAddress, Token.abi, wallet); diff --git a/test/utils.js b/test/utils.js index 544ae8e0..16a90746 100644 --- a/test/utils.js +++ b/test/utils.js @@ -62,7 +62,9 @@ describe('Distributable', () => { it('Should be able to change the distributor only as the distributor', async () => { expect(await test.distributor()).to.equal(ownerWallet.address); - await expect(test.transferDistributorship(otherWallet.address)).to.emit(test, 'DistributorshipTransferred').withArgs(otherWallet.address); + await expect(test.transferDistributorship(otherWallet.address)) + .to.emit(test, 'DistributorshipTransferred') + .withArgs(otherWallet.address); expect(await test.distributor()).to.equal(otherWallet.address); await expect(test.transferDistributorship(otherWallet.address)).to.be.revertedWithCustomError(test, 'NotDistributor'); }); @@ -344,7 +346,6 @@ describe('Pausable', () => { describe('StandardizedTokenDeployer', () => { let create3Deployer, standardizedToken, standardizedTokenDeployer; const tokenManager = new Wallet(getRandomBytes32()).address; - const distributor = new Wallet(getRandomBytes32()).address; const mintTo = new Wallet(getRandomBytes32()).address; const name = 'tokenName'; const symbol = 'tokenSymbol'; From d3cb1508a213768abf25f8bdf22aab87592d5feb Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 27 Jul 2023 16:48:36 +0300 Subject: [PATCH 18/54] stash --- .../InterchainTokenService.sol | 8 ++--- .../interfaces/IStandardizedTokenDeployer.sol | 11 +++++-- .../interfaces/ITokenManagerDeployer.sol | 5 --- contracts/utils/StandardizedTokenDeployer.sol | 15 +++++---- contracts/utils/TokenManagerDeployer.sol | 15 ++------- hardhat.config.js | 2 +- scripts/deploy.js | 3 +- test/tokenServiceFullFlow.js | 33 +++---------------- test/utils.js | 6 ++-- 9 files changed, 30 insertions(+), 68 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 08b6be68..6bfa7640 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -21,7 +21,7 @@ import { AddressBytesUtils } from '../libraries/AddressBytesUtils.sol'; import { StringToBytes32, Bytes32ToString } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/Bytes32String.sol'; import { Upgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Upgradable.sol'; -import { Create3Deployer } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3Deployer.sol'; +import { Create3 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3.sol'; import { ExpressCallHandler } from '../utils/ExpressCallHandler.sol'; import { Pausable } from '../utils/Pausable.sol'; @@ -55,7 +55,6 @@ contract InterchainTokenService is IRemoteAddressValidator public immutable remoteAddressValidator; address public immutable tokenManagerDeployer; address public immutable standardizedTokenDeployer; - Create3Deployer internal immutable deployer; bytes32 internal immutable chainNameHash; bytes32 internal immutable chainName; @@ -99,7 +98,6 @@ contract InterchainTokenService is gasService = IAxelarGasService(gasService_); tokenManagerDeployer = tokenManagerDeployer_; standardizedTokenDeployer = standardizedTokenDeployer_; - deployer = ITokenManagerDeployer(tokenManagerDeployer_).deployer(); if (tokenManagerImplementations.length != uint256(type(TokenManagerType).max) + 1) revert LengthMismatch(); @@ -160,7 +158,7 @@ contract InterchainTokenService is */ // TODO: Maybe copy the code of the create3Deployer to save gas, but would introduce duplicate code problems. function getTokenManagerAddress(bytes32 tokenId) public view returns (address tokenManagerAddress) { - tokenManagerAddress = deployer.deployedAddress(address(this), tokenId); + tokenManagerAddress = Create3.deployedAddress(address(this), tokenId); } /** @@ -191,7 +189,7 @@ contract InterchainTokenService is */ function getStandardizedTokenAddress(bytes32 tokenId) public view returns (address tokenAddress) { tokenId = _getStandardizedTokenSalt(tokenId); - tokenAddress = deployer.deployedAddress(address(this), tokenId); + tokenAddress = Create3.deployedAddress(address(this), tokenId); } /** diff --git a/contracts/interfaces/IStandardizedTokenDeployer.sol b/contracts/interfaces/IStandardizedTokenDeployer.sol index 3444b892..d6ebe4be 100644 --- a/contracts/interfaces/IStandardizedTokenDeployer.sol +++ b/contracts/interfaces/IStandardizedTokenDeployer.sol @@ -11,11 +11,16 @@ import { Create3Deployer } from '@axelar-network/axelar-gmp-sdk-solidity/contrac interface IStandardizedTokenDeployer { error AddressZero(); error TokenDeploymentFailed(); + /** + * @notice Returns the standardized token implementation address + */ + function implementationAddress() external view returns(address); /** - * @notice Getter for the Create3Deployer. - */ - function deployer() external view returns (Create3Deployer); + * @notice Returns the standardized token deployment address. + * @return tokenAddress the token address. + */ + function deployedAddress(bytes32 salt) external view returns (address tokenAddress); /** * @notice Deploys a new instance of the StandardizedTokenProxy contract diff --git a/contracts/interfaces/ITokenManagerDeployer.sol b/contracts/interfaces/ITokenManagerDeployer.sol index ceae052e..c3e8cbf8 100644 --- a/contracts/interfaces/ITokenManagerDeployer.sol +++ b/contracts/interfaces/ITokenManagerDeployer.sol @@ -12,11 +12,6 @@ interface ITokenManagerDeployer { error AddressZero(); error TokenManagerDeploymentFailed(); - /** - * @notice Getter for the Create3Deployer. - */ - function deployer() external view returns (Create3Deployer); - /** * @notice Deploys a new instance of the TokenManagerProxy contract * @param tokenId The unique identifier for the token diff --git a/contracts/utils/StandardizedTokenDeployer.sol b/contracts/utils/StandardizedTokenDeployer.sol index 5c2a2ca8..b7af5c8d 100644 --- a/contracts/utils/StandardizedTokenDeployer.sol +++ b/contracts/utils/StandardizedTokenDeployer.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import { Create3Deployer } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3Deployer.sol'; +import { Create3 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3.sol'; import { IStandardizedTokenDeployer } from '../interfaces/IStandardizedTokenDeployer.sol'; @@ -13,17 +13,14 @@ import { StandardizedTokenProxy } from '../proxies/StandardizedTokenProxy.sol'; * @notice This contract is used to deploy new instances of the StandardizedTokenProxy contract. */ contract StandardizedTokenDeployer is IStandardizedTokenDeployer { - Create3Deployer public immutable deployer; address public immutable implementationAddress; /** * @notice Constructor for the StandardizedTokenDeployer contract - * @param deployer_ Address of the Create3Deployer contract * @param implementationAddress_ Address of the StandardizedToken contract */ - constructor(address deployer_, address implementationAddress_) { - if (deployer_ == address(0) || implementationAddress_ == address(0)) revert AddressZero(); - deployer = Create3Deployer(deployer_); + constructor(address implementationAddress_) { + if (implementationAddress_ == address(0)) revert AddressZero(); implementationAddress = implementationAddress_; } @@ -53,7 +50,11 @@ contract StandardizedTokenDeployer is IStandardizedTokenDeployer { bytes memory params = abi.encode(tokenManager, distributor, name, symbol, decimals, mintAmount, mintTo); bytecode = abi.encodePacked(type(StandardizedTokenProxy).creationCode, abi.encode(implementationAddress, params)); } - address tokenAddress = deployer.deploy(bytecode, salt); + address tokenAddress = Create3.deploy(salt, bytecode); if (tokenAddress.code.length == 0) revert TokenDeploymentFailed(); } + + function deployedAddress(bytes32 salt) external view returns (address tokenAddress) { + return Create3.deployedAddress(address(this), salt); + } } diff --git a/contracts/utils/TokenManagerDeployer.sol b/contracts/utils/TokenManagerDeployer.sol index 8c552d80..14040795 100644 --- a/contracts/utils/TokenManagerDeployer.sol +++ b/contracts/utils/TokenManagerDeployer.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import { Create3Deployer } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3Deployer.sol'; +import { Create3 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3.sol'; import { ITokenManagerDeployer } from '../interfaces/ITokenManagerDeployer.sol'; @@ -13,17 +13,6 @@ import { TokenManagerProxy } from '../proxies/TokenManagerProxy.sol'; * @notice This contract is used to deploy new instances of the TokenManagerProxy contract. */ contract TokenManagerDeployer is ITokenManagerDeployer { - Create3Deployer public immutable deployer; - - /** - * @notice Constructor for the TokenManagerDeployer contract - * @param deployer_ Address of the Create3Deployer contract - */ - constructor(address deployer_) { - if (deployer_ == address(0)) revert AddressZero(); - deployer = Create3Deployer(deployer_); - } - /** * @notice Deploys a new instance of the TokenManagerProxy contract * @param tokenId The unique identifier for the token @@ -33,7 +22,7 @@ contract TokenManagerDeployer is ITokenManagerDeployer { function deployTokenManager(bytes32 tokenId, uint256 implementationType, bytes calldata params) external payable { bytes memory args = abi.encode(address(this), implementationType, tokenId, params); bytes memory bytecode = abi.encodePacked(type(TokenManagerProxy).creationCode, args); - address tokenManagerAddress = deployer.deploy(bytecode, tokenId); + address tokenManagerAddress = Create3.deploy(tokenId, bytecode); if (tokenManagerAddress.code.length == 0) revert TokenManagerDeploymentFailed(); } } diff --git a/hardhat.config.js b/hardhat.config.js index e92d0807..b4a3f6ad 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -1,3 +1,4 @@ +require('dotenv').config(); require('@nomicfoundation/hardhat-toolbox'); require('solidity-coverage'); require('solidity-docgen'); @@ -7,7 +8,6 @@ const { importNetworks, readJSON } = require('@axelar-network/axelar-contract-de const chains = require(`@axelar-network/axelar-contract-deployments/info/${env}.json`); const keys = readJSON(`${__dirname}/info/keys.json`); const { networks, etherscan } = importNetworks(chains, keys); - /** * @type import('hardhat/config').HardhatUserConfig */ diff --git a/scripts/deploy.js b/scripts/deploy.js index d23320a4..746a9e4c 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -80,10 +80,9 @@ async function deployAll(wallet, chainName, deploymentKey = 'interchainTokenServ const create3Deployer = await deployContract(wallet, 'Create3Deployer'); const gateway = await deployMockGateway(wallet); const gasService = await deployGasService(wallet); - const tokenManagerDeployer = await deployContract(wallet, 'TokenManagerDeployer', [create3Deployer.address]); + const tokenManagerDeployer = await deployContract(wallet, 'TokenManagerDeployer', []); const standardizedToken = await deployContract(wallet, 'StandardizedToken'); const standardizedTokenDeployer = await deployContract(wallet, 'StandardizedTokenDeployer', [ - create3Deployer.address, standardizedToken.address, ]); const interchainTokenServiceAddress = await getCreate3Address(create3Deployer.address, wallet, deploymentKey); diff --git a/test/tokenServiceFullFlow.js b/test/tokenServiceFullFlow.js index 23376953..02cc5954 100644 --- a/test/tokenServiceFullFlow.js +++ b/test/tokenServiceFullFlow.js @@ -11,10 +11,9 @@ const { Contract, Wallet } = ethers; const IStandardizedTokenDeployer = require('../artifacts/contracts/interfaces/IStandardizedTokenDeployer.sol/IStandardizedTokenDeployer.json'); const IStandardizedToken = require('../artifacts/contracts/interfaces/IStandardizedToken.sol/IStandardizedToken.json'); const ITokenManager = require('../artifacts/contracts/interfaces/ITokenManager.sol/ITokenManager.json'); -const Create3Deployer = require('../artifacts/@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3Deployer.sol/Create3Deployer.json'); const { getRandomBytes32 } = require('../scripts/utils'); -const { deployAll } = require('../scripts/deploy'); +const { deployAll, deployContract } = require('../scripts/deploy'); const SELECTOR_SEND_TOKEN = 1; // const SELECTOR_SEND_TOKEN_WITH_DATA = 2; @@ -40,40 +39,18 @@ describe('Interchain Token Service', () => { describe('Full canonical token registration, remote deployment and token send', async () => { let token; - const salt = getRandomBytes32(); const otherChains = ['chain 1', 'chain 2']; const gasValues = [1234, 5678]; const tokenCap = BigInt(1e18); before(async () => { // The below is used to deploy a token, but any ERC20 can be used instead. - const tokenDeployerAddress = await service.standardizedTokenDeployer(); - const tokenDeployer = new Contract(tokenDeployerAddress, IStandardizedTokenDeployer.abi, wallet); - const create3DeployerAddress = await tokenDeployer.deployer(); - const create3Deployer = new Contract(create3DeployerAddress, Create3Deployer.abi, wallet); - const tokenAddress = await create3Deployer.deployedAddress(tokenDeployer.address, salt); - token = new Contract(tokenAddress, IStandardizedToken.abi, wallet); - - tokenId = await service.getCanonicalTokenId(tokenAddress); + token = await deployContract(wallet, 'InterchainTokenTest', [name, symbol, decimals, wallet.address]) + tokenId = await service.getCanonicalTokenId(token.address); const tokenManagerAddress = await service.getTokenManagerAddress(tokenId); + await (await token.mint(wallet.address, tokenCap)).wait(); + await (await token.setTokenManager(tokenManagerAddress)).wait(); tokenManager = new Contract(tokenManagerAddress, ITokenManager.abi, wallet); - - await expect( - tokenDeployer.deployStandardizedToken( - salt, - tokenManagerAddress, - wallet.address, - name, - symbol, - decimals, - tokenCap, - wallet.address, - ), - ) - .to.emit(token, 'DistributorshipTransferred') - .withArgs(wallet.address) - .and.to.emit(token, 'Transfer') - .withArgs(AddressZero, wallet.address, tokenCap); }); it('Should register the token and initiate its deployment on other chains', async () => { diff --git a/test/utils.js b/test/utils.js index 16a90746..5ae6959e 100644 --- a/test/utils.js +++ b/test/utils.js @@ -353,10 +353,8 @@ describe('StandardizedTokenDeployer', () => { const mintAmount = 123; before(async () => { - create3Deployer = await deployContract(ownerWallet, 'Create3Deployer'); standardizedToken = await deployContract(ownerWallet, 'StandardizedToken'); standardizedTokenDeployer = await deployContract(ownerWallet, 'StandardizedTokenDeployer', [ - create3Deployer.address, standardizedToken.address, ]); }); @@ -364,7 +362,7 @@ describe('StandardizedTokenDeployer', () => { it('Should deploy a mint burn token only once', async () => { const salt = getRandomBytes32(); - const tokenAddress = await create3Deployer.deployedAddress(standardizedTokenDeployer.address, salt); + const tokenAddress = await standardizedTokenDeployer.deployedAddress(salt); const token = new Contract(tokenAddress, StandardizedToken.abi, ownerWallet); const tokenProxy = new Contract(tokenAddress, StandardizedTokenProxy.abi, ownerWallet); @@ -386,6 +384,6 @@ describe('StandardizedTokenDeployer', () => { expect(await token.tokenManager()).to.equal(tokenManager); await expect( standardizedTokenDeployer.deployStandardizedToken(salt, tokenManager, tokenManager, name, symbol, decimals, mintAmount, mintTo), - ).to.be.revertedWithCustomError(create3Deployer, 'AlreadyDeployed'); + ).to.be.revertedWithCustomError(standardizedTokenDeployer, 'AlreadyDeployed'); }); }); From 53fe6fe29bd864422d3abe16b691e0a552e2bb95 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 27 Jul 2023 16:52:00 +0300 Subject: [PATCH 19/54] import .env in hardhat.config --- hardhat.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/hardhat.config.js b/hardhat.config.js index e92d0807..ef6ece08 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -1,3 +1,4 @@ +require('dotenv').config(); require('@nomicfoundation/hardhat-toolbox'); require('solidity-coverage'); require('solidity-docgen'); From 7eb5207329aac8f7d74c21c9a1d9c7acb7924be6 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 27 Jul 2023 17:00:12 +0300 Subject: [PATCH 20/54] trying to fix .env.example --- .env.example | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 9de8b34c..5c6683a5 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1 @@ -PRIVATE_KEY_GENERATOR = 'this is a random string to get a random account. You need to provide the private key for a funded account here.' -ENV = 'local' +PRIVATE_KEY_GENERATOR = 'this is a random string to get a random account. You need to provide the private key for a funded account here.' \ No newline at end of file From bf603ebd658e14969c57ffc4fd10992f122f66f7 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 27 Jul 2023 17:06:31 +0300 Subject: [PATCH 21/54] Added some getters in IRemoteAddressValidator and removed useless check for distributor in the InterchainTokenService. --- .../interchain-token-service/InterchainTokenService.sol | 1 - contracts/interfaces/IRemoteAddressValidator.sol | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 08b6be68..c99eac33 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -691,7 +691,6 @@ contract InterchainTokenService is address tokenAddress = getStandardizedTokenAddress(tokenId); address tokenManagerAddress = getTokenManagerAddress(tokenId); address distributor = distributorBytes.length > 0 ? distributorBytes.toAddress() : tokenManagerAddress; - if (distributor == address(0)) revert ZeroAddress(); address mintTo = mintToBytes.length > 0 ? mintToBytes.toAddress() : distributor; _deployStandardizedToken(tokenId, distributor, name, symbol, decimals, mintAmount, mintTo); _deployTokenManager( diff --git a/contracts/interfaces/IRemoteAddressValidator.sol b/contracts/interfaces/IRemoteAddressValidator.sol index b5b8b4a3..7ac84468 100644 --- a/contracts/interfaces/IRemoteAddressValidator.sol +++ b/contracts/interfaces/IRemoteAddressValidator.sol @@ -16,6 +16,14 @@ interface IRemoteAddressValidator { event GatewaySupportedChainAdded(string chain); event GatewaySupportedChainRemoved(string chain); + /** + * @notice Returns the interchain token address + */ + function interchainTokenServiceAddress() external view returns (address); + /** + * @notice Returns the interchain token address to string to lower case hash, which is used to compare with incoming calls. + */ + function interchainTokenServiceAddressHash() external view returns (bytes32); /** * @dev Validates that the sender is a valid interchain token service address * @param sourceChain Source chain of the transaction From f1723c3f5cbc1d80bff240e2bf03f0ecdc241feb Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 27 Jul 2023 17:12:31 +0300 Subject: [PATCH 22/54] removed ternary operators --- .../InterchainTokenService.sol | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index c99eac33..e7cfb90c 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -690,13 +690,30 @@ contract InterchainTokenService is ) = abi.decode(payload, (uint256, bytes32, string, string, uint8, bytes, bytes, uint256, bytes)); address tokenAddress = getStandardizedTokenAddress(tokenId); address tokenManagerAddress = getTokenManagerAddress(tokenId); - address distributor = distributorBytes.length > 0 ? distributorBytes.toAddress() : tokenManagerAddress; - address mintTo = mintToBytes.length > 0 ? mintToBytes.toAddress() : distributor; + address distributor; + address mintTo; + + if(distributorBytes.length == 0) { + distributor = tokenManagerAddress; + } else { + distributor = distributorBytes.toAddress(); + } + + if(mintToBytes.length == 0) { + mintTo = distributor; + } else { + mintTo = mintToBytes.toAddress(); + } + + if(operatorBytes.length == 0) { + operatorBytes = address(this).toBytes(); + } + _deployStandardizedToken(tokenId, distributor, name, symbol, decimals, mintAmount, mintTo); _deployTokenManager( tokenId, TokenManagerType.MINT_BURN, - abi.encode(operatorBytes.length == 0 ? address(this).toBytes() : operatorBytes, tokenAddress) + abi.encode(operatorBytes, tokenAddress) ); } From bc8ed59646f8860db05b34c0848e8efb6e130b0c Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 27 Jul 2023 17:13:16 +0300 Subject: [PATCH 23/54] made lint happy --- .../InterchainTokenService.sol | 14 +++++--------- contracts/interfaces/IRemoteAddressValidator.sol | 4 +++- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index e7cfb90c..5a9eb904 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -693,28 +693,24 @@ contract InterchainTokenService is address distributor; address mintTo; - if(distributorBytes.length == 0) { + if (distributorBytes.length == 0) { distributor = tokenManagerAddress; } else { distributor = distributorBytes.toAddress(); } - - if(mintToBytes.length == 0) { + + if (mintToBytes.length == 0) { mintTo = distributor; } else { mintTo = mintToBytes.toAddress(); } - if(operatorBytes.length == 0) { + if (operatorBytes.length == 0) { operatorBytes = address(this).toBytes(); } _deployStandardizedToken(tokenId, distributor, name, symbol, decimals, mintAmount, mintTo); - _deployTokenManager( - tokenId, - TokenManagerType.MINT_BURN, - abi.encode(operatorBytes, tokenAddress) - ); + _deployTokenManager(tokenId, TokenManagerType.MINT_BURN, abi.encode(operatorBytes, tokenAddress)); } /** diff --git a/contracts/interfaces/IRemoteAddressValidator.sol b/contracts/interfaces/IRemoteAddressValidator.sol index 7ac84468..9f54bb13 100644 --- a/contracts/interfaces/IRemoteAddressValidator.sol +++ b/contracts/interfaces/IRemoteAddressValidator.sol @@ -19,11 +19,13 @@ interface IRemoteAddressValidator { /** * @notice Returns the interchain token address */ - function interchainTokenServiceAddress() external view returns (address); + function interchainTokenServiceAddress() external view returns (address); + /** * @notice Returns the interchain token address to string to lower case hash, which is used to compare with incoming calls. */ function interchainTokenServiceAddressHash() external view returns (bytes32); + /** * @dev Validates that the sender is a valid interchain token service address * @param sourceChain Source chain of the transaction From 4bf89dba8ac5793fcb04f5b283bee8537739e21f Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 27 Jul 2023 17:14:07 +0300 Subject: [PATCH 24/54] made lint happy --- contracts/interfaces/IStandardizedTokenDeployer.sol | 7 ++++--- scripts/deploy.js | 4 +--- test/tokenServiceFullFlow.js | 3 +-- test/utils.js | 6 ++---- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/contracts/interfaces/IStandardizedTokenDeployer.sol b/contracts/interfaces/IStandardizedTokenDeployer.sol index d6ebe4be..147c01c9 100644 --- a/contracts/interfaces/IStandardizedTokenDeployer.sol +++ b/contracts/interfaces/IStandardizedTokenDeployer.sol @@ -11,15 +11,16 @@ import { Create3Deployer } from '@axelar-network/axelar-gmp-sdk-solidity/contrac interface IStandardizedTokenDeployer { error AddressZero(); error TokenDeploymentFailed(); + /** * @notice Returns the standardized token implementation address - */ - function implementationAddress() external view returns(address); + */ + function implementationAddress() external view returns (address); /** * @notice Returns the standardized token deployment address. * @return tokenAddress the token address. - */ + */ function deployedAddress(bytes32 salt) external view returns (address tokenAddress); /** diff --git a/scripts/deploy.js b/scripts/deploy.js index 746a9e4c..cb153979 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -82,9 +82,7 @@ async function deployAll(wallet, chainName, deploymentKey = 'interchainTokenServ const gasService = await deployGasService(wallet); const tokenManagerDeployer = await deployContract(wallet, 'TokenManagerDeployer', []); const standardizedToken = await deployContract(wallet, 'StandardizedToken'); - const standardizedTokenDeployer = await deployContract(wallet, 'StandardizedTokenDeployer', [ - standardizedToken.address, - ]); + const standardizedTokenDeployer = await deployContract(wallet, 'StandardizedTokenDeployer', [standardizedToken.address]); const interchainTokenServiceAddress = await getCreate3Address(create3Deployer.address, wallet, deploymentKey); const remoteAddressValidator = await deployRemoteAddressValidator(wallet, interchainTokenServiceAddress); const tokenManagerImplementations = await deployTokenManagerImplementations(wallet, interchainTokenServiceAddress); diff --git a/test/tokenServiceFullFlow.js b/test/tokenServiceFullFlow.js index 02cc5954..89bcf31c 100644 --- a/test/tokenServiceFullFlow.js +++ b/test/tokenServiceFullFlow.js @@ -8,7 +8,6 @@ const { AddressZero } = ethers.constants; const { defaultAbiCoder, keccak256 } = ethers.utils; const { Contract, Wallet } = ethers; -const IStandardizedTokenDeployer = require('../artifacts/contracts/interfaces/IStandardizedTokenDeployer.sol/IStandardizedTokenDeployer.json'); const IStandardizedToken = require('../artifacts/contracts/interfaces/IStandardizedToken.sol/IStandardizedToken.json'); const ITokenManager = require('../artifacts/contracts/interfaces/ITokenManager.sol/ITokenManager.json'); @@ -45,7 +44,7 @@ describe('Interchain Token Service', () => { before(async () => { // The below is used to deploy a token, but any ERC20 can be used instead. - token = await deployContract(wallet, 'InterchainTokenTest', [name, symbol, decimals, wallet.address]) + token = await deployContract(wallet, 'InterchainTokenTest', [name, symbol, decimals, wallet.address]); tokenId = await service.getCanonicalTokenId(token.address); const tokenManagerAddress = await service.getTokenManagerAddress(tokenId); await (await token.mint(wallet.address, tokenCap)).wait(); diff --git a/test/utils.js b/test/utils.js index 5ae6959e..6d0e4f73 100644 --- a/test/utils.js +++ b/test/utils.js @@ -344,7 +344,7 @@ describe('Pausable', () => { }); describe('StandardizedTokenDeployer', () => { - let create3Deployer, standardizedToken, standardizedTokenDeployer; + let standardizedToken, standardizedTokenDeployer; const tokenManager = new Wallet(getRandomBytes32()).address; const mintTo = new Wallet(getRandomBytes32()).address; const name = 'tokenName'; @@ -354,9 +354,7 @@ describe('StandardizedTokenDeployer', () => { before(async () => { standardizedToken = await deployContract(ownerWallet, 'StandardizedToken'); - standardizedTokenDeployer = await deployContract(ownerWallet, 'StandardizedTokenDeployer', [ - standardizedToken.address, - ]); + standardizedTokenDeployer = await deployContract(ownerWallet, 'StandardizedTokenDeployer', [standardizedToken.address]); }); it('Should deploy a mint burn token only once', async () => { From 512d126ecce6c3c37cab1904d3a251789a31a9b3 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 27 Jul 2023 20:10:48 +0300 Subject: [PATCH 25/54] Added a new token manager to handle fee on transfer and added some tests for it as well --- .../InterchainTokenService.sol | 17 ++- .../interfaces/IInterchainTokenService.sol | 8 +- contracts/interfaces/ITokenManagerType.sol | 1 + contracts/test/FeeOnTransferTokenTest.sol | 72 ++++++++++++ .../TokenManagerLiquidityPool.sol | 8 +- .../TokenManagerLockUnlock.sol | 7 +- .../TokenManagerLockUnlockFeeOnTransfer.sol | 71 ++++++++++++ scripts/deploy.js | 2 +- test/tokenService.js | 109 ++++++++++++++++-- 9 files changed, 261 insertions(+), 34 deletions(-) create mode 100644 contracts/test/FeeOnTransferTokenTest.sol create mode 100644 contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 18839101..03e4356b 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -50,12 +50,13 @@ contract InterchainTokenService is address internal immutable implementationLockUnlock; address internal immutable implementationMintBurn; + address internal immutable implementationLockUnlockFee; address internal immutable implementationLiquidityPool; IAxelarGasService public immutable gasService; IRemoteAddressValidator public immutable remoteAddressValidator; address public immutable tokenManagerDeployer; address public immutable standardizedTokenDeployer; - bytes32 internal immutable chainNameHash; + bytes32 public immutable chainNameHash; bytes32 internal immutable chainName; bytes32 internal constant PREFIX_CUSTOM_TOKEN_ID = keccak256('its-custom-token-id'); @@ -103,6 +104,10 @@ contract InterchainTokenService is implementationLockUnlock = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.LOCK_UNLOCK); implementationMintBurn = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.MINT_BURN); + implementationLockUnlockFee = _sanitizeTokenManagerImplementation( + tokenManagerImplementations, + TokenManagerType.LOCK_UNLOCK_FEE_ON_TRANSFER + ); implementationLiquidityPool = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.LIQUIDITY_POOL); chainName = chainName_.toBytes32(); @@ -143,14 +148,6 @@ contract InterchainTokenService is return CONTRACT_ID; } - /** - * @notice Getter for the chain name. - * @return name the name of the chain - */ - function getChainName() public view returns (string memory name) { - name = chainName.toTrimmedString(); - } - /** * @notice Calculates the address of a TokenManager from a specific tokenId. The TokenManager does not need to exist already. * @param tokenId the tokenId. @@ -225,6 +222,8 @@ contract InterchainTokenService is return implementationLockUnlock; } else if (TokenManagerType(tokenManagerType) == TokenManagerType.MINT_BURN) { return implementationMintBurn; + } else if (TokenManagerType(tokenManagerType) == TokenManagerType.LOCK_UNLOCK_FEE_ON_TRANSFER) { + return implementationLockUnlockFee; } else if (TokenManagerType(tokenManagerType) == TokenManagerType.LIQUIDITY_POOL) { return implementationLiquidityPool; } diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index a04fcaab..a3d6d966 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -89,13 +89,7 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx * @return standardizedTokenDeployerAddress The address of the standardized token deployer contract. */ function standardizedTokenDeployer() external view returns (address standardizedTokenDeployerAddress); - - /** - * @notice Returns the name of the current chain. - * @return name The name of the current chain. - */ - function getChainName() external view returns (string memory name); - + /** * @notice Returns the address of the token manager associated with the given tokenId. * @param tokenId The tokenId of the token manager. diff --git a/contracts/interfaces/ITokenManagerType.sol b/contracts/interfaces/ITokenManagerType.sol index 9da7ec99..b9a42ade 100644 --- a/contracts/interfaces/ITokenManagerType.sol +++ b/contracts/interfaces/ITokenManagerType.sol @@ -10,6 +10,7 @@ interface ITokenManagerType { enum TokenManagerType { LOCK_UNLOCK, MINT_BURN, + LOCK_UNLOCK_FEE_ON_TRANSFER, LIQUIDITY_POOL } } diff --git a/contracts/test/FeeOnTransferTokenTest.sol b/contracts/test/FeeOnTransferTokenTest.sol new file mode 100644 index 00000000..a5163a1f --- /dev/null +++ b/contracts/test/FeeOnTransferTokenTest.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { InterchainToken } from '../interchain-token/InterchainToken.sol'; +import { Distributable } from '../utils/Distributable.sol'; +import { ITokenManager } from '../interfaces/ITokenManager.sol'; +import { IERC20BurnableMintable } from '../interfaces/IERC20BurnableMintable.sol'; + +contract FeeOnTransferTokenTest is InterchainToken, Distributable, IERC20BurnableMintable { + ITokenManager public tokenManager_; + bool internal tokenManagerRequiresApproval_ = true; + string public name; + string public symbol; + uint8 public decimals; + + constructor(string memory name_, string memory symbol_, uint8 decimals_, address tokenManagerAddress) { + name = name_; + symbol = symbol_; + decimals = decimals_; + _setDistributor(msg.sender); + tokenManager_ = ITokenManager(tokenManagerAddress); + } + + function tokenManager() public view override returns (ITokenManager) { + return tokenManager_; + } + + function _beforeInterchainTransfer( + address sender, + string calldata /*destinationChain*/, + bytes calldata /*destinationAddress*/, + uint256 amount, + bytes calldata /*metadata*/ + ) internal override { + if (!tokenManagerRequiresApproval_) return; + address tokenManagerAddress = address(tokenManager_); + uint256 allowance_ = allowance[sender][tokenManagerAddress]; + if (allowance_ != type(uint256).max) { + if (allowance_ > type(uint256).max - amount) { + allowance_ = type(uint256).max - amount; + } + + _approve(sender, tokenManagerAddress, allowance_ + amount); + } + } + + function setTokenManagerRequiresApproval(bool requiresApproval) public { + tokenManagerRequiresApproval_ = requiresApproval; + } + + function mint(address account, uint256 amount) external onlyDistributor { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external onlyDistributor { + _burn(account, amount); + } + + function setTokenManager(ITokenManager tokenManagerAddress) external { + tokenManager_ = tokenManagerAddress; + } + + // Always transfer 10 less base tokens. + function _transfer(address sender, address recipient, uint256 amount) internal override { + if (sender == address(0) || recipient == address(0)) revert InvalidAccount(); + + balanceOf[sender] -= amount; + balanceOf[recipient] += amount - 10; + emit Transfer(sender, recipient, amount); + } +} diff --git a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol index d1497425..0b5ece3d 100644 --- a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol +++ b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol @@ -26,7 +26,7 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage { constructor(address interchainTokenService_) TokenManagerAddressStorage(interchainTokenService_) {} function implementationType() external pure returns (uint256) { - return 2; + return 3; } /** @@ -82,7 +82,11 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage { SafeTokenTransferFrom.safeTransferFrom(token, from, liquidityPool_, amount); // Note: This allows support for fee-on-transfer tokens - return IERC20(token).balanceOf(liquidityPool_) - balance; + uint256 diff = IERC20(token).balanceOf(liquidityPool_) - balance; + if (diff < amount) { + amount = diff; + } + return amount; } /** diff --git a/contracts/token-manager/implementations/TokenManagerLockUnlock.sol b/contracts/token-manager/implementations/TokenManagerLockUnlock.sol index 2de125de..06e858a7 100644 --- a/contracts/token-manager/implementations/TokenManagerLockUnlock.sol +++ b/contracts/token-manager/implementations/TokenManagerLockUnlock.sol @@ -43,12 +43,10 @@ contract TokenManagerLockUnlock is TokenManagerAddressStorage { */ function _takeToken(address from, uint256 amount) internal override returns (uint256) { IERC20 token = IERC20(tokenAddress()); - uint256 balance = token.balanceOf(address(this)); SafeTokenTransferFrom.safeTransferFrom(token, from, address(this), amount); - // Note: This allows support for fee-on-transfer tokens - return IERC20(token).balanceOf(address(this)) - balance; + return amount; } /** @@ -59,10 +57,9 @@ contract TokenManagerLockUnlock is TokenManagerAddressStorage { */ function _giveToken(address to, uint256 amount) internal override returns (uint256) { IERC20 token = IERC20(tokenAddress()); - uint256 balance = IERC20(token).balanceOf(to); SafeTokenTransfer.safeTransfer(token, to, amount); - return IERC20(token).balanceOf(to) - balance; + return amount; } } diff --git a/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol b/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol new file mode 100644 index 00000000..447d3865 --- /dev/null +++ b/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { TokenManagerAddressStorage } from './TokenManagerAddressStorage.sol'; +import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; + +import { SafeTokenTransferFrom, SafeTokenTransfer } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/SafeTransfer.sol'; + +/** + * @title TokenManagerLockUnlock + * @notice This contract is an implementation of TokenManager that locks and unlocks a specific token on behalf of the interchain token service. + * @dev This contract extends TokenManagerAddressStorage and provides implementation for its abstract methods. + * It uses the Axelar SDK to safely transfer tokens. + */ +contract TokenManagerLockUnlockFee is TokenManagerAddressStorage { + /** + * @dev Constructs an instance of TokenManagerLockUnlock. Calls the constructor + * of TokenManagerAddressStorage which calls the constructor of TokenManager. + * @param interchainTokenService_ The address of the interchain token service contract + */ + constructor(address interchainTokenService_) TokenManagerAddressStorage(interchainTokenService_) {} + + function implementationType() external pure returns (uint256) { + return 2; + } + + /** + * @dev Sets up the token address. + * @param params The setup parameters in bytes. Should be encoded with the token address. + */ + function _setup(bytes calldata params) internal override { + // The first argument is reserved for the operator. + (, address tokenAddress) = abi.decode(params, (bytes, address)); + _setTokenAddress(tokenAddress); + } + + /** + * @dev Transfers a specified amount of tokens from a specified address to this contract. + * @param from The address to transfer tokens from + * @param amount The amount of tokens to transfer + * @return uint The actual amount of tokens transferred. This allows support for fee-on-transfer tokens. + */ + function _takeToken(address from, uint256 amount) internal override returns (uint256) { + IERC20 token = IERC20(tokenAddress()); + uint256 balance = token.balanceOf(address(this)); + + SafeTokenTransferFrom.safeTransferFrom(token, from, address(this), amount); + + uint256 diff = token.balanceOf(address(this)) - balance; + if (diff < amount) { + amount = diff; + } + return amount; + } + + /** + * @dev Transfers a specified amount of tokens from this contract to a specified address. + * @param to The address to transfer tokens to + * @param amount The amount of tokens to transfer + * @return uint The actual amount of tokens transferred + */ + function _giveToken(address to, uint256 amount) internal override returns (uint256) { + IERC20 token = IERC20(tokenAddress()); + uint256 balance = IERC20(token).balanceOf(to); + + SafeTokenTransfer.safeTransfer(token, to, amount); + + return IERC20(token).balanceOf(to) - balance; + } +} diff --git a/scripts/deploy.js b/scripts/deploy.js index cb153979..0d66964b 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -68,7 +68,7 @@ async function deployInterchainTokenService( async function deployTokenManagerImplementations(wallet, interchainTokenServiceAddress) { const implementations = []; - for (const type of ['LockUnlock', 'MintBurn', 'LiquidityPool']) { + for (const type of ['LockUnlock', 'MintBurn', 'LockUnlockFee', 'LiquidityPool']) { const impl = await deployContract(wallet, `TokenManager${type}`, [interchainTokenServiceAddress]); implementations.push(impl); } diff --git a/test/tokenService.js b/test/tokenService.js index ec201748..1837f683 100644 --- a/test/tokenService.js +++ b/test/tokenService.js @@ -21,7 +21,8 @@ const SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN = 4; const LOCK_UNLOCK = 0; const MINT_BURN = 1; -const LIQUIDITY_POOL = 2; +const LOCK_UNLOCK_FEE_ON_TRANSFER = 2; +const LIQUIDITY_POOL = 3; describe('Interchain Token Service', () => { let wallet, liquidityPool; @@ -53,6 +54,30 @@ describe('Interchain Token Service', () => { return [token, tokenManager, tokenId]; }; + deployFunctions.lockUnlockFee = async function deployNewLockUnlock( + tokenName, + tokenSymbol, + tokenDecimals, + mintAmount = 0, + skipApprove = false, + ) { + const salt = getRandomBytes32(); + const tokenId = await service.getCustomTokenId(wallet.address, salt); + const tokenManager = new Contract(await service.getTokenManagerAddress(tokenId), TokenManager.abi, wallet); + + const token = await deployContract(wallet, 'FeeOnTransferTokenTest', [tokenName, tokenSymbol, tokenDecimals, tokenManager.address]); + const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); + + await (await service.deployCustomTokenManager(salt, LOCK_UNLOCK_FEE_ON_TRANSFER, params)).wait(); + + if (mintAmount > 0) { + await (await token.mint(wallet.address, mintAmount)).wait(); + if (!skipApprove) await (await token.approve(tokenManager.address, mintAmount)).wait(); + } + + return [token, tokenManager, tokenId]; + }; + deployFunctions.mintBurn = async function deployNewMintBurn(tokenName, tokenSymbol, tokenDecimals, mintAmount = 0) { const salt = getRandomBytes32(); const tokenId = await service.getCustomTokenId(wallet.address, salt); @@ -562,6 +587,26 @@ describe('Interchain Token Service', () => { expect(await tokenManager.operator()).to.equal(wallet.address); }); + it('Should deploy a lock/unlock with fee on transfer token manager', async () => { + const tokenName = 'Token Name'; + const tokenSymbol = 'TN'; + const tokenDecimals = 13; + const salt = getRandomBytes32(); + const tokenId = await service.getCustomTokenId(wallet.address, salt); + const tokenManagerAddress = await service.getTokenManagerAddress(tokenId); + const token = await deployContract(wallet, 'FeeOnTransferTokenTest', [tokenName, tokenSymbol, tokenDecimals, tokenManagerAddress]); + const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); + + const tx = service.deployCustomTokenManager(salt, LOCK_UNLOCK_FEE_ON_TRANSFER, params); + await expect(tx).to.emit(service, 'TokenManagerDeployed').withArgs(tokenId, LOCK_UNLOCK_FEE_ON_TRANSFER, params); + + expect(tokenManagerAddress).to.not.equal(AddressZero); + const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); + + expect(await tokenManager.operator()).to.equal(wallet.address); + }); + + it('Should deploy a liquidity pool token manager', async () => { const tokenName = 'Token Name'; const tokenSymbol = 'TN'; @@ -798,18 +843,19 @@ describe('Interchain Token Service', () => { const destAddress = '0x5678'; const gasValue = 90; - for (const type of ['lockUnlock', 'mintBurn', 'liquidityPool']) { + for (const type of ['lockUnlock', 'mintBurn', 'lockUnlockFee', 'liquidityPool']) { it(`Should be able to initiate an interchain token transfer [${type}]`, async () => { const [token, tokenManager, tokenId] = await deployFunctions[type](`Test Token ${type}`, 'TT', 12, amount); + const sendAmount = type === 'lockUnlockFee' ? amount - 10 : amount; const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + [SELECTOR_SEND_TOKEN, tokenId, destAddress, sendAmount], ); const payloadHash = keccak256(payload); let transferToAddress = AddressZero; - if (type === 'lockUnlock') { + if (type === 'lockUnlock' || type === 'lockUnlockFee') { transferToAddress = tokenManager.address; } else if (type === 'liquidityPool') { transferToAddress = liquidityPool.address; @@ -823,7 +869,7 @@ describe('Interchain Token Service', () => { .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, destChain, service.address.toLowerCase(), payloadHash, gasValue, wallet.address) .to.emit(service, 'TokenSent') - .withArgs(tokenId, destChain, destAddress, amount); + .withArgs(tokenId, destChain, destAddress, sendAmount); }); } }); @@ -871,6 +917,23 @@ describe('Interchain Token Service', () => { .withArgs(tokenId, sourceChain, destAddress, amount); }); + it('Should be able to receive lock/unlock with fee on transfer token', async () => { + const [token, tokenManager, tokenId] = await deployFunctions.lockUnlockFee(`Test Token Lock Unlock`, 'TT', 12, amount + 10); + (await await token.transfer(tokenManager.address, amount + 10)).wait(); + + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'uint256'], + [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + ); + const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); + + await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) + .to.emit(token, 'Transfer') + .withArgs(tokenManager.address, destAddress, amount) + .and.to.emit(service, 'TokenReceived') + .withArgs(tokenId, sourceChain, destAddress, amount - 10); + }); + it('Should be able to receive liquidity pool token', async () => { const [token, , tokenId] = await deployFunctions.liquidityPool(`Test Token Liquidity Pool`, 'TTLP', 12, amount); (await await token.transfer(liquidityPool.address, amount)).wait(); @@ -900,18 +963,19 @@ describe('Interchain Token Service', () => { sourceAddress = wallet.address; }); - for (const type of ['lockUnlock', 'mintBurn', 'liquidityPool']) { + for (const type of ['lockUnlock', 'mintBurn', 'lockUnlockFee', 'liquidityPool']) { it(`Should be able to initiate an interchain token transfer [${type}]`, async () => { const [token, tokenManager, tokenId] = await deployFunctions[type](`Test Token ${type}`, 'TT', 12, amount); + const sendAmount = type === 'lockUnlockFee' ? amount - 10 : amount; const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', 'bytes'], - [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, amount, sourceAddress, data], + [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, sendAmount, sourceAddress, data], ); const payloadHash = keccak256(payload); - + let transferToAddress = AddressZero; - if (type === 'lockUnlock') { + if (type === 'lockUnlock' || type === 'lockUnlockFee') { transferToAddress = tokenManager.address; } else if (type === 'liquidityPool') { transferToAddress = liquidityPool.address; @@ -925,7 +989,7 @@ describe('Interchain Token Service', () => { .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, destChain, service.address.toLowerCase(), payloadHash, gasValue, wallet.address) .to.emit(service, 'TokenSentWithData') - .withArgs(tokenId, destChain, destAddress, amount, sourceAddress, data); + .withArgs(tokenId, destChain, destAddress, sendAmount, sourceAddress, data); }); } }); @@ -992,6 +1056,31 @@ describe('Interchain Token Service', () => { expect(await executable.lastMessage()).to.equal(msg); }); + it('Should be able to receive lock/unlock with fee on transfer token', async () => { + const [token, tokenManager, tokenId] = await deployFunctions.lockUnlockFee(`Test Token Lock Unlock`, 'TT', 12, amount + 10); + (await await token.transfer(tokenManager.address, amount + 10)).wait(); + const msg = `lock/unlock`; + const data = defaultAbiCoder.encode(['address', 'string'], [wallet.address, msg]); + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', 'bytes'], + [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, amount, sourceAddressForService, data], + ); + const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); + + await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) + .to.emit(token, 'Transfer') + .withArgs(tokenManager.address, destAddress, amount) + .to.emit(token, 'Transfer') + .withArgs(destAddress, wallet.address, amount - 10) + .and.to.emit(service, 'TokenReceivedWithData') + .withArgs(tokenId, sourceChain, destAddress, amount - 10, sourceAddressForService, data) + .and.to.emit(executable, 'MessageReceived') + .withArgs(sourceChain, sourceAddressForService, wallet.address, msg, tokenId, amount - 10); + + expect(await executable.lastMessage()).to.equal(msg); + }); + + it('Should be able to receive liquidity pool token', async () => { const [token, , tokenId] = await deployFunctions.liquidityPool(`Test Token Liquidity Pool`, 'TTLP', 12, amount); (await await token.transfer(liquidityPool.address, amount)).wait(); From 9e43b1ce445820d358b250b40034354dc38fed8c Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 28 Jul 2023 15:34:03 +0300 Subject: [PATCH 26/54] fixed the liquidity pool check. --- .../implementations/TokenManagerLiquidityPool.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol index 0b5ece3d..f3493d11 100644 --- a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol +++ b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol @@ -101,6 +101,10 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage { SafeTokenTransferFrom.safeTransferFrom(token, liquidityPool(), to, amount); - return IERC20(token).balanceOf(to) - balance; + uint256 diff = IERC20(token).balanceOf(to) - balance; + if (diff < amount) { + amount = diff; + } + return amount; } } From 1e2aeb376941675147b21fddebda653e33104005 Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 28 Jul 2023 15:40:28 +0300 Subject: [PATCH 27/54] fix a duplication bug --- .../RemoteAddressValidator.sol | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/contracts/remote-address-validator/RemoteAddressValidator.sol b/contracts/remote-address-validator/RemoteAddressValidator.sol index 3fa303e3..dfa760c6 100644 --- a/contracts/remote-address-validator/RemoteAddressValidator.sol +++ b/contracts/remote-address-validator/RemoteAddressValidator.sol @@ -47,7 +47,7 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { p1 := mload(add(interchainTokenServiceAddressString, 32)) p2 := mload(add(interchainTokenServiceAddressString, 64)) } - + interchainTokenServiceAddress1 = p1; interchainTokenServiceAddress2 = p2; } @@ -86,16 +86,6 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { return s; } - function _interchainTokenServiceAddressString() internal view returns (string memory interchainTokenServiceAddressString) { - interchainTokenServiceAddressString = new string(42); - uint256 p1 = interchainTokenServiceAddress1; - uint256 p2 = interchainTokenServiceAddress2; - assembly { - mstore(add(interchainTokenServiceAddressString, 32), p1) - mstore(add(interchainTokenServiceAddressString, 64), p2) - } - } - /** * @dev Return the interchain token service address as a string by constructing it from the two immutable variables caching it */ From 6c62270178b83e5d8b6090e7f6bcfc72b41c07ea Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 28 Jul 2023 15:40:45 +0300 Subject: [PATCH 28/54] lint --- contracts/token-implementations/StandardizedToken.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token-implementations/StandardizedToken.sol b/contracts/token-implementations/StandardizedToken.sol index adfcd4db..8d291a00 100644 --- a/contracts/token-implementations/StandardizedToken.sol +++ b/contracts/token-implementations/StandardizedToken.sol @@ -30,7 +30,7 @@ contract StandardizedToken is IERC20BurnableMintable, InterchainToken, ERC20Perm if (msg.sender != tokenManager_) { if (msg.sender != distributor()) revert NotDistributor(); } - + _; } From 30c1e539b148fb838586a255692d221e9f8bd48d Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 28 Jul 2023 15:50:15 +0300 Subject: [PATCH 29/54] added some more tests --- test/tokenService.js | 67 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/test/tokenService.js b/test/tokenService.js index 1837f683..bfee7fff 100644 --- a/test/tokenService.js +++ b/test/tokenService.js @@ -1113,19 +1113,19 @@ describe('Interchain Token Service', () => { const gasValue = 90; const metadata = '0x'; - for (const type of ['lockUnlock', 'mintBurn', 'liquidityPool']) { + for (const type of ['lockUnlock', 'mintBurn', 'lockUnlockFee', 'liquidityPool']) { it(`Should be able to initiate an interchain token transfer [${type}]`, async () => { const [token, tokenManager, tokenId] = await deployFunctions[type](`Test Token ${type}`, 'TT', 12, amount, true); - + const sendAmount = type === 'lockUnlockFee' ? amount - 10 : amount; const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + [SELECTOR_SEND_TOKEN, tokenId, destAddress, sendAmount], ); const payloadHash = keccak256(payload); let transferToAddress = AddressZero; - if (type === 'lockUnlock') { + if (type === 'lockUnlock' || type === 'lockUnlockFee') { transferToAddress = tokenManager.address; } else if (type === 'liquidityPool') { transferToAddress = liquidityPool.address; @@ -1139,7 +1139,7 @@ describe('Interchain Token Service', () => { .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, destChain, service.address.toLowerCase(), payloadHash, gasValue, wallet.address) .to.emit(service, 'TokenSent') - .withArgs(tokenId, destChain, destAddress, amount); + .withArgs(tokenId, destChain, destAddress, sendAmount); }); } }); @@ -1156,19 +1156,20 @@ describe('Interchain Token Service', () => { sourceAddress = wallet.address; }); - for (const type of ['lockUnlock', 'mintBurn', 'liquidityPool']) { + for (const type of ['lockUnlock', 'mintBurn', 'lockUnlockFee', 'liquidityPool']) { it(`Should be able to initiate an interchain token transfer [${type}]`, async () => { const [token, tokenManager, tokenId] = await deployFunctions[type](`Test Token ${type}`, 'TT', 12, amount, false); + const sendAmount = type === 'lockUnlockFee' ? amount - 10 : amount; const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', 'bytes'], - [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, amount, sourceAddress, data], + [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, sendAmount, sourceAddress, data], ); const payloadHash = keccak256(payload); let transferToAddress = AddressZero; - if (type === 'lockUnlock') { + if (type === 'lockUnlock' || type === 'lockUnlockFee') { transferToAddress = tokenManager.address; } else if (type === 'liquidityPool') { transferToAddress = liquidityPool.address; @@ -1183,7 +1184,7 @@ describe('Interchain Token Service', () => { .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, destChain, service.address.toLowerCase(), payloadHash, gasValue, wallet.address) .to.emit(service, 'TokenSentWithData') - .withArgs(tokenId, destChain, destAddress, amount, sourceAddress, data); + .withArgs(tokenId, destChain, destAddress, sendAmount, sourceAddress, data); }); } }); @@ -1282,6 +1283,27 @@ describe('Interchain Token Service', () => { .withArgs(tokenId, destAddress, amount, commandId, wallet.address); }); + it('Should be able to receive lock/unlock with fee on transfer token', async () => { + const [token, tokenManager, tokenId] = await deployFunctions.lockUnlockFee(`Test Token Lock Unlock`, 'TT', 12, 2 * amount + 10); + await (await token.transfer(tokenManager.address, amount + 10)).wait(); + await (await token.approve(service.address, amount)).wait(); + + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'uint256'], + [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + ); + const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); + + await (await service.expressReceiveToken(tokenId, destAddress, amount, commandId)).wait(); + + await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) + .to.emit(token, 'Transfer') + .withArgs(tokenManager.address, wallet.address, amount) + .and.to.emit(service, 'ExpressExecutionFulfilled') + .withArgs(tokenId, destAddress, amount, commandId, wallet.address); + }); + + it('Should be able to receive liquidity pool token', async () => { const [token, , tokenId] = await deployFunctions.liquidityPool(`Test Token Liquidity Pool`, 'TTLP', 12, amount * 2); await (await token.transfer(liquidityPool.address, amount)).wait(); @@ -1385,6 +1407,33 @@ describe('Interchain Token Service', () => { expect(await executable.lastMessage()).to.equal(msg); }); + + it('Should be able to receive lock/unlock with fee on transfer token', async () => { + const [token, tokenManager, tokenId] = await deployFunctions.lockUnlockFee(`Test Token Lock Unlock`, 'TT', 12, amount * 2 + 10); + await (await token.transfer(tokenManager.address, amount + 10)).wait(); + await (await token.approve(service.address, amount)).wait(); + + const msg = `lock/unlock`; + const data = defaultAbiCoder.encode(['address', 'string'], [wallet.address, msg]); + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', 'bytes'], + [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, amount, sourceAddressForService, data], + ); + const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); + + await expect( + service.expressReceiveTokenWithData( + tokenId, + sourceChain, + sourceAddressForService, + destAddress, + amount, + data, + commandId, + ) + ).to.be.reverted; + }); + it('Should be able to receive liquidity pool token', async () => { const [token, , tokenId] = await deployFunctions.liquidityPool(`Test Token Liquidity Pool`, 'TTLP', 12, amount * 2); (await await token.transfer(liquidityPool.address, amount)).wait(); From 51b70c17c44307ce498c51fa53f96e25c567696d Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 28 Jul 2023 15:52:37 +0300 Subject: [PATCH 30/54] Added more tests --- .../InterchainTokenService.sol | 2 ++ .../interfaces/IInterchainTokenService.sol | 2 +- test/tokenService.js | 23 +++++++------------ 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 03e4356b..d4788983 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -435,6 +435,7 @@ contract InterchainTokenService is /** * @notice Uses the caller's tokens to fullfill a sendCall ahead of time. Use this only if you have detected an outgoing * sendToken that matches the parameters passed here. + * @dev This is not to be used with fee on transfer tokens as it will incur losses for the express caller. * @param tokenId the tokenId of the TokenManager used. * @param destinationAddress the destinationAddress for the sendToken. * @param amount the amount of token to give. @@ -455,6 +456,7 @@ contract InterchainTokenService is /** * @notice Uses the caller's tokens to fullfill a callContractWithInterchainToken ahead of time. Use this only if you have * detected an outgoing sendToken that matches the parameters passed here. + * @dev This is not to be used with fee on transfer tokens as it will incur losses for the express caller and it will pass an incorrect amount to the contract. * @param tokenId the tokenId of the TokenManager used. * @param sourceChain the name of the chain where the call came from. * @param sourceAddress the caller of callContractWithInterchainToken. diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index a3d6d966..ef1a2b64 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -89,7 +89,7 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx * @return standardizedTokenDeployerAddress The address of the standardized token deployer contract. */ function standardizedTokenDeployer() external view returns (address standardizedTokenDeployerAddress); - + /** * @notice Returns the address of the token manager associated with the given tokenId. * @param tokenId The tokenId of the token manager. diff --git a/test/tokenService.js b/test/tokenService.js index bfee7fff..9b856885 100644 --- a/test/tokenService.js +++ b/test/tokenService.js @@ -594,7 +594,12 @@ describe('Interchain Token Service', () => { const salt = getRandomBytes32(); const tokenId = await service.getCustomTokenId(wallet.address, salt); const tokenManagerAddress = await service.getTokenManagerAddress(tokenId); - const token = await deployContract(wallet, 'FeeOnTransferTokenTest', [tokenName, tokenSymbol, tokenDecimals, tokenManagerAddress]); + const token = await deployContract(wallet, 'FeeOnTransferTokenTest', [ + tokenName, + tokenSymbol, + tokenDecimals, + tokenManagerAddress, + ]); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); const tx = service.deployCustomTokenManager(salt, LOCK_UNLOCK_FEE_ON_TRANSFER, params); @@ -606,7 +611,6 @@ describe('Interchain Token Service', () => { expect(await tokenManager.operator()).to.equal(wallet.address); }); - it('Should deploy a liquidity pool token manager', async () => { const tokenName = 'Token Name'; const tokenSymbol = 'TN'; @@ -972,7 +976,7 @@ describe('Interchain Token Service', () => { [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, sendAmount, sourceAddress, data], ); const payloadHash = keccak256(payload); - + let transferToAddress = AddressZero; if (type === 'lockUnlock' || type === 'lockUnlockFee') { @@ -1080,7 +1084,6 @@ describe('Interchain Token Service', () => { expect(await executable.lastMessage()).to.equal(msg); }); - it('Should be able to receive liquidity pool token', async () => { const [token, , tokenId] = await deployFunctions.liquidityPool(`Test Token Liquidity Pool`, 'TTLP', 12, amount); (await await token.transfer(liquidityPool.address, amount)).wait(); @@ -1303,7 +1306,6 @@ describe('Interchain Token Service', () => { .withArgs(tokenId, destAddress, amount, commandId, wallet.address); }); - it('Should be able to receive liquidity pool token', async () => { const [token, , tokenId] = await deployFunctions.liquidityPool(`Test Token Liquidity Pool`, 'TTLP', 12, amount * 2); await (await token.transfer(liquidityPool.address, amount)).wait(); @@ -1407,7 +1409,6 @@ describe('Interchain Token Service', () => { expect(await executable.lastMessage()).to.equal(msg); }); - it('Should be able to receive lock/unlock with fee on transfer token', async () => { const [token, tokenManager, tokenId] = await deployFunctions.lockUnlockFee(`Test Token Lock Unlock`, 'TT', 12, amount * 2 + 10); await (await token.transfer(tokenManager.address, amount + 10)).wait(); @@ -1422,15 +1423,7 @@ describe('Interchain Token Service', () => { const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); await expect( - service.expressReceiveTokenWithData( - tokenId, - sourceChain, - sourceAddressForService, - destAddress, - amount, - data, - commandId, - ) + service.expressReceiveTokenWithData(tokenId, sourceChain, sourceAddressForService, destAddress, amount, data, commandId), ).to.be.reverted; }); From 3e6d85ff94b792d7b3f4fd486d4be4e93f4f8ad8 Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 28 Jul 2023 16:03:28 +0300 Subject: [PATCH 31/54] Added proper re-entrancy protection for fee on transfer token managers. --- contracts/interfaces/INoReEntrancy.sol | 12 +++++ .../TokenManagerLiquidityPool.sol | 19 +++----- .../TokenManagerLockUnlockFeeOnTransfer.sol | 7 +-- contracts/utils/NoReEntrancy.sol | 46 +++++++++++++++++++ 4 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 contracts/interfaces/INoReEntrancy.sol create mode 100644 contracts/utils/NoReEntrancy.sol diff --git a/contracts/interfaces/INoReEntrancy.sol b/contracts/interfaces/INoReEntrancy.sol new file mode 100644 index 00000000..9e0dd68d --- /dev/null +++ b/contracts/interfaces/INoReEntrancy.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @title Pausable + * @notice This contract provides a mechanism to halt the execution of specific functions + * if a pause condition is activated. + */ +interface INoReEntrancy { + error ReEntrancy(); +} diff --git a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol index f3493d11..a3468599 100644 --- a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol +++ b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import { TokenManagerAddressStorage } from './TokenManagerAddressStorage.sol'; +import { NoReEntrancy } from '../../utils/NoReEntrancy.sol'; import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; import { SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/SafeTransfer.sol'; @@ -14,7 +15,7 @@ import { SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/c * @dev This contract extends TokenManagerAddressStorage and provides implementation for its abstract methods. * It uses the Axelar SDK to safely transfer tokens. */ -contract TokenManagerLiquidityPool is TokenManagerAddressStorage { +contract TokenManagerLiquidityPool is TokenManagerAddressStorage, NoReEntrancy { // uint256(keccak256('liquidity-pool-slot')) - 1 uint256 internal constant LIQUIDITY_POOL_SLOT = 0x8e02741a3381812d092c5689c9fc701c5185c1742fdf7954c4c4472be4cc4807; @@ -74,7 +75,7 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage { * @param amount The amount of tokens to transfer * @return uint The actual amount of tokens transferred. This allows support for fee-on-transfer tokens. */ - function _takeToken(address from, uint256 amount) internal override returns (uint256) { + function _takeToken(address from, uint256 amount) internal override noReEntrancy returns (uint256) { IERC20 token = IERC20(tokenAddress()); address liquidityPool_ = liquidityPool(); uint256 balance = token.balanceOf(liquidityPool_); @@ -82,11 +83,7 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage { SafeTokenTransferFrom.safeTransferFrom(token, from, liquidityPool_, amount); // Note: This allows support for fee-on-transfer tokens - uint256 diff = IERC20(token).balanceOf(liquidityPool_) - balance; - if (diff < amount) { - amount = diff; - } - return amount; + return IERC20(token).balanceOf(liquidityPool_) - balance; } /** @@ -95,16 +92,12 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage { * @param amount The amount of tokens to transfer * @return uint The actual amount of tokens transferred */ - function _giveToken(address to, uint256 amount) internal override returns (uint256) { + function _giveToken(address to, uint256 amount) internal override noReEntrancy returns (uint256) { IERC20 token = IERC20(tokenAddress()); uint256 balance = IERC20(token).balanceOf(to); SafeTokenTransferFrom.safeTransferFrom(token, liquidityPool(), to, amount); - uint256 diff = IERC20(token).balanceOf(to) - balance; - if (diff < amount) { - amount = diff; - } - return amount; + return IERC20(token).balanceOf(to) - balance; } } diff --git a/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol b/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol index 447d3865..7d4d26cd 100644 --- a/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol +++ b/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import { TokenManagerAddressStorage } from './TokenManagerAddressStorage.sol'; +import { NoReEntrancy } from '../../utils/NoReEntrancy.sol'; import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; import { SafeTokenTransferFrom, SafeTokenTransfer } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/SafeTransfer.sol'; @@ -13,7 +14,7 @@ import { SafeTokenTransferFrom, SafeTokenTransfer } from '@axelar-network/axelar * @dev This contract extends TokenManagerAddressStorage and provides implementation for its abstract methods. * It uses the Axelar SDK to safely transfer tokens. */ -contract TokenManagerLockUnlockFee is TokenManagerAddressStorage { +contract TokenManagerLockUnlockFee is TokenManagerAddressStorage, NoReEntrancy{ /** * @dev Constructs an instance of TokenManagerLockUnlock. Calls the constructor * of TokenManagerAddressStorage which calls the constructor of TokenManager. @@ -41,7 +42,7 @@ contract TokenManagerLockUnlockFee is TokenManagerAddressStorage { * @param amount The amount of tokens to transfer * @return uint The actual amount of tokens transferred. This allows support for fee-on-transfer tokens. */ - function _takeToken(address from, uint256 amount) internal override returns (uint256) { + function _takeToken(address from, uint256 amount) internal override noReEntrancy returns (uint256) { IERC20 token = IERC20(tokenAddress()); uint256 balance = token.balanceOf(address(this)); @@ -60,7 +61,7 @@ contract TokenManagerLockUnlockFee is TokenManagerAddressStorage { * @param amount The amount of tokens to transfer * @return uint The actual amount of tokens transferred */ - function _giveToken(address to, uint256 amount) internal override returns (uint256) { + function _giveToken(address to, uint256 amount) internal override noReEntrancy returns (uint256) { IERC20 token = IERC20(tokenAddress()); uint256 balance = IERC20(token).balanceOf(to); diff --git a/contracts/utils/NoReEntrancy.sol b/contracts/utils/NoReEntrancy.sol new file mode 100644 index 00000000..0a1e1225 --- /dev/null +++ b/contracts/utils/NoReEntrancy.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { INoReEntrancy } from '../interfaces/INoReEntrancy.sol'; + +/** + * @title Pausable + * @notice This contract provides a mechanism to halt the execution of specific functions + * if a pause condition is activated. + */ +contract NoReEntrancy is INoReEntrancy { + // uint256(keccak256('entered')) - 1 + uint256 internal constant ENTERED_SLOT = 0x01f33dd720a8dea3c4220dc5074a2239fb442c4c775306a696f97a7c54f785fc; + + /** + * @notice A modifier that throws a Paused custom error if the contract is paused + * @dev This modifier should be used with functions that can be paused + */ + modifier noReEntrancy() { + if (_hasEntered()) revert ReEntrancy(); + _setEntered(true); + _; + _setEntered(false); + } + + /** + * @notice Check if the contract is already executing. + * @return entered A boolean representing the entered status. True if already executing, false otherwise. + */ + function _hasEntered() internal view returns (bool entered) { + assembly { + entered := sload(ENTERED_SLOT) + } + } + + /** + * @notice Sets the entered status of the contract + * @param entered A boolean representing the entered status. True if already executing, false otherwise. + */ + function _setEntered(bool entered) internal { + assembly { + sstore(ENTERED_SLOT, entered) + } + } +} From d4075b84bf8145a37c3d47070e3183ffaa9cca75 Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 28 Jul 2023 18:37:02 +0300 Subject: [PATCH 32/54] change to tx.origin for refunds --- .../InterchainTokenService.sol | 15 +++++++-------- .../TokenManagerLockUnlockFeeOnTransfer.sol | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index d4788983..1a8d412a 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -511,7 +511,7 @@ contract InterchainTokenService is bytes memory payload; if (metadata.length < 4) { payload = abi.encode(SELECTOR_SEND_TOKEN, tokenId, destinationAddress, amount); - _callContract(destinationChain, payload, msg.value, sourceAddress); + _callContract(destinationChain, payload, msg.value); emit TokenSent(tokenId, destinationChain, destinationAddress, amount); return; } @@ -519,7 +519,7 @@ contract InterchainTokenService is (version, metadata) = _decodeMetadata(metadata); if (version > 0) revert InvalidMetadataVersion(version); payload = abi.encode(SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destinationAddress, amount, sourceAddress.toBytes(), metadata); - _callContract(destinationChain, payload, msg.value, sourceAddress); + _callContract(destinationChain, payload, msg.value); emit TokenSentWithData(tokenId, destinationChain, destinationAddress, amount, sourceAddress, metadata); } @@ -717,17 +717,16 @@ contract InterchainTokenService is * @param destinationChain The target chain where the contract will be called * @param payload The data payload for the transaction * @param gasValue The amount of gas to be paid for the transaction - * @param refundTo The address where the unused gas amount should be refunded to */ - function _callContract(string calldata destinationChain, bytes memory payload, uint256 gasValue, address refundTo) internal { + function _callContract(string calldata destinationChain, bytes memory payload, uint256 gasValue) internal { string memory destinationAddress = remoteAddressValidator.getRemoteAddress(destinationChain); if (gasValue > 0) { gasService.payNativeGasForContractCall{ value: gasValue }( address(this), destinationChain, destinationAddress, - payload, - refundTo + payload, // solhint-disable-next-line avoid-tx-origin + tx.origin ); } gateway.callContract(destinationChain, destinationAddress, payload); @@ -756,7 +755,7 @@ contract InterchainTokenService is bytes memory params ) internal { bytes memory payload = abi.encode(SELECTOR_DEPLOY_TOKEN_MANAGER, tokenId, tokenManagerType, params); - _callContract(destinationChain, payload, gasValue, msg.sender); + _callContract(destinationChain, payload, gasValue); emit RemoteTokenManagerDeploymentInitialized(tokenId, destinationChain, gasValue, tokenManagerType, params); } @@ -793,7 +792,7 @@ contract InterchainTokenService is mintAmount, operator ); - _callContract(destinationChain, payload, gasValue, msg.sender); + _callContract(destinationChain, payload, gasValue); emit RemoteStandardizedTokenAndManagerDeploymentInitialized( tokenId, name, diff --git a/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol b/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol index 7d4d26cd..4cf3c5dc 100644 --- a/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol +++ b/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol @@ -14,7 +14,7 @@ import { SafeTokenTransferFrom, SafeTokenTransfer } from '@axelar-network/axelar * @dev This contract extends TokenManagerAddressStorage and provides implementation for its abstract methods. * It uses the Axelar SDK to safely transfer tokens. */ -contract TokenManagerLockUnlockFee is TokenManagerAddressStorage, NoReEntrancy{ +contract TokenManagerLockUnlockFee is TokenManagerAddressStorage, NoReEntrancy { /** * @dev Constructs an instance of TokenManagerLockUnlock. Calls the constructor * of TokenManagerAddressStorage which calls the constructor of TokenManager. From 5d9d8ce9f6d6de1ad8785148dc253e7af8cb4844 Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 28 Jul 2023 18:52:32 +0300 Subject: [PATCH 33/54] Added support for more kinds of addresses. --- contracts/remote-address-validator/RemoteAddressValidator.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/remote-address-validator/RemoteAddressValidator.sol b/contracts/remote-address-validator/RemoteAddressValidator.sol index dfa760c6..6b93f394 100644 --- a/contracts/remote-address-validator/RemoteAddressValidator.sol +++ b/contracts/remote-address-validator/RemoteAddressValidator.sol @@ -80,7 +80,7 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { for (uint256 i; i < length; i++) { uint8 b = uint8(bytes(s)[i]); - if ((b >= 65) && (b <= 70)) bytes(s)[i] = bytes1(b + uint8(32)); + if ((b >= 65) && (b <= 90)) bytes(s)[i] = bytes1(b + uint8(32)); } return s; From 1b35cbf7e3114e233805edcfd486396d3b1fe89d Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 28 Jul 2023 19:13:05 +0300 Subject: [PATCH 34/54] some minor gas opts --- .../remote-address-validator/RemoteAddressValidator.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/remote-address-validator/RemoteAddressValidator.sol b/contracts/remote-address-validator/RemoteAddressValidator.sol index 6b93f394..3ad09778 100644 --- a/contracts/remote-address-validator/RemoteAddressValidator.sol +++ b/contracts/remote-address-validator/RemoteAddressValidator.sol @@ -77,9 +77,9 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { */ function _lowerCase(string memory s) internal pure returns (string memory) { uint256 length = bytes(s).length; - - for (uint256 i; i < length; i++) { - uint8 b = uint8(bytes(s)[i]); + uint b; + for (uint256 i; i < length; ++i) { + b = uint8(bytes(s)[i]); if ((b >= 65) && (b <= 90)) bytes(s)[i] = bytes1(b + uint8(32)); } From 412dce9c7f1b7f10bd07b57e831dbba57a6ccf64 Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 28 Jul 2023 20:35:37 +0300 Subject: [PATCH 35/54] some more gas optimizations. --- .../remote-address-validator/RemoteAddressValidator.sol | 9 +++++---- contracts/utils/Multicall.sol | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/contracts/remote-address-validator/RemoteAddressValidator.sol b/contracts/remote-address-validator/RemoteAddressValidator.sol index 3ad09778..08507209 100644 --- a/contracts/remote-address-validator/RemoteAddressValidator.sol +++ b/contracts/remote-address-validator/RemoteAddressValidator.sol @@ -77,7 +77,7 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { */ function _lowerCase(string memory s) internal pure returns (string memory) { uint256 length = bytes(s).length; - uint b; + uint8 b; for (uint256 i; i < length; ++i) { b = uint8(bytes(s)[i]); if ((b >= 65) && (b <= 90)) bytes(s)[i] = bytes1(b + uint8(32)); @@ -152,9 +152,9 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { */ function addGatewaySupportedChains(string[] calldata chainNames) external onlyOwner { uint256 length = chainNames.length; - + string calldata chainName; for (uint256 i; i < length; ++i) { - string calldata chainName = chainNames[i]; + chainName = chainNames[i]; supportedByGateway[chainName] = true; emit GatewaySupportedChainAdded(chainName); @@ -167,9 +167,10 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { */ function removeGatewaySupportedChains(string[] calldata chainNames) external onlyOwner { uint256 length = chainNames.length; + string calldata chainName; for (uint256 i; i < length; ++i) { - string calldata chainName = chainNames[i]; + chainName = chainNames[i]; supportedByGateway[chainName] = false; emit GatewaySupportedChainRemoved(chainName); diff --git a/contracts/utils/Multicall.sol b/contracts/utils/Multicall.sol index 4c94e407..50914d39 100644 --- a/contracts/utils/Multicall.sol +++ b/contracts/utils/Multicall.sol @@ -21,8 +21,10 @@ contract Multicall is IMulticall { */ function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) { results = new bytes[](data.length); + bool success; + bytes memory result; for (uint256 i = 0; i < data.length; ++i) { - (bool success, bytes memory result) = address(this).delegatecall(data[i]); + (success, result) = address(this).delegatecall(data[i]); if (!success) { revert(string(result)); From 0a8519fbe6fc8633387d2ac98948f4c4618343e3 Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 28 Jul 2023 22:23:24 +0300 Subject: [PATCH 36/54] Added a getter for chain name to the remote address validator. --- .../InterchainTokenService.sol | 8 ++---- .../interfaces/IRemoteAddressValidator.sol | 5 ++++ .../RemoteAddressValidator.sol | 28 +++++++++++-------- scripts/deploy.js | 7 ++--- test/RemoteAddressValidator.js | 18 ++++++++++-- 5 files changed, 41 insertions(+), 25 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 1a8d412a..f37aab94 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -57,7 +57,6 @@ contract InterchainTokenService is address public immutable tokenManagerDeployer; address public immutable standardizedTokenDeployer; bytes32 public immutable chainNameHash; - bytes32 internal immutable chainName; bytes32 internal constant PREFIX_CUSTOM_TOKEN_ID = keccak256('its-custom-token-id'); bytes32 internal constant PREFIX_STANDARDIZED_TOKEN_ID = keccak256('its-standardized-token-id'); @@ -78,7 +77,6 @@ contract InterchainTokenService is * @param gasService_ the address of the AxelarGasService. * @param remoteAddressValidator_ the address of the RemoteAddressValidator. * @param tokenManagerImplementations this need to have exactly 3 implementations in the following order: Lock/Unlock, mint/burn and then liquidity pool. - * @param chainName_ the name of the current chain. */ constructor( address tokenManagerDeployer_, @@ -86,8 +84,7 @@ contract InterchainTokenService is address gateway_, address gasService_, address remoteAddressValidator_, - address[] memory tokenManagerImplementations, - string memory chainName_ + address[] memory tokenManagerImplementations ) AxelarExecutable(gateway_) { if ( remoteAddressValidator_ == address(0) || @@ -109,8 +106,7 @@ contract InterchainTokenService is TokenManagerType.LOCK_UNLOCK_FEE_ON_TRANSFER ); implementationLiquidityPool = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.LIQUIDITY_POOL); - - chainName = chainName_.toBytes32(); + string memory chainName_ = remoteAddressValidator.chainName(); chainNameHash = keccak256(bytes(chainName_)); } diff --git a/contracts/interfaces/IRemoteAddressValidator.sol b/contracts/interfaces/IRemoteAddressValidator.sol index 9f54bb13..38846e2b 100644 --- a/contracts/interfaces/IRemoteAddressValidator.sol +++ b/contracts/interfaces/IRemoteAddressValidator.sol @@ -16,6 +16,11 @@ interface IRemoteAddressValidator { event GatewaySupportedChainAdded(string chain); event GatewaySupportedChainRemoved(string chain); + /** + * @notice Returns the interchain token address + */ + function chainName() external view returns (string memory); + /** * @notice Returns the interchain token address */ diff --git a/contracts/remote-address-validator/RemoteAddressValidator.sol b/contracts/remote-address-validator/RemoteAddressValidator.sol index 08507209..d3ce7648 100644 --- a/contracts/remote-address-validator/RemoteAddressValidator.sol +++ b/contracts/remote-address-validator/RemoteAddressValidator.sol @@ -16,6 +16,7 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { mapping(string => bytes32) public remoteAddressHashes; mapping(string => string) public remoteAddresses; mapping(string => bool) public supportedByGateway; + string public chainName; address public immutable interchainTokenServiceAddress; bytes32 public immutable interchainTokenServiceAddressHash; @@ -32,7 +33,7 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { * @dev Constructs the RemoteAddressValidator contract, both array parameters must be equal in length * @param _interchainTokenServiceAddress Address of the interchain token service */ - constructor(address _interchainTokenServiceAddress) { + constructor(address _interchainTokenServiceAddress, string memory chainName_) { if (_interchainTokenServiceAddress == address(0)) revert ZeroAddress(); interchainTokenServiceAddress = _interchainTokenServiceAddress; @@ -50,6 +51,9 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { interchainTokenServiceAddress1 = p1; interchainTokenServiceAddress2 = p2; + + if (bytes(chainName_).length == 0) revert ZeroStringLength(); + chainName = chainName_; } /** @@ -152,12 +156,12 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { */ function addGatewaySupportedChains(string[] calldata chainNames) external onlyOwner { uint256 length = chainNames.length; - string calldata chainName; + string calldata chainName_; for (uint256 i; i < length; ++i) { - chainName = chainNames[i]; - supportedByGateway[chainName] = true; + chainName_ = chainNames[i]; + supportedByGateway[chainName_] = true; - emit GatewaySupportedChainAdded(chainName); + emit GatewaySupportedChainAdded(chainName_); } } @@ -167,23 +171,23 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { */ function removeGatewaySupportedChains(string[] calldata chainNames) external onlyOwner { uint256 length = chainNames.length; - string calldata chainName; + string calldata chainName_; for (uint256 i; i < length; ++i) { - chainName = chainNames[i]; - supportedByGateway[chainName] = false; + chainName_ = chainNames[i]; + supportedByGateway[chainName_] = false; - emit GatewaySupportedChainRemoved(chainName); + emit GatewaySupportedChainRemoved(chainName_); } } /** * @dev Fetches the interchain token service address for the specified chain - * @param chainName Name of the chain + * @param chainName_ Name of the chain * @return remoteAddress Interchain token service address for the specified chain */ - function getRemoteAddress(string calldata chainName) external view returns (string memory remoteAddress) { - remoteAddress = remoteAddresses[chainName]; + function getRemoteAddress(string calldata chainName_) external view returns (string memory remoteAddress) { + remoteAddress = remoteAddresses[chainName_]; if (bytes(remoteAddress).length == 0) { remoteAddress = _interchainTokenServiceAddressString(); diff --git a/scripts/deploy.js b/scripts/deploy.js index 0d66964b..2b52705a 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -11,8 +11,8 @@ async function deployContract(wallet, contractName, args = []) { return contract; } -async function deployRemoteAddressValidator(wallet, interchainTokenServiceAddress) { - const remoteAddressValidatorImpl = await deployContract(wallet, 'RemoteAddressValidator', [interchainTokenServiceAddress]); +async function deployRemoteAddressValidator(wallet, interchainTokenServiceAddress, chainName) { + const remoteAddressValidatorImpl = await deployContract(wallet, 'RemoteAddressValidator', [interchainTokenServiceAddress, chainName]); const params = defaultAbiCoder.encode(['string[]', 'string[]'], [[], []]); const remoteAddressValidatorProxy = await deployContract(wallet, 'RemoteAddressValidatorProxy', [ @@ -54,7 +54,6 @@ async function deployInterchainTokenService( gasServiceAddress, remoteAddressValidatorAddress, tokenManagerImplementations, - chainName, ]); const proxy = await deployCreate3Contract(create3DeployerAddress, wallet, InterchainTokenServiceProxy, deploymentKey, [ implementation.address, @@ -84,7 +83,7 @@ async function deployAll(wallet, chainName, deploymentKey = 'interchainTokenServ const standardizedToken = await deployContract(wallet, 'StandardizedToken'); const standardizedTokenDeployer = await deployContract(wallet, 'StandardizedTokenDeployer', [standardizedToken.address]); const interchainTokenServiceAddress = await getCreate3Address(create3Deployer.address, wallet, deploymentKey); - const remoteAddressValidator = await deployRemoteAddressValidator(wallet, interchainTokenServiceAddress); + const remoteAddressValidator = await deployRemoteAddressValidator(wallet, interchainTokenServiceAddress, chainName); const tokenManagerImplementations = await deployTokenManagerImplementations(wallet, interchainTokenServiceAddress); const service = await deployInterchainTokenService( diff --git a/test/RemoteAddressValidator.js b/test/RemoteAddressValidator.js index 209f8732..a0b1c04a 100644 --- a/test/RemoteAddressValidator.js +++ b/test/RemoteAddressValidator.js @@ -15,24 +15,36 @@ describe('RemoteAddressValidator', () => { const otherRemoteAddress = 'any string as an address'; const otherChain = 'Chain Name'; + const chainName = 'Chain Name'; before(async () => { const wallets = await ethers.getSigners(); ownerWallet = wallets[0]; otherWallet = wallets[1]; interchainTokenServiceAddress = wallets[2].address; - remoteAddressValidator = await deployRemoteAddressValidator(ownerWallet, interchainTokenServiceAddress); + remoteAddressValidator = await deployRemoteAddressValidator(ownerWallet, interchainTokenServiceAddress, chainName); }); it('Should revert on RemoteAddressValidator deployment with invalid interchain token service address', async () => { const remoteAddressValidatorFactory = await ethers.getContractFactory('RemoteAddressValidator'); - await expect(remoteAddressValidatorFactory.deploy(AddressZero)).to.be.revertedWithCustomError( + await expect(remoteAddressValidatorFactory.deploy(AddressZero, chainName)).to.be.revertedWithCustomError( remoteAddressValidator, 'ZeroAddress', ); }); + it('Should revert on RemoteAddressValidator deployment with invalid chain name', async () => { + const remoteAddressValidatorFactory = await ethers.getContractFactory('RemoteAddressValidator'); + await expect(remoteAddressValidatorFactory.deploy(interchainTokenServiceAddress, '')).to.be.revertedWithCustomError( + remoteAddressValidator, + 'ZeroStringLength', + ); + }); + it('Should revert on RemoteAddressValidator deployment with length mismatch between chains and trusted addresses arrays', async () => { - const remoteAddressValidatorImpl = await deployContract(ownerWallet, 'RemoteAddressValidator', [interchainTokenServiceAddress]); + const remoteAddressValidatorImpl = await deployContract(ownerWallet, 'RemoteAddressValidator', [ + interchainTokenServiceAddress, + chainName, + ]); const remoteAddressValidatorProxyFactory = await ethers.getContractFactory('RemoteAddressValidatorProxy'); const params = defaultAbiCoder.encode(['string[]', 'string[]'], [['Chain A'], []]); await expect( From 959e495e809de188e3b7ef8dad32a132b3fea86e Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 28 Jul 2023 22:54:32 +0300 Subject: [PATCH 37/54] moved the tokenManager getter functionality to a separate contract which saves almost a kilobyte of codesize. --- .../InterchainTokenService.sol | 87 ++------------- .../interfaces/IInterchainTokenService.sol | 43 +------- contracts/interfaces/ITokenManagerGetter.sol | 59 ++++++++++ contracts/proxies/TokenManagerProxy.sol | 20 ++-- contracts/utils/TokenManagerDeployer.sol | 2 +- contracts/utils/TokenManagerGetter.sol | 103 ++++++++++++++++++ scripts/deploy.js | 10 +- 7 files changed, 191 insertions(+), 133 deletions(-) create mode 100644 contracts/interfaces/ITokenManagerGetter.sol create mode 100644 contracts/utils/TokenManagerGetter.sol diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index f37aab94..ff8718da 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -48,14 +48,11 @@ contract InterchainTokenService is using AddressBytesUtils for bytes; using AddressBytesUtils for address; - address internal immutable implementationLockUnlock; - address internal immutable implementationMintBurn; - address internal immutable implementationLockUnlockFee; - address internal immutable implementationLiquidityPool; IAxelarGasService public immutable gasService; IRemoteAddressValidator public immutable remoteAddressValidator; address public immutable tokenManagerDeployer; address public immutable standardizedTokenDeployer; + address public immutable tokenManagerGetter; bytes32 public immutable chainNameHash; bytes32 internal constant PREFIX_CUSTOM_TOKEN_ID = keccak256('its-custom-token-id'); @@ -76,7 +73,7 @@ contract InterchainTokenService is * @param gateway_ the address of the AxelarGateway. * @param gasService_ the address of the AxelarGasService. * @param remoteAddressValidator_ the address of the RemoteAddressValidator. - * @param tokenManagerImplementations this need to have exactly 3 implementations in the following order: Lock/Unlock, mint/burn and then liquidity pool. + * @param tokenManagerGetter_ the address of the TokenManagerGetter. */ constructor( address tokenManagerDeployer_, @@ -84,28 +81,21 @@ contract InterchainTokenService is address gateway_, address gasService_, address remoteAddressValidator_, - address[] memory tokenManagerImplementations + address tokenManagerGetter_ ) AxelarExecutable(gateway_) { if ( remoteAddressValidator_ == address(0) || gasService_ == address(0) || tokenManagerDeployer_ == address(0) || - standardizedTokenDeployer_ == address(0) + standardizedTokenDeployer_ == address(0) || + tokenManagerGetter_ == address(0) ) revert ZeroAddress(); remoteAddressValidator = IRemoteAddressValidator(remoteAddressValidator_); gasService = IAxelarGasService(gasService_); tokenManagerDeployer = tokenManagerDeployer_; standardizedTokenDeployer = standardizedTokenDeployer_; - - if (tokenManagerImplementations.length != uint256(type(TokenManagerType).max) + 1) revert LengthMismatch(); - - implementationLockUnlock = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.LOCK_UNLOCK); - implementationMintBurn = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.MINT_BURN); - implementationLockUnlockFee = _sanitizeTokenManagerImplementation( - tokenManagerImplementations, - TokenManagerType.LOCK_UNLOCK_FEE_ON_TRANSFER - ); - implementationLiquidityPool = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.LIQUIDITY_POOL); + tokenManagerGetter = tokenManagerGetter_; + string memory chainName_ = remoteAddressValidator.chainName(); chainNameHash = keccak256(bytes(chainName_)); } @@ -206,60 +196,6 @@ contract InterchainTokenService is tokenId = keccak256(abi.encode(PREFIX_CUSTOM_TOKEN_ID, sender, salt)); } - /** - * @notice Getter function for TokenManager implementations. This will mainly be called by TokenManagerProxies - * to figure out their implementations - * @param tokenManagerType the type of the TokenManager. - * @return tokenManagerAddress the address of the TokenManagerImplementation. - */ - function getImplementation(uint256 tokenManagerType) external view returns (address tokenManagerAddress) { - if (tokenManagerType > uint256(type(TokenManagerType).max)) revert InvalidImplementation(); - if (TokenManagerType(tokenManagerType) == TokenManagerType.LOCK_UNLOCK) { - return implementationLockUnlock; - } else if (TokenManagerType(tokenManagerType) == TokenManagerType.MINT_BURN) { - return implementationMintBurn; - } else if (TokenManagerType(tokenManagerType) == TokenManagerType.LOCK_UNLOCK_FEE_ON_TRANSFER) { - return implementationLockUnlockFee; - } else if (TokenManagerType(tokenManagerType) == TokenManagerType.LIQUIDITY_POOL) { - return implementationLiquidityPool; - } - } - - /** - * @notice Getter function for the parameters of a lock/unlock TokenManager. Mainly to be used by frontends. - * @param operator the operator of the TokenManager. - * @param tokenAddress the token to be managed. - * @return params the resulting params to be passed to custom TokenManager deployments. - */ - function getParamsLockUnlock(bytes memory operator, address tokenAddress) public pure returns (bytes memory params) { - params = abi.encode(operator, tokenAddress); - } - - /** - * @notice Getter function for the parameters of a mint/burn TokenManager. Mainly to be used by frontends. - * @param operator the operator of the TokenManager. - * @param tokenAddress the token to be managed. - * @return params the resulting params to be passed to custom TokenManager deployments. - */ - function getParamsMintBurn(bytes memory operator, address tokenAddress) public pure returns (bytes memory params) { - params = abi.encode(operator, tokenAddress); - } - - /** - * @notice Getter function for the parameters of a liquidity pool TokenManager. Mainly to be used by frontends. - * @param operator the operator of the TokenManager. - * @param tokenAddress the token to be managed. - * @param liquidityPoolAddress the liquidity pool to be used to store the bridged tokens. - * @return params the resulting params to be passed to custom TokenManager deployments. - */ - function getParamsLiquidityPool( - bytes memory operator, - address tokenAddress, - address liquidityPoolAddress - ) public pure returns (bytes memory params) { - params = abi.encode(operator, tokenAddress, liquidityPoolAddress); - } - /** * @notice Getter function for the flow limit of an existing token manager with a give token ID. * @param tokenId the token ID of the TokenManager. @@ -553,15 +489,6 @@ contract InterchainTokenService is _setOperator(params.toAddress()); } - function _sanitizeTokenManagerImplementation( - address[] memory implementaions, - TokenManagerType tokenManagerType - ) internal pure returns (address implementation) { - implementation = implementaions[uint256(tokenManagerType)]; - if (implementation == address(0)) revert ZeroAddress(); - if (ITokenManager(implementation).implementationType() != uint256(tokenManagerType)) revert InvalidTokenManagerImplementation(); - } - /** * @notice Executes operations based on the payload and selector. * @param sourceChain The chain where the transaction originates from diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index ef1a2b64..e431af07 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -15,7 +15,6 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx // more generic error error ZeroAddress(); error LengthMismatch(); - error InvalidTokenManagerImplementation(); error NotRemoteService(); error TokenManagerDoesNotExist(bytes32 tokenId); error NotTokenManager(); @@ -90,6 +89,12 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx */ function standardizedTokenDeployer() external view returns (address standardizedTokenDeployerAddress); + /** + * @notice Returns the address of the token manager getter contract. + * @return tokenManagerGetterAddress The address of the token manager getter contract. + */ + function tokenManagerGetter() external view returns (address tokenManagerGetterAddress); + /** * @notice Returns the address of the token manager associated with the given tokenId. * @param tokenId The tokenId of the token manager. @@ -133,35 +138,6 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx */ function getCustomTokenId(address operator, bytes32 salt) external view returns (bytes32 tokenId); - /** - * @notice Returns the parameters for the lock/unlock operation. - * @param operator The operator address. - * @param tokenAddress The address of the token. - * @return params The parameters for the lock/unlock operation. - */ - function getParamsLockUnlock(bytes memory operator, address tokenAddress) external pure returns (bytes memory params); - - /** - * @notice Returns the parameters for the mint/burn operation. - * @param operator The operator address. - * @param tokenAddress The address of the token. - * @return params The parameters for the mint/burn operation. - */ - function getParamsMintBurn(bytes memory operator, address tokenAddress) external pure returns (bytes memory params); - - /** - * @notice Returns the parameters for the liquidity pool operation. - * @param operator The operator address. - * @param tokenAddress The address of the token. - * @param liquidityPoolAddress The address of the liquidity pool. - * @return params The parameters for the liquidity pool operation. - */ - function getParamsLiquidityPool( - bytes memory operator, - address tokenAddress, - address liquidityPoolAddress - ) external pure returns (bytes memory params); - /** * @notice Registers a canonical token and returns its associated tokenId. * @param tokenAddress The address of the canonical token. @@ -248,13 +224,6 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx uint256 gasValue ) external payable; - /** - * @notice Returns the implementation address for a given token manager type. - * @param tokenManagerType The type of token manager. - * @return tokenManagerAddress The address of the token manager implementation. - */ - function getImplementation(uint256 tokenManagerType) external view returns (address tokenManagerAddress); - /** * @notice Initiates an interchain token transfer. Only callable by TokenManagers * @param tokenId The tokenId of the token to be transmitted. diff --git a/contracts/interfaces/ITokenManagerGetter.sol b/contracts/interfaces/ITokenManagerGetter.sol new file mode 100644 index 00000000..d492da26 --- /dev/null +++ b/contracts/interfaces/ITokenManagerGetter.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { ITokenManagerType } from '../interfaces/ITokenManagerType.sol'; + +/** + * @title ITokenManagerGetter + * @notice Will provide getters for TokenManager implementations and params to help with deployments. + */ +interface ITokenManagerGetter is ITokenManagerType { + error LengthMismatch(); + error ZeroAddress(); + error InvalidTokenManagerImplementation(); + + /** + * @notice Returns the parameters for the lock/unlock operation. + * @param operator The operator address. + * @param tokenAddress The address of the token. + * @return params The parameters for the lock/unlock operation. + */ + function getParamsLockUnlock(bytes memory operator, address tokenAddress) external pure returns (bytes memory params); + + /** + * @notice Returns the parameters for the mint/burn operation. + * @param operator The operator address. + * @param tokenAddress The address of the token. + * @return params The parameters for the mint/burn operation. + */ + function getParamsMintBurn(bytes memory operator, address tokenAddress) external pure returns (bytes memory params); + + /** + * @notice Returns the parameters for the lock/unlock operation. + * @param operator The operator address. + * @param tokenAddress The address of the token. + * @return params The parameters for the lock/unlock operation. + */ + function getParamsLockUnlockFee(bytes memory operator, address tokenAddress) external pure returns (bytes memory params); + + /** + * @notice Returns the parameters for the liquidity pool operation. + * @param operator The operator address. + * @param tokenAddress The address of the token. + * @param liquidityPoolAddress The address of the liquidity pool. + * @return params The parameters for the liquidity pool operation. + */ + function getParamsLiquidityPool( + bytes memory operator, + address tokenAddress, + address liquidityPoolAddress + ) external pure returns (bytes memory params); + + /** + * @notice Returns the implementation address for a given token manager type. + * @param tokenManagerType The type of token manager. + * @return tokenManagerAddress The address of the token manager implementation. + */ + function getImplementation(uint256 tokenManagerType) external view returns (address tokenManagerAddress); +} diff --git a/contracts/proxies/TokenManagerProxy.sol b/contracts/proxies/TokenManagerProxy.sol index 25163f31..1e2376be 100644 --- a/contracts/proxies/TokenManagerProxy.sol +++ b/contracts/proxies/TokenManagerProxy.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import { IInterchainTokenService } from '../interfaces/IInterchainTokenService.sol'; import { ITokenManagerProxy } from '../interfaces/ITokenManagerProxy.sol'; +import { ITokenManagerGetter } from '../interfaces/ITokenManagerGetter.sol'; /** * @title TokenManagerProxy @@ -11,22 +12,23 @@ import { ITokenManagerProxy } from '../interfaces/ITokenManagerProxy.sol'; * inherits from FixedProxy from the gmp sdk repo */ contract TokenManagerProxy is ITokenManagerProxy { - IInterchainTokenService public immutable interchainTokenServiceAddress; + ITokenManagerGetter internal immutable tokenManagerGetter; uint256 public immutable implementationType; bytes32 public immutable tokenId; /** * @dev Constructs the TokenManagerProxy contract. - * @param interchainTokenServiceAddress_ The address of the interchain token service * @param implementationType_ The token manager type * @param tokenId_ The identifier for the token * @param params The initialization parameters for the token manager contract */ - constructor(address interchainTokenServiceAddress_, uint256 implementationType_, bytes32 tokenId_, bytes memory params) { - interchainTokenServiceAddress = IInterchainTokenService(interchainTokenServiceAddress_); + constructor(uint256 implementationType_, address interchainTokenServiceAddress, bytes32 tokenId_, bytes memory params) { + IInterchainTokenService interchainTokenService = IInterchainTokenService(interchainTokenServiceAddress); implementationType = implementationType_; tokenId = tokenId_; - address impl = _getImplementation(IInterchainTokenService(interchainTokenServiceAddress_), implementationType_); + address tokenManagerGetterAddress = interchainTokenService.tokenManagerGetter(); + tokenManagerGetter = ITokenManagerGetter(tokenManagerGetterAddress); + address impl = _getImplementation(tokenManagerGetter, implementationType_); (bool success, ) = impl.delegatecall(abi.encodeWithSelector(TokenManagerProxy.setup.selector, params)); if (!success) revert SetupFailed(); @@ -37,20 +39,20 @@ contract TokenManagerProxy is ITokenManagerProxy { * @return impl The address of the current implementation */ function implementation() public view returns (address impl) { - impl = _getImplementation(interchainTokenServiceAddress, implementationType); + impl = _getImplementation(tokenManagerGetter, implementationType); } /** * @dev Returns the implementation address from the interchain token service for the provided type. - * @param interchainTokenServiceAddress_ The address of the interchain token service + * @param tokenManagerGetter_ The address of the interchain token service * @param implementationType_ The token manager type * @return impl The address of the implementation */ function _getImplementation( - IInterchainTokenService interchainTokenServiceAddress_, + ITokenManagerGetter tokenManagerGetter_, uint256 implementationType_ ) internal view returns (address impl) { - impl = interchainTokenServiceAddress_.getImplementation(implementationType_); + impl = tokenManagerGetter_.getImplementation(implementationType_); } /** diff --git a/contracts/utils/TokenManagerDeployer.sol b/contracts/utils/TokenManagerDeployer.sol index 14040795..bd829457 100644 --- a/contracts/utils/TokenManagerDeployer.sol +++ b/contracts/utils/TokenManagerDeployer.sol @@ -20,7 +20,7 @@ contract TokenManagerDeployer is ITokenManagerDeployer { * @param params Additional parameters used in the setup of the token manager */ function deployTokenManager(bytes32 tokenId, uint256 implementationType, bytes calldata params) external payable { - bytes memory args = abi.encode(address(this), implementationType, tokenId, params); + bytes memory args = abi.encode(implementationType, address(this), tokenId, params); bytes memory bytecode = abi.encodePacked(type(TokenManagerProxy).creationCode, args); address tokenManagerAddress = Create3.deploy(tokenId, bytecode); if (tokenManagerAddress.code.length == 0) revert TokenManagerDeploymentFailed(); diff --git a/contracts/utils/TokenManagerGetter.sol b/contracts/utils/TokenManagerGetter.sol new file mode 100644 index 00000000..b1448ae5 --- /dev/null +++ b/contracts/utils/TokenManagerGetter.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { ITokenManagerGetter } from '../interfaces/ITokenManagerGetter.sol'; +import { ITokenManager } from '../interfaces/ITokenManager.sol'; + +/** + * @title TokenManagerDeployer + * @notice This contract is used to deploy new instances of the TokenManagerProxy contract. + */ +contract TokenManagerGetter is ITokenManagerGetter { + address internal immutable implementationLockUnlock; + address internal immutable implementationMintBurn; + address internal immutable implementationLockUnlockFee; + address internal immutable implementationLiquidityPool; + + constructor(address[] memory tokenManagerImplementations) { + if (tokenManagerImplementations.length != uint256(type(TokenManagerType).max) + 1) revert LengthMismatch(); + + implementationLockUnlock = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.LOCK_UNLOCK); + implementationMintBurn = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.MINT_BURN); + implementationLockUnlockFee = _sanitizeTokenManagerImplementation( + tokenManagerImplementations, + TokenManagerType.LOCK_UNLOCK_FEE_ON_TRANSFER + ); + implementationLiquidityPool = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.LIQUIDITY_POOL); + + } + + function _sanitizeTokenManagerImplementation( + address[] memory implementaions, + TokenManagerType tokenManagerType + ) internal pure returns (address implementation) { + implementation = implementaions[uint256(tokenManagerType)]; + if (implementation == address(0)) revert ZeroAddress(); + if (ITokenManager(implementation).implementationType() != uint256(tokenManagerType)) revert InvalidTokenManagerImplementation(); + } + + /** + * @notice Getter function for the parameters of a lock/unlock TokenManager. Mainly to be used by frontends. + * @param operator the operator of the TokenManager. + * @param tokenAddress the token to be managed. + * @return params the resulting params to be passed to custom TokenManager deployments. + */ + function getParamsLockUnlock(bytes memory operator, address tokenAddress) public pure returns (bytes memory params) { + params = abi.encode(operator, tokenAddress); + } + + /** + * @notice Getter function for the parameters of a mint/burn TokenManager. Mainly to be used by frontends. + * @param operator the operator of the TokenManager. + * @param tokenAddress the token to be managed. + * @return params the resulting params to be passed to custom TokenManager deployments. + */ + function getParamsMintBurn(bytes memory operator, address tokenAddress) public pure returns (bytes memory params) { + params = abi.encode(operator, tokenAddress); + } + + /** + * @notice Getter function for the parameters of a lock/unlock TokenManager. Mainly to be used by frontends. + * @param operator the operator of the TokenManager. + * @param tokenAddress the token to be managed. + * @return params the resulting params to be passed to custom TokenManager deployments. + */ + function getParamsLockUnlockFee(bytes memory operator, address tokenAddress) public pure returns (bytes memory params) { + params = abi.encode(operator, tokenAddress); + } + + /** + * @notice Getter function for the parameters of a liquidity pool TokenManager. Mainly to be used by frontends. + * @param operator the operator of the TokenManager. + * @param tokenAddress the token to be managed. + * @param liquidityPoolAddress the liquidity pool to be used to store the bridged tokens. + * @return params the resulting params to be passed to custom TokenManager deployments. + */ + function getParamsLiquidityPool( + bytes memory operator, + address tokenAddress, + address liquidityPoolAddress + ) public pure returns (bytes memory params) { + params = abi.encode(operator, tokenAddress, liquidityPoolAddress); + } + + /** + * @notice Getter function for TokenManager implementations. This will mainly be called by TokenManagerProxies + * to figure out their implementations + * @param tokenManagerType the type of the TokenManager. + * @return tokenManagerAddress the address of the TokenManagerImplementation. + */ + function getImplementation(uint256 tokenManagerType) external view returns (address tokenManagerAddress) { + if (tokenManagerType > uint256(type(TokenManagerType).max)) revert InvalidTokenManagerImplementation(); + if (TokenManagerType(tokenManagerType) == TokenManagerType.LOCK_UNLOCK) { + return implementationLockUnlock; + } else if (TokenManagerType(tokenManagerType) == TokenManagerType.MINT_BURN) { + return implementationMintBurn; + } else if (TokenManagerType(tokenManagerType) == TokenManagerType.LOCK_UNLOCK_FEE_ON_TRANSFER) { + return implementationLockUnlockFee; + } else if (TokenManagerType(tokenManagerType) == TokenManagerType.LIQUIDITY_POOL) { + return implementationLiquidityPool; + } + } +} \ No newline at end of file diff --git a/scripts/deploy.js b/scripts/deploy.js index 2b52705a..93b9e86b 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -42,8 +42,7 @@ async function deployInterchainTokenService( gatewayAddress, gasServiceAddress, remoteAddressValidatorAddress, - tokenManagerImplementations, - chainName, + tokenManagerGetter, deploymentKey, operatorAddress = wallet.address, ) { @@ -53,7 +52,7 @@ async function deployInterchainTokenService( gatewayAddress, gasServiceAddress, remoteAddressValidatorAddress, - tokenManagerImplementations, + tokenManagerGetter, ]); const proxy = await deployCreate3Contract(create3DeployerAddress, wallet, InterchainTokenServiceProxy, deploymentKey, [ implementation.address, @@ -85,7 +84,7 @@ async function deployAll(wallet, chainName, deploymentKey = 'interchainTokenServ const interchainTokenServiceAddress = await getCreate3Address(create3Deployer.address, wallet, deploymentKey); const remoteAddressValidator = await deployRemoteAddressValidator(wallet, interchainTokenServiceAddress, chainName); const tokenManagerImplementations = await deployTokenManagerImplementations(wallet, interchainTokenServiceAddress); - + const tokenManagerGetter = await deployContract(wallet, 'TokenManagerGetter', [tokenManagerImplementations.map((impl) => impl.address)]); const service = await deployInterchainTokenService( wallet, create3Deployer.address, @@ -94,8 +93,7 @@ async function deployAll(wallet, chainName, deploymentKey = 'interchainTokenServ gateway.address, gasService.address, remoteAddressValidator.address, - tokenManagerImplementations.map((impl) => impl.address), - chainName, + tokenManagerGetter.address, deploymentKey, ); return [service, gateway, gasService]; From 0d097038e64745e292fa6398d939df73f0c35f6a Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 28 Jul 2023 22:56:44 +0300 Subject: [PATCH 38/54] made lint happy --- .../interchain-token-service/InterchainTokenService.sol | 2 +- contracts/interfaces/IInterchainTokenService.sol | 2 +- contracts/interfaces/ITokenManagerGetter.sol | 2 +- contracts/proxies/TokenManagerProxy.sol | 5 +---- contracts/utils/TokenManagerGetter.sol | 3 +-- scripts/deploy.js | 4 +++- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index ff8718da..2efd7bfc 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -95,7 +95,7 @@ contract InterchainTokenService is tokenManagerDeployer = tokenManagerDeployer_; standardizedTokenDeployer = standardizedTokenDeployer_; tokenManagerGetter = tokenManagerGetter_; - + string memory chainName_ = remoteAddressValidator.chainName(); chainNameHash = keccak256(bytes(chainName_)); } diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index e431af07..00ec68c5 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -94,7 +94,7 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx * @return tokenManagerGetterAddress The address of the token manager getter contract. */ function tokenManagerGetter() external view returns (address tokenManagerGetterAddress); - + /** * @notice Returns the address of the token manager associated with the given tokenId. * @param tokenId The tokenId of the token manager. diff --git a/contracts/interfaces/ITokenManagerGetter.sol b/contracts/interfaces/ITokenManagerGetter.sol index d492da26..3e392991 100644 --- a/contracts/interfaces/ITokenManagerGetter.sol +++ b/contracts/interfaces/ITokenManagerGetter.sol @@ -28,7 +28,7 @@ interface ITokenManagerGetter is ITokenManagerType { * @return params The parameters for the mint/burn operation. */ function getParamsMintBurn(bytes memory operator, address tokenAddress) external pure returns (bytes memory params); - + /** * @notice Returns the parameters for the lock/unlock operation. * @param operator The operator address. diff --git a/contracts/proxies/TokenManagerProxy.sol b/contracts/proxies/TokenManagerProxy.sol index 1e2376be..104ed231 100644 --- a/contracts/proxies/TokenManagerProxy.sol +++ b/contracts/proxies/TokenManagerProxy.sol @@ -48,10 +48,7 @@ contract TokenManagerProxy is ITokenManagerProxy { * @param implementationType_ The token manager type * @return impl The address of the implementation */ - function _getImplementation( - ITokenManagerGetter tokenManagerGetter_, - uint256 implementationType_ - ) internal view returns (address impl) { + function _getImplementation(ITokenManagerGetter tokenManagerGetter_, uint256 implementationType_) internal view returns (address impl) { impl = tokenManagerGetter_.getImplementation(implementationType_); } diff --git a/contracts/utils/TokenManagerGetter.sol b/contracts/utils/TokenManagerGetter.sol index b1448ae5..ad93ba27 100644 --- a/contracts/utils/TokenManagerGetter.sol +++ b/contracts/utils/TokenManagerGetter.sol @@ -25,7 +25,6 @@ contract TokenManagerGetter is ITokenManagerGetter { TokenManagerType.LOCK_UNLOCK_FEE_ON_TRANSFER ); implementationLiquidityPool = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.LIQUIDITY_POOL); - } function _sanitizeTokenManagerImplementation( @@ -100,4 +99,4 @@ contract TokenManagerGetter is ITokenManagerGetter { return implementationLiquidityPool; } } -} \ No newline at end of file +} diff --git a/scripts/deploy.js b/scripts/deploy.js index 93b9e86b..0d6dfb93 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -84,7 +84,9 @@ async function deployAll(wallet, chainName, deploymentKey = 'interchainTokenServ const interchainTokenServiceAddress = await getCreate3Address(create3Deployer.address, wallet, deploymentKey); const remoteAddressValidator = await deployRemoteAddressValidator(wallet, interchainTokenServiceAddress, chainName); const tokenManagerImplementations = await deployTokenManagerImplementations(wallet, interchainTokenServiceAddress); - const tokenManagerGetter = await deployContract(wallet, 'TokenManagerGetter', [tokenManagerImplementations.map((impl) => impl.address)]); + const tokenManagerGetter = await deployContract(wallet, 'TokenManagerGetter', [ + tokenManagerImplementations.map((impl) => impl.address), + ]); const service = await deployInterchainTokenService( wallet, create3Deployer.address, From 2e44518f7ceac935b784f1f79ee368d4f90228de Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 28 Jul 2023 23:21:07 +0300 Subject: [PATCH 39/54] Removed tokenManagerGetter and put params into tokenManagers --- .../InterchainTokenService.sol | 50 +++++++-- .../interfaces/IInterchainTokenService.sol | 14 +-- contracts/interfaces/ITokenManagerGetter.sol | 59 ---------- contracts/proxies/TokenManagerProxy.sol | 23 ++-- .../TokenManagerLiquidityPool.sol | 15 +++ .../TokenManagerLockUnlock.sol | 10 ++ .../TokenManagerLockUnlockFeeOnTransfer.sol | 10 ++ .../implementations/TokenManagerMintBurn.sol | 10 ++ contracts/utils/TokenManagerDeployer.sol | 2 +- contracts/utils/TokenManagerGetter.sol | 102 ------------------ scripts/deploy.js | 10 +- 11 files changed, 114 insertions(+), 191 deletions(-) delete mode 100644 contracts/interfaces/ITokenManagerGetter.sol delete mode 100644 contracts/utils/TokenManagerGetter.sol diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 2efd7bfc..0bd74c35 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -48,11 +48,14 @@ contract InterchainTokenService is using AddressBytesUtils for bytes; using AddressBytesUtils for address; + address internal immutable implementationLockUnlock; + address internal immutable implementationMintBurn; + address internal immutable implementationLockUnlockFee; + address internal immutable implementationLiquidityPool; IAxelarGasService public immutable gasService; IRemoteAddressValidator public immutable remoteAddressValidator; address public immutable tokenManagerDeployer; address public immutable standardizedTokenDeployer; - address public immutable tokenManagerGetter; bytes32 public immutable chainNameHash; bytes32 internal constant PREFIX_CUSTOM_TOKEN_ID = keccak256('its-custom-token-id'); @@ -73,7 +76,7 @@ contract InterchainTokenService is * @param gateway_ the address of the AxelarGateway. * @param gasService_ the address of the AxelarGasService. * @param remoteAddressValidator_ the address of the RemoteAddressValidator. - * @param tokenManagerGetter_ the address of the TokenManagerGetter. + * @param tokenManagerImplementations this need to have exactly 3 implementations in the following order: Lock/Unlock, mint/burn and then liquidity pool. */ constructor( address tokenManagerDeployer_, @@ -81,21 +84,28 @@ contract InterchainTokenService is address gateway_, address gasService_, address remoteAddressValidator_, - address tokenManagerGetter_ + address[] memory tokenManagerImplementations ) AxelarExecutable(gateway_) { if ( remoteAddressValidator_ == address(0) || gasService_ == address(0) || tokenManagerDeployer_ == address(0) || - standardizedTokenDeployer_ == address(0) || - tokenManagerGetter_ == address(0) + standardizedTokenDeployer_ == address(0) ) revert ZeroAddress(); remoteAddressValidator = IRemoteAddressValidator(remoteAddressValidator_); gasService = IAxelarGasService(gasService_); tokenManagerDeployer = tokenManagerDeployer_; standardizedTokenDeployer = standardizedTokenDeployer_; - tokenManagerGetter = tokenManagerGetter_; + if (tokenManagerImplementations.length != uint256(type(TokenManagerType).max) + 1) revert LengthMismatch(); + + implementationLockUnlock = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.LOCK_UNLOCK); + implementationMintBurn = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.MINT_BURN); + implementationLockUnlockFee = _sanitizeTokenManagerImplementation( + tokenManagerImplementations, + TokenManagerType.LOCK_UNLOCK_FEE_ON_TRANSFER + ); + implementationLiquidityPool = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.LIQUIDITY_POOL); string memory chainName_ = remoteAddressValidator.chainName(); chainNameHash = keccak256(bytes(chainName_)); } @@ -196,6 +206,25 @@ contract InterchainTokenService is tokenId = keccak256(abi.encode(PREFIX_CUSTOM_TOKEN_ID, sender, salt)); } + /** + * @notice Getter function for TokenManager implementations. This will mainly be called by TokenManagerProxies + * to figure out their implementations + * @param tokenManagerType the type of the TokenManager. + * @return tokenManagerAddress the address of the TokenManagerImplementation. + */ + function getImplementation(uint256 tokenManagerType) external view returns (address tokenManagerAddress) { + if (tokenManagerType > uint256(type(TokenManagerType).max)) revert InvalidImplementation(); + if (TokenManagerType(tokenManagerType) == TokenManagerType.LOCK_UNLOCK) { + return implementationLockUnlock; + } else if (TokenManagerType(tokenManagerType) == TokenManagerType.MINT_BURN) { + return implementationMintBurn; + } else if (TokenManagerType(tokenManagerType) == TokenManagerType.LOCK_UNLOCK_FEE_ON_TRANSFER) { + return implementationLockUnlockFee; + } else if (TokenManagerType(tokenManagerType) == TokenManagerType.LIQUIDITY_POOL) { + return implementationLiquidityPool; + } + } + /** * @notice Getter function for the flow limit of an existing token manager with a give token ID. * @param tokenId the token ID of the TokenManager. @@ -489,6 +518,15 @@ contract InterchainTokenService is _setOperator(params.toAddress()); } + function _sanitizeTokenManagerImplementation( + address[] memory implementaions, + TokenManagerType tokenManagerType + ) internal pure returns (address implementation) { + implementation = implementaions[uint256(tokenManagerType)]; + if (implementation == address(0)) revert ZeroAddress(); + if (ITokenManager(implementation).implementationType() != uint256(tokenManagerType)) revert InvalidTokenManagerImplementation(); + } + /** * @notice Executes operations based on the payload and selector. * @param sourceChain The chain where the transaction originates from diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index 00ec68c5..b25a8551 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -15,6 +15,7 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx // more generic error error ZeroAddress(); error LengthMismatch(); + error InvalidTokenManagerImplementation(); error NotRemoteService(); error TokenManagerDoesNotExist(bytes32 tokenId); error NotTokenManager(); @@ -89,12 +90,6 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx */ function standardizedTokenDeployer() external view returns (address standardizedTokenDeployerAddress); - /** - * @notice Returns the address of the token manager getter contract. - * @return tokenManagerGetterAddress The address of the token manager getter contract. - */ - function tokenManagerGetter() external view returns (address tokenManagerGetterAddress); - /** * @notice Returns the address of the token manager associated with the given tokenId. * @param tokenId The tokenId of the token manager. @@ -224,6 +219,13 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx uint256 gasValue ) external payable; + /** + * @notice Returns the implementation address for a given token manager type. + * @param tokenManagerType The type of token manager. + * @return tokenManagerAddress The address of the token manager implementation. + */ + function getImplementation(uint256 tokenManagerType) external view returns (address tokenManagerAddress); + /** * @notice Initiates an interchain token transfer. Only callable by TokenManagers * @param tokenId The tokenId of the token to be transmitted. diff --git a/contracts/interfaces/ITokenManagerGetter.sol b/contracts/interfaces/ITokenManagerGetter.sol deleted file mode 100644 index 3e392991..00000000 --- a/contracts/interfaces/ITokenManagerGetter.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import { ITokenManagerType } from '../interfaces/ITokenManagerType.sol'; - -/** - * @title ITokenManagerGetter - * @notice Will provide getters for TokenManager implementations and params to help with deployments. - */ -interface ITokenManagerGetter is ITokenManagerType { - error LengthMismatch(); - error ZeroAddress(); - error InvalidTokenManagerImplementation(); - - /** - * @notice Returns the parameters for the lock/unlock operation. - * @param operator The operator address. - * @param tokenAddress The address of the token. - * @return params The parameters for the lock/unlock operation. - */ - function getParamsLockUnlock(bytes memory operator, address tokenAddress) external pure returns (bytes memory params); - - /** - * @notice Returns the parameters for the mint/burn operation. - * @param operator The operator address. - * @param tokenAddress The address of the token. - * @return params The parameters for the mint/burn operation. - */ - function getParamsMintBurn(bytes memory operator, address tokenAddress) external pure returns (bytes memory params); - - /** - * @notice Returns the parameters for the lock/unlock operation. - * @param operator The operator address. - * @param tokenAddress The address of the token. - * @return params The parameters for the lock/unlock operation. - */ - function getParamsLockUnlockFee(bytes memory operator, address tokenAddress) external pure returns (bytes memory params); - - /** - * @notice Returns the parameters for the liquidity pool operation. - * @param operator The operator address. - * @param tokenAddress The address of the token. - * @param liquidityPoolAddress The address of the liquidity pool. - * @return params The parameters for the liquidity pool operation. - */ - function getParamsLiquidityPool( - bytes memory operator, - address tokenAddress, - address liquidityPoolAddress - ) external pure returns (bytes memory params); - - /** - * @notice Returns the implementation address for a given token manager type. - * @param tokenManagerType The type of token manager. - * @return tokenManagerAddress The address of the token manager implementation. - */ - function getImplementation(uint256 tokenManagerType) external view returns (address tokenManagerAddress); -} diff --git a/contracts/proxies/TokenManagerProxy.sol b/contracts/proxies/TokenManagerProxy.sol index 104ed231..69adaf23 100644 --- a/contracts/proxies/TokenManagerProxy.sol +++ b/contracts/proxies/TokenManagerProxy.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.0; import { IInterchainTokenService } from '../interfaces/IInterchainTokenService.sol'; import { ITokenManagerProxy } from '../interfaces/ITokenManagerProxy.sol'; -import { ITokenManagerGetter } from '../interfaces/ITokenManagerGetter.sol'; /** * @title TokenManagerProxy @@ -12,23 +11,22 @@ import { ITokenManagerGetter } from '../interfaces/ITokenManagerGetter.sol'; * inherits from FixedProxy from the gmp sdk repo */ contract TokenManagerProxy is ITokenManagerProxy { - ITokenManagerGetter internal immutable tokenManagerGetter; + IInterchainTokenService public immutable interchainTokenService; uint256 public immutable implementationType; bytes32 public immutable tokenId; /** * @dev Constructs the TokenManagerProxy contract. + * @param interchainTokenServiceAddress_ The address of the interchain token service * @param implementationType_ The token manager type * @param tokenId_ The identifier for the token * @param params The initialization parameters for the token manager contract */ - constructor(uint256 implementationType_, address interchainTokenServiceAddress, bytes32 tokenId_, bytes memory params) { - IInterchainTokenService interchainTokenService = IInterchainTokenService(interchainTokenServiceAddress); + constructor(address interchainTokenServiceAddress_, uint256 implementationType_, bytes32 tokenId_, bytes memory params) { + interchainTokenService = IInterchainTokenService(interchainTokenServiceAddress_); implementationType = implementationType_; tokenId = tokenId_; - address tokenManagerGetterAddress = interchainTokenService.tokenManagerGetter(); - tokenManagerGetter = ITokenManagerGetter(tokenManagerGetterAddress); - address impl = _getImplementation(tokenManagerGetter, implementationType_); + address impl = _getImplementation(IInterchainTokenService(interchainTokenServiceAddress_), implementationType_); (bool success, ) = impl.delegatecall(abi.encodeWithSelector(TokenManagerProxy.setup.selector, params)); if (!success) revert SetupFailed(); @@ -39,17 +37,20 @@ contract TokenManagerProxy is ITokenManagerProxy { * @return impl The address of the current implementation */ function implementation() public view returns (address impl) { - impl = _getImplementation(tokenManagerGetter, implementationType); + impl = _getImplementation(interchainTokenService, implementationType); } /** * @dev Returns the implementation address from the interchain token service for the provided type. - * @param tokenManagerGetter_ The address of the interchain token service + * @param interchainTokenServiceAddress_ The address of the interchain token service * @param implementationType_ The token manager type * @return impl The address of the implementation */ - function _getImplementation(ITokenManagerGetter tokenManagerGetter_, uint256 implementationType_) internal view returns (address impl) { - impl = tokenManagerGetter_.getImplementation(implementationType_); + function _getImplementation( + IInterchainTokenService interchainTokenServiceAddress_, + uint256 implementationType_ + ) internal view returns (address impl) { + impl = interchainTokenServiceAddress_.getImplementation(implementationType_); } /** diff --git a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol index a3468599..c0d30046 100644 --- a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol +++ b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol @@ -100,4 +100,19 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage, NoReEntrancy { return IERC20(token).balanceOf(to) - balance; } + + /** + * @notice Getter function for the parameters of a liquidity pool TokenManager. Mainly to be used by frontends. + * @param operator the operator of the TokenManager. + * @param tokenAddress the token to be managed. + * @param liquidityPoolAddress the liquidity pool to be used to store the bridged tokens. + * @return params the resulting params to be passed to custom TokenManager deployments. + */ + function getParams( + bytes memory operator, + address tokenAddress, + address liquidityPoolAddress + ) public pure returns (bytes memory params) { + params = abi.encode(operator, tokenAddress, liquidityPoolAddress); + } } diff --git a/contracts/token-manager/implementations/TokenManagerLockUnlock.sol b/contracts/token-manager/implementations/TokenManagerLockUnlock.sol index 06e858a7..4fb729db 100644 --- a/contracts/token-manager/implementations/TokenManagerLockUnlock.sol +++ b/contracts/token-manager/implementations/TokenManagerLockUnlock.sol @@ -62,4 +62,14 @@ contract TokenManagerLockUnlock is TokenManagerAddressStorage { return amount; } + + /** + * @notice Getter function for the parameters of a lock/unlock TokenManager. Mainly to be used by frontends. + * @param operator the operator of the TokenManager. + * @param tokenAddress the token to be managed. + * @return params the resulting params to be passed to custom TokenManager deployments. + */ + function getParams(bytes memory operator, address tokenAddress) public pure returns (bytes memory params) { + params = abi.encode(operator, tokenAddress); + } } diff --git a/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol b/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol index 4cf3c5dc..6e954f03 100644 --- a/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol +++ b/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol @@ -69,4 +69,14 @@ contract TokenManagerLockUnlockFee is TokenManagerAddressStorage, NoReEntrancy { return IERC20(token).balanceOf(to) - balance; } + + /** + * @notice Getter function for the parameters of a lock/unlock TokenManager. Mainly to be used by frontends. + * @param operator the operator of the TokenManager. + * @param tokenAddress the token to be managed. + * @return params the resulting params to be passed to custom TokenManager deployments. + */ + function getParams(bytes memory operator, address tokenAddress) public pure returns (bytes memory params) { + params = abi.encode(operator, tokenAddress); + } } diff --git a/contracts/token-manager/implementations/TokenManagerMintBurn.sol b/contracts/token-manager/implementations/TokenManagerMintBurn.sol index 70031b00..3b7c2b88 100644 --- a/contracts/token-manager/implementations/TokenManagerMintBurn.sol +++ b/contracts/token-manager/implementations/TokenManagerMintBurn.sol @@ -63,4 +63,14 @@ contract TokenManagerMintBurn is TokenManagerAddressStorage { return amount; } + + /** + * @notice Getter function for the parameters of a lock/unlock TokenManager. Mainly to be used by frontends. + * @param operator the operator of the TokenManager. + * @param tokenAddress the token to be managed. + * @return params the resulting params to be passed to custom TokenManager deployments. + */ + function getParams(bytes memory operator, address tokenAddress) public pure returns (bytes memory params) { + params = abi.encode(operator, tokenAddress); + } } diff --git a/contracts/utils/TokenManagerDeployer.sol b/contracts/utils/TokenManagerDeployer.sol index bd829457..14040795 100644 --- a/contracts/utils/TokenManagerDeployer.sol +++ b/contracts/utils/TokenManagerDeployer.sol @@ -20,7 +20,7 @@ contract TokenManagerDeployer is ITokenManagerDeployer { * @param params Additional parameters used in the setup of the token manager */ function deployTokenManager(bytes32 tokenId, uint256 implementationType, bytes calldata params) external payable { - bytes memory args = abi.encode(implementationType, address(this), tokenId, params); + bytes memory args = abi.encode(address(this), implementationType, tokenId, params); bytes memory bytecode = abi.encodePacked(type(TokenManagerProxy).creationCode, args); address tokenManagerAddress = Create3.deploy(tokenId, bytecode); if (tokenManagerAddress.code.length == 0) revert TokenManagerDeploymentFailed(); diff --git a/contracts/utils/TokenManagerGetter.sol b/contracts/utils/TokenManagerGetter.sol deleted file mode 100644 index ad93ba27..00000000 --- a/contracts/utils/TokenManagerGetter.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import { ITokenManagerGetter } from '../interfaces/ITokenManagerGetter.sol'; -import { ITokenManager } from '../interfaces/ITokenManager.sol'; - -/** - * @title TokenManagerDeployer - * @notice This contract is used to deploy new instances of the TokenManagerProxy contract. - */ -contract TokenManagerGetter is ITokenManagerGetter { - address internal immutable implementationLockUnlock; - address internal immutable implementationMintBurn; - address internal immutable implementationLockUnlockFee; - address internal immutable implementationLiquidityPool; - - constructor(address[] memory tokenManagerImplementations) { - if (tokenManagerImplementations.length != uint256(type(TokenManagerType).max) + 1) revert LengthMismatch(); - - implementationLockUnlock = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.LOCK_UNLOCK); - implementationMintBurn = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.MINT_BURN); - implementationLockUnlockFee = _sanitizeTokenManagerImplementation( - tokenManagerImplementations, - TokenManagerType.LOCK_UNLOCK_FEE_ON_TRANSFER - ); - implementationLiquidityPool = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.LIQUIDITY_POOL); - } - - function _sanitizeTokenManagerImplementation( - address[] memory implementaions, - TokenManagerType tokenManagerType - ) internal pure returns (address implementation) { - implementation = implementaions[uint256(tokenManagerType)]; - if (implementation == address(0)) revert ZeroAddress(); - if (ITokenManager(implementation).implementationType() != uint256(tokenManagerType)) revert InvalidTokenManagerImplementation(); - } - - /** - * @notice Getter function for the parameters of a lock/unlock TokenManager. Mainly to be used by frontends. - * @param operator the operator of the TokenManager. - * @param tokenAddress the token to be managed. - * @return params the resulting params to be passed to custom TokenManager deployments. - */ - function getParamsLockUnlock(bytes memory operator, address tokenAddress) public pure returns (bytes memory params) { - params = abi.encode(operator, tokenAddress); - } - - /** - * @notice Getter function for the parameters of a mint/burn TokenManager. Mainly to be used by frontends. - * @param operator the operator of the TokenManager. - * @param tokenAddress the token to be managed. - * @return params the resulting params to be passed to custom TokenManager deployments. - */ - function getParamsMintBurn(bytes memory operator, address tokenAddress) public pure returns (bytes memory params) { - params = abi.encode(operator, tokenAddress); - } - - /** - * @notice Getter function for the parameters of a lock/unlock TokenManager. Mainly to be used by frontends. - * @param operator the operator of the TokenManager. - * @param tokenAddress the token to be managed. - * @return params the resulting params to be passed to custom TokenManager deployments. - */ - function getParamsLockUnlockFee(bytes memory operator, address tokenAddress) public pure returns (bytes memory params) { - params = abi.encode(operator, tokenAddress); - } - - /** - * @notice Getter function for the parameters of a liquidity pool TokenManager. Mainly to be used by frontends. - * @param operator the operator of the TokenManager. - * @param tokenAddress the token to be managed. - * @param liquidityPoolAddress the liquidity pool to be used to store the bridged tokens. - * @return params the resulting params to be passed to custom TokenManager deployments. - */ - function getParamsLiquidityPool( - bytes memory operator, - address tokenAddress, - address liquidityPoolAddress - ) public pure returns (bytes memory params) { - params = abi.encode(operator, tokenAddress, liquidityPoolAddress); - } - - /** - * @notice Getter function for TokenManager implementations. This will mainly be called by TokenManagerProxies - * to figure out their implementations - * @param tokenManagerType the type of the TokenManager. - * @return tokenManagerAddress the address of the TokenManagerImplementation. - */ - function getImplementation(uint256 tokenManagerType) external view returns (address tokenManagerAddress) { - if (tokenManagerType > uint256(type(TokenManagerType).max)) revert InvalidTokenManagerImplementation(); - if (TokenManagerType(tokenManagerType) == TokenManagerType.LOCK_UNLOCK) { - return implementationLockUnlock; - } else if (TokenManagerType(tokenManagerType) == TokenManagerType.MINT_BURN) { - return implementationMintBurn; - } else if (TokenManagerType(tokenManagerType) == TokenManagerType.LOCK_UNLOCK_FEE_ON_TRANSFER) { - return implementationLockUnlockFee; - } else if (TokenManagerType(tokenManagerType) == TokenManagerType.LIQUIDITY_POOL) { - return implementationLiquidityPool; - } - } -} diff --git a/scripts/deploy.js b/scripts/deploy.js index 0d6dfb93..be3f5752 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -42,7 +42,7 @@ async function deployInterchainTokenService( gatewayAddress, gasServiceAddress, remoteAddressValidatorAddress, - tokenManagerGetter, + tokenManagerImplementations, deploymentKey, operatorAddress = wallet.address, ) { @@ -52,7 +52,7 @@ async function deployInterchainTokenService( gatewayAddress, gasServiceAddress, remoteAddressValidatorAddress, - tokenManagerGetter, + tokenManagerImplementations, ]); const proxy = await deployCreate3Contract(create3DeployerAddress, wallet, InterchainTokenServiceProxy, deploymentKey, [ implementation.address, @@ -84,9 +84,7 @@ async function deployAll(wallet, chainName, deploymentKey = 'interchainTokenServ const interchainTokenServiceAddress = await getCreate3Address(create3Deployer.address, wallet, deploymentKey); const remoteAddressValidator = await deployRemoteAddressValidator(wallet, interchainTokenServiceAddress, chainName); const tokenManagerImplementations = await deployTokenManagerImplementations(wallet, interchainTokenServiceAddress); - const tokenManagerGetter = await deployContract(wallet, 'TokenManagerGetter', [ - tokenManagerImplementations.map((impl) => impl.address), - ]); + const service = await deployInterchainTokenService( wallet, create3Deployer.address, @@ -95,7 +93,7 @@ async function deployAll(wallet, chainName, deploymentKey = 'interchainTokenServ gateway.address, gasService.address, remoteAddressValidator.address, - tokenManagerGetter.address, + tokenManagerImplementations.map((impl) => impl.address), deploymentKey, ); return [service, gateway, gasService]; From 6027ffee06ea11fcea54eee8f1d4d671bf98e8f3 Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 28 Jul 2023 23:28:04 +0300 Subject: [PATCH 40/54] Added separate tokenManager interfaces --- .../interfaces/ITokenManagerLiquidityPool.sol | 31 +++++++++++++++++++ .../interfaces/ITokenManagerLockUnlock.sol | 19 ++++++++++++ .../interfaces/ITokenManagerLockUnlockFee.sol | 19 ++++++++++++ .../interfaces/ITokenManagerMintBurn.sol | 19 ++++++++++++ .../TokenManagerLiquidityPool.sol | 5 +-- .../TokenManagerLockUnlock.sol | 5 +-- .../TokenManagerLockUnlockFeeOnTransfer.sol | 5 +-- .../implementations/TokenManagerMintBurn.sol | 5 +-- 8 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 contracts/interfaces/ITokenManagerLiquidityPool.sol create mode 100644 contracts/interfaces/ITokenManagerLockUnlock.sol create mode 100644 contracts/interfaces/ITokenManagerLockUnlockFee.sol create mode 100644 contracts/interfaces/ITokenManagerMintBurn.sol diff --git a/contracts/interfaces/ITokenManagerLiquidityPool.sol b/contracts/interfaces/ITokenManagerLiquidityPool.sol new file mode 100644 index 00000000..902a0d1f --- /dev/null +++ b/contracts/interfaces/ITokenManagerLiquidityPool.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { ITokenManager } from './ITokenManager.sol'; + +/** + * @title ITokenManager + * @notice This contract is responsible for handling tokens before initiating a cross chain token transfer, or after receiving one. + */ +interface ITokenManagerLiquidityPool is ITokenManager { + /** + * @notice Getter function for the parameters of a lock/unlock TokenManager. Mainly to be used by frontends. + * @param operator the operator of the TokenManager. + * @param tokenAddress the token to be managed. + * @return params the resulting params to be passed to custom TokenManager deployments. + */ + function getParams(bytes memory operator, address tokenAddress, address liquidityPool) external pure returns (bytes memory params); + + /** + * @dev Reads the stored liquidity pool address from the specified storage slot + * @return liquidityPool_ The address of the liquidity pool + */ + function liquidityPool() external view returns (address liquidityPool_); + + /** + * @dev Updates the address of the liquidity pool. Can only be called by the operator. + * @param newLiquidityPool The new address of the liquidity pool + */ + function setLiquidityPool(address newLiquidityPool) external; +} diff --git a/contracts/interfaces/ITokenManagerLockUnlock.sol b/contracts/interfaces/ITokenManagerLockUnlock.sol new file mode 100644 index 00000000..5c009ae8 --- /dev/null +++ b/contracts/interfaces/ITokenManagerLockUnlock.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { ITokenManager } from './ITokenManager.sol'; + +/** + * @title ITokenManager + * @notice This contract is responsible for handling tokens before initiating a cross chain token transfer, or after receiving one. + */ +interface ITokenManagerLockUnlock is ITokenManager { + /** + * @notice Getter function for the parameters of a lock/unlock TokenManager. Mainly to be used by frontends. + * @param operator the operator of the TokenManager. + * @param tokenAddress the token to be managed. + * @return params the resulting params to be passed to custom TokenManager deployments. + */ + function getParams(bytes memory operator, address tokenAddress) external pure returns (bytes memory params); +} diff --git a/contracts/interfaces/ITokenManagerLockUnlockFee.sol b/contracts/interfaces/ITokenManagerLockUnlockFee.sol new file mode 100644 index 00000000..686b01d3 --- /dev/null +++ b/contracts/interfaces/ITokenManagerLockUnlockFee.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { ITokenManager } from './ITokenManager.sol'; + +/** + * @title ITokenManager + * @notice This contract is responsible for handling tokens before initiating a cross chain token transfer, or after receiving one. + */ +interface ITokenManagerLockUnlockFee is ITokenManager { + /** + * @notice Getter function for the parameters of a lock/unlock TokenManager. Mainly to be used by frontends. + * @param operator the operator of the TokenManager. + * @param tokenAddress the token to be managed. + * @return params the resulting params to be passed to custom TokenManager deployments. + */ + function getParams(bytes memory operator, address tokenAddress) external pure returns (bytes memory params); +} diff --git a/contracts/interfaces/ITokenManagerMintBurn.sol b/contracts/interfaces/ITokenManagerMintBurn.sol new file mode 100644 index 00000000..b9e1f4d1 --- /dev/null +++ b/contracts/interfaces/ITokenManagerMintBurn.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { ITokenManager } from './ITokenManager.sol'; + +/** + * @title ITokenManager + * @notice This contract is responsible for handling tokens before initiating a cross chain token transfer, or after receiving one. + */ +interface ITokenManagerMintBurn is ITokenManager { + /** + * @notice Getter function for the parameters of a lock/unlock TokenManager. Mainly to be used by frontends. + * @param operator the operator of the TokenManager. + * @param tokenAddress the token to be managed. + * @return params the resulting params to be passed to custom TokenManager deployments. + */ + function getParams(bytes memory operator, address tokenAddress) external pure returns (bytes memory params); +} diff --git a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol index c0d30046..a5d37373 100644 --- a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol +++ b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.0; import { TokenManagerAddressStorage } from './TokenManagerAddressStorage.sol'; import { NoReEntrancy } from '../../utils/NoReEntrancy.sol'; import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; +import { ITokenManagerLiquidityPool } from '../../interfaces/ITokenManagerLiquidityPool.sol'; import { SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/SafeTransfer.sol'; @@ -15,7 +16,7 @@ import { SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/c * @dev This contract extends TokenManagerAddressStorage and provides implementation for its abstract methods. * It uses the Axelar SDK to safely transfer tokens. */ -contract TokenManagerLiquidityPool is TokenManagerAddressStorage, NoReEntrancy { +contract TokenManagerLiquidityPool is TokenManagerAddressStorage, NoReEntrancy, ITokenManagerLiquidityPool { // uint256(keccak256('liquidity-pool-slot')) - 1 uint256 internal constant LIQUIDITY_POOL_SLOT = 0x8e02741a3381812d092c5689c9fc701c5185c1742fdf7954c4c4472be4cc4807; @@ -112,7 +113,7 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage, NoReEntrancy { bytes memory operator, address tokenAddress, address liquidityPoolAddress - ) public pure returns (bytes memory params) { + ) external pure returns (bytes memory params) { params = abi.encode(operator, tokenAddress, liquidityPoolAddress); } } diff --git a/contracts/token-manager/implementations/TokenManagerLockUnlock.sol b/contracts/token-manager/implementations/TokenManagerLockUnlock.sol index 4fb729db..22e673fa 100644 --- a/contracts/token-manager/implementations/TokenManagerLockUnlock.sol +++ b/contracts/token-manager/implementations/TokenManagerLockUnlock.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import { TokenManagerAddressStorage } from './TokenManagerAddressStorage.sol'; import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; +import { ITokenManagerLockUnlock } from '../../interfaces/ITokenManagerLockUnlock.sol'; import { SafeTokenTransferFrom, SafeTokenTransfer } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/SafeTransfer.sol'; @@ -13,7 +14,7 @@ import { SafeTokenTransferFrom, SafeTokenTransfer } from '@axelar-network/axelar * @dev This contract extends TokenManagerAddressStorage and provides implementation for its abstract methods. * It uses the Axelar SDK to safely transfer tokens. */ -contract TokenManagerLockUnlock is TokenManagerAddressStorage { +contract TokenManagerLockUnlock is TokenManagerAddressStorage, ITokenManagerLockUnlock { /** * @dev Constructs an instance of TokenManagerLockUnlock. Calls the constructor * of TokenManagerAddressStorage which calls the constructor of TokenManager. @@ -69,7 +70,7 @@ contract TokenManagerLockUnlock is TokenManagerAddressStorage { * @param tokenAddress the token to be managed. * @return params the resulting params to be passed to custom TokenManager deployments. */ - function getParams(bytes memory operator, address tokenAddress) public pure returns (bytes memory params) { + function getParams(bytes memory operator, address tokenAddress) external pure returns (bytes memory params) { params = abi.encode(operator, tokenAddress); } } diff --git a/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol b/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol index 6e954f03..6c380802 100644 --- a/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol +++ b/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.0; import { TokenManagerAddressStorage } from './TokenManagerAddressStorage.sol'; import { NoReEntrancy } from '../../utils/NoReEntrancy.sol'; import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; +import { ITokenManagerLockUnlockFee } from '../../interfaces/ITokenManagerLockUnlockFee.sol'; import { SafeTokenTransferFrom, SafeTokenTransfer } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/SafeTransfer.sol'; @@ -14,7 +15,7 @@ import { SafeTokenTransferFrom, SafeTokenTransfer } from '@axelar-network/axelar * @dev This contract extends TokenManagerAddressStorage and provides implementation for its abstract methods. * It uses the Axelar SDK to safely transfer tokens. */ -contract TokenManagerLockUnlockFee is TokenManagerAddressStorage, NoReEntrancy { +contract TokenManagerLockUnlockFee is TokenManagerAddressStorage, NoReEntrancy, ITokenManagerLockUnlockFee { /** * @dev Constructs an instance of TokenManagerLockUnlock. Calls the constructor * of TokenManagerAddressStorage which calls the constructor of TokenManager. @@ -76,7 +77,7 @@ contract TokenManagerLockUnlockFee is TokenManagerAddressStorage, NoReEntrancy { * @param tokenAddress the token to be managed. * @return params the resulting params to be passed to custom TokenManager deployments. */ - function getParams(bytes memory operator, address tokenAddress) public pure returns (bytes memory params) { + function getParams(bytes memory operator, address tokenAddress) external pure returns (bytes memory params) { params = abi.encode(operator, tokenAddress); } } diff --git a/contracts/token-manager/implementations/TokenManagerMintBurn.sol b/contracts/token-manager/implementations/TokenManagerMintBurn.sol index 3b7c2b88..3bdb155c 100644 --- a/contracts/token-manager/implementations/TokenManagerMintBurn.sol +++ b/contracts/token-manager/implementations/TokenManagerMintBurn.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import { TokenManagerAddressStorage } from './TokenManagerAddressStorage.sol'; import { IERC20BurnableMintable } from '../../interfaces/IERC20BurnableMintable.sol'; +import { ITokenManagerMintBurn } from '../../interfaces/ITokenManagerMintBurn.sol'; import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; import { SafeTokenCall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/SafeTransfer.sol'; @@ -14,7 +15,7 @@ import { SafeTokenCall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts * @dev This contract extends TokenManagerAddressStorage and provides implementation for its abstract methods. * It uses the Axelar SDK to safely transfer tokens. */ -contract TokenManagerMintBurn is TokenManagerAddressStorage { +contract TokenManagerMintBurn is TokenManagerAddressStorage, ITokenManagerMintBurn { /** * @dev Constructs an instance of TokenManagerMintBurn. Calls the constructor * of TokenManagerAddressStorage which calls the constructor of TokenManager. @@ -70,7 +71,7 @@ contract TokenManagerMintBurn is TokenManagerAddressStorage { * @param tokenAddress the token to be managed. * @return params the resulting params to be passed to custom TokenManager deployments. */ - function getParams(bytes memory operator, address tokenAddress) public pure returns (bytes memory params) { + function getParams(bytes memory operator, address tokenAddress) external pure returns (bytes memory params) { params = abi.encode(operator, tokenAddress); } } From 04041d1136fb39ee1615450f2b044541dfbd1084 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 23 Aug 2023 15:50:51 +0300 Subject: [PATCH 41/54] addressed ackeeblockchains's 3.0 report --- .../InterchainTokenService.sol | 10 +++--- .../interchain-token/InterchainToken.sol | 3 +- contracts/interfaces/INoReEntrancy.sol | 6 ++++ .../interfaces/IRemoteAddressValidator.sol | 20 ------------ contracts/proxies/TokenManagerProxy.sol | 3 +- .../RemoteAddressValidator.sol | 32 ------------------- contracts/token-manager/TokenManager.sol | 2 +- .../TokenManagerLiquidityPool.sol | 17 +++++++--- .../TokenManagerLockUnlock.sol | 2 +- .../TokenManagerLockUnlockFeeOnTransfer.sol | 8 +++-- .../implementations/TokenManagerMintBurn.sol | 2 +- contracts/utils/Distributable.sol | 1 + contracts/utils/ExpressCallHandler.sol | 2 +- contracts/utils/NoReEntrancy.sol | 8 ++--- contracts/utils/Operatable.sol | 3 +- test/RemoteAddressValidator.js | 32 ------------------- 16 files changed, 42 insertions(+), 109 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 0bd74c35..318f69d9 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -330,8 +330,7 @@ contract InterchainTokenService is } /** - * @notice Used to deploy a standardized token alongside a TokenManager. If the `distributor` is the address of the TokenManager (which - * can be calculated ahead of time) then a mint/burn TokenManager is used. Otherwise a lock/unlcok TokenManager is used. + * @notice Used to deploy a standardized token alongside a TokenManager. * @param salt the salt to be used. * @param name the name of the token to be deployed. * @param symbol the symbol of the token to be deployed. @@ -443,9 +442,10 @@ contract InterchainTokenService is SafeTokenTransferFrom.safeTransferFrom(token, caller, destinationAddress, amount); + _setExpressReceiveTokenWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId, caller); + _expressExecuteWithInterchainTokenToken(tokenId, destinationAddress, sourceChain, sourceAddress, data, amount); - _setExpressReceiveTokenWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId, caller); } /*********************\ @@ -519,10 +519,10 @@ contract InterchainTokenService is } function _sanitizeTokenManagerImplementation( - address[] memory implementaions, + address[] memory implementations, TokenManagerType tokenManagerType ) internal pure returns (address implementation) { - implementation = implementaions[uint256(tokenManagerType)]; + implementation = implementations[uint256(tokenManagerType)]; if (implementation == address(0)) revert ZeroAddress(); if (ITokenManager(implementation).implementationType() != uint256(tokenManagerType)) revert InvalidTokenManagerImplementation(); } diff --git a/contracts/interchain-token/InterchainToken.sol b/contracts/interchain-token/InterchainToken.sol index 3da7f8dd..85668a35 100644 --- a/contracts/interchain-token/InterchainToken.sol +++ b/contracts/interchain-token/InterchainToken.sol @@ -10,7 +10,6 @@ import { ERC20 } from '../token-implementations/ERC20.sol'; * @title An example implementation of the IInterchainToken. * @notice The implementation ERC20 can be done in any way, however this example assumes that an _approve internal function exists * that can be used to create approvals, and that `allowance` is a mapping. - * @dev You can skip the `tokenManagerRequiresApproval()` function altogether if you know what it should return for your token. */ abstract contract InterchainToken is IInterchainToken, ERC20 { /** @@ -66,7 +65,7 @@ abstract contract InterchainToken is IInterchainToken, ERC20 { _approve(sender, msg.sender, _allowance - amount); } - _beforeInterchainTransfer(msg.sender, destinationChain, recipient, amount, metadata); + _beforeInterchainTransfer(sender, destinationChain, recipient, amount, metadata); ITokenManager tokenManager_ = tokenManager(); tokenManager_.transmitInterchainTransfer{ value: msg.value }(sender, destinationChain, recipient, amount, metadata); diff --git a/contracts/interfaces/INoReEntrancy.sol b/contracts/interfaces/INoReEntrancy.sol index 9e0dd68d..6b2414d6 100644 --- a/contracts/interfaces/INoReEntrancy.sol +++ b/contracts/interfaces/INoReEntrancy.sol @@ -9,4 +9,10 @@ pragma solidity ^0.8.0; */ interface INoReEntrancy { error ReEntrancy(); + + /** + * @notice Check if the contract is already executing. + * @return entered A boolean representing the entered status. True if already executing, false otherwise. + */ + function hasEntered() external view returns (bool entered); } diff --git a/contracts/interfaces/IRemoteAddressValidator.sol b/contracts/interfaces/IRemoteAddressValidator.sol index 38846e2b..c19a4a33 100644 --- a/contracts/interfaces/IRemoteAddressValidator.sol +++ b/contracts/interfaces/IRemoteAddressValidator.sol @@ -13,8 +13,6 @@ interface IRemoteAddressValidator { event TrustedAddressAdded(string souceChain, string sourceAddress); event TrustedAddressRemoved(string souceChain); - event GatewaySupportedChainAdded(string chain); - event GatewaySupportedChainRemoved(string chain); /** * @notice Returns the interchain token address @@ -58,22 +56,4 @@ interface IRemoteAddressValidator { * @return remoteAddress Interchain token service address for the specified chain */ function getRemoteAddress(string calldata chainName) external view returns (string memory remoteAddress); - - /** - * @notice Returns true if the gateway delivers token to this chain. - * @param chainName Name of the chain - */ - function supportedByGateway(string calldata chainName) external view returns (bool); - - /** - * @dev Adds chains that are supported by the Axelar gateway - * @param chainNames List of chain names to be added as supported - */ - function addGatewaySupportedChains(string[] calldata chainNames) external; - - /** - * @dev Removes chains that are no longer supported by the Axelar gateway - * @param chainNames List of chain names to be removed as supported - */ - function removeGatewaySupportedChains(string[] calldata chainNames) external; } diff --git a/contracts/proxies/TokenManagerProxy.sol b/contracts/proxies/TokenManagerProxy.sol index 69adaf23..56544890 100644 --- a/contracts/proxies/TokenManagerProxy.sol +++ b/contracts/proxies/TokenManagerProxy.sol @@ -7,8 +7,7 @@ import { ITokenManagerProxy } from '../interfaces/ITokenManagerProxy.sol'; /** * @title TokenManagerProxy - * @dev This contract is a proxy for token manager contracts. It implements ITokenManagerProxy and - * inherits from FixedProxy from the gmp sdk repo + * @dev This contract is a proxy for token manager contracts. It implements ITokenManagerProxy. */ contract TokenManagerProxy is ITokenManagerProxy { IInterchainTokenService public immutable interchainTokenService; diff --git a/contracts/remote-address-validator/RemoteAddressValidator.sol b/contracts/remote-address-validator/RemoteAddressValidator.sol index d3ce7648..75881d9e 100644 --- a/contracts/remote-address-validator/RemoteAddressValidator.sol +++ b/contracts/remote-address-validator/RemoteAddressValidator.sol @@ -15,7 +15,6 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { mapping(string => bytes32) public remoteAddressHashes; mapping(string => string) public remoteAddresses; - mapping(string => bool) public supportedByGateway; string public chainName; address public immutable interchainTokenServiceAddress; @@ -150,37 +149,6 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { emit TrustedAddressRemoved(sourceChain); } - /** - * @dev Adds chains that are supported by the Axelar gateway - * @param chainNames List of chain names to be added as supported - */ - function addGatewaySupportedChains(string[] calldata chainNames) external onlyOwner { - uint256 length = chainNames.length; - string calldata chainName_; - for (uint256 i; i < length; ++i) { - chainName_ = chainNames[i]; - supportedByGateway[chainName_] = true; - - emit GatewaySupportedChainAdded(chainName_); - } - } - - /** - * @dev Removes chains that are no longer supported by the Axelar gateway - * @param chainNames List of chain names to be removed as supported - */ - function removeGatewaySupportedChains(string[] calldata chainNames) external onlyOwner { - uint256 length = chainNames.length; - string calldata chainName_; - - for (uint256 i; i < length; ++i) { - chainName_ = chainNames[i]; - supportedByGateway[chainName_] = false; - - emit GatewaySupportedChainRemoved(chainName_); - } - } - /** * @dev Fetches the interchain token service address for the specified chain * @param chainName_ Name of the chain diff --git a/contracts/token-manager/TokenManager.sol b/contracts/token-manager/TokenManager.sol index 44788454..0a9684c5 100644 --- a/contracts/token-manager/TokenManager.sol +++ b/contracts/token-manager/TokenManager.sol @@ -167,8 +167,8 @@ abstract contract TokenManager is ITokenManager, Operatable, FlowLimit, Implemen * @return the amount of token actually given, which will onle be differen than `amount` in cases where the token takes some on-transfer fee. */ function giveToken(address destinationAddress, uint256 amount) external onlyService returns (uint256) { - amount = _giveToken(destinationAddress, amount); _addFlowIn(amount); + amount = _giveToken(destinationAddress, amount); return amount; } diff --git a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol index a5d37373..cfcd8c21 100644 --- a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol +++ b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol @@ -28,7 +28,7 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage, NoReEntrancy, constructor(address interchainTokenService_) TokenManagerAddressStorage(interchainTokenService_) {} function implementationType() external pure returns (uint256) { - return 3; + return uint256(TokenManagerType.LIQUIDITY_POOL); } /** @@ -83,8 +83,11 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage, NoReEntrancy, SafeTokenTransferFrom.safeTransferFrom(token, from, liquidityPool_, amount); - // Note: This allows support for fee-on-transfer tokens - return IERC20(token).balanceOf(liquidityPool_) - balance; + uint256 diff = token.balanceOf(liquidityPool_) - balance; + if (diff < amount) { + amount = diff; + } + return amount; } /** @@ -98,8 +101,12 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage, NoReEntrancy, uint256 balance = IERC20(token).balanceOf(to); SafeTokenTransferFrom.safeTransferFrom(token, liquidityPool(), to, amount); - - return IERC20(token).balanceOf(to) - balance; + + uint256 diff = token.balanceOf(to) - balance; + if (diff < amount) { + amount = diff; + } + return amount; } /** diff --git a/contracts/token-manager/implementations/TokenManagerLockUnlock.sol b/contracts/token-manager/implementations/TokenManagerLockUnlock.sol index 22e673fa..e9d9fb0f 100644 --- a/contracts/token-manager/implementations/TokenManagerLockUnlock.sol +++ b/contracts/token-manager/implementations/TokenManagerLockUnlock.sol @@ -23,7 +23,7 @@ contract TokenManagerLockUnlock is TokenManagerAddressStorage, ITokenManagerLock constructor(address interchainTokenService_) TokenManagerAddressStorage(interchainTokenService_) {} function implementationType() external pure returns (uint256) { - return 0; + return uint256(TokenManagerType.LOCK_UNLOCK); } /** diff --git a/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol b/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol index 6c380802..4d8b5142 100644 --- a/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol +++ b/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol @@ -24,7 +24,7 @@ contract TokenManagerLockUnlockFee is TokenManagerAddressStorage, NoReEntrancy, constructor(address interchainTokenService_) TokenManagerAddressStorage(interchainTokenService_) {} function implementationType() external pure returns (uint256) { - return 2; + return uint256(TokenManagerType.LOCK_UNLOCK_FEE_ON_TRANSFER); } /** @@ -68,7 +68,11 @@ contract TokenManagerLockUnlockFee is TokenManagerAddressStorage, NoReEntrancy, SafeTokenTransfer.safeTransfer(token, to, amount); - return IERC20(token).balanceOf(to) - balance; + uint256 diff = token.balanceOf(to) - balance; + if (diff < amount) { + amount = diff; + } + return amount; } /** diff --git a/contracts/token-manager/implementations/TokenManagerMintBurn.sol b/contracts/token-manager/implementations/TokenManagerMintBurn.sol index 3bdb155c..f226b4b7 100644 --- a/contracts/token-manager/implementations/TokenManagerMintBurn.sol +++ b/contracts/token-manager/implementations/TokenManagerMintBurn.sol @@ -24,7 +24,7 @@ contract TokenManagerMintBurn is TokenManagerAddressStorage, ITokenManagerMintBu constructor(address interchainTokenService_) TokenManagerAddressStorage(interchainTokenService_) {} function implementationType() external pure returns (uint256) { - return 1; + return uint256(TokenManagerType.MINT_BURN); } /** diff --git a/contracts/utils/Distributable.sol b/contracts/utils/Distributable.sol index 4f930f29..2d59b83e 100644 --- a/contracts/utils/Distributable.sol +++ b/contracts/utils/Distributable.sol @@ -75,6 +75,7 @@ contract Distributable is IDistributable { address proposedDistributor; assembly { proposedDistributor := sload(PROPOSED_DISTRIBUTOR_SLOT) + sstore(PROPOSED_DISTRIBUTOR_SLOT, 0) } if (msg.sender != proposedDistributor) revert NotProposedDistributor(); _setDistributor(proposedDistributor); diff --git a/contracts/utils/ExpressCallHandler.sol b/contracts/utils/ExpressCallHandler.sol index 496cc617..cf2a61fc 100644 --- a/contracts/utils/ExpressCallHandler.sol +++ b/contracts/utils/ExpressCallHandler.sol @@ -13,7 +13,7 @@ contract ExpressCallHandler is IExpressCallHandler { // uint256(keccak256('prefix-express-give-token')); uint256 internal constant PREFIX_EXPRESS_RECEIVE_TOKEN = 0x67c7b41c1cb0375e36084c4ec399d005168e83425fa471b9224f6115af865619; // uint256(keccak256('prefix-express-give-token-with-data')); - uint256 internal constant PREFIX_EXPRESS_RECEIVE_TOKEN_WITH_DATA = 0x3e607cc12a253b1d9f677a03d298ad869a90a8ba4bd0fb5739e7d79db7cdeaad; + uint256 internal constant PREFIX_EXPRESS_RECEIVE_TOKEN_WITH_DATA = 0x3e607cc12a253b1d9f677a03d298ad869a90a8ba4bd0fb5739e7d79db7cdeaaf; /** * @notice Calculates the unique slot for a given express token transfer. diff --git a/contracts/utils/NoReEntrancy.sol b/contracts/utils/NoReEntrancy.sol index 0a1e1225..16d81bbc 100644 --- a/contracts/utils/NoReEntrancy.sol +++ b/contracts/utils/NoReEntrancy.sol @@ -14,11 +14,11 @@ contract NoReEntrancy is INoReEntrancy { uint256 internal constant ENTERED_SLOT = 0x01f33dd720a8dea3c4220dc5074a2239fb442c4c775306a696f97a7c54f785fc; /** - * @notice A modifier that throws a Paused custom error if the contract is paused - * @dev This modifier should be used with functions that can be paused + * @notice A modifier that throws a ReEntrancy custom error if the contract is entered + * @dev This modifier should be used with functions that can be entered twice */ modifier noReEntrancy() { - if (_hasEntered()) revert ReEntrancy(); + if (hasEntered()) revert ReEntrancy(); _setEntered(true); _; _setEntered(false); @@ -28,7 +28,7 @@ contract NoReEntrancy is INoReEntrancy { * @notice Check if the contract is already executing. * @return entered A boolean representing the entered status. True if already executing, false otherwise. */ - function _hasEntered() internal view returns (bool entered) { + function hasEntered() public view returns (bool entered) { assembly { entered := sload(ENTERED_SLOT) } diff --git a/contracts/utils/Operatable.sol b/contracts/utils/Operatable.sol index 5ca7537e..203b9957 100644 --- a/contracts/utils/Operatable.sol +++ b/contracts/utils/Operatable.sol @@ -12,7 +12,7 @@ import { IOperatable } from '../interfaces/IOperatable.sol'; */ contract Operatable is IOperatable { // uint256(keccak256('operator')) - 1 - uint256 internal constant OPERATOR_SLOT = 0xf23ec0bb4210edd5cba85afd05127efcd2fc6a781bfed49188da1081670b22d7; + uint256 internal constant OPERATOR_SLOT = 0x46a52cf33029de9f84853745a87af28464c80bf0346df1b32e205fc73319f621; // uint256(keccak256('proposed-operator')) - 1 uint256 internal constant PROPOSED_OPERATOR_SLOT = 0x18dd7104fe20f6107b1523000995e8f87ac02b734a65cf0a45fafa7635a2c526; @@ -74,6 +74,7 @@ contract Operatable is IOperatable { address proposedOperator; assembly { proposedOperator := sload(PROPOSED_OPERATOR_SLOT) + sstore(PROPOSED_OPERATOR_SLOT, 0) } if (msg.sender != proposedOperator) revert NotProposedOperator(); _setOperator(proposedOperator); diff --git a/test/RemoteAddressValidator.js b/test/RemoteAddressValidator.js index a0b1c04a..6415bc77 100644 --- a/test/RemoteAddressValidator.js +++ b/test/RemoteAddressValidator.js @@ -126,36 +126,4 @@ describe('RemoteAddressValidator', () => { expect(await remoteAddressValidator.validateSender(otherChain, otherRemoteAddress)).to.equal(false); expect(await remoteAddressValidator.validateSender(otherChain, interchainTokenServiceAddress)).to.equal(true); }); - - it('Should have chains as not gateway supported by default', async () => { - expect(await remoteAddressValidator.supportedByGateway(otherChain)).to.equal(false); - }); - - it('Should not be able to add a chain as gateway supported as not the onwer', async () => { - await expect(remoteAddressValidator.connect(otherWallet).addGatewaySupportedChains([otherChain])).to.be.revertedWithCustomError( - remoteAddressValidator, - 'NotOwner', - ); - }); - - it('Should be able to add a chain as gateway supported as the onwer', async () => { - await expect(remoteAddressValidator.addGatewaySupportedChains([otherChain])) - .to.emit(remoteAddressValidator, 'GatewaySupportedChainAdded') - .withArgs(otherChain); - expect(await remoteAddressValidator.supportedByGateway(otherChain)).to.equal(true); - }); - - it('Should not be able to remove a chain as gateway supported as not the onwer', async () => { - await expect(remoteAddressValidator.connect(otherWallet).removeGatewaySupportedChains([otherChain])).to.be.revertedWithCustomError( - remoteAddressValidator, - 'NotOwner', - ); - }); - - it('Should be able to remove a chain as gateway supported as the onwer', async () => { - await expect(remoteAddressValidator.removeGatewaySupportedChains([otherChain])) - .to.emit(remoteAddressValidator, 'GatewaySupportedChainRemoved') - .withArgs(otherChain); - expect(await remoteAddressValidator.supportedByGateway(otherChain)).to.equal(false); - }); }); From 6ee5d06baa5620ef5b73558d6e545655412e0700 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 23 Aug 2023 15:56:46 +0300 Subject: [PATCH 42/54] prettier --- contracts/interchain-token-service/InterchainTokenService.sol | 3 +-- .../implementations/TokenManagerLiquidityPool.sol | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 318f69d9..6b8cb27c 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -443,9 +443,8 @@ contract InterchainTokenService is SafeTokenTransferFrom.safeTransferFrom(token, caller, destinationAddress, amount); _setExpressReceiveTokenWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId, caller); - - _expressExecuteWithInterchainTokenToken(tokenId, destinationAddress, sourceChain, sourceAddress, data, amount); + _expressExecuteWithInterchainTokenToken(tokenId, destinationAddress, sourceChain, sourceAddress, data, amount); } /*********************\ diff --git a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol index cfcd8c21..081d56b5 100644 --- a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol +++ b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol @@ -101,7 +101,7 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage, NoReEntrancy, uint256 balance = IERC20(token).balanceOf(to); SafeTokenTransferFrom.safeTransferFrom(token, liquidityPool(), to, amount); - + uint256 diff = token.balanceOf(to) - balance; if (diff < amount) { amount = diff; From 5acf4a8287639d9bf8d5e4b3cbb35ea5104be82e Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 28 Aug 2023 17:52:10 +0300 Subject: [PATCH 43/54] added interchain transfer methods to the service and unified receiving tokens a bit. --- .../InterchainTokenService.sol | 206 ++++++++-------- contracts/interfaces/IExpressCallHandler.sol | 69 +----- .../interfaces/IInterchainTokenService.sol | 43 ++-- contracts/interfaces/ITokenManager.sol | 8 + contracts/test/InterchainExecutableTest.sol | 2 +- .../test/utils/ExpressCallHandlerTest.sol | 47 +--- contracts/token-manager/TokenManager.sol | 12 + contracts/utils/ExpressCallHandler.sol | 220 ++---------------- test/tokenService.js | 82 +++---- test/utils.js | 113 +-------- 10 files changed, 207 insertions(+), 595 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 6b8cb27c..b16df51d 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -396,55 +396,57 @@ contract InterchainTokenService is * @notice Uses the caller's tokens to fullfill a sendCall ahead of time. Use this only if you have detected an outgoing * sendToken that matches the parameters passed here. * @dev This is not to be used with fee on transfer tokens as it will incur losses for the express caller. - * @param tokenId the tokenId of the TokenManager used. - * @param destinationAddress the destinationAddress for the sendToken. - * @param amount the amount of token to give. + * @param payload the payload of the receive token * @param commandId the sendHash detected at the sourceChain. */ - function expressReceiveToken(bytes32 tokenId, address destinationAddress, uint256 amount, bytes32 commandId) external { + function expressReceiveToken(bytes calldata payload, bytes32 commandId, string calldata sourceChain) external { if (gateway.isCommandExecuted(commandId)) revert AlreadyExecuted(commandId); address caller = msg.sender; + _setExpressReceiveToken(payload, commandId, caller); + + (uint256 selector, bytes32 tokenId, bytes memory destinationAddressBytes, uint256 amount) = abi.decode( + payload, + (uint256, bytes32, bytes, uint256) + ); + address destinationAddress = destinationAddressBytes.toAddress(); + ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId)); IERC20 token = IERC20(tokenManager.tokenAddress()); SafeTokenTransferFrom.safeTransferFrom(token, caller, destinationAddress, amount); - _setExpressReceiveToken(tokenId, destinationAddress, amount, commandId, caller); + if(selector == SELECTOR_SEND_TOKEN_WITH_DATA) { + (, , , , bytes memory sourceAddress, bytes memory data) = abi.decode(payload, (uint256, bytes32, bytes, uint256, bytes, bytes)); + IInterchainTokenExpressExecutable(destinationAddress).executeWithInterchainToken(sourceChain, sourceAddress, data, tokenId, amount); + } else if(selector != SELECTOR_SEND_TOKEN) { + revert InvalidExpressSelector(); + } } - /** - * @notice Uses the caller's tokens to fullfill a callContractWithInterchainToken ahead of time. Use this only if you have - * detected an outgoing sendToken that matches the parameters passed here. - * @dev This is not to be used with fee on transfer tokens as it will incur losses for the express caller and it will pass an incorrect amount to the contract. - * @param tokenId the tokenId of the TokenManager used. - * @param sourceChain the name of the chain where the call came from. - * @param sourceAddress the caller of callContractWithInterchainToken. - * @param destinationAddress the destinationAddress for the sendToken. - * @param amount the amount of token to give. - * @param data the data to be passed to destinationAddress after giving them the tokens specified. - * @param commandId the sendHash detected at the sourceChain. - */ - function expressReceiveTokenWithData( + function interchainTransfer( bytes32 tokenId, - string memory sourceChain, - bytes memory sourceAddress, - address destinationAddress, + string calldata destinationChain, + bytes calldata destinationAddress, uint256 amount, - bytes calldata data, - bytes32 commandId + bytes calldata metadata ) external { - if (gateway.isCommandExecuted(commandId)) revert AlreadyExecuted(commandId); - - address caller = msg.sender; - ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId)); - IERC20 token = IERC20(tokenManager.tokenAddress()); - - SafeTokenTransferFrom.safeTransferFrom(token, caller, destinationAddress, amount); - - _setExpressReceiveTokenWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId, caller); + ITokenManager tokenManager = ITokenManager(getTokenManagerAddress(tokenId)); + amount = tokenManager.takeToken(msg.sender, amount); + _transmitSendToken(tokenId, msg.sender, destinationChain, destinationAddress, amount, metadata); + } - _expressExecuteWithInterchainTokenToken(tokenId, destinationAddress, sourceChain, sourceAddress, data, amount); + function sendTokenWithData( + bytes32 tokenId, + string calldata destinationChain, + bytes calldata destinationAddress, + uint256 amount, + bytes calldata data + ) external { + ITokenManager tokenManager = ITokenManager(getTokenManagerAddress(tokenId)); + amount = tokenManager.takeToken(msg.sender, amount); + uint32 prefix = 0; + _transmitSendToken(tokenId, msg.sender, destinationChain, destinationAddress, amount, abi.encodePacked(prefix, data)); } /*********************\ @@ -468,19 +470,7 @@ contract InterchainTokenService is uint256 amount, bytes calldata metadata ) external payable onlyTokenManager(tokenId) notPaused { - bytes memory payload; - if (metadata.length < 4) { - payload = abi.encode(SELECTOR_SEND_TOKEN, tokenId, destinationAddress, amount); - _callContract(destinationChain, payload, msg.value); - emit TokenSent(tokenId, destinationChain, destinationAddress, amount); - return; - } - uint32 version; - (version, metadata) = _decodeMetadata(metadata); - if (version > 0) revert InvalidMetadataVersion(version); - payload = abi.encode(SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destinationAddress, amount, sourceAddress.toBytes(), metadata); - _callContract(destinationChain, payload, msg.value); - emit TokenSentWithData(tokenId, destinationChain, destinationAddress, amount, sourceAddress, metadata); + _transmitSendToken(tokenId, sourceAddress, destinationChain, destinationAddress, amount, metadata); } /*************\ @@ -538,10 +528,8 @@ contract InterchainTokenService is bytes calldata payload ) internal override onlyRemoteService(sourceChain, sourceAddress) notPaused { uint256 selector = abi.decode(payload, (uint256)); - if (selector == SELECTOR_SEND_TOKEN) { - _processSendTokenPayload(sourceChain, payload); - } else if (selector == SELECTOR_SEND_TOKEN_WITH_DATA) { - _processSendTokenWithDataPayload(sourceChain, payload); + if (selector == SELECTOR_SEND_TOKEN || selector == SELECTOR_SEND_TOKEN_WITH_DATA) { + _processSendTokenPayload(sourceChain, payload, selector); } else if (selector == SELECTOR_DEPLOY_TOKEN_MANAGER) { _processDeployTokenManagerPayload(payload); } else if (selector == SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN) { @@ -556,67 +544,45 @@ contract InterchainTokenService is * @param sourceChain The chain where the transaction originates from * @param payload The encoded data payload to be processed */ - function _processSendTokenPayload(string calldata sourceChain, bytes calldata payload) internal { - (, bytes32 tokenId, bytes memory destinationAddressBytes, uint256 amount) = abi.decode(payload, (uint256, bytes32, bytes, uint256)); - bytes32 commandId; - - assembly { - commandId := calldataload(4) - } - address destinationAddress = destinationAddressBytes.toAddress(); - ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId)); - address expressCaller = _popExpressReceiveToken(tokenId, destinationAddress, amount, commandId); - if (expressCaller == address(0)) { - amount = tokenManager.giveToken(destinationAddress, amount); - emit TokenReceived(tokenId, sourceChain, destinationAddress, amount); - } else { - amount = tokenManager.giveToken(expressCaller, amount); - } - } - - /** - * @notice Processes a send token with data payload. - * @param sourceChain The chain where the transaction originates from - * @param payload The encoded data payload to be processed - */ - function _processSendTokenWithDataPayload(string calldata sourceChain, bytes calldata payload) internal { + function _processSendTokenPayload(string calldata sourceChain, bytes calldata payload, uint256 selector) internal { bytes32 tokenId; - uint256 amount; - bytes memory sourceAddress; - bytes memory data; address destinationAddress; + uint256 amount; + { + bytes memory destinationAddressBytes; + (, tokenId, destinationAddressBytes, amount) = abi.decode(payload, (uint256, bytes32, bytes, uint256)); + destinationAddress = destinationAddressBytes.toAddress(); + } bytes32 commandId; assembly { commandId := calldataload(4) } + ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId)); { - bytes memory destinationAddressBytes; - (, tokenId, destinationAddressBytes, amount, sourceAddress, data) = abi.decode( - payload, - (uint256, bytes32, bytes, uint256, bytes, bytes) - ); - destinationAddress = destinationAddressBytes.toAddress(); - } - ITokenManager tokenManager = ITokenManager(getTokenManagerAddress(tokenId)); - { - address expressCaller = _popExpressReceiveTokenWithData( - tokenId, - sourceChain, - sourceAddress, - destinationAddress, - amount, - data, - commandId - ); + address expressCaller = _popExpressReceiveToken(payload, commandId); if (expressCaller != address(0)) { amount = tokenManager.giveToken(expressCaller, amount); return; } } amount = tokenManager.giveToken(destinationAddress, amount); - IInterchainTokenExpressExecutable(destinationAddress).executeWithInterchainToken(sourceChain, sourceAddress, data, tokenId, amount); - emit TokenReceivedWithData(tokenId, sourceChain, destinationAddress, amount, sourceAddress, data); + if (selector == SELECTOR_SEND_TOKEN_WITH_DATA) { + bytes memory sourceAddress; + bytes memory data; + (, , , , sourceAddress, data) = abi.decode(payload, (uint256, bytes32, bytes, uint256, bytes, bytes)); + + IInterchainTokenExpressExecutable(destinationAddress).executeWithInterchainToken( + sourceChain, + sourceAddress, + data, + tokenId, + amount + ); + emit TokenReceivedWithData(tokenId, sourceChain, destinationAddress, amount, sourceAddress, data); + } else { + emit TokenReceived(tokenId, sourceChain, destinationAddress, amount); + } } /** @@ -833,11 +799,17 @@ contract InterchainTokenService is emit StandardizedTokenDeployed(tokenId, distributor, name, symbol, decimals, mintAmount, mintTo); } - function _decodeMetadata(bytes calldata metadata) internal pure returns (uint32 version, bytes calldata data) { + function _decodeMetadata(bytes memory metadata) internal pure returns (uint32 version, bytes memory data) { + data = new bytes(metadata.length - 4); assembly { - data.length := sub(metadata.length, 4) - data.offset := add(metadata.offset, 4) - version := calldataload(sub(metadata.offset, 28)) + version := shr(224, mload(data)) + } + if(data.length == 0) return (version, data); + uint256 n = (data.length - 1) / 32; + for (uint256 i = 0; i <= n; ++i) { + assembly { + mstore(add(data, add(32, mul(32, i))), mload(add(metadata, add(36, mul(32, i))))) + } } } @@ -857,4 +829,36 @@ contract InterchainTokenService is amount ); } + + /** + * @notice Transmit a sendTokenWithData for the given tokenId. Only callable by a token manager. + * @param tokenId the tokenId of the TokenManager (which must be the msg.sender). + * @param sourceAddress the address where the token is coming from, which will also be used for reimburment of gas. + * @param destinationChain the name of the chain to send tokens to. + * @param destinationAddress the destinationAddress for the sendToken. + * @param amount the amount of token to give. + * @param metadata the data to be passed to the destiantion. + */ + function _transmitSendToken( + bytes32 tokenId, + address sourceAddress, + string calldata destinationChain, + bytes memory destinationAddress, + uint256 amount, + bytes memory metadata + ) internal { + bytes memory payload; + if (metadata.length < 4) { + payload = abi.encode(SELECTOR_SEND_TOKEN, tokenId, destinationAddress, amount); + _callContract(destinationChain, payload, msg.value); + emit TokenSent(tokenId, destinationChain, destinationAddress, amount); + return; + } + uint32 version; + (version, metadata) = _decodeMetadata(metadata); + if (version > 0) revert InvalidMetadataVersion(version); + payload = abi.encode(SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destinationAddress, amount, sourceAddress.toBytes(), metadata); + _callContract(destinationChain, payload, msg.value); + emit TokenSentWithData(tokenId, destinationChain, destinationAddress, amount, sourceAddress, metadata); + } } diff --git a/contracts/interfaces/IExpressCallHandler.sol b/contracts/interfaces/IExpressCallHandler.sol index 8a4ae602..601fdb6e 100644 --- a/contracts/interfaces/IExpressCallHandler.sol +++ b/contracts/interfaces/IExpressCallHandler.sol @@ -6,75 +6,14 @@ interface IExpressCallHandler { error AlreadyExpressCalled(); error SameDestinationAsCaller(); - event ExpressReceive( - bytes32 indexed tokenId, - address indexed destinationAddress, - uint256 amount, - bytes32 indexed sendHash, - address expressCaller - ); - event ExpressExecutionFulfilled( - bytes32 indexed tokenId, - address indexed destinationAddress, - uint256 amount, - bytes32 indexed sendHash, - address expressCaller - ); - - event ExpressReceiveWithData( - bytes32 indexed tokenId, - string sourceChain, - bytes sourceAddress, - address indexed destinationAddress, - uint256 amount, - bytes data, - bytes32 indexed sendHash, - address expressCaller - ); - event ExpressExecutionWithDataFulfilled( - bytes32 indexed tokenId, - string sourceChain, - bytes sourceAddress, - address indexed destinationAddress, - uint256 amount, - bytes data, - bytes32 indexed sendHash, - address expressCaller - ); + event ExpressReceive(bytes payload, bytes32 indexed sendHash, address indexed expressCaller); + event ExpressExecutionFulfilled(bytes payload, bytes32 indexed sendHash, address indexed expressCaller); /** * @notice Gets the address of the express caller for a specific token transfer - * @param tokenId The ID of the token being sent - * @param destinationAddress The address of the recipient - * @param amount The amount of tokens to be sent - * @param commandId The unique hash for this token transfer - * @return expressCaller The address of the express caller for this token transfer - */ - function getExpressReceiveToken( - bytes32 tokenId, - address destinationAddress, - uint256 amount, - bytes32 commandId - ) external view returns (address expressCaller); - - /** - * @notice Gets the address of the express caller for a specific token transfer with data - * @param tokenId The ID of the token being sent - * @param sourceChain The chain from which the token will be sent - * @param sourceAddress The originating address of the token on the source chain - * @param destinationAddress The address of the recipient on the destination chain - * @param amount The amount of tokens to be sent - * @param data The data associated with the token transfer + * @param payload the payload for the receive token * @param commandId The unique hash for this token transfer * @return expressCaller The address of the express caller for this token transfer */ - function getExpressReceiveTokenWithData( - bytes32 tokenId, - string memory sourceChain, - bytes memory sourceAddress, - address destinationAddress, - uint256 amount, - bytes calldata data, - bytes32 commandId - ) external view returns (address expressCaller); + function getExpressReceiveToken(bytes calldata payload, bytes32 commandId) external view returns (address expressCaller); } diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index b25a8551..92ab6db2 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -28,6 +28,7 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx error SelectorUnknown(); error InvalidMetadataVersion(uint32 version); error AlreadyExecuted(bytes32 commandId); + error InvalidExpressSelector(); event TokenSent(bytes32 tokenId, string destinationChain, bytes destinationAddress, uint256 indexed amount); event TokenSentWithData( @@ -226,6 +227,22 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx */ function getImplementation(uint256 tokenManagerType) external view returns (address tokenManagerAddress); + function interchainTransfer( + bytes32 tokenId, + string calldata destinationChain, + bytes calldata destinationAddress, + uint256 amount, + bytes calldata metadata + ) external; + + function sendTokenWithData( + bytes32 tokenId, + string calldata destinationChain, + bytes calldata destinationAddress, + uint256 amount, + bytes calldata data + ) external; + /** * @notice Initiates an interchain token transfer. Only callable by TokenManagers * @param tokenId The tokenId of the token to be transmitted. @@ -280,30 +297,8 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx /** * @notice Uses the caller's tokens to fullfill a sendCall ahead of time. Use this only if you have detected an outgoing sendToken that matches the parameters passed here. - * @param tokenId the tokenId of the TokenManager used. - * @param destinationAddress the destinationAddress for the sendToken. - * @param amount the amount of token to give. + * @param payload the payload of the receive token * @param commandId the commandId calculated from the event at the sourceChain. */ - function expressReceiveToken(bytes32 tokenId, address destinationAddress, uint256 amount, bytes32 commandId) external; - - /** - * @notice Uses the caller's tokens to fullfill a callContractWithInterchainToken ahead of time. Use this only if you have detected an outgoing sendToken that matches the parameters passed here. - * @param tokenId the tokenId of the TokenManager used. - * @param sourceChain the name of the chain where the call came from. - * @param sourceAddress the caller of callContractWithInterchainToken. - * @param destinationAddress the destinationAddress for the sendToken. - * @param amount the amount of token to give. - * @param data the data to be passed to destinationAddress after giving them the tokens specified. - * @param commandId the commandId calculated from the event at the sourceChain. - */ - function expressReceiveTokenWithData( - bytes32 tokenId, - string memory sourceChain, - bytes memory sourceAddress, - address destinationAddress, - uint256 amount, - bytes calldata data, - bytes32 commandId - ) external; + function expressReceiveToken(bytes calldata payload, bytes32 commandId, string calldata sourceChain) external; } diff --git a/contracts/interfaces/ITokenManager.sol b/contracts/interfaces/ITokenManager.sol index 832b1473..e3f2acc3 100644 --- a/contracts/interfaces/ITokenManager.sol +++ b/contracts/interfaces/ITokenManager.sol @@ -85,6 +85,14 @@ interface ITokenManager is ITokenManagerType, IOperatable, IFlowLimit, IImplemen */ function giveToken(address destinationAddress, uint256 amount) external returns (uint256); + /** + * @notice This function takes token to from a specified address. Can only be called by the service. + * @param sourceAddress the address to take tokens from. + * @param amount the amount of token to take. + * @return the amount of token actually taken, which will onle be differen than `amount` in cases where the token takes some on-transfer fee. + */ + function takeToken(address sourceAddress, uint256 amount) external returns (uint256); + /** * @notice This function sets the flow limit for this TokenManager. Can only be called by the operator. * @param flowLimit the maximum difference between the tokens flowing in and/or out at any given interval of time (6h) diff --git a/contracts/test/InterchainExecutableTest.sol b/contracts/test/InterchainExecutableTest.sol index 1f7ac97d..bf952cb0 100644 --- a/contracts/test/InterchainExecutableTest.sol +++ b/contracts/test/InterchainExecutableTest.sol @@ -23,7 +23,7 @@ contract InterchainExecutableTest is InterchainTokenExpressExecutable { (address receiver, string memory message) = abi.decode(data, (address, string)); lastMessage = message; address tokenAddress = IInterchainTokenService(msg.sender).getTokenAddress(tokenId); - IERC20(tokenAddress).transfer(receiver, amount); emit MessageReceived(sourceChain, sourceAddress, receiver, message, tokenId, amount); + IERC20(tokenAddress).transfer(receiver, amount); } } diff --git a/contracts/test/utils/ExpressCallHandlerTest.sol b/contracts/test/utils/ExpressCallHandlerTest.sol index d8c101d8..92cb8b12 100644 --- a/contracts/test/utils/ExpressCallHandlerTest.sol +++ b/contracts/test/utils/ExpressCallHandlerTest.sol @@ -7,50 +7,11 @@ import { ExpressCallHandler } from '../../utils/ExpressCallHandler.sol'; contract ExpressCallHandlerTest is ExpressCallHandler { address public lastPoppedExpressCaller; - function setExpressReceiveToken( - bytes32 tokenId, - address destinationAddress, - uint256 amount, - bytes32 commandId, - address expressCaller - ) external { - _setExpressReceiveToken(tokenId, destinationAddress, amount, commandId, expressCaller); + function setExpressReceiveToken(bytes calldata payload, bytes32 commandId, address expressCaller) external { + _setExpressReceiveToken(payload, commandId, expressCaller); } - function setExpressReceiveTokenWithData( - bytes32 tokenId, - string memory sourceChain, - bytes memory sourceAddress, - address destinationAddress, - uint256 amount, - bytes calldata data, - bytes32 commandId, - address expressCaller - ) external { - _setExpressReceiveTokenWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId, expressCaller); - } - - function popExpressReceiveToken(bytes32 tokenId, address destinationAddress, uint256 amount, bytes32 commandId) external { - lastPoppedExpressCaller = _popExpressReceiveToken(tokenId, destinationAddress, amount, commandId); - } - - function popExpressReceiveTokenWithData( - bytes32 tokenId, - string memory sourceChain, - bytes memory sourceAddress, - address destinationAddress, - uint256 amount, - bytes memory data, - bytes32 commandId - ) external { - lastPoppedExpressCaller = _popExpressReceiveTokenWithData( - tokenId, - sourceChain, - sourceAddress, - destinationAddress, - amount, - data, - commandId - ); + function popExpressReceiveToken(bytes calldata payload, bytes32 commandId) external { + lastPoppedExpressCaller = _popExpressReceiveToken(payload, commandId); } } diff --git a/contracts/token-manager/TokenManager.sol b/contracts/token-manager/TokenManager.sol index 0a9684c5..df8bfecd 100644 --- a/contracts/token-manager/TokenManager.sol +++ b/contracts/token-manager/TokenManager.sol @@ -172,6 +172,18 @@ abstract contract TokenManager is ITokenManager, Operatable, FlowLimit, Implemen return amount; } + /** + * @notice This function gives token to a specified address. Can only be called by the service. + * @param sourceAddress the address to give tokens to. + * @param amount the amount of token to give. + * @return the amount of token actually given, which will onle be differen than `amount` in cases where the token takes some on-transfer fee. + */ + function takeToken(address sourceAddress, uint256 amount) external onlyService returns (uint256) { + _addFlowOut(amount); + amount = _takeToken(sourceAddress, amount); + return amount; + } + /** * @notice This function sets the flow limit for this TokenManager. Can only be called by the operator. * @param flowLimit the maximum difference between the tokens flowing in and/or out at any given interval of time (6h) diff --git a/contracts/utils/ExpressCallHandler.sol b/contracts/utils/ExpressCallHandler.sol index cf2a61fc..02a32732 100644 --- a/contracts/utils/ExpressCallHandler.sol +++ b/contracts/utils/ExpressCallHandler.sol @@ -12,120 +12,25 @@ import { IExpressCallHandler } from '../interfaces/IExpressCallHandler.sol'; contract ExpressCallHandler is IExpressCallHandler { // uint256(keccak256('prefix-express-give-token')); uint256 internal constant PREFIX_EXPRESS_RECEIVE_TOKEN = 0x67c7b41c1cb0375e36084c4ec399d005168e83425fa471b9224f6115af865619; - // uint256(keccak256('prefix-express-give-token-with-data')); - uint256 internal constant PREFIX_EXPRESS_RECEIVE_TOKEN_WITH_DATA = 0x3e607cc12a253b1d9f677a03d298ad869a90a8ba4bd0fb5739e7d79db7cdeaaf; /** * @notice Calculates the unique slot for a given express token transfer. - * @param tokenId The ID of the token being sent - * @param destinationAddress The address of the recipient - * @param amount The amount of tokens to be sent + * @param payload the payload of the receive token * @param commandId The unique hash for this token transfer * @return slot The calculated slot for this token transfer */ - function _getExpressReceiveTokenSlot( - bytes32 tokenId, - address destinationAddress, - uint256 amount, - bytes32 commandId - ) internal pure returns (uint256 slot) { - slot = uint256(keccak256(abi.encode(PREFIX_EXPRESS_RECEIVE_TOKEN, tokenId, destinationAddress, amount, commandId))); - } - - /** - * @notice Calculates the unique slot for a given token transfer with data - * @param tokenId The ID of the token being sent - * @param sourceChain The chain from which the token will be sent - * @param sourceAddress The originating address of the token on the source chain - * @param destinationAddress The address of the recipient on the destination chain - * @param amount The amount of tokens to be sent - * @param data The data associated with the token transfer - * @param commandId The unique hash for this token transfer - * @return slot The calculated slot for this token transfer - */ - function _getExpressReceiveTokenWithDataSlot( - bytes32 tokenId, - string memory sourceChain, - bytes memory sourceAddress, - address destinationAddress, - uint256 amount, - bytes memory data, - bytes32 commandId - ) internal pure returns (uint256 slot) { - slot = uint256( - keccak256( - abi.encode( - PREFIX_EXPRESS_RECEIVE_TOKEN_WITH_DATA, - tokenId, - sourceChain, - sourceAddress, - destinationAddress, - amount, - data, - commandId - ) - ) - ); + function _getExpressReceiveTokenSlot(bytes calldata payload, bytes32 commandId) internal pure returns (uint256 slot) { + slot = uint256(keccak256(abi.encode(PREFIX_EXPRESS_RECEIVE_TOKEN, payload, commandId))); } /** * @notice Stores the address of the express caller at the storage slot determined by _getExpressSendTokenSlot - * @param tokenId The ID of the token being sent - * @param destinationAddress The address of the recipient - * @param amount The amount of tokens to be sent - * @param commandId The unique hash for this token transfer - * @param expressCaller The address of the express caller - */ - function _setExpressReceiveToken( - bytes32 tokenId, - address destinationAddress, - uint256 amount, - bytes32 commandId, - address expressCaller - ) internal { - uint256 slot = _getExpressReceiveTokenSlot(tokenId, destinationAddress, amount, commandId); - address prevExpressCaller; - assembly { - prevExpressCaller := sload(slot) - } - if (prevExpressCaller != address(0)) revert AlreadyExpressCalled(); - assembly { - sstore(slot, expressCaller) - } - emit ExpressReceive(tokenId, destinationAddress, amount, commandId, expressCaller); - } - - /** - * @notice Stores the address of the express caller for a given token transfer with data at - * the storage slot determined by _getExpressSendTokenWithDataSlot - * @param tokenId The ID of the token being sent - * @param sourceChain The chain from which the token will be sent - * @param sourceAddress The originating address of the token on the source chain - * @param destinationAddress The address of the recipient on the destination chain - * @param amount The amount of tokens to be sent - * @param data The data associated with the token transfer + * @param payload The payload for the receive token * @param commandId The unique hash for this token transfer * @param expressCaller The address of the express caller */ - function _setExpressReceiveTokenWithData( - bytes32 tokenId, - string memory sourceChain, - bytes memory sourceAddress, - address destinationAddress, - uint256 amount, - bytes calldata data, - bytes32 commandId, - address expressCaller - ) internal { - uint256 slot = _getExpressReceiveTokenWithDataSlot( - tokenId, - sourceChain, - sourceAddress, - destinationAddress, - amount, - data, - commandId - ); + function _setExpressReceiveToken(bytes calldata payload, bytes32 commandId, address expressCaller) internal { + uint256 slot = _getExpressReceiveTokenSlot(payload, commandId); address prevExpressCaller; assembly { prevExpressCaller := sload(slot) @@ -134,58 +39,17 @@ contract ExpressCallHandler is IExpressCallHandler { assembly { sstore(slot, expressCaller) } - emit ExpressReceiveWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId, expressCaller); + emit ExpressReceive(payload, commandId, expressCaller); } /** * @notice Gets the address of the express caller for a specific token transfer - * @param tokenId The ID of the token being sent - * @param destinationAddress The address of the recipient - * @param amount The amount of tokens to be sent + * @param payload The payload for the receive token * @param commandId The unique hash for this token transfer * @return expressCaller The address of the express caller for this token transfer */ - function getExpressReceiveToken( - bytes32 tokenId, - address destinationAddress, - uint256 amount, - bytes32 commandId - ) public view returns (address expressCaller) { - uint256 slot = _getExpressReceiveTokenSlot(tokenId, destinationAddress, amount, commandId); - assembly { - expressCaller := sload(slot) - } - } - - /** - * @notice Gets the address of the express caller for a specific token transfer with data - * @param tokenId The ID of the token being sent - * @param sourceChain The chain from which the token will be sent - * @param sourceAddress The originating address of the token on the source chain - * @param destinationAddress The address of the recipient on the destination chain - * @param amount The amount of tokens to be sent - * @param data The data associated with the token transfer - * @param commandId The unique hash for this token transfer - * @return expressCaller The address of the express caller for this token transfer - */ - function getExpressReceiveTokenWithData( - bytes32 tokenId, - string memory sourceChain, - bytes memory sourceAddress, - address destinationAddress, - uint256 amount, - bytes calldata data, - bytes32 commandId - ) public view returns (address expressCaller) { - uint256 slot = _getExpressReceiveTokenWithDataSlot( - tokenId, - sourceChain, - sourceAddress, - destinationAddress, - amount, - data, - commandId - ); + function getExpressReceiveToken(bytes calldata payload, bytes32 commandId) public view returns (address expressCaller) { + uint256 slot = _getExpressReceiveTokenSlot(payload, commandId); assembly { expressCaller := sload(slot) } @@ -193,59 +57,12 @@ contract ExpressCallHandler is IExpressCallHandler { /** * @notice Removes the express caller from storage for a specific token transfer, if it exists. - * @param tokenId The ID of the token being sent - * @param destinationAddress The address of the recipient - * @param amount The amount of tokens to be sent - * @param commandId The unique hash for this token transfer - * @return expressCaller The address of the express caller for this token transfer - */ - function _popExpressReceiveToken( - bytes32 tokenId, - address destinationAddress, - uint256 amount, - bytes32 commandId - ) internal returns (address expressCaller) { - uint256 slot = _getExpressReceiveTokenSlot(tokenId, destinationAddress, amount, commandId); - assembly { - expressCaller := sload(slot) - } - if (expressCaller != address(0)) { - assembly { - sstore(slot, 0) - } - emit ExpressExecutionFulfilled(tokenId, destinationAddress, amount, commandId, expressCaller); - } - } - - /** - * @notice Removes the express caller from storage for a specific token transfer with data, if it exists. - * @param tokenId The ID of the token being sent - * @param sourceChain The chain from which the token will be sent - * @param sourceAddress The originating address of the token on the source chain - * @param destinationAddress The address of the recipient on the destination chain - * @param amount The amount of tokens to be sent - * @param data The data associated with the token transfer + * @param payload the payload for the receive token * @param commandId The unique hash for this token transfer * @return expressCaller The address of the express caller for this token transfer */ - function _popExpressReceiveTokenWithData( - bytes32 tokenId, - string memory sourceChain, - bytes memory sourceAddress, - address destinationAddress, - uint256 amount, - bytes memory data, - bytes32 commandId - ) internal returns (address expressCaller) { - uint256 slot = _getExpressReceiveTokenWithDataSlot( - tokenId, - sourceChain, - sourceAddress, - destinationAddress, - amount, - data, - commandId - ); + function _popExpressReceiveToken(bytes calldata payload, bytes32 commandId) internal returns (address expressCaller) { + uint256 slot = _getExpressReceiveTokenSlot(payload, commandId); assembly { expressCaller := sload(slot) } @@ -253,16 +70,7 @@ contract ExpressCallHandler is IExpressCallHandler { assembly { sstore(slot, 0) } - emit ExpressExecutionWithDataFulfilled( - tokenId, - sourceChain, - sourceAddress, - destinationAddress, - amount, - data, - commandId, - expressCaller - ); + emit ExpressExecutionFulfilled(payload, commandId, expressCaller); } } } diff --git a/test/tokenService.js b/test/tokenService.js index 9b856885..3fd8b5da 100644 --- a/test/tokenService.js +++ b/test/tokenService.js @@ -1215,19 +1215,21 @@ describe('Interchain Token Service', () => { }); it('Should express execute', async () => { - await expect(service.expressReceiveToken(tokenId, destinationAddress, amount, commandId)) + const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'uint256'], [SELECTOR_SEND_TOKEN, tokenId, destinationAddress, amount]); + await expect(service.expressReceiveToken(payload, commandId, sourceChain)) .to.emit(service, 'ExpressReceive') - .withArgs(tokenId, destinationAddress, amount, commandId, wallet.address) + .withArgs(payload, commandId, wallet.address) .and.to.emit(token, 'Transfer') .withArgs(wallet.address, destinationAddress, amount); }); it('Should express execute with token', async () => { + const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'uint256', 'bytes',' bytes'], [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, executable.address, amount, sourceAddress, data]); await expect( - service.expressReceiveTokenWithData(tokenId, sourceChain, sourceAddress, executable.address, amount, data, commandId), + service.expressReceiveToken(payload, commandId, sourceChain), ) - .to.emit(service, 'ExpressReceiveWithData') - .withArgs(tokenId, sourceChain, sourceAddress, executable.address, amount, data, commandId, wallet.address) + .to.emit(service, 'ExpressReceive') + .withArgs(payload, commandId, wallet.address) .and.to.emit(token, 'Transfer') .withArgs(wallet.address, executable.address, amount) .and.to.emit(token, 'Transfer') @@ -1257,13 +1259,13 @@ describe('Interchain Token Service', () => { ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - await (await service.expressReceiveToken(tokenId, destAddress, amount, commandId)).wait(); + await (await service.expressReceiveToken(payload, commandId, sourceChain)).wait(); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(token, 'Transfer') .withArgs(tokenManager.address, wallet.address, amount) .and.to.emit(service, 'ExpressExecutionFulfilled') - .withArgs(tokenId, destAddress, amount, commandId, wallet.address); + .withArgs(payload, commandId, wallet.address); }); it('Should be able to receive mint/burn token', async () => { @@ -1277,13 +1279,13 @@ describe('Interchain Token Service', () => { ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - await (await service.expressReceiveToken(tokenId, destAddress, amount, commandId)).wait(); + await (await service.expressReceiveToken(payload, commandId, sourceChain)).wait(); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(token, 'Transfer') .withArgs(AddressZero, wallet.address, amount) .and.to.emit(service, 'ExpressExecutionFulfilled') - .withArgs(tokenId, destAddress, amount, commandId, wallet.address); + .withArgs(payload, commandId, wallet.address); }); it('Should be able to receive lock/unlock with fee on transfer token', async () => { @@ -1297,13 +1299,13 @@ describe('Interchain Token Service', () => { ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - await (await service.expressReceiveToken(tokenId, destAddress, amount, commandId)).wait(); + await (await service.expressReceiveToken(payload, commandId, sourceChain)).wait(); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(token, 'Transfer') .withArgs(tokenManager.address, wallet.address, amount) .and.to.emit(service, 'ExpressExecutionFulfilled') - .withArgs(tokenId, destAddress, amount, commandId, wallet.address); + .withArgs(payload, commandId, wallet.address); }); it('Should be able to receive liquidity pool token', async () => { @@ -1317,13 +1319,13 @@ describe('Interchain Token Service', () => { ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - await (await service.expressReceiveToken(tokenId, destAddress, amount, commandId)).wait(); + await (await service.expressReceiveToken(payload, commandId, sourceChain)).wait(); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(token, 'Transfer') .withArgs(liquidityPool.address, wallet.address, amount) .and.to.emit(service, 'ExpressExecutionFulfilled') - .withArgs(tokenId, destAddress, amount, commandId, wallet.address); + .withArgs(payload, commandId, wallet.address); }); }); @@ -1354,23 +1356,15 @@ describe('Interchain Token Service', () => { ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - await ( - await service.expressReceiveTokenWithData( - tokenId, - sourceChain, - sourceAddressForService, - destAddress, - amount, - data, - commandId, - ) - ).wait(); + await (await service.expressReceiveToken(payload, commandId, sourceChain)).wait(); - await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) + const tx = service.execute(commandId, sourceChain, sourceAddress, payload); + await expect(tx) .to.emit(token, 'Transfer') .withArgs(tokenManager.address, wallet.address, amount) - .and.to.emit(service, 'ExpressExecutionWithDataFulfilled') - .withArgs(tokenId, sourceChain, sourceAddressForService, destAddress, amount, data, commandId, wallet.address); + .and.to.emit(service, 'ExpressExecutionFulfilled') + .withArgs(payload, commandId, wallet.address) + expect(await executable.lastMessage()).to.equal(msg); }); @@ -1388,23 +1382,13 @@ describe('Interchain Token Service', () => { const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - await ( - await service.expressReceiveTokenWithData( - tokenId, - sourceChain, - sourceAddressForService, - destAddress, - amount, - data, - commandId, - ) - ).wait(); + await (await service.expressReceiveToken(payload, commandId, sourceChain)).wait(); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(token, 'Transfer') .withArgs(AddressZero, wallet.address, amount) - .and.to.emit(service, 'ExpressExecutionWithDataFulfilled') - .withArgs(tokenId, sourceChain, sourceAddressForService, destAddress, amount, data, commandId, wallet.address); + .and.to.emit(service, 'ExpressExecutionFulfilled') + .withArgs(payload, commandId, wallet.address); expect(await executable.lastMessage()).to.equal(msg); }); @@ -1423,7 +1407,7 @@ describe('Interchain Token Service', () => { const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); await expect( - service.expressReceiveTokenWithData(tokenId, sourceChain, sourceAddressForService, destAddress, amount, data, commandId), + service.expressReceiveToken(payload, commandId, sourceChain), ).to.be.reverted; }); @@ -1440,23 +1424,13 @@ describe('Interchain Token Service', () => { ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - await ( - await service.expressReceiveTokenWithData( - tokenId, - sourceChain, - sourceAddressForService, - destAddress, - amount, - data, - commandId, - ) - ).wait(); + await (await service.expressReceiveToken(payload, commandId, sourceChain)).wait(); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(token, 'Transfer') .withArgs(liquidityPool.address, wallet.address, amount) - .and.to.emit(service, 'ExpressExecutionWithDataFulfilled') - .withArgs(tokenId, sourceChain, sourceAddressForService, destAddress, amount, data, commandId, wallet.address); + .and.to.emit(service, 'ExpressExecutionFulfilled') + .withArgs(payload, commandId, wallet.address); expect(await executable.lastMessage()).to.equal(msg); }); diff --git a/test/utils.js b/test/utils.js index 6d0e4f73..7f077294 100644 --- a/test/utils.js +++ b/test/utils.js @@ -72,13 +72,8 @@ describe('Distributable', () => { describe('ExpressCallHandler', () => { let handler; - const tokenId = getRandomBytes32(); - const destinationAddress = new Wallet(getRandomBytes32()).address; - const amount = 123; const expressCaller = new Wallet(getRandomBytes32()).address; - const sourceChain = 'sourceChain'; - const sourceAddress = '0x1234'; - const data = '0x5678'; + const payload = '0x5678'; before(async () => { handler = await deployContract(ownerWallet, 'ExpressCallHandlerTest'); @@ -86,122 +81,38 @@ describe('ExpressCallHandler', () => { it('Should be able to set an express receive token', async () => { const commandId = getRandomBytes32(); - await expect(handler.setExpressReceiveToken(tokenId, destinationAddress, amount, commandId, expressCaller)) + await expect(handler.setExpressReceiveToken(payload, commandId, expressCaller)) .to.emit(handler, 'ExpressReceive') - .withArgs(tokenId, destinationAddress, amount, commandId, expressCaller); - expect(await handler.getExpressReceiveToken(tokenId, destinationAddress, amount, commandId)).to.equal(expressCaller); + .withArgs(payload, commandId, expressCaller); + expect(await handler.getExpressReceiveToken(payload, commandId)).to.equal(expressCaller); }); it('Should not be able to set an express receive token if it is already set', async () => { const commandId = getRandomBytes32(); - await expect(handler.setExpressReceiveToken(tokenId, destinationAddress, amount, commandId, expressCaller)) + await expect(handler.setExpressReceiveToken(payload, commandId, expressCaller)) .to.emit(handler, 'ExpressReceive') - .withArgs(tokenId, destinationAddress, amount, commandId, expressCaller); - expect(await handler.getExpressReceiveToken(tokenId, destinationAddress, amount, commandId)).to.equal(expressCaller); + .withArgs(payload, commandId, expressCaller); + expect(await handler.getExpressReceiveToken(payload, commandId)).to.equal(expressCaller); const newExpressCaller = new Wallet(getRandomBytes32()).address; await expect( - handler.setExpressReceiveToken(tokenId, destinationAddress, amount, commandId, newExpressCaller), - ).to.be.revertedWithCustomError(handler, 'AlreadyExpressCalled'); - }); - - it('Should be able to set an express receive token', async () => { - const commandId = getRandomBytes32(); - await expect( - handler.setExpressReceiveTokenWithData( - tokenId, - sourceChain, - sourceAddress, - destinationAddress, - amount, - data, - commandId, - expressCaller, - ), - ) - .to.emit(handler, 'ExpressReceiveWithData') - .withArgs(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId, expressCaller); - expect( - await handler.getExpressReceiveTokenWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId), - ).to.equal(expressCaller); - }); - - it('Should be able to set an express receive token', async () => { - const commandId = getRandomBytes32(); - await expect( - handler.setExpressReceiveTokenWithData( - tokenId, - sourceChain, - sourceAddress, - destinationAddress, - amount, - data, - commandId, - expressCaller, - ), - ) - .to.emit(handler, 'ExpressReceiveWithData') - .withArgs(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId, expressCaller); - expect( - await handler.getExpressReceiveTokenWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId), - ).to.equal(expressCaller); - - const newExpressCaller = new Wallet(getRandomBytes32()).address; - await expect( - handler.setExpressReceiveTokenWithData( - tokenId, - sourceChain, - sourceAddress, - destinationAddress, - amount, - data, - commandId, - newExpressCaller, - ), + handler.setExpressReceiveToken(payload, commandId, newExpressCaller), ).to.be.revertedWithCustomError(handler, 'AlreadyExpressCalled'); }); it('Should properly pop an express receive token', async () => { const commandId = getRandomBytes32(); - await expect(handler.popExpressReceiveToken(tokenId, destinationAddress, amount, commandId)).to.not.emit( + await expect(handler.popExpressReceiveToken(payload, commandId)).to.not.emit( handler, 'ExpressExecutionFulfilled', ); expect(await handler.lastPoppedExpressCaller()).to.equal(AddressZero); - await (await handler.setExpressReceiveToken(tokenId, destinationAddress, amount, commandId, expressCaller)).wait(); + await (await handler.setExpressReceiveToken(payload, commandId, expressCaller)).wait(); - await expect(handler.popExpressReceiveToken(tokenId, destinationAddress, amount, commandId)) + await expect(handler.popExpressReceiveToken(payload, commandId)) .to.emit(handler, 'ExpressExecutionFulfilled') - .withArgs(tokenId, destinationAddress, amount, commandId, expressCaller); - expect(await handler.lastPoppedExpressCaller()).to.equal(expressCaller); - }); - - it('Should properly pop an express receive token with data', async () => { - const commandId = getRandomBytes32(); - await expect( - handler.popExpressReceiveTokenWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId), - ).to.not.emit(handler, 'ExpressExecutionWithDataFulfilled'); - expect(await handler.lastPoppedExpressCaller()).to.equal(AddressZero); - - await ( - await handler.setExpressReceiveTokenWithData( - tokenId, - sourceChain, - sourceAddress, - destinationAddress, - amount, - data, - commandId, - expressCaller, - ) - ).wait(); - - await expect( - handler.popExpressReceiveTokenWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId), - ) - .to.emit(handler, 'ExpressExecutionWithDataFulfilled') - .withArgs(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId, expressCaller); + .withArgs(payload, commandId, expressCaller); expect(await handler.lastPoppedExpressCaller()).to.equal(expressCaller); }); }); From 244ff613398652b429627f34ff5570d38a101033 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 28 Aug 2023 17:52:26 +0300 Subject: [PATCH 44/54] made lint happy --- .../InterchainTokenService.sol | 14 +++++++++---- test/tokenService.js | 21 ++++++++++--------- test/utils.js | 12 +++++------ 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index b16df51d..972c1599 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -416,10 +416,16 @@ contract InterchainTokenService is SafeTokenTransferFrom.safeTransferFrom(token, caller, destinationAddress, amount); - if(selector == SELECTOR_SEND_TOKEN_WITH_DATA) { + if (selector == SELECTOR_SEND_TOKEN_WITH_DATA) { (, , , , bytes memory sourceAddress, bytes memory data) = abi.decode(payload, (uint256, bytes32, bytes, uint256, bytes, bytes)); - IInterchainTokenExpressExecutable(destinationAddress).executeWithInterchainToken(sourceChain, sourceAddress, data, tokenId, amount); - } else if(selector != SELECTOR_SEND_TOKEN) { + IInterchainTokenExpressExecutable(destinationAddress).executeWithInterchainToken( + sourceChain, + sourceAddress, + data, + tokenId, + amount + ); + } else if (selector != SELECTOR_SEND_TOKEN) { revert InvalidExpressSelector(); } } @@ -804,7 +810,7 @@ contract InterchainTokenService is assembly { version := shr(224, mload(data)) } - if(data.length == 0) return (version, data); + if (data.length == 0) return (version, data); uint256 n = (data.length - 1) / 32; for (uint256 i = 0; i <= n; ++i) { assembly { diff --git a/test/tokenService.js b/test/tokenService.js index 3fd8b5da..4de82364 100644 --- a/test/tokenService.js +++ b/test/tokenService.js @@ -1215,7 +1215,10 @@ describe('Interchain Token Service', () => { }); it('Should express execute', async () => { - const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'uint256'], [SELECTOR_SEND_TOKEN, tokenId, destinationAddress, amount]); + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'uint256'], + [SELECTOR_SEND_TOKEN, tokenId, destinationAddress, amount], + ); await expect(service.expressReceiveToken(payload, commandId, sourceChain)) .to.emit(service, 'ExpressReceive') .withArgs(payload, commandId, wallet.address) @@ -1224,10 +1227,11 @@ describe('Interchain Token Service', () => { }); it('Should express execute with token', async () => { - const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'uint256', 'bytes',' bytes'], [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, executable.address, amount, sourceAddress, data]); - await expect( - service.expressReceiveToken(payload, commandId, sourceChain), - ) + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', ' bytes'], + [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, executable.address, amount, sourceAddress, data], + ); + await expect(service.expressReceiveToken(payload, commandId, sourceChain)) .to.emit(service, 'ExpressReceive') .withArgs(payload, commandId, wallet.address) .and.to.emit(token, 'Transfer') @@ -1363,8 +1367,7 @@ describe('Interchain Token Service', () => { .to.emit(token, 'Transfer') .withArgs(tokenManager.address, wallet.address, amount) .and.to.emit(service, 'ExpressExecutionFulfilled') - .withArgs(payload, commandId, wallet.address) - + .withArgs(payload, commandId, wallet.address); expect(await executable.lastMessage()).to.equal(msg); }); @@ -1406,9 +1409,7 @@ describe('Interchain Token Service', () => { ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - await expect( - service.expressReceiveToken(payload, commandId, sourceChain), - ).to.be.reverted; + await expect(service.expressReceiveToken(payload, commandId, sourceChain)).to.be.reverted; }); it('Should be able to receive liquidity pool token', async () => { diff --git a/test/utils.js b/test/utils.js index 7f077294..17030fc2 100644 --- a/test/utils.js +++ b/test/utils.js @@ -95,17 +95,15 @@ describe('ExpressCallHandler', () => { expect(await handler.getExpressReceiveToken(payload, commandId)).to.equal(expressCaller); const newExpressCaller = new Wallet(getRandomBytes32()).address; - await expect( - handler.setExpressReceiveToken(payload, commandId, newExpressCaller), - ).to.be.revertedWithCustomError(handler, 'AlreadyExpressCalled'); + await expect(handler.setExpressReceiveToken(payload, commandId, newExpressCaller)).to.be.revertedWithCustomError( + handler, + 'AlreadyExpressCalled', + ); }); it('Should properly pop an express receive token', async () => { const commandId = getRandomBytes32(); - await expect(handler.popExpressReceiveToken(payload, commandId)).to.not.emit( - handler, - 'ExpressExecutionFulfilled', - ); + await expect(handler.popExpressReceiveToken(payload, commandId)).to.not.emit(handler, 'ExpressExecutionFulfilled'); expect(await handler.lastPoppedExpressCaller()).to.equal(AddressZero); await (await handler.setExpressReceiveToken(payload, commandId, expressCaller)).wait(); From 4e565769bac10065a2a00b4ddc2b0c48b941bbfc Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 29 Aug 2023 15:49:37 +0300 Subject: [PATCH 45/54] rename sendToken to interchainTransfer --- contracts/token-manager/TokenManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token-manager/TokenManager.sol b/contracts/token-manager/TokenManager.sol index df8bfecd..70fdd7f5 100644 --- a/contracts/token-manager/TokenManager.sol +++ b/contracts/token-manager/TokenManager.sol @@ -88,7 +88,7 @@ abstract contract TokenManager is ITokenManager, Operatable, FlowLimit, Implemen * @param destinationAddress the address of the user to send tokens to. * @param amount the amount of tokens to take from msg.sender. */ - function sendToken( + function interchainTransfer( string calldata destinationChain, bytes calldata destinationAddress, uint256 amount, From a8dc8a423ab94c3d3f129769346329fc56b31104 Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 29 Aug 2023 18:04:30 +0300 Subject: [PATCH 46/54] changed sendToken everywhere --- .../InterchainTokenService.sol | 6 ++--- .../interfaces/IInterchainTokenService.sol | 2 +- contracts/interfaces/ITokenManager.sol | 2 +- contracts/proxies/TokenManagerProxy.sol | 5 ---- docs/index.md | 26 +++++++++---------- test/tokenService.js | 11 ++++---- test/tokenServiceFullFlow.js | 4 +-- 7 files changed, 25 insertions(+), 31 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 972c1599..e5877886 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -394,7 +394,7 @@ contract InterchainTokenService is /** * @notice Uses the caller's tokens to fullfill a sendCall ahead of time. Use this only if you have detected an outgoing - * sendToken that matches the parameters passed here. + * interchainTransfer that matches the parameters passed here. * @dev This is not to be used with fee on transfer tokens as it will incur losses for the express caller. * @param payload the payload of the receive token * @param commandId the sendHash detected at the sourceChain. @@ -464,7 +464,7 @@ contract InterchainTokenService is * @param tokenId the tokenId of the TokenManager (which must be the msg.sender). * @param sourceAddress the address where the token is coming from, which will also be used for reimburment of gas. * @param destinationChain the name of the chain to send tokens to. - * @param destinationAddress the destinationAddress for the sendToken. + * @param destinationAddress the destinationAddress for the interchainTransfer. * @param amount the amount of token to give. * @param metadata the data to be passed to the destiantion. */ @@ -841,7 +841,7 @@ contract InterchainTokenService is * @param tokenId the tokenId of the TokenManager (which must be the msg.sender). * @param sourceAddress the address where the token is coming from, which will also be used for reimburment of gas. * @param destinationChain the name of the chain to send tokens to. - * @param destinationAddress the destinationAddress for the sendToken. + * @param destinationAddress the destinationAddress for the interchainTransfer. * @param amount the amount of token to give. * @param metadata the data to be passed to the destiantion. */ diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index 92ab6db2..3f647a5c 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -296,7 +296,7 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx function setPaused(bool paused) external; /** - * @notice Uses the caller's tokens to fullfill a sendCall ahead of time. Use this only if you have detected an outgoing sendToken that matches the parameters passed here. + * @notice Uses the caller's tokens to fullfill a sendCall ahead of time. Use this only if you have detected an outgoing interchainTransfer that matches the parameters passed here. * @param payload the payload of the receive token * @param commandId the commandId calculated from the event at the sourceChain. */ diff --git a/contracts/interfaces/ITokenManager.sol b/contracts/interfaces/ITokenManager.sol index e3f2acc3..faf0570e 100644 --- a/contracts/interfaces/ITokenManager.sol +++ b/contracts/interfaces/ITokenManager.sol @@ -41,7 +41,7 @@ interface ITokenManager is ITokenManagerType, IOperatable, IFlowLimit, IImplemen * @param destinationAddress the address of the user to send tokens to. * @param amount the amount of tokens to take from msg.sender. */ - function sendToken( + function interchainTransfer( string calldata destinationChain, bytes calldata destinationAddress, uint256 amount, diff --git a/contracts/proxies/TokenManagerProxy.sol b/contracts/proxies/TokenManagerProxy.sol index 56544890..ca5eb434 100644 --- a/contracts/proxies/TokenManagerProxy.sol +++ b/contracts/proxies/TokenManagerProxy.sol @@ -80,9 +80,4 @@ contract TokenManagerProxy is ITokenManagerProxy { } } } - - /** - * @dev Receive function which allows this contract to receive ether. - */ - receive() external payable virtual {} } diff --git a/docs/index.md b/docs/index.md index 3131a399..6f5677be 100644 --- a/docs/index.md +++ b/docs/index.md @@ -566,14 +566,14 @@ function expressReceiveToken(bytes32 tokenId, address destinationAddress, uint25 ``` Uses the caller's tokens to fullfill a sendCall ahead of time. Use this only if you have detected an outgoing -sendToken that matches the parameters passed here. +interchainTransfer that matches the parameters passed here. #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | tokenId | bytes32 | the tokenId of the TokenManager used. | -| destinationAddress | address | the destinationAddress for the sendToken. | +| destinationAddress | address | the destinationAddress for the interchainTransfer. | | amount | uint256 | the amount of token to give. | | commandId | bytes32 | the sendHash detected at the sourceChain. | @@ -584,7 +584,7 @@ function expressReceiveTokenWithData(bytes32 tokenId, string sourceChain, bytes ``` Uses the caller's tokens to fullfill a callContractWithInterchainToken ahead of time. Use this only if you have -detected an outgoing sendToken that matches the parameters passed here. +detected an outgoing interchainTransfer that matches the parameters passed here. #### Parameters @@ -593,7 +593,7 @@ detected an outgoing sendToken that matches the parameters passed here. | tokenId | bytes32 | the tokenId of the TokenManager used. | | sourceChain | string | the name of the chain where the call came from. | | sourceAddress | bytes | the caller of callContractWithInterchainToken. | -| destinationAddress | address | the destinationAddress for the sendToken. | +| destinationAddress | address | the destinationAddress for the interchainTransfer. | | amount | uint256 | the amount of token to give. | | data | bytes | the data to be passed to destinationAddress after giving them the tokens specified. | | commandId | bytes32 | the sendHash detected at the sourceChain. | @@ -613,7 +613,7 @@ Transmit a sendTokenWithData for the given tokenId. Only callable by a token man | tokenId | bytes32 | the tokenId of the TokenManager (which must be the msg.sender). | | sourceAddress | address | the address where the token is coming from, which will also be used for reimburment of gas. | | destinationChain | string | the name of the chain to send tokens to. | -| destinationAddress | bytes | the destinationAddress for the sendToken. | +| destinationAddress | bytes | the destinationAddress for the interchainTransfer. | | amount | uint256 | the amount of token to give. | | metadata | bytes | the data to be passed to the destiantion. | @@ -1712,14 +1712,14 @@ Sets the paused state of the contract. function expressReceiveToken(bytes32 tokenId, address destinationAddress, uint256 amount, bytes32 commandId) external ``` -Uses the caller's tokens to fullfill a sendCall ahead of time. Use this only if you have detected an outgoing sendToken that matches the parameters passed here. +Uses the caller's tokens to fullfill a sendCall ahead of time. Use this only if you have detected an outgoing interchainTransfer that matches the parameters passed here. #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | tokenId | bytes32 | the tokenId of the TokenManager used. | -| destinationAddress | address | the destinationAddress for the sendToken. | +| destinationAddress | address | the destinationAddress for the interchainTransfer. | | amount | uint256 | the amount of token to give. | | commandId | bytes32 | the commandId calculated from the event at the sourceChain. | @@ -1729,7 +1729,7 @@ Uses the caller's tokens to fullfill a sendCall ahead of time. Use this only if function expressReceiveTokenWithData(bytes32 tokenId, string sourceChain, bytes sourceAddress, address destinationAddress, uint256 amount, bytes data, bytes32 commandId) external ``` -Uses the caller's tokens to fullfill a callContractWithInterchainToken ahead of time. Use this only if you have detected an outgoing sendToken that matches the parameters passed here. +Uses the caller's tokens to fullfill a callContractWithInterchainToken ahead of time. Use this only if you have detected an outgoing interchainTransfer that matches the parameters passed here. #### Parameters @@ -1738,7 +1738,7 @@ Uses the caller's tokens to fullfill a callContractWithInterchainToken ahead of | tokenId | bytes32 | the tokenId of the TokenManager used. | | sourceChain | string | the name of the chain where the call came from. | | sourceAddress | bytes | the caller of callContractWithInterchainToken. | -| destinationAddress | address | the destinationAddress for the sendToken. | +| destinationAddress | address | the destinationAddress for the interchainTransfer. | | amount | uint256 | the amount of token to give. | | data | bytes | the data to be passed to destinationAddress after giving them the tokens specified. | | commandId | bytes32 | the commandId calculated from the event at the sourceChain. | @@ -2106,10 +2106,10 @@ function implementationType() external pure returns (uint256) A function that should return the implementation type of the token manager. -### sendToken +### interchainTransfer ```solidity -function sendToken(string destinationChain, bytes destinationAddress, uint256 amount, bytes metadata) external payable +function interchainTransfer(string destinationChain, bytes destinationAddress, uint256 amount, bytes metadata) external payable ``` Calls the service to initiate the a cross-chain transfer after taking the appropriate amount of tokens from the user. @@ -4154,10 +4154,10 @@ _This function should only be called by the proxy, and only once from the proxy | ---- | ---- | ----------- | | params | bytes | the parameters to be used to initialize the TokenManager. The exact format depends on the type of TokenManager used but the first 32 bytes are reserved for the address of the operator, stored as bytes (to be compatible with non-EVM chains) | -### sendToken +### interchainTransfer ```solidity -function sendToken(string destinationChain, bytes destinationAddress, uint256 amount, bytes metadata) external payable virtual +function interchainTransfer(string destinationChain, bytes destinationAddress, uint256 amount, bytes metadata) external payable virtual ``` Calls the service to initiate the a cross-chain transfer after taking the appropriate amount of tokens from the user. diff --git a/test/tokenService.js b/test/tokenService.js index 4de82364..582c908c 100644 --- a/test/tokenService.js +++ b/test/tokenService.js @@ -865,7 +865,7 @@ describe('Interchain Token Service', () => { transferToAddress = liquidityPool.address; } - await expect(tokenManager.sendToken(destChain, destAddress, amount, '0x', { value: gasValue })) + await expect(tokenManager.interchainTransfer(destChain, destAddress, amount, '0x', { value: gasValue })) .and.to.emit(token, 'Transfer') .withArgs(wallet.address, transferToAddress, amount) .and.to.emit(gateway, 'ContractCall') @@ -1452,11 +1452,10 @@ describe('Interchain Token Service', () => { // These tests will fail every once in a while since the two transactions will happen in different epochs. // LMK of any fixes to this that do not involve writing a new contract to facilitate a multicall. it('Should be able to send token only if it does not trigger the mint limit', async () => { - await (await tokenManager.sendToken(destinationChain, destinationAddress, sendAmount, '0x')).wait(); - await expect(tokenManager.sendToken(destinationChain, destinationAddress, sendAmount, '0x')).to.be.revertedWithCustomError( - tokenManager, - 'FlowLimitExceeded', - ); + await (await tokenManager.interchainTransfer(destinationChain, destinationAddress, sendAmount, '0x')).wait(); + await expect( + tokenManager.interchainTransfer(destinationChain, destinationAddress, sendAmount, '0x'), + ).to.be.revertedWithCustomError(tokenManager, 'FlowLimitExceeded'); }); it('Should be able to receive token only if it does not trigger the mint limit', async () => { diff --git a/test/tokenServiceFullFlow.js b/test/tokenServiceFullFlow.js index 89bcf31c..8dd2c420 100644 --- a/test/tokenServiceFullFlow.js +++ b/test/tokenServiceFullFlow.js @@ -101,7 +101,7 @@ describe('Interchain Token Service', () => { .to.emit(token, 'Approval') .withArgs(wallet.address, tokenManager.address, amount); - await expect(tokenManager.sendToken(destChain, destAddress, amount, '0x', { value: gasValue })) + await expect(tokenManager.interchainTransfer(destChain, destAddress, amount, '0x', { value: gasValue })) .and.to.emit(token, 'Transfer') .withArgs(wallet.address, tokenManager.address, amount) .and.to.emit(gateway, 'ContractCall') @@ -210,7 +210,7 @@ describe('Interchain Token Service', () => { ); const payloadHash = keccak256(payload); - await expect(tokenManager.sendToken(destChain, destAddress, amount, '0x', { value: gasValue })) + await expect(tokenManager.interchainTransfer(destChain, destAddress, amount, '0x', { value: gasValue })) .and.to.emit(token, 'Transfer') .withArgs(wallet.address, AddressZero, amount) .and.to.emit(gateway, 'ContractCall') From b210316ecc2ac6f84117ebf274e496c63b3a8902 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 30 Aug 2023 18:06:24 +0300 Subject: [PATCH 47/54] changed from uint256.max to a const --- contracts/interchain-token/InterchainToken.sol | 2 +- contracts/test/FeeOnTransferTokenTest.sol | 7 ++++--- contracts/test/InterchainTokenTest.sol | 6 +++--- contracts/token-implementations/ERC20.sol | 3 ++- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/contracts/interchain-token/InterchainToken.sol b/contracts/interchain-token/InterchainToken.sol index 85668a35..4a2b0681 100644 --- a/contracts/interchain-token/InterchainToken.sol +++ b/contracts/interchain-token/InterchainToken.sol @@ -61,7 +61,7 @@ abstract contract InterchainToken is IInterchainToken, ERC20 { ) external payable { uint256 _allowance = allowance[sender][msg.sender]; - if (_allowance != type(uint256).max) { + if (_allowance != UINT256_MAX) { _approve(sender, msg.sender, _allowance - amount); } diff --git a/contracts/test/FeeOnTransferTokenTest.sol b/contracts/test/FeeOnTransferTokenTest.sol index a5163a1f..d0816dd1 100644 --- a/contracts/test/FeeOnTransferTokenTest.sol +++ b/contracts/test/FeeOnTransferTokenTest.sol @@ -10,6 +10,7 @@ import { IERC20BurnableMintable } from '../interfaces/IERC20BurnableMintable.sol contract FeeOnTransferTokenTest is InterchainToken, Distributable, IERC20BurnableMintable { ITokenManager public tokenManager_; bool internal tokenManagerRequiresApproval_ = true; + string public name; string public symbol; uint8 public decimals; @@ -36,9 +37,9 @@ contract FeeOnTransferTokenTest is InterchainToken, Distributable, IERC20Burnabl if (!tokenManagerRequiresApproval_) return; address tokenManagerAddress = address(tokenManager_); uint256 allowance_ = allowance[sender][tokenManagerAddress]; - if (allowance_ != type(uint256).max) { - if (allowance_ > type(uint256).max - amount) { - allowance_ = type(uint256).max - amount; + if (allowance_ != UINT256_MAX) { + if (allowance_ > UINT256_MAX - amount) { + allowance_ = UINT256_MAX - amount; } _approve(sender, tokenManagerAddress, allowance_ + amount); diff --git a/contracts/test/InterchainTokenTest.sol b/contracts/test/InterchainTokenTest.sol index 53c369c2..96fa8cbb 100644 --- a/contracts/test/InterchainTokenTest.sol +++ b/contracts/test/InterchainTokenTest.sol @@ -36,9 +36,9 @@ contract InterchainTokenTest is InterchainToken, Distributable, IERC20BurnableMi if (!tokenManagerRequiresApproval_) return; address tokenManagerAddress = address(tokenManager_); uint256 allowance_ = allowance[sender][tokenManagerAddress]; - if (allowance_ != type(uint256).max) { - if (allowance_ > type(uint256).max - amount) { - allowance_ = type(uint256).max - amount; + if (allowance_ != UINT256_MAX) { + if (allowance_ > UINT256_MAX - amount) { + allowance_ = UINT256_MAX - amount; } _approve(sender, tokenManagerAddress, allowance_ + amount); diff --git a/contracts/token-implementations/ERC20.sol b/contracts/token-implementations/ERC20.sol index 66be0690..71b7f10d 100644 --- a/contracts/token-implementations/ERC20.sol +++ b/contracts/token-implementations/ERC20.sol @@ -34,6 +34,7 @@ contract ERC20 is IERC20 { mapping(address => mapping(address => uint256)) public override allowance; uint256 public override totalSupply; + uint256 internal constant UINT256_MAX = 2 ** 256 - 1; /** * @dev See {IERC20-transfer}. @@ -79,7 +80,7 @@ contract ERC20 is IERC20 { function transferFrom(address sender, address recipient, uint256 amount) external virtual override returns (bool) { uint256 _allowance = allowance[sender][msg.sender]; - if (_allowance != type(uint256).max) { + if (_allowance != UINT256_MAX) { _approve(sender, msg.sender, _allowance - amount); } From 2f5adacd97027f6dd8cfe8781f518560db686c8f Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 4 Sep 2023 16:47:20 +0300 Subject: [PATCH 48/54] change setting to zero to delete for storage slots. --- contracts/remote-address-validator/RemoteAddressValidator.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/remote-address-validator/RemoteAddressValidator.sol b/contracts/remote-address-validator/RemoteAddressValidator.sol index 75881d9e..89d7d40c 100644 --- a/contracts/remote-address-validator/RemoteAddressValidator.sol +++ b/contracts/remote-address-validator/RemoteAddressValidator.sol @@ -143,8 +143,8 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable { function removeTrustedAddress(string calldata sourceChain) external onlyOwner { if (bytes(sourceChain).length == 0) revert ZeroStringLength(); - remoteAddressHashes[sourceChain] = bytes32(0); - remoteAddresses[sourceChain] = ''; + delete remoteAddressHashes[sourceChain]; + delete remoteAddresses[sourceChain]; emit TrustedAddressRemoved(sourceChain); } From af4766a73c85c03903e8c30e04af4329c3be4c83 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 4 Sep 2023 16:49:14 +0300 Subject: [PATCH 49/54] rearange storage variables to save a bit of gas. --- contracts/token-implementations/StandardizedToken.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token-implementations/StandardizedToken.sol b/contracts/token-implementations/StandardizedToken.sol index 8d291a00..f1fe4951 100644 --- a/contracts/token-implementations/StandardizedToken.sol +++ b/contracts/token-implementations/StandardizedToken.sol @@ -19,10 +19,10 @@ import { Distributable } from '../utils/Distributable.sol'; contract StandardizedToken is IERC20BurnableMintable, InterchainToken, ERC20Permit, Implementation, Distributable { using AddressBytesUtils for bytes; - address internal tokenManager_; string public name; string public symbol; uint8 public decimals; + address internal tokenManager_; bytes32 private constant CONTRACT_ID = keccak256('standardized-token'); From 634581cf75b79df597f48c12c6d5a2238c41dee5 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 4 Sep 2023 16:51:20 +0300 Subject: [PATCH 50/54] Removed unecesairy casts --- .../token-manager/implementations/TokenManagerLiquidityPool.sol | 2 +- .../implementations/TokenManagerLockUnlockFeeOnTransfer.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol index 081d56b5..7ccd721e 100644 --- a/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol +++ b/contracts/token-manager/implementations/TokenManagerLiquidityPool.sol @@ -98,7 +98,7 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage, NoReEntrancy, */ function _giveToken(address to, uint256 amount) internal override noReEntrancy returns (uint256) { IERC20 token = IERC20(tokenAddress()); - uint256 balance = IERC20(token).balanceOf(to); + uint256 balance = token.balanceOf(to); SafeTokenTransferFrom.safeTransferFrom(token, liquidityPool(), to, amount); diff --git a/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol b/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol index 4d8b5142..820e269a 100644 --- a/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol +++ b/contracts/token-manager/implementations/TokenManagerLockUnlockFeeOnTransfer.sol @@ -64,7 +64,7 @@ contract TokenManagerLockUnlockFee is TokenManagerAddressStorage, NoReEntrancy, */ function _giveToken(address to, uint256 amount) internal override noReEntrancy returns (uint256) { IERC20 token = IERC20(tokenAddress()); - uint256 balance = IERC20(token).balanceOf(to); + uint256 balance = token.balanceOf(to); SafeTokenTransfer.safeTransfer(token, to, amount); From 141648bb30850c1a92b61ced0ceb1425009c8b8a Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 4 Sep 2023 16:55:10 +0300 Subject: [PATCH 51/54] made as many event params inexed as possible --- contracts/interfaces/IInterchainTokenService.sol | 10 +++++----- contracts/interfaces/IPausable.sol | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index 3f647a5c..e1ae299c 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -30,9 +30,9 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx error AlreadyExecuted(bytes32 commandId); error InvalidExpressSelector(); - event TokenSent(bytes32 tokenId, string destinationChain, bytes destinationAddress, uint256 indexed amount); + event TokenSent(bytes32 indexed tokenId, string destinationChain, bytes destinationAddress, uint256 indexed amount); event TokenSentWithData( - bytes32 tokenId, + bytes32 indexed tokenId, string destinationChain, bytes destinationAddress, uint256 indexed amount, @@ -62,7 +62,7 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx uint8 tokenDecimals, bytes distributor, bytes mintTo, - uint256 mintAmount, + uint256 indexed mintAmount, bytes operator, string destinationChain, uint256 indexed gasValue @@ -70,11 +70,11 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx event TokenManagerDeployed(bytes32 indexed tokenId, TokenManagerType indexed tokenManagerType, bytes params); event StandardizedTokenDeployed( bytes32 indexed tokenId, - address distributor, + address indexed distributor, string name, string symbol, uint8 decimals, - uint256 mintAmount, + uint256 indexed mintAmount, address mintTo ); event CustomTokenIdClaimed(bytes32 indexed tokenId, address indexed deployer, bytes32 indexed salt); diff --git a/contracts/interfaces/IPausable.sol b/contracts/interfaces/IPausable.sol index f58eecf6..c5adbd7f 100644 --- a/contracts/interfaces/IPausable.sol +++ b/contracts/interfaces/IPausable.sol @@ -8,7 +8,7 @@ pragma solidity ^0.8.0; * if a pause condition is activated. */ interface IPausable { - event PausedSet(bool paused); + event PausedSet(bool indexed paused); error Paused(); From 602d53145fb7e8487bf3257f1bca9c733a885b5c Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 4 Sep 2023 17:04:34 +0300 Subject: [PATCH 52/54] Removed unused imports --- contracts/interchain-token-service/InterchainTokenService.sol | 1 - contracts/interfaces/IInterchainTokenService.sol | 2 -- .../implementations/TokenManagerAddressStorage.sol | 2 -- 3 files changed, 5 deletions(-) diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index e5877886..2336b528 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.0; -import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol'; import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol'; import { AxelarExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol'; import { SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/SafeTransfer.sol'; diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index e1ae299c..3da2ece4 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -2,11 +2,9 @@ pragma solidity ^0.8.0; -import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol'; import { IAxelarExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarExecutable.sol'; import { IExpressCallHandler } from './IExpressCallHandler.sol'; -import { ITokenManagerDeployer } from './ITokenManagerDeployer.sol'; import { ITokenManagerType } from './ITokenManagerType.sol'; import { IPausable } from './IPausable.sol'; import { IMulticall } from './IMulticall.sol'; diff --git a/contracts/token-manager/implementations/TokenManagerAddressStorage.sol b/contracts/token-manager/implementations/TokenManagerAddressStorage.sol index ad78f9c7..309f6355 100644 --- a/contracts/token-manager/implementations/TokenManagerAddressStorage.sol +++ b/contracts/token-manager/implementations/TokenManagerAddressStorage.sol @@ -3,8 +3,6 @@ pragma solidity ^0.8.0; import { TokenManager } from '../TokenManager.sol'; -import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; -import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol'; /** * @title TokenManagerAddressStorage From 0ec429ef1b487a2b055d2839537741c8a09f167e Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 4 Sep 2023 17:16:57 +0300 Subject: [PATCH 53/54] domain separator is calculated each time. --- contracts/token-implementations/ERC20Permit.sol | 15 ++++++++++----- .../token-implementations/StandardizedToken.sol | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/contracts/token-implementations/ERC20Permit.sol b/contracts/token-implementations/ERC20Permit.sol index 022edb76..8ff3cade 100644 --- a/contracts/token-implementations/ERC20Permit.sol +++ b/contracts/token-implementations/ERC20Permit.sol @@ -22,7 +22,7 @@ abstract contract ERC20Permit is IERC20, IERC20Permit, ERC20 { * @dev Represents hash of the EIP-712 Domain Separator. */ // solhint-disable-next-line var-name-mixedcase - bytes32 public DOMAIN_SEPARATOR; + bytes32 public nameHash; string private constant EIP191_PREFIX_FOR_EIP712_STRUCTURED_DATA = '\x19\x01'; @@ -41,9 +41,14 @@ abstract contract ERC20Permit is IERC20, IERC20Permit, ERC20 { * @notice Internal function to set the domain type signature hash * @param name The token name */ - function _setDomainTypeSignatureHash(string memory name) internal { - DOMAIN_SEPARATOR = keccak256( - abi.encode(DOMAIN_TYPE_SIGNATURE_HASH, keccak256(bytes(name)), keccak256(bytes('1')), block.chainid, address(this)) + function _setNameHash(string memory name) internal { + nameHash = keccak256(bytes(name)); + } + + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() public view returns (bytes32 domainSeparator) { + domainSeparator = keccak256( + abi.encode(DOMAIN_TYPE_SIGNATURE_HASH, nameHash, keccak256(bytes('1')), block.chainid, address(this)) ); } @@ -69,7 +74,7 @@ abstract contract ERC20Permit is IERC20, IERC20Permit, ERC20 { bytes32 digest = keccak256( abi.encodePacked( EIP191_PREFIX_FOR_EIP712_STRUCTURED_DATA, - DOMAIN_SEPARATOR, + DOMAIN_SEPARATOR(), keccak256(abi.encode(PERMIT_SIGNATURE_HASH, issuer, spender, value, nonces[issuer]++, deadline)) ) ); diff --git a/contracts/token-implementations/StandardizedToken.sol b/contracts/token-implementations/StandardizedToken.sol index f1fe4951..e4e150a8 100644 --- a/contracts/token-implementations/StandardizedToken.sol +++ b/contracts/token-implementations/StandardizedToken.sol @@ -68,7 +68,7 @@ contract StandardizedToken is IERC20BurnableMintable, InterchainToken, ERC20Perm name = tokenName; _setDistributor(distributor_); - _setDomainTypeSignatureHash(tokenName); + _setNameHash(tokenName); } { uint256 mintAmount; From f089d50e7ef5aeea37458b5b1c7a90be3b724aa2 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 4 Sep 2023 17:18:10 +0300 Subject: [PATCH 54/54] added some natspec --- contracts/token-implementations/ERC20Permit.sol | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/contracts/token-implementations/ERC20Permit.sol b/contracts/token-implementations/ERC20Permit.sol index 8ff3cade..349bd7de 100644 --- a/contracts/token-implementations/ERC20Permit.sol +++ b/contracts/token-implementations/ERC20Permit.sol @@ -22,7 +22,7 @@ abstract contract ERC20Permit is IERC20, IERC20Permit, ERC20 { * @dev Represents hash of the EIP-712 Domain Separator. */ // solhint-disable-next-line var-name-mixedcase - bytes32 public nameHash; + bytes32 public nameHash; string private constant EIP191_PREFIX_FOR_EIP712_STRUCTURED_DATA = '\x19\x01'; @@ -38,18 +38,20 @@ abstract contract ERC20Permit is IERC20, IERC20Permit, ERC20 { mapping(address => uint256) public nonces; /** - * @notice Internal function to set the domain type signature hash + * @notice Internal function to set the token name hash * @param name The token name */ function _setNameHash(string memory name) internal { nameHash = keccak256(bytes(name)); } + /** + * @notice Calculates the DOMAIN_SEPARATOR. + * @dev This is not cached because chainid can change on chain forks. + */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() public view returns (bytes32 domainSeparator) { - domainSeparator = keccak256( - abi.encode(DOMAIN_TYPE_SIGNATURE_HASH, nameHash, keccak256(bytes('1')), block.chainid, address(this)) - ); + domainSeparator = keccak256(abi.encode(DOMAIN_TYPE_SIGNATURE_HASH, nameHash, keccak256(bytes('1')), block.chainid, address(this))); } /**