Skip to content

Commit

Permalink
feat(Service): implementing ValuedExpressExecutable (#131)
Browse files Browse the repository at this point in the history
* feat(Express): using executable return values

* feat(Service): implementing ValuedExpressExecutable

* feat(Service): implementing ValuedExpressExecutable

* test(Service): expressExecute

* doc(slither): ignore line

* fix(ExpressCallHandler): conform to ExpressExecutorTracker

* refactor(InterchainTokenService): inherit ExpressExecutorTracker directly

* refactor(InterchainTokenService): EXECUTE_SUCCESS constants

* refactor(IInterchainTokenService): deleted express handled interface

* Update contracts/interchain-token-service/InterchainTokenService.sol

Co-authored-by: Milap Sheth <[email protected]>

* Update contracts/interchain-token-service/InterchainTokenService.sol

Co-authored-by: Milap Sheth <[email protected]>

* Update contracts/interchain-token-service/InterchainTokenService.sol

Co-authored-by: Milap Sheth <[email protected]>

* Update contracts/interchain-token-service/InterchainTokenService.sol

Co-authored-by: Milap Sheth <[email protected]>

* refactor(InterchainTokenService): expressExecutor

* style(Solidity): prettier

* fix(InterchainTokenService): revert in contractCallValue

---------

Co-authored-by: Milap Sheth <[email protected]>
  • Loading branch information
re1ro and milapsheth authored Oct 31, 2023
1 parent ceade4a commit cd663a4
Show file tree
Hide file tree
Showing 14 changed files with 225 additions and 319 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ abstract contract InterchainTokenExecutable is IInterchainTokenExecutable {

address public immutable interchainTokenService;

bytes32 internal constant EXECUTE_SUCCESS = keccak256('its-execute-success');

constructor(address interchainTokenService_) {
interchainTokenService = interchainTokenService_;
}
Expand All @@ -25,8 +27,9 @@ abstract contract InterchainTokenExecutable is IInterchainTokenExecutable {
bytes32 tokenId,
address token,
uint256 amount
) external onlyService {
) external virtual onlyService returns (bytes32) {
_executeWithInterchainToken(sourceChain, sourceAddress, data, tokenId, token, amount);
return EXECUTE_SUCCESS;
}

function _executeWithInterchainToken(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { IInterchainTokenExpressExecutable } from '../interfaces/IInterchainToke
import { InterchainTokenExecutable } from './InterchainTokenExecutable.sol';

abstract contract InterchainTokenExpressExecutable is IInterchainTokenExpressExecutable, InterchainTokenExecutable {
bytes32 internal constant EXPRESS_EXECUTE_SUCCESS = keccak256('its-express-execute-success');

constructor(address interchainTokenService_) InterchainTokenExecutable(interchainTokenService_) {}

function expressExecuteWithInterchainToken(
Expand All @@ -15,7 +17,8 @@ abstract contract InterchainTokenExpressExecutable is IInterchainTokenExpressExe
bytes32 tokenId,
address token,
uint256 amount
) external onlyService {
) external virtual onlyService returns (bytes32) {
_executeWithInterchainToken(sourceChain, sourceAddress, data, tokenId, token, amount);
return EXPRESS_EXECUTE_SUCCESS;
}
}
150 changes: 110 additions & 40 deletions contracts/interchain-token-service/InterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity ^0.8.0;
import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol';
import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol';
import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol';
import { ExpressExecutorTracker } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/express/ExpressExecutorTracker.sol';
import { Upgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Upgradable.sol';
import { Create3Address } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3Address.sol';
import { SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol';
Expand All @@ -21,7 +22,6 @@ import { ITokenManagerProxy } from '../interfaces/ITokenManagerProxy.sol';
import { IERC20Named } from '../interfaces/IERC20Named.sol';

import { AddressBytesUtils } from '../libraries/AddressBytesUtils.sol';
import { ExpressCallHandler } from '../utils/ExpressCallHandler.sol';
import { Pausable } from '../utils/Pausable.sol';
import { Operatable } from '../utils/Operatable.sol';
import { Multicall } from '../utils/Multicall.sol';
Expand All @@ -33,13 +33,13 @@ import { Multicall } from '../utils/Multicall.sol';
* @dev The only storage used here is for ExpressCalls
*/
contract InterchainTokenService is
IInterchainTokenService,
Upgradable,
Operatable,
ExpressCallHandler,
Pausable,
Multicall,
Create3Address
Create3Address,
ExpressExecutorTracker,
IInterchainTokenService
{
using StringToBytes32 for string;
using Bytes32ToString for bytes32;
Expand Down Expand Up @@ -68,6 +68,8 @@ contract InterchainTokenService is
uint256 private constant SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN = 4;

bytes32 private constant CONTRACT_ID = keccak256('interchain-token-service');
bytes32 private constant EXECUTE_SUCCESS = keccak256('its-execute-success');
bytes32 private constant EXPRESS_EXECUTE_SUCCESS = keccak256('its-express-execute-success');

/**
* @dev All of the variables passed here are stored as immutable variables.
Expand Down Expand Up @@ -398,42 +400,78 @@ contract InterchainTokenService is
);
}

// Returns the amount of token that this call is worth. If `tokenAddress` is `0`, then value is in terms of the native token, otherwise it's in terms of the token address.
function contractCallValue(
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload
) public view virtual onlyRemoteService(sourceChain, sourceAddress) notPaused returns (address, uint256) {
(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();
}

ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId));
return (tokenManager.tokenAddress(), amount);
}

function expressExecute(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload
) external payable notPaused {
uint256 selector = abi.decode(payload, (uint256));
if (selector != SELECTOR_RECEIVE_TOKEN && selector != SELECTOR_RECEIVE_TOKEN_WITH_DATA) {
revert InvalidExpressSelector();
}
if (gateway.isCommandExecuted(commandId)) revert AlreadyExecuted();

address expressExecutor = msg.sender;
bytes32 payloadHash = keccak256(payload);

_setExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor);
_expressExecute(sourceChain, payload);

emit ExpressExecuted(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor);
}

/**
* @notice Uses the caller's tokens to fullfill a sendCall ahead of time. Use this only if you have detected an outgoing
* 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 sourceChain the name of the chain where the interchainTransfer originated from.
* @param payload the payload of the receive token
* @param commandId the sendHash detected at the sourceChain.
*/
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(
function _expressExecute(string calldata sourceChain, bytes calldata payload) internal {
(uint256 selector, bytes32 tokenId, bytes memory sourceAddress, bytes memory destinationAddressBytes, uint256 amount) = abi.decode(
payload,
(uint256, bytes32, bytes, uint256)
(uint256, bytes32, bytes, bytes, uint256)
);
address destinationAddress = destinationAddressBytes.toAddress();

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

token.safeTransferFrom(caller, destinationAddress, amount);
token.safeTransferFrom(msg.sender, destinationAddress, amount);

if (selector == SELECTOR_RECEIVE_TOKEN_WITH_DATA) {
(, , , , bytes memory sourceAddress, bytes memory data) = abi.decode(payload, (uint256, bytes32, bytes, uint256, bytes, bytes));
IInterchainTokenExpressExecutable(destinationAddress).expressExecuteWithInterchainToken(
(, , , , , bytes memory data) = abi.decode(payload, (uint256, bytes32, bytes, bytes, uint256, bytes));

bytes32 result = IInterchainTokenExpressExecutable(destinationAddress).expressExecuteWithInterchainToken(
sourceChain,
sourceAddress,
data,
tokenId,
address(token),
amount
);
} else if (selector != SELECTOR_RECEIVE_TOKEN) {
revert InvalidExpressSelector();

if (result != EXPRESS_EXECUTE_SUCCESS) revert ExpressExecuteWithInterchainTokenFailed(destinationAddress);
}
}

Expand All @@ -443,7 +481,7 @@ contract InterchainTokenService is
bytes calldata destinationAddress,
uint256 amount,
bytes calldata metadata
) external {
) external notPaused {
ITokenManager tokenManager = ITokenManager(getTokenManagerAddress(tokenId));
amount = tokenManager.takeToken(msg.sender, amount);
_transmitSendToken(tokenId, msg.sender, destinationChain, destinationAddress, amount, metadata);
Expand All @@ -455,7 +493,7 @@ contract InterchainTokenService is
bytes calldata destinationAddress,
uint256 amount,
bytes calldata data
) external {
) external notPaused {
ITokenManager tokenManager = ITokenManager(getTokenManagerAddress(tokenId));
amount = tokenManager.takeToken(msg.sender, amount);
uint32 prefix = 0;
Expand Down Expand Up @@ -533,7 +571,7 @@ contract InterchainTokenService is
/**
* @notice Executes operations based on the payload and selector.
* @param sourceChain The chain where the transaction originates from
* @param sourceAddress The address where the transaction originates from
* @param sourceAddress The address of the remote ITS where the transaction originates from
* @param payload The encoded data payload for the transaction
*/
function execute(
Expand All @@ -547,14 +585,43 @@ contract InterchainTokenService is
if (!gateway.validateContractCall(commandId, sourceChain, sourceAddress, payloadHash)) revert NotApprovedByGateway();

uint256 selector = abi.decode(payload, (uint256));
if (selector == SELECTOR_RECEIVE_TOKEN || selector == SELECTOR_RECEIVE_TOKEN_WITH_DATA)
return _processReceiveTokenPayload(commandId, sourceChain, payload, selector);
if (selector == SELECTOR_RECEIVE_TOKEN || selector == SELECTOR_RECEIVE_TOKEN_WITH_DATA) {
address expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash);
_processReceiveTokenPayload(expressExecutor, sourceChain, payload, selector);

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

return;
}

if (selector == SELECTOR_DEPLOY_TOKEN_MANAGER) return _processDeployTokenManagerPayload(payload);
if (selector == SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN) return _processDeployStandardizedTokenAndManagerPayload(payload);

revert SelectorUnknown();
}

function contractCallWithTokenValue(
string calldata /*sourceChain*/,
string calldata /*sourceAddress*/,
bytes calldata /*payload*/,
string calldata /*symbol*/,
uint256 /*amount*/
) public view virtual returns (address, uint256) {
revert ExecuteWithTokenNotSupported();
}

function expressExecuteWithToken(
bytes32 /*commandId*/,
string calldata /*sourceChain*/,
string calldata /*sourceAddress*/,
bytes calldata /*payload*/,
string calldata /*tokenSymbol*/,
uint256 /*amount*/
) external payable {
revert ExecuteWithTokenNotSupported();
}

function executeWithToken(
bytes32 /*commandId*/,
string calldata /*sourceChain*/,
Expand All @@ -572,49 +639,52 @@ contract InterchainTokenService is
* @param payload The encoded data payload to be processed
*/
function _processReceiveTokenPayload(
bytes32 commandId,
address expressExecutor,
string calldata sourceChain,
bytes calldata payload,
uint256 selector
) internal {
bytes32 tokenId;
bytes memory sourceAddress;
address destinationAddress;
uint256 amount;
{
bytes memory destinationAddressBytes;
(, tokenId, destinationAddressBytes, amount) = abi.decode(payload, (uint256, bytes32, bytes, uint256));
(, tokenId, sourceAddress, destinationAddressBytes, amount) = abi.decode(payload, (uint256, bytes32, bytes, bytes, uint256));
destinationAddress = destinationAddressBytes.toAddress();
}

ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId));
{
address expressCaller = _popExpressReceiveToken(payload, commandId);
if (expressCaller != address(0)) {
amount = tokenManager.giveToken(expressCaller, amount);
return;
}

// Return token to the existing express caller
if (expressExecutor != address(0)) {
// slither-disable-next-line unused-return
tokenManager.giveToken(expressExecutor, amount);
return;
}

amount = tokenManager.giveToken(destinationAddress, amount);

if (selector == SELECTOR_RECEIVE_TOKEN_WITH_DATA) {
bytes memory sourceAddress;
bytes memory data;
(, , , , sourceAddress, data) = abi.decode(payload, (uint256, bytes32, bytes, uint256, bytes, bytes));
(, , , , , data) = abi.decode(payload, (uint256, bytes32, bytes, bytes, uint256, bytes));

// slither-disable-next-line reentrancy-events
emit TokenReceivedWithData(tokenId, sourceChain, destinationAddress, amount, sourceAddress, data);
emit TokenReceivedWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount);

IInterchainTokenExecutable(destinationAddress).executeWithInterchainToken(
bytes32 result = IInterchainTokenExecutable(destinationAddress).executeWithInterchainToken(
sourceChain,
sourceAddress,
data,
tokenId,
tokenManager.tokenAddress(),
amount
);

if (result != EXECUTE_SUCCESS) revert ExecuteWithInterchainTokenFailed(destinationAddress);
} else {
// slither-disable-next-line reentrancy-events
emit TokenReceived(tokenId, sourceChain, destinationAddress, amount);
emit TokenReceived(tokenId, sourceChain, sourceAddress, destinationAddress, amount);
}
}

Expand Down Expand Up @@ -878,7 +948,7 @@ contract InterchainTokenService is
// slither-disable-next-line reentrancy-events
emit TokenSent(tokenId, destinationChain, destinationAddress, amount);

payload = abi.encode(SELECTOR_RECEIVE_TOKEN, tokenId, destinationAddress, amount);
payload = abi.encode(SELECTOR_RECEIVE_TOKEN, tokenId, sourceAddress.toBytes(), destinationAddress, amount);

_callContract(destinationChain, payload, msg.value);
return;
Expand All @@ -890,7 +960,7 @@ contract InterchainTokenService is
// slither-disable-next-line reentrancy-events
emit TokenSentWithData(tokenId, destinationChain, destinationAddress, amount, sourceAddress, metadata);

payload = abi.encode(SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, destinationAddress, amount, sourceAddress.toBytes(), metadata);
payload = abi.encode(SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, sourceAddress.toBytes(), destinationAddress, amount, metadata);

_callContract(destinationChain, payload, msg.value);
}
Expand Down
19 changes: 0 additions & 19 deletions contracts/interfaces/IExpressCallHandler.sol

This file was deleted.

2 changes: 1 addition & 1 deletion contracts/interfaces/IInterchainTokenExecutable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ interface IInterchainTokenExecutable {
bytes32 tokenId,
address token,
uint256 amount
) external;
) external returns (bytes32);
}
2 changes: 1 addition & 1 deletion contracts/interfaces/IInterchainTokenExpressExecutable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ interface IInterchainTokenExpressExecutable is IInterchainTokenExecutable {
bytes32 tokenId,
address token,
uint256 amount
) external;
) external returns (bytes32);
}
Loading

0 comments on commit cd663a4

Please sign in to comment.