Esempio n. 1
0
async def take_offer(args: dict, wallet_client: WalletRpcClient,
                     fingerprint: int) -> None:
    if "." in args["file"]:
        filepath = pathlib.Path(args["file"])
        with open(filepath, "r") as file:
            offer_hex: str = file.read()
            file.close()
    else:
        offer_hex = args["file"]

    examine_only: bool = args["examine_only"]
    fee: int = int(Decimal(args["fee"]) * units["chia"])

    try:
        offer = Offer.from_bech32(offer_hex)
    except ValueError:
        print("Please enter a valid offer file or hex blob")
        return

    offered, requested = offer.summary()
    print("Summary:")
    print("  OFFERED:")
    await print_offer_summary(wallet_client, offered)
    print("  REQUESTED:")
    await print_offer_summary(wallet_client, requested)
    print(f"Fees: {Decimal(offer.bundle.fees()) / units['chia']}")

    if not examine_only:
        confirmation = input("Would you like to take this offer? (y/n): ")
        if confirmation in ["y", "yes"]:
            trade_record = await wallet_client.take_offer(offer, fee=fee)
            print(f"Accepted offer with ID {trade_record.trade_id}")
            print(
                f"Use chia wallet get_offers --id {trade_record.trade_id} -f {fingerprint} to view its status"
            )
Esempio n. 2
0
 async def get_offer(self,
                     trade_id: bytes32,
                     file_contents: bool = False) -> TradeRecord:
     res = await self.fetch("get_offer", {
         "trade_id": trade_id.hex(),
         "file_contents": file_contents
     })
     offer_str = bytes(Offer.from_bech32(
         res["offer"])).hex() if file_contents else ""
     return TradeRecord.from_json_dict_convenience(res["trade_record"],
                                                   offer_str)
Esempio n. 3
0
    async def maybe_create_wallets_for_offer(self, offer: Offer):

        for key in offer.arbitrage():
            wsm = self.wallet_state_manager
            wallet: Wallet = wsm.main_wallet
            if key is None:
                continue
            exists: Optional[Wallet] = await wsm.get_wallet_for_asset_id(key.hex())
            if exists is None:
                self.log.info(f"Creating wallet for asset ID: {key}")
                await CATWallet.create_wallet_for_cat(wsm, wallet, key.hex())
Esempio n. 4
0
    async def coins_of_interest_farmed(self, coin_state: CoinState):
        """
        If both our coins and other coins in trade got removed that means that trade was successfully executed
        If coins from other side of trade got farmed without ours, that means that trade failed because either someone
        else completed trade or other side of trade canceled the trade by doing a spend.
        If our coins got farmed but coins from other side didn't, we successfully canceled trade by spending inputs.
        """
        self.log.info(f"coins_of_interest_farmed: {coin_state}")
        trade = await self.get_trade_by_coin(coin_state.coin)
        if trade is None:
            self.log.error(f"Coin: {coin_state.coin}, not in any trade")
            return
        if coin_state.spent_height is None:
            self.log.error(f"Coin: {coin_state.coin}, has not been spent so trade can remain valid")

        # Then let's filter the offer into coins that WE offered
        offer = Offer.from_bytes(trade.offer)
        primary_coin_ids = [c.name() for c in offer.get_primary_coins()]
        our_coin_records: List[WalletCoinRecord] = await self.wallet_state_manager.coin_store.get_multiple_coin_records(
            primary_coin_ids
        )
        our_primary_coins: List[bytes32] = [cr.coin.name() for cr in our_coin_records]
        all_settlement_payments: List[Coin] = [c for coins in offer.get_offered_coins().values() for c in coins]
        our_settlement_payments: List[Coin] = list(
            filter(lambda c: offer.get_root_removal(c).name() in our_primary_coins, all_settlement_payments)
        )
        our_settlement_ids: List[bytes32] = [c.name() for c in our_settlement_payments]

        # And get all relevant coin states
        coin_states = await self.wallet_state_manager.wallet_node.get_coin_state(our_settlement_ids)
        assert coin_states is not None
        coin_state_names: List[bytes32] = [cs.coin.name() for cs in coin_states]

        # If any of our settlement_payments were spent, this offer was a success!
        if set(our_settlement_ids) & set(coin_state_names):
            height = coin_states[0].spent_height
            await self.trade_store.set_status(trade.trade_id, TradeStatus.CONFIRMED, True, height)
            tx_records: List[TransactionRecord] = await self.calculate_tx_records_for_offer(offer, False)
            for tx in tx_records:
                if TradeStatus(trade.status) == TradeStatus.PENDING_ACCEPT:
                    await self.wallet_state_manager.add_transaction(
                        dataclasses.replace(tx, confirmed_at_height=height, confirmed=True)
                    )

            self.log.info(f"Trade with id: {trade.trade_id} confirmed at height: {height}")
        else:
            # In any other scenario this trade failed
            await self.wallet_state_manager.delete_trade_transactions(trade.trade_id)
            if trade.status == TradeStatus.PENDING_CANCEL.value:
                await self.trade_store.set_status(trade.trade_id, TradeStatus.CANCELLED, True)
                self.log.info(f"Trade with id: {trade.trade_id} canceled")
            elif trade.status == TradeStatus.PENDING_CONFIRM.value:
                await self.trade_store.set_status(trade.trade_id, TradeStatus.FAILED, True)
                self.log.warning(f"Trade with id: {trade.trade_id} failed")
    def generate_secure_bundle(
        self,
        selected_coins: List[Coin],
        announcements: List[Announcement],
        offered_amount: uint64,
        tail_str: Optional[str] = None,
    ) -> SpendBundle:
        announcement_assertions: List[List] = [[63, a.name()]
                                               for a in announcements]
        selected_coin_amount: int = sum([c.amount for c in selected_coins])
        non_primaries: List[Coin] = [] if len(
            selected_coins) < 2 else selected_coins[1:]
        inner_solution: List[List] = [
            [51, Offer.ph(), offered_amount],  # Offered coin
            [51, acs_ph,
             uint64(selected_coin_amount - offered_amount)],  # Change
            *announcement_assertions,
        ]

        if tail_str is None:
            bundle = SpendBundle(
                [
                    CoinSpend(
                        selected_coins[0],
                        acs,
                        Program.to(inner_solution),
                    ),
                    *[
                        CoinSpend(c, acs, Program.to([]))
                        for c in non_primaries
                    ],
                ],
                G2Element(),
            )
        else:
            spendable_cats: List[SpendableCAT] = [
                SpendableCAT(
                    c,
                    str_to_tail_hash(tail_str),
                    acs,
                    Program.to([
                        [51, 0, -113,
                         str_to_tail(tail_str),
                         Program.to([])],  # Use the TAIL rather than lineage
                        *(inner_solution if c == selected_coins[0] else []),
                    ]),
                ) for c in selected_coins
            ]
            bundle = unsigned_spend_bundle_for_spendable_cats(
                CAT_MOD, spendable_cats)

        return bundle
