Ejemplo n.º 1
0
    def cancel_order(self, order: Order) -> bool:
        assert (isinstance(order, Order))

        order_hash = self.zrx_exchange.get_order_hash(order.zrx_order)
        self.logger.info(f"Cancelling order #{order_hash}...")

        cancel_msg = self.zrx_exchange.web3.sha3(text=f'cancel:{order_hash}')
        v, r, s = to_vrs(eth_sign(cancel_msg, self.zrx_exchange.web3))
        signature = bytes_to_hexstring(bytes([v])) + \
                    bytes_to_hexstring(r)[2:] + \
                    bytes_to_hexstring(s)[2:] + \
                    "03"  # EthSign

        cancel = {
            "cancellations": [{
                "orderHash": order_hash,
                "signature": signature
            }]
        }

        response = requests.post(f"{self.zrx_api.api_server}/v2/orders/cancel",
                                 json=cancel,
                                 timeout=self.zrx_api.timeout)
        if response.ok:
            data = response.json()[0]  # We suppose only one cancel
            if data.get('success'):
                self.logger.info(f"Cancelled order #{order_hash}")
                return True
            else:
                self.logger.error(
                    f"Failed to cancel: {http_response_summary(response)}")
                return False
        else:
            self.logger.info(f"Failed to cancel order #{order_hash}")
            return False
Ejemplo n.º 2
0
    def sign_order(self, order: Order) -> Order:
        """Signs an order so it can be submitted to the relayer.

        Order will be signed by the `web3.eth.defaultAccount` account.

        Args:
            order: Order you want to sign.

        Returns:
            Signed order. Copy of the order passed as a parameter with the `ec_signature_r`, `ec_signature_s`
            and `ec_signature_v` fields filled with signature values.
        """
        assert(isinstance(order, Order))

        # TODO duplicate code below
        signed_hash = eth_sign(self.web3, hexstring_to_bytes(self.get_order_hash(order)))[2:]
        r = bytes.fromhex(signed_hash[0:64])
        s = bytes.fromhex(signed_hash[64:128])
        v = ord(bytes.fromhex(signed_hash[128:130]))

        signed_order = copy.copy(order)
        signed_order.ec_signature_r = bytes_to_hexstring(r)
        signed_order.ec_signature_s = bytes_to_hexstring(s)
        signed_order.ec_signature_v = v
        return signed_order
Ejemplo n.º 3
0
    def cancel_order(self, order: Order) -> bool:
        assert(isinstance(order, Order))

        nonce = self.next_nonce()
        signed_data = keccak_256(encode_bytes(hexstring_to_bytes(order.order_hash)) +
                                 encode_uint256(nonce)).digest()

        signature = eth_sign(signed_data, self.idex.web3)
        v, r, s = to_vrs(signature)

        data = {
            'orderHash': order.order_hash,
            'nonce': str(nonce),
            'address': self._our_address(),
            'v': v,
            'r': bytes_to_hexstring(r),
            's': bytes_to_hexstring(s)
        }

        self.logger.info(f"Cancelling order #{order.order_id}...")

        result = self._http_post("/cancel", data)
        success = result['success'] == 1

        if success:
            self.logger.info(f"Cancelled order #{order.order_id}")
        else:
            self.logger.info(f"Failed to cancel order #{order.order_id}")

        return success
