Skip to content

Commit

Permalink
feat: interchain transfer everywhere (#104)
Browse files Browse the repository at this point in the history
* renamed folder and changed version

* npmignore

* npmignore

* change version

* using include pattern instead.

* Fixed most of the things least auhority suggested.

* made lint happy

* Apply suggestions from code review

* fixed some bugs

* added events

* rename set to transfer for distributor and operator

* changed standardized token to always allow token managers to mint/burn it.

* using immutable storage for remoteAddressValidator address to save gas

* Added some recommended changes

* added milap's suggested changes

* Fixed some names and some minor gas optimizations

* prettier and lint

* stash

* import .env in hardhat.config

* trying to fix .env.example

* Added some getters in IRemoteAddressValidator and removed useless check for distributor in the InterchainTokenService.

* removed ternary operators

* made lint happy

* made lint happy

* Added a new token manager to handle fee on transfer and added some tests for it as well

* fixed the liquidity pool check.

* fix a duplication bug

* lint

* added some more tests

* Added more tests

* Added proper re-entrancy protection for fee on transfer token managers.

* change to tx.origin for refunds

* Added support for more kinds of addresses.

* some minor gas opts

* some more gas optimizations.

* Added a getter for chain name to the remote address validator.

* moved the tokenManager getter functionality to a separate contract which saves almost a kilobyte of codesize.

* made lint happy

* Removed tokenManagerGetter and put params into tokenManagers

* Added separate tokenManager interfaces

* addressed ackeeblockchains's 3.0 report

* prettier

* added interchain transfer methods to the service and unified receiving tokens a bit.

* made lint happy

* rename sendToken to interchainTransfer

* changed sendToken everywhere

* remove duplictes from merge

---------

Co-authored-by: Milap Sheth <[email protected]>
Co-authored-by: Kiryl Yermakou <[email protected]>
  • Loading branch information
3 people authored Oct 2, 2023
1 parent 463facc commit 403e3bd
Show file tree
Hide file tree
Showing 13 changed files with 246 additions and 640 deletions.
216 changes: 113 additions & 103 deletions contracts/interchain-token-service/InterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -397,57 +397,65 @@ contract InterchainTokenService is

/**
* @notice Uses the caller's tokens to fullfill a sendCall ahead of time. Use this only if you have detected an outgoing
* sendToken that matches the parameters passed here.
* interchainTransfer that matches the parameters passed here.
* @dev This is not to be used with fee on transfer tokens as it will incur losses for the express caller.
* @param tokenId the tokenId of the TokenManager used.
* @param destinationAddress the destinationAddress for the sendToken.
* @param amount the amount of token to give.
* @param payload the payload of the receive token
* @param commandId the sendHash detected at the sourceChain.
*/
function expressReceiveToken(bytes32 tokenId, address destinationAddress, uint256 amount, bytes32 commandId) external {
function expressReceiveToken(bytes calldata payload, bytes32 commandId, string calldata sourceChain) external {
if (gateway.isCommandExecuted(commandId)) revert AlreadyExecuted(commandId);

address caller = msg.sender;
_setExpressReceiveToken(payload, commandId, caller);

(uint256 selector, bytes32 tokenId, bytes memory destinationAddressBytes, uint256 amount) = abi.decode(
payload,
(uint256, bytes32, bytes, uint256)
);
address destinationAddress = destinationAddressBytes.toAddress();

ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId));
IERC20 token = IERC20(tokenManager.tokenAddress());

SafeTokenTransferFrom.safeTransferFrom(token, caller, destinationAddress, amount);

_setExpressReceiveToken(tokenId, destinationAddress, amount, commandId, caller);
if (selector == SELECTOR_SEND_TOKEN_WITH_DATA) {
(, , , , bytes memory sourceAddress, bytes memory data) = abi.decode(payload, (uint256, bytes32, bytes, uint256, bytes, bytes));
IInterchainTokenExpressExecutable(destinationAddress).executeWithInterchainToken(
sourceChain,
sourceAddress,
data,
tokenId,
amount
);
} else if (selector != SELECTOR_SEND_TOKEN) {
revert InvalidExpressSelector();
}
}

