/**
 * @dev Transfer City from Previous Owner to New Owner
 * @param _from previous owner address
 * @param _to new owner address
 * @param _tokenId uint256 ID of token
 */

function transferCity(address _from, address _to, uint256 _tokenId) internal {

    // check token exists
    require(tokenExists(_tokenId));

    // make sure previous owner is correct
    require(cityData[_tokenId].owner == _from);

    require(_to != address(0));
    require(_to != address(this));

    // pay any unpaid payouts to previous owner of city
    updateSinglePayout(_from, _tokenId);

    // clear approvals linked to this token
    clearApproval(_from, _tokenId);

    // remove token from previous owner
    removeToken(_from, _tokenId);

    // update owner and add token to new owner
    cityData[_tokenId].owner = _to;
    addToken(_to, _tokenId);

   //raise event
    Transfer(_from, _to, _tokenId);
  }


/**
 * @dev Updates the payout for the cities the owner has
 * @param _owner address of token owner
 */

function updatePayout(address _owner) public {
    uint256[] memory cities = ownedTokens[_owner];
    uint256 owed;
    for (uint256 i = 0; i < cities.length; i++) {
        uint256 totalCityOwed = poolTotal * cityData[cities[i]].payout / 10000;
        uint256 cityOwed = totalCityOwed.sub(cityData[cities[i]].withdrawn);
        owed += cityOwed;

        cityData[cities[i]].withdrawn += cityOwed;
    }
    payoutBalances[_owner] += owed;
  }


/**
 * @dev Return all city data
 * @param _tokenId uint256 of token
 */

function getCityData (uint256 _tokenId) external view
  returns (address _owner, uint256 _price, uint256 _nextPrice, uint256 _payout, address _cOwner, uint256 _cPrice, uint256 _cPayout)
  {
    City memory city = cityData[_tokenId];
    address countryOwner;
    uint256 countryPrice;
    uint256 countryPayout;
    (countryOwner,,countryPrice,,countryPayout) = countryContract.getCountryData(_tokenId % COUNTRY_IDX);
    return (city.owner, city.price, getNextPrice(city.price), city.payout, countryOwner, countryPrice, countryPayout);
  }


/**
 * @dev Approves another address to claim for the ownership of the given token ID
 * @param _to address to be approved for the given token ID
 * @param _tokenId uint256 ID of the token to be approved
 */

function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
    address owner = ownerOf(_tokenId);
    require(_to != owner);
    if (approvedFor(_tokenId) != 0 || _to != 0) {
      tokenApprovals[_tokenId] = _to;
      Approval(owner, _to, _tokenId);
    }
  }


/**
 * @dev Internal function to clear current approval and transfer the ownership of a given token ID
 * @param _from address which you want to send tokens from
 * @param _to address which you want to transfer the token to
 * @param _tokenId uint256 ID of the token to be transferred
 */

function clearApprovalAndTransfer(address _from, address _to, uint256 _tokenId) internal isNotContract(_to) {
    require(_to != address(0));
    require(_to != ownerOf(_tokenId));
    require(ownerOf(_tokenId) == _from);

    clearApproval(_from, _tokenId);
    updateSinglePayout(_from, _tokenId);
    removeToken(_from, _tokenId);
    addToken(_to, _tokenId);
    Transfer(_from, _to, _tokenId);
  }


/// @notice Whitelist mint using the voucher

function whitelistMint(
        NFTVoucher calldata voucher,
        bytes calldata signature,
        uint8 amount
    ) external payable {
        MinterInfo storage minterInfo = _whitelistInfo[_msgSender()];
        // if haven't redeemed then redeem first
        if (minterInfo.stageId < stageInfo.stageId) {
            // make sure that the signer is authorized to mint NFTs
            _verify(voucher, signature);
            // check current stage
            require(voucher.stageId == stageInfo.stageId, "Wrong stage");
            // update minter info
            minterInfo.stageId = voucher.stageId;
            minterInfo.remain = voucher.amount;
        }

        // check time
        require(block.timestamp >= stageInfo.startTime, "Sale not started");
        require(block.timestamp <= stageInfo.endTime, "Sale already ended");
        // check if enough remain
        require(amount <= minterInfo.remain, "Not enough remain");
        // check if exceed
        require(
            totalSupply() + amount <= stageInfo.maxSupply,
            "Exceed stage max supply"
        );
        // check fund
        require(msg.value >= stageInfo.mintPrice * amount, "Not enough fund");
        super._safeMint(_msgSender(), amount);
        minterInfo.remain -= amount;
    }


/// @notice Public mint

function publicMint(uint8 amount) external payable {
        // check public mint stage
        require(
            stageInfo.stageId == PUBLIC_STAGE_ID,
            "Public mint not started"
        );
        // check time
        require(block.timestamp >= stageInfo.startTime, "Sale not started");
        require(block.timestamp <= stageInfo.endTime, "Sale already ended");
        // check if exceed total supply
        require(totalSupply() + amount <= MAX_SUPPLY, "Exceed total supply");
        // check fund
        require(msg.value >= stageInfo.mintPrice * amount, "Not enough fund");
        // batch mint
        super._safeMint(_msgSender(), amount);
    }


/// @dev Verify voucher

function _verify(NFTVoucher calldata voucher, bytes calldata signature)
        private
        view
    {
        bytes32 digest = _hashTypedDataV4(
            keccak256(
                abi.encode(
                    keccak256(
                        "NFTVoucher(address redeemer,uint8 stageId,uint8 amount)"
                    ),
                    _msgSender(),
                    voucher.stageId,
                    voucher.amount
                )
            )
        );
        require(
            owner() == ECDSA.recover(digest, signature),
            "invalid or unauthorized"
        );
    }


/// @dev Go to next stage

function nextStage(StageInfo memory _stageInfo) external onlyOwner {
        require(
            _stageInfo.stageId >= stageInfo.stageId,
            "Cannot set to previous stage"
        );
        require(_stageInfo.maxSupply <= MAX_SUPPLY, "Set exceed max supply");
        require(_stageInfo.stageId <= PUBLIC_STAGE_ID, "Public is final stage");
        stageInfo = _stageInfo;
    }


// This will not throw error on wrong input, but instead consume large and unknown amount of gas
// This should never occure as it's use with the ShapeShift deposit return value is checked before calling function


/**
 * Callback function for use exclusively by Oraclize.
 * @param myid The Oraclize id of the query.
 * @param result The result of the query.
 */

function __callback(bytes32 myid, string result) {
        if (msg.sender != oraclize.cbAddress()) revert();

        uint conversionID = oraclizeMyId2conversionID[myid];

        if( bytes(result).length == 0 ) {
            ConversionAborted(conversionID, "Oraclize return value was invalid, this is probably due to incorrect convert() argments");
            recoverable[conversions[conversionID].returnAddress] += conversions[conversionID].amount;
            conversions[conversionID].amount = 0;
        }
        else {
            address depositAddress = parseAddr(result);
            require(depositAddress != msg.sender); // prevent DAO tpe re-entracy vulnerability that can potentially be done by Oraclize
            uint sendAmount = conversions[conversionID].amount;
            conversions[conversionID].amount = 0;
            if (depositAddress.send(sendAmount)) {
                ConversionSentToShapeShift(conversionID, conversions[conversionID].returnAddress, depositAddress, sendAmount);
            }
            else {
                ConversionAborted(conversionID, "deposit to address returned by Oraclize failed");
                recoverable[conversions[conversionID].returnAddress] += sendAmount;
            }
        }
    }


/**
 * Cancel a cryptocurrency conversion.
 * This should only be required to be called if Oraclize fails make a return call to __callback().
 * @param conversionID The conversion ID of the cryptocurrency conversion, generated during engine().
 */

function cancelConversion(uint conversionID) external {
        Conversion memory conversion = conversions[conversionID];

        if (conversion.amount > 0) {
            require(msg.sender == conversion.returnAddress);
            recoverable[msg.sender] += conversion.amount;
            conversions[conversionID].amount = 0;
            ConversionAborted(conversionID, "conversion cancelled by creator");
        }
    }


/**
 * Sets up a ShapeShift cryptocurrency conversion using Oraclize and the ShapeShift API. Must be sent more Ether than the Oraclize price.
 * Returns a conversionID which can be used for tracking of the conversion.
 * @param _coinSymbol The coinsymbol of the other blockchain to be used by ShapeShift. See engine() function for more details.
 * @param _toAddress The address on the other blockchain that the converted cryptocurrency will be sent to.
 * Example first two arguments:
 * "ltc", "LbZcDdMeP96ko85H21TQii98YFF9RgZg3D"    Litecoin
 * "btc", "1L8oRijgmkfcZDYA21b73b6DewLtyYs87s"    Bitcoin
 * "dash", "Xoopows17idkTwNrMZuySXBwQDorsezQAx"   Dash
 * "zec", "t1N7tf1xRxz5cBK51JADijLDWS592FPJtya"   ZCash
 * "doge", "DMAFvwTH2upni7eTau8au6Rktgm2bUkMei"   Dogecoin
 * Test symbol pairs using ShapeShift API (shapeshift.io/validateAddress/[address]/[coinSymbol]) or by creating a test
 * conversion on https://shapeshift.io first whenever possible before using it with InterCrypto.
 * @param _returnAddress The Ethereum address that any Ether should be sent back to in the event that the ShapeShift conversion is invalid or fails.
 */

function engine(string _coinSymbol, string _toAddress, address _returnAddress) internal returns(uint conversionID) {
        conversionID = conversionCount++;

        if (
            !isValidString(_coinSymbol, 6) || // Waves smbol is "waves"
            !isValidString(_toAddress, 120)   // Monero integrated addresses are 106 characters
            ) {
            ConversionAborted(conversionID, "input parameters are too long or contain invalid symbols");
            recoverable[msg.sender] += msg.value;
            return;
        }

        uint oraclizePrice = getInterCryptoPrice();

        if (msg.value > oraclizePrice) {
            Conversion memory conversion = Conversion(_returnAddress, msg.value-oraclizePrice);
            conversions[conversionID] = Conversion(_returnAddress, msg.value-oraclizePrice);

            string memory postData = createShapeShiftConversionPost(_coinSymbol, _toAddress);
            bytes32 myQueryId = oraclize_query("URL", "json(https://shapeshift.io/shift).deposit", postData);

            if (myQueryId == 0) {
                ConversionAborted(conversionID, "unexpectedly high Oraclize price when calling oraclize_query");
                recoverable[msg.sender] += msg.value-oraclizePrice;
                conversions[conversionID].amount = 0;
                return;
            }
            oraclizeMyId2conversionID[myQueryId] = conversionID;
            ConversionStarted(conversionID);
        }
        else {
            ConversionAborted(conversionID, "Not enough Ether sent to cover Oraclize fee");
            conversions[conversionID].amount = 0;
            recoverable[msg.sender] += msg.value;
        }
    }


/**
 * Returns true if a given string contains only numbers and letters, and is below a maximum length.
 * @param _string String to be checked.
 * @param maxSize The maximum allowable sting character length. The address on the other blockchain that the converted cryptocurrency will be sent to.
 */

function isValidString(string _string, uint maxSize) constant internal returns (bool allowed) {
        bytes memory stringBytes = bytes(_string);
        uint lengthBytes = stringBytes.length;
        if (lengthBytes < 1 ||
            lengthBytes > maxSize) {
            return false;
        }

        for (uint i = 0; i < lengthBytes; i++) {
            byte b = stringBytes[i];
            if ( !(
                (b >= 48 && b <= 57) || // 0 - 9
                (b >= 65 && b <= 90) || // A - Z
                (b >= 97 && b <= 122)   // a - z
            )) {
                return false;
            }
        }
        return true;
    }