Ejemplo n.º 4
0
    def place_order(self,
                    pay_token: Address,
                    pay_amount: Wad,
                    buy_token: Address,
                    buy_amount: Wad) -> Order:
        """Places a new order.

        Args:
            pay_token: Address of the ERC20 token you want to put on sale.
            pay_amount: Amount of the `pay_token` token you want to put on sale.
            buy_token: Address of the ERC20 token you want to be paid with.
            buy_amount: Amount of the `buy_token` you want to receive.

        Returns:
            New order as an instance of the :py:class:`pyexchange.idex.Order` class.
        """
        assert(isinstance(pay_token, Address))
        assert(isinstance(pay_amount, Wad))
        assert(isinstance(buy_token, Address))
        assert(isinstance(buy_amount, Wad))

        expires = 0
        nonce = self.next_nonce()
        order_hash = keccak_256(encode_address(self.idex.address) +
                                encode_address(buy_token) +
                                encode_uint256(buy_amount.value) +
                                encode_address(pay_token) +
                                encode_uint256(pay_amount.value) +
                                encode_uint256(expires) +
                                encode_uint256(nonce) +
                                encode_address(Address(self._our_address()))).digest()

        signature = eth_sign(order_hash, self.idex.web3)
        v, r, s = to_vrs(signature)

        data = {
            'tokenBuy': buy_token.address,
            'amountBuy': str(buy_amount.value),
            'tokenSell': pay_token.address,
            'amountSell': str(pay_amount.value),
            'address': self._our_address(),
            'nonce': str(nonce),
            'expires': expires,
            'v': v,
            'r': bytes_to_hexstring(r),
            's': bytes_to_hexstring(s)
        }

        self.logger.info(f"Placing order selling {pay_amount} {pay_token} for {buy_amount} {buy_token}...")

        result = self._http_post("/order", data)
        order = self._json_to_order(result)

        self.logger.info(f"Placed order selling {pay_amount} {pay_token} for {buy_amount} {buy_token} as #{order.order_id}")

        return order
Ejemplo n.º 5
0
 def __init__(self, log):
     self.maker = Address(log['args']['maker'])
     self.fee_recipient = Address(log['args']['feeRecipient'])
     self.pay_token = Address(log['args']['makerToken'])
     self.buy_token = Address(log['args']['takerToken'])
     self.cancelled_pay_amount = Wad(int(log['args']['cancelledMakerTokenAmount']))
     self.cancelled_buy_amount = Wad(int(log['args']['cancelledTakerTokenAmount']))
     self.tokens = bytes_to_hexstring(log['args']['tokens'])
     self.order_hash = bytes_to_hexstring(log['args']['orderHash'])
     self.raw = log
Ejemplo n.º 6
0
 def __init__(self, log):
     self.maker = Address(log['args']['makerAddress'])
     self.fee_recipient = Address(log['args']['feeRecipientAddress'])
     self.sender = Address(log['args']['senderAddress'])
     self.pay_asset = Asset.deserialize(
         bytes_to_hexstring(log['args']['makerAssetData']))
     self.buy_asset = Asset.deserialize(
         bytes_to_hexstring(log['args']['takerAssetData']))
     self.order_hash = bytes_to_hexstring(log['args']['orderHash'])
     self.raw = log
Ejemplo n.º 7
0
    def _func(self,
              from_account: str,
              gas: int,
              gas_price_params: dict,
              nonce: Optional[int],
              send_raw: bool = False):
        assert isinstance(from_account, str)
        assert isinstance(gas_price_params, dict)
        assert isinstance(nonce, int) or nonce is None

        nonce_dict = {'nonce': nonce} if nonce is not None else {}
        transaction_params = {
            **{
                'from': from_account,
                'gas': gas
            },
            **gas_price_params,
            **nonce_dict,
            **self._as_dict(self.extra)
        }
        if self.contract is not None:
            if self.function_name is None:

                return bytes_to_hexstring(
                    self.web3.eth.send_transaction({
                        **transaction_params,
                        **{
                            'to': self.address.address,
                            'data': self.parameters[0]
                        }
                    }))
            else:
                # TODO: implement raw_send for other if cases - required by optimism
                if send_raw:
                    prepared_transaction = self._contract_function(
                    ).buildTransaction(transaction_params)
                    local_account = _registered_accounts.get(
                        (self.web3, Address(from_account)))
                    signed_txn = self.web3.eth.account.sign_transaction(
                        prepared_transaction, local_account.privateKey)
                    return bytes_to_hexstring(
                        self.web3.eth.sendRawTransaction(
                            signed_txn.rawTransaction))
                else:
                    return bytes_to_hexstring(
                        self._contract_function().transact(transaction_params))
        else:
            return bytes_to_hexstring(
                self.web3.eth.send_transaction({
                    **transaction_params,
                    **{
                        'to': self.address.address
                    }
                }))
Ejemplo n.º 8
0
 def to_json(self) -> dict:
     return {'contractAddr': self._ether_delta.address.address,
             'tokenGet': self.buy_token.address,
             'amountGet': self.buy_amount.value,
             'tokenGive': self.pay_token.address,
             'amountGive': self.pay_amount.value,
             'expires': self.expires,
             'nonce': self.nonce,
             'v': self.v,
             'r': bytes_to_hexstring(self.r),
             's': bytes_to_hexstring(self.s),
             'user': self.maker.address}
