def test_send_payment_meets_travel_rule_threshold_sender_kyc_data_is_soft_match_and_accepted_receiver_kyc_data_is_soft_match_and_rejected(
    currency: str,
    travel_rule_threshold: int,
    target_client: RestClient,
    stub_client: RestClient,
) -> None:
    """
    Test Plan:

    1. Create sender account with kyc data that will be soft matched and then accepted by the stub wallet application and enough balance in target wallet application.
    2. Create receiver account with kyc data that will be soft matched and then rejected by the target wallet application and 0 balance in stub wallet application.
    3. Send payment from sender account to receiver account, amount is equal to travel_rule threshold.
    4. Wait for stub wallet application account events include payment command states: ["S_INIT", "R_SOFT", "S_SOFT_SEND", "R_SEND", "S_SOFT", "R_SOFT_SEND", "S_ABORT"]
    5. Expect sender and receiver accounts' balances are not changed.
    """

    send_payment_meets_travel_rule_threshold(
        sender=target_client.create_account(
            balances={currency: travel_rule_threshold},
            kyc_data=stub_client.get_kyc_sample().soft_match),
        receiver=stub_client.create_account(
            kyc_data=target_client.get_kyc_sample().soft_reject),
        payment_command_states=[
            "S_INIT", "R_SOFT", "S_SOFT_SEND", "R_SEND", "S_SOFT",
            "R_SOFT_SEND", "S_ABORT"
        ],
        currency=currency,
        amount=travel_rule_threshold,
    )
def test_send_payment_meets_travel_rule_threshold_sender_kyc_data_is_soft_match_then_receiver_aborts_for_sending_additional_kyc_data(
    currency: str,
    travel_rule_threshold: int,
    target_client: RestClient,
    stub_client: RestClient,
) -> None:
    """
    Test Plan:

    1. Create sender account with minimum valid kyc data and enough balance in target wallet application.
    2. Create receiver account with kyc data that will be soft matched by the target wallet application and 0 balance in stub wallet application.
    3. Setup the stub wallet applicatoin to abort the payment command if sender requests additional KYC data (soft match).
    4. Send payment from sender account to receiver account, amount is equal to travel_rule threshold.
    5. Wait for stub wallet application account events include payment command states: ["S_INIT", "R_SEND", "S_SOFT", "R_ABORT"]
    6. Expect sender and receiver accounts' balances are not changed.
    """

    send_payment_meets_travel_rule_threshold(
        sender=target_client.create_account(
            balances={currency: travel_rule_threshold},
            kyc_data=stub_client.get_kyc_sample().minimum),
        receiver=stub_client.create_account(
            kyc_data=target_client.get_kyc_sample().soft_match,
            reject_additional_kyc_data_request=True),
        payment_command_states=["S_INIT", "R_SEND", "S_SOFT", "R_ABORT"],
        currency=currency,
        amount=travel_rule_threshold,
    )
Exemplo n.º 3
0
def test_receive_payment_meets_travel_rule_threshold_sender_kyc_data_is_rejected_by_the_receiver(
    currency: str,
    travel_rule_threshold: int,
    target_client: RestClient,
    stub_client: RestClient,
    hrp: str,
) -> None:
    """
    Test Plan:

    1. Create sender account with kyc data that will be rejected by the target wallet application in the stub wallet application.
    2. Create receiver account with minimum valid kyc data and 0 balance in the target wallet application.
    3. Send payment from sender account to receiver account, amount is equal to travel_rule threshold.
    4. Wait for stub wallet application account events include payment command states: ["S_INIT", "R_ABORT"]
    5 . Expect sender and receiver accounts' balances are not changed.
    """

    receive_payment_meets_travel_rule_threshold(
        sender=stub_client.create_account(
            balances={currency: travel_rule_threshold},
            kyc_data=target_client.get_kyc_sample().reject,
        ),
        receiver=target_client.create_account(
            kyc_data=stub_client.get_kyc_sample().minimum),
        payment_command_states=["S_INIT", "R_ABORT"],
        currency=currency,
        amount=travel_rule_threshold,
    )
