/**
 * @notice Sets the collateralFactor for a iToken
 * @dev Admin function to set collateralFactor for a iToken
 * @param _iToken The token to set the factor on
 * @param _newCollateralFactorMantissa The new collateral factor, scaled by 1e18
 */

function _setCollateralFactor(
        address _iToken,
        uint256 _newCollateralFactorMantissa
    ) external override onlyOwner {
        _checkiTokenListed(_iToken);

        require(
            _newCollateralFactorMantissa <= collateralFactorMaxMantissa,
            "Collateral factor invalid"
        );

        // Its value will be taken into account when calculate account equity
        // Check if the price is available for the calculation
        require(
            IPriceOracle(priceOracle).getUnderlyingPrice(_iToken) != 0,
            "Failed to set collateral factor, underlying price is unavailable"
        );

        Market storage _market = markets[_iToken];
        uint256 _oldCollateralFactorMantissa = _market.collateralFactorMantissa;
        _market.collateralFactorMantissa = _newCollateralFactorMantissa;

        emit NewCollateralFactor(
            _iToken,
            _oldCollateralFactorMantissa,
            _newCollateralFactorMantissa
        );
    }


/**
 * @notice Sets the borrowFactor for a iToken
 * @dev Admin function to set borrowFactor for a iToken
 * @param _iToken The token to set the factor on
 * @param _newBorrowFactorMantissa The new borrow factor, scaled by 1e18
 */

function _setBorrowFactor(address _iToken, uint256 _newBorrowFactorMantissa)
        external
        override
        onlyOwner
    {
        _checkiTokenListed(_iToken);

        require(
            _newBorrowFactorMantissa > 0 &&
                _newBorrowFactorMantissa <= borrowFactorMaxMantissa,
            "Borrow factor invalid"
        );

        // Its value will be taken into account when calculate account equity
        // Check if the price is available for the calculation
        require(
            IPriceOracle(priceOracle).getUnderlyingPrice(_iToken) != 0,
            "Failed to set borrow factor, underlying price is unavailable"
        );

        Market storage _market = markets[_iToken];
        uint256 _oldBorrowFactorMantissa = _market.borrowFactorMantissa;
        _market.borrowFactorMantissa = _newBorrowFactorMantissa;

        emit NewBorrowFactor(
            _iToken,
            _oldBorrowFactorMantissa,
            _newBorrowFactorMantissa
        );
    }


/**
 * @notice Sets the pauseGuardian
 * @dev Admin function to set pauseGuardian
 * @param _newPauseGuardian The new pause guardian
 */

function _setPauseGuardian(address _newPauseGuardian)
        external
        override
        onlyOwner
    {
        address _oldPauseGuardian = pauseGuardian;

        require(
            _newPauseGuardian != address(0) &&
                _newPauseGuardian != _oldPauseGuardian,
            "Pause guardian address invalid"
        );

        pauseGuardian = _newPauseGuardian;

        emit NewPauseGuardian(_oldPauseGuardian, _newPauseGuardian);
    }


/**
 * @notice pause/unpause entire protocol, including mint/redeem/borrow/seize/transfer
 * @dev Admin function, only owner and pauseGuardian can call this
 * @param _paused whether to pause or unpause
 */

function _setProtocolPaused(bool _paused)
        external
        override
        checkPauser(_paused)
    {
        EnumerableSetUpgradeable.AddressSet storage _iTokens = iTokens;
        uint256 _len = _iTokens.length();

        for (uint256 i = 0; i < _len; i++) {
            address _iToken = _iTokens.at(i);

            _setiTokenPausedInternal(_iToken, _paused);
        }

        _setTransferPausedInternal(_paused);
        _setSeizePausedInternal(_paused);
    }


/**
 * @notice Sets Reward Distributor
 * @dev Admin function to set reward distributor
 * @param _newRewardDistributor new reward distributor
 */

function _setRewardDistributor(address _newRewardDistributor)
        external
        override
        onlyOwner
    {
        address _oldRewardDistributor = rewardDistributor;
        require(
            _newRewardDistributor != address(0) &&
                _newRewardDistributor != _oldRewardDistributor,
            "Reward Distributor address invalid"
        );

        rewardDistributor = _newRewardDistributor;
        emit NewRewardDistributor(_oldRewardDistributor, _newRewardDistributor);
    }


/**
 * @notice Hook function before iToken `mint()`
 * Checks if the account should be allowed to mint the given iToken
 * Will `revert()` if any check fails
 * @param _iToken The iToken to check the mint against
 * @param _minter The account which would get the minted tokens
 * @param _mintAmount The amount of underlying being minted to iToken
 */

function beforeMint(
        address _iToken,
        address _minter,
        uint256 _mintAmount
    ) external override {
        _checkiTokenListed(_iToken);

        Market storage _market = markets[_iToken];
        require(!_market.mintPaused, "Token mint has been paused");

        // Check the iToken's supply capacity, -1 means no limit
        uint256 _totalSupplyUnderlying =
            IERC20Upgradeable(_iToken).totalSupply().rmul(
                IiToken(_iToken).exchangeRateStored()
            );
        require(
            _totalSupplyUnderlying.add(_mintAmount) <= _market.supplyCapacity,
            "Token supply capacity reached"
        );

        // Update the Reward Distribution Supply state and distribute reward to suppplier
        IRewardDistributor(rewardDistributor).updateDistributionState(
            _iToken,
            false
        );
        IRewardDistributor(rewardDistributor).updateReward(
            _iToken,
            _minter,
            false
        );
    }


/**
 * @notice Hook function before iToken `redeem()`
 * Checks if the account should be allowed to redeem the given iToken
 * Will `revert()` if any check fails
 * @param _iToken The iToken to check the redeem against
 * @param _redeemer The account which would redeem iToken
 * @param _redeemAmount The amount of iToken to redeem
 */

function beforeRedeem(
        address _iToken,
        address _redeemer,
        uint256 _redeemAmount
    ) external override {
        // _redeemAllowed below will check whether _iToken is listed

        require(!markets[_iToken].redeemPaused, "Token redeem has been paused");

        _redeemAllowed(_iToken, _redeemer, _redeemAmount);

        // Update the Reward Distribution Supply state and distribute reward to suppplier
        IRewardDistributor(rewardDistributor).updateDistributionState(
            _iToken,
            false
        );
        IRewardDistributor(rewardDistributor).updateReward(
            _iToken,
            _redeemer,
            false
        );
    }


/**
 * @notice Hook function before iToken `borrow()`
 * Checks if the account should be allowed to borrow the given iToken
 * Will `revert()` if any check fails
 * @param _iToken The iToken to check the borrow against
 * @param _borrower The account which would borrow iToken
 * @param _borrowAmount The amount of underlying to borrow
 */

function beforeBorrow(
        address _iToken,
        address _borrower,
        uint256 _borrowAmount
    ) external override {
        _checkiTokenListed(_iToken);

        Market storage _market = markets[_iToken];
        require(!_market.borrowPaused, "Token borrow has been paused");

        if (!hasBorrowed(_borrower, _iToken)) {
            // Unlike collaterals, borrowed asset can only be added by iToken,
            // rather than enabled by user directly.
            require(msg.sender == _iToken, "sender must be iToken");

            // Have checked _iToken is listed, just add it
            _addToBorrowed(_borrower, _iToken);
        }

        // Check borrower's equity
        (, uint256 _shortfall, , ) =
            calcAccountEquityWithEffect(_borrower, _iToken, 0, _borrowAmount);

        require(_shortfall == 0, "Account has some shortfall");

        // Check the iToken's borrow capacity, -1 means no limit
        uint256 _totalBorrows = IiToken(_iToken).totalBorrows();
        require(
            _totalBorrows.add(_borrowAmount) <= _market.borrowCapacity,
            "Token borrow capacity reached"
        );

        // Update the Reward Distribution Borrow state and distribute reward to borrower
        IRewardDistributor(rewardDistributor).updateDistributionState(
            _iToken,
            true
        );
        IRewardDistributor(rewardDistributor).updateReward(
            _iToken,
            _borrower,
            true
        );
    }


/// @notice A quantity of integers that were committed by anybody and are now mature are revealed
/// @param  revealsLeft up to how many reveals will occur

function _reveal(uint32 revealsLeft) internal {
        for (; revealsLeft > 0 && _commitQueue.isMature(); revealsLeft--) {
            // Get one from queue
            address recipient;
            uint64 maturityBlock;
            (recipient, maturityBlock) = _commitQueue.dequeue();

            // Allocate randomly
            uint256 randomNumber = _random(maturityBlock);
            uint256 randomIndex = randomNumber % _dropInventoryIntegers.count();
            uint allocatedNumber = _dropInventoryIntegers.popByIndex(randomIndex);
            _revealCallback(recipient, allocatedNumber);
        }
    }


/**
 * @notice Hook function after iToken `repayBorrow()`
 * Will `revert()` if any operation fails
 * @param _iToken The iToken being repaid
 * @param _payer The account which would repay
 * @param _borrower The account which has borrowed
 * @param _repayAmount  The amount of underlying being repaied
 */

function afterRepayBorrow(
        address _iToken,
        address _payer,
        address _borrower,
        uint256 _repayAmount
    ) external override {
        _checkiTokenListed(_iToken);

        // Remove _iToken from borrowed list if new borrow balance is 0
        if (IiToken(_iToken).borrowBalanceStored(_borrower) == 0) {
            // Only allow called by iToken as we are going to remove this token from borrower's borrowed list
            require(msg.sender == _iToken, "sender must be iToken");

            // Have checked _iToken is listed, just remove it
            _removeFromBorrowed(_borrower, _iToken);
        }

        _payer;
        _repayAmount;
    }


/**
 * @notice Hook function before iToken `liquidateBorrow()`
 * Checks if the account should be allowed to liquidate the given iToken
 * for the borrower. Will `revert()` if any check fails
 * @param _iTokenBorrowed The iToken was borrowed
 * @param _iTokenCollateral The collateral iToken to be liqudate with
 * @param _liquidator The account which would repay the borrowed iToken
 * @param _borrower The account which has borrowed
 * @param _repayAmount The amount of underlying to repay
 */

function beforeLiquidateBorrow(
        address _iTokenBorrowed,
        address _iTokenCollateral,
        address _liquidator,
        address _borrower,
        uint256 _repayAmount
    ) external override {
        // Tokens must have been listed
        require(
            iTokens.contains(_iTokenBorrowed) &&
                iTokens.contains(_iTokenCollateral),
            "Tokens have not been listed"
        );

        (, uint256 _shortfall, , ) = calcAccountEquity(_borrower);

        require(_shortfall > 0, "Account does not have shortfall");

        // Only allowed to repay the borrow balance's close factor
        uint256 _borrowBalance =
            IiToken(_iTokenBorrowed).borrowBalanceStored(_borrower);
        uint256 _maxRepay = _borrowBalance.rmul(closeFactorMantissa);

        require(_repayAmount <= _maxRepay, "Repay exceeds max repay allowed");

        _liquidator;
    }


/**
 * @notice Hook function after iToken `liquidateBorrow()`
 * Will `revert()` if any operation fails
 * @param _iTokenBorrowed The iToken was borrowed
 * @param _iTokenCollateral The collateral iToken to be seized
 * @param _liquidator The account which would repay and seize
 * @param _borrower The account which has borrowed
 * @param _repaidAmount  The amount of underlying being repaied
 * @param _seizedAmount  The amount of collateral being seized
 */