Esempio n. 6
0
 def to_json_dict_convenience(self) -> Dict[str, Any]:
     formatted = self.to_json_dict()
     formatted["status"] = TradeStatus(self.status).name
     offer_to_summarize: bytes = self.offer if self.taken_offer is None else self.taken_offer
     offer = Offer.from_bytes(offer_to_summarize)
     offered, requested = offer.summary()
     formatted["summary"] = {
         "offered": offered,
         "requested": requested,
     }
     formatted["pending"] = offer.get_pending_amounts()
     del formatted["offer"]
     return formatted
Esempio n. 7
0
async def get_offers(args: dict, wallet_client: WalletRpcClient,
                     fingerprint: int) -> None:
    id: Optional[str] = args.get("id", None)
    filepath: Optional[str] = args.get("filepath", None)
    exclude_my_offers: bool = args.get("exclude_my_offers", False)
    exclude_taken_offers: bool = args.get("exclude_taken_offers", False)
    include_completed: bool = args.get("include_completed", False)
    summaries: bool = args.get("summaries", False)
    reverse: bool = args.get("reverse", False)
    file_contents: bool = (filepath is not None) or summaries
    records: List[TradeRecord] = []
    if id is None:
        batch_size: int = 10
        start: int = 0
        end: int = start + batch_size

        # Traverse offers page by page
        while True:
            new_records: List[
                TradeRecord] = await wallet_client.get_all_offers(
                    start,
                    end,
                    reverse=reverse,
                    file_contents=file_contents,
                    exclude_my_offers=exclude_my_offers,
                    exclude_taken_offers=exclude_taken_offers,
                    include_completed=include_completed,
                )
            records.extend(new_records)

            # If fewer records were returned than requested, we're done
            if len(new_records) < batch_size:
                break

            start = end
            end += batch_size
    else:
        records = [
            await wallet_client.get_offer(bytes32.from_hexstr(id),
                                          file_contents)
        ]
        if filepath is not None:
            with open(pathlib.Path(filepath), "w") as file:
                file.write(Offer.from_bytes(records[0].offer).to_bech32())
                file.close()

    for record in records:
        await print_trade_record(record, wallet_client, summaries=summaries)
Esempio n. 8
0
    async def cancel_pending_offer_safely(
        self, trade_id: bytes32, fee: uint64 = uint64(0)
    ) -> Optional[List[TransactionRecord]]:
        """This will create a transaction that includes coins that were offered"""
        self.log.info(f"Secure-Cancel pending offer with id trade_id {trade_id.hex()}")
        trade = await self.trade_store.get_trade_record(trade_id)
        if trade is None:
            return None

        all_txs: List[TransactionRecord] = []
        fee_to_pay: uint64 = fee
        for coin in Offer.from_bytes(trade.offer).get_primary_coins():
            wallet = await self.wallet_state_manager.get_wallet_for_coin(coin.name())

            if wallet is None:
                continue
            new_ph = await wallet.get_new_puzzlehash()
            # This should probably not switch on whether or not we're spending a CAT but it has to for now
            if wallet.type() == WalletType.CAT:
                txs = await wallet.generate_signed_transaction(
                    [coin.amount], [new_ph], fee=fee_to_pay, coins={coin}, ignore_max_send_amount=True
                )
                all_txs.extend(txs)
            else:
                if fee_to_pay > coin.amount:
                    selected_coins: Set[Coin] = await wallet.select_coins(
                        uint64(fee_to_pay - coin.amount),
                        exclude=[coin],
                    )
                    selected_coins.add(coin)
                else:
                    selected_coins = {coin}
                tx = await wallet.generate_signed_transaction(
                    uint64(sum([c.amount for c in selected_coins]) - fee_to_pay),
                    new_ph,
                    fee=fee_to_pay,
                    coins=selected_coins,
                    ignore_max_send_amount=True,
                )
                all_txs.append(tx)
            fee_to_pay = uint64(0)

        for tx in all_txs:
            await self.wallet_state_manager.add_pending_transaction(tx_record=tx)

        await self.trade_store.set_status(trade_id, TradeStatus.PENDING_CANCEL, False)

        return all_txs
Esempio n. 9
0
    async def create_offer_for_ids(
            self,
            offer_dict: Dict[uint32, int],
            fee=uint64(0),
            validate_only: bool = False
    ) -> Tuple[Optional[Offer], TradeRecord]:
        send_dict: Dict[str, int] = {}
        for key in offer_dict:
            send_dict[str(key)] = offer_dict[key]

        res = await self.fetch("create_offer_for_ids", {
            "offer": send_dict,
            "validate_only": validate_only,
            "fee": fee
        })
        offer: Optional[Offer] = None if validate_only else Offer.from_bech32(
            res["offer"])
        offer_str: str = "" if offer is None else bytes(offer).hex()
        return offer, TradeRecord.from_json_dict_convenience(
            res["trade_record"], offer_str)
Esempio n. 10
0
    async def get_locked_coins(self, wallet_id: int = None) -> Dict[bytes32, WalletCoinRecord]:
        """Returns a dictionary of confirmed coins that are locked by a trade."""
        all_pending = []
        pending_accept = await self.get_offers_with_status(TradeStatus.PENDING_ACCEPT)
        pending_confirm = await self.get_offers_with_status(TradeStatus.PENDING_CONFIRM)
        pending_cancel = await self.get_offers_with_status(TradeStatus.PENDING_CANCEL)
        all_pending.extend(pending_accept)
        all_pending.extend(pending_confirm)
        all_pending.extend(pending_cancel)

        coins_of_interest = []
        for trade_offer in all_pending:
            coins_of_interest.extend([c.name() for c in Offer.from_bytes(trade_offer.offer).get_involved_coins()])

        result = {}
        coin_records = await self.wallet_state_manager.coin_store.get_multiple_coin_records(coins_of_interest)
        for record in coin_records:
            if wallet_id is None or record.wallet_id == wallet_id:
                result[record.name()] = record

        return result
