From 6a3ae94d5436527e9fd987b580959b8a3ebeeed7 Mon Sep 17 00:00:00 2001 From: matt-o-how Date: Mon, 25 Apr 2022 14:51:57 +0100 Subject: [PATCH 01/43] WIP commit for the new NFT spec --- chia/wallet/puzzles/nft_metadata_updater.clvm | 5 ++ chia/wallet/puzzles/nft_ownership_layer.clvm | 19 +++++++ .../nft_ownership_transfer_program.clvm | 4 ++ chia/wallet/puzzles/nft_state_layer.clvm | 49 +++++++++++++++++++ chia/wallet/puzzles/nft_v1_innerpuz.clvm | 4 ++ 5 files changed, 81 insertions(+) create mode 100644 chia/wallet/puzzles/nft_metadata_updater.clvm create mode 100644 chia/wallet/puzzles/nft_ownership_layer.clvm create mode 100644 chia/wallet/puzzles/nft_ownership_transfer_program.clvm create mode 100644 chia/wallet/puzzles/nft_state_layer.clvm create mode 100644 chia/wallet/puzzles/nft_v1_innerpuz.clvm diff --git a/chia/wallet/puzzles/nft_metadata_updater.clvm b/chia/wallet/puzzles/nft_metadata_updater.clvm new file mode 100644 index 000000000000..74b9aa6dc71d --- /dev/null +++ b/chia/wallet/puzzles/nft_metadata_updater.clvm @@ -0,0 +1,5 @@ +(mod solution + + ; main + (x) +) diff --git a/chia/wallet/puzzles/nft_ownership_layer.clvm b/chia/wallet/puzzles/nft_ownership_layer.clvm new file mode 100644 index 000000000000..15dbeab1d2fe --- /dev/null +++ b/chia/wallet/puzzles/nft_ownership_layer.clvm @@ -0,0 +1,19 @@ +(mod ( + CURRENT_OWNER + TRANSFER_PROGRAM_HASH + INNER_PUZZLE + inner_solution + ) + + ; main + (defun wrap_all_create_coins_in_this (new_owner . conditions) + ... + ) + + + (wrap_all_create_coins_in_this + (loop_through_conditions_looking_for_transfer_program_reveal_and_solution_and_add_its_conditions + (a INNER_PUZZLE inner_solution) + ) + ) +) diff --git a/chia/wallet/puzzles/nft_ownership_transfer_program.clvm b/chia/wallet/puzzles/nft_ownership_transfer_program.clvm new file mode 100644 index 000000000000..47ad115824ec --- /dev/null +++ b/chia/wallet/puzzles/nft_ownership_transfer_program.clvm @@ -0,0 +1,4 @@ +(mod ARGS + + ; main +) diff --git a/chia/wallet/puzzles/nft_state_layer.clvm b/chia/wallet/puzzles/nft_state_layer.clvm new file mode 100644 index 000000000000..a9eba6fa4ae4 --- /dev/null +++ b/chia/wallet/puzzles/nft_state_layer.clvm @@ -0,0 +1,49 @@ +(mod ( + NFT_STATE_LAYER_MOD_HASH + METADATA + METADATA_UPDATER_PUZZLE_HASH + INNER_PUZZLE + solution ; either to inner puzzle or metadata updater + my_amount + metadata_updater_reveal + ) + + (include condition_codes.clvm) + (include curry-and-treehash.clinc) + + (defun-inline nft_state_layer_puzzle_hash (NFT_STATE_LAYER_MOD_HASH METADATA METADATA_UPDATER_PUZZLE_HASH inner_puzzle_hash) + (puzzle-hash-of-curried-function NFT_STATE_LAYER_MOD_HASH + (sha256 ONE inner_puzzle_hash) + (sha256 ONE METADATA_UPDATER_PUZZLE_HASHs) + (sha256tree1 METADATA) + (sha256 ONE NFT_STATE_LAYER_MOD_HASH) + ) + ) + + (defun wrap_odd_create_coins (NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (METADATA conditions) my_amount) + (if conditions + (if (= (f (f conditions)) CREATE_COIN) + (if (logand (f (r (r (f conditions)))) ONE) + (c (list CREATE_COIN (nft_state_layer_puzzle_hash NFT_STATE_LAYER_MOD_HASH METADATA METADATA_UPDATER_PUZZLE_HASH (f (r (f conditions)))) my_amount) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (list METADATA (r conditions)) my_amount)) + (c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (list METADATA (r conditions)) my_amount)) + ) + (c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (list METADATA (r conditions)) my_amount)) + ) + () + ) + ) + + ; main + (c + (list ASSERT_MY_AMOUNT my_amount) + (wrap_odd_create_coins + NFT_STATE_LAYER_MOD_HASH + METADATA_UPDATER_PUZZLE_HASH + (if metadata_updater_reveal + (list (a metadata_updater_reveal solution) (list (list CREATE_COIN (sha256tree INNER_PUZZLE) my_amount))) + (list METADATA METADATA_UPDATER_PUZZLE_HASH (a INNER_PUZZLE solution)) + ) + my_amount + ) + ) +) diff --git a/chia/wallet/puzzles/nft_v1_innerpuz.clvm b/chia/wallet/puzzles/nft_v1_innerpuz.clvm new file mode 100644 index 000000000000..47ad115824ec --- /dev/null +++ b/chia/wallet/puzzles/nft_v1_innerpuz.clvm @@ -0,0 +1,4 @@ +(mod ARGS + + ; main +) From 8b0062a22a892574295f5132076ab3339910f3d0 Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Mon, 25 Apr 2022 07:40:40 -0700 Subject: [PATCH 02/43] Update to optimized singleton --- .../puzzles/singleton_top_layer_v1_1.clvm | 185 +++++++----------- .../puzzles/singleton_top_layer_v1_1.clvm.hex | 2 +- ...ngleton_top_layer_v1_1.clvm.hex.sha256tree | 2 +- 3 files changed, 70 insertions(+), 119 deletions(-) diff --git a/chia/wallet/puzzles/singleton_top_layer_v1_1.clvm b/chia/wallet/puzzles/singleton_top_layer_v1_1.clvm index 3e4ade403c90..5b59cfa38886 100644 --- a/chia/wallet/puzzles/singleton_top_layer_v1_1.clvm +++ b/chia/wallet/puzzles/singleton_top_layer_v1_1.clvm @@ -14,77 +14,45 @@ (include curry-and-treehash.clinc) ; also imports the constant ONE == 1 (include singleton_truths.clib) - ; takes a lisp tree and returns the hash of it - (defun sha256tree1 (TREE) - (if (l TREE) - (sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE))) - (sha256 ONE TREE) + (defmacro assert items + (if (r items) + (list if (f items) (c assert (r items)) (q . (x))) + (f items) ) ) - ; "assert" is a macro that wraps repeated instances of "if" - ; usage: (assert A0 A1 ... An R) - ; all of A0, A1, ... An must evaluate to non-null, or an exception is raised - ; return the value of R (if we get that far) + (defmacro and ARGS + (if ARGS + (qq (if (unquote (f ARGS)) + (unquote (c and (r ARGS))) + () + )) + 1) + ) - (defmacro assert items - (if (r items) - (list if (f items) (c assert (r items)) (q . (x))) - (f items) - ) - ) + ; takes a lisp tree and returns the hash of it + (defun sha256tree (TREE) + (if (l TREE) + (sha256 2 (sha256tree (f TREE)) (sha256tree (r TREE))) + (sha256 ONE TREE))) - (defun-inline mod_hash_for_singleton_struct (SINGLETON_STRUCT) (f SINGLETON_STRUCT)) - (defun-inline launcher_id_for_singleton_struct (SINGLETON_STRUCT) (f (r SINGLETON_STRUCT))) - (defun-inline launcher_puzzle_hash_for_singleton_struct (SINGLETON_STRUCT) (r (r SINGLETON_STRUCT))) + (defun-inline mod_hash_for_singleton_struct (SINGLETON_STRUCT) (f SINGLETON_STRUCT)) + (defun-inline launcher_id_for_singleton_struct (SINGLETON_STRUCT) (f (r SINGLETON_STRUCT))) + (defun-inline launcher_puzzle_hash_for_singleton_struct (SINGLETON_STRUCT) (r (r SINGLETON_STRUCT))) ;; return the full puzzlehash for a singleton with the innerpuzzle curried in ; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc (defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash) (puzzle-hash-of-curried-function (mod_hash_for_singleton_struct SINGLETON_STRUCT) inner_puzzle_hash - (sha256tree1 SINGLETON_STRUCT) + (sha256tree SINGLETON_STRUCT) ) ) - ; assembles information from the solution to create our own full ID including asserting our parent is a singleton - (defun-inline create_my_ID (SINGLETON_STRUCT full_puzzle_hash parent_parent parent_inner_puzzle_hash parent_amount my_amount) - (sha256 (sha256 parent_parent (calculate_full_puzzle_hash SINGLETON_STRUCT parent_inner_puzzle_hash) parent_amount) - full_puzzle_hash - my_amount) - ) - - ;; take a boolean and a non-empty list of conditions - ;; strip off the first condition if a boolean is set - ;; this is used to remove `(CREATE_COIN xxx -113)` - ;; pretty sneaky, eh? - (defun strip_first_condition_if (boolean condition_list) - (if boolean - (r condition_list) - condition_list - ) - ) - (defun-inline morph_condition (condition SINGLETON_STRUCT) (c (f condition) (c (calculate_full_puzzle_hash SINGLETON_STRUCT (f (r condition))) (r (r condition)))) ) - ;; return the value of the coin created if this is a `CREATE_COIN` condition, or 0 otherwise - (defun-inline created_coin_value_or_0 (condition) - (if (= (f condition) CREATE_COIN) - (f (r (r condition))) - 0 - ) - ) - - ;; Returns a (bool . bool) - (defun odd_cons_m113 (output_amount) - (c - (= (logand output_amount ONE) ONE) ;; is it odd? - (= output_amount -113) ;; is it the escape value? - ) - ) - ; Assert exactly one output with odd value exists - ignore it if value is -113 ;; this function iterates over the output conditions from the inner puzzle & solution @@ -96,78 +64,61 @@ (defun check_and_morph_conditions_for_singleton (SINGLETON_STRUCT conditions has_odd_output_been_found) (if conditions - (morph_next_condition SINGLETON_STRUCT conditions has_odd_output_been_found (odd_cons_m113 (created_coin_value_or_0 (f conditions)))) - (if has_odd_output_been_found - 0 - (x) ;; no odd output found - ) + ; check if it's an odd create coin + (if (and (= (f (f conditions)) CREATE_COIN) (logand (f (r (r (f conditions)))) ONE)) + ; check that we haven't already found one + (assert (not has_odd_output_been_found) + ; then + (if (= (f (r (r (f conditions)))) -113) + ; If it's the melt condition we don't bother prepending this condition + (check_and_morph_conditions_for_singleton SINGLETON_STRUCT (r conditions) ONE) + ; If it isn't the melt condition, we morph it and prepend it + (c (morph_condition (f conditions) SINGLETON_STRUCT) (check_and_morph_conditions_for_singleton SINGLETON_STRUCT (r conditions) ONE)) + ) + ) + (c (f conditions) (check_and_morph_conditions_for_singleton SINGLETON_STRUCT (r conditions) has_odd_output_been_found)) ) + (assert has_odd_output_been_found ()) + ) ) - ;; a continuation of `check_and_morph_conditions_for_singleton` with booleans `is_output_odd` and `is_output_m113` - ;; precalculated - (defun morph_next_condition (SINGLETON_STRUCT conditions has_odd_output_been_found (is_output_odd . is_output_m113)) - (assert - (not (all is_output_odd has_odd_output_been_found)) - (strip_first_condition_if - is_output_m113 - (c (if is_output_odd - (morph_condition (f conditions) SINGLETON_STRUCT) - (f conditions) - ) - (check_and_morph_conditions_for_singleton SINGLETON_STRUCT (r conditions) (any is_output_odd has_odd_output_been_found)) - ) - ) - ) - ) - - ; this final stager asserts our ID - ; it also runs the innerpuz with the innersolution with the "truths" added - ; it then passes that output conditions from the innerpuz to the morph conditions function - (defun-inline stager_three (SINGLETON_STRUCT my_id INNER_PUZZLE inner_solution) - (c (list ASSERT_MY_COIN_ID my_id) (check_and_morph_conditions_for_singleton SINGLETON_STRUCT (a INNER_PUZZLE inner_solution) 0)) - ) + ; assert that either the lineage proof is for a parent singleton, or, if it's for the launcher, verify it matched our launcher ID + ; then return a condition asserting it actually is our parent ID + (defun verify_lineage_proof (SINGLETON_STRUCT parent_id is_not_launcher) + (assert (any is_not_launcher (= parent_id (launcher_id_for_singleton_struct SINGLETON_STRUCT))) + ; then + (list ASSERT_MY_PARENT_ID parent_id) + ) + ) - ; this checks whether we are an eve spend or not and calculates our full coin ID appropriately and passes it on to the final stager - ; if we are the eve spend it also adds the additional checks that our parent's puzzle is the standard launcher format and that out parent ID is the same as our singleton ID + ; main - (defun-inline stager_two (SINGLETON_STRUCT lineage_proof full_puzhash my_amount INNER_PUZZLE inner_solution) - (stager_three - SINGLETON_STRUCT - (if (is_not_eve_proof lineage_proof) - (create_my_ID - SINGLETON_STRUCT - full_puzhash + ; if our value is not an odd amount then we are invalid + (assert (logand my_amount ONE) + ; then + (c + (list ASSERT_MY_AMOUNT my_amount) + (c + ; Verify the lineage proof by asserting our parent's ID + (verify_lineage_proof + SINGLETON_STRUCT + ; calculate our parent's ID + (sha256 (parent_info_for_lineage_proof lineage_proof) - (puzzle_hash_for_lineage_proof lineage_proof) - (amount_for_lineage_proof lineage_proof) - my_amount - ) - (if (= - (launcher_id_for_singleton_struct SINGLETON_STRUCT) - (sha256 (parent_info_for_eve_proof lineage_proof) (launcher_puzzle_hash_for_singleton_struct SINGLETON_STRUCT) (amount_for_eve_proof lineage_proof)) - ) - (sha256 (launcher_id_for_singleton_struct SINGLETON_STRUCT) full_puzhash my_amount) - (x) + (if (is_not_eve_proof lineage_proof) ; The PH calculation changes based on the lineage proof + (calculate_full_puzzle_hash SINGLETON_STRUCT (puzzle_hash_for_lineage_proof lineage_proof)) ; wrap the innerpuz in a singleton + (launcher_puzzle_hash_for_singleton_struct SINGLETON_STRUCT) ; Use the static launcher puzzle hash + ) + (if (is_not_eve_proof lineage_proof) ; The position of "amount" changes based on the type on lineage proof + (amount_for_lineage_proof lineage_proof) + (amount_for_eve_proof lineage_proof) + ) ) + (is_not_eve_proof lineage_proof) + ) + ; finally check all of the conditions for a single odd output to wrap + (check_and_morph_conditions_for_singleton SINGLETON_STRUCT (a INNER_PUZZLE inner_solution) 0) ) - INNER_PUZZLE - inner_solution ) ) - - ; this calculates our current full puzzle hash and passes it to stager two - (defun-inline stager_one (SINGLETON_STRUCT lineage_proof my_innerpuzhash my_amount INNER_PUZZLE inner_solution) - (stager_two SINGLETON_STRUCT lineage_proof (calculate_full_puzzle_hash SINGLETON_STRUCT my_innerpuzhash) my_amount INNER_PUZZLE inner_solution) - ) - - - ; main - - ; if our value is not an odd amount then we are invalid - ; this calculates my_innerpuzhash and passes all values to stager_one - (if (logand my_amount ONE) - (stager_one SINGLETON_STRUCT lineage_proof (sha256tree1 INNER_PUZZLE) my_amount INNER_PUZZLE inner_solution) - (x) - ) ) diff --git a/chia/wallet/puzzles/singleton_top_layer_v1_1.clvm.hex b/chia/wallet/puzzles/singleton_top_layer_v1_1.clvm.hex index d76cb2876e5b..245a282670cd 100644 --- a/chia/wallet/puzzles/singleton_top_layer_v1_1.clvm.hex +++ b/chia/wallet/puzzles/singleton_top_layer_v1_1.clvm.hex @@ -1 +1 @@ -ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff10ffff04ffff02ffff03ff77ffff01ff0bffff0bff27ffff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff81b780ffff02ff36ffff04ff02ffff04ff09ffff04ffff02ff2effff04ff02ffff04ff0bff80808080ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff2f80ffff01ff02ffff03ffff09ff15ffff0bff27ff1dff578080ffff01ff0bff15ffff02ff36ffff04ff02ffff04ff09ffff04ffff02ff2effff04ff02ffff04ff0bff80808080ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff2f80ffff01ff088080ff018080ff0180ff808080ffff02ff2affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff80808080808080ffff01ff088080ff0180ffff04ffff01ffffff46ff0233ffff0401ff0102ffffff02ffff03ff05ffff01ff02ff12ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff0bffff01ff02ff3affff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ffff02ff26ffff04ff02ffff04ffff02ffff03ffff09ff23ff3880ffff0181b3ff8080ff0180ff80808080ff80808080808080ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ff02ffff03ffff20ffff22ff4fff178080ffff01ff02ff3effff04ff02ffff04ff6fffff04ffff04ffff02ffff03ff4fffff01ff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff011380ff0180ffff02ff2affff04ff02ffff04ff05ffff04ff1bffff04ffff21ff4fff1780ff80808080808080ff8080808080ffff01ff088080ff0180ffffff04ffff09ffff18ff05ff3480ff3480ffff09ff05ffff01818f8080ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff12ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bff34ff058080ff0180ff02ffff03ff05ffff011bffff010b80ff0180ff018080 \ No newline at end of file +ff02ffff01ff02ffff03ffff18ff2fff3c80ffff01ff04ffff04ff10ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff0bff27ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff02ffff03ff77ffff0181b7ffff015780ff018080ffff04ff77ff808080808080ffff02ff26ffff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffff49ff4702ff33ff0401ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff2c80ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff0bffff01ff02ffff03ffff02ffff03ffff09ff23ff1480ffff01ff02ffff03ffff18ff81b3ff3c80ffff01ff0101ff8080ff0180ff8080ff0180ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff1bffff04ff3cff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff26ffff04ff02ffff04ff05ffff04ff1bffff04ff3cff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff26ffff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ff0bff2affff0bff3cff3880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bff3cff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff28ffff04ff0bff808080ffff01ff088080ff0180ff018080 \ No newline at end of file diff --git a/chia/wallet/puzzles/singleton_top_layer_v1_1.clvm.hex.sha256tree b/chia/wallet/puzzles/singleton_top_layer_v1_1.clvm.hex.sha256tree index 01f28166d068..9084d8194e04 100644 --- a/chia/wallet/puzzles/singleton_top_layer_v1_1.clvm.hex.sha256tree +++ b/chia/wallet/puzzles/singleton_top_layer_v1_1.clvm.hex.sha256tree @@ -1 +1 @@ -ffc0846eea5898e9aefea956b7d0e08df97a1c080910648270d979a4900d88aa +f1e8350cec62f8204aaf867cc3c12cae369f619258206616108c6cfd7be760b3 From ccb95da2e10ebe714fe8039373b1c829fac3ee60 Mon Sep 17 00:00:00 2001 From: matt-o-how Date: Mon, 25 Apr 2022 17:54:17 +0100 Subject: [PATCH 03/43] flesh out ownership layer --- chia/wallet/puzzles/nft_ownership_layer.clvm | 63 ++++++++++++++++++-- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/chia/wallet/puzzles/nft_ownership_layer.clvm b/chia/wallet/puzzles/nft_ownership_layer.clvm index 15dbeab1d2fe..30f649889fc5 100644 --- a/chia/wallet/puzzles/nft_ownership_layer.clvm +++ b/chia/wallet/puzzles/nft_ownership_layer.clvm @@ -1,19 +1,70 @@ (mod ( + NFT_OWNERSHIP_LAYER_MOD_HASH CURRENT_OWNER TRANSFER_PROGRAM_HASH INNER_PUZZLE inner_solution ) - ; main - (defun wrap_all_create_coins_in_this (new_owner . conditions) - ... + (include condition_codes.clvm) + (include curry-and-treehash.clinc) + + (defconstant MAGIC_NUMBER -10) + + (defun-ineline nft_ownership_layer_puzzle_hash (NFT_OWNERSHIP_LAYER_MOD_HASH new_owner TRANSFER_PROGRAM_HASH inner_puzzle_hash) + (puzzle-hash-of-curried-function NFT_OWNERSHIP_LAYER_MOD_HASH + (sha256 ONE inner_puzzle_hash) + (sha256 ONE TRANSFER_PROGRAM_HASH) + (sha256 ONE new_owner) + (sha256 ONE NFT_OWNERSHIP_LAYER_MOD_HASH) + ) ) + (defun wrap_odd_create_coins (NFT_OWNERSHIP_LAYER_MOD_HASH (new_owner . conditions) TRANSFER_PROGRAM_HASH inner_puzzle_hash) + (if conditions + (if (= (f (f conditions)) CREATE_COIN) + (if (logand (f (r (r (f conditions)))) ONE) + (c (list CREATE_COIN (nft_ownership_layer_puzzle_hash NFT_OWNERSHIP_LAYER_MOD_HASH new_owner TRANSFER_PROGRAM_HASH) (f (r (r (f conditions))))) (wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH (c new_owner (r conditions)) TRANSFER_PROGRAM_HASH inner_puzzle_hash))) + (c (f conditions) (wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH (c new_owner (r conditions)) TRANSFER_PROGRAM_HASH inner_puzzle_hash)) + ) + (c (f conditions) (wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH (c new_owner (r conditions)) TRANSFER_PROGRAM_HASH inner_puzzle_hash)) + ) + () + ) + ) - (wrap_all_create_coins_in_this - (loop_through_conditions_looking_for_transfer_program_reveal_and_solution_and_add_its_conditions - (a INNER_PUZZLE inner_solution) + (defun merge_list (list_a list_b) + (if list_a + (c (f list_a) (merge_list (r list_a) list_b)) + list_b ) ) + + (defun process_trans_program (list_a list_b) + (c (f list_a) (merge_list (r list_a) list_b)) + ) + + (defun loop_through_conditions_looking_for_transfer_program_reveal_and_solution_and_add_its_conditions (TRANSFER_PROGRAM_HASH conditions) + (if conditions + (if (= (f (f conditions)) MAGIC_NUMBER) + (if (= (sha256tree1 (f (r (f conditions)))) TRANSFER_PROGRAM_HASH) + (process_trans_program (a (f (r (f conditions))) (f (r (r (f conditions))))) conditions) + (x) + ) + (c (f conditions) (loop_through_conditions_looking_for_transfer_program_reveal_and_solution_and_add_its_conditions TRANSFER_PROGRAM_HASH (r conditions))) + ) + (x) + ) + ) + + ; main + (wrap_odd_create_coins + NFT_OWNERSHIP_LAYER_MOD_HASH + (loop_through_conditions_looking_for_transfer_program_reveal_and_solution_and_add_its_conditions + TRANSFER_PROGRAM_HASH + (a INNER_PUZZLE inner_solution) + ) + TRANSFER_PROGRAM_HASH + (sha256tree1 INNER_PUZZLE) + ) ) From ebe6ade139acaa5b9711a6bb00998f5db6aca728 Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Fri, 15 Apr 2022 15:27:33 -0700 Subject: [PATCH 04/43] Generalize the Offer class to more than CATs --- chia/wallet/trading/offer.py | 112 ++++++++---------- chia/wallet/trading/outer_puzzles.py | 82 +++++++++++++ .../wallet/cat_wallet/test_offer_lifecycle.py | 18 ++- 3 files changed, 146 insertions(+), 66 deletions(-) create mode 100644 chia/wallet/trading/outer_puzzles.py diff --git a/chia/wallet/trading/offer.py b/chia/wallet/trading/offer.py index 4140a1e67a96..69edc0057d4c 100644 --- a/chia/wallet/trading/offer.py +++ b/chia/wallet/trading/offer.py @@ -15,16 +15,15 @@ decompress_object_with_puzzles, lowest_best_version, ) -from chia.wallet.cat_wallet.cat_utils import ( - CAT_MOD, - SpendableCAT, - construct_cat_puzzle, - match_cat_puzzle, - unsigned_spend_bundle_for_spendable_cats, -) -from chia.wallet.lineage_proof import LineageProof from chia.wallet.puzzles.load_clvm import load_clvm from chia.wallet.payment import Payment +from chia.wallet.trading.outer_puzzles import ( + AssetType, + type_of_puzzle, + asset_id_of_puzzle, + construct_puzzle, + solve_puzzle, +) OFFER_MOD = load_clvm("settlement_payments.clvm") ZERO_32 = bytes32([0] * 32) @@ -48,6 +47,7 @@ class Offer: Optional[bytes32], List[NotarizedPayment] ] # The key is the asset id of the asset being requested bundle: SpendBundle + type_dict: Dict[bytes32, AssetType] # asset_id -> asset type @staticmethod def ph(): @@ -64,23 +64,23 @@ def notarize_payments( nonce: bytes32 = Program.to(sorted_coin_list).get_tree_hash() notarized_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = {} - for tail_hash, payments in requested_payments.items(): - notarized_payments[tail_hash] = [] + for asset_id, payments in requested_payments.items(): + notarized_payments[asset_id] = [] for p in payments: puzzle_hash, amount, memos = tuple(p.as_condition_args()) - notarized_payments[tail_hash].append(NotarizedPayment(puzzle_hash, amount, memos, nonce)) + notarized_payments[asset_id].append(NotarizedPayment(puzzle_hash, amount, memos, nonce)) return notarized_payments # The announcements returned from this function must be asserted in whatever spend bundle is created by the wallet @staticmethod def calculate_announcements( - notarized_payments: Dict[Optional[bytes32], List[NotarizedPayment]], + notarized_payments: Dict[Optional[bytes32], List[NotarizedPayment]], type_dict: Dict[bytes32, AssetType] ) -> List[Announcement]: announcements: List[Announcement] = [] - for tail, payments in notarized_payments.items(): - if tail is not None: - settlement_ph: bytes32 = construct_cat_puzzle(CAT_MOD, tail, OFFER_MOD).get_tree_hash() + for asset_id, payments in notarized_payments.items(): + if asset_id is not None: + settlement_ph: bytes32 = construct_puzzle(type_dict[asset_id], asset_id, OFFER_MOD).get_tree_hash() else: settlement_ph = OFFER_MOD.get_tree_hash() @@ -114,22 +114,18 @@ def get_offered_coins(self) -> Dict[Optional[bytes32], List[Coin]]: )[0].puzzle_reveal.to_program() # Determine it's TAIL (or lack of) - matched, curried_args = match_cat_puzzle(parent_puzzle) - tail_hash: Optional[bytes32] = None - if matched: - _, tail_hash_program, _ = curried_args - tail_hash = bytes32(tail_hash_program.as_python()) - offer_ph: bytes32 = construct_cat_puzzle(CAT_MOD, tail_hash, OFFER_MOD).get_tree_hash() + asset_id = asset_id_of_puzzle(parent_puzzle) + if asset_id is not None: + offer_ph: bytes32 = construct_puzzle(self.type_dict[asset_id], asset_id, OFFER_MOD).get_tree_hash() else: - tail_hash = None offer_ph = OFFER_MOD.get_tree_hash() # Check if the puzzle_hash matches the hypothetical `settlement_payments` puzzle hash if addition.puzzle_hash == offer_ph: - if tail_hash in offered_coins: - offered_coins[tail_hash].append(addition) + if asset_id in offered_coins: + offered_coins[asset_id].append(addition) else: - offered_coins[tail_hash] = [addition] + offered_coins[asset_id] = [addition] return offered_coins @@ -234,6 +230,7 @@ def get_primary_coins(self) -> List[Coin]: def aggregate(cls, offers: List["Offer"]) -> "Offer": total_requested_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = {} total_bundle = SpendBundle([], G2Element()) + total_type_dict: Dict[bytes32, AssetType] = {} for offer in offers: # First check for any overlap in inputs total_inputs: Set[Coin] = {cs.coin for cs in total_bundle.coin_spends} @@ -242,15 +239,20 @@ def aggregate(cls, offers: List["Offer"]) -> "Offer": raise ValueError("The aggregated offers overlap inputs") # Next, do the aggregation - for tail, payments in offer.requested_payments.items(): - if tail in total_requested_payments: - total_requested_payments[tail].extend(payments) + for asset_id, payments in offer.requested_payments.items(): + if asset_id in total_requested_payments: + total_requested_payments[asset_id].extend(payments) else: - total_requested_payments[tail] = payments + total_requested_payments[asset_id] = payments + + for key, value in offer.type_dict.items(): + if key in total_type_dict and total_type_dict[key] != value: + raise ValueError(f"The offers to aggregate disagree on the type of {key.hex()}") total_bundle = SpendBundle.aggregate([total_bundle, offer.bundle]) + total_type_dict = {**total_type_dict, **offer.type_dict} - return cls(total_requested_payments, total_bundle) + return cls(total_requested_payments, total_bundle, total_type_dict) # Validity is defined by having enough funds within the offer to satisfy both sides def is_valid(self) -> bool: @@ -263,11 +265,11 @@ def to_valid_spend(self, arbitrage_ph: Optional[bytes32] = None) -> SpendBundle: raise ValueError("Offer is currently incomplete") completion_spends: List[CoinSpend] = [] - for tail_hash, payments in self.requested_payments.items(): - offered_coins: List[Coin] = self.get_offered_coins()[tail_hash] + for asset_id, payments in self.requested_payments.items(): + offered_coins: List[Coin] = self.get_offered_coins()[asset_id] # Because of CAT supply laws, we must specify a place for the leftovers to go - arbitrage_amount: int = self.arbitrage()[tail_hash] + arbitrage_amount: int = self.arbitrage()[asset_id] all_payments: List[NotarizedPayment] = payments.copy() if arbitrage_amount > 0: assert arbitrage_amount is not None @@ -282,28 +284,17 @@ def to_valid_spend(self, arbitrage_ph: Optional[bytes32] = None) -> SpendBundle: nonce_payments: List[NotarizedPayment] = list(filter(lambda p: p.nonce == nonce, all_payments)) inner_solutions.append((nonce, [np.as_condition_args() for np in nonce_payments])) - if tail_hash: + if asset_id: # CATs have a special way to be solved so we have to do some calculation before getting the solution parent_spend: CoinSpend = list( filter(lambda cs: cs.coin.name() == coin.parent_coin_info, self.bundle.coin_spends) )[0] - parent_coin: Coin = parent_spend.coin - matched, curried_args = match_cat_puzzle(parent_spend.puzzle_reveal.to_program()) - assert matched - _, _, inner_puzzle = curried_args - spendable_cat = SpendableCAT( + solution: Program = solve_puzzle( + self.type_dict[asset_id], coin, - tail_hash, OFFER_MOD, Program.to(inner_solutions), - lineage_proof=LineageProof( - parent_coin.parent_coin_info, inner_puzzle.get_tree_hash(), parent_coin.amount - ), - ) - solution: Program = ( - unsigned_spend_bundle_for_spendable_cats(CAT_MOD, [spendable_cat]) - .coin_spends[0] - .solution.to_program() + parent_spend, ) else: solution = Program.to(inner_solutions) @@ -311,7 +302,7 @@ def to_valid_spend(self, arbitrage_ph: Optional[bytes32] = None) -> SpendBundle: completion_spends.append( CoinSpend( coin, - construct_cat_puzzle(CAT_MOD, tail_hash, OFFER_MOD) if tail_hash else OFFER_MOD, + construct_puzzle(self.type_dict[asset_id], asset_id, OFFER_MOD) if asset_id else OFFER_MOD, solution, ) ) @@ -321,8 +312,10 @@ def to_valid_spend(self, arbitrage_ph: Optional[bytes32] = None) -> SpendBundle: def to_spend_bundle(self) -> SpendBundle: # Before we serialze this as a SpendBundle, we need to serialze the `requested_payments` as dummy CoinSpends additional_coin_spends: List[CoinSpend] = [] - for tail_hash, payments in self.requested_payments.items(): - puzzle_reveal: Program = construct_cat_puzzle(CAT_MOD, tail_hash, OFFER_MOD) if tail_hash else OFFER_MOD + for asset_id, payments in self.requested_payments.items(): + puzzle_reveal: Program = ( + construct_puzzle(self.type_dict[asset_id], asset_id, OFFER_MOD) if asset_id else OFFER_MOD + ) inner_solutions = [] nonces: List[bytes32] = [p.nonce for p in payments] for nonce in list(dict.fromkeys(nonces)): # dedup without messing with order @@ -352,16 +345,15 @@ def to_spend_bundle(self) -> SpendBundle: def from_spend_bundle(cls, bundle: SpendBundle) -> "Offer": # Because of the `to_spend_bundle` method, we need to parse the dummy CoinSpends as `requested_payments` requested_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = {} + type_dict: Dict[bytes32, AssetType] = {} leftover_coin_spends: List[CoinSpend] = [] for coin_spend in bundle.coin_spends: + asset_id = asset_id_of_puzzle(coin_spend.puzzle_reveal.to_program()) + if asset_id is not None: + typ = type_of_puzzle(coin_spend.puzzle_reveal.to_program()) + assert typ is not None + type_dict[asset_id] = typ if coin_spend.coin.parent_coin_info == ZERO_32: - matched, curried_args = match_cat_puzzle(coin_spend.puzzle_reveal.to_program()) - if matched: - _, tail_hash_program, _ = curried_args - tail_hash: Optional[bytes32] = bytes32(tail_hash_program.as_python()) - else: - tail_hash = None - notarized_payments: List[NotarizedPayment] = [] for payment_group in coin_spend.solution.to_program().as_iter(): nonce = bytes32(payment_group.first().as_python()) @@ -369,12 +361,12 @@ def from_spend_bundle(cls, bundle: SpendBundle) -> "Offer": notarized_payments.extend( [NotarizedPayment.from_condition_and_nonce(condition, nonce) for condition in payment_args_list] ) - requested_payments[tail_hash] = notarized_payments + requested_payments[asset_id] = notarized_payments else: leftover_coin_spends.append(coin_spend) - return cls(requested_payments, SpendBundle(leftover_coin_spends, bundle.aggregated_signature)) + return cls(requested_payments, SpendBundle(leftover_coin_spends, bundle.aggregated_signature), type_dict) def name(self) -> bytes32: return self.to_spend_bundle().name() diff --git a/chia/wallet/trading/outer_puzzles.py b/chia/wallet/trading/outer_puzzles.py new file mode 100644 index 000000000000..e97964f4f972 --- /dev/null +++ b/chia/wallet/trading/outer_puzzles.py @@ -0,0 +1,82 @@ +from enum import Enum +from typing import Optional + +from chia.types.blockchain_format.sized_bytes import bytes32 +from chia.types.blockchain_format.coin import Coin +from chia.types.blockchain_format.program import Program +from chia.types.coin_spend import CoinSpend +from chia.wallet.cat_wallet.cat_utils import ( + CAT_MOD, + SpendableCAT, + match_cat_puzzle, + construct_cat_puzzle, + unsigned_spend_bundle_for_spendable_cats, +) +from chia.wallet.lineage_proof import LineageProof + + +class AssetType(Enum): + CAT = "CAT" + + +class CATOuterPuzzle: + @staticmethod + def asset_id(puzzle: Program) -> Optional[bytes32]: + matched, curried_args = match_cat_puzzle(puzzle) + if matched: + _, tail_hash, _ = curried_args + return bytes32(tail_hash.as_python()) + else: + return None + + @staticmethod + def construct(asset_id: bytes32, inner_puzzle: Program) -> Program: + return construct_cat_puzzle(CAT_MOD, asset_id, inner_puzzle) + + @staticmethod + def solve(coin: Coin, inner_puzzle: Program, inner_solution: Program, parent_spend: CoinSpend) -> Program: + parent_coin: Coin = parent_spend.coin + matched, curried_args = match_cat_puzzle(parent_spend.puzzle_reveal.to_program()) + assert matched + _, tail_hash, parent_inner_puzzle = curried_args + spendable_cat = SpendableCAT( + coin, + tail_hash, + inner_puzzle, + inner_solution, + lineage_proof=LineageProof( + parent_coin.parent_coin_info, parent_inner_puzzle.get_tree_hash(), parent_coin.amount + ), + ) + return unsigned_spend_bundle_for_spendable_cats(CAT_MOD, [spendable_cat]).coin_spends[0].solution.to_program() + + +OUTER_PUZZLES = { + AssetType.CAT: CATOuterPuzzle, +} + + +def type_of_puzzle(puzzle: Program) -> Optional[AssetType]: + for typ, outer in OUTER_PUZZLES.items(): + asset_id = outer.asset_id(puzzle) + if asset_id is not None: + return typ + return None + + +def asset_id_of_puzzle(puzzle: Program) -> Optional[bytes32]: + for type, outer in OUTER_PUZZLES.items(): + asset_id = outer.asset_id(puzzle) + if asset_id is not None: + return asset_id + return None + + +def construct_puzzle(typ: AssetType, asset_id: bytes32, inner_puzzle: Program) -> Program: + return OUTER_PUZZLES[typ].construct(asset_id, inner_puzzle) + + +def solve_puzzle( + typ: AssetType, coin: Coin, inner_puzzle: Program, inner_solution: Program, parent_spend: CoinSpend +) -> Program: + return OUTER_PUZZLES[typ].solve(coin, inner_puzzle, inner_solution, parent_spend) diff --git a/tests/wallet/cat_wallet/test_offer_lifecycle.py b/tests/wallet/cat_wallet/test_offer_lifecycle.py index a2777376e8c7..01d4da350b4c 100644 --- a/tests/wallet/cat_wallet/test_offer_lifecycle.py +++ b/tests/wallet/cat_wallet/test_offer_lifecycle.py @@ -19,6 +19,7 @@ ) from chia.wallet.payment import Payment from chia.wallet.trading.offer import Offer, NotarizedPayment +from chia.wallet.trading.outer_puzzles import AssetType from tests.clvm.benchmark_costs import cost_of_spend_bundle acs = Program.to(1) @@ -178,6 +179,11 @@ async def test_complex_offer(self, setup_sim): red_coins: List[Coin] = all_coins["red"] blue_coins: List[Coin] = all_coins["blue"] + type_dict: Dict[bytes32, AssetType] = { + str_to_tail_hash("red"): AssetType.CAT, + str_to_tail_hash("blue"): AssetType.CAT, + } + # Create an XCH Offer for RED chia_requested_payments: Dict[Optional[bytes32], List[Payment]] = { str_to_tail_hash("red"): [ @@ -189,9 +195,9 @@ async def test_complex_offer(self, setup_sim): chia_requested_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments( chia_requested_payments, chia_coins ) - chia_announcements: List[Announcement] = Offer.calculate_announcements(chia_requested_payments) + chia_announcements: List[Announcement] = Offer.calculate_announcements(chia_requested_payments, type_dict) chia_secured_bundle: SpendBundle = generate_secure_bundle(chia_coins, chia_announcements, 1000) - chia_offer = Offer(chia_requested_payments, chia_secured_bundle) + chia_offer = Offer(chia_requested_payments, chia_secured_bundle, type_dict) assert not chia_offer.is_valid() # Create a RED Offer for XCH @@ -205,9 +211,9 @@ async def test_complex_offer(self, setup_sim): red_requested_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments( red_requested_payments, red_coins ) - red_announcements: List[Announcement] = Offer.calculate_announcements(red_requested_payments) + red_announcements: List[Announcement] = Offer.calculate_announcements(red_requested_payments, type_dict) red_secured_bundle: SpendBundle = generate_secure_bundle(red_coins, red_announcements, 350, tail_str="red") - red_offer = Offer(red_requested_payments, red_secured_bundle) + red_offer = Offer(red_requested_payments, red_secured_bundle, type_dict) assert not red_offer.is_valid() # Test aggregation of offers @@ -229,11 +235,11 @@ async def test_complex_offer(self, setup_sim): blue_requested_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments( blue_requested_payments, blue_coins ) - blue_announcements: List[Announcement] = Offer.calculate_announcements(blue_requested_payments) + blue_announcements: List[Announcement] = Offer.calculate_announcements(blue_requested_payments, type_dict) blue_secured_bundle: SpendBundle = generate_secure_bundle( blue_coins, blue_announcements, 2000, tail_str="blue" ) - blue_offer = Offer(blue_requested_payments, blue_secured_bundle) + blue_offer = Offer(blue_requested_payments, blue_secured_bundle, type_dict) assert not blue_offer.is_valid() # Test a re-aggregation From bfa0c6c700441b8c0322a723f73b55ec43b46195 Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Sat, 16 Apr 2022 12:13:40 -0700 Subject: [PATCH 05/43] Remove CAT dependencies from trade_manager --- chia/wallet/trade_manager.py | 36 +++++++++++++++---------- chia/wallet/trading/offer.py | 7 +++++ chia/wallet/wallet_state_manager.py | 8 ++++++ tests/wallet/cat_wallet/test_trades.py | 37 ++++++++++++++++++++------ 4 files changed, 66 insertions(+), 22 deletions(-) diff --git a/chia/wallet/trade_manager.py b/chia/wallet/trade_manager.py index dc9d9125d1e3..9ef72c5d5dd1 100644 --- a/chia/wallet/trade_manager.py +++ b/chia/wallet/trade_manager.py @@ -12,10 +12,10 @@ from chia.util.db_wrapper import DBWrapper from chia.util.hash import std_hash from chia.util.ints import uint32, uint64 -from chia.wallet.cat_wallet.cat_wallet import CATWallet from chia.wallet.payment import Payment from chia.wallet.trade_record import TradeRecord from chia.wallet.trading.offer import Offer, NotarizedPayment +from chia.wallet.trading.outer_puzzles import AssetType from chia.wallet.trading.trade_status import TradeStatus from chia.wallet.trading.trade_store import TradeStore from chia.wallet.transaction_record import TransactionRecord @@ -188,6 +188,7 @@ async def cancel_pending_offer_safely( continue new_ph = await wallet.get_new_puzzlehash() # This should probably not switch on whether or not we're spending a CAT but it has to for now + # ATTENTION: new_wallets if wallet.type() == WalletType.CAT: txs = await wallet.generate_signed_transaction( [coin.amount], [new_ph], fee=fee_to_pay, coins={coin}, ignore_max_send_amount=True @@ -246,9 +247,13 @@ async def save_trade(self, trade: TradeRecord): self.wallet_state_manager.state_changed("offer_added") async def create_offer_for_ids( - self, offer: Dict[Union[int, bytes32], int], fee: uint64 = uint64(0), validate_only: bool = False + self, + offer: Dict[Union[int, bytes32], int], + type_dict: Dict[bytes32, AssetType] = {}, + fee: uint64 = uint64(0), + validate_only: bool = False, ) -> Tuple[bool, Optional[TradeRecord], Optional[str]]: - success, created_offer, error = await self._create_offer_for_ids(offer, fee=fee) + success, created_offer, error = await self._create_offer_for_ids(offer, type_dict, fee=fee) if not success or created_offer is None: raise Exception(f"Error creating offer: {error}") @@ -273,7 +278,10 @@ async def create_offer_for_ids( return success, trade_offer, error async def _create_offer_for_ids( - self, offer_dict: Dict[Union[int, bytes32], int], fee: uint64 = uint64(0) + self, + offer_dict: Dict[Union[int, bytes32], int], + type_dict: Dict[bytes32, AssetType] = {}, + fee: uint64 = uint64(0), ) -> Tuple[bool, Optional[Offer], Optional[str]]: """ Offer is dictionary of wallet ids and amount @@ -315,14 +323,14 @@ async def _create_offer_for_ids( notarized_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments( requested_payments, all_coins ) - announcements_to_assert = Offer.calculate_announcements(notarized_payments) + announcements_to_assert = Offer.calculate_announcements(notarized_payments, type_dict) all_transactions: List[TransactionRecord] = [] fee_left_to_pay: uint64 = fee for wallet_id, selected_coins in coins_to_offer.items(): wallet = self.wallet_state_manager.wallets[wallet_id] # This should probably not switch on whether or not we're spending a CAT but it has to for now - + # ATTENTION: new_wallets if wallet.type() == WalletType.CAT: txs = await wallet.generate_signed_transaction( [abs(offer_dict[int(wallet_id)])], @@ -346,7 +354,7 @@ async def _create_offer_for_ids( transaction_bundles: List[Optional[SpendBundle]] = [tx.spend_bundle for tx in all_transactions] total_spend_bundle = SpendBundle.aggregate(list(filter(lambda b: b is not None, transaction_bundles))) - offer = Offer(notarized_payments, total_spend_bundle) + offer = Offer(notarized_payments, total_spend_bundle, type_dict) return True, offer, None except Exception as e: @@ -358,13 +366,12 @@ async def maybe_create_wallets_for_offer(self, offer: Offer): for key in offer.arbitrage(): wsm = self.wallet_state_manager - wallet: Wallet = wsm.main_wallet if key is None: continue - exists: Optional[Wallet] = await wsm.get_wallet_for_asset_id(key.hex()) + exists: Optional[Wallet] = await wsm.get_wallet_for_asset_id(key.hex()) # ATTENTION: new_wallets if exists is None: - self.log.info(f"Creating wallet for asset ID: {key}") - await CATWallet.create_wallet_for_cat(wsm, wallet, key.hex()) + await wsm.create_wallet_for_asset_id(key, offer.type_dict[key]) # ATTENTION: new_wallets + async def check_offer_validity(self, offer: Offer) -> bool: all_removals: List[Coin] = offer.bundle.removals() @@ -398,7 +405,7 @@ async def calculate_tx_records_for_offer(self, offer: Offer, validate: bool) -> wallet_id, _ = wallet_info if addition.parent_coin_info in settlement_coin_ids: wallet = self.wallet_state_manager.wallets[wallet_id] - to_puzzle_hash = await wallet.convert_puzzle_hash(addition.puzzle_hash) + to_puzzle_hash = await wallet.convert_puzzle_hash(addition.puzzle_hash) # ATTENTION: new wallets txs.append( TransactionRecord( confirmed_at_height=uint32(0), @@ -472,9 +479,10 @@ async def respond_to_offer(self, offer: Offer, fee=uint64(0)) -> Tuple[bool, Opt wallet = self.wallet_state_manager.main_wallet key: Union[bytes32, int] = int(wallet.id()) else: + # ATTENTION: new wallets wallet = await self.wallet_state_manager.get_wallet_for_asset_id(asset_id.hex()) if wallet is None and amount < 0: - return False, None, f"Do not have a CAT of asset ID: {asset_id} to fulfill offer" + return False, None, f"Do not have a wallet of asset ID: {asset_id} to fulfill offer" elif wallet is None: key = asset_id else: @@ -486,7 +494,7 @@ async def respond_to_offer(self, offer: Offer, fee=uint64(0)) -> Tuple[bool, Opt if not valid: return False, None, "This offer is no longer valid" - success, take_offer, error = await self._create_offer_for_ids(take_offer_dict, fee=fee) + success, take_offer, error = await self._create_offer_for_ids(take_offer_dict, offer.type_dict, fee=fee) if not success or take_offer is None: return False, None, error diff --git a/chia/wallet/trading/offer.py b/chia/wallet/trading/offer.py index 69edc0057d4c..dbdec18a28a1 100644 --- a/chia/wallet/trading/offer.py +++ b/chia/wallet/trading/offer.py @@ -80,6 +80,8 @@ def calculate_announcements( announcements: List[Announcement] = [] for asset_id, payments in notarized_payments.items(): if asset_id is not None: + if asset_id not in type_dict: + raise ValueError("Cannot calculate announcements without type of requested item") settlement_ph: bytes32 = construct_puzzle(type_dict[asset_id], asset_id, OFFER_MOD).get_tree_hash() else: settlement_ph = OFFER_MOD.get_tree_hash() @@ -103,6 +105,11 @@ def __post_init__(self): if len(set(payment_programs)) != len(payment_programs): raise ValueError("Bundle has duplicate requested payments") + # Verify we have a type for every kind of asset + for asset_id in self.requested_payments: + if asset_id is not None and asset_id not in self.type_dict: + raise ValueError("Offer does not have enough type information about the requested payments") + # This method does not get every coin that is being offered, only the `settlement_payment` children def get_offered_coins(self) -> Dict[Optional[bytes32], List[Coin]]: offered_coins: Dict[Optional[bytes32], List[Coin]] = {} diff --git a/chia/wallet/wallet_state_manager.py b/chia/wallet/wallet_state_manager.py index da56121d36b5..e6fe7d16bd62 100644 --- a/chia/wallet/wallet_state_manager.py +++ b/chia/wallet/wallet_state_manager.py @@ -44,6 +44,7 @@ from chia.wallet.rl_wallet.rl_wallet import RLWallet from chia.wallet.settings.user_settings import UserSettings from chia.wallet.trade_manager import TradeManager +from chia.wallet.trading.outer_puzzles import AssetType from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.compute_hints import compute_coin_hints from chia.wallet.util.transaction_type import TransactionType @@ -1280,6 +1281,13 @@ async def get_wallet_for_asset_id(self, asset_id: str): return wallet return None + async def create_wallet_for_asset_id(self, asset_id: bytes32, typ: AssetType): + self.log.info(f"Creating wallet for asset ID: {asset_id}") + if typ == AssetType.CAT: + await CATWallet.create_wallet_for_cat(self, self.main_wallet, asset_id.hex()) + else: + raise ValueError(f"Cannot create wallet for type {typ.value}") + async def add_new_wallet(self, wallet: Any, wallet_id: int, create_puzzle_hashes=True, in_transaction=False): self.wallets[uint32(wallet_id)] = wallet if create_puzzle_hashes: diff --git a/tests/wallet/cat_wallet/test_trades.py b/tests/wallet/cat_wallet/test_trades.py index 2ac4701bb25f..50f150a69fe3 100644 --- a/tests/wallet/cat_wallet/test_trades.py +++ b/tests/wallet/cat_wallet/test_trades.py @@ -6,9 +6,11 @@ from chia.full_node.mempool_manager import MempoolManager from chia.simulator.simulator_protocol import FarmNewBlockProtocol +from chia.types.blockchain_format.sized_bytes import bytes32 from chia.util.ints import uint64 from chia.wallet.cat_wallet.cat_wallet import CATWallet from chia.wallet.trading.offer import Offer +from chia.wallet.trading.outer_puzzles import AssetType from chia.wallet.trading.trade_status import TradeStatus from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.transaction_type import TransactionType @@ -104,9 +106,17 @@ async def test_cat_trades(self, wallets_prefarm): trade_manager_maker = wallet_node_maker.wallet_state_manager.trade_manager trade_manager_taker = wallet_node_taker.wallet_state_manager.trade_manager + type_dict = { + bytes32.from_hexstr(cat_wallet_maker.get_asset_id()): AssetType.CAT, + bytes32.from_hexstr(new_cat_wallet_maker.get_asset_id()): AssetType.CAT, + bytes32.from_hexstr(new_cat_wallet_taker.get_asset_id()): AssetType.CAT, + } + # Execute all of the trades # chia_for_cat - success, trade_make, error = await trade_manager_maker.create_offer_for_ids(chia_for_cat, fee=uint64(1)) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + chia_for_cat, type_dict, fee=uint64(1) + ) await asyncio.sleep(1) assert error is None assert success is True @@ -155,7 +165,7 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): await time_out_assert(15, assert_trade_tx_number, True, wallet_node_taker, trade_take.trade_id, 3) # cat_for_chia - success, trade_make, error = await trade_manager_maker.create_offer_for_ids(cat_for_chia) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids(cat_for_chia, type_dict) await asyncio.sleep(1) assert error is None assert success is True @@ -176,6 +186,13 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): cat_wallet_maker.get_asset_id() ) + type_dict = { + bytes32.from_hexstr(cat_wallet_maker.get_asset_id()): AssetType.CAT, + bytes32.from_hexstr(cat_wallet_taker.get_asset_id()): AssetType.CAT, + bytes32.from_hexstr(new_cat_wallet_maker.get_asset_id()): AssetType.CAT, + bytes32.from_hexstr(new_cat_wallet_taker.get_asset_id()): AssetType.CAT, + } + await time_out_assert(15, wallet_taker.get_unconfirmed_balance, TAKER_CHIA_BALANCE) await time_out_assert(15, cat_wallet_taker.get_unconfirmed_balance, TAKER_CAT_BALANCE) @@ -196,7 +213,7 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): await time_out_assert(15, assert_trade_tx_number, True, wallet_node_taker, trade_take.trade_id, 2) # cat_for_cat - success, trade_make, error = await trade_manager_maker.create_offer_for_ids(cat_for_cat) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids(cat_for_cat, type_dict) await asyncio.sleep(1) assert error is None assert success is True @@ -230,7 +247,7 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, trade_take) # chia_for_multiple_cat - success, trade_make, error = await trade_manager_maker.create_offer_for_ids(chia_for_multiple_cat) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids(chia_for_multiple_cat, type_dict) await asyncio.sleep(1) assert error is None assert success is True @@ -266,7 +283,7 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, trade_take) # multiple_cat_for_chia - success, trade_make, error = await trade_manager_maker.create_offer_for_ids(multiple_cat_for_chia) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids(multiple_cat_for_chia, type_dict) await asyncio.sleep(1) assert error is None assert success is True @@ -302,7 +319,7 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, trade_take) # chia_and_cat_for_cat - success, trade_make, error = await trade_manager_maker.create_offer_for_ids(chia_and_cat_for_cat) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids(chia_and_cat_for_cat, type_dict) await asyncio.sleep(1) assert error is None assert success is True @@ -376,11 +393,15 @@ async def test_trade_cancellation(self, wallets_prefarm): trade_manager_maker = wallet_node_maker.wallet_state_manager.trade_manager trade_manager_taker = wallet_node_taker.wallet_state_manager.trade_manager + type_dict = { + bytes32.from_hexstr(cat_wallet_maker.get_asset_id()): AssetType.CAT, + } + async def get_trade_and_status(trade_manager, trade) -> TradeStatus: trade_rec = await trade_manager.get_trade_by_id(trade.trade_id) return TradeStatus(trade_rec.status) - success, trade_make, error = await trade_manager_maker.create_offer_for_ids(cat_for_chia) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids(cat_for_chia, type_dict) await asyncio.sleep(1) assert error is None assert success is True @@ -441,7 +462,7 @@ async def get_trade_and_status(trade_manager, trade) -> TradeStatus: assert trade_take is None # Now we're going to create the other way around for test coverage sake - success, trade_make, error = await trade_manager_maker.create_offer_for_ids(chia_for_cat) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids(chia_for_cat, type_dict) await asyncio.sleep(1) assert error is None assert success is True From a4177674e8dac6b3b5138e0f9a5ff8ffac46788b Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Sat, 16 Apr 2022 14:50:32 -0700 Subject: [PATCH 06/43] Fix offer RPC --- chia/rpc/wallet_rpc_api.py | 14 +++++++++- chia/wallet/trade_manager.py | 15 ++++++++++- tests/wallet/cat_wallet/test_trades.py | 37 ++++++-------------------- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index 681fd3cdea36..b79171fa67e9 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -33,6 +33,7 @@ from chia.wallet.nft_wallet.nft_puzzles import get_uri_list_from_puzzle from chia.wallet.trade_record import TradeRecord from chia.wallet.trading.offer import Offer +from chia.wallet.trading.outer_puzzles import AssetType from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.transaction_type import TransactionType from chia.wallet.util.wallet_types import AmountWithPuzzlehash, WalletType @@ -956,6 +957,17 @@ async def create_offer_for_ids(self, request): offer: Dict[str, int] = request["offer"] fee: uint64 = uint64(request.get("fee", 0)) validate_only: bool = request.get("validate_only", False) + type_dict_str: Optional[Dict[str, str]] = request.get("type_dict", None) + + # This type_dict construction is to maintain backward compatibility where everything is assumed to be a CAT + type_dict: Dict[str, str] = {} + if type_dict_str is None: + for key in offer: + if len(key) >= 32: + type_dict[bytes32.from_hexstr(key)] = AssetType.CAT + else: + for key, value in type_dict_str: + type_dict[bytes32.from_hexstr(key)] = AssetType(value) modified_offer = {} for key in offer: @@ -967,7 +979,7 @@ async def create_offer_for_ids(self, request): trade_record, error, ) = await self.service.wallet_state_manager.trade_manager.create_offer_for_ids( - modified_offer, fee=fee, validate_only=validate_only + modified_offer, type_dict, fee=fee, validate_only=validate_only ) if success: return { diff --git a/chia/wallet/trade_manager.py b/chia/wallet/trade_manager.py index 9ef72c5d5dd1..7ec50a5b7a82 100644 --- a/chia/wallet/trade_manager.py +++ b/chia/wallet/trade_manager.py @@ -295,12 +295,17 @@ async def _create_offer_for_ids( wallet_id = uint32(id) wallet = self.wallet_state_manager.wallets[wallet_id] p2_ph: bytes32 = await wallet.get_new_puzzlehash() + # ATTENTION: new wallets if wallet.type() == WalletType.STANDARD_WALLET: key: Optional[bytes32] = None memos: List[bytes] = [] elif wallet.type() == WalletType.CAT: key = bytes32(bytes.fromhex(wallet.get_asset_id())) memos = [p2_ph] + if key in type_dict and type_dict[key] != AssetType.CAT: + raise ValueError(f"type_dict specified {type_dict[key]}, was expecting {AssetType.CAT}") + else: + type_dict[key] = AssetType.CAT else: raise ValueError(f"Offers are not implemented for {wallet.type()}") else: @@ -316,6 +321,15 @@ async def _create_offer_for_ids( if balance < abs(amount): raise Exception(f"insufficient funds in wallet {wallet_id}") coins_to_offer[wallet_id] = await wallet.select_coins(uint64(abs(amount))) + # ATTENTION: new wallets + if wallet.type() == WalletType.CAT: + asset_id = bytes32(bytes.fromhex(wallet.get_asset_id())) + if asset_id in type_dict and type_dict[asset_id] != AssetType.CAT: + raise ValueError( + f"type_dict specified {type_dict[asset_id]}, was expecting {AssetType.CAT}" + ) + else: + type_dict[asset_id] = AssetType.CAT elif amount == 0: raise ValueError("You cannot offer nor request 0 amount of something") @@ -372,7 +386,6 @@ async def maybe_create_wallets_for_offer(self, offer: Offer): if exists is None: await wsm.create_wallet_for_asset_id(key, offer.type_dict[key]) # ATTENTION: new_wallets - async def check_offer_validity(self, offer: Offer) -> bool: all_removals: List[Coin] = offer.bundle.removals() all_removal_names: List[bytes32] = [c.name() for c in all_removals] diff --git a/tests/wallet/cat_wallet/test_trades.py b/tests/wallet/cat_wallet/test_trades.py index 50f150a69fe3..2ac4701bb25f 100644 --- a/tests/wallet/cat_wallet/test_trades.py +++ b/tests/wallet/cat_wallet/test_trades.py @@ -6,11 +6,9 @@ from chia.full_node.mempool_manager import MempoolManager from chia.simulator.simulator_protocol import FarmNewBlockProtocol -from chia.types.blockchain_format.sized_bytes import bytes32 from chia.util.ints import uint64 from chia.wallet.cat_wallet.cat_wallet import CATWallet from chia.wallet.trading.offer import Offer -from chia.wallet.trading.outer_puzzles import AssetType from chia.wallet.trading.trade_status import TradeStatus from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.transaction_type import TransactionType @@ -106,17 +104,9 @@ async def test_cat_trades(self, wallets_prefarm): trade_manager_maker = wallet_node_maker.wallet_state_manager.trade_manager trade_manager_taker = wallet_node_taker.wallet_state_manager.trade_manager - type_dict = { - bytes32.from_hexstr(cat_wallet_maker.get_asset_id()): AssetType.CAT, - bytes32.from_hexstr(new_cat_wallet_maker.get_asset_id()): AssetType.CAT, - bytes32.from_hexstr(new_cat_wallet_taker.get_asset_id()): AssetType.CAT, - } - # Execute all of the trades # chia_for_cat - success, trade_make, error = await trade_manager_maker.create_offer_for_ids( - chia_for_cat, type_dict, fee=uint64(1) - ) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids(chia_for_cat, fee=uint64(1)) await asyncio.sleep(1) assert error is None assert success is True @@ -165,7 +155,7 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): await time_out_assert(15, assert_trade_tx_number, True, wallet_node_taker, trade_take.trade_id, 3) # cat_for_chia - success, trade_make, error = await trade_manager_maker.create_offer_for_ids(cat_for_chia, type_dict) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids(cat_for_chia) await asyncio.sleep(1) assert error is None assert success is True @@ -186,13 +176,6 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): cat_wallet_maker.get_asset_id() ) - type_dict = { - bytes32.from_hexstr(cat_wallet_maker.get_asset_id()): AssetType.CAT, - bytes32.from_hexstr(cat_wallet_taker.get_asset_id()): AssetType.CAT, - bytes32.from_hexstr(new_cat_wallet_maker.get_asset_id()): AssetType.CAT, - bytes32.from_hexstr(new_cat_wallet_taker.get_asset_id()): AssetType.CAT, - } - await time_out_assert(15, wallet_taker.get_unconfirmed_balance, TAKER_CHIA_BALANCE) await time_out_assert(15, cat_wallet_taker.get_unconfirmed_balance, TAKER_CAT_BALANCE) @@ -213,7 +196,7 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): await time_out_assert(15, assert_trade_tx_number, True, wallet_node_taker, trade_take.trade_id, 2) # cat_for_cat - success, trade_make, error = await trade_manager_maker.create_offer_for_ids(cat_for_cat, type_dict) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids(cat_for_cat) await asyncio.sleep(1) assert error is None assert success is True @@ -247,7 +230,7 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, trade_take) # chia_for_multiple_cat - success, trade_make, error = await trade_manager_maker.create_offer_for_ids(chia_for_multiple_cat, type_dict) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids(chia_for_multiple_cat) await asyncio.sleep(1) assert error is None assert success is True @@ -283,7 +266,7 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, trade_take) # multiple_cat_for_chia - success, trade_make, error = await trade_manager_maker.create_offer_for_ids(multiple_cat_for_chia, type_dict) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids(multiple_cat_for_chia) await asyncio.sleep(1) assert error is None assert success is True @@ -319,7 +302,7 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, trade_take) # chia_and_cat_for_cat - success, trade_make, error = await trade_manager_maker.create_offer_for_ids(chia_and_cat_for_cat, type_dict) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids(chia_and_cat_for_cat) await asyncio.sleep(1) assert error is None assert success is True @@ -393,15 +376,11 @@ async def test_trade_cancellation(self, wallets_prefarm): trade_manager_maker = wallet_node_maker.wallet_state_manager.trade_manager trade_manager_taker = wallet_node_taker.wallet_state_manager.trade_manager - type_dict = { - bytes32.from_hexstr(cat_wallet_maker.get_asset_id()): AssetType.CAT, - } - async def get_trade_and_status(trade_manager, trade) -> TradeStatus: trade_rec = await trade_manager.get_trade_by_id(trade.trade_id) return TradeStatus(trade_rec.status) - success, trade_make, error = await trade_manager_maker.create_offer_for_ids(cat_for_chia, type_dict) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids(cat_for_chia) await asyncio.sleep(1) assert error is None assert success is True @@ -462,7 +441,7 @@ async def get_trade_and_status(trade_manager, trade) -> TradeStatus: assert trade_take is None # Now we're going to create the other way around for test coverage sake - success, trade_make, error = await trade_manager_maker.create_offer_for_ids(chia_for_cat, type_dict) + success, trade_make, error = await trade_manager_maker.create_offer_for_ids(chia_for_cat) await asyncio.sleep(1) assert error is None assert success is True From 4a8ae255223cf4535e5846e70222d170a4eeb1bd Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Mon, 18 Apr 2022 09:28:17 -0700 Subject: [PATCH 07/43] isort --- chia/wallet/trading/outer_puzzles.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chia/wallet/trading/outer_puzzles.py b/chia/wallet/trading/outer_puzzles.py index e97964f4f972..dd237d69c81f 100644 --- a/chia/wallet/trading/outer_puzzles.py +++ b/chia/wallet/trading/outer_puzzles.py @@ -1,15 +1,15 @@ from enum import Enum from typing import Optional -from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.program import Program +from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.coin_spend import CoinSpend from chia.wallet.cat_wallet.cat_utils import ( CAT_MOD, SpendableCAT, - match_cat_puzzle, construct_cat_puzzle, + match_cat_puzzle, unsigned_spend_bundle_for_spendable_cats, ) from chia.wallet.lineage_proof import LineageProof From b69cfcb79f32fcf962ddf3c6b55141f8abb2fa71 Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Thu, 21 Apr 2022 07:30:35 -0700 Subject: [PATCH 08/43] Further generalize the Offer drivers --- chia/wallet/trading/offer.py | 73 +++++++++------- chia/wallet/trading/outer_puzzles.py | 86 ++++++++++++------- .../wallet/cat_wallet/test_offer_lifecycle.py | 20 ++--- 3 files changed, 106 insertions(+), 73 deletions(-) diff --git a/chia/wallet/trading/offer.py b/chia/wallet/trading/offer.py index dbdec18a28a1..4aa260da3faf 100644 --- a/chia/wallet/trading/offer.py +++ b/chia/wallet/trading/offer.py @@ -18,10 +18,11 @@ from chia.wallet.puzzles.load_clvm import load_clvm from chia.wallet.payment import Payment from chia.wallet.trading.outer_puzzles import ( - AssetType, - type_of_puzzle, - asset_id_of_puzzle, + PuzzleInfo, + Solver, + create_asset_id, construct_puzzle, + match_puzzle, solve_puzzle, ) @@ -47,7 +48,7 @@ class Offer: Optional[bytes32], List[NotarizedPayment] ] # The key is the asset id of the asset being requested bundle: SpendBundle - type_dict: Dict[bytes32, AssetType] # asset_id -> asset type + driver_dict: Dict[bytes32, PuzzleInfo] # asset_id -> asset driver @staticmethod def ph(): @@ -75,14 +76,14 @@ def notarize_payments( # The announcements returned from this function must be asserted in whatever spend bundle is created by the wallet @staticmethod def calculate_announcements( - notarized_payments: Dict[Optional[bytes32], List[NotarizedPayment]], type_dict: Dict[bytes32, AssetType] + notarized_payments: Dict[Optional[bytes32], List[NotarizedPayment]], driver_dict: Dict[bytes32, PuzzleInfo] ) -> List[Announcement]: announcements: List[Announcement] = [] for asset_id, payments in notarized_payments.items(): if asset_id is not None: - if asset_id not in type_dict: - raise ValueError("Cannot calculate announcements without type of requested item") - settlement_ph: bytes32 = construct_puzzle(type_dict[asset_id], asset_id, OFFER_MOD).get_tree_hash() + if asset_id not in driver_dict: + raise ValueError("Cannot calculate announcements without driver of requested item") + settlement_ph: bytes32 = construct_puzzle(driver_dict[asset_id], OFFER_MOD).get_tree_hash() else: settlement_ph = OFFER_MOD.get_tree_hash() @@ -107,8 +108,8 @@ def __post_init__(self): # Verify we have a type for every kind of asset for asset_id in self.requested_payments: - if asset_id is not None and asset_id not in self.type_dict: - raise ValueError("Offer does not have enough type information about the requested payments") + if asset_id is not None and asset_id not in self.driver_dict: + raise ValueError("Offer does not have enough driver information about the requested payments") # This method does not get every coin that is being offered, only the `settlement_payment` children def get_offered_coins(self) -> Dict[Optional[bytes32], List[Coin]]: @@ -121,10 +122,12 @@ def get_offered_coins(self) -> Dict[Optional[bytes32], List[Coin]]: )[0].puzzle_reveal.to_program() # Determine it's TAIL (or lack of) - asset_id = asset_id_of_puzzle(parent_puzzle) - if asset_id is not None: - offer_ph: bytes32 = construct_puzzle(self.type_dict[asset_id], asset_id, OFFER_MOD).get_tree_hash() + puzzle_driver = match_puzzle(parent_puzzle) + if puzzle_driver is not None: + asset_id = create_asset_id(puzzle_driver) + offer_ph: bytes32 = construct_puzzle(self.driver_dict[asset_id], OFFER_MOD).get_tree_hash() else: + asset_id = None offer_ph = OFFER_MOD.get_tree_hash() # Check if the puzzle_hash matches the hypothetical `settlement_payments` puzzle hash @@ -237,7 +240,7 @@ def get_primary_coins(self) -> List[Coin]: def aggregate(cls, offers: List["Offer"]) -> "Offer": total_requested_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = {} total_bundle = SpendBundle([], G2Element()) - total_type_dict: Dict[bytes32, AssetType] = {} + total_driver_dict: Dict[bytes32, PuzzleInfo] = {} for offer in offers: # First check for any overlap in inputs total_inputs: Set[Coin] = {cs.coin for cs in total_bundle.coin_spends} @@ -252,14 +255,14 @@ def aggregate(cls, offers: List["Offer"]) -> "Offer": else: total_requested_payments[asset_id] = payments - for key, value in offer.type_dict.items(): - if key in total_type_dict and total_type_dict[key] != value: - raise ValueError(f"The offers to aggregate disagree on the type of {key.hex()}") + for key, value in offer.driver_dict.items(): + if key in total_driver_dict and total_driver_dict[key] != value: + raise ValueError(f"The offers to aggregate disagree on the drivers for {key.hex()}") total_bundle = SpendBundle.aggregate([total_bundle, offer.bundle]) - total_type_dict = {**total_type_dict, **offer.type_dict} + total_driver_dict = {**total_driver_dict, **offer.driver_dict} - return cls(total_requested_payments, total_bundle, total_type_dict) + return cls(total_requested_payments, total_bundle, total_driver_dict) # Validity is defined by having enough funds within the offer to satisfy both sides def is_valid(self) -> bool: @@ -297,11 +300,15 @@ def to_valid_spend(self, arbitrage_ph: Optional[bytes32] = None) -> SpendBundle: filter(lambda cs: cs.coin.name() == coin.parent_coin_info, self.bundle.coin_spends) )[0] solution: Program = solve_puzzle( - self.type_dict[asset_id], - coin, + self.driver_dict[asset_id], + Solver( + { + "coin": coin, + "parent_spend": parent_spend, + } + ), OFFER_MOD, Program.to(inner_solutions), - parent_spend, ) else: solution = Program.to(inner_solutions) @@ -309,7 +316,7 @@ def to_valid_spend(self, arbitrage_ph: Optional[bytes32] = None) -> SpendBundle: completion_spends.append( CoinSpend( coin, - construct_puzzle(self.type_dict[asset_id], asset_id, OFFER_MOD) if asset_id else OFFER_MOD, + construct_puzzle(self.driver_dict[asset_id], OFFER_MOD) if asset_id else OFFER_MOD, solution, ) ) @@ -320,9 +327,7 @@ def to_spend_bundle(self) -> SpendBundle: # Before we serialze this as a SpendBundle, we need to serialze the `requested_payments` as dummy CoinSpends additional_coin_spends: List[CoinSpend] = [] for asset_id, payments in self.requested_payments.items(): - puzzle_reveal: Program = ( - construct_puzzle(self.type_dict[asset_id], asset_id, OFFER_MOD) if asset_id else OFFER_MOD - ) + puzzle_reveal: Program = construct_puzzle(self.driver_dict[asset_id], OFFER_MOD) if asset_id else OFFER_MOD inner_solutions = [] nonces: List[bytes32] = [p.nonce for p in payments] for nonce in list(dict.fromkeys(nonces)): # dedup without messing with order @@ -352,14 +357,16 @@ def to_spend_bundle(self) -> SpendBundle: def from_spend_bundle(cls, bundle: SpendBundle) -> "Offer": # Because of the `to_spend_bundle` method, we need to parse the dummy CoinSpends as `requested_payments` requested_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = {} - type_dict: Dict[bytes32, AssetType] = {} + driver_dict: Dict[bytes32, PuzzleInfo] = {} leftover_coin_spends: List[CoinSpend] = [] for coin_spend in bundle.coin_spends: - asset_id = asset_id_of_puzzle(coin_spend.puzzle_reveal.to_program()) - if asset_id is not None: - typ = type_of_puzzle(coin_spend.puzzle_reveal.to_program()) - assert typ is not None - type_dict[asset_id] = typ + driver = match_puzzle(coin_spend.puzzle_reveal.to_program()) + if driver is not None: + asset_id = create_asset_id(driver) + assert asset_id is not None + driver_dict[asset_id] = driver + else: + asset_id = None if coin_spend.coin.parent_coin_info == ZERO_32: notarized_payments: List[NotarizedPayment] = [] for payment_group in coin_spend.solution.to_program().as_iter(): @@ -373,7 +380,7 @@ def from_spend_bundle(cls, bundle: SpendBundle) -> "Offer": else: leftover_coin_spends.append(coin_spend) - return cls(requested_payments, SpendBundle(leftover_coin_spends, bundle.aggregated_signature), type_dict) + return cls(requested_payments, SpendBundle(leftover_coin_spends, bundle.aggregated_signature), driver_dict) def name(self) -> bytes32: return self.to_spend_bundle().name() diff --git a/chia/wallet/trading/outer_puzzles.py b/chia/wallet/trading/outer_puzzles.py index dd237d69c81f..b7dbd0bdbee9 100644 --- a/chia/wallet/trading/outer_puzzles.py +++ b/chia/wallet/trading/outer_puzzles.py @@ -1,5 +1,6 @@ from enum import Enum -from typing import Optional +from dataclasses import dataclass +from typing import Any, Dict, Optional from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.program import Program @@ -19,26 +20,55 @@ class AssetType(Enum): CAT = "CAT" +@dataclass(frozen=True) +class PuzzleInfo: + info: Dict[str, Any] + + +@dataclass(frozen=True) +class Solver: + info: Dict[str, Any] + + class CATOuterPuzzle: - @staticmethod - def asset_id(puzzle: Program) -> Optional[bytes32]: + @classmethod + def match(cls, puzzle: Program) -> Optional[PuzzleInfo]: matched, curried_args = match_cat_puzzle(puzzle) if matched: - _, tail_hash, _ = curried_args - return bytes32(tail_hash.as_python()) + _, tail_hash, inner_puzzle = curried_args + constructor_dict = { + "type": AssetType.CAT, + "tail": bytes32(tail_hash.as_python()), + } + next_constructor = match_puzzle(inner_puzzle) + if next_constructor is not None: + constructor_dict["and"] = next_constructor.info + return PuzzleInfo(constructor_dict) else: return None - @staticmethod - def construct(asset_id: bytes32, inner_puzzle: Program) -> Program: - return construct_cat_puzzle(CAT_MOD, asset_id, inner_puzzle) - - @staticmethod - def solve(coin: Coin, inner_puzzle: Program, inner_solution: Program, parent_spend: CoinSpend) -> Program: + @classmethod + def asset_id(cls, constructor: PuzzleInfo) -> Optional[bytes32]: + return bytes32(constructor.info["tail"]) + + @classmethod + def construct(cls, constructor: PuzzleInfo, inner_puzzle: Program) -> Program: + if "and" in constructor.info: + inner_puzzle = construct_puzzle(constructor.info["and"], inner_puzzle) + return construct_cat_puzzle(CAT_MOD, constructor.info["tail"], inner_puzzle) + + @classmethod + def solve(cls, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program: + tail_hash: bytes32 = constructor.info["tail"] + coin: Coin = solver.info["coin"] + parent_spend: CoinSpend = solver.info["parent_spend"] parent_coin: Coin = parent_spend.coin + if "and" in constructor.info: + inner_puzzle = construct_puzzle(PuzzleInfo(constructor.info["and"]), inner_puzzle) + inner_solution = solve_puzzle(PuzzleInfo(constructor.info["and"]), solver, inner_puzzle, inner_solution) matched, curried_args = match_cat_puzzle(parent_spend.puzzle_reveal.to_program()) assert matched - _, tail_hash, parent_inner_puzzle = curried_args + _, _, parent_inner_puzzle = curried_args spendable_cat = SpendableCAT( coin, tail_hash, @@ -51,32 +81,28 @@ def solve(coin: Coin, inner_puzzle: Program, inner_solution: Program, parent_spe return unsigned_spend_bundle_for_spendable_cats(CAT_MOD, [spendable_cat]).coin_spends[0].solution.to_program() -OUTER_PUZZLES = { +driver_lookup: Dict[AssetType, Any] = { AssetType.CAT: CATOuterPuzzle, } -def type_of_puzzle(puzzle: Program) -> Optional[AssetType]: - for typ, outer in OUTER_PUZZLES.items(): - asset_id = outer.asset_id(puzzle) - if asset_id is not None: - return typ +def match_puzzle(puzzle: Program) -> Optional[PuzzleInfo]: + for driver in driver_lookup.values(): + potential_info: Optional[PuzzleInfo] = driver.match(puzzle) + if potential_info is not None: + return potential_info return None -def asset_id_of_puzzle(puzzle: Program) -> Optional[bytes32]: - for type, outer in OUTER_PUZZLES.items(): - asset_id = outer.asset_id(puzzle) - if asset_id is not None: - return asset_id - return None +def construct_puzzle(constructor: PuzzleInfo, inner_puzzle: Program) -> Program: + return driver_lookup[constructor.info["type"]].construct(constructor, inner_puzzle) # type: ignore -def construct_puzzle(typ: AssetType, asset_id: bytes32, inner_puzzle: Program) -> Program: - return OUTER_PUZZLES[typ].construct(asset_id, inner_puzzle) +def solve_puzzle(constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program: + return driver_lookup[constructor.info["type"]].solve( # type: ignore + constructor, solver, inner_puzzle, inner_solution + ) -def solve_puzzle( - typ: AssetType, coin: Coin, inner_puzzle: Program, inner_solution: Program, parent_spend: CoinSpend -) -> Program: - return OUTER_PUZZLES[typ].solve(coin, inner_puzzle, inner_solution, parent_spend) +def create_asset_id(constructor: PuzzleInfo) -> bytes32: + return driver_lookup[constructor.info["type"]].asset_id(constructor) # type: ignore diff --git a/tests/wallet/cat_wallet/test_offer_lifecycle.py b/tests/wallet/cat_wallet/test_offer_lifecycle.py index 01d4da350b4c..5f536d8f44ad 100644 --- a/tests/wallet/cat_wallet/test_offer_lifecycle.py +++ b/tests/wallet/cat_wallet/test_offer_lifecycle.py @@ -19,7 +19,7 @@ ) from chia.wallet.payment import Payment from chia.wallet.trading.offer import Offer, NotarizedPayment -from chia.wallet.trading.outer_puzzles import AssetType +from chia.wallet.trading.outer_puzzles import AssetType, PuzzleInfo from tests.clvm.benchmark_costs import cost_of_spend_bundle acs = Program.to(1) @@ -179,9 +179,9 @@ async def test_complex_offer(self, setup_sim): red_coins: List[Coin] = all_coins["red"] blue_coins: List[Coin] = all_coins["blue"] - type_dict: Dict[bytes32, AssetType] = { - str_to_tail_hash("red"): AssetType.CAT, - str_to_tail_hash("blue"): AssetType.CAT, + driver_dict: Dict[bytes32, PuzzleInfo] = { + str_to_tail_hash("red"): PuzzleInfo({"type": AssetType.CAT, "tail": str_to_tail_hash("red")}), + str_to_tail_hash("blue"): PuzzleInfo({"type": AssetType.CAT, "tail": str_to_tail_hash("blue")}), } # Create an XCH Offer for RED @@ -195,9 +195,9 @@ async def test_complex_offer(self, setup_sim): chia_requested_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments( chia_requested_payments, chia_coins ) - chia_announcements: List[Announcement] = Offer.calculate_announcements(chia_requested_payments, type_dict) + chia_announcements: List[Announcement] = Offer.calculate_announcements(chia_requested_payments, driver_dict) chia_secured_bundle: SpendBundle = generate_secure_bundle(chia_coins, chia_announcements, 1000) - chia_offer = Offer(chia_requested_payments, chia_secured_bundle, type_dict) + chia_offer = Offer(chia_requested_payments, chia_secured_bundle, driver_dict) assert not chia_offer.is_valid() # Create a RED Offer for XCH @@ -211,9 +211,9 @@ async def test_complex_offer(self, setup_sim): red_requested_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments( red_requested_payments, red_coins ) - red_announcements: List[Announcement] = Offer.calculate_announcements(red_requested_payments, type_dict) + red_announcements: List[Announcement] = Offer.calculate_announcements(red_requested_payments, driver_dict) red_secured_bundle: SpendBundle = generate_secure_bundle(red_coins, red_announcements, 350, tail_str="red") - red_offer = Offer(red_requested_payments, red_secured_bundle, type_dict) + red_offer = Offer(red_requested_payments, red_secured_bundle, driver_dict) assert not red_offer.is_valid() # Test aggregation of offers @@ -235,11 +235,11 @@ async def test_complex_offer(self, setup_sim): blue_requested_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments( blue_requested_payments, blue_coins ) - blue_announcements: List[Announcement] = Offer.calculate_announcements(blue_requested_payments, type_dict) + blue_announcements: List[Announcement] = Offer.calculate_announcements(blue_requested_payments, driver_dict) blue_secured_bundle: SpendBundle = generate_secure_bundle( blue_coins, blue_announcements, 2000, tail_str="blue" ) - blue_offer = Offer(blue_requested_payments, blue_secured_bundle, type_dict) + blue_offer = Offer(blue_requested_payments, blue_secured_bundle, driver_dict) assert not blue_offer.is_valid() # Test a re-aggregation From a31737e637fa58c4506a02d5b3f303ff8ff5a350 Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Thu, 21 Apr 2022 08:42:57 -0700 Subject: [PATCH 09/43] Update trade manager with new generalizations --- chia/wallet/cat_wallet/cat_wallet.py | 28 ++++++++++++++++++++++ chia/wallet/trade_manager.py | 35 ++++++++++++++++------------ chia/wallet/wallet_state_manager.py | 31 ++++++++++++++++++------ 3 files changed, 72 insertions(+), 22 deletions(-) diff --git a/chia/wallet/cat_wallet/cat_wallet.py b/chia/wallet/cat_wallet/cat_wallet.py index 5d48506c301c..f34c0ab45cb1 100644 --- a/chia/wallet/cat_wallet/cat_wallet.py +++ b/chia/wallet/cat_wallet/cat_wallet.py @@ -41,6 +41,7 @@ DEFAULT_HIDDEN_PUZZLE_HASH, calculate_synthetic_secret_key, ) +from chia.wallet.trading.outer_puzzles import AssetType, PuzzleInfo from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.transaction_type import TransactionType from chia.wallet.util.wallet_types import WalletType, AmountWithPuzzlehash @@ -207,6 +208,23 @@ async def create_wallet_for_cat( await self.wallet_state_manager.add_new_wallet(self, self.id(), in_transaction=in_transaction) return self + @classmethod + async def create_from_puzzle_info( + cls, + wallet_state_manager: Any, + wallet: Wallet, + puzzle_driver: PuzzleInfo, + name=None, + in_transaction=False, + ) -> CATWallet: + return await cls.create_wallet_for_cat( + wallet_state_manager, + wallet, + puzzle_driver.info["tail"].hex(), + name, + in_transaction, + ) + @staticmethod async def create( wallet_state_manager: Any, @@ -806,3 +824,13 @@ async def save_info(self, cat_info: CATInfo, in_transaction): wallet_info = WalletInfo(current_info.id, current_info.name, current_info.type, data_str) self.wallet_info = wallet_info await self.wallet_state_manager.user_store.update_wallet(wallet_info, in_transaction) + + def match_puzzle_info(self, puzzle_driver: PuzzleInfo) -> bool: + if ( + puzzle_driver.info["type"] == AssetType.CAT + and puzzle_driver.info["tail"] == bytes.fromhex(self.get_asset_id()) + and "and" not in puzzle_driver.info + ): + return True + else: + return False diff --git a/chia/wallet/trade_manager.py b/chia/wallet/trade_manager.py index 7ec50a5b7a82..fb8de3d27713 100644 --- a/chia/wallet/trade_manager.py +++ b/chia/wallet/trade_manager.py @@ -15,7 +15,7 @@ from chia.wallet.payment import Payment from chia.wallet.trade_record import TradeRecord from chia.wallet.trading.offer import Offer, NotarizedPayment -from chia.wallet.trading.outer_puzzles import AssetType +from chia.wallet.trading.outer_puzzles import AssetType, PuzzleInfo from chia.wallet.trading.trade_status import TradeStatus from chia.wallet.trading.trade_store import TradeStore from chia.wallet.transaction_record import TransactionRecord @@ -249,11 +249,11 @@ async def save_trade(self, trade: TradeRecord): async def create_offer_for_ids( self, offer: Dict[Union[int, bytes32], int], - type_dict: Dict[bytes32, AssetType] = {}, + driver_dict: Dict[bytes32, PuzzleInfo] = {}, fee: uint64 = uint64(0), validate_only: bool = False, ) -> Tuple[bool, Optional[TradeRecord], Optional[str]]: - success, created_offer, error = await self._create_offer_for_ids(offer, type_dict, fee=fee) + success, created_offer, error = await self._create_offer_for_ids(offer, driver_dict, fee=fee) if not success or created_offer is None: raise Exception(f"Error creating offer: {error}") @@ -280,7 +280,7 @@ async def create_offer_for_ids( async def _create_offer_for_ids( self, offer_dict: Dict[Union[int, bytes32], int], - type_dict: Dict[bytes32, AssetType] = {}, + driver_dict: Dict[bytes32, PuzzleInfo] = {}, fee: uint64 = uint64(0), ) -> Tuple[bool, Optional[Offer], Optional[str]]: """ @@ -302,10 +302,13 @@ async def _create_offer_for_ids( elif wallet.type() == WalletType.CAT: key = bytes32(bytes.fromhex(wallet.get_asset_id())) memos = [p2_ph] - if key in type_dict and type_dict[key] != AssetType.CAT: - raise ValueError(f"type_dict specified {type_dict[key]}, was expecting {AssetType.CAT}") + if key in driver_dict and driver_dict[key].info["type"] != AssetType.CAT: + raise ValueError( + f"driver_dict specified {driver_dict[key].info['type']}," + f"was expecting {AssetType.CAT}" + ) else: - type_dict[key] = AssetType.CAT + driver_dict[key] = PuzzleInfo({"type": AssetType.CAT, "tail": key}) else: raise ValueError(f"Offers are not implemented for {wallet.type()}") else: @@ -324,12 +327,13 @@ async def _create_offer_for_ids( # ATTENTION: new wallets if wallet.type() == WalletType.CAT: asset_id = bytes32(bytes.fromhex(wallet.get_asset_id())) - if asset_id in type_dict and type_dict[asset_id] != AssetType.CAT: + if asset_id in driver_dict and driver_dict[asset_id].info["type"] != AssetType.CAT: raise ValueError( - f"type_dict specified {type_dict[asset_id]}, was expecting {AssetType.CAT}" + f"driver_dict specified {driver_dict[asset_id].info['type']}," + f"was expecting {AssetType.CAT}" ) else: - type_dict[asset_id] = AssetType.CAT + driver_dict[asset_id] = PuzzleInfo({"type": AssetType.CAT, "tail": asset_id}) elif amount == 0: raise ValueError("You cannot offer nor request 0 amount of something") @@ -337,7 +341,7 @@ async def _create_offer_for_ids( notarized_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments( requested_payments, all_coins ) - announcements_to_assert = Offer.calculate_announcements(notarized_payments, type_dict) + announcements_to_assert = Offer.calculate_announcements(notarized_payments, driver_dict) all_transactions: List[TransactionRecord] = [] fee_left_to_pay: uint64 = fee @@ -368,7 +372,7 @@ async def _create_offer_for_ids( transaction_bundles: List[Optional[SpendBundle]] = [tx.spend_bundle for tx in all_transactions] total_spend_bundle = SpendBundle.aggregate(list(filter(lambda b: b is not None, transaction_bundles))) - offer = Offer(notarized_payments, total_spend_bundle, type_dict) + offer = Offer(notarized_payments, total_spend_bundle, driver_dict) return True, offer, None except Exception as e: @@ -382,9 +386,10 @@ async def maybe_create_wallets_for_offer(self, offer: Offer): wsm = self.wallet_state_manager if key is None: continue - exists: Optional[Wallet] = await wsm.get_wallet_for_asset_id(key.hex()) # ATTENTION: new_wallets + # ATTENTION: new_wallets + exists: Optional[Wallet] = await wsm.get_wallet_for_puzzle_info(offer.driver_dict[key]) if exists is None: - await wsm.create_wallet_for_asset_id(key, offer.type_dict[key]) # ATTENTION: new_wallets + await wsm.create_wallet_for_puzzle_info(offer.driver_dict[key]) # ATTENTION: new_wallets async def check_offer_validity(self, offer: Offer) -> bool: all_removals: List[Coin] = offer.bundle.removals() @@ -507,7 +512,7 @@ async def respond_to_offer(self, offer: Offer, fee=uint64(0)) -> Tuple[bool, Opt if not valid: return False, None, "This offer is no longer valid" - success, take_offer, error = await self._create_offer_for_ids(take_offer_dict, offer.type_dict, fee=fee) + success, take_offer, error = await self._create_offer_for_ids(take_offer_dict, offer.driver_dict, fee=fee) if not success or take_offer is None: return False, None, error diff --git a/chia/wallet/wallet_state_manager.py b/chia/wallet/wallet_state_manager.py index e6fe7d16bd62..9272422714f2 100644 --- a/chia/wallet/wallet_state_manager.py +++ b/chia/wallet/wallet_state_manager.py @@ -44,7 +44,7 @@ from chia.wallet.rl_wallet.rl_wallet import RLWallet from chia.wallet.settings.user_settings import UserSettings from chia.wallet.trade_manager import TradeManager -from chia.wallet.trading.outer_puzzles import AssetType +from chia.wallet.trading.outer_puzzles import AssetType, PuzzleInfo from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.compute_hints import compute_coin_hints from chia.wallet.util.transaction_type import TransactionType @@ -116,6 +116,7 @@ class WalletStateManager: wallet_node: Any pool_store: WalletPoolStore default_cats: Dict[str, Any] + asset_to_wallet_map: Dict[AssetType, Any] @staticmethod async def create( @@ -181,6 +182,10 @@ async def create( self.wallets = {main_wallet_info.id: self.main_wallet} + self.asset_to_wallet_map = { + AssetType.CAT: CATWallet, + } + wallet = None for wallet_info in await self.get_all_wallet_info_entries(): if wallet_info.type == WalletType.STANDARD_WALLET: @@ -1281,12 +1286,24 @@ async def get_wallet_for_asset_id(self, asset_id: str): return wallet return None - async def create_wallet_for_asset_id(self, asset_id: bytes32, typ: AssetType): - self.log.info(f"Creating wallet for asset ID: {asset_id}") - if typ == AssetType.CAT: - await CATWallet.create_wallet_for_cat(self, self.main_wallet, asset_id.hex()) - else: - raise ValueError(f"Cannot create wallet for type {typ.value}") + async def get_wallet_for_puzzle_info(self, puzzle_driver: PuzzleInfo): + for wallet in self.wallets.values(): + match_function = getattr(wallet, "match_puzzle_info", None) + if match_function is not None and callable(match_function): + if match_function(puzzle_driver): + return wallet + return None + + async def create_wallet_for_puzzle_info(self, puzzle_driver: PuzzleInfo, name=None, in_transaction=False): + if puzzle_driver.info["type"] in self.asset_to_wallet_map: + async with self.lock: + await self.asset_to_wallet_map[puzzle_driver.info["type"]].create_from_puzzle_info( + self, + self.main_wallet, + puzzle_driver, + name, + in_transaction, + ) async def add_new_wallet(self, wallet: Any, wallet_id: int, create_puzzle_hashes=True, in_transaction=False): self.wallets[uint32(wallet_id)] = wallet From d79d57dee2b87a85213d9969bebaeb26703f7150 Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Thu, 21 Apr 2022 10:49:34 -0700 Subject: [PATCH 10/43] Fix offer RPC again --- chia/rpc/wallet_rpc_api.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index b79171fa67e9..79fbf69dcca9 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -33,7 +33,7 @@ from chia.wallet.nft_wallet.nft_puzzles import get_uri_list_from_puzzle from chia.wallet.trade_record import TradeRecord from chia.wallet.trading.offer import Offer -from chia.wallet.trading.outer_puzzles import AssetType +from chia.wallet.trading.outer_puzzles import AssetType, PuzzleInfo from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.transaction_type import TransactionType from chia.wallet.util.wallet_types import AmountWithPuzzlehash, WalletType @@ -957,17 +957,20 @@ async def create_offer_for_ids(self, request): offer: Dict[str, int] = request["offer"] fee: uint64 = uint64(request.get("fee", 0)) validate_only: bool = request.get("validate_only", False) - type_dict_str: Optional[Dict[str, str]] = request.get("type_dict", None) + driver_dict_str: Optional[Dict[str, str]] = request.get("driver_dict", None) - # This type_dict construction is to maintain backward compatibility where everything is assumed to be a CAT - type_dict: Dict[str, str] = {} - if type_dict_str is None: + # This driver_dict construction is to maintain backward compatibility where everything is assumed to be a CAT + driver_dict: Dict[bytes32, PuzzleInfo] = {} + if driver_dict_str is None: for key in offer: - if len(key) >= 32: - type_dict[bytes32.from_hexstr(key)] = AssetType.CAT + if len(key) == 32: + asset_id = bytes32.from_hexstr(key) + driver_dict[asset_id] = PuzzleInfo({"type": AssetType.CAT, "tail": asset_id}) else: - for key, value in type_dict_str: - type_dict[bytes32.from_hexstr(key)] = AssetType(value) + for key, value in driver_dict_str: + cast_type_driver_dict = driver_dict_str[key] + cast_type_driver_dict["type"] = AssetType(cast_type_driver_dict["type"]) + driver_dict[bytes32.from_hexstr(key)] = PuzzleInfo(cast_type_driver_dict) modified_offer = {} for key in offer: @@ -979,7 +982,7 @@ async def create_offer_for_ids(self, request): trade_record, error, ) = await self.service.wallet_state_manager.trade_manager.create_offer_for_ids( - modified_offer, type_dict, fee=fee, validate_only=validate_only + modified_offer, driver_dict, fee=fee, validate_only=validate_only ) if success: return { From 70b8d26ee399041d380e2aa66e4f8fd782e7b3ec Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Thu, 21 Apr 2022 12:28:15 -0700 Subject: [PATCH 11/43] Move outer_puzzles.py --- chia/rpc/wallet_rpc_api.py | 3 ++- chia/wallet/cat_wallet/cat_wallet.py | 2 +- chia/wallet/{trading => }/outer_puzzles.py | 0 chia/wallet/trade_manager.py | 2 +- chia/wallet/trading/offer.py | 6 +++--- chia/wallet/wallet_state_manager.py | 2 +- tests/wallet/cat_wallet/test_offer_lifecycle.py | 2 +- 7 files changed, 9 insertions(+), 8 deletions(-) rename chia/wallet/{trading => }/outer_puzzles.py (100%) diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index 79fbf69dcca9..18334bdb523d 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -26,6 +26,8 @@ from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS from chia.wallet.cat_wallet.cat_wallet import CATWallet from chia.wallet.derive_keys import master_sk_to_singleton_owner_sk, master_sk_to_wallet_sk_unhardened, MAX_POOL_WALLETS +from chia.wallet.did_wallet.did_wallet import DIDWallet +from chia.wallet.outer_puzzles import AssetType, PuzzleInfo from chia.wallet.rl_wallet.rl_wallet import RLWallet from chia.wallet.derive_keys import master_sk_to_farmer_sk, master_sk_to_pool_sk, master_sk_to_wallet_sk from chia.wallet.did_wallet.did_wallet import DIDWallet @@ -33,7 +35,6 @@ from chia.wallet.nft_wallet.nft_puzzles import get_uri_list_from_puzzle from chia.wallet.trade_record import TradeRecord from chia.wallet.trading.offer import Offer -from chia.wallet.trading.outer_puzzles import AssetType, PuzzleInfo from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.transaction_type import TransactionType from chia.wallet.util.wallet_types import AmountWithPuzzlehash, WalletType diff --git a/chia/wallet/cat_wallet/cat_wallet.py b/chia/wallet/cat_wallet/cat_wallet.py index f34c0ab45cb1..fa5be8f80afc 100644 --- a/chia/wallet/cat_wallet/cat_wallet.py +++ b/chia/wallet/cat_wallet/cat_wallet.py @@ -35,13 +35,13 @@ from chia.wallet.derivation_record import DerivationRecord from chia.wallet.cat_wallet.lineage_store import CATLineageStore from chia.wallet.lineage_proof import LineageProof +from chia.wallet.outer_puzzles import AssetType, PuzzleInfo from chia.wallet.payment import Payment from chia.wallet.puzzles.tails import ALL_LIMITATIONS_PROGRAMS from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import ( DEFAULT_HIDDEN_PUZZLE_HASH, calculate_synthetic_secret_key, ) -from chia.wallet.trading.outer_puzzles import AssetType, PuzzleInfo from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.transaction_type import TransactionType from chia.wallet.util.wallet_types import WalletType, AmountWithPuzzlehash diff --git a/chia/wallet/trading/outer_puzzles.py b/chia/wallet/outer_puzzles.py similarity index 100% rename from chia/wallet/trading/outer_puzzles.py rename to chia/wallet/outer_puzzles.py diff --git a/chia/wallet/trade_manager.py b/chia/wallet/trade_manager.py index fb8de3d27713..ee303f78bdbb 100644 --- a/chia/wallet/trade_manager.py +++ b/chia/wallet/trade_manager.py @@ -12,10 +12,10 @@ from chia.util.db_wrapper import DBWrapper from chia.util.hash import std_hash from chia.util.ints import uint32, uint64 +from chia.wallet.outer_puzzles import AssetType, PuzzleInfo from chia.wallet.payment import Payment from chia.wallet.trade_record import TradeRecord from chia.wallet.trading.offer import Offer, NotarizedPayment -from chia.wallet.trading.outer_puzzles import AssetType, PuzzleInfo from chia.wallet.trading.trade_status import TradeStatus from chia.wallet.trading.trade_store import TradeStore from chia.wallet.transaction_record import TransactionRecord diff --git a/chia/wallet/trading/offer.py b/chia/wallet/trading/offer.py index 4aa260da3faf..71d44f06261a 100644 --- a/chia/wallet/trading/offer.py +++ b/chia/wallet/trading/offer.py @@ -15,9 +15,7 @@ decompress_object_with_puzzles, lowest_best_version, ) -from chia.wallet.puzzles.load_clvm import load_clvm -from chia.wallet.payment import Payment -from chia.wallet.trading.outer_puzzles import ( +from chia.wallet.outer_puzzles import ( PuzzleInfo, Solver, create_asset_id, @@ -25,6 +23,8 @@ match_puzzle, solve_puzzle, ) +from chia.wallet.puzzles.load_clvm import load_clvm +from chia.wallet.payment import Payment OFFER_MOD = load_clvm("settlement_payments.clvm") ZERO_32 = bytes32([0] * 32) diff --git a/chia/wallet/wallet_state_manager.py b/chia/wallet/wallet_state_manager.py index 9272422714f2..b9d5e57cc9ee 100644 --- a/chia/wallet/wallet_state_manager.py +++ b/chia/wallet/wallet_state_manager.py @@ -40,11 +40,11 @@ from chia.wallet.derivation_record import DerivationRecord from chia.wallet.derive_keys import master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened from chia.wallet.key_val_store import KeyValStore +from chia.wallet.outer_puzzles import AssetType, PuzzleInfo from chia.wallet.puzzles.cat_loader import CAT_MOD from chia.wallet.rl_wallet.rl_wallet import RLWallet from chia.wallet.settings.user_settings import UserSettings from chia.wallet.trade_manager import TradeManager -from chia.wallet.trading.outer_puzzles import AssetType, PuzzleInfo from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.compute_hints import compute_coin_hints from chia.wallet.util.transaction_type import TransactionType diff --git a/tests/wallet/cat_wallet/test_offer_lifecycle.py b/tests/wallet/cat_wallet/test_offer_lifecycle.py index 5f536d8f44ad..34f04e0b5e3c 100644 --- a/tests/wallet/cat_wallet/test_offer_lifecycle.py +++ b/tests/wallet/cat_wallet/test_offer_lifecycle.py @@ -17,9 +17,9 @@ SpendableCAT, unsigned_spend_bundle_for_spendable_cats, ) +from chia.wallet.outer_puzzles import AssetType, PuzzleInfo from chia.wallet.payment import Payment from chia.wallet.trading.offer import Offer, NotarizedPayment -from chia.wallet.trading.outer_puzzles import AssetType, PuzzleInfo from tests.clvm.benchmark_costs import cost_of_spend_bundle acs = Program.to(1) From 390edb2ff48d66268a23a00c452455c2b0a24016 Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Thu, 21 Apr 2022 16:09:43 -0700 Subject: [PATCH 12/43] pivot from string to clvm for dict entries --- chia/rpc/wallet_rpc_api.py | 5 +- chia/wallet/cat_wallet/cat_outer_puzzle.py | 71 ++++++++++++++ chia/wallet/cat_wallet/cat_wallet.py | 9 +- chia/wallet/outer_puzzles.py | 92 +++---------------- chia/wallet/puzzle_drivers.py | 52 +++++++++++ chia/wallet/trade_manager.py | 17 ++-- chia/wallet/trading/offer.py | 10 +- chia/wallet/wallet_state_manager.py | 7 +- .../wallet/cat_wallet/test_offer_lifecycle.py | 11 ++- 9 files changed, 171 insertions(+), 103 deletions(-) create mode 100644 chia/wallet/cat_wallet/cat_outer_puzzle.py create mode 100644 chia/wallet/puzzle_drivers.py diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index 18334bdb523d..f0b255a59175 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -27,7 +27,8 @@ from chia.wallet.cat_wallet.cat_wallet import CATWallet from chia.wallet.derive_keys import master_sk_to_singleton_owner_sk, master_sk_to_wallet_sk_unhardened, MAX_POOL_WALLETS from chia.wallet.did_wallet.did_wallet import DIDWallet -from chia.wallet.outer_puzzles import AssetType, PuzzleInfo +from chia.wallet.outer_puzzles import AssetType +from chia.wallet.puzzle_drivers import PuzzleInfo from chia.wallet.rl_wallet.rl_wallet import RLWallet from chia.wallet.derive_keys import master_sk_to_farmer_sk, master_sk_to_pool_sk, master_sk_to_wallet_sk from chia.wallet.did_wallet.did_wallet import DIDWallet @@ -964,7 +965,7 @@ async def create_offer_for_ids(self, request): driver_dict: Dict[bytes32, PuzzleInfo] = {} if driver_dict_str is None: for key in offer: - if len(key) == 32: + if len(key) == 64: asset_id = bytes32.from_hexstr(key) driver_dict[asset_id] = PuzzleInfo({"type": AssetType.CAT, "tail": asset_id}) else: diff --git a/chia/wallet/cat_wallet/cat_outer_puzzle.py b/chia/wallet/cat_wallet/cat_outer_puzzle.py new file mode 100644 index 000000000000..64616c60753a --- /dev/null +++ b/chia/wallet/cat_wallet/cat_outer_puzzle.py @@ -0,0 +1,71 @@ +from dataclasses import dataclass +from typing import Any, Optional + +from chia.types.blockchain_format.coin import Coin +from chia.types.blockchain_format.program import Program +from chia.types.blockchain_format.sized_bytes import bytes32 +from chia.types.coin_spend import CoinSpend +from chia.util.ints import uint64 +from chia.wallet.cat_wallet.cat_utils import ( + CAT_MOD, + SpendableCAT, + construct_cat_puzzle, + match_cat_puzzle, + unsigned_spend_bundle_for_spendable_cats, +) +from chia.wallet.lineage_proof import LineageProof +from chia.wallet.puzzle_drivers import PuzzleInfo, Solver + + +@dataclass(frozen=True) +class CATOuterPuzzle: + _match: Any + _asset_id: Any + _construct: Any + _solve: Any + + def match(self, puzzle: Program) -> Optional[PuzzleInfo]: + matched, curried_args = match_cat_puzzle(puzzle) + if matched: + _, tail_hash, inner_puzzle = curried_args + constructor_dict = { + "type": "CAT", + "tail": "0x" + tail_hash.as_python().hex(), + } + next_constructor = self._match(inner_puzzle) + if next_constructor is not None: + constructor_dict["and"] = next_constructor.info + return PuzzleInfo(constructor_dict) + else: + return None + + def asset_id(self, constructor: PuzzleInfo) -> Optional[bytes32]: + return bytes32(constructor["tail"]) + + def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program: + if constructor.also() is not None: + inner_puzzle = self._construct(constructor.also(), inner_puzzle) + return construct_cat_puzzle(CAT_MOD, constructor["tail"], inner_puzzle) + + def solve(self, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program: + tail_hash: bytes32 = constructor["tail"] + coin_bytes: bytes = solver["coin"] + coin: Coin = Coin(bytes32(coin_bytes[0:32]), bytes32(coin_bytes[32:64]), uint64.from_bytes(coin_bytes[64:72])) + parent_spend: CoinSpend = CoinSpend.from_bytes(solver["parent_spend"]) + parent_coin: Coin = parent_spend.coin + if constructor.also() is not None: + inner_puzzle = self._construct(constructor.also(), inner_puzzle) + inner_solution = self._solve(constructor.also(), solver, inner_puzzle, inner_solution) + matched, curried_args = match_cat_puzzle(parent_spend.puzzle_reveal.to_program()) + assert matched + _, _, parent_inner_puzzle = curried_args + spendable_cat = SpendableCAT( + coin, + tail_hash, + inner_puzzle, + inner_solution, + lineage_proof=LineageProof( + parent_coin.parent_coin_info, parent_inner_puzzle.get_tree_hash(), parent_coin.amount + ), + ) + return unsigned_spend_bundle_for_spendable_cats(CAT_MOD, [spendable_cat]).coin_spends[0].solution.to_program() diff --git a/chia/wallet/cat_wallet/cat_wallet.py b/chia/wallet/cat_wallet/cat_wallet.py index fa5be8f80afc..1c8059425918 100644 --- a/chia/wallet/cat_wallet/cat_wallet.py +++ b/chia/wallet/cat_wallet/cat_wallet.py @@ -35,7 +35,8 @@ from chia.wallet.derivation_record import DerivationRecord from chia.wallet.cat_wallet.lineage_store import CATLineageStore from chia.wallet.lineage_proof import LineageProof -from chia.wallet.outer_puzzles import AssetType, PuzzleInfo +from chia.wallet.outer_puzzles import AssetType +from chia.wallet.puzzle_drivers import PuzzleInfo from chia.wallet.payment import Payment from chia.wallet.puzzles.tails import ALL_LIMITATIONS_PROGRAMS from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import ( @@ -220,7 +221,7 @@ async def create_from_puzzle_info( return await cls.create_wallet_for_cat( wallet_state_manager, wallet, - puzzle_driver.info["tail"].hex(), + puzzle_driver["tail"].hex(), name, in_transaction, ) @@ -827,8 +828,8 @@ async def save_info(self, cat_info: CATInfo, in_transaction): def match_puzzle_info(self, puzzle_driver: PuzzleInfo) -> bool: if ( - puzzle_driver.info["type"] == AssetType.CAT - and puzzle_driver.info["tail"] == bytes.fromhex(self.get_asset_id()) + AssetType(puzzle_driver.type()) == AssetType.CAT + and puzzle_driver["tail"] == bytes.fromhex(self.get_asset_id()) and "and" not in puzzle_driver.info ): return True diff --git a/chia/wallet/outer_puzzles.py b/chia/wallet/outer_puzzles.py index b7dbd0bdbee9..00bedfa1fec5 100644 --- a/chia/wallet/outer_puzzles.py +++ b/chia/wallet/outer_puzzles.py @@ -1,91 +1,16 @@ from enum import Enum -from dataclasses import dataclass from typing import Any, Dict, Optional -from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.sized_bytes import bytes32 -from chia.types.coin_spend import CoinSpend -from chia.wallet.cat_wallet.cat_utils import ( - CAT_MOD, - SpendableCAT, - construct_cat_puzzle, - match_cat_puzzle, - unsigned_spend_bundle_for_spendable_cats, -) -from chia.wallet.lineage_proof import LineageProof +from chia.wallet.cat_wallet.cat_outer_puzzle import CATOuterPuzzle +from chia.wallet.puzzle_drivers import PuzzleInfo, Solver class AssetType(Enum): CAT = "CAT" -@dataclass(frozen=True) -class PuzzleInfo: - info: Dict[str, Any] - - -@dataclass(frozen=True) -class Solver: - info: Dict[str, Any] - - -class CATOuterPuzzle: - @classmethod - def match(cls, puzzle: Program) -> Optional[PuzzleInfo]: - matched, curried_args = match_cat_puzzle(puzzle) - if matched: - _, tail_hash, inner_puzzle = curried_args - constructor_dict = { - "type": AssetType.CAT, - "tail": bytes32(tail_hash.as_python()), - } - next_constructor = match_puzzle(inner_puzzle) - if next_constructor is not None: - constructor_dict["and"] = next_constructor.info - return PuzzleInfo(constructor_dict) - else: - return None - - @classmethod - def asset_id(cls, constructor: PuzzleInfo) -> Optional[bytes32]: - return bytes32(constructor.info["tail"]) - - @classmethod - def construct(cls, constructor: PuzzleInfo, inner_puzzle: Program) -> Program: - if "and" in constructor.info: - inner_puzzle = construct_puzzle(constructor.info["and"], inner_puzzle) - return construct_cat_puzzle(CAT_MOD, constructor.info["tail"], inner_puzzle) - - @classmethod - def solve(cls, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program: - tail_hash: bytes32 = constructor.info["tail"] - coin: Coin = solver.info["coin"] - parent_spend: CoinSpend = solver.info["parent_spend"] - parent_coin: Coin = parent_spend.coin - if "and" in constructor.info: - inner_puzzle = construct_puzzle(PuzzleInfo(constructor.info["and"]), inner_puzzle) - inner_solution = solve_puzzle(PuzzleInfo(constructor.info["and"]), solver, inner_puzzle, inner_solution) - matched, curried_args = match_cat_puzzle(parent_spend.puzzle_reveal.to_program()) - assert matched - _, _, parent_inner_puzzle = curried_args - spendable_cat = SpendableCAT( - coin, - tail_hash, - inner_puzzle, - inner_solution, - lineage_proof=LineageProof( - parent_coin.parent_coin_info, parent_inner_puzzle.get_tree_hash(), parent_coin.amount - ), - ) - return unsigned_spend_bundle_for_spendable_cats(CAT_MOD, [spendable_cat]).coin_spends[0].solution.to_program() - - -driver_lookup: Dict[AssetType, Any] = { - AssetType.CAT: CATOuterPuzzle, -} - - def match_puzzle(puzzle: Program) -> Optional[PuzzleInfo]: for driver in driver_lookup.values(): potential_info: Optional[PuzzleInfo] = driver.match(puzzle) @@ -95,14 +20,21 @@ def match_puzzle(puzzle: Program) -> Optional[PuzzleInfo]: def construct_puzzle(constructor: PuzzleInfo, inner_puzzle: Program) -> Program: - return driver_lookup[constructor.info["type"]].construct(constructor, inner_puzzle) # type: ignore + return driver_lookup[AssetType(constructor.type())].construct(constructor, inner_puzzle) # type: ignore def solve_puzzle(constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program: - return driver_lookup[constructor.info["type"]].solve( # type: ignore + return driver_lookup[AssetType(constructor.type())].solve( # type: ignore constructor, solver, inner_puzzle, inner_solution ) def create_asset_id(constructor: PuzzleInfo) -> bytes32: - return driver_lookup[constructor.info["type"]].asset_id(constructor) # type: ignore + return driver_lookup[AssetType(constructor.type())].asset_id(constructor) # type: ignore + + +function_args = [match_puzzle, construct_puzzle, solve_puzzle, create_asset_id] + +driver_lookup: Dict[AssetType, Any] = { + AssetType.CAT: CATOuterPuzzle(*function_args), +} diff --git a/chia/wallet/puzzle_drivers.py b/chia/wallet/puzzle_drivers.py new file mode 100644 index 000000000000..c2ccc7c0b5e3 --- /dev/null +++ b/chia/wallet/puzzle_drivers.py @@ -0,0 +1,52 @@ +from clvm.casts import int_from_bytes +from clvm_tools.binutils import assemble, type_for_atom +from dataclasses import dataclass +from ir.Type import Type +from typing import Any, Dict, Optional + + +@dataclass(frozen=True) +class PuzzleInfo: + info: Dict[str, Any] + + def __post_init__(self) -> None: + if "type" not in self.info: + raise ValueError("A type is required to initialize a puzzle driver") + + def __getitem__(self, item: str) -> Any: + value = self.info[decode_info_value(PuzzleInfo, item)] + return decode_info_value(PuzzleInfo, value) + + def type(self) -> str: + return str(self.info["type"]) + + def also(self) -> Optional["PuzzleInfo"]: + if "also" in self.info: + return PuzzleInfo(self.info["also"]) + else: + return None + + +@dataclass(frozen=True) +class Solver: + info: Dict[str, Any] + + def __getitem__(self, item: str) -> Any: + value = self.info[decode_info_value(Solver, item)] + return decode_info_value(Solver, value) + + +def decode_info_value(cls: Any, value: Any) -> Any: + if isinstance(value, dict): + return cls(value) + elif isinstance(value, list): + return [decode_info_value(cls, v) for v in value] + else: + atom: bytes = assemble(value).as_atom() # type: ignore + typ = type_for_atom(atom) + if typ == Type.QUOTES: + return bytes(atom).decode("utf8") + elif typ == Type.INT: + return int_from_bytes(atom) + else: + return atom diff --git a/chia/wallet/trade_manager.py b/chia/wallet/trade_manager.py index ee303f78bdbb..427e87c54c13 100644 --- a/chia/wallet/trade_manager.py +++ b/chia/wallet/trade_manager.py @@ -12,7 +12,8 @@ from chia.util.db_wrapper import DBWrapper from chia.util.hash import std_hash from chia.util.ints import uint32, uint64 -from chia.wallet.outer_puzzles import AssetType, PuzzleInfo +from chia.wallet.outer_puzzles import AssetType +from chia.wallet.puzzle_drivers import PuzzleInfo from chia.wallet.payment import Payment from chia.wallet.trade_record import TradeRecord from chia.wallet.trading.offer import Offer, NotarizedPayment @@ -302,13 +303,13 @@ async def _create_offer_for_ids( elif wallet.type() == WalletType.CAT: key = bytes32(bytes.fromhex(wallet.get_asset_id())) memos = [p2_ph] - if key in driver_dict and driver_dict[key].info["type"] != AssetType.CAT: + if key in driver_dict and AssetType(driver_dict[key].type()) != AssetType.CAT: raise ValueError( - f"driver_dict specified {driver_dict[key].info['type']}," + f"driver_dict specified {AssetType(driver_dict[key].type())}," f"was expecting {AssetType.CAT}" ) else: - driver_dict[key] = PuzzleInfo({"type": AssetType.CAT, "tail": key}) + driver_dict[key] = PuzzleInfo({"type": AssetType.CAT.value, "tail": "0x" + key.hex()}) else: raise ValueError(f"Offers are not implemented for {wallet.type()}") else: @@ -327,13 +328,15 @@ async def _create_offer_for_ids( # ATTENTION: new wallets if wallet.type() == WalletType.CAT: asset_id = bytes32(bytes.fromhex(wallet.get_asset_id())) - if asset_id in driver_dict and driver_dict[asset_id].info["type"] != AssetType.CAT: + if asset_id in driver_dict and AssetType(driver_dict[asset_id].type()) != AssetType.CAT: raise ValueError( - f"driver_dict specified {driver_dict[asset_id].info['type']}," + f"driver_dict specified {AssetType(driver_dict[asset_id].type())}," f"was expecting {AssetType.CAT}" ) else: - driver_dict[asset_id] = PuzzleInfo({"type": AssetType.CAT, "tail": asset_id}) + driver_dict[asset_id] = PuzzleInfo( + {"type": AssetType.CAT.value, "tail": "0x" + asset_id.hex()} + ) elif amount == 0: raise ValueError("You cannot offer nor request 0 amount of something") diff --git a/chia/wallet/trading/offer.py b/chia/wallet/trading/offer.py index 71d44f06261a..d21e35c11c46 100644 --- a/chia/wallet/trading/offer.py +++ b/chia/wallet/trading/offer.py @@ -16,13 +16,12 @@ lowest_best_version, ) from chia.wallet.outer_puzzles import ( - PuzzleInfo, - Solver, create_asset_id, construct_puzzle, match_puzzle, solve_puzzle, ) +from chia.wallet.puzzle_drivers import PuzzleInfo, Solver from chia.wallet.puzzles.load_clvm import load_clvm from chia.wallet.payment import Payment @@ -303,8 +302,11 @@ def to_valid_spend(self, arbitrage_ph: Optional[bytes32] = None) -> SpendBundle: self.driver_dict[asset_id], Solver( { - "coin": coin, - "parent_spend": parent_spend, + "coin": "0x" + + coin.parent_coin_info.hex() + + coin.puzzle_hash.hex() + + bytes(coin.amount).hex(), + "parent_spend": "0x" + bytes(parent_spend).hex(), } ), OFFER_MOD, diff --git a/chia/wallet/wallet_state_manager.py b/chia/wallet/wallet_state_manager.py index b9d5e57cc9ee..f7dc4e371ea6 100644 --- a/chia/wallet/wallet_state_manager.py +++ b/chia/wallet/wallet_state_manager.py @@ -40,7 +40,8 @@ from chia.wallet.derivation_record import DerivationRecord from chia.wallet.derive_keys import master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened from chia.wallet.key_val_store import KeyValStore -from chia.wallet.outer_puzzles import AssetType, PuzzleInfo +from chia.wallet.outer_puzzles import AssetType +from chia.wallet.puzzle_drivers import PuzzleInfo from chia.wallet.puzzles.cat_loader import CAT_MOD from chia.wallet.rl_wallet.rl_wallet import RLWallet from chia.wallet.settings.user_settings import UserSettings @@ -1295,9 +1296,9 @@ async def get_wallet_for_puzzle_info(self, puzzle_driver: PuzzleInfo): return None async def create_wallet_for_puzzle_info(self, puzzle_driver: PuzzleInfo, name=None, in_transaction=False): - if puzzle_driver.info["type"] in self.asset_to_wallet_map: + if AssetType(puzzle_driver.type()) in self.asset_to_wallet_map: async with self.lock: - await self.asset_to_wallet_map[puzzle_driver.info["type"]].create_from_puzzle_info( + await self.asset_to_wallet_map[AssetType(puzzle_driver.type())].create_from_puzzle_info( self, self.main_wallet, puzzle_driver, diff --git a/tests/wallet/cat_wallet/test_offer_lifecycle.py b/tests/wallet/cat_wallet/test_offer_lifecycle.py index 34f04e0b5e3c..9ab601d08b18 100644 --- a/tests/wallet/cat_wallet/test_offer_lifecycle.py +++ b/tests/wallet/cat_wallet/test_offer_lifecycle.py @@ -17,7 +17,8 @@ SpendableCAT, unsigned_spend_bundle_for_spendable_cats, ) -from chia.wallet.outer_puzzles import AssetType, PuzzleInfo +from chia.wallet.outer_puzzles import AssetType +from chia.wallet.puzzle_drivers import PuzzleInfo from chia.wallet.payment import Payment from chia.wallet.trading.offer import Offer, NotarizedPayment from tests.clvm.benchmark_costs import cost_of_spend_bundle @@ -180,8 +181,12 @@ async def test_complex_offer(self, setup_sim): blue_coins: List[Coin] = all_coins["blue"] driver_dict: Dict[bytes32, PuzzleInfo] = { - str_to_tail_hash("red"): PuzzleInfo({"type": AssetType.CAT, "tail": str_to_tail_hash("red")}), - str_to_tail_hash("blue"): PuzzleInfo({"type": AssetType.CAT, "tail": str_to_tail_hash("blue")}), + str_to_tail_hash("red"): PuzzleInfo( + {"type": AssetType.CAT.value, "tail": "0x" + str_to_tail_hash("red").hex()} + ), + str_to_tail_hash("blue"): PuzzleInfo( + {"type": AssetType.CAT.value, "tail": "0x" + str_to_tail_hash("blue").hex()} + ), } # Create an XCH Offer for RED From 28d574052278090497695979aa0df00d99975247 Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Fri, 22 Apr 2022 12:46:54 -0700 Subject: [PATCH 13/43] add test coverage for driver dict in RPC --- chia/rpc/wallet_rpc_api.py | 18 ++++++++++-------- chia/rpc/wallet_rpc_client.py | 15 +++++++++++++-- tests/wallet/rpc/test_wallet_rpc.py | 8 ++++++-- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index f0b255a59175..27099de2ec36 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -959,24 +959,26 @@ async def create_offer_for_ids(self, request): offer: Dict[str, int] = request["offer"] fee: uint64 = uint64(request.get("fee", 0)) validate_only: bool = request.get("validate_only", False) - driver_dict_str: Optional[Dict[str, str]] = request.get("driver_dict", None) + driver_dict_str: Optional[Dict[str, Any]] = request.get("driver_dict", None) # This driver_dict construction is to maintain backward compatibility where everything is assumed to be a CAT driver_dict: Dict[bytes32, PuzzleInfo] = {} if driver_dict_str is None: for key in offer: if len(key) == 64: - asset_id = bytes32.from_hexstr(key) - driver_dict[asset_id] = PuzzleInfo({"type": AssetType.CAT, "tail": asset_id}) + driver_dict[bytes32.from_hexstr(key)] = PuzzleInfo( + {"type": AssetType.CAT.value, "tail": "0x" + key} + ) else: - for key, value in driver_dict_str: - cast_type_driver_dict = driver_dict_str[key] - cast_type_driver_dict["type"] = AssetType(cast_type_driver_dict["type"]) - driver_dict[bytes32.from_hexstr(key)] = PuzzleInfo(cast_type_driver_dict) + for key, value in driver_dict_str.items(): + driver_dict[bytes32.from_hexstr(key)] = PuzzleInfo(value) modified_offer = {} for key in offer: - modified_offer[int(key)] = offer[key] + if len(key) == 64: + modified_offer[bytes32.from_hexstr(key)] = offer[key] + else: + modified_offer[int(key)] = offer[key] async with self.service.wallet_state_manager.lock: ( diff --git a/chia/rpc/wallet_rpc_client.py b/chia/rpc/wallet_rpc_client.py index 6b53a4fdb3fe..07c4d0f4771f 100644 --- a/chia/rpc/wallet_rpc_client.py +++ b/chia/rpc/wallet_rpc_client.py @@ -468,13 +468,24 @@ async def cat_spend( # Offers async def create_offer_for_ids( - self, offer_dict: Dict[uint32, int], fee=uint64(0), validate_only: bool = False + self, + offer_dict: Dict[uint32, int], + driver_dict: Dict[str, Any] = None, + fee=uint64(0), + validate_only: bool = False, ) -> Tuple[Optional[Offer], TradeRecord]: send_dict: Dict[str, int] = {} for key in offer_dict: send_dict[str(key)] = offer_dict[key] - res = await self.fetch("create_offer_for_ids", {"offer": send_dict, "validate_only": validate_only, "fee": fee}) + req = { + "offer": send_dict, + "validate_only": validate_only, + "fee": fee, + } + if driver_dict is not None: + req["driver_dict"] = driver_dict + res = await self.fetch("create_offer_for_ids", req) offer: Optional[Offer] = None if validate_only else Offer.from_bech32(res["offer"]) offer_str: str = "" if offer is None else bytes(offer).hex() return offer, TradeRecord.from_json_dict_convenience(res["trade_record"], offer_str) diff --git a/tests/wallet/rpc/test_wallet_rpc.py b/tests/wallet/rpc/test_wallet_rpc.py index 32668d137b5a..46736e799bad 100644 --- a/tests/wallet/rpc/test_wallet_rpc.py +++ b/tests/wallet/rpc/test_wallet_rpc.py @@ -502,12 +502,16 @@ async def eventual_balance_det(c, wallet_id: str): ########## # Create an offer of 5 chia for one CAT - offer, trade_record = await client.create_offer_for_ids({uint32(1): -5, cat_0_id: 1}, validate_only=True) + offer, trade_record = await client.create_offer_for_ids({uint32(1): -5, col.hex(): 1}, validate_only=True) all_offers = await client.get_all_offers() assert len(all_offers) == 0 assert offer is None - offer, trade_record = await client.create_offer_for_ids({uint32(1): -5, cat_0_id: 1}, fee=uint64(1)) + offer, trade_record = await client.create_offer_for_ids( + {uint32(1): -5, col.hex(): 1}, + driver_dict={col.hex(): {"type": "CAT", "tail": "0x" + col.hex()}}, + fee=uint64(1), + ) summary = await client.get_offer_summary(offer) assert summary == {"offered": {"xch": 5}, "requested": {col.hex(): 1}, "fees": 1} From e26fc458a2e0f711261e9cddbac01bb291d85bcc Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Fri, 22 Apr 2022 14:54:11 -0700 Subject: [PATCH 14/43] Remove some CAT specific stuff from tm --- chia/wallet/cat_wallet/cat_wallet.py | 18 +++++--- chia/wallet/trade_manager.py | 62 ++++++++++++++++------------ chia/wallet/wallet.py | 6 +++ 3 files changed, 54 insertions(+), 32 deletions(-) diff --git a/chia/wallet/cat_wallet/cat_wallet.py b/chia/wallet/cat_wallet/cat_wallet.py index 1c8059425918..cf5acd8eabe8 100644 --- a/chia/wallet/cat_wallet/cat_wallet.py +++ b/chia/wallet/cat_wallet/cat_wallet.py @@ -827,11 +827,17 @@ async def save_info(self, cat_info: CATInfo, in_transaction): await self.wallet_state_manager.user_store.update_wallet(wallet_info, in_transaction) def match_puzzle_info(self, puzzle_driver: PuzzleInfo) -> bool: - if ( + return ( AssetType(puzzle_driver.type()) == AssetType.CAT and puzzle_driver["tail"] == bytes.fromhex(self.get_asset_id()) - and "and" not in puzzle_driver.info - ): - return True - else: - return False + and puzzle_driver.also() is None + ) + + def get_puzzle_info(self) -> PuzzleInfo: + return PuzzleInfo({"type": AssetType.CAT.value, "tail": "0x" + self.get_asset_id()}) + + async def get_coins_to_offer(self, asset_id: Optional[bytes32], amount: uint64) -> Set[Coin]: + balance = await self.get_confirmed_balance() + if balance < amount: + raise Exception(f"insufficient funds in wallet {self.id()}") + return await self.select_coins(amount) diff --git a/chia/wallet/trade_manager.py b/chia/wallet/trade_manager.py index 427e87c54c13..40a080c162d1 100644 --- a/chia/wallet/trade_manager.py +++ b/chia/wallet/trade_manager.py @@ -12,7 +12,6 @@ from chia.util.db_wrapper import DBWrapper from chia.util.hash import std_hash from chia.util.ints import uint32, uint64 -from chia.wallet.outer_puzzles import AssetType from chia.wallet.puzzle_drivers import PuzzleInfo from chia.wallet.payment import Payment from chia.wallet.trade_record import TradeRecord @@ -296,47 +295,58 @@ async def _create_offer_for_ids( wallet_id = uint32(id) wallet = self.wallet_state_manager.wallets[wallet_id] p2_ph: bytes32 = await wallet.get_new_puzzlehash() - # ATTENTION: new wallets if wallet.type() == WalletType.STANDARD_WALLET: key: Optional[bytes32] = None memos: List[bytes] = [] - elif wallet.type() == WalletType.CAT: + elif callable(getattr(wallet, "get_asset_id", None)) and callable( # ATTENTION: new wallets + getattr(wallet, "get_puzzle_info", None) + ): # ATTENTION: new wallets key = bytes32(bytes.fromhex(wallet.get_asset_id())) + puzzle_driver: PuzzleInfo = wallet.get_puzzle_info() memos = [p2_ph] - if key in driver_dict and AssetType(driver_dict[key].type()) != AssetType.CAT: + if key in driver_dict and driver_dict[key] != puzzle_driver: raise ValueError( - f"driver_dict specified {AssetType(driver_dict[key].type())}," - f"was expecting {AssetType.CAT}" + f"driver_dict specified {driver_dict[key]}," f" was expecting {puzzle_driver}" ) else: - driver_dict[key] = PuzzleInfo({"type": AssetType.CAT.value, "tail": "0x" + key.hex()}) + driver_dict[key] = puzzle_driver else: - raise ValueError(f"Offers are not implemented for {wallet.type()}") + raise ValueError( + f"Cannot request assets from wallet id {wallet.id()} without more information" + ) else: p2_ph = await self.wallet_state_manager.main_wallet.get_new_puzzlehash() key = id memos = [p2_ph] requested_payments[key] = [Payment(p2_ph, uint64(amount), memos)] elif amount < 0: - assert isinstance(id, int) - wallet_id = uint32(id) - wallet = self.wallet_state_manager.wallets[wallet_id] - balance = await wallet.get_confirmed_balance() - if balance < abs(amount): - raise Exception(f"insufficient funds in wallet {wallet_id}") - coins_to_offer[wallet_id] = await wallet.select_coins(uint64(abs(amount))) - # ATTENTION: new wallets - if wallet.type() == WalletType.CAT: - asset_id = bytes32(bytes.fromhex(wallet.get_asset_id())) - if asset_id in driver_dict and AssetType(driver_dict[asset_id].type()) != AssetType.CAT: - raise ValueError( - f"driver_dict specified {AssetType(driver_dict[asset_id].type())}," - f"was expecting {AssetType.CAT}" - ) + if isinstance(id, int): + wallet_id = uint32(id) + wallet = self.wallet_state_manager.wallets[wallet_id] + if wallet.type() == WalletType.STANDARD_WALLET: + asset_id = None + elif callable(getattr(wallet, "get_asset_id", None)) and callable( # ATTENTION: new wallets + getattr(wallet, "get_puzzle_info", None) + ): # ATTENTION: new wallets + asset_id = bytes32(bytes.fromhex(wallet.get_asset_id())) + puzzle_driver = wallet.get_puzzle_info() + if asset_id in driver_dict and driver_dict[asset_id] != puzzle_driver: + raise ValueError( + f"driver_dict specified {driver_dict[asset_id]}," f" was expecting {puzzle_driver}" + ) + else: + driver_dict[asset_id] = puzzle_driver else: - driver_dict[asset_id] = PuzzleInfo( - {"type": AssetType.CAT.value, "tail": "0x" + asset_id.hex()} + raise ValueError( + f"Cannot offer assets from wallet id {wallet.id()} without more information" ) + else: + asset_id = id + wallet = await self.wallet_state_manager.get_wallet_for_asset_id(asset_id) + wallet_id = wallet.id() + if not callable(getattr(wallet, "get_coins_to_offer", None)): + raise ValueError(f"Cannot offer coins from wallet id {wallet.id()}") + coins_to_offer[wallet_id] = await wallet.get_coins_to_offer(asset_id, uint64(abs(amount))) elif amount == 0: raise ValueError("You cannot offer nor request 0 amount of something") @@ -503,7 +513,7 @@ async def respond_to_offer(self, offer: Offer, fee=uint64(0)) -> Tuple[bool, Opt # ATTENTION: new wallets wallet = await self.wallet_state_manager.get_wallet_for_asset_id(asset_id.hex()) if wallet is None and amount < 0: - return False, None, f"Do not have a wallet of asset ID: {asset_id} to fulfill offer" + return False, None, f"Do not have a wallet for asset ID: {asset_id} to fulfill offer" elif wallet is None: key = asset_id else: diff --git a/chia/wallet/wallet.py b/chia/wallet/wallet.py index aa5db39c37e3..08fef8aa311c 100644 --- a/chia/wallet/wallet.py +++ b/chia/wallet/wallet.py @@ -546,3 +546,9 @@ async def create_spend_bundle_relative_chia(self, chia_amount: int, exclude: Lis self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM, ) return spend_bundle + + async def get_coins_to_offer(self, asset_id: Optional[bytes32], amount: uint64) -> Set[Coin]: + balance = await self.get_confirmed_balance() + if balance < amount: + raise Exception(f"insufficient funds in wallet {self.id()}") + return await self.select_coins(amount) From 2d27c55038a987e4da76f7426690b9f6310b4ea3 Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Mon, 25 Apr 2022 12:59:29 -0700 Subject: [PATCH 15/43] Add comments explaining the changes --- chia/wallet/outer_puzzles.py | 18 ++++++++++++++++ chia/wallet/puzzle_drivers.py | 12 +++++++++++ chia/wallet/trade_manager.py | 40 +++++++++++++++++++++++++++++++---- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/chia/wallet/outer_puzzles.py b/chia/wallet/outer_puzzles.py index 00bedfa1fec5..3c399d6b1cbe 100644 --- a/chia/wallet/outer_puzzles.py +++ b/chia/wallet/outer_puzzles.py @@ -7,6 +7,24 @@ from chia.wallet.puzzle_drivers import PuzzleInfo, Solver +""" +This file provides a central location for acquiring drivers for outer puzzles like CATs, NFTs, etc. + +A driver for a puzzle must include the following functions: + - match(self, puzzle: Program) -> Optional[PuzzleInfo] + - Given a puzzle reveal, return a PuzzleInfo object that can be used to reconstruct it later + - asset_id(self, constructor: PuzzleInfo) -> Optional[bytes32] + - Given a PuzzleInfo object, generate a 32 byte ID for use in dictionaries, etc. + - construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program + - Given a PuzzleInfo object and an innermost puzzle, construct a puzzle reveal for a coin spend + - solve(self, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program + - Given a PuzzleInfo object, a Solver object, and an innermost puzzle and its solution return a solution for a spend + - The "Solver" object can contain any dictionary, it's up to the driver to enforce the needed elements of the API + - Some classes that wish to integrate with a driver may not have access to all of the info it needs so the driver + needs to raise errors appropriately +""" + + class AssetType(Enum): CAT = "CAT" diff --git a/chia/wallet/puzzle_drivers.py b/chia/wallet/puzzle_drivers.py index c2ccc7c0b5e3..f105808b07ea 100644 --- a/chia/wallet/puzzle_drivers.py +++ b/chia/wallet/puzzle_drivers.py @@ -4,9 +4,21 @@ from ir.Type import Type from typing import Any, Dict, Optional +""" +The following two classes act as wrapper classes around dictionaries of strings. +Values in the dictionary are assumed to be strings in CLVM format (0x for bytes, etc.) +When you access a value in the dictionary, it will be deserialized to a str, int, bytes, or Program appropriately. +""" + @dataclass(frozen=True) class PuzzleInfo: + """ + There are two 'magic' keys in a PuzzleInfo object: + - 'type' must be an included key (for easy lookup of drivers) + - 'also' gets its own method as it's the supported way to do recursion of PuzzleInfos + """ + info: Dict[str, Any] def __post_init__(self) -> None: diff --git a/chia/wallet/trade_manager.py b/chia/wallet/trade_manager.py index 40a080c162d1..9cfededb363a 100644 --- a/chia/wallet/trade_manager.py +++ b/chia/wallet/trade_manager.py @@ -26,6 +26,38 @@ class TradeManager: + """ + This class is a driver for creating and accepting settlement_payments.clvm style offers. + + By default, standard XCH is supported but to support other types of assets you must implement certain functions on + the asset's wallet as well as create a driver for its puzzle(s). Here is a guide to integrating a new types of + assets with this trade manager: + + Puzzle Drivers: + - See chia/wallet/outer_puzzles.py for a full description of how to build these + - The `solve` method must be able to be solved by a Solver that looks like this: + Solver( + { + "coin": bytes + "parent_spend": bytes + } + ) + + Wallet: + - Segments in this code that call general wallet methods are highlighted by comments: # ATTENTION: new wallets + - To be able to be traded, a wallet must implement these methods on itself: + - generate_signed_transaction(...) -> List[TransactionRecord] (See cat_wallet.py for full API) + - convert_puzzle_hash(puzzle_hash: bytes32) -> bytes32 # Converts a puzzlehash from outer to inner puzzle + - get_coins_to_offer(asset_id: bytes32, amount: uint64) -> Set[Coin] + - If you would like assets from your wallet to be referenced with just a wallet ID, you must also implement: + - get_asset_id() -> bytes32 + - get_puzzle_info() -> PuzzleInfo + - Finally, you must make sure that your wallet will respond appropriately when these WSM methods are called: + - get_wallet_for_puzzle_info(puzzle_info: PuzzleInfo) -> + - create_wallet_for_puzzle_info(puzzle_info: PuzzleInfo) -> + - get_wallet_for_asset_id(asset_id: bytes32) -> + """ + wallet_state_manager: Any log: logging.Logger trade_store: TradeStore @@ -300,7 +332,7 @@ async def _create_offer_for_ids( memos: List[bytes] = [] elif callable(getattr(wallet, "get_asset_id", None)) and callable( # ATTENTION: new wallets getattr(wallet, "get_puzzle_info", None) - ): # ATTENTION: new wallets + ): key = bytes32(bytes.fromhex(wallet.get_asset_id())) puzzle_driver: PuzzleInfo = wallet.get_puzzle_info() memos = [p2_ph] @@ -327,7 +359,7 @@ async def _create_offer_for_ids( asset_id = None elif callable(getattr(wallet, "get_asset_id", None)) and callable( # ATTENTION: new wallets getattr(wallet, "get_puzzle_info", None) - ): # ATTENTION: new wallets + ): asset_id = bytes32(bytes.fromhex(wallet.get_asset_id())) puzzle_driver = wallet.get_puzzle_info() if asset_id in driver_dict and driver_dict[asset_id] != puzzle_driver: @@ -344,7 +376,7 @@ async def _create_offer_for_ids( asset_id = id wallet = await self.wallet_state_manager.get_wallet_for_asset_id(asset_id) wallet_id = wallet.id() - if not callable(getattr(wallet, "get_coins_to_offer", None)): + if not callable(getattr(wallet, "get_coins_to_offer", None)): # ATTENTION: new wallets raise ValueError(f"Cannot offer coins from wallet id {wallet.id()}") coins_to_offer[wallet_id] = await wallet.get_coins_to_offer(asset_id, uint64(abs(amount))) elif amount == 0: @@ -402,7 +434,7 @@ async def maybe_create_wallets_for_offer(self, offer: Offer): # ATTENTION: new_wallets exists: Optional[Wallet] = await wsm.get_wallet_for_puzzle_info(offer.driver_dict[key]) if exists is None: - await wsm.create_wallet_for_puzzle_info(offer.driver_dict[key]) # ATTENTION: new_wallets + await wsm.create_wallet_for_puzzle_info(offer.driver_dict[key]) async def check_offer_validity(self, offer: Offer) -> bool: all_removals: List[Coin] = offer.bundle.removals() From 1f24ec90a1c150be962ce7f7683ac6f2e3b97859 Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Mon, 25 Apr 2022 13:03:43 -0700 Subject: [PATCH 16/43] Minor fixes --- chia/wallet/cat_wallet/cat_outer_puzzle.py | 2 +- chia/wallet/puzzle_drivers.py | 24 ++++++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/chia/wallet/cat_wallet/cat_outer_puzzle.py b/chia/wallet/cat_wallet/cat_outer_puzzle.py index 64616c60753a..1852121bdf54 100644 --- a/chia/wallet/cat_wallet/cat_outer_puzzle.py +++ b/chia/wallet/cat_wallet/cat_outer_puzzle.py @@ -34,7 +34,7 @@ def match(self, puzzle: Program) -> Optional[PuzzleInfo]: } next_constructor = self._match(inner_puzzle) if next_constructor is not None: - constructor_dict["and"] = next_constructor.info + constructor_dict["also"] = next_constructor.info return PuzzleInfo(constructor_dict) else: return None diff --git a/chia/wallet/puzzle_drivers.py b/chia/wallet/puzzle_drivers.py index f105808b07ea..8492e5d5ca55 100644 --- a/chia/wallet/puzzle_drivers.py +++ b/chia/wallet/puzzle_drivers.py @@ -1,4 +1,6 @@ +from chia.types.blockchain_format.program import Program from clvm.casts import int_from_bytes +from clvm.SExp import SExp from clvm_tools.binutils import assemble, type_for_atom from dataclasses import dataclass from ir.Type import Type @@ -26,7 +28,7 @@ def __post_init__(self) -> None: raise ValueError("A type is required to initialize a puzzle driver") def __getitem__(self, item: str) -> Any: - value = self.info[decode_info_value(PuzzleInfo, item)] + value = self.info[item] return decode_info_value(PuzzleInfo, value) def type(self) -> str: @@ -44,7 +46,7 @@ class Solver: info: Dict[str, Any] def __getitem__(self, item: str) -> Any: - value = self.info[decode_info_value(Solver, item)] + value = self.info[item] return decode_info_value(Solver, value) @@ -54,11 +56,15 @@ def decode_info_value(cls: Any, value: Any) -> Any: elif isinstance(value, list): return [decode_info_value(cls, v) for v in value] else: - atom: bytes = assemble(value).as_atom() # type: ignore - typ = type_for_atom(atom) - if typ == Type.QUOTES: - return bytes(atom).decode("utf8") - elif typ == Type.INT: - return int_from_bytes(atom) + expression: Sexp = assemble(value) + if expression.atom is None: + return Program(expression) else: - return atom + atom: bytes = expression.atom + typ = type_for_atom(atom) + if typ == Type.QUOTES: + return bytes(atom).decode("utf8") + elif typ == Type.INT: + return int_from_bytes(atom) + else: + return atom From f9a68f5c545e609c4c49ab0ce38c9bd59ca1f542 Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Mon, 25 Apr 2022 13:11:20 -0700 Subject: [PATCH 17/43] isort and flake8 --- chia/wallet/puzzle_drivers.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/chia/wallet/puzzle_drivers.py b/chia/wallet/puzzle_drivers.py index 8492e5d5ca55..3f25f453b9ff 100644 --- a/chia/wallet/puzzle_drivers.py +++ b/chia/wallet/puzzle_drivers.py @@ -1,10 +1,12 @@ -from chia.types.blockchain_format.program import Program +from dataclasses import dataclass +from typing import Any, Dict, Optional + from clvm.casts import int_from_bytes from clvm.SExp import SExp from clvm_tools.binutils import assemble, type_for_atom -from dataclasses import dataclass from ir.Type import Type -from typing import Any, Dict, Optional + +from chia.types.blockchain_format.program import Program """ The following two classes act as wrapper classes around dictionaries of strings. @@ -56,7 +58,7 @@ def decode_info_value(cls: Any, value: Any) -> Any: elif isinstance(value, list): return [decode_info_value(cls, v) for v in value] else: - expression: Sexp = assemble(value) + expression: SExp = assemble(value) if expression.atom is None: return Program(expression) else: From 8963e83e3bb080de2f45e822611df8da61502fde Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Mon, 25 Apr 2022 13:28:28 -0700 Subject: [PATCH 18/43] More linting --- chia/rpc/wallet_rpc_api.py | 1 - chia/wallet/outer_puzzles.py | 1 - chia/wallet/puzzle_drivers.py | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index 27099de2ec36..567d87016bec 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -31,7 +31,6 @@ from chia.wallet.puzzle_drivers import PuzzleInfo from chia.wallet.rl_wallet.rl_wallet import RLWallet from chia.wallet.derive_keys import master_sk_to_farmer_sk, master_sk_to_pool_sk, master_sk_to_wallet_sk -from chia.wallet.did_wallet.did_wallet import DIDWallet from chia.wallet.nft_wallet.nft_wallet import NFTWallet from chia.wallet.nft_wallet.nft_puzzles import get_uri_list_from_puzzle from chia.wallet.trade_record import TradeRecord diff --git a/chia/wallet/outer_puzzles.py b/chia/wallet/outer_puzzles.py index 3c399d6b1cbe..8e4e5bbcab25 100644 --- a/chia/wallet/outer_puzzles.py +++ b/chia/wallet/outer_puzzles.py @@ -6,7 +6,6 @@ from chia.wallet.cat_wallet.cat_outer_puzzle import CATOuterPuzzle from chia.wallet.puzzle_drivers import PuzzleInfo, Solver - """ This file provides a central location for acquiring drivers for outer puzzles like CATs, NFTs, etc. diff --git a/chia/wallet/puzzle_drivers.py b/chia/wallet/puzzle_drivers.py index 3f25f453b9ff..55fad2fcd812 100644 --- a/chia/wallet/puzzle_drivers.py +++ b/chia/wallet/puzzle_drivers.py @@ -58,7 +58,7 @@ def decode_info_value(cls: Any, value: Any) -> Any: elif isinstance(value, list): return [decode_info_value(cls, v) for v in value] else: - expression: SExp = assemble(value) + expression: SExp = assemble(value) # type: ignore if expression.atom is None: return Program(expression) else: From ac8bf768f4aa9cae0e7aeb3896f5ae2190b7fddb Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Mon, 25 Apr 2022 14:42:47 -0700 Subject: [PATCH 19/43] Include drivers in offer summary --- chia/cmds/wallet_funcs.py | 4 ++-- chia/rpc/wallet_rpc_api.py | 4 ++-- chia/wallet/trade_record.py | 3 ++- chia/wallet/trading/offer.py | 14 +++++++++----- tests/wallet/cat_wallet/test_offer_lifecycle.py | 7 ++++++- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/chia/cmds/wallet_funcs.py b/chia/cmds/wallet_funcs.py index 5f5effbadf20..e45674c96f8c 100644 --- a/chia/cmds/wallet_funcs.py +++ b/chia/cmds/wallet_funcs.py @@ -354,7 +354,7 @@ async def print_trade_record(record, wallet_client: WalletRpcClient, summaries: if summaries: print("Summary:") offer = Offer.from_bytes(record.offer) - offered, requested = offer.summary() + offered, requested, _ = offer.summary() outbound_balances: Dict[str, int] = offer.get_pending_amounts() fees: Decimal = Decimal(offer.bundle.fees()) cat_name_resolver = wallet_client.cat_asset_id_to_name @@ -431,7 +431,7 @@ async def take_offer(args: dict, wallet_client: WalletRpcClient, fingerprint: in print("Please enter a valid offer file or hex blob") return - offered, requested = offer.summary() + offered, requested, _ = offer.summary() cat_name_resolver = wallet_client.cat_asset_id_to_name print("Summary:") print(" OFFERED:") diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index 567d87016bec..af6b03fea785 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -998,9 +998,9 @@ async def get_offer_summary(self, request): assert self.service.wallet_state_manager is not None offer_hex: str = request["offer"] offer = Offer.from_bech32(offer_hex) - offered, requested = offer.summary() + offered, requested, infos = offer.summary() - return {"summary": {"offered": offered, "requested": requested, "fees": offer.bundle.fees()}} + return {"summary": {"offered": offered, "requested": requested, "fees": offer.bundle.fees(), "infos": infos}} async def check_offer_validity(self, request): assert self.service.wallet_state_manager is not None diff --git a/chia/wallet/trade_record.py b/chia/wallet/trade_record.py index 08c56bb4bb60..12e9cdeb4e0d 100644 --- a/chia/wallet/trade_record.py +++ b/chia/wallet/trade_record.py @@ -33,10 +33,11 @@ def to_json_dict_convenience(self) -> Dict[str, Any]: formatted["status"] = TradeStatus(self.status).name offer_to_summarize: bytes = self.offer if self.taken_offer is None else self.taken_offer offer = Offer.from_bytes(offer_to_summarize) - offered, requested = offer.summary() + offered, requested, infos = offer.summary() formatted["summary"] = { "offered": offered, "requested": requested, + "infos": infos, "fees": offer.bundle.fees(), } formatted["pending"] = offer.get_pending_amounts() diff --git a/chia/wallet/trading/offer.py b/chia/wallet/trading/offer.py index d21e35c11c46..cf1def854ee7 100644 --- a/chia/wallet/trading/offer.py +++ b/chia/wallet/trading/offer.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import List, Optional, Dict, Set, Tuple +from typing import Any, List, Optional, Dict, Set, Tuple from blspy import G2Element from chia.types.blockchain_format.sized_bytes import bytes32 @@ -165,12 +165,12 @@ def arbitrage(self) -> Dict[Optional[bytes32], int]: return arbitrage_dict # This is a method mostly for the UI that creates a JSON summary of the offer - def summary(self) -> Tuple[Dict[str, int], Dict[str, int]]: + def summary(self) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any]]: offered_amounts: Dict[Optional[bytes32], int] = self.get_offered_amounts() requested_amounts: Dict[Optional[bytes32], int] = self.get_requested_amounts() - def keys_to_strings(dic: Dict[Optional[bytes32], int]) -> Dict[str, int]: - new_dic: Dict[str, int] = {} + def keys_to_strings(dic: Dict[Optional[bytes32], Any]) -> Dict[str, Any]: + new_dic: Dict[str, Any] = {} for key in dic: if key is None: new_dic["xch"] = dic[key] @@ -178,7 +178,11 @@ def keys_to_strings(dic: Dict[Optional[bytes32], int]) -> Dict[str, int]: new_dic[key.hex()] = dic[key] return new_dic - return keys_to_strings(offered_amounts), keys_to_strings(requested_amounts) + driver_dict: Dict[Optional[bytes32], Any] = {} + for key, value in self.driver_dict.items(): + driver_dict[key] = value.info + + return keys_to_strings(offered_amounts), keys_to_strings(requested_amounts), keys_to_strings(driver_dict) # Also mostly for the UI, returns a dictionary of assets and how much of them is pended for this offer # This method is also imperfect for sufficiently complex spends diff --git a/tests/wallet/cat_wallet/test_offer_lifecycle.py b/tests/wallet/cat_wallet/test_offer_lifecycle.py index 9ab601d08b18..e4855afa3ea4 100644 --- a/tests/wallet/cat_wallet/test_offer_lifecycle.py +++ b/tests/wallet/cat_wallet/test_offer_lifecycle.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional, List +from typing import Any, Dict, Optional, List import pytest from blspy import G2Element @@ -189,6 +189,10 @@ async def test_complex_offer(self, setup_sim): ), } + driver_dict_as_infos: Dict[bytes32, Any] = {} + for key, value in driver_dict.items(): + driver_dict_as_infos[key.hex()] = value.info + # Create an XCH Offer for RED chia_requested_payments: Dict[Optional[bytes32], List[Payment]] = { str_to_tail_hash("red"): [ @@ -262,6 +266,7 @@ async def test_complex_offer(self, setup_sim): str_to_tail_hash("blue").hex(): 2000, }, {"xch": 900, str_to_tail_hash("red").hex(): 350}, + driver_dict_as_infos, ) assert new_offer.get_pending_amounts() == { "xch": 1200, From 5c97b1cfc21a489c2cadb5d206cf96fe68417e63 Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Mon, 25 Apr 2022 15:07:37 -0700 Subject: [PATCH 20/43] Better autodetection of drivers on offer creation --- chia/wallet/cat_wallet/cat_wallet.py | 2 +- chia/wallet/trade_manager.py | 45 +++++++++++++--------------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/chia/wallet/cat_wallet/cat_wallet.py b/chia/wallet/cat_wallet/cat_wallet.py index cf5acd8eabe8..84a9602debf9 100644 --- a/chia/wallet/cat_wallet/cat_wallet.py +++ b/chia/wallet/cat_wallet/cat_wallet.py @@ -833,7 +833,7 @@ def match_puzzle_info(self, puzzle_driver: PuzzleInfo) -> bool: and puzzle_driver.also() is None ) - def get_puzzle_info(self) -> PuzzleInfo: + def get_puzzle_info(self, asset_id: bytes32) -> PuzzleInfo: return PuzzleInfo({"type": AssetType.CAT.value, "tail": "0x" + self.get_asset_id()}) async def get_coins_to_offer(self, asset_id: Optional[bytes32], amount: uint64) -> Set[Coin]: diff --git a/chia/wallet/trade_manager.py b/chia/wallet/trade_manager.py index 9cfededb363a..7afe3a8f61a0 100644 --- a/chia/wallet/trade_manager.py +++ b/chia/wallet/trade_manager.py @@ -48,10 +48,10 @@ class TradeManager: - To be able to be traded, a wallet must implement these methods on itself: - generate_signed_transaction(...) -> List[TransactionRecord] (See cat_wallet.py for full API) - convert_puzzle_hash(puzzle_hash: bytes32) -> bytes32 # Converts a puzzlehash from outer to inner puzzle + - get_puzzle_info(asset_id: bytes32) -> PuzzleInfo - get_coins_to_offer(asset_id: bytes32, amount: uint64) -> Set[Coin] - If you would like assets from your wallet to be referenced with just a wallet ID, you must also implement: - get_asset_id() -> bytes32 - - get_puzzle_info() -> PuzzleInfo - Finally, you must make sure that your wallet will respond appropriately when these WSM methods are called: - get_wallet_for_puzzle_info(puzzle_info: PuzzleInfo) -> - create_wallet_for_puzzle_info(puzzle_info: PuzzleInfo) -> @@ -328,46 +328,29 @@ async def _create_offer_for_ids( wallet = self.wallet_state_manager.wallets[wallet_id] p2_ph: bytes32 = await wallet.get_new_puzzlehash() if wallet.type() == WalletType.STANDARD_WALLET: - key: Optional[bytes32] = None + asset_id: Optional[bytes32] = None memos: List[bytes] = [] - elif callable(getattr(wallet, "get_asset_id", None)) and callable( # ATTENTION: new wallets - getattr(wallet, "get_puzzle_info", None) - ): - key = bytes32(bytes.fromhex(wallet.get_asset_id())) - puzzle_driver: PuzzleInfo = wallet.get_puzzle_info() + elif callable(getattr(wallet, "get_asset_id", None)): # ATTENTION: new wallets + asset_id = bytes32(bytes.fromhex(wallet.get_asset_id())) memos = [p2_ph] - if key in driver_dict and driver_dict[key] != puzzle_driver: - raise ValueError( - f"driver_dict specified {driver_dict[key]}," f" was expecting {puzzle_driver}" - ) - else: - driver_dict[key] = puzzle_driver else: raise ValueError( f"Cannot request assets from wallet id {wallet.id()} without more information" ) else: p2_ph = await self.wallet_state_manager.main_wallet.get_new_puzzlehash() - key = id + asset_id = id + wallet = await self.wallet_state_manager.get_wallet_for_asset_id(asset_id) memos = [p2_ph] - requested_payments[key] = [Payment(p2_ph, uint64(amount), memos)] + requested_payments[asset_id] = [Payment(p2_ph, uint64(amount), memos)] elif amount < 0: if isinstance(id, int): wallet_id = uint32(id) wallet = self.wallet_state_manager.wallets[wallet_id] if wallet.type() == WalletType.STANDARD_WALLET: asset_id = None - elif callable(getattr(wallet, "get_asset_id", None)) and callable( # ATTENTION: new wallets - getattr(wallet, "get_puzzle_info", None) - ): + elif callable(getattr(wallet, "get_asset_id", None)): # ATTENTION: new wallets asset_id = bytes32(bytes.fromhex(wallet.get_asset_id())) - puzzle_driver = wallet.get_puzzle_info() - if asset_id in driver_dict and driver_dict[asset_id] != puzzle_driver: - raise ValueError( - f"driver_dict specified {driver_dict[asset_id]}," f" was expecting {puzzle_driver}" - ) - else: - driver_dict[asset_id] = puzzle_driver else: raise ValueError( f"Cannot offer assets from wallet id {wallet.id()} without more information" @@ -382,6 +365,18 @@ async def _create_offer_for_ids( elif amount == 0: raise ValueError("You cannot offer nor request 0 amount of something") + if asset_id is not None and wallet is not None: + if callable(getattr(wallet, "get_puzzle_info", None)): + puzzle_driver: PuzzleInfo = wallet.get_puzzle_info(asset_id) + if asset_id in driver_dict and driver_dict[asset_id] != puzzle_driver: + raise ValueError( + f"driver_dict specified {driver_dict[asset_id]}," f" was expecting {puzzle_driver}" + ) + else: + driver_dict[asset_id] = puzzle_driver + else: + raise ValueError(f"Wallet for asset id {asset_id} is not properly integrated with TradeManager") + all_coins: List[Coin] = [c for coins in coins_to_offer.values() for c in coins] notarized_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments( requested_payments, all_coins From 53b678967c2ea46b9576c17bef678c0da0640c6b Mon Sep 17 00:00:00 2001 From: Matt Hauff Date: Tue, 26 Apr 2022 07:44:34 -0700 Subject: [PATCH 21/43] Forgot to update rpc test --- tests/wallet/rpc/test_wallet_rpc.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/wallet/rpc/test_wallet_rpc.py b/tests/wallet/rpc/test_wallet_rpc.py index 46736e799bad..cfbfe809a523 100644 --- a/tests/wallet/rpc/test_wallet_rpc.py +++ b/tests/wallet/rpc/test_wallet_rpc.py @@ -5,7 +5,7 @@ import logging from operator import attrgetter -from typing import Dict, Optional +from typing import Any, Dict, Optional import pytest from blspy import G2Element @@ -507,14 +507,16 @@ async def eventual_balance_det(c, wallet_id: str): assert len(all_offers) == 0 assert offer is None + driver_dict: Dict[str, Any] = {col.hex(): {"type": "CAT", "tail": "0x" + col.hex()}} + offer, trade_record = await client.create_offer_for_ids( {uint32(1): -5, col.hex(): 1}, - driver_dict={col.hex(): {"type": "CAT", "tail": "0x" + col.hex()}}, + driver_dict=driver_dict, fee=uint64(1), ) summary = await client.get_offer_summary(offer) - assert summary == {"offered": {"xch": 5}, "requested": {col.hex(): 1}, "fees": 1} + assert summary == {"offered": {"xch": 5}, "requested": {col.hex(): 1}, "infos": driver_dict, "fees": 1} assert await client.check_offer_validity(offer) From 89c5c6f4ef156a3e38f337484e798060a799fe86 Mon Sep 17 00:00:00 2001 From: matt-o-how Date: Tue, 26 Apr 2022 17:33:11 +0100 Subject: [PATCH 22/43] checkpoint --- .../puzzles/nft_metadata_updater.clvm.hex | 1 + chia/wallet/puzzles/nft_ownership_layer.clvm | 8 +++ chia/wallet/puzzles/nft_state_layer.clvm | 18 +++-- chia/wallet/puzzles/nft_state_layer.clvm.hex | 1 + .../p2_delegated_puzzle_or_hidden_puzzle.clvm | 20 +++--- tests/wallet/nft_wallet/test_nft_clvm.py | 71 +++++++++++++++++++ 6 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 chia/wallet/puzzles/nft_metadata_updater.clvm.hex create mode 100644 chia/wallet/puzzles/nft_state_layer.clvm.hex create mode 100644 tests/wallet/nft_wallet/test_nft_clvm.py diff --git a/chia/wallet/puzzles/nft_metadata_updater.clvm.hex b/chia/wallet/puzzles/nft_metadata_updater.clvm.hex new file mode 100644 index 000000000000..805fa47f8b58 --- /dev/null +++ b/chia/wallet/puzzles/nft_metadata_updater.clvm.hex @@ -0,0 +1 @@ +ff0880 \ No newline at end of file diff --git a/chia/wallet/puzzles/nft_ownership_layer.clvm b/chia/wallet/puzzles/nft_ownership_layer.clvm index 30f649889fc5..8a26630dc9d3 100644 --- a/chia/wallet/puzzles/nft_ownership_layer.clvm +++ b/chia/wallet/puzzles/nft_ownership_layer.clvm @@ -11,6 +11,14 @@ (defconstant MAGIC_NUMBER -10) + (defun sha256tree1 + (TREE) + (if (l TREE) + (sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE))) + (sha256 1 TREE) + ) + ) + (defun-ineline nft_ownership_layer_puzzle_hash (NFT_OWNERSHIP_LAYER_MOD_HASH new_owner TRANSFER_PROGRAM_HASH inner_puzzle_hash) (puzzle-hash-of-curried-function NFT_OWNERSHIP_LAYER_MOD_HASH (sha256 ONE inner_puzzle_hash) diff --git a/chia/wallet/puzzles/nft_state_layer.clvm b/chia/wallet/puzzles/nft_state_layer.clvm index a9eba6fa4ae4..ea8b1f996b0a 100644 --- a/chia/wallet/puzzles/nft_state_layer.clvm +++ b/chia/wallet/puzzles/nft_state_layer.clvm @@ -11,10 +11,18 @@ (include condition_codes.clvm) (include curry-and-treehash.clinc) + (defun sha256tree1 + (TREE) + (if (l TREE) + (sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE))) + (sha256 1 TREE) + ) + ) + (defun-inline nft_state_layer_puzzle_hash (NFT_STATE_LAYER_MOD_HASH METADATA METADATA_UPDATER_PUZZLE_HASH inner_puzzle_hash) (puzzle-hash-of-curried-function NFT_STATE_LAYER_MOD_HASH - (sha256 ONE inner_puzzle_hash) - (sha256 ONE METADATA_UPDATER_PUZZLE_HASHs) + inner_puzzle_hash + (sha256 ONE METADATA_UPDATER_PUZZLE_HASH) (sha256tree1 METADATA) (sha256 ONE NFT_STATE_LAYER_MOD_HASH) ) @@ -24,7 +32,7 @@ (if conditions (if (= (f (f conditions)) CREATE_COIN) (if (logand (f (r (r (f conditions)))) ONE) - (c (list CREATE_COIN (nft_state_layer_puzzle_hash NFT_STATE_LAYER_MOD_HASH METADATA METADATA_UPDATER_PUZZLE_HASH (f (r (f conditions)))) my_amount) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (list METADATA (r conditions)) my_amount)) + (c (list CREATE_COIN (nft_state_layer_puzzle_hash NFT_STATE_LAYER_MOD_HASH METADATA METADATA_UPDATER_PUZZLE_HASH (f (r (f conditions)))) my_amount) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (list METADATA (r conditions)) my_amount)) (c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (list METADATA (r conditions)) my_amount)) ) (c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (list METADATA (r conditions)) my_amount)) @@ -40,8 +48,8 @@ NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (if metadata_updater_reveal - (list (a metadata_updater_reveal solution) (list (list CREATE_COIN (sha256tree INNER_PUZZLE) my_amount))) - (list METADATA METADATA_UPDATER_PUZZLE_HASH (a INNER_PUZZLE solution)) + (list (a metadata_updater_reveal solution) (list (list CREATE_COIN (sha256tree1 INNER_PUZZLE) my_amount))) + (list METADATA (a INNER_PUZZLE solution)) ) my_amount ) diff --git a/chia/wallet/puzzles/nft_state_layer.clvm.hex b/chia/wallet/puzzles/nft_state_layer.clvm.hex new file mode 100644 index 000000000000..7b8d99a3151f --- /dev/null +++ b/chia/wallet/puzzles/nft_state_layer.clvm.hex @@ -0,0 +1 @@ +ff02ffff01ff04ffff04ff10ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ff17ffff04ffff02ffff03ff82017fffff01ff04ffff02ff82017fff5f80ffff04ffff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff2fff80808080ffff04ff81bfff80808080ff8080ff808080ffff01ff04ff0bffff04ffff02ff2fff5f80ff80808080ff0180ffff04ff81bfff8080808080808080ffff04ffff01ffffff4902ff33ff0401ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff2c80ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffff0bff2affff0bff3cff1880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff57ffff01ff02ffff03ffff09ff820117ff1480ffff01ff02ffff03ffff18ff820597ff3c80ffff01ff04ffff04ff14ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff820297ffff04ffff0bff3cff0b80ffff04ffff02ff2effff04ff02ffff04ff27ff80808080ffff04ffff0bff3cff0580ff8080808080808080ffff04ff2fff80808080ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff8080808080808080ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ff8080ff0180ff018080 \ No newline at end of file diff --git a/chia/wallet/puzzles/p2_delegated_puzzle_or_hidden_puzzle.clvm b/chia/wallet/puzzles/p2_delegated_puzzle_or_hidden_puzzle.clvm index aa85f3763e34..ba1bf691fca9 100644 --- a/chia/wallet/puzzles/p2_delegated_puzzle_or_hidden_puzzle.clvm +++ b/chia/wallet/puzzles/p2_delegated_puzzle_or_hidden_puzzle.clvm @@ -10,7 +10,7 @@ ; synthetic_key_offset: a private key cryptographically generated using the hidden ; puzzle and as inputs `original_public_key` ; -; synthetic_public_key: the public key that is the sum of `original_public_key` and the +; SYNTHETIC_PUBLIC_KEY: the public key that is the sum of `original_public_key` and the ; public key corresponding to `synthetic_key_offset` ; ; original_public_key: a public key, where knowledge of the corresponding private key @@ -23,17 +23,17 @@ (mod - ; A puzzle should commit to `synthetic_public_key` + ; A puzzle should commit to `SYNTHETIC_PUBLIC_KEY` ; ; The solution should pass in 0 for `original_public_key` if it wants to use ; an arbitrary `delegated_puzzle` (and `solution`) signed by the - ; `synthetic_public_key` (whose corresponding private key can be calculated + ; `SYNTHETIC_PUBLIC_KEY` (whose corresponding private key can be calculated ; if you know the private key for `original_public_key`) ; ; Or you can solve the hidden puzzle by revealing the `original_public_key`, ; the hidden puzzle in `delegated_puzzle`, and a solution to the hidden puzzle. - (synthetic_public_key original_public_key delegated_puzzle solution) + (SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle solution) ; "assert" is a macro that wraps repeated instances of "if" ; usage: (assert A0 A1 ... An R) @@ -61,9 +61,9 @@ ; "is_hidden_puzzle_correct" returns true iff the hidden puzzle is correctly encoded - (defun-inline is_hidden_puzzle_correct (synthetic_public_key original_public_key delegated_puzzle) + (defun-inline is_hidden_puzzle_correct (SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle) (= - synthetic_public_key + SYNTHETIC_PUBLIC_KEY (point_add original_public_key (pubkey_for_exp (sha256 original_public_key (sha256tree1 delegated_puzzle))) @@ -73,19 +73,19 @@ ; "possibly_prepend_aggsig" is the main entry point - (defun-inline possibly_prepend_aggsig (synthetic_public_key original_public_key delegated_puzzle conditions) + (defun-inline possibly_prepend_aggsig (SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle conditions) (if original_public_key (assert - (is_hidden_puzzle_correct synthetic_public_key original_public_key delegated_puzzle) + (is_hidden_puzzle_correct SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle) conditions ) - (c (list AGG_SIG_ME synthetic_public_key (sha256tree1 delegated_puzzle)) conditions) + (c (list AGG_SIG_ME SYNTHETIC_PUBLIC_KEY (sha256tree1 delegated_puzzle)) conditions) ) ) ; main entry point (possibly_prepend_aggsig - synthetic_public_key original_public_key delegated_puzzle + SYNTHETIC_PUBLIC_KEY original_public_key delegated_puzzle (a delegated_puzzle solution)) ) diff --git a/tests/wallet/nft_wallet/test_nft_clvm.py b/tests/wallet/nft_wallet/test_nft_clvm.py new file mode 100644 index 000000000000..229fb0bddfdf --- /dev/null +++ b/tests/wallet/nft_wallet/test_nft_clvm.py @@ -0,0 +1,71 @@ +from blspy import G1Element +from chia.types.announcement import Announcement +from chia.types.blockchain_format.coin import Coin +from chia.types.blockchain_format.program import INFINITE_COST, Program +from chia.types.blockchain_format.sized_bytes import bytes32 +from chia.util.ints import uint64 +from chia.wallet.puzzles.cat_loader import CAT_MOD +from chia.wallet.puzzles.load_clvm import load_clvm +from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import ( + DEFAULT_HIDDEN_PUZZLE_HASH, + calculate_synthetic_secret_key, + puzzle_for_pk, + solution_for_conditions, +) +from tests.core.make_block_generator import int_to_public_key +from chia.wallet.puzzles.puzzle_utils import ( + make_assert_coin_announcement, + make_assert_puzzle_announcement, + make_assert_my_coin_id_condition, + make_assert_absolute_seconds_exceeds_condition, + make_create_coin_announcement, + make_create_puzzle_announcement, + make_create_coin_condition, + make_reserve_fee_condition, +) + +SINGLETON_MOD = load_clvm("singleton_top_layer.clvm") +LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm") +DID_MOD = load_clvm("did_innerpuz.clvm") +NFT_STATE_LAYER_MOD = load_clvm("nft_state_layer.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() + +LAUNCHER_ID = Program.to(b"launcher-id").get_tree_hash() +NFT_METADATA_UPDATER = load_clvm("nft_metadata_updater.clvm") + + +def test_new_nft_ownership_layer() -> None: + destination: bytes32 = Program.to("test").get_tree_hash() + SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (destination, LAUNCHER_PUZZLE_HASH))) + + pubkey = int_to_public_key(1) + innerpuz = puzzle_for_pk(pubkey) + my_amount = 1 + condition_list = [make_create_coin_condition(destination, my_amount, [])] + metadata = [ + ("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), + ("h", 0xD4584AD463139FA8C0D9F68F4B59F185), + ] + solution = Program.to( + [ + NFT_STATE_LAYER_MOD_HASH, + metadata, + NFT_METADATA_UPDATER.get_tree_hash(), + innerpuz, + # below here is the solution + solution_for_conditions(condition_list), + my_amount, + 0, + ] + ) + + cost, res = NFT_STATE_LAYER_MOD.run_with_cost(INFINITE_COST, solution) + assert res.first().first().as_int() == 73 + assert res.first().rest().first().as_int() == 1 + assert res.rest().rest().first().first().as_int() == 51 + + + assert res.rest().rest().first().rest().first().as_atom() == NFT_STATE_LAYER_MOD.curry(NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER.get_tree_hash(), destination).get_tree_hash() From a5ba0db6415ab08af3968f757606a0cfa68502af Mon Sep 17 00:00:00 2001 From: matt-o-how Date: Wed, 27 Apr 2022 14:43:23 +0100 Subject: [PATCH 23/43] fix test and optimise state_layer puz --- chia/wallet/puzzles/nft_state_layer.clvm | 2 +- chia/wallet/puzzles/nft_state_layer.clvm.hex | 2 +- tests/wallet/nft_wallet/test_nft_clvm.py | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/chia/wallet/puzzles/nft_state_layer.clvm b/chia/wallet/puzzles/nft_state_layer.clvm index ea8b1f996b0a..c74c177715a6 100644 --- a/chia/wallet/puzzles/nft_state_layer.clvm +++ b/chia/wallet/puzzles/nft_state_layer.clvm @@ -32,7 +32,7 @@ (if conditions (if (= (f (f conditions)) CREATE_COIN) (if (logand (f (r (r (f conditions)))) ONE) - (c (list CREATE_COIN (nft_state_layer_puzzle_hash NFT_STATE_LAYER_MOD_HASH METADATA METADATA_UPDATER_PUZZLE_HASH (f (r (f conditions)))) my_amount) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (list METADATA (r conditions)) my_amount)) + (c (list CREATE_COIN (nft_state_layer_puzzle_hash NFT_STATE_LAYER_MOD_HASH METADATA METADATA_UPDATER_PUZZLE_HASH (f (r (f conditions)))) my_amount) (r conditions)) (c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (list METADATA (r conditions)) my_amount)) ) (c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (list METADATA (r conditions)) my_amount)) diff --git a/chia/wallet/puzzles/nft_state_layer.clvm.hex b/chia/wallet/puzzles/nft_state_layer.clvm.hex index 7b8d99a3151f..c16158935491 100644 --- a/chia/wallet/puzzles/nft_state_layer.clvm.hex +++ b/chia/wallet/puzzles/nft_state_layer.clvm.hex @@ -1 +1 @@ -ff02ffff01ff04ffff04ff10ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ff17ffff04ffff02ffff03ff82017fffff01ff04ffff02ff82017fff5f80ffff04ffff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff2fff80808080ffff04ff81bfff80808080ff8080ff808080ffff01ff04ff0bffff04ffff02ff2fff5f80ff80808080ff0180ffff04ff81bfff8080808080808080ffff04ffff01ffffff4902ff33ff0401ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff2c80ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffff0bff2affff0bff3cff1880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff57ffff01ff02ffff03ffff09ff820117ff1480ffff01ff02ffff03ffff18ff820597ff3c80ffff01ff04ffff04ff14ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff820297ffff04ffff0bff3cff0b80ffff04ffff02ff2effff04ff02ffff04ff27ff80808080ffff04ffff0bff3cff0580ff8080808080808080ffff04ff2fff80808080ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff8080808080808080ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ff8080ff0180ff018080 \ No newline at end of file +ff02ffff01ff04ffff04ff10ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ff17ffff04ffff02ffff03ff82017fffff01ff04ffff02ff82017fff5f80ffff04ffff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff2fff80808080ffff04ff81bfff80808080ff8080ff808080ffff01ff04ff0bffff04ffff02ff2fff5f80ff80808080ff0180ffff04ff81bfff8080808080808080ffff04ffff01ffffff4902ff33ff0401ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff2c80ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffff0bff2affff0bff3cff1880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff57ffff01ff02ffff03ffff09ff820117ff1480ffff01ff02ffff03ffff18ff820597ff3c80ffff01ff04ffff04ff14ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff820297ffff04ffff0bff3cff0b80ffff04ffff02ff2effff04ff02ffff04ff27ff80808080ffff04ffff0bff3cff0580ff8080808080808080ffff04ff2fff80808080ff81d780ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ff8080ff0180ff018080 \ No newline at end of file diff --git a/tests/wallet/nft_wallet/test_nft_clvm.py b/tests/wallet/nft_wallet/test_nft_clvm.py index 229fb0bddfdf..a1b2b05ec14c 100644 --- a/tests/wallet/nft_wallet/test_nft_clvm.py +++ b/tests/wallet/nft_wallet/test_nft_clvm.py @@ -38,13 +38,11 @@ def test_new_nft_ownership_layer() -> None: - destination: bytes32 = Program.to("test").get_tree_hash() - SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (destination, LAUNCHER_PUZZLE_HASH))) - pubkey = int_to_public_key(1) innerpuz = puzzle_for_pk(pubkey) my_amount = 1 - condition_list = [make_create_coin_condition(destination, my_amount, [])] + destination: bytes32 = puzzle_for_pk(int_to_public_key(2)) + condition_list = [make_create_coin_condition(destination.get_tree_hash(), my_amount, [])] metadata = [ ("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), ("h", 0xD4584AD463139FA8C0D9F68F4B59F185), From 571e66749db923d405a42dc5b6155e94b5651420 Mon Sep 17 00:00:00 2001 From: matt-o-how Date: Thu, 28 Apr 2022 14:23:54 +0100 Subject: [PATCH 24/43] metadata checkpoint --- chia/wallet/puzzles/nft_metadata_updater.clvm | 18 +++++++++++-- chia/wallet/puzzles/nft_state_layer.clvm | 27 ++++++++++++++++--- chia/wallet/puzzles/nft_v1_innerpuz.clvm | 2 +- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/chia/wallet/puzzles/nft_metadata_updater.clvm b/chia/wallet/puzzles/nft_metadata_updater.clvm index 74b9aa6dc71d..80fbb4c219db 100644 --- a/chia/wallet/puzzles/nft_metadata_updater.clvm +++ b/chia/wallet/puzzles/nft_metadata_updater.clvm @@ -1,5 +1,19 @@ -(mod solution +(mod (CURRENT_METADATA conditions solution) + + ; once we find 'u' we don't need to continue looping + (defun add_url (METADATA new_url) + (if METADATA + (if (= (f (f METADATA)) 'u') + (c (c 'u' (c new_url (r (f METADATA)))) (r METADATA)) + (c (f METADATA) (add_url (r METADATA) new_url)) + ) + () + ) + ) ; main - (x) + (if solution + (add_url CURRENT_METADATA solution) + CURRENT_METADATA + ) ) diff --git a/chia/wallet/puzzles/nft_state_layer.clvm b/chia/wallet/puzzles/nft_state_layer.clvm index c74c177715a6..0cd5eacb12cf 100644 --- a/chia/wallet/puzzles/nft_state_layer.clvm +++ b/chia/wallet/puzzles/nft_state_layer.clvm @@ -5,7 +5,6 @@ INNER_PUZZLE solution ; either to inner puzzle or metadata updater my_amount - metadata_updater_reveal ) (include condition_codes.clvm) @@ -41,15 +40,35 @@ ) ) + (defun cons_to_second (item list_of_items) + (c (f list_of_items) (c item (f (r list_of_items)))) + ) + + (defun check_for_metadata_prog_reveal (METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA conditions_loop all_conditions) + (if conditions_loop + (if (= (f (f conditions_loop)) -24) + (if (= (sha256tree1 (f (r (f conditions_loop)))) METADATA_UPDATER_PUZZLE_HASH) + () + (x) + ) + (check_for_metadata_prog_reveal METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA (r conditions_loop) all_conditions) + ) + (list CURRENT_METADATA 0) + ) + ) + + (defun metadata_updater_loader (CURRENT_METADATA conditions) + (check_for_metadata_prog_reveal CURRENT_METADATA conditions conditions) + ) + ; main (c (list ASSERT_MY_AMOUNT my_amount) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH - (if metadata_updater_reveal - (list (a metadata_updater_reveal solution) (list (list CREATE_COIN (sha256tree1 INNER_PUZZLE) my_amount))) - (list METADATA (a INNER_PUZZLE solution)) + (metadata_updater_loader + METADATA_UPDATER_PUZZLE_HASH METADATA (a INNER_PUZZLE solution) ) my_amount ) diff --git a/chia/wallet/puzzles/nft_v1_innerpuz.clvm b/chia/wallet/puzzles/nft_v1_innerpuz.clvm index 47ad115824ec..73c5453bbe24 100644 --- a/chia/wallet/puzzles/nft_v1_innerpuz.clvm +++ b/chia/wallet/puzzles/nft_v1_innerpuz.clvm @@ -1,4 +1,4 @@ -(mod ARGS +(mod (CURRENT_PUBKEY) ; main ) From 6573ac7785fa492e6005815d8c9e2efed14126ed Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 28 Apr 2022 16:15:26 +0100 Subject: [PATCH 25/43] fix state layer for metadata updater --- chia/wallet/puzzles/nft_metadata_updater.clvm | 7 +++--- .../puzzles/nft_metadata_updater.clvm.hex | 2 +- chia/wallet/puzzles/nft_state_layer.clvm | 22 +++++++++++++------ chia/wallet/puzzles/nft_state_layer.clvm.hex | 2 +- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/chia/wallet/puzzles/nft_metadata_updater.clvm b/chia/wallet/puzzles/nft_metadata_updater.clvm index 80fbb4c219db..91d5f56eae10 100644 --- a/chia/wallet/puzzles/nft_metadata_updater.clvm +++ b/chia/wallet/puzzles/nft_metadata_updater.clvm @@ -1,4 +1,4 @@ -(mod (CURRENT_METADATA conditions solution) +(mod (CURRENT_METADATA solution) ; once we find 'u' we don't need to continue looping (defun add_url (METADATA new_url) @@ -12,8 +12,9 @@ ) ; main + ; returns (new_metadata conditions) (if solution - (add_url CURRENT_METADATA solution) - CURRENT_METADATA + (list (add_url CURRENT_METADATA solution) 0) + (list CURRENT_METADATA 0) ) ) diff --git a/chia/wallet/puzzles/nft_metadata_updater.clvm.hex b/chia/wallet/puzzles/nft_metadata_updater.clvm.hex index 805fa47f8b58..5ffa0b747a81 100644 --- a/chia/wallet/puzzles/nft_metadata_updater.clvm.hex +++ b/chia/wallet/puzzles/nft_metadata_updater.clvm.hex @@ -1 +1 @@ -ff0880 \ No newline at end of file +ff02ffff01ff02ffff03ff0bffff01ff04ffff02ff02ffff04ff02ffff04ff05ffff04ff0bff8080808080ffff01ff808080ffff01ff04ff05ffff01ff80808080ff0180ffff04ffff01ff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff02ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff018080 \ No newline at end of file diff --git a/chia/wallet/puzzles/nft_state_layer.clvm b/chia/wallet/puzzles/nft_state_layer.clvm index 0cd5eacb12cf..311816bbda24 100644 --- a/chia/wallet/puzzles/nft_state_layer.clvm +++ b/chia/wallet/puzzles/nft_state_layer.clvm @@ -40,25 +40,33 @@ ) ) - (defun cons_to_second (item list_of_items) - (c (f list_of_items) (c item (f (r list_of_items)))) + ; take two lists and merge them into one + (defun merge_list (list_a list_b) + (if list_a + (c (f list_a) (merge_list (r list_a) list_b)) + list_b + ) ) - (defun check_for_metadata_prog_reveal (METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA conditions_loop all_conditions) + (defun check_for_metadata_prog_reveal (METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA conditions_loop) (if conditions_loop (if (= (f (f conditions_loop)) -24) (if (= (sha256tree1 (f (r (f conditions_loop)))) METADATA_UPDATER_PUZZLE_HASH) - () + (a (f (r (f conditions_loop))) (list CURRENT_METADATA (f (r (r (f conditions_loop)))))) (x) ) - (check_for_metadata_prog_reveal METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA (r conditions_loop) all_conditions) + (check_for_metadata_prog_reveal METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA (r conditions_loop)) ) (list CURRENT_METADATA 0) ) ) - (defun metadata_updater_loader (CURRENT_METADATA conditions) - (check_for_metadata_prog_reveal CURRENT_METADATA conditions conditions) + (defun process_metadata_updater (metadata_result conditions) + (c (f metadata_result) (list (merge_list (f (r metadata_result)) conditions))) + ) + + (defun metadata_updater_loader (METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA conditions) + (process_metadata_updater (check_for_metadata_prog_reveal METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA conditions) conditions) ) ; main diff --git a/chia/wallet/puzzles/nft_state_layer.clvm.hex b/chia/wallet/puzzles/nft_state_layer.clvm.hex index c16158935491..40f217bedf46 100644 --- a/chia/wallet/puzzles/nft_state_layer.clvm.hex +++ b/chia/wallet/puzzles/nft_state_layer.clvm.hex @@ -1 +1 @@ -ff02ffff01ff04ffff04ff10ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ff17ffff04ffff02ffff03ff82017fffff01ff04ffff02ff82017fff5f80ffff04ffff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff2fff80808080ffff04ff81bfff80808080ff8080ff808080ffff01ff04ff0bffff04ffff02ff2fff5f80ff80808080ff0180ffff04ff81bfff8080808080808080ffff04ffff01ffffff4902ff33ff0401ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff2c80ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffff0bff2affff0bff3cff1880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff57ffff01ff02ffff03ffff09ff820117ff1480ffff01ff02ffff03ffff18ff820597ff3c80ffff01ff04ffff04ff14ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff820297ffff04ffff0bff3cff0b80ffff04ffff02ff2effff04ff02ffff04ff27ff80808080ffff04ffff0bff3cff0580ff8080808080808080ffff04ff2fff80808080ff81d780ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ff8080ff0180ff018080 \ No newline at end of file +ff02ffff01ff04ffff04ff10ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ff17ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfff8080808080808080ffff04ffff01ffffff49ff0233ffff0401ff0102ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ffff0181e880ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff820167ff80808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ff0bffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff57ffff01ff02ffff03ffff09ff820117ff3880ffff01ff02ffff03ffff18ff820597ff3480ffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff820297ffff04ffff0bff34ff0b80ffff04ffff02ff2effff04ff02ffff04ff27ff80808080ffff04ffff0bff34ff0580ff8080808080808080ffff04ff2fff80808080ff81d780ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ff8080ff0180ff018080 \ No newline at end of file From 8363322558feedc5d732656942731a5a0287aba5 Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 28 Apr 2022 16:32:57 +0100 Subject: [PATCH 26/43] add test for metadata updating --- tests/wallet/nft_wallet/test_nft_clvm.py | 34 ++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/wallet/nft_wallet/test_nft_clvm.py b/tests/wallet/nft_wallet/test_nft_clvm.py index a1b2b05ec14c..04300473e9b7 100644 --- a/tests/wallet/nft_wallet/test_nft_clvm.py +++ b/tests/wallet/nft_wallet/test_nft_clvm.py @@ -64,6 +64,40 @@ def test_new_nft_ownership_layer() -> None: assert res.first().first().as_int() == 73 assert res.first().rest().first().as_int() == 1 assert res.rest().rest().first().first().as_int() == 51 + assert res.rest().rest().first().rest().first().as_atom() == NFT_STATE_LAYER_MOD.curry(NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER.get_tree_hash(), destination).get_tree_hash() +def test_update_metadata() -> None: + pubkey = int_to_public_key(1) + innerpuz = puzzle_for_pk(pubkey) + my_amount = 1 + destination: bytes32 = puzzle_for_pk(int_to_public_key(2)) + condition_list = [make_create_coin_condition(destination.get_tree_hash(), my_amount, [])] + condition_list.append([-24, NFT_METADATA_UPDATER, "https://www.chia.net/img/branding/chia-logo-2.svg"]) + metadata = [ + ("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), + ("h", 0xD4584AD463139FA8C0D9F68F4B59F185), + ] + solution = Program.to( + [ + NFT_STATE_LAYER_MOD_HASH, + metadata, + NFT_METADATA_UPDATER.get_tree_hash(), + innerpuz, + # below here is the solution + solution_for_conditions(condition_list), + my_amount, + 0, + ] + ) + + metadata = [ + ("u", ["https://www.chia.net/img/branding/chia-logo-2.svg", "https://www.chia.net/img/branding/chia-logo.svg"]), + ("h", 0xD4584AD463139FA8C0D9F68F4B59F185), + ] + + cost, res = NFT_STATE_LAYER_MOD.run_with_cost(INFINITE_COST, solution) + assert res.first().first().as_int() == 73 + assert res.first().rest().first().as_int() == 1 + assert res.rest().rest().first().first().as_int() == 51 assert res.rest().rest().first().rest().first().as_atom() == NFT_STATE_LAYER_MOD.curry(NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER.get_tree_hash(), destination).get_tree_hash() From b1fa967f9b8d225a99687b6b42cc0b30b4537319 Mon Sep 17 00:00:00 2001 From: Sebastjan Date: Mon, 2 May 2022 19:08:51 +0200 Subject: [PATCH 27/43] almost generates a new nft --- chia/rpc/wallet_rpc_api.py | 76 +-- chia/wallet/nft_wallet/nft_puzzles.py | 228 ++------ chia/wallet/nft_wallet/nft_wallet.py | 557 +++++++------------ chia/wallet/puzzles/nft_state_layer.clvm | 6 +- chia/wallet/puzzles/nft_state_layer.clvm.hex | 2 +- chia/wallet/wallet.py | 6 +- chia/wallet/wallet_puzzle_store.py | 1 + chia/wallet/wallet_state_manager.py | 17 +- tests/wallet/nft_wallet/test_nft_clvm.py | 44 +- 9 files changed, 293 insertions(+), 644 deletions(-) diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index af6b03fea785..1771f6363471 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -124,8 +124,7 @@ def get_routes(self) -> Dict[str, Callable]: # NFT Wallet "/nft_mint_nft": self.nft_mint_nft, "/nft_get_current_nfts": self.nft_get_current_nfts, - "/nft_transfer_nft": self.nft_transfer_nft, - "/nft_receive_nft": self.nft_receive_nft, + # "/nft_transfer_nft": self.nft_transfer_nft, # RL wallet "/rl_set_user_info": self.rl_set_user_info, "/send_clawback_transaction:": self.send_clawback_transaction, @@ -1294,12 +1293,8 @@ async def did_transfer_did(self, request): ########################################################################################## async def nft_mint_nft(self, request): - wallet_id = int(request["wallet_id"]) + wallet_id = uint32(request["wallet_id"]) nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id] - # uri: str, - # percentage: uint64, - # backpayment_address: bytes32, - # amount: int = 1 address = request["artist_address"] if isinstance(address, str): address = decode_puzzle_hash(address) @@ -1309,12 +1304,11 @@ async def nft_mint_nft(self, request): ("h", request["hash"]), ] ) - await nft_wallet.generate_new_nft(metadata, request["artist_percentage"], address) return {"wallet_id": wallet_id, "success": True} async def nft_get_current_nfts(self, request): - wallet_id = int(request["wallet_id"]) + wallet_id = uint32(request["wallet_id"]) nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id] nfts = nft_wallet.get_current_nfts() nft_uri_pairs = [] @@ -1323,70 +1317,8 @@ async def nft_get_current_nfts(self, request): nft_uri_pairs.append((nft, uri)) return {"wallet_id": wallet_id, "success": True, "nfts": nft_uri_pairs} - async def nft_transfer_nft(self, request): - wallet_id = int(request["wallet_id"]) - nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id] - # nft_coin_info: NFTCoinInfo, - # new_did, - # new_did_inner_hash, - # trade_prices_list, - # new_url=0, - new_url = 0 - if "new_url" in request: - new_url = request["new_url"] - new_did_inner_hash = 0 - if "new_did_inner_hash" in request: - new_did_inner_hash = request["new_did_inner_hash"] - else: - assert request["trade_price"] == 0 - if isinstance(request["new_did"], str): - new_did = bytes.fromhex(request["new_did"]) - else: - new_did = request["new_did"] - sb = await nft_wallet.transfer_nft( - request["nft_coin_info"], - new_did, - new_did_inner_hash, - request["trade_price"], - new_url, - ) - return {"wallet_id": wallet_id, "success": True, "spend_bundle": sb} - - async def nft_receive_nft(self, request): - wallet_id = int(request["wallet_id"]) - nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id] - if isinstance(request["spend_bundle"], str): - sending_sb = SpendBundle.from_bytes(bytes.fromhex(request["spend_bundle"])) - else: - sending_sb = request["spend_bundle"] - - if "fee" in request: - fee = request["fee"] - else: - fee = 0 - sb = await nft_wallet.receive_nft(sending_sb, fee) - return {"wallet_id": wallet_id, "success": True, "spend_bundle": sb} - async def nft_add_url(self, request): - wallet_id = int(request["wallet_id"]) - nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id] - my_did = nft_wallet.nft_wallet_info.my_did - did_wallet = self.service.wallet_state_manager.wallets[nft_wallet.nft_wallet_info.did_wallet_id] - new_did_inner_hash = did_wallet.did_info.current_inner.get_tree_hash() - new_url = request["new_url"] - # nft_coin_info: NFTCoinInfo, - # new_did, - # new_did_inner_hash, - # trade_prices_list, - # new_url=0, - sb = await nft_wallet.transfer_nft( - request["nft_coin_info"], - my_did, - new_did_inner_hash, - 0, - new_url, - ) - return {"wallet_id": wallet_id, "success": True, "spend_bundle": sb} + raise NotImplementedError ########################################################################################## # Rate Limited Wallet diff --git a/chia/wallet/nft_wallet/nft_puzzles.py b/chia/wallet/nft_wallet/nft_puzzles.py index 3bc062b45d73..209a9f276448 100644 --- a/chia/wallet/nft_wallet/nft_puzzles.py +++ b/chia/wallet/nft_wallet/nft_puzzles.py @@ -1,120 +1,72 @@ -from typing import Iterator, List, Optional, Tuple +import logging +from typing import List, Optional, Tuple from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.sized_bytes import bytes32 -from chia.util.ints import uint64 -from chia.wallet.puzzles.cat_loader import CAT_MOD from chia.wallet.puzzles.load_clvm import load_clvm +log = logging.getLogger(__name__) SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_v1_1.clvm") LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm") DID_MOD = load_clvm("did_innerpuz.clvm") NFT_MOD = load_clvm("nft_innerpuz.clvm") +NFT_STATE_LAYER_MOD = load_clvm("nft_state_layer.clvm") LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash() SINGLETON_MOD_HASH = SINGLETON_TOP_LAYER_MOD.get_tree_hash() NFT_MOD_HASH = NFT_MOD.get_tree_hash() +NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash() NFT_TRANSFER_PROGRAM = load_clvm("nft_transfer_program.clvm") OFFER_MOD = load_clvm("settlement_payments.clvm") -def create_nft_layer_puzzle( - singleton_id: bytes32, - current_owner_did: bytes32, - nft_transfer_program_mod_hash: bytes32, - metadata: Program, - backpayment_address: bytes32, - percentage: uint64, -) -> Program: - - transfer_program_curry_params = [ - backpayment_address, - percentage, - OFFER_MOD.get_tree_hash(), - CAT_MOD.get_tree_hash(), - ] - return create_nft_layer_puzzle_with_curry_params( - singleton_id, - current_owner_did, - nft_transfer_program_mod_hash, - metadata, - Program.to(transfer_program_curry_params), - ) - - def create_nft_layer_puzzle_with_curry_params( - singleton_id: bytes32, - current_owner_did: bytes32, - nft_transfer_program_mod_hash: bytes32, - metadata: Program, - transfer_program_curry_params: Program, + metadata: Program, metadata_updater_hash: bytes32, inner_puzzle: Program ) -> Program: - # NFT_MOD_HASH - # SINGLETON_STRUCT ; ((SINGLETON_MOD_HASH, (SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH))) - # CURRENT_OWNER_DID - # TRANSFER_PROGRAM_MOD_HASH - # TRANSFER_PROGRAM_CURRY_PARAMS - # METADATA - - singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH))) - return NFT_MOD.curry( + """Curries params into nft_state_layer.clvm + + Args to curry: + NFT_STATE_LAYER_MOD_HASH + METADATA + METADATA_UPDATER_PUZZLE_HASH + INNER_PUZZLE""" + log.debug( + "Creating nft layer puzzle curry: mod_hash: %s, metadata: %r, metadata_hash: %s", NFT_MOD_HASH, - singleton_struct, - current_owner_did, - nft_transfer_program_mod_hash, - transfer_program_curry_params, metadata, + metadata_updater_hash, ) + return NFT_STATE_LAYER_MOD.curry(NFT_MOD_HASH, metadata, metadata_updater_hash, inner_puzzle) def create_full_puzzle( - singleton_id: bytes32, - current_owner_did: bytes32, - nft_transfer_program_hash: bytes32, - metadata: Program, - backpayment_address: bytes32, - percentage: uint64, -) -> Program: - singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH))) - innerpuz = create_nft_layer_puzzle( - singleton_id, current_owner_did, nft_transfer_program_hash, metadata, backpayment_address, percentage - ) - return SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, innerpuz) - - -def create_full_puzzle_with_curry_params( - singleton_id: bytes32, - current_owner_did: bytes32, - nft_transfer_program_hash: bytes32, - metadata: Program, - transfer_program_curry_params: Program, + singleton_id: bytes32, metadata: Program, metadata_updater_puzhash: bytes32, inner_puzzle: Program ) -> Program: singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH))) - innerpuz = create_nft_layer_puzzle_with_curry_params( - singleton_id, current_owner_did, nft_transfer_program_hash, metadata, transfer_program_curry_params + singleton_inner_puzzle = create_nft_layer_puzzle_with_curry_params(metadata, metadata_updater_puzhash, inner_puzzle) + log.debug( + "Creating full NFT puzzle with: singleton struct: %s, inner_puzzle: %s", + singleton_struct, + singleton_inner_puzzle, ) - return SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, innerpuz) - + return SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, singleton_inner_puzzle) -def get_transfer_puzzle() -> Program: - return NFT_TRANSFER_PROGRAM - -def match_nft_puzzle(puzzle: Program) -> Tuple[bool, Iterator[Program]]: +def match_nft_puzzle(puzzle: Program) -> Tuple[bool, List[Program], List[Program]]: """ Given a puzzle test if it's an NFT and, if it is, return the curried arguments """ try: - mod, curried_args = puzzle.uncurry() + mod, singleton_curried_args = puzzle.uncurry() if mod == SINGLETON_TOP_LAYER_MOD: - mod, curried_args = curried_args.rest().first().uncurry() - if mod == NFT_MOD: - return True, curried_args.as_iter() + log.debug("Got a singleton matched") + mod, curried_args = singleton_curried_args.rest().first().uncurry() + if mod == NFT_STATE_LAYER_MOD: + log.debug("Got a NFT MOD matched") + return True, list(singleton_curried_args.as_iter()), list(curried_args.as_iter()) except Exception: - import traceback - - print(f"exception: {traceback.format_exc()}") - return False, iter(()) - return False, iter(()) + log.exception("Error extracting NFT puzzle arguments") + return False, [], [] + return False, [], [] def get_nft_id_from_puzzle(puzzle: Program) -> Optional[bytes32]: @@ -124,84 +76,11 @@ def get_nft_id_from_puzzle(puzzle: Program) -> Optional[bytes32]: try: mod, curried_args = puzzle.uncurry() if mod == SINGLETON_TOP_LAYER_MOD: - nft_id: bytes32 = curried_args.first().rest().first().as_atom() - return nft_id - except Exception: - return None - return None - - -def update_metadata(metadata: Program, solution: Program) -> Program: - tp_solution: Optional[Program] = get_transfer_program_solution_from_solution(solution) - if tp_solution is None or tp_solution.rest().first() == Program.to(0): - return metadata - new_metadata = [] - for kv_pair in metadata.as_iter(): - if kv_pair.first().as_atom() == b"u": - new_metadata.append(["u", kv_pair.rest().cons(tp_solution.rest())]) - else: - new_metadata.append(kv_pair) - updated_metadata: Program = Program.to(new_metadata) - return updated_metadata - - -def get_transfer_program_from_inner_solution(solution: Program) -> Optional[Program]: - try: - prog: Program = solution.rest().rest().rest().first() - return prog - except Exception: - return None - return None - - -def get_transfer_program_curried_args_from_puzzle(puzzle: Program) -> Optional[Program]: - try: - curried_args = match_nft_puzzle(puzzle)[1] - ( - NFT_MOD_HASH, - singleton_struct, - current_owner_did, - nft_transfer_program_hash, - transfer_program_curry_params, - metadata, - ) = curried_args - return transfer_program_curry_params - except Exception: - return None - return None - - -def get_royalty_address_from_puzzle(puzzle: Program) -> Optional[bytes32]: - try: - transfer_program_curry_params = get_transfer_program_curried_args_from_puzzle(puzzle) - if transfer_program_curry_params is not None: - ( - ROYALTY_ADDRESS, - TRADE_PRICE_PERCENTAGE, - SETTLEMENT_MOD_HASH, - CAT_MOD_HASH, - ) = transfer_program_curry_params.as_iter() - assert ROYALTY_ADDRESS is not None - royalty_address: bytes32 = ROYALTY_ADDRESS.as_atom() - return royalty_address - except Exception: - return None - return None - - -def get_percentage_from_puzzle(puzzle: Program) -> Optional[uint64]: - try: - transfer_program_curry_params = get_transfer_program_curried_args_from_puzzle(puzzle) - if transfer_program_curry_params is not None: - ( - ROYALTY_ADDRESS, - TRADE_PRICE_PERCENTAGE, - SETTLEMENT_MOD_HASH, - CAT_MOD_HASH, - ) = transfer_program_curry_params.as_iter() - assert TRADE_PRICE_PERCENTAGE is not None - percentage: uint64 = TRADE_PRICE_PERCENTAGE.as_int() - return percentage + arg = curried_args.first().rest().first().atom + if arg is not None: + nft_id: bytes32 = bytes32(arg) + return nft_id + return None except Exception: return None return None @@ -210,18 +89,10 @@ def get_percentage_from_puzzle(puzzle: Program) -> Optional[uint64]: def get_metadata_from_puzzle(puzzle: Program) -> Optional[Program]: try: curried_args = match_nft_puzzle(puzzle)[1] - ( - NFT_MOD_HASH, - singleton_struct, - current_owner_did, - nft_transfer_program_hash, - transfer_program_curry_params, - metadata, - ) = curried_args + (_, metadata, _, _) = curried_args return metadata except Exception: return None - return None def get_uri_list_from_puzzle(puzzle: Program) -> Optional[List[str]]: @@ -236,22 +107,3 @@ def get_uri_list_from_puzzle(puzzle: Program) -> Optional[List[str]]: return uri_list except Exception: return None - return None - - -def get_trade_prices_list_from_inner_solution(solution: Program) -> Optional[Program]: - try: - prog: Program = solution.rest().rest().rest().rest().first().first() - return prog - except Exception: - return None - return None - - -def get_transfer_program_solution_from_solution(solution: Program) -> Optional[Program]: - try: - prog_sol: Program = solution.rest().rest().rest().rest().first() - return prog_sol - except Exception: - return None - return None diff --git a/chia/wallet/nft_wallet/nft_wallet.py b/chia/wallet/nft_wallet/nft_wallet.py index 99c945e55980..8cd899125f12 100644 --- a/chia/wallet/nft_wallet/nft_wallet.py +++ b/chia/wallet/nft_wallet/nft_wallet.py @@ -3,10 +3,11 @@ import time from dataclasses import dataclass from secrets import token_bytes -from typing import Any, Dict, List, Optional, Set, Tuple, Type, TypeVar +from typing import Any, Dict, List, Optional, Set, Type, TypeVar -from blspy import AugSchemeMPL, G1Element +from blspy import AugSchemeMPL, G1Element, G2Element +from chia.clvm.singleton import SINGLETON_TOP_LAYER_MOD from chia.protocols.wallet_protocol import CoinState from chia.server.outbound_message import NodeType from chia.server.ws_connection import WSChiaConnection @@ -16,24 +17,26 @@ from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.coin_spend import CoinSpend from chia.types.spend_bundle import SpendBundle +from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict from chia.util.ints import uint8, uint32, uint64 from chia.util.streamable import Streamable, streamable -from chia.wallet.cat_wallet.cat_utils import ( - CAT_MOD, - SpendableCAT, - construct_cat_puzzle, - get_innerpuzzle_from_puzzle, - unsigned_spend_bundle_for_spendable_cats, -) +from chia.wallet.did_wallet.did_wallet_puzzles import LAUNCHER_PUZZLE from chia.wallet.lineage_proof import LineageProof from chia.wallet.nft_wallet import nft_puzzles +from chia.wallet.nft_wallet.nft_puzzles import NFT_MOD_HASH from chia.wallet.puzzles.load_clvm import load_clvm +from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import ( + DEFAULT_HIDDEN_PUZZLE_HASH, + calculate_synthetic_secret_key, + solution_for_conditions, +) +from chia.wallet.puzzles.puzzle_utils import make_create_coin_condition from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.transaction_type import TransactionType -from chia.wallet.util.wallet_sync_utils import subscribe_to_phs from chia.wallet.util.wallet_types import WalletType from chia.wallet.wallet import Wallet from chia.wallet.wallet_info import WalletInfo +from tests.wallet.nft_wallet.test_nft_clvm import NFT_METADATA_UPDATER _T_NFTWallet = TypeVar("_T_NFTWallet", bound="NFTWallet") @@ -45,17 +48,21 @@ class NFTCoinInfo(Streamable): coin: Coin lineage_proof: LineageProof - transfer_program: Program full_puzzle: Program @streamable @dataclass(frozen=True) class NFTWalletInfo(Streamable): - my_did: bytes32 - did_wallet_id: uint64 my_nft_coins: List[NFTCoinInfo] - known_transfer_programs: List[Tuple[bytes32, Program]] + did_wallet_id: Optional[uint32] = None + + +def create_fullpuz(innerpuz: Program, genesis_id: bytes32) -> Program: + mod_hash = SINGLETON_TOP_LAYER_MOD.get_tree_hash() + # singleton_struct = (MOD_HASH . (LAUNCHER_ID . LAUNCHER_PUZZLE_HASH)) + singleton_struct = Program.to((mod_hash, (genesis_id, LAUNCHER_PUZZLE.get_tree_hash()))) + return SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, innerpuz) class NFTWallet: @@ -65,46 +72,41 @@ class NFTWallet: nft_wallet_info: NFTWalletInfo standard_wallet: Wallet wallet_id: int - base_puzzle_program: Optional[Program] - base_inner_puzzle_hash: Optional[Program] @classmethod async def create_new_nft_wallet( cls: Type[_T_NFTWallet], wallet_state_manager: Any, wallet: Wallet, - did_wallet_id: int, + did_wallet_id: uint32 = None, name: str = "", ) -> _T_NFTWallet: """ This must be called under the wallet state manager lock """ self = cls() - self.base_puzzle_program = None - self.base_inner_puzzle_hash = None self.standard_wallet = wallet self.log = logging.getLogger(name if name else __name__) self.wallet_state_manager = wallet_state_manager - did_wallet = self.wallet_state_manager.wallets[did_wallet_id] - my_did = did_wallet.did_info.origin_coin.name() - self.nft_wallet_info = NFTWalletInfo(my_did, uint64(did_wallet_id), [], []) + self.nft_wallet_info = NFTWalletInfo([], did_wallet_id) info_as_string = json.dumps(self.nft_wallet_info.to_json_dict()) + self.wallet_info = await wallet_state_manager.user_store.create_wallet( - "NFT Wallet", WalletType.NFT.value, info_as_string + "NFT Wallet", uint32(WalletType.NFT.value), info_as_string ) if self.wallet_info is None: raise ValueError("Internal Error") self.wallet_id = self.wallet_info.id - # std_wallet_id = self.standard_wallet.wallet_id await self.wallet_state_manager.add_new_wallet(self, self.wallet_info.id) - # TODO: check if I need both - full_nodes: Dict[ - bytes32, WSChiaConnection - ] = self.wallet_state_manager.wallet_node.server.connection_by_type.get(NodeType.FULL_NODE, {}) - - for node_id, node in full_nodes.copy().items(): - await subscribe_to_phs([my_did], node, uint32(0)) - await self.wallet_state_manager.add_interested_puzzle_hashes([my_did], [self.wallet_id], in_transaction=False) + self.log.debug("Generated a new NFT wallet: %s", self.__dict__) + # await self.wallet_state_manager.update_wallet_puzzle_hashes(self.wallet_id) + if not did_wallet_id: + # default profile wallet + self.log.debug("Standard NFT wallet created") + + else: + # TODO: handle DID wallet puzhash + raise NotImplementedError() return self @classmethod @@ -123,20 +125,21 @@ async def create( self.standard_wallet = wallet self.wallet_info = wallet_info self.nft_wallet_info = NFTWalletInfo.from_json_dict(json.loads(wallet_info.data)) - self.base_puzzle_program = None - self.base_inner_puzzle_hash = None return self @classmethod def type(cls) -> uint8: return uint8(WalletType.NFT) + async def get_new_puzzle(self) -> Program: + self.log.debug("Getting new puzzle for NFT wallet: %s", self.id()) + return self.puzzle_for_pk((await self.wallet_state_manager.get_unused_derivation_record(self.id())).pubkey) + def id(self) -> uint32: return self.wallet_info.id async def add_nft_coin(self, coin: Coin, spent_height: uint32, in_transaction: bool) -> None: await self.coin_added(coin, spent_height, in_transaction=in_transaction) - return async def coin_added(self, coin: Coin, height: uint32, in_transaction: bool) -> None: """Notification from wallet state manager that wallet has been received.""" @@ -151,7 +154,10 @@ async def coin_added(self, coin: Coin, height: uint32, in_transaction: bool) -> coin_states: Optional[List[CoinState]] = await self.wallet_state_manager.wallet_node.get_coin_state( [coin.parent_coin_info] ) - assert coin_states is not None + if not coin_states: + # farm coin + return + assert coin_states parent_coin = coin_states[0].coin for node_id in full_nodes: node = server.all_connections[node_id] @@ -164,30 +170,19 @@ async def coin_added(self, coin: Coin, height: uint32, in_transaction: bool) -> async def puzzle_solution_received(self, coin_spend: CoinSpend, in_transaction: bool) -> None: coin_name = coin_spend.coin.name() puzzle: Program = Program.from_bytes(bytes(coin_spend.puzzle_reveal)) - solution: Program = Program.from_bytes(bytes(coin_spend.solution)).rest().rest().first() - matched, curried_args = nft_puzzles.match_nft_puzzle(puzzle) - nft_transfer_program = None + solution: Program = Program.from_bytes(bytes(coin_spend.solution)).rest().rest().first().first() + matched, singleton_curried_args, curried_args = nft_puzzles.match_nft_puzzle(puzzle) if matched: - ( - NFT_MOD_HASH, - singleton_struct, - current_owner, - nft_transfer_program_hash, - transfer_program_curry_params, - metadata, - ) = curried_args - # check if we already know this hash, if not then try to find reveal in solution - for hash, reveal in self.nft_wallet_info.known_transfer_programs: - if hash == bytes32(nft_transfer_program_hash.as_atom()): - nft_transfer_program = reveal - if nft_transfer_program is None: - attempt = nft_puzzles.get_transfer_program_from_inner_solution(solution) - if attempt is not None: - nft_transfer_program = attempt - await self.add_transfer_program(nft_transfer_program, in_transaction=in_transaction) - - assert nft_transfer_program is not None - self.log.info(f"found the info for coin {coin_name}") + (_, metadata, metadata_updater_puzzle_hash, inner_puzzle, solution, my_amount) = curried_args + (_, (singleton_id, _)) = singleton_curried_args + self.log.info(f"found the info for NFT coin {coin_name}") + + self.log.debug("Before spend metadata: %s", metadata) + for param in solution.as_iter(): + if param.first() == -24: + # metadata update + # (-24 (meta updater puzzle) url) + metadata = param.rest().rest().first() + metadata parent_coin = None coin_record = await self.wallet_state_manager.coin_store.get_coin_record(coin_name) if coin_record is None: @@ -200,36 +195,27 @@ async def puzzle_solution_received(self, coin_spend: CoinSpend, in_transaction: parent_coin = coin_record.coin if parent_coin is None: raise ValueError("Error in finding parent") - inner_puzzle: Program = nft_puzzles.create_nft_layer_puzzle_with_curry_params( - singleton_struct.rest().first().as_atom(), - current_owner.as_atom(), - nft_transfer_program_hash.as_atom(), + + self.log.debug("Got back updated metadata: %s", metadata) + child_puzzle: Program = nft_puzzles.create_full_puzzle( + singleton_id, metadata, - transfer_program_curry_params, + bytes32(metadata_updater_puzzle_hash), + inner_puzzle, ) child_coin: Optional[Coin] = None for new_coin in coin_spend.additions(): - if new_coin.amount % 2 == 1: + self.log.debug("Comparing addition: %s with %s ", new_coin.puzzle_hash, child_puzzle.get_tree_hash()) + if new_coin.puzzle_hash == child_puzzle.get_tree_hash(): child_coin = new_coin break - assert child_coin is not None - - metadata = nft_puzzles.update_metadata(metadata, solution) - # TODO: add smarter check for -22 to see if curry_params changed and use this for metadata too - child_puzzle: Program = nft_puzzles.create_full_puzzle_with_curry_params( - singleton_struct.rest().first().as_atom(), - self.nft_wallet_info.my_did, - nft_transfer_program_hash.as_atom(), - metadata, - transfer_program_curry_params, - ) + else: + raise ValueError("Invalid NFT spend on %r" % coin_name) - assert child_puzzle.get_tree_hash() == child_coin.puzzle_hash await self.add_coin( child_coin, - LineageProof(parent_coin.parent_coin_info, inner_puzzle.get_tree_hash(), parent_coin.amount), - nft_transfer_program, child_puzzle, + LineageProof(parent_coin.parent_coin_info, inner_puzzle.get_tree_hash(), parent_coin.amount), in_transaction=in_transaction, ) else: @@ -243,20 +229,16 @@ async def puzzle_solution_received(self, coin_spend: CoinSpend, in_transaction: # We also need to make sure there's no record of the transaction await self.wallet_state_manager.tx_store.delete_transaction_record(record.coin.name()) - async def add_coin( - self, coin: Coin, lineage_proof: LineageProof, transfer_program: Program, puzzle: Program, in_transaction: bool - ) -> None: + async def add_coin(self, coin: Coin, puzzle: Program, lineage_proof: LineageProof, in_transaction: bool) -> None: my_nft_coins = self.nft_wallet_info.my_nft_coins for coin_info in my_nft_coins: if coin_info.coin == coin: my_nft_coins.remove(coin_info) - my_nft_coins.append(NFTCoinInfo(coin, lineage_proof, transfer_program, puzzle)) + my_nft_coins.append(NFTCoinInfo(coin, lineage_proof, puzzle)) new_nft_wallet_info = NFTWalletInfo( - self.nft_wallet_info.my_did, - self.nft_wallet_info.did_wallet_id, my_nft_coins, - self.nft_wallet_info.known_transfer_programs, + self.nft_wallet_info.did_wallet_id, ) await self.save_info(new_nft_wallet_info, in_transaction=in_transaction) await self.wallet_state_manager.add_interested_coin_ids([coin.name()], in_transaction=in_transaction) @@ -268,35 +250,21 @@ async def remove_coin(self, coin: Coin, in_transaction: bool) -> None: if coin_info.coin == coin: my_nft_coins.remove(coin_info) new_nft_wallet_info = NFTWalletInfo( - self.nft_wallet_info.my_did, - self.nft_wallet_info.did_wallet_id, my_nft_coins, - self.nft_wallet_info.known_transfer_programs, - ) - await self.save_info(new_nft_wallet_info, in_transaction=in_transaction) - return - - async def add_transfer_program(self, transfer_program: Program, in_transaction: bool) -> None: - my_transfer_programs = self.nft_wallet_info.known_transfer_programs - my_transfer_programs.append((transfer_program.get_tree_hash(), transfer_program)) - new_nft_wallet_info = NFTWalletInfo( - self.nft_wallet_info.my_did, self.nft_wallet_info.did_wallet_id, - self.nft_wallet_info.my_nft_coins, - my_transfer_programs, ) await self.save_info(new_nft_wallet_info, in_transaction=in_transaction) return def puzzle_for_pk(self, pk: G1Element) -> Program: - # we don't use this puzzle - '(x pubkey)' - # TODO: check we aren't bricking ourself if someone is stupid enough to actually send to this address - prog: Program = Program.to([8, bytes(pk)]) - return prog - - async def generate_new_nft( - self, metadata: Program, percentage: uint64, backpayment_address: bytes32 - ) -> Optional[TransactionRecord]: + if not self.nft_wallet_info.did_wallet_id: + inner_puzzle = self.standard_wallet.puzzle_for_pk(bytes(pk)) + else: + raise NotImplementedError + provenance_puzzle = Program.to([NFT_MOD_HASH, inner_puzzle]) + return provenance_puzzle + + async def generate_new_nft(self, metadata: Program) -> Optional[TransactionRecord]: """ This must be called under the wallet state manager lock """ @@ -304,24 +272,25 @@ async def generate_new_nft( coins = await self.standard_wallet.select_coins(amount) if coins is None: return None - + self.log.debug("Attempt to generate a new NFT") origin = coins.copy().pop() genesis_launcher_puz = nft_puzzles.LAUNCHER_PUZZLE launcher_coin = Coin(origin.name(), genesis_launcher_puz.get_tree_hash(), uint64(amount)) - - nft_transfer_program = nft_puzzles.get_transfer_puzzle() + self.log.debug("Generating NFT with launcher coin %s and metadata: %s", launcher_coin, metadata) + inner_puzzle = await self.standard_wallet.get_new_puzzle() + # singleton eve eve_fullpuz = nft_puzzles.create_full_puzzle( - launcher_coin.name(), - self.nft_wallet_info.my_did, - nft_transfer_program.get_tree_hash(), - metadata, - backpayment_address, - percentage, + launcher_coin.name(), metadata, NFT_METADATA_UPDATER.get_tree_hash(), inner_puzzle ) + # launcher announcement announcement_set: Set[Announcement] = set() - announcement_message = Program.to([eve_fullpuz.get_tree_hash(), amount, bytes(0x80)]).get_tree_hash() + announcement_message = Program.to([eve_fullpuz.get_tree_hash(), amount, []]).get_tree_hash() announcement_set.add(Announcement(launcher_coin.name(), announcement_message)) + self.log.debug( + "Creating transaction for launcher: %s and other coins: %s (%s)", origin, coins, announcement_set + ) + # store the launcher transaction in the wallet state tx_record: Optional[TransactionRecord] = await self.standard_wallet.generate_signed_transaction( uint64(amount), genesis_launcher_puz.get_tree_hash(), @@ -333,8 +302,9 @@ async def generate_new_nft( announcement_set, ) - genesis_launcher_solution = Program.to([eve_fullpuz.get_tree_hash(), amount, bytes(0x80)]) + genesis_launcher_solution = Program.to([eve_fullpuz.get_tree_hash(), amount, []]) + # launcher spend to generate the singleton launcher_cs = CoinSpend(launcher_coin, genesis_launcher_puz, genesis_launcher_solution) launcher_sb = SpendBundle([launcher_cs], AugSchemeMPL.aggregate([])) @@ -343,25 +313,28 @@ async def generate_new_nft( if tx_record is None or tx_record.spend_bundle is None: return None + condition_list = [make_create_coin_condition(inner_puzzle.get_tree_hash(), amount, [])] + innersol = solution_for_conditions(condition_list) # EVE SPEND BELOW - did_wallet = self.wallet_state_manager.wallets[self.nft_wallet_info.did_wallet_id] - # Create a puzzle announcement - puzzle_announcements = ["a"] - message_sb = await did_wallet.create_message_spend(puzzle_announcements=puzzle_announcements) - if message_sb is None: - raise ValueError("Unable to created DID message spend.") - - innersol = Program.to([did_wallet.did_info.current_inner.get_tree_hash(), 0]) + # eve_spend_bundle = await self.generate_eve_spend(eve_coin, eve_fullpuz, self.base_inner_puzzle, launcher_coin) + fullsol = Program.to( [ [launcher_coin.parent_coin_info, launcher_coin.amount], eve_coin.amount, - innersol, + Program.to( + [ + innersol, + amount, + 0, + ] + ), ] ) list_of_coinspends = [CoinSpend(eve_coin, eve_fullpuz, fullsol)] eve_spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([])) - full_spend = SpendBundle.aggregate([tx_record.spend_bundle, eve_spend_bundle, launcher_sb, message_sb]) + eve_spend_bundle = await self.sign(eve_spend_bundle) + full_spend = SpendBundle.aggregate([tx_record.spend_bundle, eve_spend_bundle, launcher_sb]) nft_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), @@ -381,233 +354,127 @@ async def generate_new_nft( memos=[], ) await self.standard_wallet.push_transaction(nft_record) - await self.add_transfer_program(nft_transfer_program, in_transaction=False) return nft_record - async def make_announce_spend(self, nft_coin_info: NFTCoinInfo) -> SpendBundle: - did_wallet = self.wallet_state_manager.wallets[self.nft_wallet_info.did_wallet_id] - # Create a puzzle announcement - puzzle_announcements = ["a"] - message_sb = await did_wallet.create_message_spend(puzzle_announcements=puzzle_announcements) - if message_sb is None: - raise ValueError("Unable to created DID message spend.") - - innersol = Program.to( - [ - did_wallet.did_info.current_inner.get_tree_hash(), - 0, + async def generate_eve_spend(self, coin: Coin, full_puzzle: Program, innerpuz: Program, origin_coin: Coin): + # innerpuz solution is (mode p2_solution) + p2_solution = self.standard_wallet.make_solution( + primaries=[ + { + "puzzlehash": innerpuz.get_tree_hash(), + "amount": uint64(coin.amount), + "memos": [innerpuz.get_tree_hash()], + } ] ) + innersol = Program.to([1, p2_solution]) + # full solution is (lineage_proof my_amount inner_solution) fullsol = Program.to( [ - nft_coin_info.lineage_proof.to_program(), - nft_coin_info.coin.amount, + [origin_coin.parent_coin_info, origin_coin.amount], + coin.amount, innersol, ] ) - list_of_coinspends = [CoinSpend(nft_coin_info.coin, nft_coin_info.full_puzzle, fullsol)] - spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([])) - full_spend = SpendBundle.aggregate([spend_bundle, message_sb]) - nft_record = TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=uint64(int(time.time())), - to_puzzle_hash=nft_coin_info.coin.puzzle_hash, - amount=uint64(nft_coin_info.coin.amount), - fee_amount=uint64(0), - confirmed=False, - sent=uint32(0), - spend_bundle=full_spend, - additions=full_spend.additions(), - removals=full_spend.removals(), - wallet_id=self.wallet_info.id, - sent_to=[], - trade_id=None, - type=uint32(TransactionType.OUTGOING_TX.value), - name=bytes32(token_bytes()), - memos=[], - ) - await self.standard_wallet.push_transaction(nft_record) - return full_spend - - async def transfer_nft( - self, - nft_coin_info: NFTCoinInfo, - new_did, - new_did_inner_hash, - trade_prices_list, - new_url=0, - ): - did_wallet = self.wallet_state_manager.wallets[self.nft_wallet_info.did_wallet_id] - transfer_prog = nft_coin_info.transfer_program - # (sha256tree1 (list transfer_program_solution new_did)) - transfer_program_solution = [trade_prices_list, new_url] # TODO: Make this flexible for other transfer_programs - puzzle_announcements = [Program.to([transfer_program_solution, bytes(new_did)]).get_tree_hash()] - message_sb = await did_wallet.create_message_spend(puzzle_announcements=puzzle_announcements) - if message_sb is None: - raise ValueError("Unable to created DID message spend.") - # my_did_inner_hash - # new_did - # new_did_inner_hash - # transfer_program_reveal - # transfer_program_solution - - innersol = Program.to( - [ - did_wallet.did_info.current_inner.get_tree_hash(), - new_did, - new_did_inner_hash, - transfer_prog, - transfer_program_solution, # this should be expanded for other possible transfer_programs - ] - ) - fullsol = Program.to( - [ - nft_coin_info.lineage_proof.to_program(), - nft_coin_info.coin.amount, - innersol, - ] - ) - list_of_coinspends = [CoinSpend(nft_coin_info.coin, nft_coin_info.full_puzzle, fullsol)] - spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([])) - full_spend = SpendBundle.aggregate([spend_bundle, message_sb]) - # this full spend should be aggregated with the DID announcement spend of the recipient DID - if Program.to(trade_prices_list) == Program.to(0): - nft_record = TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=uint64(int(time.time())), - to_puzzle_hash=did_wallet.did_info.origin_coin.name(), - amount=uint64(nft_coin_info.coin.amount), - fee_amount=uint64(0), - confirmed=False, - sent=uint32(0), - spend_bundle=full_spend, - additions=full_spend.additions(), - removals=full_spend.removals(), - wallet_id=self.wallet_info.id, - sent_to=[], - trade_id=None, - type=uint32(TransactionType.OUTGOING_TX.value), - name=bytes32(token_bytes()), - memos=[], - ) - await self.standard_wallet.push_transaction(nft_record) - return full_spend - - async def receive_nft(self, sending_sb: SpendBundle, fee: uint64 = uint64(0)) -> SpendBundle: - trade_price_list_discovered = None - nft_id = None - - for coin_spend in sending_sb.coin_spends: - if nft_puzzles.match_nft_puzzle(Program.from_bytes(bytes(coin_spend.puzzle_reveal)))[0]: - inner_sol = Program.from_bytes(bytes(coin_spend.solution)).rest().rest().first() - trade_price_list_discovered = nft_puzzles.get_trade_prices_list_from_inner_solution(inner_sol) - nft_id = nft_puzzles.get_nft_id_from_puzzle(Program.from_bytes(bytes(coin_spend.puzzle_reveal))) - royalty_address = nft_puzzles.get_royalty_address_from_puzzle( - Program.from_bytes(bytes(coin_spend.puzzle_reveal)) - ) - royalty_percentage = nft_puzzles.get_percentage_from_puzzle( - Program.from_bytes(bytes(coin_spend.puzzle_reveal)) + list_of_coinspends = [CoinSpend(coin, full_puzzle, fullsol)] + unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element()) + return await self.sign(unsigned_spend_bundle) + + async def sign(self, spend_bundle: SpendBundle) -> SpendBundle: + sigs: List[G2Element] = [] + for spend in spend_bundle.coin_spends: + matched, _, puzzle_args = nft_puzzles.match_nft_puzzle(spend.puzzle_reveal.to_program()) + self.log.debug("Checking if spend matches a NFT puzzle: %s", matched) + if matched: + self.log.debug("Found a NFT state layer to sign") + p2_puzzle, _, _, _, _ = puzzle_args + puzzle_hash = p2_puzzle.get_tree_hash() + pubkey, private = await self.wallet_state_manager.get_keys(puzzle_hash) + synthetic_secret_key = calculate_synthetic_secret_key(private, DEFAULT_HIDDEN_PUZZLE_HASH) + error, conditions, cost = conditions_dict_for_solution( + spend.puzzle_reveal.to_program(), + spend.solution.to_program(), + self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM, ) - assert trade_price_list_discovered is not None - assert nft_id is not None - - did_wallet = self.wallet_state_manager.wallets[self.nft_wallet_info.did_wallet_id] - if trade_price_list_discovered == Program.to(0): - nft_record = TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=uint64(int(time.time())), - to_puzzle_hash=did_wallet.did_info.origin_coin.name(), - amount=uint64(coin_spend.coin.amount), - fee_amount=uint64(0), - confirmed=False, - sent=uint32(0), - spend_bundle=sending_sb, - additions=sending_sb.additions(), - removals=sending_sb.removals(), - wallet_id=self.wallet_info.id, - sent_to=[], - trade_id=None, - type=uint32(TransactionType.OUTGOING_TX.value), - name=bytes32(token_bytes()), - memos=[], - ) - await self.standard_wallet.push_transaction(nft_record) - - backpayment_amount = 0 - sb_list = [sending_sb] - - for pair in trade_price_list_discovered.as_iter(): - if len(pair.as_python()) == 1: - backpayment_amount += pair.first().as_int() - elif len(pair.as_python()) >= 2: - asset_id = pair.rest().first().as_atom() - amount = (pair.first().as_int() * royalty_percentage) // 10000 - cat_wallet = await self.wallet_state_manager.get_wallet_for_asset_id(asset_id.hex()) - assert cat_wallet is not None # TODO: catch this neater, maybe - settlement_ph: bytes32 = construct_cat_puzzle(CAT_MOD, asset_id, OFFER_MOD).get_tree_hash() - cat_tx_list = await cat_wallet.generate_signed_transaction([amount], [OFFER_MOD.get_tree_hash()]) - cat_sb = cat_tx_list[0].spend_bundle - sb_list.append(cat_sb) - coin = None - spendable_cc_list = [] - # breakpoint() - # Generate the spend of the royalty amount - # TODO: refactor this out of the NFTWallet - for coin in cat_sb.additions(): - if coin.puzzle_hash == settlement_ph: - nonce = nft_id - for cs in cat_sb.coin_spends: - if cs.coin.name() == coin.parent_coin_info: - cat_inner: Program = get_innerpuzzle_from_puzzle(cs.puzzle_reveal) - new_spendable_cc = SpendableCAT( - coin, - asset_id, - OFFER_MOD, - Program.to([(nonce, [[royalty_address, amount]])]), - lineage_proof=LineageProof( - cs.coin.parent_coin_info, cat_inner.get_tree_hash(), cs.coin.amount - ), - ) - spendable_cc_list.append(new_spendable_cc) - break - - cat_spend_bundle = unsigned_spend_bundle_for_spendable_cats(CAT_MOD, spendable_cc_list) - sb_list.append(cat_spend_bundle) - # spend_list.append(CoinSpend(coin, construct_cat_puzzle(CAT_MOD, asset_id, OFFER_MOD), )) - # offers_sb = SpendBundle(spend_list, AugSchemeMPL.aggregate([])) - # sb_list.append(offers_sb) - puzzle_announcements = [Program.to(trade_price_list_discovered.get_tree_hash() + bytes(nft_id))] - message_sb = await did_wallet.create_message_spend(puzzle_announcements=puzzle_announcements) - if message_sb is None: - raise ValueError("Unable to created DID message spend.") - sb_list.append(message_sb) - if backpayment_amount % 2 != 1: - backpayment_amount += 1 - relative_amount = (fee + backpayment_amount) * -1 - standard_sb = await self.standard_wallet.create_spend_bundle_relative_chia(relative_amount) - sb_list.append(standard_sb) - full_spend = SpendBundle.aggregate(sb_list) - nft_record = TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=uint64(int(time.time())), - to_puzzle_hash=did_wallet.did_info.origin_coin.name(), - amount=uint64(coin_spend.coin.amount), - fee_amount=uint64(0), - confirmed=False, - sent=uint32(0), - spend_bundle=full_spend, - additions=full_spend.additions(), - removals=full_spend.removals(), - wallet_id=self.wallet_info.id, - sent_to=[], - trade_id=None, - type=uint32(TransactionType.OUTGOING_TX.value), - name=bytes32(token_bytes()), - memos=[], - ) - await self.standard_wallet.push_transaction(nft_record) - return full_spend + if conditions is not None: + synthetic_pk = synthetic_secret_key.get_g1() + for pk, msg in pkm_pairs_for_conditions_dict( + conditions, spend.coin.name(), self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA + ): + try: + assert bytes(synthetic_pk) == pk + sigs.append(AugSchemeMPL.sign(synthetic_secret_key, msg)) + except AssertionError: + raise ValueError("This spend bundle cannot be signed by the DID wallet") + + agg_sig = AugSchemeMPL.aggregate(sigs) + return SpendBundle.aggregate([spend_bundle, SpendBundle([], agg_sig)]) + + # async def transfer_nft( + # self, + # nft_coin_info: NFTCoinInfo, + # new_did, + # new_did_inner_hash, + # trade_prices_list, + # new_url=0, + # ): + # did_wallet = self.wallet_state_manager.wallets[self.nft_wallet_info.did_wallet_id] + # transfer_prog = nft_coin_info.transfer_program + # # (sha256tree1 (list transfer_program_solution new_did)) + # # TODO: Make this flexible for other transfer_programs + # transfer_program_solution = [trade_prices_list, new_url] + # puzzle_announcements = [Program.to([transfer_program_solution, bytes(new_did)]).get_tree_hash()] + # message_sb = await did_wallet.create_message_spend(puzzle_announcements=puzzle_announcements) + # if message_sb is None: + # raise ValueError("Unable to created DID message spend.") + # # my_did_inner_hash + # # new_did + # # new_did_inner_hash + # # transfer_program_reveal + # # transfer_program_solution + + # innersol = Program.to( + # [ + # did_wallet.did_info.current_inner.get_tree_hash(), + # new_did, + # new_did_inner_hash, + # transfer_prog, + # transfer_program_solution, # this should be expanded for other possible transfer_programs + # ] + # ) + # fullsol = Program.to( + # [ + # nft_coin_info.lineage_proof.to_program(), + # nft_coin_info.coin.amount, + # innersol, + # ] + # ) + # list_of_coinspends = [CoinSpend(nft_coin_info.coin, nft_coin_info.full_puzzle, fullsol)] + # spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([])) + # full_spend = SpendBundle.aggregate([spend_bundle, message_sb]) + # # this full spend should be aggregated with the DID announcement spend of the recipient DID + # if Program.to(trade_prices_list) == Program.to(0): + # nft_record = TransactionRecord( + # confirmed_at_height=uint32(0), + # created_at_time=uint64(int(time.time())), + # to_puzzle_hash=did_wallet.did_info.origin_coin.name(), + # amount=uint64(nft_coin_info.coin.amount), + # fee_amount=uint64(0), + # confirmed=False, + # sent=uint32(0), + # spend_bundle=full_spend, + # additions=full_spend.additions(), + # removals=full_spend.removals(), + # wallet_id=self.wallet_info.id, + # sent_to=[], + # trade_id=None, + # type=uint32(TransactionType.OUTGOING_TX.value), + # name=bytes32(token_bytes()), + # memos=[], + # ) + # await self.standard_wallet.push_transaction(nft_record) + # return full_spend def get_current_nfts(self) -> List[NFTCoinInfo]: return self.nft_wallet_info.my_nft_coins diff --git a/chia/wallet/puzzles/nft_state_layer.clvm b/chia/wallet/puzzles/nft_state_layer.clvm index 311816bbda24..200492ad2e37 100644 --- a/chia/wallet/puzzles/nft_state_layer.clvm +++ b/chia/wallet/puzzles/nft_state_layer.clvm @@ -9,7 +9,7 @@ (include condition_codes.clvm) (include curry-and-treehash.clinc) - + (defconstant UPDATE_METADATA -24) (defun sha256tree1 (TREE) (if (l TREE) @@ -50,8 +50,10 @@ (defun check_for_metadata_prog_reveal (METADATA_UPDATER_PUZZLE_HASH CURRENT_METADATA conditions_loop) (if conditions_loop - (if (= (f (f conditions_loop)) -24) + (if (= (f (f conditions_loop)) UPDATE_METADATA) + ; check program matches puzzle has we have curried (if (= (sha256tree1 (f (r (f conditions_loop)))) METADATA_UPDATER_PUZZLE_HASH) + ; apply the metadata updater (a (f (r (f conditions_loop))) (list CURRENT_METADATA (f (r (r (f conditions_loop)))))) (x) ) diff --git a/chia/wallet/puzzles/nft_state_layer.clvm.hex b/chia/wallet/puzzles/nft_state_layer.clvm.hex index 40f217bedf46..acae6e5c5888 100644 --- a/chia/wallet/puzzles/nft_state_layer.clvm.hex +++ b/chia/wallet/puzzles/nft_state_layer.clvm.hex @@ -1 +1 @@ -ff02ffff01ff04ffff04ff10ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ff17ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfff8080808080808080ffff04ffff01ffffff49ff0233ffff0401ff0102ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ffff0181e880ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff820167ff80808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ff0bffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff57ffff01ff02ffff03ffff09ff820117ff3880ffff01ff02ffff03ffff18ff820597ff3480ffff01ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff820297ffff04ffff0bff34ff0b80ffff04ffff02ff2effff04ff02ffff04ff27ff80808080ffff04ffff0bff34ff0580ff8080808080808080ffff04ff2fff80808080ff81d780ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ff8080ff0180ff018080 \ No newline at end of file +ff02ffff01ff04ffff04ff20ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ff17ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfff8080808080808080ffff04ffff01ffffffff4902ff3304ffff0101ff0281e8ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff2cffff0bff24ff3880ffff0bff2cffff0bff2cffff0bff24ff3480ff0980ffff0bff2cff0bffff0bff24ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ff3c80ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff820167ff80808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ff0bffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff2cffff0bff24ff3080ffff0bff2cffff0bff2cffff0bff24ff3480ff0580ffff0bff2cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff24ff2480ff8080808080ffff0bff24ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff57ffff01ff02ffff03ffff09ff820117ff2880ffff01ff02ffff03ffff18ff820597ff2480ffff01ff04ffff04ff28ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff820297ffff04ffff0bff24ff0b80ffff04ffff02ff2effff04ff02ffff04ff27ff80808080ffff04ffff0bff24ff0580ff8080808080808080ffff04ff2fff80808080ff81d780ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ff8080ff0180ff018080 \ No newline at end of file diff --git a/chia/wallet/wallet.py b/chia/wallet/wallet.py index 08fef8aa311c..d60774ba12ba 100644 --- a/chia/wallet/wallet.py +++ b/chia/wallet/wallet.py @@ -330,7 +330,7 @@ async def _generate_unsigned_transaction( max_send = await self.get_max_send_amount() if total_amount > max_send: raise ValueError(f"Can't send more than {max_send} in a single transaction") - + self.log.debug("Got back max send amount: %s", max_send) if coins is None: coins = await self.select_coins(total_amount) assert len(coins) > 0 @@ -448,6 +448,7 @@ async def generate_signed_transaction( else: non_change_amount = uint64(amount + sum(p["amount"] for p in primaries)) + self.log.debug("Generating transaction for: %s %s %s", puzzle_hash, amount, repr(coins)) transaction = await self._generate_unsigned_transaction( amount, puzzle_hash, @@ -462,8 +463,7 @@ async def generate_signed_transaction( negative_change_allowed, ) assert len(transaction) > 0 - - self.log.info("About to sign a transaction") + self.log.info("About to sign a transaction: %s", transaction) await self.hack_populate_secret_keys_for_coin_spends(transaction) spend_bundle: SpendBundle = await sign_coin_spends( transaction, diff --git a/chia/wallet/wallet_puzzle_store.py b/chia/wallet/wallet_puzzle_store.py index f4113c3a7b59..f022a6211be0 100644 --- a/chia/wallet/wallet_puzzle_store.py +++ b/chia/wallet/wallet_puzzle_store.py @@ -90,6 +90,7 @@ async def add_derivation_paths(self, records: List[DerivationRecord], in_transac try: sql_records = [] for record in records: + log.debug("Adding derivation record: %s", record) self.all_puzzle_hashes.add(record.puzzle_hash) if record.hardened: hardened = 1 diff --git a/chia/wallet/wallet_state_manager.py b/chia/wallet/wallet_state_manager.py index f7dc4e371ea6..49eed2610391 100644 --- a/chia/wallet/wallet_state_manager.py +++ b/chia/wallet/wallet_state_manager.py @@ -258,11 +258,12 @@ async def create_more_puzzle_hashes(self, from_zero: bool = False, in_transactio that we can restore the wallet from only the private keys. """ targets = list(self.wallets.keys()) - + self.log.debug("Target wallets to generate puzzle hashes for: %s", repr(targets)) unused: Optional[uint32] = await self.puzzle_store.get_unused_derivation_path() if unused is None: # This handles the case where the database has entries but they have all been used unused = await self.puzzle_store.get_last_derivation_path() + self.log.debug("Tried finding unused: %s", unused) if unused is None: # This handles the case where the database is empty unused = uint32(0) @@ -377,11 +378,13 @@ async def get_unused_derivation_record( # If we have no unused public keys, we will create new ones unused: Optional[uint32] = await self.puzzle_store.get_unused_derivation_path() if unused is None: + self.log.debug("No unused paths, generate more ") await self.create_more_puzzle_hashes() + # Now we must have unused public keys + unused = await self.puzzle_store.get_unused_derivation_path() + assert unused is not None - # Now we must have unused public keys - unused = await self.puzzle_store.get_unused_derivation_path() - assert unused is not None + self.log.debug("Fetching derivation record for: %s %s %s", unused, wallet_id, hardened) record: Optional[DerivationRecord] = await self.puzzle_store.get_derivation_record( unused, wallet_id, hardened ) @@ -577,9 +580,11 @@ async def determine_coin_type( # Check if the coin is a NFT # hint # First spend where 1 mojo coin -> Singleton launcher -> NFT -> NFT - nft_matched, nft_curried_args = match_nft_puzzle(Program.from_bytes(bytes(coin_spend.puzzle_reveal))) + nft_matched, singleton_curried_args, nft_curried_args = match_nft_puzzle( + Program.from_bytes(bytes(coin_spend.puzzle_reveal)) + ) if nft_matched: - return await self.handle_nft(coin_spend, nft_curried_args) + return await self.handle_nft(coin_spend, iter(nft_curried_args)) # Check if the coin is a DID did_matched, did_curried_args = match_did_puzzle(Program.from_bytes(bytes(coin_spend.puzzle_reveal))) diff --git a/tests/wallet/nft_wallet/test_nft_clvm.py b/tests/wallet/nft_wallet/test_nft_clvm.py index 04300473e9b7..adbd3d5f2263 100644 --- a/tests/wallet/nft_wallet/test_nft_clvm.py +++ b/tests/wallet/nft_wallet/test_nft_clvm.py @@ -1,28 +1,8 @@ -from blspy import G1Element -from chia.types.announcement import Announcement -from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.program import INFINITE_COST, Program -from chia.types.blockchain_format.sized_bytes import bytes32 -from chia.util.ints import uint64 -from chia.wallet.puzzles.cat_loader import CAT_MOD from chia.wallet.puzzles.load_clvm import load_clvm -from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import ( - DEFAULT_HIDDEN_PUZZLE_HASH, - calculate_synthetic_secret_key, - puzzle_for_pk, - solution_for_conditions, -) +from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import puzzle_for_pk, solution_for_conditions +from chia.wallet.puzzles.puzzle_utils import make_create_coin_condition from tests.core.make_block_generator import int_to_public_key -from chia.wallet.puzzles.puzzle_utils import ( - make_assert_coin_announcement, - make_assert_puzzle_announcement, - make_assert_my_coin_id_condition, - make_assert_absolute_seconds_exceeds_condition, - make_create_coin_announcement, - make_create_puzzle_announcement, - make_create_coin_condition, - make_reserve_fee_condition, -) SINGLETON_MOD = load_clvm("singleton_top_layer.clvm") LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm") @@ -33,7 +13,7 @@ NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash() SINGLETON_MOD_HASH = SINGLETON_MOD.get_tree_hash() -LAUNCHER_ID = Program.to(b"launcher-id").get_tree_hash() +LAUNCHER_ID = Program.to("launcher-id").get_tree_hash() NFT_METADATA_UPDATER = load_clvm("nft_metadata_updater.clvm") @@ -41,7 +21,7 @@ def test_new_nft_ownership_layer() -> None: pubkey = int_to_public_key(1) innerpuz = puzzle_for_pk(pubkey) my_amount = 1 - destination: bytes32 = puzzle_for_pk(int_to_public_key(2)) + destination: Program = puzzle_for_pk(int_to_public_key(2)) condition_list = [make_create_coin_condition(destination.get_tree_hash(), my_amount, [])] metadata = [ ("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), @@ -64,14 +44,19 @@ def test_new_nft_ownership_layer() -> None: assert res.first().first().as_int() == 73 assert res.first().rest().first().as_int() == 1 assert res.rest().rest().first().first().as_int() == 51 - assert res.rest().rest().first().rest().first().as_atom() == NFT_STATE_LAYER_MOD.curry(NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER.get_tree_hash(), destination).get_tree_hash() + assert ( + res.rest().rest().first().rest().first().as_atom() + == NFT_STATE_LAYER_MOD.curry( + NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER.get_tree_hash(), destination + ).get_tree_hash() + ) def test_update_metadata() -> None: pubkey = int_to_public_key(1) innerpuz = puzzle_for_pk(pubkey) my_amount = 1 - destination: bytes32 = puzzle_for_pk(int_to_public_key(2)) + destination: Program = puzzle_for_pk(int_to_public_key(2)) condition_list = [make_create_coin_condition(destination.get_tree_hash(), my_amount, [])] condition_list.append([-24, NFT_METADATA_UPDATER, "https://www.chia.net/img/branding/chia-logo-2.svg"]) metadata = [ @@ -100,4 +85,9 @@ def test_update_metadata() -> None: assert res.first().first().as_int() == 73 assert res.first().rest().first().as_int() == 1 assert res.rest().rest().first().first().as_int() == 51 - assert res.rest().rest().first().rest().first().as_atom() == NFT_STATE_LAYER_MOD.curry(NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER.get_tree_hash(), destination).get_tree_hash() + assert ( + res.rest().rest().first().rest().first().as_atom() + == NFT_STATE_LAYER_MOD.curry( + NFT_STATE_LAYER_MOD_HASH, metadata, NFT_METADATA_UPDATER.get_tree_hash(), destination + ).get_tree_hash() + ) From e12332f59a5637a6e77e63ab5bf5695a8b6b0c83 Mon Sep 17 00:00:00 2001 From: Sebastjan Date: Tue, 3 May 2022 17:34:18 +0200 Subject: [PATCH 28/43] generating nfts --- chia/wallet/nft_wallet/nft_puzzles.py | 90 ++++------ chia/wallet/nft_wallet/nft_wallet.py | 196 ++++++++++++--------- chia/wallet/wallet_state_manager.py | 44 +++-- tests/wallet/nft_wallet/__init__.py | 0 tests/wallet/nft_wallet/test_nft_wallet.py | 158 +++++++++++++++++ 5 files changed, 322 insertions(+), 166 deletions(-) create mode 100644 tests/wallet/nft_wallet/__init__.py create mode 100644 tests/wallet/nft_wallet/test_nft_wallet.py diff --git a/chia/wallet/nft_wallet/nft_puzzles.py b/chia/wallet/nft_wallet/nft_puzzles.py index 209a9f276448..053416321c64 100644 --- a/chia/wallet/nft_wallet/nft_puzzles.py +++ b/chia/wallet/nft_wallet/nft_puzzles.py @@ -4,16 +4,14 @@ from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.sized_bytes import bytes32 from chia.wallet.puzzles.load_clvm import load_clvm +from chia.wallet.util.debug_spend_bundle import disassemble log = logging.getLogger(__name__) SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_v1_1.clvm") LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm") -DID_MOD = load_clvm("did_innerpuz.clvm") -NFT_MOD = load_clvm("nft_innerpuz.clvm") NFT_STATE_LAYER_MOD = load_clvm("nft_state_layer.clvm") LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash() SINGLETON_MOD_HASH = SINGLETON_TOP_LAYER_MOD.get_tree_hash() -NFT_MOD_HASH = NFT_MOD.get_tree_hash() NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash() NFT_TRANSFER_PROGRAM = load_clvm("nft_transfer_program.clvm") OFFER_MOD = load_clvm("settlement_payments.clvm") @@ -31,79 +29,57 @@ def create_nft_layer_puzzle_with_curry_params( INNER_PUZZLE""" log.debug( "Creating nft layer puzzle curry: mod_hash: %s, metadata: %r, metadata_hash: %s", - NFT_MOD_HASH, + NFT_STATE_LAYER_MOD_HASH, metadata, metadata_updater_hash, ) - return NFT_STATE_LAYER_MOD.curry(NFT_MOD_HASH, metadata, metadata_updater_hash, inner_puzzle) + return NFT_STATE_LAYER_MOD.curry(NFT_STATE_LAYER_MOD_HASH, metadata, metadata_updater_hash, inner_puzzle) + + +def create_full_puzzle_with_nft_puzzle(singleton_id: bytes32, inner_puzzle: Program) -> Program: + log.debug( + "Creating full NFT puzzle with inner puzzle: \n%r\n%r", + singleton_id, + inner_puzzle.get_tree_hash(), + ) + singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH))) + + full_puzzle = SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, inner_puzzle) + log.debug("Created NFT full puzzle with inner: %s", full_puzzle.get_tree_hash()) + return full_puzzle def create_full_puzzle( singleton_id: bytes32, metadata: Program, metadata_updater_puzhash: bytes32, inner_puzzle: Program ) -> Program: - singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH))) - singleton_inner_puzzle = create_nft_layer_puzzle_with_curry_params(metadata, metadata_updater_puzhash, inner_puzzle) log.debug( - "Creating full NFT puzzle with: singleton struct: %s, inner_puzzle: %s", - singleton_struct, - singleton_inner_puzzle, + "Creating full NFT puzzle with: \n%r\n%r\n%r\n%r", + singleton_id, + metadata.get_tree_hash(), + metadata_updater_puzhash, + inner_puzzle.get_tree_hash(), ) - return SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, singleton_inner_puzzle) + singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH))) + singleton_inner_puzzle = create_nft_layer_puzzle_with_curry_params(metadata, metadata_updater_puzhash, inner_puzzle) + + full_puzzle = SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, singleton_inner_puzzle) + log.debug("Created NFT full puzzle: %s", full_puzzle.get_tree_hash()) + return full_puzzle -def match_nft_puzzle(puzzle: Program) -> Tuple[bool, List[Program], List[Program]]: +def match_nft_puzzle(puzzle: Program) -> Tuple[bool, Program, List[Program]]: """ Given a puzzle test if it's an NFT and, if it is, return the curried arguments """ try: mod, singleton_curried_args = puzzle.uncurry() if mod == SINGLETON_TOP_LAYER_MOD: - log.debug("Got a singleton matched") mod, curried_args = singleton_curried_args.rest().first().uncurry() + log.debug("Got a singleton matched: %r", singleton_curried_args) if mod == NFT_STATE_LAYER_MOD: - log.debug("Got a NFT MOD matched") - return True, list(singleton_curried_args.as_iter()), list(curried_args.as_iter()) + log.debug("Got a NFT MOD matched: %s", curried_args) + return True, singleton_curried_args, list(curried_args.as_iter()) except Exception: log.exception("Error extracting NFT puzzle arguments") - return False, [], [] - return False, [], [] - - -def get_nft_id_from_puzzle(puzzle: Program) -> Optional[bytes32]: - """ - Given a puzzle test if it's an NFT and, if it is, return the curried arguments - """ - try: - mod, curried_args = puzzle.uncurry() - if mod == SINGLETON_TOP_LAYER_MOD: - arg = curried_args.first().rest().first().atom - if arg is not None: - nft_id: bytes32 = bytes32(arg) - return nft_id - return None - except Exception: - return None - return None - - -def get_metadata_from_puzzle(puzzle: Program) -> Optional[Program]: - try: - curried_args = match_nft_puzzle(puzzle)[1] - (_, metadata, _, _) = curried_args - return metadata - except Exception: - return None - - -def get_uri_list_from_puzzle(puzzle: Program) -> Optional[List[str]]: - try: - uri_list = [] - metadata = get_metadata_from_puzzle(puzzle) - assert metadata is not None - for kv_pair in metadata.as_iter(): - if kv_pair.first().as_atom() == b"u": - for uri in kv_pair.rest().as_iter(): - uri_list.append(uri.as_atom()) - return uri_list - except Exception: - return None + return False, Program.to([]), [] + return False, Program.to([]), [] diff --git a/chia/wallet/nft_wallet/nft_wallet.py b/chia/wallet/nft_wallet/nft_wallet.py index 8cd899125f12..e2ce1aa9dcee 100644 --- a/chia/wallet/nft_wallet/nft_wallet.py +++ b/chia/wallet/nft_wallet/nft_wallet.py @@ -6,6 +6,7 @@ from typing import Any, Dict, List, Optional, Set, Type, TypeVar from blspy import AugSchemeMPL, G1Element, G2Element +from clvm.casts import int_from_bytes from chia.clvm.singleton import SINGLETON_TOP_LAYER_MOD from chia.protocols.wallet_protocol import CoinState @@ -20,21 +21,24 @@ from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict from chia.util.ints import uint8, uint32, uint64 from chia.util.streamable import Streamable, streamable -from chia.wallet.did_wallet.did_wallet_puzzles import LAUNCHER_PUZZLE +from chia.wallet.derivation_record import DerivationRecord from chia.wallet.lineage_proof import LineageProof from chia.wallet.nft_wallet import nft_puzzles -from chia.wallet.nft_wallet.nft_puzzles import NFT_MOD_HASH +from chia.wallet.nft_wallet.nft_puzzles import LAUNCHER_PUZZLE, NFT_STATE_LAYER_MOD_HASH from chia.wallet.puzzles.load_clvm import load_clvm from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import ( DEFAULT_HIDDEN_PUZZLE_HASH, calculate_synthetic_secret_key, + puzzle_for_pk, solution_for_conditions, ) from chia.wallet.puzzles.puzzle_utils import make_create_coin_condition from chia.wallet.transaction_record import TransactionRecord +from chia.wallet.util.debug_spend_bundle import disassemble from chia.wallet.util.transaction_type import TransactionType from chia.wallet.util.wallet_types import WalletType from chia.wallet.wallet import Wallet +from chia.wallet.wallet_coin_record import WalletCoinRecord from chia.wallet.wallet_info import WalletInfo from tests.wallet.nft_wallet.test_nft_clvm import NFT_METADATA_UPDATER @@ -143,7 +147,7 @@ async def add_nft_coin(self, coin: Coin, spent_height: uint32, in_transaction: b async def coin_added(self, coin: Coin, height: uint32, in_transaction: bool) -> None: """Notification from wallet state manager that wallet has been received.""" - self.log.info(f" NFT wallet has been notified that {coin} was added") + self.log.info(f"NFT wallet has been notified that {coin} was added") for coin_info in self.nft_wallet_info.my_nft_coins: if coin_info.coin == coin: return @@ -173,16 +177,35 @@ async def puzzle_solution_received(self, coin_spend: CoinSpend, in_transaction: solution: Program = Program.from_bytes(bytes(coin_spend.solution)).rest().rest().first().first() matched, singleton_curried_args, curried_args = nft_puzzles.match_nft_puzzle(puzzle) if matched: - (_, metadata, metadata_updater_puzzle_hash, inner_puzzle, solution, my_amount) = curried_args - (_, (singleton_id, _)) = singleton_curried_args - self.log.info(f"found the info for NFT coin {coin_name}") - - self.log.debug("Before spend metadata: %s", metadata) - for param in solution.as_iter(): - if param.first() == -24: + (_, metadata, metadata_updater_puzzle_hash, inner_puzzle) = curried_args + params = singleton_curried_args.first() + self.log.info(f"found the info for NFT coin {coin_name} {inner_puzzle} {params}") + singleton_id = bytes32(params.rest().first().atom) + new_inner_puzzle = inner_puzzle + puzhash = None + self.log.debug("Before spend metadata: %s %s \n%s", metadata, singleton_id, disassemble(solution)) + for condition in solution.rest().first().rest().as_iter(): + self.log.debug("Checking solution condition: %s", disassemble(condition)) + if condition.list_len() < 2: + # invalid condition + continue + condition_code = int_from_bytes(condition.first().atom) + self.log.debug("Checking condition code: %r", condition_code) + if condition_code == -24: # metadata update # (-24 (meta updater puzzle) url) - metadata = param.rest().rest().first() + metadata + metadata = condition.rest().rest().first() + metadata + elif condition_code == 51: + puzhash = bytes32(condition.rest().first().atom) + record: DerivationRecord = ( + await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(puzhash) + ) + new_inner_puzzle = puzzle_for_pk(record.pubkey) + + else: + raise ValueError("Invalid condition") + if new_inner_puzzle is None: + raise ValueError("Invalid puzzle") parent_coin = None coin_record = await self.wallet_state_manager.coin_store.get_coin_record(coin_name) if coin_record is None: @@ -195,17 +218,29 @@ async def puzzle_solution_received(self, coin_spend: CoinSpend, in_transaction: parent_coin = coin_record.coin if parent_coin is None: raise ValueError("Error in finding parent") - self.log.debug("Got back updated metadata: %s", metadata) child_puzzle: Program = nft_puzzles.create_full_puzzle( singleton_id, metadata, - bytes32(metadata_updater_puzzle_hash), - inner_puzzle, + bytes32(metadata_updater_puzzle_hash.atom), + new_inner_puzzle, + ) + self.log.debug( + "Created NFT full puzzle with inner: %s", + nft_puzzles.create_full_puzzle_with_nft_puzzle(singleton_id, new_inner_puzzle), + ) + self.log.debug( + "Created NFT full puzzle with inner: %s", + nft_puzzles.create_full_puzzle_with_nft_puzzle(singleton_id, inner_puzzle), ) child_coin: Optional[Coin] = None for new_coin in coin_spend.additions(): - self.log.debug("Comparing addition: %s with %s ", new_coin.puzzle_hash, child_puzzle.get_tree_hash()) + self.log.debug( + "Comparing addition: %s with %s, amount: %s ", + new_coin.puzzle_hash, + child_puzzle.get_tree_hash(), + new_coin.amount, + ) if new_coin.puzzle_hash == child_puzzle.get_tree_hash(): child_coin = new_coin break @@ -220,7 +255,9 @@ async def puzzle_solution_received(self, coin_spend: CoinSpend, in_transaction: ) else: # The parent is not an NFT which means we need to scrub all of its children from our DB - child_coin_records = await self.wallet_state_manager.coin_store.get_coin_records_by_parent_id(coin_name) + child_coin_records: List[ + WalletCoinRecord + ] = await self.wallet_state_manager.coin_store.get_coin_records_by_parent_id(coin_name) if len(child_coin_records) > 0: for record in child_coin_records: if record.wallet_id == self.id(): @@ -261,7 +298,7 @@ def puzzle_for_pk(self, pk: G1Element) -> Program: inner_puzzle = self.standard_wallet.puzzle_for_pk(bytes(pk)) else: raise NotImplementedError - provenance_puzzle = Program.to([NFT_MOD_HASH, inner_puzzle]) + provenance_puzzle = Program.to([NFT_STATE_LAYER_MOD_HASH, inner_puzzle]) return provenance_puzzle async def generate_new_nft(self, metadata: Program) -> Optional[TransactionRecord]: @@ -316,7 +353,6 @@ async def generate_new_nft(self, metadata: Program) -> Optional[TransactionRecor condition_list = [make_create_coin_condition(inner_puzzle.get_tree_hash(), amount, [])] innersol = solution_for_conditions(condition_list) # EVE SPEND BELOW - # eve_spend_bundle = await self.generate_eve_spend(eve_coin, eve_fullpuz, self.base_inner_puzzle, launcher_coin) fullsol = Program.to( [ @@ -387,8 +423,8 @@ async def sign(self, spend_bundle: SpendBundle) -> SpendBundle: self.log.debug("Checking if spend matches a NFT puzzle: %s", matched) if matched: self.log.debug("Found a NFT state layer to sign") - p2_puzzle, _, _, _, _ = puzzle_args - puzzle_hash = p2_puzzle.get_tree_hash() + (_, metadata, metadata_updater_puzzle_hash, inner_puzzle) = puzzle_args + puzzle_hash = inner_puzzle.get_tree_hash() pubkey, private = await self.wallet_state_manager.get_keys(puzzle_hash) synthetic_secret_key = calculate_synthetic_secret_key(private, DEFAULT_HIDDEN_PUZZLE_HASH) error, conditions, cost = conditions_dict_for_solution( @@ -406,75 +442,65 @@ async def sign(self, spend_bundle: SpendBundle) -> SpendBundle: assert bytes(synthetic_pk) == pk sigs.append(AugSchemeMPL.sign(synthetic_secret_key, msg)) except AssertionError: - raise ValueError("This spend bundle cannot be signed by the DID wallet") + raise ValueError("This spend bundle cannot be signed by the NFT wallet") agg_sig = AugSchemeMPL.aggregate(sigs) return SpendBundle.aggregate([spend_bundle, SpendBundle([], agg_sig)]) - # async def transfer_nft( - # self, - # nft_coin_info: NFTCoinInfo, - # new_did, - # new_did_inner_hash, - # trade_prices_list, - # new_url=0, - # ): - # did_wallet = self.wallet_state_manager.wallets[self.nft_wallet_info.did_wallet_id] - # transfer_prog = nft_coin_info.transfer_program - # # (sha256tree1 (list transfer_program_solution new_did)) - # # TODO: Make this flexible for other transfer_programs - # transfer_program_solution = [trade_prices_list, new_url] - # puzzle_announcements = [Program.to([transfer_program_solution, bytes(new_did)]).get_tree_hash()] - # message_sb = await did_wallet.create_message_spend(puzzle_announcements=puzzle_announcements) - # if message_sb is None: - # raise ValueError("Unable to created DID message spend.") - # # my_did_inner_hash - # # new_did - # # new_did_inner_hash - # # transfer_program_reveal - # # transfer_program_solution - - # innersol = Program.to( - # [ - # did_wallet.did_info.current_inner.get_tree_hash(), - # new_did, - # new_did_inner_hash, - # transfer_prog, - # transfer_program_solution, # this should be expanded for other possible transfer_programs - # ] - # ) - # fullsol = Program.to( - # [ - # nft_coin_info.lineage_proof.to_program(), - # nft_coin_info.coin.amount, - # innersol, - # ] - # ) - # list_of_coinspends = [CoinSpend(nft_coin_info.coin, nft_coin_info.full_puzzle, fullsol)] - # spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([])) - # full_spend = SpendBundle.aggregate([spend_bundle, message_sb]) - # # this full spend should be aggregated with the DID announcement spend of the recipient DID - # if Program.to(trade_prices_list) == Program.to(0): - # nft_record = TransactionRecord( - # confirmed_at_height=uint32(0), - # created_at_time=uint64(int(time.time())), - # to_puzzle_hash=did_wallet.did_info.origin_coin.name(), - # amount=uint64(nft_coin_info.coin.amount), - # fee_amount=uint64(0), - # confirmed=False, - # sent=uint32(0), - # spend_bundle=full_spend, - # additions=full_spend.additions(), - # removals=full_spend.removals(), - # wallet_id=self.wallet_info.id, - # sent_to=[], - # trade_id=None, - # type=uint32(TransactionType.OUTGOING_TX.value), - # name=bytes32(token_bytes()), - # memos=[], - # ) - # await self.standard_wallet.push_transaction(nft_record) - # return full_spend + async def transfer_nft( + self, + nft_coin_info: NFTCoinInfo, + puzzle_hash=None, + did_hash=None, + ): + return None + # self.log.debug("Attempt to transfer a new NFT") + # coin = nft_coin_info.coin + # self.log.debug("Spending NFT with launcher coin %s and metadata: %s", launcher_coin, metadata) + + # full_puzzle = nft_coin_info.full_puzzle + + # condition_list = [make_create_coin_condition(inner_puzzle.get_tree_hash(), amount, [])] + # innersol = solution_for_conditions(condition_list) + # # EVE SPEND BELOW + + # fullsol = Program.to( + # [ + # [launcher_coin.parent_coin_info, launcher_coin.amount], + # eve_coin.amount, + # Program.to( + # [ + # innersol, + # amount, + # 0, + # ] + # ), + # ] + # ) + # list_of_coinspends = [CoinSpend(eve_coin, eve_fullpuz, fullsol)] + # eve_spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([])) + # eve_spend_bundle = await self.sign(eve_spend_bundle) + # full_spend = SpendBundle.aggregate([tx_record.spend_bundle, eve_spend_bundle, launcher_sb]) + # nft_record = TransactionRecord( + # confirmed_at_height=uint32(0), + # created_at_time=uint64(int(time.time())), + # to_puzzle_hash=eve_fullpuz.get_tree_hash(), + # amount=uint64(amount), + # fee_amount=uint64(0), + # confirmed=False, + # sent=uint32(0), + # spend_bundle=full_spend, + # additions=full_spend.additions(), + # removals=full_spend.removals(), + # wallet_id=self.wallet_info.id, + # sent_to=[], + # trade_id=None, + # type=uint32(TransactionType.OUTGOING_TX.value), + # name=bytes32(token_bytes()), + # memos=[], + # ) + # await self.standard_wallet.push_transaction(nft_record) + # return full_spend def get_current_nfts(self) -> List[NFTCoinInfo]: return self.nft_wallet_info.my_nft_coins diff --git a/chia/wallet/wallet_state_manager.py b/chia/wallet/wallet_state_manager.py index 49eed2610391..0e494a349b86 100644 --- a/chia/wallet/wallet_state_manager.py +++ b/chia/wallet/wallet_state_manager.py @@ -7,17 +7,18 @@ from collections import defaultdict from pathlib import Path from secrets import token_bytes -from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Iterator, cast +from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple import aiosqlite from blspy import G1Element, PrivateKey -from chia.consensus.coinbase import pool_parent_id, farmer_parent_id +from chia.consensus.coinbase import farmer_parent_id, pool_parent_id from chia.consensus.constants import ConsensusConstants from chia.pools.pool_puzzles import SINGLETON_LAUNCHER_HASH, solution_to_pool_state from chia.pools.pool_wallet import PoolWallet from chia.protocols import wallet_protocol -from chia.protocols.wallet_protocol import PuzzleSolutionResponse, RespondPuzzleSolution, CoinState +from chia.protocols.wallet_protocol import CoinState, PuzzleSolutionResponse, RespondPuzzleSolution +from chia.server.server import ChiaServer from chia.server.ws_connection import WSChiaConnection from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.program import Program @@ -27,19 +28,20 @@ from chia.types.mempool_inclusion_status import MempoolInclusionStatus from chia.util.byte_types import hexstr_to_bytes from chia.util.config import process_config_start_method +from chia.util.db_synchronous import db_synchronous_on from chia.util.db_wrapper import DBWrapper from chia.util.errors import Err -from chia.util.ints import uint32, uint64, uint128, uint8 -from chia.util.db_synchronous import db_synchronous_on -from chia.wallet.cat_wallet.cat_utils import match_cat_puzzle, construct_cat_puzzle -from chia.wallet.nft_wallet.nft_puzzles import match_nft_puzzle -from chia.wallet.did_wallet.did_wallet_puzzles import match_did_puzzle, create_fullpuz, DID_INNERPUZ_MOD -from chia.wallet.nft_wallet.nft_wallet import NFTWalletInfo -from chia.wallet.cat_wallet.cat_wallet import CATWallet +from chia.util.ints import uint8, uint32, uint64, uint128 from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS +from chia.wallet.cat_wallet.cat_utils import construct_cat_puzzle, match_cat_puzzle +from chia.wallet.cat_wallet.cat_wallet import CATWallet from chia.wallet.derivation_record import DerivationRecord from chia.wallet.derive_keys import master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened +from chia.wallet.did_wallet.did_wallet import DIDWallet +from chia.wallet.did_wallet.did_wallet_puzzles import DID_INNERPUZ_MOD, create_fullpuz, match_did_puzzle from chia.wallet.key_val_store import KeyValStore +from chia.wallet.nft_wallet.nft_puzzles import match_nft_puzzle +from chia.wallet.nft_wallet.nft_wallet import NFTWallet, NFTWalletInfo from chia.wallet.outer_puzzles import AssetType from chia.wallet.puzzle_drivers import PuzzleInfo from chia.wallet.puzzles.cat_loader import CAT_MOD @@ -64,9 +66,6 @@ from chia.wallet.wallet_sync_store import WalletSyncStore from chia.wallet.wallet_transaction_store import WalletTransactionStore from chia.wallet.wallet_user_store import WalletUserStore -from chia.server.server import ChiaServer -from chia.wallet.did_wallet.did_wallet import DIDWallet -from chia.wallet.nft_wallet.nft_wallet import NFTWallet from chia.wallet.wallet_weight_proof_handler import WalletWeightProofHandler @@ -583,6 +582,7 @@ async def determine_coin_type( nft_matched, singleton_curried_args, nft_curried_args = match_nft_puzzle( Program.from_bytes(bytes(coin_spend.puzzle_reveal)) ) + self.log.debug("Matching NFT: %s", nft_matched) if nft_matched: return await self.handle_nft(coin_spend, iter(nft_curried_args)) @@ -718,22 +718,18 @@ async def handle_nft( """ wallet_id = None wallet_type = None - ( - NFT_MOD_HASH, - singleton_struct, - current_owner_did, - nft_transfer_program_hash, - transfer_program_curry_params, - metadata, - ) = curried_args - hint_list = cast(List[bytes32], compute_coin_hints(coin_spend)) + + (_, metadata, metadata_updater_puzzle_hash, inner_puzzle) = curried_args + self.log.debug("Handling NFT: %s", coin_spend) for wallet_info in await self.get_all_wallet_info_entries(): if wallet_info.type == WalletType.NFT: nft_wallet_info = NFTWalletInfo.from_json_dict(json.loads(wallet_info.data)) - for hint in hint_list: - if nft_wallet_info.my_did == hint: + if not nft_wallet_info.did_wallet_id: + # standard NFT wallet + if inner_puzzle.get_tree_hash() in await self.puzzle_store.get_all_puzzle_hashes(): wallet_id = wallet_info.id wallet_type = WalletType.NFT + break return wallet_id, wallet_type async def new_coin_state( diff --git a/tests/wallet/nft_wallet/__init__.py b/tests/wallet/nft_wallet/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/wallet/nft_wallet/test_nft_wallet.py b/tests/wallet/nft_wallet/test_nft_wallet.py new file mode 100644 index 000000000000..6dbba3eee047 --- /dev/null +++ b/tests/wallet/nft_wallet/test_nft_wallet.py @@ -0,0 +1,158 @@ +import asyncio + +# pytestmark = pytest.mark.skip("TODO: Fix tests") +import logging + +import pytest + +from chia.full_node.mempool_manager import MempoolManager +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.ints import uint16 +from chia.wallet.nft_wallet.nft_wallet import NFTWallet +from tests.time_out_assert import time_out_assert, time_out_assert_not_none + +# TODO: remove me +logging.getLogger("aiosqlite").setLevel(logging.INFO) # Too much logging on debug level +logging.getLogger("websockets").setLevel(logging.INFO) # Too much logging on debug level +logging.getLogger("fsevents").setLevel(logging.INFO) # Too much logging on debug level +logging.getLogger("chia.plotting.create_plots").setLevel(logging.INFO) # Too much logging on debug level +logging.getLogger("filelock").setLevel(logging.INFO) # Too much logging on debug level +logging.getLogger("chia.plotting").setLevel(logging.INFO) # Too much logging on debug level + +logging.getLogger("wallet_server").setLevel(logging.INFO) # Too much logging on debug level +logging.getLogger("full_node_server").setLevel(logging.INFO) # Too much logging on debug level + + +async def tx_in_pool(mempool: MempoolManager, tx_id: bytes32): + tx = mempool.get_spendbundle(tx_id) + if tx is None: + return False + return True + + +class TestNFTWallet: + @pytest.mark.parametrize( + "trusted", + [True], + ) + @pytest.mark.asyncio + async def test_nft_wallet_creation_and_transfer(self, three_wallet_nodes, trusted): + num_blocks = 5 + full_nodes, wallets = three_wallet_nodes + full_node_api = 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 + + 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 i 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) + # for i in range(1, num_blocks): + # await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + # for i in range(1, num_blocks): + # await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2)) + + # for i 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) + nft_wallet_0 = await NFTWallet.create_new_nft_wallet(wallet_node_0.wallet_state_manager, wallet_0) + metadata = Program.to( + [ + ("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), + ("h", 0xD4584AD463139FA8C0D9F68F4B59F185), + ] + ) + + tr = await nft_wallet_0.generate_new_nft(metadata) + + await time_out_assert_not_none( + 5, full_node_api.full_node.mempool_manager.get_spendbundle, tr.spend_bundle.name() + ) + + for i in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + + await asyncio.sleep(5) + coins = nft_wallet_0.nft_wallet_info.my_nft_coins + assert len(coins) == 1, "nft not generated" + + spend_bundle_list = await wallet_node_1.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet_1.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 i in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + + nft_wallet_1 = await NFTWallet.create_new_nft_wallet(wallet_node_1.wallet_state_manager, wallet_1) + wallet_1_puzzle = await nft_wallet_1.get_new_puzzle() + + sb = await nft_wallet_0.transfer_nft(coins[0], wallet_1_puzzle.get_tree_hash()) + + assert sb is not None + + # full_sb = await nft_wallet_1.receive_nft(sb) + # await nft_wallet_1.receive_nft(sb) + assert sb is not None + await asyncio.sleep(5) + + for i in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + await asyncio.sleep(5) + + coins = nft_wallet_0.nft_wallet_info.my_nft_coins + assert len(coins) == 0 + coins = nft_wallet_1.nft_wallet_info.my_nft_coins + assert len(coins) == 1 + + # Send it back to original owner + + nsb = await nft_wallet_1.transfer_nft(coins[0], ph) + assert sb is not None + + # full_sb = await nft_wallet_0.receive_nft(nsb) + # await nft_wallet_0.receive_nft(nsb) + assert nsb is not None + await asyncio.sleep(5) + + for i in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + + await asyncio.sleep(5) + coins = nft_wallet_0.nft_wallet_info.my_nft_coins + assert len(coins) == 1 + + coins = nft_wallet_1.nft_wallet_info.my_nft_coins + assert len(coins) == 0 From 7d02c47688237bea4f482106ccf45bddea3f0b56 Mon Sep 17 00:00:00 2001 From: Sebastjan Date: Tue, 3 May 2022 17:38:14 +0200 Subject: [PATCH 29/43] rpc fix --- chia/rpc/wallet_rpc_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index 1771f6363471..0f8fbb563e67 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -32,7 +32,6 @@ from chia.wallet.rl_wallet.rl_wallet import RLWallet from chia.wallet.derive_keys import master_sk_to_farmer_sk, master_sk_to_pool_sk, master_sk_to_wallet_sk from chia.wallet.nft_wallet.nft_wallet import NFTWallet -from chia.wallet.nft_wallet.nft_puzzles import get_uri_list_from_puzzle from chia.wallet.trade_record import TradeRecord from chia.wallet.trading.offer import Offer from chia.wallet.transaction_record import TransactionRecord From bcda9a7c15c0a0f391835e61f3fbacfac05c903e Mon Sep 17 00:00:00 2001 From: ytx1991 Date: Mon, 2 May 2022 22:52:46 -0700 Subject: [PATCH 30/43] Implement nft transfer program --- .../nft_ownership_transfer_program.clvm | 47 ++++++++++++++++++- .../nft_ownership_transfer_program.clvm.hex | 1 + chia/wallet/puzzles/sha256tree.clib | 11 +++++ 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex create mode 100644 chia/wallet/puzzles/sha256tree.clib diff --git a/chia/wallet/puzzles/nft_ownership_transfer_program.clvm b/chia/wallet/puzzles/nft_ownership_transfer_program.clvm index 47ad115824ec..0db9cd82cb12 100644 --- a/chia/wallet/puzzles/nft_ownership_transfer_program.clvm +++ b/chia/wallet/puzzles/nft_ownership_transfer_program.clvm @@ -1,4 +1,47 @@ -(mod ARGS +(mod + ( + ; NFT Singleton ID + NFT_ID + ; SINGLETON_STRUCT is ((SINGLETON_MOD_HASH, (SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH))) + SINGLETON_STRUCT + ; DID of the current owner + CURRENT_OWNER + ; Royalty program puzzle hash + ROYALTY_PROG_PUZHASH + ; DID inner puzzle hash of the current owner + current_did_puzhash + ; New NFT owner's DID + new_owner + ; DID inner puzzle hash of the new owner + new_did_puzhash + ; Royalty program puzzle reveal + royalty_prog_reveal + ; Royalty program solution + royalty_prog_solution + ) - ; main + (include condition_codes.clvm) + (include curry-and-treehash.clinc) + (include sha256tree.clib) + (defconstant CHANGE_OWNERSHIP -22) + + ;; return the full puzzlehash for a singleton with the innerpuzzle curried in + ; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc + (defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash) + (puzzle-hash-of-curried-function (f SINGLETON_STRUCT) + inner_puzzle_hash + (sha256tree SINGLETON_STRUCT) + ) + ) + + ; main + (list + (c (list CHANGE_OWNERSHIP new_owner) + (c (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash SINGLETON_STRUCT current_did_puzhash) (sha256tree royalty_prog_solution) new_owner)) + (c (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (c (f SINGLETON_STRUCT) (c new_did (r (r SINGLETON_STRUCT)))) new_did_puzhash) (sha256tree royalty_prog_solution))) + (a royalty_prog_reveal royalty_prog_solution) + ) + ) + ) + ) ) diff --git a/chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex b/chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex new file mode 100644 index 000000000000..6d7004f8204a --- /dev/null +++ b/chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex @@ -0,0 +1 @@ +ff02ffff01ff04ffff04ffff04ff14ffff04ff81bfff808080ffff04ffff04ff10ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff5fffff04ffff02ff3effff04ff02ffff04ff0bff80808080ff808080808080ffff02ff3effff04ff02ffff04ff8205ffff80808080ff81bf80ff808080ffff04ffff04ff10ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ffff01876e65775f646964ff3b8080ff80808080ff808080808080ffff02ff3effff04ff02ffff04ff8205ffff8080808080ff808080ffff02ff8202ffff8205ff80808080ff8080ffff04ffff01ffffff3f02ff81eaff0401ffff0102ffff02ffff03ff05ffff01ff02ff16ffff04ff02ffff04ff0dffff04ffff0bff1affff0bff3cff2c80ffff0bff1affff0bff1affff0bff3cff1280ff0980ffff0bff1aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffff0bff1affff0bff3cff1880ffff0bff1affff0bff1affff0bff3cff1280ff0580ffff0bff1affff02ff16ffff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080 \ No newline at end of file diff --git a/chia/wallet/puzzles/sha256tree.clib b/chia/wallet/puzzles/sha256tree.clib new file mode 100644 index 000000000000..46e43b5cf28b --- /dev/null +++ b/chia/wallet/puzzles/sha256tree.clib @@ -0,0 +1,11 @@ +( + ;; hash a tree + ;; This is used to calculate a puzzle hash given a puzzle program. + (defun sha256tree + (TREE) + (if (l TREE) + (sha256 2 (sha256tree (f TREE)) (sha256tree (r TREE))) + (sha256 1 TREE) + ) + ) +) \ No newline at end of file From 033003786e1ae0ffb913e110ce743826c453ee12 Mon Sep 17 00:00:00 2001 From: ytx1991 Date: Tue, 3 May 2022 17:01:17 -0700 Subject: [PATCH 31/43] Resolve comment --- .../nft_ownership_transfer_program.clvm | 54 +++++++++++++++---- .../nft_ownership_transfer_program.clvm.hex | 2 +- ...rship_transfer_program.clvm.hex.sha256tree | 1 + chia/wallet/puzzles/nft_transfer_program.clvm | 2 +- 4 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex.sha256tree diff --git a/chia/wallet/puzzles/nft_ownership_transfer_program.clvm b/chia/wallet/puzzles/nft_ownership_transfer_program.clvm index 0db9cd82cb12..ba676025be89 100644 --- a/chia/wallet/puzzles/nft_ownership_transfer_program.clvm +++ b/chia/wallet/puzzles/nft_ownership_transfer_program.clvm @@ -1,9 +1,9 @@ (mod ( - ; NFT Singleton ID - NFT_ID ; SINGLETON_STRUCT is ((SINGLETON_MOD_HASH, (SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH))) SINGLETON_STRUCT + ; CURRY_PARAMS are: (ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH) + CURRY_PARAMS ; DID of the current owner CURRENT_OWNER ; Royalty program puzzle hash @@ -14,16 +14,17 @@ new_owner ; DID inner puzzle hash of the new owner new_did_puzhash - ; Royalty program puzzle reveal - royalty_prog_reveal - ; Royalty program solution - royalty_prog_solution + ; trade_prices_list is a list of (amount type) pairs + ; type 0 is standard chia + ; any other type is a bytes32 mod hash of a launcher + trade_prices_list ) (include condition_codes.clvm) (include curry-and-treehash.clinc) (include sha256tree.clib) (defconstant CHANGE_OWNERSHIP -22) + (defconstant TEN_THOUSAND 10000) ;; return the full puzzlehash for a singleton with the innerpuzzle curried in ; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc @@ -34,12 +35,47 @@ ) ) + ; Given a singleton ID, generate the singleton struct + (defun-inline get_singleton_struct (SINGLETON_STRUCT singleton_id) + (c (f SINGLETON_STRUCT) (c singleton_id (r (r SINGLETON_STRUCT)))) + ) + + (defun-inline cat_settlement_puzzle_hash (CAT_MOD_HASH tail_hash SETTLEMENT_MOD_HASH) + (puzzle-hash-of-curried-function CAT_MOD_HASH + SETTLEMENT_MOD_HASH + (sha256 1 tail_hash) + (sha256 1 CAT_MOD_HASH) + ) + ) + + (defun round_down_to_even (value) + (if (logand value 1) (- value 1) value) + ) + + (defun-inline calculate_percentage (amount percentage) + (f (divmod (* amount percentage) TEN_THOUSAND)) + ) + + ; Loop of the trade prices list and either assert a puzzle announcement or generate xch + (defun parse_trade_prices_list ((ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH) trade_prices_list my_nft_id) + (if trade_prices_list + (c + (if (r (f trade_prices_list)) + (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (cat_settlement_puzzle_hash CAT_MOD_HASH (f (r (f trade_prices_list))) SETTLEMENT_MOD_HASH) (sha256tree (c my_nft_id (list (list ROYALTY_ADDRESS (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE))))))) + (list CREATE_COIN ROYALTY_ADDRESS (round_down_to_even (calculate_percentage (f (f trade_prices_list)) TRADE_PRICE_PERCENTAGE))) + ) + (parse_trade_prices_list (list ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH) (r trade_prices_list) my_nft_id) + ) + () + ) + ) + ; main (list (c (list CHANGE_OWNERSHIP new_owner) - (c (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash SINGLETON_STRUCT current_did_puzhash) (sha256tree royalty_prog_solution) new_owner)) - (c (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (c (f SINGLETON_STRUCT) (c new_did (r (r SINGLETON_STRUCT)))) new_did_puzhash) (sha256tree royalty_prog_solution))) - (a royalty_prog_reveal royalty_prog_solution) + (c (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (get_singleton_struct SINGLETON_STRUCT CURRENT_OWNER) current_did_puzhash) (f (r SINGLETON_STRUCT)) (sha256tree royalty_prog_solution) new_owner)) + (c (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_full_puzzle_hash (get_singleton_struct SINGLETON_STRUCT new_owner) new_did_puzhash) (f (r SINGLETON_STRUCT)) (sha256tree royalty_prog_solution))) + (parse_trade_prices_list CURRY_PARAMS trade_prices_list (f (r SINGLETON_STRUCT))) ) ) ) diff --git a/chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex b/chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex index 6d7004f8204a..d495e4c9351e 100644 --- a/chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex +++ b/chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex @@ -1 +1 @@ -ff02ffff01ff04ffff04ffff04ff14ffff04ff81bfff808080ffff04ffff04ff10ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff5fffff04ffff02ff3effff04ff02ffff04ff0bff80808080ff808080808080ffff02ff3effff04ff02ffff04ff8205ffff80808080ff81bf80ff808080ffff04ffff04ff10ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ffff01876e65775f646964ff3b8080ff80808080ff808080808080ffff02ff3effff04ff02ffff04ff8205ffff8080808080ff808080ffff02ff8202ffff8205ff80808080ff8080ffff04ffff01ffffff3f02ff81eaff0401ffff0102ffff02ffff03ff05ffff01ff02ff16ffff04ff02ffff04ff0dffff04ffff0bff1affff0bff3cff2c80ffff0bff1affff0bff1affff0bff3cff1280ff0980ffff0bff1aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffff0bff1affff0bff3cff1880ffff0bff1affff0bff1affff0bff3cff1280ff0580ffff0bff1affff02ff16ffff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080 \ No newline at end of file +ff02ffff01ff04ffff04ffff04ff38ffff04ff81bfff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff5fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff17ff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e808080ff81bf80ff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff81bfff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e80808080ff808080ffff02ff26ffff04ff02ffff04ff0bffff04ff8202ffffff04ff15ff808080808080808080ff8080ffff04ffff01ffffff3fff0281eaffff3304ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff3480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff5dffff04ff2dffff04ffff0bffff0101ff5380ffff04ffff0bffff0101ff5d80ff80808080808080ffff02ff3effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff128080ff808080ff808080ff8080808080ff808080ffff01ff04ff24ffff04ff09ffff04ffff02ff2effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff128080ff80808080ff8080808080ff0180ffff02ff26ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ff0bff2affff0bff2cff2880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff18ff05ffff010180ffff01ff11ff05ffff010180ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080 \ No newline at end of file diff --git a/chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex.sha256tree b/chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex.sha256tree new file mode 100644 index 000000000000..8c39f0f85ae9 --- /dev/null +++ b/chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex.sha256tree @@ -0,0 +1 @@ +03557552e0bf8b200d8be8089f54786195ab8c07e3e07c39641f2f4f1fc2e48e diff --git a/chia/wallet/puzzles/nft_transfer_program.clvm b/chia/wallet/puzzles/nft_transfer_program.clvm index d71b1e4b259c..3ce24b78bb3f 100644 --- a/chia/wallet/puzzles/nft_transfer_program.clvm +++ b/chia/wallet/puzzles/nft_transfer_program.clvm @@ -77,7 +77,7 @@ (parse_trade_prices_list CURRY_PARAMS trade_prices_list (f (r SINGLETON_STRUCT))) ) ) - ) + ) ; once we find 'u' we don't need to continue looping (defun add_url (METADATA new_url) From 093b2750667ec5d01f1ca4106dbe50b3dab8c819 Mon Sep 17 00:00:00 2001 From: Sebastjan Date: Tue, 3 May 2022 18:45:01 +0200 Subject: [PATCH 32/43] transfer wip --- chia/wallet/nft_wallet/nft_wallet.py | 94 +++++++++++----------- tests/wallet/nft_wallet/test_nft_wallet.py | 15 +--- 2 files changed, 49 insertions(+), 60 deletions(-) diff --git a/chia/wallet/nft_wallet/nft_wallet.py b/chia/wallet/nft_wallet/nft_wallet.py index e2ce1aa9dcee..4f3856d94a1b 100644 --- a/chia/wallet/nft_wallet/nft_wallet.py +++ b/chia/wallet/nft_wallet/nft_wallet.py @@ -453,54 +453,52 @@ async def transfer_nft( puzzle_hash=None, did_hash=None, ): - return None - # self.log.debug("Attempt to transfer a new NFT") - # coin = nft_coin_info.coin - # self.log.debug("Spending NFT with launcher coin %s and metadata: %s", launcher_coin, metadata) - - # full_puzzle = nft_coin_info.full_puzzle - - # condition_list = [make_create_coin_condition(inner_puzzle.get_tree_hash(), amount, [])] - # innersol = solution_for_conditions(condition_list) - # # EVE SPEND BELOW - - # fullsol = Program.to( - # [ - # [launcher_coin.parent_coin_info, launcher_coin.amount], - # eve_coin.amount, - # Program.to( - # [ - # innersol, - # amount, - # 0, - # ] - # ), - # ] - # ) - # list_of_coinspends = [CoinSpend(eve_coin, eve_fullpuz, fullsol)] - # eve_spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([])) - # eve_spend_bundle = await self.sign(eve_spend_bundle) - # full_spend = SpendBundle.aggregate([tx_record.spend_bundle, eve_spend_bundle, launcher_sb]) - # nft_record = TransactionRecord( - # confirmed_at_height=uint32(0), - # created_at_time=uint64(int(time.time())), - # to_puzzle_hash=eve_fullpuz.get_tree_hash(), - # amount=uint64(amount), - # fee_amount=uint64(0), - # confirmed=False, - # sent=uint32(0), - # spend_bundle=full_spend, - # additions=full_spend.additions(), - # removals=full_spend.removals(), - # wallet_id=self.wallet_info.id, - # sent_to=[], - # trade_id=None, - # type=uint32(TransactionType.OUTGOING_TX.value), - # name=bytes32(token_bytes()), - # memos=[], - # ) - # await self.standard_wallet.push_transaction(nft_record) - # return full_spend + self.log.debug("Attempt to transfer a new NFT") + coin = nft_coin_info.coin + self.log.debug("Transfering NFT coin %s", coin) + + full_puzzle = nft_coin_info.full_puzzle + amount = coin.amount + condition_list = [make_create_coin_condition(puzzle_hash, amount, [])] + innersol = solution_for_conditions(condition_list) + lineage_proof = nft_coin_info.lineage_proof + fullsol = Program.to( + [ + [lineage_proof.parent_name, lineage_proof.inner_puzzle_hash, lineage_proof.amount], + coin.amount, + Program.to( + [ + innersol, + amount, + 0, + ] + ), + ] + ) + list_of_coinspends = [CoinSpend(coin, full_puzzle, fullsol)] + spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([])) + spend_bundle = await self.sign(spend_bundle) + full_spend = SpendBundle.aggregate([spend_bundle]) + nft_record = TransactionRecord( + confirmed_at_height=uint32(0), + created_at_time=uint64(int(time.time())), + to_puzzle_hash=full_puzzle.get_tree_hash(), + amount=uint64(amount), + fee_amount=uint64(0), + confirmed=False, + sent=uint32(0), + spend_bundle=full_spend, + additions=full_spend.additions(), + removals=full_spend.removals(), + wallet_id=self.wallet_info.id, + sent_to=[], + trade_id=None, + type=uint32(TransactionType.OUTGOING_TX.value), + name=bytes32(token_bytes()), + memos=[], + ) + await self.standard_wallet.push_transaction(nft_record) + return full_spend def get_current_nfts(self) -> List[NFTCoinInfo]: return self.nft_wallet_info.my_nft_coins diff --git a/tests/wallet/nft_wallet/test_nft_wallet.py b/tests/wallet/nft_wallet/test_nft_wallet.py index 6dbba3eee047..9fd7605c116c 100644 --- a/tests/wallet/nft_wallet/test_nft_wallet.py +++ b/tests/wallet/nft_wallet/test_nft_wallet.py @@ -108,18 +108,9 @@ async def test_nft_wallet_creation_and_transfer(self, three_wallet_nodes, truste coins = nft_wallet_0.nft_wallet_info.my_nft_coins assert len(coins) == 1, "nft not generated" - spend_bundle_list = await wallet_node_1.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet_1.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 i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - nft_wallet_1 = await NFTWallet.create_new_nft_wallet(wallet_node_1.wallet_state_manager, wallet_1) - wallet_1_puzzle = await nft_wallet_1.get_new_puzzle() - sb = await nft_wallet_0.transfer_nft(coins[0], wallet_1_puzzle.get_tree_hash()) + sb = await nft_wallet_0.transfer_nft(coins[0], ph1) assert sb is not None @@ -132,8 +123,8 @@ async def test_nft_wallet_creation_and_transfer(self, three_wallet_nodes, truste await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) await asyncio.sleep(5) - coins = nft_wallet_0.nft_wallet_info.my_nft_coins - assert len(coins) == 0 + # coins = nft_wallet_0.nft_wallet_info.my_nft_coins + # assert len(coins) == 0 coins = nft_wallet_1.nft_wallet_info.my_nft_coins assert len(coins) == 1 From ee8eda2faaf33d58b2ac82d69804d33beceb8692 Mon Sep 17 00:00:00 2001 From: Sebastjan Date: Wed, 4 May 2022 10:17:11 +0200 Subject: [PATCH 33/43] checkpoint --- chia/wallet/nft_wallet/nft_wallet.py | 36 ++++++++++++++------ chia/wallet/puzzles/nft_state_layer.clvm | 2 +- chia/wallet/puzzles/nft_state_layer.clvm.hex | 2 +- chia/wallet/wallet_state_manager.py | 14 +++++--- tests/wallet/nft_wallet/test_nft_wallet.py | 35 ++++++++++--------- 5 files changed, 54 insertions(+), 35 deletions(-) diff --git a/chia/wallet/nft_wallet/nft_wallet.py b/chia/wallet/nft_wallet/nft_wallet.py index 4f3856d94a1b..6406a36879a4 100644 --- a/chia/wallet/nft_wallet/nft_wallet.py +++ b/chia/wallet/nft_wallet/nft_wallet.py @@ -1,3 +1,4 @@ +from chia.wallet.util.compute_memos import compute_memos import json import logging import time @@ -96,11 +97,12 @@ async def create_new_nft_wallet( info_as_string = json.dumps(self.nft_wallet_info.to_json_dict()) self.wallet_info = await wallet_state_manager.user_store.create_wallet( - "NFT Wallet", uint32(WalletType.NFT.value), info_as_string + "NFT Wallet" if not name else name, uint32(WalletType.NFT.value), info_as_string ) if self.wallet_info is None: raise ValueError("Internal Error") self.wallet_id = self.wallet_info.id + self.log.debug("NFT wallet id: %r and standard wallet id: %r", self.wallet_id, self.standard_wallet.wallet_id) await self.wallet_state_manager.add_new_wallet(self, self.wallet_info.id) self.log.debug("Generated a new NFT wallet: %s", self.__dict__) # await self.wallet_state_manager.update_wallet_puzzle_hashes(self.wallet_id) @@ -147,7 +149,7 @@ async def add_nft_coin(self, coin: Coin, spent_height: uint32, in_transaction: b async def coin_added(self, coin: Coin, height: uint32, in_transaction: bool) -> None: """Notification from wallet state manager that wallet has been received.""" - self.log.info(f"NFT wallet has been notified that {coin} was added") + self.log.info(f"NFT wallet %s has been notified that {coin} was added", self.wallet_info.name) for coin_info in self.nft_wallet_info.my_nft_coins: if coin_info.coin == coin: return @@ -172,6 +174,7 @@ async def coin_added(self, coin: Coin, height: uint32, in_transaction: bool) -> await self.puzzle_solution_received(cs, in_transaction=in_transaction) async def puzzle_solution_received(self, coin_spend: CoinSpend, in_transaction: bool) -> None: + self.log.debug("Puzzle solution received to wallet: %s", self.wallet_info) coin_name = coin_spend.coin.name() puzzle: Program = Program.from_bytes(bytes(coin_spend.puzzle_reveal)) solution: Program = Program.from_bytes(bytes(coin_spend.solution)).rest().rest().first().first() @@ -181,8 +184,8 @@ async def puzzle_solution_received(self, coin_spend: CoinSpend, in_transaction: params = singleton_curried_args.first() self.log.info(f"found the info for NFT coin {coin_name} {inner_puzzle} {params}") singleton_id = bytes32(params.rest().first().atom) - new_inner_puzzle = inner_puzzle - puzhash = None + new_inner_puzzle = None + parent_inner_puzhash = singleton_curried_args.rest().first().get_tree_hash() self.log.debug("Before spend metadata: %s %s \n%s", metadata, singleton_id, disassemble(solution)) for condition in solution.rest().first().rest().as_iter(): self.log.debug("Checking solution condition: %s", disassemble(condition)) @@ -195,11 +198,16 @@ async def puzzle_solution_received(self, coin_spend: CoinSpend, in_transaction: # metadata update # (-24 (meta updater puzzle) url) metadata = condition.rest().rest().first() + metadata - elif condition_code == 51: + elif condition_code == 51 and int_from_bytes(condition.rest().rest().first().atom) == 1: puzhash = bytes32(condition.rest().first().atom) + self.log.debug("Got back puzhash from solution: %s", puzhash) record: DerivationRecord = ( await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(puzhash) ) + if record is None: + # we potentially sent it somewhere + self.remove_coin(coin_spend.coin, in_transaction=in_transaction) + return new_inner_puzzle = puzzle_for_pk(record.pubkey) else: @@ -250,7 +258,7 @@ async def puzzle_solution_received(self, coin_spend: CoinSpend, in_transaction: await self.add_coin( child_coin, child_puzzle, - LineageProof(parent_coin.parent_coin_info, inner_puzzle.get_tree_hash(), parent_coin.amount), + LineageProof(parent_coin.parent_coin_info, parent_inner_puzhash, parent_coin.amount), in_transaction=in_transaction, ) else: @@ -299,6 +307,9 @@ def puzzle_for_pk(self, pk: G1Element) -> Program: else: raise NotImplementedError 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 generate_new_nft(self, metadata: Program) -> Optional[TransactionRecord]: @@ -353,7 +364,6 @@ async def generate_new_nft(self, metadata: Program) -> Optional[TransactionRecor condition_list = [make_create_coin_condition(inner_puzzle.get_tree_hash(), amount, [])] innersol = solution_for_conditions(condition_list) # EVE SPEND BELOW - fullsol = Program.to( [ [launcher_coin.parent_coin_info, launcher_coin.amount], @@ -450,18 +460,20 @@ async def sign(self, spend_bundle: SpendBundle) -> SpendBundle: async def transfer_nft( self, nft_coin_info: NFTCoinInfo, - puzzle_hash=None, + puzzle_hash: bytes32 = None, did_hash=None, ): self.log.debug("Attempt to transfer a new NFT") coin = nft_coin_info.coin - self.log.debug("Transfering NFT coin %s", coin) + self.log.debug("Transfering NFT coin %r to puzhash: %s", nft_coin_info, puzzle_hash) full_puzzle = nft_coin_info.full_puzzle amount = coin.amount - condition_list = [make_create_coin_condition(puzzle_hash, amount, [])] + condition_list = [make_create_coin_condition(puzzle_hash, amount, [puzzle_hash])] + self.log.debug("Condition for new coin: %r", condition_list) innersol = solution_for_conditions(condition_list) lineage_proof = nft_coin_info.lineage_proof + self.log.debug("Inner solution: %r", disassemble(innersol)) fullsol = Program.to( [ [lineage_proof.parent_name, lineage_proof.inner_puzzle_hash, lineage_proof.amount], @@ -479,6 +491,7 @@ async def transfer_nft( spend_bundle = SpendBundle(list_of_coinspends, AugSchemeMPL.aggregate([])) spend_bundle = await self.sign(spend_bundle) full_spend = SpendBundle.aggregate([spend_bundle]) + self.log.debug("Memos are: %r", list(compute_memos(full_spend).items())) nft_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), @@ -495,9 +508,10 @@ async def transfer_nft( trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=bytes32(token_bytes()), - memos=[], + memos=list(compute_memos(full_spend).items()), ) await self.standard_wallet.push_transaction(nft_record) + full_spend.debug() return full_spend def get_current_nfts(self) -> List[NFTCoinInfo]: diff --git a/chia/wallet/puzzles/nft_state_layer.clvm b/chia/wallet/puzzles/nft_state_layer.clvm index 200492ad2e37..a85732ff0530 100644 --- a/chia/wallet/puzzles/nft_state_layer.clvm +++ b/chia/wallet/puzzles/nft_state_layer.clvm @@ -31,7 +31,7 @@ (if conditions (if (= (f (f conditions)) CREATE_COIN) (if (logand (f (r (r (f conditions)))) ONE) - (c (list CREATE_COIN (nft_state_layer_puzzle_hash NFT_STATE_LAYER_MOD_HASH METADATA METADATA_UPDATER_PUZZLE_HASH (f (r (f conditions)))) my_amount) (r conditions)) + (c (list CREATE_COIN (nft_state_layer_puzzle_hash NFT_STATE_LAYER_MOD_HASH METADATA METADATA_UPDATER_PUZZLE_HASH (f (r (f conditions)))) my_amount (f (r (r (r (f conditions))))) ) (r conditions)) (c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (list METADATA (r conditions)) my_amount)) ) (c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (list METADATA (r conditions)) my_amount)) diff --git a/chia/wallet/puzzles/nft_state_layer.clvm.hex b/chia/wallet/puzzles/nft_state_layer.clvm.hex index acae6e5c5888..30bd28de7774 100644 --- a/chia/wallet/puzzles/nft_state_layer.clvm.hex +++ b/chia/wallet/puzzles/nft_state_layer.clvm.hex @@ -1 +1 @@ -ff02ffff01ff04ffff04ff20ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ff17ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfff8080808080808080ffff04ffff01ffffffff4902ff3304ffff0101ff0281e8ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff2cffff0bff24ff3880ffff0bff2cffff0bff2cffff0bff24ff3480ff0980ffff0bff2cff0bffff0bff24ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ff3c80ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff820167ff80808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ff0bffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff2cffff0bff24ff3080ffff0bff2cffff0bff2cffff0bff24ff3480ff0580ffff0bff2cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff24ff2480ff8080808080ffff0bff24ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff57ffff01ff02ffff03ffff09ff820117ff2880ffff01ff02ffff03ffff18ff820597ff2480ffff01ff04ffff04ff28ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff820297ffff04ffff0bff24ff0b80ffff04ffff02ff2effff04ff02ffff04ff27ff80808080ffff04ffff0bff24ff0580ff8080808080808080ffff04ff2fff80808080ff81d780ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ff8080ff0180ff018080 \ No newline at end of file +ff02ffff01ff04ffff04ff20ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ff17ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfff8080808080808080ffff04ffff01ffffffff4902ff3304ffff0101ff0281e8ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff2cffff0bff24ff3880ffff0bff2cffff0bff2cffff0bff24ff3480ff0980ffff0bff2cff0bffff0bff24ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ff3c80ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff820167ff80808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ff0bffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff2cffff0bff24ff3080ffff0bff2cffff0bff2cffff0bff24ff3480ff0580ffff0bff2cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff24ff2480ff8080808080ffff0bff24ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff57ffff01ff02ffff03ffff09ff820117ff2880ffff01ff02ffff03ffff18ff820597ff2480ffff01ff04ffff04ff28ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff820297ffff04ffff0bff24ff0b80ffff04ffff02ff2effff04ff02ffff04ff27ff80808080ffff04ffff0bff24ff0580ff8080808080808080ffff04ff2fffff04ff820b97ff8080808080ff81d780ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ff8080ff0180ff018080 \ No newline at end of file diff --git a/chia/wallet/wallet_state_manager.py b/chia/wallet/wallet_state_manager.py index 0e494a349b86..3db8875e0917 100644 --- a/chia/wallet/wallet_state_manager.py +++ b/chia/wallet/wallet_state_manager.py @@ -724,12 +724,14 @@ async def handle_nft( for wallet_info in await self.get_all_wallet_info_entries(): if wallet_info.type == WalletType.NFT: nft_wallet_info = NFTWalletInfo.from_json_dict(json.loads(wallet_info.data)) + self.log.debug( + "Checking NFT wallet %r and inner puzzle %s", wallet_info.name, inner_puzzle.get_tree_hash() + ) if not nft_wallet_info.did_wallet_id: # standard NFT wallet - if inner_puzzle.get_tree_hash() in await self.puzzle_store.get_all_puzzle_hashes(): - wallet_id = wallet_info.id - wallet_type = WalletType.NFT - break + wallet_id = wallet_info.id + wallet_type = WalletType.NFT + break return wallet_id, wallet_type async def new_coin_state( @@ -1079,7 +1081,9 @@ async def coin_added( if existing is not None: return None - self.log.info(f"Adding coin: {coin} at {height} wallet_id:{wallet_id}") + self.log.info( + f"Adding record to state manager coin: {coin} at {height} wallet_id:{wallet_id} and type: {wallet_type}" + ) farmer_reward = False pool_reward = False if self.is_farmer_reward(height, coin.parent_coin_info): diff --git a/tests/wallet/nft_wallet/test_nft_wallet.py b/tests/wallet/nft_wallet/test_nft_wallet.py index 9fd7605c116c..0bb321ab8a63 100644 --- a/tests/wallet/nft_wallet/test_nft_wallet.py +++ b/tests/wallet/nft_wallet/test_nft_wallet.py @@ -2,6 +2,7 @@ # pytestmark = pytest.mark.skip("TODO: Fix tests") import logging +from tests.conftest import two_wallet_nodes import pytest @@ -21,6 +22,7 @@ logging.getLogger("chia.plotting.create_plots").setLevel(logging.INFO) # Too much logging on debug level logging.getLogger("filelock").setLevel(logging.INFO) # Too much logging on debug level logging.getLogger("chia.plotting").setLevel(logging.INFO) # Too much logging on debug level +logging.getLogger("cchia.full_node.block_store").setLevel(logging.INFO) # Too much logging on debug level logging.getLogger("wallet_server").setLevel(logging.INFO) # Too much logging on debug level logging.getLogger("full_node_server").setLevel(logging.INFO) # Too much logging on debug level @@ -39,9 +41,9 @@ class TestNFTWallet: [True], ) @pytest.mark.asyncio - async def test_nft_wallet_creation_and_transfer(self, three_wallet_nodes, trusted): + async def test_nft_wallet_creation_and_transfer(self, two_wallet_nodes, trusted): num_blocks = 5 - full_nodes, wallets = three_wallet_nodes + full_nodes, wallets = two_wallet_nodes full_node_api = full_nodes[0] full_node_server = full_node_api.server wallet_node_0, server_0 = wallets[0] @@ -86,8 +88,10 @@ async def test_nft_wallet_creation_and_transfer(self, three_wallet_nodes, truste # for i 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) - nft_wallet_0 = await NFTWallet.create_new_nft_wallet(wallet_node_0.wallet_state_manager, wallet_0) + # await time_out_assert(15, wallet_0.get_pending_change_balance, 0) + nft_wallet_0 = await NFTWallet.create_new_nft_wallet( + wallet_node_0.wallet_state_manager, wallet_0, name="NFT WALLET 1" + ) metadata = Program.to( [ ("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), @@ -108,28 +112,25 @@ async def test_nft_wallet_creation_and_transfer(self, three_wallet_nodes, truste coins = nft_wallet_0.nft_wallet_info.my_nft_coins assert len(coins) == 1, "nft not generated" - nft_wallet_1 = await NFTWallet.create_new_nft_wallet(wallet_node_1.wallet_state_manager, wallet_1) - - sb = await nft_wallet_0.transfer_nft(coins[0], ph1) + nft_wallet_1 = await NFTWallet.create_new_nft_wallet( + wallet_node_1.wallet_state_manager, wallet_1, name="NFT WALLET 2" + ) + # nft_puzzle = await nft_wallet_1.get_new_puzzle() + sb = await nft_wallet_0.transfer_nft(coins[0], ph1) # nft_puzzle.get_tree_hash()) assert sb is not None + await asyncio.sleep(3) + await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) - # full_sb = await nft_wallet_1.receive_nft(sb) - # await nft_wallet_1.receive_nft(sb) - assert sb is not None + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) await asyncio.sleep(5) - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - await asyncio.sleep(5) - - # coins = nft_wallet_0.nft_wallet_info.my_nft_coins - # assert len(coins) == 0 + coins = nft_wallet_0.nft_wallet_info.my_nft_coins + assert len(coins) == 0 coins = nft_wallet_1.nft_wallet_info.my_nft_coins assert len(coins) == 1 # Send it back to original owner - nsb = await nft_wallet_1.transfer_nft(coins[0], ph) assert sb is not None From 523c6766f28dba1450b30f540671efbd2dca0bea Mon Sep 17 00:00:00 2001 From: Sebastjan Date: Wed, 4 May 2022 19:04:56 +0200 Subject: [PATCH 34/43] basic rpc api working --- chia/rpc/wallet_rpc_api.py | 39 ++- chia/wallet/nft_wallet/nft_puzzles.py | 6 + chia/wallet/nft_wallet/nft_wallet.py | 9 +- tests/wallet/nft_wallet/test_nft_wallet.py | 326 ++++++++++++++------- 4 files changed, 259 insertions(+), 121 deletions(-) diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index 0f8fbb563e67..93b8bb5ed383 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -1,4 +1,5 @@ import asyncio +from chia.wallet.nft_wallet.nft_puzzles import get_uri_list_from_puzzle import dataclasses import json import logging @@ -122,8 +123,8 @@ def get_routes(self) -> Dict[str, Callable]: "/did_transfer_did": self.did_transfer_did, # NFT Wallet "/nft_mint_nft": self.nft_mint_nft, - "/nft_get_current_nfts": self.nft_get_current_nfts, - # "/nft_transfer_nft": self.nft_transfer_nft, + "/nft_get_nfts": self.nft_get_nfts, + "/nft_transfer_nft": self.nft_transfer_nft, # RL wallet "/rl_set_user_info": self.rl_set_user_info, "/send_clawback_transaction:": self.send_clawback_transaction, @@ -572,7 +573,6 @@ async def create_new_wallet(self, request: Dict): nft_wallet: NFTWallet = await NFTWallet.create_new_nft_wallet( wallet_state_manager, main_wallet, - request["did_wallet_id"], ) assert nft_wallet.nft_wallet_info is not None return { @@ -1292,21 +1292,26 @@ async def did_transfer_did(self, request): ########################################################################################## async def nft_mint_nft(self, request): + log.debug("Got minting RPC request: %s", request) wallet_id = uint32(request["wallet_id"]) nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id] - address = request["artist_address"] + address = request.get("artist_address") if isinstance(address, str): - address = decode_puzzle_hash(address) + puzzle_hash = decode_puzzle_hash(address) + elif address is None: + puzzle_hash = await nft_wallet.standard_wallet.get_new_puzzlehash() + else: + puzzle_hash = address metadata = Program.to( [ ("u", request["uris"]), ("h", request["hash"]), ] ) - await nft_wallet.generate_new_nft(metadata, request["artist_percentage"], address) - return {"wallet_id": wallet_id, "success": True} + nft_record = await nft_wallet.generate_new_nft(metadata, puzzle_hash) + return {"wallet_id": wallet_id, "success": True, "nft": nft_record} - async def nft_get_current_nfts(self, request): + async def nft_get_nfts(self, request): wallet_id = uint32(request["wallet_id"]) nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id] nfts = nft_wallet.get_current_nfts() @@ -1314,7 +1319,23 @@ async def nft_get_current_nfts(self, request): for nft in nfts: uri = get_uri_list_from_puzzle(nft.full_puzzle) nft_uri_pairs.append((nft, uri)) - return {"wallet_id": wallet_id, "success": True, "nfts": nft_uri_pairs} + return {"wallet_id": wallet_id, "success": True, "nft_list": nft_uri_pairs} + + async def nft_transfer_nft(self, request): + assert self.service.wallet_state_manager is not None + wallet_id = int(request["wallet_id"]) + address = request["target_address"] + if isinstance(address, str): + puzzle_hash = decode_puzzle_hash(address) + else: + return dict(success=False, error="target_address parameter missing") + nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id] + try: + sb = await nft_wallet.transfer_nft(bytes32.from_hexstr(request["nft_coin_id"]), puzzle_hash) + return {"wallet_id": wallet_id, "success": True, "spend_bundle": sb} + except Exception as e: + log.exception(f"Failed to transfer NFT: {e}") + return {"success": False, "error": str(e)} async def nft_add_url(self, request): raise NotImplementedError diff --git a/chia/wallet/nft_wallet/nft_puzzles.py b/chia/wallet/nft_wallet/nft_puzzles.py index 053416321c64..d6c573da7e4e 100644 --- a/chia/wallet/nft_wallet/nft_puzzles.py +++ b/chia/wallet/nft_wallet/nft_puzzles.py @@ -49,6 +49,12 @@ def create_full_puzzle_with_nft_puzzle(singleton_id: bytes32, inner_puzzle: Prog return full_puzzle +def get_uri_list_from_puzzle(puzzle: Program): + matched, singleton_args, curried_args = match_nft_puzzle(puzzle) + if matched: + return list(curried_args[1].as_iter()) + + def create_full_puzzle( singleton_id: bytes32, metadata: Program, metadata_updater_puzhash: bytes32, inner_puzzle: Program ) -> Program: diff --git a/chia/wallet/nft_wallet/nft_wallet.py b/chia/wallet/nft_wallet/nft_wallet.py index 6406a36879a4..391f7c923cea 100644 --- a/chia/wallet/nft_wallet/nft_wallet.py +++ b/chia/wallet/nft_wallet/nft_wallet.py @@ -312,7 +312,9 @@ def puzzle_for_pk(self, pk: G1Element) -> Program: ) return provenance_puzzle - async def generate_new_nft(self, metadata: Program) -> Optional[TransactionRecord]: + async def generate_new_nft( + self, metadata: Program, target_puzzle_hash: bytes32 = None + ) -> Optional[TransactionRecord]: """ This must be called under the wallet state manager lock """ @@ -325,6 +327,7 @@ async def generate_new_nft(self, metadata: Program) -> Optional[TransactionRecor genesis_launcher_puz = nft_puzzles.LAUNCHER_PUZZLE launcher_coin = Coin(origin.name(), genesis_launcher_puz.get_tree_hash(), uint64(amount)) self.log.debug("Generating NFT with launcher coin %s and metadata: %s", launcher_coin, metadata) + inner_puzzle = await self.standard_wallet.get_new_puzzle() # singleton eve eve_fullpuz = nft_puzzles.create_full_puzzle( @@ -361,7 +364,9 @@ async def generate_new_nft(self, metadata: Program) -> Optional[TransactionRecor if tx_record is None or tx_record.spend_bundle is None: return None - condition_list = [make_create_coin_condition(inner_puzzle.get_tree_hash(), amount, [])] + if not target_puzzle_hash: + target_puzzle_hash = inner_puzzle.get_tree_hash() + condition_list = [make_create_coin_condition(target_puzzle_hash, amount, [])] innersol = solution_for_conditions(condition_list) # EVE SPEND BELOW fullsol = Program.to( diff --git a/tests/wallet/nft_wallet/test_nft_wallet.py b/tests/wallet/nft_wallet/test_nft_wallet.py index 0bb321ab8a63..dfa79a582423 100644 --- a/tests/wallet/nft_wallet/test_nft_wallet.py +++ b/tests/wallet/nft_wallet/test_nft_wallet.py @@ -1,4 +1,7 @@ import asyncio +from chia.types.spend_bundle import SpendBundle +from chia.util.bech32m import encode_puzzle_hash +from chia.rpc.wallet_rpc_api import WalletRpcApi # pytestmark = pytest.mark.skip("TODO: Fix tests") import logging @@ -15,6 +18,14 @@ from chia.wallet.nft_wallet.nft_wallet import NFTWallet from tests.time_out_assert import time_out_assert, time_out_assert_not_none + +async def tx_in_pool(mempool: MempoolManager, tx_id: bytes32): + tx = mempool.get_spendbundle(tx_id) + if tx is None: + return False + return True + + # TODO: remove me logging.getLogger("aiosqlite").setLevel(logging.INFO) # Too much logging on debug level logging.getLogger("websockets").setLevel(logging.INFO) # Too much logging on debug level @@ -22,129 +33,224 @@ logging.getLogger("chia.plotting.create_plots").setLevel(logging.INFO) # Too much logging on debug level logging.getLogger("filelock").setLevel(logging.INFO) # Too much logging on debug level logging.getLogger("chia.plotting").setLevel(logging.INFO) # Too much logging on debug level -logging.getLogger("cchia.full_node.block_store").setLevel(logging.INFO) # Too much logging on debug level +logging.getLogger("chia.full_node.block_store").setLevel(logging.INFO) # Too much logging on debug level logging.getLogger("wallet_server").setLevel(logging.INFO) # Too much logging on debug level logging.getLogger("full_node_server").setLevel(logging.INFO) # Too much logging on debug level -async def tx_in_pool(mempool: MempoolManager, tx_id: bytes32): - tx = mempool.get_spendbundle(tx_id) - if tx is None: - return False - return True +@pytest.mark.parametrize( + "trusted", + [True], +) +@pytest.mark.asyncio +async def test_nft_wallet_creation_and_transfer(two_wallet_nodes, trusted): + num_blocks = 5 + full_nodes, wallets = two_wallet_nodes + full_node_api = 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 + + 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 i 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) + # for i in range(1, num_blocks): + # await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + # for i in range(1, num_blocks): + # await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2)) + + # for i 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) + nft_wallet_0 = await NFTWallet.create_new_nft_wallet( + wallet_node_0.wallet_state_manager, wallet_0, name="NFT WALLET 1" + ) + metadata = Program.to( + [ + ("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), + ("h", 0xD4584AD463139FA8C0D9F68F4B59F185), + ] + ) + + tr = await nft_wallet_0.generate_new_nft(metadata) + await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, tr.spend_bundle.name()) -class TestNFTWallet: - @pytest.mark.parametrize( - "trusted", - [True], + for i in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + + await asyncio.sleep(5) + coins = nft_wallet_0.nft_wallet_info.my_nft_coins + assert len(coins) == 1, "nft not generated" + + metadata = Program.to( + [ + ("u", ["https://www.test.net/logo.svg"]), + ("h", 0xD4584AD463139FA8C0D9F68F4B59F181), + ] + ) + + tr = await nft_wallet_0.generate_new_nft(metadata) + + await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, tr.spend_bundle.name()) + + for i in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + + await asyncio.sleep(5) + coins = nft_wallet_0.nft_wallet_info.my_nft_coins + assert len(coins) == 2, "nft not generated" + + nft_wallet_1 = await NFTWallet.create_new_nft_wallet( + wallet_node_1.wallet_state_manager, wallet_1, name="NFT WALLET 2" ) - @pytest.mark.asyncio - async def test_nft_wallet_creation_and_transfer(self, two_wallet_nodes, trusted): - num_blocks = 5 - full_nodes, wallets = two_wallet_nodes - full_node_api = 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 - - 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 i 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) - # for i in range(1, num_blocks): - # await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - # for i in range(1, num_blocks): - # await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2)) - - # for i 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) - nft_wallet_0 = await NFTWallet.create_new_nft_wallet( - wallet_node_0.wallet_state_manager, wallet_0, name="NFT WALLET 1" - ) - metadata = Program.to( - [ - ("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), - ("h", 0xD4584AD463139FA8C0D9F68F4B59F185), - ] - ) - - tr = await nft_wallet_0.generate_new_nft(metadata) - - await time_out_assert_not_none( - 5, full_node_api.full_node.mempool_manager.get_spendbundle, tr.spend_bundle.name() - ) - - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - - await asyncio.sleep(5) - coins = nft_wallet_0.nft_wallet_info.my_nft_coins - assert len(coins) == 1, "nft not generated" - - nft_wallet_1 = await NFTWallet.create_new_nft_wallet( - wallet_node_1.wallet_state_manager, wallet_1, name="NFT WALLET 2" - ) - # nft_puzzle = await nft_wallet_1.get_new_puzzle() - sb = await nft_wallet_0.transfer_nft(coins[0], ph1) # nft_puzzle.get_tree_hash()) - - assert sb is not None - await asyncio.sleep(3) - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + sb = await nft_wallet_0.transfer_nft(coins[0], ph1) + + assert sb is not None + await asyncio.sleep(3) + await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + await asyncio.sleep(5) + coins = nft_wallet_0.nft_wallet_info.my_nft_coins + assert len(coins) == 1 + coins = nft_wallet_1.nft_wallet_info.my_nft_coins + assert len(coins) == 1 + + # Send it back to original owner + nsb = await nft_wallet_1.transfer_nft(coins[0], ph) + assert sb is not None + + # full_sb = await nft_wallet_0.receive_nft(nsb) + # await nft_wallet_0.receive_nft(nsb) + assert nsb is not None + await asyncio.sleep(5) + + for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - await asyncio.sleep(5) - coins = nft_wallet_0.nft_wallet_info.my_nft_coins - assert len(coins) == 0 - coins = nft_wallet_1.nft_wallet_info.my_nft_coins - assert len(coins) == 1 + await asyncio.sleep(5) + coins = nft_wallet_0.nft_wallet_info.my_nft_coins + assert len(coins) == 2 + + coins = nft_wallet_1.nft_wallet_info.my_nft_coins + assert len(coins) == 0 + + +@pytest.mark.parametrize( + "trusted", + [True], +) +@pytest.mark.asyncio +async def test_nft_wallet_rpc_creation_and_list(two_wallet_nodes, trusted): + num_blocks = 5 + full_nodes, wallets = two_wallet_nodes + full_node_api = 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 - # Send it back to original owner - nsb = await nft_wallet_1.transfer_nft(coins[0], ph) - assert sb is not None + ph = await wallet_0.get_new_puzzlehash() + ph1 = await wallet_1.get_new_puzzlehash() - # full_sb = await nft_wallet_0.receive_nft(nsb) - # await nft_wallet_0.receive_nft(nsb) - assert nsb is not None - await asyncio.sleep(5) + 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"] = {} - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + 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) - await asyncio.sleep(5) - coins = nft_wallet_0.nft_wallet_info.my_nft_coins - assert len(coins) == 1 + for i in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + + api_0 = WalletRpcApi(wallet_node_0) + nft_wallet_0 = await api_0.create_new_wallet(dict(wallet_type="nft_wallet", name="NFT WALLET 1")) + assert isinstance(nft_wallet_0, dict) + assert nft_wallet_0.get("success") + nft_wallet_0_id = nft_wallet_0["wallet_id"] + + tr1 = await api_0.nft_mint_nft( + { + "wallet_id": nft_wallet_0_id, + "artist_address": ph, + "hash": 0xD4584AD463139FA8C0D9F68F4B59F185, + "uris": ["https://www.chia.net/img/branding/chia-logo.svg"], + } + ) + + assert isinstance(tr1, dict) + assert tr1.get("success") + sb = tr1["nft"].spend_bundle + + await asyncio.sleep(3) + 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)) + tr2 = await api_0.nft_mint_nft( + { + "wallet_id": nft_wallet_0_id, + "artist_address": ph, + "hash": 0xD4584AD463139FA8C0D9F68F4B59F184, + "uris": ["https://chialisp.com/img/logo.svg"], + } + ) + assert isinstance(tr2, dict) + assert tr2.get("success") + sb = tr2["nft"].spend_bundle + await asyncio.sleep(3) + 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)) + coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id)) + assert isinstance(coins_response, dict) + assert coins_response.get("success") + import pprint - coins = nft_wallet_1.nft_wallet_info.my_nft_coins - assert len(coins) == 0 + pprint.pprint(coins_response) + coins = coins_response["nft_list"] + assert len(coins) == 2 + uris = [] + [uris.append(x[1][0].as_python()[1]) for x in coins] + print(uris) + assert len(uris) == 2 + assert b"https://chialisp.com/img/logo.svg" in uris From c470401a1126c40260f672042bb192c1fbc03002 Mon Sep 17 00:00:00 2001 From: Sebastjan Date: Thu, 5 May 2022 10:48:34 +0200 Subject: [PATCH 35/43] pre commit hooks passing, tests pass --- chia/rpc/wallet_rpc_api.py | 34 ++++++---- chia/wallet/nft_wallet/nft_puzzles.py | 3 +- chia/wallet/nft_wallet/nft_wallet.py | 34 +++++----- .../puzzles/nft_transfer_program.clvm.hex | 2 +- chia/wallet/wallet_state_manager.py | 14 +--- tests/wallet/nft_wallet/test_nft_wallet.py | 65 +++++++------------ 6 files changed, 65 insertions(+), 87 deletions(-) diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index 8800434e3dc4..3891726893a0 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -1,38 +1,45 @@ import asyncio -from chia.wallet.nft_wallet.nft_puzzles import get_uri_list_from_puzzle import dataclasses import json import logging from pathlib import Path -from typing import Callable, Dict, List, Optional, Tuple, Set, Any +from typing import Any, Callable, Dict, List, Optional, Set, Tuple + +from blspy import G1Element, PrivateKey -from blspy import PrivateKey, G1Element -from chia.types.blockchain_format.program import Program from chia.consensus.block_rewards import calculate_base_farmer_reward from chia.pools.pool_wallet import PoolWallet -from chia.pools.pool_wallet_info import create_pool_state, FARMING_TO_POOL, PoolWalletInfo, PoolState +from chia.pools.pool_wallet_info import FARMING_TO_POOL, PoolState, PoolWalletInfo, create_pool_state from chia.protocols.protocol_message_types import ProtocolMessageTypes from chia.server.outbound_message import NodeType, make_msg from chia.simulator.simulator_protocol import FarmNewBlockProtocol from chia.types.announcement import Announcement from chia.types.blockchain_format.coin import Coin +from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.spend_bundle import SpendBundle from chia.util.bech32m import decode_puzzle_hash, encode_puzzle_hash from chia.util.byte_types import hexstr_to_bytes -from chia.util.ints import uint32, uint64, uint8 +from chia.util.config import load_config +from chia.util.ints import uint8, uint32, uint64 from chia.util.keychain import KeyringIsLocked, bytes_to_mnemonic, generate_mnemonic from chia.util.path import path_from_root from chia.util.ws_message import WsRpcMessage, create_payload_dict from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS from chia.wallet.cat_wallet.cat_wallet import CATWallet -from chia.wallet.derive_keys import master_sk_to_singleton_owner_sk, master_sk_to_wallet_sk_unhardened, MAX_POOL_WALLETS +from chia.wallet.derive_keys import ( + MAX_POOL_WALLETS, + master_sk_to_farmer_sk, + master_sk_to_pool_sk, + master_sk_to_singleton_owner_sk, + match_address_to_sk, +) from chia.wallet.did_wallet.did_wallet import DIDWallet +from chia.wallet.nft_wallet.nft_puzzles import get_uri_list_from_puzzle +from chia.wallet.nft_wallet.nft_wallet import NFTWallet from chia.wallet.outer_puzzles import AssetType from chia.wallet.puzzle_drivers import PuzzleInfo from chia.wallet.rl_wallet.rl_wallet import RLWallet -from chia.wallet.derive_keys import master_sk_to_farmer_sk, master_sk_to_pool_sk, master_sk_to_wallet_sk -from chia.wallet.nft_wallet.nft_wallet import NFTWallet from chia.wallet.trade_record import TradeRecord from chia.wallet.trading.offer import Offer from chia.wallet.transaction_record import TransactionRecord @@ -40,7 +47,6 @@ from chia.wallet.util.wallet_types import AmountWithPuzzlehash, WalletType from chia.wallet.wallet_info import WalletInfo from chia.wallet.wallet_node import WalletNode -from chia.util.config import load_config # Timeout for response from wallet/full node for sending a transaction TIMEOUT = 30 @@ -1300,9 +1306,10 @@ async def did_transfer_did(self, request): # NFT Wallet ########################################################################################## - async def nft_mint_nft(self, request): + async def nft_mint_nft(self, request) -> Dict: log.debug("Got minting RPC request: %s", request) wallet_id = uint32(request["wallet_id"]) + assert self.service.wallet_state_manager nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id] address = request.get("artist_address") if isinstance(address, str): @@ -1320,11 +1327,12 @@ async def nft_mint_nft(self, request): nft_record = await nft_wallet.generate_new_nft(metadata, puzzle_hash) return {"wallet_id": wallet_id, "success": True, "nft": nft_record} - async def nft_get_nfts(self, request): + async def nft_get_nfts(self, request) -> Dict: wallet_id = uint32(request["wallet_id"]) + assert self.service.wallet_state_manager nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id] nfts = nft_wallet.get_current_nfts() - nft_info_list = [] + nft_uri_pairs = [] for nft in nfts: uri = get_uri_list_from_puzzle(nft.full_puzzle) nft_uri_pairs.append((nft, uri)) diff --git a/chia/wallet/nft_wallet/nft_puzzles.py b/chia/wallet/nft_wallet/nft_puzzles.py index 90391ae39cc4..4153a97a5f7a 100644 --- a/chia/wallet/nft_wallet/nft_puzzles.py +++ b/chia/wallet/nft_wallet/nft_puzzles.py @@ -48,10 +48,11 @@ def create_full_puzzle_with_nft_puzzle(singleton_id: bytes32, inner_puzzle: Prog return full_puzzle -def get_uri_list_from_puzzle(puzzle: Program): +def get_uri_list_from_puzzle(puzzle: Program) -> List[Tuple[bytes, bytes]]: matched, singleton_args, curried_args = match_nft_puzzle(puzzle) if matched: return list(curried_args[1].as_iter()) + raise ValueError("Not an NFT puzzle ") def create_full_puzzle( diff --git a/chia/wallet/nft_wallet/nft_wallet.py b/chia/wallet/nft_wallet/nft_wallet.py index df794e75887b..e77f8010dfb4 100644 --- a/chia/wallet/nft_wallet/nft_wallet.py +++ b/chia/wallet/nft_wallet/nft_wallet.py @@ -1,11 +1,9 @@ -from chia.wallet.util.compute_memos import compute_memos import json import logging import time from dataclasses import dataclass from secrets import token_bytes from typing import Any, Dict, List, Optional, Set, Type, TypeVar -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, TypeVar from blspy import AugSchemeMPL, G1Element, G2Element from clvm.casts import int_from_bytes @@ -21,14 +19,12 @@ from chia.types.coin_spend import CoinSpend from chia.types.spend_bundle import SpendBundle from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict -from chia.util.ints import uint8, uint32, uint64 from chia.util.ints import uint8, uint32, uint64, uint128 from chia.util.streamable import Streamable, streamable from chia.wallet.derivation_record import DerivationRecord from chia.wallet.lineage_proof import LineageProof from chia.wallet.nft_wallet import nft_puzzles from chia.wallet.nft_wallet.nft_puzzles import LAUNCHER_PUZZLE, NFT_STATE_LAYER_MOD_HASH -from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT from chia.wallet.puzzles.load_clvm import load_clvm from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import ( DEFAULT_HIDDEN_PUZZLE_HASH, @@ -38,6 +34,7 @@ ) from chia.wallet.puzzles.puzzle_utils import make_create_coin_condition from chia.wallet.transaction_record import TransactionRecord +from chia.wallet.util.compute_memos import compute_memos from chia.wallet.util.debug_spend_bundle import disassemble from chia.wallet.util.transaction_type import TransactionType from chia.wallet.util.wallet_types import WalletType @@ -48,8 +45,6 @@ _T_NFTWallet = TypeVar("_T_NFTWallet", bound="NFTWallet") -if TYPE_CHECKING: - from chia.wallet.wallet_state_manager import WalletStateManager OFFER_MOD = load_clvm("settlement_payments.clvm") @@ -76,9 +71,8 @@ def create_fullpuz(innerpuz: Program, genesis_id: bytes32) -> Program: return SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, innerpuz) -@dataclass class NFTWallet: - wallet_state_manager: WalletStateManager + wallet_state_manager: Any log: logging.Logger wallet_info: WalletInfo nft_wallet_info: NFTWalletInfo @@ -88,7 +82,7 @@ class NFTWallet: @classmethod async def create_new_nft_wallet( cls: Type[_T_NFTWallet], - wallet_state_manager: WalletStateManager, + wallet_state_manager: Any, wallet: Wallet, did_wallet_id: uint32 = None, name: str = "", @@ -103,17 +97,17 @@ async def create_new_nft_wallet( self.nft_wallet_info = NFTWalletInfo([], did_wallet_id) info_as_string = json.dumps(self.nft_wallet_info.to_json_dict()) - self.wallet_info = await wallet_state_manager.user_store.create_wallet( + wallet_info = await wallet_state_manager.user_store.create_wallet( "NFT Wallet" if not name else name, uint32(WalletType.NFT.value), info_as_string ) if wallet_info is None: raise ValueError("Internal Error") + self.wallet_info = wallet_info self.wallet_id = self.wallet_info.id self.log.debug("NFT wallet id: %r and standard wallet id: %r", self.wallet_id, self.standard_wallet.wallet_id) await self.wallet_state_manager.add_new_wallet(self, self.wallet_info.id) self.log.debug("Generated a new NFT wallet: %s", self.__dict__) - # await self.wallet_state_manager.update_wallet_puzzle_hashes(self.wallet_id) if not did_wallet_id: # default profile wallet self.log.debug("Standard NFT wallet created") @@ -126,7 +120,7 @@ async def create_new_nft_wallet( @classmethod async def create( cls: Type[_T_NFTWallet], - wallet_state_manager: WalletStateManager, + wallet_state_manager: Any, wallet: Wallet, wallet_info: WalletInfo, name: Optional[str] = None, @@ -228,14 +222,14 @@ async def puzzle_solution_received(self, coin_spend: CoinSpend, in_transaction: elif condition_code == 51 and int_from_bytes(condition.rest().rest().first().atom) == 1: puzhash = bytes32(condition.rest().first().atom) self.log.debug("Got back puzhash from solution: %s", puzhash) - record: DerivationRecord = ( - await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(puzhash) - ) - if record is None: + derivation_record: Optional[ + DerivationRecord + ] = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(puzhash) + if derivation_record is None: # we potentially sent it somewhere self.remove_coin(coin_spend.coin, in_transaction=in_transaction) return - new_inner_puzzle = puzzle_for_pk(record.pubkey) + new_inner_puzzle = puzzle_for_pk(derivation_record.pubkey) else: raise ValueError("Invalid condition") @@ -467,7 +461,9 @@ async def sign(self, spend_bundle: SpendBundle) -> SpendBundle: self.log.debug("Found a NFT state layer to sign") (_, metadata, metadata_updater_puzzle_hash, inner_puzzle) = puzzle_args puzzle_hash = inner_puzzle.get_tree_hash() - pubkey, private = await self.wallet_state_manager.get_keys(puzzle_hash) + keys = await self.wallet_state_manager.get_keys(puzzle_hash) + assert keys + _, private = keys synthetic_secret_key = calculate_synthetic_secret_key(private, DEFAULT_HIDDEN_PUZZLE_HASH) error, conditions, cost = conditions_dict_for_solution( spend.puzzle_reveal.to_program(), @@ -492,7 +488,7 @@ async def sign(self, spend_bundle: SpendBundle) -> SpendBundle: async def transfer_nft( self, nft_coin_info: NFTCoinInfo, - puzzle_hash: bytes32 = None, + puzzle_hash: bytes32, did_hash=None, ): self.log.debug("Attempt to transfer a new NFT") diff --git a/chia/wallet/puzzles/nft_transfer_program.clvm.hex b/chia/wallet/puzzles/nft_transfer_program.clvm.hex index e24f46b4ff14..30ef4f5e5811 100644 --- a/chia/wallet/puzzles/nft_transfer_program.clvm.hex +++ b/chia/wallet/puzzles/nft_transfer_program.clvm.hex @@ -1 +1 @@ -ff02ffff01ff02ff2affff04ff02ffff04ff05ffff04ff0bffff04ff820affffff04ffff02ff3affff04ff02ffff04ff17ffff04ff2fffff04ff0bffff04ff5fffff04ff81bfffff04ff82017fffff04ff8204ffff80808080808080808080ff80808080808080ffff04ffff01ffffffff3f02ff333effff0401ff01ff82271002ffffffff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff22ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff7cffff0bff34ff2480ffff0bff7cffff0bff7cffff0bff34ff2c80ff0980ffff0bff7cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff17ffff01ff04ffff04ffff0181eaffff04ffff02ff22ffff04ff02ffff04ff05ffff04ff17ff8080808080ffff04ff0bff80808080ff2f80ffff012f80ff0180ff02ffff03ff82017fffff01ff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ffff02ff7effff04ff02ffff04ff82017fff80808080ff8080808080808080808080ffff01ff04ffff04ff38ffff01ff808080ff808080ff0180ffffff04ffff04ff38ffff04ff8202ffff808080ffff04ffff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff81bfffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff5fff1d8080ff80808080ff808080808080ff8202ffff1580ff808080ffff02ff36ffff04ff02ffff04ff17ffff04ff82017fffff04ff15ff8080808080808080ff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff5dffff04ff2dffff04ffff0bff34ff5380ffff04ffff0bff34ff5d80ff80808080808080ffff02ff7effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff808080ff808080ff8080808080ff808080ffff01ff04ff28ffff04ff09ffff04ffff02ff5effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff80808080ff8080808080ff0180ffff02ff36ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ffff0bff7cffff0bff34ff3080ffff0bff7cffff0bff7cffff0bff34ff2c80ff0580ffff0bff7cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff18ff05ff3480ffff01ff11ff05ff3480ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bff34ff058080ff0180ff018080 +ff02ffff01ff02ff2affff04ff02ffff04ff05ffff04ff0bffff04ff820affffff04ffff02ff3affff04ff02ffff04ff17ffff04ff2fffff04ff0bffff04ff5fffff04ff81bfffff04ff82017fffff04ff8204ffff80808080808080808080ff80808080808080ffff04ffff01ffffffff3f02ff333effff0401ff01ff82271002ffffffff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff22ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff7cffff0bff34ff2480ffff0bff7cffff0bff7cffff0bff34ff2c80ff0980ffff0bff7cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff17ffff01ff04ffff04ffff0181eaffff04ffff02ff22ffff04ff02ffff04ff05ffff04ff17ff8080808080ffff04ff0bff80808080ff2f80ffff012f80ff0180ff02ffff03ff82017fffff01ff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ffff02ff7effff04ff02ffff04ff82017fff80808080ff8080808080808080808080ffff01ff04ffff04ff38ffff01ff808080ff808080ff0180ffffff04ffff04ff38ffff04ff8202ffff808080ffff04ffff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff81bfffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff5fff1d8080ff80808080ff808080808080ff8202ffff1580ff808080ffff02ff36ffff04ff02ffff04ff17ffff04ff82017fffff04ff15ff8080808080808080ff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff5dffff04ff2dffff04ffff0bff34ff5380ffff04ffff0bff34ff5d80ff80808080808080ffff02ff7effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff808080ff808080ff8080808080ff808080ffff01ff04ff28ffff04ff09ffff04ffff02ff5effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff80808080ff8080808080ff0180ffff02ff36ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ffff0bff7cffff0bff34ff3080ffff0bff7cffff0bff7cffff0bff34ff2c80ff0580ffff0bff7cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff18ff05ff3480ffff01ff11ff05ff3480ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bff34ff058080ff0180ff018080 \ No newline at end of file diff --git a/chia/wallet/wallet_state_manager.py b/chia/wallet/wallet_state_manager.py index ae04a928d971..898e092cabbb 100644 --- a/chia/wallet/wallet_state_manager.py +++ b/chia/wallet/wallet_state_manager.py @@ -32,12 +32,6 @@ from chia.util.db_wrapper import DBWrapper from chia.util.errors import Err from chia.util.ints import uint8, uint32, uint64, uint128 -from chia.util.ints import uint32, uint64, uint128, uint8 -from chia.util.db_synchronous import db_synchronous_on -from chia.wallet.cat_wallet.cat_utils import match_cat_puzzle, construct_cat_puzzle -from chia.wallet.did_wallet.did_wallet_puzzles import match_did_puzzle, create_fullpuz, DID_INNERPUZ_MOD -from chia.wallet.nft_wallet.nft_wallet import NFTWalletInfo -from chia.wallet.cat_wallet.cat_wallet import CATWallet from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS from chia.wallet.cat_wallet.cat_utils import construct_cat_puzzle, match_cat_puzzle from chia.wallet.cat_wallet.cat_wallet import CATWallet @@ -50,7 +44,6 @@ from chia.wallet.nft_wallet.nft_wallet import NFTWallet, NFTWalletInfo from chia.wallet.outer_puzzles import AssetType from chia.wallet.puzzle_drivers import PuzzleInfo -from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT from chia.wallet.puzzles.cat_loader import CAT_MOD from chia.wallet.rl_wallet.rl_wallet import RLWallet from chia.wallet.settings.user_settings import UserSettings @@ -576,9 +569,7 @@ async def determine_coin_type( # Check if the coin is a NFT # hint # First spend where 1 mojo coin -> Singleton launcher -> NFT -> NFT - nft_matched, singleton_curried_args, nft_curried_args = match_nft_puzzle( - Program.from_bytes(bytes(coin_spend.puzzle_reveal)) - ) + nft_matched, _, nft_curried_args = match_nft_puzzle(Program.from_bytes(bytes(coin_spend.puzzle_reveal))) self.log.debug("Matching NFT: %s", nft_matched) if nft_matched: return await self.handle_nft(coin_spend, iter(nft_curried_args)) @@ -711,8 +702,7 @@ async def handle_did( return wallet_id, wallet_type async def handle_nft( - self, - coin_spend: CoinSpend, + self, coin_spend: CoinSpend, curried_args: Iterator[Program] ) -> Tuple[Optional[uint32], Optional[WalletType]]: """ Handle the new coin when it is a NFT diff --git a/tests/wallet/nft_wallet/test_nft_wallet.py b/tests/wallet/nft_wallet/test_nft_wallet.py index dfa79a582423..3e7c69178786 100644 --- a/tests/wallet/nft_wallet/test_nft_wallet.py +++ b/tests/wallet/nft_wallet/test_nft_wallet.py @@ -1,50 +1,35 @@ import asyncio -from chia.types.spend_bundle import SpendBundle -from chia.util.bech32m import encode_puzzle_hash -from chia.rpc.wallet_rpc_api import WalletRpcApi # pytestmark = pytest.mark.skip("TODO: Fix tests") -import logging -from tests.conftest import two_wallet_nodes +from typing import Any import pytest +from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward from chia.full_node.mempool_manager import MempoolManager +from chia.rpc.wallet_rpc_api import WalletRpcApi 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.ints import uint16 +from chia.util.ints import uint16, uint32 from chia.wallet.nft_wallet.nft_wallet import NFTWallet from tests.time_out_assert import time_out_assert, time_out_assert_not_none -async def tx_in_pool(mempool: MempoolManager, tx_id: bytes32): +async def tx_in_pool(mempool: MempoolManager, tx_id: bytes32) -> bool: tx = mempool.get_spendbundle(tx_id) if tx is None: return False return True -# TODO: remove me -logging.getLogger("aiosqlite").setLevel(logging.INFO) # Too much logging on debug level -logging.getLogger("websockets").setLevel(logging.INFO) # Too much logging on debug level -logging.getLogger("fsevents").setLevel(logging.INFO) # Too much logging on debug level -logging.getLogger("chia.plotting.create_plots").setLevel(logging.INFO) # Too much logging on debug level -logging.getLogger("filelock").setLevel(logging.INFO) # Too much logging on debug level -logging.getLogger("chia.plotting").setLevel(logging.INFO) # Too much logging on debug level -logging.getLogger("chia.full_node.block_store").setLevel(logging.INFO) # Too much logging on debug level - -logging.getLogger("wallet_server").setLevel(logging.INFO) # Too much logging on debug level -logging.getLogger("full_node_server").setLevel(logging.INFO) # Too much logging on debug level - - @pytest.mark.parametrize( "trusted", [True], ) @pytest.mark.asyncio -async def test_nft_wallet_creation_and_transfer(two_wallet_nodes, trusted): +async def test_nft_wallet_creation_and_transfer(two_wallet_nodes: Any, trusted: Any) -> None: num_blocks = 5 full_nodes, wallets = two_wallet_nodes full_node_api = full_nodes[0] @@ -74,24 +59,19 @@ async def test_nft_wallet_creation_and_transfer(two_wallet_nodes, trusted): for i 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) - # ] - # ) + 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) - # for i in range(1, num_blocks): - # await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - # for i in range(1, num_blocks): - # await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2)) + await time_out_assert(10, wallet_0.get_unconfirmed_balance, funds) + await time_out_assert(10, wallet_0.get_confirmed_balance, funds) + for i in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - # for i in range(1, num_blocks): - # await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + for i 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) + await time_out_assert(15, wallet_0.get_pending_change_balance, 0) nft_wallet_0 = await NFTWallet.create_new_nft_wallet( wallet_node_0.wallet_state_manager, wallet_0, name="NFT WALLET 1" ) @@ -103,7 +83,8 @@ async def test_nft_wallet_creation_and_transfer(two_wallet_nodes, trusted): ) tr = await nft_wallet_0.generate_new_nft(metadata) - + assert tr + assert tr.spend_bundle await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, tr.spend_bundle.name()) for i in range(1, num_blocks): @@ -121,7 +102,8 @@ async def test_nft_wallet_creation_and_transfer(two_wallet_nodes, trusted): ) tr = await nft_wallet_0.generate_new_nft(metadata) - + assert tr + assert tr.spend_bundle await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, tr.spend_bundle.name()) for i in range(1, num_blocks): @@ -173,7 +155,7 @@ async def test_nft_wallet_creation_and_transfer(two_wallet_nodes, trusted): [True], ) @pytest.mark.asyncio -async def test_nft_wallet_rpc_creation_and_list(two_wallet_nodes, trusted): +async def test_nft_wallet_rpc_creation_and_list(two_wallet_nodes: Any, trusted: Any) -> None: num_blocks = 5 full_nodes, wallets = two_wallet_nodes full_node_api = full_nodes[0] @@ -184,7 +166,7 @@ async def test_nft_wallet_rpc_creation_and_list(two_wallet_nodes, trusted): wallet_1 = wallet_node_1.wallet_state_manager.main_wallet ph = await wallet_0.get_new_puzzlehash() - ph1 = await wallet_1.get_new_puzzlehash() + _ = await wallet_1.get_new_puzzlehash() if trusted: wallet_node_0.config["trusted_peers"] = { @@ -250,7 +232,8 @@ async def test_nft_wallet_rpc_creation_and_list(two_wallet_nodes, trusted): coins = coins_response["nft_list"] assert len(coins) == 2 uris = [] - [uris.append(x[1][0].as_python()[1]) for x in coins] + for x in coins: + uris.append(x[1][0].as_python()[1]) print(uris) assert len(uris) == 2 assert b"https://chialisp.com/img/logo.svg" in uris From 150f2448d6a9ff802ddeb941b3d4c033fa485578 Mon Sep 17 00:00:00 2001 From: Sebastjan Date: Thu, 5 May 2022 11:07:01 +0200 Subject: [PATCH 36/43] removed old NFT tests --- .../build-test-macos-wallet-did_wallet.yml | 2 +- .../build-test-ubuntu-wallet-did_wallet.yml | 2 +- chia/wallet/nft_wallet/nft_wallet.py | 2 +- tests/wallet/did_wallet/test_nft_clvm.py | 301 ------------- tests/wallet/did_wallet/test_nft_rpc.py | 235 ---------- tests/wallet/did_wallet/test_nft_wallet.py | 419 ------------------ 6 files changed, 3 insertions(+), 958 deletions(-) delete mode 100644 tests/wallet/did_wallet/test_nft_clvm.py delete mode 100644 tests/wallet/did_wallet/test_nft_rpc.py delete mode 100644 tests/wallet/did_wallet/test_nft_wallet.py diff --git a/.github/workflows/build-test-macos-wallet-did_wallet.yml b/.github/workflows/build-test-macos-wallet-did_wallet.yml index 4c1ee6dfff39..6398146a3629 100644 --- a/.github/workflows/build-test-macos-wallet-did_wallet.yml +++ b/.github/workflows/build-test-macos-wallet-did_wallet.yml @@ -93,7 +93,7 @@ jobs: - name: Test wallet-did_wallet code with pytest run: | . ./activate - venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/did_wallet/test_did.py tests/wallet/did_wallet/test_did_rpc.py tests/wallet/did_wallet/test_nft_clvm.py tests/wallet/did_wallet/test_nft_rpc.py tests/wallet/did_wallet/test_nft_wallet.py + venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/did_wallet/test_did.py tests/wallet/did_wallet/test_did_rpc.py - name: Process coverage data run: | diff --git a/.github/workflows/build-test-ubuntu-wallet-did_wallet.yml b/.github/workflows/build-test-ubuntu-wallet-did_wallet.yml index 28bb25d0e214..28d4b3a61b2d 100644 --- a/.github/workflows/build-test-ubuntu-wallet-did_wallet.yml +++ b/.github/workflows/build-test-ubuntu-wallet-did_wallet.yml @@ -92,7 +92,7 @@ jobs: - name: Test wallet-did_wallet code with pytest run: | . ./activate - venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" -p no:monitor tests/wallet/did_wallet/test_did.py tests/wallet/did_wallet/test_did_rpc.py tests/wallet/did_wallet/test_nft_clvm.py tests/wallet/did_wallet/test_nft_rpc.py tests/wallet/did_wallet/test_nft_wallet.py + venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" -p no:monitor tests/wallet/did_wallet/test_did.py tests/wallet/did_wallet/test_did_rpc.py - name: Process coverage data run: | diff --git a/chia/wallet/nft_wallet/nft_wallet.py b/chia/wallet/nft_wallet/nft_wallet.py index e77f8010dfb4..c65e49c341e5 100644 --- a/chia/wallet/nft_wallet/nft_wallet.py +++ b/chia/wallet/nft_wallet/nft_wallet.py @@ -227,7 +227,7 @@ async def puzzle_solution_received(self, coin_spend: CoinSpend, in_transaction: ] = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(puzhash) if derivation_record is None: # we potentially sent it somewhere - self.remove_coin(coin_spend.coin, in_transaction=in_transaction) + await self.remove_coin(coin_spend.coin, in_transaction=in_transaction) return new_inner_puzzle = puzzle_for_pk(derivation_record.pubkey) diff --git a/tests/wallet/did_wallet/test_nft_clvm.py b/tests/wallet/did_wallet/test_nft_clvm.py deleted file mode 100644 index b2eac889507f..000000000000 --- a/tests/wallet/did_wallet/test_nft_clvm.py +++ /dev/null @@ -1,301 +0,0 @@ -from chia.types.announcement import Announcement -from chia.types.blockchain_format.coin import Coin -from chia.types.blockchain_format.program import INFINITE_COST, Program -from chia.types.blockchain_format.sized_bytes import bytes32 -from chia.util.ints import uint64 -from chia.wallet.puzzles.cat_loader import CAT_MOD -from chia.wallet.puzzles.load_clvm import load_clvm - -OFFER_MOD = load_clvm("settlement_payments.clvm") -SINGLETON_MOD = load_clvm("singleton_top_layer.clvm") -LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm") -DID_MOD = load_clvm("did_innerpuz.clvm") -NFT_MOD = load_clvm("nft_innerpuz.clvm") -LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash() -SINGLETON_MOD_HASH = SINGLETON_MOD.get_tree_hash() -NFT_MOD_HASH = NFT_MOD.get_tree_hash() -LAUNCHER_ID = Program.to(b"launcher-id").get_tree_hash() -NFT_TRANSFER_PROGRAM = load_clvm("nft_transfer_program.clvm") - - -def test_transfer_no_backpayments() -> None: - did_one: bytes32 = Program.to("did_one").get_tree_hash() - did_two: bytes32 = Program.to("did_two").get_tree_hash() - - did_one_pk: bytes32 = Program.to("did_one_pk").get_tree_hash() - did_one_innerpuz = DID_MOD.curry(did_one_pk, 0, 0) - SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (did_one, LAUNCHER_PUZZLE_HASH))) - did_one_puzzle: Program = SINGLETON_MOD.curry(SINGLETON_STRUCT, did_one_innerpuz) - did_one_parent: bytes32 = Program.to("did_one_parent").get_tree_hash() - did_one_amount = uint64(201) - - # did_two_pk: bytes32 = Program.to("did_two_pk").get_tree_hash() - did_two_innerpuz = DID_MOD.curry(did_one_pk, 0, 0) - SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (did_two, LAUNCHER_PUZZLE_HASH))) - # did_two_puzzle: bytes32 = SINGLETON_MOD.curry(SINGLETON_STRUCT, did_two_innerpuz) - # did_two_parent: bytes32 = Program.to("did_two_parent").get_tree_hash() - # did_two_amount = 401 - - did_one_coin = Coin(did_one_parent, did_one_puzzle.get_tree_hash(), did_one_amount) - # did_two_coin = Coin(did_two_parent, did_two_puzzle.get_tree_hash(), did_two_amount) - - # NFT_MOD_HASH - # SINGLETON_STRUCT ; ((SINGLETON_MOD_HASH, (SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH))) - # CURRENT_OWNER_DID - # NFT_TRANSFER_PROGRAM_HASH - # TRANSFER_PROGRAM_CURRY_PARAMS - # METADATA - # my_amount - # my_did_inner_hash - # new_did - # new_did_inner_hash - # transfer_program_reveal - # transfer_program_solution - - nft_program = Program.to(0) - trade_price = 0 - solution = Program.to( - [ - NFT_MOD_HASH, # curried in params - SINGLETON_STRUCT, - did_one, - nft_program.get_tree_hash(), - 0, - [ - ("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), - ("h", 0xD4584AD463139FA8C0D9F68F4B59F185), - ], - # below here is the solution - did_one_innerpuz.get_tree_hash(), - did_two, - did_two_innerpuz.get_tree_hash(), - nft_program, - [trade_price, 0], - ] - ) - cost, res = NFT_MOD.run_with_cost(INFINITE_COST, solution) - # (sha256tree1 (list transfer_program_solution new_did trade_prices_list)) - ann = Program.to([[trade_price, 0], did_two]).get_tree_hash() - announcement_one = Announcement(did_one_coin.puzzle_hash, ann) - # announcement_two = Announcement(did_two_coin.name(), ann) - assert res.rest().first().first().as_int() == 63 - assert res.rest().first().rest().first().as_atom() == announcement_one.name() - # assert res.rest().rest().first().first().as_int() == 63 - # assert res.rest().rest().first().rest().first().as_atom() == announcement_one.name() - - -def test_transfer_with_backpayments() -> None: - did_one: bytes32 = Program.to("did_one").get_tree_hash() - did_two: bytes32 = Program.to("did_two").get_tree_hash() - - did_one_pk: bytes32 = Program.to("did_one_pk").get_tree_hash() - did_one_innerpuz = DID_MOD.curry(did_one_pk, 0, 0) - SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (did_one, LAUNCHER_PUZZLE_HASH))) - did_one_puzzle: Program = SINGLETON_MOD.curry(SINGLETON_STRUCT, did_one_innerpuz) - did_one_parent: bytes32 = Program.to("did_one_parent").get_tree_hash() - did_one_amount = uint64(201) - - # did_two_pk: bytes32 = Program.to("did_two_pk").get_tree_hash() - did_two_innerpuz = DID_MOD.curry(did_one_pk, 0, 0) - SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (did_two, LAUNCHER_PUZZLE_HASH))) - did_two_puzzle: Program = SINGLETON_MOD.curry(SINGLETON_STRUCT, did_two_innerpuz) - did_two_parent: bytes32 = Program.to("did_two_parent").get_tree_hash() - did_two_amount = uint64(401) - - did_one_coin = Coin(did_one_parent, did_one_puzzle.get_tree_hash(), did_one_amount) - did_two_coin = Coin(did_two_parent, did_two_puzzle.get_tree_hash(), did_two_amount) - - nft_creator_address = Program.to("nft_creator_address").get_tree_hash() - # ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE METADATA SETTLEMENT_MOD_HASH CAT_MOD_HASH - - trade_price = [[20]] - # NFT_MOD_HASH - # SINGLETON_STRUCT ; ((SINGLETON_MOD_HASH, (SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH))) - # CURRENT_OWNER_DID - # TRANSFER_PROGRAM_MOD_HASH - # TRANSFER_PROGRAM_CURRY_PARAMS - # METADATA - # my_amount - # my_did_inner_hash - # new_did - # new_did_inner_hash - # transfer_program_reveal - # transfer_program_solution - - # (ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH) - transfer_program_curry_params = [nft_creator_address, 1000, OFFER_MOD.get_tree_hash(), CAT_MOD.get_tree_hash()] - solution = Program.to( - [ - NFT_MOD_HASH, # curried in params - SINGLETON_STRUCT, - did_one, - NFT_TRANSFER_PROGRAM.get_tree_hash(), - transfer_program_curry_params, - [ - ("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), - ("h", 0xD4584AD463139FA8C0D9F68F4B59F185), - ], - # below here is the solution - did_one_innerpuz.get_tree_hash(), - did_two, - did_two_innerpuz.get_tree_hash(), - NFT_TRANSFER_PROGRAM, - [trade_price, 0], - ] - ) - cost, res = NFT_MOD.run_with_cost(INFINITE_COST, solution) - - msg = Program.to([[trade_price, 0], did_two]).get_tree_hash() - announcement_one = Announcement(did_one_coin.puzzle_hash, msg) - ann = bytes(bytes(Program.to(trade_price).get_tree_hash()) + did_two) - announcement_two = Announcement(did_two_coin.puzzle_hash, ann) - assert res.rest().first().first().as_int() == 63 - assert res.rest().first().rest().first().as_atom() == announcement_one.name() - assert res.rest().rest().rest().first().first().as_int() == 63 - assert res.rest().rest().rest().first().rest().first().as_atom() == announcement_two.name() - assert res.rest().rest().rest().rest().first().first().as_int() == 51 - assert res.rest().rest().rest().rest().first().rest().first().as_atom() == nft_creator_address - - -def test_announce() -> None: - did_one: bytes32 = Program.to("did_one").get_tree_hash() - did_two: bytes32 = Program.to("did_two").get_tree_hash() - - did_one_pk: bytes32 = Program.to("did_one_pk").get_tree_hash() - did_one_innerpuz = DID_MOD.curry(did_one_pk, 0, 0) - SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (did_one, LAUNCHER_PUZZLE_HASH))) - did_one_puzzle: Program = SINGLETON_MOD.curry(SINGLETON_STRUCT, did_one_innerpuz) - did_one_parent: bytes32 = Program.to("did_one_parent").get_tree_hash() - did_one_amount = uint64(201) - - # did_two_pk: bytes32 = Program.to("did_two_pk").get_tree_hash() - # did_two_innerpuz = DID_MOD.curry(did_one_pk, 0, 0) - SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (did_two, LAUNCHER_PUZZLE_HASH))) - # did_two_puzzle: bytes32 = SINGLETON_MOD.curry(SINGLETON_STRUCT, did_two_innerpuz) - # did_two_parent: bytes32 = Program.to("did_two_parent").get_tree_hash() - # did_two_amount = 401 - - did_one_coin = Coin(did_one_parent, did_one_puzzle.get_tree_hash(), did_one_amount) - # did_two_coin = Coin(did_two_parent, did_two_puzzle.get_tree_hash(), did_two_amount) - # NFT_MOD_HASH - # SINGLETON_STRUCT ; ((SINGLETON_MOD_HASH, (NFT_SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH))) - # CURRENT_OWNER_DID - # NFT_TRANSFER_PROGRAM_HASH - # my_amount - # my_did_inner_hash - # new_did - # new_did_inner_hash - # transfer_program_reveal - # transfer_program_solution - nft_creator_address = Program.to("nft_creator_address").get_tree_hash() - transfer_program_curry_params = [nft_creator_address, 1000, OFFER_MOD.get_tree_hash(), CAT_MOD.get_tree_hash()] - solution = Program.to( - [ - NFT_MOD_HASH, # curried in params - SINGLETON_STRUCT, - did_one, - NFT_TRANSFER_PROGRAM.get_tree_hash(), - transfer_program_curry_params, - [ - ("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), - ("h", 0xD4584AD463139FA8C0D9F68F4B59F185), - ], - # below here is the solution - did_one_innerpuz.get_tree_hash(), - 0, - 0, - 0, - 0, - 0, - 0, - ] - ) - cost, res = NFT_MOD.run_with_cost(INFINITE_COST, solution) - ann = bytes("a", "utf-8") - announcement_one = Announcement(did_one_coin.puzzle_hash, ann) - assert res.rest().rest().first().first().as_int() == 63 - assert res.rest().rest().first().rest().first().as_atom() == announcement_one.name() - assert res.rest().rest().rest().first().first().as_int() == 62 - assert res.rest().rest().rest().first().rest().first().as_atom() == did_one - - -def test_update_url_spend() -> None: - did_one: bytes32 = Program.to("did_one").get_tree_hash() - did_two: bytes32 = Program.to("did_two").get_tree_hash() - - did_one_pk: bytes32 = Program.to("did_one_pk").get_tree_hash() - did_one_innerpuz = DID_MOD.curry(did_one_pk, 0, 0) - SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (did_one, LAUNCHER_PUZZLE_HASH))) - # did_one_puzzle: bytes32 = SINGLETON_MOD.curry(SINGLETON_STRUCT, did_one_innerpuz) - # did_one_parent: bytes32 = Program.to("did_one_parent").get_tree_hash() - # did_one_amount = 201 - - # did_two_pk: bytes32 = Program.to("did_two_pk").get_tree_hash() - # did_two_innerpuz = DID_MOD.curry(did_one_pk, 0, 0) - SINGLETON_STRUCT = Program.to((SINGLETON_MOD_HASH, (did_two, LAUNCHER_PUZZLE_HASH))) - # did_two_puzzle: bytes32 = SINGLETON_MOD.curry(SINGLETON_STRUCT, did_two_innerpuz) - # did_two_parent: bytes32 = Program.to("did_two_parent").get_tree_hash() - # did_two_amount = 401 - - # did_one_coin = Coin(did_one_parent, did_one_puzzle.get_tree_hash(), did_one_amount) - # did_two_coin = Coin(did_two_parent, did_two_puzzle.get_tree_hash(), did_two_amount) - - nft_creator_address = Program.to("nft_creator_address").get_tree_hash() - # ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE METADATA SETTLEMENT_MOD_HASH CAT_MOD_HASH - - trade_price = [[20]] - # NFT_MOD_HASH - # SINGLETON_STRUCT ; ((SINGLETON_MOD_HASH, (SINGLETON_LAUNCHER_ID, LAUNCHER_PUZZLE_HASH))) - # CURRENT_OWNER_DID - # TRANSFER_PROGRAM_MOD_HASH - # TRANSFER_PROGRAM_CURRY_PARAMS - # METADATA - # my_amount - # my_did_inner_hash - # new_did - # new_did_inner_hash - # transfer_program_reveal - # transfer_program_solution - - # (ROYALTY_ADDRESS TRADE_PRICE_PERCENTAGE SETTLEMENT_MOD_HASH CAT_MOD_HASH) - transfer_program_curry_params = [nft_creator_address, 1000, OFFER_MOD.get_tree_hash(), CAT_MOD.get_tree_hash()] - solution = Program.to( - [ - NFT_MOD_HASH, # curried in params - SINGLETON_STRUCT, - did_one, - NFT_TRANSFER_PROGRAM.get_tree_hash(), - transfer_program_curry_params, - [ - ("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), - ("h", 0xD4584AD463139FA8C0D9F68F4B59F185), - ], - # below here is the solution - did_one_innerpuz.get_tree_hash(), - did_one, - did_one_innerpuz.get_tree_hash(), - NFT_TRANSFER_PROGRAM, - [trade_price, "https://www.chia.net/img/branding/chia-logo-2.svg"], - ] - ) - new_inner = NFT_MOD.curry( - NFT_MOD_HASH, # curried in params - SINGLETON_STRUCT, - did_one, - NFT_TRANSFER_PROGRAM.get_tree_hash(), - transfer_program_curry_params, - [ - ( - "u", - [ - "https://www.chia.net/img/branding/chia-logo-2.svg", - "https://www.chia.net/img/branding/chia-logo.svg", - ], - ), - ("h", 0xD4584AD463139FA8C0D9F68F4B59F185), - ], - ) - cost, res = NFT_MOD.run_with_cost(INFINITE_COST, solution) - # new_full = SINGLETON_MOD.curry(SINGLETON_STRUCT, new_inner) - assert res.rest().rest().first().first().as_int() == 51 - assert res.rest().rest().first().rest().first().as_atom() == new_inner.get_tree_hash() diff --git a/tests/wallet/did_wallet/test_nft_rpc.py b/tests/wallet/did_wallet/test_nft_rpc.py deleted file mode 100644 index 6e8fe25f94e0..000000000000 --- a/tests/wallet/did_wallet/test_nft_rpc.py +++ /dev/null @@ -1,235 +0,0 @@ -import asyncio - -import pytest - -from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward -from chia.rpc.wallet_rpc_api import WalletRpcApi -from chia.simulator.simulator_protocol import FarmNewBlockProtocol -from chia.types.blockchain_format.sized_bytes import bytes32 -from chia.types.mempool_inclusion_status import MempoolInclusionStatus -from chia.types.peer_info import PeerInfo -from chia.util.ints import uint16, uint32 -from chia.wallet.transaction_record import TransactionRecord -from chia.wallet.util.wallet_types import WalletType -from tests.time_out_assert import time_out_assert - - -async def is_transaction_in_mempool(user_wallet_id, api, tx_id: bytes32) -> bool: - try: - val = await api.get_transaction({"wallet_id": user_wallet_id, "transaction_id": tx_id.hex()}) - except ValueError: - return False - for _, mis, _ in TransactionRecord.from_json_dict_convenience(val["transaction"]).sent_to: - if ( - MempoolInclusionStatus(mis) == MempoolInclusionStatus.SUCCESS - or MempoolInclusionStatus(mis) == MempoolInclusionStatus.PENDING - ): - return True - return False - - -async def is_transaction_confirmed(user_wallet_id, api, tx_id: bytes32) -> bool: - try: - val = await api.get_transaction({"wallet_id": user_wallet_id, "transaction_id": tx_id.hex()}) - except ValueError: - return False - return TransactionRecord.from_json_dict_convenience(val["transaction"]).confirmed - - -async def check_balance(api, wallet_id): - balance_response = await api.get_wallet_balance({"wallet_id": wallet_id}) - balance = balance_response["wallet_balance"]["confirmed_wallet_balance"] - return balance - - -class TestNFTRPC: - @pytest.mark.parametrize( - "trusted", - [True], - ) - @pytest.mark.asyncio - async def test_create_nft_coin(self, three_wallet_nodes, trusted): - num_blocks = 5 - full_nodes, wallets = three_wallet_nodes - full_node_api = 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_node_2, server_2 = wallets[2] - wallet_0 = wallet_node_0.wallet_state_manager.main_wallet - wallet_1 = wallet_node_1.wallet_state_manager.main_wallet - wallet_2 = wallet_node_2.wallet_state_manager.main_wallet - - ph = await wallet_0.get_new_puzzlehash() - ph1 = await wallet_1.get_new_puzzlehash() - ph2 = await wallet_2.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() - } - wallet_node_2.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"] = {} - wallet_node_2.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) - await server_2.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) - - for i 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) - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - await time_out_assert(15, wallet_1.get_unconfirmed_balance, funds) - await time_out_assert(15, wallet_1.get_confirmed_balance, funds) - - # await time_out_assert(15, wallet_height_at_least, True, wallet_node, 6) - api_0 = WalletRpcApi(wallet_node_0) - val = await api_0.create_new_wallet( - {"wallet_type": "did_wallet", "did_type": "new", "backup_dids": [], "amount": 11} - ) - await asyncio.sleep(2) - assert isinstance(val, dict) - if "success" in val: - assert val["success"] - assert val["my_did"] - assert val["type"] == WalletType.DISTRIBUTED_ID.value - # did_0 = val["my_did"] - - # did_0 = val["my_did"] - - did_wallet_id_0 = val["wallet_id"] - - api_1 = WalletRpcApi(wallet_node_1) - - val = await api_1.create_new_wallet( - {"wallet_type": "did_wallet", "did_type": "new", "backup_dids": [], "amount": 21} - ) - assert isinstance(val, dict) - if "success" in val: - assert val["success"] - assert val["my_did"] - assert val["type"] == WalletType.DISTRIBUTED_ID.value - did_1 = val["my_did"] - did_wallet_id_1 = val["wallet_id"] - await asyncio.sleep(2) - - val = await api_0.create_new_wallet({"wallet_type": "nft_wallet", "did_wallet_id": did_wallet_id_0}) - assert val["success"] - nft_wallet_id_0 = val["wallet_id"] - val = await api_1.create_new_wallet({"wallet_type": "nft_wallet", "did_wallet_id": did_wallet_id_1}) - assert val["success"] - nft_wallet_id_1 = val["wallet_id"] - - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - - await asyncio.sleep(3) - - # metadata = Program.to([ - # ('u', ["https://www.chia.net/img/branding/chia-logo.svg"]), - # ('h', 0xd4584ad463139fa8c0d9f68f4b59f185), - # ]) - val = await api_0.nft_mint_nft( - { - "wallet_id": nft_wallet_id_0, - "uris": ["https://www.chia.net/img/branding/chia-logo.svg"], - "hash": 0xD4584AD463139FA8C0D9F68F4B59F185, - "artist_percentage": 2000, - "artist_address": ph2, - } - ) - - assert val["success"] - - await asyncio.sleep(5) - - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - - await asyncio.sleep(5) - - val = await api_0.nft_get_nfts({"wallet_id": nft_wallet_id_0}) - - assert val["success"] - assert len(val["nft_list"]) == 1 - nft_coin_id = val["nft_list"][0].nft_coin_id - assert val["nft_list"][0].data_uris[0] == "https://www.chia.net/img/branding/chia-logo.svg" - - val = await api_1.did_get_current_coin_info({"wallet_id": did_wallet_id_1}) - assert val["success"] - - trade_price = [[50]] - - val = await api_0.nft_transfer_nft( - { - "wallet_id": nft_wallet_id_0, - "nft_coin_id": nft_coin_id, - "new_did": did_1, - "new_did_inner_hash": val["did_innerpuz"], - "trade_price": trade_price, - } - ) - - await asyncio.sleep(5) - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - await asyncio.sleep(5) - - assert val["success"] - assert val["spend_bundle"] is not None - - val = await api_1.nft_receive_nft({"wallet_id": nft_wallet_id_1, "spend_bundle": val["spend_bundle"]}) - - assert val["success"] - - await asyncio.sleep(5) - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - await asyncio.sleep(5) - - val = await api_1.nft_get_nfts({"wallet_id": nft_wallet_id_1}) - assert val["success"] - assert len(val["nft_list"]) == 1 - assert val["nft_list"][0].did_owner == did_1 - assert val["nft_list"][0].data_uris[0] == "https://www.chia.net/img/branding/chia-logo.svg" - # Test adding a url - # TODO: un comment out this code when DID isn't broken - - # nft_coin_info = val["nfts"][0][0] - # val = await api_0.nft_add_url( - # { - # "wallet_id": nft_wallet_id_1, - # "nft_coin_info": nft_coin_info, - # "new_url": "https://www.chia.net/img/branding/chia-logo-2.svg", - # } - # ) - # assert val["success"] - # await asyncio.sleep(5) - # for i in range(1, num_blocks): - # await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - # await asyncio.sleep(5) - # - # val = await api_1.nft_get_current_nfts({"wallet_id": nft_wallet_id_1}) - # - # assert val["success"] - # assert len(val["nfts"]) == 1 - # assert val["nfts"][0][1] == [b"https://www.chia.net/img/branding/chia-logo-2.svg", - # b"https://www.chia.net/img/branding/chia-logo.svg"] diff --git a/tests/wallet/did_wallet/test_nft_wallet.py b/tests/wallet/did_wallet/test_nft_wallet.py deleted file mode 100644 index 537b9c4d3b28..000000000000 --- a/tests/wallet/did_wallet/test_nft_wallet.py +++ /dev/null @@ -1,419 +0,0 @@ -import asyncio -from typing import List - -import pytest - -from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward -from chia.full_node.mempool_manager import MempoolManager -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.ints import uint16, uint32, uint64 -from chia.wallet.cat_wallet.cat_wallet import CATWallet -from chia.wallet.did_wallet.did_wallet import DIDWallet -from chia.wallet.nft_wallet.nft_info import NFTInfo -from chia.wallet.nft_wallet.nft_puzzles import get_nft_info_from_puzzle -from chia.wallet.nft_wallet.nft_wallet import NFTWallet -from chia.wallet.transaction_record import TransactionRecord -from tests.time_out_assert import time_out_assert, time_out_assert_not_none - -# pytestmark = pytest.mark.skip("TODO: Fix tests") - - -async def tx_in_pool(mempool: MempoolManager, tx_id: bytes32): - tx = mempool.get_spendbundle(tx_id) - if tx is None: - return False - return True - - -class TestNFTWallet: - @pytest.mark.parametrize( - "trusted", - [True], - ) - @pytest.mark.asyncio - async def test_nft_wallet_trade_chia_and_cat(self, three_wallet_nodes, trusted): - num_blocks = 5 - full_nodes, wallets = three_wallet_nodes - full_node_api = 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_node_2, server_2 = wallets[2] - wallet_0 = wallet_node_0.wallet_state_manager.main_wallet - wallet_1 = wallet_node_1.wallet_state_manager.main_wallet - wallet_2 = wallet_node_2.wallet_state_manager.main_wallet - - ph = await wallet_0.get_new_puzzlehash() - ph1 = await wallet_1.get_new_puzzlehash() - ph2 = await wallet_2.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() - } - wallet_node_2.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"] = {} - wallet_node_2.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) - await server_2.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) - - for i 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) - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2)) - - # Wallet1 sets up DIDWallet1 without any backup set - async with wallet_node_0.wallet_state_manager.lock: - did_wallet_0: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(101) - ) - - 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 i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) - - await time_out_assert(15, did_wallet_0.get_confirmed_balance, 101) - await time_out_assert(15, did_wallet_0.get_unconfirmed_balance, 101) - await time_out_assert(15, did_wallet_0.get_pending_change_balance, 0) - - nft_wallet_0 = await NFTWallet.create_new_nft_wallet( - wallet_node_0.wallet_state_manager, wallet_0, did_wallet_0.id() - ) - metadata = Program.to( - [ - ("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), - ("h", 0xD4584AD463139FA8C0D9F68F4B59F185), - ] - ) - tr = await nft_wallet_0.generate_new_nft(metadata, uint64(2000), ph) - - await time_out_assert_not_none( - 5, full_node_api.full_node.mempool_manager.get_spendbundle, tr.spend_bundle.name() - ) - - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - - await asyncio.sleep(3) - coins = nft_wallet_0.nft_wallet_info.my_nft_coins - assert len(coins) == 1 - - # Wallet2 sets up DIDWallet2 without any backup set - async with wallet_node_1.wallet_state_manager.lock: - did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_1.wallet_state_manager, wallet_1, uint64(201) - ) - - spend_bundle_list = await wallet_node_1.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet_1.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 i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - - await time_out_assert(15, did_wallet_1.get_confirmed_balance, 201) - await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 201) - - async with wallet_node_1.wallet_state_manager.lock: - cat_wallet_1: CATWallet = await CATWallet.create_new_cat_wallet( - wallet_node_1.wallet_state_manager, wallet_1, {"identifier": "genesis_by_id"}, uint64(100) - ) - tx_queue: List[TransactionRecord] = await wallet_node_1.wallet_state_manager.tx_store.get_not_sent() - tx_record = tx_queue[0] - await time_out_assert( - 15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name() - ) - - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"0")) - - await time_out_assert(15, cat_wallet_1.get_confirmed_balance, 100) - await time_out_assert(15, cat_wallet_1.get_unconfirmed_balance, 100) - - assert cat_wallet_1.cat_info.limitations_program_hash is not None - asset_id = cat_wallet_1.get_asset_id() - - cat_wallet_0: CATWallet = await CATWallet.create_wallet_for_cat( - wallet_node_0.wallet_state_manager, wallet_0, asset_id - ) - - assert cat_wallet_1.cat_info.limitations_program_hash == cat_wallet_0.cat_info.limitations_program_hash - - nft_wallet_1 = await NFTWallet.create_new_nft_wallet( - wallet_node_1.wallet_state_manager, wallet_1, did_wallet_1.id() - ) - # nft_coin_info: NFTCoinInfo, - # new_did, - # new_did_parent, - # new_did_inner_hash, - # new_did_amount, - # trade_price_list, - did_coin_threeple = await did_wallet_1.get_info_for_recovery() - trade_price_list = [[10], [20, bytes.fromhex(asset_id)]] - # trade_price_list = [[10]] - - sb = await nft_wallet_0.transfer_nft( - coins[0].coin.name(), - nft_wallet_1.nft_wallet_info.my_did, - did_coin_threeple[1], - trade_price_list, - ) - assert sb is not None - - full_sb = await nft_wallet_1.receive_nft(sb) - assert full_sb is not None - # await nft_wallet_0.receive_nft(sb) - - # from chia.wallet.util.debug_spend_bundle import debug_spend_bundle - # debug_spend_bundle(full_sb) - # breakpoint() - await asyncio.sleep(3) - - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - await asyncio.sleep(10) - - coins = nft_wallet_0.nft_wallet_info.my_nft_coins - assert len(coins) == 0 - coins = nft_wallet_1.nft_wallet_info.my_nft_coins - assert len(coins) == 1 - - await time_out_assert(15, cat_wallet_0.get_confirmed_balance, 4) - await time_out_assert(15, cat_wallet_0.get_unconfirmed_balance, 4) - - # Send it back to original owner - did_coin_threeple = await did_wallet_0.get_info_for_recovery() - trade_price_list = [[10]] - - await asyncio.sleep(10) - - nsb = await nft_wallet_1.transfer_nft( - coins[0].coin.name(), - nft_wallet_0.nft_wallet_info.my_did, - did_coin_threeple[1], - trade_price_list, - ) - assert sb is not None - - full_sb = await nft_wallet_0.receive_nft(nsb) - - assert full_sb is not None - await asyncio.sleep(5) - - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - - await asyncio.sleep(10) - coins = nft_wallet_0.nft_wallet_info.my_nft_coins - assert len(coins) == 1 - - nft_info: NFTInfo = get_nft_info_from_puzzle(coins[0].full_puzzle, coins[0].coin) - assert nft_info.data_uris[0] == "https://www.chia.net/img/branding/chia-logo.svg" - - coins = nft_wallet_1.nft_wallet_info.my_nft_coins - assert len(coins) == 0 - - @pytest.mark.parametrize( - "trusted", - [True], - ) - @pytest.mark.asyncio - async def test_nft_wallet_creation_no_trade_price(self, three_wallet_nodes, trusted): - num_blocks = 5 - full_nodes, wallets = three_wallet_nodes - full_node_api = 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_node_2, server_2 = wallets[2] - wallet_0 = wallet_node_0.wallet_state_manager.main_wallet - wallet_1 = wallet_node_1.wallet_state_manager.main_wallet - wallet_2 = wallet_node_2.wallet_state_manager.main_wallet - - ph = await wallet_0.get_new_puzzlehash() - ph1 = await wallet_1.get_new_puzzlehash() - ph2 = await wallet_2.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() - } - wallet_node_2.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"] = {} - wallet_node_2.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) - await server_2.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) - - for i 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) - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2)) - - # Wallet1 sets up DIDWallet1 without any backup set - async with wallet_node_0.wallet_state_manager.lock: - did_wallet_0: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(101) - ) - - 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 i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) - - await time_out_assert(15, did_wallet_0.get_confirmed_balance, 101) - await time_out_assert(15, did_wallet_0.get_unconfirmed_balance, 101) - await time_out_assert(15, did_wallet_0.get_pending_change_balance, 0) - - nft_wallet_0 = await NFTWallet.create_new_nft_wallet( - wallet_node_0.wallet_state_manager, wallet_0, did_wallet_0.id() - ) - metadata = Program.to( - [ - ("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), - ("h", 0xD4584AD463139FA8C0D9F68F4B59F185), - ] - ) - tr = await nft_wallet_0.generate_new_nft(metadata, 2000, ph) - - await time_out_assert_not_none( - 5, full_node_api.full_node.mempool_manager.get_spendbundle, tr.spend_bundle.name() - ) - - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - - await asyncio.sleep(3) - coins = nft_wallet_0.nft_wallet_info.my_nft_coins - assert len(coins) == 1 - - # Wallet2 sets up DIDWallet2 without any backup set - async with wallet_node_1.wallet_state_manager.lock: - did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_1.wallet_state_manager, wallet_1, uint64(201) - ) - - spend_bundle_list = await wallet_node_1.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet_1.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 i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - - await time_out_assert(15, did_wallet_1.get_confirmed_balance, 201) - await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 201) - nft_wallet_1 = await NFTWallet.create_new_nft_wallet( - wallet_node_1.wallet_state_manager, wallet_1, did_wallet_1.id() - ) - # nft_coin_info: NFTCoinInfo, - # new_did, - # new_did_parent, - # new_did_inner_hash, - # new_did_amount, - # trade_price_list, - did_coin_threeple = await did_wallet_1.get_info_for_recovery() - trade_price_list = 0 - - sb = await nft_wallet_0.transfer_nft( - coins[0].coin.name(), - nft_wallet_1.nft_wallet_info.my_did, - did_coin_threeple[1], - trade_price_list, - ) - assert sb is not None - - # full_sb = await nft_wallet_1.receive_nft(sb) - # await nft_wallet_1.receive_nft(sb) - assert sb is not None - await asyncio.sleep(5) - - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - await asyncio.sleep(5) - - coins = nft_wallet_0.nft_wallet_info.my_nft_coins - assert len(coins) == 0 - coins = nft_wallet_1.nft_wallet_info.my_nft_coins - assert len(coins) == 1 - - # Send it back to original owner - did_coin_threeple = await did_wallet_0.get_info_for_recovery() - trade_price_list = 0 - - await asyncio.sleep(3) - - nsb = await nft_wallet_1.transfer_nft( - coins[0].coin.name(), - nft_wallet_0.nft_wallet_info.my_did, - did_coin_threeple[1], - trade_price_list, - ) - assert sb is not None - - # full_sb = await nft_wallet_0.receive_nft(nsb) - # await nft_wallet_0.receive_nft(nsb) - assert nsb is not None - await asyncio.sleep(5) - - for i in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) - - await asyncio.sleep(5) - coins = nft_wallet_0.nft_wallet_info.my_nft_coins - assert len(coins) == 1 - - coins = nft_wallet_1.nft_wallet_info.my_nft_coins - assert len(coins) == 0 From 8a32eee2adae54ac9a2980f676403be6bf4767d5 Mon Sep 17 00:00:00 2001 From: Sebastjan Date: Thu, 5 May 2022 11:26:14 +0200 Subject: [PATCH 37/43] workflow files? --- .../build-test-macos-wallet-nft_wallet.yml | 101 +++++++++++++++++ .../build-test-ubuntu-wallet-nft_wallet.yml | 103 ++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 .github/workflows/build-test-macos-wallet-nft_wallet.yml create mode 100644 .github/workflows/build-test-ubuntu-wallet-nft_wallet.yml diff --git a/.github/workflows/build-test-macos-wallet-nft_wallet.yml b/.github/workflows/build-test-macos-wallet-nft_wallet.yml new file mode 100644 index 000000000000..a6358aa1bba6 --- /dev/null +++ b/.github/workflows/build-test-macos-wallet-nft_wallet.yml @@ -0,0 +1,101 @@ +# +# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme +# +name: MacOS wallet-nft_wallet Test + +on: + push: + branches: + - main + tags: + - '**' + pull_request: + branches: + - '**' + +concurrency: + # SHA is added to the end if on `main` to let all main workflows run + group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ github.ref == 'refs/heads/main' && github.sha || '' }} + cancel-in-progress: true + +jobs: + build: + name: MacOS wallet-nft_wallet Tests + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + max-parallel: 4 + matrix: + python-version: ['3.9', '3.10'] + os: [macOS-latest] + env: + CHIA_ROOT: ${{ github.workspace }}/.chia/mainnet + JOB_FILE_NAME: tests_${{ matrix.os }}_python-${{ matrix.python-version }}_wallet-nft_wallet + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup Python environment + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Create keychain for CI use + run: | + security create-keychain -p foo chiachain + security default-keychain -s chiachain + security unlock-keychain -p foo chiachain + security set-keychain-settings -t 7200 -u chiachain + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Cache pip + uses: actions/cache@v3 + with: + # Note that new runners may break this https://github.com/actions/cache/issues/292 + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + +# Omitted checking out blocks and plots repo Chia-Network/test-cache + + - name: Run install script + env: + INSTALL_PYTHON_VERSION: ${{ matrix.python-version }} + run: | + brew install boost + sh install.sh -d + +# Omitted installing Timelord + + - 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_wallet.py + + - name: Process coverage data + run: | + venv/bin/coverage combine --rcfile=.coveragerc .coverage.* + venv/bin/coverage xml --rcfile=.coveragerc -o coverage.xml + mkdir coverage_reports + cp .coverage "coverage_reports/.coverage.${{ env.JOB_FILE_NAME }}" + cp coverage.xml "coverage_reports/coverage.${{ env.JOB_FILE_NAME }}.xml" + venv/bin/coverage report --rcfile=.coveragerc --show-missing + + - name: Publish coverage + uses: actions/upload-artifact@v3 + with: + name: coverage + path: coverage_reports/* + if-no-files-found: error +# +# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme +# diff --git a/.github/workflows/build-test-ubuntu-wallet-nft_wallet.yml b/.github/workflows/build-test-ubuntu-wallet-nft_wallet.yml new file mode 100644 index 000000000000..cc20290c9ac4 --- /dev/null +++ b/.github/workflows/build-test-ubuntu-wallet-nft_wallet.yml @@ -0,0 +1,103 @@ +# +# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme +# +name: Ubuntu wallet-nft_wallet Test + +on: + push: + branches: + - main + tags: + - '**' + pull_request: + branches: + - '**' + +concurrency: + # SHA is added to the end if on `main` to let all main workflows run + group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ github.ref == 'refs/heads/main' && github.sha || '' }} + cancel-in-progress: true + +jobs: + build: + name: Ubuntu wallet-nft_wallet Test + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + max-parallel: 4 + matrix: + python-version: ['3.7', '3.8', '3.9', '3.10'] + os: [ubuntu-latest] + env: + CHIA_ROOT: ${{ github.workspace }}/.chia/mainnet + JOB_FILE_NAME: tests_${{ matrix.os }}_python-${{ matrix.python-version }}_wallet-nft_wallet + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup Python environment + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Cache npm + uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Cache pip + uses: actions/cache@v3 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + +# Omitted checking out blocks and plots repo Chia-Network/test-cache + + - name: Run install script + env: + INSTALL_PYTHON_VERSION: ${{ matrix.python-version }} + run: | + sh install.sh -d + +# Omitted installing Timelord + + - 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" -p no:monitor tests/wallet/nft_wallet/test_nft_clvm.py tests/wallet/nft_wallet/test_nft_wallet.py + + - name: Process coverage data + run: | + venv/bin/coverage combine --rcfile=.coveragerc .coverage.* + venv/bin/coverage xml --rcfile=.coveragerc -o coverage.xml + mkdir coverage_reports + cp .coverage "coverage_reports/.coverage.${{ env.JOB_FILE_NAME }}" + cp coverage.xml "coverage_reports/coverage.${{ env.JOB_FILE_NAME }}.xml" + venv/bin/coverage report --rcfile=.coveragerc --show-missing + + - name: Publish coverage + uses: actions/upload-artifact@v3 + with: + name: coverage + path: coverage_reports/* + if-no-files-found: error + +# Omitted resource usage check + +# +# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme +# From 694b737cccbab9d909af673afcb294cddf6055e0 Mon Sep 17 00:00:00 2001 From: Amine Khaldi Date: Thu, 5 May 2022 11:23:28 +0100 Subject: [PATCH 38/43] Attempt to fix workflows. --- .github/workflows/build-test-macos-core-cmds.yml | 2 +- .github/workflows/build-test-ubuntu-core-cmds.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test-macos-core-cmds.yml b/.github/workflows/build-test-macos-core-cmds.yml index 4c5aa735fd00..4b0f2580c0d5 100644 --- a/.github/workflows/build-test-macos-core-cmds.yml +++ b/.github/workflows/build-test-macos-core-cmds.yml @@ -79,7 +79,7 @@ jobs: - name: Test core-cmds code with pytest run: | . ./activate - venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 4 -m "not benchmark" tests/core/cmds/test_keys.py tests/core/cmds/test_wallet.py + venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 4 -m "not benchmark" tests/core/cmds/test_did_wallet.py tests/core/cmds/test_keys.py tests/core/cmds/test_wallet.py - name: Process coverage data run: | diff --git a/.github/workflows/build-test-ubuntu-core-cmds.yml b/.github/workflows/build-test-ubuntu-core-cmds.yml index 1de478c117bd..82dbc2688710 100644 --- a/.github/workflows/build-test-ubuntu-core-cmds.yml +++ b/.github/workflows/build-test-ubuntu-core-cmds.yml @@ -78,7 +78,7 @@ jobs: - name: Test core-cmds code with pytest run: | . ./activate - venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 4 -m "not benchmark" -p no:monitor tests/core/cmds/test_keys.py tests/core/cmds/test_wallet.py + venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 4 -m "not benchmark" -p no:monitor tests/core/cmds/test_did_wallet.py tests/core/cmds/test_keys.py tests/core/cmds/test_wallet.py - name: Process coverage data run: | From bddd7e88a5bb172f5d22186e3f645135f2858486 Mon Sep 17 00:00:00 2001 From: Amine Khaldi Date: Thu, 5 May 2022 11:31:18 +0100 Subject: [PATCH 39/43] Workflows? --- .github/workflows/build-test-macos-core-cmds.yml | 2 +- .github/workflows/build-test-ubuntu-core-cmds.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test-macos-core-cmds.yml b/.github/workflows/build-test-macos-core-cmds.yml index 4b0f2580c0d5..4c5aa735fd00 100644 --- a/.github/workflows/build-test-macos-core-cmds.yml +++ b/.github/workflows/build-test-macos-core-cmds.yml @@ -79,7 +79,7 @@ jobs: - name: Test core-cmds code with pytest run: | . ./activate - venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 4 -m "not benchmark" tests/core/cmds/test_did_wallet.py tests/core/cmds/test_keys.py tests/core/cmds/test_wallet.py + venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 4 -m "not benchmark" tests/core/cmds/test_keys.py tests/core/cmds/test_wallet.py - name: Process coverage data run: | diff --git a/.github/workflows/build-test-ubuntu-core-cmds.yml b/.github/workflows/build-test-ubuntu-core-cmds.yml index 82dbc2688710..1de478c117bd 100644 --- a/.github/workflows/build-test-ubuntu-core-cmds.yml +++ b/.github/workflows/build-test-ubuntu-core-cmds.yml @@ -78,7 +78,7 @@ jobs: - name: Test core-cmds code with pytest run: | . ./activate - venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 4 -m "not benchmark" -p no:monitor tests/core/cmds/test_did_wallet.py tests/core/cmds/test_keys.py tests/core/cmds/test_wallet.py + venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 4 -m "not benchmark" -p no:monitor tests/core/cmds/test_keys.py tests/core/cmds/test_wallet.py - name: Process coverage data run: | From 2566f68db429351953e98f63757d90185bd0cd7c Mon Sep 17 00:00:00 2001 From: Amine Khaldi Date: Thu, 5 May 2022 11:38:48 +0100 Subject: [PATCH 40/43] Prepare test blocks and plots for NFT wallet tests. --- tests/wallet/nft_wallet/config.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/wallet/nft_wallet/config.py diff --git a/tests/wallet/nft_wallet/config.py b/tests/wallet/nft_wallet/config.py new file mode 100644 index 000000000000..0257db4372d0 --- /dev/null +++ b/tests/wallet/nft_wallet/config.py @@ -0,0 +1 @@ +checkout_blocks_and_plots = True From b69323e40e7a1c4db48a8a0dee07a853f3456d6f Mon Sep 17 00:00:00 2001 From: Amine Khaldi Date: Thu, 5 May 2022 11:41:46 +0100 Subject: [PATCH 41/43] Reflect the previous commit into workflows. --- .../build-test-macos-wallet-nft_wallet.yml | 16 +++++++++++++++- .../build-test-ubuntu-wallet-nft_wallet.yml | 16 +++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test-macos-wallet-nft_wallet.yml b/.github/workflows/build-test-macos-wallet-nft_wallet.yml index a6358aa1bba6..9ad5646d84b0 100644 --- a/.github/workflows/build-test-macos-wallet-nft_wallet.yml +++ b/.github/workflows/build-test-macos-wallet-nft_wallet.yml @@ -65,7 +65,21 @@ jobs: restore-keys: | ${{ runner.os }}-pip- -# Omitted checking out blocks and plots repo Chia-Network/test-cache + - name: Cache test blocks and plots + uses: actions/cache@v2 + id: test-blocks-plots + with: + path: | + ${{ github.workspace }}/.chia/blocks + ${{ github.workspace }}/.chia/test-plots + key: 0.29.0 + + - name: Checkout test blocks and plots + if: steps.test-blocks-plots.outputs.cache-hit != 'true' + run: | + wget -qO- https://github.com/Chia-Network/test-cache/archive/refs/tags/0.29.0.tar.gz | tar xzf - + mkdir ${{ github.workspace }}/.chia + mv ${{ github.workspace }}/test-cache-0.29.0/* ${{ github.workspace }}/.chia - name: Run install script env: diff --git a/.github/workflows/build-test-ubuntu-wallet-nft_wallet.yml b/.github/workflows/build-test-ubuntu-wallet-nft_wallet.yml index cc20290c9ac4..911d158ed014 100644 --- a/.github/workflows/build-test-ubuntu-wallet-nft_wallet.yml +++ b/.github/workflows/build-test-ubuntu-wallet-nft_wallet.yml @@ -65,7 +65,21 @@ jobs: restore-keys: | ${{ runner.os }}-pip- -# Omitted checking out blocks and plots repo Chia-Network/test-cache + - name: Cache test blocks and plots + uses: actions/cache@v2 + id: test-blocks-plots + with: + path: | + ${{ github.workspace }}/.chia/blocks + ${{ github.workspace }}/.chia/test-plots + key: 0.29.0 + + - name: Checkout test blocks and plots + if: steps.test-blocks-plots.outputs.cache-hit != 'true' + run: | + wget -qO- https://github.com/Chia-Network/test-cache/archive/refs/tags/0.29.0.tar.gz | tar xzf - + mkdir ${{ github.workspace }}/.chia + mv ${{ github.workspace }}/test-cache-0.29.0/* ${{ github.workspace }}/.chia - name: Run install script env: From 4df1f4658d9d29704c7e876aa8913fe5863344ac Mon Sep 17 00:00:00 2001 From: Sebastjan Date: Thu, 5 May 2022 13:29:39 +0200 Subject: [PATCH 42/43] clvm compilation fix --- chia/wallet/puzzles/nft_innerpuz.clvm.hex | 2 +- .../puzzles/nft_metadata_updater.clvm.hex | 2 +- .../nft_metadata_updater.clvm.hex.sha256tree | 1 + chia/wallet/puzzles/nft_ownership_layer.clvm | 78 ------------------- .../puzzles/nft_ownership_layer.clvm.hex | 1 + .../nft_ownership_layer.clvm.hex.sha256tree | 0 .../nft_ownership_transfer_program.clvm.hex | 2 +- chia/wallet/puzzles/nft_state_layer.clvm.hex | 2 +- .../nft_state_layer.clvm.hex.sha256tree | 1 + .../puzzles/nft_transfer_program.clvm.hex | 2 +- chia/wallet/puzzles/nft_v1_innerpuz.clvm | 1 + chia/wallet/puzzles/nft_v1_innerpuz.clvm.hex | 1 + .../nft_v1_innerpuz.clvm.hex.sha256tree | 1 + nft_innerpuz.clvm.hex.sha256tree | 0 nft_metadata_updater.clvm.hex.sha256tree | 0 nft_ownership_layer.clvm.hex.sha256tree | 0 ...rship_transfer_program.clvm.hex.sha256tree | 0 nft_state_layer.clvm.hex.sha256tree | 0 nft_transfer_program.clvm.hex.sha256tree | 0 nft_v1_innerpuz.clvm.hex | 1 + nft_v1_innerpuz.clvm.hex.sha256tree | 0 tests/clvm/test_clvm_compilation.py | 4 + 22 files changed, 16 insertions(+), 83 deletions(-) create mode 100644 chia/wallet/puzzles/nft_metadata_updater.clvm.hex.sha256tree delete mode 100644 chia/wallet/puzzles/nft_ownership_layer.clvm create mode 100644 chia/wallet/puzzles/nft_ownership_layer.clvm.hex create mode 100644 chia/wallet/puzzles/nft_ownership_layer.clvm.hex.sha256tree create mode 100644 chia/wallet/puzzles/nft_state_layer.clvm.hex.sha256tree create mode 100644 chia/wallet/puzzles/nft_v1_innerpuz.clvm.hex create mode 100644 chia/wallet/puzzles/nft_v1_innerpuz.clvm.hex.sha256tree create mode 100644 nft_innerpuz.clvm.hex.sha256tree create mode 100644 nft_metadata_updater.clvm.hex.sha256tree create mode 100644 nft_ownership_layer.clvm.hex.sha256tree create mode 100644 nft_ownership_transfer_program.clvm.hex.sha256tree create mode 100644 nft_state_layer.clvm.hex.sha256tree create mode 100644 nft_transfer_program.clvm.hex.sha256tree create mode 100644 nft_v1_innerpuz.clvm.hex create mode 100644 nft_v1_innerpuz.clvm.hex.sha256tree diff --git a/chia/wallet/puzzles/nft_innerpuz.clvm.hex b/chia/wallet/puzzles/nft_innerpuz.clvm.hex index 491fe4e8d005..5b131e692658 100644 --- a/chia/wallet/puzzles/nft_innerpuz.clvm.hex +++ b/chia/wallet/puzzles/nft_innerpuz.clvm.hex @@ -1 +1 @@ -ff02ffff01ff04ffff04ff10ffff04ff12ff808080ffff02ffff03ff8202ffffff01ff02ffff03ffff09ffff02ff3effff04ff02ffff04ff820bffff80808080ff2f80ffff01ff04ffff04ff28ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff3b8080ff80808080ff808080808080ffff02ff3effff04ff02ffff04ffff04ff8217ffffff04ff8202ffff808080ff8080808080ff808080ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff2fffff04ff5fffff04ff81bfffff04ff8202ffffff04ffff02ff820bffffff04ff81bfffff04ff5fffff04ff0bffff04ff17ffff04ff82017fffff04ff8202ffffff04ff8205ffffff04ff8217ffff80808080808080808080ffff01ff808080808080808080808080ffff01ff088080ff0180ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff81bfff80808080ffff04ffff02ff3effff04ff02ffff04ff5fff80808080ffff04ffff0bff12ff2f80ffff04ffff0bff12ff1780ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff17ff8080ff8080808080ffff04ffff04ff28ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff3b8080ff80808080ff808080808080ffff016180ff808080ffff04ffff04ff2cffff04ff17ff808080ff8080808080ff018080ffff04ffff01ffffff49ff3f02ff33ff3e04ffff01ff0102ffffff02ffff03ff05ffff01ff02ff26ffff04ff02ffff04ff0dffff04ffff0bff3affff0bff12ff3c80ffff0bff3affff0bff3affff0bff12ff2a80ff0980ffff0bff3aff0bffff0bff12ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff82017fffff01ff02ffff03ffff09ff82047fffff0181ea80ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff820a7fff80808080ffff04ffff02ff3effff04ff02ffff04ff82167fff80808080ffff04ffff0bff12ff1780ffff04ffff0bff12ff81bf80ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff81bfff8080ff8080808080ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff01ff018080808080808080808080ffff01ff04ff82027fffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff04ff8202ffff80808080808080808080808080ff0180ffff01ff02ffff03ff8202ffff80ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff5fff80808080ffff04ffff02ff3effff04ff02ffff04ff2fff80808080ffff04ffff0bff12ff1780ffff04ffff0bff12ff81bf80ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff81bfff8080ff8080808080ff808080ff018080ff0180ffff0bff3affff0bff12ff3880ffff0bff3affff0bff3affff0bff12ff2a80ff0580ffff0bff3affff02ff26ffff04ff02ffff04ff07ffff04ffff0bff12ff1280ff8080808080ffff0bff12ff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bff12ff058080ff0180ff018080 \ No newline at end of file +ff02ffff01ff04ffff04ff10ffff04ff12ff808080ffff02ffff03ff8202ffffff01ff02ffff03ffff09ffff02ff3effff04ff02ffff04ff820bffff80808080ff2f80ffff01ff04ffff04ff28ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff3b8080ff80808080ff808080808080ffff02ff3effff04ff02ffff04ffff04ff8217ffffff04ff8202ffff808080ff8080808080ff808080ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff2fffff04ff5fffff04ff81bfffff04ff8202ffffff04ffff02ff820bffffff04ff81bfffff04ff5fffff04ff0bffff04ff17ffff04ff82017fffff04ff8202ffffff04ff8205ffffff04ff8217ffff80808080808080808080ffff01ff808080808080808080808080ffff01ff088080ff0180ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff81bfff80808080ffff04ffff02ff3effff04ff02ffff04ff5fff80808080ffff04ffff0bff12ff2f80ffff04ffff0bff12ff1780ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff17ff8080ff8080808080ffff04ffff04ff28ffff04ffff0bffff02ff2effff04ff02ffff04ff13ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff13ffff04ff17ff3b8080ff80808080ff808080808080ffff016180ff808080ffff04ffff04ff2cffff04ff17ff808080ff8080808080ff018080ffff04ffff01ffffff49ff3f02ff33ff3e04ffff01ff0102ffffff02ffff03ff05ffff01ff02ff26ffff04ff02ffff04ff0dffff04ffff0bff3affff0bff12ff3c80ffff0bff3affff0bff3affff0bff12ff2a80ff0980ffff0bff3aff0bffff0bff12ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff82017fffff01ff02ffff03ffff09ff82047fffff0181ea80ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff820a7fff80808080ffff04ffff02ff3effff04ff02ffff04ff82167fff80808080ffff04ffff0bff12ff1780ffff04ffff0bff12ff81bf80ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff81bfff8080ff8080808080ffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff01ff018080808080808080808080ffff01ff04ff82027fffff02ff36ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82037fffff04ff8202ffff80808080808080808080808080ff0180ffff01ff02ffff03ff8202ffff80ffff01ff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff02ff3effff04ff02ffff04ff5fff80808080ffff04ffff02ff3effff04ff02ffff04ff2fff80808080ffff04ffff0bff12ff1780ffff04ffff0bff12ff81bf80ffff04ffff02ff3effff04ff02ffff04ff0bff80808080ffff04ffff0bff12ff0580ff80808080808080808080ffff04ff12ffff04ffff04ff81bfff8080ff8080808080ff808080ff018080ff0180ffff0bff3affff0bff12ff3880ffff0bff3affff0bff3affff0bff12ff2a80ff0580ffff0bff3affff02ff26ffff04ff02ffff04ff07ffff04ffff0bff12ff1280ff8080808080ffff0bff12ff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bff12ff058080ff0180ff018080 diff --git a/chia/wallet/puzzles/nft_metadata_updater.clvm.hex b/chia/wallet/puzzles/nft_metadata_updater.clvm.hex index 5ffa0b747a81..4354bceba900 100644 --- a/chia/wallet/puzzles/nft_metadata_updater.clvm.hex +++ b/chia/wallet/puzzles/nft_metadata_updater.clvm.hex @@ -1 +1 @@ -ff02ffff01ff02ffff03ff0bffff01ff04ffff02ff02ffff04ff02ffff04ff05ffff04ff0bff8080808080ffff01ff808080ffff01ff04ff05ffff01ff80808080ff0180ffff04ffff01ff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff02ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff018080 \ No newline at end of file +ff02ffff01ff02ffff03ff0bffff01ff04ffff02ff02ffff04ff02ffff04ff05ffff04ff0bff8080808080ffff01ff808080ffff01ff04ff05ffff01ff80808080ff0180ffff04ffff01ff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff02ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff018080 diff --git a/chia/wallet/puzzles/nft_metadata_updater.clvm.hex.sha256tree b/chia/wallet/puzzles/nft_metadata_updater.clvm.hex.sha256tree new file mode 100644 index 000000000000..33850df9e7ae --- /dev/null +++ b/chia/wallet/puzzles/nft_metadata_updater.clvm.hex.sha256tree @@ -0,0 +1 @@ +3df9de54667a96f32eba322635f14d3474edadf23f396ba5a3e2e077a89a682a diff --git a/chia/wallet/puzzles/nft_ownership_layer.clvm b/chia/wallet/puzzles/nft_ownership_layer.clvm deleted file mode 100644 index 8a26630dc9d3..000000000000 --- a/chia/wallet/puzzles/nft_ownership_layer.clvm +++ /dev/null @@ -1,78 +0,0 @@ -(mod ( - NFT_OWNERSHIP_LAYER_MOD_HASH - CURRENT_OWNER - TRANSFER_PROGRAM_HASH - INNER_PUZZLE - inner_solution - ) - - (include condition_codes.clvm) - (include curry-and-treehash.clinc) - - (defconstant MAGIC_NUMBER -10) - - (defun sha256tree1 - (TREE) - (if (l TREE) - (sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE))) - (sha256 1 TREE) - ) - ) - - (defun-ineline nft_ownership_layer_puzzle_hash (NFT_OWNERSHIP_LAYER_MOD_HASH new_owner TRANSFER_PROGRAM_HASH inner_puzzle_hash) - (puzzle-hash-of-curried-function NFT_OWNERSHIP_LAYER_MOD_HASH - (sha256 ONE inner_puzzle_hash) - (sha256 ONE TRANSFER_PROGRAM_HASH) - (sha256 ONE new_owner) - (sha256 ONE NFT_OWNERSHIP_LAYER_MOD_HASH) - ) - ) - - (defun wrap_odd_create_coins (NFT_OWNERSHIP_LAYER_MOD_HASH (new_owner . conditions) TRANSFER_PROGRAM_HASH inner_puzzle_hash) - (if conditions - (if (= (f (f conditions)) CREATE_COIN) - (if (logand (f (r (r (f conditions)))) ONE) - (c (list CREATE_COIN (nft_ownership_layer_puzzle_hash NFT_OWNERSHIP_LAYER_MOD_HASH new_owner TRANSFER_PROGRAM_HASH) (f (r (r (f conditions))))) (wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH (c new_owner (r conditions)) TRANSFER_PROGRAM_HASH inner_puzzle_hash))) - (c (f conditions) (wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH (c new_owner (r conditions)) TRANSFER_PROGRAM_HASH inner_puzzle_hash)) - ) - (c (f conditions) (wrap_odd_create_coins NFT_OWNERSHIP_LAYER_MOD_HASH (c new_owner (r conditions)) TRANSFER_PROGRAM_HASH inner_puzzle_hash)) - ) - () - ) - ) - - (defun merge_list (list_a list_b) - (if list_a - (c (f list_a) (merge_list (r list_a) list_b)) - list_b - ) - ) - - (defun process_trans_program (list_a list_b) - (c (f list_a) (merge_list (r list_a) list_b)) - ) - - (defun loop_through_conditions_looking_for_transfer_program_reveal_and_solution_and_add_its_conditions (TRANSFER_PROGRAM_HASH conditions) - (if conditions - (if (= (f (f conditions)) MAGIC_NUMBER) - (if (= (sha256tree1 (f (r (f conditions)))) TRANSFER_PROGRAM_HASH) - (process_trans_program (a (f (r (f conditions))) (f (r (r (f conditions))))) conditions) - (x) - ) - (c (f conditions) (loop_through_conditions_looking_for_transfer_program_reveal_and_solution_and_add_its_conditions TRANSFER_PROGRAM_HASH (r conditions))) - ) - (x) - ) - ) - - ; main - (wrap_odd_create_coins - NFT_OWNERSHIP_LAYER_MOD_HASH - (loop_through_conditions_looking_for_transfer_program_reveal_and_solution_and_add_its_conditions - TRANSFER_PROGRAM_HASH - (a INNER_PUZZLE inner_solution) - ) - TRANSFER_PROGRAM_HASH - (sha256tree1 INNER_PUZZLE) - ) -) diff --git a/chia/wallet/puzzles/nft_ownership_layer.clvm.hex b/chia/wallet/puzzles/nft_ownership_layer.clvm.hex new file mode 100644 index 000000000000..9c6dd3929890 --- /dev/null +++ b/chia/wallet/puzzles/nft_ownership_layer.clvm.hex @@ -0,0 +1 @@ +expected defun, defun-inline, defmacro, or defconstant diff --git a/chia/wallet/puzzles/nft_ownership_layer.clvm.hex.sha256tree b/chia/wallet/puzzles/nft_ownership_layer.clvm.hex.sha256tree new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex b/chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex index d495e4c9351e..956a1e674d5a 100644 --- a/chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex +++ b/chia/wallet/puzzles/nft_ownership_transfer_program.clvm.hex @@ -1 +1 @@ -ff02ffff01ff04ffff04ffff04ff38ffff04ff81bfff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff5fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff17ff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e808080ff81bf80ff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff81bfff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e80808080ff808080ffff02ff26ffff04ff02ffff04ff0bffff04ff8202ffffff04ff15ff808080808080808080ff8080ffff04ffff01ffffff3fff0281eaffff3304ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff3480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff5dffff04ff2dffff04ffff0bffff0101ff5380ffff04ffff0bffff0101ff5d80ff80808080808080ffff02ff3effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff128080ff808080ff808080ff8080808080ff808080ffff01ff04ff24ffff04ff09ffff04ffff02ff2effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff128080ff80808080ff8080808080ff0180ffff02ff26ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ff0bff2affff0bff2cff2880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff18ff05ffff010180ffff01ff11ff05ffff010180ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080 \ No newline at end of file +ff02ffff01ff04ffff04ffff04ff38ffff04ff81bfff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff5fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff17ff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e808080ff81bf80ff808080ffff04ffff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff09ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff81bfff1d8080ff80808080ff808080808080ff15ffff02ff3effff04ff02ffff01ff95726f79616c74795f70726f675f736f6c7574696f6e80808080ff808080ffff02ff26ffff04ff02ffff04ff0bffff04ff8202ffffff04ff15ff808080808080808080ff8080ffff04ffff01ffffff3fff0281eaffff3304ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff3480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffffff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff10ffff04ffff0bffff02ff36ffff04ff02ffff04ff5dffff04ff2dffff04ffff0bffff0101ff5380ffff04ffff0bffff0101ff5d80ff80808080808080ffff02ff3effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff128080ff808080ff808080ff8080808080ff808080ffff01ff04ff24ffff04ff09ffff04ffff02ff2effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff128080ff80808080ff8080808080ff0180ffff02ff26ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ff0bff2affff0bff2cff2880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff18ff05ffff010180ffff01ff11ff05ffff010180ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080 diff --git a/chia/wallet/puzzles/nft_state_layer.clvm.hex b/chia/wallet/puzzles/nft_state_layer.clvm.hex index 30bd28de7774..c26a1f108f14 100644 --- a/chia/wallet/puzzles/nft_state_layer.clvm.hex +++ b/chia/wallet/puzzles/nft_state_layer.clvm.hex @@ -1 +1 @@ -ff02ffff01ff04ffff04ff20ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ff17ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfff8080808080808080ffff04ffff01ffffffff4902ff3304ffff0101ff0281e8ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff2cffff0bff24ff3880ffff0bff2cffff0bff2cffff0bff24ff3480ff0980ffff0bff2cff0bffff0bff24ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ff3c80ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff820167ff80808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ff0bffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff2cffff0bff24ff3080ffff0bff2cffff0bff2cffff0bff24ff3480ff0580ffff0bff2cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff24ff2480ff8080808080ffff0bff24ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff57ffff01ff02ffff03ffff09ff820117ff2880ffff01ff02ffff03ffff18ff820597ff2480ffff01ff04ffff04ff28ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff820297ffff04ffff0bff24ff0b80ffff04ffff02ff2effff04ff02ffff04ff27ff80808080ffff04ffff0bff24ff0580ff8080808080808080ffff04ff2fffff04ff820b97ff8080808080ff81d780ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ff8080ff0180ff018080 \ No newline at end of file +ff02ffff01ff04ffff04ff20ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ff17ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfff8080808080808080ffff04ffff01ffffffff4902ff3304ffff0101ff0281e8ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff2cffff0bff24ff3880ffff0bff2cffff0bff2cffff0bff24ff3480ff0980ffff0bff2cff0bffff0bff24ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ff3c80ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff820167ff80808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ff0bffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff2cffff0bff24ff3080ffff0bff2cffff0bff2cffff0bff24ff3480ff0580ffff0bff2cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff24ff2480ff8080808080ffff0bff24ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff57ffff01ff02ffff03ffff09ff820117ff2880ffff01ff02ffff03ffff18ff820597ff2480ffff01ff04ffff04ff28ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff820297ffff04ffff0bff24ff0b80ffff04ffff02ff2effff04ff02ffff04ff27ff80808080ffff04ffff0bff24ff0580ff8080808080808080ffff04ff2fffff04ff820b97ff8080808080ff81d780ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ff8080ff0180ff018080 diff --git a/chia/wallet/puzzles/nft_state_layer.clvm.hex.sha256tree b/chia/wallet/puzzles/nft_state_layer.clvm.hex.sha256tree new file mode 100644 index 000000000000..72be551e04f0 --- /dev/null +++ b/chia/wallet/puzzles/nft_state_layer.clvm.hex.sha256tree @@ -0,0 +1 @@ +d74cb37517813243fd496c4bea55ae47d43b7b55617992fb9590382d28a92b5b diff --git a/chia/wallet/puzzles/nft_transfer_program.clvm.hex b/chia/wallet/puzzles/nft_transfer_program.clvm.hex index 30ef4f5e5811..e24f46b4ff14 100644 --- a/chia/wallet/puzzles/nft_transfer_program.clvm.hex +++ b/chia/wallet/puzzles/nft_transfer_program.clvm.hex @@ -1 +1 @@ -ff02ffff01ff02ff2affff04ff02ffff04ff05ffff04ff0bffff04ff820affffff04ffff02ff3affff04ff02ffff04ff17ffff04ff2fffff04ff0bffff04ff5fffff04ff81bfffff04ff82017fffff04ff8204ffff80808080808080808080ff80808080808080ffff04ffff01ffffffff3f02ff333effff0401ff01ff82271002ffffffff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff22ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff7cffff0bff34ff2480ffff0bff7cffff0bff7cffff0bff34ff2c80ff0980ffff0bff7cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff17ffff01ff04ffff04ffff0181eaffff04ffff02ff22ffff04ff02ffff04ff05ffff04ff17ff8080808080ffff04ff0bff80808080ff2f80ffff012f80ff0180ff02ffff03ff82017fffff01ff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ffff02ff7effff04ff02ffff04ff82017fff80808080ff8080808080808080808080ffff01ff04ffff04ff38ffff01ff808080ff808080ff0180ffffff04ffff04ff38ffff04ff8202ffff808080ffff04ffff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff81bfffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff5fff1d8080ff80808080ff808080808080ff8202ffff1580ff808080ffff02ff36ffff04ff02ffff04ff17ffff04ff82017fffff04ff15ff8080808080808080ff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff5dffff04ff2dffff04ffff0bff34ff5380ffff04ffff0bff34ff5d80ff80808080808080ffff02ff7effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff808080ff808080ff8080808080ff808080ffff01ff04ff28ffff04ff09ffff04ffff02ff5effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff80808080ff8080808080ff0180ffff02ff36ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ffff0bff7cffff0bff34ff3080ffff0bff7cffff0bff7cffff0bff34ff2c80ff0580ffff0bff7cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff18ff05ff3480ffff01ff11ff05ff3480ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bff34ff058080ff0180ff018080 \ No newline at end of file +ff02ffff01ff02ff2affff04ff02ffff04ff05ffff04ff0bffff04ff820affffff04ffff02ff3affff04ff02ffff04ff17ffff04ff2fffff04ff0bffff04ff5fffff04ff81bfffff04ff82017fffff04ff8204ffff80808080808080808080ff80808080808080ffff04ffff01ffffffff3f02ff333effff0401ff01ff82271002ffffffff02ffff03ff05ffff01ff02ffff03ffff09ff11ffff017580ffff01ff04ffff04ffff0175ffff04ff0bff198080ff0d80ffff01ff04ff09ffff02ff22ffff04ff02ffff04ff0dffff04ff0bff80808080808080ff0180ff8080ff0180ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff7cffff0bff34ff2480ffff0bff7cffff0bff7cffff0bff34ff2c80ff0980ffff0bff7cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff17ffff01ff04ffff04ffff0181eaffff04ffff02ff22ffff04ff02ffff04ff05ffff04ff17ff8080808080ffff04ff0bff80808080ff2f80ffff012f80ff0180ff02ffff03ff82017fffff01ff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ffff02ff7effff04ff02ffff04ff82017fff80808080ff8080808080808080808080ffff01ff04ffff04ff38ffff01ff808080ff808080ff0180ffffff04ffff04ff38ffff04ff8202ffff808080ffff04ffff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff81bfffff04ffff02ff7effff04ff02ffff04ffff04ff09ffff04ff5fff1d8080ff80808080ff808080808080ff8202ffff1580ff808080ffff02ff36ffff04ff02ffff04ff17ffff04ff82017fffff04ff15ff8080808080808080ff02ffff03ff0bffff01ff04ffff02ffff03ff33ffff01ff04ff20ffff04ffff0bffff02ff2effff04ff02ffff04ff5dffff04ff2dffff04ffff0bff34ff5380ffff04ffff0bff34ff5d80ff80808080808080ffff02ff7effff04ff02ffff04ffff04ff17ffff04ffff04ff09ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff808080ff808080ff8080808080ff808080ffff01ff04ff28ffff04ff09ffff04ffff02ff5effff04ff02ffff04ffff05ffff14ffff12ff23ff1580ff5c8080ff80808080ff8080808080ff0180ffff02ff36ffff04ff02ffff04ffff04ff09ffff04ff15ffff04ff2dffff04ff5dff8080808080ffff04ff1bffff04ff17ff80808080808080ff8080ff0180ffff0bff7cffff0bff34ff3080ffff0bff7cffff0bff7cffff0bff34ff2c80ff0580ffff0bff7cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff18ff05ff3480ffff01ff11ff05ff3480ffff010580ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff7effff04ff02ffff04ff09ff80808080ffff02ff7effff04ff02ffff04ff0dff8080808080ffff01ff0bff34ff058080ff0180ff018080 diff --git a/chia/wallet/puzzles/nft_v1_innerpuz.clvm b/chia/wallet/puzzles/nft_v1_innerpuz.clvm index 73c5453bbe24..5b516eb6139f 100644 --- a/chia/wallet/puzzles/nft_v1_innerpuz.clvm +++ b/chia/wallet/puzzles/nft_v1_innerpuz.clvm @@ -1,4 +1,5 @@ (mod (CURRENT_PUBKEY) ; main + (x) ) diff --git a/chia/wallet/puzzles/nft_v1_innerpuz.clvm.hex b/chia/wallet/puzzles/nft_v1_innerpuz.clvm.hex new file mode 100644 index 000000000000..088fcb9a9e70 --- /dev/null +++ b/chia/wallet/puzzles/nft_v1_innerpuz.clvm.hex @@ -0,0 +1 @@ +ff0880 diff --git a/chia/wallet/puzzles/nft_v1_innerpuz.clvm.hex.sha256tree b/chia/wallet/puzzles/nft_v1_innerpuz.clvm.hex.sha256tree new file mode 100644 index 000000000000..e94c93f31602 --- /dev/null +++ b/chia/wallet/puzzles/nft_v1_innerpuz.clvm.hex.sha256tree @@ -0,0 +1 @@ +044ebff3ad6d351856fd37d38c599ad0e416aa8a22621d8c13c27e24561f7f77 diff --git a/nft_innerpuz.clvm.hex.sha256tree b/nft_innerpuz.clvm.hex.sha256tree new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/nft_metadata_updater.clvm.hex.sha256tree b/nft_metadata_updater.clvm.hex.sha256tree new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/nft_ownership_layer.clvm.hex.sha256tree b/nft_ownership_layer.clvm.hex.sha256tree new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/nft_ownership_transfer_program.clvm.hex.sha256tree b/nft_ownership_transfer_program.clvm.hex.sha256tree new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/nft_state_layer.clvm.hex.sha256tree b/nft_state_layer.clvm.hex.sha256tree new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/nft_transfer_program.clvm.hex.sha256tree b/nft_transfer_program.clvm.hex.sha256tree new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/nft_v1_innerpuz.clvm.hex b/nft_v1_innerpuz.clvm.hex new file mode 100644 index 000000000000..409940768f2a --- /dev/null +++ b/nft_v1_innerpuz.clvm.hex @@ -0,0 +1 @@ +23 diff --git a/nft_v1_innerpuz.clvm.hex.sha256tree b/nft_v1_innerpuz.clvm.hex.sha256tree new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/clvm/test_clvm_compilation.py b/tests/clvm/test_clvm_compilation.py index dad4a4b67101..724cf0aec917 100644 --- a/tests/clvm/test_clvm_compilation.py +++ b/tests/clvm/test_clvm_compilation.py @@ -43,6 +43,10 @@ "chia/wallet/puzzles/singleton_top_layer_v1_1.clvm", "chia/wallet/puzzles/nft_innerpuz.clvm", "chia/wallet/puzzles/nft_transfer_program.clvm", + "chia/wallet/puzzles/nft_v1_innerpuz.clvm", + "chia/wallet/puzzles/nft_ownership_transfer_program.clvm", + "chia/wallet/puzzles/nft_metadata_updater.clvm", + "chia/wallet/puzzles/nft_state_layer.clvm", ] ) From 61bb87f2e514b944089a8120100a408c15245461 Mon Sep 17 00:00:00 2001 From: Sebastjan Date: Thu, 5 May 2022 14:40:52 +0200 Subject: [PATCH 43/43] future proof create_coin in nft state layer --- chia/wallet/puzzles/nft_state_layer.clvm | 7 ++++++- chia/wallet/puzzles/nft_state_layer.clvm.hex | 2 +- chia/wallet/puzzles/nft_state_layer.clvm.hex.sha256tree | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/chia/wallet/puzzles/nft_state_layer.clvm b/chia/wallet/puzzles/nft_state_layer.clvm index a85732ff0530..9154e86c1c46 100644 --- a/chia/wallet/puzzles/nft_state_layer.clvm +++ b/chia/wallet/puzzles/nft_state_layer.clvm @@ -31,7 +31,12 @@ (if conditions (if (= (f (f conditions)) CREATE_COIN) (if (logand (f (r (r (f conditions)))) ONE) - (c (list CREATE_COIN (nft_state_layer_puzzle_hash NFT_STATE_LAYER_MOD_HASH METADATA METADATA_UPDATER_PUZZLE_HASH (f (r (f conditions)))) my_amount (f (r (r (r (f conditions))))) ) (r conditions)) + (c + (c CREATE_COIN + (c (nft_state_layer_puzzle_hash NFT_STATE_LAYER_MOD_HASH METADATA METADATA_UPDATER_PUZZLE_HASH (f (r (f conditions)))) + (c my_amount + (r (r (r (f conditions))))))) + (r conditions)) (c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (list METADATA (r conditions)) my_amount)) ) (c (f conditions) (wrap_odd_create_coins NFT_STATE_LAYER_MOD_HASH METADATA_UPDATER_PUZZLE_HASH (list METADATA (r conditions)) my_amount)) diff --git a/chia/wallet/puzzles/nft_state_layer.clvm.hex b/chia/wallet/puzzles/nft_state_layer.clvm.hex index c26a1f108f14..8d77d1c1b4c0 100644 --- a/chia/wallet/puzzles/nft_state_layer.clvm.hex +++ b/chia/wallet/puzzles/nft_state_layer.clvm.hex @@ -1 +1 @@ -ff02ffff01ff04ffff04ff20ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ff17ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfff8080808080808080ffff04ffff01ffffffff4902ff3304ffff0101ff0281e8ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff2cffff0bff24ff3880ffff0bff2cffff0bff2cffff0bff24ff3480ff0980ffff0bff2cff0bffff0bff24ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ff3c80ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff820167ff80808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ff0bffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff2cffff0bff24ff3080ffff0bff2cffff0bff2cffff0bff24ff3480ff0580ffff0bff2cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff24ff2480ff8080808080ffff0bff24ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff57ffff01ff02ffff03ffff09ff820117ff2880ffff01ff02ffff03ffff18ff820597ff2480ffff01ff04ffff04ff28ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff820297ffff04ffff0bff24ff0b80ffff04ffff02ff2effff04ff02ffff04ff27ff80808080ffff04ffff0bff24ff0580ff8080808080808080ffff04ff2fffff04ff820b97ff8080808080ff81d780ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ff8080ff0180ff018080 +ff02ffff01ff04ffff04ff20ffff04ff81bfff808080ffff02ff3effff04ff02ffff04ff05ffff04ff17ffff04ffff02ff3affff04ff02ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff808080808080ffff04ff81bfff8080808080808080ffff04ffff01ffffffff4902ff3304ffff0101ff0281e8ffffffff02ffff03ff05ffff01ff02ff22ffff04ff02ffff04ff0dffff04ffff0bff2cffff0bff24ff3880ffff0bff2cffff0bff2cffff0bff24ff3480ff0980ffff0bff2cff0bffff0bff24ff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff47ff3c80ffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff81a7ff80808080ff0580ffff01ff02ff81a7ffff04ff0bffff04ff820167ff80808080ffff01ff088080ff0180ffff01ff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff37ff80808080808080ff0180ffff01ff04ff0bffff01ff80808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff2affff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ffff02ff32ffff04ff02ffff04ff05ffff04ff0bffff04ff17ff808080808080ffff04ff17ff8080808080ffffff04ff09ffff04ffff02ff2affff04ff02ffff04ff15ffff04ff0bff8080808080ff808080ff0bff2cffff0bff24ff3080ffff0bff2cffff0bff2cffff0bff24ff3480ff0580ffff0bff2cffff02ff22ffff04ff02ffff04ff07ffff04ffff0bff24ff2480ff8080808080ffff0bff24ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff57ffff01ff02ffff03ffff09ff820117ff2880ffff01ff02ffff03ffff18ff820597ff2480ffff01ff04ffff04ff28ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff820297ffff04ffff0bff24ff0b80ffff04ffff02ff2effff04ff02ffff04ff27ff80808080ffff04ffff0bff24ff0580ff8080808080808080ffff04ff2fff820797808080ff81d780ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ffff01ff04ff8197ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ffff04ff27ffff04ff81d7ff808080ffff04ff2fff808080808080808080ff0180ff8080ff0180ff018080 \ No newline at end of file diff --git a/chia/wallet/puzzles/nft_state_layer.clvm.hex.sha256tree b/chia/wallet/puzzles/nft_state_layer.clvm.hex.sha256tree index 72be551e04f0..c239250571ea 100644 --- a/chia/wallet/puzzles/nft_state_layer.clvm.hex.sha256tree +++ b/chia/wallet/puzzles/nft_state_layer.clvm.hex.sha256tree @@ -1 +1 @@ -d74cb37517813243fd496c4bea55ae47d43b7b55617992fb9590382d28a92b5b +29ea2dee6ad649d7d52e561d2e7d72419b52493a58d697749f830db4d92cfbec