# 三分钟跨链 第一步,先安装需要用到的库 ``` $ forge install OpenZeppelin/openzeppelin-contracts@v4.8.3 --no-commit $ forge install TusimaNetwork/zkBridge-messaging-interfaces --no-commit ``` 第二步,实现一个TokenMock合约,用于给用户铸造代币和发送时代币销毁。 ITokenMock.sol ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.16; import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; interface ITokenMock is IERC20 { function mint(address _to, uint256 _amount) external; function burn(address _owner,uint256 _amount) external; } ``` TokenMock.sol ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.16; import {ITokenMock} from "./interface/ITokenMock.sol"; import {Ownable} from "lib/openzeppelin-contracts/contracts/access/Ownable.sol"; import {ERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; contract TokenMock is ERC20,Ownable,ITokenMock { mapping (address => bool) public admin; constructor( string memory name_, string memory symbol_ ) ERC20(name_, symbol_) {} modifier onlyAdmin() { require(admin[msg.sender], "Only admin allowed"); _; } function setAdmin(address _allower,bool _isAllowed) public onlyOwner { admin[_allower] = _isAllowed; } function claim(address _to) public { _mint(_to, 1000 ether); } function mint(address _to, uint256 _amount) public virtual override onlyAdmin{ _mint(_to, _amount); } function burn(address _owner, uint256 _amount) public virtual override onlyAdmin{ _burn(_owner, _amount); } } ``` > 在发送合约部署完成后,记得授予发送合约`allower`权限。 第三步,实现在源链的发送合约: 我们在合约初始化时写入源链的messaging合约地址和TokenMock合约地址,messaging合约地址在文档中查询,TokenMock需要自己部署并记录地址。 ```solidity ISender public messaging; address public tokenMock; constructor(address _tusimaMessaging, address _tokenMock) { messaging = ISender(_tusimaMessaging); tokenMock = _tokenMock; } ``` 要实现跨链发送的操作,我们需要提供接口中需要的三个参数: targetChainId:目标链的ID targetAddr:在目标链的接收合约 message:需要跨链的消息 由于我们的Token跨链操作有更多的信息,比如接收者、转账数额,我们就把剩下的参数打包成bytes字段,作为message参数,在目标链接收合约中再拆解出相应的信息来。 ```solidity bytes memory message = abi.encodePacked(_tokenAmount, _receiver); ``` 这样就能通过接口实现基本的跨链: ```solidity function sendToken( uint32 _destinationChainId, address _msgReceiver, uint256 _tokenAmount, address _receiver ) external { bytes memory message = abi.encodePacked(_tokenAmount, _receiver); messaging.send(_destinationChainId, _msgReceiver, message); } ``` 我们再完善一下合约,让用户的token在发送时销毁掉,发起跨链交易成功后打印相应的事件,完整代码如下: ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.16; import {ITokenMock} from "./interface/ITokenMock.sol"; import {ISender} from "lib/zkBridge-messaging-interfaces/src/interfaces/IMessaging.sol"; contract TokenSender { event SendToken( uint32 destinationChainId, address msgReceiver, uint256 tokenAmount, address receiver ); ISender public messaging; address public tokenMock; constructor(address _tusimaMessaging, address _tokenMock) { messaging = ISender(_tusimaMessaging); tokenMock = _tokenMock; } function sendToken( uint32 _destinationChainId, address _msgReceiver, uint256 _tokenAmount, address _receiver ) external { ITokenMock(tokenMock).burn(msg.sender, _tokenAmount); bytes memory message = abi.encodePacked(_tokenAmount, _receiver); messaging.send(_destinationChainId, _msgReceiver, message); emit SendToken( _destinationChainId, _msgReceiver, _tokenAmount, _receiver ); } } ``` 第四步,实现目标链接收合约 同样,我们需要先部署一个 TokenMock 合约,用于给跨链成功的用户铸造代币,并记录合约地址。 在接收合约部署完成后,同样记得授予接收合约`allower`权限。 我们在合约初始化时写入目标链的messaging合约地址和TokenMock合约地址,messaging合约地址在文档中查询,TokenMock需要自己部署并记录地址。 ```solidity ISender public messaging; address public tokenMock; constructor(address _tusimaMessaging, address _tokenMock) { messaging = ISender(_tusimaMessaging); tokenMock = _tokenMock; } ``` 要实现接收合约,最重要的是实现接口中的`handleMsgImpl`函数。该函数的作用是供跨链桥合约messaging调用,将接收到的信息推送到接收合约中。 ```solidity function handleMsg(uint32 sourceChainId, address sourceAddr, bytes memory message) external ``` 可以看到该函数依旧只有三个参数,对我们最重要的是 message 参数,messgae内包含了接收者地址和跨链金额信息,我们需要将它们解析出来: ```solidity= struct Info { uint256 tokenAmount; address receiver; } function decodeInfo( bytes memory _message ) public pure returns (Info memory info) { uint256 tokenAmount; // 256 / 8 = 32 address receiver; // 20 bytes assembly { tokenAmount := mload(add(_message, 32)) receiver := mload(add(_message, 52)) } info.tokenAmount = tokenAmount; info.receiver = receiver; } ``` 随后我们就可以实现`handleMsgImpl`函数了: ```solidity function handleMsgImpl( uint32 _sourceChainId, address _sourceAddress, bytes memory _message ) internal override { Info memory info = decodeInfo(_message); } ``` 我们添加一些必要操作,比如在这个地方给接收者铸造出来跨链成功的代币,打印出相应的事件等,完整代码如下: ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.16; import {ITokenMock} from "./interface/ITokenMock.sol"; import {Receiver} from "lib/zkBridge-messaging-interfaces/src/interfaces/Receiver.sol"; contract TokenReceiver is Receiver { struct Info { uint256 tokenAmount; address receiver; } event TokenMessageReceived( uint32 indexed sourceChainId, address indexed sourceAddress, Info message ); address public tokenMock; constructor( address _tusimaMessaging, address _tokenMock ) Receiver(_tusimaMessaging) { tokenMock = _tokenMock; } function handleMsgImpl( uint32 _sourceChainId, address _sourceAddress, bytes memory _message ) internal override { Info memory info = decodeInfo(_message); ITokenMock(tokenMock).mint(info.receiver, info.tokenAmount); emit TokenMessageReceived(_sourceChainId, _sourceAddress, info); } function decodeInfo( bytes memory _message ) public pure returns (Info memory info) { uint256 tokenAmount; // 256 / 8 = 32 address receiver; // 20 bytes assembly { tokenAmount := mload(add(_message, 32)) receiver := mload(add(_message, 52)) } info.tokenAmount = tokenAmount; info.receiver = receiver; } } ```