Skip to content

Commit

Permalink
Check insurance limit
Browse files Browse the repository at this point in the history
Signed-off-by: alfred richardsn <rchrdsn@protonmail.ch>
  • Loading branch information
r4rdsn committed Nov 11, 2019
1 parent f85358b commit 9ff9ad7
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 29 deletions.
29 changes: 21 additions & 8 deletions src/escrow/blockchain/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,39 @@
from src.i18n import _


class BaseBlockchain(ABC):
"""Abstract class to represent blockchain node client for escrow exchange.
class InsuranceLimits(typing.NamedTuple):
"""Maximum amount of insured asset."""

#: Limit on sum of a single offer.
single: Decimal
#: Limit on overall sum of offers.
total: Decimal

Attributes:
assets Frozen set of assets supported by blockchain.
address Address used by bot.
explorer Template of URL to transaction in blockchain explorer. Should
contain ``{}`` which gets replaced with transaction id.

"""
class BaseBlockchain(ABC):
"""Abstract class to represent blockchain node client for escrow exchange."""

#: Frozen set of assets supported by blockchain.
assets: typing.FrozenSet[str] = frozenset()
#: Address used by bot.
address: str
#: Template of URL to transaction in blockchain explorer. Should
#: contain ``{}`` which gets replaced with transaction id.
explorer: str = '{}'

_queue: typing.List[typing.Mapping[str, typing.Any]] = []

@abstractmethod
async def connect(self) -> None:
"""Establish connection with blockchain node."""

@abstractmethod
async def get_limits(self, asset: str) -> InsuranceLimits:
"""Get maximum amounts of ``asset`` which will be insured during escrow exchange.
Escrow offer starts only if sum of it doesn't exceed these limits.
"""

@abstractmethod
async def transfer(self, to: str, amount: Decimal, asset: str) -> str:
"""Transfer ``asset`` from ``self.address``.
Expand Down
5 changes: 5 additions & 0 deletions src/escrow/blockchain/golos_blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from src.database import database
from src.escrow.blockchain import BaseBlockchain
from src.escrow.blockchain import BlockchainConnectionError
from src.escrow.blockchain import InsuranceLimits


NODES = (
Expand Down Expand Up @@ -108,6 +109,10 @@ async def connect(self):
self._queue.extend(queue)
await self._start_streaming()

async def get_limits(self, asset: str):
limits = {'GOLOS': InsuranceLimits(Decimal('10000'), Decimal('100000'))}
return limits.get(asset)

async def transfer(self, to: str, amount: Decimal, asset: str):
with open('wif.json') as wif_file:
transaction = await get_running_loop().run_in_executor(
Expand Down
94 changes: 73 additions & 21 deletions src/handlers/escrow.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,24 +157,90 @@ async def set_escrow_sum(message: types.Message, state: FSMContext, offer: Escro
normalize(escrow_sum.to_decimal() * Decimal('0.95'))
)

if offer.sum_currency == offer.type:
asset = offer[offer.type]
limits = get_escrow_instance(asset).get_limits(asset)
insured = min(offer_sum, limits.single)
cursor = database.escrow.aggregate(
[{'$group': {'_id': 0, 'insured_total': {'$sum': '$insured'}}}]
)
if await cursor.fetch_next:
insured_total = cursor.next_object()['insured_total'].to_decimal()
total_difference = limits.total - insured_total - insured
if total_difference < 0:
insured += total_difference
insured = normalize(insured)
update_dict['insured'] = Decimal128(insured)
if offer_sum > insured:
keyboard = InlineKeyboardMarkup()
keyboard.add(
InlineKeyboardButton(
_('Continue'), callback_data=f'accept_insurance {offer._id}'
),
InlineKeyboardButton(
_('Cancel'), callback_data=f'init_cancel {offer._id}'
),
)
await tg.send_message(
message.chat.id,
_(
'This number exceeds maximum amount to be insured. If you '
'continue, only {} {} will be protected and refunded in '
'case of unexpected events during the exchange.\n'
'You can send a smaller number, continue with partial '
'insurance or cancel offer.'
).format(insured, asset),
reply_markup=keyboard,
)
else:
updated_offer = replace(offer, **update_dict) # type: ignore
await ask_fee(message.from_user.id, message.chat.id, updated_offer)

await offer.update_document({'$set': update_dict, '$unset': {'sum_currency': True}})


async def ask_fee(user_id: int, chat_id: int, offer: EscrowOffer):
"""Ask fee of any party."""
answer = _('Do you agree to pay a fee of 5%?') + ' '
if offer.type == 'buy':
if (user_id == offer.init['id']) == (offer.type == 'buy'):
answer += _("(You'll pay {} {})")
sum_fee_field = 'sum_fee_up'
elif offer.type == 'sell':
else:
answer += _("(You'll get {} {})")
sum_fee_field = 'sum_fee_down'
keyboard = InlineKeyboardMarkup()
keyboard.add(
InlineKeyboardButton(_('Yes'), callback_data=f'accept_fee {offer._id}'),
InlineKeyboardButton(_('No'), callback_data=f'decline_fee {offer._id}'),
)
answer = answer.format(update_dict[sum_fee_field], offer[offer.type])
await tg.send_message(message.chat.id, answer, reply_markup=keyboard)
answer = answer.format(offer[sum_fee_field], offer[offer.type])
await tg.send_message(chat_id, answer, reply_markup=keyboard)
await states.Escrow.fee.set()


@escrow_callback_handler(
lambda call: call.data.startswith('accept_insurance '), state=states.Escrow.amount
)
async def accept_insurance(
call: types.CallbackQuery, state: FSMContext, offer: EscrowOffer
):
"""Ask for fee payment agreement after accepting partial insurance."""
await ask_fee(call.from_user.id, call.message.chat.id, offer)


@escrow_callback_handler(
lambda call: call.data.startswith('init_cancel '), state=states.Escrow.amount
)
async def init_cancel(call: types.CallbackQuery, state: FSMContext, offer: EscrowOffer):
"""Cancel offer on initiator's request."""
await offer.delete_document()
await call.answer()
await tg.send_message(
call.message.chat.id, _('Escrow was cancelled.'), reply_markup=start_keyboard()
)
await state.finish()


