From de660408936e12cb5cef458b13cfef8c333bc147 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Mon, 10 Jan 2022 04:10:32 -0800 Subject: [PATCH] Add cancel taker penalty time --- .env-sample | 2 ++ api/logics.py | 46 +++++++++++++++++++++++----- api/models.py | 3 ++ api/utils.py | 2 +- api/views.py | 9 +++++- frontend/src/components/OrderPage.js | 15 ++++++++- 6 files changed, 67 insertions(+), 10 deletions(-) diff --git a/.env-sample b/.env-sample index a80d8ef6..77f06ce3 100644 --- a/.env-sample +++ b/.env-sample @@ -5,6 +5,8 @@ MARKET_PRICE_API = 'https://blockchain.info/ticker' FEE = 0.002 # Bond size in percentage % BOND_SIZE = 0.01 +# Time out penalty for canceling takers in MINUTES +PENALTY_TIMEOUT = 2 # Trade limits in satoshis MIN_TRADE = 10000 diff --git a/api/logics.py b/api/logics.py index 8374f17b..7cdc48d7 100644 --- a/api/logics.py +++ b/api/logics.py @@ -10,6 +10,7 @@ FEE = float(config('FEE')) BOND_SIZE = float(config('BOND_SIZE')) MARKET_PRICE_API = config('MARKET_PRICE_API') ESCROW_USERNAME = config('ESCROW_USERNAME') +PENALTY_TIMEOUT = int(config('PENALTY_TIMEOUT')) MIN_TRADE = int(config('MIN_TRADE')) MAX_TRADE = int(config('MAX_TRADE')) @@ -21,6 +22,7 @@ EXP_TRADE_ESCR_INVOICE = int(config('EXP_TRADE_ESCR_INVOICE')) BOND_EXPIRY = int(config('BOND_EXPIRY')) ESCROW_EXPIRY = int(config('ESCROW_EXPIRY')) + class Logics(): def validate_already_maker_or_taker(user): @@ -40,11 +42,17 @@ class Logics(): if order.t0_satoshis < MIN_TRADE: return False, {'bad_request': 'Your order is too small. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now. But limit is '+'{:,}'.format(MIN_TRADE)+ ' Sats'} return True, None - - def take(order, user): - order.taker = user - order.status = Order.Status.TAK - order.save() + + @classmethod + def take(cls, order, user): + is_penalized, time_out = cls.is_penalized(user) + if is_penalized: + return False, {'bad_request',f'You need to wait {time_out} seconds to take an order'} + else: + order.taker = user + order.status = Order.Status.TAK + order.save() + return True, None def is_buyer(order, user): is_maker = order.maker == user @@ -167,6 +175,18 @@ class Logics(): order.save() return True, None + def is_penalized(user): + ''' Checks if a user that is not participant of orders + has a limit on taking or making a order''' + + if user.profile.penalty_expiration: + if user.profile.penalty_expiration > timezone.now(): + time_out = (user.profile.penalty_expiration - timezone.now()).seconds + return True, time_out + + return False, None + + @classmethod def cancel_order(cls, order, user, state=None): @@ -181,12 +201,24 @@ class Logics(): # 2) When maker cancels after bond '''The order dissapears from book and goes to cancelled. - Maker is charged a small amount of sats, to prevent DDOS - on the LN node and order book''' + Maker is charged the bond to prevent DDOS + on the LN node and order book. TODO Only charge a small part + of the bond (requires maker submitting an invoice)''' + # 3) 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: + # adds a timeout penalty + user.profile.penalty_expiration = timezone.now() + timedelta(seconds=PENALTY_TIMEOUT) + user.save() + + order.taker = None + order.status = Order.Status.PUB + order.save() + + return True, None # 4) When taker or maker cancel after bond (before escrow) '''The order goes into cancelled status if maker cancels. diff --git a/api/models.py b/api/models.py index db6d9cf8..36b926de 100644 --- a/api/models.py +++ b/api/models.py @@ -158,6 +158,9 @@ class Profile(models.Model): # RoboHash avatar = models.ImageField(default="static/assets/misc/unknown_avatar.png", verbose_name='Avatar', blank=True) + # Penalty expiration (only used then taking/cancelling repeatedly orders in the book before comitting bond) + penalty_expiration = models.DateTimeField(null=True) + @receiver(post_save, sender=User) def create_user_profile(sender, instance, created, **kwargs): if created: diff --git a/api/utils.py b/api/utils.py index e185c3f9..3cf948c5 100644 --- a/api/utils.py +++ b/api/utils.py @@ -5,7 +5,7 @@ import ring storage = {} -@ring.dict(storage, expire=10) #keeps in cache for 10 seconds +@ring.dict(storage, expire=30) #keeps in cache for 30 seconds def get_exchange_rate(currency): # TODO Add fallback Public APIs and error handling # Think about polling price data in a different way (e.g. store locally every t seconds) diff --git a/api/views.py b/api/views.py index a4f474d4..f92c5e7a 100644 --- a/api/views.py +++ b/api/views.py @@ -105,6 +105,11 @@ class OrderView(viewsets.ViewSet): data = ListOrderSerializer(order).data + # if user is under a limit (penalty), inform him + is_penalized, time_out = Logics.is_penalized(request.user) + if is_penalized: + data['penalty'] = time_out + # Add booleans if user is maker, taker, partipant, buyer or seller data['is_maker'] = order.maker == request.user data['is_taker'] = order.taker == request.user @@ -207,7 +212,9 @@ class OrderView(viewsets.ViewSet): valid, context = Logics.validate_already_maker_or_taker(request.user) if not valid: return Response(context, status=status.HTTP_409_CONFLICT) - Logics.take(order, request.user) + valid, context = Logics.take(order, request.user) + if not valid: return Response(context, status=status.HTTP_403_FORBIDDEN) + else: Response({'bad_request':'This order is not public anymore.'}, status.HTTP_400_BAD_REQUEST) # Any other action is only allowed if the user is a participant diff --git a/frontend/src/components/OrderPage.js b/frontend/src/components/OrderPage.js index e0703490..65340f0d 100644 --- a/frontend/src/components/OrderPage.js +++ b/frontend/src/components/OrderPage.js @@ -1,5 +1,5 @@ import React, { Component } from "react"; -import { Paper, CircularProgress, Button , Grid, Typography, List, ListItem, ListItemText, ListItemAvatar, Avatar, Divider, Box, LinearProgress} from "@mui/material" +import { Alert, Paper, CircularProgress, Button , Grid, Typography, List, ListItem, ListItemText, ListItemAvatar, Avatar, Divider, Box, LinearProgress} from "@mui/material" import TradeBox from "./TradeBox"; function msToTime(duration) { @@ -101,6 +101,7 @@ export default class OrderPage extends Component { isTaker: data.is_taker, isBuyer: data.is_buyer, isSeller: data.is_seller, + penalty: data.penalty, expiresAt: data.expires_at, badRequest: data.bad_request, bondInvoice: data.bond_invoice, @@ -249,6 +250,18 @@ export default class OrderPage extends Component { + + {/* If the user has a penalty/limit */} + {this.state.penalty ? + <> + + + + You cannot take an order yet! Wait {this.state.penalty} seconds + + + + : null}