// 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 { _withdraw(childToken, msg.sender, tokenId, data); } function withdrawTo( address childToken, address receiver, uint256 tokenId, bytes memory data ) external { _withdraw(childToken, receiver, 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); } function _withdraw( address childToken, address receiver, uint256 tokenId, bytes memory data ) internal { 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" ); require(msg.sender == childTokenContract.ownerOf(tokenId)); // withdraw tokens childTokenContract.burn(tokenId); // send message to root regarding token burn _sendMessageToRoot(abi.encode(rootToken, childToken, receiver, tokenId, data)); } // check if address is contract function _isContract(address _addr) private view returns (bool) { uint32 size; assembly { size := extcodesize(_addr) } return (size > 0); } }