contract FxMintableERC20RootTunnel is FxBaseRootTunnel, Create2 {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    // maybe DEPOSIT and MAP_TOKEN can be reduced to bytes4
    bytes32 public constant DEPOSIT = keccak256("DEPOSIT");
    //bytes32 public constant MAP_TOKEN = keccak256("MAP_TOKEN");

    mapping(address => address) public rootToChildTokens;
    address public rootTokenTemplate;
    bytes32 public childTokenTemplateCodeHash;

    constructor(
        address _checkpointManager,
        address _fxRoot,
        address _rootTokenTemplate
    ) FxBaseRootTunnel(_checkpointManager, _fxRoot) {
        rootTokenTemplate = _rootTokenTemplate;
    }

    function deposit(
        address rootToken,
        address user,
        uint256 amount,
        bytes memory data
    ) public {
        // map token if not mapped
        require(rootToChildTokens[rootToken] != address(0x0), "FxMintableERC20RootTunnel: NO_MAPPING_FOUND");

        // transfer from depositor to this contract
        IERC20(rootToken).safeTransferFrom(
            msg.sender, // depositor
            address(this), // manager contract
            amount
        );

        // DEPOSIT, encode(rootToken, depositor, user, amount, extra data)
        bytes memory message = abi.encode(DEPOSIT, abi.encode(rootToken, msg.sender, user, amount, data));
        _sendMessageToChild(message);
    }

    // exit processor
    function _processMessageFromChild(bytes memory data) internal override {
        (address rootToken, address childToken, address to, uint256 amount, bytes memory metaData) = abi.decode(
            data,
            (address, address, address, uint256, bytes)
        );

        // if root token is not available, create it
        if (!_isContract(rootToken) && rootToChildTokens[rootToken] == address(0x0)) {
            (string memory name, string memory symbol, uint8 decimals) = abi.decode(metaData, (string, string, uint8));

            address _createdToken = _deployRootToken(childToken, name, symbol, decimals);
            require(_createdToken == rootToken, "FxMintableERC20RootTunnel: ROOT_TOKEN_CREATION_MISMATCH");
        }

        // validate mapping for root to child
        require(rootToChildTokens[rootToken] == childToken, "FxERC20RootTunnel: INVALID_MAPPING_ON_EXIT");

        // check if current balance for token is less than amount,
        // mint remaining amount for this address
        FxERC20 tokenObj = FxERC20(rootToken);
        uint256 balanceOf = tokenObj.balanceOf(address(this));
        if (balanceOf < amount) {
            tokenObj.mint(address(this), amount.sub(balanceOf));
        }

        //approve token transfer
        tokenObj.approve(address(this), amount);

        // transfer from tokens
        IERC20(rootToken).safeTransferFrom(address(this), to, amount);
    }

    function _deployRootToken(
        address childToken,
        string memory name,
        string memory symbol,
        uint8 decimals
    ) internal returns (address) {
        // deploy new root token
        bytes32 salt = keccak256(abi.encodePacked(childToken));
        address rootToken = createClone(salt, rootTokenTemplate);
        FxERC20(rootToken).initialize(address(this), rootToken, name, symbol, decimals);

        // add into mapped tokens
        rootToChildTokens[rootToken] = childToken;

        return rootToken;
    }

    // check if address is contract
    function _isContract(address _addr) private view returns (bool) {
        uint32 size;
        assembly {
            size := extcodesize(_addr)
        }
        return (size > 0);
    }
}
/**
 * @title FxMintableERC20RootTunnel
 */
function _processMessageFromChild(bytes memory data) internal override {
    (address rootToken, address childToken, address to, uint256 amount, bytes memory metaData) = abi.decode(
        data,
        (address, address, address, uint256, bytes)
    );

    // if root token is not available, create it
    if (!_isContract(rootToken) && rootToChildTokens[rootToken] == address(0x0)) {
        (string memory name, string memory symbol, uint8 decimals) = abi.decode(metaData, (string, string, uint8));

        address _createdToken = _deployRootToken(childToken, name, symbol, decimals);
        require(_createdToken == rootToken, "FxMintableERC20RootTunnel: ROOT_TOKEN_CREATION_MISMATCH");
    }

    // validate mapping for root to child
    require(rootToChildTokens[rootToken] == childToken, "FxERC20RootTunnel: INVALID_MAPPING_ON_EXIT");

    // check if current balance for token is less than amount,
    // mint remaining amount for this address
    FxERC20 tokenObj = FxERC20(rootToken);
    uint256 balanceOf = tokenObj.balanceOf(address(this));
    if (balanceOf < amount) {
        tokenObj.mint(address(this), amount.sub(balanceOf));
    }

    //approve token transfer
    tokenObj.approve(address(this), amount);

    // transfer from tokens
    IERC20(rootToken).safeTransferFrom(address(this), to, amount);
}

// exit processor