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