Skip to main content

chia_sdk_driver/
spend_context.rs

1use std::{
2    collections::HashMap,
3    ops::{Deref, DerefMut},
4};
5
6use chia_protocol::{Bytes32, Coin, CoinSpend, Program};
7use chia_sdk_types::{Conditions, Mod, conditions::Memos, run_puzzle};
8use clvm_traits::{FromClvm, ToClvm, clvm_quote};
9use clvm_utils::{CurriedProgram, TreeHash, tree_hash};
10use clvmr::{
11    Allocator, NodePtr,
12    allocator::Checkpoint,
13    serde::{node_from_bytes, node_to_bytes, node_to_bytes_backrefs},
14};
15
16use crate::{DriverError, HashedPtr, Spend};
17
18/// A wrapper around [`Allocator`] that caches puzzles and keeps track of a list of [`CoinSpend`].
19/// It's used to construct spend bundles in an easy and efficient way.
20#[derive(Debug, Default)]
21pub struct SpendContext {
22    allocator: Allocator,
23    puzzles: HashMap<TreeHash, NodePtr>,
24    coin_spends: Vec<CoinSpend>,
25}
26
27impl SpendContext {
28    pub fn new() -> Self {
29        Self::default()
30    }
31
32    pub fn reset(&mut self, checkpoint: &Checkpoint) {
33        self.allocator.restore_checkpoint(checkpoint);
34        self.puzzles.clear();
35        self.coin_spends.clear();
36    }
37
38    pub fn iter(&self) -> impl Iterator<Item = &CoinSpend> {
39        self.coin_spends.iter()
40    }
41
42    /// Remove all of the [`CoinSpend`] that have been collected so far.
43    pub fn take(&mut self) -> Vec<CoinSpend> {
44        std::mem::take(&mut self.coin_spends)
45    }
46
47    /// Adds a [`CoinSpend`] to the collection.
48    pub fn insert(&mut self, coin_spend: CoinSpend) {
49        self.coin_spends.push(coin_spend);
50    }
51
52    /// Serializes a [`Spend`] and adds it to the list of [`CoinSpend`].
53    pub fn spend(&mut self, coin: Coin, spend: Spend) -> Result<(), DriverError> {
54        let puzzle_reveal = self.serialize(&spend.puzzle)?;
55        let solution = self.serialize(&spend.solution)?;
56        self.insert(CoinSpend::new(coin, puzzle_reveal, solution));
57        Ok(())
58    }
59
60    /// Allocate a new node and return its pointer.
61    pub fn alloc<T>(&mut self, value: &T) -> Result<NodePtr, DriverError>
62    where
63        T: ToClvm<Allocator>,
64    {
65        Ok(value.to_clvm(&mut self.allocator)?)
66    }
67
68    /// Allocate a new node and return its pointer pre-hashed.
69    pub fn alloc_hashed<T>(&mut self, value: &T) -> Result<HashedPtr, DriverError>
70    where
71        T: ToClvm<Allocator>,
72    {
73        let ptr = value.to_clvm(&mut self.allocator)?;
74        Ok(HashedPtr::from_ptr(self, ptr))
75    }
76
77    /// Extract a value from a node pointer.
78    pub fn extract<T>(&self, ptr: NodePtr) -> Result<T, DriverError>
79    where
80        T: FromClvm<Allocator>,
81    {
82        Ok(T::from_clvm(&self.allocator, ptr)?)
83    }
84
85    /// Compute the tree hash of a node pointer.
86    pub fn tree_hash(&self, ptr: NodePtr) -> TreeHash {
87        tree_hash(&self.allocator, ptr)
88    }
89
90    /// Run a puzzle with a solution and return the result.
91    pub fn run(&mut self, puzzle: NodePtr, solution: NodePtr) -> Result<NodePtr, DriverError> {
92        Ok(run_puzzle(&mut self.allocator, puzzle, solution)?)
93    }
94
95    /// Allocate a value and serialize it into a [`Program`].
96    pub fn serialize<T>(&mut self, value: &T) -> Result<Program, DriverError>
97    where
98        T: ToClvm<Allocator>,
99    {
100        let ptr = value.to_clvm(&mut self.allocator)?;
101        Ok(node_to_bytes(&self.allocator, ptr)?.into())
102    }
103
104    /// Allocate a value and serialize it into a [`Program`] with back references enabled.
105    pub fn serialize_with_backrefs<T>(&mut self, value: &T) -> Result<Program, DriverError>
106    where
107        T: ToClvm<Allocator>,
108    {
109        let ptr = value.to_clvm(&mut self.allocator)?;
110        Ok(node_to_bytes_backrefs(&self.allocator, ptr)?.into())
111    }
112
113    pub fn memos<T>(&mut self, value: &T) -> Result<Memos<NodePtr>, DriverError>
114    where
115        T: ToClvm<Allocator>,
116    {
117        Ok(Memos::Some(self.alloc(value)?))
118    }
119
120    pub fn hint(&mut self, hint: Bytes32) -> Result<Memos<NodePtr>, DriverError> {
121        self.memos(&[hint])
122    }
123
124    pub fn alloc_mod<T>(&mut self) -> Result<NodePtr, DriverError>
125    where
126        T: Mod,
127    {
128        self.puzzle(T::mod_hash(), T::mod_reveal().as_ref())
129    }
130
131    pub fn curry<T>(&mut self, args: T) -> Result<NodePtr, DriverError>
132    where
133        T: Mod + ToClvm<Allocator>,
134    {
135        let mod_ptr = self.alloc_mod::<T>()?;
136        self.alloc(&CurriedProgram {
137            program: mod_ptr,
138            args,
139        })
140    }
141
142    pub fn get_puzzle(&self, puzzle_hash: &TreeHash) -> Option<NodePtr> {
143        self.puzzles.get(puzzle_hash).copied()
144    }
145
146    pub fn puzzle(
147        &mut self,
148        puzzle_hash: TreeHash,
149        puzzle_bytes: &[u8],
150    ) -> Result<NodePtr, DriverError> {
151        if let Some(puzzle) = self.puzzles.get(&puzzle_hash) {
152            Ok(*puzzle)
153        } else {
154            let puzzle = node_from_bytes(&mut self.allocator, puzzle_bytes)?;
155            self.puzzles.insert(puzzle_hash, puzzle);
156            Ok(puzzle)
157        }
158    }
159
160    pub fn delegated_spend(&mut self, conditions: Conditions) -> Result<Spend, DriverError> {
161        let puzzle = self.alloc(&clvm_quote!(conditions))?;
162        Ok(Spend::new(puzzle, NodePtr::NIL))
163    }
164}
165
166impl Deref for SpendContext {
167    type Target = Allocator;
168
169    fn deref(&self) -> &Self::Target {
170        &self.allocator
171    }
172}
173
174impl DerefMut for SpendContext {
175    fn deref_mut(&mut self) -> &mut Self::Target {
176        &mut self.allocator
177    }
178}
179
180impl IntoIterator for SpendContext {
181    type Item = CoinSpend;
182    type IntoIter = std::vec::IntoIter<Self::Item>;
183
184    fn into_iter(self) -> Self::IntoIter {
185        self.coin_spends.into_iter()
186    }
187}
188
189impl From<Allocator> for SpendContext {
190    fn from(allocator: Allocator) -> Self {
191        Self {
192            allocator,
193            puzzles: HashMap::new(),
194            coin_spends: Vec::new(),
195        }
196    }
197}