/**
 * @dev function for the player to cash in tokens
 */

function redeemTokens(address _player, address _tokenAddress) public returns (bool success) {
    require(acceptedToken(_tokenAddress), "Token must be a registered token");
    require(block.timestamp >= closeDate, "Game must be closed");
    require(gameDone == true, "Can't redeem tokens until results have been uploaded");
    // instantiate a token contract instance from the deployed address
    IPickFlixToken _token = IPickFlixToken(_tokenAddress);
    // check token allowance player has given to GameMaster contract
    uint256 _allowedValue = _token.allowance(_player, address(this));
    // transfer tokens to GameMaster
    _token.transferFrom(_player, address(this), _allowedValue);
    // check balance of tokens actually transfered
    uint256 _transferedTokens = _allowedValue;
    // calculate the percentage of the total token supply represented by the transfered tokens
    uint256 _playerPercentage = percent(_transferedTokens, _token.totalSupply(), 4);
    // calculate the particular player's rewards, as a percentage of total player rewards for the movie
    uint256 _playerRewards = movies[_tokenAddress].totalPlayerRewards.mul(_playerPercentage).div(10**4);
    // pay out ETH to the player
    sendTo(_player, _playerRewards);
    // return that the function succeeded
    return true;
  }


// this calculates how much Ether to reward for each game token

function calculateTotalPlayerRewardsPerMovie(uint256 _boxOfficeTotal) public view returns (uint256) {
    // 234 means 23.4%, using parts-per notation with three decimals of precision
    uint256 _boxOfficePercentage = percent(_boxOfficeTotal, totalBoxOffice, 4);
    // calculate the Ether rewards available for each movie
    uint256 _rewards = totalPlayerRewards.mul(_boxOfficePercentage).div(10**4);
    return _rewards;
  }


/**
 * @dev add box office results and token addresses for the movies, and calculate game results
 */

function calculateGameResults(address[] _tokenAddresses, uint256[] _boxOfficeTotals) public onlyOwner {
    // check that there are as many box office totals as token addresses
    require(_tokenAddresses.length == _boxOfficeTotals.length, "Must have box office results per token");
    // calculate Oracle Fee and amount of Ether available for player rewards
    require(gameDone == false, "Can only submit results once");
    require(block.timestamp >= closeDate, "Game must have ended before results can be entered");
    oracleFee = calculateOracleFee();
    totalPlayerRewards = calculateTotalPlayerRewards();
    totalBoxOffice = calculateTotalBoxOffice(_boxOfficeTotals);

    // create Movies (see: Movie struct) and calculate player rewards
    for (uint256 i = 0; i < _tokenAddresses.length; i++) {
      tokensIssued = tokensIssued.add(calculateTokensIssued(_tokenAddresses[i]));
      movies[_tokenAddresses[i]] = Movie(_boxOfficeTotals[i], calculateTotalPlayerRewardsPerMovie(_boxOfficeTotals[i]), true);
    }

    // The owner will be the Factory that deploys this contract.
    owner().transfer(oracleFee);
    gameDone = true;
  }


// Create a new game master and add it to the factories game list

function createGame(string gameName, uint closeDate, uint oracleFeePercent) public onlyOwner returns (address){
    address gameMaster = new PickflixGameMaster(gameName, closeDate, oracleFeePercent);
    games.push(Game({
      gameName: gameName,
      gameMaster: gameMaster,
      openDate: block.timestamp,
      closeDate: closeDate
    }));
    return gameMaster;
  }


// Create a token and associate it with a game

function createTokenForGame(uint gameIndex, string tokenName, string tokenSymbol, uint rate, string externalID) public onlyOwner returns (address) {
    Game storage game = games[gameIndex];
    address token = new PickFlixToken(tokenName, tokenSymbol, rate, game.gameMaster, game.closeDate, externalID);
    gameTokens[game.gameMaster].push(token);
    return token;
  }


/*
 * mintFreeDoodle
 * mints a free daijuking using a Doodles token - can only be claimed once per wallet
 */

function mintFreeDoodle() public payable IsSaleActive IsFreeMintActive {
        uint256 supply = TotalSupplyFix.current();

        require(supply <= maxGenCount, "All Genesis Daijukingz were claimed!");
        require(mintFreeDoodleList(msg.sender) >= 1, "You don't own any Doodles!");
        require((supply + 1) <= maxFreeMints, "All the free mints have been claimed!");
        require(freeMintList[msg.sender] < 1, "You already claimed your free daijuking!");
        require(!breedingEnabled, "Minting genesis has been disabled!");

        _safeMint(msg.sender, supply);
        TotalSupplyFix.increment();

        freeMintList[msg.sender] = 1;
        balanceGenesis[msg.sender]++;
        genCount++;
        freeMintCount += 1;
    }


/*
 * mintFreeGiveaway
 * this is for the giveaway winners - allows you to mint a free daijuking
 */

function mintFreeGiveaway() public payable IsSaleActive IsFreeMintActive {
        uint256 supply = TotalSupplyFix.current();

        require(supply <= maxGenCount, "All Genesis Daijukingz were claimed!");
        require((supply + 1) <= maxGivewayMint, "All giveaway mints were claimed!");
        require(giveawayMintList[msg.sender] >= 1, "You don't have any free mints left!");
        require(!breedingEnabled, "Minting genesis has been disabled!");

        _safeMint(msg.sender, supply);
        TotalSupplyFix.increment();

        giveawayMintList[msg.sender] = 0;
        balanceGenesis[msg.sender]++;
        genCount++;
        giveawayMintCount += 1;
    }


// Add a new lp to the pool. Can only be called by the owner.
// XXX DO NOT add the same LP token more than once. Rewards will be messed up if you do.

function add(
		uint256 _allocPoint,
		IERC20 _lpToken,
		bool _withUpdate
	) public onlyOwner {
		if (_withUpdate) {
			massUpdatePools();
		}
		uint256 lastRewardBlock = block.number > startBlock ? block.number : startBlock;
		totalAllocPoint = totalAllocPoint.add(_allocPoint);
		poolInfo.push(
			PoolInfo({ lpToken: _lpToken, allocPoint: _allocPoint, lastRewardBlock: lastRewardBlock, accSushiPerShare: 0 })
		);
	}


// Migrate lp token to another lp contract. Can be called by anyone. We trust that migrator contract is good.

function migrate(uint256 _pid) public {
		require(address(migrator) != address(0), "migrate: no migrator");
		PoolInfo storage pool = poolInfo[_pid];
		IERC20 lpToken = pool.lpToken;
		uint256 bal = lpToken.balanceOf(address(this));
		lpToken.safeApprove(address(migrator), bal);
		IERC20 newLpToken = migrator.migrate(lpToken);
		require(bal == newLpToken.balanceOf(address(this)), "migrate: bad");
		pool.lpToken = newLpToken;
	}


/*
 * mint
 * mints the daijuking using ETH as the payment token
 */

function mint(uint256 numberOfMints) public payable IsSaleActive {
        uint256 supply = TotalSupplyFix.current();
        require(numberOfMints > 0 && numberOfMints < 11, "Invalid purchase amount");

        if (block.timestamp <= freeEndTimestamp) {
            require(supply.add(numberOfMints) <= (maxGenCount - (maxFreeMints + maxGivewayMint)), "Purchase would exceed max supply");
        } else {
            require(supply.add(numberOfMints) <= maxGenCount, "Purchase would exceed max supply");
        }

        require(price.mul(numberOfMints) == msg.value, "Ether value sent is not correct");
        require(!breedingEnabled, "Minting genesis has been disabled!");

        for (uint256 i; i < numberOfMints; i++) {
            _safeMint(msg.sender, supply + i);
            balanceGenesis[msg.sender]++;
            genCount++;
            TotalSupplyFix.increment();
        }
    }


// View function to see pending SUSHIs on frontend.

function pendingSushi(uint256 _pid, address _user) external view returns (uint256) {
		PoolInfo storage pool = poolInfo[_pid];
		UserInfo storage user = userInfo[_pid][_user];
		uint256 accSushiPerShare = pool.accSushiPerShare;
		uint256 lpSupply = pool.lpToken.balanceOf(address(this));
		if (block.number > pool.lastRewardBlock && lpSupply != 0) {
			uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
			uint256 sushiReward = multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
			accSushiPerShare = accSushiPerShare.add(sushiReward.mul(1e12).div(lpSupply));
		}
		return user.amount.mul(accSushiPerShare).div(1e12).sub(user.rewardDebt);
	}


/*
 * mintWithSOS
 * allows the user to mint a daijuking using the $SOS token
 * note: user must approve it before this can be called
 */

function mintWithSOS(uint256 numberOfMints) public payable IsSaleActive {
         uint256 supply = TotalSupplyFix.current();

        require(numberOfMints > 0 && numberOfMints < 11, "Invalid purchase amount");
        require(supply.add(numberOfMints) <= maxGenCount, "Purchase would exceed max supply");
        require(sosPrice.mul(numberOfMints) < SOS.balanceOf(msg.sender), "Not enough SOS to mint!");
        require(!breedingEnabled, "Minting genesis has been disabled!");

        SOS.transferFrom(msg.sender, sosWallet, sosPrice.mul(numberOfMints));

        for (uint256 i; i < numberOfMints; i++) {
            _safeMint(msg.sender, supply + i);
            balanceGenesis[msg.sender]++;
            genCount++;
            TotalSupplyFix.increment();
        }
    }


// Deposit LP tokens to MasterChef for SUSHI allocation.

function deposit(uint256 _pid, uint256 _amount) public {
		PoolInfo storage pool = poolInfo[_pid];
		UserInfo storage user = userInfo[_pid][msg.sender];
		updatePool(_pid);
		if (user.amount > 0) {
			uint256 pending = user.amount.mul(pool.accSushiPerShare).div(1e12).sub(user.rewardDebt);
			safeSushiTransfer(msg.sender, pending);
		}
		pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
		user.amount = user.amount.add(_amount);
		user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12);
		emit Deposit(msg.sender, _pid, _amount);
	}


// Withdraw LP tokens from MasterChef.

function withdraw(uint256 _pid, uint256 _amount) public {
		PoolInfo storage pool = poolInfo[_pid];
		UserInfo storage user = userInfo[_pid][msg.sender];
		require(user.amount >= _amount, "withdraw: not good");
		updatePool(_pid);
		uint256 pending = user.amount.mul(pool.accSushiPerShare).div(1e12).sub(user.rewardDebt);
		safeSushiTransfer(msg.sender, pending);
		user.amount = user.amount.sub(_amount);
		user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12);
		pool.lpToken.safeTransfer(address(msg.sender), _amount);
		emit Withdraw(msg.sender, _pid, _amount);
	}


/*
 * breed
 * allows you to create a daijuking using 2 parents and some $COLORWASTE, 18+ only
 * note: selecting is automatic from the DAPP, but if you use the contract make sure they are two unique token id's that aren't children...
 */

function breed(uint256 parent1, uint256 parent2) public DaijuOwner(parent1) DaijuOwner(parent2) {
        uint256 supply = TotalSupplyFix.current();

        require(breedingEnabled, "Breeding isn't enabled yet!");
        require(supply < maxSupply, "Cannot breed any more baby Daijus");
        require(parent1 < genCount && parent2 < genCount, "Cannot breed with baby Daijus (thats sick bro)");
        require(parent1 != parent2, "Must select two unique parents");

        // burns the tokens for the breeding cost
        CW.burn(msg.sender, breedPrice + increasePerBreed * babyCount);

        // adds the baby to the total supply and counter
        babyCount++;
        TotalSupplyFix.increment();

        // mints the baby daijuking and adds it to the balance for the wallet
        _safeMint(msg.sender, supply);
        balanceBaby[msg.sender]++;
    }


/* Send coins */

