chia_sdk_driver/primitives/
vault.rs

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