Skip to main content

chia_sdk_driver/layers/
p2_controller_puzzle_layer.rs

1use chia_protocol::Bytes32;
2use chia_sdk_types::puzzles::{
3    P2_CONTROLLER_PUZZLE_PUZZLE_HASH, P2ControllerPuzzleArgs, P2ControllerPuzzleSolution,
4};
5use clvm_traits::FromClvm;
6use clvmr::{Allocator, NodePtr};
7
8use crate::{DriverError, Layer, Puzzle, Spend, SpendContext};
9
10/// The CHIP-0037 controller-puzzle [`Layer`].
11///
12/// Locks a coin so it can only be spent when a coin whose puzzle hash equals
13/// `controller_puzzle_hash` sends a `RECEIVE_MESSAGE` whose body is the tree
14/// hash of the chosen `delegated_puzzle`. Useful for collapsing many
15/// per-coin signatures into a single signature on the controller (for example
16/// a [`P2Eip712MessageLayer`](super::P2Eip712MessageLayer) coin).
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct P2ControllerPuzzleLayer {
19    pub controller_puzzle_hash: Bytes32,
20}
21
22impl P2ControllerPuzzleLayer {
23    pub fn new(controller_puzzle_hash: Bytes32) -> Self {
24        Self {
25            controller_puzzle_hash,
26        }
27    }
28
29    /// Spend a coin locked by this layer with a `delegated_spend` previously
30    /// authorised by the controller coin.
31    pub fn spend(
32        &self,
33        ctx: &mut SpendContext,
34        delegated_spend: Spend,
35    ) -> Result<Spend, DriverError> {
36        self.construct_spend(
37            ctx,
38            P2ControllerPuzzleSolution {
39                delegated_puzzle: delegated_spend.puzzle,
40                delegated_solution: delegated_spend.solution,
41            },
42        )
43    }
44}
45
46impl Layer for P2ControllerPuzzleLayer {
47    type Solution = P2ControllerPuzzleSolution<NodePtr, NodePtr>;
48
49    fn parse_puzzle(allocator: &Allocator, puzzle: Puzzle) -> Result<Option<Self>, DriverError> {
50        let Some(puzzle) = puzzle.as_curried() else {
51            return Ok(None);
52        };
53
54        if puzzle.mod_hash != P2_CONTROLLER_PUZZLE_PUZZLE_HASH {
55            return Ok(None);
56        }
57
58        let args = P2ControllerPuzzleArgs::from_clvm(allocator, puzzle.args)?;
59
60        Ok(Some(Self {
61            controller_puzzle_hash: args.controller_puzzle_hash,
62        }))
63    }
64
65    fn parse_solution(
66        allocator: &Allocator,
67        solution: NodePtr,
68    ) -> Result<Self::Solution, DriverError> {
69        Ok(P2ControllerPuzzleSolution::from_clvm(allocator, solution)?)
70    }
71
72    fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
73        ctx.curry(P2ControllerPuzzleArgs {
74            controller_puzzle_hash: self.controller_puzzle_hash,
75        })
76    }
77
78    fn construct_solution(
79        &self,
80        ctx: &mut SpendContext,
81        solution: Self::Solution,
82    ) -> Result<NodePtr, DriverError> {
83        ctx.alloc(&solution)
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    use chia_protocol::Bytes;
92    use chia_sdk_test::Simulator;
93    use chia_sdk_types::{Conditions, conditions::SendMessage};
94    use clvm_traits::{ToClvm, clvm_quote};
95
96    /// A controller coin spend authorising a delegated puzzle for one
97    /// controlled coin succeeds, demonstrating the one-message-many-spends
98    /// workflow described in CHIP-0037.
99    #[test]
100    fn test_p2_controller_puzzle() -> anyhow::Result<()> {
101        let mut sim = Simulator::new();
102        let mut ctx = SpendContext::new();
103        let ctx = &mut ctx;
104
105        // The controller is a degenerate quoted-conditions coin so that we can
106        // emit any conditions we want to test the message-routing semantics.
107        let controller_puzzle = ctx.one();
108        let controller_puzzle_hash: Bytes32 = ctx.tree_hash(controller_puzzle).into();
109
110        let layer = P2ControllerPuzzleLayer::new(controller_puzzle_hash);
111        let coin_puzzle = layer.construct_puzzle(ctx)?;
112        let coin_puzzle_hash = ctx.tree_hash(coin_puzzle);
113
114        let controller_coin = sim.new_coin(controller_puzzle_hash, 42);
115        let coin = sim.new_coin(coin_puzzle_hash.into(), 69);
116
117        let delegated_puzzle =
118            clvm_quote!(Conditions::new().reserve_fee(42 + 69)).to_clvm(&mut **ctx)?;
119        let delegated_solution = ctx.nil();
120
121        let delegated_puzzle_hash: Bytes32 = ctx.tree_hash(delegated_puzzle).into();
122
123        let coin_spend = layer.construct_coin_spend(
124            ctx,
125            coin,
126            P2ControllerPuzzleSolution {
127                delegated_puzzle,
128                delegated_solution,
129            },
130        )?;
131        ctx.insert(coin_spend);
132
133        // mode 0b010111 == puzzle hash → coin
134        let coin_id_ptr = ctx.alloc(&coin.coin_id())?;
135        let controller_solution = vec![SendMessage::new(
136            23,
137            Bytes::new(delegated_puzzle_hash.to_vec()),
138            vec![coin_id_ptr],
139        )]
140        .to_clvm(&mut **ctx)?;
141
142        let controller_spend = chia_protocol::CoinSpend::new(
143            controller_coin,
144            chia_protocol::Program::from_clvm(&**ctx, controller_puzzle)?,
145            chia_protocol::Program::from_clvm(&**ctx, controller_solution)?,
146        );
147        ctx.insert(controller_spend);
148
149        sim.spend_coins(ctx.take(), &[])?;
150        Ok(())
151    }
152}