chia_sdk_driver/action_system/
spends.rs

1use std::{collections::HashMap, mem};
2
3use chia_bls::PublicKey;
4use chia_protocol::{Bytes32, Coin};
5use chia_puzzle_types::offer::SettlementPaymentsSolution;
6use chia_sdk_types::{Conditions, conditions::AssertPuzzleAnnouncement};
7use indexmap::IndexMap;
8
9use crate::{
10    Action, Asset, Cat, CatSpend, ConditionsSpend, Delta, Deltas, Did, DriverError, FungibleSpend,
11    FungibleSpends, Id, Layer, Nft, OptionContract, Relation, SettlementLayer, SingletonSpends,
12    Spend, SpendAction, SpendContext, SpendKind, SpendWithConditions, SpendableAsset,
13    StandardLayer,
14};
15
16#[derive(Debug, Clone)]
17#[must_use]
18pub struct Spends<S = Unfinished> {
19    pub xch: FungibleSpends<Coin>,
20    pub cats: IndexMap<Id, FungibleSpends<Cat>>,
21    pub dids: IndexMap<Id, SingletonSpends<Did>>,
22    pub nfts: IndexMap<Id, SingletonSpends<Nft>>,
23    pub options: IndexMap<Id, SingletonSpends<OptionContract>>,
24    pub intermediate_puzzle_hash: Bytes32,
25    pub change_puzzle_hash: Bytes32,
26    pub outputs: Outputs,
27    pub conditions: ConditionConfig,
28    _state: S,
29}
30
31#[derive(Debug, Default, Clone)]
32pub struct ConditionConfig {
33    pub optional: Conditions,
34    pub required: Conditions,
35    pub disable_settlement_assertions: bool,
36}
37
38#[derive(Debug, Default, Clone)]
39pub struct Outputs {
40    pub xch: Vec<Coin>,
41    pub cats: IndexMap<Id, Vec<Cat>>,
42    pub dids: IndexMap<Id, Did>,
43    pub nfts: IndexMap<Id, Nft>,
44    pub options: IndexMap<Id, OptionContract>,
45    pub fee: u64,
46}
47
48#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
49pub struct Unfinished;
50
51#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
52pub struct Finished;
53
54impl Spends<Unfinished> {
55    pub fn new(change_puzzle_hash: Bytes32) -> Self {
56        Self::with_separate_change_puzzle_hash(change_puzzle_hash, change_puzzle_hash)
57    }
58
59    pub fn with_separate_change_puzzle_hash(
60        intermediate_puzzle_hash: Bytes32,
61        change_puzzle_hash: Bytes32,
62    ) -> Self {
63        Self {
64            xch: FungibleSpends::new(),
65            cats: IndexMap::new(),
66            dids: IndexMap::new(),
67            nfts: IndexMap::new(),
68            options: IndexMap::new(),
69            intermediate_puzzle_hash,
70            change_puzzle_hash,
71            outputs: Outputs::default(),
72            conditions: ConditionConfig::default(),
73            _state: Unfinished,
74        }
75    }
76
77    pub fn add(&mut self, asset: impl AddAsset) {
78        asset.add(self);
79    }
80
81    pub fn apply(
82        &mut self,
83        ctx: &mut SpendContext,
84        actions: &[Action],
85    ) -> Result<Deltas, DriverError> {
86        let deltas = Deltas::from_actions(actions);
87        for (index, action) in actions.iter().enumerate() {
88            action.spend(ctx, self, index)?;
89        }
90        Ok(deltas)
91    }
92
93    fn create_change(
94        &mut self,
95        ctx: &mut SpendContext,
96        deltas: &Deltas,
97    ) -> Result<(), DriverError> {
98        if let Some(change) = self.xch.create_change(
99            ctx,
100            deltas.get(&Id::Xch).unwrap_or(&Delta::default()),
101            self.change_puzzle_hash,
102        )? {
103            self.outputs.xch.push(change);
104        }
105
106        for (&id, cat) in &mut self.cats {
107            if let Some(change) = cat.create_change(
108                ctx,
109                deltas.get(&id).unwrap_or(&Delta::default()),
110                self.change_puzzle_hash,
111            )? {
112                self.outputs.cats.entry(id).or_default().push(change);
113            }
114        }
115
116        for (&id, did) in &mut self.dids {
117            if let Some(change) =
118                did.finalize(ctx, self.intermediate_puzzle_hash, self.change_puzzle_hash)?
119            {
120                self.outputs.dids.insert(id, change);
121            }
122        }
123
124        for (&id, nft) in &mut self.nfts {
125            if let Some(change) =
126                nft.finalize(ctx, self.intermediate_puzzle_hash, self.change_puzzle_hash)?
127            {
128                self.outputs.nfts.insert(id, change);
129            }
130        }
131
132        for (&id, option) in &mut self.options {
133            if let Some(change) =
134                option.finalize(ctx, self.intermediate_puzzle_hash, self.change_puzzle_hash)?
135            {
136                self.outputs.options.insert(id, change);
137            }
138        }
139
140        Ok(())
141    }
142
143    fn payment_assertions(&self) -> Vec<AssertPuzzleAnnouncement> {
144        let mut payment_assertions = self.xch.payment_assertions.clone();
145
146        for cat in self.cats.values() {
147            payment_assertions.extend_from_slice(&cat.payment_assertions);
148        }
149
150        for did in self.dids.values() {
151            for item in &did.lineage {
152                payment_assertions.extend_from_slice(&item.payment_assertions);
153            }
154        }
155
156        for nft in self.nfts.values() {
157            for item in &nft.lineage {
158                payment_assertions.extend_from_slice(&item.payment_assertions);
159            }
160        }
161
162        for option in self.options.values() {
163            for item in &option.lineage {
164                payment_assertions.extend_from_slice(&item.payment_assertions);
165            }
166        }
167
168        payment_assertions
169    }
170
171    fn iter_conditions_spends(&mut self) -> impl Iterator<Item = (Coin, &mut ConditionsSpend)> {
172        self.xch
173            .items
174            .iter_mut()
175            .filter_map(|item| {
176                if let SpendKind::Conditions(spend) = &mut item.kind {
177                    Some((item.asset, spend))
178                } else {
179                    None
180                }
181            })
182            .chain(self.cats.values_mut().filter_map(|cat| {
183                cat.items.iter_mut().find_map(|item| {
184                    if let SpendKind::Conditions(spend) = &mut item.kind {
185                        Some((item.asset.coin, spend))
186                    } else {
187                        None
188                    }
189                })
190            }))
191            .chain(self.dids.values_mut().filter_map(|did| {
192                did.lineage
193                    .iter_mut()
194                    .filter_map(|item| {
195                        if let SpendKind::Conditions(spend) = &mut item.kind {
196                            Some((item.asset.coin, spend))
197                        } else {
198                            None
199                        }
200                    })
201                    .last()
202            }))
203            .chain(self.nfts.values_mut().filter_map(|nft| {
204                nft.lineage
205                    .iter_mut()
206                    .filter_map(|item| {
207                        if let SpendKind::Conditions(spend) = &mut item.kind {
208                            Some((item.asset.coin, spend))
209                        } else {
210                            None
211                        }
212                    })
213                    .last()
214            }))
215            .chain(self.options.values_mut().filter_map(|option| {
216                option
217                    .lineage
218                    .iter_mut()
219                    .filter_map(|item| {
220                        if let SpendKind::Conditions(spend) = &mut item.kind {
221                            Some((item.asset.coin, spend))
222                        } else {
223                            None
224                        }
225                    })
226                    .last()
227            }))
228    }
229
230    fn emit_conditions(&mut self, ctx: &mut SpendContext) -> Result<(), DriverError> {
231        let mut conditions = self.conditions.required.clone().extend(
232            if self.conditions.disable_settlement_assertions {
233                vec![]
234            } else {
235                self.payment_assertions()
236            },
237        );
238
239        let required = !conditions.is_empty();
240
241        conditions = conditions.extend(self.conditions.optional.clone());
242
243        if self.outputs.fee > 0 {
244            conditions = conditions.reserve_fee(self.outputs.fee);
245        }
246
247        for (_, spend) in self.iter_conditions_spends() {
248            spend.add_conditions(mem::take(&mut conditions));
249        }
250
251        if conditions.is_empty() || !required {
252            return Ok(());
253        }
254
255        if let Some(index) = self
256            .xch
257            .intermediate_conditions_source(ctx, self.intermediate_puzzle_hash)?
258        {
259            match &mut self.xch.items[index].kind {
260                SpendKind::Conditions(spend) => {
261                    spend.add_conditions(mem::take(&mut conditions));
262                }
263                SpendKind::Settlement(_) => {}
264            }
265        }
266
267        for cat in self.cats.values_mut() {
268            if let Some(index) =
269                cat.intermediate_conditions_source(ctx, self.intermediate_puzzle_hash)?
270            {
271                match &mut cat.items[index].kind {
272                    SpendKind::Conditions(spend) => {
273                        spend.add_conditions(mem::take(&mut conditions));
274                    }
275                    SpendKind::Settlement(_) => {}
276                }
277            }
278        }
279
280        for did in self.dids.values_mut() {
281            if let Some(mut item) =
282                did.intermediate_fungible_xch_spend(ctx, self.intermediate_puzzle_hash)?
283            {
284                match &mut item.kind {
285                    SpendKind::Conditions(spend) => {
286                        spend.add_conditions(mem::take(&mut conditions));
287                    }
288                    SpendKind::Settlement(_) => {}
289                }
290                self.xch.items.push(item);
291            }
292        }
293
294        for nft in self.nfts.values_mut() {
295            if let Some(mut item) =
296                nft.intermediate_fungible_xch_spend(ctx, self.intermediate_puzzle_hash)?
297            {
298                match &mut item.kind {
299                    SpendKind::Conditions(spend) => {
300                        spend.add_conditions(mem::take(&mut conditions));
301                    }
302                    SpendKind::Settlement(_) => {}
303                }
304                self.xch.items.push(item);
305            }
306        }
307
308        for option in self.options.values_mut() {
309            if let Some(mut item) =
310                option.intermediate_fungible_xch_spend(ctx, self.intermediate_puzzle_hash)?
311            {
312                match &mut item.kind {
313                    SpendKind::Conditions(spend) => {
314                        spend.add_conditions(mem::take(&mut conditions));
315                    }
316                    SpendKind::Settlement(_) => {}
317                }
318                self.xch.items.push(item);
319            }
320        }
321
322        if conditions.is_empty() {
323            Ok(())
324        } else {
325            Err(DriverError::CannotEmitConditions)
326        }
327    }
328
329    fn emit_relation(&mut self, relation: Relation) {
330        match relation {
331            Relation::None => {}
332            Relation::AssertConcurrent => {
333                let coin_ids: Vec<Bytes32> = self
334                    .iter_conditions_spends()
335                    .map(|(coin, _)| coin.coin_id())
336                    .collect();
337
338                if coin_ids.len() <= 1 {
339                    return;
340                }
341
342                self.iter_conditions_spends()
343                    .enumerate()
344                    .for_each(|(i, (_, spend))| {
345                        spend.add_conditions(Conditions::new().assert_concurrent_spend(
346                            if i == 0 {
347                                coin_ids[coin_ids.len() - 1]
348                            } else {
349                                coin_ids[i - 1]
350                            },
351                        ));
352                    });
353            }
354        }
355    }
356
357    pub fn p2_puzzle_hashes(&self) -> Vec<Bytes32> {
358        let mut p2_puzzle_hashes = vec![self.intermediate_puzzle_hash];
359
360        for item in &self.xch.items {
361            p2_puzzle_hashes.push(item.asset.p2_puzzle_hash());
362        }
363
364        for (_, cat) in &self.cats {
365            for item in &cat.items {
366                p2_puzzle_hashes.push(item.asset.p2_puzzle_hash());
367            }
368        }
369
370        for (_, did) in &self.dids {
371            for item in &did.lineage {
372                p2_puzzle_hashes.push(item.asset.p2_puzzle_hash());
373            }
374        }
375
376        for (_, nft) in &self.nfts {
377            for item in &nft.lineage {
378                p2_puzzle_hashes.push(item.asset.p2_puzzle_hash());
379            }
380        }
381
382        for (_, option) in &self.options {
383            for item in &option.lineage {
384                p2_puzzle_hashes.push(item.asset.p2_puzzle_hash());
385            }
386        }
387
388        p2_puzzle_hashes
389    }
390
391    pub fn non_settlement_coin_ids(&self) -> Vec<Bytes32> {
392        let mut coin_ids = Vec::new();
393
394        for item in &self.xch.items {
395            if item.kind.is_conditions() {
396                coin_ids.push(item.asset.coin_id());
397            }
398        }
399
400        for (_, cat) in &self.cats {
401            for item in &cat.items {
402                if item.kind.is_conditions() {
403                    coin_ids.push(item.asset.coin_id());
404                }
405            }
406        }
407
408        for (_, did) in &self.dids {
409            for item in &did.lineage {
410                if item.kind.is_conditions() {
411                    coin_ids.push(item.asset.coin_id());
412                }
413            }
414        }
415
416        for (_, nft) in &self.nfts {
417            for item in &nft.lineage {
418                if item.kind.is_conditions() {
419                    coin_ids.push(item.asset.coin_id());
420                }
421            }
422        }
423
424        for (_, option) in &self.options {
425            for item in &option.lineage {
426                if item.kind.is_conditions() {
427                    coin_ids.push(item.asset.coin_id());
428                }
429            }
430        }
431
432        coin_ids
433    }
434
435    pub fn prepare(
436        mut self,
437        ctx: &mut SpendContext,
438        deltas: &Deltas,
439        relation: Relation,
440    ) -> Result<Spends<Finished>, DriverError> {
441        self.create_change(ctx, deltas)?;
442        self.emit_conditions(ctx)?;
443        self.emit_relation(relation);
444
445        Ok(Spends {
446            xch: self.xch,
447            cats: self.cats,
448            dids: self.dids,
449            nfts: self.nfts,
450            options: self.options,
451            intermediate_puzzle_hash: self.intermediate_puzzle_hash,
452            change_puzzle_hash: self.change_puzzle_hash,
453            outputs: self.outputs,
454            conditions: self.conditions,
455            _state: Finished,
456        })
457    }
458
459    pub fn finish_with_keys(
460        self,
461        ctx: &mut SpendContext,
462        deltas: &Deltas,
463        relation: Relation,
464        synthetic_keys: &IndexMap<Bytes32, PublicKey>,
465    ) -> Result<Outputs, DriverError> {
466        let spends = self.prepare(ctx, deltas, relation)?;
467        let mut coin_spends = HashMap::new();
468
469        for (asset, kind) in spends.unspent() {
470            match kind {
471                SpendKind::Conditions(spend) => {
472                    let Some(&synthetic_key) = synthetic_keys.get(&asset.p2_puzzle_hash()) else {
473                        return Err(DriverError::MissingKey);
474                    };
475                    coin_spends.insert(
476                        asset.coin().coin_id(),
477                        StandardLayer::new(synthetic_key)
478                            .spend_with_conditions(ctx, spend.finish())?,
479                    );
480                }
481                SpendKind::Settlement(spend) => {
482                    coin_spends.insert(
483                        asset.coin().coin_id(),
484                        SettlementLayer.construct_spend(
485                            ctx,
486                            SettlementPaymentsSolution::new(spend.finish()),
487                        )?,
488                    );
489                }
490            }
491        }
492
493        spends.spend(ctx, coin_spends)
494    }
495}
496
497impl Spends<Finished> {
498    pub fn unspent(&self) -> Vec<(SpendableAsset, SpendKind)> {
499        let mut result = Vec::new();
500
501        for item in &self.xch.items {
502            result.push((SpendableAsset::Xch(item.asset), item.kind.clone()));
503        }
504
505        for cat in self.cats.values() {
506            for item in &cat.items {
507                result.push((SpendableAsset::Cat(item.asset), item.kind.clone()));
508            }
509        }
510
511        for did in self.dids.values() {
512            for item in &did.lineage {
513                result.push((SpendableAsset::Did(item.asset), item.kind.clone()));
514            }
515        }
516
517        for nft in self.nfts.values() {
518            for item in &nft.lineage {
519                result.push((SpendableAsset::Nft(item.asset), item.kind.clone()));
520            }
521        }
522
523        for option in self.options.values() {
524            for item in &option.lineage {
525                result.push((SpendableAsset::Option(item.asset), item.kind.clone()));
526            }
527        }
528
529        result
530    }
531
532    pub fn spend(
533        self,
534        ctx: &mut SpendContext,
535        mut coin_spends: HashMap<Bytes32, Spend>,
536    ) -> Result<Outputs, DriverError> {
537        for item in self.xch.items {
538            let spend = coin_spends
539                .remove(&item.asset.coin_id())
540                .ok_or(DriverError::MissingSpend)?;
541            ctx.spend(item.asset, spend)?;
542        }
543
544        for cat in self.cats.into_values() {
545            let mut cat_spends = Vec::new();
546            for item in cat.items {
547                let spend = coin_spends
548                    .remove(&item.asset.coin_id())
549                    .ok_or(DriverError::MissingSpend)?;
550                cat_spends.push(CatSpend::new(item.asset, spend));
551            }
552            Cat::spend_all(ctx, &cat_spends)?;
553        }
554
555        for did in self.dids.into_values() {
556            for item in did.lineage {
557                let spend = coin_spends
558                    .remove(&item.asset.coin_id())
559                    .ok_or(DriverError::MissingSpend)?;
560                item.asset.spend(ctx, spend)?;
561            }
562        }
563
564        for nft in self.nfts.into_values() {
565            for item in nft.lineage {
566                let spend = coin_spends
567                    .remove(&item.asset.coin_id())
568                    .ok_or(DriverError::MissingSpend)?;
569                let _nft = item.asset.spend(ctx, spend)?;
570            }
571        }
572
573        for option in self.options.into_values() {
574            for item in option.lineage {
575                let spend = coin_spends
576                    .remove(&item.asset.coin_id())
577                    .ok_or(DriverError::MissingSpend)?;
578                let _option = item.asset.spend(ctx, spend)?;
579            }
580        }
581
582        Ok(self.outputs)
583    }
584}
585
586pub trait AddAsset {
587    fn add(self, spends: &mut Spends);
588}
589
590impl AddAsset for Coin {
591    fn add(self, spends: &mut Spends) {
592        spends.xch.items.push(FungibleSpend::new(self, false));
593    }
594}
595
596impl AddAsset for Cat {
597    fn add(self, spends: &mut Spends) {
598        spends
599            .cats
600            .entry(Id::Existing(self.info.asset_id))
601            .or_default()
602            .items
603            .push(FungibleSpend::new(self, false));
604    }
605}
606
607impl AddAsset for Did {
608    fn add(self, spends: &mut Spends) {
609        spends.dids.insert(
610            Id::Existing(self.info.launcher_id),
611            SingletonSpends::new(self, false),
612        );
613    }
614}
615
616impl AddAsset for Nft {
617    fn add(self, spends: &mut Spends) {
618        spends.nfts.insert(
619            Id::Existing(self.info.launcher_id),
620            SingletonSpends::new(self, false),
621        );
622    }
623}
624
625impl AddAsset for OptionContract {
626    fn add(self, spends: &mut Spends) {
627        spends.options.insert(
628            Id::Existing(self.info.launcher_id),
629            SingletonSpends::new(self, false),
630        );
631    }
632}