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

feat: added support for gateway tokens #246

Merged
merged 18 commits into from
Jan 22, 2024
32 changes: 32 additions & 0 deletions contracts/InterchainTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
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_GATEWAY_TOKEN_SALT = keccak256('gateway-token-salt');
address private constant TOKEN_FACTORY_DEPLOYER = address(0);

/**
Expand All @@ -47,6 +48,15 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
gateway = interchainTokenService.gateway();
}

/**
* @notice This modifier is used to ensure that only a the owner of the interchain token service can call a function.
*/
modifier onlyServiceOwner() {
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
if (msg.sender != interchainTokenService.owner()) revert NotServiceOwner(msg.sender);

_;
}

/**
* @notice Getter for the contract id.
* @return bytes32 The contract id of this contract.
Expand Down Expand Up @@ -76,6 +86,16 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
salt = keccak256(abi.encode(PREFIX_CANONICAL_TOKEN_SALT, chainNameHash_, tokenAddress));
}

/**
* @notice Calculates the salt for a canonical interchain token.
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
* @param chainNameHash_ The hash of the chain name.
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
* @param symbol The symbol of the address in the gateway.
* @return salt The calculated salt for the interchain token.
*/
function gatewayInterchainTokenSalt(bytes32 chainNameHash_, string calldata symbol) public pure returns (bytes32 salt) {
salt = keccak256(abi.encode(PREFIX_CANONICAL_TOKEN_SALT, chainNameHash_, symbol));
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @notice Computes the ID for an interchain token based on the deployer and a salt.
* @param deployer The address that deployed the interchain token.
Expand Down Expand Up @@ -288,6 +308,18 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
tokenId = _deployInterchainToken(salt, destinationChain, tokenName, tokenSymbol, tokenDecimals, '', gasValue);
}

