/**
 * @dev Change the feeTake amount.
 * @param _feeTake uint256 New fee take.
 */

function changeFeeTake(uint256 _feeTake) public onlyAdmin {
    // The new feeTake should be greater than or equal to the feeRebate.
    require(_feeTake >= feeRebate);

    // Store the new value.
    feeTake = _feeTake;
  }


/**
 * @dev Change the feeRebate amount.
 * @param _feeRebate uint256 New fee rebate.
 */

function changeFeeRebate(uint256 _feeRebate) public onlyAdmin {
    // The new feeRebate should be less than or equal to the feeTake.
    require(_feeRebate <= feeTake);

    // Store the new value.
    feeRebate = _feeRebate;
  }


/**
 * @dev Add a ERC20 token contract address to the whitelisted ones.
 * @param token address Address of the contract to be added to the whitelist.
 */

function addWhitelistedTokenAddr(address token) public onlyAdmin {
    // Token address should not be 0x0 (ether) and it should not be already whitelisted.
    require(token != 0x0 && !whitelistedTokens[token]);

    // Change the flag for this contract address to true.
    whitelistedTokens[token] = true;
  }


/**
 * @dev Remove a ERC20 token contract address from the whitelisted ones.
 * @param token address Address of the contract to be removed from the whitelist.
 */

function removeWhitelistedTokenAddr(address token) public onlyAdmin {
    // Token address should not be 0x0 (ether) and it should be whitelisted.
    require(token != 0x0 && whitelistedTokens[token]);

    // Change the flag for this contract address to false.
    whitelistedTokens[token] = false;
  }


/**
 * @dev Add an user address to the whitelisted ones.
 * @param user address Address to be added to the whitelist.
 */

function addWhitelistedUserAddr(address user) public onlyAdmin {
    // Address provided should be valid and not already whitelisted.
    require(user != 0x0 && !whitelistedUsers[user]);

    // Change the flag for this address to false.
    whitelistedUsers[user] = true;
  }


/**
 * @dev Remove an user address from the whitelisted ones.
 * @param user address Address to be removed from the whitelist.
 */

function removeWhitelistedUserAddr(address user) public onlyAdmin {
    // Address provided should be valid and whitelisted.
    require(user != 0x0 && whitelistedUsers[user]);

    // Change the flag for this address to false.
    whitelistedUsers[user] = false;
  }


/**
 * @dev Deposit wei into the exchange contract.
 */

function deposit() public payable {
    // Only whitelisted users can make deposits.
    require(whitelistedUsers[msg.sender]);

    // Add the deposited wei amount to the user balance.
    tokens[0x0][msg.sender] = tokens[0x0][msg.sender].add(msg.value);

    // Trigger the event.
    Deposit(0x0, msg.sender, msg.value, tokens[0x0][msg.sender]);
  }


/**
 * @dev Withdraw wei from the exchange contract back to the user.
 * @param amount uint256 Wei amount to be withdrawn.
 */

function withdraw(uint256 amount) public {
    // Requester should have enough balance.
    require(tokens[0x0][msg.sender] >= amount);

    // Substract the withdrawn wei amount from the user balance.
    tokens[0x0][msg.sender] = tokens[0x0][msg.sender].sub(amount);

    // Transfer the wei to the requester.
    msg.sender.transfer(amount);

    // Trigger the event.
    Withdraw(0x0, msg.sender, amount, tokens[0x0][msg.sender]);
  }


/**
 * @dev Perform a new token deposit to the exchange contract.
 * @dev Remember to call ERC20(address).approve(this, amount) or this contract will not
 *      be able to do the transfer on your behalf.
 * @param token  address Address of the deposited token contract
 * @param amount uint256 Amount to be deposited
 */

function depositToken(address token, uint256 amount)
    public
  {
    // Should not deposit wei using this function and
    // token contract address should be whitelisted.
    require(token != 0x0 && whitelistedTokens[token]);

    // Only whitelisted users can make deposits.
    require(whitelistedUsers[msg.sender]);

    // Add the deposited token amount to the user balance.
    tokens[token][msg.sender] = tokens[token][msg.sender].add(amount);

    // Transfer tokens from caller to this contract account.
    require(ERC20(token).transferFrom(msg.sender, address(this), amount));

    // Trigger the event.
    Deposit(token, msg.sender, amount, tokens[token][msg.sender]);
  }


/**
 * @dev Withdraw the given token amount from the requester balance.
 * @param token  address Address of the withdrawn token contract
 * @param amount uint256 Amount of tokens to be withdrawn
 */

function withdrawToken(address token, uint256 amount) public {
    // Should not withdraw wei using this function.
    require(token != 0x0);

    // Requester should have enough balance.
    require(tokens[token][msg.sender] >= amount);

    // Substract the withdrawn token amount from the user balance.
    tokens[token][msg.sender] = tokens[token][msg.sender].sub(amount);

    // Transfer the tokens to the investor.
    require(ERC20(token).transfer(msg.sender, amount));

    // Trigger the event.
    Withdraw(token, msg.sender, amount, tokens[token][msg.sender]);
  }


/**
 * @dev Place a new order to the this contract.
 * @param tokenGet   address
 * @param amountGet  uint256
 * @param tokenGive  address
 * @param amountGive uint256
 * @param expires    uint256
 * @param nonce      uint256
 */

function order(
    address tokenGet,
    uint256 amountGet,
    address tokenGive,
    uint256 amountGive,
    uint256 expires,
    uint256 nonce
  )
    public
  {
    // Order placer address should be whitelisted.
    require(whitelistedUsers[msg.sender]);

    // Order tokens addresses should be whitelisted.
    require(whitelistedTokens[tokenGet] && whitelistedTokens[tokenGive]);

    // Calculate the order hash.
    bytes32 hash = keccak256(address(this), tokenGet, amountGet, tokenGive, amountGive, expires, nonce);

    // Store the order.
    orders[msg.sender][hash] = true;

    // Trigger the event.
    Order(tokenGet, amountGet, tokenGive, amountGive, expires, nonce, msg.sender);
  }


/**
 * @dev Cancel an existing order.
 * @param tokenGet   address
 * @param amountGet  uint256
 * @param tokenGive  address
 * @param amountGive uint256
 * @param expires    uint256
 * @param nonce      uint256
 * @param v          uint8
 * @param r          bytes32
 * @param s          bytes32
 */

function cancelOrder(
    address tokenGet,
    uint256 amountGet,
    address tokenGive,
    uint256 amountGive,
    uint256 expires,
    uint256 nonce,
    uint8 v,
    bytes32 r,
    bytes32 s
  )
    public
  {
    // Calculate the order hash.
    bytes32 hash = keccak256(address(this), tokenGet, amountGet, tokenGive, amountGive, expires, nonce);

    // Ensure the message validity.
    require(validateOrderHash(hash, msg.sender, v, r, s));

    // Fill the order to the requested amount.
    orderFills[msg.sender][hash] = amountGet;

    // Trigger the event.
    Cancel(tokenGet, amountGet, tokenGive, amountGive, expires, nonce, msg.sender, v, r, s);
  }


/**
 * @dev Perform a trade.
 * @param tokenGet   address
 * @param amountGet  uint256
 * @param tokenGive  address
 * @param amountGive uint256
 * @param expires    uint256
 * @param nonce      uint256
 * @param user       address
 * @param v          uint8
 * @param r          bytes32
 * @param s          bytes32
 * @param amount     uint256 Traded amount - in amountGet terms
 */

function trade(
    address tokenGet,
    uint256 amountGet,
    address tokenGive,
    uint256 amountGive,
    uint256 expires,
    uint256 nonce,
    address user,
    uint8 v,
    bytes32 r,
    bytes32 s,
    uint256 amount
  )
    public
  {
    // Only whitelisted users can perform trades.
    require(whitelistedUsers[msg.sender]);

    // Only whitelisted tokens can be traded.
    require(whitelistedTokens[tokenGet] && whitelistedTokens[tokenGive]);

    // Expire block number should be greater than current block.
    require(block.number <= expires);

    // Calculate the trade hash.
    bytes32 hash = keccak256(address(this), tokenGet, amountGet, tokenGive, amountGive, expires, nonce);

    // Validate the hash.
    require(validateOrderHash(hash, user, v, r, s));

    // Ensure that after the trade the ordered amount will not be excedeed.
    require(SafeMath.add(orderFills[user][hash], amount) <= amountGet);

    // Add the traded amount to the order fill.
    orderFills[user][hash] = orderFills[user][hash].add(amount);

    // Trade balances.
    tradeBalances(tokenGet, amountGet, tokenGive, amountGive, user, amount);

    // Trigger the event.
    Trade(tokenGet, amount, tokenGive, SafeMath.mul(amountGive, amount).div(amountGet), user, msg.sender);
  }


/**
 * @dev Check if the trade with provided parameters will pass or not.
 * @param tokenGet   address
 * @param amountGet  uint256
 * @param tokenGive  address
 * @param amountGive uint256
 * @param expires    uint256
 * @param nonce      uint256
 * @param user       address
 * @param v          uint8
 * @param r          bytes32
 * @param s          bytes32
 * @param amount     uint256
 * @param sender     address
 * @return bool
 */

function testTrade(
    address tokenGet,
    uint256 amountGet,
    address tokenGive,
    uint256 amountGive,
    uint256 expires,
    uint256 nonce,
    address user,
    uint8 v,
    bytes32 r,
    bytes32 s,
    uint256 amount,
    address sender
  )
    public
    constant
    returns(bool)
  {
    // Traders should be whitelisted.
    require(whitelistedUsers[user] && whitelistedUsers[sender]);

    // Tokens should be whitelisted.
    require(whitelistedTokens[tokenGet] && whitelistedTokens[tokenGive]);

    // Sender should have at least the amount he wants to trade and
    require(tokens[tokenGet][sender] >= amount);

    // order should have available volume to fill.
    return availableVolume(tokenGet, amountGet, tokenGive, amountGive, expires, nonce, user, v, r, s) >= amount;
  }


/**
 * @dev Get the amount filled for the given order.
 * @param tokenGet   address
 * @param amountGet  uint256
 * @param tokenGive  address
 * @param amountGive uint256
 * @param expires    uint256
 * @param nonce      uint256
 * @param user       address
 * @return uint256
 */

function amountFilled(
    address tokenGet,
    uint256 amountGet,
    address tokenGive,
    uint256 amountGive,
    uint256 expires,
    uint256 nonce,
    address user
  )
    public
    constant
    returns (uint256)
  {
    // User should be whitelisted.
    require(whitelistedUsers[user]);

    // Tokens should be whitelisted.
    require(whitelistedTokens[tokenGet] && whitelistedTokens[tokenGive]);

    // Return the amount filled for the given order.
    return orderFills[user][keccak256(address(this), tokenGet, amountGet, tokenGive, amountGive, expires, nonce)];
  }


/**
 * @dev Trade balances of given tokens amounts between two users.
 * @param tokenGet   address
 * @param amountGet  uint256
 * @param tokenGive  address
 * @param amountGive uint256
 * @param user       address
 * @param amount     uint256
 */

function tradeBalances(
    address tokenGet,
    uint256 amountGet,
    address tokenGive,
    uint256 amountGive,
    address user,
    uint256 amount
  )
    private
  {
    // Calculate the constant taxes.
    uint256 feeMakeXfer = amount.mul(feeMake).div(1 ether);
    uint256 feeTakeXfer = amount.mul(feeTake).div(1 ether);
    uint256 feeRebateXfer = 0;

    // Calculate the tax according to account level.
    if (accountLevelsAddr != 0x0) {
      uint256 accountLevel = AccountLevels(accountLevelsAddr).accountLevel(user);
      if (accountLevel == 1) {
        feeRebateXfer = amount.mul(feeRebate).div(1 ether);
      } else if (accountLevel == 2) {
        feeRebateXfer = feeTakeXfer;
      }
    }

    // Update the balances for both maker and taker and add the fee to the feeAccount.
    tokens[tokenGet][msg.sender] = tokens[tokenGet][msg.sender].sub(amount.add(feeTakeXfer));
    tokens[tokenGet][user] = tokens[tokenGet][user].add(amount.add(feeRebateXfer).sub(feeMakeXfer));
    tokens[tokenGet][feeAccount] = tokens[tokenGet][feeAccount].add(feeMakeXfer.add(feeTakeXfer).sub(feeRebateXfer));
    tokens[tokenGive][user] = tokens[tokenGive][user].sub(amountGive.mul(amount).div(amountGet));
    tokens[tokenGive][msg.sender] = tokens[tokenGive][msg.sender].add(amountGive.mul(amount).div(amountGet));
  }


