chia_sdk_driver/action_system/
spends.rs

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