// @dev create and begin new auction.

function createAuction(uint40 _cutieId, uint128 _startPrice, uint128 _endPrice, uint40 _duration, address _seller)
        public whenNotPaused payable
    {
        require(_isOwner(msg.sender, _cutieId));
        _escrow(msg.sender, _cutieId);
        Auction memory auction = Auction(
            _startPrice,
            _endPrice,
            _seller,
            _duration,
            uint40(now),
            uint128(msg.value)
        );
        _addAuction(_cutieId, auction);
    }


// @dev Returns auction info for a token on auction.
// @param _cutieId - ID of token on auction.

function getAuctionInfo(uint40 _cutieId)
        public
        view
        returns
    (
        address seller,
        uint128 startPrice,
        uint128 endPrice,
        uint40 duration,
        uint40 startedAt,
        uint128 featuringFee
    ) {
        Auction storage auction = cutieIdToAuction[_cutieId];
        require(_isOnAuction(auction));
        return (
            auction.seller,
            auction.startPrice,
            auction.endPrice,
            auction.duration,
            auction.startedAt,
            auction.featuringFee
        );
    }


// @dev create and start a new auction
// @param _cutieId - ID of cutie to auction, sender must be owner.
// @param _startPrice - Price of item (in wei) at the beginning of auction.
// @param _endPrice - Price of item (in wei) at the end of auction.
// @param _duration - Length of auction (in seconds).
// @param _seller - Seller

function createAuction(
        uint40 _cutieId,
        uint128 _startPrice,
        uint128 _endPrice,
        uint40 _duration,
        address _seller
    )
        public
        payable
    {
        require(msg.sender == address(coreContract));
        _escrow(_seller, _cutieId);
        Auction memory auction = Auction(
            _startPrice,
            _endPrice,
            _seller,
            _duration,
            uint40(now),
            uint128(msg.value)
        );
        _addAuction(_cutieId, auction);
    }


/**
 	 * @notice Add a token ID to the list of a given address
 	 * @dev Throws if the receiver address has hit ownership limit or the PixelCon already has an owner
 	 * @param _to Address representing the new owner of the given token ID
 	 * @param _tokenId ID of the token to be added to the tokens list of the given address
 	 */

function addTokenTo(address _to, uint256 _tokenId) internal
	{
		uint64[] storage ownedList = ownedTokens[_to];
		TokenLookup storage lookupData = tokenLookup[_tokenId];
		require(ownedList.length < uint256(2 ** 32) - 1, "Max number of PixelCons per owner has been reached");
		require(lookupData.owner == address(0), "PixelCon already has an owner");
		lookupData.owner = _to;

		//update ownedTokens references
		uint ownedListIndex = ownedList.length;
		ownedList.length++;
		lookupData.ownedIndex = uint32(ownedListIndex);
		ownedList[ownedListIndex] = lookupData.tokenIndex;
	}


/**
 * @notice see IMedia
 */

function mintWithSig(
        address creator,
        MediaData memory data,
        IMarket.BidShares memory bidShares,
        EIP712Signature memory sig
    ) public override nonReentrant {
        require(
            sig.deadline == 0 || sig.deadline >= block.timestamp,
            "Media: mintWithSig expired"
        );

        bytes32 domainSeparator = _calculateDomainSeparator();

        bytes32 digest =
            keccak256(
                abi.encodePacked(
                    "\x19\x01",
                    domainSeparator,
                    keccak256(
                        abi.encode(
                            MINT_WITH_SIG_TYPEHASH,
                            data.contentHash,
                            data.metadataHash,
                            bidShares.creator.value,
                            mintWithSigNonces[creator]++,
                            sig.deadline
                        )
                    )
                )
            );

        address recoveredAddress = ecrecover(digest, sig.v, sig.r, sig.s);

        require(
            recoveredAddress != address(0) && creator == recoveredAddress && (owner() == recoveredAddress || authorized[recoveredAddress]),
            "Media: Signature invalid"
        );

        _mintForCreator(recoveredAddress, data, bidShares);
    }


/**
 * @notice See IMedia
 * @dev This method is loosely based on the permit for ERC-20 tokens in  EIP-2612, but modified
 * for ERC-721.
 */

function permit(
        address spender,
        uint256 tokenId,
        EIP712Signature memory sig
    ) public override nonReentrant onlyExistingToken(tokenId) {
        require(
            sig.deadline == 0 || sig.deadline >= block.timestamp,
            "Media: Permit expired"
        );
        require(spender != address(0), "Media: spender cannot be 0x0");
        bytes32 domainSeparator = _calculateDomainSeparator();

        bytes32 digest =
            keccak256(
                abi.encodePacked(
                    "\x19\x01",
                    domainSeparator,
                    keccak256(
                        abi.encode(
                            PERMIT_TYPEHASH,
                            spender,
                            tokenId,
                            permitNonces[ownerOf(tokenId)][tokenId]++,
                            sig.deadline
                        )
                    )
                )
            );

        address recoveredAddress = ecrecover(digest, sig.v, sig.r, sig.s);

        require(
            recoveredAddress != address(0) &&
                ownerOf(tokenId) == recoveredAddress,
            "Media: Signature invalid"
        );

        _approve(spender, tokenId);
    }


/**
 * @notice Creates a new token for `creator`. Its token ID will be automatically
 * assigned (and available on the emitted {IERC721-Transfer} event), and the token
 * URI autogenerated based on the base URI passed at construction.
 *
 * See {ERC721-_safeMint}.
 *
 * On mint, also set the sha256 hashes of the content and its metadata for integrity
 * checks, along with the initial URIs to point to the content and metadata. Attribute
 * the token ID to the creator, mark the content hash as used, and set the bid shares for
 * the media's market.
 *
 * Note that although the content hash must be unique for future mints to prevent duplicate media,
 * metadata has no such requirement.
 */

function _mintForCreator(
        address creator,
        MediaData memory data,
        IMarket.BidShares memory bidShares
    ) internal onlyValidURI(data.tokenURI) onlyValidURI(data.metadataURI) {
        require(data.contentHash != 0, "Media: content hash must be non-zero");
        require(
            _contentHashes[data.contentHash] == false,
            "Media: a token has already been created with this content hash"
        );
        require(
            data.metadataHash != 0,
            "Media: metadata hash must be non-zero"
        );

        uint256 tokenId = _tokenIdTracker.current();

        _safeMint(creator, tokenId);
        _tokenIdTracker.increment();
        _setTokenContentHash(tokenId, data.contentHash);
        _setTokenMetadataHash(tokenId, data.metadataHash);
        _setTokenMetadataURI(tokenId, data.metadataURI);
        _setTokenURI(tokenId, data.tokenURI);
        _creatorTokens[creator].add(tokenId);
        _contentHashes[data.contentHash] = true;

        tokenCreators[tokenId] = creator;
        previousTokenOwners[tokenId] = creator;
        IMarket(marketContract).setBidShares(tokenId, bidShares);
    }


/**
 * @dev Calculates EIP712 DOMAIN_SEPARATOR based on the current contract and chain ID.
 */

function _calculateDomainSeparator() internal view returns (bytes32) {
        uint256 chainID;
        /* solium-disable-next-line */
        assembly {
            chainID := chainid()
        }

        return
            keccak256(
                abi.encode(
                    keccak256(
                        "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
                    ),
                    keccak256(bytes("Arto")),
                    keccak256(bytes("1")),
                    chainID,
                    address(this)
                )
            );
    }


/**
 * @dev Withdraw ERC-20 token of this contract
 */

function withdrawToken(address tokenAddress) external onlyOwner contractActive{
        require(tokenAddress != address(0), "Contract address is zero address");
        require(tokenAddress != address(this), "Can not transfer self token");

        IERC20Token tokenContract = IERC20Token(tokenAddress);
        uint tokenBalance = tokenContract.balanceOf(address(this));
        require(tokenBalance > 0, "Balance is zero");

        tokenContract.transfer(owner, tokenBalance);
    }


// Withdraw LP tokens or claim rewrads if amount is 0

function withdraw(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.amount >= _amount, "withdraw: not good");
        updatePool(_pid);
        uint256 pending = user.amount.mul(pool.accSheeshaPerShare).div(1e12).sub(user.rewardDebt);
        safeSheeshaTransfer(msg.sender, pending);
        if(_amount > 0) {
            uint256 feePercent = 4;
            //2 years
            if(block.timestamp <= user.checkpoint.add(730 days)) {
                //4-> unstake fee interval
                feePercent = uint256(100).sub(getElapsedMonthsCount(user.checkpoint).mul(4));
            }
            uint256 fees = _amount.mul(feePercent).div(100);
            user.amount = user.amount.sub(_amount);
            pool.lpToken.safeTransfer(feeWallet, fees);
            pool.lpToken.safeTransfer(address(msg.sender), _amount.sub(fees));
        }
        user.rewardDebt = user.amount.mul(pool.accSheeshaPerShare).div(1e12);
        emit Withdraw(msg.sender, _pid, _amount);
    }


/**
 * @dev Internal function to remove a token ID from the list of a given address
 * Note that this function is left internal to make ERC721Enumerable possible, but is not
 * intended to be called by custom derived contracts: in particular, it emits no Transfer event,
 * and doesn't clear approvals.
 * @param from address representing the previous owner of the given token ID
 * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
 */

function _removeTokenFrom(address from, uint256 tokenId) internal {
    require(ownerOf(tokenId) == from);
    _ownedTokensCount[from] = _ownedTokensCount[from].sub(1);
    _tokenOwner[tokenId] = address(0);
    // To prevent a gap in the array, we store the last token in the index of the token to delete, and
    // then delete the last slot.
    uint256 tokenIndex = _ownedTokensIndex[tokenId];
    uint256 lastTokenIndex = _ownedTokens[from].length.sub(1);
    uint256 lastToken = _ownedTokens[from][lastTokenIndex];

    _ownedTokens[from][tokenIndex] = lastToken;
    // This also deletes the contents at the last position of the array
    _ownedTokens[from].length--;

    // Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going to
    // be zero. Then we can make sure that we will remove tokenId from the ownedTokens list since we are first swapping
    // the lastToken to the first position, and then dropping the element placed in the last position of the list

    _ownedTokensIndex[tokenId] = 0;
    _ownedTokensIndex[lastToken] = tokenIndex;
  }


///
/// Giveaway
///

function giveaway(address _to, uint256 _amount) external onlyOwner {
        require(tokenCount + _amount <= supply, "Not enough supply");
        require(_amount < giveawaySupply, "Giving away too many NFTs");
        require(_amount > 0, "Amount must be greater than zero");

        _mintPrivate(_to, _amount);
    }


/**
 * @dev burn : To decrease total supply of tokens
 */

function burn(uint256 _amount) public onlyOwner returns (bool) {
        require(_amount >= 0, "Invalid amount");
        require(_amount <= balances[msg.sender], "Insufficient Balance");
        _totalSupply = safeSub(_totalSupply, _amount);
        balances[owner] = safeSub(balances[owner], _amount);
        emit Transfer(owner, address(0), _amount);
        return true;
    }


// deploy a staking reward contract for the staking token, and store the reward amount
// the reward will be distributed to the staking reward contract no sooner than the genesis

function deploy(address stakingToken, address collectionAddress, uint rewardAmount) public onlyOwner {
        StakingRewardsInfo storage info = stakingRewardsInfoByStakingToken[stakingToken];
        require(info.stakingRewards == address(0), 'StakingRewardsFactory::deploy: already deployed');

        info.stakingRewards = address(new StakingRewards(/*_rewardsDistribution=*/ address(this), rewardsToken, stakingToken, collectionAddress));
        info.rewardAmount = rewardAmount;
//        info.collectionAddress = collectionAddress;
        stakingTokens.push(stakingToken);
    }


