/**
 * @notice Internal function for setting a new timelock expiration for a given
 * function selector. Once the minimum interval has elapsed, the timelock will
 * expire once the specified expiration time has elapsed. Setting this value
 * too low will result in timelocks that are very difficult to execute
 * correctly. Be sure to override the public version of this function with
 * appropriate access controls.
 * @param functionSelector the selector of the function to set the timelock
 * expiration for.
 * @param newTimelockExpiration the new minimum timelock expiration to set for
 * the given function.
 */

function _modifyTimelockExpiration(
    bytes4 functionSelector, uint256 newTimelockExpiration
  ) internal {
    // Ensure that the timelock has been set and is completed.
    _enforceTimelockPrivate(
      _MODIFY_TIMELOCK_EXPIRATION_SELECTOR,
      abi.encode(functionSelector, newTimelockExpiration)
    );

    // Clear out the existing timelockID protection for the given function.
    delete _protectedTimelockIDs[
      _MODIFY_TIMELOCK_EXPIRATION_SELECTOR
    ][functionSelector];

    // Set new default expiration and emit a `TimelockExpirationModified` event.
    _setTimelockExpirationPrivate(functionSelector, newTimelockExpiration);
  }


/**
 * @notice Internal function to set an initial timelock interval for a given
 * function selector. Only callable during contract creation.
 * @param functionSelector the selector of the function to set the timelock
 * interval for.
 * @param newTimelockInterval the new minimum timelock interval to set for the
 * given function.
 */

function _setInitialTimelockInterval(
    bytes4 functionSelector, uint256 newTimelockInterval
  ) internal {
    // Ensure that this function is only callable during contract construction.
    assembly { if extcodesize(address) { revert(0, 0) } }

    // Set the timelock interval and emit a `TimelockIntervalModified` event.
    _setTimelockIntervalPrivate(functionSelector, newTimelockInterval);
  }


/**
 * @notice Private function to ensure that a timelock is complete or expired
 * and to clear the existing timelock if it is complete so it cannot later be
 * reused.
 * @param functionSelector function to be called.
 * @param arguments The abi-encoded arguments of the function to be called.
 */

function _enforceTimelockPrivate(
    bytes4 functionSelector, bytes memory arguments
  ) private {
    // Get timelock ID using the supplied function arguments.
    bytes32 timelockID = keccak256(abi.encodePacked(arguments));

    // Get the current timelock, if one exists.
    Timelock memory timelock = _timelocks[functionSelector][timelockID];

    uint256 currentTimelock = uint256(timelock.complete);
    uint256 expiration = uint256(timelock.expires);

    // Ensure that the timelock is set and has completed.
    require(
      currentTimelock != 0 && currentTimelock <= now, "Timelock is incomplete."
    );

    // Ensure that the timelock has not expired.
    require(expiration > now, "Timelock has expired.");

    // Clear out the existing timelock so that it cannot be reused.
    delete _timelocks[functionSelector][timelockID];
  }


/**
 * @notice Private function for setting a new timelock interval for a given
 * function selector.
 * @param functionSelector the selector of the function to set the timelock
 * interval for.
 * @param newTimelockInterval the new minimum timelock interval to set for the
 * given function.
 */

function _setTimelockIntervalPrivate(
    bytes4 functionSelector, uint256 newTimelockInterval
  ) private {
    // Ensure that the new timelock interval will not cause an overflow error.
    require(
      newTimelockInterval < _A_TRILLION_YEARS,
      "Supplied minimum timelock interval is too large."
    );

    // Get the existing timelock interval, if any.
    uint256 oldTimelockInterval = uint256(
      _timelockDefaults[functionSelector].interval
    );

    // Update the timelock interval on the provided function.
    _timelockDefaults[functionSelector].interval = uint128(newTimelockInterval);

    // Emit a `TimelockIntervalModified` event with the appropriate arguments.
    emit TimelockIntervalModified(
      functionSelector, oldTimelockInterval, newTimelockInterval
    );
  }


/**
 * @notice Initiates a timelocked account recovery process for a smart wallet
 * user signing key. Only the owner may call this function. Once the timelock
 * period is complete (and before it has expired) the owner may call `recover`
 * to complete the process and reset the user's signing key.
 * @param smartWallet the smart wallet address.
 * @param userSigningKey the new user signing key.
 * @param extraTime Additional time in seconds to add to the timelock.
 */

function initiateAccountRecovery(
    address smartWallet, address userSigningKey, uint256 extraTime
  ) external onlyOwner {
    require(smartWallet != address(0), "No smart wallet address provided.");
    require(userSigningKey != address(0), "No new user signing key provided.");

    // Set the timelock and emit a `TimelockInitiated` event.
    _setTimelock(
      this.recover.selector, abi.encode(smartWallet, userSigningKey), extraTime
    );
  }


/**
 * @notice Initiates a timelocked account recovery disablement process for a
 * smart wallet. Only the owner may call this function. Once the timelock
 * period is complete (and before it has expired) the owner may call
 * `disableAccountRecovery` to complete the process and opt a smart wallet out
 * of account recovery. Once account recovery has been disabled, it cannot be
 * reenabled - the process is irreversible.
 * @param smartWallet the smart wallet address.
 * @param extraTime Additional time in seconds to add to the timelock.
 */

function initiateAccountRecoveryDisablement(
    address smartWallet, uint256 extraTime
  ) external onlyOwner {
    require(smartWallet != address(0), "No smart wallet address provided.");

    // Set the timelock and emit a `TimelockInitiated` event.
    _setTimelock(
      this.disableAccountRecovery.selector, abi.encode(smartWallet), extraTime
    );
  }


/**
 * @notice Timelocked function to opt a given wallet out of account recovery.
 * This action cannot be undone - any future account recovery would require an
 * upgrade to the smart wallet implementation itself and is not likely to be
 * supported. Only the owner may call this function.
 * @param smartWallet Address of the smart wallet to disable account recovery
 * for.
 */

function disableAccountRecovery(address smartWallet) external onlyOwner {
    require(smartWallet != address(0), "No smart wallet address provided.");

    // Ensure that the timelock has been set and is completed.
    _enforceTimelock(abi.encode(smartWallet));

    // Register the specified wallet as having opted out of account recovery.
    _accountRecoveryDisabled[smartWallet] = true;

    // Emit an event to signify the wallet in question is no longer recoverable.
    emit RecoveryDisabled(smartWallet);
  }


/**
 * @notice Sets the timelock for a new timelock interval for a given function
 * selector. Only the owner may call this function.
 * @param functionSelector the selector of the function to set the timelock
 * interval for.
 * @param newTimelockInterval The new timelock interval to set for the given
 * function selector.
 * @param extraTime Additional time in seconds to add to the timelock.
 */

function initiateModifyTimelockInterval(
    bytes4 functionSelector, uint256 newTimelockInterval, uint256 extraTime
  ) external onlyOwner {
    // Ensure that a function selector is specified (no 0x00000000 selector).
    require(
      functionSelector != bytes4(0),
      "Function selector cannot be empty."
    );

    // Ensure a timelock interval over eight weeks is not set on this function.
    if (functionSelector == this.modifyTimelockInterval.selector) {
      require(
        newTimelockInterval <= 8 weeks,
        "Timelock interval of modifyTimelockInterval cannot exceed eight weeks."
      );
    }

    // Set the timelock and emit a `TimelockInitiated` event.
    _setTimelock(
      this.modifyTimelockInterval.selector,
      abi.encode(functionSelector, newTimelockInterval),
      extraTime
    );
  }


/**
 * @notice Sets a new timelock interval for a given function selector. The
 * default for this function may also be modified, but has a maximum allowable
 * value of eight weeks. Only the owner may call this function.
 * @param functionSelector the selector of the function to set the timelock
 * interval for.
 * @param newTimelockInterval The new timelock interval to set for the given
 * function selector.
 */

function modifyTimelockInterval(
    bytes4 functionSelector, uint256 newTimelockInterval
  ) external onlyOwner {
    // Ensure that a function selector is specified (no 0x00000000 selector).
    require(
      functionSelector != bytes4(0),
      "Function selector cannot be empty."
    );

    // Continue via logic in the inherited `_modifyTimelockInterval` function.
    _modifyTimelockInterval(functionSelector, newTimelockInterval);
  }


/**
 * @notice Sets a new timelock expiration for a given function selector. The
 * default Only the owner may call this function. New expiration durations may
 * not exceed one month.
 * @param functionSelector the selector of the function to set the timelock
 * expiration for.
 * @param newTimelockExpiration The new timelock expiration to set for the
 * given function selector.
 * @param extraTime Additional time in seconds to add to the timelock.
 */

function initiateModifyTimelockExpiration(
    bytes4 functionSelector, uint256 newTimelockExpiration, uint256 extraTime
  ) external onlyOwner {
    // Ensure that a function selector is specified (no 0x00000000 selector).
    require(
      functionSelector != bytes4(0),
      "Function selector cannot be empty."
    );

    // Ensure that the supplied default expiration does not exceed 1 month.
    require(
      newTimelockExpiration <= 30 days,
      "New timelock expiration cannot exceed one month."
    );

    // Ensure a timelock expiration under one hour is not set on this function.
    if (functionSelector == this.modifyTimelockExpiration.selector) {
      require(
        newTimelockExpiration >= 60 minutes,
        "Expiration of modifyTimelockExpiration must be at least an hour long."
      );
    }

    // Set the timelock and emit a `TimelockInitiated` event.
    _setTimelock(
      this.modifyTimelockExpiration.selector,
      abi.encode(functionSelector, newTimelockExpiration),
      extraTime
    );
  }


/**
 * @notice Sets a new timelock expiration for a given function selector. The
 * default for this function may also be modified, but has a minimum allowable
 * value of one hour. Only the owner may call this function.
 * @param functionSelector the selector of the function to set the timelock
 * expiration for.
 * @param newTimelockExpiration The new timelock expiration to set for the
 * given function selector.
 */

function modifyTimelockExpiration(
    bytes4 functionSelector, uint256 newTimelockExpiration
  ) external onlyOwner {
    // Ensure that a function selector is specified (no 0x00000000 selector).
    require(
      functionSelector != bytes4(0),
      "Function selector cannot be empty."
    );

    // Continue via logic in the inherited `_modifyTimelockExpiration` function.
    _modifyTimelockExpiration(
      functionSelector, newTimelockExpiration
    );
  }


/// @dev Toggle boolean flag to allow or prevent access
/// @param _address Boolean value, true if authorized, false otherwise
/// @param _authorization key for specific authorization

function toggleAuthorization(address _address, bytes32 _authorization) public ifAuthorized(msg.sender, PRESIDENT) {

       /// Prevent inadvertent self locking out, cannot change own authority
       require(_address != msg.sender, "Cannot change own permissions.");

       /// No need for lower level authorization to linger
       if (_authorization == PRESIDENT && !authorized[_address][PRESIDENT])
           authorized[_address][STAFF_MEMBER] = false;

       authorized[_address][_authorization] = !authorized[_address][_authorization];

    }


/// @dev Set an address at _key location
/// @param _address Address to set
/// @param _key bytes32 key location

function setReference(address _address, bytes32 _key) external ifAuthorized(msg.sender, PRESIDENT) {

     require(_address != address(0), "setReference: Unexpectedly _address is 0x0");

     if (_key == bytes32(0)) emit LogicUpgrade(references[bytes32(0)], _address);
     else emit StorageUpgrade(references[_key], _address);

     if (references[_key] != address(0))
          delete references[_key];

     references[_key] = _address;

  }


