// File: soltsice/contracts/MultiOwnable.sol

pragma solidity ^0.4.18;

/*
 * A minimum multisig wallet interface. Compatible with MultiSigWallet by Gnosis.
 */
contract WalletBasic {
    function isOwner(address owner) public returns (bool);
}

/**
 * @dev MultiOwnable contract.
 */
contract MultiOwnable {
    
    WalletBasic public wallet;
    
    event MultiOwnableWalletSet(address indexed _contract, address indexed _wallet);

    function MultiOwnable 
        (address _wallet)
        public
    {
        wallet = WalletBasic(_wallet);
        MultiOwnableWalletSet(this, wallet);
    }

    /** Check if a caller is the MultiSig wallet. */
    modifier onlyWallet() {
        require(wallet == msg.sender);
        _;
    }

    /** Check if a caller is one of the current owners of the MultiSig wallet or the wallet itself. */
    modifier onlyOwner() {
        require (isOwner(msg.sender));
        _;
    }

    function isOwner(address _address) 
        public
        constant
        returns(bool)
    {
        // NB due to lazy eval wallet could be a normal address and isOwner won't be called if the first condition is met
        return wallet == _address || wallet.isOwner(_address);
    }


    /* PAUSABLE with upause callable only by wallet */ 

    bool public paused = false;

    event Pause();
    event Unpause();

    /**
    * @dev Modifier to make a function callable only when the contract is not paused.
    */
    modifier whenNotPaused() {
        require(!paused);
        _;
    }

    /**
    * @dev Modifier to make a function callable only when the contract is paused.
    */
    modifier whenPaused() {
        require(paused);
        _;
    }

    /**
    * @dev called by any MSW owner to pause, triggers stopped state
    */
    function pause() 
        onlyOwner
        whenNotPaused 
        public 
    {
        paused = true;
        Pause();
    }

    /**
    * @dev called by the MSW (all owners) to unpause, returns to normal state
    */
    function unpause() 
        onlyWallet
        whenPaused
        public
    {
        paused = false;
        Unpause();
    }
}

// File: soltsice/contracts/BotManageable.sol

pragma solidity ^0.4.18;


/**
 * @dev BotManaged contract provides a modifier isBot and methods to enable/disable bots.
 */
contract BotManageable is MultiOwnable {
    uint256 constant MASK64 = 18446744073709551615;

    // NB packing saves gas even in memory due to stack size
    // struct StartEndTimeLayout {
    //     uint64 startTime;
    //     uint64 endTime;
    // }

    /**
     * Bot addresses and their start/end times (two uint64 timestamps)
     */
    mapping (address => uint128) internal botsStartEndTime;

    event BotsStartEndTimeChange(address indexed _botAddress, uint64 _startTime, uint64 _endTime);

    function BotManageable 
        (address _wallet)
        public
        MultiOwnable(_wallet)
    { }

    /** Check if a caller is an active bot. */
    modifier onlyBot() {
        require (isBot(msg.sender));
        _;
    }

    /** Check if a caller is an active bot or an owner or the wallet. */
    modifier onlyBotOrOwner() {
        require (isBot(msg.sender) || isOwner(msg.sender));
        _;
    }

    /** Enable bot address. */
    function enableBot(address _botAddress)
        onlyWallet()
        public 
    {
        uint128 botLifetime = botsStartEndTime[_botAddress];
        // cannot re-enable existing bot
        require((botLifetime >> 64) == 0 && (botLifetime & MASK64) == 0);
        botLifetime |= uint128(now) << 64;
        botsStartEndTime[_botAddress] = botLifetime;
        BotsStartEndTimeChange(_botAddress, uint64(botLifetime >> 64), uint64(botLifetime & MASK64));
    }

    /** Disable bot address. */
    function disableBot(address _botAddress, uint64 _fromTimeStampSeconds)
        onlyOwner()
        public 
    {
        uint128 botLifetime = botsStartEndTime[_botAddress];
        // bot must have been enabled previously and not disabled before
        require((botLifetime >> 64) > 0 && (botLifetime & MASK64) == 0);
        botLifetime |= uint128(_fromTimeStampSeconds);
        botsStartEndTime[_botAddress] = botLifetime;
        BotsStartEndTimeChange(_botAddress, uint64(botLifetime >> 64), uint64(botLifetime & MASK64));
    }

    /** Operational contracts call this method to check if a caller is an approved bot. */
    function isBot(address _botAddress) 
        public
        constant
        returns(bool)
    {
        return isBotAt(_botAddress, uint64(now));
    }

    // truffle-contract doesn't like method overloading, use a different name

    function isBotAt(address _botAddress, uint64 _atTimeStampSeconds) 
        public
        constant 
        returns(bool)
    {
        uint128 botLifetime = botsStartEndTime[_botAddress];
        if ((botLifetime >> 64) == 0 || (botLifetime >> 64) > _atTimeStampSeconds) {
            return false;
        }
        if ((botLifetime & MASK64) == 0) {
            return true;
        }
        if (_atTimeStampSeconds < (botLifetime & MASK64)) {
            return true;
        }
        return false;
    }
}