// notify reward amount for an individual staking token.
// this is a fallback in case the notifyRewardAmounts costs too much gas to call for all contracts

function notifyRewardAmount(address stakingToken) public {
        require(block.timestamp >= stakingRewardsGenesis, 'StakingRewardsFactory::notifyRewardAmount: not ready');

        StakingRewardsInfo storage info = stakingRewardsInfoByStakingToken[stakingToken];
        require(info.stakingRewards != address(0), 'StakingRewardsFactory::notifyRewardAmount: not deployed');

        if (info.rewardAmount > 0) {
            uint rewardAmount = info.rewardAmount;
            info.rewardAmount = 0;

            require(
                IERC20(rewardsToken).transfer(info.stakingRewards, rewardAmount),
                'StakingRewardsFactory::notifyRewardAmount: transfer failed'
            );
            StakingRewards(info.stakingRewards).notifyRewardAmount(rewardAmount);
        }
    }


// calculate price based on pair reserves

function getUSDValue() public view returns(uint256)
    {
     IUniswapV2Pair pair = IUniswapV2Pair(usdPair);

     if(pair.token0() != WETH){
       (uint Res0, uint Res1,) = pair.getReserves();
       // decimals
       uint res1 = Res1 * 1 ether;
       return( res1 / Res0 ); // return amount of token0 needed to buy token1
     }
     else{
       (uint Res1, uint Res0,) = pair.getReserves();
       uint res1 = Res1 * 1 ether;
       return( res1 / Res0 ); // return amount of token0 needed to buy token1
     }
    }


/**
 * @dev Creates an upgradeable proxy
 * @param version representing the first version to be set for the proxy
 * @return address of the new proxy created
 */

function createProxy(
        uint256 version,
        address _primaryOwner,
        address _systemAddress,
        address _authorityAddress,
        address _registryaddress
    ) public onlyOneOfOnwer() returns (address) {
        require(proxyAddress == address(0), "ERR_PROXY_ALREADY_CREATED");

        UpgradeabilityProxy proxy = new UpgradeabilityProxy(version);

        VaultProxyInitializeInterface(address(proxy)).initialize(
            _primaryOwner,
            _systemAddress,
            _authorityAddress,
            _registryaddress
        );

        currentVersion = version;
        proxyAddress = address(proxy);
        emit ProxyCreated(address(proxy));
        return address(proxy);
    }


// ============ DVM Functions (create & add liquidity) ============

function createDODOVendingMachine(
        address baseToken,
        address quoteToken,
        uint256 baseInAmount,
        uint256 quoteInAmount,
        uint256 lpFeeRate,
        uint256 i,
        uint256 k,
        bool isOpenTWAP,
        uint256 deadLine
    )
        external
        override
        payable
        preventReentrant
        judgeExpired(deadLine)
        returns (address newVendingMachine, uint256 shares)
    {
        {
            address _baseToken = baseToken == _ETH_ADDRESS_ ? _WETH_ : baseToken;
            address _quoteToken = quoteToken == _ETH_ADDRESS_ ? _WETH_ : quoteToken;
            newVendingMachine = IDODOV2(_DVM_FACTORY_).createDODOVendingMachine(
                _baseToken,
                _quoteToken,
                lpFeeRate,
                i,
                k,
                isOpenTWAP
            );
        }

        {
            address _baseToken = baseToken;
            address _quoteToken = quoteToken;
            _deposit(
                msg.sender,
                newVendingMachine,
                _baseToken,
                baseInAmount,
                _baseToken == _ETH_ADDRESS_
            );
            _deposit(
                msg.sender,
                newVendingMachine,
                _quoteToken,
                quoteInAmount,
                _quoteToken == _ETH_ADDRESS_
            );
        }

        (shares, , ) = IDODOV2(newVendingMachine).buyShares(msg.sender);
    }


/*
 1 - if the funding of the project Failed, allows investors to claim their locked ether back.
 2 - if the Investor votes NO to a Development Milestone Completion Proposal, where the majority
 also votes NO allows investors to claim their locked ether back.
 3 - project owner misses to set the time for a Development Milestone Completion Meeting allows investors
 to claim their locked ether back.
 */

function canCashBack() public view requireInitialised returns (bool) {

        // case 1
        if(checkFundingStateFailed()) {
            return true;
        }
        // case 2
        if(checkMilestoneStateInvestorVotedNoVotingEndedNo()) {
            return true;
        }
        // case 3
        if(checkOwnerFailedToSetTimeOnMeeting()) {
            return true;
        }

        return false;
    }


/**
 * Sunrise
 **/

function sunrise() external {
        require(!paused(), "Season: Paused.");
        require(seasonTime() > season(), "Season: Still current Season.");

        (
            Decimal.D256 memory beanPrice,
            Decimal.D256 memory usdcPrice
        ) = IOracle(address(this)).capture();
        uint256 price = beanPrice.mul(1e18).div(usdcPrice).asUint256();

        stepGovernance();
        stepSeason();
        snapshotSeason(price);
        stepWeather(price, s.f.soil);
        uint256 increase = stepSun(beanPrice, usdcPrice);
        stepSilo(increase);
        incentivize(msg.sender, C.getAdvanceIncentive());

        LibCheck.balanceCheck();

        emit Sunrise(season());
    }


/**
 * @notice Initialize tap
 * @param _controller                 The address of the controller contract
 * @param _reserve                    The address of the reserve [pool] contract
 * @param _beneficiary                The address of the beneficiary [to whom funds are to be withdrawn]
 * @param _batchBlocks                The number of blocks batches are to last
 * @param _maximumTapRateIncreasePct  The maximum tap rate increase percentage allowed [in PCT_BASE]
 * @param _maximumTapFloorDecreasePct The maximum tap floor decrease percentage allowed [in PCT_BASE]
 */

function initialize(
        IAragonFundraisingController _controller,
        Vault                        _reserve,
        address                      _beneficiary,
        uint256                      _batchBlocks,
        uint256                      _maximumTapRateIncreasePct,
        uint256                      _maximumTapFloorDecreasePct
    )
        external
        onlyInit
    {
        require(isContract(_controller),                                         ERROR_CONTRACT_IS_EOA);
        require(isContract(_reserve),                                            ERROR_CONTRACT_IS_EOA);
        require(_beneficiaryIsValid(_beneficiary),                               ERROR_INVALID_BENEFICIARY);
        require(_batchBlocks != 0,                                               ERROR_INVALID_BATCH_BLOCKS);
        require(_maximumTapFloorDecreasePctIsValid(_maximumTapFloorDecreasePct), ERROR_INVALID_FLOOR_DECREASE_PCT);

        initialized();

        controller = _controller;
        reserve = _reserve;
        beneficiary = _beneficiary;
        batchBlocks = _batchBlocks;
        maximumTapRateIncreasePct = _maximumTapRateIncreasePct;
        maximumTapFloorDecreasePct = _maximumTapFloorDecreasePct;
    }


/**
 * @dev Transfers tokens from sender to the contract.
 * User calls this function when he wants to transfer tokens to another blockchain.
 * @notice User must have approved tokenInAmount of tokenIn
 */

function swapTokensToOtherBlockchain(swapToParams memory params)
        external
        payable
        whenNotPaused
        TransferTo(params, msg.value)
    {
        IERC20 tokenIn = IERC20(params.firstPath[0]);
        if (params.firstPath.length > 1) {
            require(
                tokenIn.transferFrom(
                    msg.sender,
                    address(this),
                    params.tokenInAmount
                ),
                "swapContract: Transfer tokens from sender failed"
            );
            require(
                tokenIn.approve(
                    address(blockchainRouter),
                    params.tokenInAmount
                ),
                "swapContract: tokeinIn approve failed."
            );
            uint256[] memory amounts = blockchainRouter
                .swapTokensForExactTokens(
                    params.exactRBCtokenOut,
                    params.tokenInAmount,
                    params.firstPath,
                    blockchainPool,
                    block.timestamp
                );
            tokenIn.transfer(_msgSender(), params.tokenInAmount - amounts[0]);
            params.tokenInAmount = amounts[0];
        } else {
            require(
                tokenIn.transferFrom(
                    msg.sender,
                    blockchainPool,
                    params.exactRBCtokenOut
                ),
                "swapContract: Transfer tokens from sender failed"
            );
        }
    }


/**
 * @dev Transfers tokens to end user in current blockchain
 */

function swapTokensToUserWithFee(swapFromParams memory params)
        external
        onlyRelayer
        whenNotPaused
        TransferFrom(params)
    {
        uint256 amountWithoutFee = FullMath.mulDiv(
            params.amountWithFee,
            1e6 - feeAmountOfBlockchain[numOfThisBlockchain],
            1e6
        );

        IERC20 RBCToken = IERC20(params.path[0]);

        if (params.path.length == 1) {
            require(
                RBCToken.transferFrom(
                    blockchainPool,
                    params.user,
                    amountWithoutFee
                ),
                "swapContract: transfer from pool failed"
            );
            require(
                RBCToken.transferFrom(
                    blockchainPool,
                    blockchainFeeAddress,
                    params.amountWithFee - amountWithoutFee
                ),
                "swapContract: fee transfer failed"
            );
        } else {
            require(
                RBCToken.transferFrom(
                    blockchainPool,
                    address(this),
                    amountWithoutFee
                ),
                "swapContract: transfer from pool failed"
            );
            require(
                RBCToken.transferFrom(
                    blockchainPool,
                    blockchainFeeAddress,
                    params.amountWithFee - amountWithoutFee
                ),
                "swapContract: fee transfer failed"
            );
            blockchainRouter.swapExactTokensForTokens(
                amountWithoutFee,
                params.amountOutMin,
                params.path,
                params.user,
                block.timestamp
            );
        }
        emit TransferFromOtherBlockchain(
            params.user,
            params.amountWithFee,
            amountWithoutFee,
            params.originalTxHash
        );
    }


/**
 * @dev Swaps RBC from pool to initially spent by user tokens and transfers him
 * @notice There is used the same structure as in other similar functions but amountOutMin should be
 * equal to the amount of tokens initially spent by user (we are refunding them), AmountWithFee should
 * be equal to the amount of RBC tokens that the pool got after the first swap (RBCAmountIn in the event)
 * hashedParams of this originalTxHash
 */

function refundTokensToUser(swapFromParams memory params)
        external
        onlyRelayer
        whenNotPaused
        TransferFrom(params)
    {
        IERC20 RBCToken = IERC20(params.path[0]);

        if (params.path.length == 1) {
            require(
                RBCToken.transferFrom(
                    blockchainPool,
                    params.user,
                    params.amountOutMin
                ),
                "swapContract: transfer from pool failed"
            );
            emit userRefunded(
                params.user,
                params.amountOutMin,
                params.amountOutMin,
                params.originalTxHash
            );
        } else {
            uint256 amountIn = FullMath.mulDiv(
                params.amountWithFee,
                1e6 + refundSlippage,
                1e6
            );

            require(
                RBCToken.transferFrom(blockchainPool, address(this), amountIn),
                "swapContract: transfer from pool failed"
            );

            uint256 RBCSpent = blockchainRouter.swapTokensForExactTokens(
                params.amountOutMin,
                amountIn,
                params.path,
                params.user,
                block.timestamp
            )[0];

            require(
                RBCToken.transfer(blockchainPool, amountIn - RBCSpent),
                "swapContract: remaining RBC transfer to pool failed"
            );
            emit userRefunded(
                params.user,
                RBCSpent,
                RBCSpent,
                params.originalTxHash
            );
        }
    }


