/**
 * @notice Burn synths to clear issued synths/free SNX.
 * @param amount The amount (in UNIT base) you wish to burn
 * @dev The amount to burn is debased to XDR's
 */

function burnSynths(uint amount)
        external
        optionalProxy
        // No need to check for stale rates as effectiveValue checks rates
    {
        bytes32 currencyKey = "sUSD";

        // How much debt do they have?
        uint debtToRemove = effectiveValue(currencyKey, amount, "XDR");
        uint existingDebt = debtBalanceOf(messageSender, "XDR");

        uint debtInCurrencyKey = debtBalanceOf(messageSender, currencyKey);

        require(existingDebt > 0, "No debt to forgive");

        // If they're trying to burn more debt than they actually owe, rather than fail the transaction, let's just
        // clear their debt and leave them be.
        uint amountToRemove = existingDebt < debtToRemove ? existingDebt : debtToRemove;

        // Remove their debt from the ledger
        _removeFromDebtRegister(amountToRemove, existingDebt);

        uint amountToBurn = debtInCurrencyKey < amount ? debtInCurrencyKey : amount;

        // synth.burn does a safe subtraction on balance (so it will revert if there are not enough synths).
        synths[currencyKey].burn(messageSender, amountToBurn);

        // Store their debtRatio against a feeperiod to determine their fee/rewards % for the period
        _appendAccountIssuanceRecord();
    }


/**
 * @notice The maximum synths an issuer can issue against their total synthetix quantity, priced in XDRs.
 * This ignores any already issued synths, and is purely giving you the maximimum amount the user can issue.
 */

function maxIssuableSynths(address issuer, bytes32 currencyKey)
        public
        view
        // We don't need to check stale rates here as effectiveValue will do it for us.
        returns (uint)
    {
        // What is the value of their SNX balance in the destination currency?
        uint destinationValue = effectiveValue("SNX", collateral(issuer), currencyKey);

        // They're allowed to issue up to issuanceRatio of that value
        return destinationValue.multiplyDecimal(synthetixState.issuanceRatio());
    }


/**
 * @notice If a user issues synths backed by SNX in their wallet, the SNX become locked. This function
 * will tell you how many synths a user has to give back to the system in order to unlock their original
 * debt position. This is priced in whichever synth is passed in as a currency key, e.g. you can price
 * the debt in sUSD, XDR, or any other synth you wish.
 */

function debtBalanceOf(address issuer, bytes32 currencyKey)
        public
        view
        // Don't need to check for stale rates here because totalIssuedSynths will do it for us
        returns (uint)
    {
        // What was their initial debt ownership?
        uint initialDebtOwnership;
        uint debtEntryIndex;
        (initialDebtOwnership, debtEntryIndex) = synthetixState.issuanceData(issuer);

        // If it's zero, they haven't issued, and they have no debt.
        if (initialDebtOwnership == 0) return 0;

        // Figure out the global debt percentage delta from when they entered the system.
        // This is a high precision integer of 27 (1e27) decimals.
        uint currentDebtOwnership = synthetixState.lastDebtLedgerEntry()
            .divideDecimalRoundPrecise(synthetixState.debtLedger(debtEntryIndex))
            .multiplyDecimalRoundPrecise(initialDebtOwnership);

        // What's the total value of the system in their requested currency?
        uint totalSystemValue = totalIssuedSynths(currencyKey);

        // Their debt balance is their portion of the total system value.
        uint highPrecisionBalance = totalSystemValue.decimalToPreciseDecimal()
            .multiplyDecimalRoundPrecise(currentDebtOwnership);

        // Convert back into 18 decimals (1e18)
        return highPrecisionBalance.preciseDecimalToDecimal();
    }


/**
 * @notice The remaining synths an issuer can issue against their total synthetix balance.
 * @param issuer The account that intends to issue
 * @param currencyKey The currency to price issuable value in
 */

function remainingIssuableSynths(address issuer, bytes32 currencyKey)
        public
        view
        // Don't need to check for synth existing or stale rates because maxIssuableSynths will do it for us.
        returns (uint)
    {
        uint alreadyIssued = debtBalanceOf(issuer, currencyKey);
        uint max = maxIssuableSynths(issuer, currencyKey);

        if (alreadyIssued >= max) {
            return 0;
        } else {
            return max.sub(alreadyIssued);
        }
    }


/**
 * @notice The total SNX owned by this account, both escrowed and unescrowed,
 * against which synths can be issued.
 * This includes those already being used as collateral (locked), and those
 * available for further issuance (unlocked).
 */

function collateral(address account)
        public
        view
        returns (uint)
    {
        uint balance = tokenState.balanceOf(account);

        if (escrow != address(0)) {
            balance = balance.add(escrow.balanceOf(account));
        }

        if (rewardEscrow != address(0)) {
            balance = balance.add(rewardEscrow.balanceOf(account));
        }

        return balance;
    }


/**
 * @notice The number of SNX that are free to be transferred for an account.
 * @dev Escrowed SNX are not transferable, so they are not included
 * in this calculation.
 * @notice SNX rate not stale is checked within debtBalanceOf
 */

function transferableSynthetix(address account)
        public
        view
        rateNotStale("SNX") // SNX is not a synth so is not checked in totalIssuedSynths
        returns (uint)
    {
        // How many SNX do they have, excluding escrow?
        // Note: We're excluding escrow here because we're interested in their transferable amount
        // and escrowed SNX are not transferable.
        uint balance = tokenState.balanceOf(account);

        // How many of those will be locked by the amount they've issued?
        // Assuming issuance ratio is 20%, then issuing 20 SNX of value would require
        // 100 SNX to be locked in their wallet to maintain their collateralisation ratio
        // The locked synthetix value can exceed their balance.
        uint lockedSynthetixValue = debtBalanceOf(account, "SNX").divideDecimalRound(synthetixState.issuanceRatio());

        // If we exceed the balance, no SNX are transferable, otherwise the difference is.
        if (lockedSynthetixValue >= balance) {
            return 0;
        } else {
            return balance.sub(lockedSynthetixValue);
        }
    }


/**
 * @notice Mints the inflationary SNX supply. The inflation shedule is
 * defined in the SupplySchedule contract.
 * The mint() function is publicly callable by anyone. The caller will
 receive a minter reward as specified in supplySchedule.minterReward().
 */

function mint()
        external
        returns (bool)
    {
        require(rewardsDistribution != address(0), "RewardsDistribution not set");

        uint supplyToMint = supplySchedule.mintableSupply();
        require(supplyToMint > 0, "No supply is mintable");

        // record minting event before mutation to token supply
        supplySchedule.recordMintEvent(supplyToMint);

        // Set minted SNX balance to RewardEscrow's balance
        // Minus the minterReward and set balance of minter to add reward
        uint minterReward = supplySchedule.minterReward();
        // Get the remainder
        uint amountToDistribute = supplyToMint.sub(minterReward);

        // Set the token balance to the RewardsDistribution contract
        tokenState.setBalanceOf(rewardsDistribution, tokenState.balanceOf(rewardsDistribution).add(amountToDistribute));
        emitTransfer(this, rewardsDistribution, amountToDistribute);

        // Kick off the distribution of rewards
        rewardsDistribution.distributeRewards(amountToDistribute);

        // Assign the minters reward.
        tokenState.setBalanceOf(msg.sender, tokenState.balanceOf(msg.sender).add(minterReward));
        emitTransfer(this, msg.sender, minterReward);

        totalSupply = totalSupply.add(supplyToMint);

        return true;
    }


/**
 * @dev Returns the rebuilt hash obtained by traversing a Merklee tree up
 * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
 * hash matches the root of the tree. When processing the proof, the pairs
 * of leafs & pre-images are assumed to be sorted.
 *
 * _Available since v4.4._
 */

function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            bytes32 proofElement = proof[i];
            if (computedHash <= proofElement) {
                // Hash(current computed hash + current element of the proof)
                computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
            } else {
                // Hash(current element of the proof + current computed hash)
                computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
            }
        }
        return computedHash;
    }


/**
 * @dev See {IERC721Enumerable-totalSupply}.
 */

function totalSupply() public view override returns (uint256) {
        // Counter underflow is impossible as _burnCounter cannot be incremented
        // more than _currentIndex times
        unchecked {
            return _currentIndex - _burnCounter;
        }
    }


/**
 * check_availability() returns a bunch of pool info given a pool id
 * id                    swap pool id
 * this function returns 1. exchange_addrs that can be used to determine the index
 *                       2. remaining target tokens
 *                       3. if started
 *                       4. if ended
 *                       5. swapped amount of the query address
 *                       5. exchanged amount of each token
 **/

function check_availability (bytes32 id) external view
        returns (address[] memory exchange_addrs, uint256 remaining,
                 bool started, bool expired, bool unlocked, uint256 unlock_time,
                 uint256 swapped, uint128[] memory exchanged_tokens) {
        Pool storage pool = pool_by_id[id];
        return (
            pool.exchange_addrs,                                                // exchange_addrs 0x0 means destructed
            unbox(pool.packed2, 0, 128),                                        // remaining
            block.timestamp > unbox(pool.packed1, 200, 28) + base_time,         // started
            block.timestamp > unbox(pool.packed1, 228, 28) + base_time,         // expired
            block.timestamp > pool.unlock_time + base_time,                     // unlocked
            pool.unlock_time + base_time,                                       // unlock_time
            pool.swapped_map[msg.sender],                                       // swapped number
            pool.exchanged_tokens                                               // exchanged tokens
        );
    }


/**
 * withdraw() transfers out a single token after a pool is expired or empty
 * id                    swap pool id
 * addr_i                withdraw token index
 * this function can only be called by the pool creator. after validation, it transfers the addr_i th token
 * out to the pool creator address.
 **/

function withdraw (bytes32 id, uint256 addr_i) public {
        Pool storage pool = pool_by_id[id];
        require(msg.sender == pool.creator, "Only the pool creator can withdraw.");

        uint256 withdraw_balance = pool.exchanged_tokens[addr_i];
        require(withdraw_balance > 0, "None of this token left");
        uint256 expiration = unbox(pool.packed1, 228, 28) + base_time;
        uint256 remaining_tokens = unbox(pool.packed2, 0, 128);
        // only after expiration or the pool is empty
        require(expiration <= block.timestamp || remaining_tokens == 0, "Not expired yet");
        address token_address = pool.exchange_addrs[addr_i];

        // ERC20
        if (token_address != DEFAULT_ADDRESS)
            transfer_token(token_address, address(this), msg.sender, withdraw_balance);
        // ETH
        else
            payable(msg.sender).transfer(withdraw_balance);
        // clear the record
        pool.exchanged_tokens[addr_i] = 0;
        emit WithdrawSuccess(id, token_address, withdraw_balance);
    }


/**
 * _qualification the smart contract address to verify qualification      160
 * _hash          sha3-256(password)                                      40
 * _start         start time delta                                        28
 * _end           end time  delta                                         28
 * wrap1() inserts the above variables into a 32-word block
 **/

function wrap1 (address _qualification, bytes32 _hash, uint256 _start, uint256 _end) internal pure
                    returns (uint256 packed1) {
        uint256 _packed1 = 0;
        _packed1 |= box(0, 160,  uint256(uint160(_qualification)));     // _qualification = 160 bits
        _packed1 |= box(160, 40, uint256(_hash) >> 216);                // hash = 40 bits (safe?)
        _packed1 |= box(200, 28, _start);                               // start_time = 28 bits
        _packed1 |= box(228, 28, _end);                                 // expiration_time = 28 bits
        return _packed1;
    }


/**
 * _total_tokens   target remaining         128
 * _limit          single swap limit        128
 * wrap2() inserts the above variables into a 32-word block
 **/

