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