1use darkfi::{
20 tx::{ContractCallLeaf, Transaction, TransactionBuilder},
21 Result,
22};
23use darkfi_dao_contract::{
24 blockwindow,
25 client::{DaoAuthMoneyTransferCall, DaoExecCall},
26 model::{Dao, DaoProposal},
27 DaoFunction, DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS,
28 DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS, DAO_CONTRACT_ZKAS_DAO_EARLY_EXEC_NS,
29 DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
30};
31use darkfi_money_contract::{
32 client::{transfer_v1 as xfer, MoneyNote, OwnCoin},
33 model::{CoinAttributes, MoneyFeeParamsV1, MoneyTransferParamsV1},
34 MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
35};
36use darkfi_sdk::{
37 crypto::{
38 contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
39 pedersen_commitment_u64, Blind, FuncRef, MerkleNode, ScalarBlind, SecretKey,
40 },
41 dark_tree::DarkTree,
42 ContractCall,
43};
44use darkfi_serial::AsyncEncodable;
45use log::debug;
46use rand::rngs::OsRng;
47
48use super::{Holder, TestHarness};
49
50impl TestHarness {
51 #[allow(clippy::too_many_arguments)]
53 pub async fn dao_exec_transfer(
54 &mut self,
55 holder: &Holder,
56 dao: &Dao,
57 dao_exec_secret_key: &SecretKey,
58 dao_early_exec_secret_key: &Option<SecretKey>,
59 proposal: &DaoProposal,
60 proposal_coinattrs: Vec<CoinAttributes>,
61 yes_vote_value: u64,
62 all_vote_value: u64,
63 yes_vote_blind: ScalarBlind,
64 all_vote_blind: ScalarBlind,
65 block_height: u32,
66 ) -> Result<(Transaction, MoneyTransferParamsV1, Option<MoneyFeeParamsV1>)> {
67 let dao_wallet = self.holders.get(&Holder::Dao).unwrap();
68
69 let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
70 let (burn_pk, burn_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
71
72 let (dao_exec_pk, dao_exec_zkbin) = match dao_early_exec_secret_key {
73 Some(_) => self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_EARLY_EXEC_NS).unwrap(),
74 None => self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_EXEC_NS).unwrap(),
75 };
76 let (dao_auth_xfer_pk, dao_auth_xfer_zkbin) =
77 self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS).unwrap();
78 let (dao_auth_xfer_enc_coin_pk, dao_auth_xfer_enc_coin_zkbin) =
79 self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS).unwrap();
80
81 let input_user_data_blind = Blind::random(&mut OsRng);
82 let exec_signature_secret = SecretKey::random(&mut OsRng);
83
84 assert!(!proposal_coinattrs.is_empty());
85 let proposal_token_id = proposal_coinattrs[0].token_id;
86 assert!(proposal_coinattrs.iter().all(|c| c.token_id == proposal_token_id));
87 let proposal_amount = proposal_coinattrs.iter().map(|c| c.value).sum();
88
89 let dao_coins = dao_wallet
90 .unspent_money_coins
91 .iter()
92 .filter(|x| x.note.token_id == proposal_token_id)
93 .cloned()
94 .collect();
95 let (spent_coins, change_value) = xfer::select_coins(dao_coins, proposal_amount)?;
96 let tree = dao_wallet.money_merkle_tree.clone();
97
98 let mut inputs = vec![];
99 for coin in &spent_coins {
100 inputs.push(xfer::TransferCallInput {
101 coin: coin.clone(),
102 merkle_path: tree.witness(coin.leaf_position, 0).unwrap(),
103 user_data_blind: input_user_data_blind,
104 });
105 }
106
107 let mut outputs = vec![];
108 for coin_attr in proposal_coinattrs.clone() {
109 assert_eq!(proposal_token_id, coin_attr.token_id);
110 outputs.push(coin_attr);
111 }
112
113 let spend_hook =
114 FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }
115 .to_func_id();
116
117 let dao_coin_attrs = CoinAttributes {
118 public_key: dao_wallet.keypair.public,
119 value: change_value,
120 token_id: proposal_token_id,
121 spend_hook,
122 user_data: dao.to_bulla().inner(),
123 blind: Blind::random(&mut OsRng),
124 };
125 outputs.push(dao_coin_attrs.clone());
126
127 let xfer_builder = xfer::TransferCallBuilder {
128 clear_inputs: vec![],
129 inputs,
130 outputs,
131 mint_zkbin: mint_zkbin.clone(),
132 mint_pk: mint_pk.clone(),
133 burn_zkbin: burn_zkbin.clone(),
134 burn_pk: burn_pk.clone(),
135 };
136
137 let (xfer_params, xfer_secrets) = xfer_builder.build()?;
138 let mut data = vec![MoneyFunction::TransferV1 as u8];
139 xfer_params.encode_async(&mut data).await?;
140 let xfer_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
141
142 let mut input_value = 0;
145 let mut input_value_blind = Blind::ZERO;
146 for (input, blind) in spent_coins.iter().zip(xfer_secrets.input_value_blinds.iter()) {
147 input_value += input.note.value;
148 input_value_blind += *blind;
149 }
150 assert_eq!(
151 pedersen_commitment_u64(input_value, input_value_blind),
152 xfer_params.inputs.iter().map(|input| input.value_commit).sum()
153 );
154
155 let block_target = dao_wallet.validator.consensus.module.read().await.target;
156 let current_blockwindow = blockwindow(block_height, block_target);
157 let exec_builder = DaoExecCall {
158 proposal: proposal.clone(),
159 dao: dao.clone(),
160 yes_vote_value,
161 all_vote_value,
162 yes_vote_blind,
163 all_vote_blind,
164 signature_secret: exec_signature_secret,
165 current_blockwindow,
166 };
167
168 let (exec_params, exec_proofs) = exec_builder.make(
169 dao_exec_secret_key,
170 dao_early_exec_secret_key,
171 dao_exec_zkbin,
172 dao_exec_pk,
173 )?;
174 let mut data = vec![DaoFunction::Exec as u8];
175 exec_params.encode_async(&mut data).await?;
176 let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
177
178 let auth_xfer_builder = DaoAuthMoneyTransferCall {
180 proposal: proposal.clone(),
181 proposal_coinattrs,
182 dao: dao.clone(),
183 input_user_data_blind,
184 dao_coin_attrs,
185 };
186 let (auth_xfer_params, auth_xfer_proofs) = auth_xfer_builder.make(
187 dao_auth_xfer_zkbin,
188 dao_auth_xfer_pk,
189 dao_auth_xfer_enc_coin_zkbin,
190 dao_auth_xfer_enc_coin_pk,
191 )?;
192 let mut data = vec![DaoFunction::AuthMoneyTransfer as u8];
193 auth_xfer_params.encode_async(&mut data).await?;
194 let auth_xfer_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
195
196 let mut tx_builder = TransactionBuilder::new(
204 ContractCallLeaf { call: exec_call, proofs: exec_proofs },
205 vec![
206 DarkTree::new(
207 ContractCallLeaf { call: auth_xfer_call, proofs: auth_xfer_proofs },
208 vec![],
209 None,
210 None,
211 ),
212 DarkTree::new(
213 ContractCallLeaf { call: xfer_call, proofs: xfer_secrets.proofs },
214 vec![],
215 None,
216 None,
217 ),
218 ],
219 )?;
220
221 let mut fee_params = None;
223 let mut fee_signature_secrets = None;
224 if self.verify_fees {
225 let mut tx = tx_builder.build()?;
226 let auth_xfer_sigs = vec![];
227 let xfer_sigs = tx.create_sigs(&xfer_secrets.signature_secrets)?;
228 let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
229 tx.signatures = vec![auth_xfer_sigs, xfer_sigs, exec_sigs];
230
231 let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
232 self.append_fee_call(holder, tx, block_height, &[]).await?;
233
234 tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
236 fee_signature_secrets = Some(fee_secrets);
237 fee_params = Some(fee_call_params);
238 }
239
240 let mut tx = tx_builder.build()?;
242 let auth_xfer_sigs = vec![];
243 let xfer_sigs = tx.create_sigs(&xfer_secrets.signature_secrets)?;
244 let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
245 tx.signatures = vec![auth_xfer_sigs, xfer_sigs, exec_sigs];
246
247 if let Some(fee_signature_secrets) = fee_signature_secrets {
248 let sigs = tx.create_sigs(&fee_signature_secrets)?;
249 tx.signatures.push(sigs);
250 }
251
252 Ok((tx, xfer_params, fee_params))
253 }
254
255 #[allow(clippy::too_many_arguments)]
257 pub async fn dao_exec_generic(
258 &mut self,
259 holder: &Holder,
260 dao: &Dao,
261 dao_exec_secret_key: &SecretKey,
262 dao_early_exec_secret_key: &Option<SecretKey>,
263 proposal: &DaoProposal,
264 yes_vote_value: u64,
265 all_vote_value: u64,
266 yes_vote_blind: ScalarBlind,
267 all_vote_blind: ScalarBlind,
268 block_height: u32,
269 ) -> Result<(Transaction, Option<MoneyFeeParamsV1>)> {
270 let wallet = self.holders.get_mut(holder).unwrap();
271
272 let (dao_exec_pk, dao_exec_zkbin) = match dao_early_exec_secret_key {
273 Some(_) => self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_EARLY_EXEC_NS).unwrap(),
274 None => self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_EXEC_NS).unwrap(),
275 };
276
277 let exec_signature_secret = SecretKey::random(&mut OsRng);
279 let block_target = wallet.validator.consensus.module.read().await.target;
280 let current_blockwindow = blockwindow(block_height, block_target);
281 let exec_builder = DaoExecCall {
282 proposal: proposal.clone(),
283 dao: dao.clone(),
284 yes_vote_value,
285 all_vote_value,
286 yes_vote_blind,
287 all_vote_blind,
288 signature_secret: exec_signature_secret,
289 current_blockwindow,
290 };
291 let (exec_params, exec_proofs) = exec_builder.make(
292 dao_exec_secret_key,
293 dao_early_exec_secret_key,
294 dao_exec_zkbin,
295 dao_exec_pk,
296 )?;
297
298 let mut data = vec![DaoFunction::Exec as u8];
300 exec_params.encode_async(&mut data).await?;
301 let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
302
303 let mut tx_builder = TransactionBuilder::new(
305 ContractCallLeaf { call: exec_call, proofs: exec_proofs },
306 vec![],
307 )?;
308
309 let mut fee_params = None;
311 let mut fee_signature_secrets = None;
312 if self.verify_fees {
313 let mut tx = tx_builder.build()?;
314 let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
315 tx.signatures = vec![exec_sigs];
316
317 let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
318 self.append_fee_call(holder, tx, block_height, &[]).await?;
319
320 tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
322 fee_signature_secrets = Some(fee_secrets);
323 fee_params = Some(fee_call_params);
324 }
325
326 let mut tx = tx_builder.build()?;
328 let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
329 tx.signatures = vec![exec_sigs];
330
331 if let Some(fee_signature_secrets) = fee_signature_secrets {
332 let sigs = tx.create_sigs(&fee_signature_secrets)?;
333 tx.signatures.push(sigs);
334 }
335
336 Ok((tx, fee_params))
337 }
338
339 pub async fn execute_dao_exec_tx(
343 &mut self,
344 holder: &Holder,
345 tx: Transaction,
346 xfer_params: Option<&MoneyTransferParamsV1>,
347 fee_params: &Option<MoneyFeeParamsV1>,
348 block_height: u32,
349 append: bool,
350 ) -> Result<Vec<OwnCoin>> {
351 let wallet = self.holders.get_mut(holder).unwrap();
352
353 wallet.add_transaction("dao::exec", tx, block_height).await?;
355
356 if !append {
357 return Ok(vec![])
358 }
359
360 let (mut inputs, mut outputs) = match xfer_params {
361 Some(params) => (params.inputs.to_vec(), params.outputs.to_vec()),
362 None => (vec![], vec![]),
363 };
364
365 if let Some(ref fee_params) = fee_params {
366 inputs.push(fee_params.input.clone());
367 outputs.push(fee_params.output.clone());
368 }
369
370 let nullifiers = inputs.iter().map(|i| i.nullifier.inner()).map(|l| (l, l)).collect();
371 wallet.money_null_smt.insert_batch(nullifiers).expect("smt.insert_batch()");
372
373 for input in inputs {
374 if let Some(spent_coin) = wallet
375 .unspent_money_coins
376 .iter()
377 .find(|x| x.nullifier() == input.nullifier)
378 .cloned()
379 {
380 debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder);
381 wallet.unspent_money_coins.retain(|x| x.nullifier() != input.nullifier);
382 wallet.spent_money_coins.push(spent_coin.clone());
383 }
384 }
385
386 let mut found_owncoins = vec![];
387 for output in outputs {
388 wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner()));
389
390 let Ok(note) = output.note.decrypt::<MoneyNote>(&wallet.keypair.secret) else {
391 continue
392 };
393
394 let owncoin = OwnCoin {
395 coin: output.coin,
396 note: note.clone(),
397 secret: wallet.keypair.secret,
398 leaf_position: wallet.money_merkle_tree.mark().unwrap(),
399 };
400
401 debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder);
402 wallet.unspent_money_coins.push(owncoin.clone());
403 found_owncoins.push(owncoin);
404 }
405
406 Ok(found_owncoins)
407 }
408}