// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {FxBaseChildTunnel} from "../../tunnel/FxBaseChildTunnel.sol"; import {Create2} from "../../lib/Create2.sol"; import {Ownable} from "../../lib/Ownable.sol"; import {FxERC20} from "../../tokens/FxERC20.sol"; /** * @title FxMintableERC20ChildTunnel */ contract FxMintableERC20ChildTunnel is Ownable, FxBaseChildTunnel, Create2 { bytes32 public constant DEPOSIT = keccak256("DEPOSIT"); //bytes32 public constant MAP_TOKEN = keccak256("MAP_TOKEN"); // event for token maping event TokenMapped(address indexed rootToken, address indexed childToken); // root to child token mapping(address => address) public rootToChildToken; // child token template address public childTokenTemplate; // root token tempalte code hash bytes32 public rootTokenTemplateCodeHash; constructor( address _fxChild, address _childTokenTemplate, address _rootTokenTemplate ) FxBaseChildTunnel(_fxChild) { childTokenTemplate = _childTokenTemplate; require(_isContract(_childTokenTemplate), "Token template is not contract"); // compute root token template code hash rootTokenTemplateCodeHash = keccak256(minimalProxyCreationCode(_rootTokenTemplate)); } // deploy child token with unique id function deployChildToken( uint256 uniqueId, string memory name, string memory symbol, uint8 decimals ) public onlyOwner returns (address) { // deploy new child token using unique id bytes32 childSalt = keccak256(abi.encodePacked(uniqueId)); address childToken = createClone(childSalt, childTokenTemplate); // compute root token address before deployment using create2 bytes32 rootSalt = keccak256(abi.encodePacked(childToken)); address rootToken = computedCreate2Address(rootSalt, rootTokenTemplateCodeHash, fxRootTunnel); // check if mapping is already there require(rootToChildToken[rootToken] == address(0x0), "FxMintableERC20ChildTunnel: ALREADY_MAPPED"); rootToChildToken[rootToken] = childToken; emit TokenMapped(rootToken, childToken); // initialize child token with all parameters FxERC20(childToken).initialize(address(this), rootToken, name, symbol, decimals); } //To mint tokens on child chain function mintToken(address childToken, uint256 amount) public onlyOwner { FxERC20 childTokenContract = FxERC20(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], "FxERC20ChildTunnel: NO_MAPPED_TOKEN" ); //mint token childTokenContract.mint(msg.sender, amount); } function withdraw(address childToken, uint256 amount) public { FxERC20 childTokenContract = FxERC20(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], "FxERC20ChildTunnel: NO_MAPPED_TOKEN" ); // withdraw tokens childTokenContract.burn(msg.sender, amount); // name, symbol and decimals FxERC20 rootTokenContract = FxERC20(childToken); string memory name = rootTokenContract.name(); string memory symbol = rootTokenContract.symbol(); uint8 decimals = rootTokenContract.decimals(); bytes memory metaData = abi.encode(name, symbol, decimals); // send message to root regarding token burn _sendMessageToRoot(abi.encode(rootToken, childToken, msg.sender, amount, metaData)); } // // Internal functions // 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 { revert("FxERC20ChildTunnel: INVALID_SYNC_TYPE"); } } function _syncDeposit(bytes memory syncData) internal { (address rootToken, address depositor, address to, uint256 amount, bytes memory depositData) = abi.decode( syncData, (address, address, address, uint256, bytes) ); address childToken = rootToChildToken[rootToken]; // deposit tokens FxERC20 childTokenContract = FxERC20(childToken); childTokenContract.mint(to, amount); // call `onTokenTranfer` on `to` with limit and ignore error if (_isContract(to)) { uint256 txGas = 2000000; bool success = false; bytes memory data = abi.encodeWithSignature( "onTokenTransfer(address,address,address,address,uint256,bytes)", rootToken, childToken, depositor, to, amount, depositData ); // solium-disable-next-line security/no-inline-assembly assembly { success := call(txGas, to, 0, add(data, 0x20), mload(data), 0, 0) } } } // check if address is contract function _isContract(address _addr) private view returns (bool) { uint32 size; assembly { size := extcodesize(_addr) } return (size > 0); } }