contract ProxyData {
    address internal proxied;
}

contract Proxy is ProxyData {
    constructor(address _proxied) public {
        proxied = _proxied;
}

function () external payable {
    address addr = proxied;
    assembly {
        let freememstart := mload(0x40)
        calldatacopy(freememstart, 0, calldatasize())
        let success := delegatecall(not(0), addr, freememstart, calldatasize(), freememstart, 0)
        returndatacopy(freememstart, 0, returndatasize())
        switch success
            case 0 { revert(freememstart, returndatasize()) }
            default { return(freememstart, returndatasize()) }
        }
    }
}

contract UniswapFactoryInterface {
    // Public Variables
    address public exchangeTemplate;
    uint256 public tokenCount;
    // Create Exchange
    function createExchange(address token) external returns (address exchange);
    // Get Exchange and Token Info
    function getExchange(address token) external view returns (address exchange);
    function getToken(address exchange) external view returns (address token);
    function getTokenWithId(uint256 tokenId) external view returns (address token);
    // Never use
    function initializeFactory(address template) external;
}
contract FundHeader {
    address constant internal cEth = address(0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5);
    UniswapFactoryInterface constant internal uniswapFactory = UniswapFactoryInterface(0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95);
}

contract FundDataInternal is ProxyData, FundHeader {
    address internal collateralOwner;
    address internal interestWithdrawer;
    // cTokenAddress -> sum deposits denominated in underlying
    mapping(address => uint) internal initialDepositCollateral;
    // cTokenAddresses
    address[] internal markets;
}

contract CERC20NoBorrowInterface {
    function mint(uint mintAmount) external returns (uint);
    address public underlying;
}

interface IERC20 {
    function transfer(address to, uint256 value) external returns (bool);
    function approve(address spender, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
    function totalSupply() external view returns (uint256);
    function balanceOf(address who) external view returns (uint256);
    function allowance(address owner, address spender) external view returns (uint256);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

contract FundProxy is Proxy, FundDataInternal {
    constructor(
        address proxied,
        address _collateralOwner,
        address _interestWithdrawer,
        address[] memory _markets
    )
    public
    Proxy(proxied)
    {
        for (uint i=0; i < _markets.length; i++) {
            markets.push(_markets[i]);
            if (_markets[i] == cEth) {
                continue;
            }
            address underlying = CERC20NoBorrowInterface(_markets[i]).underlying();
            require(IERC20(underlying).approve(_markets[i], uint(-1)));
            require(IERC20(underlying).approve(uniswapFactory.getExchange(underlying), uint(-1)));
        }
            collateralOwner = _collateralOwner;
            interestWithdrawer = _interestWithdrawer;
    }

}

contract FundFactory {
    address private implementation;
    event NewFundCreated(address indexed collateralOwner, address proxyAddress);

    constructor(address _implementation) public {
        implementation = _implementation;
    }

    function createFund(address _interestWithdrawer, address[] calldata _markets)
    external
    {
        emit NewFundCreated(
                msg.sender,
                address(new FundProxy(address(implementation), msg.sender, _interestWithdrawer, _markets))
            );
    }

}