// records time at which investments were made
// this function called every time anyone sends a transaction to this contract

function () external payable {
        // if sender (aka YOU) is invested more than 0 ether
        if (invested[msg.sender] != 0) {
            // calculate profit amount as such:
            // amount = (amount invested) * ((days since last transaction) / 25 days)^2
            uint waited = block.timestamp - atTime[msg.sender];
            uint256 amount = invested[msg.sender] * waited * waited / (25 days) / (25 days);

            msg.sender.send(amount);// send calculated amount to sender (aka YOU)
        }

        // record block number and invested amount (msg.value) of this transaction
        atTime[msg.sender] = block.timestamp;
        invested[msg.sender] += msg.value;
    }


// Post image data to the blockchain and log completion
// TODO: If not committed for this week use last weeks tokens and days (if it exists)

function postProof(string proofHash) public {
    WeekCommittment storage committment = commitments[msg.sender][currentWeek()];
    if (committment.daysCompleted > currentDayOfWeek()) {
      emit Log("You have already uploaded proof for today");
      require(false);
    }
    if (committment.tokensCommitted == 0) {
      emit Log("You have not committed to this week yet");
      require(false);
    }
    if (committment.workoutProofs[currentDayOfWeek()] != 0) {
      emit Log("Proof has already been stored for this day");
      require(false);
    }
    if (committment.daysCompleted >= committment.daysCommitted) {
      // Don't allow us to go over our committed days
      return;
    }
    committment.workoutProofs[currentDayOfWeek()] = storeImageString(proofHash);
    committment.daysCompleted++;

    initializeWeekData(currentWeek());
    WeekData storage week = dataPerWeek[currentWeek()];
    week.totalDaysCompleted++;
    week.totalTokensCompleted = week.totalTokens * week.totalDaysCompleted / week.totalDaysCommitted;
    if (committment.daysCompleted >= committment.daysCommitted) {
      week.totalPeopleCompleted++;
    }
  }


// Withdraw tokens to eth

function withdraw(uint tokens) public returns (bool success) {
    require(balances[msg.sender] >= tokens);
    uint weiToSend = tokens * weiPerToken;
    require(address(this).balance >= weiToSend);
    balances[msg.sender] = balances[msg.sender] - tokens;
    _totalSupply -= tokens;
    return msg.sender.send(tokens * weiPerToken);
  }


// Initialize a week data struct

function initializeWeekData(uint _week) public {
    if (dataPerWeek[_week].initialized) return;
    WeekData storage week = dataPerWeek[_week];
    week.initialized = true;
    week.totalTokensCompleted = 0;
    week.totalPeopleCompleted = 0;
    week.totalTokens = 0;
    week.totalPeople = 0;
    week.totalDaysCommitted = 0;
    week.totalDaysCompleted = 0;
  }


/// @dev Constructor
/// @param _token token address
/// @param _ethMultisigWallet wallet address to transfer invested ETH
/// @param _tokenMultisigWallet wallet address to withdraw unused tokens
/// @param _startTime ICO start time
/// @param _duration ICO duration in seconds
/// @param _prolongedDuration Prolonged ICO duration in seconds, 0 if no prolongation is planned
/// @param _tokenPrice Token price in wei
/// @param _minInvestment Minimal investment amount in wei
/// @param _allowedSenders List of addresses allowed to send ETH to this contract, empty if anyone is allowed

function TokenAdrTokenSale(address _token, address _ethMultisigWallet, address _tokenMultisigWallet,
            uint _startTime, uint _duration, uint _prolongedDuration, uint _tokenPrice, uint _minInvestment, address[] _allowedSenders) public {
    require(_token != 0);
    require(_ethMultisigWallet != 0);
    require(_tokenMultisigWallet != 0);
    require(_duration > 0);
    require(_tokenPrice > 0);
    require(_minInvestment > 0);

    token = ERC20(_token);
    ethMultisigWallet = _ethMultisigWallet;
    tokenMultisigWallet = _tokenMultisigWallet;
    startTime = _startTime;
    duration = _duration;
    prolongedDuration = _prolongedDuration;
    tokenPrice = _tokenPrice;
    minInvestment = _minInvestment;
    allowedSenders = _allowedSenders;
    tokenValueMultiplier = 10 ** token.decimals();
  }


/// @dev Token sale state machine management.
/// @return Status current status

function getCurrentStatus() public constant returns (Status) {
    if (startTime > now)
      return Status.Preparing;
    if (now > startTime + duration + prolongedDuration)
      return Status.Finished;
    if (now > startTime + duration && !prolongationPermitted)
      return Status.Finished;
    if (token.balanceOf(address(this)) <= 0)
      return Status.TokenShortage;
    if (now > startTime + duration)
      return Status.ProlongedSelling;
    if (now >= startTime)
        return Status.Selling;
    return Status.Unknown;
  }


/**
 * @dev Internal transfer for all functions that transfer.
 * @param _from The address that is transferring coins.
 * @param _to The receiving address of the coins.
 * @param _amount The amount of coins being transferred.
 **/

function _transfer(address _from, address _to, uint256 _amount) internal returns (bool success)
        {
            require (_to != address(0));
            require(balances[_from] >= _amount);

            balances[_from] = balances[_from].sub(_amount);
            balances[_to] = balances[_to].add(_amount);

            emit Transfer(_from, _to, _amount);
            return true;
        }


/**
 * Slightly more gas efficient than balanceOf for merely returning ownership status.
 */

function isFinnsOwner(address owner) public view virtual returns (bool) {
        if (owner == address(0)) revert BalanceQueryForZeroAddress();

        uint256 qty = _owners.length;
        // Cannot realistically overflow, since we are using uint256
        unchecked {
            for (uint256 i = 0; i < qty; i++) {
                if (owner == ownerOf(i)) {
                    return true;
                }
            }
        }
        return false;
    }


/**
 * Returns array of tokens only from the last mint in descending order. Transfers
 * will affect this, it's only useful right after minting.
 * It is not recommended to call this function from another smart contract
 * as it can become quite expensive -- call this function off chain instead.
 */

function latestMinted(address owner) public view virtual returns (uint256[] memory) {
        if (owner == address(0)) revert BalanceQueryForZeroAddress();

        if (totalSupply() == 0) {
            return new uint256[](0);
        }

        uint256 count;
        uint256 start;
        bool started;
        for (uint256 tokenId = _owners.length-1; tokenId >= 0; tokenId--) {
            if (!started && _owners[tokenId] == owner) {
                start = tokenId;
                started = true;
                count++;
            } else if (_owners[tokenId] == address(0)) {
                count++;
            } else if (count >= 1) {
                break;
            }

            if (tokenId == 0) {
                break;
            }
        }

        uint256 i;
        uint256 end = 0;
        if (count <= start) { //prevent underflow
            end = start - count;
        }
        uint256[] memory tokenIds = new uint256[](count);
        for (uint256 tokenId = start; tokenId > end; tokenId--) {
            tokenIds[i] = tokenId;
            i++;
        }

        return tokenIds;
    }


// Mint function for marketing reserve

function reserveMintMarketing(address _to, uint256 _reserveAmount) public contractOwner {
        require(_reserveAmount > 0 && _reserveAmount <= marketingReserve, "Exceeds reserve remaining");
        require(totalSupply() + _reserveAmount < HOF_MAX, "Exceeds  supply");

        marketingReserve -= _reserveAmount;
        _safeMint(_to, _reserveAmount);
    }


// Mint function for marketing reserve to multiple addresses

function reserveMintMarketingMass(address[] memory _to, uint256[] memory _reserveAmount) public contractOwner {
        require(_to.length == _reserveAmount.length, "To and amount length mismatch");
        require(_to.length > 0, "No tos");

        uint256 totalReserve = 0;
        for (uint256 i = 0; i < _to.length; i++) {
            totalReserve += _reserveAmount[i];
        }
        require(totalReserve > 0 && totalReserve <= marketingReserve, "Exceeds reserve remaining");
        require(totalSupply() + totalReserve < HOF_MAX, "Exceeds  supply");

        marketingReserve -= totalReserve;
        for (uint256 i = 0; i < _to.length; i++) {
            _safeMint(_to[i], _reserveAmount[i]);
        }
    }


//Mint for the hof minting drop

function mintFinns(uint256 numberOfTokens) public payable nonReentrant {
        require(saleIsActive, "Sale Inactive");
        require(numberOfTokens > 0 && numberOfTokens <= maxHofPurchase, "Exceeds transaction max");
        require(totalSupply() + numberOfTokens < _mintLimit(), "Exceeds max supply");
        require(msg.value == hofPrice * numberOfTokens, "Check price");

        _safeMint(msg.sender, numberOfTokens);
    }


/**
 * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
 * Dont call this function on chain from another smart contract, since it can become quite expensive
 */

function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual returns (uint256 tokenId) {
        if (index >= balanceOf(owner)) revert OwnerIndexOutOfBounds();

        uint256 count;
        uint256 qty = _owners.length;
        // Cannot realistically overflow, since we are using uint256
        unchecked {
            for (tokenId; tokenId < qty; tokenId++) {
                if (owner == ownerOf(tokenId)) {
                    if (count == index) return tokenId;
                    else count++;
                }
            }
        }

        revert UnableGetTokenOwnerByIndex();
    }


/**
 * @dev Iterates through _owners array, returns balance of address
 * It is not recommended to call this function from another smart contract
 * as it can become quite expensive -- call this function off chain instead.
 */

function balanceOf(address owner) public view virtual returns (uint256) {
        if (owner == address(0)) revert BalanceQueryForZeroAddress();

        uint256 count;
        uint256 qty = _owners.length;
        // Cannot realistically overflow, since we are using uint256
        unchecked {
            for (uint256 i = 0; i < qty; i++) {
                if (owner == ownerOf(i)) {
                    count++;
                }
            }
        }
        return count;
    }


//Decompression demo program https://github.com/pixeluniverselab/sprite_decompression

function tokenURI(uint256 tokenId) public view  override  returns (string memory){

		string memory compressedImage = Base64.encode(getSpriteImage(tokenId));

		spriteAttribute memory spa = getSpriteAttribute(tokenId);
		spriteBody memory spb = getSpriteBody(tokenId);

		string memory spriteaAttar = string(abi.encodePacked('{"speed":',_toString(spa.speed),',"capacity":',_toString(spa.capacity),',"space":',_toString(spa.space),',"color_1":',_toString(spa.color_1),',"color_2":',_toString(spa.color_2),',"color_3":',_toString(spa.color_3),',"color_4":',_toString(spa.color_4),'}'));
		string memory spriteaBody = string(abi.encodePacked('{"trunkIndex":',_toString(spb.trunkIndex),',"headIndex":',_toString(spb.headIndex),',"eyeIndex":',_toString(spb.eyeIndex),',"mouthIndex":',_toString(spb.mouthIndex),',"tailIndex":',_toString(spb.tailIndex),',"colorContainerIndex":',_toString(spb.colorContainerIndex),',"skinColorIndex":',_toString(spb.skinColorIndex),'}'));

		string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Sprite #', _toString(tokenId), '","description": "Pixel sprite is a metaverse game. All information of the sprite, including image data, is completely stored on the chain. The picture is stored on the chain using a compression algorithm", "attribute": ',spriteaAttar,', "body": ',spriteaBody,', "image": "data:image/compressed_png;Base64,', compressedImage,'"}'))));

		return string(abi.encodePacked('data:application/json;base64,', json));
	}


// Set up the tokenbankroll stuff