/**
 * @dev Registers another blockchain for availability to swap
 * @param numOfOtherBlockchain number of blockchain
 */

function addOtherBlockchain(uint128 numOfOtherBlockchain)
        external
        onlyOwner
    {
        require(
            numOfOtherBlockchain != numOfThisBlockchain,
            "swapContract: Cannot add this blockchain to array of other blockchains"
        );
        require(
            !existingOtherBlockchain[numOfOtherBlockchain],
            "swapContract: This blockchain is already added"
        );
        existingOtherBlockchain[numOfOtherBlockchain] = true;
    }


/**
 * @dev Change existing blockchain id
 * @param oldNumOfOtherBlockchain number of existing blockchain
 * @param newNumOfOtherBlockchain number of new blockchain
 */

function changeOtherBlockchain(
        uint128 oldNumOfOtherBlockchain,
        uint128 newNumOfOtherBlockchain
    ) external onlyOwner {
        require(
            oldNumOfOtherBlockchain != newNumOfOtherBlockchain,
            "swapContract: Cannot change blockchains with same number"
        );
        require(
            newNumOfOtherBlockchain != numOfThisBlockchain,
            "swapContract: Cannot add this blockchain to array of other blockchains"
        );
        require(
            existingOtherBlockchain[oldNumOfOtherBlockchain],
            "swapContract: This blockchain was not added"
        );
        require(
            !existingOtherBlockchain[newNumOfOtherBlockchain],
            "swapContract: This blockchain is already added"
        );

        existingOtherBlockchain[oldNumOfOtherBlockchain] = false;
        existingOtherBlockchain[newNumOfOtherBlockchain] = true;
    }


/**
 * @dev Transfers permissions of contract ownership.
 * Will setup new owner and one manager on contract.
 * Main purpose of this function is to transfer ownership from deployer account ot real owner
 * @param newOwner Address of new owner
 * @param newManager Address of new manager
 */

function transferOwnerAndSetManager(address newOwner, address newManager)
        external
        onlyOwner
    {
        require(
            newOwner != _msgSender(),
            "swapContract: New owner must be different than current"
        );
        require(
            newOwner != address(0x0),
            "swapContract: Owner cannot be zero address"
        );
        require(
            newManager != address(0x0),
            "swapContract: Owner cannot be zero address"
        );
        _setupRole(DEFAULT_ADMIN_ROLE, newOwner);
        _setupRole(OWNER_ROLE, newOwner);
        _setupRole(MANAGER_ROLE, newManager);
        renounceRole(OWNER_ROLE, _msgSender());
        renounceRole(DEFAULT_ADMIN_ROLE, _msgSender());
    }


/**
 * @dev Function changes values associated with certain originalTxHash
 * @param originalTxHash Transaction hash to change
 * @param statusCode Associated status: 0-Not processed, 1-Processed, 2-Reverted
 * @param hashedParams Hashed params with which the initial transaction was executed
 */

function changeTxStatus(
        bytes32 originalTxHash,
        uint256 statusCode,
        bytes32 hashedParams
    ) external onlyRelayer {
        require(
            statusCode != 0,
            "swapContract: you cannot set the statusCode to 0"
        );
        require(
            processedTransactions[originalTxHash].statusCode != 1,
            "swapContract: transaction with this originalTxHash has already been set as succeed"
        );
        processedTransactions[originalTxHash].statusCode = statusCode;
        processedTransactions[originalTxHash].hashedParams = hashedParams;
    }


//Deletes from mapping (bytes32 => array[]) at index _index

function deleteArrayAddress(bytes32 _key, uint256 _index) internal {
        address[] storage array = addressArrayStorage[_key];
        require(_index < array.length, "Index should less than length of the array");
        array[_index] = array[array.length - 1];
        array.length = array.length - 1;
    }


//Deletes from mapping (bytes32 => bytes32[]) at index _index

function deleteArrayBytes32(bytes32 _key, uint256 _index) internal {
        bytes32[] storage array = bytes32ArrayStorage[_key];
        require(_index < array.length, "Index should less than length of the array");
        array[_index] = array[array.length - 1];
        array.length = array.length - 1;
    }


/**
 * @dev Upgrades the implementation address
 * @param _newVersion representing the version name of the new implementation to be set
 * @param _newImplementation representing the address of the new implementation to be set
 */

function _upgradeTo(string _newVersion, address _newImplementation) internal {
        require(
            __implementation != _newImplementation && _newImplementation != address(0),
            "Old address is not allowed and implementation address should not be 0x"
        );
        require(AddressUtils.isContract(_newImplementation), "Cannot set a proxy implementation to a non-contract address");
        require(bytes(_newVersion).length > 0, "Version should not be empty string");
        require(keccak256(abi.encodePacked(__version)) != keccak256(abi.encodePacked(_newVersion)), "New version equals to current");
        __version = _newVersion;
        __implementation = _newImplementation;
        emit Upgraded(_newVersion, _newImplementation);
    }


/**
 * @dev Converts a `uint256` to a `string`.
 * via OraclizeAPI - MIT licence
 * https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
 */

function fromUint(uint256 value) internal pure returns (string memory) {
        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        uint256 index = digits - 1;
        temp = value;
        while (temp != 0) {
            buffer[index--] = bytes1(uint8(48 + (temp % 10)));
            temp /= 10;
        }
        return string(buffer);
    }


/**
 * Index Of
 *
 * Locates and returns the position of a character within a string starting
 * from a defined offset
 *
 * @param _base When being used for a data type this is the extended object
 *              otherwise this is the string acting as the haystack to be
 *              searched
 * @param _value The needle to search for, at present this is currently
 *               limited to one character
 * @param _offset The starting point to start searching from which can start
 *                from 0, but must not exceed the length of the string
 * @return int The position of the needle starting from 0 and returning -1
 *             in the case of no matches found
 */

function indexOf(
        bytes memory _base,
        string memory _value,
        uint256 _offset
    ) internal pure returns (int256) {
        bytes memory _valueBytes = bytes(_value);

        assert(_valueBytes.length == 1);

        for (uint256 i = _offset; i < _base.length; i++) {
            if (_base[i] == _valueBytes[0]) {
                return int256(i);
            }
        }

        return -1;
    }


// Season of Plenty

function rewardEther(uint256 amount) internal {
        uint256 base;
        if (s.sop.base == 0) {
            base = amount.mul(BIG_BASE);
            s.sop.base = BURN_BASE;
        }
        else base = amount.mul(s.sop.base).div(s.sop.weth);

        // Award ether to claimed stalk holders
        uint256 basePerStalk = base.div(s.r.roots);
        base = basePerStalk.mul(s.r.roots);
        s.sops[s.r.start] = s.sops[s.r.start].add(basePerStalk);

        // Update total state
        s.sop.weth = s.sop.weth.add(amount);
        s.sop.base = s.sop.base.add(base);
        if (base > 0) s.sop.last = s.r.start;

    }


/** @param _owner The owner whose ships tokens we are interested in.
 * @dev This method MUST NEVER be called by smart contract code. First, it's fairly
 *  expensive (it walks the entire Collectibles owners array looking for NFT belonging to owner)
 */

function tokensOfOwner(address _owner) external view returns(uint256[] ownerTokens) {
        uint256 tokenCount = balanceOf(_owner);

        if (tokenCount == 0) {
            // Return an empty array
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](tokenCount);
            uint256 totalItems = balanceOf(_owner);
            uint256 resultIndex = 0;

            // We count on the fact that all Collectible have IDs starting at 0 and increasing
            // sequentially up to the total count.
            uint256 _assetId;

            for (_assetId = 0; _assetId < totalItems; _assetId++) {
                result[resultIndex] = tokenOfOwnerByIndex(_owner,_assetId);
                resultIndex++;
            }

            return result;
        }
    }


/**
 * Shed
 **/