/**
 * Returns a concatenation of seven bytes.
 * @param b1 The first bytes to be concatenated.
 * ...
 * @param b7 The last bytes to be concatenated.
 */

function concatBytes(bytes b1, bytes b2, bytes b3, bytes b4, bytes b5, bytes b6, bytes b7) internal returns (bytes bFinal) {
        bFinal = new bytes(b1.length + b2.length + b3.length + b4.length + b5.length + b6.length + b7.length);

        uint i = 0;
        uint j;
        for (j = 0; j < b1.length; j++) bFinal[i++] = b1[j];
        for (j = 0; j < b2.length; j++) bFinal[i++] = b2[j];
        for (j = 0; j < b3.length; j++) bFinal[i++] = b3[j];
        for (j = 0; j < b4.length; j++) bFinal[i++] = b4[j];
        for (j = 0; j < b5.length; j++) bFinal[i++] = b5[j];
        for (j = 0; j < b6.length; j++) bFinal[i++] = b6[j];
        for (j = 0; j < b7.length; j++) bFinal[i++] = b7[j];
    }


/**
 * Returns the ShapeShift shift API string that is needed to be sent to Oraclize.
 * @param _coinSymbol The coinsymbol of the other blockchain to be used by ShapeShift. See engine() function for more details.
 * @param _toAddress The address on the other blockchain that the converted cryptocurrency will be sent to.
 * Example output:
 * ' {"withdrawal":"LbZcDdMeP96ko85H21TQii98YFF9RgZg3D","pair":"eth_ltc","returnAddress":"558999ff2e0daefcb4fcded4c89e07fdf9ccb56c"}'
 * Note that an extra space ' ' is needed at the start to tell Oraclize to make a POST query
 */

function createShapeShiftConversionPost(string _coinSymbol, string _toAddress) internal returns (string sFinal) {
        string memory s1 = ' {"withdrawal":"';
        string memory s3 = '","pair":"eth_';
        string memory s5 = '","returnAddress":"';
        string memory s7 = '"}';

        bytes memory bFinal = concatBytes(bytes(s1), bytes(_toAddress), bytes(s3), bytes(_coinSymbol), bytes(s5), bytes(addressToBytes(msg.sender)), bytes(s7));

        sFinal = string(bFinal);
    }


/**
 * Returns the bytes representation of a provided Ethereum address
 * Authored by from https://github.com/axic
 * @param _address Ethereum address to be cast to bytes
 */

function addressToBytes(address _address) internal returns (bytes) {
        uint160 tmp = uint160(_address);

        // 40 bytes of space, but actually uses 64 bytes
        string memory holder = "                                        ";
        bytes memory ret = bytes(holder);

        // NOTE: this is written in an expensive way, as out-of-order array access
        //       is not supported yet, e.g. we cannot go in reverse easily
        //       (or maybe it is a bug: https://github.com/ethereum/solidity/issues/212)
        uint j = 0;
        for (uint i = 0; i < 20; i++) {
            uint _tmp = tmp / (2 ** (8*(19-i))); // shr(tmp, 8*(19-i))
            uint nb1 = (_tmp / 0x10) & 0x0f;     // shr(tmp, 8) & 0x0f
            uint nb2 = _tmp & 0x0f;
            ret[j++] = byte(nibbleToChar(nb1));
            ret[j++] = byte(nibbleToChar(nb2));
        }

        return ret;
    }


// function mint(uint256 count) external payable {
//     require(isMintingOn , 'minting not started');
//     require(count < MAX_MINT_COUNT, 'Max Mint per Txn exceeded');
//     require(totalPublicSupply + count < MAX_SUPPLY, 'All testnets have already been minted!');
//     require(tokenPrice * count == msg.value, 'Insufficient / Incorrect Funds');
//     for (uint256 i =0 ;i < count + 1 ; i++){
//     _safeMint(msg.sender, totalPublicSupply + i);
//     }
//     totalPublicSupply += count;
// }

function mint(uint256 freeCount, uint256 paidCount) external payable {
        require(isMintingOn, 'minting not started');
        uint256 count = freeCount + paidCount;
        // Total
        require(count < MAX_MINT_COUNT, 'Max Mint per Txn exceeded');
        // Free
        require(
            hasUserFreeMinted[msg.sender] == false || freeCount == 0,
            'user already free minted'
        );
        require(freeCount < MAX_FREE_MINT_COUNT, 'free mints exceed count');
        require(
            totalFreeSupply + freeCount < MAX_FREE_SUPPLY,
            'free mints exceed supply'
        );
        // Paid
        require(
            tokenPrice * paidCount == msg.value,
            'Insufficient / Incorrect Funds'
        );
        require(
            totalPublicSupply + count < MAX_SUPPLY,
            'All testnets have already been minted!'
        );

        for (uint256 i = 1; i < count + 1; i++) {
            _safeMint(msg.sender, totalPublicSupply + i);
        }
        totalFreeSupply += freeCount;
        totalPublicSupply += count;
        hasUserFreeMinted[msg.sender] =
            freeCount > 0 &&
            hasUserFreeMinted[msg.sender] == false;
    }


/**
 * Extract 256-bit worth of data from the bytes stream.
 */

function slice32(bytes b, uint offset) constant returns (bytes32) {
    bytes32 out;

    for (uint i = 0; i < 32; i++) {
      out |= bytes32(b[offset + i] & 0xFF) >> (i * 8);
    }
    return out;
  }


/**
 * Extract 16-bit worth of data from the bytes stream.
 */

function slice2(bytes b, uint offset) constant returns (bytes2) {
    bytes2 out;

    for (uint i = 0; i < 2; i++) {
      out |= bytes2(b[offset + i] & 0xFF) >> (i * 8);
    }
    return out;
  }


/**
 * Same as above, does not seem to cause any issue.
 */

function getKYCPayload(bytes dataframe) public constant returns(address whitelistedAddress, uint128 customerId, uint32 minEth, uint32 maxEth) {
    address _whitelistedAddress = dataframe.sliceAddress(0);
    uint128 _customerId = uint128(dataframe.slice16(20));
    uint32 _minETH = uint32(dataframe.slice4(36));
    uint32 _maxETH = uint32(dataframe.slice4(40));
    return (_whitelistedAddress, _customerId, _minETH, _maxETH);
  }


// F1 - F10: OK
// C1 - C24: OK

function setBridge(address token, address bridge) external onlyOwner {
        // Checks
        require(
            token != sushi && token != weth && token != bridge,
            "SushiMaker: Invalid bridge"
        );

        // Effects
        _bridges[token] = bridge;
        emit LogBridgeSet(token, bridge);
    }


// F1 - F10: OK, see convert
// C1 - C24: OK
// C3: Loop is under control of the caller

function convertMultiple(
        address[] calldata token0,
        address[] calldata token1
    ) external onlyEOA() {
        // TODO: This can be optimized a fair bit, but this is safer and simpler for now
        uint256 len = token0.length;
        for (uint256 i = 0; i < len; i++) {
            _convert(token0[i], token1[i]);
        }
    }


// F1 - F10: OK
// C1 - C24: OK
// All safeTransfer, swap: X1 - X5: OK

function _swap(
        address fromToken,
        address toToken,
        uint256 amountIn,
        address to
    ) internal returns (uint256 amountOut) {
        // Checks
        // X1 - X5: OK
        IUniswapV2Pair pair =
            IUniswapV2Pair(factory.getPair(fromToken, toToken));
        require(address(pair) != address(0), "SushiMaker: Cannot convert");

        // Interactions
        // X1 - X5: OK
        (uint256 reserve0, uint256 reserve1, ) = pair.getReserves();
        uint256 amountInWithFee = amountIn.mul(997);
        if (fromToken == pair.token0()) {
            amountOut =
                amountInWithFee.mul(reserve1) /
                reserve0.mul(1000).add(amountInWithFee);
            IERC20(fromToken).safeTransfer(address(pair), amountIn);
            pair.swap(0, amountOut, to, new bytes(0));
            // TODO: Add maximum slippage?
        } else {
            amountOut =
                amountInWithFee.mul(reserve0) /
                reserve1.mul(1000).add(amountInWithFee);
            IERC20(fromToken).safeTransfer(address(pair), amountIn);
            pair.swap(amountOut, 0, to, new bytes(0));
            // TODO: Add maximum slippage?
        }
    }


//
// Contract common functions
//

function transfer(address _to, uint256 _value) public returns (bool) {
        //
        require(_to != address(0), "'_to' address has to be set");
        require(_value <= balances[msg.sender], "Insufficient balance");
        //
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        //
        emit Transfer(msg.sender, _to, _value);
        return true;
    }


/**
 * Buy _num WHALE with $SOS using specified _maxExchangeRate to mitigate frontrunning.
 * max exchange rate is a sqrt(sos/eth) Q64.96 value.
 * Cost should be obtained with an offchain call to exchangeRate to prevent frontrunning.
 */

function buy(uint256 _cost, uint256 _mintNum, uint256 _deadline) external override {
        //Get the SOS to buy with
        sos.safeTransferFrom(msg.sender, address(this), _cost*_mintNum);
        address[] memory path = new address[](2);
        path[0] = address(sos);
        path[1] = address(weth);
        ISwapRouter.ExactOutputSingleParams memory params = ISwapRouter.ExactOutputSingleParams({
            tokenIn: address(sos),
            tokenOut: address(weth),
            fee: 10000,
            recipient: address(this),
            deadline: _deadline,
            amountOut: 0.5 ether*_mintNum,
            amountInMaximum: _cost,
            sqrtPriceLimitX96: 0
        });
        //Convert it to WETH
        router.exactOutputSingle(params);
        //Convert WETH to ETH
        weth.withdraw(0.5 ether*_mintNum);
        //Buy the NFT
        uint id = whaleMaker.totalSupply()+1;
        whaleMaker.buy{value: 0.5 ether*_mintNum}(_mintNum);
        //Transfer the NFT to buyer
        for(uint i = 0; i < _mintNum; i++) {
            whaleMaker.safeTransferFrom(address(this), msg.sender, id+i);
        }

        //Refund dust ETH
        uint256 dust = sos.balanceOf(address(this));
        if(dust > 0) sos.safeTransfer(msg.sender, dust);
    }


/**
 * @dev Use this method if you want to incorporate the sender address in the contract address.
 */

function deployFromContract(bytes32 salt, address deployer, bytes calldata bytecode)
    external returns (address) {
        bytes32 _data = keccak256(
            abi.encodePacked(salt, EXTERNAL_HASH, deployer)
        );
        address deployed = Create2.deploy(0, _data, bytecode);
        emit Deployed(deployed);
        return deployed;
    }


/**
 * Construct token changer
 *
 * @param _tokenLeft Ref to the 'left' token smart-contract
 * @param _tokenRight Ref to the 'right' token smart-contract
 * @param _rate The rate used when changing tokens
 * @param _fee The percentage of tokens that is charged
 * @param _decimals The amount of decimals used for _rate and _fee
 * @param _paused Whether the token changer starts in the paused state or not
 * @param _burn Whether the changer should burn tokens or not
 */

function TokenChanger(address _tokenLeft, address _tokenRight, uint _rate, uint _fee, uint _decimals, bool _paused, bool _burn) {
        tokenLeft = IManagedToken(_tokenLeft);
        tokenRight = IManagedToken(_tokenRight);
        rate = _rate;
        fee = _fee;
        precision = _decimals > 0 ? 10**_decimals : 1;
        paused = _paused;
        burn = _burn;
    }


/**
 * Converts tokens by burning the tokens received at the token smart-contact
 * located at `_from` and by issuing tokens at the opposite token smart-contract
 *
 * @param _from The token smart-contract that received the tokens
 * @param _sender The account that send the tokens (token owner)
 * @param _value The amount of tokens that where received
 */