Esempio n. 11
0
    async def get_all_offers(
        self,
        start: int = 0,
        end: int = 50,
        sort_key: str = None,
        reverse: bool = False,
        file_contents: bool = False,
        exclude_my_offers: bool = False,
        exclude_taken_offers: bool = False,
        include_completed: bool = False,
    ) -> List[TradeRecord]:
        res = await self.fetch(
            "get_all_offers",
            {
                "start": start,
                "end": end,
                "sort_key": sort_key,
                "reverse": reverse,
                "file_contents": file_contents,
                "exclude_my_offers": exclude_my_offers,
                "exclude_taken_offers": exclude_taken_offers,
                "include_completed": include_completed,
            },
        )

        records = []
        if file_contents:
            optional_offers = [
                bytes(Offer.from_bech32(o)).hex() for o in res["offers"]
            ]
        else:
            optional_offers = [""] * len(res["trade_records"])
        for record, offer in zip(res["trade_records"], optional_offers):
            records.append(
                TradeRecord.from_json_dict_convenience(record, offer))

        return records
Esempio n. 12
0
async def print_trade_record(record,
                             wallet_client: WalletRpcClient,
                             summaries: bool = False) -> None:
    print()
    print(f"Record with id: {record.trade_id}")
    print("---------------")
    print(f"Created at: {timestamp_to_time(record.created_at_time)}")
    print(f"Confirmed at: {record.confirmed_at_index}")
    print(
        f"Accepted at: {timestamp_to_time(record.accepted_at_time) if record.accepted_at_time else 'N/A'}"
    )
    print(f"Status: {TradeStatus(record.status).name}")
    if summaries:
        print("Summary:")
        offer = Offer.from_bytes(record.offer)
        offered, requested = offer.summary()
        print("  OFFERED:")
        await print_offer_summary(wallet_client, 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']}")
    print("---------------")
Esempio n. 13
0
    async def _create_offer_for_ids(
        self, offer_dict: Dict[Union[int, bytes32], int], fee: uint64 = uint64(0)
    ) -> Tuple[bool, Optional[Offer], Optional[str]]:
        """
        Offer is dictionary of wallet ids and amount
        """
        try:
            coins_to_offer: Dict[uint32, List[Coin]] = {}
            requested_payments: Dict[Optional[bytes32], List[Payment]] = {}
            for id, amount in offer_dict.items():
                if amount > 0:
                    if isinstance(id, int):
                        wallet_id = uint32(id)
                        wallet = self.wallet_state_manager.wallets[wallet_id]
                        p2_ph: bytes32 = await wallet.get_new_puzzlehash()
                        if wallet.type() == WalletType.STANDARD_WALLET:
                            key: Optional[bytes32] = None
                            memos: List[bytes] = []
                        elif wallet.type() == WalletType.CAT:
                            key = bytes32(bytes.fromhex(wallet.get_asset_id()))
                            memos = [p2_ph]
                        else:
                            raise ValueError(f"Offers are not implemented for {wallet.type()}")
                    else:
                        p2_ph = await self.wallet_state_manager.main_wallet.get_new_puzzlehash()
                        key = id
                        memos = [p2_ph]
                    requested_payments[key] = [Payment(p2_ph, uint64(amount), memos)]
                elif amount < 0:
                    assert isinstance(id, int)
                    wallet_id = uint32(id)
                    wallet = self.wallet_state_manager.wallets[wallet_id]
                    balance = await wallet.get_confirmed_balance()
                    if balance < abs(amount):
                        raise Exception(f"insufficient funds in wallet {wallet_id}")
                    coins_to_offer[wallet_id] = await wallet.select_coins(uint64(abs(amount)))
                elif amount == 0:
                    raise ValueError("You cannot offer nor request 0 amount of something")

            all_coins: List[Coin] = [c for coins in coins_to_offer.values() for c in coins]
            notarized_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments(
                requested_payments, all_coins
            )
            announcements_to_assert = Offer.calculate_announcements(notarized_payments)

            all_transactions: List[TransactionRecord] = []
            fee_left_to_pay: uint64 = fee
            for wallet_id, selected_coins in coins_to_offer.items():
                wallet = self.wallet_state_manager.wallets[wallet_id]
                # This should probably not switch on whether or not we're spending a CAT but it has to for now

                if wallet.type() == WalletType.CAT:
                    txs = await wallet.generate_signed_transaction(
                        [abs(offer_dict[int(wallet_id)])],
                        [Offer.ph()],
                        fee=fee_left_to_pay,
                        coins=set(selected_coins),
                        puzzle_announcements_to_consume=announcements_to_assert,
                    )
                    all_transactions.extend(txs)
                else:
                    tx = await wallet.generate_signed_transaction(
                        abs(offer_dict[int(wallet_id)]),
                        Offer.ph(),
                        fee=fee_left_to_pay,
                        coins=set(selected_coins),
                        puzzle_announcements_to_consume=announcements_to_assert,
                    )
                    all_transactions.append(tx)

                fee_left_to_pay = uint64(0)

            transaction_bundles: List[Optional[SpendBundle]] = [tx.spend_bundle for tx in all_transactions]
            total_spend_bundle = SpendBundle.aggregate(list(filter(lambda b: b is not None, transaction_bundles)))
            offer = Offer(notarized_payments, total_spend_bundle)
            return True, offer, None

        except Exception as e:
            tb = traceback.format_exc()
            self.log.error(f"Error with creating trade offer: {type(e)}{tb}")
            return False, None, str(e)
Esempio n. 14
0
    async def test_complex_offer(self, setup_sim):
        sim, sim_client = setup_sim

        try:
            coins_needed: Dict[Optional[str], List[int]] = {
                None: [500, 400, 300],
                "red": [250, 100],
                "blue": [3000],
            }
            all_coins: Dict[Optional[str],
                            List[Coin]] = await self.generate_coins(
                                sim, sim_client, coins_needed)
            chia_coins: List[Coin] = all_coins[None]
            red_coins: List[Coin] = all_coins["red"]
            blue_coins: List[Coin] = all_coins["blue"]

            # Create an XCH Offer for RED
            chia_requested_payments: Dict[Optional[bytes32], List[Payment]] = {
                str_to_tail_hash("red"): [
                    Payment(acs_ph, 100, [b"memo"]),
                    Payment(acs_ph, 200, [b"memo"]),
                ]
            }

            chia_requested_payments: Dict[
                Optional[bytes32],
                List[NotarizedPayment]] = Offer.notarize_payments(
                    chia_requested_payments, chia_coins)
            chia_announcements: List[
                Announcement] = Offer.calculate_announcements(
                    chia_requested_payments)
            chia_secured_bundle: SpendBundle = self.generate_secure_bundle(
                chia_coins, chia_announcements, 1000)
            chia_offer = Offer(chia_requested_payments, chia_secured_bundle)
            assert not chia_offer.is_valid()

            # Create a RED Offer for XCH
            red_requested_payments: Dict[Optional[bytes32], List[Payment]] = {
                None: [
                    Payment(acs_ph, 300, [b"red memo"]),
                    Payment(acs_ph, 400, [b"red memo"]),
                ]
            }

            red_requested_payments: Dict[
                Optional[bytes32],
                List[NotarizedPayment]] = Offer.notarize_payments(
                    red_requested_payments, red_coins)
            red_announcements: List[
                Announcement] = Offer.calculate_announcements(
                    red_requested_payments)
            red_secured_bundle: SpendBundle = self.generate_secure_bundle(
                red_coins, red_announcements, 350, tail_str="red")
            red_offer = Offer(red_requested_payments, red_secured_bundle)
            assert not red_offer.is_valid()

            # Test aggregation of offers
            new_offer = Offer.aggregate([chia_offer, red_offer])
            assert new_offer.get_offered_amounts() == {
                None: 1000,
                str_to_tail_hash("red"): 350
            }
            assert new_offer.get_requested_amounts() == {
                None: 700,
                str_to_tail_hash("red"): 300
            }
            assert new_offer.is_valid()

            # Create yet another offer of BLUE for XCH and RED
            blue_requested_payments: Dict[Optional[bytes32], List[Payment]] = {
                None: [
                    Payment(acs_ph, 200, [b"blue memo"]),
                ],
                str_to_tail_hash("red"): [
                    Payment(acs_ph, 50, [b"blue memo"]),
                ],
            }

            blue_requested_payments: Dict[
                Optional[bytes32],
                List[NotarizedPayment]] = Offer.notarize_payments(
                    blue_requested_payments, blue_coins)
            blue_announcements: List[
                Announcement] = Offer.calculate_announcements(
                    blue_requested_payments)
            blue_secured_bundle: SpendBundle = self.generate_secure_bundle(
                blue_coins, blue_announcements, 2000, tail_str="blue")
            blue_offer = Offer(blue_requested_payments, blue_secured_bundle)
            assert not blue_offer.is_valid()

            # Test a re-aggregation
            new_offer: Offer = Offer.aggregate([new_offer, blue_offer])
            assert new_offer.get_offered_amounts() == {
                None: 1000,
                str_to_tail_hash("red"): 350,
                str_to_tail_hash("blue"): 2000,
            }
            assert new_offer.get_requested_amounts() == {
                None: 900,
                str_to_tail_hash("red"): 350
            }
            assert new_offer.summary() == (
                {
                    "xch": 1000,
                    str_to_tail_hash("red").hex(): 350,
                    str_to_tail_hash("blue").hex(): 2000,
                },
                {
                    "xch": 900,
                    str_to_tail_hash("red").hex(): 350
                },
            )
            assert new_offer.get_pending_amounts() == {
                "xch": 1200,
                str_to_tail_hash("red").hex(): 350,
                str_to_tail_hash("blue").hex(): 3000,
            }
            assert new_offer.is_valid()

            # Test (de)serialization
            assert Offer.from_bytes(bytes(new_offer)) == new_offer

            # Test compression
            assert Offer.from_compressed(new_offer.compress()) == new_offer

            # Make sure we can actually spend the offer once it's valid
            arbitrage_ph: bytes32 = Program.to([3, [], [], 1]).get_tree_hash()
            offer_bundle: SpendBundle = new_offer.to_valid_spend(arbitrage_ph)
            result = await sim_client.push_tx(offer_bundle)
            assert result == (MempoolInclusionStatus.SUCCESS, None)
            self.cost["complex offer"] = cost_of_spend_bundle(offer_bundle)
            await sim.farm_block()
        finally:
            await sim.close()
Esempio n. 15
0
 async def take_offer(self, offer: Offer, fee=uint64(0)) -> TradeRecord:
     res = await self.fetch("take_offer", {
         "offer": offer.to_bech32(),
         "fee": fee
     })
     return TradeRecord.from_json_dict_convenience(res["trade_record"])
Esempio n. 16
0
    async def test_cat_trades(self, wallets_prefarm):
        wallet_node_maker, wallet_node_taker, full_node = wallets_prefarm
        wallet_maker = wallet_node_maker.wallet_state_manager.main_wallet
        wallet_taker = wallet_node_taker.wallet_state_manager.main_wallet

        # Create two new CATs, one in each wallet
        async with wallet_node_maker.wallet_state_manager.lock:
            cat_wallet_maker: CATWallet = await CATWallet.create_new_cat_wallet(
                wallet_node_maker.wallet_state_manager, wallet_maker,
                {"identifier": "genesis_by_id"}, uint64(100))
            await asyncio.sleep(1)

        async with wallet_node_taker.wallet_state_manager.lock:
            new_cat_wallet_taker: CATWallet = await CATWallet.create_new_cat_wallet(
                wallet_node_taker.wallet_state_manager, wallet_taker,
                {"identifier": "genesis_by_id"}, uint64(100))
            await asyncio.sleep(1)

        for i in range(1, buffer_blocks):
            await full_node.farm_new_transaction_block(
                FarmNewBlockProtocol(token_bytes()))
        await time_out_assert(15, cat_wallet_maker.get_confirmed_balance, 100)
        await time_out_assert(15, cat_wallet_maker.get_unconfirmed_balance,
                              100)
        await time_out_assert(15, new_cat_wallet_taker.get_confirmed_balance,
                              100)
        await time_out_assert(15, new_cat_wallet_taker.get_unconfirmed_balance,
                              100)

        # Add the taker's CAT to the maker's wallet
        assert cat_wallet_maker.cat_info.my_tail is not None
        assert new_cat_wallet_taker.cat_info.my_tail is not None
        new_cat_wallet_maker: CATWallet = await CATWallet.create_wallet_for_cat(
            wallet_node_maker.wallet_state_manager, wallet_maker,
            new_cat_wallet_taker.get_asset_id())

        # Create the trade parameters
        MAKER_CHIA_BALANCE = 20 * 1000000000000 - 100
        TAKER_CHIA_BALANCE = 20 * 1000000000000 - 100
        await time_out_assert(25, wallet_maker.get_confirmed_balance,
                              MAKER_CHIA_BALANCE)
        await time_out_assert(25, wallet_taker.get_unconfirmed_balance,
                              TAKER_CHIA_BALANCE)
        MAKER_CAT_BALANCE = 100
        MAKER_NEW_CAT_BALANCE = 0
        TAKER_CAT_BALANCE = 0
        TAKER_NEW_CAT_BALANCE = 100

        chia_for_cat = {
            wallet_maker.id(): -1,
            new_cat_wallet_maker.id():
            2,  # This is the CAT that the taker made
        }
        cat_for_chia = {
            wallet_maker.id(): 3,
            cat_wallet_maker.id():
            -4,  # The taker has no knowledge of this CAT yet
        }
        cat_for_cat = {
            cat_wallet_maker.id(): -5,
            new_cat_wallet_maker.id(): 6,
        }
        chia_for_multiple_cat = {
            wallet_maker.id(): -7,
            cat_wallet_maker.id(): 8,
            new_cat_wallet_maker.id(): 9,
        }
        multiple_cat_for_chia = {
            wallet_maker.id(): 10,
            cat_wallet_maker.id(): -11,
            new_cat_wallet_maker.id(): -12,
        }
        chia_and_cat_for_cat = {
            wallet_maker.id(): -13,
            cat_wallet_maker.id(): -14,
            new_cat_wallet_maker.id(): 15,
        }

        trade_manager_maker = wallet_node_maker.wallet_state_manager.trade_manager
        trade_manager_taker = wallet_node_taker.wallet_state_manager.trade_manager

        # Execute all of the trades
        # chia_for_cat
        success, trade_make, error = await trade_manager_maker.create_offer_for_ids(
            chia_for_cat, fee=uint64(1))
        await asyncio.sleep(1)
        assert error is None
        assert success is True
        assert trade_make is not None

        success, trade_take, error = await trade_manager_taker.respond_to_offer(
            Offer.from_bytes(trade_make.offer), fee=uint64(1))
        await asyncio.sleep(1)
        assert error is None
        assert success is True
        assert trade_take is not None

        MAKER_CHIA_BALANCE -= 2  # -1 and -1 for fee
        MAKER_NEW_CAT_BALANCE += 2
        TAKER_CHIA_BALANCE += 0  # +1 and -1 for fee
        TAKER_NEW_CAT_BALANCE -= 2

        await time_out_assert(15, wallet_taker.get_unconfirmed_balance,
                              TAKER_CHIA_BALANCE)
        await time_out_assert(15, new_cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_NEW_CAT_BALANCE)

        for i in range(0, buffer_blocks):
            await full_node.farm_new_transaction_block(
                FarmNewBlockProtocol(token_bytes()))

        await time_out_assert(15, wallet_maker.get_confirmed_balance,
                              MAKER_CHIA_BALANCE)
        await time_out_assert(15, wallet_maker.get_unconfirmed_balance,
                              MAKER_CHIA_BALANCE)
        await time_out_assert(15, new_cat_wallet_maker.get_confirmed_balance,
                              MAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, new_cat_wallet_maker.get_unconfirmed_balance,
                              MAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, wallet_taker.get_confirmed_balance,
                              TAKER_CHIA_BALANCE)
        await time_out_assert(15, wallet_taker.get_unconfirmed_balance,
                              TAKER_CHIA_BALANCE)
        await time_out_assert(15, new_cat_wallet_taker.get_confirmed_balance,
                              TAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, new_cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_NEW_CAT_BALANCE)

        async def get_trade_and_status(trade_manager, trade) -> TradeStatus:
            trade_rec = await trade_manager.get_trade_by_id(trade.trade_id)
            return TradeStatus(trade_rec.status)

        await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED,
                              trade_manager_maker, trade_make)
        await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED,
                              trade_manager_taker, trade_take)

        maker_txs = await wallet_node_maker.wallet_state_manager.tx_store.get_transactions_by_trade_id(
            trade_make.trade_id)
        taker_txs = await wallet_node_taker.wallet_state_manager.tx_store.get_transactions_by_trade_id(
            trade_take.trade_id)
        assert len(
            maker_txs
        ) == 1  # The other side will show up as a regular incoming transaction
        assert len(
            taker_txs
        ) == 3  # One for each: the outgoing CAT, the incoming chia, and the outgoing chia fee

        # cat_for_chia
        success, trade_make, error = await trade_manager_maker.create_offer_for_ids(
            cat_for_chia)
        await asyncio.sleep(1)
        assert error is None
        assert success is True
        assert trade_make is not None

        success, trade_take, error = await trade_manager_taker.respond_to_offer(
            Offer.from_bytes(trade_make.offer))
        await asyncio.sleep(1)
        assert error is None
        assert success is True
        assert trade_take is not None

        MAKER_CAT_BALANCE -= 4
        MAKER_CHIA_BALANCE += 3
        TAKER_CAT_BALANCE += 4
        TAKER_CHIA_BALANCE -= 3

        cat_wallet_taker: CATWallet = await wallet_node_taker.wallet_state_manager.get_wallet_for_asset_id(
            cat_wallet_maker.get_asset_id())

        await time_out_assert(15, wallet_taker.get_unconfirmed_balance,
                              TAKER_CHIA_BALANCE)
        await time_out_assert(15, cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_CAT_BALANCE)

        for i in range(0, buffer_blocks):
            await full_node.farm_new_transaction_block(
                FarmNewBlockProtocol(token_bytes()))

        await time_out_assert(15, wallet_maker.get_confirmed_balance,
                              MAKER_CHIA_BALANCE)
        await time_out_assert(15, wallet_maker.get_unconfirmed_balance,
                              MAKER_CHIA_BALANCE)
        await time_out_assert(15, cat_wallet_maker.get_confirmed_balance,
                              MAKER_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_maker.get_unconfirmed_balance,
                              MAKER_CAT_BALANCE)
        await time_out_assert(15, wallet_taker.get_confirmed_balance,
                              TAKER_CHIA_BALANCE)
        await time_out_assert(15, wallet_taker.get_unconfirmed_balance,
                              TAKER_CHIA_BALANCE)
        await time_out_assert(15, cat_wallet_taker.get_confirmed_balance,
                              TAKER_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_CAT_BALANCE)
        await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED,
                              trade_manager_maker, trade_make)
        await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED,
                              trade_manager_taker, trade_take)

        maker_txs = await wallet_node_maker.wallet_state_manager.tx_store.get_transactions_by_trade_id(
            trade_make.trade_id)
        taker_txs = await wallet_node_taker.wallet_state_manager.tx_store.get_transactions_by_trade_id(
            trade_take.trade_id)
        assert len(
            maker_txs
        ) == 1  # The other side will show up as a regular incoming transaction
        assert len(taker_txs
                   ) == 2  # One for each: the outgoing chia, the incoming CAT

        # cat_for_cat
        success, trade_make, error = await trade_manager_maker.create_offer_for_ids(
            cat_for_cat)
        await asyncio.sleep(1)
        assert error is None
        assert success is True
        assert trade_make is not None
        success, trade_take, error = await trade_manager_taker.respond_to_offer(
            Offer.from_bytes(trade_make.offer))
        await asyncio.sleep(1)
        assert error is None
        assert success is True
        assert trade_take is not None

        MAKER_CAT_BALANCE -= 5
        MAKER_NEW_CAT_BALANCE += 6
        TAKER_CAT_BALANCE += 5
        TAKER_NEW_CAT_BALANCE -= 6

        await time_out_assert(15, new_cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_CAT_BALANCE)

        for i in range(0, buffer_blocks):
            await full_node.farm_new_transaction_block(
                FarmNewBlockProtocol(token_bytes()))

        await time_out_assert(15, new_cat_wallet_maker.get_confirmed_balance,
                              MAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, new_cat_wallet_maker.get_unconfirmed_balance,
                              MAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_maker.get_confirmed_balance,
                              MAKER_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_maker.get_unconfirmed_balance,
                              MAKER_CAT_BALANCE)
        await time_out_assert(15, new_cat_wallet_taker.get_confirmed_balance,
                              TAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, new_cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_taker.get_confirmed_balance,
                              TAKER_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_CAT_BALANCE)
        await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED,
                              trade_manager_maker, trade_make)
        await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED,
                              trade_manager_taker, trade_take)

        # chia_for_multiple_cat
        success, trade_make, error = await trade_manager_maker.create_offer_for_ids(
            chia_for_multiple_cat)
        await asyncio.sleep(1)
        assert error is None
        assert success is True
        assert trade_make is not None
        success, trade_take, error = await trade_manager_taker.respond_to_offer(
            Offer.from_bytes(trade_make.offer))
        await asyncio.sleep(1)
        assert error is None
        assert success is True
        assert trade_take is not None

        MAKER_CHIA_BALANCE -= 7
        MAKER_CAT_BALANCE += 8
        MAKER_NEW_CAT_BALANCE += 9
        TAKER_CHIA_BALANCE += 7
        TAKER_CAT_BALANCE -= 8
        TAKER_NEW_CAT_BALANCE -= 9

        await time_out_assert(15, new_cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_CAT_BALANCE)

        for i in range(0, buffer_blocks):
            await full_node.farm_new_transaction_block(
                FarmNewBlockProtocol(token_bytes()))

        await time_out_assert(15, new_cat_wallet_maker.get_confirmed_balance,
                              MAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, new_cat_wallet_maker.get_unconfirmed_balance,
                              MAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_maker.get_confirmed_balance,
                              MAKER_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_maker.get_unconfirmed_balance,
                              MAKER_CAT_BALANCE)
        await time_out_assert(15, new_cat_wallet_taker.get_confirmed_balance,
                              TAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, new_cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_taker.get_confirmed_balance,
                              TAKER_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_CAT_BALANCE)
        await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED,
                              trade_manager_maker, trade_make)
        await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED,
                              trade_manager_taker, trade_take)

        # multiple_cat_for_chia
        success, trade_make, error = await trade_manager_maker.create_offer_for_ids(
            multiple_cat_for_chia)
        await asyncio.sleep(1)
        assert error is None
        assert success is True
        assert trade_make is not None
        success, trade_take, error = await trade_manager_taker.respond_to_offer(
            Offer.from_bytes(trade_make.offer))
        await asyncio.sleep(1)
        assert error is None
        assert success is True
        assert trade_take is not None

        MAKER_CAT_BALANCE -= 11
        MAKER_NEW_CAT_BALANCE -= 12
        MAKER_CHIA_BALANCE += 10
        TAKER_CAT_BALANCE += 11
        TAKER_NEW_CAT_BALANCE += 12
        TAKER_CHIA_BALANCE -= 10

        await time_out_assert(15, new_cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_CAT_BALANCE)

        for i in range(0, buffer_blocks):
            await full_node.farm_new_transaction_block(
                FarmNewBlockProtocol(token_bytes()))

        await time_out_assert(15, new_cat_wallet_maker.get_confirmed_balance,
                              MAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, new_cat_wallet_maker.get_unconfirmed_balance,
                              MAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_maker.get_confirmed_balance,
                              MAKER_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_maker.get_unconfirmed_balance,
                              MAKER_CAT_BALANCE)
        await time_out_assert(15, new_cat_wallet_taker.get_confirmed_balance,
                              TAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, new_cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_taker.get_confirmed_balance,
                              TAKER_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_CAT_BALANCE)
        await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED,
                              trade_manager_maker, trade_make)
        await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED,
                              trade_manager_taker, trade_take)

        # chia_and_cat_for_cat
        success, trade_make, error = await trade_manager_maker.create_offer_for_ids(
            chia_and_cat_for_cat)
        await asyncio.sleep(1)
        assert error is None
        assert success is True
        assert trade_make is not None
        success, trade_take, error = await trade_manager_taker.respond_to_offer(
            Offer.from_bytes(trade_make.offer))
        await asyncio.sleep(1)
        assert error is None
        assert success is True
        assert trade_take is not None

        MAKER_CHIA_BALANCE -= 13
        MAKER_CAT_BALANCE -= 14
        MAKER_NEW_CAT_BALANCE += 15
        TAKER_CHIA_BALANCE += 13
        TAKER_CAT_BALANCE += 14
        TAKER_NEW_CAT_BALANCE -= 15

        await time_out_assert(15, new_cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_CAT_BALANCE)

        for i in range(0, buffer_blocks):
            await full_node.farm_new_transaction_block(
                FarmNewBlockProtocol(token_bytes()))

        await time_out_assert(15, new_cat_wallet_maker.get_confirmed_balance,
                              MAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, new_cat_wallet_maker.get_unconfirmed_balance,
                              MAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_maker.get_confirmed_balance,
                              MAKER_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_maker.get_unconfirmed_balance,
                              MAKER_CAT_BALANCE)
        await time_out_assert(15, new_cat_wallet_taker.get_confirmed_balance,
                              TAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, new_cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_NEW_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_taker.get_confirmed_balance,
                              TAKER_CAT_BALANCE)
        await time_out_assert(15, cat_wallet_taker.get_unconfirmed_balance,
                              TAKER_CAT_BALANCE)
        await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED,
                              trade_manager_maker, trade_make)
        await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED,
                              trade_manager_taker, trade_take)
