Skip to content

Commit

Permalink
Add cancel_status to POST /api/order
Browse files Browse the repository at this point in the history
  • Loading branch information
aftermath2 committed Jun 16, 2024
1 parent daf9ce4 commit f6ca9c4
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 16 deletions.
27 changes: 17 additions & 10 deletions api/logics.py
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,7 @@ def is_penalized(user):
return False, None

@classmethod
def cancel_order(cls, order, user, state=None):
def cancel_order(cls, order, user, cancel_status=None):
# Do not change order status if an is in order
# any of these status
do_not_cancel = [
Expand All @@ -1009,10 +1009,17 @@ def cancel_order(cls, order, user, state=None):
if order.status in do_not_cancel:
return False, {"bad_request": "You cannot cancel this order"}

# 1) When maker cancels before bond
# 1) If the order status is not the one specified by the user, do not cancel the order.
if cancel_status is not None and order.status != cancel_status:
return False, {
"bad_request": f"Order status {order.status} is not {cancel_status}. " +
"The order may have been taken before it was cancelled"
}

# 2) When maker cancels before bond
# The order never shows up on the book and order
# status becomes "cancelled"
if order.status == Order.Status.WFB and order.maker == user:
elif order.status == Order.Status.WFB and order.maker == user:
cls.cancel_bond(order.maker_bond)
order.update_status(Order.Status.UCA)

Expand All @@ -1021,7 +1028,7 @@ def cancel_order(cls, order, user, state=None):

return True, None

# 2.a) When maker cancels after bond
# 3.a) When maker cancels after bond
#
# The order disapears from book and goes to cancelled. If strict, maker is charged the bond
# to prevent DDOS on the LN node and order book. If not strict, maker is returned
Expand All @@ -1041,7 +1048,7 @@ def cancel_order(cls, order, user, state=None):

return True, None

# 2.b) When maker cancels after bond and before taker bond is locked
# 3.b) When maker cancels after bond and before taker bond is locked
#
# The order dissapears from book and goes to cancelled.
# The bond maker bond is returned.
Expand All @@ -1060,7 +1067,7 @@ def cancel_order(cls, order, user, state=None):

return True, None

# 3) When taker cancels before bond
# 4) When taker cancels before bond
# The order goes back to the book as public.
# LNPayment "order.taker_bond" is deleted()
elif order.status == Order.Status.TAK and order.taker == user:
Expand All @@ -1072,13 +1079,13 @@ def cancel_order(cls, order, user, state=None):

return True, None

# 4) When taker or maker cancel after bond (before escrow)
# 5) When taker or maker cancel after bond (before escrow)
#
# The order goes into cancelled status if maker cancels.
# The order goes into the public book if taker cancels.
# In both cases there is a small fee.

# 4.a) When maker cancel after bond (before escrow)
# 5.a) When maker cancel after bond (before escrow)
# The order into cancelled status if maker cancels.
elif (
order.status in [Order.Status.WF2, Order.Status.WFE] and order.maker == user
Expand All @@ -1101,7 +1108,7 @@ def cancel_order(cls, order, user, state=None):

return True, None

# 4.b) When taker cancel after bond (before escrow)
# 5.b) When taker cancel after bond (before escrow)
# The order into cancelled status if mtker cancels.
elif (
order.status in [Order.Status.WF2, Order.Status.WFE] and order.taker == user
Expand All @@ -1123,7 +1130,7 @@ def cancel_order(cls, order, user, state=None):

return True, None

# 5) When trade collateral has been posted (after escrow)
# 6) When trade collateral has been posted (after escrow)
#
# Always goes to CCA status. Collaboration is needed.
# When a user asks for cancel, 'order.m/t/aker_asked_cancel' goes True.
Expand Down
7 changes: 7 additions & 0 deletions api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,13 @@ class UpdateOrderSerializer(serializers.Serializer):
mining_fee_rate = serializers.DecimalField(
max_digits=6, decimal_places=3, allow_null=True, required=False, default=None
)
cancel_status = serializers.ChoiceField(
choices=Order.Status.choices,
allow_null=True,
allow_blank=True,
default=None,
help_text="Status the order should have for the cancel action to take place.",
)


class ClaimRewardSerializer(serializers.Serializer):
Expand Down
3 changes: 2 additions & 1 deletion api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ def take_update_confirm_dispute_cancel(self, request, format=None):
mining_fee_rate = serializer.data.get("mining_fee_rate")
statement = serializer.data.get("statement")
rating = serializer.data.get("rating")
cancel_status = serializer.data.get("cancel_status")

# 1) If action is take, it is a taker request!
if action == "take":
Expand Down Expand Up @@ -586,7 +587,7 @@ def take_update_confirm_dispute_cancel(self, request, format=None):

# 3) If action is cancel
elif action == "cancel":
valid, context = Logics.cancel_order(order, request.user)
valid, context = Logics.cancel_order(order, request.user, cancel_status)
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)

Expand Down
3 changes: 3 additions & 0 deletions docs/assets/schemas/api-latest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1950,6 +1950,9 @@ components:
format: decimal
pattern: ^-?\d{0,3}(?:\.\d{0,3})?$
nullable: true
cancel_status:
allOf:
- $ref: '#/components/schemas/StatusEnum'
required:
- action
Version:
Expand Down
13 changes: 10 additions & 3 deletions frontend/src/components/TradeBox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
mining_fee_rate?: number;
statement?: string;
rating?: number;
cancel_status?: number;
}

const renewOrder = function (): void {
Expand Down Expand Up @@ -203,6 +204,7 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
mining_fee_rate,
statement,
rating,
cancel_status
}: SubmitActionProps): void {
const robot = garage.getSlot()?.getRobot();
const currentOrder = garage.getSlot()?.order;
Expand All @@ -219,6 +221,7 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
mining_fee_rate,
statement,
rating,
cancel_status
},
{ tokenSHA256: robot?.tokenSHA256 },
)
Expand All @@ -244,9 +247,13 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
});
};