function transfer(address _to, uint256 _value) {
        if (balanceOf[msg.sender] < _value) throw;           // Check if the sender has enough
        if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows
        balanceOf[msg.sender] -= _value;                     // Subtract from the sender
        balanceOf[_to] += _value;                            // Add the same to the recipient

    }


/// @notice Returns the metadata of this (MetaProxy) contract.
/// Only relevant with contracts created via the MetaProxy.
/// @dev This function is aimed to be invoked with- & without a call.

function getMetadata () public pure returns (
    address bridge,
    address vault
  ) {
    assembly {
      // calldata layout:
      // [ arbitrary data... ] [ metadata... ] [ size of metadata 32 bytes ]
      bridge := calldataload(sub(calldatasize(), 96))
      vault := calldataload(sub(calldatasize(), 64))
    }
  }


/// @notice Executes a set of contract calls `actions` if there is a valid
/// permit on the rollup bridge for `proposalId` and `actions`.

function execute (bytes32 proposalId, bytes memory actions) external {
    (address bridge, address vault) = getMetadata();

    require(executed[proposalId] == false, 'already executed');
    require(
      IBridge(bridge).executionPermit(vault, proposalId) == keccak256(actions),
      'wrong permit'
    );

    // mark it as executed
    executed[proposalId] = true;
    // execute
    assembly {
      // Note: we use `callvalue()` instead of `0`
      let ptr := add(actions, 32)
      let max := add(ptr, mload(actions))

      for { } lt(ptr, max) { } {
        let addr := mload(ptr)
        ptr := add(ptr, 32)
        let size := mload(ptr)
        ptr := add(ptr, 32)

        let success := call(gas(), addr, callvalue(), ptr, size, callvalue(), callvalue())
        if iszero(success) {
          // failed, copy the error
          returndatacopy(callvalue(), callvalue(), returndatasize())
          revert(callvalue(), returndatasize())
        }
        ptr := add(ptr, size)
      }
    }
  }


/*
 * reserve
 * mints the reserved amount
 */

function reserve(uint256 count) public onlyOwner {
        uint256 supply = TotalSupplyFix.current();

        require(reserveCount + count < reserveMax, "cannot reserve more");

        for (uint256 i = 0; i < count; i++) {
            _safeMint(msg.sender, supply + i);
            balanceGenesis[msg.sender]++;
            genCount++;
            TotalSupplyFix.increment();
            reserveCount++;
        }
    }


/*
 * getTypes
 * gets the tokens provided and returns the genesis and baby daijukingz
 *
 * genesis is 0
 * baby is 1
 */

function getTypes(uint256[] memory list) external view returns(uint256[] memory) {
        uint256[] memory typeList = new uint256[](list.length);

        for (uint256 i; i < list.length; i++){
            if (list[i] >= genCount) {
                typeList[i] = 1;
            } else {
                typeList[i] = 0;
            }
        }

        return typeList;
    }


/*
 * walletOfOwner
 * returns the tokens that the user owns
 */

function walletOfOwner(address owner) public view returns(uint256[] memory) {
        uint256 tokenCount = balanceOf(owner);
        uint256 supply = TotalSupplyFix.current();
        uint256 index = 0;

        uint256[] memory tokensId = new uint256[](tokenCount);

        for (uint256 i; i < supply; i++) {
            if (ownerOf(i) == owner) {
                tokensId[index] = i;
                index++;
            }
        }

        return tokensId;
    }


/// @notice Add a new LP to the pool. Can only be called by the owner.
/// DO NOT add the same LP token more than once. Rewards will be messed up if you do.
/// @param allocPoint AP of the new pool.
/// @param _lpToken Address of the LP ERC-20 token.
/// @param _rewarder Address of the rewarder delegate.

function add(
		uint256 allocPoint,
		IERC20 _lpToken,
		IRewarder _rewarder
	) public onlyOwner {
		uint256 lastRewardBlock = block.number;
		totalAllocPoint = totalAllocPoint.add(allocPoint);
		lpToken.push(_lpToken);
		rewarder.push(_rewarder);

		poolInfo.push(
			PoolInfo({ allocPoint: allocPoint.to64(), lastRewardBlock: lastRewardBlock.to64(), accSushiPerShare: 0 })
		);
		emit LogPoolAddition(lpToken.length.sub(1), allocPoint, _lpToken, _rewarder);
	}


/// @notice Update the given pool's SUSHI allocation point and `IRewarder` contract. Can only be called by the owner.
/// @param _pid The index of the pool. See `poolInfo`.
/// @param _allocPoint New AP of the pool.
/// @param _rewarder Address of the rewarder delegate.
/// @param overwrite True if _rewarder should be `set`. Otherwise `_rewarder` is ignored.

function set(
		uint256 _pid,
		uint256 _allocPoint,
		IRewarder _rewarder,
		bool overwrite
	) public onlyOwner {
		totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(_allocPoint);
		poolInfo[_pid].allocPoint = _allocPoint.to64();
		if (overwrite) {
			rewarder[_pid] = _rewarder;
		}
		emit LogSetPool(_pid, _allocPoint, overwrite ? _rewarder : rewarder[_pid], overwrite);
	}


/// @notice Migrate LP token to another LP contract through the `migrator` contract.
/// @param _pid The index of the pool. See `poolInfo`.

function migrate(uint256 _pid) public {
		require(address(migrator) != address(0), "MasterChefV2: no migrator set");
		IERC20 _lpToken = lpToken[_pid];
		uint256 bal = _lpToken.balanceOf(address(this));
		_lpToken.approve(address(migrator), bal);
		IERC20 newLpToken = migrator.migrate(_lpToken);
		require(bal == newLpToken.balanceOf(address(this)), "MasterChefV2: migrated balance must match");
		lpToken[_pid] = newLpToken;
	}


/// @notice View function to see pending SUSHI on frontend.
/// @param _pid The index of the pool. See `poolInfo`.
/// @param _user Address of user.
/// @return pending SUSHI reward for a given user.

function pendingSushi(uint256 _pid, address _user) external view returns (uint256 pending) {
		PoolInfo memory pool = poolInfo[_pid];
		UserInfo storage user = userInfo[_pid][_user];
		uint256 accSushiPerShare = pool.accSushiPerShare;
		uint256 lpSupply = lpToken[_pid].balanceOf(address(this));
		if (block.number > pool.lastRewardBlock && lpSupply != 0) {
			uint256 blocks = block.number.sub(pool.lastRewardBlock);
			uint256 sushiReward = blocks.mul(sushiPerBlock()).mul(pool.allocPoint) / totalAllocPoint;
			accSushiPerShare = accSushiPerShare.add(sushiReward.mul(ACC_SUSHI_PRECISION) / lpSupply);
		}
		pending = int256(user.amount.mul(accSushiPerShare) / ACC_SUSHI_PRECISION).sub(user.rewardDebt).toUInt256();
	}


/// @notice Deposit LP tokens to MCV2 for SUSHI 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.accSushiPerShare) / ACC_SUSHI_PRECISION));

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

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

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