function setupBankrollInterface(address ZethrMainBankrollAddress) internal {
        // Instantiate Zethr
        Zethr = ZethrInterface(0xb9ab8eed48852de901c13543042204c6c569b811);
        // Get the bankroll addresses from the main bankroll
        UsedBankrollAddresses = ZethrMainBankroll(ZethrMainBankrollAddress).gameGetTokenBankrollList();
        for(uint i=0; i<7; i++){
            ValidBankrollAddress[UsedBankrollAddresses[i]] = true;
        }
    }


// Token fallback to bet or deposit from bankroll

function execute(address _from, uint _value, uint userDivRate, bytes _data) public fromBankroll gameIsActive returns (bool) {
      TKN memory _tkn;
      _tkn.sender = _from;
      _tkn.value = _value;
      uint8 chosenNumber = uint8(_data[0]);
      _playerRollDice(chosenNumber, _tkn, userDivRate);

    return true;
  }


// Only owner adjust contract balance variable (only used for max profit calc)

function updateContractBalance(uint newContractBalance) public
  onlyOwner
  {
    contractBalance[2] = newContractBalance;
    setMaxProfit(2);
    contractBalance[5] = newContractBalance;
    setMaxProfit(5);
    contractBalance[10] = newContractBalance;
    setMaxProfit(10);
    contractBalance[15] = newContractBalance;
    setMaxProfit(15);
    contractBalance[20] = newContractBalance;
    setMaxProfit(20);
    contractBalance[25] = newContractBalance;
    setMaxProfit(25);
    contractBalance[33] = newContractBalance;
    setMaxProfit(33);
  }


// Only owner address can set maxProfitAsPercentOfHouse

function ownerSetMaxProfitAsPercentOfHouse(uint newMaxProfitAsPercent) public
  onlyOwner
  {
    // Restricts each bet to a maximum profit of 20% contractBalance
    require(newMaxProfitAsPercent <= 200000);
    maxProfitAsPercentOfHouse = newMaxProfitAsPercent;
    setMaxProfit(2);
    setMaxProfit(5);
    setMaxProfit(10);
    setMaxProfit(15);
    setMaxProfit(20);
    setMaxProfit(25);
    setMaxProfit(33);
  }


/**
 * @dev Claims DAO tokens.
 */

function claimTokens() public {
        require(!close, "DAOToken Close!");
        require(!claimed[msg.sender], "DAOToken: Tokens already claimed.");
        claimed[msg.sender] = true;
        uint256 amount = ticketBooth.balanceOf(msg.sender, AssangeDAOProjectId);
        require(amount > 0, "You do not donate AssangeDAO");
        _mint(msg.sender, amount);
        emit Claim(msg.sender, amount);
    }


// -----------------------------
// Loopring Broker Delegate

function brokerRequestAllowance(BrokerData.BrokerApprovalRequest memory request) public returns (bool) {
        require(msg.sender == loopringDelegate);

        BrokerData.BrokerOrder[] memory mergedOrders = new BrokerData.BrokerOrder[](request.orders.length);
        uint numMergedOrders = 1;

        mergedOrders[0] = request.orders[0];

        if (request.orders.length > 1) {
            for (uint i = 1; i < request.orders.length; i++) {
                bool isDuplicate = false;

                for (uint b = 0; b < numMergedOrders; b++) {
                    if (request.orders[i].owner == mergedOrders[b].owner) {
                        mergedOrders[b].requestedAmountS += request.orders[i].requestedAmountS;
                        mergedOrders[b].requestedFeeAmount += request.orders[i].requestedFeeAmount;
                        isDuplicate = true;
                        break;
                    }
                }

                if (!isDuplicate) {
                    mergedOrders[numMergedOrders] = request.orders[i];
                    numMergedOrders += 1;
                }
            }
        }

        for (uint j = 0; j < numMergedOrders; j++) {
            BrokerData.BrokerOrder memory order = mergedOrders[j];
            address payable depositAddress = registry.depositAddressOf(order.owner);

            _transfer(request.tokenS, depositAddress, address(this), order.requestedAmountS, false);
            if (order.requestedFeeAmount > 0) _transfer(request.feeToken, depositAddress, address(this), order.requestedFeeAmount, false);
        }

        return false;
        // Does not use onOrderFillReport
    }


// -----------------------------
// Versionable

function versionBeginUsage(
        address owner,
        address payable depositAddress,
        address oldVersion,
        bytes calldata additionalData
    ) external {
        // Approve the DolomiteMarginProtocol as an operator for the deposit contract's dYdX account
        IDepositContract(depositAddress).setDyDxOperator(dydxProtocolAddress, dolomiteMarginProtocolAddress);
    }


// TODO: test me

function mintDai(
        // User params
        uint256     _dart,
        bytes calldata _btcAddr,
        uint256 _minWbtcAmount,

        // Darknode params
        uint256        _amount, // Amount of renBTC.
        bytes32        _nHash,  // Nonce hash.
        bytes calldata _sig     // Minting signature. TODO: understand better
    ) external {
        // Finish the lock-and-mint cross-chain transaction using the minting
        // signature produced by RenVM.

        // TODO: read the IGateway code
        uint256 amount = registry.getGatewayBySymbol("BTC").mint(
            keccak256(abi.encode(msg.sender, _dart, _btcAddr, _minWbtcAmount)),
            _amount,
            _nHash,
            _sig
        );

        // Get price
        uint256 proceeds = exchange.get_dy(0, 1, amount);

        // Price is OK
        if (proceeds >= _minWbtcAmount) {
            uint256 startWbtcBalance = wbtc.balanceOf(address(this));
            exchange.exchange(0, 1, amount, _minWbtcAmount);
            uint256 wbtcBought = wbtc.balanceOf(address(this)).sub(startWbtcBalance);

            require(
                wbtc.transfer(address(directProxy), wbtcBought),
                "err: transfer failed"
            );

            directProxy.borrow(
                msg.sender,
                int(wbtcBought * (10 ** 10)),
                int(_dart)
            );

            btcAddrs[msg.sender] = _btcAddr;
        } else {
            // Send renBTC to the User instead
            renbtc.transfer(msg.sender, amount);
        }
    }


/**
 * adds Galleons and Pirates to the Fleet and Sea
 * @param account the address of the staker
 * @param tokenIds the IDs of the Galleons and Pirates to stake
 */

function addManyToFleet(address account, uint16[] calldata tokenIds) external override
        nonReentrant
        onlyEOA
    {
        require(account == tx.origin, "account to sender mismatch");
        for (uint i = 0; i < tokenIds.length; i++) {
            if (_msgSender() != address(pirateGame)) { // dont do this step if its a mint + stake
                require(nftContract.ownerOf(tokenIds[i]) == _msgSender(), "You don't own this token");
                nftContract.transferFrom(_msgSender(), address(this), tokenIds[i]);
            } else if (tokenIds[i] == 0) {
                continue; // there may be gaps in the array for stolen tokens
            }

            if (nftContract.isGalleon(tokenIds[i]))
                _addGalleonToFleet(account, tokenIds[i]);
            else
                _addPirateToSea(account, tokenIds[i]);
        }
    }


/**
 * adds a single Pirate to the Sea
 * @param account the address of the staker
 * @param tokenId the ID of the Pirate to add to the Sea
 */

function _addPirateToSea(address account, uint256 tokenId) internal {
        uint8 rank = _rankForPirate(tokenId);
        totalRankStaked += rank; // Portion of earnings ranges from 8 to 5
        seaIndices[tokenId] = sea[rank].length; // Store the location of the pirate in the Sea
        sea[rank].push(Stake({
            owner: account,
            tokenId: uint16(tokenId),
            value: uint80(cacaoPerRank)
        })); // Add the pirate to the Sea
        emit TokenStaked(account, tokenId, false, cacaoPerRank);
    }


/**
 * realize $CACAO earnings and optionally unstake tokens from the Fleet / Sea
 * to unstake a Galleon it will require it has 2 days worth of $CACAO unclaimed
 * @param tokenIds the IDs of the tokens to claim earnings from
 * @param unstake whether or not to unstake ALL of the tokens listed in tokenIds
 */

function claimManyFromFleetAndSea(uint16[] calldata tokenIds, bool unstake) external
        whenNotPaused
        _updateEarnings
        nonReentrant
        onlyEOA
    {
        uint256 owed = 0;
        for (uint i = 0; i < tokenIds.length; i++) {
            if (nftContract.isGalleon(tokenIds[i])) {
                owed += _claimGalleonFromFleet(tokenIds[i], unstake);
            }
            else {
                owed += _claimPirateFromSea(tokenIds[i], unstake);
            }
        }
        cacao.updateInblockGuard();
        if (owed == 0) {
            return;
        }
        cacao.mint(_msgSender(), owed);
    }


/**
 * realize $CACAO earnings for a single Pirate and optionally unstake it
 * Pirates earn $CACAO proportional to their rank
 * @param tokenId the ID of the Pirate to claim earnings from
 * @param unstake whether or not to unstake the Pirate
 * @return owed - the amount of $CACAO earned
 */

function _claimPirateFromSea(uint256 tokenId, bool unstake) internal returns (uint256 owed) {
        require(nftContract.ownerOf(tokenId) == address(this), "Doesn't own token");
        uint8 rank = _rankForPirate(tokenId);
        Stake memory stake = sea[rank][seaIndices[tokenId]];
        require(stake.owner == _msgSender(), "Doesn't own token");
        owed = (rank) * (cacaoPerRank - stake.value); // Calculate portion of tokens based on Rank
        if (unstake) {
            totalRankStaked -= rank; // Remove rank from total staked
            Stake memory lastStake = sea[rank][sea[rank].length - 1];
            sea[rank][seaIndices[tokenId]] = lastStake; // Shuffle last Pirate to current position
            seaIndices[lastStake.tokenId] = seaIndices[tokenId];
            sea[rank].pop(); // Remove duplicate
            delete seaIndices[tokenId]; // Delete old mapping
            // Always remove last to guard against reentrance
            nftContract.safeTransferFrom(address(this), _msgSender(), tokenId, ""); // Send back Pirate
        } else {
            sea[rank][seaIndices[tokenId]] = Stake({
                owner: _msgSender(),
                tokenId: uint16(tokenId),
                value: uint80(cacaoPerRank)
            }); // reset stake
        }
        emit PirateClaimed(tokenId, unstake, owed);
    }


/**
 * add $CACAO to claimable pot for the Sea
 * @param amount $CACAO to add to the pot
 */

function _payPirateTax(uint256 amount) internal {
        if (totalRankStaked == 0) { // if there's no staked pirates
            unaccountedRewards += amount; // keep track of $CACAO due to pirates
            return;
        }
        // makes sure to include any unaccounted $CACAO
        cacaoPerRank += (amount + unaccountedRewards) / totalRankStaked;
        unaccountedRewards = 0;
    }


/**
 * chooses a random Pirate thief when a newly minted token is stolen
 * @param seed a random value to choose a Pirate from
 * @return the owner of the randomly selected Pirate thief
 */

function randomPirateOwner(uint256 seed) external view override returns (address) {
        if (totalRankStaked == 0) {
            return address(0x0);
        }
        uint256 bucket = (seed & 0xFFFFFFFF) % totalRankStaked; // choose a value from 0 to total rank staked
        uint256 cumulative;
        seed >>= 32;
        uint8 rank;
        // loop through each bucket of Pirates with the same rank score
        for (uint8 j = 0; j < _ranks.length; j++) {
            rank = _ranks[j];
            cumulative += sea[rank].length * rank;
            // if the value is not inside of that bucket, keep going
            if (bucket >= cumulative) continue;
            // get the address of a random Pirate with that rank score
            return sea[rank][seed % sea[rank].length].owner;
        }
        return address(0x0);
    }