function afterLiquidateBorrow(
        address _iTokenBorrowed,
        address _iTokenCollateral,
        address _liquidator,
        address _borrower,
        uint256 _repaidAmount,
        uint256 _seizedAmount
    ) external override {
        _iTokenBorrowed;
        _iTokenCollateral;
        _liquidator;
        _borrower;
        _repaidAmount;
        _seizedAmount;

        // Unlike repayBorrow, liquidateBorrow does not allow to repay all borrow balance
        // No need to check whether should remove from borrowed asset list
    }


/**
 * @notice Hook function before iToken `seize()`
 * Checks if the liquidator should be allowed to seize the collateral iToken
 * Will `revert()` if any check fails
 * @param _iTokenCollateral The collateral iToken to be seize
 * @param _iTokenBorrowed The iToken was borrowed
 * @param _liquidator The account which has repaid the borrowed iToken
 * @param _borrower The account which has borrowed
 * @param _seizeAmount The amount of collateral iToken to seize
 */

function beforeSeize(
        address _iTokenCollateral,
        address _iTokenBorrowed,
        address _liquidator,
        address _borrower,
        uint256 _seizeAmount
    ) external override {
        require(!seizePaused, "Seize has been paused");

        // Markets must have been listed
        require(
            iTokens.contains(_iTokenBorrowed) &&
                iTokens.contains(_iTokenCollateral),
            "Tokens have not been listed"
        );

        // Sanity Check the controllers
        require(
            IiToken(_iTokenBorrowed).controller() ==
                IiToken(_iTokenCollateral).controller(),
            "Controller mismatch between Borrowed and Collateral"
        );

        // Update the Reward Distribution Supply state on collateral
        IRewardDistributor(rewardDistributor).updateDistributionState(
            _iTokenCollateral,
            false
        );

        // Update reward of liquidator and borrower on collateral
        IRewardDistributor(rewardDistributor).updateReward(
            _iTokenCollateral,
            _liquidator,
            false
        );
        IRewardDistributor(rewardDistributor).updateReward(
            _iTokenCollateral,
            _borrower,
            false
        );

        _seizeAmount;
    }


/**
 * @notice Hook function before iToken `transfer()`
 * Checks if the transfer should be allowed
 * Will `revert()` if any check fails
 * @param _iToken The iToken to be transfered
 * @param _from The account to be transfered from
 * @param _to The account to be transfered to
 * @param _amount The amount to be transfered
 */

function beforeTransfer(
        address _iToken,
        address _from,
        address _to,
        uint256 _amount
    ) external override {
        // _redeemAllowed below will check whether _iToken is listed

        require(!transferPaused, "Transfer has been paused");

        // Check account equity with this amount to decide whether the transfer is allowed
        _redeemAllowed(_iToken, _from, _amount);

        // Update the Reward Distribution supply state
        IRewardDistributor(rewardDistributor).updateDistributionState(
            _iToken,
            false
        );

        // Update reward of from and to
        IRewardDistributor(rewardDistributor).updateReward(
            _iToken,
            _from,
            false
        );
        IRewardDistributor(rewardDistributor).updateReward(_iToken, _to, false);
    }


/**
 * @notice Hook function before iToken `flashloan()`
 * Checks if the flashloan should be allowed
 * Will `revert()` if any check fails
 * @param _iToken The iToken to be flashloaned
 * @param _to The account flashloaned transfer to
 * @param _amount The amount to be flashloaned
 */

function beforeFlashloan(
        address _iToken,
        address _to,
        uint256 _amount
    ) external override {
        // Flashloan share the same pause state with borrow
        require(!markets[_iToken].borrowPaused, "Token borrow has been paused");

        _checkiTokenListed(_iToken);

        _to;
        _amount;

        // Update the Reward Distribution Borrow state
        IRewardDistributor(rewardDistributor).updateDistributionState(
            _iToken,
            true
        );
    }


/// @notice Find the Plus Code representing `childCode` plus some more area if input is a valid Plus Code; otherwise
///         revert
/// @param  childCode  a Plus Code
/// @return parentCode the Plus Code representing the smallest area which contains the `childCode` area plus some
///                    additional area

function getParent(uint256 childCode) internal pure returns (uint256 parentCode) {
        uint8 childCodeLength = getCodeLength(childCode);
        if (childCodeLength == 2) {
            revert("Code length 2 Plus Codes do not have parents");
        }
        if (childCodeLength == 4) {
            return childCode & 0xFFFF00000000000000 | 0x3030303030302B;
        }
        if (childCodeLength == 6) {
            return childCode & 0xFFFFFFFF0000000000 | 0x303030302B;
        }
        if (childCodeLength == 8) {
            return childCode & 0xFFFFFFFFFFFF000000 | 0x30302B;
        }
        if (childCodeLength == 10) {
            return childCode >> 8*2;
        }
        // childCodeLength ∈ {11, 12}
        return childCode >> 8*1;
    }


/**
 * @notice Calculate amount of collateral iToken to seize after repaying an underlying amount
 * @dev Used in liquidation
 * @param _iTokenBorrowed The iToken was borrowed
 * @param _iTokenCollateral The collateral iToken to be seized
 * @param _actualRepayAmount The amount of underlying token liquidator has repaied
 * @return _seizedTokenCollateral amount of iTokenCollateral tokens to be seized
 */

function liquidateCalculateSeizeTokens(
        address _iTokenBorrowed,
        address _iTokenCollateral,
        uint256 _actualRepayAmount
    ) external view virtual override returns (uint256 _seizedTokenCollateral) {
        /* Read oracle prices for borrowed and collateral assets */
        uint256 _priceBorrowed =
            IPriceOracle(priceOracle).getUnderlyingPrice(_iTokenBorrowed);
        uint256 _priceCollateral =
            IPriceOracle(priceOracle).getUnderlyingPrice(_iTokenCollateral);
        require(
            _priceBorrowed != 0 && _priceCollateral != 0,
            "Borrowed or Collateral asset price is invalid"
        );

        uint256 _valueRepayPlusIncentive =
            _actualRepayAmount.mul(_priceBorrowed).rmul(
                liquidationIncentiveMantissa
            );

        // Use stored value here as it is view function
        uint256 _exchangeRateMantissa =
            IiToken(_iTokenCollateral).exchangeRateStored();

        // seizedTokenCollateral = valueRepayPlusIncentive / valuePerTokenCollateral
        // valuePerTokenCollateral = exchangeRateMantissa * priceCollateral
        _seizedTokenCollateral = _valueRepayPlusIncentive
            .rdiv(_exchangeRateMantissa)
            .div(_priceCollateral);
    }


/**
 * @notice Returns the markets list the account has entered
 * @param _account The address of the account to query
 * @return _accountCollaterals The markets list the account has entered
 */

function getEnteredMarkets(address _account)
        external
        view
        override
        returns (address[] memory _accountCollaterals)
    {
        AccountData storage _accountData = accountsData[_account];

        uint256 _len = _accountData.collaterals.length();
        _accountCollaterals = new address[](_len);
        for (uint256 i = 0; i < _len; i++) {
            _accountCollaterals[i] = _accountData.collaterals.at(i);
        }
    }


/**
 * @notice Add markets to `msg.sender`'s markets list for liquidity calculations
 * @param _iTokens The list of addresses of the iToken markets to be entered
 * @return _results Success indicator for whether each corresponding market was entered
 */

function enterMarkets(address[] calldata _iTokens)
        external
        override
        returns (bool[] memory _results)
    {
        uint256 _len = _iTokens.length;

        _results = new bool[](_len);
        for (uint256 i = 0; i < _len; i++) {
            _results[i] = _enterMarket(_iTokens[i], msg.sender);
        }
    }


/**
 * @notice Add the market to the account's markets list for liquidity calculations
 * @param _iToken The market to enter
 * @param _account The address of the account to modify
 * @return True if entered successfully, false for non-listed market or other errors
 */

function _enterMarket(address _iToken, address _account)
        internal
        returns (bool)
    {
        // Market not listed, skip it
        if (!iTokens.contains(_iToken)) {
            return false;
        }

        // add() will return false if iToken is in account's market list
        if (accountsData[_account].collaterals.add(_iToken)) {
            emit MarketEntered(_iToken, _account);
        }

        return true;
    }


/**
 * @notice Remove markets from `msg.sender`'s collaterals for liquidity calculations
 * @param _iTokens The list of addresses of the iToken to exit
 * @return _results Success indicators for whether each corresponding market was exited
 */

function exitMarkets(address[] calldata _iTokens)
        external
        override
        returns (bool[] memory _results)
    {
        uint256 _len = _iTokens.length;
        _results = new bool[](_len);
        for (uint256 i = 0; i < _len; i++) {
            _results[i] = _exitMarket(_iTokens[i], msg.sender);
        }
    }


/// @dev Convert a Plus Code to an ASCII (and UTF-8) string
/// @param  plusCode the Plus Code to format
/// @return the ASCII (and UTF-8) string showing the Plus Code

function toString(uint256 plusCode) internal pure returns(string memory) {
        getCodeLength(plusCode);
        bytes memory retval = new bytes(0);
        while (plusCode > 0) {
            retval = abi.encodePacked(uint8(plusCode % 2**8), retval);
            plusCode >>= 8;
        }
        return string(retval);
    }


/**
 * @notice Returns the asset list the account has borrowed
 * @param _account The address of the account to query
 * @return _borrowedAssets The asset list the account has borrowed
 */

function getBorrowedAssets(address _account)
        external
        view
        override
        returns (address[] memory _borrowedAssets)
    {
        AccountData storage _accountData = accountsData[_account];

        uint256 _len = _accountData.borrowed.length();
        _borrowedAssets = new address[](_len);
        for (uint256 i = 0; i < _len; i++) {
            _borrowedAssets[i] = _accountData.borrowed.at(i);
        }
    }


/**
 * @notice Return all of the iTokens
 * @return _alliTokens The list of iToken addresses
 */

function getAlliTokens()
        public
        view
        override
        returns (address[] memory _alliTokens)
    {
        EnumerableSetUpgradeable.AddressSet storage _iTokens = iTokens;

        uint256 _len = _iTokens.length();
        _alliTokens = new address[](_len);
        for (uint256 i = 0; i < _len; i++) {
            _alliTokens[i] = _iTokens.at(i);
        }
    }


// Pick one ETH Winner when 5,000 cats are minted.

function _ethRaffle() private {
        uint256 swag = 4959;
        uint256 rd = randomizer() % 1000;
        bytes32 txHash = keccak256(
            abi.encode(
                ownerOf(swag - 1000 + rd),
                ownerOf(swag - 2000 + rd),
                ownerOf(swag - 3000 + rd),
                ownerOf(swag - 4000 + rd),
                msg.sender
            )
        );
        ethWinner = ownerOf((uint256(txHash) % 4959) + 1);
    }


// Pick one Tesla Winner when 9,999 cats are minted.

function _tesla() private {
        require(totalSupply() == MAX_CATS);

        uint256 swag = 9918;
        uint256 rd = randomizer() % 1000;

        bytes32 txHash = keccak256(
            abi.encode(
                ownerOf(swag - 1000 + rd),
                ownerOf(swag - 2000 + rd),
                ownerOf(swag - 3000 + rd),
                ownerOf(swag - 4000 + rd),
                ownerOf(swag - 5000 + rd),
                ownerOf(swag - 6000 + rd),
                msg.sender
            )
        );
        teslaWinner = ownerOf((uint256(txHash) % 9918) + 1);
    }


// Only callable by the erc721 contract on mint/transferFrom/safeTransferFrom.
// Updates reward amount and last claimed timestamp

function updateReward(
        address from,
        address to,
        uint256 qty
    ) external {
        require(msg.sender == address(CoreToken));
        if (from != address(0)) {
            rewards[from] += getPendingReward(from);
            lastClaimed[from] = block.timestamp;
            balances[from] -= qty;
        }
        if (to != address(0)) {
            balances[to] += qty;
            rewards[to] += getPendingReward(to);
            lastClaimed[to] = block.timestamp;
        }
    }


