async def do_spend(
        self,
        sim: SpendSim,
        sim_client: SimClient,
        tail: Program,
        coins: List[Coin],
        lineage_proofs: List[Program],
        inner_solutions: List[Program],
        expected_result: Tuple[MempoolInclusionStatus, Err],
        reveal_limitations_program: bool = True,
        signatures: List[G2Element] = [],
        extra_deltas: Optional[List[int]] = None,
        additional_spends: List[SpendBundle] = [],
        limitations_solutions: Optional[List[Program]] = None,
        cost_str: str = "",
    ):
        if limitations_solutions is None:
            limitations_solutions = [Program.to([])] * len(coins)
        if extra_deltas is None:
            extra_deltas = [0] * len(coins)

        spendable_cat_list: List[SpendableCAT] = []
        for coin, innersol, proof, limitations_solution, extra_delta in zip(
            coins, inner_solutions, lineage_proofs, limitations_solutions, extra_deltas
        ):
            spendable_cat_list.append(
                SpendableCAT(
                    coin,
                    tail.get_tree_hash(),
                    acs,
                    innersol,
                    limitations_solution=limitations_solution,
                    lineage_proof=proof,
                    extra_delta=extra_delta,
                    limitations_program_reveal=tail if reveal_limitations_program else Program.to([]),
                )
            )

        spend_bundle: SpendBundle = unsigned_spend_bundle_for_spendable_cats(
            CAT_MOD,
            spendable_cat_list,
        )
        agg_sig = AugSchemeMPL.aggregate(signatures)
        result = await sim_client.push_tx(
            SpendBundle.aggregate(
                [
                    *additional_spends,
                    spend_bundle,
                    SpendBundle([], agg_sig),  # "Signing" the spend bundle
                ]
            )
        )
        assert result == expected_result
        self.cost[cost_str] = cost_of_spend_bundle(spend_bundle)
        await sim.farm_block()
    async def generate_issuance_bundle(
            cls, wallet, _: Dict,
            amount: uint64) -> Tuple[TransactionRecord, SpendBundle]:
        coins = await wallet.standard_wallet.select_coins(amount)

        origin = coins.copy().pop()
        origin_id = origin.name()

        cc_inner: Program = await wallet.get_new_inner_puzzle()
        await wallet.add_lineage(origin_id, LineageProof())
        genesis_coin_checker: Program = cls.construct([Program.to(origin_id)])

        minted_cc_puzzle_hash: bytes32 = construct_cat_puzzle(
            CAT_MOD, genesis_coin_checker.get_tree_hash(),
            cc_inner).get_tree_hash()

        tx_record: TransactionRecord = await wallet.standard_wallet.generate_signed_transaction(
            amount, minted_cc_puzzle_hash, uint64(0), origin_id, coins)
        assert tx_record.spend_bundle is not None

        inner_solution = wallet.standard_wallet.add_condition_to_solution(
            Program.to([51, 0, -113, genesis_coin_checker, []]),
            wallet.standard_wallet.make_solution(primaries=[{
                "puzzlehash":
                cc_inner.get_tree_hash(),
                "amount":
                amount
            }], ),
        )
        eve_spend = unsigned_spend_bundle_for_spendable_cats(
            CAT_MOD,
            [
                SpendableCAT(
                    list(
                        filter(lambda a: a.amount == amount,
                               tx_record.additions))[0],
                    genesis_coin_checker.get_tree_hash(),
                    cc_inner,
                    inner_solution,
                    limitations_program_reveal=genesis_coin_checker,
                )
            ],
        )
        signed_eve_spend = await wallet.sign(eve_spend)

        if wallet.cat_info.my_tail is None:
            await wallet.save_info(
                CATInfo(genesis_coin_checker.get_tree_hash(),
                        genesis_coin_checker, wallet.cat_info.lineage_proofs),
                False,
            )

        return tx_record, SpendBundle.aggregate(
            [tx_record.spend_bundle, signed_eve_spend])
    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
