Skip to content

Commit

Permalink
feat(its-factory)!: support arbitrary destination minter for remote d…
Browse files Browse the repository at this point in the history
…eployment
  • Loading branch information
milapsheth committed Nov 10, 2024
1 parent 476e272 commit 3c99b94
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 5 deletions.
116 changes: 111 additions & 5 deletions contracts/InterchainTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,34 @@ import { IInterchainToken } from './interfaces/IInterchainToken.sol';
contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, Multicall, Upgradable {
using AddressBytes for address;

IInterchainTokenService public immutable interchainTokenService;
bytes32 public immutable chainNameHash;
/// @dev This slot contains the storage for this contract in an upgrade-compatible manner
/// keccak256('InterchainTokenFactory.Slot') - 1;
bytes32 internal constant INTERCHAIN_TOKEN_FACTORY_SLOT =
0xd4f5c43117c663161acfe6af3208a49856d85e586baf0f60749de2055e001465;

bytes32 private constant CONTRACT_ID = keccak256('interchain-token-factory');
bytes32 internal constant PREFIX_CANONICAL_TOKEN_SALT = keccak256('canonical-token-salt');
bytes32 internal constant PREFIX_INTERCHAIN_TOKEN_SALT = keccak256('interchain-token-salt');
bytes32 internal constant PREFIX_DEPLOY_APPROVAL = keccak256('deploy-approval');
address private constant TOKEN_FACTORY_DEPLOYER = address(0);

IInterchainTokenService public immutable interchainTokenService;
bytes32 public immutable chainNameHash;

struct DeployApproval {
address minter;
address deployer;
bytes32 salt;
string destinationChain;
bytes destinationMinter;
}

/// @dev Storage for this contract
/// @param deployApprovals Mapping of deployment approvals
struct InterchainTokenFactoryStorage {
mapping(bytes32 => bool) deployApprovals;
}

/**
* @notice Constructs the InterchainTokenFactory contract.
* @param interchainTokenService_ The address of the interchain token service.
Expand Down Expand Up @@ -152,19 +172,73 @@ 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.
*/
function approveDeployRemoteInterchainToken(address deployer, bytes32 salt, string calldata destinationChain, bytes calldata destinationMinter) external {
bytes32 approvalKey = _deployApprovalKey(DeployApproval({
minter: msg.sender,
deployer: deployer,
salt: salt,
destinationChain: destinationChain,
destinationMinter: destinationMinter
}));

_interchainTokenFactoryStorage().deployApprovals[approvalKey] = true;

emit ApprovedDeployRemoteInterchainToken(msg.sender, deployer, salt, destinationChain, destinationMinter);
}

function _deployApprovalKey(DeployApproval memory approval) internal pure returns (bytes32 key) {
key = keccak256(abi.encode(PREFIX_DEPLOY_APPROVAL, approval));
}

function _useDeployApproval(DeployApproval memory approval) internal {
bytes32 approvalKey = _deployApprovalKey(approval);

InterchainTokenFactoryStorage storage slot = _interchainTokenFactoryStorage();

if (!slot.deployApprovals[approvalKey]) revert RemoteDeploymentNotApproved();

slot.deployApprovals[approvalKey] = false;
}

/**
* @notice Deploys a remote interchain token on a specified destination chain.
* @param salt The unique salt for deploying the token.
* @param minter The address to use as the minter of the deployed token on the destination chain. If the destination chain is not EVM,
* then use the more generic `deployRemoteInterchainToken` function below that allows setting an arbitrary destination minter that was approved by the current minter.
* @param destinationChain The name of the destination chain.
* @param gasValue The amount of gas to send for the deployment.
* @return tokenId The tokenId corresponding to the deployed InterchainToken.
*/
function deployRemoteInterchainToken(
bytes32 salt,
address minter,
string memory destinationChain,
uint256 gasValue
) external payable returns (bytes32 tokenId) {
return deployRemoteInterchainToken(salt, minter, destinationChain, new bytes(0), gasValue);
}

/**
* @notice Deploys a remote interchain token on a specified destination chain.
* @param salt The unique salt for deploying the token.
* @param minter The address to receive the minter and operator role of the token, in addition to ITS. If the address is `address(0)`,
* no additional minter is set on the token. Reverts if the minter does not have mint permission for 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. If this is empty, then the `minter` of the token on the current chain
* is used as the destination minter, which makes it convenient when deploying to other EVM chains.
* @param gasValue The amount of gas to send for the deployment.
* @return tokenId The tokenId corresponding to the deployed InterchainToken.
*/
function deployRemoteInterchainToken(
bytes32 salt,
address minter,
string memory destinationChain,
bytes memory destinationMinter,
uint256 gasValue
) public payable returns (bytes32 tokenId) {
string memory tokenName;
Expand All @@ -183,9 +257,27 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M

if (minter != address(0)) {
if (!token.isMinter(minter)) revert NotMinter(minter);
if (minter == address(interchainTokenService)) revert InvalidMinter(minter);

minter_ = minter.toBytes();
if (destinationMinter.length > 0) {
DeployApproval memory approval = DeployApproval({
minter: minter,
deployer: msg.sender,
salt: salt,
destinationChain: destinationChain,
destinationMinter: destinationMinter
});
_useDeployApproval(approval);
minter_ = destinationMinter;
} else {
minter_ = minter.toBytes();
}

// Sanity check to prevent accidental use of the current ITS address as the destination minter
bytes memory itsAddress = address(interchainTokenService).toBytes();
if (keccak256(minter_) == keccak256(itsAddress)) revert InvalidMinter(minter);
} 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);
Expand Down Expand Up @@ -213,7 +305,7 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
) external payable returns (bytes32 tokenId) {
if (bytes(originalChainName).length != 0) revert NotSupported();

tokenId = deployRemoteInterchainToken(salt, minter, destinationChain, gasValue);
tokenId = deployRemoteInterchainToken(salt, minter, destinationChain, new bytes(0), gasValue);
}