/**
 * @dev Validate an order hash.
 * @param hash bytes32
 * @param user address
 * @param v    uint8
 * @param r    bytes32
 * @param s    bytes32
 * @return bool
 */

function validateOrderHash(
    bytes32 hash,
    address user,
    uint8 v,
    bytes32 r,
    bytes32 s
  )
    private
    constant
    returns (bool)
  {
    return (
      orders[user][hash] ||
      ecrecover(keccak256("\x19Ethereum Signed Message:\n32", hash), v, r, s) == user
    );
  }


/**
 * @notice increase spend amount granted to spender
 * @param spender address whose allowance to increase
 * @param amount quantity by which to increase allowance
 * @return success status (always true; otherwise function will revert)
 */

function increaseAllowance (address spender, uint amount) virtual public returns (bool) {
    unchecked {
      mapping (address => uint) storage allowances = ERC20BaseStorage.layout().allowances[msg.sender];

      uint allowance = allowances[spender];
      require(allowance + amount >= allowance, 'ERC20Extended: excessive allowance');

      _approve(
        msg.sender,
        spender,
        allowances[spender] = allowance + amount
      );

      return true;
    }
  }


/**
 * @inheritdoc IERC20
 */

function transferFrom (
    address holder,
    address recipient,
    uint amount
  ) override virtual public returns (bool) {
    uint256 currentAllowance = ERC20BaseStorage.layout().allowances[holder][msg.sender];
    require(currentAllowance >= amount, 'ERC20: transfer amount exceeds allowance');
    unchecked {
      _approve(holder, msg.sender, currentAllowance - amount);
    }
    _transfer(holder, recipient, amount);
    return true;
  }


// @notice ContractManager will be the only contract that can add/remove contracts on the platform.
// @param (address) _contractManager is the contract which can upgrade/remove contracts to platform

function enableContractManagement(address _contractManager)
    external
    returns (bool){
        require(_contractManager != address(0), "Empty address");
        require(boolStorage[keccak256(abi.encodePacked("owner", msg.sender))], "Not owner");
        require(addressStorage[keccak256(abi.encodePacked("contract", "ContractManager"))] == address(0), "There is already a contract manager");
        addressStorage[keccak256(abi.encodePacked("contract", "ContractManager"))] = _contractManager;
        boolStorage[keccak256(abi.encodePacked("contract", _contractManager))] = true;
        return true;
    }


/**
 * @notice enable spender to spend tokens on behalf of holder
 * @param holder address on whose behalf tokens may be spent
 * @param spender recipient of allowance
 * @param amount quantity of tokens approved for spending
 */

function _approve (
    address holder,
    address spender,
    uint amount
  ) virtual internal {
    require(holder != address(0), 'ERC20: approve from the zero address');
    require(spender != address(0), 'ERC20: approve to the zero address');

    ERC20BaseStorage.layout().allowances[holder][spender] = amount;

    emit Approval(holder, spender, amount);
  }


/**
 * @notice mint tokens for given account
 * @param account recipient of minted tokens
 * @param amount quantity of tokens minted
 */

function _mint (
    address account,
    uint amount
  ) virtual internal {
    require(account != address(0), 'ERC20: mint to the zero address');

    _beforeTokenTransfer(address(0), account, amount);

    ERC20BaseStorage.Layout storage l = ERC20BaseStorage.layout();
    l.totalSupply += amount;
    l.balances[account] += amount;

    emit Transfer(address(0), account, amount);
  }


/**
 * @notice burn tokens held by given account
 * @param account holder of burned tokens
 * @param amount quantity of tokens burned
 */

function _burn (
    address account,
    uint amount
  ) virtual internal {
    require(account != address(0), 'ERC20: burn from the zero address');

    _beforeTokenTransfer(account, address(0), amount);

    ERC20BaseStorage.Layout storage l = ERC20BaseStorage.layout();
    uint256 balance = l.balances[account];
    require(balance >= amount, "ERC20: burn amount exceeds balance");
    unchecked {
      l.balances[account] = balance - amount;
    }
    l.totalSupply -= amount;

    emit Transfer(account, address(0), amount);
  }


/**
 * @notice transfer tokens from holder to recipient
 * @param holder owner of tokens to be transferred
 * @param recipient beneficiary of transfer
 * @param amount quantity of tokens transferred
 */

function _transfer (
    address holder,
    address recipient,
    uint amount
  ) virtual internal {
    require(holder != address(0), 'ERC20: transfer from the zero address');
    require(recipient != address(0), 'ERC20: transfer to the zero address');

    _beforeTokenTransfer(holder, recipient, amount);

    ERC20BaseStorage.Layout storage l = ERC20BaseStorage.layout();
    uint256 holderBalance = l.balances[holder];
    require(holderBalance >= amount, 'ERC20: transfer amount exceeds balance');
    unchecked {
      l.balances[holder] = holderBalance - amount;
    }
    l.balances[recipient] += amount;

    emit Transfer(holder, recipient, amount);
  }


/**
 @notice stake GWS to enter warmup
 @param _amount uint
 @return bool
 */

function stake( uint _amount, address _recipient ) external returns ( bool ) {
        if( initialStake[ _recipient ] == 0 ) {
            initialStake[ _recipient ] = epoch.number.add(initialUnstakeBlock);
        }

        rebase();

        IERC20( GWS ).safeTransferFrom( msg.sender, address(this), _amount );

        Claim memory info = warmupInfo[ _recipient ];
        require( !info.lock, "Deposits for account are locked" );

        warmupInfo[ _recipient ] = Claim ({
            deposit: info.deposit.add( _amount ),
            gons: info.gons.add( IsGWS( sGWS ).gonsForBalance( _amount ) ),
            expiry: epoch.number.add( warmupPeriod ),
            lock: false
        });

        IERC20( sGWS ).safeTransfer( warmupContract, _amount );
        return true;
    }


/**
 @notice retrieve sGWS from warmup
 @param _recipient address
 */

function claim ( address _recipient ) public {
        Claim memory info = warmupInfo[ _recipient ];
        if ( epoch.number >= info.expiry && info.expiry != 0 ) {
            delete warmupInfo[ _recipient ];
            IWarmup( warmupContract ).retrieve( _recipient, IsGWS( sGWS ).balanceForGons( info.gons ) );
        }
    }


/**
 @notice forfeit sGWS in warmup and retrieve GWS
 */

function forfeit() external {
        Claim memory info = warmupInfo[ msg.sender ];
        delete warmupInfo[ msg.sender ];

        IWarmup( warmupContract ).retrieve( address(this), IsGWS( sGWS ).balanceForGons( info.gons ) );
        IERC20( GWS ).safeTransfer( msg.sender, info.deposit );
    }


/**
 @notice redeem sGWS for GWS
 @param _amount uint
 @param _trigger bool
 */

function unstake( uint _amount, bool _trigger ) external {
        require( epoch.number >= initialStake[ msg.sender ] , 'Has not reached the initial unstake block' );

        if ( _trigger ) {
            rebase();
        }
        IERC20( sGWS ).safeTransferFrom( msg.sender, address(this), _amount );
        IERC20( GWS ).safeTransfer( msg.sender, _amount );
    }


/**
 @notice trigger rebase if epoch over
 */

function rebase() public {
        if( epoch.endBlock <= block.number ) {

            IsGWS( sGWS ).rebase( epoch.distribute, epoch.number );

            epoch.endBlock = epoch.endBlock.add( epoch.length );
            epoch.number++;

            if ( distributor != address(0) ) {
                IDistributor( distributor ).distribute();
            }

            uint balance = contractBalance();
            uint staked = IsGWS( sGWS ).circulatingSupply();

            if( balance <= staked ) {
                epoch.distribute = 0;
            } else {
                epoch.distribute = balance.sub( staked );
            }
        }
    }


/**
 @notice sets the contract address for LP staking
 @param _contract address
 */

function setContract( CONTRACTS _contract, address _address ) external onlyManager() {
        if( _contract == CONTRACTS.DISTRIBUTOR ) { // 0
            distributor = _address;
        } else if ( _contract == CONTRACTS.WARMUP ) { // 1
            require( warmupContract == address( 0 ), "Warmup cannot be set more than once" );
            warmupContract = _address;
        } else if ( _contract == CONTRACTS.LOCKER ) { // 2
            require( locker == address(0), "Locker cannot be set more than once" );
            locker = _address;
        }
    }


/**
 * Link blockchain address with CNPJ - It can be a cliente or a supplier
 * The link still needs to be validated by BNDES
 * This method can only be called by BNDESToken contract because BNDESToken can pause.
 * @param cnpj Brazilian identifier to legal entities
 * @param idFinancialSupportAgreement contract number of financial contract with BNDES. It assumes 0 if it is a supplier.
 * @param salic contract number of financial contract with ANCINE. It assumes 0 if it is a supplier.
 * @param addr the address to be associated with the legal entity.
 * @param idProofHash The legal entities have to send BNDES a PDF where it assumes as responsible for an Ethereum account.
 *                   This PDF is signed with eCNPJ and send to BNDES.
 */

function registryLegalEntity(uint64 cnpj, uint64 idFinancialSupportAgreement, uint32 salic,
        address addr, string memory idProofHash) onlyTokenAddress public {

        // Endereço não pode ter sido cadastrado anteriormente
        require (isAvailableAccount(addr), "Endereço não pode ter sido cadastrado anteriormente");

        require (isValidHash(idProofHash), "O hash da declaração é inválido");

        legalEntitiesInfo[addr] = LegalEntityInfo(cnpj, idFinancialSupportAgreement, salic, idProofHash, BlockchainAccountState.WAITING_VALIDATION);

        // Não pode haver outro endereço cadastrado para esse mesmo subcrédito
        if (idFinancialSupportAgreement > 0) {
            address account = getBlockchainAccount(cnpj,idFinancialSupportAgreement);
            require (isAvailableAccount(account), "Cliente já está associado a outro endereço. Use a função Troca.");
        }
        else {
            address account = getBlockchainAccount(cnpj,0);
            require (isAvailableAccount(account), "Fornecedor já está associado a outro endereço. Use a função Troca.");
        }

        cnpjFSAddr[cnpj][idFinancialSupportAgreement] = addr;

        emit AccountRegistration(addr, cnpj, idFinancialSupportAgreement, salic, idProofHash);
    }


/**
 * Changes the original link between CNPJ and Ethereum account.
 * The new link still needs to be validated by BNDES.
 * This method can only be called by BNDESToken contract because BNDESToken can pause and because there are
 * additional instructions there.
 * @param cnpj Brazilian identifier to legal entities
 * @param idFinancialSupportAgreement contract number of financial contract with BNDES. It assumes 0 if it is a supplier.
 * @param salic contract number of financial contract with ANCINE. It assumes 0 if it is a supplier.
 * @param newAddr the new address to be associated with the legal entity
 * @param idProofHash The legal entities have to send BNDES a PDF where it assumes as responsible for an Ethereum account.
 *                   This PDF is signed with eCNPJ and send to BNDES.
 */

