From 67fd481c1538bf719d53d9a26931b14fc929eecd Mon Sep 17 00:00:00 2001 From: Rigidity Date: Fri, 5 Jul 2024 21:41:50 -0400 Subject: [PATCH] Add examples back --- examples/factorial.rue | 11 ++++ examples/fibonacci.rue | 11 ++++ examples/hello_world.rue | 3 + examples/multisig.rue | 37 ++++++++++++ examples/p2_conditions.rue | 9 +++ examples/p2_delegated_or_hidden.rue | 25 ++++++++ examples/p2_fusion.rue | 64 ++++++++++++++++++++ examples/royalty_split.rue | 51 ++++++++++++++++ examples/singleton.rue | 94 +++++++++++++++++++++++++++++ tests.toml | 64 ++++++++++++++++++++ 10 files changed, 369 insertions(+) create mode 100644 examples/factorial.rue create mode 100644 examples/fibonacci.rue create mode 100644 examples/hello_world.rue create mode 100644 examples/multisig.rue create mode 100644 examples/p2_conditions.rue create mode 100644 examples/p2_delegated_or_hidden.rue create mode 100644 examples/p2_fusion.rue create mode 100644 examples/royalty_split.rue create mode 100644 examples/singleton.rue diff --git a/examples/factorial.rue b/examples/factorial.rue new file mode 100644 index 0000000..43f921e --- /dev/null +++ b/examples/factorial.rue @@ -0,0 +1,11 @@ +fun main() -> Int { + factorial(15) +} + +fun factorial(num: Int) -> Int { + if num > 1 { + num * factorial(num - 1) + } else { + 1 + } +} diff --git a/examples/fibonacci.rue b/examples/fibonacci.rue new file mode 100644 index 0000000..7f5d395 --- /dev/null +++ b/examples/fibonacci.rue @@ -0,0 +1,11 @@ +fun main() -> Int { + fibonacci(10) +} + +fun fibonacci(num: Int) -> Int { + if num > 1 { + fibonacci(num - 1) + fibonacci(num - 2) + } else { + num + } +} diff --git a/examples/hello_world.rue b/examples/hello_world.rue new file mode 100644 index 0000000..84c26d4 --- /dev/null +++ b/examples/hello_world.rue @@ -0,0 +1,3 @@ +fun main() -> Bytes { + "Hello, world!" +} diff --git a/examples/multisig.rue b/examples/multisig.rue new file mode 100644 index 0000000..9bba1dc --- /dev/null +++ b/examples/multisig.rue @@ -0,0 +1,37 @@ +// This puzzle has not been audited or tested, and is for example purposes only. + +fun main( + public_keys: PublicKey[], + required: Int, + indices: Int[], + conditions: Condition[], +) -> Condition[] { + let message = tree_hash(conditions); + let agg_sigs = check_signatures(public_keys, required, indices, 0, message); + concat(agg_sigs, conditions) +} + +fun check_signatures( + public_keys: PublicKey[], + required: Int, + indices: Int[], + pos: Int, + message: Bytes, +) -> Condition[] { + if required == 0 { + return nil; + } + + assume !(public_keys is Nil) && !(indices is Nil); + + if indices.first != pos { + return check_signatures(public_keys.rest, required, indices, pos + 1, message); + } + + let agg_sig = Condition::AggSigMe { + public_key: public_keys.first, + message: message, + }; + + [agg_sig, ...check_signatures(public_keys.rest, required - 1, indices.rest, pos + 1, message)] +} diff --git a/examples/p2_conditions.rue b/examples/p2_conditions.rue new file mode 100644 index 0000000..f017e95 --- /dev/null +++ b/examples/p2_conditions.rue @@ -0,0 +1,9 @@ +// This puzzle has not been audited or tested, and is for example purposes only. + +fun main(public_key: PublicKey, conditions: Condition[]) -> Condition[] { + let agg_sig = Condition::AggSigMe { + public_key: public_key, + message: tree_hash(conditions), + }; + [agg_sig, ...conditions] +} diff --git a/examples/p2_delegated_or_hidden.rue b/examples/p2_delegated_or_hidden.rue new file mode 100644 index 0000000..a4f66e4 --- /dev/null +++ b/examples/p2_delegated_or_hidden.rue @@ -0,0 +1,25 @@ +// This puzzle has not been audited or tested, and is for example purposes only. + +fun main( + synthetic_pk: PublicKey, + original_pk: PublicKey?, + delegated_puzzle: fun(...solution: Any) -> Condition[], + delegated_solution: Any +) -> Condition[] { + let conditions = delegated_puzzle(...delegated_solution); + let delegated_puzzle_hash = tree_hash(delegated_puzzle); + + if original_pk != nil { + let exponent = sha256(original_pk as Bytes + delegated_puzzle_hash); + let offset_pk = pubkey_for_exp(exponent); + assert synthetic_pk == original_pk + offset_pk; + return conditions; + } + + let agg_sig_me = Condition::AggSigMe { + public_key: synthetic_pk, + message: delegated_puzzle_hash, + }; + + [agg_sig_me, ...conditions] +} diff --git a/examples/p2_fusion.rue b/examples/p2_fusion.rue new file mode 100644 index 0000000..dbf19d0 --- /dev/null +++ b/examples/p2_fusion.rue @@ -0,0 +1,64 @@ +// This puzzle has not been audited or tested, and is for example purposes only. + +// Also known as a "singleton struct". +struct SingletonInfo { + mod_hash: Bytes32, + launcher_id: Bytes32, + ...launcher_puzzle_hash: Bytes32, +} + +// Calculate the full puzzle hash for a singleton. +inline fun singleton_puzzle_hash(singleton_info: SingletonInfo, inner_puzzle_hash: Bytes32) -> Bytes32 { + curry_tree_hash(singleton_info.mod_hash, tree_hash(singleton_info), inner_puzzle_hash) +} + +fun main( + fusion_singleton: SingletonInfo, + fusion_inner_puzzle_hash: Bytes32, + fusion_coin_id: Bytes32, + my_launcher_id: Bytes32, + my_inner_puzzle_hash: Bytes32, + my_amount: Int, + p2_puzzle_hash: Bytes32, +) -> Condition[] { + // The NFT singleton has the same mod hash and launcher puzzle hash as the fusion singleton. + let nft_singleton = SingletonInfo { + mod_hash: fusion_singleton.mod_hash, + launcher_id: my_launcher_id, + launcher_puzzle_hash: fusion_singleton.launcher_puzzle_hash, + }; + + // Calculate the full puzzle hashes for the NFT and fusion singletons. + let fusion_puzzle_hash = singleton_puzzle_hash(fusion_singleton, fusion_inner_puzzle_hash); + let nft_puzzle_hash = singleton_puzzle_hash(nft_singleton, my_inner_puzzle_hash); + + // Calculate the announcement message. + let announcement_message = sha256(fusion_coin_id + my_launcher_id + p2_puzzle_hash); + + [ + // Make sure that the amount in the solution is correct. + Condition::AssertMyAmount { amount: my_amount }, + + // Prove supplied NFT coin matches the expected derived puzzle hash. + Condition::AssertMyPuzzleHash { puzzle_hash: nft_puzzle_hash }, + + + // Assert that the fusion singleton announced this NFT spend. + Condition::AssertPuzzleAnnouncement { + announcement_id: sha256(fusion_puzzle_hash + announcement_message), + }, + + // Unlock the NFT to the new p2 puzzle hash, with a hint. + Condition::CreateCoin { + puzzle_hash: p2_puzzle_hash, + amount: my_amount, + memos: [p2_puzzle_hash], + }, + + // Announce that a specific singleton is being spent to + // help prevent ephemeral singleton spends from influencing. + Condition::CreateCoinAnnouncement { + message: announcement_message, + }, + ] +} diff --git a/examples/royalty_split.rue b/examples/royalty_split.rue new file mode 100644 index 0000000..6078916 --- /dev/null +++ b/examples/royalty_split.rue @@ -0,0 +1,51 @@ +// This puzzle has not been audited or tested, and is for example purposes only. + +struct Payout { + puzzle_hash: Bytes32, + share: Int, +} + +fun main(payouts: Payout[], total_shares: Int, my_amount: Int) -> Condition[] { + let announcement = Condition::CreateCoinAnnouncement { message: '$' }; + let assert_amount = Condition::AssertMyAmount { amount: my_amount }; + + let conditions = calculate_amount_and_split(payouts, my_amount, total_shares, 0, my_amount); + [announcement, assert_amount, ...conditions] +} + +fun calculate_amount_and_split( + payouts: Payout[], + total_amount: Int, + total_shares: Int, + shares_sum: Int, + remaining_amount: Int, +) -> Condition[] { + if payouts is (Payout, Payout[]) { + let amount = get_amount(payouts.first, total_amount, total_shares); + return split_amount_and_create_coins(payouts, amount, total_amount, total_shares, shares_sum, remaining_amount); + } + assert total_shares == shares_sum; + [] +} + +fun split_amount_and_create_coins( + payouts: (Payout, Payout[]), + this_amount: Int, + total_amount: Int, + total_shares: Int, + shares_sum: Int, + remaining_amount: Int, +) -> Condition[] { + let payout = payouts.first; + let create_coin = Condition::CreateCoin { + puzzle_hash: payout.puzzle_hash, + amount: if payout.share > 0 { this_amount } else { remaining_amount }, + memos: [payout.puzzle_hash], + }; + let rest = calculate_amount_and_split(payouts.rest, total_amount, total_shares, shares_sum + payout.share, remaining_amount - this_amount); + [create_coin, ...rest] +} + +fun get_amount(payout: Payout, total_amount: Int, total_shares: Int) -> Int { + (total_amount * payout.share) / total_shares +} diff --git a/examples/singleton.rue b/examples/singleton.rue new file mode 100644 index 0000000..8112a28 --- /dev/null +++ b/examples/singleton.rue @@ -0,0 +1,94 @@ +// This puzzle has not been audited or tested, and is for example purposes only. + +struct Singleton { + mod_hash: Bytes32, + launcher_id: Bytes32, + launcher_puzzle_hash: Bytes32, +} + +struct LineageProof { + parent_parent_coin_info: Bytes32, + parent_inner_puzzle_hash: Bytes32?, + parent_amount: Int, +} + +fun singleton_puzzle_hash(singleton: Singleton, inner_puzzle_hash: Bytes32) -> Bytes32 { + curry_tree_hash(singleton.mod_hash, tree_hash(singleton), inner_puzzle_hash) +} + +fun main( + singleton: Singleton, + inner_puzzle: fun(...solution: Any) -> Condition[], + lineage_proof: LineageProof, + my_amount: Int, + inner_solution: Any, +) -> Condition[] { + // Ensure that the amount is odd. + assert my_amount & 1 == 1; + + // Verify the lineage proof. + let is_eve = lineage_proof.parent_inner_puzzle_hash == nil; + + let parent_puzzle_hash = if is_eve { + singleton.launcher_puzzle_hash + } else { + singleton_puzzle_hash(singleton, lineage_proof.parent_inner_puzzle_hash) + }; + + let parent_coin_id = calculate_coin_id( + lineage_proof.parent_parent_coin_info, + parent_puzzle_hash, + lineage_proof.parent_amount, + ); + + assert is_eve || parent_coin_id == singleton.launcher_id; + + // Run the inner puzzle. + let conditions = inner_puzzle(...inner_solution); + + [ + Condition::AssertMyAmount { amount: my_amount }, + Condition::AssertMyParentId { parent_coin_id: parent_coin_id }, + ...morph_conditions(singleton, conditions, false), + ] +} + +fun morph_conditions( + singleton: Singleton, + conditions: Condition[], + found_singleton_output: Bool, +) -> Condition[] { + if conditions is Nil { + // We must have a singleton output. + assert found_singleton_output; + return nil; + } + + let condition = conditions.first; + let rest = conditions.rest; + + if !(condition is Condition::CreateCoin && condition.amount & 1 == 1) { + // We don't need to morph this condition. + return [ + condition, + ...morph_conditions(singleton, rest, found_singleton_output), + ]; + } + + // We need to morph this odd output, but it must be the only one. + assert !found_singleton_output; + + if condition.amount == -113 { + // We are melting the singleton, so we don't need to have an output. + return morph_conditions(singleton, rest, true); + } + + // Wrap the puzzle hash in the singleton layer. + let output = Condition::CreateCoin { + puzzle_hash: singleton_puzzle_hash(singleton, condition.puzzle_hash), + amount: condition.amount, + memos: condition.memos, + }; + + [output, ...morph_conditions(singleton, rest, true)] +} diff --git a/tests.toml b/tests.toml index bb0f628..7dbc705 100644 --- a/tests.toml +++ b/tests.toml @@ -491,3 +491,67 @@ compiler_errors = [ "Error: Unused enum variant `SameName`. (2:5)", "Error: Unused enum variant `DuplicateDiscriminant`. (4:5)", ] + +[p2_fusion] +bytes = 722 +cost = 87151 +input = "((0x4696e7a2b7682e2df01ab47e6e002d0dca895f99c6172e4a55a3e033499532b7 0x32ed6e4964102d7093ef350019dfd14c99a3ea9feac1f3502194384b8976f7c1 0x770bb2c0b2582924adf403e62374f8424a2ed510eef70b5f450eccab238a4911) 0x8d496dc7cdbc417db2132eda6894b29a91511f176e93a1ea943d210cd27822b2 0x3878fc2ed6b703c7c3d00f9f5d8f170ec252ca439be6e5b5b516e1dce1adc0d7 0x3fe4d62a7db919b25377cb207f16fa0fb6519e123366eaa23014dd5d7c967ca2 0x837525fb91b88dbf02d9ef5a0b322f6943f93424b6e8fe6d26dd1be6d3311871 1 0xc19110971a0cea01368f0c7599b8984f74e301b7cda20bd7f86eb2296bbf16c5)" +output = "((73 1) (72 0x6155f55414a5cd9193bef33744095a78d57852cf24241b25edeffad6e544c499) (63 0x9d6824bfdfb4c726334a210b657a5e4126c3fbb378598bb3a5b7a210bb75cdb8) (g1_negate 0xc19110971a0cea01368f0c7599b8984f74e301b7cda20bd7f86eb2296bbf16c5 1 (0xc19110971a0cea01368f0c7599b8984f74e301b7cda20bd7f86eb2296bbf16c5)) (60 0x81fdd3fbc407906bc0875522e7a2e77409ee5ef17d3eaa611b505b344588f6b6))" +hash = "1f6abfdd1000d9ed87740ca5a0a888a042d64a8a7b6d60f082aab5be1f0915b4" + +[multisig] +bytes = 489 +cost = 27062 +input = "((0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) 1 (()) ((51 0x0000000000000000000000000000000000000000000000000000000000000000 1000)))" +output = "((g1_multiply 0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0x502cea601814d49886e0713402860d6054f6947d811b3b93100c6b364626651a) (g1_negate 0x0000000000000000000000000000000000000000000000000000000000000000 1000))" +hash = "173385b87af5d8940767c328026fe5f8e76bc238d2a3aaddf4f55e844f400fca" + +[hello_world] +bytes = 16 +cost = 20 +input = "()" +output = "\"Hello, world!\"" +hash = "4d5ba80f14b464a42a862990c434225f0f297bec1fe146117cb5c8b96d01166b" + +[fibonacci] +bytes = 121 +cost = 414329 +input = "()" +output = "55" +hash = "951ba85ff214a65c4d07814672544b6686e8f4a819550543473fb8eb26aae6a3" + +[factorial] +bytes = 97 +cost = 43743 +input = "()" +output = "0x013077775800" +hash = "291e4594b43d58e833cab95e4b165c5fac6b4d8391c81ebfd20efdd8d58b92d8" + +[royalty_split] +bytes = 430 +cost = 20303 +input = "(((0x42840c6aebec47ce2e01629ce381b461c19695264281a7b1aab5d4ff54506775 1) (0x917b0e1ee2c8ad5755e1f97c4642ea653288acf816eee1b0d537dd8e01106711 2)) 3 100000)" +output = "((60 36) (73 0x0186a0) (g1_negate 0x42840c6aebec47ce2e01629ce381b461c19695264281a7b1aab5d4ff54506775 0x008235 (0x42840c6aebec47ce2e01629ce381b461c19695264281a7b1aab5d4ff54506775)) (g1_negate 0x917b0e1ee2c8ad5755e1f97c4642ea653288acf816eee1b0d537dd8e01106711 0x01046a (0x917b0e1ee2c8ad5755e1f97c4642ea653288acf816eee1b0d537dd8e01106711)))" +hash = "7eb7bdb24d4069d7dc9787e7727ef4f0feef354712b1963667d475065122b9de" + +[p2_conditions] +bytes = 145 +cost = 18933 +input = "(0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ((51 0x291e4594b43d58e833cab95e4b165c5fac6b4d8391c81ebfd20efdd8d58b92d8 1000)))" +output = "((g1_multiply 0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0xc239bbcfc69fb5abacdd8dc174c6b33170c6d902dec3bc1c87662020cf044313) (g1_negate 0x291e4594b43d58e833cab95e4b165c5fac6b4d8391c81ebfd20efdd8d58b92d8 1000))" +hash = "e69958bce8b4294e16e9b54f6f6795228fbd6daa47ff27ab58a953c611367be2" + +[p2_delegated_or_hidden] +bytes = 253 +cost = 24446 +input = "(0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 () (q . ((51 0x291e4594b43d58e833cab95e4b165c5fac6b4d8391c81ebfd20efdd8d58b92d8 1000))) 1)" +output = "((g1_multiply 0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0x3d7e1145b5969c12f4889f4f1f66bde0e4d3ba54b91784cf604294d162b44b69) (g1_negate 0x291e4594b43d58e833cab95e4b165c5fac6b4d8391c81ebfd20efdd8d58b92d8 1000))" +hash = "9b1c580707ca8282534c02c1a055427e0954818b6195a29f4442ac3e7ea8e8ee" + +[singleton] +bytes = 1427 +cost = 0 +input = "((0x42840c6aebec47ce2e01629ce381b461c19695264281a7b1aab5d4ff54506775 0x4696e7a2b7682e2df01ab47e6e002d0dca895f99c6172e4a55a3e033499532b7 0x291e4594b43d58e833cab95e4b165c5fac6b4d8391c81ebfd20efdd8d58b92d8) 1 (0x9b1c580707ca8282534c02c1a055427e0954818b6195a29f4442ac3e7ea8e8ee () 1) 1 ((51 0x173385b87af5d8940767c328026fe5f8e76bc238d2a3aaddf4f55e844f400fca 1)))" +output = "()" +hash = "ca8bea1d975df6e382a60ef70f92d14de0eca974c548175adc6ee8c79597f91b" +error = "()"