/**
* @notice Uses the caller's tokens to fullfill a callContractWithInterchainToken ahead of time. Use this only if you have
* detected an outgoing sendToken that matches the parameters passed here.
* @dev This is not to be used with fee on transfer tokens as it will incur losses for the express caller and it will pass an incorrect amount to the contract.
* @param tokenId the tokenId of the TokenManager used.
* @param sourceChain the name of the chain where the call came from.
* @param sourceAddress the caller of callContractWithInterchainToken.
* @param destinationAddress the destinationAddress for the sendToken.
* @param amount the amount of token to give.
* @param data the data to be passed to destinationAddress after giving them the tokens specified.
* @param commandId the sendHash detected at the sourceChain.
*/
function expressReceiveTokenWithData(
function interchainTransfer(
bytes32 tokenId,
string memory sourceChain,
bytes memory sourceAddress,
address destinationAddress,
string calldata destinationChain,
bytes calldata destinationAddress,
uint256 amount,
bytes calldata data,
bytes32 commandId
bytes calldata metadata
) external {
if (gateway.isCommandExecuted(commandId)) revert AlreadyExecuted(commandId);

address caller = msg.sender;
ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId));
IERC20 token = IERC20(tokenManager.tokenAddress());

SafeTokenTransferFrom.safeTransferFrom(token, caller, destinationAddress, amount);

_setExpressReceiveTokenWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount, data, commandId, caller);
ITokenManager tokenManager = ITokenManager(getTokenManagerAddress(tokenId));
amount = tokenManager.takeToken(msg.sender, amount);
_transmitSendToken(tokenId, msg.sender, destinationChain, destinationAddress, amount, metadata);
}

_expressExecuteWithInterchainTokenToken(tokenId, destinationAddress, sourceChain, sourceAddress, data, amount);
function sendTokenWithData(
bytes32 tokenId,
string calldata destinationChain,
bytes calldata destinationAddress,
uint256 amount,
bytes calldata data
) external {
ITokenManager tokenManager = ITokenManager(getTokenManagerAddress(tokenId));
amount = tokenManager.takeToken(msg.sender, amount);
uint32 prefix = 0;
_transmitSendToken(tokenId, msg.sender, destinationChain, destinationAddress, amount, abi.encodePacked(prefix, data));
}

/*********************\
Expand All @@ -459,7 +467,7 @@ contract InterchainTokenService is
* @param tokenId the tokenId of the TokenManager (which must be the msg.sender).
* @param sourceAddress the address where the token is coming from, which will also be used for reimbursement of gas.
* @param destinationChain the name of the chain to send tokens to.
* @param destinationAddress the destinationAddress for the sendToken.
* @param destinationAddress the destinationAddress for the interchainTransfer.
* @param amount the amount of token to give.
* @param metadata the data to be passed to the destination.
*/
Expand All @@ -471,19 +479,7 @@ contract InterchainTokenService is
uint256 amount,
bytes calldata metadata
) external payable onlyTokenManager(tokenId) notPaused {
bytes memory payload;
if (metadata.length < 4) {
payload = abi.encode(SELECTOR_SEND_TOKEN, tokenId, destinationAddress, amount);
_callContract(destinationChain, payload, msg.value);
emit TokenSent(tokenId, destinationChain, destinationAddress, amount);
return;
}
uint32 version;
(version, metadata) = _decodeMetadata(metadata);
if (version > 0) revert InvalidMetadataVersion(version);
payload = abi.encode(SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destinationAddress, amount, sourceAddress.toBytes(), metadata);
_callContract(destinationChain, payload, msg.value);
emit TokenSentWithData(tokenId, destinationChain, destinationAddress, amount, sourceAddress, metadata);
_transmitSendToken(tokenId, sourceAddress, destinationChain, destinationAddress, amount, metadata);
}