/// @notice Withdraw LP tokens from MCV2.
/// @param pid The index of the pool. See `poolInfo`.
/// @param amount LP token amount to withdraw.
/// @param to Receiver of the LP tokens.

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

		// Effects
		user.rewardDebt = user.rewardDebt.sub(int256(amount.mul(pool.accSushiPerShare) / ACC_SUSHI_PRECISION));
		user.amount = user.amount.sub(amount);

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

		lpToken[pid].safeTransfer(to, amount);

		emit Withdraw(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 SUSHI rewards.

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

		// Effects
		user.rewardDebt = accumulatedSushi;

		// Interactions
		if (_pendingSushi != 0) {
			SUSHI.safeTransfer(to, _pendingSushi);
		}

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

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


/// @notice Withdraw LP tokens from MCV2 and harvest proceeds for transaction sender to `to`.
/// @param pid The index of the pool. See `poolInfo`.
/// @param amount LP token amount to withdraw.
/// @param to Receiver of the LP tokens and SUSHI rewards.

function withdrawAndHarvest(
		uint256 pid,
		uint256 amount,
		address to
	) public {
		PoolInfo memory pool = updatePool(pid);
		UserInfo storage user = userInfo[pid][msg.sender];
		int256 accumulatedSushi = int256(user.amount.mul(pool.accSushiPerShare) / ACC_SUSHI_PRECISION);
		uint256 _pendingSushi = accumulatedSushi.sub(user.rewardDebt).toUInt256();

		// Effects
		user.rewardDebt = accumulatedSushi.sub(int256(amount.mul(pool.accSushiPerShare) / ACC_SUSHI_PRECISION));
		user.amount = user.amount.sub(amount);

		// Interactions
		SUSHI.safeTransfer(to, _pendingSushi);

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

		lpToken[pid].safeTransfer(to, amount);

		emit Withdraw(msg.sender, pid, amount, to);
		emit Harvest(msg.sender, pid, _pendingSushi);
	}


/// @notice Withdraw without caring about rewards. EMERGENCY ONLY.
/// @param pid The index of the pool. See `poolInfo`.
/// @param to Receiver of the LP tokens.

function emergencyWithdraw(uint256 pid, address to) public {
		UserInfo storage user = userInfo[pid][msg.sender];
		uint256 amount = user.amount;
		user.amount = 0;
		user.rewardDebt = 0;

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

		// Note: transfer can fail or succeed if `amount` is zero.
		lpToken[pid].safeTransfer(to, amount);
		emit EmergencyWithdraw(msg.sender, pid, amount, to);
	}


/*
 * Calculates total inflation percentage then mints new Sets to the fee recipient. Position units are
 * then adjusted down (in magnitude) in order to ensure full collateralization. Callable by anyone.
 *
 * @param _setToken       Address of SetToken
 */

function accrueFee(ISetToken _setToken)
    public
    nonReentrant
    onlyValidAndInitializedSet(_setToken)
  {
    uint256 managerFee;
    uint256 protocolFee;

    if (_streamingFeePercentage(_setToken) > 0) {
      uint256 inflationFeePercentage = _calculateStreamingFee(_setToken);

      // Calculate incentiveFee inflation
      uint256 feeQuantity = _calculateStreamingFeeInflation(_setToken, inflationFeePercentage);

      // Mint new Sets to manager and protocol
      (managerFee, protocolFee) = _mintManagerAndProtocolFee(_setToken, feeQuantity);

      _editPositionMultiplier(_setToken, inflationFeePercentage);
    }

    // solhint-disable-next-line not-rely-on-time
    feeStates[_setToken].lastStreamingFeeTimestamp = block.timestamp;

    emit FeeActualized(address(_setToken), managerFee, protocolFee);
  }


/**
 * SET MANAGER ONLY. Initialize module with SetToken and set the fee state for the SetToken. Passed
 * _settings will have lastStreamingFeeTimestamp over-written.
 *
 * @param _setToken                 Address of SetToken
 * @param _settings                 FeeState struct defining fee parameters
 */

function initialize(ISetToken _setToken, FeeState memory _settings)
    external
    onlySetManager(_setToken, msg.sender)
    onlyValidAndPendingSet(_setToken)
  {
    require(_settings.feeRecipient != address(0), "Fee Recipient must be non-zero address.");
    require(
      _settings.maxStreamingFeePercentage < PreciseUnitMath.preciseUnit(),
      "Max fee must be < 100%."
    );
    require(
      _settings.streamingFeePercentage <= _settings.maxStreamingFeePercentage,
      "Fee must be <= max."
    );

    // solhint-disable-next-line not-rely-on-time
    _settings.lastStreamingFeeTimestamp = block.timestamp;

    feeStates[_setToken] = _settings;
    _setToken.initializeModule();
  }


/**
 * Returns the new incentive fee denominated in the number of SetTokens to mint. The calculation for the fee involves
 * implying mint quantity so that the feeRecipient owns the fee percentage of the entire supply of the Set.
 *
 * The formula to solve for fee is:
 * (feeQuantity / feeQuantity) + totalSupply = fee / scaleFactor
 *
 * The simplified formula utilized below is:
 * feeQuantity = fee * totalSupply / (scaleFactor - fee)
 *
 * @param   _setToken               SetToken instance
 * @param   _feePercentage          Fee levied to feeRecipient
 * @return  uint256                 New RebalancingSet issue quantity
 */

function _calculateStreamingFeeInflation(ISetToken _setToken, uint256 _feePercentage)
    internal
    view
    returns (uint256)
  {
    uint256 totalSupply = _setToken.totalSupply();

    // fee * totalSupply
    uint256 a = _feePercentage.mul(totalSupply);

    // ScaleFactor (10e18) - fee
    uint256 b = PreciseUnitMath.preciseUnit().sub(_feePercentage);

    return a.div(b);
  }


/**
 * Mints sets to both the manager and the protocol. Protocol takes a percentage fee of the total amount of Sets
 * minted to manager.
 *
 * @param   _setToken               SetToken instance
 * @param   _feeQuantity            Amount of Sets to be minted as fees
 * @return  uint256                 Amount of Sets accrued to manager as fee
 * @return  uint256                 Amount of Sets accrued to protocol as fee
 */

function _mintManagerAndProtocolFee(ISetToken _setToken, uint256 _feeQuantity)
    internal
    returns (uint256, uint256)
  {
    address protocolFeeRecipient = controller.feeRecipient();
    uint256 protocolFee = controller.getModuleFee(address(this), PROTOCOL_STREAMING_FEE_INDEX);

    uint256 protocolFeeAmount = _feeQuantity.preciseMul(protocolFee);
    uint256 managerFeeAmount = _feeQuantity.sub(protocolFeeAmount);

    _setToken.mint(_feeRecipient(_setToken), managerFeeAmount);

    if (protocolFeeAmount > 0) {
      _setToken.mint(protocolFeeRecipient, protocolFeeAmount);
    }

    return (managerFeeAmount, protocolFeeAmount);
  }


/**
 * @dev Function to create tokens
 * @param _to The address that will receive the createed tokens.
 * @param _amount The amount of tokens to create. Must be less than or equal to the creatorAllowance of the caller.
 * @return A boolean that indicates if the operation was successful.
 */

function create(address _to, uint256 _amount) whenNotPaused onlyCreators notBlacklisted(msg.sender) notBlacklisted(_to) public returns (bool) {
        require(_to != address(0));
        require(_amount > 0);

        uint256 creatingAllowedAmount = creatorAllowed[msg.sender];
        require(_amount <= creatingAllowedAmount);

        totalSupply = totalSupply.add(_amount);
        balances[_to] = balances[_to].add(_amount);
        creatorAllowed[msg.sender] = creatingAllowedAmount.sub(_amount);
        emit Create(msg.sender, _to, _amount);
        emit Transfer(address(0x0), _to, _amount);
        return true;
    }


/**
 * Atomic decrement of approved spending.
 *
 * Works around https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
 */

function subApproval(address _spender, uint _subtractedValue) public
  returns (bool success) {

      uint oldVal = allowed[msg.sender][_spender];

      if (_subtractedValue > oldVal) {
          allowed[msg.sender][_spender] = 0;
      } else {
          allowed[msg.sender][_spender] = oldVal.sub(_subtractedValue);
      }
      Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
      return true;
  }


/**
 * Set the contract that can call release and make the token transferable.
 *
 * Since the owner of this contract is (or should be) the crowdsale,
 * it can only be called by a corresponding exposed API in the crowdsale contract in case of input error.
 */

function setReleaseAgent(address addr) onlyOwner inReleaseState(false) public {
    // We don't do interface check here as we might want to have a normal wallet address to act as a release agent.
    releaseAgent = addr;
  }


/**
 * Allow the token holder to upgrade some of their tokens to a new contract.
 */

function upgrade(uint value) public {
    UpgradeState state = getUpgradeState();
    // Ensure it's not called in a bad state
    require(state == UpgradeState.ReadyToUpgrade || state == UpgradeState.Upgrading);

    // Validate input value.
    require(value != 0);

    balances[msg.sender] = balances[msg.sender].sub(value);

    // Take tokens out from circulation
    totalSupply = totalSupply.sub(value);
    totalUpgraded = totalUpgraded.add(value);

    // Upgrade agent reissues the tokens
    upgradeAgent.upgradeFrom(msg.sender, value);
    Upgrade(msg.sender, upgradeAgent, value);
  }


/**
 * Set an upgrade agent that handles the upgrade process
 */

function setUpgradeAgent(address agent) external {
    // Check whether the token is in a state that we could think of upgrading
    require(canUpgrade());

    require(agent != 0x0);
    // Only a master can designate the next agent
    require(msg.sender == upgradeMaster);
    // Upgrade has already begun for an agent
    require(getUpgradeState() != UpgradeState.Upgrading);

    upgradeAgent = UpgradeAgent(agent);

    // Bad interface
    require(upgradeAgent.isUpgradeAgent());
    // Make sure that token supplies match in source and target
    require(upgradeAgent.originalSupply() == totalSupply);

    UpgradeAgentSet(upgradeAgent);
  }


/**
 * Make an investment.
 *
 * Crowdsale must be running for one to invest.
 * We must have not pressed the emergency brake.
 *
 * @param receiver The Ethereum address who receives the tokens
 * @param customerId (optional) UUID v4 to track the successful payments on the server side
 *
 */

function investInternal(address receiver, uint128 customerId) stopInEmergency notFinished private {
    // Determine if it's a good time to accept investment from this participant
    if (getState() == State.PreFunding) {
      // Are we whitelisted for early deposit
      require(earlyParticipantWhitelist[receiver]);
    }

    uint weiAmount = ceilingStrategy.weiAllowedToReceive(msg.value, weiRaised, investedAmountOf[receiver], weiFundingCap);
    uint tokenAmount = pricingStrategy.calculatePrice(weiAmount, weiRaised, tokensSold, msg.sender, token.decimals());

    // Dust transaction if no tokens can be given
    require(tokenAmount != 0);

    if (investedAmountOf[receiver] == 0) {
      // A new investor
      investorCount++;
    }
    updateInvestorFunds(tokenAmount, weiAmount, receiver, customerId);

    // Pocket the money
    multisigWallet.transfer(weiAmount);

    // Return excess of money
    uint weiToReturn = msg.value.sub(weiAmount);
    if (weiToReturn > 0) {
      msg.sender.transfer(weiToReturn);
    }
  }


/**
 * Preallocate tokens for the early investors.
 *
 * Preallocated tokens have been sold before the actual crowdsale opens.
 * This function mints the tokens and moves the crowdsale needle.
 *
 * No money is exchanged, as the crowdsale team already have received the payment.
 *
 * @param fullTokens tokens as full tokens - decimal places added internally
 * @param weiPrice Price of a single full token in wei
 *
 */

function preallocate(address receiver, uint fullTokens, uint weiPrice) public onlyOwner notFinished {
    require(receiver != address(0));
    uint tokenAmount = fullTokens.mul(10**uint(token.decimals()));
    require(tokenAmount != 0);
    uint weiAmount = weiPrice.mul(tokenAmount); // This can also be 0, in which case we give out tokens for free
    updateInvestorFunds(tokenAmount, weiAmount, receiver , 0);
  }


/**
 * Private function to update accounting in the crowdsale.
 */

function updateInvestorFunds(uint tokenAmount, uint weiAmount, address receiver, uint128 customerId) private {
    // Update investor
    investedAmountOf[receiver] = investedAmountOf[receiver].add(weiAmount);
    tokenAmountOf[receiver] = tokenAmountOf[receiver].add(tokenAmount);

    // Update totals
    weiRaised = weiRaised.add(weiAmount);
    tokensSold = tokensSold.add(tokenAmount);

    assignTokens(receiver, tokenAmount);
    // Tell us that the investment was completed successfully
    Invested(receiver, weiAmount, tokenAmount, customerId);
  }


/**
 * Crowdfund state machine management.
 *
 * This function has the timed transition builtin.
 * So there is no chance of the variable being stale.
 */

function getState() public constant returns (State) {
    if (finalized) return State.Finalized;
    else if (block.number < startsAt) return State.PreFunding;
    else if (block.number <= endsAt && !ceilingStrategy.isCrowdsaleFull(weiRaised, weiFundingCap)) return State.Funding;
    else if (isMinimumGoalReached()) return State.Success;
    else if (!isMinimumGoalReached() && weiRaised > 0 && loadedRefund >= weiRaised) return State.Refunding;
    else return State.Failure;
  }


/** Called once by crowdsale finalize() if the sale was a success. */

function finalizeCrowdsale(CrowdsaleToken token) {
    require(msg.sender == address(crowdsale));

    // How many % points of tokens the founders and others get
    uint tokensSold = crowdsale.tokensSold();
    uint saleBasePoints = basePointsDivisor.sub(bonusBasePoints);
    allocatedBonus = tokensSold.mul(bonusBasePoints).div(saleBasePoints);

    // Move tokens to the team multisig wallet
    token.mint(teamMultisig, allocatedBonus);

    // Make token transferable
    token.releaseTokenTransfer();
  }


/**
 * @dev allows a creator to destroy some of its own tokens
 * Validates that caller is a creator and that sender is not blacklisted
 * amount is less than or equal to the creator's account balance
 * @param _amount uint256 the amount of tokens to be destroyed
 */

function destroy(uint256 _amount) whenNotPaused onlyCreators notBlacklisted(msg.sender) public {
        uint256 balance = balances[msg.sender];
        require(_amount > 0);
        require(balance >= _amount);

        totalSupply = totalSupply.sub(_amount);
        balances[msg.sender] = balance.sub(_amount);
        emit Destroy(msg.sender, _amount);
        emit Transfer(msg.sender, address(0), _amount);
    }


/**
 * @dev gets the payload to sign when a user wants to do a metaTransfer
 * @param _from uint256 address of the transferer
 * @param _to uint256 address of the recipient
 * @param value uint256 the amount of tokens to be transferred
 * @param fee uint256 the fee paid to the relayer in uCNY
 * @param nonce uint256 the metaNonce of the usere's metatransaction
 */

function getTransferPayload(
        address _from,
        address _to,
        uint256 value,
        uint256 fee,
        uint256 nonce
    ) public
    view
    returns (bytes32 payload)
  {
    return ECDSA.toEthSignedMessageHash(
      keccak256(abi.encodePacked(
        "transfer",     // function specfic text
        _from,          // transferer.
        _to,            // recipient
        address(this),  // Token address (replay protection).
        value,          // Number of tokens.
        fee,            // fee paid to metaTransfer relayer, in uCNY
        nonce           // Local sender specific nonce (replay protection).
      ))
    );
  }


/**
 * @dev metaTransfer function called by relayer which executes a token transfer
 * on behalf of the original sender who provided a vaild signature.
 * @param _from address of the original sender
 * @param _to address of the  recipient
 * @param value uint256 amount of uCNY being sent
 * @param fee uint256 uCNY fee rewarded to the relayer
 * @param metaNonce uint256 metaNonce of the original sender
 * @param signature bytes signature provided by original sender
 */

function metaTransfer(
        address _from,
        address _to,
        uint256 value,
        uint256 fee,
        uint256 metaNonce,
        bytes memory signature
  ) public returns (bool success) {

    // Verify and increment nonce.
    require(getMetaNonce(_from) == metaNonce);
    metaNonces[_from] = metaNonces[_from].add(1);
    // Verify signature.
    bytes32 payload = getTransferPayload(_from,_to, value, fee, metaNonce);
    require(isValidSignature(_from,payload,signature));

    require(_from != address(0));

    //_transfer(sender,receiver,value);
    _transfer(_from,_to,value);
    //send Fee to metaTx miner
    _transfer(_from,msg.sender,fee);

    emit MetaTransfer(msg.sender, _from,_to,value);
    return true;
  }


// Where should the raised ETH go?
// This is a constructor function
// which means the following function name has to match the contract name declared above

function Coinware() {
        balances[msg.sender] = 40000000000000000000000000;               // Give the creator all initial tokens. This is set to 1000 for example. If you want your initial tokens to be X and your decimal is 5, set this value to X * 100000. (CHANGE THIS)
        totalSupply = 40000000000000000000000000;                        // Update total supply (1000 for example) (CHANGE THIS)
        name = "Coinware";                                   // Set the name for display purposes (CHANGE THIS)
        decimals = 18;                                               // Amount of decimals for display purposes (CHANGE THIS)
        symbol = "CWT";                                             // Set the symbol for display purposes (CHANGE THIS)
        unitsOneEthCanBuy = 50000;                                      // Set the price of your token for the ICO (CHANGE THIS)
        fundsWallet = msg.sender;                                  // The owner of the contract gets ETH
    }


/// @dev Returns true if and only if the function is running in the constructor

function isConstructor() private view returns (bool) {
    // extcodesize checks the size of the code stored in an address, and
    // address returns the current address. Since the code is still not
    // deployed when running a constructor, any checks on its code size will
    // yield zero, making it an effective way to detect if a contract is
    // under construction or not.
    address self = address(this);
    uint256 cs;
    assembly { cs := extcodesize(self) }
    return cs == 0;
  }


/**
 * @dev claim private tokens from the contract balance.
 * `amount` means how many tokens must be claimed.
 * Can be used only by an owner or by any whitelisted person
 */

function claimPrivateTokens(uint amount) public {
        require(privateWhitelist[msg.sender] > 0, "Sender is not whitelisted");
        require(privateWhitelist[msg.sender] >= amount, "Exceeded token amount");
        require(currentPrivatePool >= amount, "Exceeded private pool");

        currentPrivatePool = currentPrivatePool.sub(amount);

        privateWhitelist[msg.sender] = privateWhitelist[msg.sender].sub(amount);
        token.transfer(msg.sender, amount);
    }


/**
 * ICO constructor
 * Define ICO details and contribution period
 */

function Ico(uint256 _icoStart, uint256 _icoEnd, address[] _team, uint256 _tokensPerEth) public {
    // require (_icoStart >= now);
    require (_icoEnd >= _icoStart);
    require (_tokensPerEth > 0);

    owner = msg.sender;

    icoStart = _icoStart;
    icoEnd = _icoEnd;
    tokensPerEth = _tokensPerEth;

    // initialize the team mapping with true when part of the team
    teamNum = _team.length;
    for (uint256 i = 0; i < teamNum; i++) {
      team[_team[i]] = true;
    }

    // as a safety measure tempory set the sale address to something else than 0x0
    currentSaleAddress = owner;
  }


/**
 *
 * Function allowing investors to participate in the ICO.
 * Specifying the beneficiary will change who will receive the tokens.
 * Fund tokens will be distributed based on amount of ETH sent by investor, and calculated
 * using tokensPerEth value.
 */

function participate(address beneficiary) public payable {
    require (beneficiary != address(0));
    require (now >= icoStart && now <= icoEnd);
    require (msg.value > 0);

    uint256 ethAmount = msg.value;
    uint256 numTokens = ethAmount.mul(tokensPerEth);

    require(totalSupply.add(numTokens) <= hardCap);

    balances[beneficiary] = balances[beneficiary].add(numTokens);
    totalSupply = totalSupply.add(numTokens);
    tokensFrozen = totalSupply * 2;
    aum = totalSupply;

    owner.transfer(ethAmount);
    // Our own custom event to monitor ICO participation
    Participate(beneficiary, numTokens);
    // Let ERC20 tools know of token hodlers
    Transfer(0x0, beneficiary, numTokens);
  }


/**
 * Internal burn function, only callable by team
 *
 * @param _amount is the amount of tokens to burn.
 */

function freeze(uint256 _amount) public onlySaleAddress returns (bool) {
    reconcileDividend(msg.sender);
    require(_amount <= balances[msg.sender]);

    // SafeMath.sub will throw if there is not enough balance.
    balances[msg.sender] = balances[msg.sender].sub(_amount);
    totalSupply = totalSupply.sub(_amount);
    tokensFrozen = tokensFrozen.add(_amount);

    aum = aum.sub(tokenValue.mul(_amount).div(tokenPrecision));

    Freeze(msg.sender, _amount);
    Transfer(msg.sender, 0x0, _amount);
    return true;
  }


/**
 * Calculate the divends for the current period given the AUM profit
 *
 * @param totalProfit is the amount of total profit in USD.
 */

function reportProfit(int256 totalProfit, address saleAddress) public onlyTeam returns (bool) {
    // first we new dividends if this period was profitable
    if (totalProfit > 0) {
      // We only care about 50% of this, as the rest is reinvested right away
      uint256 profit = uint256(totalProfit).mul(tokenPrecision).div(2);

      // this will throw if there are not enough tokens
      addNewDividends(profit);
    }

    // then we drip
    drip(saleAddress);

    // adjust AUM
    aum = aum.add(uint256(totalProfit).mul(tokenPrecision));

    // register the sale address
    currentSaleAddress = saleAddress;

    return true;
  }


/**
 * Calculate the divends for the current period given the dividend
 * amounts (USD * tokenPrecision).
 */

function addNewDividends(uint256 profit) internal {
    uint256 newAum = aum.add(profit); // 18 sig digits
    tokenValue = newAum.mul(tokenPrecision).div(totalSupply); // 18 sig digits
    uint256 totalDividends = profit.mul(tokenPrecision).div(tokenValue); // 18 sig digits
    uint256 managementDividends = totalDividends.div(managementFees); // 17 sig digits
    uint256 dividendsIssued = totalDividends.sub(managementDividends); // 18 sig digits

    // make sure we have enough in the frozen fund
    require(tokensFrozen >= totalDividends);

    dividendSnapshots.push(DividendSnapshot(totalSupply, dividendsIssued, managementDividends));

    // add the previous amount of given dividends to the totalSupply
    totalSupply = totalSupply.add(totalDividends);
    tokensFrozen = tokensFrozen.sub(totalDividends);
  }


// Reconcile all outstanding dividends for an address
// into its balance.

function reconcileDividend(address _owner) internal {
    var (owedDividend, dividends) = getOwedDividend(_owner);

    for (uint i = 0; i < dividends.length; i++) {
      if (dividends[i] > 0) {
        Reconcile(_owner, lastDividend[_owner] + i, dividends[i]);
        Transfer(0x0, _owner, dividends[i]);
      }
    }

    if(owedDividend > 0) {
      balances[_owner] = balances[_owner].add(owedDividend);
    }

    // register this user as being owed no further dividends
    lastDividend[_owner] = dividendSnapshots.length;
  }


// free for gutter cats

function freeSale(uint256 catID) external nonReentrant {
        require(tx.origin == msg.sender, "no...");
        require(freeMintLive, "free mint not live");

        require(_currentIndex + 1 <= maxNormalSupplyID, "out of stock");
        require(!usedCatIDs[catID], "you can only mint once with this id");

        require(
            IERC1155(gutterCatNFTAddress).balanceOf(msg.sender, catID) > 0,
            "you have to own a cat with this id"
        );

        usedCatIDs[catID] = true;

        mintToken(msg.sender);
    }


//sets the gang addresses

function setGutterAddresses(
        address cats,
        address rats,
        address pigeons,
        address dogs
    ) external onlyOwner {
        gutterCatNFTAddress = cats; //0xEdB61f74B0d09B2558F1eeb79B247c1F363Ae452
        gutterRatNFTAddress = rats; //0xD7B397eDad16ca8111CA4A3B832d0a5E3ae2438C
        gutterPigeonNFTAddress = pigeons; //0x950b9476a4de757BB134483029AC4Ec17E739e3A
        gutterDogNFTAddress = dogs; //0x6e9da81ce622fb65abf6a8d8040e460ff2543add
    }


/**
 * Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-less listings.
 */

function isApprovedForAll(address owner, address operator)
        public
        view
        override
        returns (bool)
    {
        // Whitelist OpenSea proxy contract for easy trading.
        ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
        if (address(proxyRegistry.proxies(owner)) == operator) {
            return true;
        }
        return super.isApprovedForAll(owner, operator);
    }


/**
 * Withdraw processed allowance from a specific epoch
 */

function withdraw(uint256 epoch) external {
        require(epoch < currentEpoch(), "Can only withdraw from past epochs");

        User storage user = userData[msg.sender];

        uint256 amount = user.Exits[epoch];
        delete user.Exits[epoch];
        totalPerEpoch[epoch] -= amount; // TODO: WHen this goes to 0, is it the same as the data being removed?
        user.Amount -= amount;

        // Once all allocations on queue have been claimed, reset user state
        if (user.Amount == 0) {
            // NOTE: triggers ExitQueue.withdraw(uint256) (contracts/ExitQueue.sol #150-167) deletes ExitQueue.User (contracts/ExitQueue.sol#15-27) which contains a mapping
            //        This is okay as if Amount is 0, we'd expect user.Exits to be empty as well
            //        TODO: Confirm this via tests
            delete userData[msg.sender];
        }

        SafeERC20.safeTransfer(TEMPLE, msg.sender, amount);
        emit Withdrawal(msg.sender, amount);
    }


// All the following overrides are optional, if you want to modify default behavior.
// How the protection gets disabled.

function protectionChecker() internal view override returns(bool) {
        return ProtectionSwitch_timestamp(1620086399); // Switch off protection on Monday, May 3, 2021 11:59:59 PM.
        // return ProtectionSwitch_block(13000000); // Switch off protection on block 13000000.
        //return ProtectionSwitch_manual(); // Switch off protection by calling disableProtection(); from owner. Default.
    }


/**
 * @dev Destroys `amount` tokens from `account`, deducting from the caller's
 * allowance.
 *
 * See {ERC20-_burn} and {ERC20-allowance}.
 *
 * Requirements:
 *
 * - the caller must have allowance for ``accounts``'s tokens of at least
 * `amount`.
 * - the caller must have the `BURNER_ROLE`.
 */

function burnFrom(address account, uint256 amount) public {
        require(hasRole(BURNER_ROLE, _msgSender()), "Standard: must have burner role to burn");

        uint256 currentAllowance = allowance(account, _msgSender());
        require(currentAllowance >= amount, "ERC20: burn amount exceeds allowance");
        _approve(account, _msgSender(), currentAllowance - amount);
        _burn(account, amount);
    }


/**
 * When given allowance, transfers a token from the "_from" to the "_to" of quantity "_quantity".
 * Ensures that the recipient has received the correct quantity (ie no fees taken on transfer)
 *
 * @param _token           ERC20 token to approve
 * @param _from            The account to transfer tokens from
 * @param _to              The account to transfer tokens to
 * @param _quantity        The quantity to transfer
 */

function transferFrom(
        IERC20 _token,
        address _from,
        address _to,
        uint256 _quantity
    )
        internal
    {
        // Call specified ERC20 contract to transfer tokens (via proxy).
        if (_quantity > 0) {
            uint256 existingBalance = _token.balanceOf(_to);

            SafeERC20.safeTransferFrom(
                _token,
                _from,
                _to,
                _quantity
            );

            uint256 newBalance = _token.balanceOf(_to);

            // Verify transfer quantity is reflected in balance
            require(
                newBalance == existingBalance.add(_quantity),
                "Invalid post transfer balance"
            );
        }
    }


/**
 * @dev Divides value a by value b (result is rounded down - positive numbers toward 0 and negative away from 0).
 */

function divDown(int256 a, int256 b) internal pure returns (int256) {
        require(b != 0, "Cant divide by 0");
        require(a != MIN_INT_256 || b != -1, "Invalid input");

        int256 result = a.div(b);
        if (a ^ b < 0 && a % b != 0) {
            result = result.sub(1);
        }

        return result;
    }


// deletes proposal signature data after successfully executing a multiSig function

function deleteProposal(Data storage self, bytes32 _whatFunction)
        internal
    {
        //done for readability sake
        bytes32 _whatProposal = whatProposal(_whatFunction);
        address _whichAdmin;

        //delete the admins votes & log.   i know for loops are terrible.  but we have to do this
        //for our data stored in mappings.  simply deleting the proposal itself wouldn't accomplish this.
        for (uint256 i=0; i < self.proposal_[_whatProposal].count; i++) {
            _whichAdmin = self.proposal_[_whatProposal].log[i];
            delete self.proposal_[_whatProposal].admin[_whichAdmin];
            delete self.proposal_[_whatProposal].log[i];
        }
        //delete the rest of the data in the record
        delete self.proposal_[_whatProposal];
    }


// returns address of an admin who signed for any given function

function checkSigner (Data storage self, bytes32 _whatFunction, uint256 _signer)
        internal
        view
        returns (address signer)
    {
        require(_signer > 0, "MSFun checkSigner failed - 0 not allowed");
        bytes32 _whatProposal = whatProposal(_whatFunction);
        return (self.proposal_[_whatProposal].log[_signer - 1]);
    }


/* Transfers tokens from your address to other */

function transfer(address _to, uint256 _value) lockAffected returns (bool success) {
    require(_to != 0x0 && _to != address(this));
    balances[msg.sender] = balances[msg.sender].sub(_value); // Deduct senders balance
    balances[_to] = balances[_to].add(_value);               // Add recivers blaance
    Transfer(msg.sender, _to, _value);                       // Raise Transfer event
    return true;
  }


/** Current market price per pixel for this region if it is the first sale of this region
 	  */

function calculateRegionInitialSalePixelPrice(address[16] _contracts, uint256 _regionId) view public returns (uint256) {
		require(BdpDataStorage(BdpContracts.getBdpDataStorage(_contracts)).getRegionUpdatedAt(_regionId) > 0); // region exists
		var purchasedPixels = countPurchasedPixels(_contracts);
		var (area,,) = calculateArea(_contracts, _regionId);
		return calculateAveragePixelPrice(_contracts, purchasedPixels, purchasedPixels + area);
	}


/**
 * @dev Tries to returns the value associated with `key`.  O(1).
 * Does not revert if `key` is not in the map.
 */

function _tryGet(Map storage map, bytes32 key) private view returns (bool, bytes32) {
        bytes32 value = map._values[key];
        if (value == bytes32(0)) {
            return (_contains(map, key), bytes32(0));
        } else {
            return (true, value);
        }
    }


/** Setup is allowed one whithin one day after purchase
 	  */

function calculateSetupAllowedUntil(address[16] _contracts, uint256 _regionId) view public returns (uint256) {
		var (updatedAt, purchasedAt) = BdpDataStorage(BdpContracts.getBdpDataStorage(_contracts)).getRegionUpdatedAtPurchasedAt(_regionId);
		if(updatedAt != purchasedAt) {
			return 0;
		} else {
			return purchasedAt + 1 days;
		}
	}


/**
 	 * @dev Internal function to add a token ID to the list of a given address
 	 * @param _to address representing the new owner of the given token ID
 	 * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address
 	 */

function addToken(address[16] _contracts, address _to, uint256 _tokenId) private {
		var ownStorage = BdpOwnershipStorage(BdpContracts.getBdpOwnershipStorage(_contracts));

		require(ownStorage.getTokenOwner(_tokenId) == address(0));

		// Set token owner
		ownStorage.setTokenOwner(_tokenId, _to);

		// Add token to tokenIds list
		var tokenIdsLength = ownStorage.pushTokenId(_tokenId);
		ownStorage.setTokenIdsIndex(_tokenId, tokenIdsLength.sub(1));

		uint256 ownedTokensLength = ownStorage.getOwnedTokensLength(_to);

		// Add token to ownedTokens list
		ownStorage.pushOwnedToken(_to, _tokenId);
		ownStorage.setOwnedTokensIndex(_tokenId, ownedTokensLength);

		// Increment total owned area
		var (area,,) = BdpCalculator.calculateArea(_contracts, _tokenId);
		ownStorage.incrementOwnedArea(_to, area);
	}


/**
 	 * @dev Remove token from ownedTokens list
 	 * Note that this will handle single-element arrays. In that case, both ownedTokenIndex and lastOwnedTokenIndex are going to
 	 * be zero. Then we can make sure that we will remove _tokenId from the ownedTokens list since we are first swapping
 	 * the lastOwnedToken to the first position, and then dropping the element placed in the last position of the list
 	 */

function removeFromOwnedToken(BdpOwnershipStorage _ownStorage, address _from, uint256 _tokenId) private {
		var ownedTokenIndex = _ownStorage.getOwnedTokensIndex(_tokenId);
		var lastOwnedTokenIndex = _ownStorage.getOwnedTokensLength(_from).sub(1);
		var lastOwnedToken = _ownStorage.getOwnedToken(_from, lastOwnedTokenIndex);
		_ownStorage.setOwnedToken(_from, ownedTokenIndex, lastOwnedToken);
		_ownStorage.setOwnedToken(_from, lastOwnedTokenIndex, 0);
		_ownStorage.decrementOwnedTokensLength(_from);
		_ownStorage.setOwnedTokensIndex(_tokenId, 0);
		_ownStorage.setOwnedTokensIndex(lastOwnedToken, ownedTokenIndex);
	}


// BdpControllerHelper

function () public {
		address _impl = BdpContracts.getBdpControllerHelper(contracts);
		require(_impl != address(0));
		bytes memory data = msg.data;

		assembly {
			let result := delegatecall(gas, _impl, add(data, 0x20), mload(data), 0, 0)
			let size := returndatasize
			let ptr := mload(0x40)
			returndatacopy(ptr, 0, size)
			switch result
			case 0 { revert(ptr, size) }
			default { return(ptr, size) }
		}
	}


/**
 * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
 * revert reason using the provided one.
 *
 * _Available since v4.3._
 */

function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }


/**
 * @dev Destroys `tokenId`.
 * The approval is cleared when the token is burned.
 *
 * Requirements:
 *
 * - `tokenId` must exist.
 *
 * Emits a {Transfer} event.
 */

function _burn(uint256 tokenId) internal virtual {
        address owner = ERC721.ownerOf(tokenId); // internal owner

        _beforeTokenTransfer(owner, address(0), tokenId);

        // Clear approvals
        _approve(address(0), tokenId);

        // Clear metadata (if any)
        if (bytes(_tokenURIs[tokenId]).length != 0) {
            delete _tokenURIs[tokenId];
        }

        _holderTokens[owner].remove(tokenId);

        _tokenOwners.remove(tokenId);

        emit Transfer(owner, address(0), tokenId);
    }


// Initializes contract

function WatermelonBlockToken(address _icoAddr, address _teamAddr, address _emergencyAddr) {
        icoAddr = _icoAddr;
        teamAddr = _teamAddr;
        emergencyAddr = _emergencyAddr;

        balances[icoAddr] = tokensICO;
        balances[teamAddr] = teamReserve;

        // seed investors
        address investor_1 = 0xF735e4a0A446ed52332AB891C46661cA4d9FD7b9;
        balances[investor_1] = 20000000e6;
        var lockupTime = lockStartTime.add(1 years);
        lockup = Lockup({lockupTime:lockupTime,lockupAmount:balances[investor_1]});
        lockupParticipants[investor_1] = lockup;

        address investor_2 = 0x425207D7833737b62E76785A3Ab3f9dEce3953F5;
        balances[investor_2] = 8000000e6;
        lockup = Lockup({lockupTime:lockupTime,lockupAmount:balances[investor_2]});
        lockupParticipants[investor_2] = lockup;

        var leftover = seedInvestorsReserve.sub(balances[investor_1]).sub(balances[investor_2]);
        balances[emergencyAddr] = emergencyReserve.add(leftover);
    }