function changeAccountLegalEntity(uint64 cnpj, uint64 idFinancialSupportAgreement, uint32 salic,
        address newAddr, string memory idProofHash) onlyTokenAddress public {

        address oldAddr = getBlockchainAccount(cnpj, idFinancialSupportAgreement);

        // Tem que haver um endereço associado a esse cnpj/subcrédito
        require(!isReservedAccount(oldAddr), "Não pode trocar endereço de conta reservada");

        require(!isAvailableAccount(oldAddr), "Tem que haver um endereço associado a esse cnpj/subcrédito");

        require(isAvailableAccount(newAddr), "Novo endereço não está disponível");

        require (isChangeAccountEnabled(oldAddr), "A conta atual não está habilitada para troca");

        require (isValidHash(idProofHash), "O hash da declaração é inválido");

        require(legalEntitiesInfo[oldAddr].cnpj==cnpj
                    && legalEntitiesInfo[oldAddr].idFinancialSupportAgreement ==idFinancialSupportAgreement,
                    "Dados inconsistentes de cnpj ou subcrédito");

        // Aponta o novo endereço para o novo LegalEntityInfo
        legalEntitiesInfo[newAddr] = LegalEntityInfo(cnpj, idFinancialSupportAgreement, salic, idProofHash, BlockchainAccountState.WAITING_VALIDATION);

        // Apaga o mapping do endereço antigo
        legalEntitiesInfo[oldAddr].state = BlockchainAccountState.INVALIDATED_BY_CHANGE;

        // Aponta mapping CNPJ e Subcredito para newAddr
        cnpjFSAddr[cnpj][idFinancialSupportAgreement] = newAddr;

        emit AccountChange(oldAddr, newAddr, cnpj, idFinancialSupportAgreement, salic, idProofHash);

    }


/**
 * Validates the initial registry of a legal entity or the change of its registry
 * @param addr Ethereum address that needs to be validated
 * @param idProofHash The legal entities have to send BNDES a PDF where it assumes as responsible for an Ethereum account.
 *                   This PDF is signed with eCNPJ and send to BNDES.
 */

function validateRegistryLegalEntity(address addr, string memory idProofHash) public {

        require(isResponsibleForRegistryValidation(msg.sender), "Somente o responsável pela validação pode validar contas");

        require(legalEntitiesInfo[addr].state == BlockchainAccountState.WAITING_VALIDATION, "A conta precisa estar no estado Aguardando Validação");

        require(keccak256(abi.encodePacked(legalEntitiesInfo[addr].idProofHash)) == keccak256(abi.encodePacked(idProofHash)), "O hash recebido é diferente do esperado");

        legalEntitiesInfo[addr].state = BlockchainAccountState.VALIDATED;

        emit AccountValidation(addr, legalEntitiesInfo[addr].cnpj,
            legalEntitiesInfo[addr].idFinancialSupportAgreement,
            legalEntitiesInfo[addr].salic);
    }


/**
 * The transfer funcion follows ERC20 token signature.
 * Using them, it is possible to disburse money to the client, transfer from client to supplier and redeem.
 * @param to the Ethereum address to where the money should be sent
 * @param value how much BNDESToken the supplier wants to redeem
 */

function transfer (address to, uint256 value) public whenNotPaused returns (bool) {

        address from = msg.sender;

        require(from != to, "Não pode transferir token para si mesmo");

        if (registry.isResponsibleForDisbursement(from)) {

            require(registry.isValidatedClient(to), "O endereço não pertence a um cliente ou não está validada");

            _mint(to, value);

            emit BNDESTokenDisbursement(registry.getCNPJ(to), registry.getIdLegalFinancialAgreement(to), value);

        } else {

            if (registry.isRedemptionAddress(to)) {

                require(registry.isValidatedSupplier(from), "A conta do endereço não pertence a um fornecedor ou não está validada");

                _burn(from, value);

                emit BNDESTokenRedemption(registry.getCNPJ(from), value);

            } else {

                // Se nem from nem to são o Banco, eh transferencia normal

                require(registry.isValidatedClient(from), "O endereço não pertence a um cliente ou não está validada");
                require(registry.isValidatedSupplier(to), "A conta do endereço não pertence a um fornecedor ou não está validada");

                _transfer(msg.sender, to, value);

                emit BNDESTokenTransfer(registry.getCNPJ(from), registry.getIdLegalFinancialAgreement(from),
                                registry.getCNPJ(to), value);

            }
        }

        return true;
    }


/**
 * Using this function, the Responsible for Settlement indicates that he has made the FIAT money transfer.
 * @param redemptionTransactionHash hash of the redeem transaction in which the FIAT money settlement occurred.
 * @param receiptHash hash that proof the FIAT money transfer
 */

function notifyRedemptionSettlement(string memory redemptionTransactionHash, string memory receiptHash)
        public whenNotPaused {
        require (registry.isResponsibleForSettlement(msg.sender), "A liquidação só não pode ser realizada pelo endereço que submeteu a transação");
        require (registry.isValidHash(receiptHash), "O hash do recibo é inválido");
        emit BNDESTokenRedemptionSettlement(redemptionTransactionHash, receiptHash);
    }


/**
 * Changes the original link between CNPJ and Ethereum account.
 * The new link still needs to be validated by BNDES.
 * IMPORTANT: The BNDESTOKENs are transfered from the original to the new Ethereum address
 * @param cnpj Brazilian identifier to legal entities
 * @param idFinancialSupportAgreement contract number of financial contract with BNDES. It assumes 0 if it is a supplier.
 * @param salic contract number of financial contract with ANCINE. It assumes 0 if it is a supplier.
 * @param idProofHash The legal entities have to send BNDES a PDF where it assumes as responsible for an Ethereum account.
 *                   This PDF is signed with eCNPJ and send to BNDES.
 */

function changeAccountLegalEntity(uint64 cnpj, uint64 idFinancialSupportAgreement, uint32 salic, string memory idProofHash)
        public whenNotPaused {

        address oldAddr = registry.getBlockchainAccount(cnpj, idFinancialSupportAgreement);
        address newAddr = msg.sender;

        registry.changeAccountLegalEntity(cnpj, idFinancialSupportAgreement, salic, msg.sender, idProofHash);

        // Se há saldo no enderecoAntigo, precisa transferir
        if (balanceOf(oldAddr) > 0) {
            _transfer(oldAddr, newAddr, balanceOf(oldAddr));
        }

    }


/**
 * @notice Returns the estimated per-block borrow interest rate for this cToken after some change
 * @return The borrow interest rate per block, scaled by 1e18
 */

function estimateBorrowRatePerBlockAfterChange(uint256 change, bool repay) external view returns (uint256) {
        uint256 cashPriorNew;
        uint256 totalBorrowsNew;

        if (repay) {
            cashPriorNew = add_(getCashPrior(), change);
            totalBorrowsNew = sub_(totalBorrows, change);
        } else {
            cashPriorNew = sub_(getCashPrior(), change);
            totalBorrowsNew = add_(totalBorrows, change);
        }
        return interestRateModel.getBorrowRate(cashPriorNew, totalBorrowsNew, totalReserves);
    }


/**
 * @notice Sender borrows assets from the protocol to their own address
 * @param borrowAmount The amount of the underlying asset to borrow
 * @param isNative The amount is in native or not
 * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
 */

function borrowInternal(uint256 borrowAmount, bool isNative) internal nonReentrant returns (uint256) {
        uint256 error = accrueInterest();
        if (error != uint256(Error.NO_ERROR)) {
            // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed
            return fail(Error(error), FailureInfo.BORROW_ACCRUE_INTEREST_FAILED);
        }
        // borrowFresh emits borrow-specific logs on errors, so we don't need to
        return borrowFresh(msg.sender, borrowAmount, isNative);
    }


/**
 * @notice Sender repays their own borrow
 * @param repayAmount The amount to repay
 * @param isNative The amount is in native or not
 * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.
 */

function repayBorrowInternal(uint256 repayAmount, bool isNative) internal nonReentrant returns (uint256, uint256) {
        uint256 error = accrueInterest();
        if (error != uint256(Error.NO_ERROR)) {
            // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed
            return (fail(Error(error), FailureInfo.REPAY_BORROW_ACCRUE_INTEREST_FAILED), 0);
        }
        // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to
        return repayBorrowFresh(msg.sender, msg.sender, repayAmount, isNative);
    }


/**
 * @notice The sender liquidates the borrowers collateral.
 *  The collateral seized is transferred to the liquidator.
 * @param borrower The borrower of this cToken to be liquidated
 * @param repayAmount The amount of the underlying borrowed asset to repay
 * @param cTokenCollateral The market in which to seize collateral from the borrower
 * @param isNative The amount is in native or not
 * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount.
 */

function liquidateBorrowInternal(
        address borrower,
        uint256 repayAmount,
        CTokenInterface cTokenCollateral,
        bool isNative
    ) internal nonReentrant returns (uint256, uint256) {
        uint256 error = accrueInterest();
        if (error != uint256(Error.NO_ERROR)) {
            // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed
            return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED), 0);
        }

        error = cTokenCollateral.accrueInterest();
        if (error != uint256(Error.NO_ERROR)) {
            // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed
            return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED), 0);
        }

        // liquidateBorrowFresh emits borrow-specific logs on errors, so we don't need to
        return liquidateBorrowFresh(msg.sender, borrower, repayAmount, cTokenCollateral, isNative);
    }


/**
 * @notice Accrues interest and reduces reserves by transferring from msg.sender
 * @param addAmount Amount of addition to reserves
 * @param isNative The amount is in native or not
 * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
 */

function _addReservesInternal(uint256 addAmount, bool isNative) internal nonReentrant returns (uint256) {
        uint256 error = accrueInterest();
        if (error != uint256(Error.NO_ERROR)) {
            // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reduce reserves failed.
            return fail(Error(error), FailureInfo.ADD_RESERVES_ACCRUE_INTEREST_FAILED);
        }

        // _addReservesFresh emits reserve-addition-specific logs on errors, so we don't need to.
        (error, ) = _addReservesFresh(addAmount, isNative);
        return error;
    }


//mint a @param number of NFTs in presale

function presaleMint(uint256 number, uint8 _v, bytes32 _r, bytes32 _s) onlyValidAccess(_v,  _r, _s) public payable {
        State saleState_ = saleState();
        require(saleState_ != State.NoSale, "Sale in not open yet!");
        require(saleState_ != State.PublicSale, "Presale has closed, Check out Public Sale!");
        require(numberOfTokensPresale + number <= 750, "Not enough NFTs left to mint..");
        require(mintsPerAddress[msg.sender] + number <= 5, "Maximum 5 Mints per Address allowed!");
        require(msg.value >= mintCost(number), "Not sufficient ETH to mint this amount of NFTs");

        for (uint256 i = 0; i < number; i++) {
            uint256 tid = tokenId();
            _safeMint(msg.sender, tid);
            mintsPerAddress[msg.sender] += 1;
            numberOfTotalTokens += 1;
        }

    }


//mint a @param number of NFTs in public sale

function publicSaleMint(uint256 number) public payable {
        State saleState_ = saleState();
        require(saleState_ == State.PublicSale, "Public Sale in not open yet!");
        require(numberOfTotalTokens + number <= maxTotalTokens - (maxReservedMintsTeam + maxReservedMintsGiveaways - _reservedMintsTeam - _reservedMintsGiveaways), "Not enough NFTs left to mint..");
        require(number <= 5, "Maximum 5 Mints per Address allowed!");
        require(msg.value >= mintCost(number), "Not sufficient ETH to mint this amount of NFTs");

        for (uint256 i = 0; i < number; i++) {
            uint256 tid = tokenId();
            _safeMint(msg.sender, tid);
            mintsPerAddress[msg.sender] += 1;
            numberOfTotalTokens += 1;
        }
    }


//reserved NFTs for creator