/*************\
Expand Down Expand Up @@ -541,10 +537,8 @@ contract InterchainTokenService is
bytes calldata payload
) internal override onlyRemoteService(sourceChain, sourceAddress) notPaused {
uint256 selector = abi.decode(payload, (uint256));
if (selector == SELECTOR_SEND_TOKEN) {
_processSendTokenPayload(sourceChain, payload);
} else if (selector == SELECTOR_SEND_TOKEN_WITH_DATA) {
_processSendTokenWithDataPayload(sourceChain, payload);
if (selector == SELECTOR_SEND_TOKEN || selector == SELECTOR_SEND_TOKEN_WITH_DATA) {
_processSendTokenPayload(sourceChain, payload, selector);
} else if (selector == SELECTOR_DEPLOY_TOKEN_MANAGER) {
_processDeployTokenManagerPayload(payload);
} else if (selector == SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN) {
Expand All @@ -559,67 +553,45 @@ contract InterchainTokenService is
* @param sourceChain The chain where the transaction originates from
* @param payload The encoded data payload to be processed
*/
function _processSendTokenPayload(string calldata sourceChain, bytes calldata payload) internal {
(, bytes32 tokenId, bytes memory destinationAddressBytes, uint256 amount) = abi.decode(payload, (uint256, bytes32, bytes, uint256));
bytes32 commandId;

assembly {
commandId := calldataload(4)
}
address destinationAddress = destinationAddressBytes.toAddress();
ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId));
address expressCaller = _popExpressReceiveToken(tokenId, destinationAddress, amount, commandId);
if (expressCaller == address(0)) {
amount = tokenManager.giveToken(destinationAddress, amount);
emit TokenReceived(tokenId, sourceChain, destinationAddress, amount);
} else {
amount = tokenManager.giveToken(expressCaller, amount);
}
}

/**
* @notice Processes a send token with data payload.
* @param sourceChain The chain where the transaction originates from
* @param payload The encoded data payload to be processed
*/
function _processSendTokenWithDataPayload(string calldata sourceChain, bytes calldata payload) internal {
function _processSendTokenPayload(string calldata sourceChain, bytes calldata payload, uint256 selector) internal {
bytes32 tokenId;
uint256 amount;
bytes memory sourceAddress;
bytes memory data;
address destinationAddress;
uint256 amount;
{
bytes memory destinationAddressBytes;
(, tokenId, destinationAddressBytes, amount) = abi.decode(payload, (uint256, bytes32, bytes, uint256));
destinationAddress = destinationAddressBytes.toAddress();
}
bytes32 commandId;

assembly {
commandId := calldataload(4)
}
ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId));
{
bytes memory destinationAddressBytes;
(, tokenId, destinationAddressBytes, amount, sourceAddress, data) = abi.decode(
payload,
(uint256, bytes32, bytes, uint256, bytes, bytes)
);
destinationAddress = destinationAddressBytes.toAddress();
}
ITokenManager tokenManager = ITokenManager(getTokenManagerAddress(tokenId));
{
address expressCaller = _popExpressReceiveTokenWithData(
tokenId,
sourceChain,
sourceAddress,
destinationAddress,
amount,
data,
commandId
);
address expressCaller = _popExpressReceiveToken(payload, commandId);
if (expressCaller != address(0)) {
amount = tokenManager.giveToken(expressCaller, amount);
return;
}
}
amount = tokenManager.giveToken(destinationAddress, amount);
IInterchainTokenExpressExecutable(destinationAddress).executeWithInterchainToken(sourceChain, sourceAddress, data, tokenId, amount);
emit TokenReceivedWithData(tokenId, sourceChain, destinationAddress, amount, sourceAddress, data);
if (selector == SELECTOR_SEND_TOKEN_WITH_DATA) {
bytes memory sourceAddress;
bytes memory data;
(, , , , sourceAddress, data) = abi.decode(payload, (uint256, bytes32, bytes, uint256, bytes, bytes));

IInterchainTokenExpressExecutable(destinationAddress).executeWithInterchainToken(
sourceChain,
sourceAddress,
data,
tokenId,
amount
);
emit TokenReceivedWithData(tokenId, sourceChain, destinationAddress, amount, sourceAddress, data);
} else {
emit TokenReceived(tokenId, sourceChain, destinationAddress, amount);
}
}