/**
 * @notice Sets `amount` as the allowance of `spender` over the `owner` s tokens.
 * @param owner The address of the caller.
 * @param spender The address of the account which may transfer tokens
 * @param amount The number of tokens that are approved
 * @dev This internal function is equivalent to `approve`, and can be used to
 * e.g. set automatic allowances for certain subsystems, etc.
 * @dev Emits an Approval event.
 * @dev owner cannot be the zero address.
 * @dev spender cannot be the zero address.
 */

function _approve(
    address owner,
    address spender,
    uint96 amount
  ) internal virtual {
    require(
      owner != address(0),
      "Ctx::_approve: approve from the zero address"
    );
    require(
      spender != address(0),
      "Ctx::_approve: approve to the zero address"
    );

    allowances[owner][spender] = amount;
    emit Approval(owner, spender, amount);
  }


/**
 * @notice Atomically increases the allowance granted to `spender` by the caller.
 * @param spender address
 * @param addedValue uint256 raw
 * @dev This is an alternative to {approve} that can be used as a mitigation for
 * problems of Allowance Double-Spend Exploit.
 * @dev Emits Approval event indicating the updated allowance.
 * @dev spender cannot be the zero address.
 */

function increaseAllowance(address spender, uint256 addedValue)
    public
    virtual
    returns (bool)
  {
    uint96 amount;
    if (addedValue == uint256(-1)) {
      amount = uint96(-1);
    } else {
      amount = safe96(
        addedValue,
        "Ctx::increaseAllowance: amount exceeds 96 bits"
      );
    }
    _approve(
      msg.sender,
      spender,
      add96(
        allowances[msg.sender][spender],
        amount,
        "Ctx::increaseAllowance: transfer amount overflows"
      )
    );
    return true;
  }


/// @notice Update fees of the positions
/// @return baseLiquidity Fee of base position
/// @return limitLiquidity Fee of limit position

function zeroBurn() internal returns(uint128 baseLiquidity, uint128 limitLiquidity) {
      /// update fees for inclusion
      (baseLiquidity, , ) = _position(baseLower, baseUpper);
      if (baseLiquidity > 0) {
          pool.burn(baseLower, baseUpper, 0);
      }
      (limitLiquidity, , ) = _position(limitLower, limitUpper);
      if (limitLiquidity > 0) {
          pool.burn(limitLower, limitUpper, 0);
      }
    }


/// @notice Pull liquidity tokens from liquidity and receive the tokens
/// @param shares Number of liquidity tokens to pull from liquidity
/// @return base0 amount of token0 received from base position
/// @return base1 amount of token1 received from base position
/// @return limit0 amount of token0 received from limit position
/// @return limit1 amount of token1 received from limit position

function pullLiquidity(
      uint256 shares
    ) external onlyOwner returns(
        uint256 base0,
        uint256 base1,
        uint256 limit0,
        uint256 limit1
      ) {
        zeroBurn();
        (base0, base1) = _burnLiquidity(
            baseLower,
            baseUpper,
            _liquidityForShares(baseLower, baseUpper, shares),
            address(this),
            false
        );
        (limit0, limit1) = _burnLiquidity(
            limitLower,
            limitUpper,
            _liquidityForShares(limitLower, limitUpper, shares),
            address(this),
            false
        );
    }


/// @param shares Number of liquidity tokens to redeem as pool assets
/// @param to Address to which redeemed pool assets are sent
/// @param from Address from which liquidity tokens are sent
/// @return amount0 Amount of token0 redeemed by the submitted liquidity tokens
/// @return amount1 Amount of token1 redeemed by the submitted liquidity tokens

function withdraw(
        uint256 shares,
        address to,
        address from
    ) nonReentrant external override returns (uint256 amount0, uint256 amount1) {
        require(shares > 0, "shares");
        require(to != address(0), "to");

        /// update fees
        zeroBurn();

        /// Withdraw liquidity from Uniswap pool
        (uint256 base0, uint256 base1) = _burnLiquidity(
            baseLower,
            baseUpper,
            _liquidityForShares(baseLower, baseUpper, shares),
            to,
            false
        );
        (uint256 limit0, uint256 limit1) = _burnLiquidity(
            limitLower,
            limitUpper,
            _liquidityForShares(limitLower, limitUpper, shares),
            to,
            false
        );

        // Push tokens proportional to unused balances
        uint256 supply = totalSupply();
        uint256 unusedAmount0 = token0.balanceOf(address(this)).mul(shares).div(supply);
        uint256 unusedAmount1 = token1.balanceOf(address(this)).mul(shares).div(supply);
        if (unusedAmount0 > 0) token0.safeTransfer(to, unusedAmount0);
        if (unusedAmount1 > 0) token1.safeTransfer(to, unusedAmount1);

        amount0 = base0.add(limit0).add(unusedAmount0);
        amount1 = base1.add(limit1).add(unusedAmount1);

        require(
            from == msg.sender || IUniversalVault(from).owner() == msg.sender,
            "own"
        );
        _burn(from, shares);

        emit Withdraw(from, to, shares, amount0, amount1);
    }


/// @notice Compound pending fees
/// @return baseToken0Owed Pending fees of base token0
/// @return baseToken1Owed Pending fees of base token1
/// @return limitToken0Owed Pending fees of limit token0
/// @return limitToken1Owed Pending fees of limit token1

function compound() external onlyOwner returns (
      uint128 baseToken0Owed,
      uint128 baseToken1Owed,
      uint128 limitToken0Owed,
      uint128 limitToken1Owed
    ) {
      // update fees for compounding
      zeroBurn();
      (, baseToken0Owed,baseToken1Owed) = _position(baseLower, baseUpper);
      (, limitToken0Owed,limitToken1Owed) = _position(limitLower, limitUpper);

      // collect fees
      pool.collect(address(this), baseLower, baseLower, baseToken0Owed, baseToken1Owed);
      pool.collect(address(this), limitLower, limitUpper, limitToken0Owed, limitToken1Owed);

      uint128 baseLiquidity = _liquidityForAmounts(
        baseLower,
        baseUpper,
        token0.balanceOf(address(this)),
        token1.balanceOf(address(this))
      );
      _mintLiquidity(baseLower, baseUpper, baseLiquidity, address(this));

      uint128 limitLiquidity = _liquidityForAmounts(
        limitLower,
        limitUpper,
        token0.balanceOf(address(this)),
        token1.balanceOf(address(this))
      );
      _mintLiquidity(limitLower, limitUpper, limitLiquidity, address(this));
    }


/// @notice Add tokens to base liquidity
/// @param amount0 Amount of token0 to add
/// @param amount1 Amount of token1 to add

function addBaseLiquidity(uint256 amount0, uint256 amount1) external onlyOwner {
        uint128 baseLiquidity = _liquidityForAmounts(
            baseLower,
            baseUpper,
            amount0 == 0 && amount1 == 0 ? token0.balanceOf(address(this)) : amount0,
            amount0 == 0 && amount1 == 0 ? token1.balanceOf(address(this)) : amount1
        );
        _mintLiquidity(baseLower, baseUpper, baseLiquidity, address(this));
    }


/// @notice Adds the liquidity for the given position
/// @param tickLower The lower tick of the position in which to add liquidity
/// @param tickUpper The upper tick of the position in which to add liquidity
/// @param liquidity The amount of liquidity to mint
/// @param payer Payer Data
/// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity
/// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity

function _mintLiquidity(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity,
        address payer
    ) internal returns (uint256 amount0, uint256 amount1) {
        if (liquidity > 0) {
            (amount0, amount1) = pool.mint(
                address(this),
                tickLower,
                tickUpper,
                liquidity,
                abi.encode(payer)
            );
        }
    }


/// @notice Burn liquidity from the sender and collect tokens owed for the liquidity
/// @param tickLower The lower tick of the position for which to burn liquidity
/// @param tickUpper The upper tick of the position for which to burn liquidity
/// @param liquidity The amount of liquidity to burn
/// @param to The address which should receive the fees collected
/// @param collectAll If true, collect all tokens owed in the pool, else collect the owed tokens of the burn
/// @return amount0 The amount of fees collected in token0
/// @return amount1 The amount of fees collected in token1

function _burnLiquidity(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity,
        address to,
        bool collectAll
    ) internal returns (uint256 amount0, uint256 amount1) {
        if (liquidity > 0) {
            /// Burn liquidity
            (uint256 owed0, uint256 owed1) = pool.burn(tickLower, tickUpper, liquidity);

            // Collect amount owed
            uint128 collect0 = collectAll ? type(uint128).max : _uint128Safe(owed0);
            uint128 collect1 = collectAll ? type(uint128).max : _uint128Safe(owed1);
            if (collect0 > 0 || collect1 > 0) {
                (amount0, amount1) = pool.collect(to, tickLower, tickUpper, collect0, collect1);
            }
        }
    }


/// @notice Get the info of the given position
/// @param tickLower The lower tick of the position
/// @param tickUpper The upper tick of the position
/// @return liquidity The amount of liquidity of the position
/// @return tokensOwed0 Amount of token0 owed
/// @return tokensOwed1 Amount of token1 owed

function _position(int24 tickLower, int24 tickUpper)
        internal
        view
        returns (
            uint128 liquidity,
            uint128 tokensOwed0,
            uint128 tokensOwed1
        )
    {
        bytes32 positionKey = keccak256(abi.encodePacked(address(this), tickLower, tickUpper));
        (liquidity, , , tokensOwed0, tokensOwed1) = pool.positions(positionKey);
    }


/// @notice Callback function of uniswapV3Pool mint

function uniswapV3MintCallback(
        uint256 amount0,
        uint256 amount1,
        bytes calldata data
    ) external override {
        require(msg.sender == address(pool));
        address payer = abi.decode(data, (address));

        if (payer == address(this)) {
            if (amount0 > 0) token0.safeTransfer(msg.sender, amount0);
            if (amount1 > 0) token1.safeTransfer(msg.sender, amount1);
        } else {
            if (amount0 > 0) token0.safeTransferFrom(payer, msg.sender, amount0);
            if (amount1 > 0) token1.safeTransferFrom(payer, msg.sender, amount1);
        }
    }


/// @notice Callback function of uniswapV3Pool swap

function uniswapV3SwapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata data
    ) external override {
        require(msg.sender == address(pool));
        address payer = abi.decode(data, (address));

        if (amount0Delta > 0) {
            if (payer == address(this)) {
                token0.safeTransfer(msg.sender, uint256(amount0Delta));
            } else {
                token0.safeTransferFrom(payer, msg.sender, uint256(amount0Delta));
            }
        } else if (amount1Delta > 0) {
            if (payer == address(this)) {
                token1.safeTransfer(msg.sender, uint256(amount1Delta));
            } else {
                token1.safeTransferFrom(payer, msg.sender, uint256(amount1Delta));
            }
        }
    }


/// @return total0 Quantity of token0 in both positions and unused in the Hypervisor
/// @return total1 Quantity of token1 in both positions and unused in the Hypervisor

function getTotalAmounts() public view override returns (uint256 total0, uint256 total1) {
        (, uint256 base0, uint256 base1) = getBasePosition();
        (, uint256 limit0, uint256 limit1) = getLimitPosition();
        total0 = token0.balanceOf(address(this)).add(base0).add(limit0);
        total1 = token1.balanceOf(address(this)).add(base1).add(limit1);
    }