// File: zeppelin-solidity/contracts/math/SafeMath.sol

pragma solidity ^0.4.24;


/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {

  /**
  * @dev Multiplies two numbers, throws on overflow.
  */
  function mul(uint256 _a, uint256 _b) internal pure returns (uint256 c) {
    // Gas optimization: this is cheaper than asserting 'a' not being zero, but the
    // benefit is lost if 'b' is also tested.
    // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
    if (_a == 0) {
      return 0;
    }

    c = _a * _b;
    assert(c / _a == _b);
    return c;
  }

  /**
  * @dev Integer division of two numbers, truncating the quotient.
  */
  function div(uint256 _a, uint256 _b) internal pure returns (uint256) {
    // assert(_b > 0); // Solidity automatically throws when dividing by 0
    // uint256 c = _a / _b;
    // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold
    return _a / _b;
  }

  /**
  * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
  */
  function sub(uint256 _a, uint256 _b) internal pure returns (uint256) {
    assert(_b <= _a);
    return _a - _b;
  }

  /**
  * @dev Adds two numbers, throws on overflow.
  */
  function add(uint256 _a, uint256 _b) internal pure returns (uint256 c) {
    c = _a + _b;
    assert(c >= _a);
    return c;
  }
}

// File: contracts/Auction.sol

pragma solidity ^0.4.18;



contract ERC20Basic {
    function totalSupply() public view returns (uint256);
    function transferFrom(address from, address to, uint256 value) public returns (bool);
    function transfer(address to, uint256 value) public returns (bool);
}

