contract DexProtect {

    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    // Constants and Default Tokens
    address uniswapV2Factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
    address uniswapV2Router = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
    address WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; //CHANGE TO MAINET WETH: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
    uint256 maxApproval = 115792089237316195423570985008687907853269984665640564039457584007913129639935;

    // Script Variables
    address payable public owner;
    IERC20 Token;
    IUniswapV2Router02 public uniswapRouter;
    IUniswapV2Factory public uniswapFactory;
    address[] depositers;
    address[] depositedTokens;
    uint256 newDeposit;
    uint256 newToken;
    address pairAddress;
    uint256 tradeBalance;
    uint256 amountTokenSold;
    uint256 pretradegasCost;
    uint256 gasCost;
    uint256 pretradeETHBalance;
    uint256 ETHpayout;

    constructor() public {
        uniswapRouter = IUniswapV2Router02(uniswapV2Router);
        uniswapFactory = IUniswapV2Factory(uniswapV2Factory);
        owner = msg.sender;
    }

    struct Balance {
        address[] tokendeposited;
        uint256[] amountdeposited;
        uint256 ethpayout;
    }

    mapping (address => Balance) balances;

    modifier _onlyOwner() {require(msg.sender == owner,'You dont have authorization to sell tokens, withdraw them instead');_;}

    // Deposit any ERC-20 traded on UniswapV2 into the DexProtect contract and specify amount
    function depositToken(address tokenaddress, uint256 amount) public {
        // Dont allow deposits of tokens that arent traded on uniswapv2 currently
        pairAddress = uniswapFactory.getPair(tokenaddress,WETH);
        require(pairAddress != address(0), "Pair is not traded with ETH on UniswapV2");
        // Dont allow deposits greater than current balance
        Token = IERC20(tokenaddress);
        require(Token.balanceOf(msg.sender) >= amount,'Exceeds your current wallet balance');
        // Store in struct
        Balance storage balance = balances[msg.sender];
        // If it's a new depositer, add address to the list
        if (balance.tokendeposited.length == 0) {
            depositers.push(msg.sender);
        }
        // If the token has already been deposited before, we just increase user balance
        newDeposit = 1 ;
        for (uint i = 0; i < balance.tokendeposited.length; i++) {
            if (balance.tokendeposited[i] == tokenaddress) {
                balance.amountdeposited[i] += amount;
                newDeposit = 0;
            }
        }
        // If this is the first time depositing this token, add it to the users balance
        if (newDeposit == 1) {
            balance.tokendeposited.push(tokenaddress);
            balance.amountdeposited.push(amount);
        }
        // transfer the tokens from the sender to this contract
        Token.safeTransferFrom(msg.sender, address(this), amount);
        // approve token for DexProtect contract to trade it if it hasn't been yet
        newToken = 1 ;
        for (uint j = 0; j < depositedTokens.length; j++) {
            if (depositedTokens[j] == tokenaddress) {
                newToken = 0;
            }
        }
        // If this is the first time this token has been sent we need to approve it and add it to list
        if (newToken == 1) {
            depositedTokens.push(tokenaddress);
            Token.approve(uniswapV2Router,maxApproval);
        }
    }

    // Withdraw ERC-20 Tokens from the contract (not ETH)
    function withdrawToken(address tokenaddress, uint256 amount) public {
        Token = IERC20(tokenaddress);
        // Update struct
        Balance storage balance = balances[msg.sender];
        // Withdraw appropriate token from the contract
        for (uint i = 0; i < balance.tokendeposited.length; i++) {
            if (balance.tokendeposited[i] == tokenaddress) {
                require(balance.amountdeposited[i] >= amount,'Exceeds your deposited amount, check ETH payout if sold');
                // If it is a withdrawal of all tokens left in the contract, get rid of it
                if (Token.balanceOf(address(this)) == balance.amountdeposited[i]) {
                    // Get rid of the token in the balance list
                    updateDepositedTokens(tokenaddress);
                }
                balance.amountdeposited[i] -= amount;
            }
        }
        Token.safeTransfer(msg.sender, amount);
    }

    // Get the way we will trade it
    function getTradePath(address tokenaddress) internal view returns (address[] memory) {
        address[] memory path = new address[](2);
        path[0] = tokenaddress;
        path[1] = WETH;
        return path;
    }

    // Trigger sell of certain token (CAN ONLY BE CALLED BY OWNER)
    function emergencySell(address tokenaddress) public _onlyOwner() payable {
        Token = IERC20(tokenaddress);
        require(Token.balanceOf(address(this)) > 0,'Contract doesnt hold this token anymore');
        tradeBalance = Token.balanceOf(address(this));
        pretradeETHBalance = address(this).balance;
        pretradegasCost = owner.balance;
        uniswapRouter.swapExactTokensForETHSupportingFeeOnTransferTokens(tradeBalance,0,getTradePath(tokenaddress),address(this),now+100);
        amountTokenSold = tradeBalance.sub(Token.balanceOf(address(this)));
        gasCost = pretradegasCost.sub(owner.balance);
        require(amountTokenSold > 0,'Token was not able to be sold');
        pretradeETHBalance += gasCost; // add gas cost to the pretrade eth balance
        ETHpayout = (((address(this).balance).sub(pretradeETHBalance)).mul(9)).div(10); // 90% is paid out to people who held the token, 10% to rug fund and devs as detailed in WP
        require(ETHpayout > 0,'Gas cost likely higher than liquidation value, didnt sell'); // if value gained from selling is less than gas dont bother (rare edge case)
        owner.transfer((((address(this).balance).sub(pretradeETHBalance)).mul(1)).div(10));
        updateDepositedTokens(tokenaddress);
        updateBalancesAfterTrade(tokenaddress,amountTokenSold,1-(amountTokenSold.div(tradeBalance)),ETHpayout);
    }

    // Delete token from deposited tokens list
    function updateDepositedTokens(address tokenaddress) internal {
        // Get rid of the token in the balance list
        for (uint d = 0; d < depositedTokens.length; d++) {
            if (depositedTokens[d] == tokenaddress) {
                delete depositedTokens[d];
            }
        }
    }

    // Update balances if a token was sold and increase reward by appropriate amount of ETH
    function updateBalancesAfterTrade(address tokenaddress, uint256 sold, uint256 portionleft, uint256 gainz) internal {
        // Find which owners held the token and adjust balances
        for (uint b = 0; b < depositers.length; b++) { // iterate through people that have sent tokens
            Balance storage balance = balances[depositers[b]];
            for (uint t = 0; t < balance.tokendeposited.length; t++) {
                if (balance.tokendeposited[t] == tokenaddress && balance.amountdeposited[t] > 0) {
                    balance.ethpayout += (gainz.mul(balance.amountdeposited[t])).div(sold); // increase eth payout balance by proportionate amount
                    balance.amountdeposited[t] = balance.amountdeposited[t] * (portionleft); // reduce token balance by proportionate amount
                }
            }
        }
    }

    // When users want to cash ETH out after tokens were sold
    function requestETHPayout() public {
        Balance storage balance = balances[msg.sender];
        require(balance.ethpayout > 0, 'You dont have enough ETH to cashout');
        msg.sender.transfer(balance.ethpayout);
        balance.ethpayout = 0;
    }

    // Get all token balances and amounts based on the user that called it
    function getMyBalances() view public returns (address[] memory, uint256[] memory, uint256) {
        return (balances[msg.sender].tokendeposited,balances[msg.sender].amountdeposited,balances[msg.sender].ethpayout);
    }

    // Get all token balances and amounts based on the user
    function getBalances(address useraddress) view public returns (address[] memory, uint256[] memory, uint256) {
        return (balances[useraddress].tokendeposited,balances[useraddress].amountdeposited,balances[useraddress].ethpayout);
    }

    // Get list of all currently deposited tokens in the contract
    function getDepositedTokens() view public returns (address[] memory) {
        return depositedTokens;
    }

    // Get list of all addresses who have ever deposited in the contract
    function getDepositers() view public returns (address[] memory) {
        return depositers;
    }

    // So contract can receive ETH after the uniswap trade
    receive() payable external {}

}
function getBalances(address useraddress) view public returns (address[] memory, uint256[] memory, uint256) {
    return (balances[useraddress].tokendeposited,balances[useraddress].amountdeposited,balances[useraddress].ethpayout);
}

// Get all token balances and amounts based on the user