OmnichainFungibleToken.sol 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. // SPDX-License-Identifier: BUSL-1.1
  2. pragma solidity 0.7.6;
  3. import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
  4. import "@openzeppelin/contracts/access/Ownable.sol";
  5. import "./interfaces/ILayerZeroReceiver.sol";
  6. import "./interfaces/ILayerZeroEndpoint.sol";
  7. import "./interfaces/ILayerZeroUserApplicationConfig.sol";
  8. //---------------------------------------------------------------------------
  9. // THIS CONTRACT IS OF BUSINESS LICENSE. CONTACT US BEFORE YOU USE IT.
  10. //
  11. // LayerZero is pushing now a new cross-chain token standard with permissive license soon
  12. //
  13. // Stay tuned for maximum cross-chain compatability of your token
  14. //---------------------------------------------------------------------------
  15. contract OmnichainFungibleToken is ERC20, Ownable, ILayerZeroReceiver, ILayerZeroUserApplicationConfig {
  16. ILayerZeroEndpoint immutable public endpoint;
  17. mapping(uint16 => bytes) public dstContractLookup; // a map of the connected contracts
  18. bool public paused; // indicates cross chain transfers are paused
  19. bool public isMain; // indicates this contract is on the main chain
  20. event Paused(bool isPaused);
  21. event SendToChain(uint16 srcChainId, bytes toAddress, uint256 qty, uint64 nonce);
  22. event ReceiveFromChain(uint16 srcChainId, address toAddress, uint256 qty, uint64 nonce);
  23. constructor(
  24. string memory _name,
  25. string memory _symbol,
  26. address _endpoint,
  27. uint16 _mainChainId,
  28. uint256 _initialSupplyOnMainEndpoint
  29. ) ERC20(_name, _symbol) {
  30. // only mint the total supply on the main chain
  31. if (ILayerZeroEndpoint(_endpoint).getChainId() == _mainChainId) {
  32. _mint(msg.sender, _initialSupplyOnMainEndpoint);
  33. isMain = true;
  34. }
  35. // set the LayerZero endpoint
  36. endpoint = ILayerZeroEndpoint(_endpoint);
  37. }
  38. function pauseSendTokens(bool _pause) external onlyOwner {
  39. paused = _pause;
  40. emit Paused(_pause);
  41. }
  42. function setDestination(uint16 _dstChainId, bytes calldata _destinationContractAddress) public onlyOwner {
  43. dstContractLookup[_dstChainId] = _destinationContractAddress;
  44. }
  45. function chainId() external view returns (uint16){
  46. return endpoint.getChainId();
  47. }
  48. function sendTokens(
  49. uint16 _dstChainId, // send tokens to this chainId
  50. bytes calldata _to, // where to deliver the tokens on the destination chain
  51. uint256 _qty, // how many tokens to send
  52. address _zroPaymentAddress, // ZRO payment address
  53. bytes calldata _adapterParam // txParameters
  54. ) public payable {
  55. require(!paused, "OFT: sendTokens() is currently paused");
  56. // lock by transferring to this contract if leaving the main chain, otherwise burn
  57. if (isMain) {
  58. _transfer(msg.sender, address(this), _qty);
  59. } else {
  60. _burn(msg.sender, _qty);
  61. }
  62. // abi.encode() the payload with the values to send
  63. bytes memory payload = abi.encode(_to, _qty);
  64. // send LayerZero message
  65. endpoint.send{value: msg.value}(
  66. _dstChainId, // destination chainId
  67. dstContractLookup[_dstChainId], // destination UA address
  68. payload, // abi.encode()'ed bytes
  69. msg.sender, // refund address (LayerZero will refund any extra gas back to msg.sender
  70. _zroPaymentAddress, // 'zroPaymentAddress'
  71. _adapterParam // 'adapterParameters'
  72. );
  73. uint64 nonce = endpoint.getOutboundNonce(_dstChainId, address(this));
  74. emit SendToChain(_dstChainId, _to, _qty, nonce);
  75. }
  76. function lzReceive(
  77. uint16 _srcChainId,
  78. bytes memory _fromAddress,
  79. uint64 _nonce,
  80. bytes memory _payload
  81. ) external override {
  82. require(msg.sender == address(endpoint)); // lzReceive must only be called by the endpoint
  83. require(
  84. _fromAddress.length == dstContractLookup[_srcChainId].length && keccak256(_fromAddress) == keccak256(dstContractLookup[_srcChainId]),
  85. "OFT: invalid source sending contract"
  86. );
  87. // decode and load the toAddress
  88. (bytes memory _to, uint256 _qty) = abi.decode(_payload, (bytes, uint256));
  89. address toAddress;
  90. assembly { toAddress := mload(add(_to, 20)) }
  91. // if the toAddress is 0x0, burn it
  92. if (toAddress == address(0x0)) toAddress == address(0xdEaD);
  93. // on the main chain unlock via transfer, otherwise _mint
  94. if (isMain) {
  95. _transfer(address(this), toAddress, _qty);
  96. } else {
  97. _mint(toAddress, _qty);
  98. }
  99. emit ReceiveFromChain(_srcChainId, toAddress, _qty, _nonce);
  100. }
  101. function estimateSendTokensFee(uint16 _dstChainId, bytes calldata _toAddress, bool _useZro, bytes calldata _txParameters) external view returns (uint256 nativeFee, uint256 zroFee) {
  102. // mock the payload for sendTokens()
  103. bytes memory payload = abi.encode(_toAddress, 1);
  104. return endpoint.estimateFees(_dstChainId, address(this), payload, _useZro, _txParameters);
  105. }
  106. //---------------------------DAO CALL----------------------------------------
  107. // generic config for user Application
  108. function setConfig(
  109. uint16 _version,
  110. uint16 _chainId,
  111. uint256 _configType,
  112. bytes calldata _config
  113. ) external override onlyOwner {
  114. endpoint.setConfig(_version, _chainId, _configType, _config);
  115. }
  116. function setSendVersion(uint16 _version) external override onlyOwner {
  117. endpoint.setSendVersion(_version);
  118. }
  119. function setReceiveVersion(uint16 _version) external override onlyOwner {
  120. endpoint.setReceiveVersion(_version);
  121. }
  122. function forceResumeReceive(uint16 _srcChainId, bytes calldata _srcAddress) external override onlyOwner {
  123. endpoint.forceResumeReceive(_srcChainId, _srcAddress);
  124. }
  125. function renounceOwnership() public override onlyOwner {}
  126. }