function wrap2 (uint256 _total_tokens, uint256 _limit) internal pure returns (uint256 packed2) {
        uint256 _packed2 = 0;
        _packed2 |= box(0, 128, _total_tokens);             // total_tokens = 128 bits ~= 3.4e38
        _packed2 |= box(128, 128, _limit);                  // limit = 128 bits
        return _packed2;
    }


/**
 * position      position in a memory block
 * size          data size
 * data          data
 * box() inserts the data in a 256bit word with the given position and returns it
 * data is checked by validRange() to make sure it is not over size
 **/

function box (uint16 position, uint16 size, uint256 data) internal pure returns (uint256 boxed) {
        require(validRange(size, data), "Value out of range BOX");
        assembly {
            // data << position
            boxed := shl(position, data)
        }
    }


/**
 * _box          32byte data to be modified
 * position      position in a memory block
 * size          data size
 * data          data to be inserted
 * rewriteBox() updates a 32byte word with a data at the given position with the specified size
 **/

function rewriteBox (uint256 _box, uint16 position, uint16 size, uint256 data)
                        internal pure returns (uint256 boxed) {
        assembly {
            // mask = ~((1 << size - 1) << position)
            // _box = (mask & _box) | ()data << position)
            boxed := or( and(_box, not(shl(position, sub(shl(size, 1), 1)))), shl(position, data))
        }
    }


// ----------------------------
// withdraw expense funds to arbiter
// ----------------------------

function withdrawArbFunds() public
        {
                if (!validArb2(msg.sender)) {
                        StatEvent("invalid arbiter");
                } else {
                        arbiter xarb = arbiters[msg.sender];
                        if (xarb.arbHoldover == 0) {
                                StatEvent("0 Balance");
                                return;
                        } else {
                                uint _amount = xarb.arbHoldover;
                                xarb.arbHoldover = 0;
                                if (!msg.sender.call.gas(acctCallGas).value(_amount)())
                                        throw;
                        }
                }
        }


// mint (Mint a token in the GuXeClub contract)
//  Anyone can call mint if they transfer the required ETH
//  Once the token is minted payments are transferred to the owner.
//  A RequestedGuXe event is emitted so that GuXeClub.com can
//   generate the image and update the URI. See setURI

function mint(uint256 parentId)
        public payable
    {
        require(sproutPaused != true, "Sprout is paused");
        // Check if the parent GuXe exists
        require(_exists(parentId), "Parent GuXe does not exists");

        uint256 currentSproutFee = getSproutFee(parentId);
        uint256 totalFee = artistFee.add(currentSproutFee);
        // Check if enough ETH was sent
        require(msg.value >= totalFee, "Did not provide enough ETH");

        tokenCount += 1;
        _safeMint(msg.sender, tokenCount);

        //want the request event to occur before transfer for ease workflow
        emit RequestedGuXe(tokenCount, parentId);

        //increase the sprout fee of the parent
        _incrementSproutFee(parentId);

        //transfer eth to parent owner account
        address parentOwner = ownerOf(parentId);

        //https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/
        (bool sent, bytes memory data) = parentOwner.call{value: currentSproutFee}("");
        require(sent, "Failed to send Ether");
    }


// Set the URI to point a json file with metadata on the newly minted
//  Guxe Token. The file is stored on the IPFS distributed web and is immutable.
//  Once setURI is called for a token the URI can not be updated and
//  the file on the IPFS system that the token points to can be verified as
//  unaltered as well. The json file is in a standard format used by opensea.io
//  It contains the image uri as well as information pertaining to the rareness
//  and limited additions of the art included in the image.

function setURI(uint256 _tokenId, string memory _tokenURI)
        external onlyRole(URI_ROLE)
    {
        string memory defaultURI = string(abi.encodePacked(_baseURI(), _tokenId.toString()));
        string memory currentURI = super.tokenURI(_tokenId);
        require (compareStrings(defaultURI, currentURI), "URI has already been set.");

        //update the URI
        _setTokenURI(_tokenId, _tokenURI);
        string memory finalURI = super.tokenURI(_tokenId);

        //opensea will watch this
        emit PermanentURI(finalURI, _tokenId);
    }


/**
 * @notice Transfers vested tokens to beneficiary.
 */

function release() public {
        int8 unreleasedIdx = _releasableIdx(block.timestamp);

        require(unreleasedIdx >= 0, "TokenVesting: no tokens are due");

        uint256 unreleasedAmount = _amount.mul(_percent[uint(unreleasedIdx)]).div(100);

        _token.safeTransfer(_beneficiary, unreleasedAmount);

        _percent[uint(unreleasedIdx)] = 0;
        _released = _released.add(unreleasedAmount);

        emit TokensReleased(address(_token), unreleasedAmount);
    }


/**
 * @dev Calculates the index that has already vested but hasn't been released yet.
 */

function _releasableIdx(uint256 ts) private view returns (int8) {
        for (uint8 i = 0; i < _schedule.length; i++) {
            if (ts > _schedule[i] && _percent[i] > 0) {
                return int8(i);
            }
        }

        return -1;
    }


/* @notice This function was invoked when user want to swap their collections with skully
 	 * @param skullyId the id of skully that user want to swap
 	 * @param exchangeTokenId the id of their collections
 	 * @param typeERC the number of erc721 in the list of contract that allow to exchange with
 	 * return none - just emit a result to the network
 	*/

function swap(uint256 skullyId, uint256 exchangeTokenId, uint64 typeERC) public whenNotPaused {
        ERC721(listERC721[typeERC]).transferFrom(msg.sender, address(this), exchangeTokenId);
        // cancel sale auction
        auctionContract.cancelAuction(skullyId);

        // set flag
        itemContract.increaseSkullyExp(skullyId, plusFlags);

        skullyContract.transferFrom(address(this), msg.sender, skullyId);

        emit Swapped(skullyId, exchangeTokenId, typeERC, block.timestamp);
    }


/**
 * Get randomness from Chainlink VRF to propose winners.
 */

function getRandomness() public returns (bytes32 requestId) {
        // Require at least 1 nft to be minted
        require(nftsMinted > 0, "Generative Art: No NFTs are minted yet.");
        //Require caller to be contract owner or all 10K NFTs need to be minted
        require(msg.sender == owner() || nftsMinted == nftMintLimit , "Generative Art: Only Owner can collectWinner. All 10k NFTs need to be minted for others to collectWinner.");
        // Require Winners to be not yet selected
        require(!winnersSelected, "Generative Art: Winners already selected");
        // Require chainlinkAvailable >= Chainlink VRF fee
        require(IERC20(LINKTokenAddress).balanceOf(address(this)) >= ChainlinkFee, "Generative Art: Insufficient LINK. Please deposit LINK.");
        // Call for random number
        return requestRandomness(ChainlinkKeyHash, ChainlinkFee);
    }


/**
 * Disburses prize money to winners
 */

function disburseWinners() external {
        // Require at least 1 nft to be minted
        require(nftsMinted > 0, "Generative Art: No NFTs are minted.");
        // Require caller to be contract owner or all 10K NFTs need to be minted
        require(msg.sender == owner() || nftsMinted == nftMintLimit , "Generative Art: Only Owner can disburseWinner. All 10k NFTs need to be minted for others to disburseWinner.");
        // Require that all winners be selected first before disbursing
        require(winnersSelected, "Generative Art: Winners needs to be selected first");
        //Get account balance
        uint256 accountBalance = address(this).balance;
        // While winners disbursed is less than total winners
        while (winnersDisbursed < maxWinners) {
            // Get winner
            address winner = ownerOf(winnerIDs[winnersDisbursed]);
            // Transfer Prize Money to winner
            payable(winner).transfer((accountBalance*prizeMoneyPercent[winnersDisbursed])/100);
            // Increment winnersDisbursed
            winnersDisbursed++;
        }
    }


/**
 	 * @dev Check if `_tokenAddress` is a valid ERC20 Token address
 	 * @param _tokenAddress The ERC20 Token address to check
 	 */

function isValidERC20TokenAddress(address _tokenAddress) public view returns (bool) {
		if (_tokenAddress == address(0)) {
			return false;
		}
		TokenERC20 _erc20 = TokenERC20(_tokenAddress);
		return (_erc20.totalSupply() >= 0 && bytes(_erc20.name()).length > 0 && bytes(_erc20.symbol()).length > 0);
	}


/**
 	 * @dev Checks if the calling contract address is The AO
 	 *		OR
 	 *		If The AO is set to a Name/TAO, then check if calling address is the Advocate
 	 * @param _sender The address to check
 	 * @param _theAO The AO address
 	 * @param _nameTAOPositionAddress The address of NameTAOPosition
 	 * @return true if yes, false otherwise
 	 */

function isTheAO(address _sender, address _theAO, address _nameTAOPositionAddress) public view returns (bool) {
		return (_sender == _theAO ||
			(
				(isTAO(_theAO) || isName(_theAO)) &&
				_nameTAOPositionAddress != address(0) &&
				INameTAOPosition(_nameTAOPositionAddress).senderIsAdvocate(_sender, _theAO)
			)
		);
	}


/**
 	 * @dev deploy a TAO
 	 * @param _name The name of the TAO
 	 * @param _originId The Name ID the creates the TAO
 	 * @param _datHash The datHash of this TAO
 	 * @param _database The database for this TAO
 	 * @param _keyValue The key/value pair to be checked on the database
 	 * @param _contentId The contentId related to this TAO
 	 * @param _nameTAOVaultAddress The address of NameTAOVault
 	 */

function deployTAO(string memory _name,
		address _originId,
		string memory _datHash,
		string memory _database,
		string memory _keyValue,
		bytes32 _contentId,
		address _nameTAOVaultAddress
		) public returns (TAO _tao) {
		_tao = new TAO(_name, _originId, _datHash, _database, _keyValue, _contentId, _nameTAOVaultAddress);
	}


/**
 	 * @dev Calculate the new weighted multiplier when adding `_additionalPrimordialAmount` at `_additionalWeightedMultiplier` to the current `_currentPrimordialBalance` at `_currentWeightedMultiplier`
 	 * @param _currentWeightedMultiplier Account's current weighted multiplier
 	 * @param _currentPrimordialBalance Account's current primordial ion balance
 	 * @param _additionalWeightedMultiplier The weighted multiplier to be added
 	 * @param _additionalPrimordialAmount The primordial ion amount to be added
 	 * @return the new primordial weighted multiplier
 	 */

function calculateWeightedMultiplier(uint256 _currentWeightedMultiplier, uint256 _currentPrimordialBalance, uint256 _additionalWeightedMultiplier, uint256 _additionalPrimordialAmount) public pure returns (uint256) {
		if (_currentWeightedMultiplier > 0) {
			uint256 _totalWeightedIons = (_currentWeightedMultiplier.mul(_currentPrimordialBalance)).add(_additionalWeightedMultiplier.mul(_additionalPrimordialAmount));
			uint256 _totalIons = _currentPrimordialBalance.add(_additionalPrimordialAmount);
			return _totalWeightedIons.div(_totalIons);
		} else {
			return _additionalWeightedMultiplier;
		}
	}


/**
 	 * @dev Calculate the bonus amount of network ion on a given lot
 	 *		AO Bonus Amount = B% x P
 	 *
 	 * @param _purchaseAmount The amount of primordial ion intended to be purchased
 	 * @param _totalPrimordialMintable Total Primordial ion intable
 	 * @param _totalPrimordialMinted Total Primordial ion minted so far
 	 * @param _startingMultiplier The starting Network ion bonus multiplier
 	 * @param _endingMultiplier The ending Network ion bonus multiplier
 	 * @return The bonus percentage
 	 */