/**
* @notice Enables the service oweners to register 'canonical' gateway tokens. The need to pass the same salt for the same token.
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
* @param salt The salt to be used for the token registration. Should be the same for all tokens and something that will not have collisions with any of the other salts used by the factory.
* @param symbol The symbol of the token to register.
*/
function registerGatewayToken(bytes32 salt, string calldata symbol) external onlyServiceOwner {
address tokenAddress = gateway.tokenAddresses(symbol);
if (tokenAddress == address(0)) revert NotGatewayToken(symbol);
bytes memory params = abi.encode('', tokenAddress);
interchainTokenService.deployTokenManager(salt, '', TokenManagerType.GATEWAY, params, 0);
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @notice Checks if a given token is a gateway token.
* @param token The address of the token to check.
Expand Down
173 changes: 131 additions & 42 deletions contracts/InterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import { IInterchainTokenExecutable } from './interfaces/IInterchainTokenExecutable.sol';
import { IInterchainTokenExpressExecutable } from './interfaces/IInterchainTokenExpressExecutable.sol';
import { ITokenManager } from './interfaces/ITokenManager.sol';
import { IERC20Named } from './interfaces/IERC20Named.sol';

import { Operator } from './utils/Operator.sol';

Expand Down Expand Up @@ -368,7 +369,7 @@
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload
) external payable whenNotPaused {
) public payable whenNotPaused {
uint256 messageType = abi.decode(payload, (uint256));
if (messageType != MESSAGE_TYPE_INTERCHAIN_TRANSFER) {
revert InvalidExpressMessageType(messageType);
Expand Down Expand Up @@ -460,11 +461,22 @@
bytes calldata metadata,
uint256 gasValue
) external payable whenNotPaused {
amount = _takeToken(tokenId, msg.sender, amount, false);
string memory symbol;
(amount, symbol) = _takeToken(tokenId, msg.sender, amount, false);

(MetadataVersion metadataVersion, bytes memory data) = _decodeMetadata(metadata);

_transmitInterchainTransfer(tokenId, msg.sender, destinationChain, destinationAddress, amount, metadataVersion, data, gasValue);
_transmitInterchainTransfer(
tokenId,
msg.sender,
destinationChain,
destinationAddress,
amount,
metadataVersion,
data,
symbol,
gasValue
);
}

/**
Expand All @@ -484,8 +496,8 @@
uint256 gasValue
) external payable whenNotPaused {
if (data.length == 0) revert EmptyData();

amount = _takeToken(tokenId, msg.sender, amount, false);
string memory symbol;
(amount, symbol) = _takeToken(tokenId, msg.sender, amount, false);

_transmitInterchainTransfer(
tokenId,
Expand All @@ -495,6 +507,7 @@
amount,
MetadataVersion.CONTRACT_CALL,
data,
symbol,
gasValue
);
}
Expand All @@ -521,11 +534,22 @@
uint256 amount,
bytes calldata metadata
) external payable whenNotPaused {
amount = _takeToken(tokenId, sourceAddress, amount, true);
string memory symbol;
(amount, symbol) = _takeToken(tokenId, sourceAddress, amount, true);

(MetadataVersion metadataVersion, bytes memory data) = _decodeMetadata(metadata);

_transmitInterchainTransfer(tokenId, sourceAddress, destinationChain, destinationAddress, amount, metadataVersion, data, msg.value);
_transmitInterchainTransfer(
tokenId,
sourceAddress,
destinationChain,
destinationAddress,
amount,
metadataVersion,
data,
symbol,
msg.value
);
}

/*************\
Expand Down Expand Up @@ -612,27 +636,12 @@
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload
) external onlyRemoteService(sourceChain, sourceAddress) whenNotPaused {
) public onlyRemoteService(sourceChain, sourceAddress) whenNotPaused {
bytes32 payloadHash = keccak256(payload);

if (!gateway.validateContractCall(commandId, sourceChain, sourceAddress, payloadHash)) revert NotApprovedByGateway();

uint256 messageType = abi.decode(payload, (uint256));
if (messageType == MESSAGE_TYPE_INTERCHAIN_TRANSFER) {
address expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash);

_processInterchainTransferPayload(commandId, expressExecutor, sourceChain, payload);

if (expressExecutor != address(0)) {
emit ExpressExecutionFulfilled(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor);
}
} else if (messageType == MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER) {
_processDeployTokenManagerPayload(payload);
} else if (messageType == MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) {
_processDeployInterchainTokenPayload(payload);
} else {
revert InvalidMessageType(messageType);
}
_execute(commandId, sourceChain, sourceAddress, payload, payloadHash);
}

function contractCallWithTokenValue(
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -646,25 +655,30 @@
}

function expressExecuteWithToken(
bytes32 /*commandId*/,
string calldata /*sourceChain*/,
string calldata /*sourceAddress*/,
bytes calldata /*payload*/,
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload,
string calldata /*tokenSymbol*/,
uint256 /*amount*/
) external payable {
revert ExecuteWithTokenNotSupported();
expressExecute(commandId, sourceChain, sourceAddress, payload);
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
}

function executeWithToken(
bytes32 /*commandId*/,
string calldata /*sourceChain*/,
string calldata /*sourceAddress*/,
bytes calldata /*payload*/,
string calldata /*tokenSymbol*/,
uint256 /*amount*/
) external pure {
revert ExecuteWithTokenNotSupported();
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload,
string calldata tokenSymbol,
uint256 amount
) external {
bytes32 payloadHash = keccak256(payload);

if (!gateway.validateContractCallAndMint(commandId, sourceChain, sourceAddress, payloadHash, tokenSymbol, amount))
revert NotApprovedByGateway();

_execute(commandId, sourceChain, sourceAddress, payload, payloadHash);
}