Ejemplo n.º 9
0
 def __init__(self, log):
     self.maker = Address(log['args']['maker'])
     self.taker = Address(log['args']['taker'])
     self.fee_recipient = Address(log['args']['feeRecipient'])
     self.pay_token = Address(log['args']['makerToken'])
     self.buy_token = Address(log['args']['takerToken'])
     self.filled_pay_amount = Wad(int(log['args']['filledMakerTokenAmount']))
     self.filled_buy_amount = Wad(int(log['args']['filledTakerTokenAmount']))
     self.paid_maker_fee = Wad(int(log['args']['paidMakerFee']))
     self.paid_taker_fee = Wad(int(log['args']['paidTakerFee']))
     self.tokens = bytes_to_hexstring(array.array('B', [ord(x) for x in log['args']['tokens']]).tobytes())
     self.order_hash = bytes_to_hexstring(array.array('B', [ord(x) for x in log['args']['orderHash']]).tobytes())
     self.raw = log
Ejemplo n.º 10
0
def eth_sign(message: bytes, web3: Web3):
    assert (isinstance(message, bytes))
    assert (isinstance(web3, Web3))

    local_account = _registered_accounts.get(
        (web3, Address(web3.eth.defaultAccount)))

    if local_account:
        message_hash = defunct_hash_message(primitive=message)
        signature = web3.eth.account.signHash(
            message_hash,
            private_key=local_account.privateKey).signature.hex()

        return signature

    else:
        signature = bytes_to_hexstring(
            web3.manager.request_blocking(
                "eth_sign",
                [web3.eth.defaultAccount,
                 encode_hex(message)],
            ))

        # for `EthereumJS TestRPC/v2.2.1/ethereum-js`
        if signature.endswith("00"):
            signature = signature[:-2] + "1b"

        if signature.endswith("01"):
            signature = signature[:-2] + "1c"

        return signature
Ejemplo n.º 11
0
    async def cancel_async(self, gas_price: GasPrice):
        assert isinstance(gas_price, GasPrice)
        initial_time = time.time()
        self.gas_price_last = self.current_gas
        self.tx_hashes.clear()

        if gas_price.get_gas_price(0) <= self.current_gas * 1.125:
            self.logger.warning(f"Recovery gas price is less than current gas price {self.current_gas}; "
                                "cancellation will be deferred until the strategy produces an acceptable price.")

        while True:
            seconds_elapsed = int(time.time() - initial_time)
            gas_price_value = gas_price.get_gas_price(seconds_elapsed)
            if gas_price_value > self.gas_price_last * 1.125:
                self.gas_price_last = gas_price_value
                # Transaction lock isn't needed here, as we are replacing an existing nonce
                tx_hash = bytes_to_hexstring(self.web3.eth.sendTransaction({'from': self.address.address,
                                                                            'to': self.address.address,
                                                                            'gasPrice': gas_price_value,
                                                                            'nonce': self.nonce,
                                                                            'value': 0}))
                self.tx_hashes.append(tx_hash)
                self.logger.info(f"Attempting to cancel recovered tx with nonce={self.nonce}, "
                                 f"gas_price={gas_price_value} (tx_hash={tx_hash})")

            for tx_hash in self.tx_hashes:
                receipt = self._get_receipt(tx_hash)
                if receipt:
                    self.logger.info(f"{self.name()} was cancelled (tx_hash={tx_hash})")
                    return

            await asyncio.sleep(0.75)
Ejemplo n.º 12
0
    def fill_order(self, order: Order, fill_buy_amount: Wad) -> Transact:
        """Fills an order.

        Args:
            order: The order to be filled.
            fill_buy_amount: The amount (in terms of `buy_token` of the original order) to be filled.

        Returns:
            A :py:class:`pymaker.Transact` instance, which can be used to trigger the transaction.
        """
        assert (isinstance(order, Order))
        assert (isinstance(fill_buy_amount, Wad))

        method_signature = self.web3.keccak(
            text=f"fillOrder({self.ORDER_INFO_TYPE},uint256,bytes)")[0:4]
        method_parameters = encode_single(
            f"({self.ORDER_INFO_TYPE},uint256,bytes)", [
                self._order_tuple(order), fill_buy_amount.value,
                hexstring_to_bytes(order.signature)
            ])

        request = bytes_to_hexstring(method_signature + method_parameters)

        return Transact(self, self.web3, self.abi, self.address,
                        self._contract, None, [request])