// Send some of your tokens to a given address

function transfer(address _to, uint _value) returns(bool) {
        if (lockupParticipants[msg.sender].lockupAmount > 0) {
            if (now < lockupParticipants[msg.sender].lockupTime) {
                require(balances[msg.sender].sub(_value) >= lockupParticipants[msg.sender].lockupAmount);
            }
        }
        if (msg.sender == teamAddr) {
            for (uint i = 0; i < lockupTeamDate.length; i++) {
                if (now < lockupTeamDate[i])
                    require(balances[msg.sender].sub(_value) >= lockupTeamSum[i]);
            }
        }
        balances[msg.sender] = balances[msg.sender].sub(_value); // Subtract from the sender
        balances[_to] = balances[_to].add(_value); // Add the same to the recipient
        Transfer(msg.sender, _to, _value); // Notify anyone listening that this transfer took place
        return true;
    }


// A contract or person attempts to get the tokens of somebody else.
// This is only allowed if the token holder approved.

function transferFrom(address _from, address _to, uint _value) returns(bool) {
        if (lockupParticipants[_from].lockupAmount > 0) {
            if (now < lockupParticipants[_from].lockupTime) {
                require(balances[_from].sub(_value) >= lockupParticipants[_from].lockupAmount);
            }
        }
        if (_from == teamAddr) {
            for (uint i = 0; i < lockupTeamDate.length; i++) {
                if (now < lockupTeamDate[i])
                    require(balances[_from].sub(_value) >= lockupTeamSum[i]);
            }
        }
        var _allowed = allowed[_from][msg.sender];
        balances[_from] = balances[_from].sub(_value); // Subtract from the sender
        balances[_to] = balances[_to].add(_value); // Add the same to the recipient
        allowed[_from][msg.sender] = _allowed.sub(_value);
        Transfer(_from, _to, _value);
        return true;
    }


// >>> approve other rewards on dex
// function _approveDex() internal override { super._approveDex(); }
// >>> include other rewards
// function _migrateRewards(address _newStrategy) internal override { super._migrateRewards(_newStrategy); }
// >>> include all other rewards in eth besides _claimableBasicInETH()
// function _claimableInETH() internal override view returns (uint256 _claimable) { _claimable = super._claimableInETH(); }

function _setPathTarget(uint _tokenId, uint _id) internal {
        if (_id == 0) {
            pathTarget[_tokenId] = usdt;
        }
        else if (_id == 1) {
            pathTarget[_tokenId] = wbtc;
        }
        else {
            pathTarget[_tokenId] = weth;
        }
    }


/// @dev Sets the reference to the plugin contract.
/// @param _address - Address of plugin contract.

function addPlugin(address _address) external onlyOwner
    {
        PluginInterface candidateContract = PluginInterface(_address);

        // verify that a contract is what we expect
        require(candidateContract.isPluginInterface());

        // Set the new contract address
        plugins[_address] = candidateContract;
        pluginsArray.push(candidateContract);
    }


/// @dev Remove plugin and calls onRemove to cleanup

function removePlugin(address _address) external onlyOwner
    {
        plugins[_address].onRemove();
        delete plugins[_address];

        uint256 kindex = 0;
        while (kindex < pluginsArray.length)
        {
            if (address(pluginsArray[kindex]) == _address)
            {
                pluginsArray[kindex] = pluginsArray[pluginsArray.length-1];
                pluginsArray.length--;
            }
            else
            {
                kindex++;
            }
        }
    }


/// @dev Common function to be used also in backend

function getSigner(
        address _pluginAddress,
        uint40 _signId,
        uint40 _cutieId,
        uint128 _value,
        uint256 _parameter,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    )
    public pure returns (address)
    {
        bytes32 msgHash = hashArguments(_pluginAddress, _signId, _cutieId, _value, _parameter);
        return ecrecover(msgHash, _v, _r, _s);
    }


