diff --git a/.github/workflows/build-test-macos-wallet-nft_wallet.yml b/.github/workflows/build-test-macos-wallet-nft_wallet.yml index 0de31bbc703d5..a2547ca669f9a 100644 --- a/.github/workflows/build-test-macos-wallet-nft_wallet.yml +++ b/.github/workflows/build-test-macos-wallet-nft_wallet.yml @@ -95,7 +95,7 @@ jobs: - name: Test wallet-nft_wallet code with pytest run: | . ./activate - venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/nft_wallet/test_nft_clvm.py tests/wallet/nft_wallet/test_nft_offers.py tests/wallet/nft_wallet/test_nft_puzzles.py tests/wallet/nft_wallet/test_nft_wallet.py + venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/nft_wallet/test_nft_clvm.py tests/wallet/nft_wallet/test_nft_lifecycle.py tests/wallet/nft_wallet/test_nft_offers.py tests/wallet/nft_wallet/test_nft_puzzles.py tests/wallet/nft_wallet/test_nft_wallet.py - name: Process coverage data run: | diff --git a/.github/workflows/build-test-ubuntu-wallet-nft_wallet.yml b/.github/workflows/build-test-ubuntu-wallet-nft_wallet.yml index 9cf319d1e6edc..1b2e096f5de73 100644 --- a/.github/workflows/build-test-ubuntu-wallet-nft_wallet.yml +++ b/.github/workflows/build-test-ubuntu-wallet-nft_wallet.yml @@ -94,7 +94,7 @@ jobs: - name: Test wallet-nft_wallet code with pytest run: | . ./activate - venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/nft_wallet/test_nft_clvm.py tests/wallet/nft_wallet/test_nft_offers.py tests/wallet/nft_wallet/test_nft_puzzles.py tests/wallet/nft_wallet/test_nft_wallet.py + venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/nft_wallet/test_nft_clvm.py tests/wallet/nft_wallet/test_nft_lifecycle.py tests/wallet/nft_wallet/test_nft_offers.py tests/wallet/nft_wallet/test_nft_puzzles.py tests/wallet/nft_wallet/test_nft_wallet.py - name: Process coverage data run: | diff --git a/chia/wallet/nft_wallet/nft_info.py b/chia/wallet/nft_wallet/nft_info.py index a3005e5cbe811..bade90808b134 100644 --- a/chia/wallet/nft_wallet/nft_info.py +++ b/chia/wallet/nft_wallet/nft_info.py @@ -26,9 +26,6 @@ class NFTInfo(Streamable): owner_did: Optional[bytes32] """Owner DID""" - owner_pubkey: Optional[bytes] - """Pubkey of the NFT owner""" - royalty_percentage: Optional[uint16] """Percentage of the transaction fee paid to the author, e.g. 1000 = 1%""" diff --git a/chia/wallet/nft_wallet/nft_puzzles.py b/chia/wallet/nft_wallet/nft_puzzles.py index 8521acf95484c..0a64a48266514 100644 --- a/chia/wallet/nft_wallet/nft_puzzles.py +++ b/chia/wallet/nft_wallet/nft_puzzles.py @@ -1,7 +1,6 @@ import logging from typing import Any, Dict, List, Optional, Tuple -from blspy import G1Element from clvm.casts import int_from_bytes from clvm_tools.binutils import disassemble @@ -107,7 +106,6 @@ def get_nft_info_from_puzzle(nft_coin_info: NFTCoinInfo) -> NFTInfo: uncurried_nft.singleton_launcher_id, nft_coin_info.coin.name(), uncurried_nft.owner_did, - uncurried_nft.owner_pubkey, uncurried_nft.trade_price_percentage, uncurried_nft.royalty_address, data_uris, @@ -204,7 +202,7 @@ def create_ownership_layer_puzzle( log.debug( "Creating ownership layer puzzle with NFT_ID: %s DID_ID: %s Royalty_Percentage: %d P2_puzzle: %s", nft_id.hex(), - did_id.hex(), + did_id, percentage, p2_puzzle, ) @@ -212,7 +210,6 @@ def create_ownership_layer_puzzle( if not royalty_puzzle_hash: royalty_puzzle_hash = p2_puzzle.get_tree_hash() transfer_program = NFT_TRANSFER_PROGRAM_DEFAULT.curry( - STANDARD_PUZZLE_MOD.get_tree_hash(), singleton_struct, royalty_puzzle_hash, percentage, @@ -221,9 +218,7 @@ def create_ownership_layer_puzzle( ) nft_inner_puzzle = p2_puzzle - nft_ownership_layer_puzzle = NFT_OWNERSHIP_LAYER.curry( - NFT_OWNERSHIP_LAYER.get_tree_hash(), did_id, transfer_program, nft_inner_puzzle - ) + nft_ownership_layer_puzzle = construct_ownership_layer(bytes32(did_id), transfer_program, nft_inner_puzzle) return nft_ownership_layer_puzzle @@ -231,30 +226,28 @@ def create_ownership_layer_transfer_solution( new_did: bytes, new_did_inner_hash: bytes32, trade_prices_list: List[List[int]], - new_pubkey: G1Element, + new_puzhash: bytes32, ) -> Program: log.debug( - "Creating a transfer solution with: DID:%s Inner_puzhash:%s trade_price:%s pubkey:%s", + "Creating a transfer solution with: DID:%s Inner_puzhash:%s trade_price:%s puzhash:%s", new_did.hex(), new_did_inner_hash.hex(), str(trade_prices_list), - new_pubkey, + new_puzhash.hex(), ) - puzhash = STANDARD_PUZZLE_MOD.curry(new_pubkey).get_tree_hash() condition_list = [ [ 51, - puzhash, + new_puzhash, 1, - [puzhash], + [new_puzhash], ], - [-10, new_did, trade_prices_list, new_pubkey, [new_did_inner_hash]], + [-10, new_did, trade_prices_list, new_did_inner_hash], ] log.debug("Condition list raw: %r", condition_list) solution = Program.to( [ [solution_for_conditions(condition_list)], - 1, ] ) log.debug("Generated transfer solution: %s", solution) @@ -285,35 +278,26 @@ def get_metadata_and_phs(unft: UncurriedNFT, puzzle: Program, solution: Serializ if puzhash_for_derivation is not None: # ignore duplicated create coin conditions continue - puzhash = bytes32(condition.rest().first().atom) memo = bytes32(condition.as_python()[-1][0]) - if memo != puzhash: - puzhash_for_derivation = memo - else: - puzhash_for_derivation = puzhash + puzhash_for_derivation = memo log.debug("Got back puzhash from solution: %s", puzhash_for_derivation) assert puzhash_for_derivation return metadata, puzhash_for_derivation -def recurry_nft_puzzle(unft: UncurriedNFT, solution: Program) -> Program: - log.debug("Generating NFT puzzle with ownership support: %s", solution) +def recurry_nft_puzzle(unft: UncurriedNFT, solution: Program, sp2_puzzle: Program) -> Program: + log.debug("Generating NFT puzzle with ownership support: %s", disassemble(solution)) conditions = solution.at("frfr").as_iter() - for change_did_condition in conditions: - if change_did_condition.first().as_int() == -10: + new_did_id = None + new_puzhash = None + for condition in conditions: + if condition.first().as_int() == -10: # this is the change owner magic condition - break - else: - raise ValueError("Not a valid puzzle") - new_did_id = change_did_condition.at("rf").atom - # trade_list_price = change_did_condition.at("rrf").as_python() - # new_did_inner_hash = change_did_condition.at("rrrrf").atom - new_pub_key = G1Element.from_bytes(change_did_condition.at("rrrf").atom) - log.debug(f"Found NFT puzzle details: {new_did_id.hex()} ") - inner_puzzle = NFT_OWNERSHIP_LAYER.curry( - NFT_OWNERSHIP_LAYER.get_tree_hash(), - new_did_id, - unft.transfer_program, - STANDARD_PUZZLE_MOD.curry(new_pub_key), - ) + new_did_id = condition.at("rf").atom + elif condition.first().as_int() == 51: + new_puzhash = condition.at("rf").atom + # assert new_puzhash and new_did_id + log.debug(f"Found NFT puzzle details: {new_did_id} {new_puzhash}") + assert unft.transfer_program + inner_puzzle = construct_ownership_layer(new_did_id, unft.transfer_program, sp2_puzzle) return inner_puzzle diff --git a/chia/wallet/nft_wallet/nft_wallet.py b/chia/wallet/nft_wallet/nft_wallet.py index 695e855605442..4b66aaa3dacab 100644 --- a/chia/wallet/nft_wallet/nft_wallet.py +++ b/chia/wallet/nft_wallet/nft_wallet.py @@ -207,27 +207,21 @@ class NFTWallet: ) singleton_id = uncurried_nft.singleton_launcher_id parent_inner_puzhash = uncurried_nft.nft_state_layer.get_tree_hash() - metadata, p2_puzzle_hash = get_metadata_and_phs( - uncurried_nft, puzzle, Program.from_bytes(bytes(coin_spend.solution)) - ) + metadata, p2_puzzle_hash = get_metadata_and_phs(uncurried_nft, puzzle, coin_spend.solution.to_program()) self.log.debug("Got back puzhash from solution: %s", p2_puzzle_hash) + self.log.debug("Got back updated metadata: %s", metadata) derivation_record: Optional[ DerivationRecord ] = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(p2_puzzle_hash) - if derivation_record: - p2_puzzle = puzzle_for_pk(derivation_record.pubkey) - else: - # we don't have this puzhash in puzzle store - # either it's not our coin or it's a NFT with a DID - p2_puzzle = None - self.log.debug("Got back updated metadata: %s", metadata) - if p2_puzzle is None and uncurried_nft.owner_pubkey is None: + self.log.debug("Record for %s is: %s", p2_puzzle_hash, derivation_record) + if derivation_record is None: self.log.info("Received a puzzle hash that is not ours, returning") # we transferred it to another wallet, remove the coin from our wallet await self.remove_coin(coin_spend.coin, in_transaction=in_transaction) return - if p2_puzzle is None: - inner_puzzle = nft_puzzles.recurry_nft_puzzle(uncurried_nft, delegated_puz_solution) + p2_puzzle = puzzle_for_pk(derivation_record.pubkey) + if uncurried_nft.supports_did: + inner_puzzle = nft_puzzles.recurry_nft_puzzle(uncurried_nft, delegated_puz_solution, p2_puzzle) else: inner_puzzle = p2_puzzle child_puzzle: Program = nft_puzzles.create_full_puzzle( @@ -318,9 +312,6 @@ class NFTWallet: def puzzle_for_pk(self, pk: G1Element) -> Program: inner_puzzle = self.standard_wallet.puzzle_for_pk(bytes(pk)) provenance_puzzle = Program.to([NFT_STATE_LAYER_MOD_HASH, inner_puzzle]) - self.log.debug( - "Wallet name %s generated a puzzle: %s", self.wallet_info.name, provenance_puzzle.get_tree_hash() - ) return provenance_puzzle async def get_did_approval_info( @@ -426,17 +417,8 @@ class NFTWallet: record: Optional[DerivationRecord] = None # Create inner solution for eve spend if did_id is not None: - record = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash( - p2_inner_puzzle.get_tree_hash() - ) - self.log.debug("Got back a pubkey record: %s", record) - if not record: - record = await self.wallet_state_manager.get_unused_derivation_record(self.id(), False) - assert record did_inner_hash, did_bundle = await self.get_did_approval_info(launcher_coin.name()) - pubkey = record.pubkey - self.log.debug("Going to use this pubkey for NFT mint: %s", pubkey) - innersol = create_ownership_layer_transfer_solution(did_id, did_inner_hash, [], pubkey) + innersol = create_ownership_layer_transfer_solution(did_id, did_inner_hash, [], target_puzzle_hash) bundles_to_agg.append(did_bundle) self.log.debug("Created an inner DID NFT solution: %s", disassemble(innersol)) @@ -593,7 +575,7 @@ class NFTWallet: self.log.info( "Attempting to add urls to NFT coin %s in the metadata: %s", nft_coin_info, uncurried_nft.metadata ) - inner_solution = Program.to([solution_for_conditions(condition_list), 1]) + inner_solution = Program.to([solution_for_conditions(condition_list)]) nft_tx_record = await self._make_nft_transaction(nft_coin_info, inner_solution, [puzzle_hash], fee) await self.standard_wallet.push_transaction(nft_tx_record) self.wallet_state_manager.state_changed("nft_coin_updated", self.wallet_info.id) @@ -605,16 +587,20 @@ class NFTWallet: puzzle_hash: bytes32, fee: uint64 = uint64(0), ) -> Optional[SpendBundle]: - self.log.debug("Attempt to transfer a new NFT") + self.log.info("Attempt to transfer a new NFT") coin = nft_coin_info.coin - self.log.debug("Transferring NFT coin %r to puzhash: %s", nft_coin_info, puzzle_hash) + self.log.error("Transferring NFT coin %r to puzhash: %s", nft_coin_info.coin, puzzle_hash) amount = coin.amount unft = UncurriedNFT.uncurry(nft_coin_info.full_puzzle) - puzzle_hash_to_sign = unft.inner_puzzle.get_tree_hash() - condition_list = [make_create_coin_condition(puzzle_hash, amount, [puzzle_hash])] - inner_solution = Program.to([solution_for_conditions(condition_list), amount]) - self.log.debug("Solution for new coin: %r", disassemble(inner_solution)) + puzzle_hash_to_sign = unft.p2_puzzle.get_tree_hash() + if unft.supports_did: + self.log.error("Transferring NFT with ownership layer") + inner_solution = create_ownership_layer_transfer_solution(int_to_bytes(0), int_to_bytes(0), [], puzzle_hash) + else: + condition_list = [make_create_coin_condition(puzzle_hash, amount, [puzzle_hash])] + inner_solution = Program.to([solution_for_conditions(condition_list), amount]) + self.log.error("Solution for new coin: %r", disassemble(inner_solution)) nft_tx_record = await self._make_nft_transaction( nft_coin_info, inner_solution, diff --git a/chia/wallet/nft_wallet/uncurry_nft.py b/chia/wallet/nft_wallet/uncurry_nft.py index 9afea92b41c25..12241c5db1b4f 100644 --- a/chia/wallet/nft_wallet/uncurry_nft.py +++ b/chia/wallet/nft_wallet/uncurry_nft.py @@ -4,8 +4,6 @@ import logging from dataclasses import dataclass from typing import Optional, Type, TypeVar -from blspy import G1Element - from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.sized_bytes import bytes32 from chia.util.ints import uint16 @@ -72,9 +70,6 @@ class UncurriedNFT: supports_did: bool """If the inner puzzle support the DID""" - owner_pubkey: Optional[G1Element] - """Owner's Pubkey in the P2 puzzle""" - nft_inner_puzzle_hash: Optional[bytes32] """Puzzle hash of the ownership layer inner puzzle """ @@ -141,7 +136,6 @@ class UncurriedNFT: if kv_pair.first().as_atom() == b"st": series_total = kv_pair.rest() current_did = None - pubkey = None transfer_program = None transfer_program_args = None royalty_address = None @@ -153,17 +147,14 @@ class UncurriedNFT: supports_did = True log.debug("Parsing ownership layer") _, current_did, transfer_program, p2_puzzle = ol_args.as_iter() - _, p2_args = p2_puzzle.uncurry() - (pubkey_sexp,) = p2_args.as_iter() transfer_program_mod, transfer_program_args = transfer_program.uncurry() - _, _, royalty_address_p, royalty_percentage, _, _ = transfer_program_args.as_iter() + _, royalty_address_p, royalty_percentage, _, _ = transfer_program_args.as_iter() royalty_percentage = uint16(royalty_percentage.as_int()) royalty_address = royalty_address_p.atom current_did = current_did.atom if current_did == b"": # For unassigned NFT, set owner DID to None current_did = None - pubkey = pubkey_sexp.atom else: log.debug("Creating a standard NFT puzzle") p2_puzzle = inner_puzzle @@ -190,7 +181,6 @@ class UncurriedNFT: inner_puzzle=inner_puzzle, owner_did=current_did, supports_did=supports_did, - owner_pubkey=pubkey, transfer_program=transfer_program, transfer_program_curry_params=transfer_program_args, royalty_address=royalty_address, diff --git a/chia/wallet/puzzles/nft_ownership_layer.clvm b/chia/wallet/puzzles/nft_ownership_layer.clvm index e69cc43bc02b8..14f8649ac80b2 100644 --- a/chia/wallet/puzzles/nft_ownership_layer.clvm +++ b/chia/wallet/puzzles/nft_ownership_layer.clvm @@ -71,7 +71,7 @@ (c CREATE_COIN (c - (nft_ownership_layer_puzzle_hash NFT_OWNERSHIP_LAYER_MOD_HASH (f tp_output) (f (r tp_output)) (f odd_args)) + (nft_ownership_layer_puzzle_hash NFT_OWNERSHIP_LAYER_MOD_HASH (f tp_output) (if (f (r tp_output)) (f (r tp_output)) TRANSFER_PROGRAM) (f odd_args)) (r odd_args) ) ) diff --git a/chia/wallet/puzzles/nft_ownership_layer.clvm.hex b/chia/wallet/puzzles/nft_ownership_layer.clvm.hex index 3937f5e30c99b..7ddf6231993ee 100644 --- a/chia/wallet/puzzles/nft_ownership_layer.clvm.hex +++ b/chia/wallet/puzzles/nft_ownership_layer.clvm.hex @@ -1 +1 @@ -ff02ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff80808080808080ffff04ffff01ffffff88ad4cd55cf7ad6414ff0233ffff3e04ff81f601ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff3480ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff2fffff01ff80ff80ff80ff80ff808080808080808080ff0bff2affff0bff3cff2880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff5fffff01ff02ffff03ffff09ff82011fff3880ffff01ff02ffff03ffff09ffff18ff82059f80ff3c80ffff01ff02ffff03ffff20ff81bf80ffff01ff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ffff0101ffff04ff82017fffff04ff82019fffff04ff8205ffff808080808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff8080808080808080808080808080ff0180ffff01ff02ffff03ffff09ff82011fff2c80ffff01ff02ffff03ffff20ff82017f80ffff01ff04ffff04ff24ffff04ffff0eff10ffff02ff2effff04ff02ffff04ff82019fff8080808080ff808080ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ffff0101ffff04ff8202ffffff04ffff02ff0bffff04ff17ffff04ff2fffff04ff82019fff8080808080ff80808080808080808080808080ffff01ff088080ff0180ffff01ff02ffff03ffff09ff82011fff2480ffff01ff02ffff03ffff22ffff20ffff09ffff0cff82029fff80ffff010880ff108080ffff09ffff0128ffff0dff82029f808080ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff80808080808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff8080808080808080808080808080ff018080ff018080ff0180ffff01ff02ffff03ff81bfffff01ff02ffff03ff82017fffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff8204ffffff04ffff02ff2effff04ff02ffff04ff8215ffff80808080ffff04ffff0bff3cff8209ff80ffff04ffff0bff3cff0580ff8080808080808080ff8206ff8080ff822dff80ffff01ff088080ff0180ffff01ff088080ff018080ff0180ff018080 \ No newline at end of file +ff02ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff80808080808080ffff04ffff01ffffff88ad4cd55cf7ad6414ff0233ffff3e04ff81f601ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff3480ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff2fffff01ff80ff80ff80ff80ff808080808080808080ff0bff2affff0bff3cff2880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff5fffff01ff02ffff03ffff09ff82011fff3880ffff01ff02ffff03ffff09ffff18ff82059f80ff3c80ffff01ff02ffff03ffff20ff81bf80ffff01ff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ffff0101ffff04ff82017fffff04ff82019fffff04ff8205ffff808080808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff8080808080808080808080808080ff0180ffff01ff02ffff03ffff09ff82011fff2c80ffff01ff02ffff03ffff20ff82017f80ffff01ff04ffff04ff24ffff04ffff0eff10ffff02ff2effff04ff02ffff04ff82019fff8080808080ff808080ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ffff0101ffff04ff8202ffffff04ffff02ff0bffff04ff17ffff04ff2fffff04ff82019fff8080808080ff80808080808080808080808080ffff01ff088080ff0180ffff01ff02ffff03ffff09ff82011fff2480ffff01ff02ffff03ffff22ffff20ffff09ffff0cff82029fff80ffff010880ff108080ffff09ffff0128ffff0dff82029f808080ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff80808080808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff8080808080808080808080808080ff018080ff018080ff0180ffff01ff02ffff03ff81bfffff01ff02ffff03ff82017fffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff8204ffffff04ffff02ff2effff04ff02ffff04ffff02ffff03ff8215ffffff018215ffffff010b80ff0180ff80808080ffff04ffff0bff3cff8209ff80ffff04ffff0bff3cff0580ff8080808080808080ff8206ff8080ff822dff80ffff01ff088080ff0180ffff01ff088080ff018080ff0180ff018080 \ No newline at end of file diff --git a/tests/wallet/nft_wallet/test_nft_lifecycle.py b/tests/wallet/nft_wallet/test_nft_lifecycle.py index 7da46565240ef..c36d87629a113 100644 --- a/tests/wallet/nft_wallet/test_nft_lifecycle.py +++ b/tests/wallet/nft_wallet/test_nft_lifecycle.py @@ -1,10 +1,10 @@ import itertools -import pytest - -from blspy import G2Element from typing import List, Tuple -from chia.clvm.spend_sim import SpendSim, SimClient +import pytest +from blspy import G2Element + +from chia.clvm.spend_sim import SimClient, SpendSim from chia.types.announcement import Announcement from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.sized_bytes import bytes32 @@ -13,11 +13,11 @@ from chia.types.mempool_inclusion_status import MempoolInclusionStatus from chia.types.spend_bundle import SpendBundle from chia.util.errors import Err from chia.wallet.nft_wallet.nft_puzzles import ( - create_nft_layer_puzzle_with_curry_params, - metadata_to_program, NFT_METADATA_UPDATER, NFT_TRANSFER_PROGRAM_DEFAULT, construct_ownership_layer, + create_nft_layer_puzzle_with_curry_params, + metadata_to_program, ) ACS = Program.to(1) diff --git a/tests/wallet/nft_wallet/test_nft_puzzles.py b/tests/wallet/nft_wallet/test_nft_puzzles.py index c870422b51cf2..d58d4ad10600e 100644 --- a/tests/wallet/nft_wallet/test_nft_puzzles.py +++ b/tests/wallet/nft_wallet/test_nft_puzzles.py @@ -7,6 +7,7 @@ from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.sized_bytes import bytes32 from chia.wallet.nft_wallet import uncurry_nft from chia.wallet.nft_wallet.nft_puzzles import ( + construct_ownership_layer, create_full_puzzle, create_nft_layer_puzzle_with_curry_params, recurry_nft_puzzle, @@ -22,7 +23,6 @@ DID_MOD = load_clvm("did_innerpuz.clvm") NFT_STATE_LAYER_MOD = load_clvm("nft_state_layer.clvm") NFT_OWNERSHIP_LAYER = load_clvm("nft_ownership_layer.clvm") NFT_TRANSFER_PROGRAM_DEFAULT = load_clvm("nft_ownership_transfer_program_one_way_claim_with_royalties.clvm") -STANDARD_PUZZLE_MOD = load_clvm("p2_delegated_puzzle_or_hidden_puzzle.clvm") LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash() NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash() SINGLETON_MOD_HASH = SINGLETON_MOD.get_tree_hash() @@ -32,30 +32,30 @@ LAUNCHER_ID = Program.to(b"launcher-id").get_tree_hash() NFT_METADATA_UPDATER_DEFAULT = load_clvm("nft_metadata_updater_default.clvm") -def make_a_new_solution() -> Tuple[bytes, Program]: +def make_a_new_solution() -> Tuple[Program, Program]: destination = int_to_public_key(2) + p2_puzzle = puzzle_for_pk(destination) + puzhash = p2_puzzle.get_tree_hash() new_did = Program.to("test").get_tree_hash() + print(f"NEW DID: {new_did.hex()} {puzhash.hex()}") new_did_inner_hash = Program.to("fake").get_tree_hash() trade_prices_list = [[200]] - my_amount = 1 condition_list = [ [ 51, - STANDARD_PUZZLE_MOD.curry(destination).get_tree_hash(), + puzhash, 1, - [STANDARD_PUZZLE_MOD.curry(destination).get_tree_hash()], + [puzhash], ], - [-10, new_did, trade_prices_list, destination, [new_did_inner_hash]], + [-10, new_did, trade_prices_list, new_did_inner_hash], ] solution = Program.to( [ [solution_for_conditions(condition_list)], - my_amount, ] ) - print(disassemble(solution)) - return destination, solution + return p2_puzzle, solution def make_a_new_ownership_layer_puzzle() -> Tuple[Program, Program]: @@ -65,20 +65,14 @@ def make_a_new_ownership_layer_puzzle() -> Tuple[Program, Program]: nft_id = Program.to("nft_id") SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (nft_id, LAUNCHER_PUZZLE_HASH))) curried_tp = NFT_TRANSFER_PROGRAM_DEFAULT.curry( - STANDARD_PUZZLE_MOD.get_tree_hash(), SINGLETON_STRUCT, innerpuz.get_tree_hash(), 2000, OFFER_MOD.get_tree_hash(), CAT_MOD.get_tree_hash(), ) - curried_inner = STANDARD_PUZZLE_MOD.curry(pubkey) - curried_ownership_layer = NFT_OWNERSHIP_LAYER.curry( - NFT_OWNERSHIP_LAYER.get_tree_hash(), - old_did, - curried_tp, - curried_inner, - ) + curried_inner = innerpuz + curried_ownership_layer = construct_ownership_layer(old_did, curried_tp, curried_inner) return innerpuz, curried_ownership_layer @@ -107,10 +101,11 @@ def test_transfer_puzzle_builder() -> None: ("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), ("h", 0xD4584AD463139FA8C0D9F68F4B59F185), ] - destination, solution = make_a_new_solution() + sp2_puzzle, solution = make_a_new_solution() p2_puzzle, ownership_puzzle = make_a_new_ownership_layer_puzzle() - clvm_nft_puzzle = make_a_new_nft_puzzle(ownership_puzzle, Program.to(metadata)) - print("NFT state layer: %r" % clvm_nft_puzzle.get_tree_hash()) + clvm_nft_puzzle = create_nft_layer_puzzle_with_curry_params( + Program.to(metadata), NFT_METADATA_UPDATER_DEFAULT.get_tree_hash(), ownership_puzzle + ) puzzle = create_full_puzzle( Program.to(["singleton_id"]).get_tree_hash(), Program.to(metadata), @@ -119,10 +114,11 @@ def test_transfer_puzzle_builder() -> None: ) clvm_puzzle_hash = get_updated_nft_puzzle(clvm_nft_puzzle, solution) unft = uncurry_nft.UncurriedNFT.uncurry(puzzle) + assert unft.nft_state_layer == clvm_nft_puzzle assert unft.inner_puzzle == ownership_puzzle - - ol_puzzle = recurry_nft_puzzle(unft, solution.first()) - py_puzzle = create_nft_layer_puzzle_with_curry_params( + assert unft.p2_puzzle == p2_puzzle + ol_puzzle = recurry_nft_puzzle(unft, solution.first(), sp2_puzzle) + nft_puzzle = create_nft_layer_puzzle_with_curry_params( Program.to(metadata), NFT_METADATA_UPDATER_DEFAULT.get_tree_hash(), ol_puzzle ) - assert clvm_puzzle_hash == py_puzzle.get_tree_hash() + assert clvm_puzzle_hash == nft_puzzle.get_tree_hash() diff --git a/tests/wallet/nft_wallet/test_nft_wallet.py b/tests/wallet/nft_wallet/test_nft_wallet.py index d75dd7b42fb8e..57c8b73a0f3ab 100644 --- a/tests/wallet/nft_wallet/test_nft_wallet.py +++ b/tests/wallet/nft_wallet/test_nft_wallet.py @@ -1,4 +1,5 @@ import asyncio +import logging from typing import Any import pytest @@ -12,6 +13,7 @@ from chia.simulator.simulator_protocol import FarmNewBlockProtocol from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.peer_info import PeerInfo +from chia.util.bech32m import encode_puzzle_hash from chia.util.byte_types import hexstr_to_bytes from chia.util.ints import uint16, uint32, uint64 from chia.wallet.did_wallet.did_wallet import DIDWallet @@ -20,6 +22,8 @@ from chia.wallet.util.compute_memos import compute_memos from chia.wallet.util.wallet_types import WalletType from tests.time_out_assert import time_out_assert, time_out_assert_not_none +logging.getLogger("aiosqlite").setLevel(logging.INFO) # Too much logging on debug level + async def tx_in_pool(mempool: MempoolManager, tx_id: bytes32) -> bool: tx = mempool.get_spendbundle(tx_id) @@ -654,7 +658,6 @@ async def test_nft_with_did_wallet_creation(two_wallet_nodes: Any, trusted: Any) assert did_nft["data_uris"][0] == "https://www.chia.net/img/branding/chia-logo.svg" assert did_nft["data_hash"] == "0xD4584AD463139FA8C0D9F68F4B59F185".lower() assert did_nft["owner_did"][2:] == hex_did_id - assert did_nft["owner_pubkey"] is not None # Check unassigned NFT nft_wallets = await wallet_node_0.wallet_state_manager.get_all_wallet_info_entries(WalletType.NFT) assert len(nft_wallets) == 2 @@ -669,7 +672,6 @@ async def test_nft_with_did_wallet_creation(two_wallet_nodes: Any, trusted: Any) assert non_did_nft["data_uris"][0] == "https://url1" assert non_did_nft["data_hash"] == "0xD4584AD463139FA8C0D9F68F4B59F181".lower() assert non_did_nft["owner_did"] is None - assert non_did_nft["owner_pubkey"] is not None @pytest.mark.parametrize( @@ -792,3 +794,128 @@ async def test_nft_rpc_mint(two_wallet_nodes: Any, trusted: Any) -> None: assert did_nft.series_total == st assert did_nft.series_number == sn assert did_nft.royalty_percentage == royalty_percentage + + +@pytest.mark.parametrize( + "trusted", + [True, False], +) +@pytest.mark.asyncio +async def test_nft_transfer_nft_with_did(two_wallet_nodes: Any, trusted: Any) -> None: + num_blocks = 3 + full_nodes, wallets = two_wallet_nodes + full_node_api: FullNodeSimulator = full_nodes[0] + full_node_server = full_node_api.server + wallet_node_0, server_0 = wallets[0] + wallet_node_1, server_1 = wallets[1] + wallet_0 = wallet_node_0.wallet_state_manager.main_wallet + wallet_1 = wallet_node_1.wallet_state_manager.main_wallet + api_0 = WalletRpcApi(wallet_node_0) + ph = await wallet_0.get_new_puzzlehash() + ph1 = await wallet_1.get_new_puzzlehash() + + if trusted: + wallet_node_0.config["trusted_peers"] = { + full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex() + } + wallet_node_1.config["trusted_peers"] = { + full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex() + } + else: + wallet_node_0.config["trusted_peers"] = {} + wallet_node_1.config["trusted_peers"] = {} + + await server_0.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) + await server_1.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) + + for _ in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + + funds = sum( + [calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1)] + ) + + await time_out_assert(10, wallet_0.get_unconfirmed_balance, funds) + await time_out_assert(10, wallet_0.get_confirmed_balance, funds) + did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(1) + ) + spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet_0.id()) + + spend_bundle = spend_bundle_list[0].spend_bundle + await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) + + for _ in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + await time_out_assert(15, wallet_0.get_pending_change_balance, 0) + hex_did_id = did_wallet.get_my_DID() + + res = await api_0.create_new_wallet(dict(wallet_type="nft_wallet", name="NFT WALLET 1", did_id=hex_did_id)) + assert isinstance(res, dict) + assert res.get("success") + nft_wallet_0_id = res["wallet_id"] + + # Create a NFT with DID + resp = await api_0.nft_mint_nft( + { + "wallet_id": nft_wallet_0_id, + "hash": "0xD4584AD463139FA8C0D9F68F4B59F185", + "uris": ["https://www.chia.net/img/branding/chia-logo.svg"], + } + ) + assert resp.get("success") + sb = resp["spend_bundle"] + + # ensure hints are generated + assert compute_memos(sb) + await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + + for i in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + + # Check DID NFT + time_left = 5.0 + coins_response = {} + while time_left > 0: + coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id)) + if coins_response.get("nft_list"): + break + await asyncio.sleep(0.5) + time_left -= 0.5 + assert coins_response["nft_list"], isinstance(coins_response, dict) + assert coins_response.get("success") + coins = coins_response["nft_list"] + assert len(coins) == 1 + + try: + wallet_1.wallet_state_manager.wallets[2] + raise AssertionError("NFT wallet shouldn't exist yet") + except KeyError: + # there shouldn't be a nft wallet yet + pass + resp = await api_0.nft_transfer_nft( + dict( + wallet_id=nft_wallet_0_id, + target_address=encode_puzzle_hash(ph1, "xch"), + nft_coin_id=coins[0].nft_coin_id.hex(), + ) + ) + assert resp.get("success") + sb = resp["spend_bundle"] + assert compute_memos(sb) + await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + + for i in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + time_left = 5.0 + while time_left > 0: + coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id)) + if len(coins_response["nft_list"]) == 0: + break + await asyncio.sleep(0.5) + time_left -= 0.5 + else: + raise AssertionError("NFT not transferred") + + nft_wallet_1 = wallet_1.wallet_state_manager.wallets[2] + await time_out_assert(15, len, 1, nft_wallet_1.nft_wallet_info.my_nft_coins)