Exemplo n.º 4
0
def test_receive_payment_meets_travel_rule_threshold_both_kyc_data_evaluations_are_accepted(
    currency: str,
    travel_rule_threshold: int,
    target_client: RestClient,
    stub_client: RestClient,
) -> None:
    """
    Test Plan:

    1. Create sender account with minimum valid kyc data and enough balance in the stub wallet application.
    2. Create receiver account with minimum valid kyc data with 0 balance in the target wallet application.
    3. Send payment from sender account to receiver account, amount is equal to travel_rule threshold.
    4. Wait for stub wallet application account events include payment command states: ["S_INIT", "R_SEND", "READY"]
    5 . Expect send payment success; receiver account balance increased by the amount sent; sender account balance decreased by the amount sent.
    """

    receive_payment_meets_travel_rule_threshold(
        sender=stub_client.create_account(
            balances={currency: travel_rule_threshold},
            kyc_data=target_client.get_kyc_sample().minimum,
        ),
        receiver=target_client.create_account(
            kyc_data=stub_client.get_kyc_sample().minimum),
        payment_command_states=["S_INIT", "R_SEND", "READY"],
        currency=currency,
        amount=travel_rule_threshold,
    )
Exemplo n.º 5
0
def test_receive_payment_with_travel_rule_metadata_and_valid_reference_id(
    stub_client: RestClient,
    target_client: RestClient,
    currency: str,
    travel_rule_threshold: int,
) -> None:
    """
    Test Plan:

    1. Generate a valid account identifier from receiver account as payee.
    2. Send a payment meeting travel rule threshold to the account identifier.
    3. Wait for the transaction executed successfully.
    4. Assert receiver account received the fund.

    """
    amount = travel_rule_threshold
    sender_account = stub_client.create_account(
        balances={currency: amount},
        kyc_data=target_client.get_kyc_sample().minimum)
    receiver_account = target_client.create_account(
        kyc_data=stub_client.get_kyc_sample().minimum)
    try:
        account_identifier = receiver_account.generate_account_identifier()
        pay = sender_account.send_payment(currency,
                                          travel_rule_threshold,
                                          payee=account_identifier)
        wait_for_payment_transaction_complete(sender_account, pay.id)
        receiver_account.wait_for_balance(currency, travel_rule_threshold)
    finally:
        receiver_account.log_events()
        sender_account.log_events()
def test_send_payment_meets_travel_rule_threshold_receiver_kyc_data_is_soft_match_then_accepted_after_reviewing_additional_kyc_data(
    currency: str,
    travel_rule_threshold: int,
    target_client: RestClient,
    stub_client: RestClient,
) -> None:
    """
    Test Plan:

    1. Create sender account with minimum valid kyc data and enough balance in target wallet application.
    2. Create receiver account with kyc data that will be soft matched by the target wallet application and 0 balance in stub wallet application.
    3. Send payment from sender account to receiver account, amount is equal to travel_rule threshold.
    4. Wait for stub wallet application account events include payment command states: ["S_INIT", "R_SEND", "S_SOFT", "R_SOFT_SEND", "READY"]
    5. Expect send payment success; receiver account balance increased by the amount sent; sender account balance decreased by the amount sent.
    """

    send_payment_meets_travel_rule_threshold(
        sender=target_client.create_account(
            balances={currency: travel_rule_threshold},
            kyc_data=stub_client.get_kyc_sample().minimum),
        receiver=stub_client.create_account(
            kyc_data=target_client.get_kyc_sample().soft_match),
        payment_command_states=[
            "S_INIT", "R_SEND", "S_SOFT", "R_SOFT_SEND", "READY"
        ],
        currency=currency,
        amount=travel_rule_threshold,
    )
Exemplo n.º 7
0
def test_create_account_with_balances_and_kyc_data(target_client: RestClient,
                                                   currency: str) -> None:
    kyc_data = target_client.get_kyc_sample().minimum
    balances = {currency: 123}
    account = target_client.create_account(balances, kyc_data=kyc_data)
    assert account.kyc_data == kyc_data
    assert account.balances() == balances
Exemplo n.º 8
0
def test_replay_the_same_payment_command(
    currency: str,
    travel_rule_threshold: int,
    stub_wallet_app: App,
    stub_client: RestClient,
    target_client: RestClient,
) -> None:
    """
    Test Plan:

    1. Generate a valid account identifier from receiver account as payee.
    2. Disable stub wallet application background tasks.
    3. Send a payment that requires off-chain PaymentCommand to the receiver account identifier.
    4. Send initial payment command to receiver wallet offchain API endpoint.
    5. Re-send the offchain PaymentCommand.
    6. Expect no error raised.
    7. Enable stub wallet application background tasks.
    """

    sender_account = stub_client.create_account(
        balances={currency: travel_rule_threshold},
        kyc_data=target_client.get_kyc_sample().minimum,
        disable_background_tasks=True,
    )
    receiver_account = target_client.create_account(
        kyc_data=stub_client.get_kyc_sample().minimum)
    try:
        payee = receiver_account.generate_account_identifier()

        payment = sender_account.send_payment(currency=currency,
                                              amount=travel_rule_threshold,
                                              payee=payee)
        txn = stub_wallet_app.store.find(Transaction, id=payment.id)
        # send initial payment command
        cmd = stub_wallet_app.send_initial_payment_command(txn)
        stub_wallet_app.send_offchain_command_without_retries(cmd)
        stub_wallet_app.send_offchain_command_without_retries(cmd)
    finally:
        receiver_account.log_events()
        sender_account.log_events()