contract AuctionHub is BotManageable {
    using SafeMath for uint256;

    /*
     *  Data structures
     */
    
    struct TokenBalance {
        address token;
        uint256 value;
    }

    struct TokenRate {
        uint256 value;
        uint256 decimals;
    }

    struct BidderState {
        uint256 etherBalance;
        uint256 tokensBalanceInEther;
        //uint256 managedBid;
        TokenBalance[] tokenBalances;        
        uint256 etherBalanceInUsd; // (decimals = 2)
        uint256 tokensBalanceInUsd; // (decimals = 2)
        //uint256 managedBidInUsd;
    }

    struct ActionState {
        uint256 endSeconds; // end time in Unix seconds, 1514160000 for Dec 25, 2017 (need to double check!)
        uint256 maxTokenBidInEther; // максимальная ставка токенов в эфире. Думаю убрать это ограничение.
        uint256 minPrice; // минимальная цена лота в WEI
        
        uint256 highestBid; 
        
        // next 5 fields should be packed into one 32-bytes slot
        address highestBidder;
        //uint64 highestManagedBidder;
        //bool allowManagedBids;
        bool cancelled;
        bool finalized;        

        uint256 maxTokenBidInUsd; // max token bid in usd (decimals = 2)
        uint256 highestBidInUsd; // highest bid in usd (decimals = 2)
        address highestBidderInUsd; // highest bidder address in usd (decimals = 2)
        //uint64 highestManagedBidderInUsd; // highest manage bid in usd

        mapping(address => BidderState) bidderStates;

        bytes32 item;       
    }

    /*
     *  Storage
     */
    mapping(address => ActionState) public auctionStates;
    mapping(address => TokenRate) public tokenRates;    
    // ether rate in usd
    uint256 public etherRate;

    /*
     *  Events
     */

    event NewAction(address indexed auction, string item);
    event Bid(address indexed auction, address bidder, uint256 totalBidInEther, uint256 indexed tokensBidInEther, uint256 totalBidInUsd, uint256 indexed tokensBidInUsd);
    event TokenBid(address indexed auction, address bidder, address token, uint256 numberOfTokens);
    //event ManagedBid(address indexed auction, uint64 bidder, uint256 bid, address knownManagedBidder);
    //event NewHighestBidder(address indexed auction, address bidder, uint64 managedBidder, uint256 totalBid);
    event NewHighestBidder(address indexed auction, address bidder, uint256 totalBid);
    //event NewHighestBidderInUsd(address indexed auction, address bidder, uint64 managedBidderInUsd, uint256 totalBidInUsd);
    event NewHighestBidderInUsd(address indexed auction, address bidder, uint256 totalBidInUsd);
    event TokenRateUpdate(address indexed token, uint256 rate);
    event EtherRateUpdate(uint256 rate); // in usdt
    event Withdrawal(address indexed auction, address bidder, uint256 etherAmount, uint256 tokensBidInEther);
    event Charity(address indexed auction, address bidder, uint256 etherAmount, uint256 tokensAmount); // not used
    //event Finalized(address indexed auction, address highestBidder, uint64 highestManagedBidder, uint256 amount);
    event Finalized(address indexed auction, address highestBidder, uint256 amount);
    event FinalizedInUsd(address indexed auction, address highestBidderInUsd, uint256 amount);
    event FinalizedTokenTransfer(address indexed auction, address token, uint256 tokensBidInEther);
    event FinalizedEtherTransfer(address indexed auction, uint256 etherAmount);
    event ExtendedEndTime(address indexed auction, uint256 newEndtime);
    event Cancelled(address indexed auction);

    /*
     *  Modifiers
     */

    modifier onlyActive {
        // NB this modifier also serves as check that an auction exists (otherwise endSeconds == 0)
        ActionState storage auctionState = auctionStates[msg.sender];
        require (now < auctionState.endSeconds && !auctionState.cancelled);
        _;
    }

    modifier onlyBeforeEnd {
        // NB this modifier also serves as check that an auction exists (otherwise endSeconds == 0)
        ActionState storage auctionState = auctionStates[msg.sender];
        require (now < auctionState.endSeconds);
        _;
    }

    modifier onlyAfterEnd {
        ActionState storage auctionState = auctionStates[msg.sender];
        require (now > auctionState.endSeconds && auctionState.endSeconds > 0);
        _;
    }

    modifier onlyNotCancelled {
        ActionState storage auctionState = auctionStates[msg.sender];
        require (!auctionState.cancelled);
        _;
    }

    /*modifier onlyAllowedManagedBids {
        ActionState storage auctionState = auctionStates[msg.sender];
        require (auctionState.allowManagedBids);
        _;
    }*/

    /*
     * _rates are per big token (e.g. Ether vs. wei), i.e. number of wei per [number of tokens]*[10 ** decimals]
     */
    function AuctionHub 
        (address _wallet, address[] _tokens, uint256[] _rates, uint256[] _decimals, uint256 _etherRate)
        public
        BotManageable(_wallet)
    {
        // make sender a bot to avoid an additional step
        botsStartEndTime[msg.sender] = uint128(now) << 64;

        require(_tokens.length == _rates.length);
        require(_tokens.length == _decimals.length);

        // save initial token list
        for (uint i = 0; i < _tokens.length; i++) {
            require(_tokens[i] != 0x0);
            require(_rates[i] > 0);
            ERC20Basic token = ERC20Basic(_tokens[i]);
            tokenRates[token] = TokenRate(_rates[i], _decimals[i]);
            emit TokenRateUpdate(token, _rates[i]);
        }

        // save ether rate in usd
        require(_etherRate > 0);
        etherRate = _etherRate;
        emit EtherRateUpdate(_etherRate);
    }

    function stringToBytes32(string memory source) returns (bytes32 result) {
        bytes memory tempEmptyStringTest = bytes(source);
        if (tempEmptyStringTest.length == 0) {
            return 0x0;
        }

        assembly {
            result := mload(add(source, 32))
        }
    }

    function createAuction(
        uint _endSeconds, 
        uint256 _maxTokenBidInEther,
        uint256 _minPrice,
        string _item
        //bool _allowManagedBids
    )
        onlyBot
        public
        returns (address)
    {
        require (_endSeconds > now);
        require(_maxTokenBidInEther <= 1000 ether);
        require(_minPrice > 0);

        Auction auction = new Auction(this);

        ActionState storage auctionState = auctionStates[auction];

        auctionState.endSeconds = _endSeconds;
        auctionState.maxTokenBidInEther = _maxTokenBidInEther;
        // пока не используется в коде
        auctionState.maxTokenBidInUsd = _maxTokenBidInEther.mul(etherRate).div(10 ** 2);
        auctionState.minPrice = _minPrice;
        //auctionState.allowManagedBids = _allowManagedBids;
        string memory item = _item;
        auctionState.item = stringToBytes32(item);

        emit NewAction(auction, _item);
        return address(auction);
    }

    function () 
        payable
        public
    {
        throw;
        // It's charity!
        // require(wallet.send(msg.value));
        // Charity(0x0, msg.sender, msg.value, 0);
    }

    function bid(address _bidder, uint256 _value, address _token, uint256 _tokensNumber)
        // onlyActive - inline check to reuse auctionState variable
        public
        returns (bool isHighest, bool isHighestInUsd)
    {
        ActionState storage auctionState = auctionStates[msg.sender];
        // same as onlyActive modifier, but we already have a variable here
        require (now < auctionState.endSeconds && !auctionState.cancelled);

        BidderState storage bidderState = auctionState.bidderStates[_bidder];
        
        uint256 totalBid;
        uint256 totalBidInUsd;

        if (_tokensNumber > 0) {
            (totalBid, totalBidInUsd) = tokenBid(msg.sender, _bidder,  _token, _tokensNumber);
        }else {
            require(_value > 0);

            // NB if current token bid == 0 we still could have previous token bids
            (totalBid, totalBidInUsd) = (bidderState.tokensBalanceInEther, bidderState.tokensBalanceInUsd);
        }

        uint256 etherBid = bidderState.etherBalance + _value;
        // error "CompilerError: Stack too deep, try removing local variables"
        
        bidderState.etherBalance = etherBid;      

        //totalBid = totalBid + etherBid + bidderState.managedBid;
        totalBid = totalBid + etherBid;
        //totalBidInUsd = totalBidInUsd + etherBidInUsd + bidderState.managedBidInUsd;
        

        if (totalBid > auctionState.highestBid && totalBid >= auctionState.minPrice) {
            auctionState.highestBid = totalBid;
            auctionState.highestBidder = _bidder;
            //auctionState.highestManagedBidder = 0;
            emit NewHighestBidder(msg.sender, _bidder, totalBid);
            if ((auctionState.endSeconds - now) < 1800) {
                /*uint256 newEnd = now + 1800;
                auctionState.endSeconds = newEnd;
                ExtendedEndTime(msg.sender, newEnd);*/
                //uint256 newEnd = now + 1800;
                // убираем увеличение времени аукциона на 30 мин. при высокой ставки в Ether
                /*auctionState.endSeconds = now + 1800;
                ExtendedEndTime(msg.sender, auctionState.endSeconds);*/
            }
            isHighest = true;
        }

        /*    
        uint256 etherBidInUsd = bidderState.etherBalanceInUsd + _value.mul(etherRate);
        bidderState.etherBalanceInUsd = etherBidInUsd;
        totalBidInUsd = totalBidInUsd + etherBidInUsd;*/
        uint256 etherBidInUsd = bidderState.etherBalanceInUsd + _value.mul(etherRate).div(10 ** 2);
        bidderState.etherBalanceInUsd = etherBidInUsd;
        totalBidInUsd = totalBidInUsd + etherBidInUsd;

        if (totalBidInUsd > auctionState.highestBidInUsd && totalBidInUsd >= auctionState.minPrice.mul(etherRate).div(10 ** 2)) {
            auctionState.highestBidInUsd = totalBidInUsd;
            auctionState.highestBidderInUsd = _bidder;
            //auctionState.highestManagedBidderInUsd = 0;
            emit NewHighestBidderInUsd(msg.sender, _bidder, totalBidInUsd);
            if ((auctionState.endSeconds - now) < 1800) {
                //uint256 newEndUsd = now + 1800;
                //auctionState.endSeconds = newEndUsd;
                //ExtendedEndTime(msg.sender, newEndUsd);
                //uint256 newEndUsd = now + 1800;
                auctionState.endSeconds = now + 1800;
                emit ExtendedEndTime(msg.sender, auctionState.endSeconds);
            }
            isHighestInUsd = true;
        }

        emit Bid(msg.sender, _bidder, totalBid, totalBid - etherBid, totalBidInUsd, totalBidInUsd - etherBidInUsd);        

        return (isHighest, isHighestInUsd);
    }

    function tokenBid(address _auction, address _bidder, address _token, uint256 _tokensNumber)
        internal
        returns (uint256 tokenBid, uint256 tokenBidInUsd)
    {
        // NB actual token transfer happens in auction contracts, which owns both ether and tokens
        // This Hub contract is for accounting

        ActionState storage auctionState = auctionStates[_auction];
        BidderState storage bidderState = auctionState.bidderStates[_bidder];
        
        uint256 totalBid = bidderState.tokensBalanceInEther;
        uint256 totalBidInUsd = bidderState.tokensBalanceInUsd;

        TokenRate storage tokenRate = tokenRates[_token];
        require(tokenRate.value > 0);

        // find token index
        uint256 index = bidderState.tokenBalances.length;
        for (uint i = 0; i < index; i++) {
            if (bidderState.tokenBalances[i].token == _token) {
                index = i;
                break;
            }
        }

        // array was empty/token not found - push empty to the end
        if (index == bidderState.tokenBalances.length) {
            bidderState.tokenBalances.push(TokenBalance(_token, _tokensNumber));
        } else {
            // safe math is already in transferFrom
            bidderState.tokenBalances[index].value += _tokensNumber;
        }
        
        //totalBid = totalBid + _tokensNumber.mul(tokenRate.value).div(10 ** tokenRate.decimals);
        
        totalBid = calcTokenTotalBid(totalBid, _token, _tokensNumber);
        //totalBidInUsd = totalBidInUsd + _tokensNumber.mul(tokenRate.value).mul(etherRate).div(10 ** 2).div(10 ** tokenRate.decimals);
        
        totalBidInUsd = calcTokenTotalBidInUsd(totalBidInUsd, _token, _tokensNumber);

        // !Note! зачем тут ограничивать макс ставку токена эфиром
        //require(totalBid <= auctionState.maxTokenBidInEther);

        bidderState.tokensBalanceInEther = totalBid;
        bidderState.tokensBalanceInUsd = totalBidInUsd;

        //TokenBid(_auction, _bidder, _token, _tokensNumber);
        //emit TokenBid(_auction, _bidder, _token, _tokensNumber, _tokensNumber.mul(tokenRate.value).div(10 ** tokenRate.decimals), _tokensNumber.mul(tokenRate.value).mul(etherRate).div(10 ** 2).div(10 ** tokenRate.decimals));
        emit TokenBid(_auction, _bidder, _token, _tokensNumber);
        return (totalBid, totalBidInUsd);
    }

    function calcTokenTotalBid(uint256 totalBid, address _token, uint256 _tokensNumber)
        internal
        //returns(uint256 _totalBid, uint256 _bidInEther){
        returns(uint256 _totalBid){
            TokenRate storage tokenRate = tokenRates[_token];
            // tokenRate.value is for a whole/big token (e.g. ether vs. wei) but _tokensNumber is in small/wei tokens, need to divide by decimals
            uint256 bidInEther = _tokensNumber.mul(tokenRate.value).div(10 ** tokenRate.decimals);
            //totalBid = totalBid + _tokensNumber.mul(tokenRate.value).div(10 ** tokenRate.decimals);
            totalBid += bidInEther;
            //return (totalBid, bidInEther);
            return totalBid;
        }
    
    function calcTokenTotalBidInUsd(uint256 totalBidInUsd, address _token, uint256 _tokensNumber)
        internal
        returns(uint256 _totalBidInUsd){
            TokenRate storage tokenRate = tokenRates[_token];
            uint256 bidInUsd = _tokensNumber.mul(tokenRate.value).mul(etherRate).div(10 ** 2).div(10 ** tokenRate.decimals);
            //totalBidInUsd = totalBidInUsd + _tokensNumber.mul(tokenRate.value).mul(etherRate).div(10 ** 2).div(10 ** tokenRate.decimals);
            totalBidInUsd += bidInUsd;
            return totalBidInUsd;
        }
   
    function totalDirectBid(address _auction, address _bidder)
        view
        public
        returns (uint256 _totalBid)
    {
        ActionState storage auctionState = auctionStates[_auction];
        BidderState storage bidderState = auctionState.bidderStates[_bidder];
        return bidderState.tokensBalanceInEther + bidderState.etherBalance;
    }

    function totalDirectBidInUsd(address _auction, address _bidder)
        view
        public
        returns (uint256 _totalBidInUsd)
    {
        ActionState storage auctionState = auctionStates[_auction];
        BidderState storage bidderState = auctionState.bidderStates[_bidder];
        return bidderState.tokensBalanceInUsd + bidderState.etherBalanceInUsd;
    }

    function setTokenRate(address _token, uint256 _tokenRate)
        onlyBot
        public
    {
        TokenRate storage tokenRate = tokenRates[_token];
        require(tokenRate.value > 0);
        tokenRate.value = _tokenRate;
        emit TokenRateUpdate(_token, _tokenRate);
    }

    function setEtherRate(uint256 _etherRate)
        onlyBot
        public
    {        
        require(_etherRate > 0);
        etherRate = _etherRate;
        emit EtherRateUpdate(_etherRate);
    }

    function withdraw(address _bidder)
        public
        returns (bool success)
    {
        ActionState storage auctionState = auctionStates[msg.sender];
        BidderState storage bidderState = auctionState.bidderStates[_bidder];

        bool sent; 

        // anyone could withdraw at any time except the highest bidder
        // if cancelled, the highest bidder could withdraw as well
        //require((_bidder != auctionState.highestBidder) || auctionState.cancelled);
        require((_bidder != auctionState.highestBidderInUsd) || auctionState.cancelled);
        uint256 tokensBalanceInEther = bidderState.tokensBalanceInEther;
        uint256 tokensBalanceInUsd = bidderState.tokensBalanceInUsd;
        if (bidderState.tokenBalances.length > 0) {
            for (uint i = 0; i < bidderState.tokenBalances.length; i++) {
                uint256 tokenBidValue = bidderState.tokenBalances[i].value;
                if (tokenBidValue > 0) {
                    bidderState.tokenBalances[i].value = 0;
                    sent = Auction(msg.sender).sendTokens(bidderState.tokenBalances[i].token, _bidder, tokenBidValue);
                    require(sent);
                }
            }
            bidderState.tokensBalanceInEther = 0;
            bidderState.tokensBalanceInUsd = 0;
        } else {
            require(tokensBalanceInEther == 0);
        }

        uint256 etherBid = bidderState.etherBalance;
        if (etherBid > 0) {
            bidderState.etherBalance = 0;
            bidderState.etherBalanceInUsd = 0;
            sent = Auction(msg.sender).sendEther(_bidder, etherBid);
            require(sent);
        }

        emit Withdrawal(msg.sender, _bidder, etherBid, tokensBalanceInEther);
        
        return true;
    }

    function finalize()
        // onlyNotCancelled - inline check to reuse auctionState variable
        // onlyAfterEnd - inline check to reuse auctionState variable
        public
        returns (bool)
    {
        ActionState storage auctionState = auctionStates[msg.sender];
        // same as onlyNotCancelled+onlyAfterEnd modifiers, but we already have a variable here
        require (!auctionState.finalized && now > auctionState.endSeconds && auctionState.endSeconds > 0 && !auctionState.cancelled);

        // если есть хоть одна ставка
        if (auctionState.highestBidder != address(0)) {
            bool sent; 
            BidderState storage bidderState = auctionState.bidderStates[auctionState.highestBidder];
            uint256 tokensBalanceInEther = bidderState.tokensBalanceInEther;
            uint256 tokensBalanceInUsd = bidderState.tokensBalanceInUsd;
            if (bidderState.tokenBalances.length > 0) {
                for (uint i = 0; i < bidderState.tokenBalances.length; i++) {
                    uint256 tokenBid = bidderState.tokenBalances[i].value;
                    if (tokenBid > 0) {
                        bidderState.tokenBalances[i].value = 0;
                        sent = Auction(msg.sender).sendTokens(bidderState.tokenBalances[i].token, wallet, tokenBid);
                        require(sent);
                        emit FinalizedTokenTransfer(msg.sender, bidderState.tokenBalances[i].token, tokenBid);
                    }
                }
                bidderState.tokensBalanceInEther = 0;
                bidderState.tokensBalanceInUsd = 0;
            } else {
                require(tokensBalanceInEther == 0);
            }
            
            uint256 etherBid = bidderState.etherBalance;
            if (etherBid > 0) {
                bidderState.etherBalance = 0;
                bidderState.etherBalanceInUsd = 0;
                sent = Auction(msg.sender).sendEther(wallet, etherBid);
                require(sent);
                emit FinalizedEtherTransfer(msg.sender, etherBid);
            }
        }

        auctionState.finalized = true;
        emit Finalized(msg.sender, auctionState.highestBidder, auctionState.highestBid);
        emit FinalizedInUsd(msg.sender, auctionState.highestBidderInUsd, auctionState.highestBidInUsd);

        return true;
    }

    function cancel()
        // onlyActive - inline check to reuse auctionState variable
        public
        returns (bool success)
    {
        ActionState storage auctionState = auctionStates[msg.sender];
        // same as onlyActive modifier, but we already have a variable here
        require (now < auctionState.endSeconds && !auctionState.cancelled);

        auctionState.cancelled = true;
        emit Cancelled(msg.sender);
        return true;
    }

}