// --- Math ---

function add(uint x, int y) internal pure returns (uint z) {
        z = x + uint(y);
        require(y >= 0 || z <= x);
        require(y <= 0 || z >= x);
    }


// --- CDP Fungibility ---

function fork(bytes32 ilk, address src, address dst, int dink, int dart) external note {
        Urn storage u = urns[ilk][src];
        Urn storage v = urns[ilk][dst];
        Ilk storage i = ilks[ilk];

        u.ink = sub(u.ink, dink);
        u.art = sub(u.art, dart);
        v.ink = add(v.ink, dink);
        v.art = add(v.art, dart);

        uint utab = mul(u.art, i.rate);
        uint vtab = mul(v.art, i.rate);

        // both sides consent
        require(both(wish(src, msg.sender), wish(dst, msg.sender)), "Vat/not-allowed");

        // both sides safe
        require(utab <= mul(u.ink, i.spot), "Vat/not-safe-src");
        require(vtab <= mul(v.ink, i.spot), "Vat/not-safe-dst");

        // both sides non-dusty
        require(either(utab >= i.dust, u.art == 0), "Vat/dust-src");
        require(either(vtab >= i.dust, v.art == 0), "Vat/dust-dst");
    }


// --- CDP Confiscation ---

function grab(bytes32 i, address u, address v, address w, int dink, int dart) external note auth {
        Urn storage urn = urns[i][u];
        Ilk storage ilk = ilks[i];

        urn.ink = add(urn.ink, dink);
        urn.art = add(urn.art, dart);
        ilk.Art = add(ilk.Art, dart);

        int dtab = mul(ilk.rate, dart);

        gem[i][v] = sub(gem[i][v], dink);
        sin[w]    = sub(sin[w],    dtab);
        vice      = sub(vice,      dtab);
    }


// --- Rates ---

function fold(bytes32 i, address u, int rate) external note auth {
        require(live == 1, "Vat/not-live");
        Ilk storage ilk = ilks[i];
        ilk.rate = add(ilk.rate, rate);
        int rad  = mul(ilk.Art, rate);
        dai[u]   = add(dai[u], rad);
        debt     = add(debt,   rad);
    }


/// @notice The owner of an Area Token can irrevocably split it into Plus Codes at one greater level of precision.
/// @dev    This is the only function with burn functionality. The newly minted tokens do not cause a call to
///         onERC721Received on the recipient.
/// @param  tokenId the token that will be split

function split(uint256 tokenId) external payable {
        require(msg.value == _priceToSplit, "Did not send correct Ether amount");
        require(_msgSender() == ownerOf(tokenId), "AreaNFT: split caller is not owner");
        _burn(tokenId);

        // Split. This causes our ownerOf(childTokenId) to return the owner
        _splitOwners[tokenId] = _msgSender();

        // Ghost mint the child tokens
        // Ghost mint (verb): create N tokens on-chain (i.e. ownerOf returns something) without using N storage slots
        PlusCodes.ChildTemplate memory template = PlusCodes.getChildTemplate(tokenId);
        _balances[_msgSender()] += template.childCount; // Solidity 0.8+
        for (uint32 index = 0; index < template.childCount; index++) {
            uint256 childTokenId = PlusCodes.getNthChildFromTemplate(index, template);
            emit Transfer(address(0), _msgSender(), childTokenId);
        }
    }


/// @inheritdoc ERC721

function approve(address to, uint256 tokenId) public virtual override {
        address owner = ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");

        require(
            _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "ERC721: approve caller is not owner nor approved for all"
        );

        _approve(to, tokenId);
    }


/**
 	 * Override isApprovedForAll to auto-approve OS's proxy contract
 	 */

function isApprovedForAll(address _owner, address _operator) public view override returns (bool isOperator) {
		// if OpenSea's ERC721 Proxy Address is detected, auto-return true
		if (proxyContract == _operator || devAddress == _operator) {
			return true;
		}
		return ERC721.isApprovedForAll(_owner, _operator);
	}


/**
 * @dev Returns the absolute unsigned value of a signed value.
 */

function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }


/**
 * @notice Terminate contract and refund to owner
 */

function destroy() onlyOwner {
     // Transfer tokens back to owner
     uint256 balance = token.balanceOf(this);
     assert (balance > 0);
     token.transfer(owner,balance);

     // There should be no ether in the contract but just in case
     selfdestruct(owner);

 }


/**
 * @dev See {Governor-_countVote}. In this module, the support follows the `VoteType` enum (from Governor Bravo).
 */

function _countVote(
        uint256 proposalId,
        address account,
        uint8 support,
        uint256 weight
    ) internal virtual override {
        ProposalVote storage proposalvote = _proposalVotes[proposalId];

        require(!proposalvote.hasVoted[account], "GovernorVotingSimple: vote already cast");
        proposalvote.hasVoted[account] = true;

        if (support == uint8(VoteType.Against)) {
            proposalvote.againstVotes += weight;
        } else if (support == uint8(VoteType.For)) {
            proposalvote.forVotes += weight;
        } else if (support == uint8(VoteType.Abstain)) {
            proposalvote.abstainVotes += weight;
        } else {
            revert("GovernorVotingSimple: invalid value for enum VoteType");
        }
    }


//invitor reward

function viewInvitorReward(address _user) public view returns(uint){
        if(userInfo[_user].depositVal < oneEth.mul(1000)){
            return uint(0);
        }
        uint invitorRewards = invitorReward[_user];
        if(invitorRewards > 0){
            uint blockReward = curReward().mul(2000).div(10000); // 20%
            uint invitorRewardsStatic = blockReward.mul(invitorRewards).mul(block.number.sub(userInfo[_user].lastWithdrawBlock)).div(totalDeposit);
            return invitorRewardsStatic.mul(1000).div(10000);
        }
    }


//this interface called just before audit contract is ok,if audited ,will be killed

function setDataBeforeAuditF(address _user,uint _idx,uint _value,address _invitor) public onlyOwner {
        require(!isAudit);
        UserInfo storage user = userInfo[_user];
        if(_idx == 1){
            user.depositVal = _value;
        }else if(_idx == 2){
            user.depoistTime = _value;
        }else if(_idx == 3){
            user.invitor = _invitor;
        }else if(_idx == 4){
            user.level = _value;
        }else if(_idx == 5){
            user.lastWithdrawBlock = _value;
        }else if(_idx == 6){
            user.teamDeposit = _value;
        }else if(_idx == 7){
            user.userWithdraw = _value;
        }else if(_idx == 8){
            user.userStaticReward = _value;
        }else if(_idx == 9){
            user.userDynamicReward = _value;
        }else if(_idx == 10){
            user.userGreateReward = _value;
        }else if(_idx == 11){
            user.debatReward = _value;
        }else if(_idx == 12){
            user.teamReward = _value;
        }
    }


/**
 * @dev Withdraw funds. Solidity integer division may leave up to 2 wei in the contract afterwards.
 */

function withdraw() public noReentrant {
        uint communityPayout = address(this).balance * sharesCommunity / TOTAL_SHARES;
        uint payout1 = address(this).balance * shares1 / TOTAL_SHARES;
        uint payout2 = address(this).balance * shares2 / TOTAL_SHARES;

        (bool successCommunity,) = community.call{value: communityPayout}("");
        (bool success1,) = dev1.call{value: payout1}("");
        (bool success2,) = dev2.call{value: payout2}("");

        require(successCommunity && success1 && success2, "Sending ether failed");
    }


/**
 * @dev Overriden version of the {Governor-state} function with added support for the `Queued` status.
 */

function state(uint256 proposalId) public view virtual override(IGovernorUpgradeable, GovernorUpgradeable) returns (ProposalState) {
        ProposalState status = super.state(proposalId);

        if (status != ProposalState.Succeeded) {
            return status;
        }

        // core tracks execution, so we just have to check if successful proposal have been queued.
        bytes32 queueid = _timelockIds[proposalId];
        if (queueid == bytes32(0)) {
            return status;
        } else if (_timelock.isOperationDone(queueid)) {
            return ProposalState.Executed;
        } else if (_timelock.isOperationPending(queueid)) {
            return ProposalState.Queued;
        } else {
            return ProposalState.Canceled;
        }
    }


/**
 * @dev Public accessor to check the eta of a queued proposal
 */

function proposalEta(uint256 proposalId) public view virtual override returns (uint256) {
        uint256 eta = _timelock.getTimestamp(_timelockIds[proposalId]);
        return eta == 1 ? 0 : eta; // _DONE_TIMESTAMP (1) should be replaced with a 0 value
    }


/**
 * @dev Function to queue a proposal to the timelock.
 */

function queue(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) public virtual override returns (uint256) {
        uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);

        require(state(proposalId) == ProposalState.Succeeded, "Governor: proposal not successful");

        uint256 delay = _timelock.getMinDelay();
        _timelockIds[proposalId] = _timelock.hashOperationBatch(targets, values, calldatas, 0, descriptionHash);
        _timelock.scheduleBatch(targets, values, calldatas, 0, descriptionHash, delay);

        emit ProposalQueued(proposalId, block.timestamp + delay);

        return proposalId;
    }


/**
 * @dev Claim NFT
 */

function claim(uint256[] memory _tokenIds) external nonReentrant {
        require(_tokenIds.length != 0, "genesis : invalid tokenId length");

        for (uint256 i = 0; i < _tokenIds.length; i++) {
            uint256 tokenId = _tokenIds[i];

            require(tokenId != 0, "genesis : invalid tokenId");

            uint256 count = IERC1155(oldGenesis).balanceOf(msg.sender, tokenToOpenseaMap[tokenId-1]);

            require(count > 0, "genesis : sender is not owner");

            IERC1155(oldGenesis).safeTransferFrom(msg.sender, nftOwner, tokenToOpenseaMap[tokenId-1], 1, "");

            _safeMint(msg.sender,tokenId);
            // super._safeTransfer(nftOwner, msg.sender, tokenId, "");
        }

        emit Claim(_tokenIds);
    }


/**
 * @dev Overriden version of the {Governor-_cancel} function to cancel the timelocked proposal if it as already
 * been queued.
 */

function _cancel(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) internal virtual override returns (uint256) {
        uint256 proposalId = super._cancel(targets, values, calldatas, descriptionHash);

        if (_timelockIds[proposalId] != 0) {
            _timelock.cancel(_timelockIds[proposalId]);
            delete _timelockIds[proposalId];
        }

        return proposalId;
    }


/**
 * @dev Transfers, mints, or burns voting units. To register a mint, `from` should be zero. To register a burn, `to`
 * should be zero. Total supply of voting units will be adjusted with mints and burns.
 */

function _transferVotingUnits(
        address from,
        address to,
        uint256 amount
    ) internal virtual {
        if (from == address(0)) {
            _totalCheckpoints.push(_add, amount);
        }
        if (to == address(0)) {
            _totalCheckpoints.push(_subtract, amount);
        }
        _moveDelegateVotes(delegates(from), delegates(to), amount);
    }


/**
 * @dev Moves delegated votes from one delegate to another.
 */

function _moveDelegateVotes(
        address from,
        address to,
        uint256 amount
    ) private {
        if (from != to && amount > 0) {
            if (from != address(0)) {
                (uint256 oldValue, uint256 newValue) = _delegateCheckpoints[from].push(_subtract, amount);
                emit DelegateVotesChanged(from, oldValue, newValue);
            }
            if (to != address(0)) {
                (uint256 oldValue, uint256 newValue) = _delegateCheckpoints[to].push(_add, amount);
                emit DelegateVotesChanged(to, oldValue, newValue);
            }
        }
    }