function reservedMintTeam(uint number, address recipient) public onlyOwner {
        require(_reservedMintsTeam + number <= maxReservedMintsTeam, "Not enough Reserved NFTs left to mint..");

        for (uint i = 0; i < number; i++) {
            _safeMint(recipient, tokenId());
            mintsPerAddress[recipient] += 1;
            numberOfTotalTokens += 1;
            _reservedMintsTeam += 1;
        }

    }


//retrieve all funds recieved from minting

function withdraw() public onlyOwner {
        uint256 balance = accountBalance();
        require(balance > 0, 'No Funds to withdraw, Balance is 0');

        _withdraw(shareholder5, (balance * 20) / 100);
        _withdraw(shareholder4, (balance * 20) / 100);
        _withdraw(shareholder3, (balance * 20) / 100);
        _withdraw(shareholder2, (balance * 20) / 100);
        _withdraw(shareholder1, accountBalance()); //to avoid dust eth, gets a little more than 20%
    }


//see current state of sale
//see the current state of the sale

function saleState() public view returns(State){
        if (presaleLaunchTime == 0 || paused == true) {
            return State.NoSale;
        }
        else if (publicSaleLaunchTime == 0) {
            return State.Presale;
        }
        else {
            return State.PublicSale;
        }
    }


//gets the cost of current mint

function mintCost(uint number) public view returns(uint) {
        require(0 < number && number < 6, "Incorrect Amount!");
        State saleState_ = saleState();
        if (saleState_ == State.NoSale || saleState_ == State.Presale) {
            if (number == 1) {
                return 0.04 ether;
            }
            else if (number == 2) {
                return 0.07 ether;
            }
            else if (number == 3) {
                return 0.1 ether;
            }
            else if (number == 4) {
                return 0.13 ether;
            }
            else {
                return 0.15 ether;
            }

        }
        else {
            if (number == 1) {
                return 0.06 ether;
            }
            else if (number == 2) {
                return 0.1 ether;
            }
            else if (number == 3) {
                return 0.16 ether;
            }
            else if (number == 4) {
                return 0.2 ether;
            }
            else {
                return 0.26 ether;
            }
        }
    }


//Exclude from draw array

function ExcludeFEA (address _address) private {
         for (uint256 j = 0; j < _DrawHolders.length; j++) {
                  if( _DrawHolders[j] == _address){
                  _DrawHolders[j] = _DrawHolders[_DrawHolders.length - 1];
                  _ExistInDrawHolders[_address] = false;
                  _DrawHolders.pop();
                 break;
            }
        }
    }


//Called when no more ETH is in the contract and everything needs to be manually reset.

function minter(string _currency, uint128 _Multiplier) { //,uint8 _DecimalPlaces
        // CONSTRUCTOR
        Currency=_currency;
        Multiplier = _Multiplier;
        // can't add new contracts here as it gives out of gas messages.  Too much code.
    }


//****************************//
// Constant functions (Ones that don't write to the blockchain)

function StaticEthAvailable()
			constant
			returns (uint128)  {
		/** @dev Returns the total amount of eth that can be sent to buy StatiCoins
		  * @return amount of Eth
        */
		return StaticEthAvailable(cast(Risk.totalSupply()), cast(this.balance));
    }


//****************************//
// Only owner can access the following functions

function setFee(uint128 _newFee)
			onlyOwner {
        /** @dev Allows the minting fee to be changed, only owner can modify
		  * Fee is only charged on coin creation
          * @param _newFee Size of new fee
          * return nothing
        */
        mintFee=_newFee;
    }


//****************************//
// Only Pricer can access the following function

function PriceReturn(uint _TransID,uint128 _Price)
			onlyPricer {
	    /** @dev Return function for the Pricer contract only.  Controls melting and minting of new coins.
          * @param _TransID Tranasction ID issued by the minter.
          * @param _Price Quantity of Base currency per ETH delivered by the Pricer contract
          * Nothing returned.  One of 4 functions is implemented
        */
	    Trans memory details=pending[_TransID][0];//Get the details for this transaction.
        if(0==_Price||frozen){ //If there is an error in pricing or contract is frozen, use the old price
            _Price=lastPrice;
        } else {
			if(Static.totalSupply()>0 && Risk.totalSupply()>0) {// dont update if there are coins missing
				lastPrice=_Price; // otherwise update the last price
			}
        }
		//Mint some new StatiCoins
        if(Action.NewStatic==details.action){
            ActionNewStatic(details,_TransID, _Price);
        }
		//Melt some old StatiCoins
        if(Action.RetStatic==details.action){
            ActionRetStatic(details,_TransID, _Price);
        }
		//Mint some new Riskcoins
        if(Action.NewRisk==details.action){
            ActionNewRisk(details,_TransID, _Price);
        }
		//Melt some old Riskcoins
        if(Action.RetRisk==details.action){
            ActionRetRisk(details,_TransID, _Price);
        }
		//Remove the transaction from the blockchain (saving some gas)
		delete pending[_TransID];
    }


/** factory role config nft clone
 *
 * @param localNFT nft on this chain
 * @param destNFT nft on L2
 * @param originNFTChainId origin NFT ChainId
 * @param destGasLimit L2 gas limit
 */

function configNFT(address localNFT, address destNFT, uint256 originNFTChainId, uint32 destGasLimit) external payable onlyRole(NFT_FACTORY_ROLE) {

        uint256 localChainId = getChainID();

        require((originNFTChainId == DEST_CHAINID || originNFTChainId == localChainId), "ChainId not supported");

        require(clone[localNFT] == address(0), "NFT already configured.");

        uint32 minGasLimit = uint32(oracle.getMinL2Gas());
        if (destGasLimit < minGasLimit) {
            destGasLimit = minGasLimit;
        }
        require(destGasLimit * oracle.getDiscount() <= msg.value, string(abi.encodePacked("insufficient fee supplied. send at least ", uint2str(destGasLimit * oracle.getDiscount()))));

        clone[localNFT] = destNFT;

        isOrigin[localNFT] = false;
        if(localChainId == originNFTChainId){
            isOrigin[localNFT] = true;
        }

        bytes memory message = abi.encodeWithSelector(
            ICrollDomainConfig.configNFT.selector,
            localNFT,
            destNFT,
            originNFTChainId
        );

        sendCrossDomainMessageViaChainId(
            DEST_CHAINID,
            destNFTBridge,
            destGasLimit,
            message,
            msg.value
        );

         emit CONFIT_NFT(localNFT, destNFT, originNFTChainId);
    }


/** deposit nft into L1 deposit
 *
 * @param localNFT nft on this chain
 * @param destTo owns nft on L2
 * @param id nft id
 * @param nftStandard nft type
 * @param destGasLimit L2 gas limit
 */

function depositTo(address localNFT, address destTo, uint256 id,  nftenum nftStandard, uint32 destGasLimit) external onlyEOA() payable {

        require(clone[localNFT] != address(0), "NFT not config.");

        require(isDeposit[localNFT][id] == false, "Don't redeposit.");

        uint32 minGasLimit = uint32(oracle.getMinL2Gas());
        if (destGasLimit < minGasLimit) {
            destGasLimit = minGasLimit;
        }

        require(destGasLimit * oracle.getDiscount() <= msg.value, string(abi.encodePacked("insufficient fee supplied. send at least ", uint2str(destGasLimit * oracle.getDiscount()))));

        uint256 amount = 0;

        if(nftenum.ERC721 == nftStandard) {
            IERC721(localNFT).safeTransferFrom(msg.sender, localNFTDeposit, id);
        }

        if(nftenum.ERC1155 == nftStandard) {
            amount = IERC1155(localNFT).balanceOf(msg.sender, id);
            require(amount == 1, "Not an NFT token.");
            IERC1155(localNFT).safeTransferFrom(msg.sender, localNFTDeposit, id, amount, "");
        }

        _depositStatus(localNFT, id, msg.sender, true);

        address destNFT = clone[localNFT];

        _messenger(DEST_CHAINID, destNFT, msg.sender, destTo, id, amount, uint8(nftStandard), destGasLimit);

        emit DEPOSIT_TO(destNFT, msg.sender, destTo, id, amount, uint8(nftStandard));
    }


/** deposit messenger
 *
 * @param chainId L2 chainId
 * @param destNFT nft on L2
 * @param from msg.sender
 * @param destTo owns nft on L2
 * @param id nft id
 * @param amount amount
 * @param nftStandard nft type
 * @param destGasLimit L2 gas limit
 */

function _messenger(uint256 chainId, address destNFT, address from, address destTo, uint256 id, uint256 amount, uint8 nftStandard, uint32 destGasLimit) internal {

        bytes memory message =  abi.encodeWithSelector(
            ICrollDomain.finalizeDeposit.selector,
            destNFT,
            from,
            destTo,
            id,
            amount,
            nftStandard
        );

        sendCrossDomainMessageViaChainId(
            chainId,
            destNFTBridge,
            destGasLimit,
            message,
            msg.value
        );
    }


/** clone nft
 *
 * @param  _localNFT nft
 * @param  _destFrom  owns nft on l2
 * @param  _localTo give to
 * @param  id nft id
 * @param  _amount nft amount
 * @param  nftStandard nft type
 */

function finalizeDeposit(address _localNFT, address _destFrom, address _localTo, uint256 id, uint256 _amount, nftenum nftStandard) external onlyFromCrossDomainAccount(destNFTBridge) {

        if(nftenum.ERC721 == nftStandard) {
            if(isDeposit[_localNFT][id]){
                INFTDeposit(localNFTDeposit).withdrawERC721(_localNFT, _localTo, id);
            }else{
                if(isOrigin[_localNFT]){
                    // What happened
                    emit DEPOSIT_FAILED(_localNFT, _destFrom, _localTo, id, _amount, uint8(nftStandard));
                }else{
                    IStandarERC721(_localNFT).mint(_localTo, id);
                }
            }
        }

        if(nftenum.ERC1155 == nftStandard) {
            if(isDeposit[_localNFT][id]){
                INFTDeposit(localNFTDeposit).withdrawERC1155(_localNFT, _localTo, id, _amount);
            }else{
                if(isOrigin[_localNFT]){
                    // What happened
                    emit DEPOSIT_FAILED(_localNFT, _destFrom, _localTo, id, _amount, uint8(nftStandard));
                }else{
                    IStandarERC1155(_localNFT).mint(_localTo, id, _amount, "");
                }
            }
        }

        _depositStatus(_localNFT, id, address(0), false);

        emit FINALIZE_DEPOSIT(_localNFT, _destFrom, _localTo, id, _amount, uint8(nftStandard));
    }


/** hack rollback
 *
 * @param nftStandard nft type
 * @param _localNFT nft
 * @param ids tokenid
 */

function rollback(nftenum nftStandard, address _localNFT, uint256[] memory ids) public onlyRole(ROLLBACK_ROLE) {

        for( uint256 index; index < ids.length; index++ ){

            uint256 id = ids[index];

            address _depositUser = depositUser[_localNFT][id];

            require(isDeposit[_localNFT][id], "Not Deposited");

            require(_depositUser != address(0), "user can not be zero address.");

            uint256 amount = 0;

            if(nftenum.ERC721 == nftStandard) {
                INFTDeposit(localNFTDeposit).withdrawERC721(_localNFT, _depositUser, id);
            }else{
                amount = 1;
                INFTDeposit(localNFTDeposit).withdrawERC1155(_localNFT, _depositUser, id, amount);
            }

            _depositStatus(_localNFT, id, address(0), false);

            emit ROLLBACK(_localNFT, localNFTDeposit, _depositUser, id, amount, uint8(nftStandard));
        }
    }


/**
 * Fallback and entrypoint for deposits.
 */

function() public payable isHuman {
    if (msg.value == 0) {
      collectPayoutForAddress(msg.sender);
    } else {
      uint refId = 1;
      address referrer = bytesToAddress(msg.data);
      if (investorToDepostIndex[referrer].isExist) {
        refId = investorToDepostIndex[referrer].id;
      }
      deposit(refId);
    }
  }


// Add a new staking token to the pool. Can only be called by the manager.

