chia_sdk_driver/layers/
standard_layer.rs

1use chia_bls::PublicKey;
2use chia_protocol::Coin;
3use chia_puzzle_types::standard::{StandardArgs, StandardSolution};
4use chia_puzzles::P2_DELEGATED_PUZZLE_OR_HIDDEN_PUZZLE_HASH;
5use chia_sdk_types::Conditions;
6use clvm_traits::{clvm_quote, FromClvm};
7use clvm_utils::{ToTreeHash, TreeHash};
8use clvmr::{Allocator, NodePtr};
9
10use crate::{DriverError, Layer, Puzzle, Spend, SpendContext, SpendWithConditions};
11
12/// This is the actual puzzle name for the [`StandardLayer`].
13pub type P2DelegatedOrHiddenLayer = StandardLayer;
14
15/// The standard [`Layer`] is used for most coins on the Chia blockchain. It allows a single key
16/// to spend the coin by providing a delegated puzzle (for example to output [`Conditions`]).
17///
18/// There is also additional hidden puzzle functionality which can be encoded in the key.
19/// To do this, you calculate a "synthetic key" from the original key and the hidden puzzle hash.
20/// When spending the coin, you can reveal this hidden puzzle and provide the original key.
21/// This functionality is seldom used in Chia, and usually the "default hidden puzzle" is used instead.
22/// The default hidden puzzle is not spendable, so you can only spend XCH coins by signing with your key.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct StandardLayer {
25    pub synthetic_key: PublicKey,
26}
27
28impl StandardLayer {
29    pub fn new(synthetic_key: PublicKey) -> Self {
30        Self { synthetic_key }
31    }
32
33    pub fn spend(
34        &self,
35        ctx: &mut SpendContext,
36        coin: Coin,
37        conditions: Conditions,
38    ) -> Result<(), DriverError> {
39        let spend = self.spend_with_conditions(ctx, conditions)?;
40        ctx.spend(coin, spend)
41    }
42
43    pub fn delegated_inner_spend(
44        &self,
45        ctx: &mut SpendContext,
46        spend: Spend,
47    ) -> Result<Spend, DriverError> {
48        self.construct_spend(
49            ctx,
50            StandardSolution {
51                original_public_key: None,
52                delegated_puzzle: spend.puzzle,
53                solution: spend.solution,
54            },
55        )
56    }
57}
58
59impl Layer for StandardLayer {
60    type Solution = StandardSolution<NodePtr, NodePtr>;
61
62    fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
63        ctx.curry(StandardArgs::new(self.synthetic_key))
64    }
65
66    fn construct_solution(
67        &self,
68        ctx: &mut SpendContext,
69        solution: Self::Solution,
70    ) -> Result<NodePtr, DriverError> {
71        ctx.alloc(&solution)
72    }
73
74    fn parse_puzzle(allocator: &Allocator, puzzle: Puzzle) -> Result<Option<Self>, DriverError> {
75        let Some(puzzle) = puzzle.as_curried() else {
76            return Ok(None);
77        };
78
79        if puzzle.mod_hash != P2_DELEGATED_PUZZLE_OR_HIDDEN_PUZZLE_HASH.into() {
80            return Ok(None);
81        }
82
83        let args = StandardArgs::from_clvm(allocator, puzzle.args)?;
84
85        Ok(Some(Self {
86            synthetic_key: args.synthetic_key,
87        }))
88    }
89
90    fn parse_solution(
91        allocator: &Allocator,
92        solution: NodePtr,
93    ) -> Result<Self::Solution, DriverError> {
94        Ok(StandardSolution::from_clvm(allocator, solution)?)
95    }
96}
97
98impl SpendWithConditions for StandardLayer {
99    fn spend_with_conditions(
100        &self,
101        ctx: &mut SpendContext,
102        conditions: Conditions,
103    ) -> Result<Spend, DriverError> {
104        let delegated_puzzle = ctx.alloc(&clvm_quote!(conditions))?;
105        self.construct_spend(
106            ctx,
107            StandardSolution {
108                original_public_key: None,
109                delegated_puzzle,
110                solution: NodePtr::NIL,
111            },
112        )
113    }
114}
115
116impl ToTreeHash for StandardLayer {
117    fn tree_hash(&self) -> TreeHash {
118        StandardArgs::curry_tree_hash(self.synthetic_key)
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use chia_puzzle_types::Memos;
125    use chia_sdk_test::Simulator;
126
127    use super::*;
128
129    #[test]
130    fn test_flash_loan() -> anyhow::Result<()> {
131        let mut sim = Simulator::new();
132        let ctx = &mut SpendContext::new();
133        let alice = sim.bls(1);
134        let p2 = StandardLayer::new(alice.pk);
135
136        p2.spend(
137            ctx,
138            alice.coin,
139            Conditions::new().create_coin(alice.puzzle_hash, u64::MAX, Memos::None),
140        )?;
141
142        p2.spend(
143            ctx,
144            Coin::new(alice.coin.coin_id(), alice.puzzle_hash, u64::MAX),
145            Conditions::new().create_coin(alice.puzzle_hash, 1, Memos::None),
146        )?;
147
148        sim.spend_coins(ctx.take(), &[alice.sk])?;
149
150        Ok(())
151    }
152}