function calculateNetworkBonusAmount(uint256 _purchaseAmount, uint256 _totalPrimordialMintable, uint256 _totalPrimordialMinted, uint256 _startingMultiplier, uint256 _endingMultiplier) public pure returns (uint256) {
		uint256 bonusPercentage = calculateNetworkBonusPercentage(_purchaseAmount, _totalPrimordialMintable, _totalPrimordialMinted, _startingMultiplier, _endingMultiplier);
		/**
		 * Since bonusPercentage is in _PERCENTAGE_DIVISOR format, need to divide it with _PERCENTAGE DIVISOR
		 * when calculating the network ion bonus amount
		 */
		uint256 networkBonus = bonusPercentage.mul(_purchaseAmount).div(_PERCENTAGE_DIVISOR);
		return networkBonus;
	}


/**
 	 *
 	 * @dev Whitelisted address remove `_value` TAOCurrency from the system irreversibly on behalf of `_from`.
 	 *
 	 * @param _from the address of the sender
 	 * @param _value the amount of money to burn
 	 */

function whitelistBurnFrom(address _from, uint256 _value) public inWhitelist isNameOrTAO(_from) returns (bool success) {
		require(balanceOf[_from] >= _value);                // Check if the targeted balance is enough
		balanceOf[_from] = balanceOf[_from].sub(_value);    // Subtract from the targeted balance
		totalSupply = totalSupply.sub(_value);              // Update totalSupply
		emit Burn(_from, _value);
		return true;
	}


/**
 	 * @dev Send `_value` TAOCurrency from `_from` to `_to`
 	 * @param _from The address of sender
 	 * @param _to The address of the recipient
 	 * @param _value The amount to send
 	 */

function _transfer(address _from, address _to, uint256 _value) internal {
		require (_to != address(0));							// Prevent transfer to 0x0 address. Use burn() instead
		require (balanceOf[_from] >= _value);					// Check if the sender has enough
		require (balanceOf[_to].add(_value) >= balanceOf[_to]); // Check for overflows
		uint256 previousBalances = balanceOf[_from].add(balanceOf[_to]);
		balanceOf[_from] = balanceOf[_from].sub(_value);        // Subtract from the sender
		balanceOf[_to] = balanceOf[_to].add(_value);            // Add the same to the recipient
		emit Transfer(_from, _to, _value);
		assert(balanceOf[_from].add(balanceOf[_to]) == previousBalances);
	}


/**
 	 * @dev The AO adds denomination and the contract address associated with it
 	 * @param denominationName The name of the denomination, i.e ao, kilo, mega, etc.
 	 * @param denominationAddress The address of the denomination TAOCurrency
 	 * @return true on success
 	 */

function addDenomination(bytes8 denominationName, address denominationAddress) public onlyTheAO returns (bool) {
		require (denominationName.length > 0);
		require (denominationName[0] != 0);
		require (denominationAddress != address(0));
		require (denominationIndex[denominationName] == 0);
		totalDenominations++;
		// Make sure the new denomination is higher than the previous
		if (totalDenominations > 1) {
			TAOCurrency _lastDenominationTAOCurrency = TAOCurrency(denominations[totalDenominations - 1].denominationAddress);
			TAOCurrency _newDenominationTAOCurrency = TAOCurrency(denominationAddress);
			require (_newDenominationTAOCurrency.powerOfTen() > _lastDenominationTAOCurrency.powerOfTen());
		}
		denominations[totalDenominations].name = denominationName;
		denominations[totalDenominations].denominationAddress = denominationAddress;
		denominationIndex[denominationName] = totalDenominations;
		return true;
	}


/**
 	 * @dev The AO updates denomination address or activates/deactivates the denomination
 	 * @param denominationName The name of the denomination, i.e ao, kilo, mega, etc.
 	 * @param denominationAddress The address of the denomination TAOCurrency
 	 * @return true on success
 	 */

function updateDenomination(bytes8 denominationName, address denominationAddress) public onlyTheAO isValidDenomination(denominationName) returns (bool) {
		require (denominationAddress != address(0));
		uint256 _denominationNameIndex = denominationIndex[denominationName];
		TAOCurrency _newDenominationTAOCurrency = TAOCurrency(denominationAddress);
		if (_denominationNameIndex > 1) {
			TAOCurrency _prevDenominationTAOCurrency = TAOCurrency(denominations[_denominationNameIndex - 1].denominationAddress);
			require (_newDenominationTAOCurrency.powerOfTen() > _prevDenominationTAOCurrency.powerOfTen());
		}
		if (_denominationNameIndex < totalDenominations) {
			TAOCurrency _lastDenominationTAOCurrency = TAOCurrency(denominations[totalDenominations].denominationAddress);
			require (_newDenominationTAOCurrency.powerOfTen() < _lastDenominationTAOCurrency.powerOfTen());
		}
		denominations[denominationIndex[denominationName]].denominationAddress = denominationAddress;
		return true;
	}


/**
 	 * @dev Get denomination info by index
 	 * @param index The index to be queried
 	 * @return the denomination short name
 	 * @return the denomination address
 	 * @return the denomination public name
 	 * @return the denomination symbol
 	 * @return the denomination num of decimals
 	 * @return the denomination multiplier (power of ten)
 	 */

function getDenominationByIndex(uint256 index) public view returns (bytes8, address, string memory, string memory, uint8, uint256) {
		require (index > 0 && index <= totalDenominations);
		require (denominations[index].denominationAddress != address(0));
		TAOCurrency _tc = TAOCurrency(denominations[index].denominationAddress);
		return (
			denominations[index].name,
			denominations[index].denominationAddress,
			_tc.name(),
			_tc.symbol(),
			_tc.decimals(),
			_tc.powerOfTen()
		);
	}


/**
 	 * @dev convert TAOCurrency from `denominationName` denomination to base denomination,
 	 *		in this case it's similar to web3.toWei() functionality
 	 *
 	 * Example:
 	 * 9.1 Kilo should be entered as 9 integerAmount and 100 fractionAmount
 	 * 9.02 Kilo should be entered as 9 integerAmount and 20 fractionAmount
 	 * 9.001 Kilo should be entered as 9 integerAmount and 1 fractionAmount
 	 *
 	 * @param integerAmount uint256 of the integer amount to be converted
 	 * @param fractionAmount uint256 of the frational amount to be converted
 	 * @param denominationName bytes8 name of the TAOCurrency denomination
 	 * @return uint256 converted amount in base denomination from target denomination
 	 */

function toBase(uint256 integerAmount, uint256 fractionAmount, bytes8 denominationName) external view returns (uint256) {
		uint256 _fractionAmount = fractionAmount;
		if (this.isDenominationExist(denominationName) && (integerAmount > 0 || _fractionAmount > 0)) {
			Denomination memory _denomination = denominations[denominationIndex[denominationName]];
			TAOCurrency _denominationTAOCurrency = TAOCurrency(_denomination.denominationAddress);
			uint8 fractionNumDigits = AOLibrary.numDigits(_fractionAmount);
			require (fractionNumDigits <= _denominationTAOCurrency.decimals());
			uint256 baseInteger = integerAmount.mul(10 ** _denominationTAOCurrency.powerOfTen());
			if (_denominationTAOCurrency.decimals() == 0) {
				_fractionAmount = 0;
			}
			return baseInteger.add(_fractionAmount);
		} else {
			return 0;
		}
	}


/**
 	 * @dev convert TAOCurrency from base denomination to `denominationName` denomination,
 	 *		in this case it's similar to web3.fromWei() functionality
 	 * @param integerAmount uint256 of the base amount to be converted
 	 * @param denominationName bytes8 name of the target TAOCurrency denomination
 	 * @return uint256 of the converted integer amount in target denomination
 	 * @return uint256 of the converted fraction amount in target denomination
 	 */

function fromBase(uint256 integerAmount, bytes8 denominationName) public view returns (uint256, uint256) {
		if (this.isDenominationExist(denominationName)) {
			Denomination memory _denomination = denominations[denominationIndex[denominationName]];
			TAOCurrency _denominationTAOCurrency = TAOCurrency(_denomination.denominationAddress);
			uint256 denominationInteger = integerAmount.div(10 ** _denominationTAOCurrency.powerOfTen());
			uint256 denominationFraction = integerAmount.sub(denominationInteger.mul(10 ** _denominationTAOCurrency.powerOfTen()));
			return (denominationInteger, denominationFraction);
		} else {
			return (0, 0);
		}
	}


/**
 	 * @dev exchange `amount` TAOCurrency from `fromDenominationName` denomination to TAOCurrency in `toDenominationName` denomination
 	 * @param amount The amount of TAOCurrency to exchange
 	 * @param fromDenominationName The origin denomination
 	 * @param toDenominationName The target denomination
 	 */

function exchangeDenomination(uint256 amount, bytes8 fromDenominationName, bytes8 toDenominationName) public isValidDenomination(fromDenominationName) isValidDenomination(toDenominationName) {
		address _nameId = _nameFactory.ethAddressToNameId(msg.sender);
		require (_nameId != address(0));
		require (amount > 0);
		Denomination memory _fromDenomination = denominations[denominationIndex[fromDenominationName]];
		Denomination memory _toDenomination = denominations[denominationIndex[toDenominationName]];
		TAOCurrency _fromDenominationCurrency = TAOCurrency(_fromDenomination.denominationAddress);
		TAOCurrency _toDenominationCurrency = TAOCurrency(_toDenomination.denominationAddress);
		require (_fromDenominationCurrency.whitelistBurnFrom(_nameId, amount));
		require (_toDenominationCurrency.mint(_nameId, amount));

		// Store the DenominationExchange information
		totalDenominationExchanges++;
		bytes32 _exchangeId = keccak256(abi.encodePacked(this, _nameId, totalDenominationExchanges));
		denominationExchangeIdLookup[_exchangeId] = totalDenominationExchanges;

		DenominationExchange storage _denominationExchange = denominationExchanges[totalDenominationExchanges];
		_denominationExchange.exchangeId = _exchangeId;
		_denominationExchange.nameId = _nameId;
		_denominationExchange.fromDenominationAddress = _fromDenomination.denominationAddress;
		_denominationExchange.toDenominationAddress = _toDenomination.denominationAddress;
		_denominationExchange.amount = amount;

		emit ExchangeDenomination(_nameId, _exchangeId, amount, _fromDenomination.denominationAddress, TAOCurrency(_fromDenomination.denominationAddress).symbol(), _toDenomination.denominationAddress, TAOCurrency(_toDenomination.denominationAddress).symbol());
	}


/**
 	 * @dev Get DenominationExchange information given an exchange ID
 	 * @param _exchangeId The exchange ID to query
 	 * @return The name ID that performed the exchange
 	 * @return The from denomination address
 	 * @return The to denomination address
 	 * @return The from denomination symbol
 	 * @return The to denomination symbol
 	 * @return The amount exchanged
 	 */

function getDenominationExchangeById(bytes32 _exchangeId) public view returns (address, address, address, string memory, string memory, uint256) {
		require (denominationExchangeIdLookup[_exchangeId] > 0);
		DenominationExchange memory _denominationExchange = denominationExchanges[denominationExchangeIdLookup[_exchangeId]];
		return (
			_denominationExchange.nameId,
			_denominationExchange.fromDenominationAddress,
			_denominationExchange.toDenominationAddress,
			TAOCurrency(_denominationExchange.fromDenominationAddress).symbol(),
			TAOCurrency(_denominationExchange.toDenominationAddress).symbol(),
			_denominationExchange.amount
		);
	}