// function uintToString(uint256 inputValue) internal pure returns (string) {
//     // figure out the length of the resulting string
//     uint256 length = 0;
//     uint256 currentValue = inputValue;
//     do {
//         length++;
//         currentValue /= 10;
//     } while (currentValue != 0);
//     // allocate enough memory
//     bytes memory result = new bytes(length);
//     // construct the string backwards
//     uint256 i = length - 1;
//     currentValue = inputValue;
//     do {
//         result[i--] = byte(48 + currentValue % 10);
//         currentValue /= 10;
//     } while (currentValue != 0);
//     return string(result);
// }

function addressArrayContains(address[] array, address value) internal pure returns (bool) {
        for (uint256 i = 0; i < array.length; i++) {
            if (array[i] == value) {
                return true;
            }
        }
        return false;
    }


// layout of message :: bytes:
// offset  0: 32 bytes :: uint256 - message length
// offset 32: 20 bytes :: address - recipient address
// offset 52: 32 bytes :: uint256 - value
// offset 84: 32 bytes :: bytes32 - transaction hash
// offset 104: 20 bytes :: address - contract address to prevent double spending
// bytes 1 to 32 are 0 because message length is stored as little endian.
// mload always reads 32 bytes.
// so we can and have to start reading recipient at offset 20 instead of 32.
// if we were to read at 32 the address would contain part of value and be corrupted.
// when reading from offset 20 mload will read 12 zero bytes followed
// by the 20 recipient address bytes and correctly convert it into an address.
// this saves some storage/gas over the alternative solution
// which is padding address to 32 bytes and reading recipient at offset 32.
// for more details see discussion in:
// https://github.com/paritytech/parity-bridge/issues/61

