From 851fb3be3cae1d416c9c1b8d04999cd0f28174cd Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 5 Mar 2024 12:50:45 -0500 Subject: [PATCH] Fix spending more than 2 CATs at once --- Cargo.lock | 39 ++++++-- Cargo.toml | 3 +- src/spends/cat/raw_spend.rs | 180 +++++++++++++++++++++++++++++++++++- 3 files changed, 209 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8331b6c5..3369d1e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -208,6 +208,24 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chia" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89de95a5daa48a2749b7af27188b635d2843f52c8f5c230cab7ac22429413b1f" +dependencies = [ + "chia-protocol", + "chia-traits 0.5.2", + "chia-wallet", + "clvm-derive", + "clvm-traits", + "clvm-utils", + "clvmr", + "hex", + "hex-literal", + "thiserror", +] + [[package]] name = "chia-bls" version = "0.4.0" @@ -234,7 +252,7 @@ dependencies = [ "anyhow", "arbitrary", "blst", - "chia-traits 0.5.1", + "chia-traits 0.5.2", "hex", "hkdf", "sha2 0.10.8", @@ -249,7 +267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5ffc39ab1aa7e333539afe68739eca59c47160a5a3db85b886571b0b3438ad8" dependencies = [ "chia-protocol", - "chia-traits 0.5.1", + "chia-traits 0.5.2", "futures-util", "thiserror", "tokio", @@ -265,7 +283,7 @@ checksum = "10a4f5e87514749bdc03b2a7915e34d8105980f161a29689de321b0dd7c7a835" dependencies = [ "arbitrary", "chia-bls 0.5.1", - "chia-traits 0.5.1", + "chia-traits 0.5.2", "chia_streamable_macro", "clvm-traits", "clvm-utils", @@ -303,9 +321,9 @@ dependencies = [ [[package]] name = "chia-traits" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630c8f3ee351d7a70abd2b6edaae248e5e7e874c24c9251608a9f2e410073de7" +checksum = "a80916358aa139bc9130f147ff784ba8858d41b959d6795e1e91a2c20ccc7409" dependencies = [ "chia_streamable_macro", "hex", @@ -332,10 +350,11 @@ dependencies = [ [[package]] name = "chia-wallet-sdk" -version = "0.6.0" +version = "0.6.1" dependencies = [ "bech32", "bip39", + "chia", "chia-bls 0.5.1", "chia-client", "chia-protocol", @@ -372,9 +391,9 @@ dependencies = [ [[package]] name = "clvm-derive" -version = "0.2.14" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9110e638f8a4d34922e0436282c7fe1a8149020aef3aacf699377dcd3f1553da" +checksum = "26b1b1ba82707086cd36e6d36574559270d80f486dfc31be00cad202f842675f" dependencies = [ "proc-macro2", "quote", @@ -383,9 +402,9 @@ dependencies = [ [[package]] name = "clvm-traits" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fdd5089c9e32a4a563da945309c41577181087f5017a3104b031a4e58591e7" +checksum = "421b8c8a1312d55840533b0facae8c0522627e24a9484001374d03055de6df00" dependencies = [ "chia-bls 0.5.1", "clvm-derive", diff --git a/Cargo.toml b/Cargo.toml index c76e1e17..9f576afe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chia-wallet-sdk" -version = "0.6.0" +version = "0.6.1" edition = "2021" license = "Apache-2.0" description = "An unofficial SDK for building Chia wallets." @@ -31,5 +31,6 @@ rand_chacha = "0.3.1" [dev-dependencies] bip39 = "2.0.0" +chia = "0.5.2" hex-literal = "0.4.1" once_cell = "1.19.0" diff --git a/src/spends/cat/raw_spend.rs b/src/spends/cat/raw_spend.rs index 59ccc05a..e2cf3f3d 100644 --- a/src/spends/cat/raw_spend.rs +++ b/src/spends/cat/raw_spend.rs @@ -38,6 +38,8 @@ pub fn spend_cat_coins( ) -> Result, ToClvmError> { let mut total_delta = 0; + let len = cat_spends.len(); + cat_spends .iter() .enumerate() @@ -61,8 +63,8 @@ pub fn spend_cat_coins( total_delta += delta; // Find information of neighboring coins on the ring. - let prev_cat = &cat_spends[index.wrapping_sub(1) % cat_spends.len()]; - let next_cat = &cat_spends[index.wrapping_add(1) % cat_spends.len()]; + let prev_cat = &cat_spends[if index == 0 { len - 1 } else { index - 1 }]; + let next_cat = &cat_spends[if index == len - 1 { 0 } else { index + 1 }]; // Construct the puzzle. let puzzle = CurriedProgram { @@ -112,6 +114,10 @@ pub fn spend_cat_coins( #[cfg(test)] mod tests { + use chia::gen::{ + conditions::EmptyVisitor, run_block_generator::run_block_generator, + solution_generator::solution_generator, + }; use chia_bls::{derive_keys::master_to_wallet_unhardened, SecretKey}; use chia_protocol::Bytes32; use chia_wallet::{ @@ -197,4 +203,174 @@ mod tests { ); assert_eq!(hex::encode(actual), hex::encode(expected)); } + + #[test] + fn test_cat_spend_multi() { + let synthetic_key = + master_to_wallet_unhardened(&SecretKey::from_seed(SEED.as_ref()).public_key(), 0) + .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + + let mut a = Allocator::new(); + let standard_puzzle_ptr = node_from_bytes(&mut a, &STANDARD_PUZZLE).unwrap(); + let cat_puzzle_ptr = node_from_bytes(&mut a, &CAT_PUZZLE).unwrap(); + + let asset_id = [42; 32]; + + let p2_puzzle_hash = standard_puzzle_hash(&synthetic_key); + let cat_puzzle_hash = cat_puzzle_hash(asset_id, p2_puzzle_hash); + + let parent_coin_1 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); + let coin_1 = Coin::new( + Bytes32::from(parent_coin_1.coin_id()), + Bytes32::new(cat_puzzle_hash), + 42, + ); + + let parent_coin_2 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); + let coin_2 = Coin::new( + Bytes32::from(parent_coin_2.coin_id()), + Bytes32::new(cat_puzzle_hash), + 34, + ); + + let parent_coin_3 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); + let coin_3 = Coin::new( + Bytes32::from(parent_coin_3.coin_id()), + Bytes32::new(cat_puzzle_hash), + 69, + ); + + let conditions = vec![CatCondition::Normal(Condition::CreateCoin( + CreateCoin::Normal { + puzzle_hash: coin_1.puzzle_hash, + amount: coin_1.amount + coin_2.amount + coin_3.amount, + }, + ))]; + + let coin_spends = spend_cat_coins( + &mut a, + standard_puzzle_ptr, + cat_puzzle_ptr, + asset_id, + &[ + CatSpend { + coin: coin_1, + synthetic_key: synthetic_key.clone(), + conditions, + extra_delta: 0, + lineage_proof: LineageProof { + parent_coin_info: parent_coin_1.parent_coin_info, + inner_puzzle_hash: p2_puzzle_hash.into(), + amount: parent_coin_1.amount, + }, + p2_puzzle_hash, + }, + CatSpend { + coin: coin_2, + synthetic_key: synthetic_key.clone(), + conditions: Vec::new(), + extra_delta: 0, + lineage_proof: LineageProof { + parent_coin_info: parent_coin_2.parent_coin_info, + inner_puzzle_hash: p2_puzzle_hash.into(), + amount: parent_coin_2.amount, + }, + p2_puzzle_hash, + }, + CatSpend { + coin: coin_3, + synthetic_key, + conditions: Vec::new(), + extra_delta: 0, + lineage_proof: LineageProof { + parent_coin_info: parent_coin_3.parent_coin_info, + inner_puzzle_hash: p2_puzzle_hash.into(), + amount: parent_coin_3.amount, + }, + p2_puzzle_hash, + }, + ], + ) + .unwrap(); + + let spend_vec = coin_spends + .clone() + .into_iter() + .map(|coin_spend| { + ( + coin_spend.coin, + coin_spend.puzzle_reveal, + coin_spend.solution, + ) + }) + .collect::>(); + let gen = solution_generator(spend_vec).unwrap(); + let block = + run_block_generator::(&mut a, &gen, &[], u64::MAX, 0).unwrap(); + + assert_eq!(block.cost, 101289468); + + assert_eq!(coin_spends.len(), 3); + + let output_ptr_1 = coin_spends[0] + .puzzle_reveal + .run(&mut a, 0, u64::MAX, &coin_spends[0].solution) + .unwrap() + .1; + let actual = node_to_bytes(&a, output_ptr_1).unwrap(); + + let expected = hex!( + " + ffff46ffa06438c882c2db9f5c2a8b4cbda9258c40a6583b2d7c6becc1678607 + 4d558c834980ffff3cffa1cb1cb6597fe61e67a6cbbcd4e8f0bda5e9fc56cd84 + c9e9502772b410dc8a03207680ffff3dffa0742ddb368882193072ea013bde24 + 4a5c9d40ab4454c09666e84777a79307e17a80ffff32ffb08584adae5630842a + 1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc + c9183fe61e48d8bfffa004c476adfcffeacfef7c979bdd03b4641f1870d3f81b + 20636eefbcf879bb64ec80ffff33ffa0f9f2d59294f2aae8f9833db876d1bf43 + 95d46af18c17312041c6f4a4d73fa041ff8200918080 + " + ); + assert_eq!(hex::encode(actual), hex::encode(expected)); + + let output_ptr_2 = coin_spends[1] + .puzzle_reveal + .run(&mut a, 0, u64::MAX, &coin_spends[1].solution) + .unwrap() + .1; + let actual = node_to_bytes(&a, output_ptr_2).unwrap(); + + let expected = hex!( + " + ffff46ffa0ae60b8db0664959078a1c6e51ca6a8fc55207c63a8ac74d026f1d9 + 15c406bac480ffff3cffa1cb9a41843ab318a8336f61a6bf9e8b0b1d555b9f07 + cd19582e0bc52a961c65dc9e80ffff3dffa0294cda8d35164e01c4e3b7c07c36 + a5bb2f38a23e93ef49c882ee74349a0df8bd80ffff32ffb08584adae5630842a + 1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc + c9183fe61e48d8bfffa0ba4484b961b7a2369d948d06c55b64bdbfaffb326bc1 + 3b490ab1215dd33d8d468080 + " + ); + assert_eq!(hex::encode(actual), hex::encode(expected)); + + let output_ptr_3 = coin_spends[2] + .puzzle_reveal + .run(&mut a, 0, u64::MAX, &coin_spends[2].solution) + .unwrap() + .1; + let actual = node_to_bytes(&a, output_ptr_3).unwrap(); + + let expected = hex!( + " + ffff46ffa0f8eacbef2bad0c7b27b638a90a37244e75013e977f250230856d05 + a2784e1d0980ffff3cffa1cb17c47c5fa8d795efa0d9227d2066cde36dd4e845 + 7e8f4e507d2015a1c7f3d94b80ffff3dffa0629abc502829339c7880ee003c4e + 68a8181d71206e50e7b36c29301ef60128f580ffff32ffb08584adae5630842a + 1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc + c9183fe61e48d8bfffa0ba4484b961b7a2369d948d06c55b64bdbfaffb326bc1 + 3b490ab1215dd33d8d468080 + " + ); + assert_eq!(hex::encode(actual), hex::encode(expected)); + } }