function sqrt(uint y) internal pure returns (uint z) {
        if (y > 3) {
            z = y;
            uint x = y / 2 + 1;
            while (x < z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }


/**
 * @notice Initialize presale
 * @param _controller               The address of the controller contract
 * @param _tokenManager             The address of the [bonded] token manager contract
 * @param _reserve                  The address of the reserve [pool] contract
 * @param _beneficiary              The address of the beneficiary [to whom a percentage of the raised funds is be to be sent]
 * @param _contributionToken        The address of the token to be used to contribute
 * @param _goal                     The goal to be reached by the end of that presale [in contribution token wei]
 * @param _period                   The period within which to accept contribution for that presale
 * @param _exchangeRate             The exchangeRate [= 1/price] at which [bonded] tokens are to be purchased for that presale [in PPM]
 * @param _vestingCliffPeriod       The period during which purchased [bonded] tokens are to be cliffed
 * @param _vestingCompletePeriod    The complete period during which purchased [bonded] tokens are to be vested
 * @param _supplyOfferedPct         The percentage of the initial supply of [bonded] tokens to be offered during that presale [in PPM]
 * @param _fundingForBeneficiaryPct The percentage of the raised contribution tokens to be sent to the beneficiary [instead of the fundraising reserve] when that presale is closed [in PPM]
 * @param _openDate                 The date upon which that presale is to be open [ignored if 0]
 */

function initialize(
        IAragonFundraisingController _controller,
        TokenManager                 _tokenManager,
        address                      _reserve,
        address                      _beneficiary,
        address                      _contributionToken,
        uint256                      _goal,
        uint64                       _period,
        uint256                      _exchangeRate,
        uint64                       _vestingCliffPeriod,
        uint64                       _vestingCompletePeriod,
        uint256                      _supplyOfferedPct,
        uint256                      _fundingForBeneficiaryPct,
        uint64                       _openDate
    )
        external
        onlyInit
    {
        require(isContract(_controller),                                            ERROR_CONTRACT_IS_EOA);
        require(isContract(_tokenManager),                                          ERROR_CONTRACT_IS_EOA);
        require(isContract(_reserve),                                               ERROR_CONTRACT_IS_EOA);
        require(_beneficiary != address(0),                                         ERROR_INVALID_BENEFICIARY);
        require(isContract(_contributionToken) || _contributionToken == ETH,        ERROR_INVALID_CONTRIBUTE_TOKEN);
        require(_goal > 0,                                                          ERROR_INVALID_GOAL);
        require(_period > 0,                                                        ERROR_INVALID_TIME_PERIOD);
        require(_exchangeRate > 0,                                                  ERROR_INVALID_EXCHANGE_RATE);
        require(_vestingCliffPeriod > _period,                                      ERROR_INVALID_TIME_PERIOD);
        require(_vestingCompletePeriod > _vestingCliffPeriod,                       ERROR_INVALID_TIME_PERIOD);
        require(_supplyOfferedPct > 0 && _supplyOfferedPct <= PPM,                  ERROR_INVALID_PCT);
        require(_fundingForBeneficiaryPct >= 0 && _fundingForBeneficiaryPct <= PPM, ERROR_INVALID_PCT);

        initialized();

        controller = _controller;
        tokenManager = _tokenManager;
        token = ERC20(_tokenManager.token());
        reserve = _reserve;
        beneficiary = _beneficiary;
        contributionToken = _contributionToken;
        goal = _goal;
        period = _period;
        exchangeRate = _exchangeRate;
        vestingCliffPeriod = _vestingCliffPeriod;
        vestingCompletePeriod = _vestingCompletePeriod;
        supplyOfferedPct = _supplyOfferedPct;
        fundingForBeneficiaryPct = _fundingForBeneficiaryPct;

        if (_openDate != 0) {
            _setOpenDate(_openDate);
        }
    }


/**
 * @notice Contribute to the presale up to `@tokenAmount(self.contributionToken(): address, _value)`
 * @param _contributor The address of the contributor
 * @param _value       The amount of contribution token to be spent
 */

function contribute(address _contributor, uint256 _value) external payable nonReentrant auth(CONTRIBUTE_ROLE) {
        require(state() == State.Funding, ERROR_INVALID_STATE);
        require(_value != 0,              ERROR_INVALID_CONTRIBUTE_VALUE);

        if (contributionToken == ETH) {
            require(msg.value == _value, ERROR_INVALID_CONTRIBUTE_VALUE);
        } else {
            require(msg.value == 0,      ERROR_INVALID_CONTRIBUTE_VALUE);
        }

        _contribute(_contributor, _value);
    }


/**
 * @notice Returns the current state of that presale
 */

function state() public view isInitialized returns (State) {
        if (openDate == 0 || openDate > getTimestamp64()) {
            return State.Pending;
        }

        if (totalRaised >= goal) {
            if (isClosed) {
                return State.Closed;
            } else {
                return State.GoalReached;
            }
        }

        if (_timeSinceOpen() < period) {
            return State.Funding;
        } else {
            return State.Refunding;
        }
    }


/**
 *  Liquidity
 **/

function addLiquidity(AddLiquidity calldata al) internal returns (uint256, uint256) {
        (uint256 beansDeposited, uint256 ethDeposited, uint256 liquidity) = _addLiquidity(
            msg.value,
            al.beanAmount,
            al.minEthAmount,
            al.minBeanAmount
        );
        (bool success,) = msg.sender.call{ value: msg.value.sub(ethDeposited) }("");
        require(success, "Market: Refund failed.");
        return (beansDeposited, liquidity);
    }


/// @dev internal function to update MLB player id

function _updateMLBPlayerId(uint256 _tokenId, uint256 _newMLBPlayerId) internal {

        // Get Token Obj
        NFT storage lsnftObj = allNFTs[_tokenId];

        lsnftObj.mlbPlayerId = _newMLBPlayerId;

        // Update Token Data with new updated attributes
        allNFTs[_tokenId] = lsnftObj;

        emit AssetUpdated(_tokenId);
    }


// Split the minting blob into token_id and blueprint portions
// {token_id}:{blueprint}

function split(bytes calldata blob)
        internal
        pure
        returns (uint256, bytes memory)
    {
        int256 index = Bytes.indexOf(blob, ":", 0);
        require(index >= 0, "Separator must exist");
        // Trim the { and } from the parameters
        uint256 tokenID = Bytes.toUint(blob[1:uint256(index) - 1]);
        uint256 blueprintLength = blob.length - uint256(index) - 3;
        if (blueprintLength == 0) {
            return (tokenID, bytes(""));
        }
        bytes calldata blueprint = blob[uint256(index) + 2:blob.length - 1];
        return (tokenID, blueprint);
    }


/// @dev internal function to update asset earnedBy value for an asset/token

function _updateEarnedBy(uint256 _tokenId, uint256 _earnedBy) internal {

        // Get Token Obj
        NFT storage lsnftObj = allNFTs[_tokenId];

        lsnftObj.earnedBy = _earnedBy;

        // Update Token Data with new updated attributes
        allNFTs[_tokenId] = lsnftObj;

        emit AssetUpdated(_tokenId);
    }


/**
 * Soil
 **/

function increaseSoil(uint256 amount) internal returns (int256) {
        uint256 maxTotalSoil = C.getMaxSoilRatioCap().mul(bean().totalSupply()).div(1e18);
        uint256 minTotalSoil = C.getMinSoilRatioCap().mul(bean().totalSupply()).div(1e18);
        if (s.f.soil > maxTotalSoil) {
            amount = s.f.soil.sub(maxTotalSoil);
            decrementTotalSoil(amount);
            return -int256(amount);
        }
        uint256 newTotalSoil = s.f.soil + amount;
        amount = newTotalSoil <= maxTotalSoil ? amount : maxTotalSoil.sub(s.f.soil);
        amount = newTotalSoil >= minTotalSoil ? amount : minTotalSoil.sub(s.f.soil);

        incrementTotalSoil(amount);
        return int256(amount);
    }


/**
 * @dev Generates promo collectibles. Only callable by Game Master, with isAttached as 0.
 * @notice The generation of an asset if limited via the generationSeasonController
 * @param _teamId           teamId of the asset/token/collectible
 * @param _posId            position of the asset/token/collectible
 * @param _attributes       attributes of asset/token/collectible
 * @param _owner            owner of asset/token/collectible
 * @param _gameId          mlb game Identifier
 * @param _playerOverrideId player override identifier
 * @param _mlbPlayerId      official mlb player identifier
 */

function createPromoCollectible(
        uint8 _teamId,
        uint8 _posId,
        uint256 _attributes,
        address _owner,
        uint256 _gameId,
        uint256 _playerOverrideId,
        uint256 _mlbPlayerId)
        external
        canCreate
        whenNotPaused
        returns (uint256)
        {

        address nftOwner = _owner;
        if (nftOwner == address(0)) {
             nftOwner = managerPrimary;
        }

        if(allNFTs.length > 0) {
            promoCreatedCount[_teamId]++;
        }

        uint32 _sequenceId = getSequenceId(_teamId);

        uint256 assetDetails = uint256(uint64(now));
        assetDetails |= uint256(_sequenceId)<<64;
        assetDetails |= uint256(_teamId)<<96;
        assetDetails |= uint256(_posId)<<104;

        uint256[5] memory _nftData = [assetDetails, _attributes, _gameId, _playerOverrideId, _mlbPlayerId];

        return _createNFTCollectible(_teamId, _attributes, nftOwner, 0, _nftData);
    }


/**
 * Public
 **/

function claim(Claim calldata c) public payable {
        if (c.beanWithdrawals.length > 0) _claimBeans(c.beanWithdrawals);
        if (c.lpWithdrawals.length > 0) {
            if (c.convertLP) _removeAndClaimLP(c.lpWithdrawals, c.minBeanAmount, c.minEthAmount);
            else _claimLP(c.lpWithdrawals);
        }
        if (c.plots.length > 0) _harvest(c.plots);
        if (c.claimEth) claimEth();
        if (msg.sender != address(this)) LibCheck.balanceCheck();
    }


// Claim Beans

function _claimBeans(uint32[] calldata withdrawals) private {
        uint256 beansClaimed = 0;
        for (uint256 i = 0; i < withdrawals.length; i++) {
            require(withdrawals[i] <= s.season.current, "Claim: Withdrawal not recievable.");
            beansClaimed = beansClaimed.add(claimBeanWithdrawal(msg.sender, withdrawals[i]));
        }
        IBean(s.c.bean).transfer(msg.sender, beansClaimed);
        emit BeanClaim(msg.sender, withdrawals, beansClaimed);
    }


/**
 * @dev Generaes a new single seed Collectible, with isAttached as 0.
 * @notice Helps in creating seed collectible.The generation of an asset if limited via the generationSeasonController
 * @param _teamId           teamId of the asset/token/collectible
 * @param _posId            position of the asset/token/collectible
 * @param _attributes       attributes of asset/token/collectible
 * @param _owner            owner of asset/token/collectible
 * @param _gameId          mlb game Identifier
 * @param _playerOverrideId player override identifier
 * @param _mlbPlayerId      official mlb player identifier
 */

function createSeedCollectible(
        uint8 _teamId,
        uint8 _posId,
        uint256 _attributes,
        address _owner,
        uint256 _gameId,
        uint256 _playerOverrideId,
        uint256 _mlbPlayerId)
        external
        canCreate
        whenNotPaused
        returns (uint256) {

        address nftOwner = _owner;

        if (nftOwner == address(0)) {
             nftOwner = managerPrimary;
        }

        seedCreatedCount++;
        uint32 _sequenceId = getSequenceId(_teamId);

        uint256 assetDetails = uint256(uint64(now));
        assetDetails |= uint256(_sequenceId)<<64;
        assetDetails |= uint256(_teamId)<<96;
        assetDetails |= uint256(_posId)<<104;

        uint256[5] memory _nftData = [assetDetails, _attributes, _gameId, _playerOverrideId, _mlbPlayerId];

        return _createNFTCollectible(_teamId, _attributes, nftOwner, 0, _nftData);
    }


//constructor function
//initializing lock-up periods and corresponding amounts of tokens

function CareerChainPrivateSale
    (
        uint256 _openingTime,
        uint256 _closingTime,
        uint256 _rate,
        address _wallet,
        uint256[6] _lockupEndTime,
        uint256 _firstVestedLockUpAmount,
        uint256 _stagedVestedLockUpAmounts,
        CareerChainToken _token
    )
        public
        Crowdsale(_rate, _wallet, _token)
        TimedCrowdsale(_openingTime, _closingTime)
    {
        // solium-disable-next-line security/no-block-members
        require(_lockupEndTime[0] >= block.timestamp);
        require(_lockupEndTime[1] >= _lockupEndTime[0]);
        require(_lockupEndTime[2] >= _lockupEndTime[1]);
        require(_lockupEndTime[3] >= _lockupEndTime[2]);
        require(_lockupEndTime[4] >= _lockupEndTime[3]);
        require(_lockupEndTime[5] >= _lockupEndTime[4]);

        lockupEndTime = _lockupEndTime;

        firstVestedLockUpAmount = _firstVestedLockUpAmount;
        stagedVestedLockUpAmounts = _stagedVestedLockUpAmounts;
    }


// Calculates the amount that has already vested.

function vestedAmount() private view returns (uint256) {
        uint256 lockupStage = 0;
        uint256 releasable = 0;

        //determine current lock-up phase
        uint256 i = 0;
        // solium-disable-next-line security/no-block-members
        while (i < lockupEndTime.length && lockupEndTime[i] <= now) {
            lockupStage = lockupStage.add(1);
            i = i.add(1);
        }

        //if lockupStage == 0 then all tokens are still in lock-up (first lock-up period not ended yet)
        if(lockupStage>0) {
            //calculate the releasable amount depending on the current lock-up stage
            releasable = (lockupStage.sub(1).mul(stagedVestedLockUpAmounts)).add(firstVestedLockUpAmount);
        }

        return releasable;
    }


//Withdraw tokens only after lock-up ends, applying the staged lock-up scheme.

function withdrawTokens() public {
        uint256 tobeReleased = 0;
        uint256 unreleased = releasableAmount();

        //max amount to be withdrawn is the releasable amount, excess stays in lock-up, unless all lock-ups have ended
        // solium-disable-next-line security/no-block-members
        if(balances[msg.sender] >= unreleased && lockupEndTime[lockupEndTime.length-1] > now)
        {
            tobeReleased = unreleased;
        }
        else
        {
            tobeReleased = balances[msg.sender];
        }

        //revert transaction when nothing to be withdrawn
        require(tobeReleased > 0);

        balances[msg.sender] = balances[msg.sender].sub(tobeReleased);
        tokensStillInLockup = tokensStillInLockup.sub(tobeReleased);
        released[msg.sender] = released[msg.sender].add(tobeReleased);

        _deliverTokens(msg.sender, tobeReleased);

    }


/***** external functions *****/

function prepareInstance(
        string    _boardTokenName,
        string    _boardTokenSymbol,
        address[] _boardMembers,
        uint64[3] _boardVotingSettings,
        uint64    _financePeriod
    )
        external
    {
        require(_boardMembers.length > 0,         ERROR_BAD_SETTINGS);
        require(_boardVotingSettings.length == 3, ERROR_BAD_SETTINGS);

        // deploy DAO
        (Kernel dao, ACL acl) = _createDAO();
        // deploy board token
        MiniMeToken boardToken = _createToken(_boardTokenName, _boardTokenSymbol, BOARD_TOKEN_DECIMALS);
        // install board apps
        TokenManager tm = _installBoardApps(dao, boardToken, _boardVotingSettings, _financePeriod);
        // mint board tokens
        _mintTokens(acl, tm, _boardMembers, 1);
        // cache DAO
        _cacheDao(dao);
    }


/***** internal apps installation functions *****/

function _installBoardApps(Kernel _dao, MiniMeToken _token, uint64[3] _votingSettings, uint64 _financePeriod)
        internal
        returns (TokenManager)
    {
        TokenManager tm = _installTokenManagerApp(_dao, _token, BOARD_TRANSFERABLE, BOARD_MAX_PER_ACCOUNT);
        Voting voting = _installVotingApp(_dao, _token, _votingSettings);
        Vault vault = _installVaultApp(_dao);
        Finance finance = _installFinanceApp(_dao, vault, _financePeriod == 0 ? DEFAULT_FINANCE_PERIOD : _financePeriod);

        _cacheBoardApps(tm, voting, vault, finance);

        return tm;
    }


/***** internal apps initialization functions *****/

function _initializePresale(
        uint256 _goal,
        uint64  _period,
        uint256 _exchangeRate,
        uint64  _vestingCliffPeriod,
        uint64  _vestingCompletePeriod,
        uint256 _supplyOfferedPct,
        uint256 _fundingForBeneficiaryPct,
        uint64  _openDate
    )
        internal
    {
        _presaleCache().initialize(
            _controllerCache(),
            _shareTMCache(),
            _reserveCache(),
            _vaultCache(),
            collaterals[0],
            _goal,
            _period,
            _exchangeRate,
            _vestingCliffPeriod,
            _vestingCompletePeriod,
            _supplyOfferedPct,
            _fundingForBeneficiaryPct,
            _openDate
        );
    }


/***** internal setup functions *****/

function _setupCollaterals(
        Kernel     _dao,
        uint256[2] _virtualSupplies,
        uint256[2] _virtualBalances,
        uint256[2] _slippages,
        uint256    _rateDAI,
        uint256    _floorDAI
    )
        internal
    {
        ACL acl = ACL(_dao.acl());
        (, Voting shareVoting) = _shareAppsCache();
        (,,,, AragonFundraisingController controller) = _fundraisingAppsCache();

        // create and grant ADD_COLLATERAL_TOKEN_ROLE to this template
        _createPermissionForTemplate(acl, address(controller), controller.ADD_COLLATERAL_TOKEN_ROLE());
        // add DAI both as a protected collateral and a tapped token
        controller.addCollateralToken(
            collaterals[0],
            _virtualSupplies[0],
            _virtualBalances[0],
            DAI_RESERVE_RATIO,
            _slippages[0],
            _rateDAI,
            _floorDAI
        );
        // add ANT as a protected collateral [but not as a tapped token]
        controller.addCollateralToken(
            collaterals[1],
            _virtualSupplies[1],
            _virtualBalances[1],
            ANT_RESERVE_RATIO,
            _slippages[1],
            0,
            0
        );
        // transfer ADD_COLLATERAL_TOKEN_ROLE
        _transferPermissionFromTemplate(acl, controller, shareVoting, controller.ADD_COLLATERAL_TOKEN_ROLE(), shareVoting);
    }


/**
 * @dev Generate new Reward Collectible and transfer it to the owner, with isAttached as 0.
 * @notice Helps in redeeming the Rewards using our Oracle. Creates & transfers the asset to the redeemer (_owner)
 * The generation of an asset if limited via the generationSeasonController
 * @param _teamId           teamId of the asset/token/collectible
 * @param _posId            position of the asset/token/collectible
 * @param _attributes       attributes of asset/token/collectible
 * @param _owner            owner (redeemer) of asset/token/collectible
 * @param _gameId           mlb game Identifier
 * @param _playerOverrideId player override identifier
 * @param _mlbPlayerId      official mlb player identifier
 */

function createRewardCollectible (
        uint8 _teamId,
        uint8 _posId,
        uint256 _attributes,
        address _owner,
        uint256 _gameId,
        uint256 _playerOverrideId,
        uint256 _mlbPlayerId)
        external
        canCreate
        whenNotPaused
        returns (uint256) {

        address nftOwner = _owner;

        if (nftOwner == address(0)) {
             nftOwner = managerPrimary;
        }

        rewardsRedeemed++;
        uint32 _sequenceId = getSequenceId(_teamId);

        uint256 assetDetails = uint256(uint64(now));
        assetDetails |= uint256(_sequenceId)<<64;
        assetDetails |= uint256(_teamId)<<96;
        assetDetails |= uint256(_posId)<<104;

        uint256[5] memory _nftData = [assetDetails, _attributes, _gameId, _playerOverrideId, _mlbPlayerId];

        return _createNFTCollectible(_teamId, _attributes, nftOwner, 0, _nftData);
    }


// function create(address _beneficiary, uint256 _start, uint256 _cliff, uint256 _duration, bool _revocable, uint256 noOfTokens) onlyOwner public returns(StandardTokenVesting) {

function create(address _beneficiary, uint256 _cliff, uint256 _duration, bool _revocable, uint256 noOfTokens) public onlyOwner  returns(StandardTokenVesting) {
        StandardTokenVesting vesting = new StandardTokenVesting(_beneficiary, now , _cliff , _duration, _revocable);

        vesting.transferOwnership(msg.sender);
        vestingContractAddresses[_beneficiary] = vesting;
        emit CreatedStandardVestingContract(vesting);
        assert(token.transferFrom(owner, vesting, noOfTokens));

        return vesting;
    }


// Funds withdrawal to cover costs of fairwin operation.

function withdrawFunds(address beneficiary, uint withdrawAmount) external onlyOwner {
        require (withdrawAmount <= address(this).balance, "Increase amount larger than balance.");
        require (jackpotSize + lockedInBets + withdrawAmount <= address(this).balance, "Not enough funds.");
        sendFunds(beneficiary, withdrawAmount, withdrawAmount);
    }


// This is the method used to settle 99% of bets. To process a bet with a specific
// "commit", settleBet should supply a "reveal" number that would Keccak256-hash to
// "commit". "blockHash" is the block hash of placeBet block as seen by croupier; it
// is additionally asserted to prevent changing the bet outcomes on Ethereum reorgs.

function settleBet(uint reveal, bytes32 blockHash) external onlyCroupier {
        uint commit = uint(keccak256(abi.encodePacked(reveal)));

        Bet storage bet = bets[commit];
        uint placeBlockNumber = bet.placeBlockNumber;

        // Check that bet has not expired yet (see comment to BET_EXPIRATION_BLOCKS).
        require (block.number > placeBlockNumber, "settleBet in the same block as placeBet, or before.");
        require (block.number <= placeBlockNumber + BET_EXPIRATION_BLOCKS, "Blockhash can't be queried by EVM.");
        require (blockhash(placeBlockNumber) == blockHash);

        // Settle bet using reveal and blockHash as entropy sources.
        settleBetCommon(bet, reveal, blockHash);
    }


// This method is used to settle a bet that was mined into an uncle block. At this
// point the player was shown some bet outcome, but the blockhash at placeBet height
// is different because of Ethereum chain reorg. We supply a full merkle proof of the
// placeBet transaction receipt to provide untamperable evidence that uncle block hash
// indeed was present on-chain at some point.

function settleBetUncleMerkleProof(uint reveal, uint40 canonicalBlockNumber) external onlyCroupier {
        // "commit" for bet settlement can only be obtained by hashing a "reveal".
        uint commit = uint(keccak256(abi.encodePacked(reveal)));

        Bet storage bet = bets[commit];

        // Check that canonical block hash can still be verified.
        require (block.number <= canonicalBlockNumber + BET_EXPIRATION_BLOCKS, "Blockhash can't be queried by EVM.");

        // Verify placeBet receipt.
        requireCorrectReceipt(4 + 32 + 32 + 4);

        // Reconstruct canonical & uncle block hashes from a receipt merkle proof, verify them.
        bytes32 canonicalHash;
        bytes32 uncleHash;
        (canonicalHash, uncleHash) = verifyMerkleProof(commit, 4 + 32 + 32);
        require (blockhash(canonicalBlockNumber) == canonicalHash);

        // Settle bet using reveal and uncleHash as entropy sources.
        settleBetCommon(bet, reveal, uncleHash);
    }


// Refund transaction - return the bet amount of a roll that was not processed in a
// due timeframe. Processing such blocks is not possible due to EVM limitations (see
// BET_EXPIRATION_BLOCKS comment above for details). In case you ever find yourself
// in a situation like this, just contact the fairwin support, however nothing
// precludes you from invoking this method yourself.

function refundBet(uint commit) external {
        // Check that bet is in 'active' state.
        Bet storage bet = bets[commit];
        uint amount = bet.amount;

        require (amount != 0, "Bet should be in an 'active' state");

        // Check that bet has already expired.
        require (block.number > bet.placeBlockNumber + BET_EXPIRATION_BLOCKS, "Blockhash can't be queried by EVM.");

        // Move bet into 'processed' state, release funds.
        bet.amount = 0;

        uint diceWinAmount;
        uint jackpotFee;
        (diceWinAmount, jackpotFee) = getDiceWinAmount(amount, bet.modulo, bet.rollUnder);

        lockedInBets -= uint128(diceWinAmount);
        jackpotSize -= uint128(jackpotFee);

        // Send the refund.
        sendFunds(bet.gambler, amount, amount);
    }


// Get the expected win amount after house edge is subtracted.

function getDiceWinAmount(uint amount, uint modulo, uint rollUnder) private pure returns (uint winAmount, uint jackpotFee) {
        require (0 < rollUnder && rollUnder <= modulo, "Win probability out of range.");

        jackpotFee = amount >= MIN_JACKPOT_BET ? JACKPOT_FEE : 0;

        uint houseEdge = amount * HOUSE_EDGE_PERCENT / 100;

        if (houseEdge < HOUSE_EDGE_MINIMUM_AMOUNT) {
            houseEdge = HOUSE_EDGE_MINIMUM_AMOUNT;
        }

        require (houseEdge + jackpotFee <= amount, "Bet doesn't even cover house edge.");
        winAmount = (amount - houseEdge - jackpotFee) * modulo / rollUnder;
    }


// Memory copy.

function memcpy(uint dest, uint src, uint len) pure private {
        // Full 32 byte words
        for(; len >= 32; len -= 32) {
            assembly { mstore(dest, mload(src)) }
            dest += 32; src += 32;
        }

        // Remaining bytes
        uint mask = 256 ** (32 - len) - 1;
        assembly {
            let srcpart := and(mload(src), not(mask))
            let destpart := and(mload(dest), mask)
            mstore(dest, or(destpart, srcpart))
        }
    }


/**
 * @dev Generate new ETH Card Collectible, with isAttached as 2.
 * @notice Helps to generate Collectibles/Tokens/Asset and transfer to ETH Cards,
 * which can be redeemed using our web-app.The generation of an asset if limited via the generationSeasonController
 * @param _teamId           teamId of the asset/token/collectible
 * @param _posId            position of the asset/token/collectible
 * @param _attributes       attributes of asset/token/collectible
 * @param _owner            owner of asset/token/collectible
 * @param _gameId           mlb game Identifier
 * @param _playerOverrideId player override identifier
 * @param _mlbPlayerId      official mlb player identifier
 */

function createETHCardCollectible (
        uint8 _teamId,
        uint8 _posId,
        uint256 _attributes,
        address _owner,
        uint256 _gameId,
        uint256 _playerOverrideId,
        uint256 _mlbPlayerId)
        external
        canCreate
        whenNotPaused
        returns (uint256) {

        address nftOwner = _owner;

        if (nftOwner == address(0)) {
             nftOwner = managerPrimary;
        }

        rewardsRedeemed++;
        uint32 _sequenceId = getSequenceId(_teamId);

        uint256 assetDetails = uint256(uint64(now));
        assetDetails |= uint256(_sequenceId)<<64;
        assetDetails |= uint256(_teamId)<<96;
        assetDetails |= uint256(_posId)<<104;

        uint256[5] memory _nftData = [assetDetails, _attributes, _gameId, _playerOverrideId, _mlbPlayerId];

        return _createNFTCollectible(_teamId, _attributes, nftOwner, 2, _nftData);
    }


/**
 * @dev Returns all the relevant information about a specific Collectible.
 * @notice Get details about your collectible
 * @param _tokenId              The token identifier
 * @return isAttached           Is Object attached
 * @return teamId               team identifier of the asset/token/collectible
 * @return positionId           position identifier of the asset/token/collectible
 * @return creationTime         creation timestamp
 * @return attributes           attribute of the asset/token/collectible
 * @return currentGameCardId    current game card of the asset/token/collectible
 * @return mlbGameID            mlb game identifier in which the asset/token/collectible was generated
 * @return playerOverrideId     player override identifier of the asset/token/collectible
 * @return playerStatus         status of the player (Rookie/Veteran/Historical)
 * @return playerHandedness     handedness of the asset
 * @return mlbPlayerId          official MLB Player Identifier
 */

function getCollectibleDetails(uint256 _tokenId)
        external
        view
        returns (
        uint256 isAttached,
        uint32 sequenceId,
        uint8 teamId,
        uint8 positionId,
        uint64 creationTime,
        uint256 attributes,
        uint256 playerOverrideId,
        uint256 mlbGameId,
        uint256 currentGameCardId,
        uint256 mlbPlayerId,
        uint256 earnedBy,
        uint256 generationSeason
        ) {
        NFT memory obj  = _getAttributesOfToken(_tokenId);

        attributes = obj.attributes;
        currentGameCardId = obj.currentGameCardId;
        mlbGameId = obj.mlbGameId;
        playerOverrideId = obj.playerOverrideId;
        mlbPlayerId = obj.mlbPlayerId;

        creationTime = uint64(obj.assetDetails);
        sequenceId = uint32(obj.assetDetails>>64);
        teamId = uint8(obj.assetDetails>>96);
        positionId = uint8(obj.assetDetails>>104);
        isAttached = obj.isAttached;
        earnedBy = obj.earnedBy;

        generationSeason = generationSeasonDict[(obj.attributes % 1000000) / 1000];
    }


// External function

function spin(bytes32 hash, uint8 _v, bytes32 _r, bytes32 _s)
        external
        payable
        mustSignWithECDSA(hash, _v, _r, _s)
    {
        // Conditions
        require(msg.value >= minimumWager, "wager must be greater than or equal minimumWager.");
        require(msg.value <= maximumWager, "wager must be lower than or equal maximumWager.");
        require(
            address(this).balance >= msg.value * maximumMultiplier,
            "contract balance must greater than wager * maximumMultiplier."
        );
        require(msg.sender == tx.origin, 'only EOA can call this contract');

        //Interaction
        string memory firstSymbol;
        string memory secondSymbol;
        string memory thirdSymbol;
        uint rewardAmount = 0;
        uint8 multiplier = 0;
        uint8 cherryCount = 0;
        bool isWin = false;

        (firstSymbol, secondSymbol, thirdSymbol) = _findThreeSymbols(_s);

        if (_isWin(firstSymbol, secondSymbol, thirdSymbol)) {
            // Normal win
            isWin = true;
            (rewardAmount, multiplier) = _calculateRewardAmount(msg.value, firstSymbol);
            _sendReward(rewardAmount);
        } else {
            cherryCount = _countCherry(firstSymbol, secondSymbol, thirdSymbol);
            if (cherryCount > 0) {
                // Cherry win
                isWin = true;
                (rewardAmount, multiplier) = _calculateRewardAmountForCherry(msg.value, cherryCount);
                _sendCherryReward(rewardAmount);
            }
        }

        emit LogSpinResult(msg.sender, msg.value, isWin, firstSymbol, secondSymbol, thirdSymbol, multiplier, rewardAmount);
    }


/**
 * @dev Only allows trasnctions to go throught if the msg.sender is in the apporved list
 * @notice Updates the gameCardID properrty of the asset
 * @param _gameCardNumber  The game card number
 * @param _playerId        The player identifier
 */

function updateCurrentGameCardId(uint256 _gameCardNumber, uint256 _playerId) public whenNotPaused {
        require (contractsApprovedList[msg.sender]);

        NFT memory obj  = _getAttributesOfToken(_playerId);

        obj.currentGameCardId = _gameCardNumber;

        if ( _gameCardNumber == 0 ) {
            obj.isAttached = 0;
        } else {
            obj.isAttached = 1;
        }

        allNFTs[_playerId] = obj;
    }


//  redeem(): burn one star token, receive ownership of the most recently deposited star in exchange
//

function redeem() external returns (uint16) {
        // must have sufficient balance
        require(startoken.balanceOf(_msgSender()) >= ONE_STAR, "Treasury: Not enough balance");

        // there must be at least one star in the asset list
        require(assets.length > 0, "Treasury: no star available to redeem");

        // remove the star to be redeemed
        uint16 _star = assets[assets.length-1];
        assets.pop();

        // burn the tokens
        startoken.ownerBurn(_msgSender(), ONE_STAR);

        // transfer ownership
        // note: Treasury should be the owner of the point and able to transfer it. this check happens inside
        // transferPoint().
        IEcliptic ecliptic = IEcliptic(azimuth.owner());
        // note: _star is uint16, ecliptic expects a uint32 point
        ecliptic.transferPoint(_star, _msgSender(), true);

        emit Redeem(azimuth.getPrefix(_star), _star, _msgSender());
        return _star;
    }


// Decrease the amount of tokens that an owner allowed to a spender.
// approve should be called when allowed[spender] == 0.
// To decrement allowed value is better to use this function to avoid 2 calls (and wait until the first transaction is mined)

function decreaseApproval(address spender, uint256 subtractedValue ) public returns (bool){
    uint256 oldValue = allowed[msg.sender][spender];
    if (subtractedValue >= oldValue) {
      allowed[msg.sender][spender] = 0;
    } else {
      allowed[msg.sender][spender] = oldValue.sub(subtractedValue);
    }
    emit Approval(msg.sender, spender, allowed[msg.sender][spender]);
    return true;
  }


//freeze HXY tokens to contract

function Freeze(address token)
        public
    {
        if(isFreezeFinished(msg.sender, token)){
            Unfreeze(token);//unfreezes all currently frozen tokens
        }
        uint amt;
        //update balances
        if(token == hxyAddress){
            amt = mintedHxy[msg.sender];
            require(amt > 0, "Error: insufficient balance available to freeze");//ensure user has enough funds allocated
            mintedHxy[msg.sender] = 0;
            tokenHxyFrozenBalances[msg.sender] += amt;
            totalHxyFrozen += amt;
            frozen[msg.sender].freezeHxyStartTimestamp = now;
        }
        else if(token == hxbAddress){
            amt = mintedHxb[msg.sender];
            require(amt > 0, "Error: insufficient balance available to lock");//ensure user has enough funds allocated
            mintedHxb[msg.sender] = 0;
            tokenHxbLockedBalances[msg.sender] += amt;
            totalHxbLocked += amt;
            frozen[msg.sender].lockHxbStartTimestamp = now;
        }
        else if(token == hxpAddress){
            amt = mintedHxp[msg.sender];
            require(amt > 0, "Error: insufficient balance available to freeze");//ensure user has enough funds allocated
            mintedHxp[msg.sender] = 0;
            tokenHxpFrozenBalances[msg.sender] += amt;
            totalHxpFrozen += amt;
            frozen[msg.sender].freezeHxpStartTimestamp = now;
        }
        else{
            revert();
        }
        emit TokenFreeze(msg.sender, amt, token);
    }


//unfreeze HXY tokens from contract

function Unfreeze(address token)
        public
    {
        uint amt;
        if(token == hxyAddress){
            require(tokenHxyFrozenBalances[msg.sender] > 0,"Error: unsufficient frozen balance");//ensure user has enough frozen funds
            require(isFreezeFinished(msg.sender, token), "tokens cannot be unfrozen yet, min 90 days");
            amt = tokenHxyFrozenBalances[msg.sender];
            tokenHxyFrozenBalances[msg.sender] = 0;
            frozen[msg.sender].freezeHxyStartTimestamp = 0;
            totalHxyFrozen -= amt;
            hxy.transfer(msg.sender, amt);//make transfer
        }
        else if(token == hxbAddress){
            require(tokenHxbLockedBalances[msg.sender] > 0,"Error: unsufficient frozen balance");//ensure user has enough frozen funds
            require(isFreezeFinished(msg.sender, token), "tokens cannot be unlocked yet, max HXB supply much be reached");
            amt = tokenHxbLockedBalances[msg.sender];
            tokenHxbLockedBalances[msg.sender] = 0;
            frozen[msg.sender].lockHxbStartTimestamp = 0;
            totalHxbLocked -= amt;
            hxb.transfer(msg.sender, amt);//make transfer
        }
        else if(token == hxpAddress){
            require(tokenHxpFrozenBalances[msg.sender] > 0,"Error: unsufficient frozen balance");//ensure user has enough frozen funds
            require(isFreezeFinished(msg.sender, token), "tokens cannot be unfrozen yet, can be unfrozen on platform launch");
            amt = tokenHxpFrozenBalances[msg.sender];
            tokenHxpFrozenBalances[msg.sender] = 0;
            frozen[msg.sender].freezeHxpStartTimestamp = 0;
            totalHxpFrozen -= amt;
            hxp.transfer(msg.sender, amt);//make transfer
        }

        else{
            revert();
        }

        emit TokenUnfreeze(msg.sender, amt, token);
    }


//HXY frozen for 365 days , HXB locked till maxSupply , HXP frozen till platform launch

function isFreezeFinished(address _user, address token)
        public
        view
        returns(bool)
    {
        if(token == hxyAddress){
            if(frozen[_user].freezeHxyStartTimestamp == 0){
                return false;
            }
            else{
               return (frozen[_user].freezeHxyStartTimestamp + (365 * daySeconds)) <= now;
            }
        }
        else if(token == hxbAddress){
            if(frozen[_user].lockHxbStartTimestamp == 0){
                return false;
            }
            else{
                return hxb.mintBlock();
            }
        }
        else if(token == hxpAddress){
            if(frozen[_user].freezeHxpStartTimestamp == 0){
                return false;
            }
            else{
                return hxpUnlocked;
            }
        }
        else{
            return false;
        }
    }


/**
 * @dev A batch function to facilitate batching of asset creation. canCreate modifier
 * helps in controlling who can call the function
 * @notice Batch Function to Create Assets
 * @param      _teamId            The team identifier
 * @param      _attributes        The attributes
 * @param      _playerOverrideId  The player override identifier
 * @param      _mlbPlayerId       The mlb player identifier
 * @param      _to                To Address
 */

function batchCreateAsset(
        uint8[] _teamId,
        uint256[] _attributes,
        uint256[] _playerOverrideId,
        uint256[] _mlbPlayerId,
        address[] _to)
        external
        canCreate
        whenNotPaused {
            require (isBatchSupported);

            require (_teamId.length > 0 && _attributes.length > 0 &&
                _playerOverrideId.length > 0 && _mlbPlayerId.length > 0 &&
                _to.length > 0);

            uint256 assetDetails;
            uint256[5] memory _nftData;

            for(uint ii = 0; ii < _attributes.length; ii++){
                require (_to[ii] != address(0) && _teamId[ii] != 0 && _attributes.length != 0 &&
                    _mlbPlayerId[ii] != 0);

                assetDetails = uint256(uint64(now));
                assetDetails |= uint256(getSequenceId(_teamId[ii]))<<64;
                assetDetails |= uint256(_teamId[ii])<<96;
                assetDetails |= uint256((_attributes[ii]/1000000000000000000000000000000000000000)-800)<<104;

                _nftData = [assetDetails, _attributes[ii], 0, _playerOverrideId[ii], _mlbPlayerId[ii]];

                _createNFTCollectible(_teamId[ii], _attributes[ii], _to[ii], 0, _nftData);
            }
        }


/*
 * @notify Modify an uint256 parameter
 * @param parameter The name of the parameter to change
 * @param val The new value for the parameter
 */

function modifyParameters(bytes32 parameter, uint256 val) external isAuthority {
        if (parameter == "nb") {
          require(both(val > 0, val <= EIGHTEEN_DECIMAL_NUMBER), "PRawPerSecondCalculator/invalid-nb");
          noiseBarrier = val;
        }
        else if (parameter == "ps") {
          require(val > 0, "PRawPerSecondCalculator/null-ps");
          periodSize = val;
        }
        else if (parameter == "foub") {
          require(both(val < subtract(subtract(uint(-1), defaultRedemptionRate), 1), val > 0), "PRawPerSecondCalculator/invalid-foub");
          feedbackOutputUpperBound = val;
        }
        else if (parameter == "allReaderToggle") {
          allReaderToggle = val;
        }
        else revert("PRawPerSecondCalculator/modify-unrecognized-param");
    }


/**
 * @dev        Overriden TransferFrom, with the modifier canTransfer which uses our attachment system
 * @notice     Helps in trasnferring assets
 * @param      _from     the address sending from
 * @param      _to       the address sending to
 * @param      _tokenId  The token identifier
 */

function transferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    )
        public
        canTransfer(_tokenId)
    {
        // Asset should not be in play
        require (checkIsAttached(_tokenId) == 0);

        require (_from != address(0));

        require (_to != address(0));

        clearApproval(_from, _tokenId);
        removeTokenFrom(_from, _tokenId);
        addTokenTo(_to, _tokenId);

        emit Transfer(_from, _to, _tokenId);
    }


/*
 * @notice Return a redemption rate bounded by feedbackOutputLowerBound and feedbackOutputUpperBound as well as the
 timeline over which that rate will take effect
 * @param pOutput The raw redemption rate computed from the proportional and integral terms
 */

function getBoundedRedemptionRate(int pOutput) public isReader view returns (uint256, uint256) {
        int  boundedPOutput = pOutput;
        uint newRedemptionRate;

        if (pOutput < feedbackOutputLowerBound) {
          boundedPOutput = feedbackOutputLowerBound;
        } else if (pOutput > int(feedbackOutputUpperBound)) {
          boundedPOutput = int(feedbackOutputUpperBound);
        }

        // newRedemptionRate cannot be lower than 10^0 (1) because of the way rpower is designed
        bool negativeOutputExceedsHundred = (boundedPOutput < 0 && -boundedPOutput >= int(defaultRedemptionRate));

        // If it is smaller than 1, set it to the nagative rate limit
        if (negativeOutputExceedsHundred) {
          newRedemptionRate = NEGATIVE_RATE_LIMIT;
        } else {
          // If boundedPOutput is lower than -int(NEGATIVE_RATE_LIMIT) set newRedemptionRate to 1
          if (boundedPOutput < 0 && boundedPOutput <= -int(NEGATIVE_RATE_LIMIT)) {
            newRedemptionRate = uint(addition(int(defaultRedemptionRate), -int(NEGATIVE_RATE_LIMIT)));
          } else {
            // Otherwise add defaultRedemptionRate and boundedPOutput together
            newRedemptionRate = uint(addition(int(defaultRedemptionRate), boundedPOutput));
          }
        }

        return (newRedemptionRate, defaultGlobalTimeline);
    }


/*
 * @notice Compute and return the upcoming redemption rate
 * @param marketPrice The system coin market price
 * @param redemptionPrice The system coin redemption price
 */

function getNextRedemptionRate(uint marketPrice, uint redemptionPrice, uint)
      public isReader view returns (uint256, int256, uint256) {
        // The proportional term is just redemption - market. Market is read as having 18 decimals so we multiply by 10**9
        // in order to have 27 decimals like the redemption price
        int256 rawProportionalTerm = subtract(int(redemptionPrice), multiply(int(marketPrice), int(10**9)));
        // Multiply P by Kp
        int256 gainProportionalTerm = multiply(rawProportionalTerm, int(Kp)) / int(EIGHTEEN_DECIMAL_NUMBER);
        // If the P * Kp output breaks the noise barrier, you can recompute a non null rate. Also make sure the output is not null
        if (
          breaksNoiseBarrier(absolute(gainProportionalTerm), redemptionPrice) &&
          gainProportionalTerm != 0
        ) {
          // Get the new redemption rate by taking into account the feedbackOutputUpperBound and feedbackOutputLowerBound
          (uint newRedemptionRate, uint rateTimeline) = getBoundedRedemptionRate(gainProportionalTerm);
          return (newRedemptionRate, rawProportionalTerm, rateTimeline);
        } else {
          return (TWENTY_SEVEN_DECIMAL_NUMBER, rawProportionalTerm, defaultGlobalTimeline);
        }
    }


/**
 * @dev     Facilitates batch trasnfer of collectible with multiple TO Address, depending if batch is supported on contract.
 * @notice  Batch Trasnfer with multpple TO addresses
 * @param      _tokenIds  The token identifiers
 * @param      _fromB     the address sending from
 * @param      _toB       the address sending to
 */

function multiBatchTransferFrom(
        uint256[] _tokenIds,
        address[] _fromB,
        address[] _toB)
        public
    {
        require (isBatchSupported);

        require (_tokenIds.length > 0 && _fromB.length > 0 && _toB.length > 0);

        uint256 _id;
        address _to;
        address _from;

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

            require (_tokenIds[i] != 0 && _fromB[i] != 0 && _toB[i] != 0);

            _id = _tokenIds[i];
            _to = _toB[i];
            _from = _fromB[i];

            transferFrom(_from, _to, _id);
        }

    }