/**
Expand Down Expand Up @@ -839,11 +811,17 @@ contract InterchainTokenService is
emit StandardizedTokenDeployed(tokenId, distributor, name, symbol, decimals, mintAmount, mintTo);
}

function _decodeMetadata(bytes calldata metadata) internal pure returns (uint32 version, bytes calldata data) {
function _decodeMetadata(bytes memory metadata) internal pure returns (uint32 version, bytes memory data) {
data = new bytes(metadata.length - 4);
assembly {
data.length := sub(metadata.length, 4)
data.offset := add(metadata.offset, 4)
version := calldataload(sub(metadata.offset, 28))
version := shr(224, mload(data))
}
if (data.length == 0) return (version, data);
uint256 n = (data.length - 1) / 32;
for (uint256 i = 0; i <= n; ++i) {
assembly {
mstore(add(data, add(32, mul(32, i))), mload(add(metadata, add(36, mul(32, i)))))
}
}
}

Expand All @@ -863,4 +841,36 @@ contract InterchainTokenService is
amount
);
}

/**
* @notice Transmit a sendTokenWithData for the given tokenId. Only callable by a token manager.
* @param tokenId the tokenId of the TokenManager (which must be the msg.sender).
* @param sourceAddress the address where the token is coming from, which will also be used for reimburment of gas.
* @param destinationChain the name of the chain to send tokens to.
* @param destinationAddress the destinationAddress for the interchainTransfer.
* @param amount the amount of token to give.
* @param metadata the data to be passed to the destiantion.
*/
function _transmitSendToken(
bytes32 tokenId,
address sourceAddress,
string calldata destinationChain,
bytes memory destinationAddress,
uint256 amount,
bytes memory metadata
) internal {
bytes memory payload;
if (metadata.length < 4) {
payload = abi.encode(SELECTOR_SEND_TOKEN, tokenId, destinationAddress, amount);
_callContract(destinationChain, payload, msg.value);
emit TokenSent(tokenId, destinationChain, destinationAddress, amount);
return;
}
uint32 version;
(version, metadata) = _decodeMetadata(metadata);
if (version > 0) revert InvalidMetadataVersion(version);
payload = abi.encode(SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destinationAddress, amount, sourceAddress.toBytes(), metadata);
_callContract(destinationChain, payload, msg.value);
emit TokenSentWithData(tokenId, destinationChain, destinationAddress, amount, sourceAddress, metadata);
}
}
69 changes: 4 additions & 65 deletions contracts/interfaces/IExpressCallHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,75 +6,14 @@ interface IExpressCallHandler {
error AlreadyExpressCalled();
error SameDestinationAsCaller();

event ExpressReceive(
bytes32 indexed tokenId,
address indexed destinationAddress,
uint256 amount,
bytes32 indexed sendHash,
address expressCaller
);
event ExpressExecutionFulfilled(
bytes32 indexed tokenId,
address indexed destinationAddress,
uint256 amount,
bytes32 indexed sendHash,
address expressCaller
);

event ExpressReceiveWithData(
bytes32 indexed tokenId,
string sourceChain,
bytes sourceAddress,
address indexed destinationAddress,
uint256 amount,
bytes data,
bytes32 indexed sendHash,
address expressCaller
);
event ExpressExecutionWithDataFulfilled(
bytes32 indexed tokenId,
string sourceChain,
bytes sourceAddress,
address indexed destinationAddress,
uint256 amount,
bytes data,
bytes32 indexed sendHash,
address expressCaller
);
event ExpressReceive(bytes payload, bytes32 indexed sendHash, address indexed expressCaller);
event ExpressExecutionFulfilled(bytes payload, bytes32 indexed sendHash, address indexed expressCaller);

/**
* @notice Gets the address of the express caller for a specific token transfer
* @param tokenId The ID of the token being sent
* @param destinationAddress The address of the recipient
* @param amount The amount of tokens to be sent
* @param commandId The unique hash for this token transfer
* @return expressCaller The address of the express caller for this token transfer
*/
function getExpressReceiveToken(
bytes32 tokenId,
address destinationAddress,
uint256 amount,
bytes32 commandId
) external view returns (address expressCaller);

/**
* @notice Gets the address of the express caller for a specific token transfer with data
* @param tokenId The ID of the token being sent
* @param sourceChain The chain from which the token will be sent
* @param sourceAddress The originating address of the token on the source chain
* @param destinationAddress The address of the recipient on the destination chain
* @param amount The amount of tokens to be sent
* @param data The data associated with the token transfer
* @param payload the payload for the receive token
* @param commandId The unique hash for this token transfer
* @return expressCaller The address of the express caller for this token transfer
*/
function getExpressReceiveTokenWithData(
bytes32 tokenId,
string memory sourceChain,
bytes memory sourceAddress,
address destinationAddress,
uint256 amount,
bytes calldata data,
bytes32 commandId
) external view returns (address expressCaller);
function getExpressReceiveToken(bytes calldata payload, bytes32 commandId) external view returns (address expressCaller);
}
Loading

0 comments on commit 403e3bd

Please sign in to comment.