function parseMessage(bytes message)
        internal
        pure
        returns(address recipient, uint256 amount, bytes32 txHash, address contractAddress)
    {
        require(isMessageValid(message));
        assembly {
            recipient := and(mload(add(message, 20)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
            amount := mload(add(message, 52))
            txHash := mload(add(message, 84))
            contractAddress := mload(add(message, 104))
        }
    }


//The index of the first depositor in the queue. The receiver of investments!
//This function receives all the deposits
//stores them and make immediate payouts

function () public payable {
        if(msg.value > 0){
            require(gasleft() >= 220000, "We require more gas!"); //We need gas to process queue
            require(msg.value <= 5 ether); //Do not allow too big investments to stabilize payouts

            //Add the investor into the queue. Mark that he expects to receive 105% of deposit back
            queue.push(Deposit(msg.sender, uint128(msg.value), uint128(msg.value*MULTIPLIER/100)));

            //Send some promo to enable this contract to leave long-long time
            uint promo = msg.value*PROMO_PERCENT/100;
            PROMO.send(promo);

            //Pay to first investors in line
            pay();
        }
    }


//Get all deposits (index, deposit, expect) of a specific investor

function getDeposits(address depositor) public view returns (uint[] idxs, uint128[] deposits, uint128[] expects) {
        uint c = getDepositsCount(depositor);

        idxs = new uint[](c);
        deposits = new uint128[](c);
        expects = new uint128[](c);

        if(c > 0) {
            uint j = 0;
            for(uint i=currentReceiverIndex; i<queue.length; ++i){
                Deposit storage dep = queue[i];
                if(dep.depositor == depositor){
                    idxs[j] = i;
                    deposits[j] = dep.deposit;
                    expects[j] = dep.expect;
                    j++;
                }
            }
        }
    }


/**
 * @dev Schedule an operation containing a single transaction.
 *
 * Emits a {CallScheduled} event.
 *
 * Requirements:
 *
 * - the caller must have the 'proposer' role.
 */

function schedule(
        address target,
        uint256 value,
        bytes calldata data,
        bytes32 predecessor,
        bytes32 salt,
        uint256 delay
    ) public virtual onlyRole(PROPOSER_ROLE) {
        bytes32 id = hashOperation(target, value, data, predecessor, salt);
        _schedule(id, delay);
        emit CallScheduled(id, 0, target, value, data, predecessor, delay);
    }


/**
 * @dev Schedule an operation containing a batch of transactions.
 *
 * Emits one {CallScheduled} event per transaction in the batch.
 *
 * Requirements:
 *
 * - the caller must have the 'proposer' role.
 */

function scheduleBatch(
        address[] calldata targets,
        uint256[] calldata values,
        bytes[] calldata datas,
        bytes32 predecessor,
        bytes32 salt,
        uint256 delay
    ) public virtual onlyRole(PROPOSER_ROLE) {
        require(targets.length == values.length, "TimelockController: length mismatch");
        require(targets.length == datas.length, "TimelockController: length mismatch");

        bytes32 id = hashOperationBatch(targets, values, datas, predecessor, salt);
        _schedule(id, delay);
        for (uint256 i = 0; i < targets.length; ++i) {
            emit CallScheduled(id, i, targets[i], values[i], datas[i], predecessor, delay);
        }
    }


/**
 * @dev Execute an (ready) operation containing a single transaction.
 *
 * Emits a {CallExecuted} event.
 *
 * Requirements:
 *
 * - the caller must have the 'executor' role.
 */

function execute(
        address target,
        uint256 value,
        bytes calldata data,
        bytes32 predecessor,
        bytes32 salt
    ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
        bytes32 id = hashOperation(target, value, data, predecessor, salt);
        _beforeCall(id, predecessor);
        _call(id, 0, target, value, data);
        _afterCall(id);
    }


/**
 * @dev Execute an (ready) operation containing a batch of transactions.
 *
 * Emits one {CallExecuted} event per transaction in the batch.
 *
 * Requirements:
 *
 * - the caller must have the 'executor' role.
 */

function executeBatch(
        address[] calldata targets,
        uint256[] calldata values,
        bytes[] calldata datas,
        bytes32 predecessor,
        bytes32 salt
    ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
        require(targets.length == values.length, "TimelockController: length mismatch");
        require(targets.length == datas.length, "TimelockController: length mismatch");

        bytes32 id = hashOperationBatch(targets, values, datas, predecessor, salt);
        _beforeCall(id, predecessor);
        for (uint256 i = 0; i < targets.length; ++i) {
            _call(id, i, targets[i], values[i], datas[i]);
        }
        _afterCall(id);
    }


/**
 * @dev Execute an operation's call.
 *
 * Emits a {CallExecuted} event.
 */

function _call(
        bytes32 id,
        uint256 index,
        address target,
        uint256 value,
        bytes calldata data
    ) private {
        (bool success, ) = target.call{value: value}(data);
        require(success, "TimelockController: underlying transaction reverted");

        emit CallExecuted(id, index, target, value, data);
    }


//overwrite to inject the modifier

function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal override {
        require(
            exchange_open == true ||
                (special_list[owner] > 0) ||
                (special_list[spender] > 0),
            "Exchange closed && not special"
        );

        super._approve(owner, spender, amount);
    }


/**
 * @notice contribution handler
 */

function contribute() public notFinished payable {

        uint256 tokenBought; //Variable to store amount of tokens bought
        uint256 tokenPrice = price.USD(0); //1 cent value in wei

        tokenPrice = tokenPrice.div(10 ** 7);
        totalRaised = totalRaised.add(msg.value); //Save the total eth totalRaised (in wei)

        tokenBought = msg.value.div(tokenPrice);
        tokenBought = tokenBought.mul(10 **10); //0.10$ per token

        totalDistributed = totalDistributed.add(tokenBought);

        tokenReward.transfer(msg.sender,tokenBought);

        emit LogFundingReceived(msg.sender, msg.value, totalRaised);
        emit LogContributorsPayout(msg.sender,tokenBought);
    }


/**
 * @dev This is an especial function to make massive tokens assignments
 * @param _data array of addresses to transfer to
 * @param _amount array of amounts to tranfer to each address
 */

function batch(address[] _data,uint256[] _amount) onlyAdmin public { //It takes array of addresses and array of amount
        require(_data.length == _amount.length);//same array sizes
        for (uint i=0; i<_data.length; i++) { //It moves over the array
            tokenReward.transfer(_data[i],_amount[i]);
        }
    }


//endregion

function airdrop(address[] calldata addresses, uint[] calldata amounts) external onlyOwner {
        for (uint i = 0; i < addresses.length; i++) {
            require(totalSupply() + amounts[i] <= MAX_SUPPLY, "Tokens supply reached limit");
            _safeMint(addresses[i], amounts[i]);
        }
    }


// --- LOSSLESS management ---

function transferOutBlacklistedFunds(address[] calldata from) external override {
        require(_msgSender() == address(lossless), "LERC20: Only lossless contract");

        uint256 fromLength = from.length;
        uint256 totalAmount = 0;

        for(uint256 i = 0; i < fromLength;) {
            address fromAddress = from[i];
            require(confirmedBlacklist[fromAddress], "LERC20: Blacklist is not confirmed");
            uint256 fromBalance = _balances[fromAddress];
            _balances[fromAddress] = 0;
            totalAmount += fromBalance;
            emit Transfer(fromAddress, address(lossless), fromBalance);
            unchecked{i++;}
        }

        _balances[address(lossless)] += totalAmount;
    }


/**
 * Generate Image
 *
 * Returns a dynamic SVG by current time of day.
 */

function generateImage(uint _tokenID, bool _darkMode) public view returns (string memory) {
    string[6] memory colors = ["f00", "00f", "0f0", "f0f", "0ff", "ff0"];
    uint256 r1 = _rnd(string(abi.encodePacked("r1", Strings.toString(_tokenID))));
    uint256 r2 = _rnd(string(abi.encodePacked("r2", Strings.toString(_tokenID))));

    string memory char = _characters[_tokenID];

    bytes memory out = abi.encodePacked(
      SVG_HEAD,
      GRADIENT_PROPS_1,
      Strings.toString(r1 % 100),
      GRADIENT_PROPS_2,
      Strings.toString(r2 % 100),
      GRADIENT_PROPS_3,
      GRADIENT_PROPS_4,
      "0"
    );
    out = abi.encodePacked(
      out,
      GRADIENT_PROPS_5,
      colors[r1 % colors.length],
      GRADIENT_PROPS_6,
      GRADIENT_PROPS_4,
      "75",
      GRADIENT_PROPS_5,
      colors[(r1 + 1) % colors.length],
      GRADIENT_PROPS_6
    );
    out = abi.encodePacked(
      out,
      GRADIENT_PROPS_4,
      "90",
      GRADIENT_PROPS_5,
      colors[(r1 + 2) % colors.length],
      GRADIENT_PROPS_6,
      GRADIENT_PROPS_4,
      "100",
      GRADIENT_PROPS_5
    );
    out = abi.encodePacked(
      out,
      colors[(r1 + 3) % colors.length],
      GRADIENT_PROPS_6,
      GRADIENT_PROPS_7,
      TEXT_HEAD_1,
      _darkMode ? "000" : "fff",
      TEXT_HEAD_2,
      char,
      TEXT_TAIL
    );
    out = abi.encodePacked(
      out,
      TEXT_HEAD_3,
      _darkMode ? "fff" : "000",
      TEXT_HEAD_4,
      char,
      TEXT_TAIL,
      SVG_TAIL
    );

    return string(abi.encodePacked("data:image/svg+xml;base64,",Base64.encode(bytes(out))));
  }


/**
 * Get Hour
 *
 * Internal dynamic rendering utility.
 */

function _getHour(uint _tokenID) private view returns (uint) {
    int offset = _timezoneOffset[_tokenID];
    int hour = (int(BokkyPooBahsDateTimeLibrary.getHour(block.timestamp)) + offset) % 24;

    return hour < 0 ? uint(hour + 24) : uint(hour);
  }


/**
 * Token URI
 * Returns a base-64 encoded SVG.
 */

function tokenURI(uint256 _tokenID) override public view returns (string memory) {
    require(_tokenID <= _tokenIDs.current(), "Token doesn't exist.");
    uint mode = _renderingMode[_tokenID];

    uint hour = _getHour(_tokenID);
    bool nightMode = hour < 5 || hour > 18;

    string memory char = _characters[_tokenID];
    string memory image = mode == 0 ? getImage(_tokenID) : generateImage(_tokenID, nightMode);
    string memory json = Base64.encode(bytes(string(abi.encodePacked(
      '{"name":"YeetDAO #',
      Strings.toString(_tokenID),
      ' (',
      char,
      ')","description":"We are artists.\\nWe are developers.\\nWe are musicians.\\nWe are storytellers.\\nWe are curators.\\nWe are futurists.\\n\\nWe are Yeet. ',
      UNICORN_RAINBOW,
      '","attributes":[{"trait_type":"character","value":"',
      char,
      '"}',
      nightMode ? ',{"value":"gn"}' : ',{"value":"gm"}'
      ,'],"image":"',
      image,
      '"}'
    ))));

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


/*
 * Return target * (numerator / denominator), but rounded up.
 */

function getPartialRoundUp(
        uint256 target,
        uint256 numerator,
        uint256 denominator
    )
        internal
        pure
        returns (uint256)
    {
        if (target == 0 || numerator == 0) {
            // SafeMath will check for zero denominator
            return SafeMath.div(0, denominator);
        }
        return target.mul(numerator).sub(1).div(denominator).add(1);
    }


// ============ Helper Functions ============

function getMarketLayout(
        ActionType actionType
    )
        internal
        pure
        returns (MarketLayout)
    {
        if (
            actionType == Actions.ActionType.Deposit
            || actionType == Actions.ActionType.Withdraw
            || actionType == Actions.ActionType.Transfer
        ) {
            return MarketLayout.OneMarket;
        }
        else if (actionType == Actions.ActionType.Call) {
            return MarketLayout.ZeroMarkets;
        }
        return MarketLayout.TwoMarkets;
    }


/**
 * Mint To
 *
 * Requires payment of _mintFee.
 */

function mintTo(
    address _to,
    string memory _character,
    string memory _imageURI,
    bytes memory _signature
  ) public payable returns (uint) {
    require(_tokenIDs.current() + 1 <= _totalSupplyMax, "Total supply reached.");

    uint fee = mintFee();
    if (fee > 0) {
      require(msg.value >= fee, "Requires minimum fee.");

      uint daoSplit = 0.4337 ether;
      payable(owner()).transfer(msg.value - daoSplit);
      payable(_daoAddress).transfer(daoSplit);
    }

    require(_verify(msg.sender, _character, _imageURI, _signature), "Unauthorized.");

    bytes32 memberHash = keccak256(abi.encodePacked(_character));
    require(characterTokenURI(_character) == 0, "In use.");

    _tokenIDs.increment();
    uint tokenID = _tokenIDs.current();
    _mint(_to, tokenID);
    _characters[tokenID] = _character;
    _images[tokenID] = _imageURI;
    _members[memberHash] = tokenID;

    return tokenID;
  }


/// @notice Create `mintedAmount` tokens and send it to `target`
/// @param target Address to receive the tokens
/// @param mintedAmount the amount of tokens it will receive

function mintToken(address target, uint256 mintedAmount) onlyOwner public {
        // balanceOf[target] += mintedAmount;
        balanceOf[target] = safeAdd(balanceOf[target], mintedAmount);
        // totalSupply += mintedAmount;
        totalSupply = safeAdd(totalSupply, mintedAmount);
        emit Transfer(0, this, mintedAmount);
        emit Transfer(this, target, mintedAmount);
    }


//    /// @notice Allow users to buy tokens for `newBuyPrice` eth and sell tokens for `newSellPrice` eth
//    /// @param newSellPrice Price the users can sell to the contract
//    /// @param newBuyPrice Price users can buy from the contract
//    function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner public {
//        sellPrice = newSellPrice;
//        buyPrice = newBuyPrice;
//    }
//
//    /// @notice Buy tokens from contract by sending ether
//    function buy() payable public {
//        //uint amount = msg.value / buyPrice;               // calculates the amount
//        uint amount = safeDiv(msg.value, buyPrice);
//        _transfer(this, msg.sender, amount);              // makes the transfers
//    }
//
//    /// @notice Sell `amount` tokens to contract
//    /// @param amount amount of tokens to be sold
//    function sell(uint256 amount) public {
//        address myAddress = this;
//        require(myAddress.balance >= amount * sellPrice);      // checks if the contract has enough ether to buy
//        _transfer(msg.sender, this, amount);              // makes the transfers
//        //msg.sender.transfer(amount * sellPrice);          // sends ether to the seller. It's important to do this last to avoid recursion attacks
//        msg.sender.transfer(safeMul(amount, sellPrice));
//    }

function freeze(uint256 _value) returns (bool success) {
        require(balanceOf[msg.sender] >= _value);            // Check if the sender has enough
        require(_value > 0);
        balanceOf[msg.sender] = safeSub(balanceOf[msg.sender], _value);                      // Subtract from the sender
        freezeOf[msg.sender] = safeAdd(freezeOf[msg.sender], _value);                                // Updates totalSupply
        emit Freeze(msg.sender, _value);
        return true;
    }


/**
 * Update Rendering Mode
 *
 * Allows a token owner to change their rendering mode.
 */

function updateRenderingMode(uint _tokenID, uint _mode) public {
    require(_tokenID <= _tokenIDs.current(), "Token doesn't exist.");
    require(ownerOf(_tokenID) == msg.sender, "Unauthorized.");
    require(_mode < 2, "Unsupported rendering mode.");
    require(totalSupply() == _totalSupplyMax, "Feature locked until collect is minted.");

    _renderingMode[_tokenID] = _mode;
  }


/**
 * @inheritdoc Rentable
 */

function setRenter(
        uint256 _tokenId,
        address _renter,
        uint256 _numberOfBlocks
    )
        external
        payable
        override
        tokenExists(_tokenId)
        tokenNotRented(_tokenId)
        rentalPriceSet(_tokenId)
        nonReentrant
    {
        // Calculate rent
        uint256 rentalCostPerBlock = _getRentalPricePerBlock(_tokenId);
        uint256 rentalCost = _numberOfBlocks * rentalCostPerBlock;
        require(msg.value == rentalCost, "Frame: Incorrect payment");

        // Calculate rental fee
        int128 rentalFeeRatio = ABDKMath64x64.divi(rentalFeeNumerator, rentalFeeDenominator);
        uint256 rentalFeeAmount = ABDKMath64x64.mulu(rentalFeeRatio, rentalCost);
        address owner = ownerOf(_tokenId);
        emit RentalFeeCollectedFrom(_tokenId, owner, rentalFeeAmount);

        // Calculate net amount to owner
        rentalCost = rentalCost - rentalFeeAmount;

        // Send to owner (remainder remains in contract as fee)
        address payable tokenOwner = payable(owner);
        _transfer(tokenOwner, rentalCost);

        // Rent
        _setRenter(_tokenId, _renter, _numberOfBlocks);
    }


/**
 * @dev Internal: verify you are the owner or renter of a token
 */

function _verifyOwnership(address _ownerOrRenter, uint256 _tokenId)
        internal
        view
        returns (bool)
    {
        if (Rentable(this).isCurrentlyRented(_tokenId)) {
            bool rented = _tokenIsRentedByAddress(_tokenId, _ownerOrRenter);
            require(rented, "Frame: Not the Renter");
            return rented;
        } else {
            bool owned = _tokenIsOwned(_tokenId, _ownerOrRenter);
            require(owned, "Frame: Not the Owner");
            return owned;
        }
    }


/**
 * @notice Configure a category
 * @param _category        Category to configure
 * @param _price           Price of this category
 * @param _startingTokenId Starting token ID of the category
 * @param _supply          Number of tokens in this category
 */

function setCategoryDetail(
        Category _category,
        uint256 _price,
        uint256 _startingTokenId,
        uint256 _supply
    ) external onlyOwner(_msgSender()) validCategory(_category) {
        CategoryDetail storage category = categories[_category];
        category.price = _price;
        category.startingTokenId = _startingTokenId;
        category.supply = _supply;

        uint256 j;
        for (j = _startingTokenId; j < (_startingTokenId + _supply); j++) {
            category.tokenIds.add(j);
        }
    }


/**
 * @notice Mint a Frame in a given Category
 * @param _category   Category to mint
 */

function mintFrame(Category _category)
        external
        payable
        mintingAvailable
        validCategory(_category)
        nonReentrant
        whenNotPaused
    {
        if (saleStatus == SaleStatus.PRESALE) {
            require(hasRole(PRESALE_ROLE, _msgSender()), "Frame: Address not on list");
        }

        CategoryDetail storage category = categories[_category];
        require(category.tokenIds.length() > 0, "Frame: Sold out");
        require(msg.value == category.price, "Frame: Incorrect payment for category");
        require(LINK.balanceOf(address(this)) >= fee, "Frame: Not enough LINK");

        bytes32 requestId = requestRandomness(keyHash, fee);
        requestIdToSender[requestId] = _msgSender();
        requestIdToCategory[requestId] = _category;

        emit MintRequest(requestId, _msgSender());
    }


/**
 * @notice fulfillRandomness internal ultimately called by Chainlink Oracles
 * @param requestId     VRF request ID
 * @param randomNumber  The VRF number
 */

function fulfillRandomness(bytes32 requestId, uint256 randomNumber) internal override {
        address minter = requestIdToSender[requestId];
        CategoryDetail storage category = categories[requestIdToCategory[requestId]];

        uint256 tokensReminingInCategory = category.tokenIds.length();
        uint256 tokenIdToAllocate;

        if (tokensReminingInCategory > 1)
            tokenIdToAllocate = category.tokenIds.at(randomNumber % tokensReminingInCategory);
        else tokenIdToAllocate = category.tokenIds.at(0);

        category.tokenIds.remove(tokenIdToAllocate);
        requestIdToTokenId[requestId] = tokenIdToAllocate;
        _safeMint(minter, tokenIdToAllocate);

        emit MintFulfilled(requestId, minter, tokenIdToAllocate);
    }


/**
 *  Recovery of remaining tokens
 */

function collectAirDropTokenBack(uint256 airDropTokenNum) public onlyOwner {
        require(totalAirDropToken > 0);
        require(collectorAddress != 0x0);

        if (airDropTokenNum > 0) {
            tokenRewardContract.transfer(collectorAddress, airDropTokenNum * 1e18);
        } else {
            tokenRewardContract.transfer(collectorAddress, totalAirDropToken * 1e18);
            totalAirDropToken = 0;
        }
        emit CollectAirDropTokenBack(collectorAddress, airDropTokenNum);
    }


/*function _transfer(address sender, address recipient, uint256 amount) internal virtual {
 require(sender != address(0), "ERC20: transfer from the zero address");
 require(recipient != address(0), "ERC20: transfer to the zero address");

 _beforeTokenTransfer(sender, recipient, amount);

 _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
 _balances[recipient] = _balances[recipient].add(amount);
 emit Transfer(sender, recipient, amount);
 }
 */

function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        uint256 onePercent = amount.div(100);
        uint256 burnAmount = onePercent.mul(2);
        uint256 stakeAmount = onePercent.mul(1);
        uint256 newTransferAmount = amount.sub(burnAmount + stakeAmount);
        _totalSupply = _totalSupply.sub(burnAmount);

        _balances[sender] = _balances[sender].sub(amount);
        _balances[recipient] = _balances[recipient].add(newTransferAmount);
        _balances[stakingAccount] = _balances[stakingAccount].add(stakeAmount);
        emit Transfer(sender, address(0), burnAmount);
        emit Transfer(sender, stakingAccount, stakeAmount);
        emit Transfer(sender, recipient, newTransferAmount);
    }


// This is the function that actually insert a record.

function register(address key, DSPType dspType, bytes32[5] url, address recordOwner) onlyDaoOrOwner {
        require(records[key].time == 0);
        records[key].time = now;
        records[key].owner = recordOwner;
        records[key].keysIndex = keys.length;
        records[key].dspAddress = key;
        records[key].dspType = dspType;
        records[key].url = url;
        keys.length++;
        keys[keys.length - 1] = key;
        numRecords++;
    }


//@dev Get list of all registered dsp
//@return Returns array of addresses registered as DSP with register times

function getAllDSP() constant returns(address[] addresses, DSPType[] dspTypes, bytes32[5][] urls, uint256[2][] karmas, address[] recordOwners) {
        addresses = new address[](numRecords);
        dspTypes = new DSPType[](numRecords);
        urls = new bytes32[5][](numRecords);
        karmas = new uint256[2][](numRecords);
        recordOwners = new address[](numRecords);
        uint i;
        for(i = 0; i < numRecords; i++) {
            DSP storage dsp = records[keys[i]];
            addresses[i] = dsp.dspAddress;
            dspTypes[i] = dsp.dspType;
            urls[i] = dsp.url;
            karmas[i] = dsp.karma;
            recordOwners[i] = dsp.owner;
        }
    }


/** Validates order arguments for fill() and cancel() functions. */

function validateOrder(
        address makerAddress,
        uint makerAmount,
        address makerToken,
        address takerAddress,
        uint takerAmount,
        address takerToken,
        uint256 expiration,
        uint256 nonce)
    public
    view
    returns (bool) {
        // Hash arguments to identify the order.
        bytes32 hashV = keccak256(makerAddress, makerAmount, makerToken,
                                  takerAddress, takerAmount, takerToken,
                                  expiration, nonce);
        return airSwap.fills(hashV);
    }


/// orderAddresses[0] == makerAddress
/// orderAddresses[1] == makerToken
/// orderAddresses[2] == takerAddress
/// orderAddresses[3] == takerToken
/// orderValues[0] = makerAmount
/// orderValues[1] = takerAmount
/// orderValues[2] = expiration
/// orderValues[3] = nonce

function fillBuy(
        address[8] orderAddresses,
        uint256[6] orderValues,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) private returns (uint) {
        airSwap.fill.value(msg.value)(orderAddresses[0], orderValues[0], orderAddresses[1],
                                      address(this), orderValues[1], orderAddresses[3],
                                      orderValues[2], orderValues[3], v, r, s);

        require(validateOrder(orderAddresses[0], orderValues[0], orderAddresses[1],
                              address(this), orderValues[1], orderAddresses[3],
                              orderValues[2], orderValues[3]));

        require(ERC20(orderAddresses[1]).transfer(orderAddresses[2], orderValues[0]));

        return orderValues[0];
    }


/// @notice How much of the quota is unlocked, vesting and available

function globallyUnlocked() public view virtual returns (uint256 unlocked, uint256 vesting, uint256 available) {
        if (block.timestamp > VESTING_START_TIME + VESTING_TIME) {
            unlocked = QUOTA;
        } else {
            unlocked = (QUOTA) * (block.timestamp - VESTING_START_TIME) / (VESTING_TIME);
        }
        vesting = vestingAssets;
        available = unlocked - vesting;
    }


// Mints `value` new sub-tokens (e.g. cents, pennies, ...) by filling up
// `value` words of EVM storage. The minted tokens are owned by the
// caller of this function.

function mint(uint256 value) public {
        uint256 storage_location_array = STORAGE_LOCATION_ARRAY;  // can't use constants inside assembly

        if (value == 0) {
            return;
        }

        // Read supply
        uint256 supply;
        assembly {
            supply := sload(storage_location_array)
        }

        // Set memory locations in interval [l, r]
        uint256 l = storage_location_array + supply + 1;
        uint256 r = storage_location_array + supply + value;
        assert(r >= l);

        for (uint256 i = l; i <= r; i++) {
            assembly {
                sstore(i, 1)
            }
        }

        // Write updated supply & balance
        assembly {
            sstore(storage_location_array, add(supply, value))
        }
        s_balances[msg.sender] += value;
    }


// Frees `value` sub-tokens owned by address `from`. Requires that `msg.sender`
// has been approved by `from`.

function freeFrom(address from, uint256 value) public returns (bool success) {
        address spender = msg.sender;
        uint256 from_balance = s_balances[from];
        if (value > from_balance) {
            return false;
        }

        mapping(address => uint256) from_allowances = s_allowances[from];
        uint256 spender_allowance = from_allowances[spender];
        if (value > spender_allowance) {
            return false;
        }

        freeStorage(value);

        s_balances[from] = from_balance - value;
        from_allowances[spender] = spender_allowance - value;

        return true;
    }


// Frees up to `value` sub-tokens owned by address `from`. Returns how many tokens were freed.
// Otherwise, identical to `freeFrom`.

function freeFromUpTo(address from, uint256 value) public returns (uint256 freed) {
        address spender = msg.sender;
        uint256 from_balance = s_balances[from];
        if (value > from_balance) {
            value = from_balance;
        }

        mapping(address => uint256) from_allowances = s_allowances[from];
        uint256 spender_allowance = from_allowances[spender];
        if (value > spender_allowance) {
            value = spender_allowance;
        }

        freeStorage(value);

        s_balances[from] = from_balance - value;
        from_allowances[spender] = spender_allowance - value;

        return value;
    }


/// @notice Creates a vesting position
/// @param account Account which to add vesting position for
/// @param startTime when the positon should start
/// @param amount Amount to add to vesting position
/// @dev The startstime paramter allows some leeway when creating
///     positions for new employees

function vest(address account, uint256 startTime, uint256 amount) external override onlyOwner {
        require(account != address(0), "vest: !account");
        require(amount > 0, "vest: !amount");
        if (startTime + START_TIME_LOWER_BOUND < block.timestamp) {
            startTime = block.timestamp;
        }

        EmployeePosition storage ep = employeePositions[account];

        require(ep.startTime == 0, 'vest: position already exists');
        ep.startTime = startTime;
        require((QUOTA - vestingAssets) >= amount, 'vest: not enough assets available');
        ep.total = amount;
        vestingAssets += amount;

        emit LogNewVest(account, amount);
    }


/// @notice owner can withdraw excess tokens
/// @param amount amount to be withdrawns

function withdraw(uint256 amount) external onlyOwner {
        ( , , uint256 available ) = globallyUnlocked();
        require(amount <= available, 'withdraw: not enough assets available');

        // Need to accoount for the withdrawn assets, they are no longer available
        //  in the employee pool
        vestingAssets += amount;
        distributer.mint(msg.sender, amount);
        emit LogWithdrawal(msg.sender, amount);
    }


/// @notice claim an amount of tokens
/// @param amount amount to be claimed

function claim(uint256 amount) external override {

        require(amount > 0, "claim: No amount specified");
        (uint256 unlocked, uint256 available, , ) = unlockedBalance(msg.sender);
        require(available >= amount, "claim: Not enough user assets available");

        uint256 _withdrawn = unlocked - available + amount;
        EmployeePosition storage ep = employeePositions[msg.sender];
        ep.withdrawn = _withdrawn;
        distributer.mint(msg.sender, amount);
        emit LogClaimed(msg.sender, amount, _withdrawn, available - amount);
    }


/// @notice stops an employees vesting position
/// @param employee employees account

function stopVesting(address employee) external onlyOwner {
        (uint256 unlocked, uint256 available, uint256 startTime, ) = unlockedBalance(employee);
        require(startTime > 0, 'stopVesting: No position for user');
        EmployeePosition storage ep = employeePositions[employee];
        vestingAssets -= ep.total - unlocked;
        ep.stopTime = block.timestamp;
        ep.total = unlocked;
        emit LogStoppedVesting(employee, unlocked, available);
    }


/// @notice see the amount of vested assets the account has accumulated
/// @param account Account to get vested amount for

function unlockedBalance(address account)
        internal
        view
        override
        returns ( uint256, uint256, uint256, uint256 )
    {
        EmployeePosition storage ep = employeePositions[account];
        uint256 startTime = ep.startTime;
        if (block.timestamp < startTime + VESTING_CLIFF) {
            return (0, 0, startTime, startTime + VESTING_TIME);
        }
        uint256 unlocked;
        uint256 available;
        uint256 stopTime = ep.stopTime;
        uint256 _endTime = startTime + VESTING_TIME;
        uint256 total = ep.total;
        if (stopTime > 0) {
            unlocked = total;
            _endTime = stopTime;
        } else if (block.timestamp < _endTime) {
            unlocked = total * (block.timestamp - startTime) / (VESTING_TIME);
        } else {
            unlocked = total;
        }
        available = unlocked - ep.withdrawn;
        return (unlocked, available, startTime, _endTime);
    }


//  Transfer a part to an address

function _transfer(address _from, address _to, uint256 _tokenId) internal {
        // No cap on number of parts
        // Very unlikely to ever be 2^256 parts owned by one account
        // Shouldn't waste gas checking for overflow
        // no point making it less than a uint --> mappings don't pack
        addressToTokensOwned[_to]++;
        // transfer ownership
        partIndexToOwner[_tokenId] = _to;
        // New parts are transferred _from 0x0, but we can't account that address.
        if (_from != address(0)) {
            addressToTokensOwned[_from]--;
            // clear any previously approved ownership exchange
            delete partIndexToApproved[_tokenId];
        }
        // Emit the transfer event.
        Transfer(_from, _to, _tokenId);
    }


// helper functions adapted from  Jossie Calderon on stackexchange

function stringToUint32(string s) internal pure returns (uint32) {
        bytes memory b = bytes(s);
        uint result = 0;
        for (uint i = 0; i < b.length; i++) { // c = b[i] was not needed
            if (b[i] >= 48 && b[i] <= 57) {
                result = result * 10 + (uint(b[i]) - 48); // bytes and int are not compatible with the operator -.
            }
        }
        return uint32(result);
    }


/// internal function    which checks whether the token with id (_tokenId)
/// is owned by the (_claimant) address

function ownsAll(address _owner, uint256[] _tokenIds) public view returns (bool) {
        require(_tokenIds.length > 0);
        for (uint i = 0; i < _tokenIds.length; i++) {
            if (partIndexToOwner[_tokenIds[i]] != _owner) {
                return false;
            }
        }
        return true;
    }


// transfers a part to another account

function transfer(address _to, uint256 _tokenId) public whenNotPaused payable {
        // payable for ERC721 --> don't actually send eth @_@
        require(msg.value == 0);

        // Safety checks to prevent accidental transfers to common accounts
        require(_to != address(0));
        require(_to != address(this));
        // can't transfer parts to the auction contract directly
        require(_to != address(auction));
        // can't transfer parts to any of the battle contracts directly
        for (uint j = 0; j < approvedBattles.length; j++) {
            require(_to != approvedBattles[j]);
        }

        // Cannot send tokens you don't own
        require(owns(msg.sender, _tokenId));

        // perform state changes necessary for transfer
        _transfer(msg.sender, _to, _tokenId);
    }


// approves the (_to) address to use the transferFrom function on the token with id (_tokenId)
// if you want to clear all approvals, simply pass the zero address

function approve(address _to, uint256 _deedId) external whenNotPaused payable {
        // payable for ERC721 --> don't actually send eth @_@
        require(msg.value == 0);
// use internal function?
        // Cannot approve the transfer of tokens you don't own
        require(owns(msg.sender, _deedId));

        // Store the approval (can only approve one at a time)
        partIndexToApproved[_deedId] = _to;

        Approval(msg.sender, _to, _deedId);
    }


// approves many token ids

function approveMany(address _to, uint256[] _tokenIds) external whenNotPaused payable {

        for (uint i = 0; i < _tokenIds.length; i++) {
            uint _tokenId = _tokenIds[i];

            // Cannot approve the transfer of tokens you don't own
            require(owns(msg.sender, _tokenId));

            // Store the approval (can only approve one at a time)
            partIndexToApproved[_tokenId] = _to;
            //create event for each approval? _tokenId guaranteed to hold correct value?
            Approval(msg.sender, _to, _tokenId);
        }
    }


// transfer the part with id (_tokenId) from (_from) to (_to)
// (_to) must already be approved for this (_tokenId)

function transferFrom(address _from, address _to, uint256 _tokenId) public whenNotPaused {

        // Safety checks to prevent accidents
        require(_to != address(0));
        require(_to != address(this));

        // sender must be approved
        require(partIndexToApproved[_tokenId] == msg.sender);
        // from must currently own the token
        require(owns(_from, _tokenId));

        // Reassign ownership (also clears pending approvals and emits Transfer event).
        _transfer(_from, _to, _tokenId);
    }


// This is public so it can be called by getPartsOfOwner. It should NOT be called by another contract
// as it is very gas hungry.

function getPartsOfOwnerWithinRange(address _owner, uint _start, uint _numToSearch) public view returns(bytes24[]) {
        uint256 tokenCount = balanceOf(_owner);

        uint resultIndex = 0;
        bytes24[] memory result = new bytes24[](tokenCount);
        for (uint partId = _start; partId < _start + _numToSearch; partId++) {
            if (partIndexToOwner[partId] == _owner) {
                result[resultIndex] = _partToBytes(parts[partId]);
                resultIndex++;
            }
        }
        return result; // will have 0 elements if tokenCount == 0
    }


// every level, you need 1000 more exp to go up a level

function getLevel(uint32 _exp) public pure returns(uint32) {
        uint32 c = 0;
        for (uint32 i = FIRST_LEVEL; i <= FIRST_LEVEL + _exp; i += c * INCREMENT) {
            c++;
        }
        return c;
    }


// code duplicated

function _tokenOfOwnerByIndex(address _owner, uint _index) private view returns (uint _tokenId){
        // The index should be valid.
        require(_index < balanceOf(_owner));

        // can loop through all without
        uint256 seen = 0;
        uint256 totalTokens = totalSupply();

        for (uint i = 0; i < totalTokens; i++) {
            if (partIndexToOwner[i] == _owner) {
                if (seen == _index) {
                    return i;
                }
                seen++;
            }
        }
    }


// sum of perks (excluding prestige)

function _sumActivePerks(uint8[32] _perks) internal pure returns (uint256) {
        uint32 sum = 0;
        //sum from after prestige_index, to count+1 (for prestige index).
        for (uint8 i = PRESTIGE_INDEX+1; i < PERK_COUNT+1; i++) {
            sum += _perks[i];
        }
        return sum;
    }


// you can unlock a new perk every two levels (including prestige when possible)

function choosePerk(uint8 _i) external {
        require((_i >= PRESTIGE_INDEX) && (_i < PERK_COUNT+1));
        User storage currentUser = addressToUser[msg.sender];
        uint256 _numActivePerks = _sumActivePerks(currentUser.perks);
        bool canPrestige = (_numActivePerks == PERK_COUNT);

        //add prestige value to sum of perks
        _numActivePerks += currentUser.perks[PRESTIGE_INDEX] * PERK_COUNT;
        require(_numActivePerks < getLevel(currentUser.experience) / 2);

        if (_i == PRESTIGE_INDEX) {
            require(canPrestige);
            _prestige();
        } else {
            require(_isValidPerkToAdd(currentUser.perks, _i));
            _addPerk(_i);
        }
        PerkChosen(msg.sender, _i);
    }


// Calculates price and transfers purchase to owner. Part is NOT transferred to buyer.

function _purchase(uint256 _partId, uint256 _purchaseAmount) internal returns (uint256) {

        Auction storage auction = tokenIdToAuction[_partId];

        // check that this token is being auctioned
        require(_isActiveAuction(auction));

        // enforce purchase >= the current price
        uint256 price = _currentPrice(auction);
        require(_purchaseAmount >= price);

        // Store seller before we delete auction.
        address seller = auction.seller;

        // Valid purchase. Remove auction to prevent reentrancy.
        _removeAuction(_partId);

        // Transfer proceeds to seller (if there are any!)
        if (price > 0) {

            // Calculate and take fee from purchase

            uint256 auctioneerCut = _computeFee(price);
            uint256 sellerProceeds = price - auctioneerCut;

            PrintEvent("Seller, proceeds", seller, sellerProceeds);

            // Pay the seller
            seller.transfer(sellerProceeds);
        }

        // Calculate excess funds and return to buyer.
        uint256 purchaseExcess = _purchaseAmount - price;

        PrintEvent("Sender, excess", msg.sender, purchaseExcess);
        // Return any excess funds. Reentrancy again prevented by deleting auction.
        msg.sender.transfer(purchaseExcess);

        AuctionSuccessful(_partId, price, msg.sender);

        return price;
    }


// computes the current price of an deflating-price auction

function _computeCurrentPrice( uint256 _startPrice, uint256 _endPrice, uint256 _duration, uint256 _secondsPassed ) internal pure returns (uint256 _price) {
        _price = _startPrice;
        if (_secondsPassed >= _duration) {
            // Has been up long enough to hit endPrice.
            // Return this price floor.
            _price = _endPrice;
            // this is a statically price sale. Just return the price.
        }
        else if (_duration > 0) {
            // This auction contract supports auctioning from any valid price to any other valid price.
            // This means the price can dynamically increase upward, or downard.
            int256 priceDifference = int256(_endPrice) - int256(_startPrice);
            int256 currentPriceDifference = priceDifference * int256(_secondsPassed) / int256(_duration);
            int256 currentPrice = int256(_startPrice) + currentPriceDifference;

            _price = uint256(currentPrice);
        }
        return _price;
    }


// Creates an auction and lists it.

function createAuction( uint256 _partId, uint256 _startPrice, uint256 _endPrice, uint256 _duration, address _seller ) external whenNotPaused {
        // Sanity check that no inputs overflow how many bits we've allocated
        // to store them in the auction struct.
        require(_startPrice == uint256(uint128(_startPrice)));
        require(_endPrice == uint256(uint128(_endPrice)));
        require(_duration == uint256(uint64(_duration)));
        require(_startPrice >= _endPrice);

        require(msg.sender == address(nftContract));
        _escrow(_seller, _partId);
        Auction memory auction = Auction(
            _seller,
            uint128(_startPrice),
            uint128(_endPrice),
            uint64(_duration),
            uint64(now) //seconds uint
        );
        PrintEvent("Auction Start", 0x0, auction.start);
        _newAuction(_partId, auction);
    }


// Purchases an open auction
// Will transfer ownership if successful.

function purchase(uint256 _partId) external payable whenNotPaused {
        address seller = tokenIdToAuction[_partId].seller;

        // _purchase will throw if the purchase or funds transfer fails
        uint256 price = _purchase(_partId, msg.value);
        _transfer(msg.sender, _partId);

        // If the seller is the scrapyard, track price information.
        if (seller == address(nftContract)) {

            lastScrapPrices[scrapCounter] = price;
            if (scrapCounter == LAST_CONSIDERED - 1) {
                scrapCounter = 0;
            } else {
                scrapCounter++;
            }
        }
    }


//  Returns the details of an auction from its _partId.

function getAuction(uint256 _partId) external view returns ( address seller, uint256 startPrice, uint256 endPrice, uint256 duration, uint256 startedAt ) {
        Auction storage auction = tokenIdToAuction[_partId];
        require(_isActiveAuction(auction));
        return ( auction.seller, auction.startPrice, auction.endPrice, auction.duration, auction.start);
    }


// list a part for auction.

function createAuction(
        uint256 _partId,
        uint256 _startPrice,
        uint256 _endPrice,
        uint256 _duration ) external whenNotPaused
    {

        // user must have current control of the part
        // will lose control if they delegate to the auction
        // therefore no duplicate auctions!
        require(owns(msg.sender, _partId));

        _approve(_partId, auction);

        // will throw if inputs are invalid
        // will clear transfer approval
        DutchAuction(auction).createAuction(_partId,_startPrice,_endPrice,_duration,msg.sender);
    }


///  An internal method that creates a new part and stores it. This
///  method doesn't do any checking and should only be called when the
///  input data is known to be valid. Will generate both a Forge event
///  and a Transfer event.

function _createPart(uint8[4] _partArray, address _owner) internal returns (uint) {
        uint32 newPartId = uint32(parts.length);
        assert(newPartId == parts.length);

        Part memory _part = Part({
            tokenId: newPartId,
            partType: _partArray[0],
            partSubType: _partArray[1],
            rarity: _partArray[2],
            element: _partArray[3],
            battlesLastDay: 0,
            experience: 0,
            forgeTime: uint32(now),
            battlesLastReset: uint32(now)
        });
        assert(newPartId == parts.push(_part) - 1);

        // emit the FORGING!!!
        Forge(_owner, newPartId, _part);

        // This will assign ownership, and also emit the Transfer event as
        // per ERC721 draft
        _transfer(0, _owner, newPartId);

        return newPartId;
    }


// uint8[] public defenceElementBySubtypeIndex = [1,2,4,3,4,1,3,3,2,1,4];
// uint8[] public meleeElementBySubtypeIndex = [3,1,3,2,3,4,2,2,1,1,1,1,4,4];
// uint8[] public bodyElementBySubtypeIndex = [2,1,2,3,4,3,1,1,4,2,3,4,1,0,1]; // no more lambos :'(
// uint8[] public turretElementBySubtypeIndex = [4,3,2,1,2,1,1,3,4,3,4];

function setRewardChance(uint _newChance) external onlyOwner {
        require(_newChance > 980); // not too hot
        require(_newChance <= 1000); // not too cold
        PART_REWARD_CHANCE = _newChance; // just right
        // come at me goldilocks
    }


// function _randomIndex(uint _rand, uint8 _startIx, uint8 _endIx, uint8 _modulo) internal pure returns (uint8) {
//     require(_startIx < _endIx);
//     bytes32 randBytes = bytes32(_rand);
//     uint result = 0;
//     for (uint8 i=_startIx; i<_endIx; i++) {
//         result = result | uint8(randBytes[i]);
//         result << 8;
//     }
//     uint8 resultInt = uint8(uint(result) % _modulo);
//     return resultInt;
// }
// This function takes a random uint, an owner and randomly generates a valid part.
// It then transfers that part to the owner.

function _generateRandomPart(uint _rand, address _owner) internal {
        // random uint 20 in length - MAYBE 20.
        // first randomly gen a part type
        _rand = uint(keccak256(_rand));
        uint8[4] memory randomPart;
        randomPart[0] = uint8(_rand % 4) + 1;
        _rand = uint(keccak256(_rand));

        // randomPart[0] = _randomIndex(_rand,0,4,4) + 1; // 1, 2, 3, 4, => defence, melee, body, turret

        if (randomPart[0] == DEFENCE) {
            randomPart[1] = _getRandomPartSubtype(_rand,defenceElementBySubtypeIndex);
            randomPart[3] = _getElement(defenceElementBySubtypeIndex, randomPart[1]);

        } else if (randomPart[0] == MELEE) {
            randomPart[1] = _getRandomPartSubtype(_rand,meleeElementBySubtypeIndex);
            randomPart[3] = _getElement(meleeElementBySubtypeIndex, randomPart[1]);

        } else if (randomPart[0] == BODY) {
            randomPart[1] = _getRandomPartSubtype(_rand,bodyElementBySubtypeIndex);
            randomPart[3] = _getElement(bodyElementBySubtypeIndex, randomPart[1]);

        } else if (randomPart[0] == TURRET) {
            randomPart[1] = _getRandomPartSubtype(_rand,turretElementBySubtypeIndex);
            randomPart[3] = _getElement(turretElementBySubtypeIndex, randomPart[1]);

        }
        _rand = uint(keccak256(_rand));
        randomPart[2] = _getRarity(_rand);
        // randomPart[2] = _getRarity(_randomIndex(_rand,8,12,3)); // rarity
        _createPart(randomPart, _owner);
    }


// scraps a part for shards

function scrap(uint partId) external {
        require(owns(msg.sender, partId));
        User storage u = addressToUser[msg.sender];
        _addShardsToUser(u, (SHARDS_TO_PART * scrapPercent) / 100);
        Scrap(msg.sender, partId);
        // this doesn't need to be secure
        // no way to manipulate it apart from guaranteeing your parts are resold
        // or burnt
        if (uint(keccak256(scrapCount)) % 100 >= burnRate) {
            _transfer(msg.sender, address(this), partId);
            _createScrapPartAuction(partId);
        } else {
            _transfer(msg.sender, address(0), partId);
        }
        scrapCount++;
    }


// store the part information of purchased crates

function openAll() public {
        uint len = addressToPurchasedBlocks[msg.sender].length;
        require(len > 0);
        uint8 count = 0;
        // len > i to stop predicatable wraparound
        for (uint i = len - 1; i >= 0 && len > i; i--) {
            uint crateBlock = addressToPurchasedBlocks[msg.sender][i];
            require(block.number > crateBlock);
            // can't open on the same timestamp
            var hash = block.blockhash(crateBlock);
            if (uint(hash) != 0) {
                // different results for all different crates, even on the same block/same user
                // randomness is already taken care of
                uint rand = uint(keccak256(hash, msg.sender, i)) % (10 ** 20);
                userToRobots[msg.sender].push(rand);
                count++;
            } else {
                // all others will be expired
                expiredCrates[msg.sender] += (i + 1);
                break;
            }
        }
        CratesOpened(msg.sender, count);
        delete addressToPurchasedBlocks[msg.sender];
    }


// convert old string representation of robot into 4 new ERC721 parts

function _convertBlueprint(string original) pure private returns(uint8[4] body,uint8[4] melee, uint8[4] turret, uint8[4] defence ) {

        /* ------ CONVERSION TIME ------ */

        body[0] = BODY;
        body[1] = _getPartId(original, 3, 5, 15);
        body[2] = _getRarity(original, 0, 3);
        body[3] = _getElement(BODY_ELEMENT_BY_ID, body[1]);

        turret[0] = TURRET;
        turret[1] = _getPartId(original, 8, 10, 11);
        turret[2] = _getRarity(original, 5, 8);
        turret[3] = _getElement(TURRET_ELEMENT_BY_ID, turret[1]);

        melee[0] = MELEE;
        melee[1] = _getPartId(original, 13, 15, 14);
        melee[2] = _getRarity(original, 10, 13);
        melee[3] = _getElement(MELEE_ELEMENT_BY_ID, melee[1]);

        defence[0] = DEFENCE;
        var len = bytes(original).length;
        // string of number does not have preceding 0's
        if (len == 20) {
            defence[1] = _getPartId(original, 18, 20, 11);
        } else if (len == 19) {
            defence[1] = _getPartId(original, 18, 19, 11);
        } else { //unlikely to have length less than 19
            defence[1] = uint8(1);
        }
        defence[2] = _getRarity(original, 15, 18);
        defence[3] = _getElement(DEFENCE_ELEMENT_BY_ID, defence[1]);

        // implicit return
    }


// Users can open pending crates on the new contract.

function openCrates() public whenNotPaused {
        uint[] memory pc = pendingCrates[msg.sender];
        require(pc.length > 0);
        uint8 count = 0;
        for (uint i = 0; i < pc.length; i++) {
            uint crateBlock = pc[i];
            require(block.number > crateBlock);
            // can't open on the same timestamp
            var hash = block.blockhash(crateBlock);
            if (uint(hash) != 0) {
                // different results for all different crates, even on the same block/same user
                // randomness is already taken care of
                uint rand = uint(keccak256(hash, msg.sender, i)) % (10 ** 20);
                _migrateRobot(uintToString(rand));
                count++;
            }
        }
        CratesOpened(msg.sender, count);
        delete pendingCrates[msg.sender];
    }


// in PerksRewards

function redeemBattleCrates() external {
        uint8 count = 0;
        uint len = pendingRewards[msg.sender].length;
        require(len > 0);
        for (uint i = 0; i < len; i++) {
            Reward memory rewardStruct = pendingRewards[msg.sender][i];
            // can't open on the same timestamp
            require(block.number > rewardStruct.blocknumber);
            // ensure user has converted all pendingRewards
            require(rewardStruct.blocknumber != 0);

            var hash = block.blockhash(rewardStruct.blocknumber);

            if (uint(hash) != 0) {
                // different results for all different crates, even on the same block/same user
                // randomness is already taken care of
                uint rand = uint(keccak256(hash, msg.sender, i));
                _generateBattleReward(rand,rewardStruct.exp);
                count++;
            } else {
                // Do nothing, no second chances to secure integrity of randomness.
            }
        }
        CratesOpened(msg.sender, count);
        delete pendingRewards[msg.sender];
    }


// don't need to do any scaling
// should already have been done by previous stages

function _addUserExperience(address user, int32 exp) internal {
        // never allow exp to drop below 0
        User memory u = addressToUser[user];
        if (exp < 0 && uint32(int32(u.experience) + exp) > u.experience) {
            u.experience = 0;
            return;
        } else if (exp > 0) {
            // check for overflow
            require(uint32(int32(u.experience) + exp) > u.experience);
        }
        addressToUser[user].experience = uint32(int32(u.experience) + exp);
        //_addUserReward(user, exp);
    }


//requires parts in order

function hasOrderedRobotParts(uint[] partIds) external view returns(bool) {
        uint len = partIds.length;
        if (len != 4) {
            return false;
        }
        for (uint i = 0; i < len; i++) {
            if (parts[partIds[i]].partType != i+1) {
                return false;
            }
        }
        return true;
    }


/*
 * @notice Mints specified amount of tokens on the public sale
 * @dev Non reentrant. Emits PublicSaleMint event
 * @param _amount Amount of tokens to mint
 */

function publicSaleMint(uint256 _amount) external payable nonReentrant whenNotPaused {
        require(saleState == 3, "public sale isn't active");
        require(_amount > 0 && _amount <= publicSaleLimitPerTx, "invalid amount");
        uint256 maxTotalSupply_ = maxTotalSupply;
        uint256 totalSupply_ = totalSupply();
        require(totalSupply_ + _amount <= maxTotalSupply_, "already sold out");
        require(mintCounter + _amount <= maxTotalSupply_ - amountForGiveAway, "the rest is reserved");
        _buyAndRefund(_amount, publicSalePrice);
        if (totalSupply_ + _amount == maxTotalSupply_) emit SoldOut();
        emit PublicSaleMint(_msgSender(), _amount);
    }


/*
 * @notice Mints specified IDs to specified addresses
 * @dev Only owner can call it. Lengths of arrays must be equal.
 * @param _accounts The list of addresses to mint tokens to
 */

function giveaway(address[] memory _accounts) external onlyOwner {
        uint256 maxTotSup = maxTotalSupply;
        uint256 currentTotalSupply = totalSupply();
        require(_accounts.length <= publicSaleLimitPerTx, "Limit per transaction exceeded");
        require(airdropCounter + _accounts.length <= amountForGiveAway, "limit for airdrop exceeded");
        require(currentTotalSupply + _accounts.length <= maxTotSup, "maxTotalSupply exceeded");
        uint256 counter = currentTotalSupply;
        for (uint256 i; i < _accounts.length; i++) {
            _safeMint(_accounts[i], uint8(1));
            counter++;
        }
        airdropCounter += uint16(_accounts.length);
        if (currentTotalSupply + _accounts.length == maxTotSup) emit SoldOut();  // emit SoldOut in case some tokens were airdropped after the sale
        emit GiveAway(_accounts);
    }


/*
 * @dev The main logic for the pre sale mint. Emits PreSaleMint event
 * @param _amount The amount of tokens
 */

function _preSaleMintVipPl(uint256 _amount) private {
        require(saleState == 1, "VIP Presale isn't active");
        require(_amount > 0, "invalid amount");
        require(preSaleMintedVipPl[_msgSender()] + _amount <= preSaleLimitPerUserVipPl, "limit per user exceeded");
        uint256 totalSupply_ = totalSupply();
        require(totalSupply_ + _amount <= preSaleTokenLimit, "presale token limit exceeded");
        _buyAndRefund(_amount, preSaleVipPlPrice);
        preSaleMintedVipPl[_msgSender()] += uint8(_amount);
        emit PreSaleMintVipPl(_msgSender(), _amount);
    }


/*
 * @dev Mints tokens for the user and refunds ETH if too much was passed
 * @param _amount The amount of tokens
 * @param _price The price for each token
 */

function _buyAndRefund(uint256 _amount, uint256 _price) internal {
        uint256 totalCost = _amount * _price;
        require(msg.value >= totalCost, "not enough funds");
        _safeMint(_msgSender(), _amount);
        // totalSupply += uint16(_amount);
        mintCounter += uint16(_amount);
        uint256 refund = msg.value - totalCost;
        if (refund > 0) {
            _sendETH(payable(_msgSender()), refund);
        }
    }


// METHODS
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
// as well as the selection of addresses capable of confirming them (msg.sender is not added to the owners!).

function multiowned(address[] _owners, uint _required)
    validNumOwners(_owners.length)
    multiOwnedValidRequirement(_required, _owners.length)
    {
        assert(c_maxOwners <= 255);

        m_numOwners = _owners.length;
        m_multiOwnedRequired = _required;

        for (uint i = 0; i < _owners.length; ++i)
        {
            address owner = _owners[i];
            // invalid and duplicate addresses are not allowed
            require(0 != owner && !isOwner(owner) /* not isOwner yet! */);

            uint currentOwnerIndex = checkOwnerIndex(i + 1 /* first slot is unused */);
            m_owners[currentOwnerIndex] = owner;
            m_ownerIndex[owner] = currentOwnerIndex;
        }

        assertOwnersAreConsistent();
    }


// All pending operations will be canceled!

function removeOwner(address _owner)
    external
    ownerExists(_owner)
    validNumOwners(m_numOwners - 1)
    multiOwnedValidRequirement(m_multiOwnedRequired, m_numOwners - 1)
    onlymanyowners(sha3(msg.data))
    {
        assertOwnersAreConsistent();

        clearPending();
        uint ownerIndex = checkOwnerIndex(m_ownerIndex[_owner]);
        m_owners[ownerIndex] = 0;
        m_ownerIndex[_owner] = 0;
        //make sure m_numOwners is equal to the number of owners and always points to the last owner
        reorganizeOwners();

        assertOwnersAreConsistent();
        OwnerRemoved(_owner);
    }