diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 1f0a82fd..f6edf371 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -9,6 +9,7 @@ import { ExpressExecutorTracker } from '@axelar-network/axelar-gmp-sdk-solidity/ import { Upgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Upgradable.sol'; import { Create3Address } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3Address.sol'; import { SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol'; +import { AddressBytes } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/AddressBytes.sol'; import { StringToBytes32, Bytes32ToString } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/Bytes32String.sol'; import { Multicall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/Multicall.sol'; import { IInterchainAddressTracker } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IInterchainAddressTracker.sol'; @@ -22,7 +23,6 @@ import { IInterchainTokenExpressExecutable } from '../interfaces/IInterchainToke import { ITokenManager } from '../interfaces/ITokenManager.sol'; import { IERC20Named } from '../interfaces/IERC20Named.sol'; -import { AddressBytesUtils } from '../libraries/AddressBytesUtils.sol'; import { Operatable } from '../utils/Operatable.sol'; /** @@ -42,8 +42,8 @@ contract InterchainTokenService is { using StringToBytes32 for string; using Bytes32ToString for bytes32; - using AddressBytesUtils for bytes; - using AddressBytesUtils for address; + using AddressBytes for bytes; + using AddressBytes for address; using SafeTokenTransferFrom for IERC20; address internal immutable implementationLockUnlock; diff --git a/contracts/interfaces/ICanonicalTokenRegistrar.sol b/contracts/interfaces/ICanonicalTokenRegistrar.sol deleted file mode 100644 index d959d2e2..00000000 --- a/contracts/interfaces/ICanonicalTokenRegistrar.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -interface ICanonicalTokenRegistrar { - error ZeroAddress(); - error ApproveFailed(); - - function chainNameHash() external view returns (bytes32); - - function canonicalTokenSalt(address tokenAddress) external view returns (bytes32 salt); - - function canonicalTokenId(address tokenAddress) external view returns (bytes32 tokenId); - - function registerCanonicalToken(address tokenAddress) external payable returns (bytes32 tokenId); - - function deployAndRegisterRemoteCanonicalToken(bytes32 salt, string calldata destinationChain, uint256 gasValue) external payable; -} diff --git a/contracts/interfaces/IStandardizedTokenRegistrar.sol b/contracts/interfaces/IStandardizedTokenRegistrar.sol deleted file mode 100644 index 60e7616b..00000000 --- a/contracts/interfaces/IStandardizedTokenRegistrar.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -interface IStandardizedTokenRegistrar { - error ZeroAddress(); - error NotDistributor(address distributor); - error NotOperator(address operator); - error NonZeroMintAmount(); - - function chainNameHash() external view returns (bytes32); - - function standardizedTokenSalt(address deployer, bytes32 salt) external view returns (bytes32); - - function standardizedTokenId(address deployer, bytes32 salt) external view returns (bytes32 tokenId); - - function interchainTokenAddress(address deployer, bytes32 salt) external view returns (address tokenAddress); - - function deployInterchainToken( - bytes32 salt, - string calldata name, - string calldata symbol, - uint8 decimals, - uint256 mintAmount, - address distributor, - address operator - ) external payable; - - function deployRemoteStandarizedToken( - bytes32 salt, - address additionalDistributor, - address optionalOperator, - uint256 mintAmount, - string memory destinationChain, - uint256 gasValue - ) external payable; -} diff --git a/contracts/interfaces/ITokenRegistrar.sol b/contracts/interfaces/ITokenRegistrar.sol new file mode 100644 index 00000000..a8a11746 --- /dev/null +++ b/contracts/interfaces/ITokenRegistrar.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface ITokenRegistrar { + error ZeroAddress(); + error NotDistributor(address distributor); + error NotOperator(address operator); + error NonZeroMintAmount(); + error ApproveFailed(); + + function chainNameHash() external view returns (bytes32); + + function standardizedTokenSalt(bytes32 chainAddressHash_, address deployer, bytes32 salt) external view returns (bytes32); + + function standardizedTokenId(address deployer, bytes32 salt) external view returns (bytes32 tokenId); + + function interchainTokenAddress(address deployer, bytes32 salt) external view returns (address tokenAddress); + + function deployInterchainToken( + bytes32 salt, + string calldata name, + string calldata symbol, + uint8 decimals, + uint256 mintAmount, + address distributor, + address operator + ) external payable; + + function deployRemoteInterchainToken( + string calldata originalChainName, + bytes32 salt, + address additionalDistributor, + address optionalOperator, + string memory destinationChain, + uint256 gasValue + ) external payable; + + function canonicalTokenSalt(bytes32 chainAddressHash_, address tokenAddress) external view returns (bytes32 salt); + + function canonicalTokenId(address tokenAddress) external view returns (bytes32 tokenId); + + function registerCanonicalToken(address tokenAddress) external payable returns (bytes32 tokenId); + + function deployRemoteCanonicalToken( + string calldata originalChainName, + address originalAddress, + string calldata destinationChain, + uint256 gasValue + ) external payable; + + function interchainTransfer( + bytes32 tokenId, + string calldata destinationChain, + bytes calldata destinationAddress, + uint256 amount, + uint256 gasValue + ) external payable; + + function interchainTransferFrom( + bytes32 tokenId, + string calldata destinationChain, + bytes calldata destinationAddress, + uint256 amount, + uint256 gasValue + ) external payable; +} diff --git a/contracts/libraries/AddressBytesUtils.sol b/contracts/libraries/AddressBytesUtils.sol deleted file mode 100644 index f02570fe..00000000 --- a/contracts/libraries/AddressBytesUtils.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -/** - * @title AddressBytesUtils - * @dev This library provides utility functions to convert between `address` and `bytes`. - */ -library AddressBytesUtils { - error InvalidBytesLength(bytes bytesAddress); - - /** - * @dev Converts a bytes address to an address type. - * @param bytesAddress The bytes representation of an address - * @return addr The converted address - */ - function toAddress(bytes memory bytesAddress) internal pure returns (address addr) { - if (bytesAddress.length != 20) revert InvalidBytesLength(bytesAddress); - - assembly { - addr := mload(add(bytesAddress, 20)) - } - } - - /** - * @dev Converts an address to bytes. - * @param addr The address to be converted - * @return bytesAddress The bytes representation of the address - */ - 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/proxies/StandardizedTokenRegistrarProxy.sol b/contracts/proxies/TokenRegistrarProxy.sol similarity index 86% rename from contracts/proxies/StandardizedTokenRegistrarProxy.sol rename to contracts/proxies/TokenRegistrarProxy.sol index ffbbc038..0a33957f 100644 --- a/contracts/proxies/StandardizedTokenRegistrarProxy.sol +++ b/contracts/proxies/TokenRegistrarProxy.sol @@ -8,8 +8,8 @@ import { Proxy } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgrada * @title StandardizedTokenRegistrarProxy * @dev Proxy contract for interchain token service contracts. Inherits from the Proxy contract. */ -contract StandardizedTokenRegistrarProxy is Proxy { - bytes32 private constant CONTRACT_ID = keccak256('standardized-token-registrar'); +contract TokenRegistrarProxy is Proxy { + bytes32 private constant CONTRACT_ID = keccak256('token-registrar'); /** * @dev Constructs the InterchainTokenServiceProxy contract. diff --git a/contracts/test/AxelarGasService.sol b/contracts/test/AxelarGasService.sol deleted file mode 100644 index 292254ec..00000000 --- a/contracts/test/AxelarGasService.sol +++ /dev/null @@ -1,251 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import { IERC20 } from '@axelar-network/axelar-cgp-solidity/contracts/interfaces/IERC20.sol'; - -// This should be owned by the microservice that is paying for gas. -contract AxelarGasService { - error TransferFailed(); - error NothingReceived(); - error InvalidAddress(); - error InvalidAmounts(); - error NotCollector(); - - event GasPaidForContractCall( - address indexed sourceAddress, - string destinationChain, - string destinationAddress, - bytes32 indexed payloadHash, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ); - - event GasPaidForContractCallWithToken( - address indexed sourceAddress, - string destinationChain, - string destinationAddress, - bytes32 indexed payloadHash, - string symbol, - uint256 amount, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ); - - event NativeGasPaidForContractCall( - address indexed sourceAddress, - string destinationChain, - string destinationAddress, - bytes32 indexed payloadHash, - uint256 gasFeeAmount, - address refundAddress - ); - - event NativeGasPaidForContractCallWithToken( - address indexed sourceAddress, - string destinationChain, - string destinationAddress, - bytes32 indexed payloadHash, - string symbol, - uint256 amount, - uint256 gasFeeAmount, - address refundAddress - ); - - event GasPaidForExpressCallWithToken( - address indexed sourceAddress, - string destinationChain, - string destinationAddress, - bytes32 indexed payloadHash, - string symbol, - uint256 amount, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ); - - event NativeGasPaidForExpressCallWithToken( - address indexed sourceAddress, - string destinationChain, - string destinationAddress, - bytes32 indexed payloadHash, - string symbol, - uint256 amount, - uint256 gasFeeAmount, - address refundAddress - ); - - event GasAdded(bytes32 indexed txHash, uint256 indexed logIndex, address gasToken, uint256 gasFeeAmount, address refundAddress); - - event NativeGasAdded(bytes32 indexed txHash, uint256 indexed logIndex, uint256 gasFeeAmount, address refundAddress); - - event ExpressGasAdded(bytes32 indexed txHash, uint256 indexed logIndex, address gasToken, uint256 gasFeeAmount, address refundAddress); - - event NativeExpressGasAdded(bytes32 indexed txHash, uint256 indexed logIndex, uint256 gasFeeAmount, address refundAddress); - - address public immutable gasCollector; - - constructor(address gasCollector_) { - gasCollector = gasCollector_; - } - - modifier onlyCollector() { - if (msg.sender != gasCollector) revert NotCollector(); - - _; - } - - // This is called on the source chain before calling the gateway to execute a remote contract. - function payGasForContractCall( - address sender, - string calldata destinationChain, - string calldata destinationAddress, - bytes calldata payload, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ) external { - _safeTransferFrom(gasToken, msg.sender, gasFeeAmount); - - emit GasPaidForContractCall( - sender, - destinationChain, - destinationAddress, - keccak256(payload), - gasToken, - gasFeeAmount, - refundAddress - ); - } - - // This is called on the source chain before calling the gateway to execute a remote contract. - function payGasForContractCallWithToken( - address sender, - string calldata destinationChain, - string calldata destinationAddress, - bytes calldata payload, - string memory symbol, - uint256 amount, - address gasToken, - uint256 gasFeeAmount, - address refundAddress - ) external { - _safeTransferFrom(gasToken, msg.sender, gasFeeAmount); - - emit GasPaidForContractCallWithToken( - sender, - destinationChain, - destinationAddress, - keccak256(payload), - symbol, - amount, - gasToken, - gasFeeAmount, - refundAddress - ); - } - - // This is called on the source chain before calling the gateway to execute a remote contract. - function payNativeGasForContractCall( - address sender, - string calldata destinationChain, - string calldata destinationAddress, - bytes calldata payload, - address refundAddress - ) external payable { - if (msg.value == 0) revert NothingReceived(); - - emit NativeGasPaidForContractCall(sender, destinationChain, destinationAddress, keccak256(payload), msg.value, refundAddress); - } - - // This is called on the source chain before calling the gateway to execute a remote contract. - function payNativeGasForContractCallWithToken( - address sender, - string calldata destinationChain, - string calldata destinationAddress, - bytes calldata payload, - string calldata symbol, - uint256 amount, - address refundAddress - ) external payable { - if (msg.value == 0) revert NothingReceived(); - - emit NativeGasPaidForContractCallWithToken( - sender, - destinationChain, - destinationAddress, - keccak256(payload), - symbol, - amount, - msg.value, - refundAddress - ); - } - - function addGas(bytes32 txHash, uint256 logIndex, address gasToken, uint256 gasFeeAmount, address refundAddress) external { - _safeTransferFrom(gasToken, msg.sender, gasFeeAmount); - - emit GasAdded(txHash, logIndex, gasToken, gasFeeAmount, refundAddress); - } - - function addNativeGas(bytes32 txHash, uint256 logIndex, address refundAddress) external payable { - if (msg.value == 0) revert NothingReceived(); - - emit NativeGasAdded(txHash, logIndex, msg.value, refundAddress); - } - - function collectFees(address payable receiver, address[] calldata tokens, uint256[] calldata amounts) external onlyCollector { - if (receiver == address(0)) revert InvalidAddress(); - - uint256 tokensLength = tokens.length; - if (tokensLength != amounts.length) revert InvalidAmounts(); - - for (uint256 i; i < tokensLength; i++) { - address token = tokens[i]; - uint256 amount = amounts[i]; - if (amount == 0) revert InvalidAmounts(); - - if (token == address(0)) { - if (amount <= address(this).balance) receiver.transfer(amount); - } else { - if (amount <= IERC20(token).balanceOf(address(this))) _safeTransfer(token, receiver, amount); - } - } - } - - function refund(address payable receiver, address token, uint256 amount) external onlyCollector { - if (receiver == address(0)) revert InvalidAddress(); - - if (token == address(0)) { - receiver.transfer(amount); - } else { - _safeTransfer(token, receiver, amount); - } - } - - function _safeTransfer(address tokenAddress, address receiver, uint256 amount) internal { - if (amount == 0) revert NothingReceived(); - - (bool success, bytes memory returnData) = tokenAddress.call(abi.encodeWithSelector(IERC20.transfer.selector, receiver, amount)); - bool transferred = success && (returnData.length == uint256(0) || abi.decode(returnData, (bool))); - - if (!transferred || tokenAddress.code.length == 0) revert TransferFailed(); - } - - function _safeTransferFrom(address tokenAddress, address from, uint256 amount) internal { - if (amount == 0) revert NothingReceived(); - - (bool success, bytes memory returnData) = tokenAddress.call( - abi.encodeWithSelector(IERC20.transferFrom.selector, from, address(this), amount) - ); - bool transferred = success && (returnData.length == uint256(0) || abi.decode(returnData, (bool))); - - if (!transferred || tokenAddress.code.length == 0) revert TransferFailed(); - } - - function contractId() external pure returns (bytes32) { - return keccak256('axelar-gas-service'); - } -} diff --git a/contracts/test/Imports.sol b/contracts/test/Imports.sol index 701a0b11..8281c493 100644 --- a/contracts/test/Imports.sol +++ b/contracts/test/Imports.sol @@ -3,4 +3,5 @@ pragma solidity ^0.8.0; // solhint-disable no-unused-import -import { InterchainAddressTracker } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/InterchainAddressTracker.sol'; +import { MockGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/test/mocks/MockGateway.sol'; +import { AxelarGasService } from '@axelar-network/axelar-cgp-solidity/contracts/gas-service/AxelarGasService.sol'; diff --git a/contracts/test/InvalidStandardizedToken.sol b/contracts/test/InvalidStandardizedToken.sol index 56a2a60d..021759cd 100644 --- a/contracts/test/InvalidStandardizedToken.sol +++ b/contracts/test/InvalidStandardizedToken.sol @@ -2,17 +2,18 @@ pragma solidity ^0.8.0; +import { AddressBytes } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/AddressBytes.sol'; + import { IERC20MintableBurnable } from '../interfaces/IERC20MintableBurnable.sol'; import { ITokenManager } from '../interfaces/ITokenManager.sol'; import { InterchainToken } from '../interchain-token/InterchainToken.sol'; import { ERC20Permit } from '../token-implementations/ERC20Permit.sol'; -import { AddressBytesUtils } from '../libraries/AddressBytesUtils.sol'; import { Implementation } from '../utils/Implementation.sol'; import { Distributable } from '../utils/Distributable.sol'; contract InvalidStandardizedToken is IERC20MintableBurnable, InterchainToken, ERC20Permit, Implementation, Distributable { - using AddressBytesUtils for bytes; + using AddressBytes for bytes; string public name; string public symbol; diff --git a/contracts/test/MockAxelarGateway.sol b/contracts/test/MockAxelarGateway.sol deleted file mode 100644 index be7cfd20..00000000 --- a/contracts/test/MockAxelarGateway.sol +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import { IMockAxelarGateway } from '../interfaces/IMockAxelarGateway.sol'; - -contract MockAxelarGateway is IMockAxelarGateway { - mapping(bytes32 => address) private _addressStorage; - mapping(bytes32 => bool) private _boolStorage; - - bytes32 internal constant PREFIX_COMMAND_EXECUTED = keccak256('command-executed'); - bytes32 internal constant PREFIX_TOKEN_ADDRESS = keccak256('token-address'); - bytes32 internal constant PREFIX_TOKEN_TYPE = keccak256('token-type'); - bytes32 internal constant PREFIX_CONTRACT_CALL_APPROVED = keccak256('contract-call-approved'); - bytes32 internal constant PREFIX_CONTRACT_CALL_APPROVED_WITH_MINT = keccak256('contract-call-approved-with-mint'); - - /******************\ - |* Public Methods *| - \******************/ - - function callContract(string calldata destinationChain, string calldata destinationContractAddress, bytes calldata payload) external { - emit ContractCall(msg.sender, destinationChain, destinationContractAddress, keccak256(payload), payload); - } - - function isContractCallApproved( - bytes32 commandId, - string calldata sourceChain, - string calldata sourceAddress, - address contractAddress, - bytes32 payloadHash - ) external view override returns (bool) { - return _boolStorage[_getIsContractCallApprovedKey(commandId, sourceChain, sourceAddress, contractAddress, payloadHash)]; - } - - function validateContractCall( - bytes32 commandId, - string calldata sourceChain, - string calldata sourceAddress, - bytes32 payloadHash - ) external override returns (bool valid) { - bytes32 key = _getIsContractCallApprovedKey(commandId, sourceChain, sourceAddress, msg.sender, payloadHash); - valid = _boolStorage[key]; - if (valid) _boolStorage[key] = false; - } - - /***********\ - |* Getters *| - \***********/ - - function isCommandExecuted(bytes32 commandId) public view override returns (bool) { - return _boolStorage[_getIsCommandExecutedKey(commandId)]; - } - - function tokenAddresses(string calldata symbol) external view returns (address tokenAddress) { - tokenAddress = _addressStorage[_tokenAddressKey(symbol)]; - } - - /********************\ - |* Setter Functions *| - \********************/ - - function approveContractCall(bytes calldata params, bytes32 commandId) external { - ( - string memory sourceChain, - string memory sourceAddress, - address contractAddress, - bytes32 payloadHash, - bytes32 sourceTxHash, - uint256 sourceEventIndex - ) = abi.decode(params, (string, string, address, bytes32, bytes32, uint256)); - - _setContractCallApproved(commandId, sourceChain, sourceAddress, contractAddress, payloadHash); - emit ContractCallApproved(commandId, sourceChain, sourceAddress, contractAddress, payloadHash, sourceTxHash, sourceEventIndex); - } - - function setTokenAddress(string calldata symbol, address tokenAddress) external { - _addressStorage[_tokenAddressKey(symbol)] = tokenAddress; - } - - function setCommandExecuted(bytes32 commandId, bool executed) external { - _setCommandExecuted(commandId, executed); - } - - /********************\ - |* Pure Key Getters *| - \********************/ - - function _getIsCommandExecutedKey(bytes32 commandId) internal pure returns (bytes32) { - return keccak256(abi.encodePacked(PREFIX_COMMAND_EXECUTED, commandId)); - } - - function _getIsContractCallApprovedKey( - bytes32 commandId, - string memory sourceChain, - string memory sourceAddress, - address contractAddress, - bytes32 payloadHash - ) internal pure returns (bytes32) { - return keccak256(abi.encode(PREFIX_CONTRACT_CALL_APPROVED, commandId, sourceChain, sourceAddress, contractAddress, payloadHash)); - } - - function _tokenAddressKey(string memory symbol) internal pure returns (bytes32) { - return keccak256(abi.encodePacked(PREFIX_TOKEN_ADDRESS, symbol)); - } - - /********************\ - |* Internal Setters *| - \********************/ - - function _setCommandExecuted(bytes32 commandId, bool executed) internal { - _boolStorage[_getIsCommandExecutedKey(commandId)] = executed; - } - - function _setContractCallApproved( - bytes32 commandId, - string memory sourceChain, - string memory sourceAddress, - address contractAddress, - bytes32 payloadHash - ) internal { - _boolStorage[_getIsContractCallApprovedKey(commandId, sourceChain, sourceAddress, contractAddress, payloadHash)] = true; - } -} diff --git a/contracts/test/utils/AddressBytesUtilsTest.sol b/contracts/test/utils/AddressBytesUtilsTest.sol deleted file mode 100644 index d25355cb..00000000 --- a/contracts/test/utils/AddressBytesUtilsTest.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import { AddressBytesUtils } from '../../libraries/AddressBytesUtils.sol'; - -contract AddressBytesUtilsTest { - using AddressBytesUtils for address; - using AddressBytesUtils for bytes; - - function toAddress(bytes memory bytesAddress) external pure returns (address addr) { - return bytesAddress.toAddress(); - } - - function toBytes(address addr) external pure returns (bytes memory bytesAddress) { - return addr.toBytes(); - } -} diff --git a/contracts/token-implementations/StandardizedToken.sol b/contracts/token-implementations/StandardizedToken.sol index fce1d2f0..66975e61 100644 --- a/contracts/token-implementations/StandardizedToken.sol +++ b/contracts/token-implementations/StandardizedToken.sol @@ -2,13 +2,14 @@ pragma solidity ^0.8.0; +import { AddressBytes } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/AddressBytes.sol'; + import { IImplementation } from '../interfaces/IImplementation.sol'; import { IStandardizedToken } from '../interfaces/IStandardizedToken.sol'; import { ITokenManager } from '../interfaces/ITokenManager.sol'; import { InterchainToken } from '../interchain-token/InterchainToken.sol'; import { ERC20Permit } from '../token-implementations/ERC20Permit.sol'; -import { AddressBytesUtils } from '../libraries/AddressBytesUtils.sol'; import { Implementation } from '../utils/Implementation.sol'; import { Distributable } from '../utils/Distributable.sol'; @@ -18,7 +19,7 @@ import { Distributable } from '../utils/Distributable.sol'; * This contract also inherits Distributable and Implementation logic. */ contract StandardizedToken is InterchainToken, ERC20Permit, Implementation, Distributable, IStandardizedToken { - using AddressBytesUtils for bytes; + using AddressBytes for bytes; string public name; string public symbol; diff --git a/contracts/token-manager/TokenManager.sol b/contracts/token-manager/TokenManager.sol index 4f355a0d..9a020847 100644 --- a/contracts/token-manager/TokenManager.sol +++ b/contracts/token-manager/TokenManager.sol @@ -2,12 +2,13 @@ pragma solidity ^0.8.0; +import { AddressBytes } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/AddressBytes.sol'; + import { ITokenManager } from '../interfaces/ITokenManager.sol'; import { IInterchainTokenService } from '../interfaces/IInterchainTokenService.sol'; import { Operatable } from '../utils/Operatable.sol'; import { FlowLimit } from '../utils/FlowLimit.sol'; -import { AddressBytesUtils } from '../libraries/AddressBytesUtils.sol'; import { Implementation } from '../utils/Implementation.sol'; /** @@ -15,7 +16,7 @@ import { Implementation } from '../utils/Implementation.sol'; * @notice This contract is responsible for handling tokens before initiating a cross chain token transfer, or after receiving one. */ abstract contract TokenManager is ITokenManager, Operatable, FlowLimit, Implementation { - using AddressBytesUtils for bytes; + using AddressBytes for bytes; IInterchainTokenService public immutable interchainTokenService; diff --git a/contracts/token-registrars/CanonicalTokenRegistrar.sol b/contracts/token-registrars/CanonicalTokenRegistrar.sol deleted file mode 100644 index 531c69d7..00000000 --- a/contracts/token-registrars/CanonicalTokenRegistrar.sol +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import { Upgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Upgradable.sol'; -import { SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol'; -import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; -import { Multicall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/Multicall.sol'; - -import { IInterchainTokenService } from '../interfaces/IInterchainTokenService.sol'; -import { ICanonicalTokenRegistrar } from '../interfaces/ICanonicalTokenRegistrar.sol'; -import { ITokenManagerType } from '../interfaces/ITokenManagerType.sol'; -import { IERC20Named } from '../interfaces/IERC20Named.sol'; - -contract CanonicalTokenRegistrar is ICanonicalTokenRegistrar, ITokenManagerType, Multicall, Upgradable { - using SafeTokenTransferFrom for IERC20; - - IInterchainTokenService public immutable service; - bytes32 public immutable chainNameHash; - - bytes32 internal constant PREFIX_CANONICAL_TOKEN_SALT = keccak256('canonical-token-salt'); - bytes32 private constant CONTRACT_ID = keccak256('canonical-token-registrar'); - - constructor(address interchainTokenService) { - if (interchainTokenService == address(0)) revert ZeroAddress(); - service = IInterchainTokenService(interchainTokenService); - string memory chainName_ = IInterchainTokenService(interchainTokenService).interchainAddressTracker().chainName(); - chainNameHash = keccak256(bytes(chainName_)); - } - - /** - * @notice Getter for the contract id. - */ - function contractId() external pure returns (bytes32) { - return CONTRACT_ID; - } - - function canonicalTokenSalt(address tokenAddress) public view returns (bytes32 salt) { - salt = keccak256(abi.encode(PREFIX_CANONICAL_TOKEN_SALT, chainNameHash, tokenAddress)); - } - - function canonicalTokenId(address tokenAddress) public view returns (bytes32 tokenId) { - tokenId = service.tokenId(address(this), canonicalTokenSalt(tokenAddress)); - } - - function registerCanonicalToken(address tokenAddress) external payable returns (bytes32 tokenId) { - bytes memory params = abi.encode('', tokenAddress); - bytes32 salt = canonicalTokenSalt(tokenAddress); - tokenId = service.deployTokenManager(salt, '', TokenManagerType.LOCK_UNLOCK, params, 0); - } - - function deployAndRegisterRemoteCanonicalToken(bytes32 salt, string calldata destinationChain, uint256 gasValue) external payable { - // This ensures that the token manager has been deployed by this address, so it's safe to trust it. - bytes32 tokenId = service.tokenId(address(this), salt); - IERC20Named token = IERC20Named(service.tokenAddress(tokenId)); - // The 3 lines below will revert if the token manager does not exist. - string memory tokenName = token.name(); - string memory tokenSymbol = token.symbol(); - uint8 tokenDecimals = token.decimals(); - - // slither-disable-next-line arbitrary-send-eth - service.deployInterchainToken{ value: gasValue }(salt, destinationChain, tokenName, tokenSymbol, tokenDecimals, '', '', gasValue); - } - - function transferCanonicalToken( - address tokenAddress, - string calldata destinationChain, - bytes calldata destinationAddress, - uint256 amount, - uint256 gasValue - ) external payable { - // This ensures that the token manager has been deployed by this address, so it's safe to trust it. - bytes32 tokenId = canonicalTokenId(tokenAddress); - // slither-disable-next-line unused-return - service.validTokenManagerAddress(tokenId); - IERC20 token = IERC20(tokenAddress); - - token.safeTransferFrom(msg.sender, address(this), amount); - - address tokenManagerAddress = service.tokenManagerAddress(tokenId); - if (!token.approve(tokenManagerAddress, amount)) revert ApproveFailed(); - - // slither-disable-next-line arbitrary-send-eth - service.interchainTransfer{ value: gasValue }(tokenId, destinationChain, destinationAddress, amount, bytes('')); - } -} diff --git a/contracts/token-registrars/StandardizedTokenRegistrar.sol b/contracts/token-registrars/StandardizedTokenRegistrar.sol deleted file mode 100644 index 01f28917..00000000 --- a/contracts/token-registrars/StandardizedTokenRegistrar.sol +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import { SafeTokenTransfer } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol'; -import { Multicall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/Multicall.sol'; - -import { Upgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Upgradable.sol'; - -import { IInterchainTokenService } from '../interfaces/IInterchainTokenService.sol'; -import { IStandardizedTokenRegistrar } from '../interfaces/IStandardizedTokenRegistrar.sol'; -import { ITokenManagerType } from '../interfaces/ITokenManagerType.sol'; -import { ITokenManager } from '../interfaces/ITokenManager.sol'; -import { IStandardizedToken } from '../interfaces/IStandardizedToken.sol'; - -import { AddressBytesUtils } from '../libraries/AddressBytesUtils.sol'; - -contract StandardizedTokenRegistrar is IStandardizedTokenRegistrar, ITokenManagerType, Multicall, Upgradable { - using AddressBytesUtils for bytes; - using AddressBytesUtils for address; - using SafeTokenTransfer for IStandardizedToken; - - IInterchainTokenService public immutable service; - bytes32 public immutable chainNameHash; - - bytes32 private constant CONTRACT_ID = keccak256('standardized-token-registrar'); - - struct DeployParams { - address deployer; - bytes distributor; - bytes operator; - } - - mapping(bytes32 => DeployParams) public deploymentParameterMap; - - bytes32 internal constant PREFIX_STANDARDIZED_TOKEN_SALT = keccak256('standardized-token-salt'); - - constructor(address interchainTokenServiceAddress) { - if (interchainTokenServiceAddress == address(0)) revert ZeroAddress(); - service = IInterchainTokenService(interchainTokenServiceAddress); - string memory chainName_ = IInterchainTokenService(interchainTokenServiceAddress).interchainAddressTracker().chainName(); - chainNameHash = keccak256(bytes(chainName_)); - } - - /** - * @notice Getter for the contract id. - */ - function contractId() external pure returns (bytes32) { - return CONTRACT_ID; - } - - function standardizedTokenSalt(address deployer, bytes32 salt) public view returns (bytes32) { - return keccak256(abi.encode(PREFIX_STANDARDIZED_TOKEN_SALT, chainNameHash, deployer, salt)); - } - - function standardizedTokenId(address deployer, bytes32 salt) public view returns (bytes32 tokenId) { - tokenId = service.tokenId(address(this), standardizedTokenSalt(deployer, salt)); - } - - function interchainTokenAddress(address deployer, bytes32 salt) public view returns (address tokenAddress) { - tokenAddress = service.interchainTokenAddress(standardizedTokenId(deployer, salt)); - } - - function deployInterchainToken( - bytes32 salt, - string calldata name, - string calldata symbol, - uint8 decimals, - uint256 mintAmount, - address distributor, - address operator - ) external payable { - address sender = msg.sender; - salt = standardizedTokenSalt(sender, salt); - bytes32 tokenId = service.tokenId(address(this), salt); - - service.deployInterchainToken(salt, '', name, symbol, decimals, address(this).toBytes(), operator.toBytes(), 0); - ITokenManager tokenManager = ITokenManager(service.tokenManagerAddress(tokenId)); - tokenManager.transferOperatorship(sender); - - IStandardizedToken token = IStandardizedToken(service.interchainTokenAddress(tokenId)); - token.mint(sender, mintAmount); - token.transferDistributorship(distributor); - } - - function deployRemoteStandarizedToken( - bytes32 salt, - address additionalDistributor, - address optionalOperator, - uint256 mintAmount, - string memory destinationChain, - uint256 gasValue - ) external payable { - string memory tokenName; - string memory tokenSymbol; - uint8 tokenDecimals; - bytes memory distributor = new bytes(0); - bytes memory operator = new bytes(0); - - { - address sender = msg.sender; - salt = standardizedTokenSalt(sender, salt); - bytes32 tokenId = service.tokenId(address(this), salt); - - IStandardizedToken token = IStandardizedToken(service.interchainTokenAddress(tokenId)); - ITokenManager tokenManager = ITokenManager(service.tokenManagerAddress(tokenId)); - - tokenName = token.name(); - tokenSymbol = token.symbol(); - tokenDecimals = token.decimals(); - if (additionalDistributor != address(0)) { - if (!token.isDistributor(additionalDistributor)) revert NotDistributor(additionalDistributor); - distributor = additionalDistributor.toBytes(); - } else if (mintAmount != 0) { - revert NonZeroMintAmount(); - } - if (optionalOperator != address(0)) { - if (!tokenManager.isOperator(optionalOperator)) revert NotOperator(optionalOperator); - operator = optionalOperator.toBytes(); - } - } - - _deployInterchainToken(salt, tokenName, tokenSymbol, tokenDecimals, distributor, operator, destinationChain, gasValue); - } - - function _deployInterchainToken( - bytes32 salt, - string memory tokenName, - string memory tokenSymbol, - uint8 tokenDecimals, - bytes memory distributor, - bytes memory operator, - string memory destinationChain, - uint256 gasValue - ) internal { - // slither-disable-next-line arbitrary-send-eth - service.deployInterchainToken{ value: gasValue }( - salt, - destinationChain, - tokenName, - tokenSymbol, - tokenDecimals, - distributor, - operator, - gasValue - ); - } -} diff --git a/contracts/token-registrars/TokenRegistrar.sol b/contracts/token-registrars/TokenRegistrar.sol new file mode 100644 index 00000000..0108b177 --- /dev/null +++ b/contracts/token-registrars/TokenRegistrar.sol @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { AddressBytes } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/AddressBytes.sol'; +import { SafeTokenTransfer, SafeTokenTransferFrom, SafeTokenCall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol'; +import { Multicall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/Multicall.sol'; +import { Upgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Upgradable.sol'; + +import { IInterchainTokenService } from '../interfaces/IInterchainTokenService.sol'; +import { ITokenRegistrar } from '../interfaces/ITokenRegistrar.sol'; +import { ITokenManagerType } from '../interfaces/ITokenManagerType.sol'; +import { ITokenManager } from '../interfaces/ITokenManager.sol'; +import { IStandardizedToken } from '../interfaces/IStandardizedToken.sol'; + +contract TokenRegistrar is ITokenRegistrar, ITokenManagerType, Multicall, Upgradable { + using AddressBytes for bytes; + using AddressBytes for address; + using SafeTokenTransfer for IStandardizedToken; + using SafeTokenTransferFrom for IStandardizedToken; + using SafeTokenCall for IStandardizedToken; + + error NotApproved(address tokenAddress); + + IInterchainTokenService public immutable service; + bytes32 public immutable chainNameHash; + + bytes32 private constant CONTRACT_ID = keccak256('token-registrar'); + bytes32 internal constant PREFIX_CANONICAL_TOKEN_SALT = keccak256('canonical-token-salt'); + bytes32 internal constant PREFIX_STANDARDIZED_TOKEN_SALT = keccak256('standardized-token-salt'); + + constructor(address interchainTokenServiceAddress) { + if (interchainTokenServiceAddress == address(0)) revert ZeroAddress(); + service = IInterchainTokenService(interchainTokenServiceAddress); + string memory chainName_ = IInterchainTokenService(interchainTokenServiceAddress).interchainAddressTracker().chainName(); + chainNameHash = keccak256(bytes(chainName_)); + } + + /** + * @notice Getter for the contract id. + */ + function contractId() external pure returns (bytes32) { + return CONTRACT_ID; + } + + function standardizedTokenSalt(bytes32 chainNameHash_, address deployer, bytes32 salt) public pure returns (bytes32) { + return keccak256(abi.encode(PREFIX_STANDARDIZED_TOKEN_SALT, chainNameHash_, deployer, salt)); + } + + function standardizedTokenId(address deployer, bytes32 salt) public view returns (bytes32 tokenId) { + tokenId = service.tokenId(address(this), standardizedTokenSalt(chainNameHash, deployer, salt)); + } + + function interchainTokenAddress(address deployer, bytes32 salt) public view returns (address tokenAddress) { + tokenAddress = service.interchainTokenAddress(standardizedTokenId(deployer, salt)); + } + + function deployInterchainToken( + bytes32 salt, + string calldata name, + string calldata symbol, + uint8 decimals, + uint256 mintAmount, + address distributor, + address operator + ) external payable { + address sender = msg.sender; + salt = standardizedTokenSalt(chainNameHash, sender, salt); + bytes memory distributorBytes; + + if (mintAmount > 0) { + distributorBytes = address(this).toBytes(); + } else { + distributorBytes = distributor.toBytes(); + } + + _deployInterchainToken(salt, '', name, symbol, decimals, distributorBytes, operator.toBytes(), 0); + + if (mintAmount > 0) { + bytes32 tokenId = service.tokenId(address(this), salt); + IStandardizedToken token = IStandardizedToken(service.interchainTokenAddress(tokenId)); + token.mint(address(this), mintAmount); + token.transferDistributorship(distributor); + } + } + + function deployRemoteInterchainToken( + string calldata originalChainName, + bytes32 salt, + address additionalDistributor, + address optionalOperator, + string memory destinationChain, + uint256 gasValue + ) external payable { + string memory tokenName; + string memory tokenSymbol; + uint8 tokenDecimals; + bytes memory distributor = new bytes(0); + bytes memory operator = new bytes(0); + + { + bytes32 chainNameHash_; + if (bytes(originalChainName).length == 0) { + chainNameHash_ = chainNameHash; + } else { + chainNameHash_ = keccak256(bytes(originalChainName)); + } + address sender = msg.sender; + salt = standardizedTokenSalt(chainNameHash_, sender, salt); + bytes32 tokenId = service.tokenId(address(this), salt); + + IStandardizedToken token = IStandardizedToken(service.interchainTokenAddress(tokenId)); + ITokenManager tokenManager = ITokenManager(service.tokenManagerAddress(tokenId)); + + tokenName = token.name(); + tokenSymbol = token.symbol(); + tokenDecimals = token.decimals(); + if (additionalDistributor != address(0)) { + if (!token.isDistributor(additionalDistributor)) revert NotDistributor(additionalDistributor); + distributor = additionalDistributor.toBytes(); + } + + if (optionalOperator != address(0)) { + if (!tokenManager.isOperator(optionalOperator)) revert NotOperator(optionalOperator); + operator = optionalOperator.toBytes(); + } + } + + _deployInterchainToken(salt, destinationChain, tokenName, tokenSymbol, tokenDecimals, distributor, operator, gasValue); + } + + function _deployInterchainToken( + bytes32 salt, + string memory destinationChain, + string memory tokenName, + string memory tokenSymbol, + uint8 tokenDecimals, + bytes memory distributor, + bytes memory operator, + uint256 gasValue + ) internal { + // slither-disable-next-line arbitrary-send-eth + service.deployInterchainToken{ value: gasValue }( + salt, + destinationChain, + tokenName, + tokenSymbol, + tokenDecimals, + distributor, + operator, + gasValue + ); + } + + function canonicalTokenSalt(bytes32 chainNameHash_, address tokenAddress) public pure returns (bytes32 salt) { + salt = keccak256(abi.encode(PREFIX_CANONICAL_TOKEN_SALT, chainNameHash_, tokenAddress)); + } + + function canonicalTokenId(address tokenAddress) public view returns (bytes32 tokenId) { + tokenId = service.tokenId(address(this), canonicalTokenSalt(chainNameHash, tokenAddress)); + } + + function registerCanonicalToken(address tokenAddress) external payable returns (bytes32 tokenId) { + bytes memory params = abi.encode('', tokenAddress); + bytes32 salt = canonicalTokenSalt(chainNameHash, tokenAddress); + tokenId = service.deployTokenManager(salt, '', TokenManagerType.LOCK_UNLOCK, params, 0); + } + + function deployRemoteCanonicalToken( + string calldata originalChain, + address originalTokenAddress, + string calldata destinationChain, + uint256 gasValue + ) external payable { + bytes32 salt; + IStandardizedToken token; + + { + bytes32 chainNameHash_; + if (bytes(originalChain).length == 0) { + chainNameHash_ = chainNameHash; + } else { + chainNameHash_ = keccak256(bytes(originalChain)); + } + // This ensures that the token manager has been deployed by this address, so it's safe to trust it. + salt = canonicalTokenSalt(chainNameHash_, originalTokenAddress); + bytes32 tokenId = service.tokenId(address(this), salt); + token = IStandardizedToken(service.tokenAddress(tokenId)); + } + + // The 3 lines below will revert if the token does not exist. + string memory tokenName = token.name(); + string memory tokenSymbol = token.symbol(); + uint8 tokenDecimals = token.decimals(); + + // slither-disable-next-line arbitrary-send-eth + service.deployInterchainToken{ value: gasValue }(salt, destinationChain, tokenName, tokenSymbol, tokenDecimals, '', '', gasValue); + } + + function interchainTransfer( + bytes32 tokenId, + string calldata destinationChain, + bytes calldata destinationAddress, + uint256 amount, + uint256 gasValue + ) external payable { + if (bytes(destinationChain).length == 0) { + address tokenAddress = service.interchainTokenAddress(tokenId); + IStandardizedToken token = IStandardizedToken(tokenAddress); + token.safeTransfer(destinationAddress.toAddress(), amount); + } else { + // slither-disable-next-line arbitrary-send-eth + service.interchainTransfer{ value: gasValue }(tokenId, destinationChain, destinationAddress, amount, new bytes(0)); + } + } + + function interchainTransferFrom( + bytes32 tokenId, + string calldata destinationChain, + bytes calldata destinationAddress, + uint256 amount, + uint256 gasValue + ) external payable { + address tokenAddress = service.interchainTokenAddress(tokenId); + IStandardizedToken token = IStandardizedToken(tokenAddress); + + if (bytes(destinationChain).length == 0) { + token.safeTransferFrom(msg.sender, destinationAddress.toAddress(), amount); + } else { + token.safeTransferFrom(msg.sender, address(this), amount); + if (!token.approve(address(service), amount)) revert NotApproved(tokenAddress); + + // slither-disable-next-line arbitrary-send-eth + service.interchainTransfer{ value: gasValue }(tokenId, destinationChain, destinationAddress, amount, new bytes(0)); + } + } +} diff --git a/scripts/deploy.js b/scripts/deploy.js index ad2c4385..89c4a0a7 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -22,7 +22,7 @@ async function deployAddressTracker(wallet, chainName, interchainTokenServiceAdd } async function deployMockGateway(wallet) { - const gateway = await deployContract(wallet, 'MockAxelarGateway'); + const gateway = await deployContract(wallet, 'MockGateway'); return gateway; } diff --git a/test/TokenRegistrars.js b/test/TokenRegistrars.js index f7c3d26c..adfd49a7 100644 --- a/test/TokenRegistrars.js +++ b/test/TokenRegistrars.js @@ -1,5 +1,5 @@ 'use strict'; -/* + const chai = require('chai'); const { expect } = chai; require('dotenv').config(); @@ -7,13 +7,15 @@ const { ethers } = require('hardhat'); const { defaultAbiCoder, keccak256 } = ethers.utils; const { Contract, + Wallet, constants: { AddressZero }, } = ethers; const ITokenManager = require('../artifacts/contracts/interfaces/ITokenManager.sol/ITokenManager.json'); -const IERC20 = require('../artifacts/@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol/IERC20.json'); +const IStandardizedToken = require('../artifacts/contracts/interfaces/IStandardizedToken.sol/IStandardizedToken.json'); const { deployAll, deployContract } = require('../scripts/deploy'); +const { getRandomBytes32 } = require('./utils'); // const SELECTOR_SEND_TOKEN_WITH_DATA = 2; // const SELECTOR_DEPLOY_TOKEN_MANAGER = 3; @@ -22,13 +24,14 @@ const SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN = 4; const LOCK_UNLOCK = 2; const MINT_BURN = 0; -// const DISTRIBUTOR_ROLE = 0; +const DISTRIBUTOR_ROLE = 0; const OPERATOR_ROLE = 1; +const FLOW_LIMITER_ROLE = 2; - -describe('Token Registrsrs', () => { +describe('Token Registrars', () => { let wallet; - let service, gateway, gasService, canonicalTokenRegistrar, standardizedTokenRegistrar; + let service, gateway, gasService, tokenRegistrar; + const chainName = 'Test'; const name = 'tokenName'; const symbol = 'tokenSymbol'; const decimals = 18; @@ -37,18 +40,12 @@ describe('Token Registrsrs', () => { before(async () => { const wallets = await ethers.getSigners(); wallet = wallets[0]; - [service, gateway, gasService] = await deployAll(wallet, 'Test', [destinationChain]); - let proxy, factory; - - canonicalTokenRegistrar = await deployContract(wallet, 'CanonicalTokenRegistrar', [service.address]); - proxy = await deployContract(wallet, 'CanonicalTokenRegistrarProxy', [canonicalTokenRegistrar.address, wallet.address]); - factory = await ethers.getContractFactory('CanonicalTokenRegistrar', wallet); - canonicalTokenRegistrar = factory.attach(proxy.address); - - standardizedTokenRegistrar = await deployContract(wallet, 'StandardizedTokenRegistrar', [service.address]); - proxy = await deployContract(wallet, 'StandardizedTokenRegistrarProxy', [standardizedTokenRegistrar.address, wallet.address]); - factory = await ethers.getContractFactory('StandardizedTokenRegistrar', wallet); - standardizedTokenRegistrar = factory.attach(proxy.address); + [service, gateway, gasService] = await deployAll(wallet, chainName, [destinationChain]); + + tokenRegistrar = await deployContract(wallet, 'TokenRegistrar', [service.address]); + const proxy = await deployContract(wallet, 'TokenRegistrarProxy', [tokenRegistrar.address, wallet.address]); + const factory = await ethers.getContractFactory('TokenRegistrar', wallet); + tokenRegistrar = factory.attach(proxy.address); }); describe('Canonical Token Registrar', async () => { @@ -57,7 +54,7 @@ describe('Token Registrsrs', () => { async function deployToken() { token = await deployContract(wallet, 'InterchainTokenTest', [name, symbol, decimals, wallet.address]); - tokenId = await canonicalTokenRegistrar.canonicalTokenId(token.address); + tokenId = await tokenRegistrar.canonicalTokenId(token.address); tokenManagerAddress = await service.tokenManagerAddress(tokenId); await (await token.mint(wallet.address, tokenCap)).wait(); await (await token.setTokenManager(tokenManagerAddress)).wait(); @@ -68,7 +65,7 @@ describe('Token Registrsrs', () => { const params = defaultAbiCoder.encode(['bytes', 'address'], ['0x', token.address]); - await expect(canonicalTokenRegistrar.registerCanonicalToken(token.address)) + await expect(tokenRegistrar.registerCanonicalToken(token.address)) .to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, tokenManagerAddress, LOCK_UNLOCK, params); }); @@ -78,81 +75,87 @@ describe('Token Registrsrs', () => { await deployToken(); - const salt = await canonicalTokenRegistrar.canonicalTokenSalt(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'], + ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes'], + [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, name, symbol, decimals, '0x', '0x'], ); - await expect(canonicalTokenRegistrar.registerCanonicalToken(token.address)) + await expect(tokenRegistrar.registerCanonicalToken(token.address)) .to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, tokenManagerAddress, LOCK_UNLOCK, params); await expect( - canonicalTokenRegistrar.deployAndRegisterRemoteCanonicalToken(salt, destinationChain, gasValue, { value: gasValue }), + tokenRegistrar.deployRemoteCanonicalToken(chainName, token.address, destinationChain, gasValue, { value: gasValue }), ) .to.emit(service, 'RemoteInterchainTokenDeploymentInitialized') - .withArgs(tokenId, name, symbol, decimals, '0x', '0x', 0, '0x', destinationChain, gasValue) + .withArgs(tokenId, name, symbol, decimals, '0x', '0x', destinationChain, gasValue) .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, destinationChain, service.address, keccak256(payload), gasValue, wallet.address) .and.to.emit(gateway, 'ContractCall') .withArgs(service.address, destinationChain, service.address, keccak256(payload), payload); }); - it('Should transfer some tokens though the registrar as the deployer', async () => { - const destinationAddress = '0x659703'; - const amount = 1234; - const gasValue = 1234; - + it('Should transfer some tokens through the registrar as the deployer', async () => { await deployToken(); const params = defaultAbiCoder.encode(['bytes', 'address'], ['0x', token.address]); - await expect(canonicalTokenRegistrar.registerCanonicalToken(token.address)) + await expect(tokenRegistrar.registerCanonicalToken(token.address)) .to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, tokenManagerAddress, LOCK_UNLOCK, params); tokenManagerAddress = await service.validTokenManagerAddress(tokenId); - await (await token.approve(canonicalTokenRegistrar.address, amount)).wait(); - - await expect( - canonicalTokenRegistrar.transferCanonicalToken(token.address, destinationChain, destinationAddress, amount, gasValue, { - value: gasValue, - }), - ) - .to.emit(service, 'TokenSent') - .withArgs(tokenId, destinationChain, destinationAddress, amount) - .to.emit(token, 'Transfer') - .withArgs(wallet.address, canonicalTokenRegistrar.address, amount) - .to.emit(token, 'Transfer') - .withArgs(canonicalTokenRegistrar.address, tokenManagerAddress, amount); + // TODO: fix test + // await token.approve(tokenRegistrar.address, amount).then((tx) => tx.wait()); + + // await expect( + // tokenRegistrar.interchainTransferFrom(tokenId, '', arrayify(wallet.address), amount, 0), + // ) + // // .to.emit(service, 'TokenSent') + // // .withArgs(tokenId, destinationChain, destinationAddress, amount) + // .to.emit(token, 'Transfer') + // .withArgs(wallet.address, tokenRegistrar.address, amount) + // .to.emit(token, 'Transfer') + // .withArgs(tokenRegistrar.address, wallet.address, amount); }); }); describe('Standardized Token Registrar', async () => { let tokenId; const mintAmount = 1234; + const distributor = new Wallet(getRandomBytes32()).address; + const operator = new Wallet(getRandomBytes32()).address; it('Should register a token', async () => { const salt = keccak256('0x'); - tokenId = await standardizedTokenRegistrar.standardizedTokenId(wallet.address, salt); - const tokenAddress = await standardizedTokenRegistrar.interchainTokenAddress(wallet.address, salt); - const params = defaultAbiCoder.encode(['bytes', 'address'], [standardizedTokenRegistrar.address, tokenAddress]); + tokenId = await tokenRegistrar.standardizedTokenId(wallet.address, salt); + const tokenAddress = await tokenRegistrar.interchainTokenAddress(wallet.address, salt); + const params = defaultAbiCoder.encode(['bytes', 'address'], [operator, tokenAddress]); const tokenManager = new Contract(await service.tokenManagerAddress(tokenId), ITokenManager.abi, wallet); - const token = new Contract(tokenAddress, IERC20.abi, wallet); - await expect(standardizedTokenRegistrar.deployInterchainToken(salt, name, symbol, decimals, mintAmount, wallet.address)) + const token = new Contract(tokenAddress, IStandardizedToken.abi, wallet); + + await expect(tokenRegistrar.deployInterchainToken(salt, name, symbol, decimals, mintAmount, distributor, operator)) .to.emit(service, 'InterchainTokenDeployed') - .withArgs(tokenId, tokenAddress, wallet.address, name, symbol, decimals, mintAmount, standardizedTokenRegistrar.address) + .withArgs(tokenId, tokenAddress, tokenRegistrar.address, name, symbol, decimals) .and.to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, tokenManager.address, MINT_BURN, params) .and.to.emit(token, 'Transfer') - .withArgs(standardizedTokenRegistrar.address, wallet.address, mintAmount) + .withArgs(AddressZero, tokenRegistrar.address, mintAmount) .and.to.emit(tokenManager, 'RolesAdded') - .withArgs(wallet.address, 1 << OPERATOR_ROLE) - .and.to.emit(tokenManager, 'RolesRemoved') - .withArgs(standardizedTokenRegistrar.address, 1 << OPERATOR_ROLE); + .withArgs(operator, (1 << OPERATOR_ROLE) | (1 << FLOW_LIMITER_ROLE)) + .and.to.emit(token, 'RolesAdded') + .withArgs(distributor, 1 << DISTRIBUTOR_ROLE) + .and.to.emit(token, 'RolesRemoved') + .withArgs(tokenRegistrar.address, 1 << DISTRIBUTOR_ROLE); + + await expect(tokenRegistrar.interchainTransfer(tokenId, '', distributor, mintAmount, 0)) + .to.emit(token, 'Transfer') + .withArgs(tokenRegistrar.address, distributor, mintAmount); + + expect(await token.balanceOf(tokenRegistrar.address)).to.equal(0); + expect(await token.balanceOf(distributor)).to.equal(mintAmount); }); it('Should initiate a remote standardized token deployment with the same distributor', async () => { @@ -160,27 +163,27 @@ describe('Token Registrsrs', () => { const mintAmount = 5678; const salt = keccak256('0x12'); - tokenId = await standardizedTokenRegistrar.standardizedTokenId(wallet.address, salt); - const tokenAddress = await standardizedTokenRegistrar.interchainTokenAddress(wallet.address, salt); - let params = defaultAbiCoder.encode(['bytes', 'address'], [standardizedTokenRegistrar.address, tokenAddress]); + tokenId = await tokenRegistrar.standardizedTokenId(wallet.address, salt); + const tokenAddress = await tokenRegistrar.interchainTokenAddress(wallet.address, salt); + let params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, tokenAddress]); const tokenManager = new Contract(await service.tokenManagerAddress(tokenId), ITokenManager.abi, wallet); - const token = new Contract(tokenAddress, IERC20.abi, wallet); + const token = new Contract(tokenAddress, IStandardizedToken.abi, wallet); - await expect(standardizedTokenRegistrar.deployInterchainToken(salt, name, symbol, decimals, mintAmount, wallet.address)) + await expect(tokenRegistrar.deployInterchainToken(salt, name, symbol, decimals, mintAmount, wallet.address, wallet.address)) .to.emit(service, 'InterchainTokenDeployed') - .withArgs(tokenId, tokenAddress, wallet.address, name, symbol, decimals, mintAmount, standardizedTokenRegistrar.address) + .withArgs(tokenId, tokenAddress, tokenRegistrar.address, name, symbol, decimals) .and.to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, tokenManager.address, MINT_BURN, params) .and.to.emit(token, 'Transfer') - .withArgs(standardizedTokenRegistrar.address, wallet.address, mintAmount) - .and.to.emit(tokenManager, 'RolesAdded') - .withArgs(wallet.address, 1 << OPERATOR_ROLE) - .and.to.emit(tokenManager, 'RolesRemoved') - .withArgs(standardizedTokenRegistrar.address, 1 << OPERATOR_ROLE); + .withArgs(AddressZero, tokenRegistrar.address, mintAmount) + .and.to.emit(token, 'RolesAdded') + .withArgs(wallet.address, 1 << DISTRIBUTOR_ROLE) + .and.to.emit(token, 'RolesRemoved') + .withArgs(tokenRegistrar.address, 1 << DISTRIBUTOR_ROLE); params = defaultAbiCoder.encode(['bytes', 'address'], ['0x', token.address]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes', 'uint256', 'bytes'], + ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes'], [ SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, @@ -189,23 +192,13 @@ describe('Token Registrsrs', () => { decimals, wallet.address.toLowerCase(), wallet.address.toLowerCase(), - mintAmount, - wallet.address.toLowerCase(), ], ); await expect( - standardizedTokenRegistrar.deployRemoteStandarizedToken( - salt, - wallet.address, - wallet.address, - mintAmount, - destinationChain, - gasValue, - { - value: gasValue, - }, - ), + tokenRegistrar.deployRemoteInterchainToken(chainName, salt, wallet.address, wallet.address, destinationChain, gasValue, { + value: gasValue, + }), ) .to.emit(service, 'RemoteInterchainTokenDeploymentInitialized') .withArgs( @@ -215,8 +208,6 @@ describe('Token Registrsrs', () => { decimals, wallet.address.toLowerCase(), wallet.address.toLowerCase(), - mintAmount, - wallet.address.toLowerCase(), destinationChain, gasValue, ) @@ -230,90 +221,41 @@ describe('Token Registrsrs', () => { const gasValue = 1234; const salt = keccak256('0x1245'); - tokenId = await standardizedTokenRegistrar.standardizedTokenId(wallet.address, salt); - const tokenAddress = await standardizedTokenRegistrar.interchainTokenAddress(wallet.address, salt); - let params = defaultAbiCoder.encode(['bytes', 'address'], [standardizedTokenRegistrar.address, tokenAddress]); + tokenId = await tokenRegistrar.standardizedTokenId(wallet.address, salt); + const tokenAddress = await tokenRegistrar.interchainTokenAddress(wallet.address, salt); + let params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, tokenAddress]); const tokenManager = new Contract(await service.tokenManagerAddress(tokenId), ITokenManager.abi, wallet); - const token = new Contract(tokenAddress, IERC20.abi, wallet); - await expect(standardizedTokenRegistrar.deployInterchainToken(salt, name, symbol, decimals, mintAmount, wallet.address)) + const token = new Contract(tokenAddress, IStandardizedToken.abi, wallet); + + await expect(tokenRegistrar.deployInterchainToken(salt, name, symbol, decimals, mintAmount, wallet.address, wallet.address)) .to.emit(service, 'InterchainTokenDeployed') - .withArgs(tokenId, tokenAddress, wallet.address, name, symbol, decimals, mintAmount, standardizedTokenRegistrar.address) + .withArgs(tokenId, tokenAddress, tokenRegistrar.address, name, symbol, decimals) .and.to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, tokenManager.address, MINT_BURN, params) .and.to.emit(token, 'Transfer') - .withArgs(standardizedTokenRegistrar.address, wallet.address, mintAmount) - .and.to.emit(tokenManager, 'RolesAdded') - .withArgs(wallet.address, 1 << OPERATOR_ROLE) - .and.to.emit(tokenManager, 'RolesRemoved') - .withArgs(standardizedTokenRegistrar.address, 1 << OPERATOR_ROLE); + .withArgs(AddressZero, tokenRegistrar.address, mintAmount) + .and.to.emit(token, 'RolesAdded') + .withArgs(wallet.address, 1 << DISTRIBUTOR_ROLE) + .and.to.emit(token, 'RolesRemoved') + .withArgs(tokenRegistrar.address, 1 << DISTRIBUTOR_ROLE); 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, - wallet.address.toLowerCase(), - ], + ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes'], + [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, name, symbol, decimals, '0x', wallet.address], ); await expect( - standardizedTokenRegistrar.deployRemoteStandarizedToken(salt, AddressZero, wallet.address, 0, destinationChain, gasValue, { + tokenRegistrar.deployRemoteInterchainToken(chainName, salt, AddressZero, wallet.address, destinationChain, gasValue, { value: gasValue, }), ) .to.emit(service, 'RemoteInterchainTokenDeploymentInitialized') - .withArgs(tokenId, name, symbol, decimals, '0x', '0x', 0, wallet.address.toLowerCase(), destinationChain, gasValue) + .withArgs(tokenId, name, symbol, decimals, '0x', wallet.address.toLowerCase(), destinationChain, gasValue) .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, destinationChain, service.address, keccak256(payload), gasValue, wallet.address) .and.to.emit(gateway, 'ContractCall') .withArgs(service.address, destinationChain, service.address, keccak256(payload), payload); }); - - it('Should fail initiate a remote standardized token deployment without the same distributor with a mintAmount', async () => { - const gasValue = 1234; - const mintAmount = 5678; - - const salt = keccak256('0x124567'); - tokenId = await standardizedTokenRegistrar.standardizedTokenId(wallet.address, salt); - const tokenAddress = await standardizedTokenRegistrar.interchainTokenAddress(wallet.address, salt); - let params = defaultAbiCoder.encode(['bytes', 'address'], [standardizedTokenRegistrar.address, tokenAddress]); - const tokenManager = new Contract(await service.tokenManagerAddress(tokenId), ITokenManager.abi, wallet); - const token = new Contract(tokenAddress, IERC20.abi, wallet); - await expect(standardizedTokenRegistrar.deployInterchainToken(salt, name, symbol, decimals, mintAmount, wallet.address)) - .to.emit(service, 'InterchainTokenDeployed') - .withArgs(tokenId, tokenAddress, wallet.address, name, symbol, decimals, mintAmount, standardizedTokenRegistrar.address) - .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, tokenManager.address, MINT_BURN, params) - .and.to.emit(token, 'Transfer') - .withArgs(standardizedTokenRegistrar.address, wallet.address, mintAmount) - .and.to.emit(tokenManager, 'RolesAdded') - .withArgs(wallet.address, 1 << OPERATOR_ROLE) - .and.to.emit(tokenManager, 'RolesRemoved') - .withArgs(standardizedTokenRegistrar.address, 1 << OPERATOR_ROLE); - - params = defaultAbiCoder.encode(['bytes', 'address'], ['0x', token.address]); - - await expect( - standardizedTokenRegistrar.deployRemoteStandarizedToken( - salt, - AddressZero, - wallet.address, - mintAmount, - destinationChain, - gasValue, - { - value: gasValue, - }, - ), - ).to.be.revertedWithCustomError(standardizedTokenRegistrar, 'NonZeroMintAmount'); - }); }); }); -*/ diff --git a/test/TokenService.js b/test/TokenService.js index 7d6b0942..e0aa96f8 100644 --- a/test/TokenService.js +++ b/test/TokenService.js @@ -1627,7 +1627,6 @@ describe('Interchain Token Service', () => { ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - await gateway.setCommandExecuted(commandId, true).then((tx) => tx.wait()); await expectRevert( (gasOptions) => service.expressExecute(commandId, sourceChain, sourceAddress, payload, gasOptions), @@ -1664,9 +1663,10 @@ describe('Interchain Token Service', () => { ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, amount], ); - const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); + const commandId = getRandomBytes32(); await (await service.expressExecute(commandId, sourceChain, sourceAddress, payload)).wait(); + await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload, getRandomBytes32(), 0, commandId); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(token, 'Transfer') @@ -1684,9 +1684,10 @@ describe('Interchain Token Service', () => { ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, amount], ); - const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); + const commandId = getRandomBytes32(); await (await service.expressExecute(commandId, sourceChain, sourceAddress, payload)).wait(); + await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload, getRandomBytes32(), 0, commandId); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(token, 'Transfer') @@ -1704,9 +1705,10 @@ describe('Interchain Token Service', () => { ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, amount], ); - const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); + const commandId = getRandomBytes32(); await (await service.expressExecute(commandId, sourceChain, sourceAddress, payload)).wait(); + await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload, getRandomBytes32(), 0, commandId); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(token, 'Transfer') @@ -1740,9 +1742,10 @@ describe('Interchain Token Service', () => { ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, sourceAddressForService, destAddress, amount, data], ); - const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); + const commandId = getRandomBytes32(); await (await service.expressExecute(commandId, sourceChain, sourceAddress, payload)).wait(); + await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload, getRandomBytes32(), 0, commandId); const tx = service.execute(commandId, sourceChain, sourceAddress, payload); await expect(tx) @@ -1765,9 +1768,9 @@ describe('Interchain Token Service', () => { [SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, sourceAddressForService, destAddress, amount, data], ); - const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - + const commandId = getRandomBytes32(); await (await service.expressExecute(commandId, sourceChain, sourceAddress, payload)).wait(); + await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload, getRandomBytes32(), 0, commandId); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(token, 'Transfer') diff --git a/test/UtilsTest.js b/test/UtilsTest.js index c4ec3b20..9b79d276 100644 --- a/test/UtilsTest.js +++ b/test/UtilsTest.js @@ -6,7 +6,7 @@ const { ethers } = require('hardhat'); const { time } = require('@nomicfoundation/hardhat-network-helpers'); const { Wallet, Contract } = ethers; const { AddressZero } = ethers.constants; -const { defaultAbiCoder, arrayify, toUtf8Bytes, hexlify } = ethers.utils; +const { defaultAbiCoder } = ethers.utils; const { expect } = chai; const { getRandomBytes32, expectRevert, isHardhat, waitFor } = require('./utils'); const { deployContract } = require('../scripts/deploy'); @@ -312,32 +312,4 @@ describe('InterchainTokenDeployer', () => { 'AlreadyDeployed', ); }); - - describe('AddressBytesUtils', () => { - let addressBytesUtils; - - before(async () => { - addressBytesUtils = await deployContract(ownerWallet, 'AddressBytesUtilsTest'); - }); - - it('Should convert bytes address to address', async () => { - const bytesAddress = arrayify(ownerWallet.address); - const convertedAddress = await addressBytesUtils.toAddress(bytesAddress); - expect(convertedAddress).to.eq(ownerWallet.address); - }); - - it('Should revert on invalid bytes length', async () => { - const bytesAddress = defaultAbiCoder.encode(['bytes'], [toUtf8Bytes(ownerWallet.address)]); - await expectRevert( - (gasOptions) => addressBytesUtils.toAddress(bytesAddress, gasOptions), - addressBytesUtils, - 'InvalidBytesLength', - ); - }); - - it('Should convert address to bytes address', async () => { - const convertedAddress = await addressBytesUtils.toBytes(ownerWallet.address); - expect(convertedAddress).to.eq(hexlify(ownerWallet.address)); - }); - }); });