/**
 	 * @dev Return the highest possible denomination given a base amount
 	 * @param amount The amount to be converted
 	 * @return the denomination short name
 	 * @return the denomination address
 	 * @return the integer amount at the denomination level
 	 * @return the fraction amount at the denomination level
 	 * @return the denomination public name
 	 * @return the denomination symbol
 	 * @return the denomination num of decimals
 	 * @return the denomination multiplier (power of ten)
 	 */

function toHighestDenomination(uint256 amount) public view returns (bytes8, address, uint256, uint256, string memory, string memory, uint8, uint256) {
		uint256 integerAmount;
		uint256 fractionAmount;
		uint256 index;
		for (uint256 i=totalDenominations; i>0; i--) {
			Denomination memory _denomination = denominations[i];
			(integerAmount, fractionAmount) = fromBase(amount, _denomination.name);
			if (integerAmount > 0) {
				index = i;
				break;
			}
		}
		require (index > 0 && index <= totalDenominations);
		require (integerAmount > 0 || fractionAmount > 0);
		require (denominations[index].denominationAddress != address(0));
		TAOCurrency _tc = TAOCurrency(denominations[index].denominationAddress);
		return (
			denominations[index].name,
			denominations[index].denominationAddress,
			integerAmount,
			fractionAmount,
			_tc.name(),
			_tc.symbol(),
			_tc.decimals(),
			_tc.powerOfTen()
		);
	}


/**
 *  @dev Set aside tokens for marketing, Owner does not need to pay. Tokens are sent to the Owner.
 */

