@@ -3,11 +3,14 @@ use chia_puzzles::{
3
3
cat:: { CatArgs , CatSolution } ,
4
4
CoinProof , LineageProof ,
5
5
} ;
6
- use chia_sdk_types:: { run_puzzle, Condition } ;
6
+ use chia_sdk_types:: { run_puzzle, Condition , CreateCoin } ;
7
7
use clvm_traits:: FromClvm ;
8
+ use clvm_utils:: CurriedProgram ;
8
9
use clvmr:: { Allocator , NodePtr } ;
9
10
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 } ;
11
14
12
15
#[ derive( Debug , Clone , Copy ) ]
13
16
pub struct Cat {
@@ -32,31 +35,92 @@ impl Cat {
32
35
}
33
36
}
34
37
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
+
35
105
/// Creates a coin spend for this CAT.
36
- #[ allow( clippy:: too_many_arguments) ]
37
106
pub fn spend (
38
107
& self ,
39
108
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 ,
46
110
) -> 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 ) ;
48
112
49
113
let puzzle_ptr = cat_layer. construct_puzzle ( ctx) ?;
50
114
let solution_ptr = cat_layer. construct_solution (
51
115
ctx,
52
116
CatSolution {
53
117
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 ,
60
124
} ,
61
125
) ?;
62
126
@@ -150,3 +214,178 @@ impl Primitive for Cat {
150
214
} ) )
151
215
}
152
216
}
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