/**
 * Updates House Oracle function
 *
 */

function changeHouseOracle(address oracleAddress, uint oraclePercentage) onlyOwner public {
        require(add(houseData.housePercentage,oraclePercentage)<1000,"House + Oracle percentage should be lower than 100%");
        if (oracleAddress != houseData.oracleAddress) {
            houseData.oldOracleAddress = houseData.oracleAddress;
            houseData.oracleAddress = oracleAddress;
        }
        houseData.oraclePercentage = oraclePercentage;
        emit HousePropertiesUpdated();
    }


/*
 * Places a Pool Bet
 */

function placePoolBet(uint eventId, uint outputId, uint forecast, uint closingDateTime, uint256 minimumWager, uint256 maximumWager, string memory createdBy) payable public {
        require(msg.value > 0,"Wager should be greater than zero");
        require(!houseData.newBetsPaused,"Bets are paused right now");
        betNextId += 1;
        bets[betNextId].oracleAddress = houseData.oracleAddress;
        bets[betNextId].betType = BetType.poolbet;
        bets[betNextId].createdBy = msg.sender;

        updateBetDataFromOracle(betNextId, eventId, outputId);
        require(!bets[betNextId].isCancelled,"Event has been cancelled");
        require(!bets[betNextId].isOutcomeSet,"Event has already an outcome");
        if (closingDateTime>0) {
            setBetCloseTime(betNextId, closingDateTime);
        }
        uint betCloseTime = getBetCloseTime(betNextId);
        require(betCloseTime >= now,"Close time has passed");
        setBetEventId(betNextId, eventId);
        setBetEventOutputId(betNextId, outputId);
        if (minimumWager != 0) {
            bets[betNextId].minimumWager = minimumWager;
        } else {
            bets[betNextId].minimumWager = msg.value;
        }
        if (maximumWager != 0) {
            bets[betNextId].maximumWager = maximumWager;
        }

        playerBetTotalBets[msg.sender][betNextId] = 1;
        betTotalBets[betNextId] = 1;
        betTotalAmount[betNextId] = msg.value;

        betForcastTotalAmount[betNextId][forecast] = msg.value;

        playerBetTotalAmount[msg.sender][betNextId] = msg.value;

        playerBetForecastWager[msg.sender][betNextId][forecast] = msg.value;

        lastBettingActivity = block.number;

        playerHasBet[msg.sender] = true;

        emit BetPlacedOrModified(betNextId, msg.sender, BetEvent.placeBet, msg.value, forecast, createdBy, betCloseTime);
    }