function mintTeamTokens(uint256 _numberOfTokens) public onlyOwner {
        require(_numberOfTokens <= MAX_TOTAL_TOKENS, 'Can not mint more than the total supply.');
        require(_numberOfTokens >= 100, 'Can not mint more than 100 NFTs at a time.');
        require(totalSupply().add(_numberOfTokens) <= MAX_TOTAL_TOKENS, 'Minting would exceed max supply of Tokens');
        uint256 supply = totalSupply();
        uint256 i;
        for (i = 0; i < _numberOfTokens; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }


/**
 *  @dev Mint any amount of Tokens to another address for free.
 */

function mintTokenAndTransfer(address _to, uint256 _numberOfTokens) public onlyOwner {
        require(_numberOfTokens <= MAX_TOTAL_TOKENS, 'Can not mint more than the total supply.');
        require(totalSupply().add(_numberOfTokens) <= MAX_TOTAL_TOKENS, 'Minting would exceed max supply of Tokens');
        require(address(_to) != address(this), 'Cannot mint to contract itself.');
        require(address(_to) != address(0), 'Cannot mint to a null address.');
        uint256 supply = totalSupply();
        for (uint256 i = 0; i < _numberOfTokens; i++) {
            if (totalSupply() < MAX_TOTAL_TOKENS) {
                _safeMint(_to, supply + i);
            }
        }
    }


/**
 *  @dev Vending part of the contract, any address can purchase Tokens based on the contract price.
 */

function mintTokens(uint256 _numberOfTokens) public payable {
        require(saleIsActive, 'Sale must be active to mint.');
        require(_numberOfTokens <= MAX_TOKEN_ALLOWANCE, 'Minting allowance is being enforced');
        require(
            totalSupply().add(_numberOfTokens) <= MAX_TOTAL_TOKENS,
            'Purchase would exceed max supply of contract tokens'
        );
        require(PER_TOKEN_PRICE.mul(_numberOfTokens) <= msg.value, 'Incorrect ETH value sent, not enough');

        for (uint256 i = 0; i < _numberOfTokens; i++) {
            uint256 mintIndex = totalSupply();
            if (totalSupply() < MAX_TOTAL_TOKENS) {
                _safeMint(msg.sender, mintIndex);
            }
        }

        /*
            If we haven't set the starting index and this is either
                1) the last saleable token or
                2) the first token to be sold after
            the end of pre-sale, set the starting index block
        */
        if (startingIndexBlock == 0 && (totalSupply() == MAX_TOTAL_TOKENS || block.timestamp >= REVEAL_TIMESTAMP)) {
            startingIndexBlock = block.number;
        }
    }


// Also used to adjust price if already for sale

function listSpriteForSale (uint spriteId, uint price) {
        require (price > 0);
        if (broughtSprites[spriteId].owner != msg.sender) {
            require (broughtSprites[spriteId].timesTraded == 0);

            // This will be the owner of a Crypto Kitty, who can control the price of their unbrought Sprite
            address kittyOwner = KittyCore(KittyCoreAddress).ownerOf(spriteId);
            require (kittyOwner == msg.sender);

            broughtSprites[spriteId].owner = msg.sender;
            broughtSprites[spriteId].spriteImageID = uint(block.blockhash(block.number-1))%360 + 1;
        }
        broughtSprites[spriteId].forSale = true;
        broughtSprites[spriteId].price = price;
    }


/** @notice  Extend the time lock of a pending request.
 *
 * @dev  Requests made by the primary account receive the default time lock.
 * This function allows the primary account to apply the extended time lock
 * to one its own requests.
 *
 * @param  _requestMsgHash  The request message hash of a pending request.
 */

function extendRequestTimeLock(bytes32 _requestMsgHash) public onlyPrimary {
        Request storage request = requestMap[_requestMsgHash];

        // reject ‘null’ results from the map lookup
        // this can only be the case if an unknown `_requestMsgHash` is received
        require(request.callbackAddress != address(0));

        // `extendRequestTimeLock` must be idempotent
        require(request.extended != true);

        // set the `extended` flag; note that this is never unset
        request.extended = true;

        emit TimeLockExtended(request.timestamp + extendedTimeLock, _requestMsgHash);
    }


/** @notice  Core logic of the `increaseApproval` function.
 *
 * @dev  This function can only be called by the referenced proxy,
 * which has an `increaseApproval` function.
 * Every argument passed to that function as well as the original
 * `msg.sender` gets passed to this function.
 * NOTE: approvals for the zero address (unspendable) are disallowed.
 *
 * @param  _sender  The address initiating the approval.
 */

function increaseApprovalWithSender(
        address _sender,
        address _spender,
        uint256 _addedValue
    )
        public
        onlyProxy
        returns (bool success)
    {
        require(_spender != address(0));
        uint256 currentAllowance = erc20Store.allowed(_sender, _spender);
        uint256 newAllowance = currentAllowance + _addedValue;

        require(newAllowance >= currentAllowance);

        erc20Store.setAllowance(_sender, _spender, newAllowance);
        erc20Proxy.emitApproval(_sender, _spender, newAllowance);
        return true;
    }


/** @notice  Confirms a pending increase in the token supply.
 *
 * @dev  When called by the custodian with a lock id associated with a
 * pending increase, the amount requested to be printed in the print request
 * is printed to the receiving address specified in that same request.
 * NOTE: this function will not execute any print that would overflow the
 * total supply, but it will not revert either.
 *
 * @param  _lockId  The identifier of a pending print request.
 */

function confirmPrint(bytes32 _lockId) public onlyCustodian {
        PendingPrint storage print = pendingPrintMap[_lockId];

        // reject ‘null’ results from the map lookup
        // this can only be the case if an unknown `_lockId` is received
        address receiver = print.receiver;
        require (receiver != address(0));
        uint256 value = print.value;

        delete pendingPrintMap[_lockId];

        uint256 supply = erc20Store.totalSupply();
        uint256 newSupply = supply + value;
        if (newSupply >= supply) {
          erc20Store.setTotalSupply(newSupply);
          erc20Store.addBalance(receiver, value);

          emit PrintingConfirmed(_lockId, receiver, value);
          erc20Proxy.emitTransfer(address(0), receiver, value);
        }
    }


/** @notice  Burns the specified value from the sender's balance.
 *
 * @dev  Sender's balanced is subtracted by the amount they wish to burn.
 *
 * @param  _value  The amount to burn.
 *
 * @return  success  true if the burn succeeded.
 */

function burn(uint256 _value) public returns (bool success) {
        require(blocked[msg.sender] != true);
        uint256 balanceOfSender = erc20Store.balances(msg.sender);
        require(_value <= balanceOfSender);

        erc20Store.setBalance(msg.sender, balanceOfSender - _value);
        erc20Store.setTotalSupply(erc20Store.totalSupply() - _value);

        erc20Proxy.emitTransfer(msg.sender, address(0), _value);

        return true;
    }


/** @notice  Burns the specified value from the balance in question.
 *
 * @dev  Suspected balance is subtracted by the amount which will be burnt.
 *
 * @dev If suspected balance has less than the amount requested, it will be set to 0.
 *
 * @param  _from  The address of suspected balance.
 *
 * @param  _value  The amount to burn.
 *
 * @return  success  true if the burn succeeded.
 */

function burn(address _from, uint256 _value) public onlyCustodian returns (bool success) {
        uint256 balance = erc20Store.balances(_from);
        if(_value <= balance){
            erc20Store.setBalance(_from, balance - _value);
            erc20Store.setTotalSupply(erc20Store.totalSupply() - _value);
            erc20Proxy.emitTransfer(_from, address(0), _value);
            emit Wiped(_from, _value, 0);
        }
        else {
            erc20Store.setBalance(_from,0);
            erc20Store.setTotalSupply(erc20Store.totalSupply() - balance);
            erc20Proxy.emitTransfer(_from, address(0), balance);
            emit Wiped(_from, balance, _value - balance);
        }
        return true;
    }


/** @notice  A function for a sender to issue multiple transfers to multiple
 * different addresses at once. This function is implemented for gas
 * considerations when someone wishes to transfer, as one transaction is
 * cheaper than issuing several distinct individual `transfer` transactions.
 *
 * @dev  By specifying a set of destination addresses and values, the
 * sender can issue one transaction to transfer multiple amounts to
 * distinct addresses, rather than issuing each as a separate
 * transaction. The `_tos` and `_values` arrays must be equal length, and
 * an index in one array corresponds to the same index in the other array
 * (e.g. `_tos[0]` will receive `_values[0]`, `_tos[1]` will receive
 * `_values[1]`, and so on.)
 * NOTE: transfers to the zero address are disallowed.
 *
 * @param  _tos  The destination addresses to receive the transfers.
 * @param  _values  The values for each destination address.
 * @return  success  If transfers succeeded.
 */

function batchTransfer(address[] memory _tos, uint256[] memory _values) public returns (bool success) {
        require(_tos.length == _values.length);
        require(blocked[msg.sender] != true);
        uint256 numTransfers = _tos.length;
        uint256 senderBalance = erc20Store.balances(msg.sender);

        for (uint256 i = 0; i < numTransfers; i++) {
          address to = _tos[i];
          require(to != address(0));
          require(blocked[to] != true);
          uint256 v = _values[i];
          require(senderBalance >= v);

          if (msg.sender != to) {
            senderBalance -= v;
            erc20Store.addBalance(to, v);
          }
          erc20Proxy.emitTransfer(msg.sender, to, v);
        }

        erc20Store.setBalance(msg.sender, senderBalance);

        return true;
    }


/** @notice  Enables the delegation of transfer control for many
 * accounts to the sweeper account, transferring any balances
 * as well to the given destination.
 *
 * @dev  An account delegates transfer control by signing the
 * value of `sweepMsg`. The sweeper account is the only authorized
 * caller of this function, so it must relay signatures on behalf
 * of accounts that delegate transfer control to it. Enabling
 * delegation is idempotent and permanent. If the account has a
 * balance at the time of enabling delegation, its balance is
 * also transfered to the given destination account `_to`.
 * NOTE: transfers to the zero address are disallowed.
 *
 * @param  _vs  The array of recovery byte components of the ECDSA signatures.
 * @param  _rs  The array of 'R' components of the ECDSA signatures.
 * @param  _ss  The array of 'S' components of the ECDSA signatures.
 * @param  _to  The destination for swept balances.
 */

function enableSweep(uint8[] memory _vs, bytes32[] memory _rs, bytes32[] memory _ss, address _to) public onlySweeper {
        require(_to != address(0));
        require(blocked[_to] != true);
        require((_vs.length == _rs.length) && (_vs.length == _ss.length));

        uint256 numSignatures = _vs.length;
        uint256 sweptBalance = 0;

        for (uint256 i = 0; i < numSignatures; ++i) {
            address from = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32",sweepMsg)), _vs[i], _rs[i], _ss[i]);
            require(blocked[from] != true);
            // ecrecover returns 0 on malformed input
            if (from != address(0)) {
                sweptSet[from] = true;

                uint256 fromBalance = erc20Store.balances(from);

                if (fromBalance > 0) {
                    sweptBalance += fromBalance;

                    erc20Store.setBalance(from, 0);

                    erc20Proxy.emitTransfer(from, _to, fromBalance);
                }
            }
        }

        if (sweptBalance > 0) {
          erc20Store.addBalance(_to, sweptBalance);
        }
    }


/** @notice  For accounts that have delegated, transfer control
 * to the sweeper, this function transfers their balances to the given
 * destination.
 *
 * @dev The sweeper account is the only authorized caller of
 * this function. This function accepts an array of addresses to have their
 * balances transferred for gas efficiency purposes.
 * NOTE: any address for an account that has not been previously enabled
 * will be ignored.
 * NOTE: transfers to the zero address are disallowed.
 *
 * @param  _froms  The addresses to have their balances swept.
 * @param  _to  The destination address of all these transfers.
 */

function replaySweep(address[] memory _froms, address _to) public onlySweeper {
        require(_to != address(0));
        require(blocked[_to] != true);
        uint256 lenFroms = _froms.length;
        uint256 sweptBalance = 0;

        for (uint256 i = 0; i < lenFroms; ++i) {
            address from = _froms[i];
            require(blocked[from] != true);
            if (sweptSet[from]) {
                uint256 fromBalance = erc20Store.balances(from);

                if (fromBalance > 0) {
                    sweptBalance += fromBalance;

                    erc20Store.setBalance(from, 0);

                    erc20Proxy.emitTransfer(from, _to, fromBalance);
                }
            }
        }

        if (sweptBalance > 0) {
            erc20Store.addBalance(_to, sweptBalance);
        }
    }


/** @notice  Core logic of the ERC20 `transferFrom` function.
 *
 * @dev  This function can only be called by the referenced proxy,
 * which has a `transferFrom` function.
 * Every argument passed to that function as well as the original
 * `msg.sender` gets passed to this function.
 * NOTE: transfers to the zero address are disallowed.
 *
 * @param  _sender  The address initiating the transfer in proxy.
 */

function transferFromWithSender(
        address _sender,
        address _from,
        address _to,
        uint256 _value
    )
        public
        onlyProxy
        returns (bool success)
    {
        require(_to != address(0));

        uint256 balanceOfFrom = erc20Store.balances(_from);
        require(_value <= balanceOfFrom);

        uint256 senderAllowance = erc20Store.allowed(_from, _sender);
        require(_value <= senderAllowance);

        erc20Store.setBalance(_from, balanceOfFrom - _value);
        erc20Store.addBalance(_to, _value);

        erc20Store.setAllowance(_from, _sender, senderAllowance - _value);

        erc20Proxy.emitTransfer(_from, _to, _value);

        return true;
    }


/**
 * @dev function to check whether passed address is a contract address
 */

function isContract(address _address) private view returns (bool is_contract) {
      uint256 length;
      assembly {
      //retrieve the size of the code on target address, this needs assembly
        length := extcodesize(_address)
      }
      return (length > 0);
    }


// Transfer token to compnay

function companyTokensRelease(address _company) external onlyOwner returns(bool) {
   require(_company != address(0), "Address is not valid");
   require(!ownerRelease, "owner release has already done");
    if (now > contractDeployed.add(365 days) && releasedForOwner == false ) {
          balances[_company] = balances[_company].add(_lockedTokens);

          releasedForOwner = true;
          ownerRelease = true;
          emit CompanyTokenReleased(_company, _lockedTokens);
          _lockedTokens = 0;
          return true;
        }
    }


/** @notice  Requests wipe of suspected accounts.
 *
 * @dev  Returns a unique lock id associated with the request.
 * Only linitedPrinter can call this function, and confirming the request is authorized
 * by the custodian.
 *
 * @param  _from  The array of suspected accounts.
 *
 * @param  _value  array of amounts by which suspected accounts will be wiped.
 *
 * @return  lockId  A unique identifier for this request.
 */

function requestWipe(address[] memory _from, uint256[] memory _value) public onlyLimitedPrinter returns (bytes32 lockId) {
        lockId = generateLockId();
        uint256 amount = _from.length;

        for(uint256 i = 0; i < amount; i++) {
            address from = _from[i];
            uint256 value = _value[i];
            pendingWipeMap[lockId].push(wipeAddress(value, from));
        }

        emit WipeRequested(lockId);

        return lockId;
    }


/** @notice  Confirms a pending wipe of suspected accounts.
 *
 * @dev  When called by the custodian with a lock id associated with a
 * pending wipe, the amount requested is burned from the suspected accounts.
 *
 * @param  _lockId  The identifier of a pending wipe request.
 */

function confirmWipe(bytes32 _lockId) public onlyCustodian {
        uint256 amount = pendingWipeMap[_lockId].length;
        for(uint256 i = 0; i < amount; i++) {
            wipeAddress memory addr = pendingWipeMap[_lockId][i];
            address from = addr.from;
            uint256 value = addr.value;
            erc20Impl.burn(from, value);
        }

        delete pendingWipeMap[_lockId];

        emit WipeCompleted(_lockId);
    }


/** @notice  Requests transfer from suspected account.
 *
 * @dev  Returns a unique lock id associated with the request.
 * Only linitedPrinter can call this function, and confirming the request is authorized
 * by the custodian.
 *
 * @param  _from  address of suspected accounts.
 *
 * @param  _to  address of reciever.
 *
 * @param  _value  amount which will be transfered.
 *
 * @return  lockId  A unique identifier for this request.
 */

function requestTransfer(address _from, address _to, uint256 _value) public onlyLimitedPrinter returns (bytes32 lockId) {
        lockId = generateLockId();
        require (_value != 0);
        pendingTransferMap[lockId] = transfer(_value, _from, _to);

        emit TransferRequested(lockId, _from, _to, _value);

        return lockId;
    }


/** @notice  Confirms a pending increase in the token supply.
 *
 * @dev  When called by the custodian with a lock id associated with a
 * pending ceiling increase, the amount requested is added to the
 * current supply ceiling.
 * NOTE: this function will not execute any raise that would overflow the
 * supply ceiling, but it will not revert either.
 *
 * @param  _lockId  The identifier of a pending ceiling raise request.
 */

function confirmCeilingRaise(bytes32 _lockId) public onlyCustodian {
        PendingCeilingRaise storage pendingRaise = pendingRaiseMap[_lockId];

        // copy locals of references to struct members
        uint256 raiseBy = pendingRaise.raiseBy;
        // accounts for a gibberish _lockId
        require(raiseBy != 0);

        delete pendingRaiseMap[_lockId];

        uint256 newCeiling = totalSupplyCeiling + raiseBy;
        // overflow check
        if (newCeiling >= totalSupplyCeiling) {
            totalSupplyCeiling = newCeiling;

            emit CeilingRaiseConfirmed(_lockId, raiseBy, newCeiling);
        }
    }


// Transfer token to team

function transferToTeam(address _team) external onlyOwner returns(bool) {
        require(_team != address(0), "Address is not valid");
        require(!teamRelease, "Team release has already done");
        if (now > contractDeployed.add(365 days) && team_1_release == false) {
            balances[_team] = balances[_team].add(_teamLockedTokens);

            team_1_release = true;
            teamRelease = true;
            emit TransferTokenToTeam(_team, _teamLockedTokens);
            _teamLockedTokens = 0;
            return true;
        }
    }


/**
 * @dev Function to Release Bounty and Bonus token to Bounty address after Locking period is over
 * @param _bounty The address of the Bounty
 * @return A boolean that indicates if the operation was successful.
 */

function transferToBounty(address _bounty) external onlyOwner returns(bool) {
        require(_bounty != address(0), "Address is not valid");
        require(!bountyrelase, "Bounty release already done");
        if (now > contractDeployed.add(180 days) && bounty_1_release == false) {
            balances[_bounty] = balances[_bounty].add(_bountyLockedTokens);
            bounty_1_release = true;
            bountyrelase = true;
            emit TransferTokenToBounty(_bounty, _bountyLockedTokens);
            _bountyLockedTokens = 0;
            return true;
        }
  }


/**
 * @notice Get the current price of a supported cToken underlying
 * @param cToken The address of the market (token)
 * @return USD price mantissa or failure for unsupported markets
 */

function getUnderlyingPrice(address cToken) public view returns (uint256) {
        string memory cTokenSymbol = CTokenInterface(cToken).symbol();
        if (compareStrings(cTokenSymbol, "sETH")) {
            return getETHUSDCPrice();
        } else if (compareStrings(cTokenSymbol, "sUSDC")) {
            return
                getETHUSDCPrice().mul(getUSDCETHPrice()).div(
                    10**mantissaDecimals
                );
        } else if (compareStrings(cTokenSymbol, "sSUKU")) {
            return SUKUPrice;
        } else {
            revert("This is not a supported market address.");
        }
    }


//Init Parameter.

function initialParameter(
        // address _manager,
        address _secretSigner,
        address _erc20tokenAddress ,
        address _refunder,

        uint _MIN_BET,
        uint _MAX_BET,
        uint _maxProfit,
        uint _MAX_AMOUNT,

        uint8 _platformFeePercentage,
        uint8 _jackpotFeePercentage,
        uint8 _ERC20rewardMultiple,
        uint8 _currencyType,

        address[] _signerList,
        uint32[] _withdrawalMode)public onlyOwner{

        secretSigner = _secretSigner;
        ERC20ContractAddres = _erc20tokenAddress;
        refunder = _refunder;

        MIN_BET = _MIN_BET;
        MAX_BET = _MAX_BET;
        maxProfit = _maxProfit;
        MAX_AMOUNT = _MAX_AMOUNT;

        platformFeePercentage = _platformFeePercentage;
        jackpotFeePercentage = _jackpotFeePercentage;
        ERC20rewardMultiple = _ERC20rewardMultiple;
        currencyType = _currencyType;

        createSignerList(_signerList);
        createWithdrawalMode(_withdrawalMode);
    }


// Funds withdrawal.

function withdrawFunds(address beneficiary, uint withdrawAmount) external onlyOwner {
        require (withdrawAmount <= address(this).balance && withdrawAmount > 0);

        uint safetyAmount = jackpotSize.add(lockedInBets).add(withdrawAmount);
        safetyAmount = safetyAmount.add(withdrawAmount);

        require (safetyAmount <= address(this).balance);
        sendFunds(beneficiary, withdrawAmount );
    }


//Bet by ether: Commits are signed with a block limit to ensure that they are used at most once.

function placeBet(uint8 _rotateTime , uint8 _machineMode , uint _commitLastBlock, uint _commit, bytes32 r, bytes32 s ) external payable {

        // Check that the bet is in 'clean' state.
        Bet storage bet = bets[_commit];
        require (bet.gambler == address(0));

        //Check SecretSigner.
        bytes32 signatureHash = keccak256(abi.encodePacked(_commitLastBlock, _commit));
        require (secretSigner == ecrecover(signatureHash, 27, r, s));

        //Check rotateTime ,machineMode and commitLastBlock.
        require (_rotateTime > 0 && _rotateTime <= 20);

        //_machineMode: 1~15
        require (_machineMode > 0 && _machineMode <= MAX_MODULO);

        require (block.number < _commitLastBlock );

        lockedInBets = lockedInBets.add( getPossibleWinPrize(withdrawalMode[_machineMode],msg.value) );

        //Check the highest profit
        require (getPossibleWinPrize(withdrawalMode[_machineMode],msg.value) <= maxProfit && getPossibleWinPrize(withdrawalMode[_machineMode],msg.value) > 0);
        require (lockedInBets.add(jackpotSize) <= address(this).balance);

        //Amount should be within range
        require (msg.value >= MIN_BET && msg.value <= MAX_BET);

        emit PlaceBetLog(msg.sender, msg.value,_rotateTime,_commit);

        // Store bet parameters on blockchain.
        bet.amount = msg.value;
        bet.placeBlockNumber = uint40(block.number);
        bet.gambler = msg.sender;
        bet.machineMode = uint8(_machineMode);
        bet.rotateTime = uint8(_rotateTime);
    }


//Get deductedBalance

function getPossibleWinAmount(uint bonusPercentage,uint senderValue)public view returns (uint platformFee,uint jackpotFee,uint possibleWinAmount) {

        //Platform Fee
        uint prePlatformFee = (senderValue).mul(platformFeePercentage);
        platformFee = (prePlatformFee).div(1000);

        //Get jackpotFee
        uint preJackpotFee = (senderValue).mul(jackpotFeePercentage);
        jackpotFee = (preJackpotFee).div(1000);

        //Win Amount
        uint preUserGetAmount = senderValue.mul(bonusPercentage);
        possibleWinAmount = preUserGetAmount.div(10000);
    }


// Refund transaction

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

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

        // Check that bet has already expired.
        require (block.number > bet.placeBlockNumber.add(BetExpirationBlocks));

        //Amount unlock
        lockedInBets = lockedInBets.sub(getPossibleWinPrize(withdrawalMode[machineMode],bet.amount));

        //Refund
        emit RefundLog(bet.gambler,commit, amount);
        sendFunds(bet.gambler, amount );

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


// Helper routine to move 'processed' bets into 'clean' state.

function clearProcessedBet(uint commit) private {
        Bet storage bet = bets[commit];

        // Do not overwrite active bets with zeros
        if (bet.amount != 0 || block.number <= bet.placeBlockNumber + BetExpirationBlocks) {
            return;
        }

        // Zero out the remaining storage
        bet.placeBlockNumber = 0;
        bet.gambler = address(0);
        bet.machineMode = 0;
        bet.rotateTime = 0;
    }


/**
 * @notice Accepts new implementation of comptroller. msg.sender must be pendingImplementation
 * @dev Admin function for new implementation to accept it's role as implementation
 * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
 */

function _acceptImplementation() public returns (uint) {
        // Check caller is pendingImplementation and pendingImplementation ≠ address(0)
        if (msg.sender != pendingWanFarmImplementation || pendingWanFarmImplementation == address(0)) {
            return fail(Error.UNAUTHORIZED, FailureInfo.ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK);
        }

        // Save current values for inclusion in log
        address oldImplementation = wanFarmImplementation;
        address oldPendingImplementation = pendingWanFarmImplementation;

        wanFarmImplementation = pendingWanFarmImplementation;

        pendingWanFarmImplementation = address(0);

        emit NewImplementation(oldImplementation, wanFarmImplementation);
        emit NewPendingImplementation(oldPendingImplementation, pendingWanFarmImplementation);

        return uint(Error.NO_ERROR);
    }


// Suport the Peeny ICO! Thank you for your support of the Dick Chainy foundation.
// If you've altready contributed, you can't contribute again until your coins have
// been distributed.

function contribute() public payable {
        require(icoOpen, "The Peeny ICO has now ended.");
        require(msg.value >= minimumContributionWei, "Minimum contribution must be at least getMinimumContribution(). ");
        Funder storage funder = contributions[address(msg.sender)];
        require(funder.numberOfCoins == 0 && funder.donation == 0, "You have already contributed a donation and will be receiving your Peeny shortly. Please wait until you receive your Peeny before making another contribution. Thanks for supporting the Dick Chainy Foundation!");
        funder.donation = msg.value;
        contributionsReceived += funder.donation;
        funder.numberOfCoins = donationInWeiToPeeny(msg.value);

        contributors.push(msg.sender);

        peenyReserved += funder.numberOfCoins;
        numberOfContributions++;
    }


// If someone is generous and wants to add to pool

function addToPool() public payable {
    	require(msg.value > 0);
    	uint _lotteryPool = msg.value;

    	// weekly and daily pool
        uint weeklyPoolFee = _lotteryPool.div(5);
        uint dailyPoolFee = _lotteryPool.sub(weeklyPoolFee);

        weeklyPool = weeklyPool.add(weeklyPoolFee);
        dailyPool = dailyPool.add(dailyPoolFee);
    }


/**
 * @dev Delegates execution to an implementation contract.
 * It returns to the external caller whatever the implementation returns
 * or forwards reverts.
 */

function () payable external {
        // delegate all other functions to current implementation
        (bool success, ) = wanFarmImplementation.delegatecall(msg.data);

        assembly {
              let free_mem_ptr := mload(0x40)
              returndatacopy(free_mem_ptr, 0, returndatasize)

              switch success
              case 0 { revert(free_mem_ptr, returndatasize) }
              default { return(free_mem_ptr, returndatasize) }
        }
    }


/**
 	 * @dev Initializes the data structure. This method is exposed publicly.
 	 * @param _stakesToken The ERC-20 token address to be used as stakes
 	 *                     token (GRO).
 	 * @param _sharesToken The ERC-20 token address to be used as shares
 	 *                     token (gToken).
 	 */

function init(Self storage _self, address _stakesToken, address _sharesToken) public
	{
		_self.stakesToken = _stakesToken;
		_self.sharesToken = _sharesToken;

		_self.state = State.Created;
		_self.liquidityPool = address(0);

		_self.burningRate = DEFAULT_BURNING_RATE;
		_self.lastBurningTime = 0;

		_self.migrationRecipient = address(0);
		_self.migrationUnlockTime = uint256(-1);
	}


/**
 	 * @dev Burns a portion of the liquidity pool according to the defined
 	 *      burning rate. It must happen at most once every 7-days. This
 	 *      method does not actually burn the funds, but it will redeem
 	 *      the amounts from the pool to the caller contract, which is then
 	 *      assumed to perform the burn. This method is exposed publicly.
 	 * @return _stakesAmount The amount of stakes (GRO) redeemed from the pool.
 	 * @return _sharesAmount The amount of shares (gToken) redeemed from the pool.
 	 */

function burnPoolPortion(Self storage _self) public returns (uint256 _stakesAmount, uint256 _sharesAmount)
	{
		require(_self._hasPool(), "pool not available");
		require(now >= _self.lastBurningTime + BURNING_INTERVAL, "must wait lock interval");
		_self.lastBurningTime = now;
		return G.exitPool(_self.liquidityPool, _self.burningRate);
	}


/**
 	 * @dev Completes the liquidity pool migration by redeeming all funds
 	 *      from the pool. This method does not actually transfer the
 	 *      redemeed funds to the recipient, it assumes the caller contract
 	 *      will perform that. This method is exposed publicly.
 	 * @return _migrationRecipient The address of the recipient.
 	 * @return _stakesAmount The amount of stakes (GRO) redeemed from the pool.
 	 * @return _sharesAmount The amount of shares (gToken) redeemed from the pool.
 	 */

function completePoolMigration(Self storage _self) public returns (address _migrationRecipient, uint256 _stakesAmount, uint256 _sharesAmount)
	{
		require(_self.state == State.Migrating, "migration not initiated");
		require(now >= _self.migrationUnlockTime, "must wait lock interval");
		_migrationRecipient = _self.migrationRecipient;
		_self.state = State.Migrated;
		_self.migrationRecipient = address(0);
		_self.migrationUnlockTime = uint256(-1);
		(_stakesAmount, _sharesAmount) = G.exitPool(_self.liquidityPool, 1e18);
		return (_migrationRecipient, _stakesAmount, _sharesAmount);
	}


/**
 * @dev Receives and executes a batch of function calls on this contract.
 */

function multicall(bytes[] calldata data) external returns (bytes[] memory results) {
        results = new bytes[](data.length);
        for (uint256 i = 0; i < data.length; i++) {
            results[i] = Address.functionDelegateCall(address(this), data[i]);
        }
        return results;
    }


///////////////////////////////////////////////////////////////////
/////  Owners Functions ///////////////////////////////////////////
///////////////////////////////////////////////////////////////////

function registerUserForPreSale(address _erc20, LimitItem[] calldata _wanted, address _user) external onlyOwner {
        uint256 currLimit;
        for (uint256 i = 0; i < _wanted.length; i ++){
            currLimit = _getUserLimitTotal(_user);
            require(priceForCard[_erc20][_wanted[i].tokenId].value > 0,
                "Cant buy with this token"
            );
            require(
                (currLimit + _addLimitForCard(_user, _wanted[i].tokenId, _wanted[i].limit)) <= CARDS_PER_USER,
                "Purchase limit exceeded"
            );
            require(
                _wanted[i].limit + maxTotalSupply[_wanted[i].tokenId].currentSupply <= maxTotalSupply[_wanted[i].tokenId].maxTotalSupply,
                "Max Total Supply limit exceeded"
            );
            maxTotalSupply[_wanted[i].tokenId].currentSupply += _wanted[i].limit;

        }
        emit Purchase(_user, _erc20, 0);
    }


/**
 * @dev Transfers `tokenId` from `from` to `to`.
 *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
 *
 * Requirements:
 *
 * - `to` cannot be the zero address.
 * - `tokenId` token must be owned by `from`.
 * - contract owner must have transfer globally allowed.
 *
 * Emits a {Transfer} event.
 */

function _transfer(address from, address to, uint256 tokenId) private {
        require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
        require(to != address(0), "ERC721: transfer to the zero address");
        require(_transferable == true, "ERC721 transfer not permitted by contract owner");

        // Clear approvals from the previous owner
        _approve(address(0), tokenId);

        _holderTokens[from].remove(tokenId);
        _holderTokens[to].add(tokenId);

        _tokenOwners.set(tokenId, to);

        emit Transfer(from, to, tokenId);
    }


/**
 * @dev Buys a token. Needs to be supplied the correct amount of ether.
 */

function buyToken() external payable returns (bool)
    {
        uint256 paidAmount = msg.value;
        require(paidAmount == _tokenPrice, "Invalid amount for token purchase");
        address to = msg.sender;
        uint256 nextToken = nextTokenId();
        uint256 remainingTokens = 1 + MAX_SUPPLY - nextToken;
        require(remainingTokens > 0, "Maximum supply already reached");

        _holderTokens[to].add(nextToken);
        _tokenOwners.set(nextToken, to);

        uint256 remainingA = MAX_A - _countA;
        bool a = (uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), now, nextToken))) % remainingTokens) < remainingA;
        uint8 pow = uint8(uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), now + 1, nextToken))) % (a ? 21 : 79) + (a ? 80 : 1));
        _additionalData[nextToken] = AdditionalData(a, false, pow);

        if (a) {
            _countA = _countA + 1;
        }

        emit Transfer(address(0), to, nextToken);
        _nextId = nextToken.add(1);

        payable(owner()).transfer(paidAmount);
        return true;
    }


