Beispiel #1
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)
    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()