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
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
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
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
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
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
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 } }))
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}
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
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
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)
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])
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
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()))
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
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
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}'")
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())
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}}))
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
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
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])
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
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())
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}")
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])
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)
def format_parameter(parameter): if isinstance(parameter, bytes): return bytes_to_hexstring(parameter) else: return parameter
def _to_hex(self, num: int) -> str: return bytes_to_hexstring(int_to_bytes32(num))
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'