Esempio n. 17
0
    async def respond_to_offer(self, offer: Offer, fee=uint64(0)) -> Tuple[bool, Optional[TradeRecord], Optional[str]]:
        take_offer_dict: Dict[Union[bytes32, int], int] = {}
        arbitrage: Dict[Optional[bytes32], int] = offer.arbitrage()
        for asset_id, amount in arbitrage.items():
            if asset_id is None:
                wallet = self.wallet_state_manager.main_wallet
                key: Union[bytes32, int] = int(wallet.id())
            else:
                wallet = await self.wallet_state_manager.get_wallet_for_asset_id(asset_id.hex())
                if wallet is None and amount < 0:
                    return False, None, f"Do not have a CAT of asset ID: {asset_id} to fulfill offer"
                elif wallet is None:
                    key = asset_id
                else:
                    key = int(wallet.id())
            take_offer_dict[key] = amount

        # First we validate that all of the coins in this offer exist
        valid: bool = await self.check_offer_validity(offer)
        if not valid:
            return False, None, "This offer is no longer valid"

        success, take_offer, error = await self._create_offer_for_ids(take_offer_dict, fee=fee)
        if not success or take_offer is None:
            return False, None, error

        complete_offer = Offer.aggregate([offer, take_offer])
        assert complete_offer.is_valid()
        final_spend_bundle: SpendBundle = complete_offer.to_valid_spend()

        await self.maybe_create_wallets_for_offer(complete_offer)

        tx_records: List[TransactionRecord] = await self.calculate_tx_records_for_offer(complete_offer, True)

        trade_record: TradeRecord = TradeRecord(
            confirmed_at_index=uint32(0),
            accepted_at_time=uint64(int(time.time())),
            created_at_time=uint64(int(time.time())),
            is_my_offer=False,
            sent=uint32(0),
            offer=bytes(complete_offer),
            taken_offer=bytes(offer),
            coins_of_interest=complete_offer.get_involved_coins(),
            trade_id=complete_offer.name(),
            status=uint32(TradeStatus.PENDING_CONFIRM.value),
            sent_to=[],
        )

        await self.save_trade(trade_record)

        # Dummy transaction for the sake of the wallet push
        push_tx = TransactionRecord(
            confirmed_at_height=uint32(0),
            created_at_time=uint64(int(time.time())),
            to_puzzle_hash=bytes32([1] * 32),
            amount=uint64(0),
            fee_amount=uint64(0),
            confirmed=False,
            sent=uint32(0),
            spend_bundle=final_spend_bundle,
            additions=[],
            removals=[],
            wallet_id=uint32(0),
            sent_to=[],
            trade_id=bytes32([1] * 32),
            type=uint32(TransactionType.OUTGOING_TRADE.value),
            name=final_spend_bundle.name(),
            memos=[],
        )
        await self.wallet_state_manager.add_pending_transaction(push_tx)
        for tx in tx_records:
            await self.wallet_state_manager.add_transaction(tx)

        return True, trade_record, None