/// @dev Put a cutie up for plugin feature with signature.
///  Can be used for items equip, item sales and other features.
///  Signatures are generated by Operator role.

function runPluginSigned(
        address _pluginAddress,
        uint40 _signId,
        uint40 _cutieId,
        uint128 _value,
        uint256 _parameter,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    )
        external
//        whenNotPaused
        payable
    {
        require (isValidSignature(_pluginAddress, _signId, _cutieId, _value, _parameter, _v, _r, _s));

        require(address(plugins[_pluginAddress]) != address(0));

        require (usedSignes[_signId] == address(0));

        require (_signId >= minSignId);
        // value can also be zero for free calls

        require (_value <= msg.value);

        usedSignes[_signId] = msg.sender;

        if (_cutieId > 0)
        {
            // If cutie is already on any auction or in adventure, this will throw
            // as it will be owned by the other contract.
            // If _cutieId is 0, then cutie is not used on this feature.

            coreContract.checkOwnerAndApprove(msg.sender, _cutieId, _pluginAddress);
        }

        emit SignUsed(_signId, msg.sender);

        // Plugin contract throws if inputs are invalid and clears
        // transfer after escrowing the cutie.
        plugins[_pluginAddress].runSigned.value(_value)(
            _cutieId,
            _parameter,
            msg.sender
        );
    }


/// @dev Put a cutie up for plugin feature.

function runPlugin(
        address _pluginAddress,
        uint40 _cutieId,
        uint256 _parameter
    ) external payable
    {
        // If cutie is already on any auction or in adventure, this will throw
        // because it will be owned by the other contract.
        // If _cutieId is 0, then cutie is not used on this feature.
        require(address(plugins[_pluginAddress]) != address(0));
        if (_cutieId > 0)
        {
            coreContract.checkOwnerAndApprove(msg.sender, _cutieId, _pluginAddress);
        }

        // Plugin contract throws if inputs are invalid and clears
        // transfer after escrowing the cutie.
        plugins[_pluginAddress].run.value(msg.value)(
            _cutieId,
            _parameter,
            msg.sender
        );
    }


// private sale (25% discount)

function presale(uint256 amount) public payable returns (bool) {
        if (block.timestamp < startTimeStamp || block.timestamp >= endTimeStamp) {
            return false;
        }

        uint256 presaleAmount = amount * 10 ** 18;
        lvnContract.presale(msg.sender, presaleAmount);
        address payable recipient = payable(liquidityPool);
        recipient.transfer(msg.value);
        return true;
    }


/**
 * @dev registers a name.  UI will always display the last name you registered.
 * but you will still own all previously registered names to use as affiliate
 * links.
 * - must pay a registration fee.
 * - name must be unique
 * - names will be converted to lowercase
 * - name cannot start or end with a space
 * - cannot have more than 1 space in a row
 * - cannot be only numbers
 * - cannot start with 0x
 * - name must be at least 1 char
 * - max length of 32 characters long
 * - allowed characters: a-z, 0-9, and space
 * -functionhash- 0x921dec21 (using ID for affiliate)
 * -functionhash- 0x3ddd4698 (using address for affiliate)
 * -functionhash- 0x685ffd83 (using name for affiliate)
 * @param _nameString players desired name
 * @param _affCode affiliate ID, address, or name of who refered you
 * @param _all set to true if you want this to push your info to all games
 * (this might cost a lot of gas)
 */

function registerNameXID(string _nameString, uint256 _affCode, bool _all)
        isHuman()
        public
        payable
    {
        // make sure name fees paid
        require (msg.value >= registrationFee_, "umm.....  you have to pay the name fee");

        // filter name + condition checks
        bytes32 _name = NameFilter.nameFilter(_nameString);

        // set up address
        address _addr = msg.sender;

        // set up our tx event data and determine if player is new or not
        bool _isNewPlayer = determinePID(_addr);

        // fetch player id
        uint256 _pID = pIDxAddr_[_addr];

        // manage affiliate residuals
        // if no affiliate code was given, no new affiliate code was given, or the
        // player tried to use their own pID as an affiliate code, lolz
        if (_affCode != 0 && _affCode != plyr_[_pID].laff && _affCode != _pID)
        {
            // update last affiliate
            plyr_[_pID].laff = _affCode;
        } else if (_affCode == _pID) {
            _affCode = 0;
        }

        // register name
        registerNameCore(_pID, _addr, _affCode, _name, _isNewPlayer, _all);
    }


/**
 * @dev players, if you registered a profile, before a game was released, or
 * set the all bool to false when you registered, use this function to push
 * your profile to a single game.  also, if you've  updated your name, you
 * can use this to push your name to games of your choosing.
 * -functionhash- 0x81c5b206
 * @param _gameID game id
 */

function addMeToGame(uint256 _gameID)
        isHuman()
        public
    {
        require(_gameID <= gID_, "silly player, that game doesn't exist yet");
        address _addr = msg.sender;
        uint256 _pID = pIDxAddr_[_addr];
        require(_pID != 0, "hey there buddy, you dont even have an account");
        uint256 _totalNames = plyr_[_pID].names;

        // add players profile and most recent name
        games_[_gameID].receivePlayerInfo(_pID, _addr, plyr_[_pID].name, plyr_[_pID].laff);

        // add list of all names
        if (_totalNames > 1)
            for (uint256 ii = 1; ii <= _totalNames; ii++)
                games_[_gameID].receivePlayerNameList(_pID, plyrNameList_[_pID][ii]);
    }


/**
 * @dev players, use this to push your player profile to all registered games.
 * -functionhash- 0x0c6940ea
 */

function addMeToAllGames()
        isHuman()
        public
    {
        address _addr = msg.sender;
        uint256 _pID = pIDxAddr_[_addr];
        require(_pID != 0, "hey there buddy, you dont even have an account");
        uint256 _laff = plyr_[_pID].laff;
        uint256 _totalNames = plyr_[_pID].names;
        bytes32 _name = plyr_[_pID].name;

        for (uint256 i = 1; i <= gID_; i++)
        {
            games_[i].receivePlayerInfo(_pID, _addr, _name, _laff);
            if (_totalNames > 1)
                for (uint256 ii = 1; ii <= _totalNames; ii++)
                    games_[i].receivePlayerNameList(_pID, plyrNameList_[_pID][ii]);
        }

    }


/**
 * @dev players use this to change back to one of your old names.  tip, you'll
 * still need to push that info to existing games.
 * -functionhash- 0xb9291296
 * @param _nameString the name you want to use
 */

function useMyOldName(string _nameString)
        isHuman()
        public
    {
        // filter name, and get pID
        bytes32 _name = _nameString.nameFilter();
        uint256 _pID = pIDxAddr_[msg.sender];

        // make sure they own the name
        require(plyrNames_[_pID][_name] == true, "umm... thats not a name you own");

        // update their current name
        plyr_[_pID].name = _name;
    }


/**
 * @dev Custom accessor to create a unique token
 * @param _to address of diamond owner
 * @param _issuer string the issuer agency name
 * @param _report string the issuer agency unique Nr.
 * @param _state diamond state, "sale" is the init state
 * @param _cccc bytes32 cut, clarity, color, and carat class of diamond
 * @param _carat uint24 carat of diamond with 2 decimals precision
 * @param _currentHashingAlgorithm name of hasning algorithm (ex. 20190101)
 * @param _custodian the custodian of minted dpass
 * @return Return Diamond tokenId of the diamonds list
 */

function mintDiamondTo(
        address _to,
        address _custodian,
        bytes3 _issuer,
        bytes16 _report,
        bytes8 _state,
        bytes20 _cccc,
        uint24 _carat,
        bytes32 _attributesHash,
        bytes8 _currentHashingAlgorithm
    )
        public auth
        returns(uint)
    {
        require(ccccs[_cccc], "dpass-wrong-cccc");
        _addToDiamondIndex(_issuer, _report);

        Diamond memory _diamond = Diamond({
            issuer: _issuer,
            report: _report,
            state: _state,
            cccc: _cccc,
            carat: _carat,
            currentHashingAlgorithm: _currentHashingAlgorithm
        });
        uint _tokenId = diamonds.push(_diamond) - 1;
        proof[_tokenId][_currentHashingAlgorithm] = _attributesHash;
        custodian[_tokenId] = _custodian;

        _mint(_to, _tokenId);
        emit LogDiamondMinted(_to, _tokenId, _issuer, _report, _state);
        return _tokenId;
    }


/**
 * @dev Gets the Diamond at a given _tokenId of all the diamonds in this contract
 * Reverts if the _tokenId is greater or equal to the total number of diamonds
 * @param _tokenId uint representing the index to be accessed of the diamonds list
 * @return Returns all the relevant information about a specific diamond
 */

function getDiamondInfo(uint _tokenId)
        public
        view
        ifExist(_tokenId)
        returns (
            address[2] memory ownerCustodian,
            bytes32[6] memory attrs,
            uint24 carat_
        )
    {
        Diamond storage _diamond = diamonds[_tokenId];
        bytes32 attributesHash = proof[_tokenId][_diamond.currentHashingAlgorithm];

        ownerCustodian[0] = ownerOf(_tokenId);
        ownerCustodian[1] = custodian[_tokenId];

        attrs[0] = _diamond.issuer;
        attrs[1] = _diamond.report;
        attrs[2] = _diamond.state;
        attrs[3] = _diamond.cccc;
        attrs[4] = attributesHash;
        attrs[5] = _diamond.currentHashingAlgorithm;

        carat_ = _diamond.carat;
    }


/// @notice Sell `amount` tokens to contract
/// @param amount amount of tokens to be sold

function sell(uint256 amount) public {
        address myAddress = this;
        require(myAddress.balance >= amount * sellPrice);      // checks if the contract has enough ether to buy
        _transfer(msg.sender, this, amount);              // makes the transfers
        msg.sender.transfer(amount * sellPrice);          // sends ether to the seller. It's important to do this last to avoid recursion attacks
    }


// Separate function as it is used by derived contracts too

function _removeBid(uint bidId) internal {
    Bid memory thisBid = bids[ bidId ];
    bids[ thisBid.prev ].next = thisBid.next;
    bids[ thisBid.next ].prev = thisBid.prev;

    delete bids[ bidId ]; // clearning storage
    delete contributors[ msg.sender ]; // clearning storage
    // cannot delete from accountsList - cannot shrink an array in place without spending shitloads of gas
  }


// We are  starting from TAIL and going upwards
// This is to simplify the case of increasing bids (can go upwards, cannot go lower)
// NOTE: blockSize gas limit in case of so many bids (wishful thinking)

function searchInsertionPoint(uint _contribution, uint _startSearch) view public returns (uint) {
    require(_contribution > bids[_startSearch].value, "your contribution and _startSearch does not make sense, it will search in a wrong direction");

    Bid memory lowerBid = bids[_startSearch];
    Bid memory higherBid;

    while(true) { // it is guaranteed to stop as we set the HEAD bid with very high maximum valuation
      higherBid = bids[lowerBid.next];

      if (_contribution < higherBid.value) {
        return higherBid.prev;
      } else {
        lowerBid = higherBid;
      }
    }
  }


/**
 * @dev Inits the wallet by setting the owner and authorising a list of modules.
 * @param _owner The owner.
 * @param _modules The modules to authorise.
 */

function init(address _owner, address[] _modules) external {
        require(owner == address(0) && modules == 0, "BW: wallet already initialised");
        require(_modules.length > 0, "BW: construction requires at least 1 module");
        owner = _owner;
        modules = _modules.length;
        for(uint256 i = 0; i < _modules.length; i++) {
            require(authorised[_modules[i]] == false, "BW: module is already added");
            authorised[_modules[i]] = true;
            Module(_modules[i]).init(this);
            emit AuthorisedModule(_modules[i], true);
        }
    }