/*
 * Places a HeadToHEad Bet
 */

function placeH2HBet(uint eventId, uint outputId, uint forecast, uint closingDateTime, uint256 payoutRate, string memory createdBy) payable public {
        require(msg.value > 0,"Wager should be greater than zero");
        require(!houseData.newBetsPaused,"Bets are paused right now");
        betNextId += 1;
        bets[betNextId].oracleAddress = houseData.oracleAddress;
        bets[betNextId].betType = BetType.headtohead;
        bets[betNextId].createdBy = msg.sender;
        updateBetDataFromOracle(betNextId, eventId, outputId);
        require(!bets[betNextId].isCancelled,"Event has been cancelled");
        require(!bets[betNextId].isOutcomeSet,"Event has already an outcome");
        if (closingDateTime>0) {
            setBetCloseTime(betNextId, closingDateTime);
        }
        uint betCloseTime = getBetCloseTime(betNextId);
        require( betCloseTime >= now,"Close time has passed");
        setBetEventId(betNextId, eventId);
        setBetEventOutputId(betNextId, outputId);
        checkPayoutRate(payoutRate);
        require(forecast>0 && forecast < getEventOutputMaxUint(bets[betNextId].oracleAddress, eventId, outputId),"Forecast should be greater than zero and less than Max accepted forecast(All options true)");
        setBetPayoutRate(betNextId, payoutRate);
        headToHeadForecasts[betNextId][msg.sender] = forecast;

        playerBetTotalBets[msg.sender][betNextId] = 1;
        betTotalBets[betNextId] = 1;
        betTotalAmount[betNextId] = msg.value;

        betForcastTotalAmount[betNextId][forecast] = msg.value;

        playerBetTotalAmount[msg.sender][betNextId] = msg.value;

        playerBetForecastWager[msg.sender][betNextId][forecast] = msg.value;

        lastBettingActivity = block.number;

        playerHasBet[msg.sender] = true;

        emit BetPlacedOrModified(betNextId, msg.sender, BetEvent.placeBet, msg.value, forecast, createdBy, betCloseTime);
    }


