From 9f89c148259ca3337ed856415df6407f830ec4ea Mon Sep 17 00:00:00 2001 From: Blockchain Guy Date: Wed, 1 Nov 2023 13:43:38 +0530 Subject: [PATCH] refactor: inter-chain-token service events and errors [AXE-2064] (#125) * refactor: contracts and interfaces for better error and event names, with additional args for faster debugging * fix: tests for changes in errors and events * refactor: test/utils.js for expecting custom args with revert if provided in funciton args * refactor: update index.md for changes in errors and events * chore: add operator address in event FlowLimitSet * chore: refactor error argument names * Added roles, tests pending * fix: tests * Added some tests and fixed constants * made lint happy * Chnaged roles constants to enum to make slither happy * Fixed tests * chore: also check error arguments in tests while reverting * chore: resolve pr comments for including tokenId in some errors * Added getter for distributor and opearator. * made lint happy * fixed a spelling error * chore: add tokenManager address in deployment event and update tests * chore: add deployed token address in event * chore: fix slither failing pipeline by adding comment * chore: resolve pr comments by adding params in erorrs and events, adding spaces * fix: test after merging branch feat/roles * fix: tests after resolving merge conflicts with main --------- Co-authored-by: Foivos Co-authored-by: Dean Amiel --- .../executable/InterchainTokenExecutable.sol | 4 +- .../InterchainTokenService.sol | 52 +++++--- contracts/interfaces/IFlowLimit.sol | 4 +- .../interfaces/IInterchainTokenService.sol | 18 +-- .../interfaces/IStandardizedTokenDeployer.sol | 3 +- contracts/interfaces/ITokenManager.sol | 4 +- .../interfaces/ITokenManagerDeployer.sol | 7 +- contracts/interfaces/ITokenManagerProxy.sol | 2 +- contracts/proxies/TokenManagerProxy.sol | 4 +- contracts/test/utils/FlowLimitTest.sol | 4 +- .../test/utils/FlowLimitTestLiveNetwork.sol | 9 +- contracts/token-manager/TokenManager.sol | 6 +- contracts/utils/FlowLimit.sol | 9 +- contracts/utils/Multicall.sol | 2 +- contracts/utils/StandardizedTokenDeployer.sol | 5 +- contracts/utils/TokenManagerDeployer.sol | 11 +- docs/index.md | 115 +++++++++-------- test/TokenManager.js | 5 + test/TokenService.js | 117 +++++++++++++----- test/TokenServiceFullFlow.js | 43 +++++-- test/UtilsTest.js | 35 ++++-- test/tokenRegistrars.js | 28 ++--- test/utils.js | 10 +- 23 files changed, 330 insertions(+), 167 deletions(-) diff --git a/contracts/executable/InterchainTokenExecutable.sol b/contracts/executable/InterchainTokenExecutable.sol index 67a6e35d..86f48d8f 100644 --- a/contracts/executable/InterchainTokenExecutable.sol +++ b/contracts/executable/InterchainTokenExecutable.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import { IInterchainTokenExecutable } from '../interfaces/IInterchainTokenExecutable.sol'; abstract contract InterchainTokenExecutable is IInterchainTokenExecutable { - error NotService(); + error NotService(address caller); address public immutable interchainTokenService; @@ -16,7 +16,7 @@ abstract contract InterchainTokenExecutable is IInterchainTokenExecutable { } modifier onlyService() { - if (msg.sender != interchainTokenService) revert NotService(); + if (msg.sender != interchainTokenService) revert NotService(msg.sender); _; } diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 23ca2420..a99193cb 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -130,7 +130,9 @@ contract InterchainTokenService is * @param tokenId the `tokenId` of the TokenManager trying to perform the call. */ modifier onlyTokenManager(bytes32 tokenId) { - if (msg.sender != getTokenManagerAddress(tokenId)) revert NotTokenManager(); + address tokenManager = getTokenManagerAddress(tokenId); + if (msg.sender != tokenManager) revert NotTokenManager(msg.sender, tokenManager); + _; } @@ -279,10 +281,14 @@ contract InterchainTokenService is * @dev `gasValue` exists because this function can be part of a multicall involving multiple functions that could make remote contract calls. */ function deployRemoteCanonicalToken(bytes32 tokenId, string calldata destinationChain, uint256 gasValue) public payable notPaused { - address tokenAddress = getValidTokenManagerAddress(tokenId); - tokenAddress = ITokenManager(tokenAddress).tokenAddress(); + address tokenAddress; + { + tokenAddress = getValidTokenManagerAddress(tokenId); + tokenAddress = ITokenManager(tokenAddress).tokenAddress(); + bytes32 canonicalTokenId = getCanonicalTokenId(tokenAddress); - if (getCanonicalTokenId(tokenAddress) != tokenId) revert NotCanonicalTokenManager(); + if (canonicalTokenId != tokenId) revert InvalidCanonicalTokenId(canonicalTokenId); + } (string memory tokenName, string memory tokenSymbol, uint8 tokenDecimals) = _validateToken(tokenAddress); _deployRemoteStandardizedToken(tokenId, tokenName, tokenSymbol, tokenDecimals, '', '', 0, '', destinationChain, gasValue); @@ -408,7 +414,7 @@ contract InterchainTokenService is (uint256 selector, bytes32 tokenId, , uint256 amount) = abi.decode(payload, (uint256, bytes32, bytes, uint256)); if (selector != SELECTOR_RECEIVE_TOKEN && selector != SELECTOR_RECEIVE_TOKEN_WITH_DATA) { - revert InvalidExpressSelector(); + revert InvalidExpressSelector(selector); } ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId)); @@ -423,7 +429,7 @@ contract InterchainTokenService is ) external payable notPaused { uint256 selector = abi.decode(payload, (uint256)); if (selector != SELECTOR_RECEIVE_TOKEN && selector != SELECTOR_RECEIVE_TOKEN_WITH_DATA) { - revert InvalidExpressSelector(); + revert InvalidExpressSelector(selector); } if (gateway.isCommandExecuted(commandId)) revert AlreadyExecuted(); @@ -572,7 +578,8 @@ contract InterchainTokenService is ) internal pure returns (address implementation_) { implementation_ = tokenManagerImplementations[uint256(tokenManagerType)]; if (implementation_ == address(0)) revert ZeroAddress(); - if (ITokenManager(implementation_).implementationType() != uint256(tokenManagerType)) revert InvalidTokenManagerImplementation(); + if (ITokenManager(implementation_).implementationType() != uint256(tokenManagerType)) + revert InvalidTokenManagerImplementationType(implementation_); } /** @@ -605,7 +612,7 @@ contract InterchainTokenService is if (selector == SELECTOR_DEPLOY_TOKEN_MANAGER) return _processDeployTokenManagerPayload(payload); if (selector == SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN) return _processDeployStandardizedTokenAndManagerPayload(payload); - revert SelectorUnknown(); + revert SelectorUnknown(selector); } function contractCallWithTokenValue( @@ -857,14 +864,19 @@ contract InterchainTokenService is * @param params Additional parameters for the token manager deployment */ function _deployTokenManager(bytes32 tokenId, TokenManagerType tokenManagerType, bytes memory params) internal { - // slither-disable-next-line reentrancy-events - emit TokenManagerDeployed(tokenId, tokenManagerType, params); - // slither-disable-next-line controlled-delegatecall - (bool success, ) = tokenManagerDeployer.delegatecall( + (bool success, bytes memory returnData) = tokenManagerDeployer.delegatecall( abi.encodeWithSelector(ITokenManagerDeployer.deployTokenManager.selector, tokenId, tokenManagerType, params) ); - if (!success) revert TokenManagerDeploymentFailed(); + if (!success) revert TokenManagerDeploymentFailed(returnData); + + address tokenManager; + assembly { + tokenManager := mload(add(returnData, 0x20)) + } + + // slither-disable-next-line reentrancy-events + emit TokenManagerDeployed(tokenId, tokenManager, tokenManagerType, params); } /** @@ -895,13 +907,11 @@ contract InterchainTokenService is uint256 mintAmount, address mintTo ) internal { - emit StandardizedTokenDeployed(tokenId, distributor, name, symbol, decimals, mintAmount, mintTo); - bytes32 salt = _getStandardizedTokenSalt(tokenId); address tokenManagerAddress = getTokenManagerAddress(tokenId); // slither-disable-next-line controlled-delegatecall - (bool success, ) = standardizedTokenDeployer.delegatecall( + (bool success, bytes memory returnData) = standardizedTokenDeployer.delegatecall( abi.encodeWithSelector( IStandardizedTokenDeployer.deployStandardizedToken.selector, salt, @@ -915,8 +925,16 @@ contract InterchainTokenService is ) ); if (!success) { - revert StandardizedTokenDeploymentFailed(); + revert StandardizedTokenDeploymentFailed(returnData); + } + + address tokenAddress; + assembly { + tokenAddress := mload(add(returnData, 0x20)) } + + // slither-disable-next-line reentrancy-events + emit StandardizedTokenDeployed(tokenId, tokenAddress, distributor, name, symbol, decimals, mintAmount, mintTo); } function _decodeMetadata(bytes memory metadata) internal pure returns (uint32 version, bytes memory data) { diff --git a/contracts/interfaces/IFlowLimit.sol b/contracts/interfaces/IFlowLimit.sol index b25a255e..84c921fa 100644 --- a/contracts/interfaces/IFlowLimit.sol +++ b/contracts/interfaces/IFlowLimit.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.0; interface IFlowLimit { - error FlowLimitExceeded(); + error FlowLimitExceeded(uint256 limit, uint256 flowAmount); - event FlowLimitSet(uint256 flowLimit); + event FlowLimitSet(bytes32 indexed tokenId, address operator, uint256 flowLimit); /** * @notice Returns the current flow limit diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index 8f7ea8c4..65565479 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -13,21 +13,20 @@ import { IRemoteAddressValidator } from './IRemoteAddressValidator.sol'; interface IInterchainTokenService is ITokenManagerType, IAxelarValuedExpressExecutable, IPausable, IMulticall, IContractIdentifier { error ZeroAddress(); error LengthMismatch(); - error InvalidTokenManagerImplementation(); + error InvalidTokenManagerImplementationType(address implementation); error NotRemoteService(); error TokenManagerDoesNotExist(bytes32 tokenId); - error NotTokenManager(); + error NotTokenManager(address caller, address tokenManager); error ExecuteWithInterchainTokenFailed(address contractAddress); + error InvalidCanonicalTokenId(bytes32 expectedCanonicalTokenId); error ExpressExecuteWithInterchainTokenFailed(address contractAddress); - error NotCanonicalTokenManager(); error GatewayToken(); - error TokenManagerDeploymentFailed(); - error StandardizedTokenDeploymentFailed(); - error DoesNotAcceptExpressExecute(address contractAddress); - error SelectorUnknown(); + error TokenManagerDeploymentFailed(bytes error); + error StandardizedTokenDeploymentFailed(bytes error); + error SelectorUnknown(uint256 selector); error InvalidMetadataVersion(uint32 version); error ExecuteWithTokenNotSupported(); - error InvalidExpressSelector(); + error InvalidExpressSelector(uint256 selector); event TokenSent(bytes32 indexed tokenId, string destinationChain, bytes destinationAddress, uint256 indexed amount); event TokenSentWithData( @@ -71,9 +70,10 @@ interface IInterchainTokenService is ITokenManagerType, IAxelarValuedExpressExec string destinationChain, uint256 indexed gasValue ); - event TokenManagerDeployed(bytes32 indexed tokenId, TokenManagerType indexed tokenManagerType, bytes params); + event TokenManagerDeployed(bytes32 indexed tokenId, address tokenManager, TokenManagerType indexed tokenManagerType, bytes params); event StandardizedTokenDeployed( bytes32 indexed tokenId, + address tokenAddress, address indexed distributor, string name, string symbol, diff --git a/contracts/interfaces/IStandardizedTokenDeployer.sol b/contracts/interfaces/IStandardizedTokenDeployer.sol index 2601b04d..f54b1274 100644 --- a/contracts/interfaces/IStandardizedTokenDeployer.sol +++ b/contracts/interfaces/IStandardizedTokenDeployer.sol @@ -31,6 +31,7 @@ interface IStandardizedTokenDeployer { * @param decimals Decimals of the token * @param mintAmount Amount of tokens to mint initially * @param mintTo Address to mint initial tokens to + * @return tokenAddress Address of the deployed token */ function deployStandardizedToken( bytes32 salt, @@ -41,5 +42,5 @@ interface IStandardizedTokenDeployer { uint8 decimals, uint256 mintAmount, address mintTo - ) external payable; + ) external payable returns (address tokenAddress); } diff --git a/contracts/interfaces/ITokenManager.sol b/contracts/interfaces/ITokenManager.sol index 6658645c..ade33785 100644 --- a/contracts/interfaces/ITokenManager.sol +++ b/contracts/interfaces/ITokenManager.sol @@ -13,10 +13,10 @@ import { IImplementation } from './IImplementation.sol'; */ interface ITokenManager is ITokenManagerType, IOperatable, IFlowLimit, IImplementation { error TokenLinkerZeroAddress(); - error NotService(); + error NotService(address caller); error TakeTokenFailed(); error GiveTokenFailed(); - error NotToken(); + error NotToken(address caller); error ZeroAddress(); error AlreadyFlowLimiter(address flowLimiter); error NotFlowLimiter(address flowLimiter); diff --git a/contracts/interfaces/ITokenManagerDeployer.sol b/contracts/interfaces/ITokenManagerDeployer.sol index 2379fe42..c858788e 100644 --- a/contracts/interfaces/ITokenManagerDeployer.sol +++ b/contracts/interfaces/ITokenManagerDeployer.sol @@ -15,6 +15,11 @@ interface ITokenManagerDeployer { * @param tokenId The unique identifier for the token * @param implementationType Token manager implementation type * @param params Additional parameters used in the setup of the token manager + * @return tokenManager Address of the deployed tokenManager */ - function deployTokenManager(bytes32 tokenId, uint256 implementationType, bytes calldata params) external payable; + function deployTokenManager( + bytes32 tokenId, + uint256 implementationType, + bytes calldata params + ) external payable returns (address tokenManager); } diff --git a/contracts/interfaces/ITokenManagerProxy.sol b/contracts/interfaces/ITokenManagerProxy.sol index ccbf3273..8324e360 100644 --- a/contracts/interfaces/ITokenManagerProxy.sol +++ b/contracts/interfaces/ITokenManagerProxy.sol @@ -9,7 +9,7 @@ pragma solidity ^0.8.0; */ interface ITokenManagerProxy { error ImplementationLookupFailed(); - error SetupFailed(); + error SetupFailed(bytes returnData); error NativeTokenNotAccepted(); /** diff --git a/contracts/proxies/TokenManagerProxy.sol b/contracts/proxies/TokenManagerProxy.sol index dfb177c7..c52fa962 100644 --- a/contracts/proxies/TokenManagerProxy.sol +++ b/contracts/proxies/TokenManagerProxy.sol @@ -28,8 +28,8 @@ contract TokenManagerProxy is ITokenManagerProxy { tokenId = tokenId_; address impl = _getImplementation(IInterchainTokenService(interchainTokenServiceAddress_), implementationType_); - (bool success, ) = impl.delegatecall(abi.encodeWithSelector(TokenManagerProxy.setup.selector, params)); - if (!success) revert SetupFailed(); + (bool success, bytes memory returnData) = impl.delegatecall(abi.encodeWithSelector(TokenManagerProxy.setup.selector, params)); + if (!success) revert SetupFailed(returnData); } /** diff --git a/contracts/test/utils/FlowLimitTest.sol b/contracts/test/utils/FlowLimitTest.sol index 4ec4b17f..57b5af2a 100644 --- a/contracts/test/utils/FlowLimitTest.sol +++ b/contracts/test/utils/FlowLimitTest.sol @@ -5,8 +5,10 @@ pragma solidity ^0.8.0; import { FlowLimit } from '../../utils/FlowLimit.sol'; contract FlowLimitTest is FlowLimit { + bytes32 public constant TOKEN_ID = 0x0; + function setFlowLimit(uint256 flowLimit) external { - _setFlowLimit(flowLimit); + _setFlowLimit(flowLimit, TOKEN_ID); } function addFlowIn(uint256 flowInAmount) external { diff --git a/contracts/test/utils/FlowLimitTestLiveNetwork.sol b/contracts/test/utils/FlowLimitTestLiveNetwork.sol index d85fda38..2f02bc4a 100644 --- a/contracts/test/utils/FlowLimitTestLiveNetwork.sol +++ b/contracts/test/utils/FlowLimitTestLiveNetwork.sol @@ -8,6 +8,7 @@ contract FlowLimitTestLiveNetwork is IFlowLimit { uint256 internal constant FLOW_LIMIT_SLOT = 0x201b7a0b7c19aaddc4ce9579b7df8d2db123805861bc7763627f13e04d8af42f; uint256 internal constant PREFIX_FLOW_OUT_AMOUNT = uint256(keccak256('flow-out-amount')); uint256 internal constant PREFIX_FLOW_IN_AMOUNT = uint256(keccak256('flow-in-amount')); + bytes32 public constant TOKEN_ID = 0x0; uint256 internal constant EPOCH_TIME = 60; @@ -22,7 +23,7 @@ contract FlowLimitTestLiveNetwork is IFlowLimit { sstore(FLOW_LIMIT_SLOT, flowLimit) } - emit FlowLimitSet(flowLimit); + emit FlowLimitSet(TOKEN_ID, msg.sender, flowLimit); } function _getFlowOutSlot(uint256 epoch) internal pure returns (uint256 slot) { @@ -60,8 +61,10 @@ contract FlowLimitTestLiveNetwork is IFlowLimit { flowToCompare := sload(slotToCompare) } - if (flowToAdd + flowAmount > flowToCompare + flowLimit) revert FlowLimitExceeded(); - if (flowAmount > flowLimit) revert FlowLimitExceeded(); + uint256 maxFlowLimit = flowToCompare + flowLimit; + uint256 netFlowAmount = flowToAdd + flowAmount; + if (netFlowAmount > maxFlowLimit) revert FlowLimitExceeded(maxFlowLimit, netFlowAmount); + if (flowAmount > flowLimit) revert FlowLimitExceeded(flowLimit, flowAmount); assembly { sstore(slotToAdd, add(flowToAdd, flowAmount)) diff --git a/contracts/token-manager/TokenManager.sol b/contracts/token-manager/TokenManager.sol index 22d218a6..fa80793b 100644 --- a/contracts/token-manager/TokenManager.sol +++ b/contracts/token-manager/TokenManager.sol @@ -35,7 +35,7 @@ abstract contract TokenManager is ITokenManager, Operatable, FlowLimit, Implemen * @dev A modifier that allows only the interchain token service to execute the function. */ modifier onlyService() { - if (msg.sender != address(interchainTokenService)) revert NotService(); + if (msg.sender != address(interchainTokenService)) revert NotService(msg.sender); _; } @@ -43,7 +43,7 @@ abstract contract TokenManager is ITokenManager, Operatable, FlowLimit, Implemen * @dev A modifier that allows only the token to execute the function. */ modifier onlyToken() { - if (msg.sender != tokenAddress()) revert NotToken(); + if (msg.sender != tokenAddress()) revert NotToken(msg.sender); _; } @@ -224,7 +224,7 @@ abstract contract TokenManager is ITokenManager, Operatable, FlowLimit, Implemen * @param flowLimit the maximum difference between the tokens flowing in and/or out at any given interval of time (6h) */ function setFlowLimit(uint256 flowLimit) external onlyRole(uint8(Roles.FLOW_LIMITER)) { - _setFlowLimit(flowLimit); + _setFlowLimit(flowLimit, tokenId()); } /** diff --git a/contracts/utils/FlowLimit.sol b/contracts/utils/FlowLimit.sol index 94acdc79..5ec88714 100644 --- a/contracts/utils/FlowLimit.sol +++ b/contracts/utils/FlowLimit.sol @@ -31,12 +31,12 @@ contract FlowLimit is IFlowLimit { * @dev Internal function to set the flow limit * @param flowLimit The value to set the flow limit to */ - function _setFlowLimit(uint256 flowLimit) internal { + function _setFlowLimit(uint256 flowLimit, bytes32 tokenId) internal { assembly { sstore(FLOW_LIMIT_SLOT, flowLimit) } - emit FlowLimitSet(flowLimit); + emit FlowLimitSet(tokenId, msg.sender, flowLimit); } /** @@ -99,8 +99,9 @@ contract FlowLimit is IFlowLimit { flowToCompare := sload(slotToCompare) } - if (flowToAdd + flowAmount > flowToCompare + flowLimit) revert FlowLimitExceeded(); - if (flowAmount > flowLimit) revert FlowLimitExceeded(); + if (flowToAdd + flowAmount > flowToCompare + flowLimit) + revert FlowLimitExceeded((flowToCompare + flowLimit), flowToAdd + flowAmount); + if (flowAmount > flowLimit) revert FlowLimitExceeded(flowLimit, flowAmount); assembly { sstore(slotToAdd, add(flowToAdd, flowAmount)) diff --git a/contracts/utils/Multicall.sol b/contracts/utils/Multicall.sol index 55fe0ad2..897eb9cc 100644 --- a/contracts/utils/Multicall.sol +++ b/contracts/utils/Multicall.sol @@ -28,7 +28,7 @@ contract Multicall is IMulticall { (success, result) = address(this).delegatecall(data[i]); if (!success) { - revert(string(result)); + revert MulticallFailed(result); } results[i] = result; diff --git a/contracts/utils/StandardizedTokenDeployer.sol b/contracts/utils/StandardizedTokenDeployer.sol index c0669e37..e5f71441 100644 --- a/contracts/utils/StandardizedTokenDeployer.sol +++ b/contracts/utils/StandardizedTokenDeployer.sol @@ -34,6 +34,7 @@ contract StandardizedTokenDeployer is IStandardizedTokenDeployer, Create3 { * @param decimals Decimals of the token * @param mintAmount Amount of tokens to mint initially * @param mintTo Address to mint initial tokens to + * @return tokenAddress Address of the deployed token */ // slither-disable-next-line locked-ether function deployStandardizedToken( @@ -45,12 +46,12 @@ contract StandardizedTokenDeployer is IStandardizedTokenDeployer, Create3 { uint8 decimals, uint256 mintAmount, address mintTo - ) external payable { + ) external payable returns (address tokenAddress) { bytes memory params = abi.encode(tokenManager, distributor, name, symbol, decimals, mintAmount, mintTo); // slither-disable-next-line too-many-digits bytes memory bytecode = bytes.concat(type(StandardizedTokenProxy).creationCode, abi.encode(implementationAddress, params)); - address tokenAddress = _create3(bytecode, salt); + tokenAddress = _create3(bytecode, salt); if (tokenAddress.code.length == 0) revert TokenDeploymentFailed(); } diff --git a/contracts/utils/TokenManagerDeployer.sol b/contracts/utils/TokenManagerDeployer.sol index 1e19c92f..b46f6e26 100644 --- a/contracts/utils/TokenManagerDeployer.sol +++ b/contracts/utils/TokenManagerDeployer.sol @@ -18,15 +18,20 @@ contract TokenManagerDeployer is ITokenManagerDeployer, Create3 { * @param tokenId The unique identifier for the token * @param implementationType Token manager implementation type * @param params Additional parameters used in the setup of the token manager + * @return tokenManager The address of the deployed tokenManager */ // slither-disable-next-line locked-ether - function deployTokenManager(bytes32 tokenId, uint256 implementationType, bytes calldata params) external payable { + function deployTokenManager( + bytes32 tokenId, + uint256 implementationType, + bytes calldata params + ) external payable returns (address tokenManager) { bytes memory args = abi.encode(address(this), implementationType, tokenId, params); // slither-disable-next-line too-many-digits bytes memory bytecode = abi.encodePacked(type(TokenManagerProxy).creationCode, args); - address tokenManagerAddress = _create3(bytecode, tokenId); + tokenManager = _create3(bytecode, tokenId); - if (tokenManagerAddress.code.length == 0) revert TokenManagerDeploymentFailed(); + if (tokenManager.code.length == 0) revert TokenManagerDeploymentFailed(); } } diff --git a/docs/index.md b/docs/index.md index 430eaa02..740129e3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -895,13 +895,7 @@ Getter for the decimals of the token ### AlreadyExpressCalled ```solidity -error AlreadyExpressCalled() -``` - -### SameDestinationAsCaller - -```solidity -error SameDestinationAsCaller() +error AlreadyExpressCalled(address prevExpressCaller, address expressCaller) ``` ### ExpressReceive @@ -982,13 +976,13 @@ Gets the address of the express caller for a specific token transfer with data ### FlowLimitExceeded ```solidity -error FlowLimitExceeded() +error FlowLimitExceeded(bytes32 tokenId, uint256 limit, uint256 flowAmount) ``` ### FlowLimitSet ```solidity -event FlowLimitSet(uint256 flowLimit) +event FlowLimitSet(bytes32 indexed tokenId, address operator, uint256 flowLimit) ``` ### getFlowLimit @@ -1103,10 +1097,10 @@ error ZeroAddress() error LengthMismatch() ``` -### InvalidTokenManagerImplementation +### InvalidTokenManagerImplementationType ```solidity -error InvalidTokenManagerImplementation() +error InvalidTokenManagerImplementationType(address implementation) ``` ### NotRemoteService @@ -1124,7 +1118,7 @@ error TokenManagerDoesNotExist(bytes32 tokenId) ### NotTokenManager ```solidity -error NotTokenManager() +error NotTokenManager(address caller, address tokenManager); ``` ### ExecuteWithInterchainTokenFailed @@ -1133,10 +1127,10 @@ error NotTokenManager() error ExecuteWithInterchainTokenFailed(address contractAddress) ``` -### NotCanonicalTokenManager +### InvalidCanonicalTokenId ```solidity -error NotCanonicalTokenManager() +error InvalidCanonicalTokenId(bytes32 expectedCanonicalTokenId); ``` ### GatewayToken @@ -1148,25 +1142,19 @@ error GatewayToken() ### TokenManagerDeploymentFailed ```solidity -error TokenManagerDeploymentFailed() +error TokenManagerDeploymentFailed(bytes error) ``` ### StandardizedTokenDeploymentFailed ```solidity -error StandardizedTokenDeploymentFailed() -``` - -### DoesNotAcceptExpressExecute - -```solidity -error DoesNotAcceptExpressExecute(address contractAddress) +error StandardizedTokenDeploymentFailed(bytes error) ``` ### SelectorUnknown ```solidity -error SelectorUnknown() +error SelectorUnknown(uint256 selector) ``` ### InvalidMetadataVersion @@ -1226,7 +1214,7 @@ event TokenManagerDeployed(bytes32 tokenId, enum ITokenManagerType.TokenManagerT ### StandardizedTokenDeployed ```solidity -event StandardizedTokenDeployed(bytes32 tokenId, string name, string symbol, uint8 decimals, uint256 mintAmount, address mintTo) +event StandardizedTokenDeployed(bytes32 indexed tokenId, address tokenAddress, address indexed distributor, string name, string symbol, uint8 decimals, uint256 indexed mintAmount, address mintTo) ``` ### CustomTokenIdClaimed @@ -1776,7 +1764,7 @@ If any of the calls fail, the function will revert with the failure message._ ### NotOperator ```solidity -error NotOperator() +error NotOperator(address caller) ``` ### OperatorshipTransferred @@ -2031,7 +2019,7 @@ Getter for the Create3Deployer. ### deployStandardizedToken ```solidity -function deployStandardizedToken(bytes32 salt, address tokenManager, address distributor, string name, string symbol, uint8 decimals, uint256 mintAmount, address mintTo) external payable +function deployStandardizedToken(bytes32 salt, address tokenManager, address distributor, string name, string symbol, uint8 decimals, uint256 mintAmount, address mintTo) external payable returns (address tokenAddress) ``` Deploys a new instance of the StandardizedTokenProxy contract @@ -2049,6 +2037,12 @@ Deploys a new instance of the StandardizedTokenProxy contract | mintAmount | uint256 | Amount of tokens to mint initially | | mintTo | address | Address to mint initial tokens to | +#### Return Values + +| Name | Type | Description | +| ------------ | ------- | ----------------------------- | +| tokenAddress | address | address of the token. | + ## ITokenManager This contract is responsible for handling tokens before initiating a cross chain token transfer, or after receiving one. @@ -2062,7 +2056,7 @@ error TokenLinkerZeroAddress() ### NotService ```solidity -error NotService() +error NotService(address caller) ``` ### TakeTokenFailed @@ -2080,7 +2074,7 @@ error GiveTokenFailed() ### NotToken ```solidity -error NotToken() +error NotToken(address caller) ``` ### tokenAddress @@ -2220,7 +2214,7 @@ Getter for the Create3Deployer. ### deployTokenManager ```solidity -function deployTokenManager(bytes32 tokenId, uint256 implementationType, bytes params) external payable +function deployTokenManager(bytes32 tokenId, uint256 implementationType, bytes params) external payable returns(address tokenManager) ``` Deploys a new instance of the TokenManagerProxy contract @@ -2233,6 +2227,12 @@ Deploys a new instance of the TokenManagerProxy contract | implementationType | uint256 | Token manager implementation type | | params | bytes | Additional parameters used in the setup of the token manager | +#### Return Values + +| Name | Type | Description | +| ------------------- | ------- | -----------------------------------------| +| tokenManager | address | the address of the deployed tokenManager | + ## ITokenManagerProxy _This contract is a proxy for token manager contracts. It implements ITokenManagerProxy and @@ -2247,7 +2247,7 @@ error ImplementationLookupFailed() ### SetupFailed ```solidity -error SetupFailed() +error SetupFailed(bytes error) ``` ### implementationType @@ -2924,7 +2924,7 @@ or from derived contracts._ ### NotService ```solidity -error NotService() +error NotService(address caller) ``` ### interchainTokenService @@ -3088,13 +3088,13 @@ A different implementation could have `metadata` that tells this function which ### NotDistributor ```solidity -error NotDistributor() +error NotDistributor(address caller) ``` ### DistributorshipTransferred ```solidity -event DistributorshipTransferred(address distributor) +event DistributorshipTransferred(address previousDistributor, address distributor) ``` ### distributor @@ -4785,16 +4785,17 @@ Returns the current flow limit ### \_setFlowLimit ```solidity -function _setFlowLimit(uint256 flowLimit) internal +function _setFlowLimit(uint256 flowLimit, bytes32 tokenId) internal ``` _Internal function to set the flow limit_ #### Parameters -| Name | Type | Description | -| --------- | ------- | ---------------------------------- | -| flowLimit | uint256 | The value to set the flow limit to | +| Name | Type | Description | +| --------- | ------- | ---------------------------------------------| +| flowLimit | uint256 | The value to set the flow limit to | +| tokenId | bytes32 | The id of the token whose limit is being set | ### \_getFlowOutSlot @@ -4874,12 +4875,12 @@ _Adds a flow amount while ensuring it does not exceed the flow limit_ #### Parameters -| Name | Type | Description | -| ------------- | ------- | ------------------------------------ | -| flowLimit | uint256 | | -| slotToAdd | uint256 | The slot to add the flow to | -| slotToCompare | uint256 | The slot to compare the flow against | -| flowAmount | uint256 | The flow amount to add | +| Name | Type | Description | +| ------------- | ------- | ------------------------------------------- | +| flowLimit | uint256 | | +| slotToAdd | uint256 | The slot to add the flow to | +| slotToCompare | uint256 | The slot to compare the flow against | +| flowAmount | uint256 | The flow amount to add | ### \_addFlowOut @@ -4891,9 +4892,9 @@ _Adds a flow out amount_ #### Parameters -| Name | Type | Description | -| ------------- | ------- | -------------------------- | -| flowOutAmount | uint256 | The flow out amount to add | +| Name | Type | Description | +| ------------- | ------- | ------------------------------------ | +| flowOutAmount | uint256 | The flow out amount to add | ### \_addFlowIn @@ -4905,9 +4906,9 @@ _Adds a flow in amount_ #### Parameters -| Name | Type | Description | -| ------------ | ------- | ------------------------- | -| flowInAmount | uint256 | The flow in amount to add | +| Name | Type | Description | +| ------------ | ------- | --------------------------------------- | +| flowInAmount | uint256 | The flow in amount to add | ## Implementation @@ -4977,7 +4978,7 @@ Constructor for the TokenManagerDeployer contract ### deployTokenManager ```solidity -function deployTokenManager(bytes32 tokenId, uint256 implementationType, bytes params) external payable +function deployTokenManager(bytes32 tokenId, uint256 implementationType, bytes params) external payable returns(address tokenManager) ``` Deploys a new instance of the TokenManagerProxy contract @@ -4990,6 +4991,12 @@ Deploys a new instance of the TokenManagerProxy contract | implementationType | uint256 | Token manager implementation type | | params | bytes | Additional parameters used in the setup of the token manager | +#### Return Values + +| Name | Type | Description | +| ------------------- | ------- | -----------------------------------------| +| tokenManager | address | the address of the deployed tokenManager | + ## IStandardizedToken This contract implements a standardized token which extends InterchainToken functionality. @@ -5107,7 +5114,7 @@ Constructor for the StandardizedTokenDeployer contract ### deployStandardizedToken ```solidity -function deployStandardizedToken(bytes32 salt, address tokenManager, address distributor, string name, string symbol, uint8 decimals, uint256 mintAmount, address mintTo) external payable +function deployStandardizedToken(bytes32 salt, address tokenManager, address distributor, string name, string symbol, uint8 decimals, uint256 mintAmount, address mintTo) external payable returns (address tokenAddress) ``` Deploys a new instance of the StandardizedTokenProxy contract @@ -5124,3 +5131,9 @@ Deploys a new instance of the StandardizedTokenProxy contract | decimals | uint8 | Decimals of the token | | mintAmount | uint256 | Amount of tokens to mint initially | | mintTo | address | Address to mint initial tokens to | + +#### Return Values + +| Name | Type | Description | +| ------------ | ------- | ----------------------------- | +| tokenAddress | address | address of the token. | diff --git a/test/TokenManager.js b/test/TokenManager.js index d467cafe..38003ac3 100644 --- a/test/TokenManager.js +++ b/test/TokenManager.js @@ -11,6 +11,7 @@ const { expectRevert } = require('./utils'); const { deployContract } = require('../scripts/deploy'); describe('Token Manager', () => { + const FLOW_LIMITER_ROLE = 2; let owner, user, token, service, liquidityPool; let tokenManagerLockUnlock, tokenManagerMintBurn, tokenManagerLiquidityPool, tokenManagerLockUnlockFeeOnTransfer; @@ -59,6 +60,7 @@ describe('Token Manager', () => { ), tokenManagerLockUnlock, 'NotToken', + [sender], ); }); @@ -70,6 +72,7 @@ describe('Token Manager', () => { (gasOptions) => tokenManagerLockUnlock.giveToken(destinationAddress, amount, gasOptions), tokenManagerLockUnlock, 'NotService', + [owner.address], ); }); @@ -81,6 +84,7 @@ describe('Token Manager', () => { (gasOptions) => tokenManagerLockUnlock.takeToken(sourceAddress, amount, gasOptions), tokenManagerLockUnlock, 'NotService', + [owner.address], ); }); @@ -91,6 +95,7 @@ describe('Token Manager', () => { (gasOptions) => tokenManagerLockUnlock.setFlowLimit(flowLimit, gasOptions), tokenManagerLockUnlock, 'MissingRole', + [owner.address, FLOW_LIMITER_ROLE], ); }); diff --git a/test/TokenService.js b/test/TokenService.js index 3304ad9d..c28c49ef 100644 --- a/test/TokenService.js +++ b/test/TokenService.js @@ -5,7 +5,7 @@ const { expect } = chai; require('dotenv').config(); const { ethers } = require('hardhat'); const { AddressZero, MaxUint256 } = ethers.constants; -const { defaultAbiCoder, solidityPack, keccak256, arrayify, hexlify } = ethers.utils; +const { defaultAbiCoder, solidityPack, keccak256, arrayify, toUtf8Bytes, hexlify } = ethers.utils; const { Contract, Wallet } = ethers; const Create3Deployer = require('@axelar-network/axelar-gmp-sdk-solidity/artifacts/contracts/deploy/Create3Deployer.sol/Create3Deployer.json'); const TokenManager = require('../artifacts/contracts/token-manager/TokenManager.sol/TokenManager.json'); @@ -347,7 +347,8 @@ describe('Interchain Token Service', () => { gasOptions, ), service, - 'InvalidTokenManagerImplementation', + 'InvalidTokenManagerImplementationType', + [tokenManagerImplementations[length - 1].address], ); }); @@ -363,11 +364,13 @@ describe('Interchain Token Service', () => { ]); const invalidParams = '0x1234'; + const revertData = '0x'; await expectRevert( (gasOptions) => deployContract(wallet, `TokenManagerProxy`, [service.address, LOCK_UNLOCK, tokenId, invalidParams, gasOptions]), tokenManagerProxy, 'SetupFailed', + [revertData], ); }); }); @@ -403,9 +406,10 @@ describe('Interchain Token Service', () => { it('Should register a canonical token', async () => { const params = defaultAbiCoder.encode(['bytes', 'address'], ['0x', token.address]); + const expectedTokenManagerAddress = await service.getTokenManagerAddress(tokenId); await expect(service.registerCanonicalToken(token.address)) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, LOCK_UNLOCK, params); + .withArgs(tokenId, expectedTokenManagerAddress, LOCK_UNLOCK, params); const tokenManagerAddress = await service.getValidTokenManagerAddress(tokenId); expect(tokenManagerAddress).to.not.equal(AddressZero); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); @@ -415,14 +419,17 @@ describe('Interchain Token Service', () => { it('Should revert if canonical token has already been registered', async () => { const params = defaultAbiCoder.encode(['bytes', 'address'], ['0x', token.address]); + const expectedTokenManagerAddress = await service.getTokenManagerAddress(tokenId); await expect(service.registerCanonicalToken(token.address)) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, LOCK_UNLOCK, params); + .withArgs(tokenId, expectedTokenManagerAddress, LOCK_UNLOCK, params); + const revertData = keccak256(toUtf8Bytes('AlreadyDeployed()')).substring(0, 10); await expectRevert( (gasOptions) => service.registerCanonicalToken(token.address, gasOptions), service, 'TokenManagerDeploymentFailed', + [revertData], ); }); @@ -487,19 +494,22 @@ describe('Interchain Token Service', () => { const tokenId = await service.getCustomTokenId(wallet.address, salt); const tokenAddress = await service.getStandardizedTokenAddress(tokenId); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, tokenAddress]); + const expectedTokenManagerAddress = await service.getTokenManagerAddress(tokenId); await expect( service.deployAndRegisterStandardizedToken(salt, tokenName, tokenSymbol, tokenDecimals, mintAmount, wallet.address), ) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, MINT_BURN, params); + .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params); const tokenManagerAddress = await service.getValidTokenManagerAddress(tokenId); expect(tokenManagerAddress).to.not.equal(AddressZero); const gasValue = 1e6; + const expectedCanonicalTokenId = await service.getCanonicalTokenId(tokenAddress); await expectRevert( (gasOptions) => service.deployRemoteCanonicalToken(tokenId, destinationChain, gasValue, { ...gasOptions, value: gasValue }), service, - 'NotCanonicalTokenManager', + 'InvalidCanonicalTokenId', + [expectedCanonicalTokenId], ); }); @@ -539,11 +549,12 @@ describe('Interchain Token Service', () => { const tokenId = await service.getCustomTokenId(wallet.address, salt); const tokenAddress = await service.getStandardizedTokenAddress(tokenId); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, tokenAddress]); + const expectedTokenManagerAddress = await service.getTokenManagerAddress(tokenId); await expect( service.deployAndRegisterStandardizedToken(salt, tokenName, tokenSymbol, tokenDecimals, mintAmount, wallet.address), ) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, MINT_BURN, params); + .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params); const tokenManagerAddress = await service.getValidTokenManagerAddress(tokenId); expect(tokenManagerAddress).to.not.equal(AddressZero); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); @@ -581,11 +592,12 @@ describe('Interchain Token Service', () => { const tokenId = await service.getCustomTokenId(wallet.address, salt); const tokenAddress = await service.getStandardizedTokenAddress(tokenId); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, tokenAddress]); + const expectedTokenManagerAddress = await service.getTokenManagerAddress(tokenId); await expect( service.deployAndRegisterStandardizedToken(salt, tokenName, tokenSymbol, tokenDecimals, mintAmount, wallet.address), ) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, MINT_BURN, params); + .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params); const tokenManagerAddress = await service.getValidTokenManagerAddress(tokenId); expect(tokenManagerAddress).to.not.equal(AddressZero); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); @@ -593,6 +605,7 @@ describe('Interchain Token Service', () => { expect(await tokenManager.hasRole(wallet.address, OPERATOR_ROLE)).to.be.true; // Register the same token again + const revertData = keccak256(toUtf8Bytes('AlreadyDeployed()')).substring(0, 10); await expectRevert( (gasOptions) => service.deployAndRegisterStandardizedToken( @@ -606,6 +619,7 @@ describe('Interchain Token Service', () => { ), service, 'StandardizedTokenDeploymentFailed', + [revertData], ); }); }); @@ -778,11 +792,11 @@ describe('Interchain Token Service', () => { await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(service, 'StandardizedTokenDeployed') - .withArgs(tokenId, distributor, tokenName, tokenSymbol, tokenDecimals, mintAmount, distributor) + .withArgs(tokenId, tokenAddress, 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); + .withArgs(tokenId, tokenManagerAddress, MINT_BURN, params); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); expect(await tokenManager.tokenAddress()).to.equal(tokenAddress); expect(await tokenManager.hasRole(wallet.address, OPERATOR_ROLE)).to.be.true; @@ -815,9 +829,9 @@ describe('Interchain Token Service', () => { await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(service, 'StandardizedTokenDeployed') - .withArgs(tokenId, distributor, tokenName, tokenSymbol, tokenDecimals, mintAmount, distributor) + .withArgs(tokenId, tokenAddress, distributor, tokenName, tokenSymbol, tokenDecimals, mintAmount, distributor) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, MINT_BURN, params); + .withArgs(tokenId, tokenManagerAddress, MINT_BURN, params); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); expect(await tokenManager.tokenAddress()).to.equal(tokenAddress); expect(await tokenManager.hasRole(operator, OPERATOR_ROLE)).to.be.true; @@ -851,11 +865,20 @@ describe('Interchain Token Service', () => { await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(service, 'StandardizedTokenDeployed') - .withArgs(tokenId, tokenManagerAddress, tokenName, tokenSymbol, tokenDecimals, mintAmount, tokenManagerAddress) + .withArgs( + tokenId, + tokenAddress, + 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); + .withArgs(tokenId, tokenManagerAddress, MINT_BURN, params); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); expect(await tokenManager.tokenAddress()).to.equal(tokenAddress); expect(await tokenManager.hasRole(service.address, OPERATOR_ROLE)).to.be.true; @@ -889,11 +912,20 @@ describe('Interchain Token Service', () => { await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(service, 'StandardizedTokenDeployed') - .withArgs(tokenId, tokenManagerAddress, tokenName, tokenSymbol, tokenDecimals, mintAmount, tokenManagerAddress) + .withArgs( + tokenId, + tokenAddress, + 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); + .withArgs(tokenId, tokenManagerAddress, MINT_BURN, params); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); expect(await tokenManager.tokenAddress()).to.equal(tokenAddress); expect(await tokenManager.hasRole(service.address, OPERATOR_ROLE)).to.be.true; @@ -932,7 +964,8 @@ describe('Interchain Token Service', () => { const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); const tx = service.deployCustomTokenManager(salt, LOCK_UNLOCK, params); - await expect(tx).to.emit(service, 'TokenManagerDeployed').withArgs(tokenId, LOCK_UNLOCK, params); + const expectedTokenManagerAddress = await service.getTokenManagerAddress(tokenId); + await expect(tx).to.emit(service, 'TokenManagerDeployed').withArgs(tokenId, expectedTokenManagerAddress, LOCK_UNLOCK, params); expect(tokenManagerAddress).to.not.equal(AddressZero); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); @@ -954,7 +987,8 @@ describe('Interchain Token Service', () => { const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); const tx = service.deployCustomTokenManager(salt, MINT_BURN, params); - await expect(tx).to.emit(service, 'TokenManagerDeployed').withArgs(tokenId, MINT_BURN, params); + const expectedTokenManagerAddress = await service.getTokenManagerAddress(tokenId); + await expect(tx).to.emit(service, 'TokenManagerDeployed').withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params); expect(tokenManagerAddress).to.not.equal(AddressZero); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); @@ -976,7 +1010,10 @@ describe('Interchain Token Service', () => { const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); const tx = service.deployCustomTokenManager(salt, MINT_BURN_FROM, params); - await expect(tx).to.emit(service, 'TokenManagerDeployed').withArgs(tokenId, MINT_BURN_FROM, params); + const expectedTokenManagerAddress = await service.getTokenManagerAddress(tokenId); + await expect(tx) + .to.emit(service, 'TokenManagerDeployed') + .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN_FROM, params); expect(tokenManagerAddress).to.not.equal(AddressZero); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); @@ -1003,7 +1040,10 @@ describe('Interchain Token Service', () => { 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); + const expectedTokenManagerAddress = await service.getTokenManagerAddress(tokenId); + await expect(tx) + .to.emit(service, 'TokenManagerDeployed') + .withArgs(tokenId, expectedTokenManagerAddress, LOCK_UNLOCK_FEE_ON_TRANSFER, params); expect(tokenManagerAddress).to.not.equal(AddressZero); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); @@ -1025,12 +1065,15 @@ describe('Interchain Token Service', () => { const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); const tx = service.deployCustomTokenManager(salt, LOCK_UNLOCK, params); - await expect(tx).to.emit(service, 'TokenManagerDeployed').withArgs(tokenId, LOCK_UNLOCK, params); + const expectedTokenManagerAddress = await service.getTokenManagerAddress(tokenId); + await expect(tx).to.emit(service, 'TokenManagerDeployed').withArgs(tokenId, expectedTokenManagerAddress, LOCK_UNLOCK, params); + const revertData = keccak256(toUtf8Bytes('AlreadyDeployed()')).substring(0, 10); await expectRevert( (gasOptions) => service.deployCustomTokenManager(salt, LOCK_UNLOCK, params, gasOptions), service, 'TokenManagerDeploymentFailed', + [revertData], ); }); @@ -1178,9 +1221,10 @@ describe('Interchain Token Service', () => { ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); + const expectedTokenManagerAddress = await service.getTokenManagerAddress(tokenId); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, LOCK_UNLOCK, params); + .withArgs(tokenId, expectedTokenManagerAddress, LOCK_UNLOCK, params); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); expect(await tokenManager.tokenAddress()).to.equal(token.address); expect(await tokenManager.hasRole(wallet.address, OPERATOR_ROLE)).to.be.true; @@ -1201,9 +1245,10 @@ describe('Interchain Token Service', () => { ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); + const expectedTokenManagerAddress = await service.getTokenManagerAddress(tokenId); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, MINT_BURN, params); + .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params); const tokenManager = new Contract(tokenManagerAddress, TokenManager.abi, wallet); expect(await tokenManager.tokenAddress()).to.equal(token.address); expect(await tokenManager.hasRole(wallet.address, OPERATOR_ROLE)).to.be.true; @@ -1271,6 +1316,7 @@ describe('Interchain Token Service', () => { }), service, 'NotTokenManager', + [wallet.address, tokenManager.address], ); }); }); @@ -1340,6 +1386,7 @@ describe('Interchain Token Service', () => { (gasOptions) => service.execute(commandId, sourceChain, sourceAddress, payload, gasOptions), service, 'SelectorUnknown', + [INVALID_SELECTOR], ); }); }); @@ -1831,6 +1878,7 @@ describe('Interchain Token Service', () => { (gasOptions) => service.expressExecute(commandId, sourceChain, sourceAddress, payload, gasOptions), service, 'InvalidExpressSelector', + [SELECTOR_DEPLOY_TOKEN_MANAGER], ); }); @@ -1992,6 +2040,7 @@ describe('Interchain Token Service', () => { (gasOptions) => tokenManager.interchainTransfer(destinationChain, destinationAddress, sendAmount, '0x', gasOptions), tokenManager, 'FlowLimitExceeded', + [flowLimit, 2 * sendAmount], ); }); @@ -2023,7 +2072,10 @@ describe('Interchain Token Service', () => { expect(flowIn).to.eq(sendAmount); expect(flowOut).to.eq(0); - await expectRevert((gasOptions) => receiveToken(sendAmount, gasOptions), tokenManager, 'FlowLimitExceeded'); + await expectRevert((gasOptions) => receiveToken(sendAmount, gasOptions), tokenManager, 'FlowLimitExceeded', [ + flowLimit, + 2 * sendAmount, + ]); }); it('Should be able to set flow limits for each token manager', async () => { @@ -2044,15 +2096,16 @@ describe('Interchain Token Service', () => { (gasOptions) => service.connect(otherWallet).setFlowLimits(tokenIds, flowLimits, gasOptions), service, 'MissingRole', + [otherWallet.address, OPERATOR_ROLE], ); await expect(service.setFlowLimits(tokenIds, flowLimits)) .to.emit(tokenManagers[0], 'FlowLimitSet') - .withArgs(flowLimit) + .withArgs(tokenIds[0], service.address, flowLimit) .to.emit(tokenManagers[1], 'FlowLimitSet') - .withArgs(flowLimit) + .withArgs(tokenIds[1], service.address, flowLimit) .to.emit(tokenManagers[2], 'FlowLimitSet') - .withArgs(flowLimit); + .withArgs(tokenIds[2], service.address, flowLimit); flowLimits.pop(); @@ -2098,6 +2151,7 @@ describe('Interchain Token Service', () => { (gasOptions) => tokenManager.connect(otherWallet).addFlowLimiter(otherWallet.address, gasOptions), tokenManager, 'MissingRole', + [otherWallet.address, OPERATOR_ROLE], ); }); @@ -2106,11 +2160,17 @@ describe('Interchain Token Service', () => { (gasOptions) => tokenManager.connect(otherWallet).removeFlowLimiter(wallet.address, gasOptions), tokenManager, 'MissingRole', + [otherWallet.address, OPERATOR_ROLE], ); }); it('Should revert if trying to add an existing flow limiter', async () => { - await expectRevert((gasOptions) => tokenManager.addFlowLimiter(wallet.address, gasOptions), tokenManager, 'AlreadyFlowLimiter'); + await expectRevert( + (gasOptions) => tokenManager.addFlowLimiter(wallet.address, gasOptions), + tokenManager, + 'AlreadyFlowLimiter', + [wallet.address], + ); }); it('Should revert if trying to add a flow limiter as not the operator', async () => { @@ -2118,6 +2178,7 @@ describe('Interchain Token Service', () => { (gasOptions) => tokenManager.removeFlowLimiter(otherWallet.address, gasOptions), tokenManager, 'NotFlowLimiter', + [otherWallet.address], ); }); }); diff --git a/test/TokenServiceFullFlow.js b/test/TokenServiceFullFlow.js index 9f98cd43..2ff4a0dc 100644 --- a/test/TokenServiceFullFlow.js +++ b/test/TokenServiceFullFlow.js @@ -69,9 +69,10 @@ describe('Interchain Token Service Full Flow', () => { ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes', 'bytes', 'uint256', 'bytes'], [SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN, tokenId, name, symbol, decimals, '0x', '0x', 0, '0x'], ); + const expectedTokenManagerAddress = await service.getTokenManagerAddress(tokenId); await expect(service.multicall(data, { value })) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, LOCK_UNLOCK, params) + .withArgs(tokenId, expectedTokenManagerAddress, LOCK_UNLOCK, params) .and.to.emit(service, 'RemoteStandardizedTokenAndManagerDeploymentInitialized') .withArgs(tokenId, name, symbol, decimals, '0x', '0x', 0, '0x', otherChains[0], gasValues[0]) .and.to.emit(gasService, 'NativeGasPaidForContractCall') @@ -127,8 +128,14 @@ describe('Interchain Token Service Full Flow', () => { .to.emit(token, 'RolesAdded') .withArgs(newAddress, 1 << DISTRIBUTOR_ROLE); - await expectRevert((gasOptions) => token.mint(newAddress, amount, gasOptions), token, 'MissingRole'); - await expectRevert((gasOptions) => token.burn(newAddress, amount, gasOptions), token, 'MissingRole'); + await expectRevert((gasOptions) => token.mint(newAddress, amount, gasOptions), token, 'MissingRole', [ + wallet.address, + DISTRIBUTOR_ROLE, + ]); + await expectRevert((gasOptions) => token.burn(newAddress, amount, gasOptions), token, 'MissingRole', [ + wallet.address, + DISTRIBUTOR_ROLE, + ]); }); }); @@ -183,11 +190,13 @@ describe('Interchain Token Service Full Flow', () => { ); const tx = service.multicall(data, { value }); + const expectedTokenManagerAddress = await service.getTokenManagerAddress(tokenId); + const expectedTokenAddress = await service.getStandardizedTokenAddress(tokenId); await expect(tx) .to.emit(service, 'StandardizedTokenDeployed') - .withArgs(tokenId, wallet.address, name, symbol, decimals, tokenCap, wallet.address) + .withArgs(tokenId, expectedTokenAddress, wallet.address, name, symbol, decimals, tokenCap, wallet.address) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, MINT_BURN, params) + .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params) .and.to.emit(service, 'RemoteStandardizedTokenAndManagerDeploymentInitialized') .withArgs(tokenId, name, symbol, decimals, '0x', '0x', 0, wallet.address.toLowerCase(), otherChains[0], gasValues[0]) .and.to.emit(gasService, 'NativeGasPaidForContractCall') @@ -226,6 +235,7 @@ describe('Interchain Token Service Full Flow', () => { }); // For this test the token must be a standardized token (or a distributable token in general) + // TODO no token is deployed so how will mint and burn work? it('Should be able to change the token distributor', async () => { const newAddress = new Wallet(getRandomBytes32()).address; const amount = 1234; @@ -239,8 +249,14 @@ describe('Interchain Token Service Full Flow', () => { .to.emit(token, 'RolesAdded') .withArgs(newAddress, 1 << DISTRIBUTOR_ROLE); - await expectRevert((gasOptions) => token.mint(newAddress, amount, gasOptions), token, 'MissingRole'); - await expectRevert((gasOptions) => token.burn(newAddress, amount, gasOptions), token, 'MissingRole'); + await expectRevert((gasOptions) => token.mint(newAddress, amount, gasOptions), token, 'MissingRole', [ + wallet.address, + DISTRIBUTOR_ROLE, + ]); + await expectRevert((gasOptions) => token.burn(newAddress, amount, gasOptions), token, 'MissingRole', [ + wallet.address, + DISTRIBUTOR_ROLE, + ]); }); }); @@ -286,9 +302,10 @@ describe('Interchain Token Service Full Flow', () => { ['uint256', 'bytes32', 'uint256', 'bytes'], [SELECTOR_DEPLOY_TOKEN_MANAGER, tokenId, MINT_BURN, params], ); + const expectedTokenManagerAddress = await service.getTokenManagerAddress(tokenId); await expect(service.multicall(data, { value })) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, MINT_BURN, params) + .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params) .and.to.emit(service, 'RemoteTokenManagerDeploymentInitialized') .withArgs(tokenId, otherChains[0], gasValues[0], MINT_BURN, params) .and.to.emit(gasService, 'NativeGasPaidForContractCall') @@ -317,8 +334,14 @@ describe('Interchain Token Service Full Flow', () => { .to.emit(token, 'RolesAdded') .withArgs(tokenManager.address, 1 << DISTRIBUTOR_ROLE); - await expectRevert((gasOptions) => token.mint(newAddress, amount, gasOptions), token, 'MissingRole'); - await expectRevert((gasOptions) => token.burn(newAddress, amount, gasOptions), token, 'MissingRole'); + await expectRevert((gasOptions) => token.mint(newAddress, amount, gasOptions), token, 'MissingRole', [ + wallet.address, + DISTRIBUTOR_ROLE, + ]); + await expectRevert((gasOptions) => token.burn(newAddress, amount, gasOptions), token, 'MissingRole', [ + wallet.address, + DISTRIBUTOR_ROLE, + ]); }); // In order to be able to receive tokens the distributorship should be changed on other chains as well. diff --git a/test/UtilsTest.js b/test/UtilsTest.js index 4354c048..84a37e8d 100644 --- a/test/UtilsTest.js +++ b/test/UtilsTest.js @@ -43,7 +43,10 @@ describe('Operatable', () => { }); it('Should not be able to run the onlyOperatorable function as not the operator', async () => { - await expectRevert((gasOptions) => test.connect(otherWallet).testOperatorable(gasOptions), test, 'MissingRole'); + await expectRevert((gasOptions) => test.connect(otherWallet).testOperatorable(gasOptions), test, 'MissingRole', [ + otherWallet.address, + operatorRole, + ]); }); it('Should be able to change the operator only as the operator', async () => { @@ -57,13 +60,19 @@ describe('Operatable', () => { expect(await test.hasRole(otherWallet.address, operatorRole)).to.be.true; - await expectRevert((gasOptions) => test.transferOperatorship(otherWallet.address, gasOptions), test, 'MissingRole'); + await expectRevert((gasOptions) => test.transferOperatorship(otherWallet.address, gasOptions), test, 'MissingRole', [ + ownerWallet.address, + operatorRole, + ]); }); it('Should be able to propose operator only as the operator', async () => { expect(await test.hasRole(otherWallet.address, operatorRole)).to.be.true; - await expectRevert((gasOptions) => test.proposeOperatorship(ownerWallet.address, gasOptions), test, 'MissingRole'); + await expectRevert((gasOptions) => test.proposeOperatorship(ownerWallet.address, gasOptions), test, 'MissingRole', [ + ownerWallet.address, + operatorRole, + ]); await expect(test.connect(otherWallet).proposeOperatorship(ownerWallet.address)) .to.emit(test, 'RolesProposed') @@ -77,6 +86,7 @@ describe('Operatable', () => { (gasOptions) => test.connect(otherWallet).acceptOperatorship(otherWallet.address, gasOptions), test, 'InvalidProposedRoles', + [otherWallet.address, otherWallet.address, 1 << operatorRole], ); await expect(test.connect(ownerWallet).acceptOperatorship(otherWallet.address)) @@ -107,7 +117,10 @@ describe('Distributable', () => { }); it('Should not be able to run the onlyDistributor function as not the distributor', async () => { - await expectRevert((gasOptions) => test.connect(otherWallet).testDistributable(gasOptions), test, 'MissingRole'); + await expectRevert((gasOptions) => test.connect(otherWallet).testDistributable(gasOptions), test, 'MissingRole', [ + otherWallet.address, + distributorRole, + ]); }); it('Should be able to change the distributor only as the distributor', async () => { @@ -121,7 +134,10 @@ describe('Distributable', () => { expect(await test.hasRole(otherWallet.address, distributorRole)).to.be.true; - await expectRevert((gasOptions) => test.transferDistributorship(otherWallet.address, gasOptions), test, 'MissingRole'); + await expectRevert((gasOptions) => test.transferDistributorship(otherWallet.address, gasOptions), test, 'MissingRole', [ + ownerWallet.address, + distributorRole, + ]); }); it('Should be able to propose a new distributor only as distributor', async () => { @@ -131,6 +147,7 @@ describe('Distributable', () => { (gasOptions) => test.connect(ownerWallet).proposeDistributorship(ownerWallet.address, gasOptions), test, 'MissingRole', + [ownerWallet.address, distributorRole], ); await expect(test.connect(otherWallet).proposeDistributorship(ownerWallet.address)) @@ -157,12 +174,14 @@ describe('Distributable', () => { describe('FlowLimit', async () => { let test; + let tokenId; const flowLimit = isHardhat ? 5 : 2; before(async () => { test = isHardhat ? await deployContract(ownerWallet, 'FlowLimitTest') : await deployContract(ownerWallet, 'FlowLimitTestLiveNetwork'); + tokenId = await test.TOKEN_ID(); }); async function nextEpoch() { @@ -183,7 +202,7 @@ describe('FlowLimit', async () => { }); it('Should be able to set the flow limit', async () => { - await expect(test.setFlowLimit(flowLimit)).to.emit(test, 'FlowLimitSet').withArgs(flowLimit); + await expect(test.setFlowLimit(flowLimit)).to.emit(test, 'FlowLimitSet').withArgs(tokenId, ownerWallet.address, flowLimit); expect(await test.getFlowLimit()).to.equal(flowLimit); }); @@ -196,7 +215,7 @@ describe('FlowLimit', async () => { expect(await test.getFlowInAmount()).to.equal(i + 1); } - await expectRevert((gasOptions) => test.addFlowIn(1, gasOptions), test, 'FlowLimitExceeded'); + await expectRevert((gasOptions) => test.addFlowIn(1, gasOptions), test, 'FlowLimitExceeded', [flowLimit, flowLimit + 1]); await nextEpoch(); @@ -213,7 +232,7 @@ describe('FlowLimit', async () => { expect(await test.getFlowOutAmount()).to.equal(i + 1); } - await expectRevert((gasOptions) => test.addFlowOut(1, gasOptions), test, 'FlowLimitExceeded'); + await expectRevert((gasOptions) => test.addFlowOut(1, gasOptions), test, 'FlowLimitExceeded', [flowLimit, flowLimit + 1]); await nextEpoch(); diff --git a/test/tokenRegistrars.js b/test/tokenRegistrars.js index 95410266..64256b5b 100644 --- a/test/tokenRegistrars.js +++ b/test/tokenRegistrars.js @@ -51,13 +51,13 @@ describe('Token Registrsrs', () => { }); describe('Canonical Token Registrar', async () => { - let token, tokenId; + let token, tokenId, tokenManagerAddress; const tokenCap = BigInt(1e18); async function deployToken() { token = await deployContract(wallet, 'InterchainTokenTest', [name, symbol, decimals, wallet.address]); tokenId = await canonicalTokenRegistrar.getCanonicalTokenId(token.address); - const tokenManagerAddress = await service.getTokenManagerAddress(tokenId); + tokenManagerAddress = await service.getTokenManagerAddress(tokenId); await (await token.mint(wallet.address, tokenCap)).wait(); await (await token.setTokenManager(tokenManagerAddress)).wait(); } @@ -69,7 +69,7 @@ describe('Token Registrsrs', () => { await expect(canonicalTokenRegistrar.registerCanonicalToken(token.address)) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, LOCK_UNLOCK, params); + .withArgs(tokenId, tokenManagerAddress, LOCK_UNLOCK, params); }); it('Should initiate a remote standardized token deployment', async () => { @@ -86,7 +86,7 @@ describe('Token Registrsrs', () => { await expect(canonicalTokenRegistrar.registerCanonicalToken(token.address)) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, LOCK_UNLOCK, params); + .withArgs(tokenId, tokenManagerAddress, LOCK_UNLOCK, params); await expect( canonicalTokenRegistrar.deployAndRegisterRemoteCanonicalToken(salt, destinationChain, gasValue, { value: gasValue }), @@ -110,9 +110,9 @@ describe('Token Registrsrs', () => { await expect(canonicalTokenRegistrar.registerCanonicalToken(token.address)) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, LOCK_UNLOCK, params); + .withArgs(tokenId, tokenManagerAddress, LOCK_UNLOCK, params); - const tokenManagerAddress = await service.getValidTokenManagerAddress(tokenId); + tokenManagerAddress = await service.getValidTokenManagerAddress(tokenId); await (await token.approve(canonicalTokenRegistrar.address, amount)).wait(); @@ -143,9 +143,9 @@ describe('Token Registrsrs', () => { const token = new Contract(tokenAddress, IERC20.abi, wallet); await expect(standardizedTokenRegistrar.deployStandardizedToken(salt, name, symbol, decimals, mintAmount, wallet.address)) .to.emit(service, 'StandardizedTokenDeployed') - .withArgs(tokenId, wallet.address, name, symbol, decimals, mintAmount, standardizedTokenRegistrar.address) + .withArgs(tokenId, tokenAddress, wallet.address, name, symbol, decimals, mintAmount, standardizedTokenRegistrar.address) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, MINT_BURN, params) + .withArgs(tokenId, tokenManager.address, MINT_BURN, params) .and.to.emit(token, 'Transfer') .withArgs(standardizedTokenRegistrar.address, wallet.address, mintAmount) .and.to.emit(tokenManager, 'RolesAdded') @@ -166,9 +166,9 @@ describe('Token Registrsrs', () => { const token = new Contract(tokenAddress, IERC20.abi, wallet); await expect(standardizedTokenRegistrar.deployStandardizedToken(salt, name, symbol, decimals, mintAmount, wallet.address)) .to.emit(service, 'StandardizedTokenDeployed') - .withArgs(tokenId, wallet.address, name, symbol, decimals, mintAmount, standardizedTokenRegistrar.address) + .withArgs(tokenId, tokenAddress, wallet.address, name, symbol, decimals, mintAmount, standardizedTokenRegistrar.address) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, MINT_BURN, params) + .withArgs(tokenId, tokenManager.address, MINT_BURN, params) .and.to.emit(token, 'Transfer') .withArgs(standardizedTokenRegistrar.address, wallet.address, mintAmount) .and.to.emit(tokenManager, 'RolesAdded') @@ -235,9 +235,9 @@ describe('Token Registrsrs', () => { const token = new Contract(tokenAddress, IERC20.abi, wallet); await expect(standardizedTokenRegistrar.deployStandardizedToken(salt, name, symbol, decimals, mintAmount, wallet.address)) .to.emit(service, 'StandardizedTokenDeployed') - .withArgs(tokenId, wallet.address, name, symbol, decimals, mintAmount, standardizedTokenRegistrar.address) + .withArgs(tokenId, tokenAddress, wallet.address, name, symbol, decimals, mintAmount, standardizedTokenRegistrar.address) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, MINT_BURN, params) + .withArgs(tokenId, tokenManager.address, MINT_BURN, params) .and.to.emit(token, 'Transfer') .withArgs(standardizedTokenRegistrar.address, wallet.address, mintAmount) .and.to.emit(tokenManager, 'RolesAdded') @@ -286,9 +286,9 @@ describe('Token Registrsrs', () => { const token = new Contract(tokenAddress, IERC20.abi, wallet); await expect(standardizedTokenRegistrar.deployStandardizedToken(salt, name, symbol, decimals, mintAmount, wallet.address)) .to.emit(service, 'StandardizedTokenDeployed') - .withArgs(tokenId, wallet.address, name, symbol, decimals, mintAmount, standardizedTokenRegistrar.address) + .withArgs(tokenId, tokenAddress, wallet.address, name, symbol, decimals, mintAmount, standardizedTokenRegistrar.address) .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, MINT_BURN, params) + .withArgs(tokenId, tokenManager.address, MINT_BURN, params) .and.to.emit(token, 'Transfer') .withArgs(standardizedTokenRegistrar.address, wallet.address, mintAmount) .and.to.emit(tokenManager, 'RolesAdded') diff --git a/test/utils.js b/test/utils.js index 84dea506..5711be2e 100644 --- a/test/utils.js +++ b/test/utils.js @@ -12,11 +12,17 @@ const getGasOptions = () => { const isHardhat = network.name === 'hardhat'; -const expectRevert = async (txFunc, contract, error) => { +const expectRevert = async (txFunc, contract, error, args) => { if (network.config.skipRevertTests) { await expect(txFunc(getGasOptions())).to.be.reverted; } else { - await expect(txFunc(null)).to.be.revertedWithCustomError(contract, error); + if (args) { + await expect(txFunc(null)) + .to.be.revertedWithCustomError(contract, error) + .withArgs(...args); + } else { + await expect(txFunc(null)).to.be.revertedWithCustomError(contract, error); + } } };