diff --git a/crates/chia-puzzles/src/puzzles/cat.rs b/crates/chia-puzzles/src/puzzles/cat.rs index 30d2da4ba..598cc5ac9 100644 --- a/crates/chia-puzzles/src/puzzles/cat.rs +++ b/crates/chia-puzzles/src/puzzles/cat.rs @@ -83,7 +83,7 @@ impl GenesisByCoinIdTailArgs { #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(list)] +#[clvm(solution)] pub struct CatSolution { pub inner_puzzle_solution: I, pub lineage_proof: Option, diff --git a/crates/chia-puzzles/src/puzzles/did.rs b/crates/chia-puzzles/src/puzzles/did.rs index 7238cfe51..f5b5d5442 100644 --- a/crates/chia-puzzles/src/puzzles/did.rs +++ b/crates/chia-puzzles/src/puzzles/did.rs @@ -59,7 +59,7 @@ impl DidArgs { #[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(list)] +#[clvm(solution)] #[repr(u8)] pub enum DidSolution { Recover(#[clvm(rest)] Box) = 0, @@ -68,7 +68,7 @@ pub enum DidSolution { #[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(list)] +#[clvm(solution)] pub struct DidRecoverySolution { pub amount: u64, pub new_inner_puzzle_hash: Bytes32, diff --git a/crates/chia-puzzles/src/puzzles/nft.rs b/crates/chia-puzzles/src/puzzles/nft.rs index 64db1db14..b092e2dd1 100644 --- a/crates/chia-puzzles/src/puzzles/nft.rs +++ b/crates/chia-puzzles/src/puzzles/nft.rs @@ -74,7 +74,7 @@ impl NftStateLayerArgs { #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(list)] +#[clvm(solution)] pub struct NftStateLayerSolution { pub inner_solution: I, } @@ -121,7 +121,7 @@ impl NftOwnershipLayerArgs { #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(list)] +#[clvm(solution)] pub struct NftOwnershipLayerSolution { pub inner_solution: I, } diff --git a/crates/chia-puzzles/src/puzzles/singleton.rs b/crates/chia-puzzles/src/puzzles/singleton.rs index 1a12c684a..254dd148f 100644 --- a/crates/chia-puzzles/src/puzzles/singleton.rs +++ b/crates/chia-puzzles/src/puzzles/singleton.rs @@ -57,7 +57,7 @@ impl SingletonStruct { #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(list)] +#[clvm(solution)] pub struct SingletonSolution { pub lineage_proof: Proof, pub amount: u64, @@ -66,7 +66,7 @@ pub struct SingletonSolution { #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(list)] +#[clvm(solution)] pub struct LauncherSolution { pub singleton_puzzle_hash: Bytes32, pub amount: u64, diff --git a/crates/chia-puzzles/src/puzzles/standard.rs b/crates/chia-puzzles/src/puzzles/standard.rs index 16e2e9ebf..9e903b6e6 100644 --- a/crates/chia-puzzles/src/puzzles/standard.rs +++ b/crates/chia-puzzles/src/puzzles/standard.rs @@ -26,7 +26,7 @@ impl StandardArgs { #[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[clvm(list)] +#[clvm(solution)] pub struct StandardSolution { pub original_public_key: Option, pub delegated_puzzle: P, diff --git a/crates/clvm-derive/src/from_clvm.rs b/crates/clvm-derive/src/from_clvm.rs index eaca4b197..c350c8eac 100644 --- a/crates/clvm-derive/src/from_clvm.rs +++ b/crates/clvm-derive/src/from_clvm.rs @@ -43,7 +43,7 @@ fn field_parser_fn_body( let decode_next = match repr { Repr::Atom | Repr::Transparent => unreachable!(), // Decode `(A . B)` pairs for lists. - Repr::List => quote!(decode_pair), + Repr::List | Repr::Solution => quote!(decode_pair), // Decode `(c (q . A) B)` pairs for curried arguments. Repr::Curry => quote!(decode_curried_arg), }; @@ -150,6 +150,8 @@ fn field_parser_fn_body( fn check_rest_value(crate_name: &Ident, repr: Repr) -> TokenStream { match repr { Repr::Atom | Repr::Transparent => unreachable!(), + // We don't need to check the terminator of a solution. + Repr::Solution => quote! {}, Repr::List => { // If the last field is not `rest`, we need to check that the `node` is nil. // If it's not nil, it's not a proper list, and we should return an error. @@ -286,7 +288,7 @@ fn impl_for_enum( let decode_next = match enum_info.default_repr { Repr::Atom | Repr::Transparent => unreachable!(), // Decode `(A . B)` pairs for lists. - Repr::List => quote!(decode_pair), + Repr::List | Repr::Solution => quote!(decode_pair), // Decode `(c (q . A) B)` pairs for curried arguments. Repr::Curry => quote!(decode_curried_arg), }; diff --git a/crates/clvm-derive/src/parser/attributes.rs b/crates/clvm-derive/src/parser/attributes.rs index 482d9d5c5..834cbe478 100644 --- a/crates/clvm-derive/src/parser/attributes.rs +++ b/crates/clvm-derive/src/parser/attributes.rs @@ -11,6 +11,8 @@ use syn::{ pub enum Repr { /// Represents `(A . (B . (C . ())))`. List, + /// The same as `list`, but the terminator doesn't have to be `()`. + Solution, /// Represents `(c (q . A) (c (q . B) (c (q . C) 1)))`. Curry, /// Represents the first field `A` on its own, with no other fields allowed. @@ -22,7 +24,7 @@ pub enum Repr { impl Repr { pub fn expect(repr: Option) -> Repr { repr.expect( - "missing either `list`, `curry`, `transparent`, or `atom` in `clvm` attribute options", + "missing either `list`, `curry`, `solution`, `transparent`, or `atom` in `clvm` attribute options", ) } } @@ -31,6 +33,7 @@ impl fmt::Display for Repr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { Self::List => "list", + Self::Solution => "solution", Self::Curry => "curry", Self::Transparent => "transparent", Self::Atom => "atom", @@ -75,6 +78,7 @@ impl Parse for ClvmOption { match ident.to_string().as_str() { "list" => Ok(Self::Repr(Repr::List)), + "solution" => Ok(Self::Repr(Repr::Solution)), "curry" => Ok(Self::Repr(Repr::Curry)), "transparent" => Ok(Self::Repr(Repr::Transparent)), "atom" => Ok(Self::Repr(Repr::Atom)), diff --git a/crates/clvm-derive/src/to_clvm.rs b/crates/clvm-derive/src/to_clvm.rs index 2dcb4890d..77120a291 100644 --- a/crates/clvm-derive/src/to_clvm.rs +++ b/crates/clvm-derive/src/to_clvm.rs @@ -48,14 +48,16 @@ fn encode_fields( let encode_next = match repr { Repr::Atom | Repr::Transparent => unreachable!(), // Encode `(A . B)` pairs for lists. - Repr::List => quote!(encode_pair), + Repr::List | Repr::Solution => quote!(encode_pair), // Encode `(c (q . A) B)` pairs for curried arguments. Repr::Curry => quote!(encode_curried_arg), }; let initial_value = match repr { Repr::Atom | Repr::Transparent => unreachable!(), - Repr::List => quote!(encoder.encode_atom(#crate_name::Atom::Borrowed(&[]))?), + Repr::List | Repr::Solution => { + quote!(encoder.encode_atom(#crate_name::Atom::Borrowed(&[]))?) + } Repr::Curry => quote!(encoder.encode_atom(#crate_name::Atom::Borrowed(&[1]))?), }; @@ -243,7 +245,7 @@ fn impl_for_enum( let encode_next = match enum_info.default_repr { Repr::Atom | Repr::Transparent => unreachable!(), // Encode `(A . B)` pairs for lists. - Repr::List => quote!(encode_pair), + Repr::List | Repr::Solution => quote!(encode_pair), // Encode `(c (q . A) B)` pairs for curried arguments. Repr::Curry => quote!(encode_curried_arg), }; diff --git a/crates/clvm-traits/docs/derive_macros.md b/crates/clvm-traits/docs/derive_macros.md index b440b0e33..92c0cc83e 100644 --- a/crates/clvm-traits/docs/derive_macros.md +++ b/crates/clvm-traits/docs/derive_macros.md @@ -50,6 +50,12 @@ let ptr = value.to_clvm(a).unwrap(); assert_eq!(Tiers::from_clvm(a, ptr).unwrap(), value); ``` +### Solution + +The solution representation is the same as list, except it does not check the nil terminator when parsing. +This allows it to be lenient to additional parameters that are in the CLVM object, since they don't affect anything. +If you want your solution to be parsed strictly, you can use list instead. + ### Curry This represents the argument part of a curried CLVM program. diff --git a/crates/clvm-traits/src/lib.rs b/crates/clvm-traits/src/lib.rs index 823a3762b..f76f7cc74 100644 --- a/crates/clvm-traits/src/lib.rs +++ b/crates/clvm-traits/src/lib.rs @@ -93,6 +93,55 @@ mod derive_tests { check(&Struct { a: 52, b: -32 }, "ff3481e0"); } + #[test] + fn test_solution_struct() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] + #[clvm(solution)] + struct Struct { + a: u64, + b: i32, + } + + // Includes the nil terminator. + check(&Struct { a: 52, b: -32 }, "ff34ff81e080"); + + // Allows additional parameters. + let mut allocator = Allocator::new(); + let ptr = clvm_list!(100, 200, 300, 400) + .to_clvm(&mut allocator) + .unwrap(); + let value = Struct::from_clvm(&allocator, ptr).unwrap(); + assert_eq!(value, Struct { a: 100, b: 200 }); + + // Doesn't allow differing types for the actual solution parameters. + let mut allocator = Allocator::new(); + let ptr = clvm_list!([1, 2, 3], 200, 300) + .to_clvm(&mut allocator) + .unwrap(); + Struct::from_clvm(&allocator, ptr).unwrap_err(); + } + + #[test] + fn test_solution_struct_with_rest() { + #[derive(Debug, ToClvm, FromClvm, PartialEq)] + #[clvm(solution)] + struct Struct { + a: u64, + #[clvm(rest)] + b: i32, + } + + // Does not include the nil terminator. + check(&Struct { a: 52, b: -32 }, "ff3481e0"); + + // Does not allow additional parameters, since it consumes the rest. + let mut allocator = Allocator::new(); + let ptr = clvm_list!(100, 200, 300, 400) + .to_clvm(&mut allocator) + .unwrap(); + Struct::from_clvm(&allocator, ptr).unwrap_err(); + } + #[test] fn test_curry_struct() { #[derive(Debug, ToClvm, FromClvm, PartialEq)]