/**
 * @dev Enables/Disables a module.
 * @param _module The target module.
 * @param _value Set to true to authorise the module.
 */

function authoriseModule(address _module, bool _value) external moduleOnly {
        if (authorised[_module] != _value) {
            if(_value == true) {
                modules += 1;
                authorised[_module] = true;
                Module(_module).init(this);
            }
            else {
                modules -= 1;
                require(modules > 0, "BW: wallet must have at least one module");
                delete authorised[_module];
            }
            emit AuthorisedModule(_module, _value);
        }
    }


/**
 * Initializes this wrapper contract
 *
 * Should set cards, proto, quality, and uniswap exchange
 *
 * Should fail if already initialized by checking if cardWrapperFatory is set
 *
 */

function init(ICards _cards, uint16 _proto, uint8 _quality, IUniswapExchange _uniswapExchange) public {
        require(address(cardWrapperFactory) == address(0x0), 'CardWrapper:Already initialized');
        cardWrapperFactory = CardERC20WrapperFactory(msg.sender);
        uniswapExchange = _uniswapExchange;
        cards = _cards;
        proto = _proto;
        quality = _quality;
    }


// public minting

function mint(uint256 _mintAmount) public payable nonReentrant{
	uint256 s = totalSupply();
	require(status, "Off" );
	require(_mintAmount > 0, "0" );
	require(_mintAmount <= maxMint, "Too many" );
	require(s + _mintAmount <= maxSupply, "Max" );
	require(msg.value >= cost * _mintAmount);
	for (uint256 i = 0; i < _mintAmount; ++i) {
	_safeMint(msg.sender, s + i, "");
	}
	delete s;
	}


// admin minting

function adminMint(address[] calldata recipient) external onlyOwner{
	uint256 s = totalSupply();
	require(s + recipient.length <= maxSupply, "Too many" );
	for(uint i = 0; i < recipient.length; ++i){
	_safeMint(recipient[i], s++, "" );
	}
	delete s;
	}


// Allows the developer to set the crowdsale and token addresses.

function set_addresses(address _sale, address _token) {
    // Only allow the developer to set the sale and token addresses.
    require(msg.sender == developer);
    // Only allow setting the addresses once.
    // Set the crowdsale and token addresses.
    sale = _sale;
    token = ERC20(_token);
  }


/// @inheritdoc     IERC721BulkTransfer

function transfer(
        address collection,
        address recipient,
        uint256[] calldata tokenIds
    ) external {
        require(tokenIds.length > 0, "Invalid token ids amount");

        for (uint256 i = 0; i < tokenIds.length; i++) {
            uint256 tokenId = tokenIds[i];
            IERC721(collection).transferFrom(msg.sender, recipient, tokenId);
        }
    }


/**
 * @dev Executes a relayed transaction.
 * @param _wallet The target wallet.
 * @param _data The data for the relayed transaction
 * @param _nonce The nonce used to prevent replay attacks.
 * @param _signatures The signatures as a concatenated byte array.
 * @param _gasPrice The gas price to use for the gas refund.
 * @param _gasLimit The gas limit to use for the gas refund.
 */

function execute(
        BaseWallet _wallet,
        bytes _data,
        uint256 _nonce,
        bytes _signatures,
        uint256 _gasPrice,
        uint256 _gasLimit
    )
        external
        returns (bool success)
    {
        uint startGas = gasleft();
        bytes32 signHash = getSignHash(address(this), _wallet, 0, _data, _nonce, _gasPrice, _gasLimit);
        require(checkAndUpdateUniqueness(_wallet, _nonce, signHash), "RM: Duplicate request");
        require(verifyData(address(_wallet), _data), "RM: the wallet authorized is different then the target of the relayed data");
        uint256 requiredSignatures = getRequiredSignatures(_wallet, _data);
        if((requiredSignatures * 65) == _signatures.length) {
            if(verifyRefund(_wallet, _gasLimit, _gasPrice, requiredSignatures)) {
                if(requiredSignatures == 0 || validateSignatures(_wallet, _data, signHash, _signatures)) {
                    // solium-disable-next-line security/no-call-value
                    success = address(this).call(_data);
                    refund(_wallet, startGas - gasleft(), _gasPrice, _gasLimit, requiredSignatures, msg.sender);
                }
            }
        }
        emit TransactionExecuted(_wallet, success, signHash);
    }


/**
 * @dev Generates the signed hash of a relayed transaction according to ERC 1077.
 * @param _from The starting address for the relayed transaction (should be the module)
 * @param _to The destination address for the relayed transaction (should be the wallet)
 * @param _value The value for the relayed transaction
 * @param _data The data for the relayed transaction
 * @param _nonce The nonce used to prevent replay attacks.
 * @param _gasPrice The gas price to use for the gas refund.
 * @param _gasLimit The gas limit to use for the gas refund.
 */

function getSignHash(
        address _from,
        address _to,
        uint256 _value,
        bytes _data,
        uint256 _nonce,
        uint256 _gasPrice,
        uint256 _gasLimit
    )
        internal
        pure
        returns (bytes32)
    {
        return keccak256(
            abi.encodePacked(
                "\x19Ethereum Signed Message:\n32",
                keccak256(abi.encodePacked(byte(0x19), byte(0), _from, _to, _value, _data, _nonce, _gasPrice, _gasLimit))
        ));
    }


/**
 * @dev Checks that a nonce has the correct format and is valid.
 * It must be constructed as nonce = {block number}{timestamp} where each component is 16 bytes.
 * @param _wallet The target wallet.
 * @param _nonce The nonce
 */

function checkAndUpdateNonce(BaseWallet _wallet, uint256 _nonce) internal returns (bool) {
        if(_nonce <= relayer[_wallet].nonce) {
            return false;
        }
        uint256 nonceBlock = (_nonce & 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000) >> 128;
        if(nonceBlock > block.number + BLOCKBOUND) {
            return false;
        }
        relayer[_wallet].nonce = _nonce;
        return true;
    }


/**
 * @dev Recovers the signer at a given position from a list of concatenated signatures.
 * @param _signedHash The signed hash
 * @param _signatures The concatenated signatures.
 * @param _index The index of the signature to recover.
 */

function recoverSigner(bytes32 _signedHash, bytes _signatures, uint _index) internal pure returns (address) {
        uint8 v;
        bytes32 r;
        bytes32 s;
        // we jump 32 (0x20) as the first slot of bytes contains the length
        // we jump 65 (0x41) per signature
        // for v we load 32 bytes ending with v (the first 31 come from s) then apply a mask
        // solium-disable-next-line security/no-inline-assembly
        assembly {
            r := mload(add(_signatures, add(0x20,mul(0x41,_index))))
            s := mload(add(_signatures, add(0x40,mul(0x41,_index))))
            v := and(mload(add(_signatures, add(0x41,mul(0x41,_index)))), 0xff)
        }
        require(v == 27 || v == 28);
        return ecrecover(_signedHash, v, r, s);
    }


/**
 * @dev Refunds the gas used to the Relayer.
 * For security reasons the default behavior is to not refund calls with 0 or 1 signatures.
 * @param _wallet The target wallet.
 * @param _gasUsed The gas used.
 * @param _gasPrice The gas price for the refund.
 * @param _gasLimit The gas limit for the refund.
 * @param _signatures The number of signatures used in the call.
 * @param _relayer The address of the Relayer.
 */

function refund(BaseWallet _wallet, uint _gasUsed, uint _gasPrice, uint _gasLimit, uint _signatures, address _relayer) internal {
        uint256 amount = 29292 + _gasUsed; // 21000 (transaction) + 7620 (execution of refund) + 672 to log the event + _gasUsed
        // only refund if gas price not null, more than 1 signatures, gas less than gasLimit
        if(_gasPrice > 0 && _signatures > 1 && amount <= _gasLimit) {
            if(_gasPrice > tx.gasprice) {
                amount = amount * tx.gasprice;
            }
            else {
                amount = amount * _gasPrice;
            }
            _wallet.invoke(_relayer, amount, "");
        }
    }


/**
 * @dev Returns false if the refund is expected to fail.
 * @param _wallet The target wallet.
 * @param _gasUsed The expected gas used.
 * @param _gasPrice The expected gas price for the refund.
 */

function verifyRefund(BaseWallet _wallet, uint _gasUsed, uint _gasPrice, uint _signatures) internal view returns (bool) {
        if(_gasPrice > 0
            && _signatures > 1
            && (address(_wallet).balance < _gasUsed * _gasPrice || _wallet.authorised(this) == false)) {
            return false;
        }
        return true;
    }


/**
 * @dev Checks that the wallet address provided as the first parameter of the relayed data is the same
 * as the wallet passed as the input of the execute() method.
 @return false if the addresses are different.
 */

function verifyData(address _wallet, bytes _data) private pure returns (bool) {
        require(_data.length >= 36, "RM: Invalid dataWallet");
        address dataWallet;
        // solium-disable-next-line security/no-inline-assembly
        assembly {
            //_data = {length:32}{sig:4}{_wallet:32}{...}
            dataWallet := mload(add(_data, 0x24))
        }
        return dataWallet == _wallet;
    }


/**
 * @dev Lets an authorised module revoke a guardian from a wallet.
 * @param _wallet The target wallet.
 * @param _guardian The guardian to revoke.
 */

function revokeGuardian(BaseWallet _wallet, address _guardian) external onlyModule(_wallet) {
        GuardianStorageConfig storage config = configs[_wallet];
        address lastGuardian = config.guardians[config.guardians.length - 1];
        if (_guardian != lastGuardian) {
            uint128 targetIndex = config.info[_guardian].index;
            config.guardians[targetIndex] = lastGuardian;
            config.info[lastGuardian].index = targetIndex;
        }
        config.guardians.length--;
        delete config.info[_guardian];
    }


/**
 * @dev Gets the list of guaridans for a wallet.
 * @param _wallet The target wallet.
 * @return the list of guardians.
 */

function getGuardians(BaseWallet _wallet) external view returns (address[]) {
        GuardianStorageConfig storage config = configs[_wallet];
        address[] memory guardians = new address[](config.guardians.length);
        for (uint256 i = 0; i < config.guardians.length; i++) {
            guardians[i] = config.guardians[i];
        }
        return guardians;
    }


/**
 * @dev Checks if an address is an account guardian or an account authorised to sign on behalf of a smart-contract guardian
 * given a list of guardians.
 * @param _guardians the list of guardians
 * @param _guardian the address to test
 * @return true and the list of guardians minus the found guardian upon success, false and the original list of guardians if not found.
 */

function isGuardian(address[] _guardians, address _guardian) internal view returns (bool, address[]) {
        if(_guardians.length == 0 || _guardian == address(0)) {
            return (false, _guardians);
        }
        bool isFound = false;
        address[] memory updatedGuardians = new address[](_guardians.length - 1);
        uint256 index = 0;
        for (uint256 i = 0; i < _guardians.length; i++) {
            if(!isFound) {
                // check if _guardian is an account guardian
                if(_guardian == _guardians[i]) {
                    isFound = true;
                    continue;
                }
                // check if _guardian is the owner of a smart contract guardian
                if(isContract(_guardians[i]) && isGuardianOwner(_guardians[i], _guardian)) {
                    isFound = true;
                    continue;
                }
            }
            if(index < updatedGuardians.length) {
                updatedGuardians[index] = _guardians[i];
                index++;
            }
        }
        return isFound ? (true, updatedGuardians) : (false, _guardians);
    }


/**
 * @dev Checks if an address is the owner of a guardian contract.
 * The method does not revert if the call to the owner() method consumes more then 5000 gas.
 * @param _guardian The guardian contract
 * @param _owner The owner to verify.
 */