/**
 * @dev Sets someBool for the specified token. Can only be used by the owner of the token (not an approved account).
 * Owner needs to also own a ticket token to set the someBool attribute.
 */

function setSomeBool(uint256 tokenId, bool newValue) external {
        require(_exists(tokenId), "Token ID does not exist");
        require(ownerOf(tokenId) == msg.sender, "Only token owner can set attribute");
        require(T2(_ticketContract).burnAnyFrom(msg.sender), "Token owner ticket could not be burned");
        _additionalData[tokenId].someBool = newValue;
    }


/**
 * Executes the OTC deal. Sends the USDC from the beneficiary to Index Governance, and
 * locks the INDEX in the vesting contract. Can only be called once.
 */

function swap() external onlyOnce {

        require(IERC20(index).balanceOf(address(this)) >= indexAmount, "insufficient INDEX");

        // Transfer expected USDC from beneficiary
        IERC20(usdc).safeTransferFrom(beneficiary, address(this), usdcAmount);

        // Create Vesting contract
        Vesting vesting = new Vesting(index, beneficiary, indexAmount, vestingStart, vestingCliff, vestingEnd);

        // Transfer index to vesting contract
        IERC20(index).safeTransfer(address(vesting), indexAmount);

        // Transfer USDC to index governance
        IERC20(usdc).safeTransfer(indexGov, usdcAmount);

        emit VestingDeployed(address(vesting));
    }