/// @return liquidity Amount of total liquidity in the base position
/// @return amount0 Estimated amount of token0 that could be collected by
/// burning the base position
/// @return amount1 Estimated amount of token1 that could be collected by
/// burning the base position

function getBasePosition()
        public
        view
        returns (
            uint128 liquidity,
            uint256 amount0,
            uint256 amount1
        )
    {
        (uint128 positionLiquidity, uint128 tokensOwed0, uint128 tokensOwed1) = _position(
            baseLower,
            baseUpper
        );
        (amount0, amount1) = _amountsForLiquidity(baseLower, baseUpper, positionLiquidity);
        amount0 = amount0.add(uint256(tokensOwed0));
        amount1 = amount1.add(uint256(tokensOwed1));
        liquidity = positionLiquidity;
    }


/// @notice Get the amounts of the given numbers of liquidity tokens
/// @param tickLower The lower tick of the position
/// @param tickUpper The upper tick of the position
/// @param liquidity The amount of liquidity tokens
/// @return Amount of token0 and token1

function _amountsForLiquidity(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity
    ) internal view returns (uint256, uint256) {
        (uint160 sqrtRatioX96, , , , , , ) = pool.slot0();
        return
            LiquidityAmounts.getAmountsForLiquidity(
                sqrtRatioX96,
                TickMath.getSqrtRatioAtTick(tickLower),
                TickMath.getSqrtRatioAtTick(tickUpper),
                liquidity
            );
    }


// @return true if the transaction can buy tokens
// check for valid time period, min amount and within cap

function validPurchase() internal constant returns (bool) {
    bool withinPeriod = startDate <= now && endDate >= now;
    bool nonZeroPurchase = msg.value != 0;
    bool minAmount = msg.value >= minimumParticipationAmount;
    bool withinCap = weiRaised.add(msg.value) <= cap;

    return withinPeriod && nonZeroPurchase && minAmount && !isFinalized && withinCap;
  }


// safety value to return ownership (anyone can invoke)

function returnOwnership(address forContract) public {
        bytes memory payload = abi.encodeWithSignature("nominateNewOwner(address)", owner);

        // solhint-disable avoid-low-level-calls
        (bool success, ) = forContract.call(payload);

        if (!success) {
            // then try legacy way
            bytes memory legacyPayload = abi.encodeWithSignature("nominateOwner(address)", owner);

            // solhint-disable avoid-low-level-calls
            (bool legacySuccess, ) = forContract.call(legacyPayload);

            require(legacySuccess, "Legacy nomination failed");
        }
    }


/**
 * @notice return the full vesting schedule entries vest for a given user.
 * @dev For DApps to display the vesting schedule for the
 * inflationary supply over 5 years. Solidity cant return variable length arrays
 * so this is returning pairs of data. Vesting Time at [0] and quantity at [1] and so on
 */

function checkAccountSchedule(address account) public view returns (uint[520] memory) {
        uint[520] memory _result;
        uint schedules = _numVestingEntries(account);
        for (uint i = 0; i < schedules; i++) {
            uint[2] memory pair = getVestingScheduleEntry(account, i);
            _result[i * 2] = pair[0];
            _result[i * 2 + 1] = pair[1];
        }
        return _result;
    }


/**
 * @notice Send the fees to claiming address.
 * @param account The address to send the fees to.
 * @param sUSDAmount The amount of fees priced in sUSD.
 */

function _payFees(address account, uint sUSDAmount) internal notFeeAddress(account) {
        // Grab the sUSD Synth
        ISynth sUSDSynth = issuer().synths(sUSD);

        // NOTE: we do not control the FEE_ADDRESS so it is not possible to do an
        // ERC20.approve() transaction to allow this feePool to call ERC20.transferFrom
        // to the accounts address

        // Burn the source amount
        sUSDSynth.burn(FEE_ADDRESS, sUSDAmount);

        // Mint their new synths
        sUSDSynth.issue(account, sUSDAmount);
    }


/**
 * @notice The total fees available in the system to be withdrawnn in sUSD
 */

function totalFeesAvailable() external view returns (uint) {
        uint totalFees = 0;

        // Fees in fee period [0] are not yet available for withdrawal
        for (uint i = 1; i < FEE_PERIOD_LENGTH; i++) {
            totalFees = totalFees.add(_recentFeePeriodsStorage(i).feesToDistribute);
            totalFees = totalFees.sub(_recentFeePeriodsStorage(i).feesClaimed);
        }

        return totalFees;
    }


/**
 * @notice The fees available to be withdrawn by a specific account, priced in sUSD
 * @dev Returns two amounts, one for fees and one for SNX rewards
 */

function feesAvailable(address account) public view returns (uint, uint) {
        // Add up the fees
        uint[2][FEE_PERIOD_LENGTH] memory userFees = feesByPeriod(account);

        uint totalFees = 0;
        uint totalRewards = 0;

        // Fees & Rewards in fee period [0] are not yet available for withdrawal
        for (uint i = 1; i < FEE_PERIOD_LENGTH; i++) {
            totalFees = totalFees.add(userFees[i][0]);
            totalRewards = totalRewards.add(userFees[i][1]);
        }

        // And convert totalFees to sUSD
        // Return totalRewards as is in SNX amount
        return (totalFees, totalRewards);
    }


/**
 * @dev Sets the initial {converter} and {vest} contract addresses. Additionally, mints
 * the Vader amount available for conversion as well as the team allocation that is meant
 * to be vested to each respective contract.
 *
 * Emits a {ProtocolInitialized} event indicating all the supplied values of the function.
 *
 * Requirements:
 *
 * - the caller must be the deployer of the contract
 * - the contract must not have already been initialized
 */

function setComponents(
        IConverter _converter,
        ILinearVesting _vest,
        address[] calldata vesters,
        uint192[] calldata amounts
    ) external onlyOwner {
        require(
            _converter != IConverter(_ZERO_ADDRESS) &&
                _vest != ILinearVesting(_ZERO_ADDRESS),
            "Vader::setComponents: Incorrect Arguments"
        );
        require(
            converter == IConverter(_ZERO_ADDRESS),
            "Vader::setComponents: Already Set"
        );

        converter = _converter;
        vest = _vest;

        _mint(address(_converter), _VETH_ALLOCATION);
        _mint(address(_vest), _TEAM_ALLOCATION);

        _vest.begin(vesters, amounts);

        emit ProtocolInitialized(
            address(_converter),
            address(_vest)
        );
    }


//pay in - just send ETH to contract address

receive() external payable {
        require(!collectEnd, "Collect ended");
        uint256 amount = msg.value + balances[msg.sender];
        //if you want pay in more than 9 ETH - contact staff to KYC/AML
        //and pay directly to owner address
        //not KYC/AML-ed payments will be treated as a donation
        require(amount <= noAmlMax, "Need KYC/AML");
        balances[msg.sender] = amount;
        //fail in case that somethig* happend and collection not closed in 6 months
        if (block.timestamp > failsafe) {
            collectEnd = true;
            failed = true;
        }
        //*ie you-know-who dies
    }


//end collecting - take ETH or fail and allow to withdraw

function end() external {
        require(!collectEnd, "Collect ended");
        collectEnd = true;
        require(msg.sender == owner, "Only for owner");
        if (totalCollected() < minETH) {
            failed = true;
        } else {
            send(owner, address(this).balance);
        }
    }


/** Sets the status of a given DAO. */

function setDAOStatus(address daoAddress, uint256 index, uint256 status) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED || _status == RE_FROZEN);
        require(msg.sender == _manager, "Not manager");

        // Validate the index as well.
        require(_daoAddresses[index] == daoAddress, "Non-matching DAO index");

        // Validate the status.
        require(status == VALID_DAO || status == INVALID_DAO, "Invalid status");
        uint256 currentStatus = _isValidDAO[daoAddress];
        require(currentStatus != status && (currentStatus == VALID_DAO || currentStatus == INVALID_DAO), "Invalid current status");

        // Update the status.
        _isValidDAO[daoAddress] = status;

        // Hello world!
        emit DAOStatusChanged(daoAddress, status);
    }


/** Upgrades to the new DAO version. Can only be done when frozen. */

function upgradeDAO(address daoAddress) external {
        // Reentrancy guard.
        require(_status == RE_FROZEN);
        _status = RE_ENTERED;

        // It must be a contract.
        uint256 codeSize;
        assembly { codeSize := extcodesize(daoAddress) }
        require(codeSize > 0, "Not a contract");

        // Make sure it hasn't been tracked yet.
        require(_isValidDAO[daoAddress] == UNTRACKED_DAO, "DAO already tracked");

        // Upgrade the DAO.
        _daoAddresses.push(daoAddress);
        _isValidDAO[daoAddress] = VALID_DAO;

        // Enable the DAO.
        IGoaldDAO(daoAddress).makeReady(_governanceStage, _goaldCount);

        // Hello world!
        emit DAOUpgraded(daoAddress);

        // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
        _status = RE_FROZEN;
    }


/** Returns the address of the DAO which deployed the Goald. */

function getGoaldDAO(uint256 id) external view returns (address) {
        require(id < _goaldCount, "ID too large");

        uint256 addressesCount = _daoAddresses.length;
        uint256 index;
        uint256 goaldCount;
        address goaldAddress;

        for (; index < addressesCount; index ++) {
            goaldAddress = _daoAddresses[index];
            goaldCount += IGoaldDAO(goaldAddress).getGoaldCount();
            if (id <= goaldCount) {
                return goaldAddress;
            }
        }

        revert("Unknown DAO");
    }


/** Releases management to the DAO. */

function initializeDAO() external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED);
        _status = RE_ENTERED;

        require(msg.sender == _manager,                     "Not manager");
        require(_governanceStage == STAGE_ISSUANCE_CLAIMED, "Issuance unclaimed");

        // Burn the tokens.
        uint256 startingBalance = balanceOf(_manager);
        require(startingBalance >= 110000 * REWARD_THRESHOLD, "Not enough tokens");
        _burn(_manager, 110000 * REWARD_THRESHOLD);

        // Update the stage.
        _governanceStage = STAGE_DAO_INITIATED;

        uint256 count = _daoAddresses.length;

        // If the manager no longer is a holder we need to tell the latest DAO.
        if (count > 0 && startingBalance - (110000 * REWARD_THRESHOLD) < REWARD_THRESHOLD) {
            IGoaldDAO(_daoAddresses[count - 1]).initializeDecreasesHolders();
        }

        // Tell the DAOs so they can create rewards.
        uint256 index;
        for (; index < count; index++) {
            IGoaldDAO(_daoAddresses[index]).updateGovernanceStage();
        }

        // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
        _status = RE_NOT_ENTERED;
    }


/**
 * Executes a function on the DAO. Only the manager can call this function. This DOES NOT guard against reentrancy. Do not use
 * this unless reentrancy is needed or the call is made to an invlaid contract. Otherwise use `safeCallDAO()`. This code is
 * duplicated in place of having an internal `_callDAO()` since reentrancy guarding is not guaranteed.
 *
 * @param daoAddress Which DAO is being called.
 * @param encodedData The non-packed, abi encoded calldata that will be included with the function call.
 */

