def test_decode_addr_success(): # test enocded_addr_with_none_subaddr addr, subaddr = identifier.decode_account(enocded_addr_with_none_subaddr, "lbr") assert addr.to_hex() == test_onchain_address assert subaddr is None # test enocded_addr_with_subaddr addr, subaddr = identifier.decode_account(enocded_addr_with_subaddr, "lbr") assert addr.to_hex() == test_onchain_address assert subaddr.hex() == test_sub_address
def test_decode_addr_success(hrp_addresses): hrp, enocded_addr_with_none_subaddr, enocded_addr_with_subaddr = hrp_addresses # test enocded_addr_with_none_subaddr addr, subaddr = identifier.decode_account(enocded_addr_with_none_subaddr, hrp) assert addr.to_hex() == test_onchain_address assert subaddr is None # test enocded_addr_with_subaddr addr, subaddr = identifier.decode_account(enocded_addr_with_subaddr, hrp) assert addr.to_hex() == test_onchain_address assert subaddr.hex() == test_sub_address
async def test_send_payment_with_invalid_account_identifier_hrp_as_payee( stub_client: RestClient, target_client: RestClient, currency: str, hrp: str) -> None: """ Test Plan: 1. Generate valid receive payment account identifier. 2. Extract account onchain address and subaddress from receiving payment account identifier. 3. Use a different hrp and extracted account address and subaddress to create a new account identifier. 4. Call send payment `POST /accounts/{account_id}/payments` with created account identifier. 5. Expect server response 400 client error, and sender account balance is not changed. """ amount = 1_000_000 sender_account = await target_client.create_account( balances={currency: amount}) receiver_account = await stub_client.create_account() try: receiver_account_identifier = await receiver_account.generate_account_identifier( ) account_address, subaddress = identifier.decode_account( receiver_account_identifier, hrp) new_hrp = identifier.TDM if hrp != identifier.TDM else identifier.PDM new_account_identifier = identifier.encode_account( account_address, subaddress, new_hrp) with pytest.raises(aiohttp.ClientResponseError, match="400"): await sender_account.send_payment(currency=currency, amount=amount, payee=new_account_identifier) assert await sender_account.balance(currency) == amount finally: await receiver_account.log_events() await sender_account.log_events()
async def send_request_json( diem_client: AsyncClient, sender_account: LocalAccount, sender_address: Optional[str], receiver_address: str, request_json: str, hrp: str, x_request_id: Optional[str] = str(uuid.uuid4()), request_body: Optional[bytes] = None, ) -> Tuple[int, offchain.CommandResponseObject]: headers = {} if x_request_id: headers[offchain.http_header.X_REQUEST_ID] = x_request_id if sender_address: headers[offchain.http_header.X_REQUEST_SENDER_ADDRESS] = sender_address account_address, _ = identifier.decode_account(receiver_address, hrp) base_url, public_key = await diem_client.get_base_url_and_compliance_key( account_address) if request_body is None: request_body = jws.encode(request_json, sender_account.compliance_key.sign) url = f"{base_url.rstrip('/')}/v2/command" async with diem_client._session.post(url, data=request_body, headers=headers) as resp: cmd_resp_obj = offchain.jws.deserialize(await resp.read(), offchain.CommandResponseObject, public_key.verify) return (resp.status, cmd_resp_obj)
async def target_account_vasp_domains(target_client: RestClient, diem_client: AsyncClient, hrp: str) -> List[str]: account_identifier = await target_client.random_account_identifier() account_address, _ = identifier.decode_account(account_identifier, hrp) account = await diem_client.get_parent_vasp_account(account_address) return list(account.role.vasp_domains)
def post(self): try: tx_params = request.json user = self.user account_id = user.account_id currency = DiemCurrency[tx_params["currency"]] amount = int(tx_params["amount"]) recv_address: str = tx_params["receiver_address"] dest_address, dest_subaddress = identifier.decode_account( recv_address, context.get().config.diem_address_hrp()) tx = transaction_service.send_transaction( sender_id=account_id, amount=amount, currency=currency, destination_address=utils.account_address_bytes( dest_address).hex(), destination_subaddress=dest_subaddress.hex() if dest_subaddress else None, ) transaction = AccountRoutes.get_transaction_response_object( user.account_id, tx) return transaction, HTTPStatus.OK except transaction_service.RiskCheckError as risk_check_failed_error: return self.respond_with_error(HTTPStatus.FAILED_DEPENDENCY, str(risk_check_failed_error)) except transaction_service.SelfAsDestinationError as send_to_self_error: return self.respond_with_error(HTTPStatus.FORBIDDEN, str(send_to_self_error))
def test_send_payment_with_invalid_account_identifier_onchain_account_address_as_payee( stub_client: RestClient, target_client: RestClient, currency: str, hrp: str) -> None: """ Test Plan: 1. Generate valid receive payment account identifier. 2. Extract account onchain address and subaddress from receiving payment account identifier. 3. Use an invalid onchain account address and extracted subaddress to create a new account identifier. 4. Call send payment `POST /accounts/{account_id}/payments` with created account identifier. 5. Expect server response 400 client error, and sender account balance is not changed. """ amount = 1_000_000 sender_account = target_client.create_account(balances={currency: amount}) receiver_account = stub_client.create_account() try: receiver_account_identifier = receiver_account.generate_account_identifier( ) _, subaddress = identifier.decode_account(receiver_account_identifier, hrp) invalid_account_address = LocalAccount().account_address invalid_account_identifier = identifier.encode_account( invalid_account_address, subaddress, hrp) with pytest.raises(requests.HTTPError, match="400 Client Error"): sender_account.send_payment(currency=currency, amount=amount, payee=invalid_account_identifier) assert sender_account.balance(currency) == amount finally: receiver_account.log_events() sender_account.log_events()
def test_send_payment_with_invalid_account_identifier_hrp_as_payee( sender_account: AccountResource, receiver_account: AccountResource, currency: str, hrp: str) -> None: """ Test Plan: 1. Generate valid receive payment account identifier. 2. Extract account onchain address and subaddress from receiving payment account identifier. 3. Use a different hrp and extracted account address and subaddress to create a new account identifier. 4. Call send payment `POST /accounts/{account_id}/payments` with created account identifier. 5. Expect server response 400 client error, and sender account balance is not changed. """ initial_amount = sender_account.balance(currency) receiver_account_identifier = receiver_account.generate_account_identifier( ) account_address, subaddress = identifier.decode_account( receiver_account_identifier, hrp) new_hrp = identifier.TDM if hrp != identifier.TDM else identifier.PDM new_account_identifier = identifier.encode_account(account_address, subaddress, new_hrp) with pytest.raises(requests.HTTPError, match="400 Client Error"): sender_account.send_payment(currency=currency, amount=amount, payee=new_account_identifier) assert sender_account.balance(currency) == initial_amount
def _send_additional_kyc_data(self, command: offchain.Command) -> typing.Tuple[ActionResultType, offchain.Command]: command = typing.cast(offchain.PaymentCommand, command) account_id = command.my_actor_obj().address _, subaddress = identifier.decode_account(account_id, self.hrp) if subaddress: user = self._find_user_by_subaddress(subaddress) else: raise ValueError(f"{account_id} has no sub-address") new_cmd = command.new_command(additional_kyc_data=user.additional_kyc_data()) return (ActionResult.SENT_ADDITIONAL_KYC_DATA, new_cmd)
def __trade_and_execute_buy(self, quote, trade, diem_deposit_address): if not diem_deposit_address: raise TradeError("Can't execute trade without a deposit address") receiver_vasp, receiver_subaddress = identifier.decode_account( diem_deposit_address, identifier.HRPS[CHAIN_ID.to_int()]) tx_version, tx_sequence = self.send_transaction( currency=quote.currency_pair.value.base, amount=quote.amount, dest_vasp_address=utils.account_address_hex(receiver_vasp), dest_sub_address=receiver_subaddress.hex(), ) trade.executed(tx_version)
def _trade_and_execute_buy(self, quote, trade, diem_deposit_address): if not diem_deposit_address: raise TradeError("Can't execute trade without a deposit address") receiver_vasp, receiver_sub_address = identifier.decode_account( diem_deposit_address, identifier.HRPS[testnet.CHAIN_ID.to_int()]) client = testnet.create_client() account = client.get_account(receiver_vasp) faucet = testnet.Faucet(client) faucet.mint(account.authentication_key, quote.amount, quote.currency_pair.value.base) trade.executed(-1)
def refund(payment): if not payment_can_refund(payment): raise InvalidPaymentStatus("unclearedrefund") if not len(payment.chain_transactions) == 1: raise InvalidPaymentStatus("chain_transactions") # TODO - support refund of multiple payments # TODO - Resolve refund address, currency and amount from blockchain target_transaction = payment.chain_transactions[0] if target_transaction.is_refund: raise InvalidPaymentStatus("refund_transaction") refund_target_address, refund_target_sub_address = identifier.decode_account( target_transaction.sender_address, CHAIN_HRP) refund_target_address = utils.account_address_hex(refund_target_address) refund_target_sub_address = refund_target_sub_address.hex() refund_amount = target_transaction.amount # payment.requested_amount refund_currency = DiemCurrency(target_transaction.currency) payment.set_status(PaymentStatus.refund_requested) refund_tx_id = None try: wallet = OnchainWallet() refund_tx_id, _ = wallet.send_transaction( refund_currency, refund_amount, refund_target_address, refund_target_sub_address, # TODO - new sub_address for refund? ) payment.add_chain_transaction( amount=refund_amount, sender_address=wallet.address_str, currency=refund_currency, tx_id=refund_tx_id, is_refund=True, ) payment.set_status(PaymentStatus.refund_completed) except Exception as e: # TODO - narrow to blockchain exception logger.exception("Failed during refund") payment.set_status(PaymentStatus.refund_error) return refund_tx_id, target_transaction
def mint( self, authkey_hex: str, amount: int, identifier: str, session: Optional[requests.Session] = None, timeout: Optional[Union[float, Tuple[float, float]]] = None, ) -> int: dd_address_hex = account_address_hex(DESIGNATED_DEALER_ADDRESS) account = BlockchainMock.get_account_resource(dd_address_hex) sequence = account.sequence_number version = BlockchainMock.blockchain.version decoded_addr, decoded_subaddr = decode_account(authkey_hex) metadata = general_metadata(to_subaddress=decoded_subaddr) address_hex_bytes = account_address_bytes(decoded_addr) process_incoming_transaction( sender_address=dd_address_hex, receiver_address=authkey_hex, sequence=sequence, amount=amount, currency=DiemCurrency.Coin1, metadata=metadata, ) BlockchainMock.blockchain.version += 1 tx = MockSignedTransaction( sender=bytes.fromhex(ASSOC_AUTHKEY), amount=amount, currency=identifier, receiver=address_hex_bytes, metadata=metadata, sequence=account.sequence_number, version=version, ) account.sequence_number += 1 account.transactions[sequence] = tx BlockchainMock.blockchain.transactions[version] = tx return sequence
def _cover_buy(order: Order, quote: QuoteData) -> bool: deposit_address = get_inventory_deposit_address() trade_id = LpClient().trade_and_execute( quote_id=quote.quote_id, direction=Direction[order.direction], diem_deposit_address=deposit_address, ) trade_info = _wait_for_trade(order=order, trade_id=trade_id) if not trade_info: update_order( order_id=OrderId(UUID(order.id)), cover_status=CoverStatus.FailedCoverLPTradeError, ) return False update_order( order_id=OrderId(UUID(order.id)), cover_status=CoverStatus.PendingCoverValidation, ) vasp_address, internal_subaddress = decode_account( deposit_address, context.get().config.diem_address_hrp() ) if not _validate_blockchain_transaction( blockchain_version=trade_info.tx_version, vasp_address=utils.account_address_hex(vasp_address), internal_subaddress=internal_subaddress.hex(), amount=round(trade_info.amount), ): update_order( order_id=OrderId(UUID(order.id)), cover_status=CoverStatus.FailedCoverTransactionError, ) return False return True
def trade_and_execute( self, quote_id: QuoteId, direction: Direction, diem_deposit_address: Optional[str] = None, tx_version: Optional[int] = None, ) -> TradeId: quote = LpClientMock.QUOTES[quote_id] trade_id = TradeId(uuid4()) metadata = diem_types.Metadata__Undefined() if diem_deposit_address is not None: addr, subaddr = identifier.decode_account(diem_deposit_address, "tdm") metadata = general_metadata(to_subaddress=subaddr) if direction == Direction.Buy: process_incoming_transaction( sender_address="", receiver_address=diem_deposit_address, sequence=1, amount=quote.amount, currency=quote.rate.pair.base.value, metadata=metadata, blockchain_version=1, ) trade_data = TradeData( trade_id=trade_id, direction=direction, pair=quote.rate.pair, amount=quote.amount, status=TradeStatus.Complete, quote=quote, tx_version=1, ) LpClientMock.TRADES[trade_id] = trade_data return trade_id
def test_sender_addresses(factory): cmd = factory.new_sender_payment_command() account_address, subaddress = identifier.decode_account( cmd.payment.sender.address, factory.hrp()) assert account_address == cmd.sender_account_address(factory.hrp()) assert subaddress == cmd.sender_subaddress(factory.hrp())
def _account_address_and_subaddress( account_id: str) -> Tuple[str, Optional[str]]: account_address, sub = identifier.decode_account( account_id, context.get().config.diem_address_hrp()) return (account_address.to_hex(), sub.hex() if sub else None)
def test_decode_addr_fail(): # fail to decode invalid hrp invalid_hrp_encoded_address = "btc1p7ujcndcl7nudzwt8fglhx6wxn08kgs5tm6mz4usw5p72t" with pytest.raises(ValueError): identifier.decode_account(invalid_hrp_encoded_address, "lbr") # fail to decode invalid "expected" hrp with pytest.raises(ValueError): identifier.decode_account( "lbr1p7ujcndcl7nudzwt8fglhx6wxn08kgs5tm6mz4usw5p72t", "tlb") # fail to decode invalid version invalid_version_encoded_address = "lbr1q7ujcndcl7nudzwt8fglhx6wxn08kgs5tm6mz4usw5p72t" # v = 0 with pytest.raises(ValueError): identifier.decode_account(invalid_version_encoded_address, "lbr") # fail to decode due to checksum error invalid_checksum_encoded_address = ( "lbr1p7ujcndcl7nudzwt8fglhx6wxn08kgs5tm6mz4usw5p72p" # change last char from t to p ) with pytest.raises(ValueError): identifier.decode_account(invalid_checksum_encoded_address, "lbr") # fail to decode mixed case per BIP 173 mixedcase_encoded_address = "LbR1p7ujcndcl7nudzwt8fglhx6wxn08kgs5tm6mz4usw5P72T" # some uppercase with pytest.raises(ValueError): identifier.decode_account(mixedcase_encoded_address, "lbr") # fail to decode shorter payload short_encoded_address = "lbr1p7ujcndcl7nudzwt8fglhx6wxnvqqqqqqqqqqqqelu3xv" # sample 23 bytes encoded with pytest.raises(ValueError): identifier.decode_account(short_encoded_address, "lbr") # fail to decode larger payload large_encoded_address = "lbr1p7ujcndcl7nudzwt8fglhx6wxn08kgs5tm6mz4us4g3ysw8a" # sample 25 bytes encoded with pytest.raises(ValueError): identifier.decode_account(large_encoded_address, "lbr") # fail to decode invalid separator invalid_separator_encoded_address = "lbr2p7ujcndcl7nudzwt8fglhx6wxn08kgs5tm6mz4usw5p72t" # separator = 2 with pytest.raises(ValueError): identifier.decode_account(invalid_separator_encoded_address, "lbr") # fail to decode invalid character invalid_char_encoded_address = "lbr1pbujcndcl7nudzwt8fglhx6wxn08kgs5tm6mz4usw5p72t" # add b char with pytest.raises(ValueError): identifier.decode_account(invalid_char_encoded_address, "lbr")
def general_metadata(self, from_subaddress: bytes, payee: str) -> Tuple[bytes, bytes]: to_account, to_subaddress = identifier.decode_account(payee, self.hrp) return (txnmetadata.general_metadata(from_subaddress, to_subaddress), b"")
def decode_account_identifier( self, encoded_id: str ) -> Tuple[diem_types.AccountAddress, Optional[bytes]]: return identifier.decode_account(encoded_id, self.hrp)
def test_encode_decode_with_random_hrp(): # test with none sub_address id = identifier.encode_account(test_onchain_address, None, "abc") addr, sub = identifier.decode_account(id, "abc") assert addr.to_hex() == test_onchain_address assert sub is None
def test_decode_addr_fail(hrp_addresses): hrp, enocded_addr_with_none_subaddr, enocded_addr_with_subaddr = hrp_addresses # fail to decode invalid hrp invalid_hrp_encoded_address = "btc1p7ujcndcl7nudzwt8fglhx6wxn08kgs5tm6mz4usw5p72t" with pytest.raises(ValueError): identifier.decode_account(invalid_hrp_encoded_address, hrp) # fail to decode invalid "expected" hrp with pytest.raises(ValueError): identifier.decode_account(enocded_addr_with_none_subaddr, "xdm") # fail to decode invalid version invalid_version_encoded_address = enocded_addr_with_none_subaddr.replace( "1p7", "1q7") # p (1) -> q (2) with pytest.raises(ValueError): identifier.decode_account(invalid_version_encoded_address, hrp) # fail to decode due to checksum error invalid_checksum_encoded_address = enocded_addr_with_none_subaddr.replace( "d8p9cq", "d8p9c7").replace("v88j4s", "v88j4q") with pytest.raises(ValueError): identifier.decode_account(invalid_checksum_encoded_address, hrp) # fail to decode mixed case per BIP 173 mixedcase_encoded_address = enocded_addr_with_none_subaddr.replace( "qqqqqqqqqqqqq", "qqQqqqqqqqqqq") with pytest.raises(ValueError): identifier.decode_account(mixedcase_encoded_address, hrp) # fail to decode shorter payload short_encoded_address = enocded_addr_with_none_subaddr.replace( "qqqqqqqqqqqqq", "qqqqqqqqqqq") with pytest.raises(ValueError): identifier.decode_account(short_encoded_address, hrp) # fail to decode larger payload large_encoded_address = enocded_addr_with_none_subaddr.replace( "qqqqqqqqqqqqq", "qqqqqqqqqqqqqq") with pytest.raises(ValueError): identifier.decode_account(large_encoded_address, hrp) # fail to decode invalid separator invalid_separator_encoded_address = enocded_addr_with_none_subaddr.replace( "1p7", "0p7") with pytest.raises(ValueError): identifier.decode_account(invalid_separator_encoded_address, hrp) # fail to decode invalid character invalid_char_encoded_address = enocded_addr_with_none_subaddr.replace( "1p7", "1pb") with pytest.raises(ValueError): identifier.decode_account(invalid_char_encoded_address, hrp)
def test_new_payment_request_and_object(factory): sender = LocalAccount.generate() receiver = LocalAccount.generate() payment = factory.new_payment_object(sender, receiver) request = offchain.new_payment_request(payment) reference_id = payment.reference_id assert reference_id assert_cid(request.cid) assert uuid.UUID(reference_id) assert "-" in reference_id payment = offchain.from_dict(request.command, offchain.PaymentCommandObject).payment address, subaddress = identifier.decode_account(payment.sender.address, identifier.TDM) assert subaddress is not None assert address == sender.account_address address, subaddress = identifier.decode_account(payment.receiver.address, identifier.TDM) assert subaddress is not None assert address == receiver.account_address expected = f"""{{ "cid": "{request.cid}", "command_type": "PaymentCommand", "command": {{ "_ObjectType": "PaymentCommand", "payment": {{ "reference_id": "{reference_id}", "sender": {{ "address": "{payment.sender.address}", "status": {{ "status": "needs_kyc_data" }}, "kyc_data": {{ "type": "individual", "payload_version": 1, "given_name": "Jack", "surname": "G", "address": {{ "city": "San Francisco" }} }} }}, "receiver": {{ "address": "{payment.receiver.address}", "status": {{ "status": "none" }} }}, "action": {{ "amount": 1000000000000, "currency": "XUS", "action": "charge", "timestamp": {payment.action.timestamp} }} }} }}, "_ObjectType": "CommandRequestObject" }}""" assert json.loads(offchain.to_json(request)) == json.loads(expected) assert request == offchain.from_json(expected, offchain.CommandRequestObject) assert request == offchain.from_json(expected)
def test_my_subaddress(factory): cmd = factory.new_sender_payment_command() _, subaddress = identifier.decode_account(cmd.payment.sender.address, factory.hrp()) assert cmd.my_subaddress(factory.hrp()) == subaddress
async def test_generate_account_identifier(target_client: RestClient, hrp: str) -> None: account = await target_client.create_account() account_identifier = await account.generate_account_identifier() account_address, _ = identifier.decode_account(account_identifier, hrp) assert account_address