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