Ejemplo n.º 13
0
def eth_sign(message: bytes, web3: Web3):
    assert(isinstance(message, bytes))
    assert(isinstance(web3, Web3))

    local_account = _registered_accounts.get((web3, Address(web3.eth.defaultAccount)))

    if local_account:
        start_time = time.time()
        start_clock = time.clock()
        try:
            message_hash = defunct_hash_message(primitive=message)
            signature = web3.eth.account.signHash(message_hash, private_key=local_account.privateKey).signature.hex()
        finally:
            end_time = time.time()
            end_clock = time.clock()

        logging.debug(f"Local signing took {end_time - start_time:.3f}s time, {end_clock - start_clock:.3f}s clock")

        return signature

    else:
        signature = bytes_to_hexstring(web3.manager.request_blocking(
            "eth_sign", [web3.eth.defaultAccount, encode_hex(message)],
        ))

        # for `EthereumJS TestRPC/v2.2.1/ethereum-js`
        if signature.endswith("00"):
            signature = signature[:-2] + "1b"

        if signature.endswith("01"):
            signature = signature[:-2] + "1c"

        return signature
Ejemplo n.º 14
0
    def zrx_asset(self) -> str:
        """Get the asset data of the ZRX token contract associated with this `ExchangeV2` contract.

        Returns:
            The asset data of the `ZRX` token.
        """
        return str(bytes_to_hexstring(self._contract.call().ZRX_ASSET_DATA()))
Ejemplo n.º 15
0
    def sign_order(self, order: Order) -> Order:
        assert (isinstance(order, Order))

        # TODO duplicate code below
        signed_hash = eth_sign(self.web3,
                               hexstring_to_bytes(
                                   self.get_order_hash(order)))[2:]
        r = bytes.fromhex(signed_hash[0:64])
        s = bytes.fromhex(signed_hash[64:128])
        v = ord(bytes.fromhex(signed_hash[128:130]))

        signed_order = copy.copy(order)
        signed_order.ec_signature_r = bytes_to_hexstring(r)
        signed_order.ec_signature_s = bytes_to_hexstring(s)
        signed_order.ec_signature_v = v
        return signed_order
Ejemplo n.º 16
0
 def __init__(self, log):
     self.sender = Address(log['args']['senderAddress'])
     self.maker = Address(log['args']['makerAddress'])
     self.taker = Address(log['args']['takerAddress'])
     self.fee_recipient = Address(log['args']['feeRecipientAddress'])
     self.pay_asset = Asset.deserialize(
         bytes_to_hexstring(log['args']['makerAssetData']))
     self.buy_asset = Asset.deserialize(
         bytes_to_hexstring(log['args']['takerAssetData']))
     self.filled_pay_amount = Wad(int(
         log['args']['makerAssetFilledAmount']))
     self.filled_buy_amount = Wad(int(
         log['args']['takerAssetFilledAmount']))
     self.paid_maker_fee = Wad(int(log['args']['makerFeePaid']))
     self.paid_taker_fee = Wad(int(log['args']['takerFeePaid']))
     self.order_hash = bytes_to_hexstring(log['args']['orderHash'])
     self.raw = log
Ejemplo n.º 17
0
    def __init__(self, value):
        if isinstance(value, str):
            assert(value.startswith('0x'))
            self.value = value

        elif isinstance(value, bytes):
            self.value = bytes_to_hexstring(value)

        else:
            raise Exception(f"Unable to create calldata from '{value}'")
Ejemplo n.º 18
0
    def get_order_hash(self, order: Order) -> str:
        assert (isinstance(order, Order))

        # the hash depends on the exchange contract address as well
        assert (order.exchange_contract_address == self.address)

        result = self._contract.call().getOrderHash(
            self._order_addresses(order), self._order_values(order))
        return bytes_to_hexstring(
            array.array('B', [ord(x) for x in result]).tobytes())
