Skip to content

Commit

Permalink
Fix spending more than 2 CATs at once
Browse files Browse the repository at this point in the history
  • Loading branch information
Rigidity committed Mar 5, 2024
1 parent 2e42f59 commit 3c1f1d0
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 11 deletions.
37 changes: 28 additions & 9 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
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));
}
}
19 changes: 19 additions & 0 deletions wallet.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDLDCCAhSgAwIBAgIUGR3DzdigH5k75jkIx4N1NvdVFsAwDQYJKoZIhvcNAQEL
BQAwRDENMAsGA1UECgwEQ2hpYTEQMA4GA1UEAwwHQ2hpYSBDQTEhMB8GA1UECwwY
T3JnYW5pYyBGYXJtaW5nIERpdmlzaW9uMCAXDTI0MDIyODAzMTkyNloYDzIxMDAw
ODAyMDAwMDAwWjBBMQ0wCwYDVQQDDARDaGlhMQ0wCwYDVQQKDARDaGlhMSEwHwYD
VQQLDBhPcmdhbmljIEZhcm1pbmcgRGl2aXNpb24wggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQC7ee1QHZmSuX5HK/ex0olm7hd1A5mDAS6ExkdzHWaoZE5c
Co+DC+juiqoihbKFMbjiv6NLG3tVTtn4L+bqexX2RkkptxlWk1KIBY1DRXaI1V7v
Cq1ZSncXiD0pAJPDPuSDKhT8uyOkHplL0p3DbV8pJo/yeMILz69kCT/mTglCPwTV
ciY9ybCitwpVygp5HMK7a+JkAZzrAfpMNJ4W+pwFZQIx6l7mQNE1fAGZHtxbRvSY
fOgJsdNR7/TGWIsccwScHaWtAPEUMg4ovwFb5SqFEx2NJbMxzEYMOnJSsT0oQoOx
gFkvZFC7aAtiXOyzr7MYPgWlY3I9fInWSMQ+b6LFAgMBAAGjFzAVMBMGA1UdEQQM
MAqCCGNoaWEubmV0MA0GCSqGSIb3DQEBCwUAA4IBAQCloOT/ngKwW0qUc4xGb8iN
ovsQDOwiyGrMlUDA2Pqw1vicQ8vpHLZfAl5359pXYw2Cov8ueVxeejoSWKJPAqg3
hh+3lHEcI+tEwAzLfI7JSfNDq5xgJRwjzBbt5bswf0WffJLFBSmbOV6ufsfT86wu
XERBcSumMWbpgCASPqtRBY4e7J57vilgMho+vwPe+eMGYVxRFeTCuPWQYtwxnTTG
wB8LL7e6r+wRRJ+wWTyjKXkyr8xaEen5+wQOQ4ZN8/kfcCMox2Isk67Z/zDdEB8m
RYT37/79qLF+NYa5tPiCnWH2FVJ70c/8Cy3U0dMy0ttQHwXpXhLkpMdxLVjaaiXh
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions wallet.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC7ee1QHZmSuX5H
K/ex0olm7hd1A5mDAS6ExkdzHWaoZE5cCo+DC+juiqoihbKFMbjiv6NLG3tVTtn4
L+bqexX2RkkptxlWk1KIBY1DRXaI1V7vCq1ZSncXiD0pAJPDPuSDKhT8uyOkHplL
0p3DbV8pJo/yeMILz69kCT/mTglCPwTVciY9ybCitwpVygp5HMK7a+JkAZzrAfpM
NJ4W+pwFZQIx6l7mQNE1fAGZHtxbRvSYfOgJsdNR7/TGWIsccwScHaWtAPEUMg4o
vwFb5SqFEx2NJbMxzEYMOnJSsT0oQoOxgFkvZFC7aAtiXOyzr7MYPgWlY3I9fInW
SMQ+b6LFAgMBAAECggEAGqnvt6pOElExhZvaQb9I7YvFaE4Xwksju5ybwuKWnDKP
9oVF/gRun6a3JevQBSOaQR2yctc4xlxNof/mHslcyUcb1sQE5YxUe6YWfk/d2P5H
30Wse/ucTouA71j2qxlB1OniTEOtgQUdFmyhJY9jrm2eoYTNTHLAZkDuhqnopTQl
AbtnWrUzDQc7cSzeQeH40y40rgevN5B7Bnc6cmB/QZL109GfZtA6zTqLvDFsQ7fz
OTFmVPyBrEfVvELQtTZYBdZ5BKSTEfJEO9+humH2wYn5dtIWvX4NLjrgddBqsP8N
QK3391d8RHK68G2zBg3bejPOPx/ytrlOJxnk4UDawQKBgQDbEgPaa0reB8pseAKX
iiCwc0q61LS1kd1RMMYyJoorccdL6wg+30Qintle2cGZUqhSWk284eTKgU2A8kqy
t7Ae47r2JJ7f++KJVSYaxJw16uBOPpK9Q+2Bl84b8IiEhywLh2MpY/+H6YlnW+Uy
PjSAk1d1Qjzxq809hXPLtDwXcQKBgQDbFHfoERmfDCBuEPcPp6bwOJxrsZEJiP7M
PHbwKnlZM7rzeYQiYJZp1M5jZKvFdd52tQj6XgVfzVMpNCN2A4G+hd0u1EeZOVdz
hRQzkxdgqaspJm7GJt2RY84tAJt0nWoe2yyFlhJtlVZSbDwlB4PjnGcdXcoWXxgN
cADVMELelQKBgDA7VifXqYMa2gFppsBwg+P1bkWWITc8pyqB/5D5FUCda1CWrZkS
FPE+YgNVFB/KqA6BnU3xuiqvrz2tmq6gbKFQaFBbHbOYxHaSDbKlCPOCA+KWOj57
XyZCIvnZymTLUfIz26w4PQFSdJMSE3MtaOfKCN5jOAHJXoWmBSmbRAOxAoGAditm
OgDvRwDthTEVcpZFz5FZniVbTD9e4J9EsGrYparnjmjfQwPCXjdxU997X7kiU/l2
bEXGtFQ2JSWAcymVFEV0dsv+CsIJT8Baky8mDve14S5f1fYk0UV6cn+/WnmGQjqK
m0vFiOJie3byQi75jw5P+GEcnlCQ0r61VEkB6LkCgYAzNmUS2FMg2KGjyd/nC61L
f4Y5MT36K3sxfw3JkZfuPcvnnDl1LE7GaHs4QHje9dLObweH4gh+Cm7xApaBozck
or36maTIERjkbhwgaRQoZxrllUGsaIF6JrbIuVbVgEeYFTtUEkcD1L2l0ar2uFEi
qukqXs/BqeeBXgV4T75sOw==
-----END PRIVATE KEY-----

0 comments on commit 3c1f1d0

Please sign in to comment.