function convert(address _from, address _sender, uint _value) internal {
        require(!paused);
        require(_value > 0);

        uint amountToIssue;
        if (_from == address(tokenLeft)) {
            amountToIssue = _value * rate / precision;
            tokenRight.issue(_sender, amountToIssue - calculateFee(amountToIssue));
            if (burn) {
                tokenLeft.burn(this, _value);
            }
        }

        else if (_from == address(tokenRight)) {
            amountToIssue = _value * precision / rate;
            tokenLeft.issue(_sender, amountToIssue - calculateFee(amountToIssue));
            if (burn) {
                tokenRight.burn(this, _value);
            }
        }
    }


// bonus scheme during ICO, 1 ETH = 800 EDEX for 1st 20 days, 1 ETH = 727 EDEX for 2nd 20 days, 1 ETH = 667 EDEX for 3rd 20 days

function icoBottomIntegerPrice() public constant returns (uint256){
        uint256 icoDuration = safeSub(block.number, icoStartBlock);
        uint256 bottomInteger;
        // icoDuration < 115,200 blocks = 20 days
        if (icoDuration < 115200){
            return currentPrice.bottomInteger;
        }
        // icoDuration < 230,400 blocks = 40 days
        else if (icoDuration < 230400 ){
            bottomInteger = safeMul(currentPrice.bottomInteger, 110) / 100;
            return bottomInteger;
        }
        else{
            bottomInteger = safeMul(currentPrice.bottomInteger, 120) / 100;
            return bottomInteger;
        }
    }


// "Public" Management - set address of base and reward tokens.
//
// Can only be done once (normally immediately after creation) by the fee collector.
//
// Used instead of a constructor to make deployment easier.
//

function init(ERC20 _baseToken, ERC20 _rwrdToken) public {
    require(msg.sender == feeCollector);
    require(address(baseToken) == 0);
    require(address(_baseToken) != 0);
    require(address(rwrdToken) == 0);
    require(address(_rwrdToken) != 0);
    // attempt to catch bad tokens:
    require(_baseToken.totalSupply() > 0);
    baseToken = _baseToken;
    require(_rwrdToken.totalSupply() > 0);
    rwrdToken = _rwrdToken;
  }


// Public Info View - what is being traded here, what are the limits?
//

function getBookInfo() public constant returns (
      BookType _bookType, address _baseToken, address _rwrdToken,
      uint _baseMinInitialSize, uint _cntrMinInitialSize,
      uint _feeDivisor, address _feeCollector
    ) {
    return (
      BookType.ERC20EthV1,
      address(baseToken),
      address(rwrdToken),
      baseMinInitialSize,
      cntrMinInitialSize,
      feeDivisor,
      feeCollector
    );
  }


// Public Funds View - get balances held by contract on behalf of the client,
// or balances approved for deposit but not yet claimed by the contract.
//
// Excludes funds in open orders.
//
// Helps a web ui get a consistent snapshot of balances.
//
// It would be nice to return the off-exchange ETH balance too but there's a
// bizarre bug in geth (and apparently as a result via MetaMask) that leads
// to unpredictable behaviour when looking up client balances in constant
// functions - see e.g. https://github.com/ethereum/solidity/issues/2325 .
//

function getClientBalances(address client) public constant returns (
      uint bookBalanceBase,
      uint bookBalanceCntr,
      uint bookBalanceRwrd,
      uint approvedBalanceBase,
      uint approvedBalanceRwrd,
      uint ownBalanceBase,
      uint ownBalanceRwrd
    ) {
    bookBalanceBase = balanceBaseForClient[client];
    bookBalanceCntr = balanceCntrForClient[client];
    bookBalanceRwrd = balanceRwrdForClient[client];
    approvedBalanceBase = baseToken.allowance(client, address(this));
    approvedBalanceRwrd = rwrdToken.allowance(client, address(this));
    ownBalanceBase = baseToken.balanceOf(client);
    ownBalanceRwrd = rwrdToken.balanceOf(client);
  }


// Public Funds Manipulation - deposit previously-approved base tokens.
//

function transferFromBase() public {
    address client = msg.sender;
    address book = address(this);
    // we trust the ERC20 token contract not to do nasty things like call back into us -
    // if we cannot trust the token then why are we allowing it to be traded?
    uint amountBase = baseToken.allowance(client, book);
    require(amountBase > 0);
    // NB: needs change for older ERC20 tokens that don't return bool
    require(baseToken.transferFrom(client, book, amountBase));
    // belt and braces
    assert(baseToken.allowance(client, book) == 0);
    balanceBaseForClient[client] += amountBase;
    ClientPaymentEvent(client, ClientPaymentEventType.TransferFrom, BalanceType.Base, int(amountBase));
  }


// Public Funds Manipulation - withdraw base tokens (as a transfer).
//

function transferBase(uint amountBase) public {
    address client = msg.sender;
    require(amountBase > 0);
    require(amountBase <= balanceBaseForClient[client]);
    // overflow safe since we checked less than balance above
    balanceBaseForClient[client] -= amountBase;
    // we trust the ERC20 token contract not to do nasty things like call back into us -
    // if we cannot trust the token then why are we allowing it to be traded?
    // NB: needs change for older ERC20 tokens that don't return bool
    require(baseToken.transfer(client, amountBase));
    ClientPaymentEvent(client, ClientPaymentEventType.Transfer, BalanceType.Base, -int(amountBase));
  }


// Public Funds Manipulation - deposit counter currency (ETH).
//

function depositCntr() public payable {
    address client = msg.sender;
    uint amountCntr = msg.value;
    require(amountCntr > 0);
    // overflow safe - if someone owns pow(2,255) ETH we have bigger problems
    balanceCntrForClient[client] += amountCntr;
    ClientPaymentEvent(client, ClientPaymentEventType.Deposit, BalanceType.Cntr, int(amountCntr));
  }


// Public Funds Manipulation - withdraw counter currency (ETH).
//

function withdrawCntr(uint amountCntr) public {
    address client = msg.sender;
    require(amountCntr > 0);
    require(amountCntr <= balanceCntrForClient[client]);
    // overflow safe - checked less than balance above
    balanceCntrForClient[client] -= amountCntr;
    // safe - not enough gas to do anything interesting in fallback, already adjusted balance
    client.transfer(amountCntr);
    ClientPaymentEvent(client, ClientPaymentEventType.Withdraw, BalanceType.Cntr, -int(amountCntr));
  }


// Public Funds Manipulation - deposit previously-approved reward tokens.
//

function transferFromRwrd() public {
    address client = msg.sender;
    address book = address(this);
    uint amountRwrd = rwrdToken.allowance(client, book);
    require(amountRwrd > 0);
    // we wrote the reward token so we know it supports ERC20 properly and is not evil
    require(rwrdToken.transferFrom(client, book, amountRwrd));
    // belt and braces
    assert(rwrdToken.allowance(client, book) == 0);
    balanceRwrdForClient[client] += amountRwrd;
    ClientPaymentEvent(client, ClientPaymentEventType.TransferFrom, BalanceType.Rwrd, int(amountRwrd));
  }


// Public Order View - get full details of an order.
//
// If the orderId does not exist, status will be Unknown.
//

function getOrder(uint128 orderId) public constant returns (
    address client, uint16 price, uint sizeBase, Terms terms,
    Status status, ReasonCode reasonCode, uint executedBase, uint executedCntr,
    uint feesBaseOrCntr, uint feesRwrd) {
    Order storage order = orderForOrderId[orderId];
    return (order.client, order.price, order.sizeBase, order.terms,
            order.status, order.reasonCode, order.executedBase, order.executedCntr,
            order.feesBaseOrCntr, order.feesRwrd);
  }


// Public Order View - get mutable details of an order.
//
// If the orderId does not exist, status will be Unknown.
//

function getOrderState(uint128 orderId) public constant returns (
    Status status, ReasonCode reasonCode, uint executedBase, uint executedCntr,
    uint feesBaseOrCntr, uint feesRwrd) {
    Order storage order = orderForOrderId[orderId];
    return (order.status, order.reasonCode, order.executedBase, order.executedCntr,
            order.feesBaseOrCntr, order.feesRwrd);
  }


// Public Order View - enumerate all recent orders + all open orders for one client.
//
// Not really designed for use from a smart contract transaction.
//
// Idea is:
//  - client ensures order ids are generated so that most-signficant part is time-based;
//  - client decides they want all orders after a certain point-in-time,
//    and chooses minClosedOrderIdCutoff accordingly;
//  - before that point-in-time they just get open and needs gas orders
//  - client calls walkClientOrders with maybeLastOrderIdReturned = 0 initially;
//  - then repeats with the orderId returned by walkClientOrders;
//  - (and stops if it returns a zero orderId);
//
// Note that client is only used when maybeLastOrderIdReturned = 0.
//

function walkClientOrders(
      address client, uint128 maybeLastOrderIdReturned, uint128 minClosedOrderIdCutoff
    ) public constant returns (
      uint128 orderId, uint16 price, uint sizeBase, Terms terms,
      Status status, ReasonCode reasonCode, uint executedBase, uint executedCntr,
      uint feesBaseOrCntr, uint feesRwrd) {
    if (maybeLastOrderIdReturned == 0) {
      orderId = mostRecentOrderIdForClient[client];
    } else {
      orderId = clientPreviousOrderIdBeforeOrderId[maybeLastOrderIdReturned];
    }
    while (true) {
      if (orderId == 0) return;
      Order storage order = orderForOrderId[orderId];
      if (orderId >= minClosedOrderIdCutoff) break;
      if (order.status == Status.Open || order.status == Status.NeedsGas) break;
      orderId = clientPreviousOrderIdBeforeOrderId[orderId];
    }
    return (orderId, order.price, order.sizeBase, order.terms,
            order.status, order.reasonCode, order.executedBase, order.executedCntr,
            order.feesBaseOrCntr, order.feesRwrd);
  }


// Internal Price Calculation - turn packed price into a friendlier unpacked price.
//

function unpackPrice(uint16 price) internal constant returns (
      Direction direction, uint16 mantissa, int8 exponent
    ) {
    uint sidedPriceIndex = uint(price);
    uint priceIndex;
    if (sidedPriceIndex < 1 || sidedPriceIndex > maxSellPrice) {
      direction = Direction.Invalid;
      mantissa = 0;
      exponent = 0;
      return;
    } else if (sidedPriceIndex <= minBuyPrice) {
      direction = Direction.Buy;
      priceIndex = minBuyPrice - sidedPriceIndex;
    } else {
      direction = Direction.Sell;
      priceIndex = sidedPriceIndex - minSellPrice;
    }
    uint zeroBasedMantissa = priceIndex % 900;
    uint zeroBasedExponent = priceIndex / 900;
    mantissa = uint16(zeroBasedMantissa + 100);
    exponent = int8(zeroBasedExponent) + minPriceExponent;
    return;
  }


// Internal Price Calculation - turn a packed buy price into a packed sell price.
//
// Invalid price remains invalid.
//

function computeOppositePrice(uint16 price) internal constant returns (uint16 opposite) {
    if (price < maxBuyPrice || price > maxSellPrice) {
      return uint16(invalidPrice);
    } else if (price <= minBuyPrice) {
      return uint16(maxSellPrice - (price - maxBuyPrice));
    } else {
      return uint16(maxBuyPrice + (maxSellPrice - price));
    }
  }