function add(uint256 _allocPoint, IERC20 _stakingToken, bool _isLP, bool _withUpdate) external {
        require(msg.sender == address(manager), "fund: sender is not manager");
        if (_withUpdate) {
            massUpdatePools();
        }
        uint256 lastRewardBlock = block.number > startBlock ? block.number : startBlock;
        totalAllocPoint = totalAllocPoint.add(_allocPoint);
        poolInfo.push(PoolInfo({
            stakingToken: _stakingToken,
            supply: 0,
            allocPoint: _allocPoint,
            lastRewardBlock: lastRewardBlock,
            accERC20PerShare: 0,
            isLP: _isLP
        }));
    }


/**
 * Perform the Midnight Run
 */

function doMidnightRun() public isHuman {
    require(now>nextPrizeTime , "Not yet");

    // set the next prize time to the next payout time (MidnightRun)
    nextPrizeTime = getNextPayoutTime();

    if (currentPrizeStakeID > 5) {
      uint toPay = midnightPrize;
      midnightPrize = 0;

      if (toPay > address(this).balance){
        toPay = address(this).balance;
      }

      uint totalValue = stakeIDToDepositIndex[currentPrizeStakeID].value + stakeIDToDepositIndex[currentPrizeStakeID - 1].value + stakeIDToDepositIndex[currentPrizeStakeID - 2].value + stakeIDToDepositIndex[currentPrizeStakeID - 3].value + stakeIDToDepositIndex[currentPrizeStakeID - 4].value;

      stakeIDToDepositIndex[currentPrizeStakeID].user.transfer(toPay.mul(stakeIDToDepositIndex[currentPrizeStakeID].value).div(totalValue));
      emit MidnightRunPayout(stakeIDToDepositIndex[currentPrizeStakeID].user, toPay.mul(stakeIDToDepositIndex[currentPrizeStakeID].value).div(totalValue), totalValue, stakeIDToDepositIndex[currentPrizeStakeID].value, now);

      stakeIDToDepositIndex[currentPrizeStakeID - 1].user.transfer(toPay.mul(stakeIDToDepositIndex[currentPrizeStakeID - 1].value).div(totalValue));
      emit MidnightRunPayout(stakeIDToDepositIndex[currentPrizeStakeID - 1].user, toPay.mul(stakeIDToDepositIndex[currentPrizeStakeID - 1].value).div(totalValue), totalValue, stakeIDToDepositIndex[currentPrizeStakeID - 1].value, now);

      stakeIDToDepositIndex[currentPrizeStakeID - 2].user.transfer(toPay.mul(stakeIDToDepositIndex[currentPrizeStakeID - 2].value).div(totalValue));
      emit MidnightRunPayout(stakeIDToDepositIndex[currentPrizeStakeID - 2].user, toPay.mul(stakeIDToDepositIndex[currentPrizeStakeID - 2].value).div(totalValue), totalValue, stakeIDToDepositIndex[currentPrizeStakeID - 2].value, now);

      stakeIDToDepositIndex[currentPrizeStakeID - 3].user.transfer(toPay.mul(stakeIDToDepositIndex[currentPrizeStakeID - 3].value).div(totalValue));
      emit MidnightRunPayout(stakeIDToDepositIndex[currentPrizeStakeID - 3].user, toPay.mul(stakeIDToDepositIndex[currentPrizeStakeID - 3].value).div(totalValue), totalValue, stakeIDToDepositIndex[currentPrizeStakeID - 3].value, now);

      stakeIDToDepositIndex[currentPrizeStakeID - 4].user.transfer(toPay.mul(stakeIDToDepositIndex[currentPrizeStakeID - 4].value).div(totalValue));
      emit MidnightRunPayout(stakeIDToDepositIndex[currentPrizeStakeID - 4].user, toPay.mul(stakeIDToDepositIndex[currentPrizeStakeID - 4].value).div(totalValue), totalValue, stakeIDToDepositIndex[currentPrizeStakeID - 4].value, now);
    }
  }


// move LP tokens from one farm to another. only callable by Manager.
// tx.origin is user EOA, msg.sender is the Manager.

function move(uint256 _pid) external {
        require(msg.sender == address(manager), "move: sender is not manager");
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][tx.origin];
        updatePool(_pid);
        uint256 pendingAmount = user.amount.mul(pool.accERC20PerShare).div(1e36).sub(user.rewardDebt);
        erc20Transfer(tx.origin, pendingAmount);
        pool.supply = pool.supply.sub(user.amount);
        pool.stakingToken.safeTransfer(address(manager), user.amount);
        user.amount = 0;
        user.rewardDebt = user.amount.mul(pool.accERC20PerShare).div(1e36);
        emit Withdraw(msg.sender, _pid);
    }


// Deposit LP tokens to Farm for ERC20 allocation.
// can come from manager or user address directly; in either case, tx.origin is the user.
// In the case the call is coming from the mananger, msg.sender is the manager.

function deposit(uint256 _pid, uint256 _amount) external {
        require(manager.getPaused()==false, "deposit: farm paused");
        address userAddress = ((msg.sender == address(manager)) ? tx.origin : msg.sender);
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][userAddress];
        require(user.withdrawTime == 0, "deposit: user is unstaking");
        updatePool(_pid);
        if (user.amount > 0) {
            uint256 pendingAmount = user.amount.mul(pool.accERC20PerShare).div(1e36).sub(user.rewardDebt);
            erc20Transfer(userAddress, pendingAmount);
        }
        pool.stakingToken.safeTransferFrom(address(msg.sender), address(this), _amount);
        pool.supply = pool.supply.add(_amount);
        user.amount = user.amount.add(_amount);
        user.rewardDebt = user.amount.mul(pool.accERC20PerShare).div(1e36);
        emit Deposit(userAddress, _pid, _amount);
    }


// Distribute rewards and start unstake period.

function withdraw(uint256 _pid) external {
        require(manager.getPaused()==false, "withdraw: farm paused");
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.amount > 0, "withdraw: amount must be greater than 0");
        require(user.withdrawTime == 0, "withdraw: user is unstaking");
        updatePool(_pid);

        // transfer any rewards due
        uint256 pendingAmount = user.amount.mul(pool.accERC20PerShare).div(1e36).sub(user.rewardDebt);
        erc20Transfer(msg.sender, pendingAmount);
        pool.supply = pool.supply.sub(user.amount);
        user.rewardDebt = 0;
        user.withdrawTime = block.timestamp;
        emit Withdraw(msg.sender, _pid);
    }


// unstake LP tokens from Farm. if done within "unstakeEpochs" days, apply burn.

function unstake(uint256 _pid) external {
        require(manager.getPaused()==false, "unstake: farm paused");
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.withdrawTime > 0, "unstake: user is not unstaking");
        updatePool(_pid);
        //apply burn fee if unstaking before unstake epochs.
        uint256 unstakeEpochs = manager.getUnstakeEpochs();
        uint256 burnRate = manager.getBurnRate();
        address redistributor = manager.getRedistributor();
        if((user.withdrawTime.add(SECS_EPOCH.mul(unstakeEpochs)) > block.timestamp) && burnRate > 0){
            uint penalty = user.amount.div(1000).mul(burnRate);
            user.amount = user.amount.sub(penalty);
            // if the staking address is an LP, send 50% of penalty to redistributor, and 50% to lp lock address.
            if(pool.isLP){
                pool.stakingToken.safeTransfer(redistributor, penalty.div(2));
                pool.stakingToken.safeTransfer(manager.getLpLock(), penalty.div(2));
            }else {
                // for normal ERC20 tokens, 50% of the penalty is sent to the redistributor address, 50% is burned from the supply.
                pool.stakingToken.safeTransfer(redistributor, penalty.div(2));
                IBurnable(address(pool.stakingToken)).burn(penalty.div(2));
            }
        }
        uint userAmount = user.amount;
        // allows user to stake again.
        user.withdrawTime = 0;
        user.amount = 0;
        pool.stakingToken.safeTransfer(address(msg.sender), userAmount);
        emit Unstake(msg.sender, _pid);
    }


// claim LP tokens from Farm.

function claim(uint256 _pid) external {
        require(manager.getPaused() == false, "claim: farm paused");
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.amount > 0, "claim: amount is equal to 0");
        require(user.withdrawTime == 0, "claim: user is unstaking");
        updatePool(_pid);
        uint256 pendingAmount = user.amount.mul(pool.accERC20PerShare).div(1e36).sub(user.rewardDebt);
        erc20Transfer(msg.sender, pendingAmount);
        user.rewardDebt = user.amount.mul(pool.accERC20PerShare).div(1e36);
        user.lastClaimTime = block.timestamp;
        emit Claim(msg.sender, _pid);
    }


/**
 * Initialize the farming contract. This is called only once upon farm creation and the FarmGenerator
 * ensures the farm has the correct paramaters
 *
 * @param _rewardToken          Instance of reward token contract
 * @param _amount               Total sum of reward
 * @param _lpToken              Instance of LP token contract
 * @param _blockReward          Reward per block
 * @param _startBlock           Block number to start reward
 * @param _bonusEndBlock        Block number to end the bonus reward
 * @param _bonus                Bonus multipler which will be applied until bonus end block
 */

function init(
        IERC20 _rewardToken,
        uint256 _amount,
        IERC20 _lpToken,
        uint256 _blockReward,
        uint256 _startBlock,
        uint256 _endBlock,
        uint256 _bonusEndBlock,
        uint256 _bonus
    ) external {
        require(msg.sender == address(farmGenerator), "FORBIDDEN");

        _rewardToken.safeTransferFrom(msg.sender, address(this), _amount);
        farmInfo.rewardToken = _rewardToken;

        farmInfo.startBlock = _startBlock;
        farmInfo.blockReward = _blockReward;
        farmInfo.bonusEndBlock = _bonusEndBlock;
        farmInfo.bonus = _bonus;

        uint256 lastRewardBlock = block.number > _startBlock ? block.number : _startBlock;
        farmInfo.lpToken = _lpToken;
        farmInfo.lastRewardBlock = lastRewardBlock;
        farmInfo.accRewardPerShare = 0;

        farmInfo.endBlock = _endBlock;
        farmInfo.farmableSupply = _amount;
    }


/**
 * Updates pool information to be up to date to the current block
 */

function updatePool() public {
        if (block.number <= farmInfo.lastRewardBlock) {
            return;
        }
        uint256 lpSupply = farmInfo.lpToken.balanceOf(address(this));
        if (lpSupply == 0) {
            farmInfo.lastRewardBlock = block.number < farmInfo.endBlock ? block.number : farmInfo.endBlock;
            return;
        }
        uint256 multiplier = getMultiplier(farmInfo.lastRewardBlock, block.number);
        uint256 tokenReward = multiplier.mul(farmInfo.blockReward);
        farmInfo.accRewardPerShare = farmInfo.accRewardPerShare.add(tokenReward.mul(1e12).div(lpSupply));
        farmInfo.lastRewardBlock = block.number < farmInfo.endBlock ? block.number : farmInfo.endBlock;
    }


/**
 * Deposit LP token function for msg.sender
 *
 * @param _amount               the total deposit amount
 */

function deposit(uint256 _amount) external {
        UserInfo storage user = userInfo[msg.sender];
        updatePool();
        if (user.amount > 0) {
            uint256 pending = user.amount.mul(farmInfo.accRewardPerShare).div(1e12).sub(user.rewardDebt);
            _safeRewardTransfer(msg.sender, pending);
        }
        if (user.amount == 0 && _amount > 0) {
            factory.userEnteredFarm(msg.sender);
            farmInfo.numFarmers++;
        }
        farmInfo.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
        user.amount = user.amount.add(_amount);
        user.rewardDebt = user.amount.mul(farmInfo.accRewardPerShare).div(1e12);
        emit Deposit(msg.sender, _amount);
    }


/**
 * Withdraw LP token function for msg.sender
 *
 * @param                       _amount the total withdrawable amount
 */