const cancel = function (): void {
const cancel = function (no_confirmation: boolean) {
const currentOrder = garage.getSlot()?.order;
setLoadingButtons({ ...noLoadingButtons, cancel: true });
submitAction({ action: 'cancel' });
submitAction({
action: 'cancel',
cancel_status: no_confirmation ? currentOrder?.status : undefined
});
};

const openDispute = function (): void {
Expand Down Expand Up @@ -834,7 +841,7 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
<Grid item>
<CancelButton
order={garage.getSlot()?.order ?? null}
onClickCancel={cancel}
onClickCancel={() => cancel(true)}
openCancelDialog={() => {
setOpen({ ...closeAll, confirmCancel: true });
}}
Expand Down
3 changes: 3 additions & 0 deletions tests/api_specs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1990,6 +1990,9 @@ components:
format: decimal
pattern: ^-?\d{0,3}(?:\.\d{0,3})?$
nullable: true
cancel_status:
allOf:
- $ref: '#/components/schemas/StatusEnum'
required:
- action
Version:
Expand Down
48 changes: 48 additions & 0 deletions tests/test_trade_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,54 @@ def test_cancel_public_order(self):
self.assertEqual(
data["bad_request"], "This order has been cancelled by the maker"
)

def test_cancel_order_cancel_status(self):
"""
Tests the cancellation of a public order using cancel_status.
"""
trade = Trade(self.client)
trade.publish_order()
data = trade.response.json()

self.assertEqual(trade.response.status_code, 200)
self.assertResponse(trade.response)

self.assertEqual(data["status_message"], Order.Status(Order.Status.PUB).label)

# Cancel order if the order status is public
trade.cancel_order(cancel_status=Order.Status.PUB)

self.assertEqual(trade.response.status_code, 200)
self.assertResponse(trade.response)

def test_cancel_order_different_cancel_status(self):
"""
Tests the cancellation of a paused order with a different cancel_status.
"""
trade = Trade(self.client)
trade.publish_order()
trade.pause_order()
data = trade.response.json()

self.assertEqual(trade.response.status_code, 200)
self.assertResponse(trade.response)

self.assertEqual(data["status_message"], Order.Status(Order.Status.PAU).label)

# Try to cancel order if it is public
trade.cancel_order(cancel_status=Order.Status.PUB)
data = trade.response.json()

self.assertEqual(trade.response.status_code, 400)
self.assertResponse(trade.response)

self.assertEqual(
data["bad_request"], f"Order status {Order.Status.PAU} is not {Order.Status.PUB}. " +
"The order may have been taken before it was cancelled"
)

# Cancel order to avoid leaving pending HTLCs after a successful test
trade.cancel_order()

def test_collaborative_cancel_order_in_chat(self):
"""
Expand Down
4 changes: 2 additions & 2 deletions tests/utils/trade.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,11 @@ def get_order(self, robot_index=1, first_encounter=False):
self.response = self.client.get(path + params, **headers)

@patch("api.tasks.send_notification.delay", send_notification)
def cancel_order(self, robot_index=1):
def cancel_order(self, robot_index=1, cancel_status=None):
path = reverse("order")
params = f"?order_id={self.order_id}"
headers = self.get_robot_auth(robot_index)
body = {"action": "cancel"}
body = {"action": "cancel", "cancel_status": cancel_status}
self.response = self.client.post(path + params, body, **headers)

def pause_order(self, robot_index=1):
Expand Down

0 comments on commit f6ca9c4

Please sign in to comment.