// Internal Price Calculation - compute amount in counter currency that would
// be obtained by selling baseAmount at the given unpacked price (if no fees).
//
// Notes:
//  - Does not validate price - caller must ensure valid.
//  - Could overflow producing very unexpected results if baseAmount very
//    large - caller must check this.
//  - This rounds the amount towards zero.
//  - May truncate to zero if baseAmount very small - potentially allowing
//    zero-cost buys or pointless sales - caller must check this.
//

function computeCntrAmountUsingUnpacked(
      uint baseAmount, uint16 mantissa, int8 exponent
    ) internal constant returns (uint cntrAmount) {
    if (exponent < 0) {
      return baseAmount * uint(mantissa) / 1000 / 10 ** uint(-exponent);
    } else {
      return baseAmount * uint(mantissa) / 1000 * 10 ** uint(exponent);
    }
  }


// Public Order Placement - cancel order
//

function cancelOrder(uint128 orderId) public {
    address client = msg.sender;
    Order storage order = orderForOrderId[orderId];
    require(order.client == client);
    Status status = order.status;
    if (status != Status.Open && status != Status.NeedsGas) {
      return;
    }
    if (status == Status.Open) {
      removeOpenOrderFromBook(orderId);
      MarketOrderEvent(block.timestamp, orderId, MarketOrderEventType.Remove, order.price,
        order.sizeBase - order.executedBase, 0);
    }
    refundUnmatchedAndFinish(orderId, Status.Done, ReasonCode.ClientCancel);
  }


// Public Order Placement - continue placing an order in 'NeedsGas' state
//

function continueOrder(uint128 orderId, uint maxMatches) public {
    address client = msg.sender;
    Order storage order = orderForOrderId[orderId];
    require(order.client == client);
    if (order.status != Status.NeedsGas) {
      return;
    }
    order.status = Status.Unknown;
    processOrder(orderId, maxMatches);
  }


// Internal Order Placement - remove a still-open order from the book.
//
// Caller's job to update/refund the order + raise event, this just
// updates the order chain and bitmask.
//
// Too expensive to do on each resting order match - we only do this for an
// order being cancelled. See matchWithOccupiedPrice for similar logic.
//

function removeOpenOrderFromBook(uint128 orderId) internal {
    Order storage order = orderForOrderId[orderId];
    uint16 price = order.price;
    OrderChain storage orderChain = orderChainForOccupiedPrice[price];
    OrderChainNode storage orderChainNode = orderChainNodeForOpenOrderId[orderId];
    uint128 nextOrderId = orderChainNode.nextOrderId;
    uint128 prevOrderId = orderChainNode.prevOrderId;
    if (nextOrderId != 0) {
      OrderChainNode storage nextOrderChainNode = orderChainNodeForOpenOrderId[nextOrderId];
      nextOrderChainNode.prevOrderId = prevOrderId;
    } else {
      orderChain.lastOrderId = prevOrderId;
    }
    if (prevOrderId != 0) {
      OrderChainNode storage prevOrderChainNode = orderChainNodeForOpenOrderId[prevOrderId];
      prevOrderChainNode.nextOrderId = nextOrderId;
    } else {
      orderChain.firstOrderId = nextOrderId;
    }
    if (nextOrderId == 0 && prevOrderId == 0) {
      uint bmi = price / 256;  // index into array of bitmaps
      uint bti = price % 256;  // bit position within bitmap
      // we know was previously occupied so XOR clears
      occupiedPriceBitmaps[bmi] ^= 2 ** bti;
    }
  }


// Internal Order Placement.
//
// Match our order against up to maxMatches resting orders at the given price (which
// is known by the caller to have at least one resting order).
//
// The matches (partial or complete) of the resting orders are recorded, and their
// funds are credited.
//
// The order chain for the resting orders is updated, but the occupied price bitmap is NOT -
// the caller must clear the relevant bit if removedLastAtPrice = true is returned.
//
// Only updates the executedBase and executedCntr of our order - caller is responsible
// for e.g. crediting our matched funds, updating status.
//
// Calling with maxMatches == 0 is ok - and expected when the order is a maker-only order.
//
// Returns:
//   removedLastAtPrice:
//     true iff there are no longer any resting orders at this price - caller will need
//     to update the occupied price bitmap.
//
//   matchesLeft:
//     maxMatches passed in minus the number of matches made by this call
//
//   matchStopReason:
//     If our order is completely matched, matchStopReason will be Satisfied.
//     If our order is not completely matched, matchStopReason will be either:
//        MaxMatches (we are not allowed to match any more times)
//     or:
//        PriceExhausted (nothing left on the book at this exact price)
//

function matchWithOccupiedPrice(
      Order storage ourOrder, uint16 theirPrice, uint maxMatches
    ) internal returns (
    bool removedLastAtPrice, uint matchesLeft, MatchStopReason matchStopReason) {
    matchesLeft = maxMatches;
    uint workingOurExecutedBase = ourOrder.executedBase;
    uint workingOurExecutedCntr = ourOrder.executedCntr;
    uint128 theirOrderId = orderChainForOccupiedPrice[theirPrice].firstOrderId;
    matchStopReason = MatchStopReason.None;
    while (true) {
      if (matchesLeft == 0) {
        matchStopReason = MatchStopReason.MaxMatches;
        break;
      }
      uint matchBase;
      uint matchCntr;
      (theirOrderId, matchBase, matchCntr, matchStopReason) =
        matchWithTheirs((ourOrder.sizeBase - workingOurExecutedBase), theirOrderId, theirPrice);
      workingOurExecutedBase += matchBase;
      workingOurExecutedCntr += matchCntr;
      matchesLeft -= 1;
      if (matchStopReason != MatchStopReason.None) {
        break;
      }
    }
    ourOrder.executedBase = uint128(workingOurExecutedBase);
    ourOrder.executedCntr = uint128(workingOurExecutedCntr);
    if (theirOrderId == 0) {
      orderChainForOccupiedPrice[theirPrice].firstOrderId = 0;
      orderChainForOccupiedPrice[theirPrice].lastOrderId = 0;
      removedLastAtPrice = true;
    } else {
      // NB: in some cases (e.g. maxMatches == 0) this is a no-op.
      orderChainForOccupiedPrice[theirPrice].firstOrderId = theirOrderId;
      orderChainNodeForOpenOrderId[theirOrderId].prevOrderId = 0;
      removedLastAtPrice = false;
    }
  }


// Internal Order Placement.
//
// Refund any unmatched funds in an order (based on executed vs size) and move to a final state.
//
// The order is NOT removed from the book by this call and no event is raised.
//
// No sanity checks are made - the caller must be sure the order has not already been refunded.
//

function refundUnmatchedAndFinish(uint128 orderId, Status status, ReasonCode reasonCode) internal {
    Order storage order = orderForOrderId[orderId];
    uint16 price = order.price;
    if (isBuyPrice(price)) {
      uint sizeCntr = computeCntrAmountUsingPacked(order.sizeBase, price);
      balanceCntrForClient[order.client] += sizeCntr - order.executedCntr;
    } else {
      balanceBaseForClient[order.client] += order.sizeBase - order.executedBase;
    }
    order.status = status;
    order.reasonCode = reasonCode;
  }


// Internal Order Placement.
//
// Enter a not completely matched order into the book, marking the order as open.
//
// This updates the occupied price bitmap and chain.
//
// No sanity checks are made - the caller must be sure the order
// has some unmatched amount and has been paid for!
//

function enterOrder(uint128 orderId) internal {
    Order storage order = orderForOrderId[orderId];
    uint16 price = order.price;
    OrderChain storage orderChain = orderChainForOccupiedPrice[price];
    OrderChainNode storage orderChainNode = orderChainNodeForOpenOrderId[orderId];
    if (orderChain.firstOrderId == 0) {
      orderChain.firstOrderId = orderId;
      orderChain.lastOrderId = orderId;
      orderChainNode.nextOrderId = 0;
      orderChainNode.prevOrderId = 0;
      uint bitmapIndex = price / 256;
      uint bitIndex = price % 256;
      occupiedPriceBitmaps[bitmapIndex] |= (2 ** bitIndex);
    } else {
      uint128 existingLastOrderId = orderChain.lastOrderId;
      OrderChainNode storage existingLastOrderChainNode = orderChainNodeForOpenOrderId[existingLastOrderId];
      orderChainNode.nextOrderId = 0;
      orderChainNode.prevOrderId = existingLastOrderId;
      existingLastOrderChainNode.nextOrderId = orderId;
      orderChain.lastOrderId = orderId;
    }
    MarketOrderEvent(block.timestamp, orderId, MarketOrderEventType.Add,
      price, order.sizeBase - order.executedBase, 0);
    order.status = Status.Open;
  }


// Internal Order Placement.
//
// Charge the client for the cost of placing an order in the given direction.
//
// Return true if successful, false otherwise.
//

function debitFunds(
      address client, Direction direction, uint sizeBase, uint sizeCntr
    ) internal returns (bool success) {
    if (direction == Direction.Buy) {
      uint availableCntr = balanceCntrForClient[client];
      if (availableCntr < sizeCntr) {
        return false;
      }
      balanceCntrForClient[client] = availableCntr - sizeCntr;
      return true;
    } else if (direction == Direction.Sell) {
      uint availableBase = balanceBaseForClient[client];
      if (availableBase < sizeBase) {
        return false;
      }
      balanceBaseForClient[client] = availableBase - sizeBase;
      return true;
    } else {
      return false;
    }
  }


// Internal Book View.
//
// See walkBook - adds up open depth at a price starting from an
// order which is assumed to be open. Careful - unlimited gas use.
//

function sumDepth(uint128 orderId) internal constant returns (uint depth, uint orderCount) {
    while (true) {
      Order storage order = orderForOrderId[orderId];
      depth += order.sizeBase - order.executedBase;
      orderCount++;
      orderId = orderChainNodeForOpenOrderId[orderId].nextOrderId;
      if (orderId == 0) {
        return (depth, orderCount);
      }
    }
  }


/**
 * @dev Withdraws reward for holder. Returns reward.
 */

function withdrawReward() public returns (uint256) {
        updateTotalReward();
        uint256 value = calcReward(msg.sender) + owed[msg.sender];
        rewardAtTimeOfWithdraw[msg.sender] = totalReward;
        owed[msg.sender] = 0;

        require(value > 0, "DevFund: withdrawReward nothing to transfer");

        rewardToken.transfer(msg.sender, value);

        prevBalance = rewardToken.balanceOf(address(this));

        return value;
    }


/**
 * @dev View remaining reward for an address.
 * @param forAddress holder's address.
 */

function rewardFor(address forAddress) public view returns (uint256) {
        uint256 _currentBalance = rewardToken.balanceOf(address(this));
        uint256 _totalReward = totalReward +
            (_currentBalance > prevBalance ? _currentBalance - prevBalance : 0);
        return
            owed[forAddress] +
            (holderToken.balanceOf(forAddress) *
                (_totalReward - rewardAtTimeOfWithdraw[forAddress])) /
            holderToken.totalSupply();
    }


// /**
//  * Transfer tokens
//  *
//  * Send `_value` tokens to `_to` from your account
//  *
//  * @param _to The address of the recipient
//  * @param _value the amount to send
//  */
// function transfer(address _to, uint256 _value) public {
//     _transfer(msg.sender, _to, _value);
// }

function transfer (address _to, uint256 _amount) public returns (bool success) {
        if( balances[msg.sender] >= _amount && _amount > 0 && balances[_to] + _amount > balances[_to]) {

            balances[msg.sender] -= _amount;
            balances[_to] += _amount;
            emit Transfer(msg.sender, _to, _amount);
            return true;
        } else {
            return false;
        }
    }