Esempio n. 18
0
    async def calculate_tx_records_for_offer(self, offer: Offer, validate: bool) -> List[TransactionRecord]:
        if validate:
            final_spend_bundle: SpendBundle = offer.to_valid_spend()
        else:
            final_spend_bundle = offer.bundle

        settlement_coins: List[Coin] = [c for coins in offer.get_offered_coins().values() for c in coins]
        settlement_coin_ids: List[bytes32] = [c.name() for c in settlement_coins]
        additions: List[Coin] = final_spend_bundle.not_ephemeral_additions()
        removals: List[Coin] = final_spend_bundle.removals()
        all_fees = uint64(final_spend_bundle.fees())

        txs = []

        addition_dict: Dict[uint32, List[Coin]] = {}
        for addition in additions:
            wallet_info = await self.wallet_state_manager.get_wallet_id_for_puzzle_hash(addition.puzzle_hash)
            if wallet_info is not None:
                wallet_id, _ = wallet_info
                if addition.parent_coin_info in settlement_coin_ids:
                    wallet = self.wallet_state_manager.wallets[wallet_id]
                    to_puzzle_hash = await wallet.convert_puzzle_hash(addition.puzzle_hash)
                    txs.append(
                        TransactionRecord(
                            confirmed_at_height=uint32(0),
                            created_at_time=uint64(int(time.time())),
                            to_puzzle_hash=to_puzzle_hash,
                            amount=addition.amount,
                            fee_amount=uint64(0),
                            confirmed=False,
                            sent=uint32(10),
                            spend_bundle=None,
                            additions=[addition],
                            removals=[],
                            wallet_id=wallet_id,
                            sent_to=[],
                            trade_id=offer.name(),
                            type=uint32(TransactionType.INCOMING_TRADE.value),
                            name=std_hash(final_spend_bundle.name() + addition.name()),
                            memos=[],
                        )
                    )
                else:  # This is change
                    addition_dict.setdefault(wallet_id, [])
                    addition_dict[wallet_id].append(addition)

        # While we want additions to show up as separate records, removals of the same wallet should show as one
        removal_dict: Dict[uint32, List[Coin]] = {}
        for removal in removals:
            wallet_info = await self.wallet_state_manager.get_wallet_id_for_puzzle_hash(removal.puzzle_hash)
            if wallet_info is not None:
                wallet_id, _ = wallet_info
                removal_dict.setdefault(wallet_id, [])
                removal_dict[wallet_id].append(removal)

        for wid, grouped_removals in removal_dict.items():
            wallet = self.wallet_state_manager.wallets[wid]
            to_puzzle_hash = bytes32([1] * 32)  # We use all zeros to be clear not to send here
            removal_tree_hash = Program.to([rem.as_list() for rem in grouped_removals]).get_tree_hash()
            # We also need to calculate the sent amount
            removed: int = sum(c.amount for c in grouped_removals)
            change_coins: List[Coin] = addition_dict[wid] if wid in addition_dict else []
            change_amount: int = sum(c.amount for c in change_coins)
            sent_amount: int = removed - change_amount
            txs.append(
                TransactionRecord(
                    confirmed_at_height=uint32(0),
                    created_at_time=uint64(int(time.time())),
                    to_puzzle_hash=to_puzzle_hash,
                    amount=uint64(sent_amount),
                    fee_amount=all_fees,
                    confirmed=False,
                    sent=uint32(10),
                    spend_bundle=None,
                    additions=change_coins,
                    removals=grouped_removals,
                    wallet_id=wallet.id(),
                    sent_to=[],
                    trade_id=offer.name(),
                    type=uint32(TransactionType.OUTGOING_TRADE.value),
                    name=std_hash(final_spend_bundle.name() + removal_tree_hash),
                    memos=[],
                )
            )

        return txs
