Skip to content

Commit

Permalink
Add a less strict solution representation (#674)
Browse files Browse the repository at this point in the history
* Add a less strict solution representation

* Use the new representation for solutions
  • Loading branch information
Rigidity committed Aug 30, 2024
1 parent 2026d01 commit a24aebc
Show file tree
Hide file tree
Showing 10 changed files with 77 additions and 14 deletions.
2 changes: 1 addition & 1 deletion crates/chia-puzzles/src/puzzles/cat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<I> {
pub inner_puzzle_solution: I,
pub lineage_proof: Option<LineageProof>,
Expand Down
4 changes: 2 additions & 2 deletions crates/chia-puzzles/src/puzzles/did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl DidArgs<TreeHash, TreeHash> {

#[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[clvm(list)]
#[clvm(solution)]
#[repr(u8)]
pub enum DidSolution<I> {
Recover(#[clvm(rest)] Box<DidRecoverySolution>) = 0,
Expand All @@ -68,7 +68,7 @@ pub enum DidSolution<I> {

#[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,
Expand Down
4 changes: 2 additions & 2 deletions crates/chia-puzzles/src/puzzles/nft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl NftStateLayerArgs<TreeHash, TreeHash> {

#[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[clvm(list)]
#[clvm(solution)]
pub struct NftStateLayerSolution<I> {
pub inner_solution: I,
}
Expand Down Expand Up @@ -121,7 +121,7 @@ impl NftOwnershipLayerArgs<TreeHash, TreeHash> {

#[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[clvm(list)]
#[clvm(solution)]
pub struct NftOwnershipLayerSolution<I> {
pub inner_solution: I,
}
Expand Down
4 changes: 2 additions & 2 deletions crates/chia-puzzles/src/puzzles/singleton.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<I> {
pub lineage_proof: Proof,
pub amount: u64,
Expand All @@ -66,7 +66,7 @@ pub struct SingletonSolution<I> {

#[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[clvm(list)]
#[clvm(solution)]
pub struct LauncherSolution<T> {
pub singleton_puzzle_hash: Bytes32,
pub amount: u64,
Expand Down
2 changes: 1 addition & 1 deletion crates/chia-puzzles/src/puzzles/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<P, S> {
pub original_public_key: Option<PublicKey>,
pub delegated_puzzle: P,
Expand Down
6 changes: 4 additions & 2 deletions crates/clvm-derive/src/from_clvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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),
};
Expand Down
6 changes: 5 additions & 1 deletion crates/clvm-derive/src/parser/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -22,7 +24,7 @@ pub enum Repr {
impl Repr {
pub fn expect(repr: Option<Repr>) -> 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",
)
}
}
Expand All @@ -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",
Expand Down Expand Up @@ -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)),
Expand Down
8 changes: 5 additions & 3 deletions crates/clvm-derive/src/to_clvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]))?),
};

Expand Down Expand Up @@ -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),
};
Expand Down
6 changes: 6 additions & 0 deletions crates/clvm-traits/docs/derive_macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
49 changes: 49 additions & 0 deletions crates/clvm-traits/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down

0 comments on commit a24aebc

Please sign in to comment.