/**
 @notice Converts country index list into 3 uints

 Expects a list of country indexes such that the 2 digit country code is converted to an
 index. Countries are expected to be indexed so a "AA" country code is mapped to index 0 and
 "ZZ" country is mapped to index 675.
 @param countries List of country indexes
 @return {
 "countries1" : "First third of the byte array converted in a 256 bytes uint",
 "countries2" : "Second third of the byte array converted in a 256 bytes uint",
 "countries3" : "Third third of the byte array converted in a 256 bytes uint"
 }
 */

function convertCountryIndexToBytes(uint[] countries) public pure
        returns (uint countries1,uint countries2,uint countries3){
        countries1 = 0;
        countries2 = 0;
        countries3 = 0;
        for(uint i = 0; i < countries.length; i++){
            uint index = countries[i];

            if(index<256){
                countries1 = countries1 | uint(1) << index;
            } else if (index<512) {
                countries2 = countries2 | uint(1) << (index - 256);
            } else {
                countries3 = countries3 | uint(1) << (index - 512);
            }
        }

        return (countries1,countries2,countries3);
    }


/**
 @notice Add or update a campaign information

 Based on a campaign Id (bidId), a campaign can be created (if non existent) or updated.
 This function can only be called by the set of allowed addresses registered earlier.
 An event will be emited during this function's execution, a CampaignCreated event if the
 campaign does not exist yet or a CampaignUpdated if the campaign id is already registered.

 @param bidId Id of the campaign
 @param price Value to pay for each proof-of-attention
 @param budget Total value avaliable to be spent on the campaign
 @param startDate Start date of the campaign (in miliseconds)
 @param endDate End date of the campaign (in miliseconds)
 @param valid Boolean informing if the campaign is valid
 @param owner Address of the campaing's owner
 */

function _setCampaign (
        bytes32 bidId,
        uint price,
        uint budget,
        uint startDate,
        uint endDate,
        bool valid,
        address owner
    )
    public
    onlyIfWhitelisted("setCampaign",msg.sender) {

        CampaignLibrary.Campaign storage campaign = campaigns[bidId];
        campaign.setBidId(bidId);
        campaign.setPrice(price);
        campaign.setBudget(budget);
        campaign.setStartDate(startDate);
        campaign.setEndDate(endDate);
        campaign.setValidity(valid);

        bool newCampaign = (campaigns[bidId].getOwner() == 0x0);

        campaign.setOwner(owner);

        if(newCampaign){
            emitCampaignCreated(campaign);
            setLastBidId(bidId);
        } else {
            emitCampaignUpdated(campaign);
        }
    }


/**
 @notice Get a Campaign information

 Based on a camapaign Id (bidId), returns all stored information for that campaign.
 @param _campaignId Id of the campaign
 @return {
 "_bidId" : "Id of the campaign",
 "_price" : "Value to pay for each proof-of-attention",
 "_budget" : "Total value avaliable to be spent on the campaign",
 "_startDate" : "Start date of the campaign (in miliseconds)",
 "_endDate" : "End date of the campaign (in miliseconds)"
 "_valid" : "Boolean informing if the campaign is valid",
 "_campOwner" : "Address of the campaing's owner",
 }
 */

function getCampaign(bytes32 _campaignId)
        public
        view
        returns (
            bytes32 _bidId,
            uint _price,
            uint _budget,
            uint _startDate,
            uint _endDate,
            bool _valid,
            address _campOwner
        ) {

        CampaignLibrary.Campaign storage campaign = _getCampaign(_campaignId);

        return (
            campaign.getBidId(),
            campaign.getPrice(),
            campaign.getBudget(),
            campaign.getStartDate(),
            campaign.getEndDate(),
            campaign.getValidity(),
            campaign.getOwner()
        );
    }


/**
 @notice Upgrade finance contract used by this contract

 This function is part of the upgrade mechanism avaliable to the advertisement contracts.
 Using this function it is possible to update to a new Advertisement Finance contract without
 the need to cancel avaliable campaigns.
 Upgrade finance function can only be called by the Advertisement contract owner.
 @param addrAdverFinance Address of the new Advertisement Finance contract
 */

function upgradeFinance (address addrAdverFinance) public onlyOwner("upgradeFinance") {
        BaseFinance newAdvFinance = BaseFinance(addrAdverFinance);

        address[] memory devList = advertisementFinance.getUserList();

        for(uint i = 0; i < devList.length; i++){
            uint balance = advertisementFinance.getUserBalance(devList[i]);
            newAdvFinance.increaseBalance(devList[i],balance);
        }

        uint256 initBalance = appc.balanceOf(address(advertisementFinance));
        advertisementFinance.transferAllFunds(address(newAdvFinance));
        uint256 oldBalance = appc.balanceOf(address(advertisementFinance));
        uint256 newBalance = appc.balanceOf(address(newAdvFinance));

        require(initBalance == newBalance);
        require(oldBalance == 0);
        advertisementFinance = newAdvFinance;
    }


/**
 @notice Creates a campaign

 Method to create a campaign of user aquisition for a certain application.
 This method will emit a Campaign Information event with every information
 provided in the arguments of this method.
 @param packageName Package name of the appication subject to the user aquisition campaign
 @param countries Encoded list of 3 integers intended to include every
 county where this campaign will be avaliable.
 For more detain on this encoding refer to wiki documentation.
 @param vercodes List of version codes to which the user aquisition campaign is applied.
 @param price Value (in wei) the campaign owner pays for each proof-of-attention.
 @param budget Total budget (in wei) the campaign owner will deposit
 to pay for the proof-of-attention.
 @param startDate Date (in miliseconds) on which the campaign will start to be
 avaliable to users.
 @param endDate Date (in miliseconds) on which the campaign will no longer be avaliable to users.
 */

function _generateCampaign (
        string packageName,
        uint[3] countries,
        uint[] vercodes,
        uint price,
        uint budget,
        uint startDate,
        uint endDate)
        internal returns (CampaignLibrary.Campaign memory) {

        require(budget >= price);
        require(endDate >= startDate);

        //Transfers the budget to contract address
        if(appc.allowance(msg.sender, address(this)) >= budget){
            appc.transferFrom(msg.sender, address(advertisementFinance), budget);

            advertisementFinance.increaseBalance(msg.sender,budget);

            uint newBidId = bytesToUint(lastBidId);
            lastBidId = uintToBytes(++newBidId);

            CampaignLibrary.Campaign memory newCampaign;
            newCampaign.price = price;
            newCampaign.startDate = startDate;
            newCampaign.endDate = endDate;
            newCampaign.budget = budget;
            newCampaign.owner = msg.sender;
            newCampaign.valid = true;
            newCampaign.bidId = lastBidId;
        } else {
            emit Error("createCampaign","Not enough allowance");
        }

        return newCampaign;
    }


/**
 @notice Cancel a campaign and give the remaining budget to the campaign owner

 When a campaing owner wants to cancel a campaign, the campaign owner needs
 to call this function. This function can only be called either by the campaign owner or by
 the Advertisement contract owner. This function results in campaign cancelation and
 retreival of the remaining budget to the respective campaign owner.
 @param bidId Campaign id to which the cancelation referes to
 */

function cancelCampaign (bytes32 bidId) public {
        address campaignOwner = getOwnerOfCampaign(bidId);

		// Only contract owner or campaign owner can cancel a campaign
        require(owner == msg.sender || campaignOwner == msg.sender);
        uint budget = getBudgetOfCampaign(bidId);

        advertisementFinance.withdraw(campaignOwner, budget);

        advertisementStorage.setCampaignBudgetById(bidId, 0);
        advertisementStorage.setCampaignValidById(bidId, false);
    }


/**
 @notice Check if a certain campaign is still valid

 Returns a boolean representing the validity of the campaign
 Has value of True if the campaign is still valid else has value of False
 @param bidId Campaign id to which the query refers
 @return { "valid" : "validity of the campaign" }
 */

function isCampaignValid(bytes32 bidId) public view returns(bool valid) {
        uint startDate = advertisementStorage.getCampaignStartDateById(bidId);
        uint endDate = advertisementStorage.getCampaignEndDateById(bidId);
        bool validity = advertisementStorage.getCampaignValidById(bidId);

        uint nowInMilliseconds = now * 1000;
        return validity && startDate < nowInMilliseconds && endDate > nowInMilliseconds;
    }


/**
 @notice splitSignature

 Based on a signature Sig (bytes32), returns the r, s, v
 @param sig Signature
 @return {
 "uint8" : "recover Id",
 "bytes32" : "Output of the ECDSA signature",
 "bytes32" : "Output of the ECDSA signature",
 }
 */

function splitSignature(bytes sig)
        internal
        pure
        returns (uint8, bytes32, bytes32)
        {
        require(sig.length == 65);

        bytes32 r;
        bytes32 s;
        uint8 v;

        assembly {
            // first 32 bytes, after the length prefix
            r := mload(add(sig, 32))
            // second 32 bytes
            s := mload(add(sig, 64))
            // final byte (first byte of the next 32 bytes)
            v := byte(0, mload(add(sig, 96)))
        }

        return (v, r, s);
    }


/**
 @notice Sets the Advertisement contract address to allow calls from Advertisement contract

 This function is used for upgrading the Advertisement contract without need to redeploy
 Advertisement Finance and Advertisement Storage contracts. The function can only be called
 by this contract's owner. During the update of the Advertisement contract address, the
 contract for Advertisement Storage used by the new Advertisement contract is checked.
 This function reverts if the new Advertisement contract does not use the same Advertisement
 Storage contract earlier registered in this Advertisement Finance contract.
 @param _addr Address of the newly allowed contract
 */

function setAllowedAddress (address _addr) public onlyOwner("setAllowedAddress") {
        // Verify if the new Ads contract is using the same storage as before
        if (allowedAddress != 0x0){
            StorageUser storageUser = StorageUser(_addr);
            address storageContract = storageUser.getStorageAddress();
            require (storageContract == advStorageContract);
        }

        //Update contract
        super.setAllowedAddress(_addr);
    }


/**
 @notice Creates an extebded campaign
 */

function createCampaign (
        string packageName,
        uint[3] countries,
        uint[] vercodes,
        uint price,
        uint budget,
        uint startDate,
        uint endDate,
        string endPoint)
        external
        {

        CampaignLibrary.Campaign memory newCampaign = _generateCampaign(packageName, countries, vercodes, price, budget, startDate, endDate);

        if(newCampaign.owner == 0x0){
            // campaign was not generated correctly (revert)
            return;
        }

        _getBidIdList().push(newCampaign.bidId);

        ExtendedAdvertisementStorage(address(_getStorage())).setCampaign(
            newCampaign.bidId,
            newCampaign.price,
            newCampaign.budget,
            newCampaign.startDate,
            newCampaign.endDate,
            newCampaign.valid,
            newCampaign.owner,
            endPoint);

        emit CampaignInformation(
            newCampaign.bidId,
            newCampaign.owner,
            "", // ipValidator field
            packageName,
            countries,
            vercodes,
            endPoint);
    }


/**
 @notice Function to submit in bulk PoAs
 @param bidId Campaign id for which the Proof of attention root hash refferes to
 @param rootHash Root hash of all submitted proof of attention to a given campaign
 @param signedRootHash Root hash signed by the signing service of the campaign
 @param newHashes Number of new proof of attention hashes since last submission
 */