/**
Expand Down Expand Up @@ -310,4 +402,18 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M

tokenId = deployRemoteCanonicalInterchainToken(originalTokenAddress, destinationChain, gasValue);
}

/********************\
|* Pure Key Getters *|
\********************/

/**
* @notice Gets the specific storage location for preventing upgrade collisions
* @return slot containing the storage struct
*/
function _interchainTokenFactoryStorage() private pure returns (InterchainTokenFactoryStorage storage slot) {
assembly {
slot.slot := INTERCHAIN_TOKEN_FACTORY_SLOT
}
}
}
30 changes: 30 additions & 0 deletions contracts/interfaces/IInterchainTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ interface IInterchainTokenFactory is IUpgradable, IMulticall {
error NotOperator(address operator);
error NotServiceOwner(address sender);
error NotSupported();
error RemoteDeploymentNotApproved();

/// @notice Emitted when a minter approves a deployer for a remote interchain token deployment that uses a custom destinationMinter address.
event ApprovedDeployRemoteInterchainToken(address indexed minter, address indexed deployer, bytes32 indexed salt, string destinationChain, bytes destinationMinter);

/**
* @notice Returns the address of the interchain token service.
Expand Down Expand Up @@ -76,18 +80,44 @@ interface IInterchainTokenFactory is IUpgradable, IMulticall {
address minter
) external payable returns (bytes32 tokenId);

/**
* @notice Allows the minter to approve a 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.
*/
function approveDeployRemoteInterchainToken(address deployer, bytes32 salt, string calldata destinationChain, bytes calldata destinationMinter) external;

/**
* @notice Deploys a remote interchain token on a specified destination chain.
* @param salt The unique salt for deploying the token.
* @param minter The address to use as the minter of the deployed token on the destination chain. If the destination chain is not EVM,
* then use the more generic `deployRemoteInterchainToken` function below that allows setting an arbitrary destination minter that was approved by the current minter.
* @param destinationChain The name of the destination chain.
* @param gasValue The amount of gas to send for the deployment.
* @return tokenId The tokenId corresponding to the deployed InterchainToken.
*/
function deployRemoteInterchainToken(
bytes32 salt,
address minter,
string memory destinationChain,
uint256 gasValue
) external payable returns (bytes32 tokenId);

/**
* @notice Deploys a remote interchain token on a specified destination chain.
* @param salt The unique salt for deploying the token.
* @param minter The address to distribute the token on the destination chain.
* @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. If this is empty, then the `minter` of the token on the current chain
* is used as the destination minter, which makes it convenient when deploying to other EVM chains.
* @param gasValue The amount of gas to send for the deployment.
* @return tokenId The tokenId corresponding to the deployed InterchainToken.
*/
function deployRemoteInterchainToken(
bytes32 salt,
address minter,
string memory destinationChain,
bytes memory destinationMinter,
uint256 gasValue
) external payable returns (bytes32 tokenId);

Expand Down

0 comments on commit 3c99b94

Please sign in to comment.