// File: Context.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}
// File: HashSale.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./Ownable.sol";
import "./TransferHelper.sol";

contract HashSale is Ownable {
	address public hashTokenAddress;
	address payable paymentAddress;
	address kycSigner;
	uint public endTime;
	uint public stageAmount;
	uint public minPayAmount;
	uint public tokensSold = 0;
	uint public tokensWithdrawn = 0;
	uint public discountThreshold;
	uint8 public discount = 25;
	uint8 public currentStage = 0;
	bool isFinished = false;
	uint public constant HASH_TOKEN_DECIMALS = 18;
	string public constant AGREEMENT = "I confirm I am not a citizen, national, resident (tax or otherwise) or holder of a green card of the USA and have never been a citizen, national, resident (tax or otherwise) or holder of a green card of the USA in the past.";
	string constant AGREEMENT_LENGTH = "223";

    struct Stage {
        uint tokenPrice;
        uint tokensSold;
    }
	struct Sale {
	    address customerAddress;
		uint payAmount;
		uint tokenAmount;
		bytes agreementSignature;
		uint16 referral;
		bool tokensWithdrawn;
	}

	Stage[] public stages;
	Sale[] public sales;

	event NewSale(uint indexed saleId, address indexed customerAddress, uint16 indexed referral, uint payAmount, uint tokenAmount, uint8 stage, uint stageSales, bool isSaleFinished);
	event TokensWithdrawn(address indexed customerAddress, uint tokenAmount);
	event StageSwitch(uint8 previousStage, uint8 newStage, bool isSaleFinished);

	constructor(
	    address _hashTokenAddress, address payable _paymentAddress, address _kycSigner, uint _endTime,
	    uint _stageAmount, uint _minPayAmount, uint _discountThreshold,
	    uint _tokenPrice1, uint _tokenPrice2, uint _tokenPrice3, uint _tokenPrice4, uint _tokenPrice5
    ) {
        hashTokenAddress = _hashTokenAddress;
        paymentAddress = _paymentAddress;
        endTime = _endTime;
        stageAmount = _stageAmount * (10 ** HASH_TOKEN_DECIMALS);
        minPayAmount = _minPayAmount;
        kycSigner = _kycSigner;
        discountThreshold = _discountThreshold;
        stages.push(Stage(_tokenPrice1, 0));
        stages.push(Stage(_tokenPrice2, 0));
		stages.push(Stage(_tokenPrice3, 0));
		stages.push(Stage(_tokenPrice4, 0));
		stages.push(Stage(_tokenPrice5, 0));
    }

    function changeKycSigner(address _kycSigner) public onlyOwner {
        require(_kycSigner != address(0), "Incorrect address");
        kycSigner = _kycSigner;
    }

    function changeMinPayAmount(uint _minPayAmount) public onlyOwner {
        minPayAmount = _minPayAmount;
    }

    function changeDiscount(uint8 _discount) public onlyOwner {
        discount = _discount;
    }

    function changeDiscountThreshold(uint _discountThreshold) public onlyOwner {
        discountThreshold = _discountThreshold;
    }

    function switchStage(uint8 _stage) public onlyOwner {
        require(!isSaleFinished(), "The sale is over");
        uint8 previousStage = currentStage;
        _switchStage(_stage);
        emit StageSwitch(previousStage, currentStage, isFinished);
    }

    function withdrawRemainingTokens(uint _tokenAmount) public onlyOwner {
        require(_tokenAmount > 0, "Nothing to withdraw");
        require(_tokenAmount <= _getHashBalance(), "Not enough HASH tokens to withdraw");
        TransferHelper.safeTransfer(hashTokenAddress, msg.sender, _tokenAmount);
    }

	function buyTokens(bytes calldata _agreementSignature, uint16 _referral) payable public {
	    require(!isSaleFinished(), "The sale is over");
	    require(msg.value >= minPayAmount, "Amount must be greater than the minimal value");
		require (_verifySign(_agreementSignature, msg.sender), "Incorrect agreement signature");
		uint etherAmount = msg.value;
		if (etherAmount >= discountThreshold) {
		    etherAmount = etherAmount * 100 / (100 - discount);
		}
		uint tokenAmount = 0;
		for (uint8 i = currentStage; i < stages.length && etherAmount > 0; i++) {
		    uint buyAmount = _calculateTokenAmount(etherAmount, i);
		    uint thisStageAmount = stageAmount - stages[i].tokensSold;
		    if (buyAmount >= thisStageAmount) {
		        tokenAmount += thisStageAmount;
		        etherAmount -= _calculateEtherAmount(thisStageAmount, i);
		        stages[i].tokensSold = stageAmount;
		        _switchStage(currentStage + 1);
		    } else {
		        tokenAmount += buyAmount;
		        etherAmount = 0;
		        stages[i].tokensSold += buyAmount;
		    }
		}
		require(etherAmount == 0, "Not enough HASH tokens to buy");
		require(tokenAmount > 0, "Amount must be greater than 0");
		tokensSold += tokenAmount;
		paymentAddress.transfer(msg.value);
		uint saleId = sales.length;
		sales.push(Sale(msg.sender, msg.value, tokenAmount, _agreementSignature, _referral, false));
		emit NewSale(saleId, msg.sender, _referral, msg.value, tokenAmount, currentStage, stages[currentStage].tokensSold, isFinished);
	}

	function getUnwithdrawnTokenAmount(address _customerAddress) public view returns (uint tokenAmount) {
	    tokenAmount = 0;
	    for (uint i = 0; i < sales.length; i++) {
			if (sales[i].customerAddress == _customerAddress && sales[i].tokensWithdrawn == false) {
				tokenAmount += sales[i].tokenAmount;
			}
		}
	}

	function withdrawTokens(bytes calldata _kycSignature) public {
	    require(isSaleFinished(), "The withdrawal of HASH tokens is not yet available");
	    require(_verifyKycSign(_kycSignature, msg.sender), "Incorrect KYC signature");
	    uint tokenAmount = 0;
	    for (uint i = 0; i < sales.length; i++) {
			if (sales[i].customerAddress == msg.sender && sales[i].tokensWithdrawn == false) {
				tokenAmount += sales[i].tokenAmount;
				sales[i].tokensWithdrawn = true;
			}
		}
		require(tokenAmount > 0, "You have nothing to withdraw");
		tokensWithdrawn += tokenAmount;
        TransferHelper.safeTransfer(hashTokenAddress, msg.sender, tokenAmount);
		emit TokensWithdrawn(msg.sender, tokenAmount);
	}

	function isSaleFinished() public view returns (bool) {
	    return isFinished || block.timestamp > endTime;
	}

	function calculateTokenAmount(uint etherAmount) public view returns(uint tokenAmount) {
	    require(!isSaleFinished(), "The sale is over");
	    require(etherAmount >= minPayAmount, "Amount must be greater than the minimal value");
	    if (etherAmount >= discountThreshold) {
            etherAmount = etherAmount * 100 / (100 - discount);
        }
	    for (uint8 i = currentStage; i < stages.length && etherAmount > 0; i++) {
		    uint buyAmount = _calculateTokenAmount(etherAmount, i);
		    uint thisStageAmount = stageAmount - stages[i].tokensSold;
		    if (buyAmount >= thisStageAmount) {
		        tokenAmount += thisStageAmount;
		        etherAmount -= _calculateEtherAmount(thisStageAmount, i);
		    } else {
		        tokenAmount += buyAmount;
		        etherAmount = 0;
		    }
		}
		require(etherAmount == 0, "Not enough HASH tokens to buy");
	}

	function getMinAndMaxPayAmounts() public view returns(uint, uint) {
	    uint maxPayAmount = 0;
	    for (uint8 i = currentStage; i < stages.length && !isSaleFinished(); i++) {
		    uint thisStageAmount = stageAmount - stages[i].tokensSold;
		    maxPayAmount += _calculateEtherAmount(thisStageAmount, i);
		}
		if (maxPayAmount >= discountThreshold) {
		    uint maxPayAmountWithDiscount = maxPayAmount * (100 - discount) / 100;
		    if (maxPayAmountWithDiscount >= discountThreshold) {
		        maxPayAmount = maxPayAmountWithDiscount;
		    } else {
		        maxPayAmount = discountThreshold - 1;
		    }
		}
		return (minPayAmount, maxPayAmount);
	}

	function _switchStage(uint8 _stage) private {
	    require(_stage > currentStage, "The next stage value must be more than the current one");
	    if (_stage >= stages.length) {
	        isFinished = true;
        } else {
            currentStage = _stage;
        }
	}

	function _calculateTokenAmount(uint etherAmount, uint8 stage) private view returns(uint tokenAmount) {
	    tokenAmount = etherAmount * (10 ** HASH_TOKEN_DECIMALS) / stages[stage].tokenPrice;
	}

	function _calculateEtherAmount(uint tokenAmount, uint8 stage) private view returns(uint etherAmount) {
	    etherAmount = tokenAmount * stages[stage].tokenPrice / (10 ** HASH_TOKEN_DECIMALS);
	}

    function _getHashBalance() private returns(uint balance) {
        (bool success, bytes memory data) = hashTokenAddress.call(
            abi.encodeWithSelector(bytes4(keccak256(bytes('balanceOf(address)'))), address(this))
        );
        require(success, "Getting HASH balance failed");
        balance = abi.decode(data, (uint));
    }

	function _verifySign(bytes memory _sign, address _signer) pure private returns (bool) {
        bytes32 hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", AGREEMENT_LENGTH, AGREEMENT));
        address[] memory signList = _recoverAddresses(hash, _sign);
        return signList[0] == _signer;
    }

    function _verifyKycSign(bytes memory _sign, address _customerAddress) view private returns (bool) {
        bytes32 hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n20", _customerAddress));
        address[] memory signList = _recoverAddresses(hash, _sign);
        return signList[0] == kycSigner;
    }

	function _recoverAddresses(bytes32 _hash, bytes memory _signatures) pure private returns (address[] memory addresses) {
        uint8 v;
        bytes32 r;
        bytes32 s;
        uint count = _countSignatures(_signatures);
        addresses = new address[](count);
        for (uint i = 0; i < count; i++) {
            (v, r, s) = _parseSignature(_signatures, i);
            addresses[i] = ecrecover(_hash, v, r, s);
        }
    }

	function _parseSignature(bytes memory _signatures, uint _pos) pure private returns (uint8 v, bytes32 r, bytes32 s) {
        uint offset = _pos * 65;
        assembly {
            r := mload(add(_signatures, add(32, offset)))
            s := mload(add(_signatures, add(64, offset)))
            v := and(mload(add(_signatures, add(65, offset))), 0xff)
        }
        if (v < 27) v += 27;
        require(v == 27 || v == 28);
    }

    function _countSignatures(bytes memory _signatures) pure private returns (uint) {
        return _signatures.length % 65 == 0 ? _signatures.length / 65 : 0;
    }
}
// File: Ownable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./Context.sol";
/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor () {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}
// File: TransferHelper.sol



// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity >=0.6.0;

// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
    function safeApprove(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeApprove: approve failed'
        );
    }

    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeTransfer: transfer failed'
        );
    }

    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::transferFrom: transferFrom failed'
        );
    }

    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
    }
}