Esempio n. 19
0
 async def get_offer_summary(self,
                             offer: Offer) -> Dict[str, Dict[str, int]]:
     res = await self.fetch("get_offer_summary",
                            {"offer": offer.to_bech32()})
     return res["summary"]
Esempio n. 20
0
 async def check_offer_validity(self, offer: Offer) -> bool:
     res = await self.fetch("check_offer_validity",
                            {"offer": offer.to_bech32()})
     return res["valid"]
Esempio n. 21
0
    async def test_trade_cancellation(self, wallets_prefarm):
        wallet_node_maker, wallet_node_taker, full_node = wallets_prefarm
        wallet_maker = wallet_node_maker.wallet_state_manager.main_wallet
        wallet_taker = wallet_node_taker.wallet_state_manager.main_wallet

        async with wallet_node_maker.wallet_state_manager.lock:
            cat_wallet_maker: CATWallet = await CATWallet.create_new_cat_wallet(
                wallet_node_maker.wallet_state_manager, wallet_maker,
                {"identifier": "genesis_by_id"}, uint64(100))
            tx_queue: List[
                TransactionRecord] = await wallet_node_maker.wallet_state_manager.tx_store.get_not_sent(
                )
            await time_out_assert(15, tx_in_pool, True,
                                  full_node.full_node.mempool_manager,
                                  tx_queue[0].spend_bundle.name())

        for i in range(1, buffer_blocks):
            await full_node.farm_new_transaction_block(
                FarmNewBlockProtocol(token_bytes()))

        await time_out_assert(15, cat_wallet_maker.get_confirmed_balance, 100)
        await time_out_assert(15, cat_wallet_maker.get_unconfirmed_balance,
                              100)
        MAKER_CHIA_BALANCE = 20 * 1000000000000 - 100
        MAKER_CAT_BALANCE = 100
        TAKER_CHIA_BALANCE = 20 * 1000000000000
        await time_out_assert(15, wallet_maker.get_confirmed_balance,
                              MAKER_CHIA_BALANCE)

        cat_for_chia = {
            wallet_maker.id(): 1,
            cat_wallet_maker.id(): -2,
        }

        chia_for_cat = {
            wallet_maker.id(): -3,
            cat_wallet_maker.id(): 4,
        }

        trade_manager_maker = wallet_node_maker.wallet_state_manager.trade_manager
        trade_manager_taker = wallet_node_taker.wallet_state_manager.trade_manager

        async def get_trade_and_status(trade_manager, trade) -> TradeStatus:
            trade_rec = await trade_manager.get_trade_by_id(trade.trade_id)
            return TradeStatus(trade_rec.status)

        success, trade_make, error = await trade_manager_maker.create_offer_for_ids(
            cat_for_chia)
        await asyncio.sleep(1)
        assert error is None
        assert success is True
        assert trade_make is not None

        await trade_manager_maker.cancel_pending_offer(trade_make.trade_id)
        await time_out_assert(15, get_trade_and_status, TradeStatus.CANCELLED,
                              trade_manager_maker, trade_make)

        # Due to current mempool rules, trying to force a take out of the mempool with a cancel will not work.
        # Uncomment this when/if it does

        # success, trade_take, error = await trade_manager_taker.respond_to_offer(Offer.from_bytes(trade_make.offer))
        # await asyncio.sleep(1)
        # assert error is None
        # assert success is True
        # assert trade_take is not None
        # await time_out_assert(15, get_trade_and_status, TradeStatus.PENDING_CONFIRM, trade_manager_taker, trade_take)
        # await time_out_assert(
        #     15,
        #     tx_in_pool,
        #     True,
        #     full_node.full_node.mempool_manager,
        #     Offer.from_bytes(trade_take.offer).to_valid_spend().name(),
        # )

        FEE = uint64(2000000000000)

        txs = await trade_manager_maker.cancel_pending_offer_safely(
            trade_make.trade_id, fee=FEE)
        await time_out_assert(15, get_trade_and_status,
                              TradeStatus.PENDING_CANCEL, trade_manager_maker,
                              trade_make)
        for tx in txs:
            if tx.spend_bundle is not None:
                await time_out_assert(15, tx_in_pool, True,
                                      full_node.full_node.mempool_manager,
                                      tx.spend_bundle.name())

        for i in range(1, buffer_blocks):
            await full_node.farm_new_transaction_block(
                FarmNewBlockProtocol(token_bytes()))

        await time_out_assert(15, get_trade_and_status, TradeStatus.CANCELLED,
                              trade_manager_maker, trade_make)
        # await time_out_assert(15, get_trade_and_status, TradeStatus.FAILED, trade_manager_taker, trade_take)

        await time_out_assert(15, wallet_maker.get_pending_change_balance, 0)
        await time_out_assert(15, wallet_maker.get_confirmed_balance,
                              MAKER_CHIA_BALANCE - FEE)
        await time_out_assert(15, cat_wallet_maker.get_confirmed_balance,
                              MAKER_CAT_BALANCE)
        await time_out_assert(15, wallet_taker.get_confirmed_balance,
                              TAKER_CHIA_BALANCE)

        success, trade_take, error = await trade_manager_taker.respond_to_offer(
            Offer.from_bytes(trade_make.offer))
        await asyncio.sleep(1)
        assert error is not None
        assert success is False
        assert trade_take is None

        # Now we're going to create the other way around for test coverage sake
        success, trade_make, error = await trade_manager_maker.create_offer_for_ids(
            chia_for_cat)
        await asyncio.sleep(1)
        assert error is None
        assert success is True
        assert trade_make is not None

        # This take should fail since we have no CATs to fulfill it with
        success, trade_take, error = await trade_manager_taker.respond_to_offer(
            Offer.from_bytes(trade_make.offer))
        await asyncio.sleep(1)
        assert error is not None
        assert success is False
        assert trade_take is None

        txs = await trade_manager_maker.cancel_pending_offer_safely(
            trade_make.trade_id, fee=uint64(0))
        await time_out_assert(15, get_trade_and_status,
                              TradeStatus.PENDING_CANCEL, trade_manager_maker,
                              trade_make)
        for tx in txs:
            if tx.spend_bundle is not None:
                await time_out_assert(15, tx_in_pool, True,
                                      full_node.full_node.mempool_manager,
                                      tx.spend_bundle.name())

        for i in range(1, buffer_blocks):
            await full_node.farm_new_transaction_block(
                FarmNewBlockProtocol(token_bytes()))

        await time_out_assert(15, get_trade_and_status, TradeStatus.CANCELLED,
                              trade_manager_maker, trade_make)