/*
 * Increase wager
 */

function increaseWager(uint betId, uint forecast, string memory createdBy) payable public {
        require(msg.value > 0,"Increase wager amount should be greater than zero");
        require(bets[betId].betType == BetType.poolbet,"Only poolbet supports the increaseWager");
        require(playerBetForecastWager[msg.sender][betId][forecast] > 0,"Haven't placed any bet for this forecast. Use callBet instead");
        uint256 wager = playerBetForecastWager[msg.sender][betId][forecast] + msg.value;
        require(bets[betId].maximumWager==0 || wager<=bets[betId].maximumWager,"The updated wager is higher then the maximum accepted");
        updateBetDataFromOracle(betId, getBetEventId(betId), getBetEventOutputId(betId));
        require(!bets[betId].isCancelled,"Bet has been cancelled");
        require(!bets[betId].isOutcomeSet,"Event has already an outcome");
        uint betCloseTime = getBetCloseTime(betId);
        require(betCloseTime >= now,"Close time has passed");
        betTotalAmount[betId] += msg.value;

        betForcastTotalAmount[betId][forecast] += msg.value;

        playerBetTotalAmount[msg.sender][betId] += msg.value;

        playerBetForecastWager[msg.sender][betId][forecast] += msg.value;

        lastBettingActivity = block.number;

        emit BetPlacedOrModified(betId, msg.sender, BetEvent.increaseWager, msg.value, forecast, createdBy, betCloseTime);
    }