async def full_card_number_request(chat_id: int, offer: EscrowOffer):
"""Ask to send full card number."""
keyboard = InlineKeyboardMarkup()
Expand Down Expand Up @@ -513,26 +579,12 @@ async def set_init_send_address(

@escrow_callback_handler(lambda call: call.data.startswith('accept '))
async def accept_offer(call: types.CallbackQuery, offer: EscrowOffer):
"""React to counteragent accepting offer by askiing for fee payment agreement."""
"""React to counteragent accepting offer by asking for fee payment agreement."""
await offer.update_document(
{'$set': {'pending_input_from': call.message.chat.id, 'react_time': time()}}
)
answer = _('Do you agree to pay a fee of 5%?') + ' '
if offer.type == 'buy':
answer += _("(You'll get {} {})")
sum_fee_field = 'sum_fee_down'
elif offer.type == 'sell':
answer += _("(You'll pay {} {})")
sum_fee_field = 'sum_fee_up'
answer = answer.format(offer[sum_fee_field], offer[offer.type])
buy_keyboard = InlineKeyboardMarkup()
buy_keyboard.add(
InlineKeyboardButton(_('Yes'), callback_data=f'accept_fee {offer._id}'),
InlineKeyboardButton(_('No'), callback_data=f'decline_fee {offer._id}'),
)
await call.answer()
await tg.send_message(call.message.chat.id, answer, reply_markup=buy_keyboard)
await states.Escrow.fee.set()
await ask_fee(call.from_user.id, call.message.chat.id, offer)


@escrow_callback_handler(lambda call: call.data.startswith('decline '))
Expand Down Expand Up @@ -642,7 +694,7 @@ async def cancel_offer(call: types.CallbackQuery, offer: EscrowOffer):
"""React to offer cancellation.
While first party is transferring, second party can't cancel offer,
because we can't be sure that first party hasn't alredy completed
because we can't be sure that first party hasn't already completed
transfer before confirming.
"""
if offer.trx_id:
Expand Down

0 comments on commit 9ff9ad7

Please sign in to comment.