contract Auction {

    AuctionHub public owner;

    modifier onlyOwner {
        require(owner == msg.sender);
        _;
    }

    modifier onlyBot {
        require(owner.isBot(msg.sender));
        _;
    }

    modifier onlyNotBot {
        require(!owner.isBot(msg.sender));
        _;
    }

    function Auction(
        address _owner
    ) 
        public 
    {
        require(_owner != address(0x0));
        owner = AuctionHub(_owner);
    }

    function () 
        payable
        public
    {
        owner.bid(msg.sender, msg.value, 0x0, 0);
    }

    function bid(address _token, uint256 _tokensNumber)
        payable
        public
        returns (bool isHighest, bool isHighestInUsd)
    {
        if (_token != 0x0 && _tokensNumber > 0) {
            require(ERC20Basic(_token).transferFrom(msg.sender, this, _tokensNumber));
        }
        return owner.bid(msg.sender, msg.value, _token, _tokensNumber);
    }   

    function sendTokens(address _token, address _to, uint256 _amount)
        onlyOwner
        public
        returns (bool)
    {
        return ERC20Basic(_token).transfer(_to, _amount);
    }

    function sendEther(address _to, uint256 _amount)
        onlyOwner
        public
        returns (bool)
    {
        return _to.send(_amount);
    }

    function withdraw()
        public
        returns (bool success)
    {
        return owner.withdraw(msg.sender);
    }

    function finalize()
        onlyBot
        public
        returns (bool)
    {
        return owner.finalize();
    }

    function cancel()
        onlyBot
        public
        returns (bool success)
    {
        return  owner.cancel();
    }

    function totalDirectBid(address _bidder)
        public
        view
        returns (uint256)
    {
        return owner.totalDirectBid(this, _bidder);
    }

    function totalDirectBidInUsd(address _bidder)
        public
        view
        returns (uint256)
    {
        return owner.totalDirectBidInUsd(this, _bidder);
    }

    function maxTokenBidInEther()
        public
        view
        returns (uint256)
    {
        //var (,maxTokenBidInEther,,,,,,,,,,,) = owner.auctionStates(this);
        //var (endSeconds,maxTokenBidInEther,minPrice,highestBid,highestBidder,allowManagedBids,cancelled,finalized,maxTokenBidInUsd,highestBidInUsd,highestBidderInUsd,bidderStates) = owner.auctionStates(this);
        //(endSeconds,maxTokenBidInEther,minPrice,highestBid,highestBidder,cancelled,finalized,maxTokenBidInUsd,highestBidInUsd,highestBidderInUsd,item) = owner.auctionStates(this);
        var (,maxTokenBidInEther,,,,,,,,,) = owner.auctionStates(this);
        return maxTokenBidInEther;
    }

    function maxTokenBidInUsd()
        public
        view
        returns (uint256)
    {
        //var (,,,,,,,,,,maxTokenBidInUsd,,) = owner.auctionStates(this);
        var (endSeconds,maxTokenBidInEther,minPrice,highestBid,highestBidder,cancelled,finalized,maxTokenBidInUsd,highestBidInUsd,highestBidderInUsd,item) = owner.auctionStates(this);
        return maxTokenBidInUsd;
    }

    function endSeconds()
        public
        view
        returns (uint256)
    {
        //var (endSeconds,,,,,,,,,) = owner.auctionStates(this);
        //(endSeconds,maxTokenBidInEther,minPrice,highestBid,highestBidder,cancelled,finalized,maxTokenBidInUsd,highestBidInUsd,highestBidderInUsd,item) = owner.auctionStates(this);
        var (endSeconds,,,,,,,,,,) = owner.auctionStates(this);
        return endSeconds;
    }

    function item()
        public
        view
        returns (string)
    {
        var (endSeconds,maxTokenBidInEther,minPrice,highestBid,highestBidder,cancelled,finalized,maxTokenBidInUsd,highestBidInUsd,highestBidderInUsd,item) = owner.auctionStates(this);
        //var (endSeconds,maxTokenBidInEther,minPrice,highestBid,highestBidder,allowManagedBids,cancelled,finalized,maxTokenBidInUsd,highestBidInUsd,highestBidderInUsd,bidderStates,item) = owner.auctionStates(this);
        bytes memory bytesArray = new bytes(32);
        for (uint256 i; i < 32; i++) {
            bytesArray[i] = item[i];
            }
        return string(bytesArray);
    }

    function minPrice()
        public
        view
        returns (uint256)
    {
        //var (,,minPrice,,,,,,,) = owner.auctionStates(this);
        var (endSeconds,maxTokenBidInEther,minPrice,highestBid,highestBidder,cancelled,finalized,maxTokenBidInUsd,highestBidInUsd,highestBidderInUsd,item) = owner.auctionStates(this);
        return minPrice;
    }

    function cancelled()
        public
        view
        returns (bool)
    {
        //var (,,,,,,cancelled,,,) = owner.auctionStates(this);
        var (endSeconds,maxTokenBidInEther,minPrice,highestBid,highestBidder,cancelled,finalized,maxTokenBidInUsd,highestBidInUsd,highestBidderInUsd,item) = owner.auctionStates(this);
        return cancelled;
    }

    function finalized()
        public
        view
        returns (bool)
    {
        //var (,,,,,,,finalized,,) = owner.auctionStates(this);
        var (endSeconds,maxTokenBidInEther,minPrice,highestBid,highestBidder,cancelled,finalized,maxTokenBidInUsd,highestBidInUsd,highestBidderInUsd,item) = owner.auctionStates(this);
        return finalized;
    }

    function highestBid()
        public
        view
        returns (uint256)
    {
        //var (,,,highestBid,,,,,,,,,) = owner.auctionStates(this);
        var (endSeconds,maxTokenBidInEther,minPrice,highestBid,highestBidder,cancelled,finalized,maxTokenBidInUsd,highestBidInUsd,highestBidderInUsd,item) = owner.auctionStates(this);
        // ,,,,,,,,,,,,
        // ,,,highestBid,,,,,,,,,
        return highestBid;
    }

    function highestBidInUsd()
        public
        view
        returns (uint256)
    {
        //var (,,,,,,,,,,,highestBidInUsd,) = owner.auctionStates(this);
        var (endSeconds,maxTokenBidInEther,minPrice,highestBid,highestBidder,cancelled,finalized,maxTokenBidInUsd,highestBidInUsd,highestBidderInUsd,item) = owner.auctionStates(this);
        // ,,,,,,,,,,,,
        return highestBidInUsd;
    }

    function highestBidder()
        public
        view
        returns (address)
    {
        //var (,,,,highestBidder,,,,,) = owner.auctionStates(this);
        //var (,,,,highestBidder,,,,,,,,) = owner.auctionStates(this);
        var (endSeconds,maxTokenBidInEther,minPrice,highestBid,highestBidder,cancelled,finalized,maxTokenBidInUsd,highestBidInUsd,highestBidderInUsd,item) = owner.auctionStates(this);
        // ,,,,highestBidder,,,,,,,,
        return highestBidder;
    }

    
    function highestBidderInUsd()
        public
        view
        returns (address)
    {
        //var (,,,,highestBidder,,,,,) = owner.auctionStates(this);
        //var (,,,,,,,,,,,,highestBidderInUsd) = owner.auctionStates(this);
        var (endSeconds,maxTokenBidInEther,minPrice,highestBid,highestBidder,cancelled,finalized,maxTokenBidInUsd,highestBidInUsd,highestBidderInUsd,item) = owner.auctionStates(this);
        // ,,,,,,,,,,,,
        return highestBidderInUsd;
    }

    /*function highestManagedBidder()
        public
        view
        returns (uint64)
    {
        //var (,,,,,highestManagedBidder,,,,) = owner.auctionStates(this);
        var (,,,,,highestManagedBidder,,,,,,,) = owner.auctionStates(this);
        // ,,,,,highestManagedBidder,,,,,,,
        return highestManagedBidder;
    }*/


    /*function allowManagedBids()
        public
        view
        returns (bool)
    {
        //var (,,,,,,allowManagedBids,,,) = owner.auctionStates(this);
        var (endSeconds,maxTokenBidInEther,minPrice,highestBid,highestBidder,allowManagedBids,cancelled,finalized,maxTokenBidInUsd,highestBidInUsd,highestBidderInUsd,bidderStates) = owner.auctionStates(this);
        return allowManagedBids;
    }*/


    // mapping(address => uint256) public etherBalances;
    // mapping(address => uint256) public tokenBalances;
    // mapping(address => uint256) public tokenBalancesInEther;
    // mapping(address => uint256) public managedBids;
    
    // bool allowManagedBids;
}