function withdraw(uint256 _amount) external {
        UserInfo storage user = userInfo[msg.sender];
        require(user.amount >= _amount, "INSUFFICIENT");
        updatePool();
        if (user.amount == _amount && _amount > 0) {
            factory.userLeftFarm(msg.sender);
            farmInfo.numFarmers--;
        }
        uint256 pending = user.amount.mul(farmInfo.accRewardPerShare).div(1e12).sub(user.rewardDebt);
        _safeRewardTransfer(msg.sender, pending);
        user.amount = user.amount.sub(_amount);
        user.rewardDebt = user.amount.mul(farmInfo.accRewardPerShare).div(1e12);
        farmInfo.lpToken.safeTransfer(address(msg.sender), _amount);
        emit Withdraw(msg.sender, _amount);
    }


/**
 * Withdraw tokens emergency.
 *
 * @param _token                Token contract address
 * @param _to                   Address where the token withdraw to
 * @param _amount               Amount of tokens to withdraw
 */

function emergencyWithdraw(address _token, address _to, uint256 _amount) external onlyOwner {
        IERC20 erc20Token = IERC20(_token);
        require(erc20Token.balanceOf(address(this)) > 0, "Insufficient balane");

        uint256 amountToWithdraw = _amount;
        if (_amount == 0) {
            amountToWithdraw = erc20Token.balanceOf(address(this));
        }
        erc20Token.safeTransfer(_to, amountToWithdraw);
    }


/**
 * Get the reward multiplier over the given _from_block until _to block
 *
 * @param _fromBlock            the start of the period to measure rewards for
 * @param _to                   the end of the period to measure rewards for
 *
 * @return                      The weighted multiplier for the given period
 */

function getMultiplier(uint256 _fromBlock, uint256 _to) public view returns (uint256) {
        uint256 _from = _fromBlock >= farmInfo.startBlock ? _fromBlock : farmInfo.startBlock;
        uint256 to = farmInfo.endBlock > _to ? _to : farmInfo.endBlock;
        if (to <= farmInfo.bonusEndBlock) {
            return to.sub(_from).mul(farmInfo.bonus);
        } else if (_from >= farmInfo.bonusEndBlock) {
            return to.sub(_from);
        } else {
            return farmInfo.bonusEndBlock.sub(_from).mul(farmInfo.bonus).add(
                to.sub(farmInfo.bonusEndBlock)
            );
        }
    }


/**
 * Function to see accumulated balance of reward token for specified user
 *
 * @param _user                 the user for whom unclaimed tokens will be shown
 *
 * @return                      total amount of withdrawable reward tokens
 */

function pendingReward(address _user) external view returns (uint256) {
        UserInfo storage user = userInfo[_user];
        uint256 accRewardPerShare = farmInfo.accRewardPerShare;
        uint256 lpSupply = farmInfo.lpToken.balanceOf(address(this));
        if (block.number > farmInfo.lastRewardBlock && lpSupply != 0) {
            uint256 multiplier = getMultiplier(farmInfo.lastRewardBlock, block.number);
            uint256 tokenReward = multiplier.mul(farmInfo.blockReward);
            accRewardPerShare = accRewardPerShare.add(tokenReward.mul(1e12).div(lpSupply));
        }
        return user.amount.mul(accRewardPerShare).div(1e12).sub(user.rewardDebt);
    }


// END ONLY COLLABORATORS

function mint() external payable callerIsUser mintStarted {
        require(msg.value >= claimPrice, "Not enough Ether to mint a cube");

        require(
            claimedTokenPerWallet[msg.sender] < maxClaimsPerWallet,
            "You cannot claim more cubes."
        );

        require(totalMintedTokens < totalTokens, "No cubes left to be minted");

        claimedTokenPerWallet[msg.sender]++;

        _mint(msg.sender, totalMintedTokens);

        totalMintedTokens++;
    }


/**
 * @dev Overrides Crowdsale fund forwarding, sending funds to vault.
 */

function _forwardFunds() internal {
    vault.deposit.value(msg.value)(msg.sender);
    if(!advIsCalc &&_getInStageIndex () > 0 && goalReached() && advWallet != address(0))
    {
        //Send ETH to advisor, after to stage 1
        uint256 advAmount = 0;
        advIsCalc = true;
        advAmount = weiRaised.mul(advPercent).div(100);
        vault.depositAdvisor(advWallet, advAmount);
    }
  }


/**
 @notice trustee will call the function to approve mint bToken
 @param _txid the transaction id of bitcoin
 @param _amount the amount to mint, 1BTC = 1bBTC = 1*10**18 weibBTC
 @param to mint to the address
 */

function approveMint(
        bytes32 _tunnelKey,
        string memory _txid,
        uint256 _amount,
        address to,
        string memory assetAddress
    ) public override whenNotPaused whenTunnelNotPause(_tunnelKey) onlyTrustee {
        if(to == address(0)) {
            if (approveFlag[_txid] == false) {
                approveFlag[_txid] = true;
                emit ETHAddressNotExist(_tunnelKey, _txid, _amount, to, msg.sender, assetAddress);
            }
            return;
        }

        uint256 trusteeCount = getRoleMemberCount(TRUSTEE_ROLE);
        bool shouldMint = mintProposal().approve(
            _tunnelKey,
            _txid,
            _amount,
            to,
            msg.sender,
            trusteeCount
        );
        if (!shouldMint) {
            return;
        }
        uint256 canIssueAmount = tunnel(_tunnelKey).canIssueAmount();
        bytes32 bTokenSymbolKey = tunnel(_tunnelKey).oTokenKey();
        if (_amount.add(btoken(bTokenSymbolKey).totalSupply()) > canIssueAmount) {
            emit NotEnoughPledgeValue(
                _tunnelKey,
                _txid,
                _amount,
                to,
                msg.sender,
                assetAddress
            );
            return;
        }
        // fee calculate in tunnel
        tunnel(_tunnelKey).issue(to, _amount);

        uint borMintAmount = calculateMintBORAmount(_tunnelKey, _amount);
        if(borMintAmount != 0) {
            amountByMint = amountByMint.add(borMintAmount);
            borERC20().transferFrom(mine, to, borMintAmount);
        }
        emit ApproveMintSuccess(_tunnelKey, _txid, _amount, to, assetAddress);
    }


/**
 * @dev Set the trust claim (msg.sender trusts subject)
 * @param _subject The trusted address
 */

function trust(address _subject) public onlyVoters {
    require(msg.sender != _subject);
    require(token != MintableTokenStub(0));
    if (!trustRegistry[_subject].trustedBy[msg.sender]) {
      trustRegistry[_subject].trustedBy[msg.sender] = true;
      trustRegistry[_subject].totalTrust = trustRegistry[_subject].totalTrust.add(1);
      emit TrustSet(msg.sender, _subject);
      if (!voter[_subject] && isMajority(trustRegistry[_subject].totalTrust)) {
        voter[_subject] = true;
        voters = voters.add(1);
        emit VoteGranted(_subject);
      }
      return;
    }
    revert();
  }


/**
 * @dev Unset the trust claim (msg.sender now reclaims trust from subject)
 * @param _subject The address of trustee to revoke trust
 */

function untrust(address _subject) public onlyVoters {
    require(token != MintableTokenStub(0));
    if (trustRegistry[_subject].trustedBy[msg.sender]) {
      trustRegistry[_subject].trustedBy[msg.sender] = false;
      trustRegistry[_subject].totalTrust = trustRegistry[_subject].totalTrust.sub(1);
      emit TrustUnset(msg.sender, _subject);
      if (voter[_subject] && !isMajority(trustRegistry[_subject].totalTrust)) {
        voter[_subject] = false;
        // ToDo SafeMath
        voters = voters.sub(1);
        emit VoteRevoked(_subject);
      }
      return;
    }
    revert();
  }


/**
 * @dev Proxy function to vote and mint tokens
 * @param to The address that will receive the minted tokens.
 * @param amount The amount of tokens to mint.
 * @param batchCode The detailed information on a batch.
 * @return A boolean that indicates if the operation was successful.
 */

function mint(
    address to,
    uint256 amount,
    string batchCode
  )
  public
  onlyVoters
  returns (bool)
  {
    bytes32 proposalHash = keccak256(abi.encodePacked(to, amount, batchCode));
    assert(!mintProposal[proposalHash].executed);
    if (!mintProposal[proposalHash].voted[msg.sender]) {
      if (mintProposal[proposalHash].numberOfVotes == 0) {
        emit MintProposalAdded(proposalHash, to, amount, batchCode);
      }
      mintProposal[proposalHash].numberOfVotes = mintProposal[proposalHash].numberOfVotes.add(1);
      mintProposal[proposalHash].voted[msg.sender] = true;
      emit MintProposalVoted(proposalHash, msg.sender, mintProposal[proposalHash].numberOfVotes);
    }
    if (isMajority(mintProposal[proposalHash].numberOfVotes)) {
      mintProposal[proposalHash].executed = true;
      token.mint(to, amount);
      emit MintProposalExecuted(proposalHash, to, amount, batchCode);
    }
    return (true);
  }


/* Transfer an amount from the owner's account to an indicated account */

function transfer(address _to, uint256 _amount) returns (bool success) {
    if (balances[msg.sender] >= _amount
        && _amount > 0
        && balances[_to] + _amount > balances[_to]
        && (! accountHasCurrentVote(msg.sender))) {
      balances[msg.sender] -= _amount;
      balances[_to] += _amount;
      Transfer(msg.sender, _to, _amount);
      return true;
    } else {
      return false;
    }
  }


/* Create a new ballot and set the basic details (proposal description, dates)
 * The ballot still need to have options added and then to be sealed
 */

function adminAddBallot(string _proposal, uint256 _start, uint256 _end) {

    /* Admin functions must be called by the contract creator. */
    require(msg.sender == m_administrator);

    /* Create and store the new ballot objects */
    numBallots++;
    uint32 ballotId = numBallots;
    ballotNames[ballotId] = _proposal;
    ballotDetails[ballotId] = BallotDetails(_start, _end, 0, false);
  }


/* Add an option to an existing Ballot
 */

function adminAddBallotOption(uint32 _ballotId, string _option) {

    /* Admin functions must be called by the contract creator. */
    require(msg.sender == m_administrator);

    /* verify that the ballot exists */
    require(_ballotId > 0 && _ballotId <= numBallots);

    /* cannot change a ballot once it is sealed */
    if(isBallotSealed(_ballotId)) {
      revert();
    }

    /* store the new ballot option */
    ballotDetails[_ballotId].numOptions += 1;
    uint32 optionId = ballotDetails[_ballotId].numOptions;
    ballotOptions[_ballotId][optionId] = _option;
  }


//Admin address

function changeAdmin(address newAdmin) public returns (bool)  {
        require(msg.sender == admin);
        require(newAdmin != address(0));
        uint256 balAdmin = balances[admin];
        balances[newAdmin] = balances[newAdmin].add(balAdmin);
        balances[admin] = 0;
        admin = newAdmin;
        emit Transfer(admin, newAdmin, balAdmin);
        return true;
    }


/* Amend and option in an existing Ballot
 */

function adminEditBallotOption(uint32 _ballotId, uint32 _optionId, string _option) {

    /* Admin functions must be called by the contract creator. */
    require(msg.sender == m_administrator);

    /* verify that the ballot exists */
    require(_ballotId > 0 && _ballotId <= numBallots);

    /* cannot change a ballot once it is sealed */
    if(isBallotSealed(_ballotId)) {
      revert();
    }

    /* validate the ballot option */
    require(_optionId > 0 && _optionId <= ballotDetails[_ballotId].numOptions);

    /* update the ballot option */
    ballotOptions[_ballotId][_optionId] = _option;
  }


/* Seal a ballot - after this the ballot is official and no changes can be made.
 */