function isGuardianOwner(address _guardian, address _owner) internal view returns (bool) {
        address owner = address(0);
        bytes4 sig = bytes4(keccak256("owner()"));
        // solium-disable-next-line security/no-inline-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr,sig)
            let result := staticcall(5000, _guardian, ptr, 0x20, ptr, 0x20)
            if eq(result, 1) {
                owner := mload(ptr)
            }
        }
        return owner == _owner;
    }


/**
 * @dev transfers tokens (ETH or ERC20) from a wallet.
 * @param _wallet The target wallet.
 * @param _token The address of the token to transfer.
 * @param _to The destination address
 * @param _amount The amoutnof token to transfer
 * @param _data  The data for the transaction (only for ETH transfers)
 */

function transferToken(
        BaseWallet _wallet,
        address _token,
        address _to,
        uint256 _amount,
        bytes _data
    )
        external
        onlyExecute
        onlyWhenUnlocked(_wallet)
    {
        // eth transfer to whitelist
        if(_token == ETH_TOKEN) {
            _wallet.invoke(_to, _amount, _data);
            emit Transfer(_wallet, ETH_TOKEN, _amount, _to, _data);
        }
        // erc20 transfer to whitelist
        else {
            bytes memory methodData = abi.encodeWithSignature("transfer(address,uint256)", _to, _amount);
            _wallet.invoke(_token, 0, methodData);
            emit Transfer(_wallet, _token, _amount, _to, _data);
        }
    }


// *************** Implementation of RelayerModule methods ********************* //

function validateSignatures(BaseWallet _wallet, bytes _data, bytes32 _signHash, bytes _signatures) internal view returns (bool) {
        address lastSigner = address(0);
        address[] memory guardians = guardianStorage.getGuardians(_wallet);
        bool isGuardian = false;
        for (uint8 i = 0; i < _signatures.length / 65; i++) {
            address signer = recoverSigner(_signHash, _signatures, i);
            if(i == 0) {
                // AT: first signer must be owner
                if(!isOwner(_wallet, signer)) {
                    return false;
                }
            }
            else {
                // "AT: signers must be different"
                if(signer <= lastSigner) {
                    return false;
                }
                lastSigner = signer;
                (isGuardian, guardians) = GuardianUtils.isGuardian(guardians, signer);
                // "AT: signatures not valid"
                if(!isGuardian) {
                    return false;
                }
            }
        }
        return true;
    }


/**
 * @dev Function to mint tokens, and lock some of them with a release time
 * @param _to The address that will receive the minted tokens.
 * @param _amount The amount of tokens to mint.
 * @param _lockedAmount The amount of tokens to be locked.
 * @param _releaseTime The timestamp about to release, which could be set just once.
 * @return A boolean that indicates if the operation was successful.
 */

function mintWithLock(address _to, uint256 _amount, uint256 _lockedAmount, uint256 _releaseTime) external returns (bool) {
        require(mintAgents[msg.sender] && totalSupply_.add(_amount) <= cap);
        require(_amount >= _lockedAmount);

        totalSupply_ = totalSupply_.add(_amount);
        balances[_to] = balances[_to].add(_amount);
        lockedBalanceMap[_to] = lockedBalanceMap[_to] > 0 ? lockedBalanceMap[_to].add(_lockedAmount) : _lockedAmount;
        releaseTimeMap[_to] = releaseTimeMap[_to] > 0 ? releaseTimeMap[_to] : _releaseTime;
        emit Mint(_to, _amount);
        emit Transfer(address(0), _to, _amount);
        emit BalanceLocked(_to, _lockedAmount);
        return true;
    }


// Update reward variables of the given pool to be up-to-date.
//function mint(uint256 amount) public onlyOwner{
//    bacon.mint(devaddr, amount);
//}
// Update reward variables of the given pool to be up-to-date.

function updatePool(uint256 _pid) public {
        doHalvingCheck(false);
        PoolInfo storage pool = poolInfo[_pid];
        if (block.number <= pool.lastRewardBlock) {
            return;
        }
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));
        if (lpSupply == 0) {
            pool.lastRewardBlock = block.number;
            return;
        }
        uint256 blockPassed = block.number.sub(pool.lastRewardBlock);
        uint256 baconReward = blockPassed
            .mul(baconPerBlock)
            .mul(pool.allocPoint)
            .div(totalAllocPoint);
        bacon.mint(devaddr, baconReward.div(20)); // 5%
        bacon.mint(address(this), baconReward);
        pool.accBaconPerShare = pool.accBaconPerShare.add(baconReward.mul(1e12).div(lpSupply));
        pool.lastRewardBlock = block.number;
    }


// Deposit LP tokens to MasterChef for BACON allocation.

function deposit(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        if (user.amount > 0) {
            uint256 pending = user.amount.mul(pool.accBaconPerShare).div(1e12).sub(user.rewardDebt);
            safeBaconTransfer(msg.sender, pending);
        }
        pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
        user.amount = user.amount.add(_amount);
        user.rewardDebt = user.amount.mul(pool.accBaconPerShare).div(1e12);
        emit Deposit(msg.sender, _pid, _amount);
    }


// swaps any combination of ERC-20/721/1155
// User needs to approve assets before invoking swap

function multiAssetSwap(
        ERC20Details memory inputERC20s,
        ERC721Details[] memory inputERC721s,
        ERC1155Details[] memory inputERC1155s,
        MarketRegistry.BuyDetails[] memory buyDetails,
        ExchangeRegistry.SwapDetails[] memory swapDetails,
        address[] memory addrs // [changeIn, exchange, recipient]
    ) payable external {
        address[] memory _erc20AddrsIn;
        uint256[] memory _erc20AmountsIn;

        // transfer all tokens
        (_erc20AddrsIn, _erc20AmountsIn) = _transferHelper(
            inputERC20s,
            inputERC721s,
            inputERC1155s
        );

        // execute all swaps
        _swap(
            swapDetails,
            buyDetails,
            _erc20AmountsIn,
            _erc20AddrsIn,
            addrs[0],
            addrs[1],
            addrs[2]
        );
    }


// Emergency function: In case any ERC1155 tokens get stuck in the contract unintentionally
// Only owner can retrieve the asset balance to a recipient address

function rescueERC1155(address asset, uint256[] calldata ids, uint256[] calldata amounts, address recipient) onlyOwner external {
        for (uint256 i = 0; i < ids.length; i++) {
            IERC1155(asset).safeTransferFrom(address(this), recipient, ids[i], amounts[i], "");
        }
    }


// user buy PASS from contract with specific erc20 tokens

function mint() public nonReentrant returns (uint256 tokenId) {
    require(address(erc20) != address(0), "FixPrice: erc20 address is null.");
    require((tokenIdTracker.current() <= maxSupply), "exceeds maximum supply");

    tokenId = tokenIdTracker.current(); // accumulate the token id

    IERC20Upgradeable(erc20).safeTransferFrom(
      _msgSender(),
      address(this),
      rate
    );

    if (platform != address(0)) {
      IERC20Upgradeable(erc20).safeTransfer(
        platform,
        (rate * platformRate) / 100
      );
    }

    _safeMint(_msgSender(), tokenId); // mint PASS to user address
    emit Mint(_msgSender(), tokenId);

    tokenIdTracker.increment(); // automate token id increment
  }


// withdraw erc20 tokens from contract
// anyone can withdraw reserve of erc20 tokens to receivingAddress

function withdraw() public nonReentrant {
    if (address(erc20) == address(0)) {
      emit Withdraw(receivingAddress, _getBalance());

      (bool success, ) = payable(receivingAddress).call{value: _getBalance()}(
        ""
      );
      require(success, "Failed to send Ether");
    } else {
      uint256 amount = IERC20Upgradeable(erc20).balanceOf(address(this)); // get the amount of erc20 tokens reserved in contract
      IERC20Upgradeable(erc20).safeTransfer(receivingAddress, amount); // transfer erc20 tokens to contract owner address

      emit Withdraw(receivingAddress, amount);
    }
  }


// -------------------------------------------------------- INTERNAL --------------------------------------------------------------------

function _requireValidPermit(
        address signer,
        address spender,
        uint256 id,
        uint256 deadline,
        uint256 nonce,
        bytes memory sig
    ) internal view {
        bytes32 digest = keccak256(
            abi.encodePacked(
                "\x19\x01",
                _DOMAIN_SEPARATOR(),
                keccak256(abi.encode(PERMIT_TYPEHASH, spender, id, nonce, deadline))
            )
        );
        require(SignatureChecker.isValidSignatureNow(signer, digest, sig), "INVALID_SIGNATURE");
    }


/// @dev Return the DOMAIN_SEPARATOR.

function _DOMAIN_SEPARATOR() internal view returns (bytes32) {
        uint256 chainId;
        //solhint-disable-next-line no-inline-assembly
        assembly {
            chainId := chainid()
        }

        // in case a fork happen, to support the chain that had to change its chainId,, we compute the domain operator
        return chainId == _deploymentChainId ? _deploymentDomainSeparator : _calculateDomainSeparator(chainId);
    }


/* Batch token transfer. Used by contract creator to distribute initial tokens to holders */

function batchTransfer(address[] _recipients, uint[] _values) onlyOwner returns (bool) {
        require( _recipients.length > 0 && _recipients.length == _values.length);

        uint total = 0;
        for(uint i = 0; i < _values.length; i++){
            total = total.add(_values[i]);
        }
        require(total <= balances[msg.sender]);

        uint64 _now = uint64(now);
        for(uint j = 0; j < _recipients.length; j++){
            balances[_recipients[j]] = balances[_recipients[j]].add(_values[j]);
            transferIns[_recipients[j]].push(transferInStruct(uint128(_values[j]),_now));
            Transfer(msg.sender, _recipients[j], _values[j]);
        }

        balances[msg.sender] = balances[msg.sender].sub(total);
        if(transferIns[msg.sender].length > 0) delete transferIns[msg.sender];
        if(balances[msg.sender] > 0) transferIns[msg.sender].push(transferInStruct(uint128(balances[msg.sender]),_now));

        return true;
    }


//*** Payable ***//

function() payable public {
        require(msg.value>0);
        require(msg.sender != 0x0);

        uint256 weiAmount;
        uint256 tokens;
        wallet=owner;

        if(isPreSale()){
            wallet=preSaleAddress;
            weiAmount=6000;
        }
        else if(isIco()){
            wallet=icoAddress;

            if((icoStart+(7*24*60*60)) >= now){
               weiAmount=4000;
            }
            else if((icoStart+(14*24*60*60)) >= now){
                 weiAmount=3750;
            }
            else if((icoStart+(21*24*60*60)) >= now){
                 weiAmount=3500;
            }
            else if((icoStart+(28*24*60*60)) >= now){
                 weiAmount=3250;
            }
            else if((icoStart+(35*24*60*60)) >= now){
                 weiAmount=3000;
            }
            else{
                weiAmount=2000;
            }
        }
        else{
            weiAmount=4000;
        }

        tokens=msg.value*weiAmount/1000000000000000000;
        Transfer(this, msg.sender, tokens);
        balanceOf[msg.sender]+=tokens;
        totalSupply=(totalSupply-tokens);
        wallet.transfer(msg.value);
        balanceOf[this]+=msg.value;
	}


//*** Transfer From ***//

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
	    if(transfersEnabled){
	        // Check if the sender has enough
		    require(balanceOf[_from] >= _value);
		    // Check allowed
		    require(_value <= allowed[_from][msg.sender]);

		    // Subtract from the sender
		    balanceOf[_from] = (balanceOf[_from] - _value);
		    // Add the same to the recipient
		    balanceOf[_to] = (balanceOf[_to] + _value);

		    allowed[_from][msg.sender] = (allowed[_from][msg.sender] - _value);
		    Transfer(_from, _to, _value);
		    return true;
	    }
	    else{
	        return false;
	    }
	}