// File: contracts/TokenStarsAuction.sol

pragma solidity ^0.4.18;


contract TokenStarsAuctionHub is AuctionHub {
    //real
    address public ACE = 0x06147110022B768BA8F99A8f385df11a151A9cc8;
    //renkeby
    //address public ACE = 0xa0813ad2e1124e0779dc04b385f5229776dcbba8;
    //real
    address public TEAM = 0x1c79ab32C66aCAa1e9E81952B8AAa581B43e54E7;
    // rinkeby
    //address public TEAM = 0x10b882e7da9ef31ef6e0e9c4c5457dfaf8dd9a24;
    //address public wallet = 0x963dF7904cF180aB2C033CEAD0be8687289f05EC;
    address public wallet = 0x0C9b07209750BbcD1d1716DA52B591f371eeBe77;//
    address[] public tokens = [ACE, TEAM];
    // ACE = 0.01 ETH; 
    // TEAM = 0,002 ETH
    uint256[] public rates = [10000000000000000, 2000000000000000];
    uint256[] public decimals = [0, 4];
    // ETH = $138.55
    uint256 public etherRate = 13855;

    function TokenStarsAuctionHub()
        public
        AuctionHub(wallet, tokens, rates, decimals, etherRate)
    {
    }

    function createAuction(
        address _wallet,
        uint _endSeconds, 
        uint256 _maxTokenBidInEther,
        uint256 _minPrice,
        string _item
        //bool _allowManagedBids
    )
        onlyBot
        public
        returns (address)
    {
        require (_endSeconds > now);
        require(_maxTokenBidInEther <= 1000 ether);
        require(_minPrice > 0);

        Auction auction = new TokenStarsAuction(this);

        ActionState storage auctionState = auctionStates[auction];

        auctionState.endSeconds = _endSeconds;
        auctionState.maxTokenBidInEther = _maxTokenBidInEther;
        auctionState.minPrice = _minPrice;
        //auctionState.allowManagedBids = _allowManagedBids;
        string memory item = _item;
        auctionState.item = stringToBytes32(item);

        NewAction(auction, _item);
        return address(auction);
    }
}

