// 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: HashBridge.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./Ownable.sol";
import "./TransferHelper.sol";

contract HashBridge is Ownable {
	address public signer;
	uint public reservationTime;
	uint public constant RATE_DECIMALS = 18;
	
	struct Offer {
		address token;
		uint amount;
		address payToken;
		uint rate;
		address ownerAddress;
		address payAddress;
		uint minPurchase;
		bool active;
	}
	struct Order {
		uint offerId;
		uint rate;
		address ownerAddress;
		address withdrawAddress;
		uint amount;
		uint payAmount;
		address payAddress;
		uint reservedUntil;
		bool complete;
	}
	struct Payment {
		uint orderId;
		uint payAmount;
		address payToken;
		address payAddress;
	}
	
	Offer[] offers;
	Order[] orders;
	Payment[] payments;
	
	event OfferAdd(uint indexed offerId, address indexed token, address indexed payToken, address ownerAddress, address payAddress, uint amount, uint rate, uint minPurchase, bool active);
	event OfferUpdate(uint indexed offerId, address payAddress, uint amount, uint rate, uint minPurchase, bool active);
	event OrderAdd(uint indexed orderId, uint indexed offerId, address indexed ownerAddress, uint rate, address withdrawAddress, uint amount, uint payAmount, address payAddress, uint reservedUntil);
	event OrderPay(uint indexed paymentId, uint indexed orderId, uint payAmount, address payToken, address payAddress);
	event OrderComplete(uint indexed orderId, uint offerAmount);
	
	constructor(address _signer, uint _reservationTime) {
        signer = _signer;
		reservationTime = _reservationTime;
    }
	
	function changeSigner(address _signer) public onlyOwner {
        signer = _signer;
    }
	
	function changeReservationTime(uint _reservationTime) public onlyOwner {
		reservationTime = _reservationTime;
	}
	
	function addOffer(address _token, uint _amount, address _payToken, uint _rate, address _payAddress, uint _minPurchase) public {
		require(_amount > 0, "Amount must be greater than 0");
		require(_amount >= _minPurchase, "Amount must not be less than the minimum purchase");
		require(_rate > 0, "Rate must be greater than 0");
		TransferHelper.safeTransferFrom(_token, msg.sender, address(this), _amount);
		uint offerId = offers.length;
		offers.push(Offer(_token, _amount, _payToken, _rate, msg.sender, _payAddress, _minPurchase, true));
		emit OfferAdd(offerId, _token, _payToken, msg.sender, _payAddress, _amount, _rate, _minPurchase, true);
	}
	
	function updateOffer(uint _offerId, uint _amount, uint _rate, address _payAddress, uint _minPurchase) public {
		_checkOfferAccess(_offerId);
		require(_rate > 0, "Rate must be greater than 0");
		uint blockedAmount = _getBlockedAmount(_offerId);
		require(_amount >= blockedAmount, "You can not withdraw tokens ordered by customers");
		if (_amount > offers[_offerId].amount) {
			TransferHelper.safeTransferFrom(offers[_offerId].token, msg.sender, address(this), _amount - offers[_offerId].amount);
		} else {
			TransferHelper.safeTransfer(offers[_offerId].token, msg.sender, offers[_offerId].amount - _amount);
		}
		offers[_offerId].amount = _amount;
		offers[_offerId].rate = _rate;
		offers[_offerId].payAddress = _payAddress;
		offers[_offerId].minPurchase = _minPurchase;
		emit OfferUpdate(_offerId, _payAddress, _amount, _rate, _minPurchase, offers[_offerId].active);
	}
	
	function activateOffer(uint _offerId) public {
		_checkOfferAccess(_offerId);
		require(offers[_offerId].active == false, "Offer is already active");
		offers[_offerId].active = true;
		emit OfferUpdate(_offerId, offers[_offerId].payAddress, offers[_offerId].amount, offers[_offerId].rate, offers[_offerId].minPurchase, true);
	}
	
	function deactivateOffer(uint _offerId) public {
		_checkOfferAccess(_offerId);
		require(offers[_offerId].active == true, "Offer is already inactive");
		offers[_offerId].active = false;
		emit OfferUpdate(_offerId, offers[_offerId].payAddress, offers[_offerId].amount, offers[_offerId].rate, offers[_offerId].minPurchase, false);
	}
	
	function addOrder(uint _offerId, address _withdrawAddress, uint _amount, uint _payAmount) public {
		require(_offerId < offers.length, "Incorrect offerId");
		require(offers[_offerId].active == true, "Offer is inactive");
		require(_amount > 0 || _payAmount > 0, "Amount must be greater than 0");
		uint rate = offers[_offerId].rate;
		if (_amount > 0) {
			_payAmount = _amount * rate / (10 ** RATE_DECIMALS);
		} else {
			_amount = _payAmount * (10 ** RATE_DECIMALS) / rate;
		}
		require(_amount >= offers[_offerId].minPurchase, "Amount is less than the minimum purchase");
		uint blockedAmount = _getBlockedAmount(_offerId);
		require(_amount <= offers[_offerId].amount - blockedAmount, "Not enough tokens in the offer");
		address _payAddress = offers[_offerId].payAddress;
		uint reservedUntil = block.timestamp + reservationTime;
		uint orderId = orders.length;
		orders.push(Order(_offerId, rate, msg.sender, _withdrawAddress, _amount, _payAmount, _payAddress, reservedUntil, false));
		emit OrderAdd(orderId, _offerId, msg.sender, rate, _withdrawAddress, _amount, _payAmount, _payAddress, reservedUntil);
	}
	
	function payOrder(uint _orderId, uint _payAmount, address _payToken, address _payAddress) public {
		require(_payAmount > 0, "Amount must be greater than 0");
		TransferHelper.safeTransferFrom(_payToken, msg.sender, _payAddress, _payAmount);
		uint paymentId = payments.length;
		payments.push(Payment(_orderId, _payAmount, _payToken, _payAddress));
		emit OrderPay(paymentId, _orderId, _payAmount, _payToken, _payAddress);
	}
	
	function withdrawTokens(uint _orderId, bytes calldata _sign) public {
		require(_orderId < orders.length, "Incorrect orderId");
		require(orders[_orderId].complete == false, "Tokens already withdrawn");
		uint offerId = orders[_orderId].offerId;
		uint amount = orders[_orderId].amount;
		uint payAmount = orders[_orderId].payAmount;
		address payToken = offers[offerId].payToken;
		address payAddress = orders[_orderId].payAddress;
		require(orders[_orderId].reservedUntil >= block.timestamp || offers[offerId].amount - _getBlockedAmount(offerId) >= amount, "Not enough tokens in the offer");
		bytes32 data = keccak256(abi.encodePacked(_orderId, payAmount, payToken, payAddress));
		require(_verifySign(data, _sign), "Incorrect signature");
		TransferHelper.safeTransfer(offers[offerId].token, orders[_orderId].withdrawAddress, amount);
		orders[_orderId].complete = true;
		offers[offerId].amount -= amount;
		emit OrderComplete(_orderId, offers[offerId].amount);
	}
	
	function _checkOfferAccess(uint _offerId) private view {
		require(_offerId < offers.length, "Incorrect offerId");
		require(offers[_offerId].ownerAddress == msg.sender, "Forbidden");
	}
	
	function _getBlockedAmount(uint _offerId) private view returns(uint blockedAmount) {
		blockedAmount = 0;
		for (uint i = 0; i < orders.length; i++) {
			if (orders[i].offerId == _offerId && orders[i].complete == false && orders[i].reservedUntil >= block.timestamp) {
				blockedAmount += orders[i].amount;
			}
		}
	}
	
	function _verifySign(bytes32 _data, bytes memory _sign) private view returns (bool) {
        bytes32 hash = _toEthBytes32SignedMessageHash(_data);
        address[] memory signList = _recoverAddresses(hash, _sign);
        return signList[0] == signer;
    }
	
	function _toEthBytes32SignedMessageHash (bytes32 _data) pure private returns (bytes32 signHash) {
        signHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _data));
    }
	
	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');
    }
}