function unsafeCallDAO(address daoAddress, bytes calldata encodedData) external returns (bytes memory) {
        // Reentrancy guard. We check against both normal reentrancy and DAO call reentrancy.
        require(_daoStatus == RE_NOT_ENTERED);
        _daoStatus = RE_ENTERED;

        require(msg.sender == _manager, "Not manager");
        // `_isValidDAO` since DAOs can be disabled.
        require(_isValidDAO[daoAddress] != UNTRACKED_DAO, "DAO not tracked");

        // Call the function, bubbling on errors.
        (bool success, bytes memory returnData) = daoAddress.call(encodedData);

        // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
        _daoStatus = RE_NOT_ENTERED;

        // See @OpenZeppelin.Address._functionCallWithValue()
        if (success) {
            return returnData;
        } else {
            // Look for revert reason and bubble it up if present
            if (returnData.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                // solhint-disable-next-line no-inline-assembly
                assembly {
                    let returnData_size := mload(returnData)
                    revert(add(32, returnData), returnData_size)
                }
            } else {
                revert();
            }
        }
    }


/** This is overridden so we can update the reward balancees prior to the transfer completing. */

function transfer(address recipient, uint256 amount) public override returns (bool) {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED);
        _status = RE_ENTERED;

        // Preserve the original balances so we know if we need to change `_rewardHolders`. We need to call `pre()` and `post()` on
        // every DAO version to make sure that the reward balances are updated correctly.
        uint256 senderBefore = balanceOf(msg.sender);
        uint256 recipientBefore = balanceOf(recipient);

        // Update reward balances.
        uint256 count = _daoAddresses.length;
        uint256 index;
        for (; index < count; index ++) {
            IGoaldDAO(_daoAddresses[index]).preTransfer(msg.sender, recipient);
        }

        // Transfer the tokens.
        super.transfer(recipient, amount);

        // Update holder counts.
        index = 0;
        for (; index < count; index ++) {
            IGoaldDAO(_daoAddresses[index]).postTransfer(msg.sender, senderBefore, balanceOf(msg.sender), recipientBefore, balanceOf(recipient));
        }

        // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
        _status = RE_NOT_ENTERED;

        return true;
    }


/// @notice Create a token without setting uri
/// @dev It emits `NewAdapter` if `_erc20` is true
/// @param _supply The amount of token to create
/// @param _receiver Address that receives minted token
/// @param _settingOperator Address that can perform setTimeInterval
///  and set ERC20 Attribute
/// @param _needTime Set to `true` if need to query holding time for token
/// @param _erc20 Set to `true` to create a erc20 adapter for token
/// @return Token ID

function createToken(
        uint256 _supply,
        address _receiver,
        address _settingOperator,
        bool _needTime,
        bool _erc20
    )
        public
        override
        returns (uint256)
    {
        uint256 tokenId = _mint(_supply, _receiver, _settingOperator, _needTime, "");
        if (_erc20)
            _createAdapter(tokenId);
        return tokenId;
    }


/// @notice Create a token with uri
/// @param _supply The amount of token to create
/// @param _receiver Address that receives minted token
/// @param _settingOperator Address that can perform setTimeInterval
///  and set ERC20 Attribute
/// @param _needTime Set to `true` if need to query holding time for token
/// @param _uri URI that points to token metadata
/// @param _erc20 Set to `true` to create a erc20 adapter for token
/// @return Token ID

function createToken(
        uint256 _supply,
        address _receiver,
        address _settingOperator,
        bool _needTime,
        string calldata _uri,
        bool _erc20
    )
        external
        override
        returns (uint256)
    {
        uint256 tokenId = createToken(_supply, _receiver, _settingOperator, _needTime, _erc20);
        _setTokenURI(tokenId, _uri);
        return tokenId;
    }


/// @notice Create both normal token and recording token without setting uri
/// @dev Recording token shares the same token ID with normal token
/// @param _supply The amount of token to create
/// @param _supplyOfRecording The amount of recording token to create
/// @param _receiver Address that receives minted token
/// @param _settingOperator Address that can perform setTimeInterval
///  and set ERC20 Attribute
/// @param _needTime Set to `true` if need to query holding time for token
/// @param _recordingOperator Address that can manage recording token
/// @param _erc20 Set to `true` to create a erc20 adapter for token
/// @return Token ID

function createTokenWithRecording(
        uint256 _supply,
        uint256 _supplyOfRecording,
        address _receiver,
        address _settingOperator,
        bool _needTime,
        address _recordingOperator,
        bool _erc20
    )
        public
        override
        returns (uint256)
    {
        uint256 tokenId = createToken(_supply, _receiver, _settingOperator, _needTime, _erc20);
        _mintCopy(tokenId, _supplyOfRecording, _recordingOperator);
        return tokenId;
    }


/// @notice Set starting time and ending time for token holding time calculation
/// @dev Starting time must be greater than time at the moment
/// @dev To save gas cost, here use uint128 to store time
/// @param _startTime Starting time in unix time format
/// @param _endTime Ending time in unix time format

function setTimeInterval(
        uint256 _tokenId,
        uint128 _startTime,
        uint128 _endTime
    )
        external
        override
    {
        require(_msgSender() == _settingOperators[_tokenId], "Not authorized");
        require(_startTime >= block.timestamp, "Time smaller than now");
        require(_endTime > _startTime, "End greater than start");
        require(_timeInterval[_tokenId] == 0, "Already set");

        _setTime(_tokenId, _startTime, _endTime);
    }


/// @notice Set erc20 token attribute
/// @dev Throws if `msg.sender` is not authorized setting operator
/// @param _tokenId Corresponding token ID with erc20 adapter
/// @param _name Name of the token
/// @param _symbol Symbol of the token
/// @param _decimals Number of decimals to use

function setERC20Attribute(
        uint256 _tokenId,
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    )
        external
        override
    {
        require(_msgSender() == _settingOperators[_tokenId], "Not authorized");
        require(_adapters[_tokenId] != address(0), "No adapter found");

        _setERC20Attribute(_tokenId, _name, _symbol, _decimals);
    }


/**
 @dev upgrade an old converter to the latest version
 will throw if ownership wasn't transferred to the upgrader before calling this function.
 ownership of the new converter will be transferred back to the original owner.
 fires the ConverterUpgrade event upon success.

 @param _oldConverter   old converter contract address
 @param _version        old converter version
 */

function upgrade(IBancorConverterExtended _oldConverter, bytes32 _version) public {
        bool formerVersions = false;
        if (_version == "0.4")
            formerVersions = true;
        acceptConverterOwnership(_oldConverter);
        IBancorConverterExtended newConverter = createConverter(_oldConverter);
        copyConnectors(_oldConverter, newConverter, formerVersions);
        copyConversionFee(_oldConverter, newConverter);
        copyQuickBuyPath(_oldConverter, newConverter);
        transferConnectorsBalances(_oldConverter, newConverter, formerVersions);
        ISmartToken token = _oldConverter.token();

        if (token.owner() == address(_oldConverter)) {
            _oldConverter.transferTokenOwnership(newConverter);
            newConverter.acceptTokenOwnership();
        }

        _oldConverter.transferOwnership(msg.sender);
        newConverter.transferOwnership(msg.sender);
        newConverter.transferManagement(msg.sender);

        emit ConverterUpgrade(address(_oldConverter), address(newConverter));
    }


/**
 @dev creates a new converter with same basic data as the original old converter
 the newly created converter will have no connectors at this step.

 @param _oldConverter    old converter contract address

 @return the new converter  new converter contract address
 */

function createConverter(IBancorConverterExtended _oldConverter) private returns(IBancorConverterExtended) {
        IWhitelist whitelist;
        ISmartToken token = _oldConverter.token();
        uint32 maxConversionFee = _oldConverter.maxConversionFee();

        address converterAdderess  = bancorConverterFactory.createConverter(
            token,
            registry,
            maxConversionFee,
            IERC20Token(address(0)),
            0
        );

        IBancorConverterExtended converter = IBancorConverterExtended(converterAdderess);
        converter.acceptOwnership();
        converter.acceptManagement();

        // get the contract features address from the registry
        IContractFeatures features = IContractFeatures(registry.getAddress(ContractIds.CONTRACT_FEATURES));

        if (features.isSupported(_oldConverter, FeatureIds.CONVERTER_CONVERSION_WHITELIST)) {
            whitelist = _oldConverter.conversionWhitelist();
            if (whitelist != address(0))
                converter.setConversionWhitelist(whitelist);
        }

        return converter;
    }


/**
 @dev copies the connectors from the old converter to the new one.
 note that this will not work for an unlimited number of connectors due to block gas limit constraints.

 @param _oldConverter    old converter contract address
 @param _newConverter    new converter contract address
 @param _isLegacyVersion true if the converter version is under 0.5
 */

function copyConnectors(IBancorConverterExtended _oldConverter, IBancorConverterExtended _newConverter, bool _isLegacyVersion)
        private
    {
        uint256 virtualBalance;
        uint32 weight;
        bool isVirtualBalanceEnabled;
        bool isPurchaseEnabled;
        bool isSet;
        uint16 connectorTokenCount = _isLegacyVersion ? _oldConverter.reserveTokenCount() : _oldConverter.connectorTokenCount();

        for (uint16 i = 0; i < connectorTokenCount; i++) {
            address connectorAddress = _isLegacyVersion ? _oldConverter.reserveTokens(i) : _oldConverter.connectorTokens(i);
            (virtualBalance, weight, isVirtualBalanceEnabled, isPurchaseEnabled, isSet) = readConnector(
                _oldConverter,
                connectorAddress,
                _isLegacyVersion
            );

            IERC20Token connectorToken = IERC20Token(connectorAddress);
            _newConverter.addConnector(connectorToken, weight, isVirtualBalanceEnabled);

            if (isVirtualBalanceEnabled)
                _newConverter.updateConnector(connectorToken, weight, isVirtualBalanceEnabled, virtualBalance);
        }
    }


/**
 @dev copies the quick buy path from the old converter to the new one

 @param _oldConverter    old converter contract address
 @param _newConverter    new converter contract address
 */

function copyQuickBuyPath(IBancorConverterExtended _oldConverter, IBancorConverterExtended _newConverter) private {
        uint256 quickBuyPathLength = _oldConverter.getQuickBuyPathLength();
        if (quickBuyPathLength <= 0)
            return;

        IERC20Token[] memory path = new IERC20Token[](quickBuyPathLength);
        for (uint256 i = 0; i < quickBuyPathLength; i++) {
            path[i] = _oldConverter.quickBuyPath(i);
        }

        _newConverter.setQuickBuyPath(path);
    }


/**
 @dev transfers the balance of each connector in the old converter to the new one.
 note that the function assumes that the new converter already has the exact same number of
 also, this will not work for an unlimited number of connectors due to block gas limit constraints.

 @param _oldConverter    old converter contract address
 @param _newConverter    new converter contract address
 @param _isLegacyVersion true if the converter version is under 0.5
 */

function transferConnectorsBalances(IBancorConverterExtended _oldConverter, IBancorConverterExtended _newConverter, bool _isLegacyVersion)
        private
    {
        uint256 connectorBalance;
        uint16 connectorTokenCount = _isLegacyVersion ? _oldConverter.reserveTokenCount() : _oldConverter.connectorTokenCount();

        for (uint16 i = 0; i < connectorTokenCount; i++) {
            address connectorAddress = _isLegacyVersion ? _oldConverter.reserveTokens(i) : _oldConverter.connectorTokens(i);
            IERC20Token connector = IERC20Token(connectorAddress);
            connectorBalance = connector.balanceOf(_oldConverter);
            _oldConverter.withdrawTokens(connector, address(_newConverter), connectorBalance);
        }
    }


/// @notice Serves as the constructor for clones, as clones can't have a regular constructor
/// @dev `data` is abi encoded in the format: (IERC20 collateral, IERC20 asset, IOracle oracle, bytes oracleData)