Beispiel #4
0
    def to_valid_spend(self,
                       arbitrage_ph: Optional[bytes32] = None) -> SpendBundle:
        if not self.is_valid():
            raise ValueError("Offer is currently incomplete")

        completion_spends: List[CoinSpend] = []
        for tail_hash, payments in self.requested_payments.items():
            offered_coins: List[Coin] = self.get_offered_coins()[tail_hash]

            # Because of CAT supply laws, we must specify a place for the leftovers to go
            arbitrage_amount: int = self.arbitrage()[tail_hash]
            all_payments: List[NotarizedPayment] = payments.copy()
            if arbitrage_amount > 0:
                assert arbitrage_amount is not None
                assert arbitrage_ph is not None
                all_payments.append(
                    NotarizedPayment(arbitrage_ph, uint64(arbitrage_amount),
                                     []))

            for coin in offered_coins:
                inner_solutions = []
                if coin == offered_coins[0]:
                    nonces: List[bytes32] = [p.nonce for p in all_payments]
                    for nonce in list(dict.fromkeys(
                            nonces)):  # dedup without messing with order
                        nonce_payments: List[NotarizedPayment] = list(
                            filter(lambda p: p.nonce == nonce, all_payments))
                        inner_solutions.append(
                            (nonce,
                             [np.as_condition_args()
                              for np in nonce_payments]))

                if tail_hash:
                    # CATs have a special way to be solved so we have to do some calculation before getting the solution
                    parent_spend: CoinSpend = list(
                        filter(
                            lambda cs: cs.coin.name() == coin.parent_coin_info,
                            self.bundle.coin_spends))[0]
                    parent_coin: Coin = parent_spend.coin
                    matched, curried_args = match_cat_puzzle(
                        parent_spend.puzzle_reveal.to_program())
                    assert matched
                    _, _, inner_puzzle = curried_args
                    spendable_cat = SpendableCAT(
                        coin,
                        tail_hash,
                        OFFER_MOD,
                        Program.to(inner_solutions),
                        lineage_proof=LineageProof(
                            parent_coin.parent_coin_info,
                            inner_puzzle.get_tree_hash(), parent_coin.amount),
                    )
                    solution: Program = (
                        unsigned_spend_bundle_for_spendable_cats(
                            CAT_MOD, [spendable_cat
                                      ]).coin_spends[0].solution.to_program())
                else:
                    solution = Program.to(inner_solutions)

                completion_spends.append(
                    CoinSpend(
                        coin,
                        construct_cat_puzzle(CAT_MOD, tail_hash, OFFER_MOD)
                        if tail_hash else OFFER_MOD,
                        solution,
                    ))

        return SpendBundle.aggregate(
            [SpendBundle(completion_spends, G2Element()), self.bundle])