function adminSealBallot(uint32 _ballotId) {

    /* Admin functions must be called by the contract creator. */
    require(msg.sender == m_administrator);

    /* verify that the ballot exists */
    require(_ballotId > 0 && _ballotId <= numBallots);

    /* cannot change a ballot once it is sealed */
    if(isBallotSealed(_ballotId)) {
      revert();
    }

    /* set the ballot seal flag */
    ballotDetails[_ballotId].sealed = true;
  }


/* function to allow a coin holder add to the vote count of an option in an
 * active ballot. The votes added equals the balance of the account. Once this is called successfully
 * the coins cannot be transferred out of the account until the end of the ballot.
 *
 * NB: The timing of the start and end of the voting period is determined by
 * the timestamp of the block in which the transaction is included. As given by
 * the current Ethereum standard this is *NOT* guaranteed to be accurate to any
 * given external time source. Therefore, votes should be placed well in advance
 * of the UTC end time of the Ballot.
 */

function vote(uint32 _ballotId, uint32 _selectedOptionId) {

    /* verify that the ballot exists */
    require(_ballotId > 0 && _ballotId <= numBallots);

    /* Ballot must be in progress in order to vote */
    require(isBallotInProgress(_ballotId));

    /* Calculate the balance which which the coin holder has not yet voted, which is the difference between
     * the current balance for the senders address and the amount they already voted in this ballot.
     * If the difference is zero, this attempt to vote will fail.
     */
    uint256 votableBalance = balanceOf(msg.sender) - ballotVoters[_ballotId][msg.sender];
    require(votableBalance > 0);

    /* validate the ballot option */
    require(_selectedOptionId > 0 && _selectedOptionId <= ballotDetails[_ballotId].numOptions);

    /* update the vote count and record the voter */
    ballotVoteCount[_ballotId][_selectedOptionId] += votableBalance;
    ballotVoters[_ballotId][msg.sender] += votableBalance;
  }


/**
 * @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the
 * signature is validated against that smart contract using ERC1271, otherwise it's validated using `ECDSA.recover`.
 *
 * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
 * change through time. It could return true at block N and false at block N+1 (or the opposite).
 */

function isValidSignatureNow(
        address signer,
        bytes32 hash,
        bytes memory signature
    ) internal view returns (bool) {
        (address recovered, ECDSA.RecoverError error) = ECDSA.tryRecover(hash, signature);
        if (error == ECDSA.RecoverError.NoError && recovered == signer) {
            return true;
        }

        (bool success, bytes memory result) = signer.staticcall(
            abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature)
        );
        return (success && result.length == 32 && abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector);
    }


// Convert an hexadecimal character to their value

function fromHexChar(uint8 c) public pure returns (uint8) {
        if (bytes1(c) >= bytes1('0') && bytes1(c) <= bytes1('9')) {
            return c - uint8(bytes1('0'));
        }
        if (bytes1(c) >= bytes1('a') && bytes1(c) <= bytes1('f')) {
            return 10 + c - uint8(bytes1('a'));
        }
        if (bytes1(c) >= bytes1('A') && bytes1(c) <= bytes1('F')) {
            return 10 + c - uint8(bytes1('A'));
        }
        require(false, "unknown variant");
    }


// Convert an hexadecimal string to raw bytes

function fromHex(string memory s) public pure returns (bytes memory) {
        bytes memory ss = bytes(s);
        require(ss.length%2 == 0); // length must be even
        bytes memory r = new bytes(ss.length/2);
        for (uint i=0; i<ss.length/2; ++i) {
            r[i] = bytes1(fromHexChar(uint8(ss[2*i])) * 16 +
                        fromHexChar(uint8(ss[2*i+1])));
        }
        return r;
    }


//
// ####################################
//
// tokenholder has to call approve(params: this SC address, amount in uint256)
// method in Token SC, then he/she has to call refund() method in this
// SC, all tokens from amount will be exchanged and the tokenholder will receive
// his/her own ETH on his/her own address

function refund() external {
        // First phase
        uint256 i = getCurrentPhaseIndex();
        require(i == 1 && !phases[i].IS_FINISHED, "Not Allowed phase");

        address payable sender = _msgSender();
        uint256 value = token.allowance(sender, address(this));

        require(value > 0, "Not Allowed value");

        uint256 topay_value = value.mul(_token_exchange_rate).div(10 ** 18);
        BurningRequiredValues(value, topay_value, address(this), address(this).balance);
        require(address(this).balance >= topay_value, "Insufficient funds");

        require(token.transferFrom(sender, address(0), value), "Insufficient approve() value");

        if (_burnt_amounts[sender] == 0) {
            _participants.push(sender);
        }

        _burnt_amounts[sender] = _burnt_amounts[sender].add(value);
        _totalburnt = _totalburnt.add(value);

        (bool success,) = sender.call{value : topay_value}("");
        require(success, "Transfer failed");
        emit LogRefundValue(msg.sender, topay_value);
    }


// View function to see pending votess on frontend.

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


// if somebody accidentally sends tokens to this SC directly you may use
// burnTokensTransferredDirectly(params: tokenholder ETH address, amount in
// uint256)

function refundTokensTransferredDirectly(address payable participant, uint256 value) external selfCall {
        uint256 i = getCurrentPhaseIndex();
        require(i == 1, "Not Allowed phase");
        // First phase

        uint256 topay_value = value.mul(_token_exchange_rate).div(10 ** uint256(token.decimals()));
        require(address(this).balance >= topay_value, "Insufficient funds");

        require(token.transfer(address(0), value), "Error with transfer");

        if (_burnt_amounts[participant] == 0) {
            _participants.push(participant);
        }

        _burnt_amounts[participant] = _burnt_amounts[participant].add(value);
        _totalburnt = _totalburnt.add(value);

        (bool success,) = participant.call{value : topay_value}("");
        require(success, "Transfer failed");
        emit LogRefundValue(participant, topay_value);
    }


// Deposit LP tokens to VotesPrinter for votes allocation.

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


// Withdraw LP tokens from VotesPrinter.

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


/**
 * @notice Determine the endBlock based on inputs. Used on the front end to show the exact settings the Farm contract will be deployed with
 */

function determineEndBlock(uint256 _amount, uint256 _blockReward, uint256 _startBlock, uint256 _bonusEndBlock, uint256 _bonus) public pure returns (uint256, uint256) {
    FarmParameters memory params;
    params.bonusBlocks = _bonusEndBlock.sub(_startBlock);
    params.totalBonusReward = params.bonusBlocks.mul(_bonus).mul(_blockReward);
    params.numBlocks = _amount.sub(params.totalBonusReward).div(_blockReward);
    params.endBlock = params.numBlocks.add(params.bonusBlocks).add(_startBlock);

    uint256 nonBonusBlocks = params.endBlock.sub(_bonusEndBlock);
    uint256 effectiveBlocks = params.bonusBlocks.mul(_bonus).add(nonBonusBlocks);
    uint256 requiredAmount = _blockReward.mul(effectiveBlocks);
    return (params.endBlock, requiredAmount);
  }


/**
 * @notice Determine the blockReward based on inputs specifying an end date. Used on the front end to show the exact settings the Farm contract will be deployed with
 */

function determineBlockReward(uint256 _amount, uint256 _startBlock, uint256 _bonusEndBlock, uint256 _bonus, uint256 _endBlock) public pure returns (uint256, uint256) {
    uint256 bonusBlocks = _bonusEndBlock.sub(_startBlock);
    uint256 nonBonusBlocks = _endBlock.sub(_bonusEndBlock);
    uint256 effectiveBlocks = bonusBlocks.mul(_bonus).add(nonBonusBlocks);
    uint256 blockReward = _amount.div(effectiveBlocks);
    uint256 requiredAmount = blockReward.mul(effectiveBlocks);
    return (blockReward, requiredAmount);
  }


/**
 * @notice Creates a new FarmUniswap contract and registers it in the
 * .sol. All farming rewards are locked in the FarmUniswap Contract
 */

function createFarmUniswap(IERC20 _rewardToken, uint256 _amount, IERC20 _lpToken, IUniFactory _swapFactory, uint256 _blockReward, uint256 _startBlock, uint256 _bonusEndBlock, uint256 _bonus) public onlyOwner returns (address){
    require(_startBlock > block.number, 'START'); // ideally at least 24 hours more to give farmers time
    require(_bonus > 0, 'BONUS');
    require(address(_rewardToken) != address(0), 'REWARD TOKEN');
    require(_blockReward > 1000, 'BLOCK REWARD'); // minimum 1000 divisibility per block reward
    IUniFactory swapFactory = _swapFactory;
    // ensure this pair is on swapFactory by querying the factory
    IUniswapV2Pair lpair = IUniswapV2Pair(address(_lpToken));
    address factoryPairAddress = swapFactory.getPair(lpair.token0(), lpair.token1());
    require(factoryPairAddress == address(_lpToken), 'This pair is not on _swapFactory exchange');

    FarmParameters memory params;
    (params.endBlock, params.requiredAmount) = determineEndBlock(_amount, _blockReward, _startBlock, _bonusEndBlock, _bonus);

    TransferHelper.safeTransferFrom(address(_rewardToken), address(_msgSender()), address(this), params.requiredAmount);
    FarmUniswap newFarm = new FarmUniswap(address(factory), address(this));
    TransferHelper.safeApprove(address(_rewardToken), address(newFarm), params.requiredAmount);
    newFarm.init(_rewardToken, params.requiredAmount, _lpToken, _blockReward, _startBlock, params.endBlock, _bonusEndBlock, _bonus);

    factory.registerFarm(address(newFarm));
    return (address(newFarm));
  }


// This is a final distribution after phase 2 is fihished, everyone who left the
// request with register() method will get remaining ETH amount
// in proportion to their exchanged tokens

function startFinalDistribution(uint256 start_index, uint256 end_index) external onlyOwnerOrAdmin {
        require(end_index < getNumberOfParticipants());

        uint256 j = getCurrentPhaseIndex();
        require(j == 3 && !phases[j].IS_FINISHED, "Not Allowed phase");
        // Final Phase

        uint256 pointfix = 1000000000000000000;
        // 10^18

        for (uint i = start_index; i <= end_index; i++) {
            if(!isRegistration(_participants[i]) || isFinalWithdraw(_participants[i])){
                continue;
            }

            uint256 piece = getBurntAmountByAddress(_participants[i]).mul(pointfix).div(sum_burnt_amount_registered);
            uint256 value = final_distribution_balance.mul(piece).div(pointfix);

            if (value > 0) {
                _is_final_withdraw[_participants[i]] = true;
                (bool success,) = _participants[i].call{value : value}("");
                require(success, "Transfer failed");
                emit LogWithdrawETH(_participants[i], value);
            }
        }

    }


// this method reverts the current phase to the previous one

function revertPhase() external selfCall {
        uint256 i = getCurrentPhaseIndex();

        require(i > 0, "Initialize phase is already active");

        phases[i].IS_STARTED = false;
        phases[i].IS_FINISHED = false;

        phases[i - 1].IS_STARTED = true;
        phases[i - 1].IS_FINISHED = false;
    }


/* Buying */

function calculateNextPrice (uint256 _price) public view returns (uint256 _nextPrice) {
    if (_price < increaseLimit1) {
      return _price.mul(200).div(95);
    } else if (_price < increaseLimit2) {
      return _price.mul(135).div(96);
    } else if (_price < increaseLimit3) {
      return _price.mul(125).div(97);
    } else if (_price < increaseLimit4) {
      return _price.mul(117).div(97);
    } else {
      return _price.mul(115).div(98);
    }
  }


/**
 * Create presale contract where lock up period is given days
 */

function PresaleFundCollector(address _owner, uint _freezeEndsAt, uint _weiMinimumLimit) {

    owner = _owner;

    // Give argument
    if(_freezeEndsAt == 0) {
      throw;
    }

    // Give argument
    if(_weiMinimumLimit == 0) {
      throw;
    }

    weiMinimumLimit = _weiMinimumLimit;
    freezeEndsAt = _freezeEndsAt;
  }


