Skip to content

Commit 110dc75

Browse files
authored
Merge pull request #57 from Rigidity/move-cat-spend
Consolidate CAT spends into the Cat primitive
2 parents 305ae96 + b70a4e2 commit 110dc75

File tree

10 files changed

+313
-302
lines changed

10 files changed

+313
-302
lines changed

crates/chia-sdk-driver/src/driver_error.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::num::TryFromIntError;
2+
13
use chia_sdk_types::ConditionError;
24
use clvm_traits::{FromClvmError, ToClvmError};
35
use clvmr::reduction::EvalErr;
@@ -40,11 +42,6 @@ pub enum DriverError {
4042
#[error("invalid singleton struct")]
4143
InvalidSingletonStruct,
4244

43-
#[error("mismatched singleton output (maybe no spend revealed the new singleton state)")]
44-
MismatchedOutput,
45-
46-
#[error(
47-
"missing puzzle (required to build innermost puzzle - usually fixed by using .with_puzzle)"
48-
)]
49-
MissingPuzzle,
45+
#[error("try from int error")]
46+
TryFromInt(#[from] TryFromIntError),
5047
}

crates/chia-sdk-driver/src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ mod layers;
4343
mod primitive;
4444
mod primitives;
4545
mod puzzle;
46-
mod puzzles;
4746
mod spend;
4847
mod spend_context;
4948
mod spend_error;
@@ -55,7 +54,6 @@ pub use layers::*;
5554
pub use primitive::*;
5655
pub use primitives::*;
5756
pub use puzzle::*;
58-
pub use puzzles::*;
5957
pub use spend::*;
6058
pub use spend_context::*;
6159
pub use spend_error::*;

crates/chia-sdk-driver/src/primitives.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
mod cat;
2+
mod cat_spend;
23
mod debug;
34
mod did;
45
mod did_info;
56
mod did_launcher;
67
mod intermediate_launcher;
8+
mod issue_cat;
79
mod launcher;
810
mod nft;
911
mod nft_info;
1012
mod nft_launcher;
1113

1214
pub use cat::*;
15+
pub use cat_spend::*;
1316
pub use debug::*;
1417
pub use did::*;
1518
pub use did_info::*;
1619
pub use intermediate_launcher::*;
20+
pub use issue_cat::*;
1721
pub use launcher::*;
1822
pub use nft::*;
1923
pub use nft_info::*;

crates/chia-sdk-driver/src/primitives/cat.rs

Lines changed: 255 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ use chia_puzzles::{
33
cat::{CatArgs, CatSolution},
44
CoinProof, LineageProof,
55
};
6-
use chia_sdk_types::{run_puzzle, Condition};
6+
use chia_sdk_types::{run_puzzle, Condition, CreateCoin};
77
use clvm_traits::FromClvm;
8+
use clvm_utils::CurriedProgram;
89
use clvmr::{Allocator, NodePtr};
910

10-
use crate::{CatLayer, DriverError, Layer, Primitive, Puzzle, Spend, SpendContext};
11+
use crate::{CatLayer, DriverError, Layer, Primitive, Puzzle, SpendContext};
12+
13+
use super::{CatSpend, RawCatSpend};
1114

1215
#[derive(Debug, Clone, Copy)]
1316
pub struct Cat {
@@ -32,31 +35,92 @@ impl Cat {
3235
}
3336
}
3437

