generate and transfer ported

This commit is contained in:
Sebastjan 2022-06-13 16:10:34 +02:00
parent 0bb21abe38
commit ccf16cb223
No known key found for this signature in database
GPG Key ID: 1495E61EB5446A69
11 changed files with 201 additions and 121 deletions

View File

@ -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: |

View File

@ -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: |

View File

@ -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%"""

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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)
)
)

View File

@ -1 +1 @@
ff02ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff80808080808080ffff04ffff01ffffff88ad4cd55cf7ad6414ff0233ffff3e04ff81f601ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff3480ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff2fffff01ff80ff80ff80ff80ff808080808080808080ff0bff2affff0bff3cff2880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff5fffff01ff02ffff03ffff09ff82011fff3880ffff01ff02ffff03ffff09ffff18ff82059f80ff3c80ffff01ff02ffff03ffff20ff81bf80ffff01ff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ffff0101ffff04ff82017fffff04ff82019fffff04ff8205ffff808080808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff8080808080808080808080808080ff0180ffff01ff02ffff03ffff09ff82011fff2c80ffff01ff02ffff03ffff20ff82017f80ffff01ff04ffff04ff24ffff04ffff0eff10ffff02ff2effff04ff02ffff04ff82019fff8080808080ff808080ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ffff0101ffff04ff8202ffffff04ffff02ff0bffff04ff17ffff04ff2fffff04ff82019fff8080808080ff80808080808080808080808080ffff01ff088080ff0180ffff01ff02ffff03ffff09ff82011fff2480ffff01ff02ffff03ffff22ffff20ffff09ffff0cff82029fff80ffff010880ff108080ffff09ffff0128ffff0dff82029f808080ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff80808080808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff8080808080808080808080808080ff018080ff018080ff0180ffff01ff02ffff03ff81bfffff01ff02ffff03ff82017fffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff8204ffffff04ffff02ff2effff04ff02ffff04ff8215ffff80808080ffff04ffff0bff3cff8209ff80ffff04ffff0bff3cff0580ff8080808080808080ff8206ff8080ff822dff80ffff01ff088080ff0180ffff01ff088080ff018080ff0180ff018080
ff02ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff80808080808080ffff04ffff01ffffff88ad4cd55cf7ad6414ff0233ffff3e04ff81f601ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff3480ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff2fffff01ff80ff80ff80ff80ff808080808080808080ff0bff2affff0bff3cff2880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff5fffff01ff02ffff03ffff09ff82011fff3880ffff01ff02ffff03ffff09ffff18ff82059f80ff3c80ffff01ff02ffff03ffff20ff81bf80ffff01ff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ffff0101ffff04ff82017fffff04ff82019fffff04ff8205ffff808080808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff8080808080808080808080808080ff0180ffff01ff02ffff03ffff09ff82011fff2c80ffff01ff02ffff03ffff20ff82017f80ffff01ff04ffff04ff24ffff04ffff0eff10ffff02ff2effff04ff02ffff04ff82019fff8080808080ff808080ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ffff0101ffff04ff8202ffffff04ffff02ff0bffff04ff17ffff04ff2fffff04ff82019fff8080808080ff80808080808080808080808080ffff01ff088080ff0180ffff01ff02ffff03ffff09ff82011fff2480ffff01ff02ffff03ffff22ffff20ffff09ffff0cff82029fff80ffff010880ff108080ffff09ffff0128ffff0dff82029f808080ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff80808080808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffff8080808080808080808080808080ff018080ff018080ff0180ffff01ff02ffff03ff81bfffff01ff02ffff03ff82017fffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff8204ffffff04ffff02ff2effff04ff02ffff04ffff02ffff03ff8215ffffff018215ffffff010b80ff0180ff80808080ffff04ffff0bff3cff8209ff80ffff04ffff0bff3cff0580ff8080808080808080ff8206ff8080ff822dff80ffff01ff088080ff0180ffff01ff088080ff018080ff0180ff018080

View File

@ -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)

View File

@ -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()

View File

@ -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)