chia_sdk_driver/layers/action_layer/
m_of_n_layer.rs1use chia_bls::PublicKey;
2use chia_protocol::{Bytes32, Coin};
3use chia_sdk_types::{
4 puzzles::{
5 P2MOfNDelegateDirectArgs, P2MOfNDelegateDirectSolution,
6 P2_M_OF_N_DELEGATE_DIRECT_PUZZLE_HASH,
7 },
8 Condition, Conditions,
9};
10use clvm_traits::{clvm_quote, FromClvm};
11use clvm_utils::{ToTreeHash, TreeHash};
12use clvmr::{Allocator, NodePtr};
13
14use crate::{DriverError, Layer, Puzzle, Spend, SpendContext};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct MOfNLayer {
18 pub m: usize,
19 pub public_key_list: Vec<PublicKey>,
20}
21
22impl Layer for MOfNLayer {
23 type Solution = P2MOfNDelegateDirectSolution<NodePtr, NodePtr>;
24
25 fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
26 ctx.curry(P2MOfNDelegateDirectArgs::new(
27 self.m,
28 self.public_key_list.clone(),
29 ))
30 }
31
32 fn construct_solution(
33 &self,
34 ctx: &mut SpendContext,
35 solution: Self::Solution,
36 ) -> Result<NodePtr, DriverError> {
37 ctx.alloc(&solution)
38 }
39
40 fn parse_puzzle(allocator: &Allocator, puzzle: Puzzle) -> Result<Option<Self>, DriverError> {
41 let Some(puzzle) = puzzle.as_curried() else {
42 return Ok(None);
43 };
44
45 if puzzle.mod_hash != P2_M_OF_N_DELEGATE_DIRECT_PUZZLE_HASH {
46 return Ok(None);
47 }
48
49 let args = P2MOfNDelegateDirectArgs::from_clvm(allocator, puzzle.args)?;
50
51 Ok(Some(Self {
52 m: args.m,
53 public_key_list: args.public_key_list,
54 }))
55 }
56
57 fn parse_solution(
58 allocator: &Allocator,
59 solution: NodePtr,
60 ) -> Result<Self::Solution, DriverError> {
61 Ok(P2MOfNDelegateDirectSolution::from_clvm(
62 allocator, solution,
63 )?)
64 }
65}
66
67impl MOfNLayer {
68 pub fn new(m: usize, public_key_list: Vec<PublicKey>) -> Self {
69 Self { m, public_key_list }
70 }
71
72 pub fn ensure_non_replayable(
73 conditions: Conditions,
74 coin_id: Bytes32,
75 genesis_challenge: NodePtr,
76 ) -> Conditions {
77 let found_condition = conditions.clone().into_iter().find(|c| {
78 matches!(c, Condition::AssertMyCoinId(..))
79 || matches!(c, Condition::AssertMyParentId(..))
80 });
81
82 if found_condition.is_some() {
83 conditions
84 } else {
85 conditions.assert_my_coin_id(coin_id)
86 }
87 .remark(genesis_challenge)
88 }
89
90 pub fn spend(
91 &self,
92 ctx: &mut SpendContext,
93 coin: Coin,
94 conditions: Conditions,
95 used_pubkeys: &[PublicKey],
96 genesis_challenge: Bytes32,
97 ) -> Result<(), DriverError> {
98 let genesis_challenge = ctx.alloc(&genesis_challenge)?;
99 let spend = self.spend_with_conditions(
100 ctx,
101 Self::ensure_non_replayable(conditions, coin.coin_id(), genesis_challenge),
102 used_pubkeys,
103 )?;
104 ctx.spend(coin, spend)
105 }
106
107 pub fn spend_with_conditions(
108 &self,
109 ctx: &mut SpendContext,
110 conditions: Conditions,
111 used_pubkeys: &[PublicKey],
112 ) -> Result<Spend, DriverError> {
113 let delegated_puzzle = ctx.alloc(&clvm_quote!(conditions))?;
114 self.construct_spend(
115 ctx,
116 P2MOfNDelegateDirectSolution {
117 selectors: P2MOfNDelegateDirectArgs::selectors_for_used_pubkeys(
118 &self.public_key_list,
119 used_pubkeys,
120 ),
121 delegated_puzzle,
122 delegated_solution: NodePtr::NIL,
123 },
124 )
125 }
126}
127
128impl ToTreeHash for MOfNLayer {
129 fn tree_hash(&self) -> TreeHash {
130 P2MOfNDelegateDirectArgs::curry_tree_hash(self.m, self.public_key_list.clone())
131 }
132}