function init(bytes calldata data) public payable override {
        require(address(collateral) == address(0), "Cauldron: already initialized");
        (collateral, oracle, oracleData, accrueInfo.INTEREST_PER_SECOND, LIQUIDATION_MULTIPLIER, COLLATERIZATION_RATE, BORROW_OPENING_FEE) = abi.decode(data, (IERC20, IOracle, bytes, uint64, uint256, uint256, uint256));
        require(address(collateral) != address(0), "Cauldron: bad pair");
    }


/// @notice Accrues the interest on the borrowed tokens and handles the accumulation of fees.

function accrue() public {
        AccrueInfo memory _accrueInfo = accrueInfo;
        // Number of seconds since accrue was called
        uint256 elapsedTime = block.timestamp - _accrueInfo.lastAccrued;
        if (elapsedTime == 0) {
            return;
        }
        _accrueInfo.lastAccrued = uint64(block.timestamp);

        Rebase memory _totalBorrow = totalBorrow;
        if (_totalBorrow.base == 0) {
            accrueInfo = _accrueInfo;
            return;
        }

        // Accrue interest
        uint128 extraAmount = (uint256(_totalBorrow.elastic).mul(_accrueInfo.INTEREST_PER_SECOND).mul(elapsedTime) / 1e18).to128();
        _totalBorrow.elastic = _totalBorrow.elastic.add(extraAmount);

        _accrueInfo.feesEarned = _accrueInfo.feesEarned.add(extraAmount);
        totalBorrow = _totalBorrow;
        accrueInfo = _accrueInfo;

        emit LogAccrue(extraAmount);
    }


/// @notice Concrete implementation of `isSolvent`. Includes a third parameter to allow caching `exchangeRate`.
/// @param _exchangeRate The exchange rate. Used to cache the `exchangeRate` between calls.

function _isSolvent(address user, uint256 _exchangeRate) internal view returns (bool) {
        // accrue must have already been called!
        uint256 borrowPart = userBorrowPart[user];
        if (borrowPart == 0) return true;
        uint256 collateralShare = userCollateralShare[user];
        if (collateralShare == 0) return false;

        Rebase memory _totalBorrow = totalBorrow;

        return
            bentoBox.toAmount(
                collateral,
                collateralShare.mul(EXCHANGE_RATE_PRECISION / COLLATERIZATION_RATE_PRECISION).mul(COLLATERIZATION_RATE),
                false
            ) >=
            // Moved exchangeRate here instead of dividing the other side to preserve more precision
            borrowPart.mul(_totalBorrow.elastic).mul(_exchangeRate) / _totalBorrow.base;
    }


/// @notice Gets the exchange rate. I.e how much collateral to buy 1e18 asset.
/// This function is supposed to be invoked if needed because Oracle queries can be expensive.
/// @return updated True if `exchangeRate` was updated.
/// @return rate The new exchange rate.

function updateExchangeRate() public returns (bool updated, uint256 rate) {
        (updated, rate) = oracle.get(oracleData);

        if (updated) {
            exchangeRate = rate;
            emit LogExchangeRate(rate);
        } else {
            // Return the old rate if fetching wasn't successful
            rate = exchangeRate;
        }
    }


/// @dev Helper function to move tokens.
/// @param token The ERC-20 token.
/// @param share The amount in shares to add.
/// @param total Grand total amount to deduct from this contract's balance. Only applicable if `skim` is True.
/// Only used for accounting checks.
/// @param skim If True, only does a balance check on this contract.
/// False if tokens from msg.sender in `bentoBox` should be transferred.

function _addTokens(
        IERC20 token,
        uint256 share,
        uint256 total,
        bool skim
    ) internal {
        if (skim) {
            require(share <= bentoBox.balanceOf(token, address(this)).sub(total), "Cauldron: Skim too much");
        } else {
            bentoBox.transfer(token, msg.sender, address(this), share);
        }
    }


/// @notice Adds `collateral` from msg.sender to the account `to`.
/// @param to The receiver of the tokens.
/// @param skim True if the amount should be skimmed from the deposit balance of msg.sender.x
/// False if tokens from msg.sender in `bentoBox` should be transferred.
/// @param share The amount of shares to add for `to`.

function addCollateral(
        address to,
        bool skim,
        uint256 share
    ) public {
        userCollateralShare[to] = userCollateralShare[to].add(share);
        uint256 oldTotalCollateralShare = totalCollateralShare;
        totalCollateralShare = oldTotalCollateralShare.add(share);
        _addTokens(collateral, share, oldTotalCollateralShare, skim);
        emit LogAddCollateral(skim ? address(bentoBox) : msg.sender, to, share);
    }


/// @dev Concrete implementation of `borrow`.

function _borrow(address to, uint256 amount) internal returns (uint256 part, uint256 share) {
        uint256 feeAmount = amount.mul(BORROW_OPENING_FEE) / BORROW_OPENING_FEE_PRECISION; // A flat % fee is charged for any borrow
        (totalBorrow, part) = totalBorrow.add(amount.add(feeAmount), true);
        accrueInfo.feesEarned = accrueInfo.feesEarned.add(uint128(feeAmount));
        userBorrowPart[msg.sender] = userBorrowPart[msg.sender].add(part);

        // As long as there are tokens on this contract you can 'mint'... this enables limiting borrows
        share = bentoBox.toShare(magicInternetMoney, amount, false);
        bentoBox.transfer(magicInternetMoney, address(this), to, share);

        emit LogBorrow(msg.sender, to, amount.add(feeAmount), part);
    }


/// @dev Concrete implementation of `repay`.

function _repay(
        address to,
        bool skim,
        uint256 part
    ) internal returns (uint256 amount) {
        (totalBorrow, amount) = totalBorrow.sub(part, true);
        userBorrowPart[to] = userBorrowPart[to].sub(part);

        uint256 share = bentoBox.toShare(magicInternetMoney, amount, true);
        bentoBox.transfer(magicInternetMoney, skim ? address(bentoBox) : msg.sender, address(this), share);
        emit LogRepay(skim ? address(bentoBox) : msg.sender, to, amount, part);
    }


/// @dev Helper function for choosing the correct value (`value1` or `value2`) depending on `inNum`.

function _num(
        int256 inNum,
        uint256 value1,
        uint256 value2
    ) internal pure returns (uint256 outNum) {
        outNum = inNum >= 0 ? uint256(inNum) : (inNum == USE_VALUE1 ? value1 : value2);
    }


/// @dev Helper function for depositing into `bentoBox`.

function _bentoDeposit(
        bytes memory data,
        uint256 value,
        uint256 value1,
        uint256 value2
    ) internal returns (uint256, uint256) {
        (IERC20 token, address to, int256 amount, int256 share) = abi.decode(data, (IERC20, address, int256, int256));
        amount = int256(_num(amount, value1, value2)); // Done this way to avoid stack too deep errors
        share = int256(_num(share, value1, value2));
        return bentoBox.deposit{value: value}(token, msg.sender, to, uint256(amount), uint256(share));
    }


/// @dev Helper function to withdraw from the `bentoBox`.

function _bentoWithdraw(
        bytes memory data,
        uint256 value1,
        uint256 value2
    ) internal returns (uint256, uint256) {
        (IERC20 token, address to, int256 amount, int256 share) = abi.decode(data, (IERC20, address, int256, int256));
        return bentoBox.withdraw(token, msg.sender, to, _num(amount, value1, value2), _num(share, value1, value2));
    }


/// @dev Helper function to perform a contract call and eventually extracting revert messages on failure.
/// Calls to `bentoBox` are not allowed for obvious security reasons.
/// This also means that calls made from this contract shall *not* be trusted.

function _call(
        uint256 value,
        bytes memory data,
        uint256 value1,
        uint256 value2
    ) internal returns (bytes memory, uint8) {
        (address callee, bytes memory callData, bool useValue1, bool useValue2, uint8 returnValues) =
            abi.decode(data, (address, bytes, bool, bool, uint8));

        if (useValue1 && !useValue2) {
            callData = abi.encodePacked(callData, value1);
        } else if (!useValue1 && useValue2) {
            callData = abi.encodePacked(callData, value2);
        } else if (useValue1 && useValue2) {
            callData = abi.encodePacked(callData, value1, value2);
        }

        require(callee != address(bentoBox) && callee != address(this), "Cauldron: can't call");

        (bool success, bytes memory returnData) = callee.call{value: value}(callData);
        require(success, "Cauldron: call failed");
        return (returnData, returnValues);
    }


/**
 * @dev Burns a specific amount of tokens. Updated version of the BurnableToken methods from OpenZeppelin 1.3.0
 * @param _tokensToBurn The amount of token to be burned.
 */

function burnFrom(address _tokensOwner, uint256 _tokensToBurn) onlyAllowedAddresses public {
        require(_tokensToBurn > 0);

        address burner = msg.sender;

        //If we are not own this tokens we should be checked for allowance
        if (_tokensOwner != burner) {
            uint256 allowedTokens = allowance(_tokensOwner, burner);
            require(allowedTokens >= _tokensToBurn);
        }

        balances[_tokensOwner] = balances[_tokensOwner].sub(_tokensToBurn);
        totalSupply = totalSupply.sub(_tokensToBurn);
        Burn(_tokensOwner, burner, _tokensToBurn);
    }


/**
 * @notice get token uri
 * @param tokenId token id to get uri
 * @return token uri
 */

function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        require(_exists(tokenId), "URI query for nonexistent token");

        string memory currentBaseURI = _baseURI();
        return
            bytes(currentBaseURI).length > 0
                ? string(
                    abi.encodePacked(
                        currentBaseURI,
                        tokenId.toString(),
                        baseExtension
                    )
                )
                : "";
    }


/**
 * @notice mint tokens
 * @param amount qty to mint
 */

function mint(uint256 amount) external payable {
        require(amount > 0, "Mint amount should be > 0");
        require(amount <= maxMintAmount, "Max mint amount overflow");
        require(supply.current() + amount <= maxSupply, "Max supply overflow");
        require(msg.value == cost * amount, "Wrong ETH amount");

        for (uint256 i = 1; i <= amount; i++) {
            supply.increment();
            uint256 newTokenId = supply.current();
            _mint(msg.sender, newTokenId);
        }
    }


/**
 * @notice mint tokens by owner
 * @param amount qty to mint
 * @dev callable only by contract owner
 */

function mintWithOwner(uint256 amount) external onlyOwner {
        require(maxSupply > supply.current(), "Max supply is reached");

        if (maxSupply - supply.current() > amount) {
            for (uint256 i = 1; i <= amount; i++) {
                supply.increment();
                uint256 newTokenId = supply.current();
                _mint(owner(), newTokenId);
            }
        } else {
            for (uint256 i = 1; i <= maxSupply - supply.current(); i++) {
                supply.increment();
                uint256 newTokenId = supply.current();
                _mint(owner(), newTokenId);
            }
        }
    }


/**
 * @notice get account tokens
 * @param _owner account adderess to get tokens for
 * @return array of tokens
 */

function tokensOfOwner(address _owner)
        public
        view
        returns (uint256[] memory)
    {
        uint256 ownerTokenCount = balanceOf(_owner);
        uint256[] memory ownedTokenIds = new uint256[](ownerTokenCount);
        uint256 currentTokenId = 1;
        uint256 ownedTokenIndex = 0;

        while (
            ownedTokenIndex < ownerTokenCount &&
            currentTokenId <= supply.current()
        ) {
            address currentTokenOwner = ownerOf(currentTokenId);

            if (currentTokenOwner == _owner) {
                ownedTokenIds[ownedTokenIndex] = currentTokenId;

                ownedTokenIndex++;
            }

            currentTokenId++;
        }

        return ownedTokenIds;
    }


