chia_sdk_driver/primitives/
clawback.rs1use crate::{DriverError, Layer, P2OneOfManyLayer, Puzzle, Spend, SpendContext};
2use chia_protocol::{Bytes, Bytes32};
3use chia_puzzles::AUGMENTED_CONDITION_HASH;
4use chia_sdk_types::{
5 Condition, MerkleTree,
6 conditions::Remark,
7 puzzles::{
8 AugmentedConditionArgs, AugmentedConditionSolution, P2_CURRIED_PUZZLE_HASH, P2CurriedArgs,
9 P2CurriedSolution, P2OneOfManySolution,
10 },
11 run_puzzle,
12};
13use chia_streamable_macro::streamable;
14use chia_traits::Streamable;
15use clvm_traits::FromClvm;
16use clvm_traits::ToClvm;
17use clvm_traits::clvm_list;
18use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash};
19use clvmr::{Allocator, NodePtr};
20
21#[streamable]
22pub struct VersionedBlob {
23 version: u16,
24 blob: Bytes,
25}
26
27#[streamable]
28#[derive(Copy)]
29pub struct Clawback {
30 pub timelock: u64,
32 pub sender_puzzle_hash: Bytes32,
34 pub receiver_puzzle_hash: Bytes32,
36}
37
38impl Clawback {
39 pub fn parse_children(
40 allocator: &mut Allocator,
41 parent_puzzle: Puzzle, parent_solution: NodePtr,
43 ) -> Result<Option<Vec<Self>>, DriverError>
44 where
45 Self: Sized,
46 {
47 let output = run_puzzle(allocator, parent_puzzle.ptr(), parent_solution)?;
48 let conditions = Vec::<Condition>::from_clvm(allocator, output)?;
49 let mut outputs = Vec::<Clawback>::new();
50 let mut metadatas = Vec::<Clawback>::new();
51 let mut puzhashes = Vec::<[u8; 32]>::with_capacity(conditions.len());
52 for condition in conditions {
53 match condition {
54 Condition::CreateCoin(cc) => puzhashes.push(cc.puzzle_hash.into()),
55 Condition::Remark(rm) => match allocator.sexp(rm.rest) {
56 clvmr::SExp::Atom => {}
57 clvmr::SExp::Pair(first, rest) => {
58 match allocator.sexp(first) {
59 clvmr::SExp::Atom => {
60 let Some(atom) = allocator.small_number(first) else {
61 continue;
62 };
63 if atom != 2 {
64 continue;
65 } }
67 clvmr::SExp::Pair(_, _) => continue,
68 }
69 match allocator.sexp(rest) {
72 clvmr::SExp::Atom => {}
73 clvmr::SExp::Pair(r_first, _r_rest) => match allocator.sexp(r_first) {
74 clvmr::SExp::Atom => {
75 let rest_atom = &allocator.atom(r_first);
76 metadatas.push(
77 Clawback::from_bytes_unchecked(
78 VersionedBlob::from_bytes_unchecked(rest_atom)
79 .map_err(|_| DriverError::InvalidMemo)?
80 .blob
81 .as_ref(),
82 )
83 .map_err(|_| DriverError::InvalidMemo)?,
84 );
85 }
86 clvmr::SExp::Pair(_, _) => {}
87 },
88 }
89 }
90 },
91 _ => {}
92 }
93 }
94 for &clawback in &metadatas {
95 if puzhashes.contains(&clawback.to_layer().tree_hash().to_bytes()) {
96 outputs.push(clawback);
97 }
98 }
99 Ok(Some(outputs))
100 }
101
102 pub fn receiver_path_puzzle_hash(&self) -> TreeHash {
103 CurriedProgram {
104 program: TreeHash::new(AUGMENTED_CONDITION_HASH),
105 args: AugmentedConditionArgs::new(
106 Condition::<TreeHash>::assert_seconds_relative(self.timelock),
107 TreeHash::from(self.receiver_puzzle_hash),
108 ),
109 }
110 .tree_hash()
111 }
112
113 pub fn receiver_path_puzzle(
114 &self,
115 ctx: &mut SpendContext,
116 inner_puzzle: NodePtr,
117 ) -> Result<NodePtr, DriverError> {
118 ctx.curry(AugmentedConditionArgs::new(
119 Condition::<NodePtr>::assert_seconds_relative(self.timelock),
120 inner_puzzle,
121 ))
122 }
123
124 pub fn sender_path_puzzle_hash(&self) -> TreeHash {
125 CurriedProgram {
126 program: P2_CURRIED_PUZZLE_HASH,
127 args: P2CurriedArgs::new(self.sender_puzzle_hash),
128 }
129 .tree_hash()
130 }
131
132 pub fn sender_path_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
133 ctx.curry(P2CurriedArgs::new(self.sender_puzzle_hash))
134 }
135
136 pub fn merkle_tree(&self) -> MerkleTree {
137 MerkleTree::new(&[
138 self.receiver_path_puzzle_hash().into(),
139 self.sender_path_puzzle_hash().into(),
140 ])
141 }
142
143 pub fn to_layer(&self) -> P2OneOfManyLayer {
144 P2OneOfManyLayer::new(self.merkle_tree().root())
145 }
146
147 pub fn get_remark_condition(
150 &self,
151 allocator: &mut Allocator,
152 ) -> Result<Remark<NodePtr>, DriverError> {
153 let vb = VersionedBlob {
154 version: 1,
155 blob: self
156 .to_bytes()
157 .map_err(|_| DriverError::InvalidMemo)?
158 .into(),
159 };
160 let node_ptr = clvm_list!(
162 2,
163 Bytes::new(vb.to_bytes().map_err(|_| DriverError::InvalidMemo)?)
164 )
165 .to_clvm(allocator)?;
166
167 Ok(Remark::new(node_ptr))
168 }
169
170 pub fn receiver_spend(
171 &self,
172 ctx: &mut SpendContext,
173 spend: Spend,
174 ) -> Result<Spend, DriverError> {
175 let merkle_tree = self.merkle_tree();
176
177 let puzzle = self.receiver_path_puzzle(ctx, spend.puzzle)?;
178 let solution = ctx.alloc(&AugmentedConditionSolution::new(spend.solution))?;
179
180 let proof = merkle_tree
181 .proof(ctx.tree_hash(puzzle).into())
182 .ok_or(DriverError::InvalidMerkleProof)?;
183
184 P2OneOfManyLayer::new(merkle_tree.root())
185 .construct_spend(ctx, P2OneOfManySolution::new(proof, puzzle, solution))
186 }
187
188 pub fn sender_spend(&self, ctx: &mut SpendContext, spend: Spend) -> Result<Spend, DriverError> {
189 let merkle_tree = self.merkle_tree();
190
191 let puzzle = self.sender_path_puzzle(ctx)?;
192 let solution = ctx.alloc(&P2CurriedSolution::new(spend.puzzle, spend.solution))?;
193
194 let proof = merkle_tree
195 .proof(ctx.tree_hash(puzzle).into())
196 .ok_or(DriverError::InvalidMerkleProof)?;
197
198 P2OneOfManyLayer::new(merkle_tree.root())
199 .construct_spend(ctx, P2OneOfManySolution::new(proof, puzzle, solution))
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use std::slice;
206
207 use chia_protocol::{Coin, SpendBundle};
208 use chia_puzzle_types::Memos;
209 use chia_sdk_test::Simulator;
210 use chia_sdk_types::Conditions;
211 use clvm_traits::ToClvm;
212
213 use crate::{SpendWithConditions, StandardLayer};
214
215 use super::*;
216
217 #[test]
218 #[allow(clippy::similar_names)]
219 fn test_clawback_coin_claim() -> anyhow::Result<()> {
220 let mut sim = Simulator::new();
221 let ctx = &mut SpendContext::new();
222
223 let alice = sim.bls(1);
224 let alice_p2 = StandardLayer::new(alice.pk);
225
226 let bob = sim.bls(1);
227 let bob_p2 = StandardLayer::new(bob.pk);
228
229 let clawback = Clawback {
230 timelock: 1,
231 sender_puzzle_hash: alice.puzzle_hash,
232 receiver_puzzle_hash: bob.puzzle_hash,
233 };
234 let clawback_puzzle_hash = clawback.to_layer().tree_hash().into();
235 let coin = alice.coin;
236 let conditions = Conditions::new()
237 .create_coin(clawback_puzzle_hash, 1, Memos::None)
238 .with(clawback.get_remark_condition(ctx)?);
239 alice_p2.spend(ctx, coin, conditions)?;
240
241 let cs = ctx.take();
242
243 let clawback_coin = Coin::new(coin.coin_id(), clawback_puzzle_hash, 1);
244
245 sim.spend_coins(cs, &[alice.sk])?;
246
247 let puzzle_reveal = sim
248 .puzzle_reveal(coin.coin_id())
249 .expect("missing puzzle")
250 .to_clvm(ctx)?;
251
252 let solution = sim
253 .solution(coin.coin_id())
254 .expect("missing solution")
255 .to_clvm(ctx)?;
256
257 let puzzle = Puzzle::parse(ctx, puzzle_reveal);
258
259 let children = Clawback::parse_children(ctx, puzzle, solution)
261 .expect("we should have found the child")
262 .expect("we should have found children");
263 assert_eq!(children.len(), 1);
264 assert_eq!(children[0], clawback);
265
266 let bob_inner = bob_p2.spend_with_conditions(ctx, Conditions::new().reserve_fee(1))?;
267 let receiver_spend = clawback.receiver_spend(ctx, bob_inner)?;
268 ctx.spend(clawback_coin, receiver_spend)?;
269
270 sim.spend_coins(ctx.take(), &[bob.sk])?;
271
272 Ok(())
273 }
274
275 #[test]
276 fn test_clawback_compatible_with_python() -> anyhow::Result<()> {
277 let ctx = &mut SpendContext::new();
278 let bytes = hex_literal::hex!(
279 "00000001e3b0c44298fc1c149afbf4c8996fb924000000000000000000000000000000014eb7420f8651b09124e1d40cdc49eeddacbaa0c25e6ae5a0a482fac8e3b5259f000001977420dc00ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0b50b02adba343fff8bf3a94e92ed7df43743aedf0006b81a6c00ae573c0cce7d08216f60886fe84e4078a5209b0e5171ff018080ff80ffff01ffff33ffa0aeb663f32c4cfe1122710bc03cdc086f87e3243c055e8bebba42189cafbaf465ff840098968080ffff01ff02ffc04e00010000004800000000000000644eb7420f8651b09124e1d40cdc49eeddacbaa0c25e6ae5a0a482fac8e3b5259f5abb5d5568b4a7411dd97b3356cfedfac09b5fb35621a7fa29ab9b59dc905fb68080ff8080a8a06f869d849d69f194df0c5e003a302aa360309a8a75eb50867f8f4c90484d8fe6cc63d4d3bc1f4d5ac456e75678ad09209f744a4aea5857e2771f0c351623f90f72418d086862c66d4270d8b04c13814d8279050ff9e9944c8d491377da87"
280 );
281 let sb = SpendBundle::from_bytes(&bytes)?;
282 let puzzle_clvm = sb.coin_spends[0].puzzle_reveal.to_clvm(ctx)?;
283 let puz = Puzzle::parse(ctx, puzzle_clvm);
284 let sol = sb.coin_spends[0].solution.to_clvm(ctx)?;
285 let children = Clawback::parse_children(ctx, puz, sol)
286 .expect("we should have found the child")
287 .expect("we should have found children");
288 assert_eq!(children.len(), 1);
289 Ok(())
290 }
291
292 #[test]
293 #[allow(clippy::similar_names)]
294 fn test_clawback_coin_clawback() -> anyhow::Result<()> {
295 let mut sim = Simulator::new();
296 let ctx = &mut SpendContext::new();
297
298 let alice = sim.bls(1);
299 let alice_p2 = StandardLayer::new(alice.pk);
300
301 let clawback = Clawback {
302 timelock: u64::MAX,
303 sender_puzzle_hash: alice.puzzle_hash,
304 receiver_puzzle_hash: Bytes32::default(),
305 };
306 let clawback_puzzle_hash = clawback.to_layer().tree_hash().into();
307
308 alice_p2.spend(
309 ctx,
310 alice.coin,
311 Conditions::new().create_coin(clawback_puzzle_hash, 1, Memos::None),
312 )?;
313 let clawback_coin = Coin::new(alice.coin.coin_id(), clawback_puzzle_hash, 1);
314
315 sim.spend_coins(ctx.take(), slice::from_ref(&alice.sk))?;
316
317 let inner = alice_p2.spend_with_conditions(ctx, Conditions::new().reserve_fee(1))?;
318 let sender_spend = clawback.sender_spend(ctx, inner)?;
319 ctx.spend(clawback_coin, sender_spend)?;
320
321 sim.spend_coins(ctx.take(), &[alice.sk])?;
322
323 Ok(())
324 }
325}