def test_invalid_jws_message_signature(
    stub_config: AppConfig,
    target_client: RestClient,
    diem_client: jsonrpc.Client,
    currency: str,
    travel_rule_threshold: int,
    hrp: str,
) -> None:
    """
    Test Plan:

    1. Create a new on-chain account with base_url and a new compliance key.
    2. Use new on-chain account address as sender address to create payment command request.
    3. Send request to the target wallet application offchain API endpoint with new on-chain account address and a different compliance key.
    4. Expect response status code is 400
    5. Expect response `CommandResponseObject` with failure status, `protocol_error` error type,
       and `invalid_jws_signature` error code.
    """

    new_stub_account = testnet.gen_account(diem_client)
    new_stub_account.hrp = hrp
    new_compliance_key = LocalAccount().compliance_public_key_bytes
    new_stub_account.rotate_dual_attestation_info(diem_client,
                                                  stub_config.server_url,
                                                  new_compliance_key)

    receiver_address = target_client.create_account(
    ).generate_account_identifier()
    sender_address = new_stub_account.account_identifier()
    request = payment_command_request_sample(
        sender_address=sender_address,
        sender_kyc_data=target_client.get_kyc_sample().minimum,
        receiver_address=receiver_address,
        currency=currency,
        amount=travel_rule_threshold,
    )

    status_code, resp = send_request_json(
        diem_client,
        new_stub_account,
        sender_address,
        receiver_address,
        json.dumps(request),
        hrp,
    )
    assert status_code == 400
    assert resp.status == "failure"
    assert_response_error(resp, "invalid_jws_signature", "protocol_error")
def test_decoded_command_request_object_field_value_type_is_invalid(
    stub_config: AppConfig,
    stub_client: RestClient,
    target_client: RestClient,
    diem_client: jsonrpc.Client,
    currency: str,
    travel_rule_threshold: int,
    hrp: str,
    field_name: str,
) -> None:
    """
    Test Plan:

    1. Create a valid offchain request object.
    2. Set a field value to boolean type True.
    3. Send the request to the target wallet application offchain API endpoint.
    4. Expect response status code is 400
    5. Expect response `CommandResponseObject` with failure status, `protocol_error` error type,
       and `invalid_field_value` error code.
    """

    receiver_address = target_client.create_account(
    ).generate_account_identifier()
    sender_address = stub_client.create_account().generate_account_identifier()
    request = payment_command_request_sample(
        sender_address=sender_address,
        sender_kyc_data=target_client.get_kyc_sample().minimum,
        receiver_address=receiver_address,
        currency=currency,
        amount=travel_rule_threshold,
    )
    request[field_name] = True

    status_code, resp = send_request_json(
        diem_client,
        stub_config.account,
        sender_address,
        receiver_address,
        json.dumps(request),
        hrp,
    )
    assert status_code == 400
    assert resp.status == "failure"
    assert_response_error(resp,
                          "invalid_field_value",
                          "protocol_error",
                          field=field_name)
Exemplo n.º 11
0
def test_account_balance_validation_should_exclude_canceled_transactions(
        target_client: RestClient, stub_client: RestClient, currency: str,
        travel_rule_threshold: int) -> None:
    amount = travel_rule_threshold
    receiver = target_client.create_account()
    payee = receiver.generate_account_identifier()
    sender = stub_client.create_account(
        {currency: amount}, kyc_data=target_client.get_kyc_sample().reject)
    # payment should be rejected during offchain kyc data exchange
    payment = sender.send_payment(currency, amount, payee=payee)

    sender.wait_for_event("updated_transaction",
                          id=payment.id,
                          status="canceled")

    sender.send_payment(currency, travel_rule_threshold - 1, payee)

    receiver.wait_for_balance(currency, travel_rule_threshold - 1)
    sender.wait_for_balance(currency, 1)