/**
Expand Down Expand Up @@ -800,6 +814,77 @@
gateway.callContract(destinationChain, destinationAddress, payload);
}

/**
* @notice Calls a contract on a specific destination chain with the given payload
* @param destinationChain The target chain where the contract will be called.
* @param payload The data payload for the transaction.
* @param gasValue The amount of gas to be paid for the transaction.
Foivos marked this conversation as resolved.
Show resolved Hide resolved
*/
function _callContractWithToken(
string calldata destinationChain,
bytes memory payload,
string memory symbol,
uint256 amount,
MetadataVersion metadataVersion,
uint256 gasValue
) internal {
string memory destinationAddress = trustedAddress(destinationChain);
if (bytes(destinationAddress).length == 0) revert UntrustedChain();

if (gasValue > 0) {
if (metadataVersion == MetadataVersion.CONTRACT_CALL) {
gasService.payNativeGasForContractCallWithToken{ value: gasValue }(
address(this),
destinationChain,
destinationAddress,
payload, // solhint-disable-next-line avoid-tx-origin
symbol,
amount,
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
tx.origin

Check warning on line 843 in contracts/InterchainTokenService.sol

View workflow job for this annotation

GitHub Actions / lint (18.x, ubuntu-latest)

Avoid to use tx.origin
);
} else if (metadataVersion == MetadataVersion.EXPRESS_CALL) {
gasService.payNativeGasForExpressCallWithToken{ value: gasValue }(
address(this),
destinationChain,
destinationAddress,
payload, // solhint-disable-next-line avoid-tx-origin
symbol,
amount,
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
tx.origin

Check warning on line 853 in contracts/InterchainTokenService.sol

View workflow job for this annotation

GitHub Actions / lint (18.x, ubuntu-latest)

Avoid to use tx.origin
);
} else {
revert InvalidMetadataVersion(uint32(metadataVersion));
}
}

gateway.callContractWithToken(destinationChain, destinationAddress, payload, symbol, amount);
}

function _execute(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload,
bytes32 payloadHash
) internal {
uint256 messageType = abi.decode(payload, (uint256));
if (messageType == MESSAGE_TYPE_INTERCHAIN_TRANSFER) {
address expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash);

if (expressExecutor != address(0)) {
emit ExpressExecutionFulfilled(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor);
}

_processInterchainTransferPayload(commandId, expressExecutor, sourceChain, payload);
} else if (messageType == MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER) {
_processDeployTokenManagerPayload(payload);
} else if (messageType == MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) {
_processDeployInterchainTokenPayload(payload);
} else {
revert InvalidMessageType(messageType);
}
}

/**
* @notice Deploys a token manager on a destination chain.
* @param tokenId The ID of the token.
Expand Down Expand Up @@ -962,6 +1047,7 @@
uint256 amount,
MetadataVersion metadataVersion,
bytes memory data,
string memory symbol,
uint256 gasValue
) internal {
// slither-disable-next-line reentrancy-events
Expand All @@ -982,14 +1068,17 @@
amount,
data
);

_callContract(destinationChain, payload, metadataVersion, gasValue);
if (bytes(symbol).length > 0) {
_callContractWithToken(destinationChain, payload, symbol, amount, metadataVersion, gasValue);
} else {
_callContract(destinationChain, payload, metadataVersion, gasValue);
}
}

/**
* @dev Takes token from a sender via the token service. `tokenOnly` indicates if the caller should be restricted to the token only.
*/
function _takeToken(bytes32 tokenId, address from, uint256 amount, bool tokenOnly) internal returns (uint256) {
function _takeToken(bytes32 tokenId, address from, uint256 amount, bool tokenOnly) internal returns (uint256, string memory symbol) {
address tokenManager_ = tokenManagerAddress(tokenId);
uint256 tokenManagerType;
address tokenAddress;
Expand All @@ -1006,8 +1095,8 @@

/// @dev Track the flow amount being sent out as a message
ITokenManager(tokenManager_).addFlowOut(amount);
milapsheth marked this conversation as resolved.
Show resolved Hide resolved

return amount;
if (tokenManagerType == uint256(TokenManagerType.GATEWAY)) symbol = IERC20Named(tokenAddress).symbol();
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
return (amount, symbol);
}

/**
Expand Down
Loading
Loading