From 14d272c5a3d3b04251702cb592cffffd9ab57e6a Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sat, 1 Jun 2024 08:06:01 -0400 Subject: [PATCH 1/5] Rework and expand puzzle types --- crates/chia-puzzles/src/proof.rs | 9 ++ crates/chia-puzzles/src/puzzles/cat.rs | 11 +-- crates/chia-puzzles/src/puzzles/did.rs | 124 ++++++++++++++++++------- crates/chia-puzzles/src/puzzles/nft.rs | 10 +- crates/chia-tools/src/bin/run-spend.rs | 6 +- crates/clvm-traits/src/from_clvm.rs | 37 ++++++++ crates/clvm-traits/src/to_clvm.rs | 37 ++++++++ 7 files changed, 182 insertions(+), 52 deletions(-) diff --git a/crates/chia-puzzles/src/proof.rs b/crates/chia-puzzles/src/proof.rs index 37bfbbf73..2cc7c0e02 100644 --- a/crates/chia-puzzles/src/proof.rs +++ b/crates/chia-puzzles/src/proof.rs @@ -22,6 +22,15 @@ pub struct LineageProof { #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[clvm(list)] pub struct EveProof { + pub parent_parent_coin_id: Bytes32, + pub parent_amount: u64, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[clvm(list)] +pub struct CoinProof { pub parent_coin_info: Bytes32, + pub inner_puzzle_hash: Bytes32, pub amount: u64, } diff --git a/crates/chia-puzzles/src/puzzles/cat.rs b/crates/chia-puzzles/src/puzzles/cat.rs index b7e6beb98..4f55b6f2a 100644 --- a/crates/chia-puzzles/src/puzzles/cat.rs +++ b/crates/chia-puzzles/src/puzzles/cat.rs @@ -4,7 +4,7 @@ use clvm_traits::{FromClvm, ToClvm}; use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash}; use hex_literal::hex; -use crate::LineageProof; +use crate::{CoinProof, LineageProof}; #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] @@ -94,15 +94,6 @@ pub struct CatSolution { pub extra_delta: i64, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(list)] -pub struct CoinProof { - pub parent_coin_info: Bytes32, - pub inner_puzzle_hash: Bytes32, - pub amount: u64, -} - /// This is the puzzle reveal of the [CAT2 standard](https://chialisp.com/cats) puzzle. pub const CAT_PUZZLE: [u8; 1672] = hex!( " diff --git a/crates/chia-puzzles/src/puzzles/did.rs b/crates/chia-puzzles/src/puzzles/did.rs index a8b413396..92ec0b5a2 100644 --- a/crates/chia-puzzles/src/puzzles/did.rs +++ b/crates/chia-puzzles/src/puzzles/did.rs @@ -1,58 +1,82 @@ +use chia_bls::PublicKey; use chia_protocol::Bytes32; -use clvm_traits::{ - clvm_list, match_list, match_tuple, ClvmDecoder, ClvmEncoder, FromClvm, FromClvmError, Raw, - ToClvm, ToClvmError, -}; -use clvm_utils::TreeHash; +use clvm_traits::{FromClvm, ToClvm}; +use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash}; use hex_literal::hex; -use crate::singleton::SingletonStruct; +use crate::{singleton::SingletonStruct, CoinProof}; #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[clvm(curry)] pub struct DidArgs { pub inner_puzzle: I, - pub recovery_did_list_hash: Bytes32, + pub recovery_list_hash: Bytes32, pub num_verifications_required: u64, pub singleton_struct: SingletonStruct, pub metadata: M, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -pub enum DidSolution { - InnerSpend(I), -} - -impl FromClvm for DidSolution -where - I: FromClvm, -{ - fn from_clvm(decoder: &impl ClvmDecoder, node: N) -> Result { - let (mode, args) = )>::from_clvm(decoder, node)?; - match mode { - 1 => Ok(Self::InnerSpend( - ::from_clvm(decoder, args.0)?.0, - )), - _ => Err(FromClvmError::Custom(format!( - "unexpected did spend mode {mode}" - ))), +impl DidArgs { + pub fn new( + inner_puzzle: I, + recovery_list_hash: Bytes32, + num_verifications_required: u64, + singleton_struct: SingletonStruct, + metadata: M, + ) -> Self { + Self { + inner_puzzle, + recovery_list_hash, + num_verifications_required, + singleton_struct, + metadata, } } } -impl ToClvm for DidSolution -where - I: ToClvm, -{ - fn to_clvm(&self, encoder: &mut impl ClvmEncoder) -> Result { - match self { - Self::InnerSpend(solution) => clvm_list!(1, solution).to_clvm(encoder), +impl DidArgs { + pub fn curry_tree_hash( + inner_puzzle: TreeHash, + recovery_list_hash: Bytes32, + num_verifications_required: u64, + singleton_struct: SingletonStruct, + metadata: TreeHash, + ) -> TreeHash { + CurriedProgram { + program: DID_INNER_PUZZLE_HASH, + args: DidArgs { + inner_puzzle, + recovery_list_hash, + num_verifications_required, + singleton_struct, + metadata, + }, } + .tree_hash() } } +#[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[clvm(list)] +#[repr(u8)] +pub enum DidSolution { + Recover(#[clvm(rest)] Box) = 0, + Spend(I) = 1, +} + +#[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[clvm(list)] +pub struct DidRecoverySolution { + pub amount: u64, + pub new_inner_puzzle_hash: Bytes32, + pub recovery_coins: Vec, + pub public_key: PublicKey, + pub recovery_list_reveal: Vec, +} + /// This is the puzzle reveal of the [DID1 standard](https://chialisp.com/dids) puzzle. pub const DID_INNER_PUZZLE: [u8; 1012] = hex!( " @@ -100,7 +124,8 @@ pub const DID_INNER_PUZZLE_HASH: TreeHash = TreeHash::new(hex!( #[cfg(test)] mod tests { - use clvmr::Allocator; + use clvm_traits::{clvm_list, match_list}; + use clvmr::{run_program, serde::node_from_bytes, Allocator, ChiaDialect}; use super::*; @@ -114,7 +139,36 @@ mod tests { #[test] fn did_solution() { let a = &mut Allocator::new(); - let did_solution = DidSolution::InnerSpend(a.nil()); + + let ptr = clvm_list!(1, clvm_list!(42, "test")).to_clvm(a).unwrap(); + let did_solution = DidSolution::::from_clvm(a, ptr).unwrap(); + assert_eq!( + did_solution, + DidSolution::Spend(clvm_list!(42, "test".to_string())) + ); + + let puzzle = node_from_bytes(a, &DID_INNER_PUZZLE).unwrap(); + let curried = CurriedProgram { + program: puzzle, + args: DidArgs::new( + 1, + Bytes32::default(), + 1, + SingletonStruct::new(Bytes32::default()), + (), + ), + } + .to_clvm(a) + .unwrap(); + + run_program(a, &ChiaDialect::new(0), curried, ptr, u64::MAX) + .expect("could not run did puzzle and solution"); + } + + #[test] + fn did_solution_roundtrip() { + let a = &mut Allocator::new(); + let did_solution = DidSolution::Spend(a.nil()); let ptr = did_solution.to_clvm(a).unwrap(); let roundtrip = DidSolution::from_clvm(a, ptr).unwrap(); assert_eq!(did_solution, roundtrip); diff --git a/crates/chia-puzzles/src/puzzles/nft.rs b/crates/chia-puzzles/src/puzzles/nft.rs index 24d1d4724..321ec2ba5 100644 --- a/crates/chia-puzzles/src/puzzles/nft.rs +++ b/crates/chia-puzzles/src/puzzles/nft.rs @@ -132,33 +132,33 @@ pub struct NftOwnershipLayerSolution { pub struct NftRoyaltyTransferPuzzleArgs { pub singleton_struct: SingletonStruct, pub royalty_puzzle_hash: Bytes32, - pub trade_price_percentage: u16, + pub royalty_percentage: u16, } impl NftRoyaltyTransferPuzzleArgs { pub fn new( launcher_id: Bytes32, royalty_puzzle_hash: Bytes32, - trade_price_percentage: u16, + royalty_percentage: u16, ) -> Self { Self { singleton_struct: SingletonStruct::new(launcher_id), royalty_puzzle_hash, - trade_price_percentage, + royalty_percentage, } } pub fn curry_tree_hash( launcher_id: Bytes32, royalty_puzzle_hash: Bytes32, - trade_price_percentage: u16, + royalty_percentage: u16, ) -> TreeHash { CurriedProgram { program: NFT_ROYALTY_TRANSFER_PUZZLE_HASH, args: NftRoyaltyTransferPuzzleArgs { singleton_struct: SingletonStruct::new(launcher_id), royalty_puzzle_hash, - trade_price_percentage, + royalty_percentage, }, } .tree_hash() diff --git a/crates/chia-tools/src/bin/run-spend.rs b/crates/chia-tools/src/bin/run-spend.rs index 777d92b19..faeea0958 100644 --- a/crates/chia-tools/src/bin/run-spend.rs +++ b/crates/chia-tools/src/bin/run-spend.rs @@ -188,7 +188,7 @@ fn print_puzzle_info(a: &Allocator, puzzle: NodePtr, solution: NodePtr) { }; println!( " recovery_did_list_hash: {:?}", - uncurried.args.recovery_did_list_hash + uncurried.args.recovery_list_hash ); println!( " num_verifications_required: {:?}", @@ -205,7 +205,9 @@ fn print_puzzle_info(a: &Allocator, puzzle: NodePtr, solution: NodePtr) { }; println!("\nInner Puzzle\n"); - let DidSolution::InnerSpend(inner_sol) = sol; + let DidSolution::Spend(inner_sol) = sol else { + unimplemented!(); + }; print_puzzle_info(a, uncurried.args.inner_puzzle, inner_sol); } SINGLETON_TOP_LAYER_PUZZLE_HASH => { diff --git a/crates/clvm-traits/src/from_clvm.rs b/crates/clvm-traits/src/from_clvm.rs index d6849b4a4..320f36146 100644 --- a/crates/clvm-traits/src/from_clvm.rs +++ b/crates/clvm-traits/src/from_clvm.rs @@ -1,3 +1,5 @@ +use std::{rc::Rc, sync::Arc}; + use num_bigint::{BigInt, Sign}; use crate::{ClvmDecoder, FromClvmError}; @@ -68,6 +70,33 @@ impl FromClvm for bool { } } +impl FromClvm for Box +where + T: FromClvm, +{ + fn from_clvm(decoder: &impl ClvmDecoder, node: N) -> Result { + T::from_clvm(decoder, node).map(Box::new) + } +} + +impl FromClvm for Rc +where + T: FromClvm, +{ + fn from_clvm(decoder: &impl ClvmDecoder, node: N) -> Result { + T::from_clvm(decoder, node).map(Rc::new) + } +} + +impl FromClvm for Arc +where + T: FromClvm, +{ + fn from_clvm(decoder: &impl ClvmDecoder, node: N) -> Result { + T::from_clvm(decoder, node).map(Arc::new) + } +} + impl FromClvm for (A, B) where A: FromClvm, @@ -237,6 +266,14 @@ mod tests { ); } + #[test] + fn test_smart_pointers() { + let a = &mut Allocator::new(); + assert_eq!(decode(a, "80"), Ok(Box::new(0u8))); + assert_eq!(decode(a, "80"), Ok(Rc::new(0u8))); + assert_eq!(decode(a, "80"), Ok(Arc::new(0u8))); + } + #[test] fn test_pair() { let a = &mut Allocator::new(); diff --git a/crates/clvm-traits/src/to_clvm.rs b/crates/clvm-traits/src/to_clvm.rs index d6ed169ce..0d7281b45 100644 --- a/crates/clvm-traits/src/to_clvm.rs +++ b/crates/clvm-traits/src/to_clvm.rs @@ -1,3 +1,5 @@ +use std::{rc::Rc, sync::Arc}; + use num_bigint::BigInt; use crate::{ClvmEncoder, ToClvmError}; @@ -55,6 +57,33 @@ where } } +impl ToClvm for Box +where + T: ToClvm, +{ + fn to_clvm(&self, encoder: &mut impl ClvmEncoder) -> Result { + T::to_clvm(self, encoder) + } +} + +impl ToClvm for Rc +where + T: ToClvm, +{ + fn to_clvm(&self, encoder: &mut impl ClvmEncoder) -> Result { + T::to_clvm(self, encoder) + } +} + +impl ToClvm for Arc +where + T: ToClvm, +{ + fn to_clvm(&self, encoder: &mut impl ClvmEncoder) -> Result { + T::to_clvm(self, encoder) + } +} + impl ToClvm for (A, B) where A: ToClvm, @@ -195,6 +224,14 @@ mod tests { assert_eq!(encode(a, Some(&42)), encode(a, Some(42))); } + #[test] + fn test_smart_pointers() { + let a = &mut Allocator::new(); + assert_eq!(encode(a, Box::new(42)), encode(a, 42)); + assert_eq!(encode(a, Rc::new(42)), encode(a, 42)); + assert_eq!(encode(a, Arc::new(42)), encode(a, 42)); + } + #[test] fn test_pair() { let a = &mut Allocator::new(); From dd8bcd3cf98613b566f7b63d8baf7f2b3e37971b Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sat, 1 Jun 2024 08:14:41 -0400 Subject: [PATCH 2/5] Add Payment constructor --- crates/chia-puzzles/src/puzzles/offer.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/chia-puzzles/src/puzzles/offer.rs b/crates/chia-puzzles/src/puzzles/offer.rs index 1abf0977f..0fa29128e 100644 --- a/crates/chia-puzzles/src/puzzles/offer.rs +++ b/crates/chia-puzzles/src/puzzles/offer.rs @@ -29,6 +29,20 @@ pub struct Payment { pub memos: Vec, } +impl Payment { + pub fn new(puzzle_hash: Bytes32, amount: u64) -> Self { + Self::with_memos(puzzle_hash, amount, Vec::new()) + } + + pub fn with_memos(puzzle_hash: Bytes32, amount: u64, memos: Vec) -> Self { + Self { + puzzle_hash, + amount, + memos, + } + } +} + /// This is the puzzle reveal of the [offer settlement payments](https://chialisp.com/offers) puzzle. pub const SETTLEMENT_PAYMENTS_PUZZLE: [u8; 293] = hex!( " From 6319a4586012213924c7ff56124337265eedac7e Mon Sep 17 00:00:00 2001 From: Rigidity Date: Fri, 7 Jun 2024 08:25:36 -0400 Subject: [PATCH 3/5] Assert output value --- crates/chia-puzzles/src/puzzles/did.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/chia-puzzles/src/puzzles/did.rs b/crates/chia-puzzles/src/puzzles/did.rs index 92ec0b5a2..f900925e8 100644 --- a/crates/chia-puzzles/src/puzzles/did.rs +++ b/crates/chia-puzzles/src/puzzles/did.rs @@ -125,7 +125,11 @@ pub const DID_INNER_PUZZLE_HASH: TreeHash = TreeHash::new(hex!( #[cfg(test)] mod tests { use clvm_traits::{clvm_list, match_list}; - use clvmr::{run_program, serde::node_from_bytes, Allocator, ChiaDialect}; + use clvmr::{ + run_program, + serde::{node_from_bytes, node_to_bytes}, + Allocator, ChiaDialect, + }; use super::*; @@ -161,8 +165,12 @@ mod tests { .to_clvm(a) .unwrap(); - run_program(a, &ChiaDialect::new(0), curried, ptr, u64::MAX) + let output = run_program(a, &ChiaDialect::new(0), curried, ptr, u64::MAX) .expect("could not run did puzzle and solution"); + assert_eq!( + hex::encode(node_to_bytes(a, output.1).unwrap()), + "ff2aff847465737480" + ); } #[test] From ec26f2d91c4931bfb156e6ff2a9b00a0bdc21a46 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Fri, 7 Jun 2024 08:46:21 -0400 Subject: [PATCH 4/5] Rename royalty and add comment explaining it --- crates/chia-puzzles/src/puzzles/nft.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/chia-puzzles/src/puzzles/nft.rs b/crates/chia-puzzles/src/puzzles/nft.rs index 321ec2ba5..529fb5bd9 100644 --- a/crates/chia-puzzles/src/puzzles/nft.rs +++ b/crates/chia-puzzles/src/puzzles/nft.rs @@ -132,33 +132,35 @@ pub struct NftOwnershipLayerSolution { pub struct NftRoyaltyTransferPuzzleArgs { pub singleton_struct: SingletonStruct, pub royalty_puzzle_hash: Bytes32, - pub royalty_percentage: u16, + /// The royalty percentage expressed as ten-thousandths. + /// For example, 300 represents 3%. + pub royalty_ten_thousandths: u16, } impl NftRoyaltyTransferPuzzleArgs { pub fn new( launcher_id: Bytes32, royalty_puzzle_hash: Bytes32, - royalty_percentage: u16, + royalty_ten_thousandths: u16, ) -> Self { Self { singleton_struct: SingletonStruct::new(launcher_id), royalty_puzzle_hash, - royalty_percentage, + royalty_ten_thousandths, } } pub fn curry_tree_hash( launcher_id: Bytes32, royalty_puzzle_hash: Bytes32, - royalty_percentage: u16, + royalty_ten_thousandths: u16, ) -> TreeHash { CurriedProgram { program: NFT_ROYALTY_TRANSFER_PUZZLE_HASH, args: NftRoyaltyTransferPuzzleArgs { singleton_struct: SingletonStruct::new(launcher_id), royalty_puzzle_hash, - royalty_percentage, + royalty_ten_thousandths, }, } .tree_hash() From 8c65f79236892b64327c782fc8f8a13b00a48697 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Fri, 7 Jun 2024 08:47:30 -0400 Subject: [PATCH 5/5] parent_coin_info --- crates/chia-consensus/src/fast_forward.rs | 8 ++++---- crates/chia-puzzles/src/proof.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/chia-consensus/src/fast_forward.rs b/crates/chia-consensus/src/fast_forward.rs index fa32e869b..1905744e1 100644 --- a/crates/chia-consensus/src/fast_forward.rs +++ b/crates/chia-consensus/src/fast_forward.rs @@ -115,7 +115,7 @@ pub fn fast_forward_singleton( // now that we know the parent coin's puzzle hash, we have all the pieces to // compute the coin being spent (before the fast-forward). let parent_coin = Coin { - parent_coin_info: lineage_proof.parent_parent_coin_id, + parent_coin_info: lineage_proof.parent_parent_coin_info, puzzle_hash: parent_puzzle_hash, amount: lineage_proof.parent_amount, }; @@ -139,7 +139,7 @@ pub fn fast_forward_singleton( } // update the solution to use the new parent coin's information - lineage_proof.parent_parent_coin_id = new_parent.parent_coin_info; + lineage_proof.parent_parent_coin_info = new_parent.parent_coin_info; lineage_proof.parent_amount = new_parent.amount; new_solution.amount = new_coin.amount; @@ -382,7 +382,7 @@ mod tests { }; // corrupt the lineage proof - lineage_proof.parent_parent_coin_id = Bytes32::from(hex!( + lineage_proof.parent_parent_coin_info = Bytes32::from(hex!( "fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe" )); @@ -455,7 +455,7 @@ mod tests { ); *new_parent = Coin { - parent_coin_info: lineage_proof.parent_parent_coin_id, + parent_coin_info: lineage_proof.parent_parent_coin_info, puzzle_hash: parent_puzzle_hash, amount: lineage_proof.parent_amount, }; diff --git a/crates/chia-puzzles/src/proof.rs b/crates/chia-puzzles/src/proof.rs index 2cc7c0e02..e14949ec2 100644 --- a/crates/chia-puzzles/src/proof.rs +++ b/crates/chia-puzzles/src/proof.rs @@ -13,7 +13,7 @@ pub enum Proof { #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[clvm(list)] pub struct LineageProof { - pub parent_parent_coin_id: Bytes32, + pub parent_parent_coin_info: Bytes32, pub parent_inner_puzzle_hash: Bytes32, pub parent_amount: u64, } @@ -22,7 +22,7 @@ pub struct LineageProof { #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[clvm(list)] pub struct EveProof { - pub parent_parent_coin_id: Bytes32, + pub parent_parent_coin_info: Bytes32, pub parent_amount: u64, }