From 130e4a1e7eb1069af268b9510a0311287f84afdd Mon Sep 17 00:00:00 2001 From: Stanley Date: Fri, 13 Sep 2024 18:52:09 -0400 Subject: [PATCH 1/4] PoC implementation of Zetachain crosschain contract --- src/module/token/crosschain/zetachain.sol | 119 ++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/module/token/crosschain/zetachain.sol diff --git a/src/module/token/crosschain/zetachain.sol b/src/module/token/crosschain/zetachain.sol new file mode 100644 index 00000000..3ffdaa0c --- /dev/null +++ b/src/module/token/crosschain/zetachain.sol @@ -0,0 +1,119 @@ +// 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"; + +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 { + + /*////////////////////////////////////////////////////////////// + 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.sendMessage.selector, permissionBits: 0}); + config.fallbackFunctions[2] = FallbackFunction({selector: this.getTss.selector, permissionBits: 0}); + config.fallbackFunctions[4] = FallbackFunction({selector: this.getERC20Custody.selector, permissionBits: 0}); + config.fallbackFunctions[1] = + FallbackFunction({selector: this.setTss.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 sendMessage(address recipient, address token, uint256 value, bytes memory data) external { + if (token == address(0)) { + (bool success,) = payable(_zetaChainCrossChainStorage().tss).call{value: value}(data); + require(success, "Failed to send message"); + } else { + IERC20Custody(_zetaChainCrossChainStorage().erc20Custody).deposit( + abi.encode(recipient), IERC20(token), value, data + ); + } + } + + function getTss() external view returns (address) { + return _zetaChainCrossChainStorage().tss; + } + + function setTss(address _tss) external { + _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(); + } + +} From 2c9ed2efb2c946120d64907dbb3de2a7b1316f88 Mon Sep 17 00:00:00 2001 From: Stanley Date: Fri, 13 Sep 2024 20:03:40 -0400 Subject: [PATCH 2/4] updated to include callAddress --- src/module/token/crosschain/zetachain.sol | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/module/token/crosschain/zetachain.sol b/src/module/token/crosschain/zetachain.sol index 3ffdaa0c..c0693f1b 100644 --- a/src/module/token/crosschain/zetachain.sol +++ b/src/module/token/crosschain/zetachain.sol @@ -85,13 +85,18 @@ contract ZetaChainCrossChain is Module { FALLBACK FUNCTIONS //////////////////////////////////////////////////////////////*/ - function sendMessage(address recipient, address token, uint256 value, bytes memory data) external { + function sendMessage(address callAddress, address recipient, address token, uint256 value, bytes memory data) + external + { + // 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, data); if (token == address(0)) { - (bool success,) = payable(_zetaChainCrossChainStorage().tss).call{value: value}(data); + (bool success,) = payable(_zetaChainCrossChainStorage().tss).call{value: value}(encodedData); require(success, "Failed to send message"); } else { IERC20Custody(_zetaChainCrossChainStorage().erc20Custody).deposit( - abi.encode(recipient), IERC20(token), value, data + abi.encode(recipient), IERC20(token), value, encodedData ); } } From be6a221bfe76ff81fa9612f28bb934b6a38fd2f2 Mon Sep 17 00:00:00 2001 From: Stanley Date: Mon, 16 Sep 2024 17:09:56 -0400 Subject: [PATCH 3/4] updated to align with unified crosschain interface --- src/module/token/crosschain/zetachain.sol | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/module/token/crosschain/zetachain.sol b/src/module/token/crosschain/zetachain.sol index c0693f1b..265c5613 100644 --- a/src/module/token/crosschain/zetachain.sol +++ b/src/module/token/crosschain/zetachain.sol @@ -42,7 +42,8 @@ contract ZetaChainCrossChain is Module { config.callbackFunctions = new CallbackFunction[](1); config.fallbackFunctions = new FallbackFunction[](5); - config.fallbackFunctions[0] = FallbackFunction({selector: this.sendMessage.selector, permissionBits: 0}); + config.fallbackFunctions[0] = + FallbackFunction({selector: this.sendCrossChainTransaction.selector, permissionBits: 0}); config.fallbackFunctions[2] = FallbackFunction({selector: this.getTss.selector, permissionBits: 0}); config.fallbackFunctions[4] = FallbackFunction({selector: this.getERC20Custody.selector, permissionBits: 0}); config.fallbackFunctions[1] = @@ -85,18 +86,24 @@ contract ZetaChainCrossChain is Module { FALLBACK FUNCTIONS //////////////////////////////////////////////////////////////*/ - function sendMessage(address callAddress, address recipient, address token, uint256 value, bytes memory data) - external - { + function sendCrossChainTransaction( + uint64 _destinationChain, + address _callAddress, + address _recipient, + address _token, + uint256 _amount, + bytes calldata _data, + bytes memory _extraArgs + ) external { // 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, data); - if (token == address(0)) { - (bool success,) = payable(_zetaChainCrossChainStorage().tss).call{value: value}(encodedData); + bytes memory encodedData = abi.encodePacked(_callAddress, _data); + 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), value, encodedData + abi.encode(_recipient), IERC20(_token), _amount, encodedData ); } } From 3edff9680fba9883385e13b751837c26cb259ead Mon Sep 17 00:00:00 2001 From: Stanley Date: Fri, 20 Sep 2024 14:22:51 -0400 Subject: [PATCH 4/4] updated to use the CrossChain abstract contract --- src/interface/ICrosschain.sol | 52 ----------------------- src/module/token/crosschain/zetachain.sol | 39 +++++++++++------ 2 files changed, 27 insertions(+), 64 deletions(-) delete mode 100644 src/interface/ICrosschain.sol 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 index 265c5613..340fd82b 100644 --- a/src/module/token/crosschain/zetachain.sol +++ b/src/module/token/crosschain/zetachain.sol @@ -5,6 +5,7 @@ import {Module} from "../../../Module.sol"; import {Role} from "../../../Role.sol"; import {IERC20} from "../../../interface/IERC20.sol"; +import {CrossChain} from "./CrossChain.sol"; library ZetaChainCrossChainStorage { @@ -32,7 +33,7 @@ interface IERC20Custody { } -contract ZetaChainCrossChain is Module { +contract ZetaChainCrossChain is Module, CrossChain { /*////////////////////////////////////////////////////////////// MODULE CONFIG @@ -44,10 +45,10 @@ contract ZetaChainCrossChain is Module { config.fallbackFunctions[0] = FallbackFunction({selector: this.sendCrossChainTransaction.selector, permissionBits: 0}); - config.fallbackFunctions[2] = FallbackFunction({selector: this.getTss.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.setTss.selector, permissionBits: Role._MANAGER_ROLE}); + FallbackFunction({selector: this.setRouter.selector, permissionBits: Role._MANAGER_ROLE}); config.fallbackFunctions[3] = FallbackFunction({selector: this.setERC20Custody.selector, permissionBits: Role._MANAGER_ROLE}); @@ -89,15 +90,14 @@ contract ZetaChainCrossChain is Module { function sendCrossChainTransaction( uint64 _destinationChain, address _callAddress, - address _recipient, - address _token, - uint256 _amount, - bytes calldata _data, - bytes memory _extraArgs - ) external { + 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, _data); + 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"); @@ -106,13 +106,15 @@ contract ZetaChainCrossChain is Module { abi.encode(_recipient), IERC20(_token), _amount, encodedData ); } + + onCrossChainTransactionSent(_destinationChain, _callAddress, _payload, _extraArgs); } - function getTss() external view returns (address) { + function getRouter() external view override returns (address) { return _zetaChainCrossChainStorage().tss; } - function setTss(address _tss) external { + function setRouter(address _tss) external override { _zetaChainCrossChainStorage().tss = _tss; } @@ -128,4 +130,17 @@ contract ZetaChainCrossChain is Module { return ZetaChainCrossChainStorage.data(); } + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + function onCrossChainTransactionSent( + uint64 _destinationChain, + address _callAddress, + bytes calldata _payload, + bytes calldata _extraArgs + ) internal override { + // implementation goes here + } + }