1use darkfi::{
20 tx::{ContractCallLeaf, Transaction, TransactionBuilder},
21 Result,
22};
23use darkfi_dao_contract::{
24 blockwindow,
25 client::{DaoProposeCall, DaoProposeStakeInput},
26 model::{Dao, DaoAuthCall, DaoProposal, DaoProposeParams},
27 DaoFunction, DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
28};
29use darkfi_money_contract::{
30 client::{MoneyNote, OwnCoin},
31 model::{CoinAttributes, MoneyFeeParamsV1},
32 MoneyFunction,
33};
34use darkfi_sdk::{
35 crypto::{
36 contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
37 Blind, MerkleNode, SecretKey,
38 },
39 pasta::pallas,
40 ContractCall,
41};
42use darkfi_serial::AsyncEncodable;
43use log::debug;
44use rand::rngs::OsRng;
45
46use super::{Holder, TestHarness};
47
48impl TestHarness {
49 #[allow(clippy::too_many_arguments)]
51 pub async fn dao_propose_transfer(
52 &mut self,
53 proposer: &Holder,
54 proposal_coinattrs: &[CoinAttributes],
55 user_data: pallas::Base,
56 dao: &Dao,
57 dao_proposer_secret_key: &SecretKey,
58 block_height: u32,
59 duration_blockwindows: u64,
60 ) -> Result<(Transaction, DaoProposeParams, Option<MoneyFeeParamsV1>, DaoProposal)> {
61 let wallet = self.holders.get(proposer).unwrap();
62
63 let (dao_propose_burn_pk, dao_propose_burn_zkbin) =
64 self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS).unwrap();
65
66 let (dao_propose_main_pk, dao_propose_main_zkbin) =
67 self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS).unwrap();
68
69 let propose_owncoin: OwnCoin = wallet
70 .unspent_money_coins
71 .iter()
72 .find(|x| x.note.token_id == dao.gov_token_id)
73 .unwrap()
74 .clone();
75
76 let input = DaoProposeStakeInput {
91 secret: wallet.keypair.secret,
92 note: propose_owncoin.note.clone(),
93 leaf_position: propose_owncoin.leaf_position,
94 merkle_path: wallet
95 .money_merkle_tree
96 .witness(propose_owncoin.leaf_position, 0)
97 .unwrap(),
98 };
99
100 let mut proposal_coins = vec![];
102 for coin_params in proposal_coinattrs {
103 proposal_coins.push(coin_params.to_coin());
104 }
105 let mut proposal_data = vec![];
106 proposal_coins.encode_async(&mut proposal_data).await?;
107
108 let auth_calls = vec![
110 DaoAuthCall {
111 contract_id: *DAO_CONTRACT_ID,
112 function_code: DaoFunction::AuthMoneyTransfer as u8,
113 auth_data: proposal_data,
114 },
115 DaoAuthCall {
116 contract_id: *MONEY_CONTRACT_ID,
117 function_code: MoneyFunction::TransferV1 as u8,
118 auth_data: vec![],
119 },
120 ];
121
122 let block_target = wallet.validator.consensus.module.read().await.target;
123 let creation_blockwindow = blockwindow(block_height, block_target);
124 let proposal = DaoProposal {
125 auth_calls,
126 creation_blockwindow,
127 duration_blockwindows,
128 user_data,
129 dao_bulla: dao.to_bulla(),
130 blind: Blind::random(&mut OsRng),
131 };
132
133 let signature_secret = SecretKey::random(&mut OsRng);
134 let dao_bulla = dao.to_bulla();
135
136 let call = DaoProposeCall {
137 money_null_smt: &wallet.money_null_smt,
138 inputs: vec![input],
139 proposal: proposal.clone(),
140 dao: dao.clone(),
141 dao_leaf_position: *wallet.dao_leafs.get(&dao_bulla).unwrap(),
142 dao_merkle_path: wallet
143 .dao_merkle_tree
144 .witness(*wallet.dao_leafs.get(&dao_bulla).unwrap(), 0)
145 .unwrap(),
146 dao_merkle_root: wallet.dao_merkle_tree.root(0).unwrap(),
147 signature_secret,
148 };
149
150 let (params, proofs) = call.make(
151 dao_proposer_secret_key,
152 dao_propose_burn_zkbin,
153 dao_propose_burn_pk,
154 dao_propose_main_zkbin,
155 dao_propose_main_pk,
156 )?;
157
158 let mut data = vec![DaoFunction::Propose as u8];
160 params.encode_async(&mut data).await?;
161 let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
162 let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
163
164 let mut fee_params = None;
166 let mut fee_signature_secrets = None;
167 if self.verify_fees {
168 let mut tx = tx_builder.build()?;
169 let sigs = tx.create_sigs(&[signature_secret])?;
170 tx.signatures = vec![sigs];
171
172 let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
173 self.append_fee_call(proposer, tx, block_height, &[]).await?;
174
175 tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
177 fee_signature_secrets = Some(fee_secrets);
178 fee_params = Some(fee_call_params);
179 }
180
181 let mut tx = tx_builder.build()?;
183 let sigs = tx.create_sigs(&[signature_secret])?;
184 tx.signatures = vec![sigs];
185 if let Some(fee_signature_secrets) = fee_signature_secrets {
186 let sigs = tx.create_sigs(&fee_signature_secrets)?;
187 tx.signatures.push(sigs);
188 }
189
190 Ok((tx, params, fee_params, proposal))
191 }
192
193 pub async fn dao_propose_generic(
195 &mut self,
196 proposer: &Holder,
197 user_data: pallas::Base,
198 dao: &Dao,
199 dao_proposer_secret_key: &SecretKey,
200 block_height: u32,
201 duration_blockwindows: u64,
202 ) -> Result<(Transaction, DaoProposeParams, Option<MoneyFeeParamsV1>, DaoProposal)> {
203 let wallet = self.holders.get(proposer).unwrap();
204
205 let (dao_propose_burn_pk, dao_propose_burn_zkbin) =
206 self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS).unwrap();
207
208 let (dao_propose_main_pk, dao_propose_main_zkbin) =
209 self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS).unwrap();
210
211 let propose_owncoin: OwnCoin = wallet
212 .unspent_money_coins
213 .iter()
214 .find(|x| x.note.token_id == dao.gov_token_id)
215 .unwrap()
216 .clone();
217
218 let input = DaoProposeStakeInput {
233 secret: wallet.keypair.secret,
234 note: propose_owncoin.note.clone(),
235 leaf_position: propose_owncoin.leaf_position,
236 merkle_path: wallet
237 .money_merkle_tree
238 .witness(propose_owncoin.leaf_position, 0)
239 .unwrap(),
240 };
241
242 let block_target = wallet.validator.consensus.module.read().await.target;
243 let creation_blockwindow = blockwindow(block_height, block_target);
244 let proposal = DaoProposal {
245 auth_calls: vec![],
246 creation_blockwindow,
247 duration_blockwindows,
248 user_data,
249 dao_bulla: dao.to_bulla(),
250 blind: Blind::random(&mut OsRng),
251 };
252
253 let signature_secret = SecretKey::random(&mut OsRng);
254 let dao_bulla = dao.to_bulla();
255
256 let call = DaoProposeCall {
257 money_null_smt: &wallet.money_null_smt,
258 inputs: vec![input],
259 proposal: proposal.clone(),
260 dao: dao.clone(),
261 dao_leaf_position: *wallet.dao_leafs.get(&dao_bulla).unwrap(),
262 dao_merkle_path: wallet
263 .dao_merkle_tree
264 .witness(*wallet.dao_leafs.get(&dao_bulla).unwrap(), 0)
265 .unwrap(),
266 dao_merkle_root: wallet.dao_merkle_tree.root(0).unwrap(),
267 signature_secret,
268 };
269
270 let (params, proofs) = call.make(
271 dao_proposer_secret_key,
272 dao_propose_burn_zkbin,
273 dao_propose_burn_pk,
274 dao_propose_main_zkbin,
275 dao_propose_main_pk,
276 )?;
277
278 let mut data = vec![DaoFunction::Propose as u8];
280 params.encode_async(&mut data).await?;
281 let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
282 let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
283
284 let mut fee_params = None;
286 let mut fee_signature_secrets = None;
287 if self.verify_fees {
288 let mut tx = tx_builder.build()?;
289 let sigs = tx.create_sigs(&[signature_secret])?;
290 tx.signatures = vec![sigs];
291
292 let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
293 self.append_fee_call(proposer, tx, block_height, &[]).await?;
294
295 tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
297 fee_signature_secrets = Some(fee_secrets);
298 fee_params = Some(fee_call_params);
299 }
300
301 let mut tx = tx_builder.build()?;
303 let sigs = tx.create_sigs(&[signature_secret])?;
304 tx.signatures = vec![sigs];
305 if let Some(fee_signature_secrets) = fee_signature_secrets {
306 let sigs = tx.create_sigs(&fee_signature_secrets)?;
307 tx.signatures.push(sigs);
308 }
309
310 Ok((tx, params, fee_params, proposal))
311 }
312
313 pub async fn execute_dao_propose_tx(
317 &mut self,
318 holder: &Holder,
319 tx: Transaction,
320 params: &DaoProposeParams,
321 fee_params: &Option<MoneyFeeParamsV1>,
322 block_height: u32,
323 append: bool,
324 ) -> Result<Vec<OwnCoin>> {
325 let wallet = self.holders.get_mut(holder).unwrap();
326
327 wallet.add_transaction("dao::propose", tx, block_height).await?;
329
330 wallet.money_null_smt_snapshot = Some(wallet.money_null_smt.clone());
331
332 if !append {
333 return Ok(vec![])
334 }
335
336 wallet.dao_proposals_tree.append(MerkleNode::from(params.proposal_bulla.inner()));
337 let prop_leaf_pos = wallet.dao_proposals_tree.mark().unwrap();
338 let prop_money_snapshot = wallet.money_merkle_tree.clone();
339 wallet.dao_prop_leafs.insert(params.proposal_bulla, (prop_leaf_pos, prop_money_snapshot));
340
341 if let Some(ref fee_params) = fee_params {
342 let nullifier = fee_params.input.nullifier.inner();
343 wallet
344 .money_null_smt
345 .insert_batch(vec![(nullifier, nullifier)])
346 .expect("smt.insert_batch()");
347
348 if let Some(spent_coin) = wallet
349 .unspent_money_coins
350 .iter()
351 .find(|x| x.nullifier() == fee_params.input.nullifier)
352 .cloned()
353 {
354 debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder);
355 wallet.unspent_money_coins.retain(|x| x.nullifier() != fee_params.input.nullifier);
356 wallet.spent_money_coins.push(spent_coin.clone());
357 }
358
359 wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner()));
360
361 let Ok(note) = fee_params.output.note.decrypt::<MoneyNote>(&wallet.keypair.secret)
362 else {
363 return Ok(vec![])
364 };
365
366 let owncoin = OwnCoin {
367 coin: fee_params.output.coin,
368 note: note.clone(),
369 secret: wallet.keypair.secret,
370 leaf_position: wallet.money_merkle_tree.mark().unwrap(),
371 };
372
373 debug!("Found new OwnCoin({}) for {:?}:", owncoin.coin, holder);
374 wallet.unspent_money_coins.push(owncoin.clone());
375 return Ok(vec![owncoin])
376 }
377
378 Ok(vec![])
379 }
380}