Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

refactor(its-factory)!: simplify token id derivation #302

Merged
merged 6 commits into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/codecov.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ name: Code Coverage

on:
pull_request:
push:
branches:
- main
- releases/**

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand Down
158 changes: 95 additions & 63 deletions contracts/InterchainTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { IInterchainTokenFactory } from './interfaces/IInterchainTokenFactory.so
import { ITokenManagerType } from './interfaces/ITokenManagerType.sol';
import { ITokenManager } from './interfaces/ITokenManager.sol';
import { IInterchainToken } from './interfaces/IInterchainToken.sol';
import { IERC20Named } from './interfaces/IERC20Named.sol';

/**
* @title InterchainTokenFactory
Expand Down Expand Up @@ -66,24 +67,22 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
}

/**
* @notice Calculates the salt for an interchain token.
* @param chainNameHash_ The hash of the chain name.
* @notice Computes the deploy salt for an interchain token.
* @param deployer The address of the deployer.
* @param salt A unique identifier to generate the salt.
* @return tokenSalt The calculated salt for the interchain token.
* @return deploySalt The deploy salt for the interchain token.
*/
function interchainTokenSalt(bytes32 chainNameHash_, address deployer, bytes32 salt) public pure returns (bytes32 tokenSalt) {
tokenSalt = keccak256(abi.encode(PREFIX_INTERCHAIN_TOKEN_SALT, chainNameHash_, deployer, salt));
function interchainTokenDeploySalt(address deployer, bytes32 salt) public view returns (bytes32 deploySalt) {
deploySalt = keccak256(abi.encode(PREFIX_INTERCHAIN_TOKEN_SALT, chainNameHash, deployer, salt));
}

/**
* @notice Calculates the salt for a canonical interchain token.
* @param chainNameHash_ The hash of the chain name.
* @notice Computes the deploy salt for a canonical interchain token.
* @param tokenAddress The address of the token.
* @return salt The calculated salt for the interchain token.
* @return deploySalt The deploy salt for the interchain token.
*/
function canonicalInterchainTokenSalt(bytes32 chainNameHash_, address tokenAddress) public pure returns (bytes32 salt) {
salt = keccak256(abi.encode(PREFIX_CANONICAL_TOKEN_SALT, chainNameHash_, tokenAddress));
function canonicalInterchainTokenDeploySalt(address tokenAddress) public view returns (bytes32 deploySalt) {
deploySalt = keccak256(abi.encode(PREFIX_CANONICAL_TOKEN_SALT, chainNameHash, tokenAddress));
}

/**
Expand All @@ -93,7 +92,8 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
* @return tokenId The ID of the interchain token.
*/
function interchainTokenId(address deployer, bytes32 salt) public view returns (bytes32 tokenId) {
tokenId = interchainTokenService.interchainTokenId(TOKEN_FACTORY_DEPLOYER, interchainTokenSalt(chainNameHash, deployer, salt));
bytes32 deploySalt = interchainTokenDeploySalt(deployer, salt);
tokenId = _interchainTokenId(deploySalt);
}

/**
Expand All @@ -102,20 +102,17 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
* @return tokenId The ID of the canonical interchain token.
*/
function canonicalInterchainTokenId(address tokenAddress) public view returns (bytes32 tokenId) {
tokenId = interchainTokenService.interchainTokenId(
TOKEN_FACTORY_DEPLOYER,
canonicalInterchainTokenSalt(chainNameHash, tokenAddress)
);
bytes32 deploySalt = canonicalInterchainTokenDeploySalt(tokenAddress);
tokenId = _interchainTokenId(deploySalt);
}

/**
* @notice Retrieves the address of an interchain token based on the deployer and a salt.
* @param deployer The address that deployed the interchain token.
* @param salt A unique identifier used in the deployment process.
* @return tokenAddress The address of the interchain token.
* @notice Computes the tokenId for an interchain token based on the deploySalt.
* @param deploySalt The salt used for the deployment.
* @return tokenId The tokenId of the interchain token.
*/
function interchainTokenAddress(address deployer, bytes32 salt) public view returns (address tokenAddress) {
tokenAddress = interchainTokenService.interchainTokenAddress(interchainTokenId(deployer, salt));
function _interchainTokenId(bytes32 deploySalt) internal view returns (bytes32 tokenId) {
tokenId = interchainTokenService.interchainTokenId(TOKEN_FACTORY_DEPLOYER, deploySalt);
}

/**
Expand All @@ -141,8 +138,10 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
address minter
) external payable returns (bytes32 tokenId) {
address sender = msg.sender;
salt = interchainTokenSalt(chainNameHash, sender, salt);
bytes32 deploySalt = interchainTokenDeploySalt(sender, salt);
bytes memory minterBytes = new bytes(0);
string memory currentChain = '';
uint256 gasValue = 0;

if (initialSupply > 0) {
minterBytes = address(this).toBytes();
Expand All @@ -152,11 +151,11 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
minterBytes = minter.toBytes();
}

tokenId = _deployInterchainToken(salt, '', name, symbol, decimals, minterBytes, 0);
tokenId = _deployInterchainToken(deploySalt, currentChain, name, symbol, decimals, minterBytes, gasValue);

if (initialSupply > 0) {
IInterchainToken token = IInterchainToken(interchainTokenService.interchainTokenAddress(tokenId));
ITokenManager tokenManager = ITokenManager(interchainTokenService.tokenManagerAddress(tokenId));
IInterchainToken token = IInterchainToken(interchainTokenService.registeredTokenAddress(tokenId));
ITokenManager tokenManager = ITokenManager(interchainTokenService.deployedTokenManager(tokenId));

token.mint(sender, initialSupply);

Expand All @@ -173,6 +172,11 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
/**
* @notice Allow the minter to approve the deployer for a remote interchain token deployment that uses a custom destinationMinter address.
* This ensures that a token deployer can't choose the destinationMinter itself, and requires the approval of the minter to reduce trust assumptions on the deployer.
* @param deployer The address of the deployer.
* @param salt The unique salt for deploying the token.
* @param destinationChain The name of the destination chain.
* @param destinationMinter The minter address to set on the deployed token on the destination chain. This can be arbitrary bytes
* since the encoding of the account is dependent on the destination chain.
*/
function approveDeployRemoteInterchainToken(
address deployer,
Expand All @@ -182,8 +186,8 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
) external {
address minter = msg.sender;
bytes32 tokenId = interchainTokenId(deployer, salt);
IInterchainToken token = IInterchainToken(interchainTokenService.interchainTokenAddress(tokenId));
if (!token.isMinter(minter)) revert InvalidMinter(minter);

_checkTokenMinter(tokenId, minter);

if (bytes(interchainTokenService.trustedAddress(destinationChain)).length == 0) revert InvalidChainName();
ahramy marked this conversation as resolved.
Show resolved Hide resolved

Expand All @@ -196,6 +200,9 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M

/**
* @notice Allows the minter to revoke a deployer's approval for a remote interchain token deployment that uses a custom destinationMinter address.
* @param deployer The address of the deployer.
* @param salt The unique salt for deploying the token.
* @param destinationChain The name of the destination chain.
*/
function revokeDeployRemoteInterchainToken(address deployer, bytes32 salt, string calldata destinationChain) external {
address minter = msg.sender;
Expand All @@ -208,10 +215,16 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
emit RevokedDeployRemoteInterchainTokenApproval(minter, deployer, tokenId, destinationChain);
}

/**
* @dev Compute the key for the deploy approval mapping.
*/
function _deployApprovalKey(DeployApproval memory approval) internal pure returns (bytes32 key) {
key = keccak256(abi.encode(PREFIX_DEPLOY_APPROVAL, approval));
}

/**
* @dev Use the deploy approval to check that the destination minter is valid and then delete the approval.
*/
function _useDeployApproval(DeployApproval memory approval, bytes memory destinationMinter) internal {
bytes32 approvalKey = _deployApprovalKey(approval);

Expand Down Expand Up @@ -259,38 +272,28 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
bytes memory destinationMinter,
uint256 gasValue
) public payable returns (bytes32 tokenId) {
string memory tokenName;
string memory tokenSymbol;
uint8 tokenDecimals;
bytes memory minter_ = new bytes(0);

salt = interchainTokenSalt(chainNameHash, msg.sender, salt);
tokenId = interchainTokenService.interchainTokenId(TOKEN_FACTORY_DEPLOYER, salt);

IInterchainToken token = IInterchainToken(interchainTokenService.interchainTokenAddress(tokenId));

tokenName = token.name();
tokenSymbol = token.symbol();
tokenDecimals = token.decimals();
bytes32 deploySalt = interchainTokenDeploySalt(msg.sender, salt);

if (minter != address(0)) {
if (!token.isMinter(minter)) revert NotMinter(minter);
// Sanity check to prevent accidental use of the current ITS address as the destination minter
if (minter == address(interchainTokenService)) revert InvalidMinter(minter);
bytes32 deployedTokenId = _interchainTokenId(deploySalt);
_checkTokenMinter(deployedTokenId, minter);

if (destinationMinter.length > 0) {
DeployApproval memory approval = DeployApproval({ minter: minter, tokenId: tokenId, destinationChain: destinationChain });
DeployApproval memory approval = DeployApproval({
minter: minter,
tokenId: deployedTokenId,
destinationChain: destinationChain
});
_useDeployApproval(approval, destinationMinter);
minter_ = destinationMinter;
} else {
minter_ = minter.toBytes();
destinationMinter = minter.toBytes();
}
} else if (destinationMinter.length > 0) {
// If a destinationMinter is provided, then minter must not be address(0)
revert InvalidMinter(minter);
}

tokenId = _deployInterchainToken(salt, destinationChain, tokenName, tokenSymbol, tokenDecimals, minter_, gasValue);
tokenId = _deployRemoteInterchainToken(deploySalt, destinationChain, destinationMinter, gasValue);
}

/**
Expand Down Expand Up @@ -318,6 +321,20 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
tokenId = deployRemoteInterchainTokenWithMinter(salt, minter, destinationChain, new bytes(0), gasValue);
}

/**
* @notice Checks that the minter is registered for the token on the current chain and not the ITS address.
* @param tokenId The unique identifier for the token. The token must be an interchain token deployed via ITS.
* @param minter The address to be checked as a minter for the interchain token.
*/
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
function _checkTokenMinter(bytes32 tokenId, address minter) internal view {
// Ensure that the minter is registered for the token on the current chain
IInterchainToken token = IInterchainToken(interchainTokenService.registeredTokenAddress(tokenId));
if (!token.isMinter(minter)) revert NotMinter(minter);

// Sanity check to prevent accidental use of the current ITS address as the token minter
if (minter == address(interchainTokenService)) revert InvalidMinter(minter);
}

/**
* @notice Deploys a new interchain token with specified parameters.
* @param salt The unique salt for deploying the token.
Expand Down Expand Up @@ -350,6 +367,29 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
);
}

/**
* @notice Deploys a remote interchain token on a specified destination chain.
* @param deploySalt The salt used for the deployment.
* @param destinationChain The name of the destination chain.
* @param minter The address to receive the minter and operator role of the token, in addition to ITS.
* @param gasValue The amount of gas to send for the deployment.
* @return tokenId The tokenId corresponding to the deployed InterchainToken.
*/
function _deployRemoteInterchainToken(
bytes32 deploySalt,
string memory destinationChain,
bytes memory minter,
uint256 gasValue
) internal returns (bytes32 tokenId) {
bytes32 expectedTokenId = _interchainTokenId(deploySalt);
// Ensure that a local token has been registered for the tokenId
IERC20Named token = IERC20Named(interchainTokenService.registeredTokenAddress(expectedTokenId));

// The local token must expose the name, symbol, and decimals metadata
tokenId = _deployInterchainToken(deploySalt, destinationChain, token.name(), token.symbol(), token.decimals(), minter, gasValue);
if (tokenId != expectedTokenId) revert InvalidTokenId(tokenId, expectedTokenId);
}

/**
* @notice Registers a canonical token as an interchain token and deploys its token manager.
* @dev This function is `payable` because non-payable functions cannot be called in a multicall that calls other `payable` functions.
Expand All @@ -358,9 +398,11 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
*/
function registerCanonicalInterchainToken(address tokenAddress) external payable returns (bytes32 tokenId) {
bytes memory params = abi.encode('', tokenAddress);
bytes32 salt = canonicalInterchainTokenSalt(chainNameHash, tokenAddress);
bytes32 deploySalt = canonicalInterchainTokenDeploySalt(tokenAddress);
string memory currentChain = '';
uint256 gasValue = 0;

tokenId = interchainTokenService.deployTokenManager(salt, '', TokenManagerType.LOCK_UNLOCK, params, 0);
tokenId = interchainTokenService.deployTokenManager(deploySalt, currentChain, TokenManagerType.LOCK_UNLOCK, params, gasValue);
}

/**
Expand All @@ -375,21 +417,11 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
string calldata destinationChain,
uint256 gasValue
) public payable returns (bytes32 tokenId) {
bytes32 salt;
IInterchainToken token;

// This ensures that the token manager has been deployed by this address, so it's safe to trust it.
salt = canonicalInterchainTokenSalt(chainNameHash, originalTokenAddress);
tokenId = interchainTokenService.interchainTokenId(TOKEN_FACTORY_DEPLOYER, salt);
token = IInterchainToken(interchainTokenService.registeredTokenAddress(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();
bytes memory minter = ''; // No additional minter is set on a canonical token deployment
// No additional minter is set on a canonical token deployment
bytes memory minter = '';
bytes32 deploySalt = canonicalInterchainTokenDeploySalt(originalTokenAddress);

tokenId = _deployInterchainToken(salt, destinationChain, tokenName, tokenSymbol, tokenDecimals, minter, gasValue);
tokenId = _deployRemoteInterchainToken(deploySalt, destinationChain, minter, gasValue);
}

/**
Expand Down
Loading