mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-11-11 01:28:17 +03:00
generate and transfer ported
This commit is contained in:
parent
0bb21abe38
commit
ccf16cb223
@ -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: |
|
||||
|
@ -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: |
|
||||
|
@ -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%"""
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
)
|
||||
)
|
||||
|
@ -1 +1 @@
|
||||
ff02ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff80808080808080ffff04ffff01ffffff88ad4cd55cf7ad6414ff0233ffff3e04ff81f601ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff3480ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff2fffff01ff80ff80ff80ff80ff808080808080808080ff0bff2affff0bff3cff2880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff5fffff01ff02ffff03ffff09ff82011fff3880ffff01ff02ffff03ffff09ffff18ff82059f80ff3c80ffff01ff02ffff03ffff20ff81bf80ffff01ff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ffff0101ffff04ff82017fffff04ff82019fffff04ff8205ffff808080808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff8080808080808080808080808080ff0180ffff01ff02ffff03ffff09ff82011fff2c80ffff01ff02ffff03ffff20ff82017f80ffff01ff04ffff04ff24ffff04ffff0eff10ffff02ff2effff04ff02ffff04ff82019fff8080808080ff808080ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ffff0101ffff04ff8202ffffff04ffff02ff0bffff04ff17ffff04ff2fffff04ff82019fff8080808080ff80808080808080808080808080ffff01ff088080ff0180ffff01ff02ffff03ffff09ff82011fff2480ffff01ff02ffff03ffff22ffff20ffff09ffff0cff82029fff80ffff010880ff108080ffff09ffff0128ffff0dff82029f808080ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff80808080808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff8080808080808080808080808080ff018080ff018080ff0180ffff01ff02ffff03ff81bfffff01ff02ffff03ff82017fffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff8204ffffff04ffff02ff2effff04ff02ffff04ff8215ffff80808080ffff04ffff0bff3cff8209ff80ffff04ffff0bff3cff0580ff8080808080808080ff8206ff8080ff822dff80ffff01ff088080ff0180ffff01ff088080ff018080ff0180ff018080
|
||||
ff02ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff80808080808080ffff04ffff01ffffff88ad4cd55cf7ad6414ff0233ffff3e04ff81f601ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff3480ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff2fffff01ff80ff80ff80ff80ff808080808080808080ff0bff2affff0bff3cff2880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff5fffff01ff02ffff03ffff09ff82011fff3880ffff01ff02ffff03ffff09ffff18ff82059f80ff3c80ffff01ff02ffff03ffff20ff81bf80ffff01ff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ffff0101ffff04ff82017fffff04ff82019fffff04ff8205ffff808080808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff8080808080808080808080808080ff0180ffff01ff02ffff03ffff09ff82011fff2c80ffff01ff02ffff03ffff20ff82017f80ffff01ff04ffff04ff24ffff04ffff0eff10ffff02ff2effff04ff02ffff04ff82019fff8080808080ff808080ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ffff0101ffff04ff8202ffffff04ffff02ff0bffff04ff17ffff04ff2fffff04ff82019fff8080808080ff80808080808080808080808080ffff01ff088080ff0180ffff01ff02ffff03ffff09ff82011fff2480ffff01ff02ffff03ffff22ffff20ffff09ffff0cff82029fff80ffff010880ff108080ffff09ffff0128ffff0dff82029f808080ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff80808080808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff8080808080808080808080808080ff018080ff018080ff0180ffff01ff02ffff03ff81bfffff01ff02ffff03ff82017fffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff8204ffffff04ffff02ff2effff04ff02ffff04ffff02ffff03ff8215ffffff018215ffffff010b80ff0180ff80808080ffff04ffff0bff3cff8209ff80ffff04ffff0bff3cff0580ff8080808080808080ff8206ff8080ff822dff80ffff01ff088080ff0180ffff01ff088080ff018080ff0180ff018080
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user