Improve handling of unknown pending balances (likely change from addi… (#10984)

* Improve handling of unknown pending balances (likely change from adding a maker fee).
Minor improvement for fingerprint selection -- enter/return selects the logged-in fingerprint.

* Minor output formatting improvements when showing offer summaries.
Minor wallet key selection improvements.
Added tests for print_offer_summary

* Linter fixes

* isort fix

* Coroutine -> Awaitable

* Removed problematic fee calculation from get_pending_amounts per feedback.
This commit is contained in:
Jeff 2022-04-05 16:09:00 -07:00 committed by GitHub
parent 45e4c7c156
commit bb57ccffa9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 178 additions and 32 deletions

View File

@ -4,7 +4,7 @@ import sys
import time
from datetime import datetime
from decimal import Decimal
from typing import Any, Callable, List, Optional, Tuple, Dict
from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple
import aiohttp
@ -24,6 +24,8 @@ from chia.wallet.trading.trade_status import TradeStatus
from chia.wallet.transaction_record import TransactionRecord
from chia.wallet.util.wallet_types import WalletType
CATNameResolver = Callable[[bytes32], Awaitable[Optional[Tuple[Optional[uint32], str]]]]
def print_transaction(tx: TransactionRecord, verbose: bool, name, address_prefix: str, mojo_per_unit: int) -> None:
if verbose:
@ -308,21 +310,37 @@ def timestamp_to_time(timestamp):
return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
async def print_offer_summary(wallet_client: WalletRpcClient, sum_dict: dict):
async def print_offer_summary(cat_name_resolver: CATNameResolver, sum_dict: Dict[str, int], has_fee: bool = False):
for asset_id, amount in sum_dict.items():
if asset_id == "xch":
wid: str = "1"
name: str = "XCH"
unit: int = units["chia"]
else:
result = await wallet_client.cat_asset_id_to_name(bytes32.from_hexstr(asset_id))
wid = "Unknown"
description: str = ""
unit: int = units["chia"]
wid: str = "1" if asset_id == "xch" else ""
mojo_amount: int = int(Decimal(amount))
name: str = "XCH"
if asset_id != "xch":
name = asset_id
unit = units["cat"]
if result is not None:
wid = str(result[0])
name = result[1]
print(f" - {name} (Wallet ID: {wid}): {Decimal(int(amount)) / unit} ({int(Decimal(amount))} mojos)")
if asset_id == "unknown":
name = "Unknown"
unit = units["mojo"]
if has_fee:
description = " [Typically represents change returned from the included fee]"
else:
unit = units["cat"]
result = await cat_name_resolver(bytes32.from_hexstr(asset_id))
if result is not None:
wid = str(result[0])
name = result[1]
output: str = f" - {name}"
mojo_str: str = f"{mojo_amount} {'mojo' if mojo_amount == 1 else 'mojos'}"
if len(wid) > 0:
output += f" (Wallet ID: {wid})"
if unit == units["mojo"]:
output += f": {mojo_str}"
else:
output += f": {mojo_amount / unit} ({mojo_str})"
if len(description) > 0:
output += f" {description}"
print(output)
async def print_trade_record(record, wallet_client: WalletRpcClient, summaries: bool = False) -> None:
@ -337,13 +355,16 @@ async def print_trade_record(record, wallet_client: WalletRpcClient, summaries:
print("Summary:")
offer = Offer.from_bytes(record.offer)
offered, requested = offer.summary()
outbound_balances: Dict[str, int] = offer.get_pending_amounts()
fees: Decimal = Decimal(offer.bundle.fees())
cat_name_resolver = wallet_client.cat_asset_id_to_name
print(" OFFERED:")
await print_offer_summary(wallet_client, offered)
await print_offer_summary(cat_name_resolver, offered)
print(" REQUESTED:")
await print_offer_summary(wallet_client, requested)
print("Pending Balances:")
await print_offer_summary(wallet_client, offer.get_pending_amounts())
print(f"Fees: {Decimal(offer.bundle.fees()) / units['chia']}")
await print_offer_summary(cat_name_resolver, requested)
print("Pending Outbound Balances:")
await print_offer_summary(cat_name_resolver, outbound_balances, has_fee=(fees > 0))
print(f"Included Fees: {fees / units['chia']}")
print("---------------")
@ -411,12 +432,13 @@ async def take_offer(args: dict, wallet_client: WalletRpcClient, fingerprint: in
return
offered, requested = offer.summary()
cat_name_resolver = wallet_client.cat_asset_id_to_name
print("Summary:")
print(" OFFERED:")
await print_offer_summary(wallet_client, offered)
await print_offer_summary(cat_name_resolver, offered)
print(" REQUESTED:")
await print_offer_summary(wallet_client, requested)
print(f"Fees: {Decimal(offer.bundle.fees()) / units['chia']}")
await print_offer_summary(cat_name_resolver, requested)
print(f"Included Fees: {Decimal(offer.bundle.fees()) / units['chia']}")
if not examine_only:
confirmation = input("Would you like to take this offer? (y/n): ")
@ -534,7 +556,7 @@ async def get_wallet(wallet_client: WalletRpcClient, fingerprint: int = None) ->
current_sync_status = "Syncing"
else:
current_sync_status = "Not Synced"
print("Choose wallet key:")
print("Wallet keys:")
for i, fp in enumerate(fingerprints):
row: str = f"{i+1}) "
row += "* " if fp == logged_in_fingerprint else spacing
@ -543,15 +565,21 @@ async def get_wallet(wallet_client: WalletRpcClient, fingerprint: int = None) ->
row += f" ({current_sync_status})"
print(row)
val = None
prompt: str = (
f"Choose a wallet key [1-{len(fingerprints)}] ('q' to quit, or Enter to use {logged_in_fingerprint}): "
)
while val is None:
val = input("Enter a number to pick or q to quit: ")
val = input(prompt)
if val == "q":
return None
if not val.isdigit():
elif val == "" and logged_in_fingerprint is not None:
fingerprint = logged_in_fingerprint
break
elif not val.isdigit():
val = None
else:
index = int(val) - 1
if index >= len(fingerprints):
if index < 0 or index >= len(fingerprints):
print("Invalid value")
val = None
continue

View File

@ -193,12 +193,6 @@ class Offer:
for addition in filter(lambda c: c.parent_coin_info == root_removal.name(), all_additions):
pending_dict[name] += addition.amount
# Then we add a potential fee as pending XCH
fee: int = sum(c.amount for c in all_removals) - sum(c.amount for c in all_additions)
if fee > 0:
pending_dict.setdefault("xch", 0)
pending_dict["xch"] += fee
# Then we gather anything else as unknown
sum_of_additions_so_far: int = sum(pending_dict.values())
unknown: int = sum([c.amount for c in non_ephemeral_removals]) - sum_of_additions_so_far

View File

@ -0,0 +1,124 @@
from typing import Any, Dict, Optional, Tuple
import pytest
from chia.cmds.wallet_funcs import print_offer_summary
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.ints import uint32
TEST_DUCKSAUCE_ASSET_ID = "1000000000000000000000000000000000000000000000000000000000000001"
TEST_CRUNCHBERRIES_ASSET_ID = "1000000000000000000000000000000000000000000000000000000000000002"
TEST_UNICORNTEARS_ASSET_ID = "1000000000000000000000000000000000000000000000000000000000000003"
TEST_ASSET_ID_NAME_MAPPING: Dict[bytes32, Tuple[uint32, str]] = {
bytes32.from_hexstr(TEST_DUCKSAUCE_ASSET_ID): (uint32(2), "DuckSauce"),
bytes32.from_hexstr(TEST_CRUNCHBERRIES_ASSET_ID): (uint32(3), "CrunchBerries"),
bytes32.from_hexstr(TEST_UNICORNTEARS_ASSET_ID): (uint32(4), "UnicornTears"),
}
async def cat_name_resolver(asset_id: bytes32) -> Optional[Tuple[Optional[uint32], str]]:
return TEST_ASSET_ID_NAME_MAPPING.get(asset_id)
@pytest.mark.asyncio
async def test_print_offer_summary_xch(capsys: Any) -> None:
summary_dict = {"xch": 1_000_000_000_000}
await print_offer_summary(cat_name_resolver, summary_dict)
captured = capsys.readouterr()
assert "XCH (Wallet ID: 1): 1.0 (1000000000000 mojos)" in captured.out
@pytest.mark.asyncio
async def test_print_offer_summary_cat(capsys: Any) -> None:
summary_dict = {
TEST_DUCKSAUCE_ASSET_ID: 1_000,
}
await print_offer_summary(cat_name_resolver, summary_dict)
captured = capsys.readouterr()
assert "DuckSauce (Wallet ID: 2): 1.0 (1000 mojos)" in captured.out
@pytest.mark.asyncio
async def test_print_offer_summary_multiple_cats(capsys: Any) -> None:
summary_dict = {
TEST_DUCKSAUCE_ASSET_ID: 1_000,
TEST_CRUNCHBERRIES_ASSET_ID: 2_000,
}
await print_offer_summary(cat_name_resolver, summary_dict)
captured = capsys.readouterr()
assert "DuckSauce (Wallet ID: 2): 1.0 (1000 mojos)" in captured.out
assert "CrunchBerries (Wallet ID: 3): 2.0 (2000 mojos)" in captured.out
@pytest.mark.asyncio
async def test_print_offer_summary_xch_and_cats(capsys: Any) -> None:
summary_dict = {
"xch": 2_500_000_000_000,
TEST_DUCKSAUCE_ASSET_ID: 1_111,
TEST_CRUNCHBERRIES_ASSET_ID: 2_222,
TEST_UNICORNTEARS_ASSET_ID: 3_333,
}
await print_offer_summary(cat_name_resolver, summary_dict)
captured = capsys.readouterr()
assert "XCH (Wallet ID: 1): 2.5 (2500000000000 mojos)" in captured.out
assert "DuckSauce (Wallet ID: 2): 1.111 (1111 mojos)" in captured.out
assert "CrunchBerries (Wallet ID: 3): 2.222 (2222 mojos)" in captured.out
assert "UnicornTears (Wallet ID: 4): 3.333 (3333 mojos)" in captured.out
@pytest.mark.asyncio
async def test_print_offer_summary_xch_and_cats_with_zero_values(capsys: Any) -> None:
summary_dict = {
"xch": 0,
TEST_DUCKSAUCE_ASSET_ID: 0,
TEST_CRUNCHBERRIES_ASSET_ID: 0,
TEST_UNICORNTEARS_ASSET_ID: 0,
}
await print_offer_summary(cat_name_resolver, summary_dict)
captured = capsys.readouterr()
assert "XCH (Wallet ID: 1): 0.0 (0 mojos)" in captured.out
assert "DuckSauce (Wallet ID: 2): 0.0 (0 mojos)" in captured.out
assert "CrunchBerries (Wallet ID: 3): 0.0 (0 mojos)" in captured.out
assert "UnicornTears (Wallet ID: 4): 0.0 (0 mojos)" in captured.out
@pytest.mark.asyncio
async def test_print_offer_summary_cat_with_fee_and_change(capsys: Any) -> None:
summary_dict = {
TEST_DUCKSAUCE_ASSET_ID: 1_000,
"unknown": 3_456,
}
await print_offer_summary(cat_name_resolver, summary_dict, has_fee=True)
captured = capsys.readouterr()
assert "DuckSauce (Wallet ID: 2): 1.0 (1000 mojos)" in captured.out
assert "Unknown: 3456 mojos [Typically represents change returned from the included fee]" in captured.out
@pytest.mark.asyncio
async def test_print_offer_summary_xch_with_one_mojo(capsys: Any) -> None:
summary_dict = {"xch": 1}
await print_offer_summary(cat_name_resolver, summary_dict)
captured = capsys.readouterr()
assert "XCH (Wallet ID: 1): 1e-12 (1 mojo)" in captured.out