chia_sdk_driver/primitives/
vault.rs1mod 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}