diff --git a/src/interface/ICrosschain.sol b/src/interface/ICrosschain.sol deleted file mode 100644 index b8de6212..00000000 --- a/src/interface/ICrosschain.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -interface ICrosschain { - - /** - * @notice Sends a cross-chain transaction. - * @param _destinationChain The destination chain ID. - * @param _callAddress The address of the contract on the destination chain. - * @param _payload The payload to send to the destination chain. - * @param _extraArgs The extra arguments to pass - * @dev extraArgs may contain items such as token, amount, feeTokenAddress, receipient, gasLimit, etc - */ - function sendCrossChainTransaction( - uint64 _destinationChain, - address _callAddress, - bytes calldata _payload, - bytes calldata _extraArgs - ) external payable; - - /** - * @notice callback function for when a cross-chain transaction is sent. - * @param _destinationChain The destination chain ID. - * @param _callAddress The address of the contract on the destination chain. - * @param _payload The payload sent to the destination chain. - * @param _extraArgs The extra arguments sent to the callAddress on the destination chain. - */ - function onCrossChainTransactionSent( - uint64 _destinationChain, - address _callAddress, - bytes calldata _payload, - bytes calldata _extraArgs - ) internal; - - /** - * @notice callback function for when a cross-chain transaction is received. - * @param _sourceChain The source chain ID. - * @param _sourceAddress The address of the contract on the source chain. - * @param _payload The payload sent to the destination chain. - * @param _extraArgs The extra arguments sent to the callAddress on the destination chain. - */ - function onCrossChainTransactionReceived( - uint64 _sourceChain, - address _sourceAddress, - bytes calldata _payload, - bytes calldata _extraArgs - ) internal; - - function setRouter(address _router) external; - function getRouter() external view returns (address); - -} diff --git a/src/module/token/crosschain/zetachain.sol b/src/module/token/crosschain/zetachain.sol new file mode 100644 index 00000000..340fd82b --- /dev/null +++ b/src/module/token/crosschain/zetachain.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {Module} from "../../../Module.sol"; + +import {Role} from "../../../Role.sol"; +import {IERC20} from "../../../interface/IERC20.sol"; +import {CrossChain} from "./CrossChain.sol"; + +library ZetaChainCrossChainStorage { + + /// @custom:storage-location erc7201:token.minting.mintable + bytes32 public constant ZETACHAIN_CROSS_CHAIN_STORAGE_POSITION = + keccak256(abi.encode(uint256(keccak256("token.crosschain.zetachain")) - 1)) & ~bytes32(uint256(0xff)); + + struct Data { + address erc20Custody; + address tss; + } + + function data() internal pure returns (Data storage data_) { + bytes32 position = ZETACHAIN_CROSS_CHAIN_STORAGE_POSITION; + assembly { + data_.slot := position + } + } + +} + +interface IERC20Custody { + + function deposit(bytes calldata recipient, IERC20 asset, uint256 amount, bytes calldata message) external; + +} + +contract ZetaChainCrossChain is Module, CrossChain { + + /*////////////////////////////////////////////////////////////// + MODULE CONFIG + //////////////////////////////////////////////////////////////*/ + + function getModuleConfig() external pure virtual override returns (ModuleConfig memory config) { + config.callbackFunctions = new CallbackFunction[](1); + config.fallbackFunctions = new FallbackFunction[](5); + + config.fallbackFunctions[0] = + FallbackFunction({selector: this.sendCrossChainTransaction.selector, permissionBits: 0}); + config.fallbackFunctions[2] = FallbackFunction({selector: this.getRouter.selector, permissionBits: 0}); + config.fallbackFunctions[4] = FallbackFunction({selector: this.getERC20Custody.selector, permissionBits: 0}); + config.fallbackFunctions[1] = + FallbackFunction({selector: this.setRouter.selector, permissionBits: Role._MANAGER_ROLE}); + config.fallbackFunctions[3] = + FallbackFunction({selector: this.setERC20Custody.selector, permissionBits: Role._MANAGER_ROLE}); + + config.registerInstallationCallback = true; + } + + /*////////////////////////////////////////////////////////////// + CALLBACK FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @dev Called by a Core into an Module during the installation of the Module. + function onInstall(bytes calldata data) external { + (address tss, address erc20Custody) = abi.decode(data, (address, address)); + _zetaChainCrossChainStorage().tss = tss; + _zetaChainCrossChainStorage().erc20Custody = erc20Custody; + } + + /// @dev Called by a Core into an Module during the uninstallation of the Module. + function onUninstall(bytes calldata data) external {} + + /*////////////////////////////////////////////////////////////// + Encode install / uninstall data + //////////////////////////////////////////////////////////////*/ + + /// @dev Returns bytes encoded install params, to be sent to `onInstall` function + function encodeBytesOnInstall(address tss, address erc20Custody) external pure returns (bytes memory) { + return abi.encode(tss, erc20Custody); + } + + /// @dev Returns bytes encoded uninstall params, to be sent to `onUninstall` function + function encodeBytesOnUninstall() external pure returns (bytes memory) { + return ""; + } + + /*////////////////////////////////////////////////////////////// + FALLBACK FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + function sendCrossChainTransaction( + uint64 _destinationChain, + address _callAddress, + bytes calldata _payload, + bytes calldata _extraArgs + ) external payable override { + (address _recipient, address _token, uint256 _amount) = abi.decode(_extraArgs, (address, address, uint256)); + + // Mimics the encoding of the ZetaChain client library + // https://github.com/zeta-chain/toolkit/tree/main/packages/client/src + bytes memory encodedData = abi.encodePacked(_callAddress, _payload); + if (_token == address(0)) { + (bool success,) = payable(_zetaChainCrossChainStorage().tss).call{value: _amount}(encodedData); + require(success, "Failed to send message"); + } else { + IERC20Custody(_zetaChainCrossChainStorage().erc20Custody).deposit( + abi.encode(_recipient), IERC20(_token), _amount, encodedData + ); + } + + onCrossChainTransactionSent(_destinationChain, _callAddress, _payload, _extraArgs); + } + + function getRouter() external view override returns (address) { + return _zetaChainCrossChainStorage().tss; + } + + function setRouter(address _tss) external override { + _zetaChainCrossChainStorage().tss = _tss; + } + + function getERC20Custody() external view returns (address) { + return _zetaChainCrossChainStorage().erc20Custody; + } + + function setERC20Custody(address _erc20Custody) external { + _zetaChainCrossChainStorage().erc20Custody = _erc20Custody; + } + + function _zetaChainCrossChainStorage() internal pure returns (ZetaChainCrossChainStorage.Data storage) { + return ZetaChainCrossChainStorage.data(); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + function onCrossChainTransactionSent( + uint64 _destinationChain, + address _callAddress, + bytes calldata _payload, + bytes calldata _extraArgs + ) internal override { + // implementation goes here + } + +}