def test_invalid_jws_message_body_that_misses_parts(
    stub_config: AppConfig,
    target_client: RestClient,
    stub_client: RestClient,
    diem_client: jsonrpc.Client,
    currency: str,
    travel_rule_threshold: int,
    hrp: str,
) -> None:
    """
    Test Plan:

    1. Create a new on-chain account with base_url and compliance key.
    2. Use new on-chain account address as sender address to create payment command request.
    3. Send request that missed jws header part to the target wallet application offchain API endpoint.
    4. Expect response status code is 400
    5. Expect response `CommandResponseObject` with failure status, `protocol_error` error type,
       and `invalid_jws` error code.
    """

    receiver_address = target_client.create_account(
    ).generate_account_identifier()
    sender_address = stub_client.create_account().generate_account_identifier()
    request = payment_command_request_sample(
        sender_address=sender_address,
        sender_kyc_data=target_client.get_kyc_sample().minimum,
        receiver_address=receiver_address,
        currency=currency,
        amount=travel_rule_threshold,
    )
    status_code, resp = send_request_json(
        diem_client,
        stub_config.account,
        sender_address,
        receiver_address,
        json.dumps(request),
        hrp,
        request_body=b"invalid.jws_msg",
    )
    assert status_code == 400
    assert resp.status == "failure"
    assert_response_error(resp, "invalid_jws", "protocol_error")
def test_invalid_x_request_id(
    stub_config: AppConfig,
    target_client: RestClient,
    stub_client: RestClient,
    diem_client: jsonrpc.Client,
    currency: str,
    travel_rule_threshold: int,
    hrp: str,
) -> None:
    """
    Test Plan:

    1. Create a valid offchain request object.
    2. Send request to the target wallet application offchain API endpoint with X-Request-ID does not
       match UUID format.
    3. Expect response status code is 400
    4. Expect response `CommandResponseObject` with failure status, `protocol_error` error type,
       and `invalid_http_header` error code.
    """

    receiver_address = target_client.create_account(
    ).generate_account_identifier()
    sender_address = stub_client.create_account().generate_account_identifier()
    request = payment_command_request_sample(
        sender_address=sender_address,
        sender_kyc_data=target_client.get_kyc_sample().minimum,
        receiver_address=receiver_address,
        currency=currency,
        amount=travel_rule_threshold,
    )
    status_code, resp = send_request_json(
        diem_client,
        stub_config.account,
        sender_address,
        receiver_address,
        json.dumps(request),
        hrp,
        x_request_id="invalid uuid",
    )
    assert status_code == 400
    assert resp.status == "failure"
    assert_response_error(resp, "invalid_http_header", "protocol_error")
def test_x_request_sender_is_valid_but_no_compliance_key(
    target_client: RestClient,
    diem_client: jsonrpc.Client,
    currency: str,
    travel_rule_threshold: int,
    hrp: str,
) -> None:
    """
    Test Plan:

    1. Create a new on-chain account without base_url and compliance key.
    2. Use new on-chain account address as sender address to create payment command request.
    3. Send request to the target wallet application offchain API endpoint with new on-chain account address.
    4. Expect response status code is 400
    5. Expect response `CommandResponseObject` with failure status, `protocol_error` error type,
       and `invalid_http_header` error code.
    """

    new_stub_account = testnet.gen_account(diem_client)
    receiver_address = target_client.create_account(
    ).generate_account_identifier()
    sender_address = new_stub_account.account_identifier()
    request = payment_command_request_sample(
        sender_address=sender_address,
        sender_kyc_data=target_client.get_kyc_sample().minimum,
        receiver_address=receiver_address,
        currency=currency,
        amount=travel_rule_threshold,
    )
    status_code, resp = send_request_json(
        diem_client,
        new_stub_account,
        sender_address,
        receiver_address,
        json.dumps(request),
        hrp,
    )
    assert status_code == 400
    assert resp.status == "failure"
    assert_response_error(resp, "invalid_http_header", "protocol_error")
