1use crate::Bytes32;
2use crate::Coin;
3use crate::coin_spend::CoinSpend;
4use chia_bls::G2Element;
5use chia_streamable_macro::streamable;
6use chia_traits::Streamable;
7use clvm_traits::FromClvm;
8use clvmr::cost::Cost;
9use clvmr::error::EvalErr;
10use clvmr::op_utils::{first, rest};
11use clvmr::{Allocator, ClvmFlags, NodePtr, SExp};
12
13#[cfg(feature = "py-bindings")]
14use pyo3::prelude::*;
15#[cfg(feature = "py-bindings")]
16use pyo3::types::PyType;
17
18#[streamable(subclass)]
19pub struct SpendBundle {
20 coin_spends: Vec<CoinSpend>,
21 aggregated_signature: G2Element,
22}
23
24impl SpendBundle {
25 pub fn aggregate(spend_bundles: &[SpendBundle]) -> SpendBundle {
26 let mut coin_spends = Vec::<CoinSpend>::new();
27 let mut aggregated_signature = G2Element::default();
28 for sb in spend_bundles {
29 coin_spends.extend_from_slice(&sb.coin_spends[..]);
30 aggregated_signature.aggregate(&sb.aggregated_signature);
31 }
32 SpendBundle {
33 coin_spends,
34 aggregated_signature,
35 }
36 }
37
38 pub fn name(&self) -> Bytes32 {
39 self.hash().into()
40 }
41
42 pub fn additions(&self) -> Result<Vec<Coin>, EvalErr> {
47 const CREATE_COIN_COST: Cost = 1_350_000;
54 const CREATE_COIN: u8 = 51;
55
56 let mut ret = Vec::<Coin>::new();
57 let mut cost_left = 11_000_000_000;
58 let mut a = Allocator::new();
59 let checkpoint = a.checkpoint();
60
61 for cs in &self.coin_spends {
62 a.restore_checkpoint(&checkpoint);
63 let (cost, mut conds) =
64 cs.puzzle_reveal
65 .run(&mut a, ClvmFlags::empty(), cost_left, &cs.solution)?;
66 if cost > cost_left {
67 return Err(EvalErr::CostExceeded);
68 }
69 cost_left -= cost;
70 let parent_coin_info: Bytes32 = cs.coin.coin_id();
71
72 while let Some((c, tail)) = a.next(conds) {
73 conds = tail;
74 let op = first(&a, c)?;
75 let c = rest(&a, c)?;
76 let buf = match a.sexp(op) {
77 SExp::Atom => a.atom(op),
78 SExp::Pair(..) => {
79 return Err(EvalErr::InvalidOpArg(op, "invalid condition".to_string()));
80 }
81 };
82 let buf = buf.as_ref();
83 if buf.len() != 1 {
84 continue;
85 }
86 if buf[0] == CREATE_COIN {
87 let (puzzle_hash, (amount, _)) = <(Bytes32, (u64, NodePtr))>::from_clvm(&a, c)
88 .map_err(|_| {
89 EvalErr::InvalidOpArg(c, "failed to parse spend".to_string())
90 })?;
91 ret.push(Coin {
92 parent_coin_info,
93 puzzle_hash,
94 amount,
95 });
96 if CREATE_COIN_COST > cost_left {
97 return Err(EvalErr::CostExceeded);
98 }
99 cost_left -= CREATE_COIN_COST;
100 }
101 }
102 }
103 Ok(ret)
104 }
105}
106
107#[cfg(feature = "py-bindings")]
108#[pymethods]
109#[allow(clippy::needless_pass_by_value)]
110impl SpendBundle {
111 #[classmethod]
112 #[pyo3(name = "aggregate")]
113 fn py_aggregate(
114 cls: &Bound<'_, PyType>,
115 py: Python<'_>,
116 spend_bundles: Vec<Self>,
117 ) -> PyResult<Py<PyAny>> {
118 let aggregated = Bound::new(py, Self::aggregate(&spend_bundles))?;
119 if aggregated.is_exact_instance(cls) {
120 Ok(aggregated.into_any().unbind())
121 } else {
122 let aggregated_py = aggregated.into_any().unbind();
123 let instance = cls.call_method1("from_parent", (Py::clone_ref(&aggregated_py, py),))?;
124 Ok(instance.into_any().unbind())
125 }
126 }
127
128 #[classmethod]
129 #[pyo3(name = "from_parent")]
130 pub fn from_parent(cls: &Bound<'_, PyType>, spend_bundle: Self) -> PyResult<Py<PyAny>> {
131 let instance = cls.call(
133 (spend_bundle.coin_spends, spend_bundle.aggregated_signature),
134 None,
135 )?;
136
137 Ok(instance.into_any().unbind())
138 }
139
140 #[pyo3(name = "name")]
141 fn py_name(&self) -> Bytes32 {
142 self.name()
143 }
144
145 fn removals(&self) -> Vec<Coin> {
146 let mut ret = Vec::<Coin>::with_capacity(self.coin_spends.len());
147 for cs in &self.coin_spends {
148 ret.push(cs.coin);
149 }
150 ret
151 }
152
153 #[pyo3(name = "additions")]
154 fn py_additions(&self) -> PyResult<Vec<Coin>> {
155 self.additions()
156 .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use crate::Program;
164 use rstest::rstest;
165 use std::fs;
166
167 #[rstest]
168 #[case(
169 "e3c0",
170 "fd65e4b0f21322f78d1025e8a8ff7a1df77cd40b86885b851f4572e5ce06e4ff",
171 "e3c000a395f8f69d5e263a9548f13bffb1c4b701ab8f3faa03f7647c8750d077"
172 )]
173 #[case(
174 "bb13",
175 "6b2aaee962cb1de3fdeb1f0506c02df4b9e162e2af3dd1db22048454b5122a87",
176 "bb13d1e13438736c7ba0217c7b82ee4db56a7f4fb9d22c703c2152362b2314ee"
177 )]
178 fn test_additions_ff(
179 #[case] spend_file: &str,
180 #[case] expect_parent: &str,
181 #[case] expect_ph: &str,
182 ) {
183 let spend_bytes =
184 fs::read(format!("../../ff-tests/{spend_file}.spend")).expect("read file");
185 let spend = CoinSpend::from_bytes(&spend_bytes).expect("parse CoinSpend");
186 let bundle = SpendBundle::new(vec![spend], G2Element::default());
187
188 let additions = bundle.additions().expect("additions");
189
190 assert_eq!(additions.len(), 1);
191 assert_eq!(
192 additions[0].parent_coin_info.as_ref(),
193 &hex::decode(expect_parent).expect("hex::decode")
194 );
195 assert_eq!(
196 additions[0].puzzle_hash.as_ref(),
197 &hex::decode(expect_ph).expect("hex::decode")
198 );
199 assert_eq!(additions[0].amount, 1);
200 }
201
202 fn test_impl<F: Fn(Coin, SpendBundle)>(solution: &str, body: F) {
203 let solution = hex::decode(solution).expect("hex::decode");
204 let test_coin = Coin::new(
205 hex::decode("4444444444444444444444444444444444444444444444444444444444444444")
206 .unwrap()
207 .try_into()
208 .unwrap(),
209 hex::decode("3333333333333333333333333333333333333333333333333333333333333333")
210 .unwrap()
211 .try_into()
212 .unwrap(),
213 1,
214 );
215 let spend = CoinSpend::new(
216 test_coin,
217 Program::new(vec![1_u8].into()),
218 Program::new(solution.into()),
219 );
220 let bundle = SpendBundle::new(vec![spend], G2Element::default());
221 body(test_coin, bundle);
222 }
223
224 #[test]
228 fn test_single_create_coin() {
229 let solution = "ff\
233ff33\
234ffa02222222222222222222222222222222222222222222222222222222222222222\
235ff01\
23680\
23780";
238 test_impl(solution, |test_coin: Coin, bundle: SpendBundle| {
239 let additions = bundle.additions().expect("additions");
240
241 let new_coin = Coin::new(
242 test_coin.coin_id(),
243 hex::decode("2222222222222222222222222222222222222222222222222222222222222222")
244 .unwrap()
245 .try_into()
246 .unwrap(),
247 1,
248 );
249 assert_eq!(additions, [new_coin]);
250 });
251 }
252
253 #[test]
254 fn test_invalid_condition() {
255 let solution = "ff\
259ffff0133\
260ffa02222222222222222222222222222222222222222222222222222222222222222\
261ff01\
26280\
26380";
264
265 test_impl(solution, |_test_coin, bundle: SpendBundle| {
266 assert_eq!(
267 bundle.additions().unwrap_err().to_string(),
268 "InvalidOperatorArg: invalid condition"
269 );
270 });
271 }
272
273 #[test]
274 fn test_invalid_spend() {
275 let solution = "ff\
279ff33\
280ffa02222222222222222222222222222222222222222222222222222222222222222\
281ffff0101\
28280\
28380";
284
285 test_impl(solution, |_test_coin, bundle: SpendBundle| {
286 assert_eq!(
287 bundle.additions().unwrap_err().to_string(),
288 "InvalidOperatorArg: failed to parse spend"
289 );
290 });
291 }
292}
293
294#[cfg(all(test, feature = "serde"))]
295mod serde_tests {
296 use chia_bls::Signature;
297 use indoc::indoc;
298
299 use crate::Program;
300
301 use super::*;
302
303 #[test]
304 fn test_json_spend_bundle() -> anyhow::Result<()> {
305 let json = serde_json::to_string_pretty(&SpendBundle::new(
306 vec![CoinSpend::new(
307 Coin::new([0; 32].into(), [1; 32].into(), 42),
308 Program::from(b"abc".to_vec()),
309 Program::from(b"xyz".to_vec()),
310 )],
311 Signature::default(),
312 ))?;
313
314 let output = indoc! {r#"{
315 "coin_spends": [
316 {
317 "coin": {
318 "parent_coin_info": "0x0000000000000000000000000000000000000000000000000000000000000000",
319 "puzzle_hash": "0x0101010101010101010101010101010101010101010101010101010101010101",
320 "amount": 42
321 },
322 "puzzle_reveal": "616263",
323 "solution": "78797a"
324 }
325 ],
326 "aggregated_signature": "0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
327 }"#};
328
329 assert_eq!(json, output);
330
331 Ok(())
332 }
333}