Ejemplo n.º 19
0
    def _func(self, from_account: str, gas: int, gas_price: Optional[int], nonce: Optional[int]):
        gas_price_dict = {'gasPrice': gas_price} if gas_price is not None else {}
        nonce_dict = {'nonce': nonce} if nonce is not None else {}

        transaction_params = {**{'from': from_account, 'gas': gas},
                              **gas_price_dict,
                              **nonce_dict,
                              **self._as_dict(self.extra)}

        if self.contract is not None:
            if self.function_name is None:

                return bytes_to_hexstring(self.web3.eth.sendTransaction({**transaction_params,
                                                                         **{'to': self.address.address,
                                                                            'data': self.parameters[0]}}))
            else:
                return bytes_to_hexstring(self._contract_function().transact(transaction_params))
        else:
            return bytes_to_hexstring(self.web3.eth.sendTransaction({**transaction_params,
                                                                     **{'to': self.address.address}}))
Ejemplo n.º 20
0
    def sign_order(self, order: Order) -> Order:
        """Signs an order so it can be submitted to the relayer.

        Order will be signed by the `web3.eth.defaultAccount` account.

        Args:
            order: Order you want to sign.

        Returns:
            Signed order. Copy of the order passed as a parameter with the `ec_signature_r`, `ec_signature_s`
            and `ec_signature_v` fields filled with signature values.
        """
        assert(isinstance(order, Order))

        signature = eth_sign(hexstring_to_bytes(self.get_order_hash(order)), self.web3)
        v, r, s = to_vrs(signature)

        signed_order = copy.copy(order)
        signed_order.ec_signature_r = bytes_to_hexstring(r)
        signed_order.ec_signature_s = bytes_to_hexstring(s)
        signed_order.ec_signature_v = v
        return signed_order
Ejemplo n.º 21
0
    def sign_order(self, order: Order) -> Order:
        """Signs an order so it can be submitted to the relayer.

        Order will be signed by the `web3.eth.defaultAccount` account.

        Args:
            order: Order you want to sign.

        Returns:
            Signed order. Copy of the order passed as a parameter with the `signature` field filled with signature.
        """
        assert (isinstance(order, Order))

        signature = eth_sign(hexstring_to_bytes(self.get_order_hash(order)),
                             self.web3)
        v, r, s = to_vrs(signature)

        signed_order = copy.copy(order)
        signed_order.signature = bytes_to_hexstring(bytes([v])) + \
                                 bytes_to_hexstring(r)[2:] + \
                                 bytes_to_hexstring(s)[2:] + \
                                 "03"  # EthSign
        return signed_order
Ejemplo n.º 22
0
    def get_order_hash(self, order: Order) -> str:
        """Calculates hash of an order.

        Args:
            order: Order you want to calculate the hash of.

        Returns:
            Order hash as a hex string starting with `0x`.
        """
        assert (isinstance(order, Order))

        # the hash depends on the exchange contract address as well
        assert (order.exchange_contract_address == self.address)

        return bytes_to_hexstring(self._get_order_info(order)[0][1])
Ejemplo n.º 23
0
    def _get_order_info(self, order):
        assert (isinstance(order, Order))

        method_signature = self.web3.keccak(
            text=f"getOrderInfo({self.ORDER_INFO_TYPE})")[0:4]
        method_parameters = encode_single(f"({self.ORDER_INFO_TYPE})",
                                          [self._order_tuple(order)])

        request = bytes_to_hexstring(method_signature + method_parameters)
        response = self.web3.eth.call({
            'to': self.address.address,
            'data': request
        })
        response_decoded = decode_single("((uint8,bytes32,uint256))", response)

        return response_decoded
Ejemplo n.º 24
0
    def get_order_hash(self, order: Order) -> str:
        """Calculates hash of an order.

        Args:
            order: Order you want to calculate the hash of.

        Returns:
            Order hash as a hex string starting with `0x`.
        """
        assert(isinstance(order, Order))

        # the hash depends on the exchange contract address as well
        assert(order.exchange_contract_address == self.address)

        result = self._contract.call().getOrderHash(self._order_addresses(order), self._order_values(order))
        return bytes_to_hexstring(array.array('B', [ord(x) for x in result]).tobytes())