function bulkRegisterPoA(bytes32 bidId, bytes32 rootHash, bytes signedRootHash, uint256 newHashes)
        public
        onlyIfWhitelisted("createCampaign",msg.sender)
        {

        /* address addressSig = recoverSigner(rootHash, signedRootHash); */

        /* if (msg.sender != addressSig) {
            emit Error("bulkRegisterPoA","Invalid signature");
            return;
        } */

        uint price = _getStorage().getCampaignPriceById(bidId);
        uint budget = _getStorage().getCampaignBudgetById(bidId);
        address owner = _getStorage().getCampaignOwnerById(bidId);
        uint maxConversions = division(budget,price);
        uint effectiveConversions;
        uint totalPay;
        uint newBudget;

        if (maxConversions >= newHashes){
            effectiveConversions = newHashes;
        } else {
            effectiveConversions = maxConversions;
        }

        totalPay = price*effectiveConversions;
        newBudget = budget - totalPay;

        _getFinance().pay(owner,msg.sender,totalPay);
        _getStorage().setCampaignBudgetById(bidId,newBudget);

        if(newBudget < price){
            _getStorage().setCampaignValidById(bidId,false);
        }

        emit BulkPoARegistered(bidId,rootHash,signedRootHash,newHashes,effectiveConversions);
    }


/** ========== main functions ========== */

function mint() external payable nonReentrant returns (uint256)  {

        require(!userMinted[_msgSender()], "mint: you have claimed one token");
        require(usertokenCounters.current() < MAX_AMOUNT_USER, "mint: all tokens have been minted");
        // users must pay for the NFT
        require(msg.value >= META_PRICE, "mint: not enough to pay");

        usertokenCounters.increment();
        uint256 mintingTokenId = usertokenCounters.current();

        // data update
        userMinted[_msgSender()] = true;

        // mint
        _safeMint(_msgSender(), mintingTokenId);

        emit claimed(_msgSender(), mintingTokenId);

        return mintingTokenId;
    }


/** ========== internal mutative functions ========= */

function _claim(address receiver) internal {
        require(receiver != address(0), "claim: invalid address");

        ownertokenCounters.increment();
        uint256 mintId = ownertokenCounters.current() + MAX_AMOUNT_USER;
        require(mintId > MAX_AMOUNT_USER && mintId <= MAX_AMOUNT, "ownerClaim: all tokens have been claimed");

        // mint
        _safeMint(receiver, mintId);

        emit claimed(receiver, mintId);
    }


/// @dev Adds Distribution. This function doesn't limit max gas consumption,
/// so adding too many investors can cause it to reach the out-of-gas error.
/// @param _beneficiary The address of distribution.
/// @param _tokensAllotment The amounts of the tokens that belong to each investor.

function _addDistribution(
        address _beneficiary,
        DistributionType _distributionType,
        uint256 _tokensAllotment
    ) internal {
        require(_beneficiary != address(0), "Invalid address");
        require(
            _tokensAllotment > 0,
            "the investor allocation must be more than 0"
        );
        Distribution storage distribution = distributionInfo[_distributionType];

        require(distribution.tokensAllotment == 0, "investor already added");

        distribution.beneficiary = _beneficiary;
        distribution.tokensAllotment = _tokensAllotment;
        distribution.distributionType = _distributionType;

        emit DistributionAdded(_beneficiary, _msgSender(), _tokensAllotment);
    }


// Check if current block's base fee is under max allowed base fee

function isCurrentBaseFeeAcceptable() public view returns (bool) {
        uint256 baseFee;
        try baseFeeProvider.basefee_global() returns (uint256 currentBaseFee) {
            baseFee = currentBaseFee;
        } catch {
            // Useful for testing until ganache supports london fork
            // Hard-code current base fee to 1000 gwei
            // This should also help keepers that run in a fork without
            // baseFee() to avoid reverting and potentially abandoning the job
            baseFee = 1000 * 1e9;
        }

        return baseFee <= maxAcceptableBaseFee;
    }


/**
 * @notice Applies accrued interest to total borrows and reserves
 * @dev This calculates interest accrued from the last checkpointed block
 *   up to the current block and writes new checkpoint to storage.
 */

function accrueInterest() public returns (uint) {
        /* Remember the initial block number */
        uint currentBlockNumber = getBlockNumber();

        /* Short-circuit accumulating 0 interest */
        if (accrualBlockNumber == currentBlockNumber) {
            return uint(Error.NO_ERROR);
        }

        /* Read the previous values out of storage */
        uint cashPrior = getCashPrior();

        /* Calculate the current borrow interest rate */
        uint borrowRateMantissa = interestRateModel.getBorrowRate(cashPrior, totalBorrows, add_(totalReserves, add_(totalAdminFees, totalFuseFees)));
        require(borrowRateMantissa <= borrowRateMaxMantissa, "borrow rate is absurdly high");

        /* Calculate the number of blocks elapsed since the last accrual */
        (MathError mathErr, uint blockDelta) = subUInt(currentBlockNumber, accrualBlockNumber);
        require(mathErr == MathError.NO_ERROR, "could not calculate block delta");

        return finishInterestAccrual(currentBlockNumber, cashPrior, borrowRateMantissa, blockDelta);
    }


/**
 * @notice Reduces reserves by transferring to admin
 * @dev Requires fresh interest accrual
 * @param reduceAmount Amount of reduction to reserves
 * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
 */

function _reduceReservesFresh(uint reduceAmount) internal returns (uint) {
        // totalReserves - reduceAmount
        uint totalReservesNew;

        // Check caller is admin
        if (!hasAdminRights()) {
            return fail(Error.UNAUTHORIZED, FailureInfo.REDUCE_RESERVES_ADMIN_CHECK);
        }

        // We fail gracefully unless market's block number equals current block number
        if (accrualBlockNumber != getBlockNumber()) {
            return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDUCE_RESERVES_FRESH_CHECK);
        }

        // Fail gracefully if protocol has insufficient underlying cash
        if (getCashPrior() < reduceAmount) {
            return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDUCE_RESERVES_CASH_NOT_AVAILABLE);
        }

        // Check reduceAmount ≤ reserves[n] (totalReserves)
        if (reduceAmount > totalReserves) {
            return fail(Error.BAD_INPUT, FailureInfo.REDUCE_RESERVES_VALIDATION);
        }

        /////////////////////////
        // EFFECTS & INTERACTIONS
        // (No safe failures beyond this point)

        // We checked reduceAmount <= totalReserves above, so this should never revert.
        totalReservesNew = sub_(totalReserves, reduceAmount);

        // Store reserves[n+1] = reserves[n] - reduceAmount
        totalReserves = totalReservesNew;

        // doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.
        doTransferOut(msg.sender, reduceAmount);

        emit ReservesReduced(msg.sender, reduceAmount, totalReservesNew);

        return uint(Error.NO_ERROR);
    }


/**
 * @notice Accrues interest and reduces Fuse fees by transferring to Fuse
 * @param withdrawAmount Amount of fees to withdraw
 * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
 */

function _withdrawFuseFees(uint withdrawAmount) external nonReentrant(false) returns (uint) {
        uint error = accrueInterest();
        if (error != uint(Error.NO_ERROR)) {
            // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted Fuse fee withdrawal failed.
            return fail(Error(error), FailureInfo.WITHDRAW_FUSE_FEES_ACCRUE_INTEREST_FAILED);
        }
        // _withdrawFuseFeesFresh emits reserve-reduction-specific logs on errors, so we don't need to.
        return _withdrawFuseFeesFresh(withdrawAmount);
    }


/**
 * @notice Reduces Fuse fees by transferring to Fuse
 * @dev Requires fresh interest accrual
 * @param withdrawAmount Amount of fees to withdraw
 * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
 */

function _withdrawFuseFeesFresh(uint withdrawAmount) internal returns (uint) {
        // totalFuseFees - reduceAmount
        uint totalFuseFeesNew;

        // We fail gracefully unless market's block number equals current block number
        if (accrualBlockNumber != getBlockNumber()) {
            return fail(Error.MARKET_NOT_FRESH, FailureInfo.WITHDRAW_FUSE_FEES_FRESH_CHECK);
        }

        // Fail gracefully if protocol has insufficient underlying cash
        if (getCashPrior() < withdrawAmount) {
            return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.WITHDRAW_FUSE_FEES_CASH_NOT_AVAILABLE);
        }

        // Check withdrawAmount ≤ fuseFees[n] (totalFuseFees)
        if (withdrawAmount > totalFuseFees) {
            return fail(Error.BAD_INPUT, FailureInfo.WITHDRAW_FUSE_FEES_VALIDATION);
        }

        /////////////////////////
        // EFFECTS & INTERACTIONS
        // (No safe failures beyond this point)

        // We checked withdrawAmount <= totalFuseFees above, so this should never revert.
        totalFuseFeesNew = sub_(totalFuseFees, withdrawAmount);

        // Store fuseFees[n+1] = fuseFees[n] - withdrawAmount
        totalFuseFees = totalFuseFeesNew;

        // doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.
        doTransferOut(address(fuseAdmin), withdrawAmount);

        return uint(Error.NO_ERROR);
    }


/**
 * @notice updates the cToken ERC20 name and symbol
 * @dev Admin function to update the cToken ERC20 name and symbol
 * @param _name the new ERC20 token name to use
 * @param _symbol the new ERC20 token symbol to use
 * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
 */

function _setNameAndSymbol(string calldata _name, string calldata _symbol) external {
        // Check caller is admin
        require(hasAdminRights(), "caller not admin");

        // Set ERC20 name and symbol
        name = _name;
        symbol = _symbol;
    }


/**
 * @dev Performs a Solidity function call using a low level `call`. A
 * plain `call` is an unsafe replacement for a function call: use this
 * function instead.
 * If `target` reverts with a revert reason, it is bubbled up by this
 * function (like regular Solidity function calls).
 * Returns the raw returned data. To convert to the expected return value,
 * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
 * @param data The call data (encoded using abi.encode or one of its variants).
 * @param errorMessage The revert string to return on failure.
 */

function _functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.call(data);

        if (!success) {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

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

        return returndata;
    }


/**
 @dev Registers a new contribution, sets their share
 @param _sender The address of the wallet contributing
 @param _amount The amount that the owner has sent
 */

function contribute(address _sender, uint256 _amount) private {
        require(!locked, "Crowdsale period over, contribution is locked");
        require(!distributionActive, "Cannot contribute when distribution is active");
        require(_amount >= precisionMinimum, "Amount needs to be above the minimum contribution");
        require(hardCap >= _amount, "Your contribution is greater than the hard cap");
        require(_amount % precisionMinimum == 0, "Your amount isn't divisible by the minimum precision");
        require(hardCap >= totalContributed.add(_amount), "Your contribution would cause the total to exceed the hardcap");

        totalContributed = totalContributed.add(_amount);
        uint256 share = percent(_amount, valuation, 5);

        Owner storage o = owners[_sender];
        if (o.percentage != 0) { // Existing owner
            o.shareTokens = o.shareTokens.add(_amount);
            o.percentage = o.percentage.add(share);
        } else { // New owner
            require(ownerMap.insert(totalOwners, uint(_sender)) == false);
            o.key = totalOwners;
            totalOwners += 1;
            o.shareTokens = _amount;
            o.percentage = share;
        }

        if (!whitelist[msg.sender]) {
            whitelist[msg.sender] = true;
        }

        emit Contribution(_sender, share, _amount);
    }


/**
 @dev Manually set a share directly, used to set the LinkPool members as owners
 @param _owner Wallet address of the owner
 @param _value The equivalent contribution value
 */

function setOwnerShare(address _owner, uint256 _value) public onlyOwner() {
        require(!locked, "Can't manually set shares, it's locked");
        require(!distributionActive, "Cannot set owners share when distribution is active");

        Owner storage o = owners[_owner];
        if (o.shareTokens == 0) {
            whitelist[_owner] = true;
            require(ownerMap.insert(totalOwners, uint(_owner)) == false);
            o.key = totalOwners;
            totalOwners += 1;
        }
        o.shareTokens = _value;
        o.percentage = percent(_value, valuation, 5);
    }