contract TokenStarsAuctionHubMock is AuctionHub {
    uint256[] public rates = [2400000000000000, 2400000000000000];
    uint256[] public decimals = [0, 4];
    uint256 public etherRate = 13855;

    function TokenStarsAuctionHubMock(address _wallet, address[] _tokens)
        public
        AuctionHub(_wallet, _tokens, rates, decimals, etherRate)
    {
    }

    function createAuction(
        uint _endSeconds, 
        uint256 _maxTokenBidInEther,
        uint256 _minPrice,
        string _item
        //bool _allowManagedBids
    )
        onlyBot
        public
        returns (address)
    {
        require (_endSeconds > now);
        require(_maxTokenBidInEther <= 1000 ether);
        require(_minPrice > 0);

        Auction auction = new TokenStarsAuction(this);

        ActionState storage auctionState = auctionStates[auction];

        auctionState.endSeconds = _endSeconds;
        auctionState.maxTokenBidInEther = _maxTokenBidInEther;
        auctionState.maxTokenBidInUsd = _maxTokenBidInEther.mul(etherRate).div(10 ** 2);
        auctionState.minPrice = _minPrice;
        //auctionState.allowManagedBids = _allowManagedBids;
        string memory item = _item;
        auctionState.item = stringToBytes32(item);

        NewAction(auction, _item);
        return address(auction);
    }
}

contract TokenStarsAuction is Auction {
        
    function TokenStarsAuction(
        address _owner) 
        public
        Auction(_owner)
    {
        
    }

    function bidAce(uint256 _tokensNumber)
        payable
        public
        returns (bool isHighest, bool isHighestInUsd)
    {
        return super.bid(TokenStarsAuctionHub(owner).ACE(), _tokensNumber);
    }

    function bidTeam(uint256 _tokensNumber)
        payable
        public
        returns (bool isHighest, bool isHighestInUsd)
    {
        return super.bid(TokenStarsAuctionHub(owner).TEAM(), _tokensNumber);
    }
}