Beispiel #5
0
    async def generate_unsigned_spendbundle(
        self,
        payments: List[Payment],
        fee: uint64 = uint64(0),
        cat_discrepancy: Optional[Tuple[
            int, Program]] = None,  # (extra_delta, limitations_solution)
        coins: Set[Coin] = None,
        coin_announcements_to_consume: Optional[Set[Announcement]] = None,
        puzzle_announcements_to_consume: Optional[Set[Announcement]] = None,
    ) -> Tuple[SpendBundle, Optional[TransactionRecord]]:
        if coin_announcements_to_consume is not None:
            coin_announcements_bytes: Optional[Set[bytes32]] = {
                a.name()
                for a in coin_announcements_to_consume
            }
        else:
            coin_announcements_bytes = None

        if puzzle_announcements_to_consume is not None:
            puzzle_announcements_bytes: Optional[Set[bytes32]] = {
                a.name()
                for a in puzzle_announcements_to_consume
            }
        else:
            puzzle_announcements_bytes = None

        if cat_discrepancy is not None:
            extra_delta, limitations_solution = cat_discrepancy
        else:
            extra_delta, limitations_solution = 0, Program.to([])
        payment_amount: int = sum([p.amount for p in payments])
        starting_amount: int = payment_amount - extra_delta

        if coins is None:
            cat_coins = await self.select_coins(uint64(starting_amount))
        else:
            cat_coins = coins

        selected_cat_amount = sum([c.amount for c in cat_coins])
        assert selected_cat_amount >= starting_amount

        # Figure out if we need to absorb/melt some XCH as part of this
        regular_chia_to_claim: int = 0
        if payment_amount > starting_amount:
            fee = uint64(fee + payment_amount - starting_amount)
        elif payment_amount < starting_amount:
            regular_chia_to_claim = payment_amount

        need_chia_transaction = (fee > 0 or regular_chia_to_claim > 0) and (
            fee - regular_chia_to_claim != 0)

        # Calculate standard puzzle solutions
        change = selected_cat_amount - starting_amount
        primaries: List[AmountWithPuzzlehash] = []
        for payment in payments:
            primaries.append({
                "puzzlehash": payment.puzzle_hash,
                "amount": payment.amount,
                "memos": payment.memos
            })

        if change > 0:
            changepuzzlehash = await self.get_new_inner_hash()
            primaries.append({
                "puzzlehash": changepuzzlehash,
                "amount": uint64(change),
                "memos": []
            })

        limitations_program_reveal = Program.to([])
        if self.cat_info.my_tail is None:
            assert cat_discrepancy is None
        elif cat_discrepancy is not None:
            limitations_program_reveal = self.cat_info.my_tail

        # Loop through the coins we've selected and gather the information we need to spend them
        spendable_cc_list = []
        chia_tx = None
        first = True
        for coin in cat_coins:
            if first:
                first = False
                if need_chia_transaction:
                    if fee > regular_chia_to_claim:
                        announcement = Announcement(coin.name(), b"$", b"\xca")
                        chia_tx, _ = await self.create_tandem_xch_tx(
                            fee,
                            uint64(regular_chia_to_claim),
                            announcement_to_assert=announcement)
                        innersol = self.standard_wallet.make_solution(
                            primaries=primaries,
                            coin_announcements={announcement.message},
                            coin_announcements_to_assert=
                            coin_announcements_bytes,
                            puzzle_announcements_to_assert=
                            puzzle_announcements_bytes,
                        )
                    elif regular_chia_to_claim > fee:
                        chia_tx, _ = await self.create_tandem_xch_tx(
                            fee, uint64(regular_chia_to_claim))
                        innersol = self.standard_wallet.make_solution(
                            primaries=primaries,
                            coin_announcements_to_assert={announcement.name()})
                else:
                    innersol = self.standard_wallet.make_solution(
                        primaries=primaries,
                        coin_announcements_to_assert=coin_announcements_bytes,
                        puzzle_announcements_to_assert=
                        puzzle_announcements_bytes,
                    )
            else:
                innersol = self.standard_wallet.make_solution(primaries=[])
            inner_puzzle = await self.inner_puzzle_for_cc_puzhash(
                coin.puzzle_hash)
            lineage_proof = await self.get_lineage_proof_for_coin(coin)
            assert lineage_proof is not None
            new_spendable_cc = SpendableCAT(
                coin,
                self.cat_info.limitations_program_hash,
                inner_puzzle,
                innersol,
                limitations_solution=limitations_solution,
                extra_delta=extra_delta,
                lineage_proof=lineage_proof,
                limitations_program_reveal=limitations_program_reveal,
            )
            spendable_cc_list.append(new_spendable_cc)

        cat_spend_bundle = unsigned_spend_bundle_for_spendable_cats(
            CAT_MOD, spendable_cc_list)
        chia_spend_bundle = SpendBundle([], G2Element())
        if chia_tx is not None and chia_tx.spend_bundle is not None:
            chia_spend_bundle = chia_tx.spend_bundle

        return (
            SpendBundle.aggregate([
                cat_spend_bundle,
                chia_spend_bundle,
            ]),
            chia_tx,
        )
    async def generate_coins(
        self,
        sim,
        sim_client,
        requested_coins: Dict[Optional[str], List[uint64]],
    ) -> Dict[Optional[str], List[Coin]]:
        await sim.farm_block(acs_ph)
        parent_coin: Coin = [
            cr.coin for cr in await (
                sim_client.get_coin_records_by_puzzle_hash(acs_ph))
        ][0]

        # We need to gather a list of initial coins to create as well as spends that do the eve spend for every CAT
        payments: List[Payment] = []
        cat_bundles: List[SpendBundle] = []
        for tail_str, amounts in requested_coins.items():
            for amount in amounts:
                if tail_str:
                    tail: Program = str_to_tail(
                        tail_str)  # Making a fake but unique TAIL
                    cat_puzzle: Program = construct_cat_puzzle(
                        CAT_MOD, tail.get_tree_hash(), acs)
                    payments.append(
                        Payment(cat_puzzle.get_tree_hash(), amount, []))
                    cat_bundles.append(
                        unsigned_spend_bundle_for_spendable_cats(
                            CAT_MOD,
                            [
                                SpendableCAT(
                                    Coin(parent_coin.name(),
                                         cat_puzzle.get_tree_hash(), amount),
                                    tail.get_tree_hash(),
                                    acs,
                                    Program.to([[51, acs_ph, amount],
                                                [51, 0, -113, tail, []]]),
                                )
                            ],
                        ))
                else:
                    payments.append(Payment(acs_ph, amount, []))

        # This bundle create all of the initial coins
        parent_bundle = SpendBundle(
            [
                CoinSpend(
                    parent_coin,
                    acs,
                    Program.to([[51, p.puzzle_hash, p.amount]
                                for p in payments]),
                )
            ],
            G2Element(),
        )

        # Then we aggregate it with all of the eve spends
        await sim_client.push_tx(
            SpendBundle.aggregate([parent_bundle, *cat_bundles]))
        await sim.farm_block()

        # Search for all of the coins and put them into a dictionary
        coin_dict: Dict[Optional[str], List[Coin]] = {}
        for tail_str, _ in requested_coins.items():
            if tail_str:
                tail_hash: bytes32 = str_to_tail_hash(tail_str)
                cat_ph: bytes32 = construct_cat_puzzle(CAT_MOD, tail_hash,
                                                       acs).get_tree_hash()
                coin_dict[tail_str] = [
                    cr.coin for cr in await (
                        sim_client.get_coin_records_by_puzzle_hash(
                            cat_ph, include_spent_coins=False))
                ]
            else:
                coin_dict[None] = list(
                    filter(
                        lambda c: c.amount < 250000000000,
                        [
                            cr.coin for cr in await (
                                sim_client.get_coin_records_by_puzzle_hash(
                                    acs_ph, include_spent_coins=False))
                        ],
                    ))

        return coin_dict