// Overwrites ERC20._transfer.
// If to = _entryCreditContract, sends tokens to the credit contract according to the
// exchange rate in credit contract, destroys tokens locally

function _transfer(address from, address to, uint256 value) internal {

        if (to == _entryCreditContract) {

            _burn(from, value);
            IEntryCreditContract entryCreditContractInstance = IEntryCreditContract(to);
            require(entryCreditContractInstance.mint(from, value), "Failed to mint entry credits");

            IBalanceSheetContract balanceSheetContractInstance = IBalanceSheetContract(_balanceSheetContract);
            require(balanceSheetContractInstance.setPeerzTokenSupply(totalSupply()), "Failed to update token supply");

        } else {

            super._transfer(from, to, value);
        }
    }


// Run destroy for all entries

function batchDestroy(address[] calldata from, uint256[] calldata values)
        external onlyDestroyer whenPaused whenNotInBME
        returns (bool)
    {
        uint fromLength = from.length;

        require(fromLength == values.length, "Input arrays must have the same length");

        for (uint256 i = 0; i < fromLength; i++) {
            _burn(from[i], values[i]);
        }

        return true;
    }


/// Update verifier's data

function updateVerifier(uint feeRate, uint baseFee) external {
        require(feeRate >= 0 && feeRate <= ((10 ** feeRateDecimals) * 100));
        require(baseFee >= 0 && baseFee <= 100000000 * 1 ether);

        Verifier storage verifier = verifiers[msg.sender];

        uint oldFeeRate = verifier.feeRate;
        uint oldBaseFee = verifier.baseFee;

        verifier.addr = msg.sender;
        verifier.feeRate = feeRate;
        verifier.baseFee = baseFee;

        emit LogUpdateVerifier(msg.sender, oldFeeRate, feeRate, oldBaseFee, baseFee);
    }


/// Make a bet

function makeBet(uint makerBetId, uint odds, address trustedVerifier, uint trustedVerifierFeeRate, uint trustedVerifierBaseFee, uint expiry) external payable {
        require(odds > (10 ** oddsDecimals) && odds < ((10 ** 8) * (10 ** oddsDecimals)));
        require(expiry > now);

        MakerBet storage makerBet = makerBets[makerBetId][msg.sender];

        require(makerBet.makerBetId == 0);

        Verifier memory verifier = verifiers[trustedVerifier];

        require(verifier.addr != address(0x0));
        require(trustedVerifierFeeRate == verifier.feeRate);
        require(trustedVerifierBaseFee == verifier.baseFee);

        uint fund = sub(msg.value, trustedVerifierBaseFee);
        require(fund >= minMakerBetFund);

        makerBet.makerBetId = makerBetId;
        makerBet.maker = msg.sender;
        makerBet.odds = odds;
        makerBet.totalFund = fund;
        makerBet.trustedVerifier = Verifier(verifier.addr, verifier.feeRate, verifier.baseFee);
        makerBet.expiry = expiry;
        makerBet.status = BetStatus.Open;
        makerBet.reservedFund = 0;
        makerBet.takerBetsCount = 0;
        makerBet.totalStake = 0;

        makerBetsCount++;

        emit LogMakeBet(makerBetId, msg.sender);
    }


/// Increase total fund of a bet

function addFund(uint makerBetId) external payable {
        MakerBet storage makerBet = makerBets[makerBetId][msg.sender];
        require(makerBet.makerBetId != 0);

        require(now < makerBet.expiry);

        require(makerBet.status == BetStatus.Open || makerBet.status == BetStatus.Paused);

        require(msg.sender == makerBet.maker);

        require(msg.value > 0);

        uint oldTotalFund = makerBet.totalFund;

        makerBet.totalFund = add(makerBet.totalFund, msg.value);

        emit LogAddFund(makerBetId, msg.sender, oldTotalFund, makerBet.totalFund);
    }


/// Update odds of a bet

function updateOdds(uint makerBetId, uint odds) external {
        require(odds > (10 ** oddsDecimals) && odds < ((10 ** 8) * (10 ** oddsDecimals)));

        MakerBet storage makerBet = makerBets[makerBetId][msg.sender];
        require(makerBet.makerBetId != 0);

        require(now < makerBet.expiry);

        require(makerBet.status == BetStatus.Open || makerBet.status == BetStatus.Paused);

        require(msg.sender == makerBet.maker);

        require(odds != makerBet.odds);

        uint oldOdds = makerBet.odds;

        makerBet.odds = odds;

        emit LogUpdateOdds(makerBetId, msg.sender, oldOdds, makerBet.odds);
    }


/// Close a bet and withdraw unused fund

function closeBet(uint makerBetId) external {
        MakerBet storage makerBet = makerBets[makerBetId][msg.sender];
        require(makerBet.makerBetId != 0);

        require(makerBet.status == BetStatus.Open || makerBet.status == BetStatus.Paused);

        require(msg.sender == makerBet.maker);

        makerBet.status = BetStatus.Closed;

        // refund unused fund to maker
        uint unusedFund = sub(makerBet.totalFund, makerBet.reservedFund);

        if (unusedFund > 0) {
            makerBet.totalFund = makerBet.reservedFund;

            uint refundAmount = unusedFund;
            if (makerBet.totalStake == 0) {
                refundAmount = add(refundAmount, makerBet.trustedVerifier.baseFee); // Refund base verifier fee too if no taker-bets, because verifier do not need to settle the bet with no takers
                makerBet.makerFundWithdrawn = true;
            }

            if (!makerBet.maker.send(refundAmount)) {
                makerBet.totalFund = add(makerBet.totalFund, unusedFund);
                makerBet.status = BetStatus.Paused;
                makerBet.makerFundWithdrawn = false;
            } else {
                emit LogCloseBet(makerBetId, msg.sender);
            }
        } else {
            emit LogCloseBet(makerBetId, msg.sender);
        }
    }


/// Settle a bet by trusted verifier

function settleBet(uint makerBetId, address maker, uint outcome) external {
        require(outcome == 1 || outcome == 2 || outcome == 3 || outcome == 4);

        MakerBet storage makerBet = makerBets[makerBetId][maker];
        require(makerBet.makerBetId != 0);

        require(msg.sender == makerBet.trustedVerifier.addr);

        require(makerBet.totalStake > 0);

        require(makerBet.status != BetStatus.Settled);

        BetOutcome betOutcome = BetOutcome(outcome);
        makerBet.outcome = betOutcome;
        makerBet.status = BetStatus.Settled;

        payMaker(makerBet);
        payVerifier(makerBet);

        emit LogSettleBet(makerBetId, maker);
    }


/// Manual withdraw fund from a bet after outcome is set

function withdraw(uint makerBetId, address maker) external {
        MakerBet storage makerBet = makerBets[makerBetId][maker];
        require(makerBet.makerBetId != 0);

        require(makerBet.outcome != BetOutcome.NotSettled);

        require(makerBet.status == BetStatus.Settled);

        bool fullyWithdrawn = false;

        if (msg.sender == maker) {
            fullyWithdrawn = payMaker(makerBet);
        } else if (msg.sender == makerBet.trustedVerifier.addr) {
            fullyWithdrawn = payVerifier(makerBet);
        } else {
            fullyWithdrawn = payTaker(makerBet, msg.sender);
        }

        if (fullyWithdrawn) {
            emit LogWithdraw(makerBetId, maker, msg.sender);
        }
    }


/// Payout to maker

function payMaker(MakerBet storage makerBet) private returns (bool fullyWithdrawn) {
        fullyWithdrawn = false;

        if (!makerBet.makerFundWithdrawn) {
            makerBet.makerFundWithdrawn = true;

            uint payout = 0;
            if (makerBet.outcome == BetOutcome.MakerWin) {
                uint trustedVerifierFeeMakerWin = mul(makerBet.totalStake, makerBet.trustedVerifier.feeRate) / ((10 ** feeRateDecimals) * 100);
                payout = sub(add(makerBet.totalFund, makerBet.totalStake), trustedVerifierFeeMakerWin);
            } else if (makerBet.outcome == BetOutcome.TakerWin) {
                payout = sub(makerBet.totalFund, makerBet.reservedFund);
            } else if (makerBet.outcome == BetOutcome.Draw || makerBet.outcome == BetOutcome.Canceled) {
                payout = makerBet.totalFund;
            }

            if (payout > 0) {
                fullyWithdrawn = true;

                if (!makerBet.maker.send(payout)) {
                    makerBet.makerFundWithdrawn = false;
                    fullyWithdrawn = false;
                }
            }
        }

        return fullyWithdrawn;
    }


/// Payout to taker

function payTaker(MakerBet storage makerBet, address taker) private returns (bool fullyWithdrawn) {
        fullyWithdrawn = false;

        uint payout = 0;

        for (uint betIndex = 0; betIndex < makerBet.takerBetsCount; betIndex++) {
            if (makerBet.takerBets[betIndex].taker == taker) {
                if (!makerBet.takerBets[betIndex].settled) {
                    makerBet.takerBets[betIndex].settled = true;

                    if (makerBet.outcome == BetOutcome.MakerWin) {
                        continue;
                    } else if (makerBet.outcome == BetOutcome.TakerWin) {
                        uint netProfit = mul(mul(makerBet.takerBets[betIndex].stake, sub(makerBet.takerBets[betIndex].odds, (10 ** oddsDecimals))), sub(((10 ** feeRateDecimals) * 100), makerBet.trustedVerifier.feeRate)) / (10 ** oddsDecimals) / ((10 ** feeRateDecimals) * 100);
                        payout = add(payout, add(makerBet.takerBets[betIndex].stake, netProfit));
                    } else if (makerBet.outcome == BetOutcome.Draw || makerBet.outcome == BetOutcome.Canceled) {
                        payout = add(payout, makerBet.takerBets[betIndex].stake);
                    }
                }
            }
        }

        if (payout > 0) {
            fullyWithdrawn = true;

            if (!taker.send(payout)) {
                fullyWithdrawn = false;

                for (uint betIndex2 = 0; betIndex2 < makerBet.takerBetsCount; betIndex2++) {
                    if (makerBet.takerBets[betIndex2].taker == taker) {
                        if (makerBet.takerBets[betIndex2].settled) {
                            makerBet.takerBets[betIndex2].settled = false;
                        }
                    }
                }
            }
        }

        return fullyWithdrawn;
    }