Exemplo n.º 15
0
def assert_payment_command_field_error(
    stub_config: AppConfig,
    stub_client: RestClient,
    target_client: RestClient,
    diem_client: jsonrpc.Client,
    currency: str,
    travel_rule_threshold: int,
    hrp: str,
    field_name: str,
    field_value: Union[None, str, int, List[int]],
    error_code: str,
) -> None:
    receiver_address = target_client.create_account(
    ).generate_account_identifier()
    sender_address = stub_client.create_account().generate_account_identifier()
    request = payment_command_request_sample(
        sender_address=sender_address,
        sender_kyc_data=target_client.get_kyc_sample().minimum,
        receiver_address=receiver_address,
        currency=currency,
        amount=travel_rule_threshold,
    )
    full_field_name = "command." + field_name
    set_field(request, full_field_name, field_value)

    status_code, resp = send_request_json(
        diem_client,
        stub_config.account,
        sender_address,
        receiver_address,
        json.dumps(request),
        hrp,
    )
    assert status_code == 400
    assert resp.status == "failure"
    assert_response_error(resp,
                          error_code,
                          "command_error",
                          field=full_field_name)
Exemplo n.º 16
0
def test_receive_payment_with_travel_rule_metadata_and_invalid_reference_id(
    stub_client: RestClient,
    target_client: RestClient,
    currency: str,
    hrp: str,
    stub_config: AppConfig,
    diem_client: jsonrpc.Client,
    stub_wallet_pending_income_account: AccountResource,
    invalid_ref_id: Optional[str],
    travel_rule_threshold: int,
) -> None:
    """
    There is no way to create travel rule metadata with invalid reference id when the payment
    amount meets travel rule threshold, because the metadata signature is verified by transaction
    script.
    Also, if metadata signature is provided, transaction script will also verify it regardless
    whether the amount meets travel rule threshold, thus no need to test invalid metadata
    signature case.

    This test bypasses the transaction script validation by sending payment amount under the
    travel rule threshold without metadata signature, and receiver should handle it properly and refund.

    Test Plan:

    1. Generate a valid account identifier from receiver account as payee.
    2. Submit payment under travel rule threshold transaction from sender to receiver on-chain account.
    3. Wait for the transaction executed successfully.
    4. Assert the payment is refund eventually.

    Note: the refund payment will be received by pending income account of the MiniWallet Stub, because
    no account owns the original invalid payment transaction which is sent by test.

    """

    amount = travel_rule_threshold
    sender_account = stub_client.create_account(
        balances={currency: amount},
        kyc_data=target_client.get_kyc_sample().minimum)
    receiver_account = target_client.create_account(
        kyc_data=stub_client.get_kyc_sample().minimum)
    try:
        receiver_account_identifier = receiver_account.generate_account_identifier(
        )
        receiver_account_address = identifier.decode_account_address(
            receiver_account_identifier, hrp)

        sender_account_identifier = sender_account.generate_account_identifier(
        )
        sender_address = identifier.decode_account_address(
            sender_account_identifier, hrp)
        metadata, _ = txnmetadata.travel_rule(invalid_ref_id, sender_address,
                                              amount)  # pyre-ignore
        original_payment_txn: jsonrpc.Transaction = stub_config.account.submit_and_wait_for_txn(
            diem_client,
            stdlib.encode_peer_to_peer_with_metadata_script(
                currency=utils.currency_code(currency),
                amount=amount / 1000,
                payee=receiver_account_address,
                metadata=metadata,
                metadata_signature=b"",
            ),
        )

        stub_wallet_pending_income_account.wait_for_event(
            "created_transaction",
            status=Transaction.Status.completed,
            refund_diem_txn_version=original_payment_txn.version,
        )
        assert receiver_account.balance(currency) == 0
    finally:
        receiver_account.log_events()
        sender_account.log_events()