38+
/// Creates coin spends for one or more CATs in a ring.
39+
/// Without the ring announcements, CAT spends cannot share inputs and outputs.
40+
///
41+
/// Each item is a CAT and the inner spend for that CAT.
42+
pub fn spend_all(
43+
ctx: &mut SpendContext,
44+
cat_spends: &[CatSpend],
45+
) -> Result<Vec<CoinSpend>, DriverError> {
46+
let mut coin_spends = Vec::new();
47+
48+
let cat_puzzle_ptr = ctx.cat_puzzle()?;
49+
let len = cat_spends.len();
50+
51+
let mut total_delta = 0;
52+
53+
for (index, cat_spend) in cat_spends.iter().enumerate() {
54+
let CatSpend {
55+
cat,
56+
inner_spend,
57+
extra_delta,
58+
} = cat_spend;
59+
60+
// Calculate the delta and add it to the subtotal.
61+
let output = ctx.run(inner_spend.puzzle, inner_spend.solution)?;
62+
let conditions: Vec<NodePtr> = ctx.extract(output)?;
63+
64+
let create_coins = conditions
65+
.into_iter()
66+
.filter_map(|ptr| ctx.extract::<CreateCoin>(ptr).ok());
67+
68+
let delta = create_coins.fold(
69+
i128::from(cat.coin.amount) - i128::from(*extra_delta),
70+
|delta, create_coin| delta - i128::from(create_coin.amount),
71+
);
72+
73+
let prev_subtotal = total_delta;
74+
total_delta += delta;
75+
76+
// Find information of neighboring coins on the ring.
77+
let prev = &cat_spends[if index == 0 { len - 1 } else { index - 1 }];
78+
let next = &cat_spends[if index == len - 1 { 0 } else { index + 1 }];
79+
80+
let puzzle_reveal = ctx.serialize(&CurriedProgram {
81+
program: cat_puzzle_ptr,
82+
args: CatArgs::new(cat.asset_id, cat_spend.inner_spend.puzzle),
83+
})?;
84+
85+
let solution = ctx.serialize(&CatSolution {
86+
inner_puzzle_solution: inner_spend.solution,
87+
lineage_proof: cat.lineage_proof,
88+
prev_coin_id: prev.cat.coin.coin_id(),
89+
this_coin_info: cat.coin,
90+
next_coin_proof: CoinProof {
91+
parent_coin_info: next.cat.coin.parent_coin_info,
92+
inner_puzzle_hash: ctx.tree_hash(inner_spend.puzzle).into(),
93+
amount: next.cat.coin.amount,
94+
},
95+
prev_subtotal: prev_subtotal.try_into()?,
96+
extra_delta: *extra_delta,
97+
})?;
98+
99+
coin_spends.push(CoinSpend::new(cat.coin, puzzle_reveal, solution));
100+
}
101+
102+
Ok(coin_spends)
103+
}
104+
35105
/// Creates a coin spend for this CAT.
36-
#[allow(clippy::too_many_arguments)]
37106
pub fn spend(
38107
&self,
39108
ctx: &mut SpendContext,
40-
prev_coin_id: Bytes32,
41-
this_coin_info: Coin,
42-
next_coin_proof: CoinProof,
43-
prev_subtotal: i64,
44-
extra_delta: i64,
45-
inner_spend: Spend,
109+
spend: RawCatSpend,
46110
) -> Result<CoinSpend, DriverError> {
47-
let cat_layer = CatLayer::new(self.asset_id, inner_spend.puzzle);
111+
let cat_layer = CatLayer::new(self.asset_id, spend.inner_spend.puzzle);
48112

49113
let puzzle_ptr = cat_layer.construct_puzzle(ctx)?;
50114
let solution_ptr = cat_layer.construct_solution(
51115
ctx,
52116
CatSolution {
53117
lineage_proof: self.lineage_proof,
54-
prev_coin_id,
55-
this_coin_info,
56-
next_coin_proof,
57-
prev_subtotal,
58-
extra_delta,
59-
inner_puzzle_solution: inner_spend.solution,
118+
prev_coin_id: spend.prev_coin_id,
119+
this_coin_info: self.coin,
120+
next_coin_proof: spend.next_coin_proof,
121+
prev_subtotal: spend.prev_subtotal,
122+
extra_delta: spend.extra_delta,
123+
inner_puzzle_solution: spend.inner_spend.solution,
60124
},
61125
)?;
62126

@@ -150,3 +214,178 @@ impl Primitive for Cat {
150214
}))
151215
}
152216
}
217+
218+
#[cfg(test)]
219+
mod tests {
220+
use chia_puzzles::{cat::EverythingWithSignatureTailArgs, standard::StandardArgs};
221+
use chia_sdk_test::{secret_key, test_transaction, Simulator};
222+
use chia_sdk_types::{Condition, RunTail};
223+
224+
use crate::{issue_cat_from_coin, issue_cat_from_key, Conditions};
225+
226+
use super::*;
227+
228+
#[tokio::test]
229+
async fn test_cat_spend_multi() -> anyhow::Result<()> {
230+
let sim = Simulator::new().await?;
231+
let peer = sim.connect().await?;
232+
let ctx = &mut SpendContext::new();
233+
234+
let sk = secret_key()?;
235+
let pk = sk.public_key();
236+
237+
let puzzle_hash = StandardArgs::curry_tree_hash(pk).into();
238+
let coin = sim.mint_coin(puzzle_hash, 6).await;
239+
240+
let (issue_cat, issuance) = issue_cat_from_coin(
241+
ctx,
242+
coin.coin_id(),
243+
6,
244+
Conditions::new()
245+
.create_hinted_coin(puzzle_hash, 1, puzzle_hash)
246+
.create_hinted_coin(puzzle_hash, 2, puzzle_hash)
247+
.create_hinted_coin(puzzle_hash, 3, puzzle_hash),
248+
)?;
249+
250+
ctx.spend_p2_coin(coin, pk, issue_cat)?;
251+
252+
let cat_puzzle_hash =
253+
CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into();
254+
255+
let cat_spends = [
256+
CatSpend::new(
257+
Cat::new(
258+
Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 1),
259+
Some(issuance.lineage_proof),
260+
issuance.asset_id,
261+
puzzle_hash,
262+
),
263+
Conditions::new()
264+
.create_hinted_coin(puzzle_hash, 1, puzzle_hash)
265+
.p2_spend(ctx, pk)?,
266+
),
267+
CatSpend::new(
268+
Cat::new(
269+
Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 2),
270+
Some(issuance.lineage_proof),
271+
issuance.asset_id,
272+
puzzle_hash,
273+
),
274+
Conditions::new()
275+
.create_hinted_coin(puzzle_hash, 2, puzzle_hash)
276+
.p2_spend(ctx, pk)?,
277+
),
278+
CatSpend::new(
279+
Cat::new(
280+
Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 3),
281+
Some(issuance.lineage_proof),
282+
issuance.asset_id,
283+
puzzle_hash,
284+
),
285+
Conditions::new()
286+
.create_hinted_coin(puzzle_hash, 3, puzzle_hash)
287+
.p2_spend(ctx, pk)?,
288+
),
289+
];
290+
for coin_spend in Cat::spend_all(ctx, &cat_spends)? {
291+
ctx.insert_coin_spend(coin_spend);
292+
}
293+
294+
test_transaction(&peer, ctx.take_spends(), &[sk], &sim.config().constants).await;
295+
296+
Ok(())
297+
}
298+
299+
#[tokio::test]
300+
async fn test_cat_spend() -> anyhow::Result<()> {
301+
let sim = Simulator::new().await?;
302+
let peer = sim.connect().await?;
303+
let ctx = &mut SpendContext::new();
304+
305+
let sk = secret_key()?;
306+
let pk = sk.public_key();
307+
308+
let puzzle_hash = StandardArgs::curry_tree_hash(pk).into();
309+
let coin = sim.mint_coin(puzzle_hash, 1).await;
310+
311+
let conditions = Conditions::new().create_hinted_coin(puzzle_hash, 1, puzzle_hash);
312+
let (issue_cat, issuance) = issue_cat_from_coin(ctx, coin.coin_id(), 1, conditions)?;
313+
314+
ctx.spend_p2_coin(coin, pk, issue_cat)?;
315+
316+
let cat_puzzle_hash =
317+
CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into();
318+
let cat_coin = Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 1);
319+
320+
let cat_spends = [CatSpend::new(
321+
Cat::new(
322+
cat_coin,
323+
Some(issuance.lineage_proof),
324+
issuance.asset_id,
325+
puzzle_hash,
326+
),
327+
Conditions::new()
328+
.create_hinted_coin(puzzle_hash, 1, puzzle_hash)
329+
.p2_spend(ctx, pk)?,
330+
)];
331+
332+
for coin_spend in Cat::spend_all(ctx, &cat_spends)? {
333+
ctx.insert_coin_spend(coin_spend);
334+
}
335+
336+
test_transaction(&peer, ctx.take_spends(), &[sk], &sim.config().constants).await;
337+
338+
Ok(())
339+
}
340+
341+
#[tokio::test]
342+
async fn test_cat_melt() -> anyhow::Result<()> {
343+
let sim = Simulator::new().await?;
344+
let peer = sim.connect().await?;
345+
let ctx = &mut SpendContext::new();
346+
347+
let sk = secret_key()?;
348+
let pk = sk.public_key();
349+
350+
let puzzle_hash = StandardArgs::curry_tree_hash(pk).into();
351+
let coin = sim.mint_coin(puzzle_hash, 10000).await;
352+
353+
let conditions = Conditions::new().create_hinted_coin(puzzle_hash, 10000, puzzle_hash);
354+
let (issue_cat, issuance) = issue_cat_from_key(ctx, coin.coin_id(), pk, 10000, conditions)?;
355+
356+
ctx.spend_p2_coin(coin, pk, issue_cat)?;
357+
358+
let tail = ctx.everything_with_signature_tail_puzzle()?;
359+
let tail_program = ctx.alloc(&CurriedProgram {
360+
program: tail,
361+
args: EverythingWithSignatureTailArgs::new(pk),
362+
})?;
363+
let run_tail = Condition::Other(ctx.alloc(&RunTail::new(tail_program, ()))?);
364+
365+
let cat_puzzle_hash =
366+
CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into();
367+
let cat_coin = Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 10000);
368+
369+
let cat_spend = CatSpend::with_extra_delta(
370+
Cat::new(
371+
cat_coin,
372+
Some(issuance.lineage_proof),
373+
issuance.asset_id,
374+
puzzle_hash,
375+
),
376+
Conditions::new()
377+
.create_hinted_coin(puzzle_hash, 7000, puzzle_hash)
378+
.condition(run_tail)
379+
.p2_spend(ctx, pk)?,
380+
-3000,
381+
);
382+
383+
for coin_spend in Cat::spend_all(ctx, &[cat_spend])? {
384+
ctx.insert_coin_spend(coin_spend);
385+
}
386+
387+
test_transaction(&peer, ctx.take_spends(), &[sk], &sim.config().constants).await;
388+
389+
Ok(())
390+
}
391+
}

0 commit comments

Comments
 (0)