Skip to content

Commit

Permalink
Merge pull request #13 from Rigidity/multi-cat-fix
Browse files Browse the repository at this point in the history
Fix spending more than 2 CATs at once
  • Loading branch information
Rigidity authored Mar 5, 2024
2 parents 2e42f59 + 851fb3b commit f8454c1
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 13 deletions.
39 changes: 29 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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."
Expand Down Expand Up @@ -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"
180 changes: 178 additions & 2 deletions src/spends/cat/raw_spend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ pub fn spend_cat_coins(
) -> Result<Vec<CoinSpend>, ToClvmError> {
let mut total_delta = 0;

let len = cat_spends.len();

cat_spends
.iter()
.enumerate()
Expand All @@ -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 {
Expand Down Expand Up @@ -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::{
Expand Down Expand Up @@ -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::<Vec<_>>();
let gen = solution_generator(spend_vec).unwrap();
let block =
run_block_generator::<Program, EmptyVisitor>(&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));
}
}

0 comments on commit f8454c1

Please sign in to comment.