Exemplo n.º 17
0
def test_payment_command_sender_kyc_data_can_only_be_written_once(
    currency: str,
    travel_rule_threshold: int,
    stub_wallet_app: App,
    stub_client: RestClient,
    target_client: RestClient,
) -> None:
    """
    Test Plan:

    1. Generate a valid account identifier from receiver account as payee.
    2. Disable stub wallet application background tasks.
    3. Send a payment that requires off-chain PaymentCommand to the receiver account identifier.
    4. Send initial payment command to receiver wallet offchain API endpoint.
    5. Wait for receiver's wallet application to send payment command back with receiver ready for settlement.
    6. Update payment command sender KYC data.
    7. Send the updated payment command to receiver with sender ready for settlement.
    8. Expect http response status code is 400
    9. Expect response `CommandResponseObject` with failure status, `command_error` error type,
       and `invalid_overwrite` error code.
    """

    sender_account = stub_client.create_account(
        balances={currency: travel_rule_threshold},
        kyc_data=target_client.get_kyc_sample().minimum,
        disable_background_tasks=True,
    )
    receiver_account = target_client.create_account(
        kyc_data=stub_client.get_kyc_sample().minimum)
    try:
        payee = receiver_account.generate_account_identifier()

        payment = sender_account.send_payment(currency=currency,
                                              amount=travel_rule_threshold,
                                              payee=payee)
        txn = stub_wallet_app.store.find(Transaction, id=payment.id)
        # send initial payment command
        initial_cmd = stub_wallet_app.send_initial_payment_command(txn)

        # wait for receiver to update payment command
        sender_account.wait_for_event("updated_payment_command")
        # find updated payment command
        updated_cmd = stub_wallet_app.store.find(
            PaymentCommand, reference_id=initial_cmd.reference_id())
        offchain_cmd = updated_cmd.to_offchain_command()

        # update sender KYC data
        assert offchain_cmd.payment.sender.kyc_data
        if offchain_cmd.payment.sender.kyc_data.dob == "01/01/1979":
            kyc_data = replace(offchain_cmd.payment.sender.kyc_data,
                               dob="01/01/1979")
        else:
            kyc_data = replace(offchain_cmd.payment.sender.kyc_data,
                               dob="01/02/1979")
        # create new payment command with correct status and changed kyc data
        new_cmd = offchain_cmd.new_command(
            status=offchain.Status.ready_for_settlement, kyc_data=kyc_data)

        with pytest.raises(offchain.CommandResponseError) as err:
            stub_wallet_app.send_offchain_command_without_retries(new_cmd)

        assert_response_error(err.value.resp,
                              "invalid_overwrite",
                              "command_error",
                              field="payment.sender.kyc_data")
    finally:
        receiver_account.log_events()
        sender_account.log_events()
Exemplo n.º 18
0
def test_payment_command_action_can_only_be_written_once(
    currency: str,
    travel_rule_threshold: int,
    stub_wallet_app: App,
    hrp: str,
    stub_client: RestClient,
    target_client: RestClient,
    field_name: str,
) -> None:
    """
    Test Plan:

    1. Generate a valid account identifier from receiver account as payee.
    2. Disable stub wallet application background tasks.
    3. Send a payment that requires off-chain PaymentCommand to the receiver account identifier.
    4. Send initial payment command to receiver wallet offchain API endpoint.
    5. Wait for receiver's wallet application to send payment command back with receiver ready for settlement.
    6. Update payment command action.
    7. Send the updated payment command to receiver with sender ready for settlement.
    8. Expect http response status code is 400
    9. Expect response `CommandResponseObject` with failure status, `command_error` error type,
       and `invalid_overwrite` error code.
    """

    sender_account = stub_client.create_account(
        balances={currency: travel_rule_threshold},
        kyc_data=target_client.get_kyc_sample().minimum,
        disable_background_tasks=True,
    )
    receiver_account = target_client.create_account(
        kyc_data=stub_client.get_kyc_sample().minimum)
    try:
        payee = receiver_account.generate_account_identifier()

        payment = sender_account.send_payment(currency=currency,
                                              amount=travel_rule_threshold,
                                              payee=payee)
        txn = stub_wallet_app.store.find(Transaction, id=payment.id)
        # send initial payment command
        initial_cmd = stub_wallet_app.send_initial_payment_command(txn)

        # wait for receiver to update payment command
        sender_account.wait_for_event("updated_payment_command")
        # find updated payment command
        updated_cmd = stub_wallet_app.store.find(
            PaymentCommand, reference_id=initial_cmd.reference_id())
        offchain_cmd = updated_cmd.to_offchain_command()

        # update action field value
        if field_name == "currency":
            field_value = "XDX" if offchain_cmd.payment.action.currency == "XUS" else "XUS"
        elif field_name == "amount":
            field_value = offchain_cmd.payment.action.amount + 10_000
        elif field_name == "timestamp":
            field_value = offchain_cmd.payment.action.timestamp - 1
        new_action = replace(offchain_cmd.payment.action,
                             **{field_name: field_value})
        new_payment = replace(offchain_cmd.payment, action=new_action)
        new_offchain_cmd = replace(offchain_cmd, payment=new_payment)

        new_cmd = new_offchain_cmd.new_command(
            status=offchain.Status.ready_for_settlement)

        with pytest.raises(offchain.CommandResponseError) as err:
            stub_wallet_app.send_offchain_command_without_retries(new_cmd)

        assert_response_error(err.value.resp,
                              "invalid_overwrite",
                              "command_error",
                              field="payment.action")
    finally:
        receiver_account.log_events()
        sender_account.log_events()