dao_exec.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use darkfi::{
  19. tx::{ContractCallLeaf, Transaction, TransactionBuilder},
  20. Result,
  21. };
  22. use darkfi_dao_contract::{
  23. client::{DaoAuthMoneyTransferCall, DaoExecCall},
  24. model::{Dao, DaoProposal},
  25. DaoFunction, DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS,
  26. DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS, DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
  27. };
  28. use darkfi_money_contract::{
  29. client::{transfer_v1 as xfer, MoneyNote, OwnCoin},
  30. model::{CoinAttributes, MoneyFeeParamsV1, MoneyTransferParamsV1},
  31. MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
  32. };
  33. use darkfi_sdk::{
  34. crypto::{
  35. contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
  36. pedersen_commitment_u64, Blind, FuncRef, MerkleNode, ScalarBlind, SecretKey,
  37. },
  38. dark_tree::DarkTree,
  39. ContractCall,
  40. };
  41. use darkfi_serial::AsyncEncodable;
  42. use log::debug;
  43. use rand::rngs::OsRng;
  44. use super::{Holder, TestHarness};
  45. impl TestHarness {
  46. /// Create a transfer `Dao::Exec` transaction.
  47. #[allow(clippy::too_many_arguments)]
  48. pub async fn dao_exec_transfer(
  49. &mut self,
  50. holder: &Holder,
  51. dao: &Dao,
  52. proposal: &DaoProposal,
  53. proposal_coinattrs: Vec<CoinAttributes>,
  54. yes_vote_value: u64,
  55. all_vote_value: u64,
  56. yes_vote_blind: ScalarBlind,
  57. all_vote_blind: ScalarBlind,
  58. block_height: u32,
  59. ) -> Result<(Transaction, MoneyTransferParamsV1, Option<MoneyFeeParamsV1>)> {
  60. let dao_wallet = self.holders.get(&Holder::Dao).unwrap();
  61. let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
  62. let (burn_pk, burn_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
  63. let (dao_exec_pk, dao_exec_zkbin) =
  64. self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_EXEC_NS).unwrap();
  65. let (dao_auth_xfer_pk, dao_auth_xfer_zkbin) =
  66. self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS).unwrap();
  67. let (dao_auth_xfer_enc_coin_pk, dao_auth_xfer_enc_coin_zkbin) =
  68. self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS).unwrap();
  69. let input_user_data_blind = Blind::random(&mut OsRng);
  70. let exec_signature_secret = SecretKey::random(&mut OsRng);
  71. assert!(!proposal_coinattrs.is_empty());
  72. let proposal_token_id = proposal_coinattrs[0].token_id;
  73. assert!(proposal_coinattrs.iter().all(|c| c.token_id == proposal_token_id));
  74. let proposal_amount = proposal_coinattrs.iter().map(|c| c.value).sum();
  75. let dao_coins = dao_wallet
  76. .unspent_money_coins
  77. .iter()
  78. .filter(|x| x.note.token_id == proposal_token_id)
  79. .cloned()
  80. .collect();
  81. let (spent_coins, change_value) = xfer::select_coins(dao_coins, proposal_amount)?;
  82. let tree = dao_wallet.money_merkle_tree.clone();
  83. let mut inputs = vec![];
  84. for coin in &spent_coins {
  85. inputs.push(xfer::TransferCallInput {
  86. coin: coin.clone(),
  87. merkle_path: tree.witness(coin.leaf_position, 0).unwrap(),
  88. user_data_blind: input_user_data_blind,
  89. });
  90. }
  91. let mut outputs = vec![];
  92. for coin_attr in proposal_coinattrs.clone() {
  93. assert_eq!(proposal_token_id, coin_attr.token_id);
  94. outputs.push(coin_attr);
  95. }
  96. let spend_hook =
  97. FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }
  98. .to_func_id();
  99. let dao_coin_attrs = CoinAttributes {
  100. public_key: dao_wallet.keypair.public,
  101. value: change_value,
  102. token_id: proposal_token_id,
  103. spend_hook,
  104. user_data: dao.to_bulla().inner(),
  105. blind: Blind::random(&mut OsRng),
  106. };
  107. outputs.push(dao_coin_attrs.clone());
  108. let xfer_builder = xfer::TransferCallBuilder {
  109. clear_inputs: vec![],
  110. inputs,
  111. outputs,
  112. mint_zkbin: mint_zkbin.clone(),
  113. mint_pk: mint_pk.clone(),
  114. burn_zkbin: burn_zkbin.clone(),
  115. burn_pk: burn_pk.clone(),
  116. };
  117. let (xfer_params, xfer_secrets) = xfer_builder.build()?;
  118. let mut data = vec![MoneyFunction::TransferV1 as u8];
  119. xfer_params.encode_async(&mut data).await?;
  120. let xfer_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
  121. // We need to extract stuff from the inputs and outputs that we'll also
  122. // use in the DAO::Exec call. This DAO API needs to be better.
  123. let mut input_value = 0;
  124. let mut input_value_blind = Blind::ZERO;
  125. for (input, blind) in spent_coins.iter().zip(xfer_secrets.input_value_blinds.iter()) {
  126. input_value += input.note.value;
  127. input_value_blind += *blind;
  128. }
  129. assert_eq!(
  130. pedersen_commitment_u64(input_value, input_value_blind),
  131. xfer_params.inputs.iter().map(|input| input.value_commit).sum()
  132. );
  133. let exec_builder = DaoExecCall {
  134. proposal: proposal.clone(),
  135. dao: dao.clone(),
  136. yes_vote_value,
  137. all_vote_value,
  138. yes_vote_blind,
  139. all_vote_blind,
  140. signature_secret: exec_signature_secret,
  141. };
  142. let (exec_params, exec_proofs) = exec_builder.make(dao_exec_zkbin, dao_exec_pk)?;
  143. let mut data = vec![DaoFunction::Exec as u8];
  144. exec_params.encode_async(&mut data).await?;
  145. let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
  146. // Auth module
  147. let auth_xfer_builder = DaoAuthMoneyTransferCall {
  148. proposal: proposal.clone(),
  149. proposal_coinattrs,
  150. dao: dao.clone(),
  151. input_user_data_blind,
  152. dao_coin_attrs,
  153. };
  154. let (auth_xfer_params, auth_xfer_proofs) = auth_xfer_builder.make(
  155. dao_auth_xfer_zkbin,
  156. dao_auth_xfer_pk,
  157. dao_auth_xfer_enc_coin_zkbin,
  158. dao_auth_xfer_enc_coin_pk,
  159. )?;
  160. let mut data = vec![DaoFunction::AuthMoneyTransfer as u8];
  161. auth_xfer_params.encode_async(&mut data).await?;
  162. let auth_xfer_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
  163. // We need to construct this tree, where exec is the parent:
  164. //
  165. // exec ->
  166. // auth_xfer
  167. // xfer
  168. //
  169. let mut tx_builder = TransactionBuilder::new(
  170. ContractCallLeaf { call: exec_call, proofs: exec_proofs },
  171. vec![
  172. DarkTree::new(
  173. ContractCallLeaf { call: auth_xfer_call, proofs: auth_xfer_proofs },
  174. vec![],
  175. None,
  176. None,
  177. ),
  178. DarkTree::new(
  179. ContractCallLeaf { call: xfer_call, proofs: xfer_secrets.proofs },
  180. vec![],
  181. None,
  182. None,
  183. ),
  184. ],
  185. )?;
  186. // If fees are enabled, make an offering
  187. let mut fee_params = None;
  188. let mut fee_signature_secrets = None;
  189. if self.verify_fees {
  190. let mut tx = tx_builder.build()?;
  191. let auth_xfer_sigs = vec![];
  192. let xfer_sigs = tx.create_sigs(&xfer_secrets.signature_secrets)?;
  193. let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
  194. tx.signatures = vec![auth_xfer_sigs, xfer_sigs, exec_sigs];
  195. let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
  196. self.append_fee_call(holder, tx, block_height, &[]).await?;
  197. // Append the fee call to the transaction
  198. tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
  199. fee_signature_secrets = Some(fee_secrets);
  200. fee_params = Some(fee_call_params);
  201. }
  202. // Now build the actual transaction and sign it with necessary keys.
  203. let mut tx = tx_builder.build()?;
  204. let auth_xfer_sigs = vec![];
  205. let xfer_sigs = tx.create_sigs(&xfer_secrets.signature_secrets)?;
  206. let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
  207. tx.signatures = vec![auth_xfer_sigs, xfer_sigs, exec_sigs];
  208. if let Some(fee_signature_secrets) = fee_signature_secrets {
  209. let sigs = tx.create_sigs(&fee_signature_secrets)?;
  210. tx.signatures.push(sigs);
  211. }
  212. Ok((tx, xfer_params, fee_params))
  213. }
  214. /// Create a generic `Dao::Exec` transaction.
  215. #[allow(clippy::too_many_arguments)]
  216. pub async fn dao_exec_generic(
  217. &mut self,
  218. holder: &Holder,
  219. dao: &Dao,
  220. proposal: &DaoProposal,
  221. yes_vote_value: u64,
  222. all_vote_value: u64,
  223. yes_vote_blind: ScalarBlind,
  224. all_vote_blind: ScalarBlind,
  225. block_height: u32,
  226. ) -> Result<(Transaction, Option<MoneyFeeParamsV1>)> {
  227. let (dao_exec_pk, dao_exec_zkbin) =
  228. self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_EXEC_NS).unwrap();
  229. // Create the exec call
  230. let exec_signature_secret = SecretKey::random(&mut OsRng);
  231. let exec_builder = DaoExecCall {
  232. proposal: proposal.clone(),
  233. dao: dao.clone(),
  234. yes_vote_value,
  235. all_vote_value,
  236. yes_vote_blind,
  237. all_vote_blind,
  238. signature_secret: exec_signature_secret,
  239. };
  240. let (exec_params, exec_proofs) = exec_builder.make(dao_exec_zkbin, dao_exec_pk)?;
  241. // Encode the call
  242. let mut data = vec![DaoFunction::Exec as u8];
  243. exec_params.encode_async(&mut data).await?;
  244. let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
  245. // Create the TransactionBuilder containing the `DAO::Exec` call
  246. let mut tx_builder = TransactionBuilder::new(
  247. ContractCallLeaf { call: exec_call, proofs: exec_proofs },
  248. vec![],
  249. )?;
  250. // If fees are enabled, make an offering
  251. let mut fee_params = None;
  252. let mut fee_signature_secrets = None;
  253. if self.verify_fees {
  254. let mut tx = tx_builder.build()?;
  255. let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
  256. tx.signatures = vec![exec_sigs];
  257. let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
  258. self.append_fee_call(holder, tx, block_height, &[]).await?;
  259. // Append the fee call to the transaction
  260. tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
  261. fee_signature_secrets = Some(fee_secrets);
  262. fee_params = Some(fee_call_params);
  263. }
  264. // Now build the actual transaction and sign it with necessary keys.
  265. let mut tx = tx_builder.build()?;
  266. let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
  267. tx.signatures = vec![exec_sigs];
  268. if let Some(fee_signature_secrets) = fee_signature_secrets {
  269. let sigs = tx.create_sigs(&fee_signature_secrets)?;
  270. tx.signatures.push(sigs);
  271. }
  272. Ok((tx, fee_params))
  273. }
  274. /// Execute the transaction made by `dao_exec_*()` for a given [`Holder`].
  275. ///
  276. /// Returns any found [`OwnCoin`]s.
  277. pub async fn execute_dao_exec_tx(
  278. &mut self,
  279. holder: &Holder,
  280. tx: Transaction,
  281. xfer_params: Option<&MoneyTransferParamsV1>,
  282. fee_params: &Option<MoneyFeeParamsV1>,
  283. block_height: u32,
  284. append: bool,
  285. ) -> Result<Vec<OwnCoin>> {
  286. let wallet = self.holders.get_mut(holder).unwrap();
  287. // Execute the transaction
  288. wallet.add_transaction("dao::exec", tx, block_height).await?;
  289. if !append {
  290. return Ok(vec![])
  291. }
  292. let (mut inputs, mut outputs) = match xfer_params {
  293. Some(params) => (params.inputs.to_vec(), params.outputs.to_vec()),
  294. None => (vec![], vec![]),
  295. };
  296. if let Some(ref fee_params) = fee_params {
  297. inputs.push(fee_params.input.clone());
  298. outputs.push(fee_params.output.clone());
  299. }
  300. let nullifiers = inputs.iter().map(|i| i.nullifier.inner()).map(|l| (l, l)).collect();
  301. wallet.money_null_smt.insert_batch(nullifiers).expect("smt.insert_batch()");
  302. for input in inputs {
  303. if let Some(spent_coin) = wallet
  304. .unspent_money_coins
  305. .iter()
  306. .find(|x| x.nullifier() == input.nullifier)
  307. .cloned()
  308. {
  309. debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder);
  310. wallet.unspent_money_coins.retain(|x| x.nullifier() != input.nullifier);
  311. wallet.spent_money_coins.push(spent_coin.clone());
  312. }
  313. }
  314. let mut found_owncoins = vec![];
  315. for output in outputs {
  316. wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner()));
  317. let Ok(note) = output.note.decrypt::<MoneyNote>(&wallet.keypair.secret) else {
  318. continue
  319. };
  320. let owncoin = OwnCoin {
  321. coin: output.coin,
  322. note: note.clone(),
  323. secret: wallet.keypair.secret,
  324. leaf_position: wallet.money_merkle_tree.mark().unwrap(),
  325. };
  326. debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder);
  327. wallet.unspent_money_coins.push(owncoin.clone());
  328. found_owncoins.push(owncoin);
  329. }
  330. Ok(found_owncoins)
  331. }
  332. }