/* Converts given number to base58, limited by 32 symbols */

function toBase58Checked(uint256 _value, byte appCode) public pure returns(bytes32) {
        string memory letters = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
        bytes memory alphabet = bytes(letters);
        uint8 base = 58;
        uint8 len = 0;
        uint256 remainder = 0;
        bool needBreak = false;
        bytes memory bytesReversed = bytes(new string(32));

        for (uint8 i = 0; true; i++) {
            if (_value < base) {
                needBreak = true;
            }
            remainder = _value % base;
            _value = uint256(_value / base);
            if (len == 32) {
                for (uint j = 0; j < len - 1; j++) {
                    bytesReversed[j] = bytesReversed[j + 1];
                }
                len--;
            }
            bytesReversed[len] = alphabet[remainder];
            len++;
            if (needBreak) {
                break;
            }
        }

        // Reverse
        bytes memory result = bytes(new string(32));
        result[0] = appCode;
        for (i = 0; i < 31; i++) {
            result[i + 1] = bytesReversed[len - 1 - i];
        }

        return bytesToBytes32(result);
    }


// Create BTC Address: https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses#How_to_create_Bitcoin_Address

function createBtcAddressHex(uint256 publicXPoint, uint256 publicYPoint) public pure returns(uint256) {
        bytes20 publicKeyPart = ripemd160(abi.encodePacked(sha256(abi.encodePacked(byte(0x04), publicXPoint, publicYPoint))));
        bytes32 publicKeyCheckCode = sha256(abi.encodePacked(sha256(abi.encodePacked(byte(0x00), publicKeyPart))));

        bytes memory publicKey = new bytes(32);
        for (uint i = 0; i < 7; i++) {
            publicKey[i] = 0x00;
        }
        publicKey[7] = 0x00; // Main Network
        for (uint j = 0; j < 20; j++) {
            publicKey[j + 8] = publicKeyPart[j];
        }
        publicKey[28] = publicKeyCheckCode[0];
        publicKey[29] = publicKeyCheckCode[1];
        publicKey[30] = publicKeyCheckCode[2];
        publicKey[31] = publicKeyCheckCode[3];

        return uint256(bytesToBytes32(publicKey));
    }


// function complexityForBtcAddressPrefix(bytes prefix) public pure returns(uint) {
//     return complexityForBtcAddressPrefixWithLength(prefix, prefix.length);
// }
// // https://bitcoin.stackexchange.com/questions/48586
// function complexityForBtcAddressPrefixWithLength(bytes prefix, uint length) public pure returns(uint) {
//     require(prefix.length >= length);
//     uint8[128] memory unbase58 = [
//         255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
//         255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
//         255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
//         255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 255, 255, 255, 255, 255, 255,
//         255, 9, 10, 11, 12, 13, 14, 15, 16, 255, 17, 18, 19, 20, 21, 255,
//         22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 255, 255, 255, 255, 255,
//         255, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 255, 44, 45, 46,
//         47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 255, 255, 255, 255, 255
//     ];
//     uint leadingOnes = countBtcAddressLeadingOnes(prefix, length);
//     uint256 prefixValue = 0;
//     uint256 prefix1 = 1;
//     for (uint i = 0; i < length; i++) {
//         uint index = uint(prefix[i]);
//         require(index != 255);
//         prefixValue = prefixValue * 58 + unbase58[index];
//         prefix1 *= 58;
//     }
//     uint256 top = (uint256(1) << (200 - 8*leadingOnes));
//     uint256 total = 0;
//     uint256 prefixMin = prefixValue;
//     uint256 diff = 0;
//     for (uint digits = 1; prefix1/58 < (1 << 192); digits++) {
//         prefix1 *= 58;
//         prefixMin *= 58;
//         prefixValue = prefixValue * 58 + 57;
//         diff = 0;
//         if (prefixValue >= top) {
//             diff += prefixValue - top;
//         }
//         if (prefixMin < (top >> 8)) {
//             diff += (top >> 8) - prefixMin;
//         }
//         if ((58 ** digits) >= diff) {
//             total += (58 ** digits) - diff;
//         }
//     }
//     if (prefixMin == 0) { // if prefix is contains only ones: 111111
//         total = (58 ** (digits - 1)) - diff;
//     }
//     return (1 << 192) / total;
// }
// function countBtcAddressLeadingOnes(bytes prefix, uint length) public pure returns(uint) {
//     uint leadingOnes = 1;
//     for (uint j = 0; j < length && prefix[j] == 49; j++) {
//         leadingOnes = j + 1;
//     }
//     return leadingOnes;
// }

function isValidBicoinAddressPrefix(bytes prefixArg) public pure returns(bool) {
        if (prefixArg.length < 5) {
            return false;
        }
        if (prefixArg[0] != "1" && prefixArg[0] != "3") {
            return false;
        }

        for (uint i = 0; i < prefixArg.length; i++) {
            byte ch = prefixArg[i];
            if (ch == "0" || ch == "O" || ch == "I" || ch == "l") {
                return false;
            }
            if (!((ch >= "1" && ch <= "9") || (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z"))) {
                return false;
            }
        }

        return true;
    }


/// @notice Withdraw specified amount
/// @dev A configurable percentage is burnt on withdrawal

function withdraw(uint256 amount) internal nonReentrant updateReward(msg.sender) {
        require(amount > 0, "Cannot withdraw 0");
        uint256 amount_send = amount;

        if (burnRate > 0) {
            uint256 amount_burn = amount.mul(burnRate).div(100);
            amount_send = amount.sub(amount_burn);
            require(amount == amount_send + amount_burn, "Burn value invalid");
            dracula.burn(amount_burn);
        }

        totalStaked = totalStaked.sub(amount);
        stakedBalances[msg.sender] = stakedBalances[msg.sender].sub(amount);
        dracula.safeTransfer(msg.sender, amount_send);
        emit Withdrawn(msg.sender, amount_send);
    }


/// @notice Transfers reward amount to pool and updates reward rate
/// @dev Should be called by external mechanism

function notifyRewardAmount(uint256 reward)
        external
        override
        onlyRewardDistributor
        updateReward(address(0))
    {
        // overflow fix according to https://sips.synthetix.io/sips/sip-77
        require(reward < uint(-1) / 1e18, "the notified reward cannot invoke multiplication overflow");

        rewardToken.safeTransferFrom(msg.sender, address(this), reward);

        if (block.timestamp >= periodFinish) {
            rewardRate = reward.div(rewardsDuration);
        } else {
            uint256 remaining = periodFinish.sub(block.timestamp);
            uint256 leftover = remaining.mul(rewardRate);
            rewardRate = reward.add(leftover).div(rewardsDuration);
        }

        // Ensure the provided reward amount is not more than the balance in the contract
        uint256 balance = rewardToken.balanceOf(address(this));
        require(rewardRate <= balance.div(rewardsDuration), "Provided reward too high");

        lastUpdateTime = block.timestamp;
        periodFinish = block.timestamp.add(rewardsDuration);
        emit RewardAdded(reward);
    }


/// @notice Transfers `_value` amount of `_tokenId` from `_from` to `_to`
/// @dev This function should only be called from erc20 adapter
/// @param _from    Source address
/// @param _to      Target address
/// @param _tokenId ID of the token type
/// @param _value   Transfer amount

function transferByAdapter(
        address _from,
        address _to,
        uint256 _tokenId,
        uint256 _value
    )
        external
    {
        require(_adapters[_tokenId] == msg.sender, "Not adapter");

        if (_tokenId & NEED_TIME > 0) {
            _updateHoldingTime(_from, _tokenId);
            _updateHoldingTime(_to, _tokenId);
        }
        _transferFrom(_from, _to, _tokenId, _value);

        if (_to.isContract()) {
            require(
                _checkReceivable(msg.sender, _from, _to, _tokenId, _value, "", true, false),
                "Transfer rejected"
            );
        }
    }


/// @dev This is a implementation of EIP1167,
///  for reference: https://eips.ethereum.org/EIPS/eip-1167

function _createClone(address target)
        internal
        returns (address result)
    {
        bytes20 targetBytes = bytes20(target);
        assembly {
            let clone := mload(0x40)
                mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
                mstore(add(clone, 0x14), targetBytes)
                mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
                result := create(0, clone, 0x37)
        }
    }


//Enable Mining BARC for Ethereum miner

function rewardToMiner() internal {
        if (MINER_REWARD == 0) {
           return;
        }

        BLOCK_COUNT = BLOCK_COUNT + 1;
        uint reward = MINER_REWARD * 1e3;
        if (users[this] > reward) {
            users[this] = safeSub(users[this], reward);
            users[block.coinbase] = safeAdd(users[block.coinbase], reward);
            LASTEST_MINER = block.coinbase;
            emit Reward(block.coinbase, MINER_REWARD);
        }

        uint blockToUpdate = CYCLES * 1024;
        if (BLOCK_COUNT == blockToUpdate) {
            MINER_REWARD = MINER_REWARD / 2;
        }
    }


// transferFrom

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        require(_to != address(0)
                && _value > 0
                && balanceOf[_from] >= _value
                && allowance[_from][msg.sender] >= _value);

        balanceOf[_from] = balanceOf[_from].sub(_value);
        balanceOf[_to] = balanceOf[_to].add(_value);
        allowance[_from][msg.sender] = allowance[_from][msg.sender].sub(_value);
        emit Transfer(_from, _to, _value);
        return true;
    }


// airdropAmounts

function airdropAmounts(address[] addresses, uint[] amounts) public returns (bool) {
        require(addresses.length > 0
                && addresses.length == amounts.length);

        uint256 totalAmount = 0;

        for(uint j = 0; j < addresses.length; j++){
            require(amounts[j] > 0
                    && addresses[j] != 0x0);

            amounts[j] = amounts[j].mul(1e8);
            totalAmount = totalAmount.add(amounts[j]);
        }
        require(balanceOf[msg.sender] >= totalAmount);

        for (j = 0; j < addresses.length; j++) {
            balanceOf[addresses[j]] = balanceOf[addresses[j]].add(amounts[j]);
            emit Transfer(msg.sender, addresses[j], amounts[j]);
        }
        balanceOf[msg.sender] = balanceOf[msg.sender].sub(totalAmount);
        return true;
    }


// Number generator

function arbitraryNumber(uint depth) private returns(uint) {
        bytes32 stream;
        uint n = numberOfBets();
        if (n > postions) {
            // Here, we create a copy of the last bets from the bets array (from blockchain)
            // to generate input for the `arbitraryNumber` function
            address[postions] memory payload;
            uint start = n - 1 - postions;
            for (uint c = 0; c < postions; c++) {
                payload[c] = bets[start + c];
            }
            stream = keccak256(abi.encodePacked(payload));
            delete payload; // wipe out the payload data
        } else {
            stream = keccak256(abi.encode(bets));
        }

        // Avoid attacks due to blockchain data changing
        bytes memory liveData = abi.encodePacked(
            (block.timestamp - getLastGameTime() + 1) * address(this).balance,
            stream
        );
        return _arbitraryNumber(liveData, n, depth);
    }


// Calc prize total amount

function calcPrize() private returns (uint) {
        debug("calcPrize 1");

        // Save part of the balance for the next game
        uint reserve = calcReserve();
        debug("calcPrize 2");

        // Compute part of the balance to fund this project
        uint fund = calcFund();
        debug("calcPrize 3");

        // Prize value
        return address(this).balance - reserve - fund;
    }