// Contract: InstaPool
// Target function: function deposit(address token, uint amt) external payable returns (uint _amt)
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;


interface CTokenInterface {
    function mint(uint mintAmount) external returns (uint);
    function redeem(uint redeemTokens) external returns (uint);
    function repayBorrow(uint repayAmount) external returns (uint);

    function borrowBalanceCurrent(address) external returns (uint);
    function redeemUnderlying(uint) external returns (uint);
    function borrow(uint) external returns (uint);
    function underlying() external view returns (address);
    function borrowBalanceStored(address) external view returns (uint);
}

interface CETHInterface {
    function mint() external payable;
    function repayBorrow() external payable;
}

interface ComptrollerInterface {
    function getAssetsIn(address account) external view returns (address[] memory);
    function enterMarkets(address[] calldata cTokens) external returns (uint[] memory);
    function exitMarket(address cTokenAddress) external returns (uint);
}

interface AccountInterface {	
    function version() external view returns (uint);	
}

interface ListInterface {
    function accountID(address) external view returns (uint64);
}

interface IndexInterface {
    function master() external view returns (address);
    function list() external view returns (address);
    function isClone(uint, address) external view returns (bool);
}

interface TokenInterface {
    function allowance(address, address) external view returns (uint);
    function balanceOf(address) external view returns (uint);
    function approve(address, uint) external;
    function transfer(address, uint) external returns (bool);
    function transferFrom(address, address, uint) external returns (bool);
    function decimals() external returns (uint);
}

contract DSMath {
    function sub(uint x, uint y) internal pure returns (uint z) {
        require((z = x - y) <= x, "sub-overflow");
    }
}


contract Helpers is DSMath {

    address constant public instaIndex = 0x2971AdFa57b20E5a416aE5a708A8655A9c74f723;
    address constant public comptrollerAddr = 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B;

    address constant public ethAddr = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    address constant public cEth = 0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5;

    mapping (address => bool) public isTknAllowed;
    mapping (address => address) public tknToCTkn;

    /**
     * FOR SECURITY PURPOSE
     * only Smart DEFI Account can access the liquidity pool contract
     */
    modifier isDSA {
        IndexInterface indexContract = IndexInterface(instaIndex);
        uint64 id = ListInterface(indexContract.list()).accountID(msg.sender);
        require(id != 0, "not-dsa-id");
        require(indexContract.isClone(AccountInterface(msg.sender).version(), msg.sender), "not-dsa-clone");
        _;
    }

    function tokenBal(address token) internal view returns (uint _bal) {
        _bal = token == ethAddr ? address(this).balance : TokenInterface(token).balanceOf(address(this));
    }

    function _transfer(address token, uint _amt) internal {
         token == ethAddr ?
             msg.sender.transfer(_amt) :
            require(TokenInterface(token).transfer(msg.sender, _amt), "token-transfer-failed");
    }
}


contract CompoundResolver is Helpers {

    function borrowAndSend(address[] memory tokens, uint[] memory tknAmt) internal {
        if (tokens.length > 0) {
            for (uint i = 0; i < tokens.length; i++) {
                address token = tokens[i];
                address cToken = tknToCTkn[token];
                if (cToken != address(0) && tknAmt[i] > 0) {
                    require(CTokenInterface(cToken).borrow(tknAmt[i]) == 0, "borrow-failed");
                    _transfer(token, tknAmt[i]);
                }
            }
        }
    }

    function payback(address[] memory tokens) internal {
        if (tokens.length > 0) {
            for (uint i = 0; i < tokens.length; i++) {
                address token = tokens[i];
                address cToken = tknToCTkn[token];
                if (cToken != address(0)) {
                    CTokenInterface ctknContract = CTokenInterface(cToken);
                    token != ethAddr ?
                        require(ctknContract.repayBorrow(uint(-1)) == 0, "payback-failed") :
                        CETHInterface(cToken).repayBorrow.value(ctknContract.borrowBalanceCurrent(address(this)))();
                }
            }
        }
    }
}

contract AccessLiquidity is CompoundResolver {
    event LogPoolBorrow(address indexed user, address[] tknAddr, uint[] amt);
    event LogPoolPayback(address indexed user, address[] tknAddr);

    /**
     * @dev borrow tokens and use them on DSA.
     * @param tokens Array of tokens.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
     * @param amounts Array of tokens amount.
    */
    function accessLiquidity(address[] calldata tokens, uint[] calldata amounts) external isDSA {
        require(tokens.length == amounts.length, "length-not-equal");
        borrowAndSend(tokens, amounts);
        emit LogPoolBorrow(
            msg.sender,
            tokens,
            amounts
        );
    }
   
    /**
     * @dev Payback borrowed tokens.
     * @param tokens Array of tokens.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
    */
    function returnLiquidity(address[] calldata tokens) external payable isDSA {
        payback(tokens);
        emit LogPoolPayback(msg.sender, tokens);
    }
    
    /**
     * @dev Check if no tokens are borrowed.
    */
    function isOk() public view returns(bool ok) {
        ok = true;
        address[] memory markets = ComptrollerInterface(comptrollerAddr).getAssetsIn(address(this));
        for (uint i = 0; i < markets.length; i++) {
            uint tknBorrowed = CTokenInterface(markets[i]).borrowBalanceStored(address(this));
            if(tknBorrowed > 0){
                ok = false;
                break;
            }
        }
    }
}


contract ProvideLiquidity is  AccessLiquidity {
    event LogDeposit(address indexed user, address indexed token, uint amount, uint cAmount);
    event LogWithdraw(address indexed user, address indexed token, uint amount, uint cAmount);

    mapping (address => mapping (address => uint)) public liquidityBalance;