Ejemplo n.º 25
0
 async def cancel_async(self):
     supports_eip1559 = _get_endpoint_behavior(web3).supports_eip1559
     # Transaction lock isn't needed here, as we are replacing an existing nonce
     if self.gas_feecap and self.gas_tip:
         assert supports_eip1559
         base_fee = int(self.web3.eth.get_block('pending')['baseFeePerGas'])
         bumped_tip = math.ceil(min(1 * GWEI, self.gas_tip) * 1.125)
         bumped_feecap = max(
             base_fee + bumped_tip,
             math.ceil((self.gas_feecap + bumped_tip) * 1.125))
         gas_fees = {
             'maxFeePerGas': bumped_feecap,
             'maxPriorityFeePerGas': bumped_tip
         }
         # CAUTION: On OpenEthereum//v3.3.0-rc.4, this produces an underpriced gas error; even when multiplying by 2
     else:
         assert False
         if supports_eip1559:
             base_fee = math.ceil(
                 self.web3.eth.get_block('pending')['baseFeePerGas'])
             bumped_tip = math.ceil(
                 min(1 * GWEI, self.gas_price - base_fee) * 1.125)
             gas_fees = {
                 'maxFeePerGas': math.ceil(
                     (self.gas_price + bumped_tip) * 1.25),
                 'maxPriorityFeePerGas': bumped_tip
             }
         else:
             bumped_gas = math.ceil(self.gas_price * 1.125)
             gas_fees = {'gasPrice': bumped_gas}
     self.logger.info(
         f"Attempting to cancel TX with nonce={self.nonce} using gas_fees={gas_fees}"
     )
     tx_hash = bytes_to_hexstring(
         self.web3.eth.sendTransaction({
             'from': self.address.address,
             'to': self.address.address,
             **gas_fees, 'nonce': self.nonce,
             'value': 0
         }))
     self.logger.info(
         f"Cancelled TX with nonce={self.nonce}; TX hash: {tx_hash}")
Ejemplo n.º 26
0
    def cancel_order(self, order: Order) -> Transact:
        """Cancels an order.

        Args:
            order: Order you want to cancel.

        Returns:
            A :py:class:`pymaker.Transact` instance, which can be used to trigger the transaction.
        """
        assert (isinstance(order, Order))

        method_signature = self.web3.keccak(
            text=f"cancelOrder({self.ORDER_INFO_TYPE})")[0:4]
        method_parameters = encode_single(f"({self.ORDER_INFO_TYPE})",
                                          [self._order_tuple(order)])

        request = bytes_to_hexstring(method_signature + method_parameters)

        return Transact(self, self.web3, self.abi, self.address,
                        self._contract, None, [request])
Ejemplo n.º 27
0
    def encode_route_to_path(route: Route, exact_output: bool) -> str:
        """ Convert a route to a hex encoded path"""
        assert (isinstance(route, Route))
        assert (isinstance(exact_output, bool))

        route_input_token = route.input

        path_to_encode = {}
        path_to_encode["input_token"] = route_input_token

        for i, pool in enumerate(route.pools):
            output_token = pool.token_1 if pool.token_0 == path_to_encode[
                "input_token"] else pool.token_0
            if i == 0:
                path_to_encode["input_token"] = output_token
                path_to_encode["types"] = ['address', 'uint24', 'address']
                path_to_encode["path"] = [
                    route_input_token.address.address, pool.fee,
                    output_token.address.address
                ]
            else:
                path_to_encode["input_token"] = output_token
                path_to_encode["types"] = path_to_encode["types"] + [
                    'uint24', 'address'
                ]
                path_to_encode["path"] = path_to_encode["path"] + [
                    pool.fee, output_token.address.address
                ]

        # encode
        if exact_output:
            path_to_encode["types"].reverse()
            path_to_encode["path"].reverse()

        encoded_path = encode_abi_packed(path_to_encode["types"],
                                         path_to_encode["path"])

        return bytes_to_hexstring(encoded_path)
Ejemplo n.º 28
0
 def format_parameter(parameter):
     if isinstance(parameter, bytes):
         return bytes_to_hexstring(parameter)
     else:
         return parameter
Ejemplo n.º 29
0
 def _to_hex(self, num: int) -> str:
     return bytes_to_hexstring(int_to_bytes32(num))
Ejemplo n.º 30
0
def test_bytes_to_hexstring():
    assert bytes_to_hexstring(bytes([0x00])) == '0x00'
    assert bytes_to_hexstring(bytes([0x01, 0x02, 0x03])) == '0x010203'
    assert bytes_to_hexstring(bytes([0xff, 0xff])) == '0xffff'