Skip to content

Commit f8454c1

Browse files
authored
Merge pull request #13 from Rigidity/multi-cat-fix
Fix spending more than 2 CATs at once
2 parents 2e42f59 + 851fb3b commit f8454c1

File tree

3 files changed

+209
-13
lines changed

3 files changed

+209
-13
lines changed

Cargo.lock

Lines changed: 29 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "chia-wallet-sdk"
3-
version = "0.6.0"
3+
version = "0.6.1"
44
edition = "2021"
55
license = "Apache-2.0"
66
description = "An unofficial SDK for building Chia wallets."
@@ -31,5 +31,6 @@ rand_chacha = "0.3.1"
3131

3232
[dev-dependencies]
3333
bip39 = "2.0.0"
34+
chia = "0.5.2"
3435
hex-literal = "0.4.1"
3536
once_cell = "1.19.0"

src/spends/cat/raw_spend.rs

Lines changed: 178 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ pub fn spend_cat_coins(
3838
) -> Result<Vec<CoinSpend>, ToClvmError> {
3939
let mut total_delta = 0;
4040

41+
let len = cat_spends.len();
42+
4143
cat_spends
4244
.iter()
4345
.enumerate()
@@ -61,8 +63,8 @@ pub fn spend_cat_coins(
6163
total_delta += delta;
6264

6365
// Find information of neighboring coins on the ring.
64-
let prev_cat = &cat_spends[index.wrapping_sub(1) % cat_spends.len()];
65-
let next_cat = &cat_spends[index.wrapping_add(1) % cat_spends.len()];
66+
let prev_cat = &cat_spends[if index == 0 { len - 1 } else { index - 1 }];
67+
let next_cat = &cat_spends[if index == len - 1 { 0 } else { index + 1 }];
6668

6769
// Construct the puzzle.
6870
let puzzle = CurriedProgram {
@@ -112,6 +114,10 @@ pub fn spend_cat_coins(
112114

113115
#[cfg(test)]
114116
mod tests {
117+
use chia::gen::{
118+
conditions::EmptyVisitor, run_block_generator::run_block_generator,
119+
solution_generator::solution_generator,
120+
};
115121
use chia_bls::{derive_keys::master_to_wallet_unhardened, SecretKey};
116122
use chia_protocol::Bytes32;
117123
use chia_wallet::{
@@ -197,4 +203,174 @@ mod tests {
197203
);
198204
assert_eq!(hex::encode(actual), hex::encode(expected));
199205
}
206+
207+
#[test]
208+
fn test_cat_spend_multi() {
209+
let synthetic_key =
210+
master_to_wallet_unhardened(&SecretKey::from_seed(SEED.as_ref()).public_key(), 0)
211+
.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH);
212+
213+
let mut a = Allocator::new();
214+
let standard_puzzle_ptr = node_from_bytes(&mut a, &STANDARD_PUZZLE).unwrap();
215+
let cat_puzzle_ptr = node_from_bytes(&mut a, &CAT_PUZZLE).unwrap();
216+
217+
let asset_id = [42; 32];
218+
219+
let p2_puzzle_hash = standard_puzzle_hash(&synthetic_key);
220+
let cat_puzzle_hash = cat_puzzle_hash(asset_id, p2_puzzle_hash);
221+
222+
let parent_coin_1 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69);
223+
let coin_1 = Coin::new(
224+
Bytes32::from(parent_coin_1.coin_id()),
225+
Bytes32::new(cat_puzzle_hash),
226+
42,
227+
);
228+
229+
let parent_coin_2 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69);
230+
let coin_2 = Coin::new(
231+
Bytes32::from(parent_coin_2.coin_id()),
232+
Bytes32::new(cat_puzzle_hash),
233+
34,
234+
);
235+
236+
let parent_coin_3 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69);
237+
let coin_3 = Coin::new(
238+
Bytes32::from(parent_coin_3.coin_id()),
239+
Bytes32::new(cat_puzzle_hash),
240+
69,
241+
);
242+
243+
let conditions = vec![CatCondition::Normal(Condition::CreateCoin(
244+
CreateCoin::Normal {
245+
puzzle_hash: coin_1.puzzle_hash,
246+
amount: coin_1.amount + coin_2.amount + coin_3.amount,
247+
},
248+
))];
249+
250+
let coin_spends = spend_cat_coins(
251+
&mut a,
252+
standard_puzzle_ptr,
253+
cat_puzzle_ptr,
254+
asset_id,
255+
&[
256+
CatSpend {
257+
coin: coin_1,
258+
synthetic_key: synthetic_key.clone(),
259+
conditions,
260+
extra_delta: 0,
261+
lineage_proof: LineageProof {
262+
parent_coin_info: parent_coin_1.parent_coin_info,
263+
inner_puzzle_hash: p2_puzzle_hash.into(),
264+
amount: parent_coin_1.amount,
265+
},
266+
p2_puzzle_hash,
267+
},
268+
CatSpend {
269+
coin: coin_2,
270+
synthetic_key: synthetic_key.clone(),
271+
conditions: Vec::new(),
272+
extra_delta: 0,
273+
lineage_proof: LineageProof {
274+
parent_coin_info: parent_coin_2.parent_coin_info,
275+
inner_puzzle_hash: p2_puzzle_hash.into(),
276+
amount: parent_coin_2.amount,
277+
},
278+
p2_puzzle_hash,
279+
},
280+
CatSpend {
281+
coin: coin_3,
282+
synthetic_key,
283+
conditions: Vec::new(),
284+
extra_delta: 0,
285+
lineage_proof: LineageProof {
286+
parent_coin_info: parent_coin_3.parent_coin_info,
287+
inner_puzzle_hash: p2_puzzle_hash.into(),
288+
amount: parent_coin_3.amount,
289+
},
290+
p2_puzzle_hash,
291+
},
292+
],
293+
)
294+
.unwrap();
295+
296+
let spend_vec = coin_spends
297+
.clone()
298+
.into_iter()
299+
.map(|coin_spend| {
300+
(
301+
coin_spend.coin,
302+
coin_spend.puzzle_reveal,
303+
coin_spend.solution,
304+
)
305+
})
306+
.collect::<Vec<_>>();
307+
let gen = solution_generator(spend_vec).unwrap();
308+
let block =
309+
run_block_generator::<Program, EmptyVisitor>(&mut a, &gen, &[], u64::MAX, 0).unwrap();
310+
311+
assert_eq!(block.cost, 101289468);
312+
313+
assert_eq!(coin_spends.len(), 3);
314+
315+
let output_ptr_1 = coin_spends[0]
316+
.puzzle_reveal
317+
.run(&mut a, 0, u64::MAX, &coin_spends[0].solution)
318+
.unwrap()
319+
.1;
320+
let actual = node_to_bytes(&a, output_ptr_1).unwrap();
321+
322+
let expected = hex!(
323+
"
324+
ffff46ffa06438c882c2db9f5c2a8b4cbda9258c40a6583b2d7c6becc1678607
325+
4d558c834980ffff3cffa1cb1cb6597fe61e67a6cbbcd4e8f0bda5e9fc56cd84
326+
c9e9502772b410dc8a03207680ffff3dffa0742ddb368882193072ea013bde24
327+
4a5c9d40ab4454c09666e84777a79307e17a80ffff32ffb08584adae5630842a
328+
1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc
329+
c9183fe61e48d8bfffa004c476adfcffeacfef7c979bdd03b4641f1870d3f81b
330+
20636eefbcf879bb64ec80ffff33ffa0f9f2d59294f2aae8f9833db876d1bf43
331+
95d46af18c17312041c6f4a4d73fa041ff8200918080
332+
"
333+
);
334+
assert_eq!(hex::encode(actual), hex::encode(expected));
335+
336+
let output_ptr_2 = coin_spends[1]
337+
.puzzle_reveal
338+
.run(&mut a, 0, u64::MAX, &coin_spends[1].solution)
339+
.unwrap()
340+
.1;
341+
let actual = node_to_bytes(&a, output_ptr_2).unwrap();
342+
343+
let expected = hex!(
344+
"
345+
ffff46ffa0ae60b8db0664959078a1c6e51ca6a8fc55207c63a8ac74d026f1d9
346+
15c406bac480ffff3cffa1cb9a41843ab318a8336f61a6bf9e8b0b1d555b9f07
347+
cd19582e0bc52a961c65dc9e80ffff3dffa0294cda8d35164e01c4e3b7c07c36
348+
a5bb2f38a23e93ef49c882ee74349a0df8bd80ffff32ffb08584adae5630842a
349+
1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc
350+
c9183fe61e48d8bfffa0ba4484b961b7a2369d948d06c55b64bdbfaffb326bc1
351+
3b490ab1215dd33d8d468080
352+
"
353+
);
354+
assert_eq!(hex::encode(actual), hex::encode(expected));
355+
356+
let output_ptr_3 = coin_spends[2]
357+
.puzzle_reveal
358+
.run(&mut a, 0, u64::MAX, &coin_spends[2].solution)
359+
.unwrap()
360+
.1;
361+
let actual = node_to_bytes(&a, output_ptr_3).unwrap();
362+
363+
let expected = hex!(
364+
"
365+
ffff46ffa0f8eacbef2bad0c7b27b638a90a37244e75013e977f250230856d05
366+
a2784e1d0980ffff3cffa1cb17c47c5fa8d795efa0d9227d2066cde36dd4e845
367+
7e8f4e507d2015a1c7f3d94b80ffff3dffa0629abc502829339c7880ee003c4e
368+
68a8181d71206e50e7b36c29301ef60128f580ffff32ffb08584adae5630842a
369+
1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc
370+
c9183fe61e48d8bfffa0ba4484b961b7a2369d948d06c55b64bdbfaffb326bc1
371+
3b490ab1215dd33d8d468080
372+
"
373+
);
374+
assert_eq!(hex::encode(actual), hex::encode(expected));
375+
}
200376
}

0 commit comments

Comments
 (0)