/*
 * Remove a Bet
 */

function removeBet(uint betId, string memory createdBy) public {
        require(bets[betId].createdBy == msg.sender,"Caller and player created don't match");
        require(playerBetTotalBets[msg.sender][betId] > 0, "Player should has placed at least one bet");
        require(betTotalBets[betId] == playerBetTotalBets[msg.sender][betId],"The bet has been called by other player");
        require(bets[betId].betType == BetType.headtohead || bets[betId].betType == BetType.poolbet,"Only poolbet and headtohead bets are implemented");
        updateBetDataFromOracle(betId, getBetEventId(betId), getBetEventOutputId(betId));
        bets[betId].isCancelled = true;
        uint256 wager = betTotalAmount[betId];
        betTotalBets[betId] = 0;
        betTotalAmount[betId] = 0;
        playerBetTotalAmount[msg.sender][betId] = 0;
        playerBetTotalBets[msg.sender][betId] = 0;
        lastBettingActivity = block.number;
        msg.sender.transfer(wager);
        emit BetPlacedOrModified(betId, msg.sender, BetEvent.removeBet, wager, 0, createdBy, getBetCloseTime(betId));
    }


/*
 * Refute a Bet
 */

function refuteBet(uint betId, string memory createdBy) public {
        require(playerBetTotalAmount[msg.sender][betId]>0,"Caller hasn't placed any bet");
        require(!playerBetRefuted[msg.sender][betId],"Already refuted");
        require(bets[betId].betType == BetType.headtohead || bets[betId].betType == BetType.poolbet,"Only poolbet and headtohead bets are implemented");
        updateBetDataFromOracle(betId, getBetEventId(betId), getBetEventOutputId(betId));
        require(bets[betId].isOutcomeSet, "Refute isn't allowed when no outcome has been set");
        require(getBetFreezeTime(betId) > now, "Refute isn't allowed when Event freeze has passed");
        playerBetRefuted[msg.sender][betId] = true;
        betRefutedAmount[betId] += playerBetTotalAmount[msg.sender][betId];
        if (betRefutedAmount[betId] >= betTotalAmount[betId]) {
            bets[betId].isCancelled = true;
        }
        lastBettingActivity = block.number;
        emit BetPlacedOrModified(betId, msg.sender, BetEvent.refuteBet, 0, 0, createdBy, getBetCloseTime(betId));
    }


/**
 * Cancels a Bet
 */

function cancelBet(uint betId) onlyOwner public {
        require(houseData.managed, "Cancel available on managed Houses");
        updateBetDataFromOracle(betId, getBetEventId(betId), getBetEventOutputId(betId));
        require(getBetFreezeTime(betId) > now,"Freeze time passed");
        bets[betId].isCancelled = true;
        emit BetPlacedOrModified(betId, msg.sender, BetEvent.cancelledByHouse, 0, 0, "", getBetCloseTime(betId));
    }


/// @notice Backward compatible function
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Trade from src to dest token and sends dest token to destAddress
/// @param trader Address of the taker side of this trade
/// @param src Source token
/// @param srcAmount Amount of src tokens in twei
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param maxDestAmount A limit on the amount of dest tokens in twei
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @param walletId Platform wallet address for receiving fees
/// @param hint Advanced instructions for running the trade
/// @return destAmount Amount of actual dest tokens in twei

function tradeWithHint(
        address payable trader,
        ERC20 src,
        uint256 srcAmount,
        ERC20 dest,
        address payable destAddress,
        uint256 maxDestAmount,
        uint256 minConversionRate,
        address payable walletId,
        bytes calldata hint
    ) external payable returns (uint256 destAmount) {
        TradeData memory tradeData = initTradeInput({
            trader: trader,
            src: src,
            dest: dest,
            srcAmount: srcAmount,
            destAddress: destAddress,
            maxDestAmount: maxDestAmount,
            minConversionRate: minConversionRate,
            platformWallet: walletId,
            platformFeeBps: 0
        });

        return trade(tradeData, hint);
    }


/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Trade from src to dest token and sends dest token to destAddress
/// @param trader Address of the taker side of this trade
/// @param src Source token
/// @param srcAmount Amount of src tokens in twei
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param maxDestAmount A limit on the amount of dest tokens in twei
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @param platformWallet Platform wallet address for receiving fees
/// @param platformFeeBps Part of the trade that is allocated as fee to platform wallet. Ex: 1000 = 10%
/// @param hint Advanced instructions for running the trade
/// @return destAmount Amount of actual dest tokens in twei

function tradeWithHintAndFee(
        address payable trader,
        IERC20 src,
        uint256 srcAmount,
        IERC20 dest,
        address payable destAddress,
        uint256 maxDestAmount,
        uint256 minConversionRate,
        address payable platformWallet,
        uint256 platformFeeBps,
        bytes calldata hint
    ) external payable override returns (uint256 destAmount) {
        TradeData memory tradeData = initTradeInput({
            trader: trader,
            src: src,
            dest: dest,
            srcAmount: srcAmount,
            destAddress: destAddress,
            maxDestAmount: maxDestAmount,
            minConversionRate: minConversionRate,
            platformWallet: platformWallet,
            platformFeeBps: platformFeeBps
        });

        return trade(tradeData, hint);
    }


/// @notice Can be called only by operator
/// @dev Allow or prevent to trade token -> eth for list of reserves
///      Useful for migration to new network contract
///      Call storage to get list of reserves supporting token -> eth
/// @param token Token address
/// @param startIndex start index in reserves list
/// @param endIndex end index in reserves list (can be larger)
/// @param add If true, then give reserve token allowance, otherwise set zero allowance

function listReservesForToken(
        IERC20 token,
        uint256 startIndex,
        uint256 endIndex,
        bool add
    ) external {
        onlyOperator();

        if (startIndex > endIndex) {
            // no need to do anything
            return;
        }

        address[] memory reserves = kyberStorage.getReserveAddressesPerTokenSrc(
            token, startIndex, endIndex
        );

        if (reserves.length == 0) {
            // no need to do anything
            return;
        }

        for(uint i = 0; i < reserves.length; i++) {
            if (add) {
                token.safeApprove(reserves[i], MAX_ALLOWANCE);
                setDecimals(token);
            } else {
                token.safeApprove(reserves[i], 0);
            }
        }

        emit ListedReservesForToken(token, reserves, add);
    }


/// @dev gets the expected rates when trading src -> dest token, with / without fees
/// @param src Source token
/// @param dest Destination token
/// @param srcQty Amount of src tokens in twei
/// @param platformFeeBps Part of the trade that is allocated as fee to platform wallet. Ex: 1000 = 10%
/// @param hint Advanced instructions for running the trade
/// @return rateWithNetworkFee Rate after deducting network fee but excluding platform fee
/// @return rateWithAllFees = actual rate. Rate after accounting for both network and platform fees

function getExpectedRateWithHintAndFee(
        IERC20 src,
        IERC20 dest,
        uint256 srcQty,
        uint256 platformFeeBps,
        bytes calldata hint
    )
        external
        view
        override
        returns (
            uint256 rateWithNetworkFee,
            uint256 rateWithAllFees
        )
    {
        if (src == dest) return (0, 0);

        TradeData memory tradeData = initTradeInput({
            trader: payable(address(0)),
            src: src,
            dest: dest,
            srcAmount: (srcQty == 0) ? 1 : srcQty,
            destAddress: payable(address(0)),
            maxDestAmount: 2**255,
            minConversionRate: 0,
            platformWallet: payable(address(0)),
            platformFeeBps: platformFeeBps
        });

        tradeData.networkFeeBps = getNetworkFee();

        uint256 destAmount;
        (destAmount, rateWithNetworkFee) = calcRatesAndAmounts(tradeData, hint);

        rateWithAllFees = calcRateFromQty(
            tradeData.input.srcAmount,
            destAmount,
            tradeData.tokenToEth.decimals,
            tradeData.ethToToken.decimals
        );
    }


/// @notice Backward compatible API
/// @dev Gets the expected and slippage rate for exchanging src -> dest token
/// @dev worstRate is hardcoded to be 3% lower of expectedRate
/// @param src Source token
/// @param dest Destination token
/// @param srcQty Amount of src tokens in twei
/// @return expectedRate for a trade after deducting network fee.
/// @return worstRate for a trade. Calculated to be expectedRate * 97 / 100

function getExpectedRate(
        ERC20 src,
        ERC20 dest,
        uint256 srcQty
    ) external view returns (uint256 expectedRate, uint256 worstRate) {
        if (src == dest) return (0, 0);
        uint256 qty = srcQty & ~PERM_HINT_GET_RATE;

        TradeData memory tradeData = initTradeInput({
            trader: payable(address(0)),
            src: src,
            dest: dest,
            srcAmount: (qty == 0) ? 1 : qty,
            destAddress: payable(address(0)),
            maxDestAmount: 2**255,
            minConversionRate: 0,
            platformWallet: payable(address(0)),
            platformFeeBps: 0
        });

        tradeData.networkFeeBps = getNetworkFee();

        (, expectedRate) = calcRatesAndAmounts(tradeData, "");

        worstRate = (expectedRate * 97) / 100; // backward compatible formula
    }


/// @notice Calculates platform fee and reserve rebate percentages for the trade.
///     Transfers eth and rebate wallet data to kyberFeeHandler

function handleFees(TradeData memory tradeData) internal {
        uint256 sentFee = tradeData.networkFeeWei + tradeData.platformFeeWei;
        //no need to handle fees if total fee is zero
        if (sentFee == 0)
            return;

        // update reserve eligibility and rebate percentages
        (
            address[] memory rebateWallets,
            uint256[] memory rebatePercentBps
        ) = calculateRebates(tradeData);

        // send total fee amount to fee handler with reserve data
        kyberFeeHandler.handleFees{value: sentFee}(
            ETH_TOKEN_ADDRESS,
            rebateWallets,
            rebatePercentBps,
            tradeData.input.platformWallet,
            tradeData.platformFeeWei,
            tradeData.networkFeeWei
        );
    }