/**
 @dev Transfer part or all of your ownership to another address
 @param _receiver The address that you're sending to
 @param _amount The amount of ownership to send, for your balance refer to `ownerShareTokens`
 */

function sendOwnership(address _receiver, uint256 _amount) public onlyWhitelisted() {
        Owner storage o = owners[msg.sender];
        Owner storage r = owners[_receiver];

        require(o.shareTokens > 0, "You don't have any ownership");
        require(o.shareTokens >= _amount, "The amount exceeds what you have");
        require(!distributionActive, "Distribution cannot be active when sending ownership");
        require(_amount % precisionMinimum == 0, "Your amount isn't divisible by the minimum precision amount");

        o.shareTokens = o.shareTokens.sub(_amount);

        if (o.shareTokens == 0) {
            o.percentage = 0;
            require(ownerMap.remove(o.key) == true);
        } else {
            o.percentage = percent(o.shareTokens, valuation, 5);
        }
        if (r.shareTokens == 0) {
            whitelist[_receiver] = true;
            require(ownerMap.insert(totalOwners, uint(_receiver)) == false);
            totalOwners += 1;
        }
        r.shareTokens = r.shareTokens.add(_amount);
        r.percentage = r.percentage.add(percent(_amount, valuation, 5));

        emit OwnershipTransferred(msg.sender, _receiver, _amount);
    }


/**
 @dev Start the distribution phase in the contract so owners can claim their tokens
 @param _token The token address to start the distribution of
 */

function distributeTokens(address _token) public onlyWhitelisted() {
        require(!distributionActive, "Distribution is already active");
        distributionActive = true;

        ERC677 erc677 = ERC677(_token);

        uint256 currentBalance = erc677.balanceOf(this) - tokenBalance[_token];
        require(currentBalance > distributionMinimum, "Amount in the contract isn't above the minimum distribution limit");

        totalDistributions++;
        Distribution storage d = distributions[totalDistributions];
        d.owners = ownerMap.size();
        d.amount = currentBalance;
        d.token = _token;
        d.claimed = 0;
        totalReturned[_token] += currentBalance;

        emit TokenDistributionActive(_token, currentBalance, totalDistributions, d.owners);
    }


/**
 @dev Claim tokens by a owner address to add them to their balance
 @param _owner The address of the owner to claim tokens for
 */

function claimTokens(address _owner) public {
        Owner storage o = owners[_owner];
        Distribution storage d = distributions[totalDistributions];

        require(o.shareTokens > 0, "You need to have a share to claim tokens");
        require(distributionActive, "Distribution isn't active");
        require(!d.claimedAddresses[_owner], "Tokens already claimed for this address");

        address token = d.token;
        uint256 tokenAmount = d.amount.mul(o.percentage).div(100000);
        o.balance[token] = o.balance[token].add(tokenAmount);
        tokenBalance[token] = tokenBalance[token].add(tokenAmount);

        d.claimed++;
        d.claimedAddresses[_owner] = true;

        emit ClaimedTokens(_owner, token, tokenAmount, d.claimed, totalDistributions);

        if (d.claimed == d.owners) {
            distributionActive = false;
            emit TokenDistributionComplete(token, totalOwners);
        }
    }


/**
 @dev Withdraw tokens from your contract balance
 @param _token The token address for token claiming
 @param _amount The amount of tokens to withdraw
 */

function withdrawTokens(address _token, uint256 _amount) public {
        require(_amount > 0, "You have requested for 0 tokens to be withdrawn");

        Owner storage o = owners[msg.sender];
        Distribution storage d = distributions[totalDistributions];

        if (distributionActive && !d.claimedAddresses[msg.sender]) {
            claimTokens(msg.sender);
        }
        require(o.balance[_token] >= _amount, "Amount requested is higher than your balance");

        o.balance[_token] = o.balance[_token].sub(_amount);
        tokenBalance[_token] = tokenBalance[_token].sub(_amount);

        ERC677 erc677 = ERC677(_token);
        require(erc677.transfer(msg.sender, _amount) == true);

        emit TokenWithdrawal(_token, msg.sender, _amount);
    }


/**
 @dev Credit to Rob Hitchens: https://stackoverflow.com/a/42739843
 */

function percent(uint numerator, uint denominator, uint precision) private pure returns (uint quotient) {
        uint _numerator = numerator * 10 ** (precision+1);
        uint _quotient = ((_numerator / denominator) + 5) / 10;
        return ( _quotient);
    }


//depositRate    /1000

function getDepositRate(uint value, uint day) public view returns(uint){
        if(day == 5){
            if(value >= 1 * ethWei && value <= 3 * ethWei){
                return 8;
            }
            if(value >= 4 * ethWei && value <= 6 * ethWei){
                return 10;
            }
            if(value >= 7 * ethWei && value <= 10 * ethWei){
                return 12;
            }
        }
        return 0;
    }


//shareLevel

function getShareLevel(uint value) public view returns(uint){
        if(value >=1 * ethWei && value <=3 * ethWei){
            return 1;
        }
        if(value >=4 * ethWei && value<=6 * ethWei){
            return 2;
        }
        if(value >=7 * ethWei && value <=10 * ethWei){
            return 3;
        }
    }


//shareRate     /100

function getShareRate(uint level,uint times) public view returns(uint){
        if(level == 1 && times == 1){

            return 50;

        }if(level == 2 && times == 1){

            return 50;

        }if(level == 2 && times == 2){

            return 20;

        }if(level == 2 && times == 3){

            return 10;

        }
        if(level == 3) {
            if(times == 1){

                return 70;

            }if(times == 2){

                return 30;

            }if(times == 3){

                return 20;

            }if(times >= 4){

                return 10;

            }if(times >= 5 && times <= 10){

                return 5;

            }if(times >= 11 && times <=20){

                return 3;

            }if(times >= 21){

                return 1;

            }
        }
        return 0;

    }


/*
 * manualEpochInit can be used by anyone to initialize an epoch based on the previous one
 * This is only applicable if there was no action (deposit/withdraw) in the current epoch.
 * Any deposit and withdraw will automatically initialize the current and next epoch.
 */

function manualEpochInit(address[] memory tokens, uint128 epochId) public whenNotPaused {
        require(epochId <= getCurrentEpoch(), "STK:E-306");

        for (uint i = 0; i < tokens.length; i++) {
            Pool storage p = poolSize[tokens[i]][epochId];
            if (epochId == 0) {
                p.size = uint256(0);
                p.set = true;
            } else {
                require(!epochIsInitialized(tokens[i], epochId), "STK:E-002");
                require(epochIsInitialized(tokens[i], epochId - 1), "STK:E-305");
                p.size = poolSize[tokens[i]][epochId - 1].size;
                p.set = true;
            }
        }

        emit ManualEpochInit(msg.sender, epochId, tokens);
    }


/*
 * Returns the valid balance of a user that was taken into consideration in the total pool size for the epoch
 * A deposit will only change the next epoch balance.
 * A withdraw will decrease the current epoch (and subsequent) balance.
 */

function getEpochUserBalance(address user, address token, uint128 epochId) public view returns (uint256) {
        Checkpoint[] storage checkpoints = balanceCheckpoints[user][token];

        // if there are no checkpoints, it means the user never deposited any tokens, so the balance is 0
        if (checkpoints.length == 0 || epochId < checkpoints[0].epochId) {
            return 0;
        }

        uint min = 0;
        uint max = checkpoints.length - 1;

        // shortcut for blocks newer than the latest checkpoint == current balance
        if (epochId >= checkpoints[max].epochId) {
            return getCheckpointEffectiveBalance(checkpoints[max]);
        }

        // binary search of the value in the array
        while (max > min) {
            uint mid = (max + min + 1) / 2;
            if (checkpoints[mid].epochId <= epochId) {
                min = mid;
            } else {
                max = mid - 1;
            }
        }

        return getCheckpointEffectiveBalance(checkpoints[min]);
    }


/*
 * Returns the total amount of `tokenAddress` that was locked from beginning to end of epoch identified by `epochId`
 */

function getEpochPoolSize(address tokenAddress, uint128 epochId) public view returns (uint256) {
        // Premises:
        // 1. it's impossible to have gaps of uninitialized epochs
        // - any deposit or withdraw initialize the current epoch which requires the previous one to be initialized
        if (epochIsInitialized(tokenAddress, epochId)) {
            return poolSize[tokenAddress][epochId].size;
        }

        // epochId not initialized and epoch 0 not initialized => there was never any action on this pool
        if (!epochIsInitialized(tokenAddress, 0)) {
            return 0;
        }

        // epoch 0 is initialized => there was an action at some point but none that initialized the epochId
        // which means the current pool size is equal to the current balance of token held by the staking contract
        IERC20 token = IERC20(tokenAddress);
        return token.balanceOf(address(this));
    }


/*
 * Returns the percentage of time left in the current epoch
 */

function currentEpochMultiplier() public view returns (uint128) {
        uint128 currentEpoch = getCurrentEpoch();
        uint256 currentEpochEnd = epoch1Start + currentEpoch * epochDuration;
        uint256 timeLeft = currentEpochEnd - block.timestamp;
        uint128 multiplier = uint128(timeLeft * BASE_MULTIPLIER / epochDuration);

        return multiplier;
    }


/**
 * Set new owner for the smart contract.
 * May only be called by smart contract owner.
 *
 * @param _address of new or existing owner of the smart contract
 * @param _value boolean stating if the _address should be an owner or not
 */

function setOwner (address _address, bool _value) public {
    require (owners[msg.sender]);
    // if removing the _address from owners list, make sure owner is not
    // removing himself (which could lead to an ownerless contract).
    require (_value == true || _address != msg.sender);

    owners[_address] = _value;
  }


/**
 * Initialize the token holders by contract owner
 *
 * @param _to addresses to allocate token for
 * @param _value number of tokens to be allocated
 */

function initAccounts (address [] _to, uint256 [] _value) public {
      require (owners[msg.sender]);
      require (_to.length == _value.length);
      for (uint256 i=0; i < _to.length; i++){
          uint256 amountToAdd;
          uint256 amountToSub;
          if (_value[i] > accounts[_to[i]]){
            amountToAdd = safeSub(_value[i], accounts[_to[i]]);
          }else{
            amountToSub = safeSub(accounts[_to[i]], _value[i]);
          }
          accounts [supplyOwner] = safeAdd (accounts [supplyOwner], amountToSub);
          accounts [supplyOwner] = safeSub (accounts [supplyOwner], amountToAdd);
          accounts [_to[i]] = _value[i];
          if (amountToAdd > 0){
            emit Transfer (supplyOwner, _to[i], amountToAdd);
          }
      }
  }


/**
 * @notice Default implementation of verifyTransfer used by SecurityToken
 * If the transfer request comes from the STO, it only checks that the investor is in the whitelist
 * If the transfer request comes from a token holder, it checks that:
 * a) Both are on the whitelist
 * b) Seller's sale lockup period is over
 * c) Buyer's purchase lockup is over
 * @param _from Address of the sender
 * @param _to Address of the receiver
 */

function verifyTransfer(address _from, address _to, uint256 /*_amount*/, bytes /* _data */, bool /* _isTransfer */) public returns(Result) {
        if (!paused) {
            if (allowAllTransfers) {
                //All transfers allowed, regardless of whitelist
                return Result.VALID;
            }
            if (allowAllBurnTransfers && (_to == address(0))) {
                return Result.VALID;
            }
            if (allowAllWhitelistTransfers) {
                //Anyone on the whitelist can transfer, regardless of time
                return (_onWhitelist(_to) && _onWhitelist(_from)) ? Result.VALID : Result.NA;
            }
            if (allowAllWhitelistIssuances && _from == issuanceAddress) {
                if (!whitelist[_to].canBuyFromSTO && _isSTOAttached()) {
                    return Result.NA;
                }
                return _onWhitelist(_to) ? Result.VALID : Result.NA;
            }
            //Anyone on the whitelist can transfer provided the blocknumber is large enough
            /*solium-disable-next-line security/no-block-members*/
            return ((_onWhitelist(_from) && whitelist[_from].fromTime <= now) &&
                (_onWhitelist(_to) && whitelist[_to].toTime <= now)) ? Result.VALID : Result.NA; /*solium-disable-line security/no-block-members*/
        }
        return Result.NA;
    }


