chia_sdk_driver/layers/
standard_layer.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
use chia_bls::PublicKey;
use chia_protocol::Coin;
use chia_puzzles::standard::{StandardArgs, StandardSolution, STANDARD_PUZZLE_HASH};
use chia_sdk_types::Conditions;
use clvm_traits::{clvm_quote, FromClvm};
use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash};
use clvmr::{Allocator, NodePtr};

use crate::{DriverError, Layer, Puzzle, Spend, SpendContext, SpendWithConditions};

/// This is the actual puzzle name for the [`StandardLayer`].
pub type P2DelegatedOrHiddenLayer = StandardLayer;

/// The standard [`Layer`] is used for most coins on the Chia blockchain. It allows a single key
/// to spend the coin by providing a delegated puzzle (for example to output [`Conditions`]).
///
/// There is also additional hidden puzzle functionality which can be encoded in the key.
/// To do this, you calculate a "synthetic key" from the original key and the hidden puzzle hash.
/// When spending the coin, you can reveal this hidden puzzle and provide the original key.
/// This functionality is seldom used in Chia, and usually the "default hidden puzzle" is used instead.
/// The default hidden puzzle is not spendable, so you can only spend XCH coins by signing with your key.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StandardLayer {
    pub synthetic_key: PublicKey,
}

impl StandardLayer {
    pub fn new(synthetic_key: PublicKey) -> Self {
        Self { synthetic_key }
    }

    pub fn spend(
        &self,
        ctx: &mut SpendContext,
        coin: Coin,
        conditions: Conditions,
    ) -> Result<(), DriverError> {
        let spend = self.spend_with_conditions(ctx, conditions)?;
        ctx.spend(coin, spend)
    }

    pub fn delegated_inner_spend(
        &self,
        ctx: &mut SpendContext,
        spend: Spend,
    ) -> Result<Spend, DriverError> {
        self.construct_spend(
            ctx,
            StandardSolution {
                original_public_key: None,
                delegated_puzzle: spend.puzzle,
                solution: spend.solution,
            },
        )
    }
}

impl Layer for StandardLayer {
    type Solution = StandardSolution<NodePtr, NodePtr>;

    fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
        let curried = CurriedProgram {
            program: ctx.standard_puzzle()?,
            args: StandardArgs::new(self.synthetic_key),
        };
        ctx.alloc(&curried)
    }

    fn construct_solution(
        &self,
        ctx: &mut SpendContext,
        solution: Self::Solution,
    ) -> Result<NodePtr, DriverError> {
        ctx.alloc(&solution)
    }

    fn parse_puzzle(allocator: &Allocator, puzzle: Puzzle) -> Result<Option<Self>, DriverError> {
        let Some(puzzle) = puzzle.as_curried() else {
            return Ok(None);
        };

        if puzzle.mod_hash != STANDARD_PUZZLE_HASH {
            return Ok(None);
        }

        let args = StandardArgs::from_clvm(allocator, puzzle.args)?;

        Ok(Some(Self {
            synthetic_key: args.synthetic_key,
        }))
    }

    fn parse_solution(
        allocator: &Allocator,
        solution: NodePtr,
    ) -> Result<Self::Solution, DriverError> {
        Ok(StandardSolution::from_clvm(allocator, solution)?)
    }
}

impl SpendWithConditions for StandardLayer {
    fn spend_with_conditions(
        &self,
        ctx: &mut SpendContext,
        conditions: Conditions,
    ) -> Result<Spend, DriverError> {
        let delegated_puzzle = ctx.alloc(&clvm_quote!(conditions))?;
        self.construct_spend(
            ctx,
            StandardSolution {
                original_public_key: None,
                delegated_puzzle,
                solution: NodePtr::NIL,
            },
        )
    }
}

impl ToTreeHash for StandardLayer {
    fn tree_hash(&self) -> TreeHash {
        StandardArgs::curry_tree_hash(self.synthetic_key)
    }
}

#[cfg(test)]
mod tests {
    use chia_sdk_test::Simulator;

    use super::*;

    #[test]
    fn test_flash_loan() -> anyhow::Result<()> {
        let mut sim = Simulator::new();
        let ctx = &mut SpendContext::new();
        let (sk, pk, puzzle_hash, coin) = sim.new_p2(1)?;
        let p2 = StandardLayer::new(pk);

        p2.spend(
            ctx,
            coin,
            Conditions::new().create_coin(puzzle_hash, u64::MAX, Vec::new()),
        )?;

        p2.spend(
            ctx,
            Coin::new(coin.coin_id(), puzzle_hash, u64::MAX),
            Conditions::new().create_coin(puzzle_hash, 1, Vec::new()),
        )?;

        sim.spend_coins(ctx.take(), &[sk])?;

        Ok(())
    }
}