diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 61f9e5130..afc5313e8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -121,6 +121,7 @@ jobs: cargo +nightly test -p pallet-ismp --all-targets --all-features --locked cargo +nightly test -p ismp-testsuite --all-targets --all-features --locked cargo +nightly test -p ethereum-trie --all-features --locked + cd evm/forge && cargo test - name: Clone eth-pos-devnet repository run: | diff --git a/.gitmodules b/.gitmodules index 866166ddc..9834cd35f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "evm/lib/openzeppelin-contracts"] path = evm/lib/openzeppelin-contracts url = https://github.com/openzeppelin/openzeppelin-contracts +[submodule "evm/lib/multi-chain-tokens"] + path = evm/lib/multi-chain-tokens + url = https://github.com/polytope-labs/multi-chain-tokens diff --git a/evm/lib/multi-chain-tokens b/evm/lib/multi-chain-tokens new file mode 160000 index 000000000..01a46a270 --- /dev/null +++ b/evm/lib/multi-chain-tokens @@ -0,0 +1 @@ +Subproject commit 01a46a270ec9359670ab79b46baf2aa7c30a5b81 diff --git a/evm/remappings.txt b/evm/remappings.txt index b597f08f6..af92a6b6f 100644 --- a/evm/remappings.txt +++ b/evm/remappings.txt @@ -1,4 +1,4 @@ ismp/=lib/ismp-solidity/src/ openzeppelin/=lib/openzeppelin-contracts/contracts/ solidity-merkle-trees/=lib/solidity-merkle-trees/src/ -multichain-token/=lib/multichain-native-tokens/src/ \ No newline at end of file +multi-chain-tokens/=lib/multi-chain-tokens/src/ \ No newline at end of file diff --git a/evm/script/Deploy.s.sol b/evm/script/Deploy.s.sol index ddeba4cf4..65457607f 100644 --- a/evm/script/Deploy.s.sol +++ b/evm/script/Deploy.s.sol @@ -50,7 +50,7 @@ contract DeployScript is Script { // set the ismphost on the cross-chain governor governor.setIsmpHost(hostAddress); // deploy the ping module as well -// PingModule m = new PingModule{salt: salt}(hostAddress); + // PingModule m = new PingModule{salt: salt}(hostAddress); vm.stopBroadcast(); } diff --git a/evm/src/EvmHost.sol b/evm/src/EvmHost.sol index 6260892f9..952c75b79 100644 --- a/evm/src/EvmHost.sol +++ b/evm/src/EvmHost.sol @@ -374,7 +374,9 @@ abstract contract EvmHost is IIsmpHost, Context { * @param request - post dispatch request */ function dispatch(DispatchPost memory request) external { - uint64 timeout = request.timeout == 0 ? 0 : uint64(this.timestamp()) + uint64(Math.max(_hostParams.defaultTimeout, request.timeout)); + uint64 timeout = request.timeout == 0 + ? 0 + : uint64(this.timestamp()) + uint64(Math.max(_hostParams.defaultTimeout, request.timeout)); PostRequest memory _request = PostRequest({ source: host(), dest: request.dest, diff --git a/evm/src/HandlerV1.sol b/evm/src/HandlerV1.sol index 0b281eefe..4d8ea3959 100644 --- a/evm/src/HandlerV1.sol +++ b/evm/src/HandlerV1.sol @@ -76,7 +76,10 @@ contract HandlerV1 is IHandler, Context { PostRequestLeaf memory leaf = request.requests[i]; require(leaf.request.dest.equals(host.host()), "IHandler: Invalid request destination"); - require(leaf.request.timeoutTimestamp == 0 || leaf.request.timeoutTimestamp > host.timestamp(), "IHandler: Request timed out"); + require( + leaf.request.timeoutTimestamp == 0 || leaf.request.timeoutTimestamp > host.timestamp(), + "IHandler: Request timed out" + ); bytes32 commitment = Message.hash(leaf.request); require(!host.requestReceipts(commitment), "IHandler: Duplicate request"); @@ -149,7 +152,9 @@ contract HandlerV1 is IHandler, Context { for (uint256 i = 0; i < timeoutsLength; i++) { PostRequest memory request = message.timeouts[i]; - require(request.timeoutTimestamp != 0 && state.timestamp > request.timeoutTimestamp, "Request not timed out"); + require( + request.timeoutTimestamp != 0 && state.timestamp > request.timeoutTimestamp, "Request not timed out" + ); bytes32 requestCommitment = Message.hash(request); require(host.requestCommitments(requestCommitment), "IHandler: Unknown request"); @@ -186,7 +191,10 @@ contract HandlerV1 is IHandler, Context { bytes32 requestCommitment = Message.hash(request); require(host.requestCommitments(requestCommitment), "IHandler: Unknown GET request"); - require(request.timeoutTimestamp == 0 || request.timeoutTimestamp > host.timestamp(), "IHandler: GET request timed out"); + require( + request.timeoutTimestamp == 0 || request.timeoutTimestamp > host.timestamp(), + "IHandler: GET request timed out" + ); StorageValue[] memory values = MerklePatricia.ReadChildProofCheck(root, proof, request.keys, bytes.concat(requestCommitment)); @@ -209,7 +217,10 @@ contract HandlerV1 is IHandler, Context { bytes32 requestCommitment = Message.hash(request); require(host.requestCommitments(requestCommitment), "IHandler: Unknown request"); - require(request.timeoutTimestamp != 0 && host.timestamp() > request.timeoutTimestamp, "IHandler: GET request not timed out"); + require( + request.timeoutTimestamp != 0 && host.timestamp() > request.timeoutTimestamp, + "IHandler: GET request not timed out" + ); host.dispatchIncoming(request); } } diff --git a/evm/src/modules/TokenGateway.sol b/evm/src/modules/TokenGateway.sol index e7129de78..227a3ce2b 100644 --- a/evm/src/modules/TokenGateway.sol +++ b/evm/src/modules/TokenGateway.sol @@ -1,158 +1,87 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.17; -// -//// import "../IISMPRouter.sol"; -//import "../interfaces/IISMPModule.sol"; -//import "openzeppelin/utils/introspection/IERC165.sol"; -//import "multichain-token/interfaces/IERC6160Ext20.sol"; -//import {IERC_ACL_CORE} from "multichain-token/interfaces/IERCAclCore.sol"; -//import "../interfaces/IIsmp.sol"; -//import "../interfaces/IIsmpHost.sol"; -// -//// -//// Add supports interface for custom bride token -//// -//error LengthMismatch(); -//error ZeroAddress(); -//error TokenNotMultiChainNative(); -//error BurnerRoleMissing(); -//error MinterRoleMissing(); -//error AuthFailed(); -//error NotDispatcher(); -// -//contract TokenGateway is IIsmpModule { -// address admin; -// address host; -// bytes4 constant IERC6160Ext20ID = 0xbbb8b47e; -// -// bytes32 constant MINTER_ROLE = keccak256("MINTER ROLE"); -// bytes32 constant BURNER_ROLE = keccak256("BURNER ROLE"); -// -// mapping(uint256 => address) public chains; -// mapping(uint256 => address) public tokenIds; -// -// // auth modifier -// modifier auth() { -// if (msg.sender != admin) { -// revert AuthFailed(); -// } -// _; -// } -// -// // restricts call to `dispatcher` -// modifier onlyDispatcher() { -// if (msg.sender != host) { -// revert NotDispatcher(); -// } -// _; -// } -// -// constructor( -// address _host, -// uint256[] memory _SMids, -// address[] memory _SMaddresses, -// uint256[] memory _Tids, -// address[] memory _Taddresses -// ) { -// admin = msg.sender; -// host = _host; -// setStateMachineIds(_SMids, _SMaddresses); -// setTokenIds(_Tids, _Taddresses); -// } -// -// // sets the addresses for a given StateMachineId -// function setStateMachineIds(uint256[] memory _ids, address[] memory _addresses) public auth { -// if (_ids.length != _addresses.length) revert LengthMismatch(); -// for (uint256 i = 0; i < _ids.length;) { -// address _address = _addresses[i]; -// if (_address == address(0)) continue; -// chains[_ids[i]] = _addresses[i]; -// unchecked { -// ++i; -// } -// } -// } -// -// // sets the Id for a bridge compatible token -// function setTokenIds(uint256[] memory _tokenIds, address[] memory _addresses) public auth { -// if (_tokenIds.length != _addresses.length) revert LengthMismatch(); -// for (uint256 i = 0; i < _tokenIds.length;) { -// address _tokenAddress = _addresses[i]; -// if (_tokenAddress == address(0)) revert ZeroAddress(); -// if (!IERC_ACL_CORE(_tokenAddress).hasRole(BURNER_ROLE, address(this))) revert BurnerRoleMissing(); -// if (!IERC_ACL_CORE(_tokenAddress).hasRole(MINTER_ROLE, address(this))) revert MinterRoleMissing(); -// if (!IERC165(_tokenAddress).supportsInterface(IERC6160Ext20ID)) revert TokenNotMultiChainNative(); -// tokenIds[_tokenIds[i]] = _tokenAddress; -// unchecked { -// ++i; -// } -// } -// } -// -// // The Gateway contract has to have the roles `MINTER` and `BURNER`. -// function send( -// bytes memory stateMachine, -// uint256 tokenId, -// uint256 amount, -// address to, -// bytes memory module, -// uint64 timestamp, -// uint64 gasLimit -// ) public { -// // USDC -> HyperUSDC(ERC6160) -// address tokenAddress = tokenIds[tokenId]; -// // check permision at set token. -// IERC6160Ext20(tokenAddress).burn(msg.sender, amount, ""); -// bytes memory data = abi.encodePacked(to, amount, tokenId); -// bytes memory source = IIsmpHost(host).host(); -// DispatchPost memory postRequest = DispatchPost({ -// destChain: stateMachine, -// from: source, -// to: module, -// body: data, -// timeoutTimestamp: timestamp, -// gaslimit: gasLimit -// }); -// IIsmp(host).dispatch(postRequest); -// } -// -// function onAccept(PostRequest memory request) public onlyDispatcher { -// (address to, uint256 amount, uint256 tokenId) = _decodePackedData(request.body); -// address tokenAddress = tokenIds[tokenId]; -// -// IERC6160Ext20(tokenAddress).mint(to, amount, ""); -// } -// -// function onPostResponse(PostResponse memory response) public view onlyDispatcher { -// revert("Token gateway doesn't emit responses"); -// } -// -// function onPostTimeout(PostRequest memory request) public onlyDispatcher { -// (address to, uint256 amount, uint256 tokenId) = _decodePackedData(request.body); -// address tokenAddress = tokenIds[tokenId]; -// -// if (tokenAddress == address(0)) revert ZeroAddress(); -// -// IERC6160Ext20(tokenAddress).mint(to, amount, ""); -// } -// -// function onGetResponse(GetResponse memory response) public view onlyDispatcher { -// revert("Not implemented"); -// } -// -// function onGetTimeout(GetRequest memory request) public view onlyDispatcher { -// revert("Not implemented"); -// } -// -// function _decodePackedData(bytes memory data) -// internal -// pure -// returns (address to_, uint256 amount_, uint256 tokenId_) -// { -// assembly { -// to_ := div(mload(add(data, 32)), 0x1000000000000000000000000) // hex slicing to get first 20-bytes. -// amount_ := mload(add(data, 52)) -// tokenId_ := mload(add(data, 84)) -// } -// } -//} + +import "ismp/interfaces/IIsmpModule.sol"; +import "ismp/interfaces/IIsmp.sol"; +import "multi-chain-tokens/interfaces/IERC6160Ext20.sol"; + +error ZeroAddress(); + +contract TokenGateway is IIsmpModule { + address private host; + + mapping(uint256 => address) public chains; + mapping(uint256 => address) public tokenIds; + + // restricts call to `dispatcher` + modifier onlyIsmpHost() { + if (msg.sender != host) { + revert("Unauthorized call"); + } + _; + } + + constructor(address _host) { + host = _host; + } + + // The Gateway contract has to have the roles `MINTER` and `BURNER`. + function send( + uint256 amount, + address to, + bytes memory dest, + address tokenContract, + address gateway, + uint64 gasLimit + ) public { + address from = msg.sender; + IERC6160Ext20(tokenContract).burn(from, amount, ""); + bytes memory data = abi.encodePacked(from, to, amount, tokenContract); + DispatchPost memory postRequest = DispatchPost({ + dest: dest, + to: abi.encodePacked(gateway), + body: data, + timeout: 60 * 60, // seconds + gaslimit: gasLimit + }); + IIsmp(host).dispatch(postRequest); + } + + function onAccept(PostRequest memory request) public onlyIsmpHost { + (address _from, address to, uint256 amount, address tokenContract) = _decodePackedData(request.body); + + IERC6160Ext20(tokenContract).mint(to, amount, ""); + } + + function onPostTimeout(PostRequest memory request) public onlyIsmpHost { + (address from, address _to, uint256 amount, address tokenContract) = _decodePackedData(request.body); + + IERC6160Ext20(tokenContract).mint(from, amount, ""); + } + + function onPostResponse(PostResponse memory response) public view onlyIsmpHost { + revert("Token gateway doesn't emit responses"); + } + + function onGetResponse(GetResponse memory response) public view onlyIsmpHost { + revert("Token gateway doesn't emit Get Requests"); + } + + function onGetTimeout(GetRequest memory request) public view onlyIsmpHost { + revert("Token gateway doesn't emit Get Requests"); + } + + function _decodePackedData(bytes memory data) + internal + pure + returns (address from_, address to_, uint256 amount_, address tokenContract_) + { + // todo: + assembly { + from_ := div(mload(add(data, 32)), 0x1000000000000000000000000) // hex slicing to get first 20-bytes. + to_ := div(mload(add(data, 32)), 0x1000000000000000000000000) // hex slicing to get first 20-bytes. + amount_ := mload(add(data, 52)) + tokenContract_ := mload(add(data, 84)) + } + } +}