// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import { FxBaseChildTunnel } from '../../tunnel/FxBaseChildTunnel.sol'; import { Create2 } from '../../lib/Create2.sol'; import { IFxERC721 } from '../../tokens/IFxERC721.sol'; import { IERC721Receiver } from "../../lib/IERC721Receiver.sol"; /** * @title FxERC721ChildTunnel */ contract FxERC721ChildTunnel is FxBaseChildTunnel, Create2, IERC721Receiver { bytes32 public constant DEPOSIT = keccak256("DEPOSIT"); bytes32 public constant MAP_TOKEN = keccak256("MAP_TOKEN"); string public constant SUFFIX_NAME = " (FXERC721)"; string public constant PREFIX_SYMBOL = "fx"; // event for token maping event TokenMapped(address indexed rootToken, address indexed childToken); // root to child token mapping(address => address) public rootToChildToken; // token template address public tokenTemplate; constructor(address _fxChild, address _tokenTemplate) FxBaseChildTunnel(_fxChild) { tokenTemplate = _tokenTemplate; require(_isContract(_tokenTemplate), "Token template is not contract"); } function onERC721Received( address /* operator */, address /* from */, uint256 /* tokenId */, bytes calldata /* data */ ) external pure override returns (bytes4) { return this.onERC721Received.selector; } function withdraw(address childToken, uint256 tokenId, bytes memory data) external { IFxERC721 childTokenContract = IFxERC721(childToken); // child token contract will have root token address rootToken = childTokenContract.connectedToken(); // validate root and child token mapping require( childToken != address(0x0) && rootToken != address(0x0) && childToken == rootToChildToken[rootToken], "FxERC721ChildTunnel: NO_MAPPED_TOKEN" ); // withdraw tokens childTokenContract.burn(tokenId); // send message to root regarding token burn _sendMessageToRoot(abi.encode(rootToken, childToken, msg.sender, tokenId, data)); } // // Internal methods // function _processMessageFromRoot(uint256 /* stateId */, address sender, bytes memory data) internal override validateSender(sender) { // decode incoming data (bytes32 syncType, bytes memory syncData) = abi.decode(data, (bytes32, bytes)); if (syncType == DEPOSIT) { _syncDeposit(syncData); } else if (syncType == MAP_TOKEN) { _mapToken(syncData); } else { revert("FxERC721ChildTunnel: INVALID_SYNC_TYPE"); } } function _mapToken(bytes memory syncData) internal returns (address) { (address rootToken, string memory name, string memory symbol) = abi.decode(syncData, (address, string, string)); // get root to child token address childToken = rootToChildToken[rootToken]; // check if it's already mapped require(childToken == address(0x0), "FxERC721ChildTunnel: ALREADY_MAPPED"); // deploy new child token bytes32 salt = keccak256(abi.encodePacked(rootToken)); childToken = createClone(salt, tokenTemplate); IFxERC721(childToken).initialize(address(this), rootToken, string(abi.encodePacked(name, SUFFIX_NAME)), string(abi.encodePacked(PREFIX_SYMBOL, symbol))); // map the token rootToChildToken[rootToken] = childToken; emit TokenMapped(rootToken, childToken); // return new child token return childToken; } function _syncDeposit(bytes memory syncData) internal { (address rootToken, address depositor, address to, uint256 tokenId, bytes memory depositData) = abi.decode(syncData, (address, address, address, uint256, bytes)); address childToken = rootToChildToken[rootToken]; // deposit tokens IFxERC721 childTokenContract = IFxERC721(childToken); childTokenContract.mint(to, tokenId, depositData); } // check if address is contract function _isContract(address _addr) private view returns (bool){ uint32 size; assembly { size := extcodesize(_addr) } return (size > 0); } }