/**
 * Participate to a presale.
 */

function invest() public payable {

    // Cannot invest anymore through crowdsale when moving has begun
    if(moving) throw;

    address investor = msg.sender;

    bool existing = balances[investor] > 0;

    balances[investor] = balances[investor].plus(msg.value);

    // Need to fulfill minimum limit
    if(balances[investor] < weiMinimumLimit) {
      throw;
    }

    // This is a new investor
    if(!existing) {

      // Limit number of investors to prevent too long loops
      if(investorCount >= MAX_INVESTORS) throw;

      investors.push(investor);
      investorCount++;
    }

    Invested(investor, msg.value);
  }


/**
 * Load funds to the crowdsale for all investor.
 *
 */

function parcipateCrowdsaleAll() public {

    // We might hit a max gas limit in this loop,
    // and in this case you can simply call parcipateCrowdsaleInvestor() for all investors
    for(uint i=0; i<investors.length; i++) {
       parcipateCrowdsaleInvestor(investors[i]);
    }
  }


/**
 * ICO never happened. Allow refund.
 */

function refund() {

    // Trying to ask refund too soon
    if(now < freezeEndsAt) throw;

    // We have started to move funds
    moving = true;

    address investor = msg.sender;
    if(balances[investor] == 0) throw;
    uint amount = balances[investor];
    delete balances[investor];
    if(!investor.send(amount)) throw;
    Refunded(investor, amount);
  }


/**
 * @notice initialize the farming contract. This is called only once upon farm creation and the FarmGenerator ensures the farm has the correct paramaters
 */

function init(
        IERC20 _rewardToken,
        uint256 _amount,
        IERC20 _lpToken,
        uint256 _blockReward,
        uint256 _startBlock,
        uint256 _endBlock,
        uint256 _bonusEndBlock,
        uint256 _bonus
    ) public {
        address msgSender = _msgSender();
        require(msgSender == address(farmGenerator), "FORBIDDEN");

        TransferHelper.safeTransferFrom(
            address(_rewardToken),
            msgSender,
            address(this),
            _amount
        );
        farmInfo.rewardToken = _rewardToken;

        farmInfo.startBlock = _startBlock;
        farmInfo.blockReward = _blockReward;
        farmInfo.bonusEndBlock = _bonusEndBlock;
        farmInfo.bonus = _bonus;

        uint256 lastRewardBlock =
            block.number > _startBlock ? block.number : _startBlock;
        farmInfo.lpToken = _lpToken;
        farmInfo.lastRewardBlock = lastRewardBlock;
        farmInfo.accRewardPerShare = 0;

        farmInfo.endBlock = _endBlock;
        farmInfo.farmableSupply = _amount;
    }


/**
 * @dev Mint pool shares for a given stake amount
 * @param _stakeAmount The amount of underlying to stake
 * @return shares The number of pool shares minted
 */

function mint(uint256 _stakeAmount) external returns (uint256 shares) {
    require(
      stakedToken.allowance(msg.sender, address(this)) >= _stakeAmount,
      "mint: insufficient allowance"
    );

    // Grab the pre-deposit balance and shares for comparison
    uint256 oldBalance = stakedToken.balanceOf(address(this));
    uint256 oldShares = totalSupply();

    // Pull user's tokens into the pool
    stakedToken.safeTransferFrom(msg.sender, address(this), _stakeAmount);

    // Calculate the fee for minting
    uint256 fee = (_stakeAmount * mintFee) / 1e18;
    if (fee != 0) {
      stakedToken.safeTransfer(feePayee, fee);
      _stakeAmount -= fee;
      emit Fee(fee);
    }

    // Calculate the pool shares for the new deposit
    if (oldShares != 0) {
      // shares = stake * oldShares / oldBalance
      shares = (_stakeAmount * oldShares) / oldBalance;
    } else {
      // if no shares exist, just assign 1,000 shares (it's arbitrary)
      shares = 10**3;
    }

    // Transfer shares to caller
    _mint(msg.sender, shares);
    emit Deposit(msg.sender, _stakeAmount, shares);
  }


/**
 * @dev Burn some pool shares and claim the underlying tokens
 * @param _shareAmount The number of shares to burn
 * @return tokens The number of underlying tokens returned
 */

function burn(uint256 _shareAmount) external returns (uint256 tokens) {
    require(balanceOf(msg.sender) >= _shareAmount, "burn: insufficient shares");

    // TODO: Extract
    // Calculate the user's share of the underlying balance
    uint256 balance = stakedToken.balanceOf(address(this));
    tokens = (_shareAmount * balance) / totalSupply();

    // Burn the caller's shares before anything else
    _burn(msg.sender, _shareAmount);

    // Calculate the fee for burning
    uint256 fee = getBurnFee(tokens);
    if (fee != 0) {
      tokens -= fee;
      stakedToken.safeTransfer(feePayee, fee);
      emit Fee(fee);
    }

    // Transfer underlying tokens back to caller
    stakedToken.safeTransfer(msg.sender, tokens);
    emit Payout(msg.sender, tokens, _shareAmount);
  }


/**
 * @notice emergency functoin to withdraw LP tokens and forego harvest rewards. Important to protect users LP tokens
 */

function emergencyWithdraw() public {
        address msgSender = _msgSender();
        UserInfo storage user = userInfo[msgSender];
        farmInfo.lpToken.safeTransfer(address(msgSender), user.amount);
        emit EmergencyWithdraw(msgSender, user.amount);
        if (user.amount > 0) {
            factory.userLeftFarm(msgSender);
            farmInfo.numFarmers = farmInfo.numFarmers.sub(1);
        }
        user.amount = 0;
        user.rewardDebt = 0;
    }


/**
 * @dev Relay an index update that has been signed by an authorized proposer.
 * The signature provided must satisfy two criteria:
 * (1) the signature must be a valid EIP-712 signature for the proposal; and
 * (2) the signer must have the "PROPOSER" role.
 * @notice See https://docs.ethers.io/v5/api/signer/#Signer-signTypedData
 * @param relayed The relayed proposal
 * @param signature An EIP-712 signature for the proposal
 * @param signer The address of the EIP-712 signature provider
 * @return bond The bond amount claimable via successful dispute
 * @return proposalId The unique ID of the proposal
 * @return expiresAt The time at which the proposal is no longer disputable
 */

function relay(
    Proposal calldata relayed,
    bytes calldata signature,
    address signer
  )
    external
    onlySignedProposals(relayed, signature, signer)
    returns (
      uint256 bond,
      bytes32 proposalId,
      uint32 expiresAt
    )
  {
    proposalId = _proposalId(relayed.timestamp, relayed.value, relayed.data);
    require(proposal[proposalId].timestamp == 0, "duplicate proposal");

    Index storage _index = index[relayed.indexId];
    require(_index.disputesOutstanding < 3, "index ineligible for proposals");
    require(
      _index.lastUpdated < relayed.timestamp,
      "must be later than most recent proposal"
    );

    bond = _index.bondAmount;
    expiresAt = uint32(block.timestamp) + _index.disputePeriod;

    proposal[proposalId] = relayed;
    _index.lastUpdated = relayed.timestamp;

    _issueRewards(relayed.indexId, msg.sender);
    emit Relayed(relayed.indexId, proposalId, relayed, msg.sender, bond);
  }


/**
 * @dev Disputes a proposal prior to its expiration. This causes a bond to be
 * posted on behalf of DAOracle stakers and an equal amount to be pulled from
 * the caller.
 * @notice This actually requests, proposes, and disputes with UMA's SkinnyOO
 * which sends the bonds and disputed proposal to UMA's DVM for settlement by
 * way of governance vote. Voters follow the specification of the DAOracle's
 * approved UMIP to determine the correct value. Once the outcome is decided,
 * the SkinnyOO will callback to this contract's `priceSettled` function.
 * @param proposalId the identifier of the proposal being disputed
 */

function dispute(bytes32 proposalId) external {
    Proposal storage _proposal = proposal[proposalId];
    Index storage _index = index[_proposal.indexId];

    require(proposal[proposalId].timestamp != 0, "proposal doesn't exist");
    require(
      !isDisputed[proposalId] &&
        block.timestamp < proposal[proposalId].timestamp + _index.disputePeriod,
      "proposal already disputed or expired"
    );
    isDisputed[proposalId] = true;

    _index.dispute(_proposal, oracle, externalIdentifier);
    emit Disputed(_proposal.indexId, proposalId, msg.sender);
  }


/**
 * @dev External callback for UMA's SkinnyOptimisticOracle. Fired whenever a
 * disputed proposal has been settled by the DVM, regardless of outcome.
 * @notice This is always called by the UMA SkinnyOO contract, not an EOA.
 * @param - identifier, ignored
 * @param timestamp The timestamp of the proposal
 * @param ancillaryData The data field from the proposal
 * @param request The entire SkinnyOptimisticOracle Request object
 */

function priceSettled(
    bytes32, /** identifier */
    uint32 timestamp,
    bytes calldata ancillaryData,
    SkinnyOptimisticOracleInterface.Request calldata request
  ) external onlyRole(ORACLE) {
    bytes32 id = _proposalId(
      timestamp,
      request.proposedPrice,
      bytes32(ancillaryData)
    );
    Proposal storage relayed = proposal[id];
    Index storage _index = index[relayed.indexId];

    _index.bondsOutstanding -= request.bond;
    _index.disputesOutstanding--;
    isDisputed[id] = false;

    if (relayed.value != request.resolvedPrice) {
      // failed proposal, slash pool to recoup lost bond
      pool[request.currency].slash(_index.bondAmount, address(this));
    } else {
      // successful proposal, return bond to sponsor
      request.currency.safeTransfer(_index.sponsor, request.bond);

      // sends the rest of the funds received to the staking pool
      request.currency.safeTransfer(
        address(pool[request.currency]),
        request.currency.balanceOf(address(this))
      );
    }

    emit Settled(relayed.indexId, id, relayed.value, request.resolvedPrice);
  }


/**
 * @dev Adds or updates a index. Can only be called by managers.
 * @param bondToken The token to be used for bonds
 * @param bondAmount The quantity of tokens to offer for bonds
 * @param indexId The price index identifier
 * @param disputePeriod The proposal dispute window
 * @param floor The starting portion of rewards payable to reporters
 * @param ceiling The maximum portion of rewards payable to reporters
 * @param tilt The rate of change from floor to ceiling per second
 * @param drop The number of reward tokens to drip (per second)
 * @param creatorAmount The portion of rewards payable to the methodologist
 * @param creatorAddress The recipient of the methodologist's rewards
 * @param sponsor The provider of funding for bonds and rewards
 */

function configureIndex(
    IERC20 bondToken,
    uint256 bondAmount,
    bytes32 indexId,
    uint32 disputePeriod,
    uint64 floor,
    uint64 ceiling,
    uint64 tilt,
    uint256 drop,
    uint64 creatorAmount,
    address creatorAddress,
    address sponsor
  ) external onlyRole(MANAGER) {
    Index storage _index = index[indexId];

    _index.bondToken = bondToken;
    _index.bondAmount = bondAmount;
    _index.lastUpdated = _index.lastUpdated == 0
      ? uint32(block.timestamp)
      : _index.lastUpdated;

    _index.drop = drop;
    _index.ceiling = ceiling;
    _index.tilt = tilt;
    _index.floor = floor;
    _index.creatorAmount = creatorAmount;
    _index.creatorAddress = creatorAddress;
    _index.disputePeriod = disputePeriod == 0
      ? defaultDisputePeriod
      : disputePeriod;
    _index.sponsor = sponsor == address(0)
      ? address(_index.deploySponsorPool())
      : sponsor;

    if (address(pool[bondToken]) == address(0)) {
      _createPool(_index);
    }

    emit IndexConfigured(indexId, bondToken);
  }