# 三分钟跨链
第一步,先安装需要用到的库
```
$ 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;
}
}
```