/// Payout to verifier

function payVerifier(MakerBet storage makerBet) private returns (bool fullyWithdrawn) {
        fullyWithdrawn = false;

        if (!makerBet.trustedVerifierFeeSent) {
            makerBet.trustedVerifierFeeSent = true;

            uint payout = 0;
            if (makerBet.outcome == BetOutcome.MakerWin) {
                uint trustedVerifierFeeMakerWin = mul(makerBet.totalStake, makerBet.trustedVerifier.feeRate) / ((10 ** feeRateDecimals) * 100);
                payout = add(makerBet.trustedVerifier.baseFee, trustedVerifierFeeMakerWin);
            } else if (makerBet.outcome == BetOutcome.TakerWin) {
                uint trustedVerifierFeeTakerWin = mul(makerBet.reservedFund, makerBet.trustedVerifier.feeRate) / ((10 ** feeRateDecimals) * 100);
                payout = add(makerBet.trustedVerifier.baseFee, trustedVerifierFeeTakerWin);
            } else if (makerBet.outcome == BetOutcome.Draw || makerBet.outcome == BetOutcome.Canceled) {
                payout = makerBet.trustedVerifier.baseFee;
            }

            if (payout > 0) {
                fullyWithdrawn = true;

                if (!makerBet.trustedVerifier.addr.send(payout)) {
                    makerBet.trustedVerifierFeeSent = false;
                    fullyWithdrawn = false;
                }
            }
        }

        return fullyWithdrawn;
    }


/* Math utilities */

function mul(uint256 _a, uint256 _b) private pure returns(uint256 c) {
        if (_a == 0) {
            return 0;
        }

        c = _a * _b;
        assert(c / _a == _b);
        return c;
    }


/**
 * @dev Returns the average of two numbers. The result is rounded towards
 * zero.
 */

function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow, so we distribute
        return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
    }


/**
 * Mints Bananass
 */

function mintBanana(uint numberOfTokens) public payable {
        require(saleIsActive, "Sale must be active to mint Bananas");
        require(numberOfTokens <= maxBananaPurchase, "Can only mint 20 tokens at a time");
        require(totalSupply().add(numberOfTokens) <= MAX_BANANAS, "Purchase would exceed max supply of Bananas");
        require(BananaPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_BANANAS) {
                _safeMint(msg.sender, mintIndex);
            }
        }

        // If we haven't set the starting index and this is either 1) the last saleable token or 2) the first token to be sold after
        // the end of pre-sale, set the starting index block
        if (startingIndexBlock == 0 && (totalSupply() == MAX_BANANAS || block.timestamp >= REVEAL_TIMESTAMP)) {
            startingIndexBlock = block.number;
        }
    }


/// @notice Deposit LP tokens to Staking contract for Reward token allocation.
/// @param pid The index of the pool. See `poolInfo`.
/// @param amount LP token amount to deposit.
/// @param to The receiver of `amount` deposit benefit.

function deposit(
        uint256 pid,
        uint256 amount,
        address to
    ) public {
        PoolInfo memory pool = updatePool(pid);
        UserInfo storage user = userInfo[pid][to];

        // Effects
        user.amount = user.amount.add(amount);
        user.rewardDebt = user.rewardDebt.add(int256(amount.mul(pool.accRewardPerShare) / ACC_PRECISION));

        // Interactions
        IRewarder _rewarder = rewarder[pid];
        if (address(_rewarder) != address(0)) {
            _rewarder.onTokenReward(pid, to, to, 0, user.amount);
        }

        lpToken[pid].safeTransferFrom(msg.sender, address(this), amount);

        emit Deposit(msg.sender, pid, amount, to);
    }


/// @notice Harvest proceeds for transaction sender to `to`.
/// @param pid The index of the pool. See `poolInfo`.
/// @param to Receiver of Token rewards.

function harvest(uint256 pid, address to) public {
        PoolInfo memory pool = updatePool(pid);
        UserInfo storage user = userInfo[pid][msg.sender];
        int256 accumulatedRewards = int256(user.amount.mul(pool.accRewardPerShare) / ACC_PRECISION);
        uint256 _pendingRewards = accumulatedRewards.sub(user.rewardDebt).toUInt256();

        // Effects
        user.rewardDebt = accumulatedRewards;

        // Interactions
        if (_pendingRewards != 0) {
            rewardToken.safeTransferFrom(rewardOwner, to, _pendingRewards);
        }

        IRewarder _rewarder = rewarder[pid];
        if (address(_rewarder) != address(0)) {
            _rewarder.onTokenReward(pid, msg.sender, to, _pendingRewards, user.amount);
        }

        emit Harvest(msg.sender, pid, _pendingRewards);
    }


/**
 * @dev Swaps ETH for wrapped stETH and deposits the resulting wstETH to the ZkSync bridge.
 * First withdraws ETH from the bridge if there is a pending balance.
 * @param _amountIn The amount of ETH to swap.
 */

function swapEthForStEth(uint256 _amountIn) internal returns (uint256) {
        // swap Eth for stEth on the Lido contract
        ILido(stEth).submit{value: _amountIn}(lidoReferral);
        // approve the wStEth contract to take the stEth
        IERC20(stEth).approve(wStEth, _amountIn);
        // wrap to wStEth and return deposited amount
        return IWstETH(wStEth).wrap(_amountIn);
    }


/**
 * Returns the amount of days passed with vesting
 */

function getDays(uint256 afterDays) public view returns (uint256) {
        uint256 releaseTime = getListingTime();
        uint256 time = releaseTime.add(afterDays);

        if (block.timestamp < time) {
            return 0;
        }

        uint256 diff = block.timestamp.sub(time);
        uint256 ds = diff.div(1 days).add(1);

        return ds;
    }


// Returns the amount of tokens unlocked by vesting so far

function getUnlockedVestingAmount(address sender) public view returns (uint256) {

        if (!isStarted(0)) {
            return 0;
        }

        uint256 dailyTransferableAmount = 0;

        for (uint256 i=0; i<vestingWallets[sender].length; i++) {

            if (vestingWallets[sender][i].totalAmount == 0) {
                continue;
            }

            uint256 trueDays = getDays(vestingWallets[sender][i].afterDays);
            uint256 dailyTransferableAmountCurrent = 0;

            // Unlock the first month right away on the first day of vesting;
            // But only start the real vesting after the first month (0, 30, 30, .., 31)
            if (trueDays > 0 && trueDays < 30) {
                trueDays = 30;
                dailyTransferableAmountCurrent = vestingWallets[sender][i].firstMonthAmount;
            }

            if (trueDays >= 30) {
                dailyTransferableAmountCurrent = vestingWallets[sender][i].firstMonthAmount.add(vestingWallets[sender][i].dayAmount.mul(trueDays.sub(30)));
            }

            if (dailyTransferableAmountCurrent > vestingWallets[sender][i].totalAmount) {
                dailyTransferableAmountCurrent = vestingWallets[sender][i].totalAmount;
            }

            dailyTransferableAmount = dailyTransferableAmount.add(dailyTransferableAmountCurrent);
        }

        return dailyTransferableAmount;
    }


/// @notice Removes and returns the n-th logical element
/// @param  self   the data structure
/// @param  index  which element (zero indexed) to remove and return
/// @return popped the specified element

function popByIndex(Self storage self, uint256 index) internal returns (uint256 popped) {
        popped = getByIndex(self, index);
        uint256 lastIndex = self.length - 1; // will not underflow b/c prior get
        if (index < lastIndex) {
            uint256 lastElement = getByIndex(self, lastIndex);
            self.elements[index] = lastElement;
        }
        delete self.elements[lastIndex];
        self.length -= 1;
    }


/// @notice Returns the n-th logical element
/// @param  self    the data structure
/// @param  index   which element (zero indexed) to get
/// @return element the specified element

function getByIndex(Self storage self, uint256 index) internal view returns (uint256 element) {
        require(index < self.length, "Out of bounds");
        return self.elements[index] == 0
            ? index + 1 // revert on overflow
            : self.elements[index];
    }


/// @notice Adds a new entry to the end of the queue
/// @param  self        the data structure
/// @param  beneficiary an address associated with the commitment
/// @param  quantity    how many to enqueue

function enqueue(Self storage self, address beneficiary, uint32 quantity) internal {
        require(quantity > 0, "Quantity is missing");
        self.elements[self.endIndex] = Element(
            beneficiary,
            uint64(block.number), // maturityBlock, hash thereof not yet known
            quantity
        );
        self.endIndex += 1;
        self.length += quantity;
    }


/// @notice Removes and returns the first element of the multi-queue; reverts if queue is empty
/// @param  self          the data structure
/// @return beneficiary   an address associated with the commitment
/// @return maturityBlock when this commitment matured

function dequeue(Self storage self) internal returns (address beneficiary, uint64 maturityBlock) {
        require(!_isEmpty(self), "Queue is empty");
        beneficiary = self.elements[self.startIndex].beneficiary;
        maturityBlock = self.elements[self.startIndex].maturityBlock;
        if (self.elements[self.startIndex].quantity == 1) {
            delete self.elements[self.startIndex];
            self.startIndex += 1;
        } else {
            self.elements[self.startIndex].quantity -= 1;
        }
        self.length -= 1;
    }


/**
 * @notice Admin function to add iToken into supported markets
 * Checks if the iToken already exsits
 * Will `revert()` if any check fails
 * @param _iToken The _iToken to add
 * @param _collateralFactor The _collateralFactor of _iToken
 * @param _borrowFactor The _borrowFactor of _iToken
 * @param _supplyCapacity The _supplyCapacity of _iToken
 * @param _distributionFactor The _distributionFactor of _iToken
 */

function _addMarket(
        address _iToken,
        uint256 _collateralFactor,
        uint256 _borrowFactor,
        uint256 _supplyCapacity,
        uint256 _borrowCapacity,
        uint256 _distributionFactor
    ) external override onlyOwner {
        require(IiToken(_iToken).isSupported(), "Token is not supported");

        // Market must not have been listed, EnumerableSet.add() will return false if it exsits
        require(iTokens.add(_iToken), "Token has already been listed");

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

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

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

        markets[_iToken] = Market({
            collateralFactorMantissa: _collateralFactor,
            borrowFactorMantissa: _borrowFactor,
            borrowCapacity: _borrowCapacity,
            supplyCapacity: _supplyCapacity,
            mintPaused: false,
            redeemPaused: false,
            borrowPaused: false
        });

        IRewardDistributor(rewardDistributor)._addRecipient(
            _iToken,
            _distributionFactor
        );

        emit MarketAdded(
            _iToken,
            _collateralFactor,
            _borrowFactor,
            _supplyCapacity,
            _borrowCapacity,
            _distributionFactor
        );
    }


/**
 * @notice Sets liquidationIncentive
 * @dev Admin function to set liquidationIncentive
 * @param _newLiquidationIncentiveMantissa New liquidationIncentive scaled by 1e18
 */

function _setLiquidationIncentive(uint256 _newLiquidationIncentiveMantissa)
        external
        override
        onlyOwner
    {
        require(
            _newLiquidationIncentiveMantissa >=
                liquidationIncentiveMinMantissa &&
                _newLiquidationIncentiveMantissa <=
                liquidationIncentiveMaxMantissa,
            "Liquidation incentive invalid"
        );

        uint256 _oldLiquidationIncentiveMantissa = liquidationIncentiveMantissa;
        liquidationIncentiveMantissa = _newLiquidationIncentiveMantissa;

        emit NewLiquidationIncentive(
            _oldLiquidationIncentiveMantissa,
            _newLiquidationIncentiveMantissa
        );
    }