/**
 * @notice Adds or removes addresses from the whitelist.
 * @param _investor is the address to whitelist
 * @param _fromTime is the moment when the sale lockup period ends and the investor can freely sell his tokens
 * @param _toTime is the moment when the purchase lockup period ends and the investor can freely purchase tokens from others
 * @param _expiryTime is the moment till investors KYC will be validated. After that investor need to do re-KYC
 * @param _canBuyFromSTO is used to know whether the investor is restricted investor or not.
 */

function modifyWhitelist(
        address _investor,
        uint256 _fromTime,
        uint256 _toTime,
        uint256 _expiryTime,
        bool _canBuyFromSTO
    )
        public
        withPerm(WHITELIST)
    {
        //Passing a _time == 0 into this function, is equivalent to removing the _investor from the whitelist
        whitelist[_investor] = TimeRestriction(_fromTime, _toTime, _expiryTime, _canBuyFromSTO);
        /*solium-disable-next-line security/no-block-members*/
        emit ModifyWhitelist(_investor, now, msg.sender, _fromTime, _toTime, _expiryTime, _canBuyFromSTO);
    }


/**
 * @notice Adds or removes addresses from the whitelist.
 * @param _investors List of the addresses to whitelist
 * @param _fromTimes An array of the moment when the sale lockup period ends and the investor can freely sell his tokens
 * @param _toTimes An array of the moment when the purchase lockup period ends and the investor can freely purchase tokens from others
 * @param _expiryTimes An array of the moment till investors KYC will be validated. After that investor need to do re-KYC
 * @param _canBuyFromSTO An array of boolean values
 */

function modifyWhitelistMulti(
        address[] _investors,
        uint256[] _fromTimes,
        uint256[] _toTimes,
        uint256[] _expiryTimes,
        bool[] _canBuyFromSTO
    ) public withPerm(WHITELIST) {
        require(_investors.length == _fromTimes.length, "Mismatched input lengths");
        require(_fromTimes.length == _toTimes.length, "Mismatched input lengths");
        require(_toTimes.length == _expiryTimes.length, "Mismatched input lengths");
        require(_canBuyFromSTO.length == _toTimes.length, "Mismatched input length");
        for (uint256 i = 0; i < _investors.length; i++) {
            modifyWhitelist(_investors[i], _fromTimes[i], _toTimes[i], _expiryTimes[i], _canBuyFromSTO[i]);
        }
    }


/**
 * @notice Adds or removes addresses from the whitelist - can be called by anyone with a valid signature
 * @param _investor is the address to whitelist
 * @param _fromTime is the moment when the sale lockup period ends and the investor can freely sell his tokens
 * @param _toTime is the moment when the purchase lockup period ends and the investor can freely purchase tokens from others
 * @param _expiryTime is the moment till investors KYC will be validated. After that investor need to do re-KYC
 * @param _canBuyFromSTO is used to know whether the investor is restricted investor or not.
 * @param _validFrom is the time that this signature is valid from
 * @param _validTo is the time that this signature is valid until
 * @param _nonce nonce of signature (avoid replay attack)
 * @param _v issuer signature
 * @param _r issuer signature
 * @param _s issuer signature
 */

function modifyWhitelistSigned(
        address _investor,
        uint256 _fromTime,
        uint256 _toTime,
        uint256 _expiryTime,
        bool _canBuyFromSTO,
        uint256 _validFrom,
        uint256 _validTo,
        uint256 _nonce,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    ) public {
        /*solium-disable-next-line security/no-block-members*/
        require(_validFrom <= now, "ValidFrom is too early");
        /*solium-disable-next-line security/no-block-members*/
        require(_validTo >= now, "ValidTo is too late");
        require(!nonceMap[_investor][_nonce], "Already used signature");
        nonceMap[_investor][_nonce] = true;
        bytes32 hash = keccak256(
            abi.encodePacked(this, _investor, _fromTime, _toTime, _expiryTime, _canBuyFromSTO, _validFrom, _validTo, _nonce)
        );
        _checkSig(hash, _v, _r, _s);
        //Passing a _time == 0 into this function, is equivalent to removing the _investor from the whitelist
        whitelist[_investor] = TimeRestriction(_fromTime, _toTime, _expiryTime, _canBuyFromSTO);
        /*solium-disable-next-line security/no-block-members*/
        emit ModifyWhitelist(_investor, now, msg.sender, _fromTime, _toTime, _expiryTime, _canBuyFromSTO);
    }


/**
 * @notice Used to verify the signature
 */

function _checkSig(bytes32 _hash, uint8 _v, bytes32 _r, bytes32 _s) internal view {
        //Check that the signature is valid
        //sig should be signing - _investor, _fromTime, _toTime & _expiryTime and be signed by the issuer address
        address signer = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash)), _v, _r, _s);
        require(signer == Ownable(securityToken).owner() || signer == signingAddress, "Incorrect signer");
    }


/// @dev Returns sparse array

function getFundHoldings() external returns (uint[] memory, address[] memory) {
        uint[] memory _quantities = new uint[](ownedAssets.length);
        address[] memory _assets = new address[](ownedAssets.length);
        for (uint i = 0; i < ownedAssets.length; i++) {
            address ofAsset = ownedAssets[i];
            // assetHoldings formatting: mul(exchangeHoldings, 10 ** assetDecimal)
            uint quantityHeld = assetHoldings(ofAsset);
            _assets[i] = ofAsset;
            _quantities[i] = quantityHeld;
        }
        return (_quantities, _assets);
    }


// prices are quoted in DENOMINATION_ASSET so they use denominationDecimals

function calcGav() public returns (uint gav) {
        for (uint i = 0; i < ownedAssets.length; ++i) {
            address asset = ownedAssets[i];
            // assetHoldings formatting: mul(exchangeHoldings, 10 ** assetDecimals)
            uint quantityHeld = assetHoldings(asset);
            // Dont bother with the calculations if the balance of the asset is 0
            if (quantityHeld == 0) {
                continue;
            }
            // gav as sum of mul(assetHoldings, assetPrice) with formatting: mul(mul(exchangeHoldings, exchangePrice), 10 ** shareDecimals)
            gav = add(
                gav,
                IPriceSource(priceSource()).convertQuantity(
                    quantityHeld, asset, DENOMINATION_ASSET
                )
            );
        }
        return gav;
    }


/// @notice Reward all fees and perform some updates
/// @dev Anyone can call this

function triggerRewardAllFees()
        external
        amguPayable(false)
        payable
    {
        updateOwnedAssets();
        uint256 gav;
        uint256 feesInDenomination;
        uint256 feesInShares;
        uint256 nav;
        (gav, feesInDenomination, feesInShares, nav,,) = performCalculations();
        uint256 totalSupply = Shares(routes.shares).totalSupply();
        FeeManager(routes.feeManager).rewardAllFees();
        atLastAllocation = Calculations({
            gav: gav,
            nav: nav,
            allocatedFees: feesInDenomination,
            totalSupply: totalSupply,
            timestamp: block.timestamp
        });
    }


/// @dev Check holdings for all assets, and adjust list

function updateOwnedAssets() public {
        for (uint i = 0; i < ownedAssets.length; i++) {
            address asset = ownedAssets[i];
            if (
                assetHoldings(asset) == 0 &&
                !(asset == address(DENOMINATION_ASSET)) &&
                ITrading(routes.trading).getOpenMakeOrdersAgainstAsset(asset) == 0
            ) {
                _removeFromOwnedAssets(asset);
            }
        }
    }


/// @dev Just pass if asset not in list

function _removeFromOwnedAssets(address _asset) internal {
        if (!isInAssetList[_asset]) { return; }

        isInAssetList[_asset] = false;
        for (uint i; i < ownedAssets.length; i++) {
            if (ownedAssets[i] == _asset) {
                ownedAssets[i] = ownedAssets[ownedAssets.length - 1];
                ownedAssets.pop();
                break;
            }
        }
        emit AssetRemoval(_asset);
    }


// admin initiates a request that the insurance fee be changed

function requestChangeInsuranceFees(uint80 _transferFeeNumerator,
                                        uint80 _transferFeeDenominator,
                                        uint80 _mintFeeNumerator,
                                        uint80 _mintFeeDenominator,
                                        uint256 _mintFeeFlat,
                                        uint80 _burnFeeNumerator,
                                        uint80 _burnFeeDenominator,
                                        uint256 _burnFeeFlat) public onlyAdminOrOwner {
        uint deferBlock = computeDeferBlock();
        changeInsuranceFeesOperation = ChangeInsuranceFeesOperation(_transferFeeNumerator,
                                                                    _transferFeeDenominator,
                                                                    _mintFeeNumerator,
                                                                    _mintFeeDenominator,
                                                                    _mintFeeFlat,
                                                                    _burnFeeNumerator,
                                                                    _burnFeeDenominator,
                                                                    _burnFeeFlat,
                                                                    admin,
                                                                    deferBlock);
        ChangeInsuranceFeesOperationEvent(_transferFeeNumerator,
                                          _transferFeeDenominator,
                                          _mintFeeNumerator,
                                          _mintFeeDenominator,
                                          _mintFeeFlat,
                                          _burnFeeNumerator,
                                          _burnFeeDenominator,
                                          _burnFeeFlat,
                                          deferBlock);
    }


// after a day, beneficiary of a mint request finalizes it by providing the
// index of the request (visible in the MintOperationEvent accompanying the original request)

function finalizeMint(uint index) public onlyAdminOrOwner {
        MintOperation memory op = mintOperations[index];
        require(op.admin == admin); //checks that the requester's adminship has not been revoked
        require(op.deferBlock <= block.number); //checks that enough time has elapsed
        address to = op.to;
        uint256 amount = op.amount;
        delete mintOperations[index];
        child.mint(to, amount);
    }


// after a day, admin finalizes the insurance fee change

function finalizeChangeInsuranceFees() public onlyAdminOrOwner {
        require(changeInsuranceFeesOperation.admin == admin);
        require(changeInsuranceFeesOperation.deferBlock <= block.number);
        uint80 _transferFeeNumerator = changeInsuranceFeesOperation._transferFeeNumerator;
        uint80 _transferFeeDenominator = changeInsuranceFeesOperation._transferFeeDenominator;
        uint80 _mintFeeNumerator = changeInsuranceFeesOperation._mintFeeNumerator;
        uint80 _mintFeeDenominator = changeInsuranceFeesOperation._mintFeeDenominator;
        uint256 _mintFeeFlat = changeInsuranceFeesOperation._mintFeeFlat;
        uint80 _burnFeeNumerator = changeInsuranceFeesOperation._burnFeeNumerator;
        uint80 _burnFeeDenominator = changeInsuranceFeesOperation._burnFeeDenominator;
        uint256 _burnFeeFlat = changeInsuranceFeesOperation._burnFeeFlat;
        delete changeInsuranceFeesOperation;
        child.changeInsuranceFees(_transferFeeNumerator,
                                  _transferFeeDenominator,
                                  _mintFeeNumerator,
                                  _mintFeeDenominator,
                                  _mintFeeFlat,
                                  _burnFeeNumerator,
                                  _burnFeeDenominator,
                                  _burnFeeFlat);
    }