chia_sdk_driver/primitives/
vault.rs

1mod vault_launcher;
2
3use chia_protocol::{Bytes32, Coin};
4use chia_puzzle_types::{
5    singleton::{SingletonArgs, SingletonSolution},
6    LineageProof, Proof,
7};
8use clvm_utils::TreeHash;
9
10use crate::{DriverError, Spend, SpendContext};
11
12use super::{member_puzzle_hash, MipsSpend, Restriction};
13
14#[derive(Debug, Clone, Copy)]
15pub struct Vault {
16    pub coin: Coin,
17    pub launcher_id: Bytes32,
18    pub proof: Proof,
19    pub custody_hash: TreeHash,
20}
21
22impl Vault {
23    pub fn new(coin: Coin, launcher_id: Bytes32, proof: Proof, custody_hash: TreeHash) -> Self {
24        Self {
25            coin,
26            launcher_id,
27            proof,
28            custody_hash,
29        }
30    }
31
32    pub fn custody_hash(
33        nonce: usize,
34        restrictions: Vec<Restriction>,
35        inner_puzzle_hash: TreeHash,
36    ) -> TreeHash {
37        member_puzzle_hash(nonce, restrictions, inner_puzzle_hash, true)
38    }
39
40    pub fn child_lineage_proof(&self) -> LineageProof {
41        LineageProof {
42            parent_parent_coin_info: self.coin.parent_coin_info,
43            parent_inner_puzzle_hash: self.custody_hash.into(),
44            parent_amount: self.coin.amount,
45        }
46    }
47
48    #[must_use]
49    pub fn child(&self, custody_hash: TreeHash) -> Self {
50        Self {
51            coin: Coin::new(
52                self.coin.coin_id(),
53                SingletonArgs::curry_tree_hash(self.launcher_id, custody_hash).into(),
54                self.coin.amount,
55            ),
56            launcher_id: self.launcher_id,
57            proof: Proof::Lineage(self.child_lineage_proof()),
58            custody_hash,
59        }
60    }
61
62    pub fn spend(&self, ctx: &mut SpendContext, spend: &MipsSpend) -> Result<(), DriverError> {
63        let custody_spend = spend.spend(ctx, self.custody_hash)?;
64
65        let puzzle = ctx.curry(SingletonArgs::new(self.launcher_id, custody_spend.puzzle))?;
66        let solution = ctx.alloc(&SingletonSolution {
67            lineage_proof: self.proof,
68            amount: self.coin.amount,
69            inner_solution: custody_spend.solution,
70        })?;
71
72        ctx.spend(self.coin, Spend::new(puzzle, solution))?;
73
74        Ok(())
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use chia_puzzle_types::Memos;
81    use chia_sdk_test::{K1Pair, Simulator};
82    use chia_sdk_types::{
83        puzzles::{K1Member, K1MemberSolution},
84        Conditions, Mod,
85    };
86    use chia_secp::{K1SecretKey, K1Signature};
87    use chia_sha2::Sha256;
88    use rstest::rstest;
89
90    use crate::{InnerPuzzleSpend, Launcher, MofN, StandardLayer};
91
92    use super::*;
93
94    fn mint_vault(
95        sim: &mut Simulator,
96        ctx: &mut SpendContext,
97        custody_hash: TreeHash,
98    ) -> anyhow::Result<Vault> {
99        let alice = sim.bls(1);
100        let alice_p2 = StandardLayer::new(alice.pk);
101
102        let (mint_vault, vault) =
103            Launcher::new(alice.coin.coin_id(), 1).mint_vault(ctx, custody_hash, ())?;
104        alice_p2.spend(ctx, alice.coin, mint_vault)?;
105
106        sim.spend_coins(ctx.take(), &[alice.sk])?;
107
108        Ok(vault)
109    }
110
111    fn k1_sign(
112        ctx: &SpendContext,
113        vault: &Vault,
114        spend: &MipsSpend,
115        k1: &K1SecretKey,
116    ) -> anyhow::Result<K1Signature> {
117        let mut hasher = Sha256::new();
118        hasher.update(ctx.tree_hash(spend.delegated.puzzle));
119        hasher.update(vault.coin.coin_id());
120        Ok(k1.sign_prehashed(&hasher.finalize())?)
121    }
122
123    #[test]
124    fn test_simple_vault() -> anyhow::Result<()> {
125        let mut sim = Simulator::new();
126        let ctx = &mut SpendContext::new();
127
128        let k1 = K1Pair::default();
129        let custody = K1Member::new(k1.pk);
130        let custody_hash = Vault::custody_hash(0, Vec::new(), custody.curry_tree_hash());
131
132        let vault = mint_vault(&mut sim, ctx, custody_hash)?;
133
134        let conditions = Conditions::new().create_coin(vault.custody_hash.into(), 1, Memos::None);
135        let mut spend = MipsSpend::new(ctx.delegated_spend(conditions)?);
136
137        let signature = k1_sign(ctx, &vault, &spend, &k1.sk)?;
138        let k1_puzzle = ctx.curry(custody)?;
139        let k1_solution = ctx.alloc(&K1MemberSolution::new(vault.coin.coin_id(), signature))?;
140
141        spend.members.insert(
142            custody_hash,
143            InnerPuzzleSpend::new(0, Vec::new(), Spend::new(k1_puzzle, k1_solution)),
144        );
145
146        vault.spend(ctx, &spend)?;
147
148        sim.spend_coins(ctx.take(), &[])?;
149
150        Ok(())
151    }
152
153    #[rstest]
154    #[case::vault_1_of_1(1, 1)]
155    #[case::vault_1_of_2(1, 2)]
156    #[case::vault_1_of_3(1, 3)]
157    #[case::vault_1_of_4(1, 4)]
158    #[case::vault_2_of_2(2, 2)]
159    #[case::vault_2_of_3(2, 3)]
160    #[case::vault_2_of_4(2, 4)]
161    #[case::vault_3_of_3(3, 3)]
162    #[case::vault_3_of_4(3, 4)]
163    #[case::vault_4_of_4(4, 4)]
164    fn test_m_of_n_vault(#[case] required: usize, #[case] key_count: usize) -> anyhow::Result<()> {
165        let mut sim = Simulator::new();
166        let ctx = &mut SpendContext::new();
167
168        let keys = K1Pair::range_vec(key_count);
169
170        let members = keys.iter().map(|k| K1Member::new(k.pk)).collect::<Vec<_>>();
171
172        let hashes = members
173            .iter()
174            .map(|m| member_puzzle_hash(0, Vec::new(), m.curry_tree_hash(), false))
175            .collect::<Vec<_>>();
176
177        let custody = MofN::new(required, hashes.clone());
178        let custody_hash = Vault::custody_hash(0, Vec::new(), custody.inner_puzzle_hash());
179
180        let mut vault = mint_vault(&mut sim, ctx, custody_hash)?;
181
182        for start in 0..key_count {
183            let conditions =
184                Conditions::new().create_coin(vault.custody_hash.into(), 1, Memos::None);
185            let mut spend = MipsSpend::new(ctx.delegated_spend(conditions)?);
186
187            spend.members.insert(
188                custody_hash,
189                InnerPuzzleSpend::m_of_n(0, Vec::new(), custody.required, custody.items.clone()),
190            );
191
192            let mut i = start;
193
194            for _ in 0..required {
195                let signature = k1_sign(ctx, &vault, &spend, &keys[i].sk)?;
196
197                let k1_puzzle = ctx.curry(members[i])?;
198                let k1_solution =
199                    ctx.alloc(&K1MemberSolution::new(vault.coin.coin_id(), signature))?;
200
201                spend.members.insert(
202                    hashes[i],
203                    InnerPuzzleSpend::new(0, Vec::new(), Spend::new(k1_puzzle, k1_solution)),
204                );
205
206                i += 1;
207
208                if i >= key_count {
209                    i = 0;
210                }
211            }
212
213            vault.spend(ctx, &spend)?;
214            vault = vault.child(vault.custody_hash);
215
216            sim.spend_coins(ctx.take(), &[])?;
217        }
218
219        Ok(())
220    }
221}