rpc_dao.rs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2023 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 anyhow::{anyhow, Result};
  19. use darkfi::{
  20. tx::Transaction,
  21. zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit},
  22. zkas::ZkBinary,
  23. };
  24. use darkfi_dao_contract::{
  25. client as dao_client,
  26. client::{DaoInfo, DaoProposalInfo, DaoVoteCall, DaoVoteInput},
  27. model::DaoBlindAggregateVote,
  28. DaoFunction, DAO_CONTRACT_ZKAS_DAO_EXEC_NS, DAO_CONTRACT_ZKAS_DAO_MINT_NS,
  29. DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
  30. DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
  31. };
  32. use darkfi_money_contract::{
  33. client::{transfer_v1::TransferCallBuilder, OwnCoin},
  34. MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
  35. };
  36. use darkfi_sdk::{
  37. crypto::{
  38. pedersen_commitment_u64, Keypair, PublicKey, SecretKey, TokenId, DAO_CONTRACT_ID,
  39. MONEY_CONTRACT_ID,
  40. },
  41. pasta::pallas,
  42. ContractCall,
  43. };
  44. use darkfi_serial::Encodable;
  45. use rand::rngs::OsRng;
  46. use super::Drk;
  47. use crate::wallet_dao::{Dao, DaoProposal};
  48. impl Drk {
  49. /// Mint a DAO on-chain
  50. pub async fn dao_mint(&self, dao_id: u64) -> Result<Transaction> {
  51. let dao = self.get_dao_by_id(dao_id).await?;
  52. if dao.tx_hash.is_some() {
  53. return Err(anyhow!("This DAO seems to have already been minted on-chain"))
  54. }
  55. let dao_info = DaoInfo {
  56. proposer_limit: dao.proposer_limit,
  57. quorum: dao.quorum,
  58. approval_ratio_base: dao.approval_ratio_base,
  59. approval_ratio_quot: dao.approval_ratio_quot,
  60. gov_token_id: dao.gov_token_id,
  61. public_key: PublicKey::from_secret(dao.secret_key),
  62. bulla_blind: dao.bulla_blind,
  63. };
  64. let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
  65. let Some(dao_mint_zkbin) = zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_MINT_NS)
  66. else {
  67. return Err(anyhow!("DAO Mint circuit not found"))
  68. };
  69. let dao_mint_zkbin = ZkBinary::decode(&dao_mint_zkbin.1)?;
  70. let dao_mint_circuit = ZkCircuit::new(empty_witnesses(&dao_mint_zkbin)?, &dao_mint_zkbin);
  71. eprintln!("Creating DAO Mint proving key");
  72. let dao_mint_pk = ProvingKey::build(dao_mint_zkbin.k, &dao_mint_circuit);
  73. let (params, proofs) =
  74. dao_client::make_mint_call(&dao_info, &dao.secret_key, &dao_mint_zkbin, &dao_mint_pk)?;
  75. let mut data = vec![DaoFunction::Mint as u8];
  76. params.encode(&mut data)?;
  77. let calls = vec![ContractCall { contract_id: *DAO_CONTRACT_ID, data }];
  78. let proofs = vec![proofs];
  79. let mut tx = Transaction { calls, proofs, signatures: vec![] };
  80. let sigs = tx.create_sigs(&mut OsRng, &[dao.secret_key])?;
  81. tx.signatures = vec![sigs];
  82. Ok(tx)
  83. }
  84. /// Create a DAO proposal
  85. pub async fn dao_propose(
  86. &self,
  87. dao_id: u64,
  88. recipient: PublicKey,
  89. amount: u64,
  90. token_id: TokenId,
  91. ) -> Result<Transaction> {
  92. let Ok(dao) = self.get_dao_by_id(dao_id).await else {
  93. return Err(anyhow!("DAO not found in wallet"))
  94. };
  95. if dao.leaf_position.is_none() || dao.tx_hash.is_none() {
  96. return Err(anyhow!("DAO seems to not have been deployed yet"))
  97. }
  98. let bulla = dao.bulla();
  99. let owncoins = self.get_coins(false).await?;
  100. let mut dao_owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect();
  101. dao_owncoins.retain(|x| {
  102. x.note.token_id == token_id &&
  103. x.note.spend_hook == DAO_CONTRACT_ID.inner() &&
  104. x.note.user_data == bulla.inner()
  105. });
  106. let mut gov_owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect();
  107. gov_owncoins.retain(|x| x.note.token_id == dao.gov_token_id);
  108. if dao_owncoins.is_empty() {
  109. return Err(anyhow!("Did not find any {} coins owned by this DAO", token_id))
  110. }
  111. if gov_owncoins.is_empty() {
  112. return Err(anyhow!("Did not find any governance {} coins in wallet", dao.gov_token_id))
  113. }
  114. if dao_owncoins.iter().map(|x| x.note.value).sum::<u64>() < amount {
  115. return Err(anyhow!("Not enough DAO balance for token ID: {}", token_id))
  116. }
  117. if gov_owncoins.iter().map(|x| x.note.value).sum::<u64>() < dao.proposer_limit {
  118. return Err(anyhow!("Not enough gov token {} balance to propose", dao.gov_token_id))
  119. }
  120. // FIXME: Here we're looking for a coin == proposer_limit but this shouldn't have to
  121. // be the case {
  122. let Some(gov_coin) = gov_owncoins.iter().find(|x| x.note.value == dao.proposer_limit)
  123. else {
  124. return Err(anyhow!("Did not find a single gov coin of value {}", dao.proposer_limit))
  125. };
  126. // }
  127. // Lookup the zkas bins
  128. let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
  129. let Some(propose_burn_zkbin) =
  130. zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS)
  131. else {
  132. return Err(anyhow!("Propose Burn circuit not found"))
  133. };
  134. let Some(propose_main_zkbin) =
  135. zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS)
  136. else {
  137. return Err(anyhow!("Propose Main circuit not found"))
  138. };
  139. let propose_burn_zkbin = ZkBinary::decode(&propose_burn_zkbin.1)?;
  140. let propose_main_zkbin = ZkBinary::decode(&propose_main_zkbin.1)?;
  141. let propose_burn_circuit =
  142. ZkCircuit::new(empty_witnesses(&propose_burn_zkbin)?, &propose_burn_zkbin);
  143. let propose_main_circuit =
  144. ZkCircuit::new(empty_witnesses(&propose_main_zkbin)?, &propose_main_zkbin);
  145. eprintln!("Creating Propose Burn circuit proving key");
  146. let propose_burn_pk = ProvingKey::build(propose_burn_zkbin.k, &propose_burn_circuit);
  147. eprintln!("Creating Propose Main circuit proving key");
  148. let propose_main_pk = ProvingKey::build(propose_main_zkbin.k, &propose_main_circuit);
  149. // Now create the parameters for the proposal tx
  150. let signature_secret = SecretKey::random(&mut OsRng);
  151. // Get the Merkle path for the gov coin in the money tree
  152. let money_merkle_tree = self.get_money_tree().await?;
  153. let gov_coin_merkle_path = money_merkle_tree.witness(gov_coin.leaf_position, 0).unwrap();
  154. // Fetch the daos Merkle tree
  155. let (daos_tree, _) = self.get_dao_trees().await?;
  156. let input = dao_client::DaoProposeStakeInput {
  157. secret: gov_coin.secret, // <-- TODO: Is this correct?
  158. note: gov_coin.note.clone(),
  159. leaf_position: gov_coin.leaf_position,
  160. merkle_path: gov_coin_merkle_path,
  161. signature_secret,
  162. };
  163. let (dao_merkle_path, dao_merkle_root) = {
  164. let root = daos_tree.root(0).unwrap();
  165. let leaf_pos = dao.leaf_position.unwrap();
  166. let dao_merkle_path = daos_tree.witness(leaf_pos, 0).unwrap();
  167. (dao_merkle_path, root)
  168. };
  169. let proposal_blind = pallas::Base::random(&mut OsRng);
  170. let proposal = dao_client::DaoProposalInfo {
  171. dest: recipient,
  172. amount,
  173. token_id,
  174. blind: proposal_blind,
  175. };
  176. let daoinfo = DaoInfo {
  177. proposer_limit: dao.proposer_limit,
  178. quorum: dao.quorum,
  179. approval_ratio_quot: dao.approval_ratio_quot,
  180. approval_ratio_base: dao.approval_ratio_base,
  181. gov_token_id: dao.gov_token_id,
  182. public_key: PublicKey::from_secret(dao.secret_key),
  183. bulla_blind: dao.bulla_blind,
  184. };
  185. let call = dao_client::DaoProposeCall {
  186. inputs: vec![input],
  187. proposal,
  188. dao: daoinfo,
  189. dao_leaf_position: dao.leaf_position.unwrap(),
  190. dao_merkle_path,
  191. dao_merkle_root,
  192. };
  193. eprintln!("Creating ZK proofs...");
  194. let (params, proofs) = call.make(
  195. &propose_burn_zkbin,
  196. &propose_burn_pk,
  197. &propose_main_zkbin,
  198. &propose_main_pk,
  199. )?;
  200. let mut data = vec![DaoFunction::Propose as u8];
  201. params.encode(&mut data)?;
  202. let calls = vec![ContractCall { contract_id: *DAO_CONTRACT_ID, data }];
  203. let proofs = vec![proofs];
  204. let mut tx = Transaction { calls, proofs, signatures: vec![] };
  205. let sigs = tx.create_sigs(&mut OsRng, &[signature_secret])?;
  206. tx.signatures = vec![sigs];
  207. Ok(tx)
  208. }
  209. /// Vote on a DAO proposal
  210. pub async fn dao_vote(
  211. &self,
  212. dao_id: u64,
  213. proposal_id: u64,
  214. vote_option: bool,
  215. weight: u64,
  216. ) -> Result<Transaction> {
  217. let dao = self.get_dao_by_id(dao_id).await?;
  218. let proposals = self.get_dao_proposals(dao_id).await?;
  219. let Some(proposal) = proposals.iter().find(|x| x.id == proposal_id) else {
  220. return Err(anyhow!("Proposal ID not found"))
  221. };
  222. let money_tree = proposal.money_snapshot_tree.clone().unwrap();
  223. let mut coins: Vec<OwnCoin> =
  224. self.get_coins(false).await?.iter().map(|x| x.0.clone()).collect();
  225. coins.retain(|x| x.note.token_id == dao.gov_token_id);
  226. coins.retain(|x| x.note.spend_hook == pallas::Base::zero());
  227. if coins.iter().map(|x| x.note.value).sum::<u64>() < weight {
  228. return Err(anyhow!("Not enough balance for vote weight"))
  229. }
  230. // TODO: The spent coins need to either be marked as spent here, and/or on scan
  231. let mut spent_value = 0;
  232. let mut spent_coins = vec![];
  233. let mut inputs = vec![];
  234. let mut input_secrets = vec![];
  235. // FIXME: We don't take back any change so it's possible to vote with > requested weight.
  236. for coin in coins {
  237. if spent_value >= weight {
  238. break
  239. }
  240. spent_value += coin.note.value;
  241. spent_coins.push(coin.clone());
  242. let signature_secret = SecretKey::random(&mut OsRng);
  243. input_secrets.push(signature_secret);
  244. let leaf_position = coin.leaf_position;
  245. let merkle_path = money_tree.witness(coin.leaf_position, 0).unwrap();
  246. let input = DaoVoteInput {
  247. secret: coin.secret,
  248. note: coin.note.clone(),
  249. leaf_position,
  250. merkle_path,
  251. signature_secret,
  252. };
  253. inputs.push(input);
  254. }
  255. // We use the DAO secret to encrypt the vote.
  256. let vote_keypair = Keypair::new(dao.secret_key);
  257. let proposal_info = DaoProposalInfo {
  258. dest: proposal.recipient,
  259. amount: proposal.amount,
  260. token_id: proposal.token_id,
  261. blind: proposal.bulla_blind,
  262. };
  263. let dao_info = DaoInfo {
  264. proposer_limit: dao.proposer_limit,
  265. quorum: dao.quorum,
  266. approval_ratio_quot: dao.approval_ratio_quot,
  267. approval_ratio_base: dao.approval_ratio_base,
  268. gov_token_id: dao.gov_token_id,
  269. public_key: PublicKey::from_secret(dao.secret_key),
  270. bulla_blind: dao.bulla_blind,
  271. };
  272. let call = DaoVoteCall {
  273. inputs,
  274. vote_option,
  275. yes_vote_blind: pallas::Scalar::random(&mut OsRng),
  276. vote_keypair,
  277. proposal: proposal_info,
  278. dao: dao_info,
  279. };
  280. let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
  281. let Some(dao_vote_burn_zkbin) =
  282. zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS)
  283. else {
  284. return Err(anyhow!("DAO Vote Burn circuit not found"))
  285. };
  286. let Some(dao_vote_main_zkbin) =
  287. zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS)
  288. else {
  289. return Err(anyhow!("DAO Vote Main circuit not found"))
  290. };
  291. let dao_vote_burn_zkbin = ZkBinary::decode(&dao_vote_burn_zkbin.1)?;
  292. let dao_vote_main_zkbin = ZkBinary::decode(&dao_vote_main_zkbin.1)?;
  293. let dao_vote_burn_circuit =
  294. ZkCircuit::new(empty_witnesses(&dao_vote_burn_zkbin)?, &dao_vote_burn_zkbin);
  295. let dao_vote_main_circuit =
  296. ZkCircuit::new(empty_witnesses(&dao_vote_main_zkbin)?, &dao_vote_main_zkbin);
  297. eprintln!("Creating DAO Vote Burn proving key");
  298. let dao_vote_burn_pk = ProvingKey::build(dao_vote_burn_zkbin.k, &dao_vote_burn_circuit);
  299. eprintln!("Creating DAO Vote Main proving key");
  300. let dao_vote_main_pk = ProvingKey::build(dao_vote_main_zkbin.k, &dao_vote_main_circuit);
  301. let (params, proofs) = call.make(
  302. &dao_vote_burn_zkbin,
  303. &dao_vote_burn_pk,
  304. &dao_vote_main_zkbin,
  305. &dao_vote_main_pk,
  306. )?;
  307. let mut data = vec![DaoFunction::Vote as u8];
  308. params.encode(&mut data)?;
  309. let calls = vec![ContractCall { contract_id: *DAO_CONTRACT_ID, data }];
  310. let proofs = vec![proofs];
  311. let mut tx = Transaction { calls, proofs, signatures: vec![] };
  312. let sigs = tx.create_sigs(&mut OsRng, &input_secrets)?;
  313. tx.signatures = vec![sigs];
  314. Ok(tx)
  315. }
  316. /// Import given DAO votes into the wallet
  317. /// This function is really bad but I'm also really tired and annoyed.
  318. pub async fn dao_exec(&self, dao: Dao, proposal: DaoProposal) -> Result<Transaction> {
  319. let dao_bulla = dao.bulla();
  320. eprintln!("Fetching proposal's votes");
  321. let votes = self.get_dao_proposal_votes(proposal.id).await?;
  322. // Find the treasury coins that can be used for this proposal
  323. let mut coins: Vec<OwnCoin> =
  324. self.get_coins(false).await?.iter().map(|x| x.0.clone()).collect();
  325. coins.retain(|x| x.note.spend_hook == DAO_CONTRACT_ID.inner());
  326. coins.retain(|x| x.note.user_data == dao_bulla.inner());
  327. coins.retain(|x| x.note.token_id == proposal.token_id);
  328. if coins.iter().map(|x| x.note.value).sum::<u64>() < proposal.amount {
  329. return Err(anyhow!("Not enough balance in DAO treasury to execute proposal"))
  330. }
  331. // FIXME: This assumes we aren't sending to a protocol.
  332. let rcpt_spend_hook = pallas::Base::ZERO;
  333. let rcpt_user_data = pallas::Base::ZERO;
  334. let rcpt_user_data_blind = pallas::Base::random(&mut OsRng);
  335. let change_spend_hook = DAO_CONTRACT_ID.inner();
  336. let change_user_data = dao_bulla.inner();
  337. let change_user_data_blind = pallas::Base::random(&mut OsRng);
  338. let money_merkle_tree = self.get_money_tree().await?;
  339. let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
  340. let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1)
  341. else {
  342. return Err(anyhow!("Money Mint circuit not found"))
  343. };
  344. let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_BURN_NS_V1)
  345. else {
  346. return Err(anyhow!("Money Burn circuit not found"))
  347. };
  348. let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
  349. let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
  350. let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
  351. let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
  352. eprintln!("Creating Money Mint circuit proving key");
  353. let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
  354. eprintln!("Creating Money Burn circuit proving key");
  355. let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
  356. let xfer_builder = TransferCallBuilder {
  357. keypair: dao.keypair(),
  358. recipient: proposal.recipient,
  359. value: proposal.amount,
  360. token_id: proposal.token_id,
  361. rcpt_spend_hook,
  362. rcpt_user_data,
  363. rcpt_user_data_blind,
  364. change_spend_hook,
  365. change_user_data,
  366. change_user_data_blind,
  367. coins,
  368. tree: money_merkle_tree,
  369. mint_zkbin: mint_zkbin.clone(),
  370. mint_pk: mint_pk.clone(),
  371. burn_zkbin: burn_zkbin.clone(),
  372. burn_pk: burn_pk.clone(),
  373. clear_input: false,
  374. };
  375. let xfer_debris = xfer_builder.build()?;
  376. let mut data = vec![MoneyFunction::TransferV1 as u8];
  377. xfer_debris.params.encode(&mut data)?;
  378. let xfer_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
  379. let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
  380. let Some(exec_zkbin) = zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_EXEC_NS)
  381. else {
  382. return Err(anyhow!("DAO Exec circuit not found"))
  383. };
  384. let exec_zkbin = ZkBinary::decode(&exec_zkbin.1)?;
  385. let exec_circuit = ZkCircuit::new(empty_witnesses(&exec_zkbin)?, &exec_zkbin);
  386. eprintln!("Creating DAO Exec circuit proving key");
  387. let exec_pk = ProvingKey::build(exec_zkbin.k, &exec_circuit);
  388. // Count votes
  389. let mut total_yes_vote_value = 0;
  390. let mut total_all_vote_value = 0;
  391. let mut blind_total_vote = DaoBlindAggregateVote::default();
  392. let mut total_yes_vote_blind = pallas::Scalar::zero();
  393. let mut total_all_vote_blind = pallas::Scalar::zero();
  394. for (_, vote) in votes.iter().enumerate() {
  395. total_yes_vote_blind += vote.yes_vote_blind;
  396. total_all_vote_blind += vote.all_vote_blind;
  397. let yes_vote_value = vote.vote_option as u64 * vote.all_vote_value;
  398. eprintln!("yes_vote = {}", yes_vote_value);
  399. total_yes_vote_value += yes_vote_value;
  400. total_all_vote_value += vote.all_vote_value;
  401. let yes_vote_commit = pedersen_commitment_u64(yes_vote_value, vote.yes_vote_blind);
  402. let all_vote_commit = pedersen_commitment_u64(vote.all_vote_value, vote.all_vote_blind);
  403. let blind_vote = DaoBlindAggregateVote { yes_vote_commit, all_vote_commit };
  404. blind_total_vote.aggregate(blind_vote);
  405. }
  406. eprintln!("yes = {}, all = {}", total_yes_vote_value, total_all_vote_value);
  407. let prop_t = DaoProposalInfo {
  408. dest: proposal.recipient,
  409. amount: proposal.amount,
  410. token_id: proposal.token_id,
  411. blind: proposal.bulla_blind, // <-- FIXME: wtf
  412. };
  413. // TODO: allvote/yesvote is 11 weirdly
  414. let dao_t = DaoInfo {
  415. proposer_limit: dao.proposer_limit,
  416. quorum: dao.quorum,
  417. approval_ratio_quot: dao.approval_ratio_quot,
  418. approval_ratio_base: dao.approval_ratio_base,
  419. gov_token_id: dao.gov_token_id,
  420. public_key: PublicKey::from_secret(dao.secret_key),
  421. bulla_blind: dao.bulla_blind,
  422. };
  423. // We need to extract stuff from the inputs and outputs that we'll also
  424. // use in the DAO::Exec call. This DAO API needs to be better.
  425. let mut input_value = 0;
  426. let mut input_value_blind = pallas::Scalar::ZERO;
  427. for (input, blind) in xfer_debris.spent_coins.iter().zip(xfer_debris.input_value_blinds) {
  428. input_value += input.note.value;
  429. input_value_blind += blind;
  430. }
  431. // First output is change, second output is recipient.
  432. let dao_serial = xfer_debris.minted_coins[0].note.serial;
  433. let user_serial = xfer_debris.minted_coins[1].note.serial;
  434. // TODO: FIXME: This is not checked anywhere!
  435. let exec_signature_secret = SecretKey::random(&mut OsRng);
  436. let dao_exec_call = dao_client::DaoExecCall {
  437. proposal: prop_t,
  438. dao: dao_t,
  439. yes_vote_value: total_yes_vote_value,
  440. all_vote_value: total_all_vote_value,
  441. yes_vote_blind: total_yes_vote_blind,
  442. all_vote_blind: total_all_vote_blind,
  443. user_serial,
  444. dao_serial,
  445. input_value,
  446. input_value_blind,
  447. hook_dao_exec: DAO_CONTRACT_ID.inner(),
  448. signature_secret: exec_signature_secret,
  449. };
  450. let (exec_params, exec_proofs) = dao_exec_call.make(&exec_zkbin, &exec_pk)?;
  451. let mut data = vec![DaoFunction::Exec as u8];
  452. exec_params.encode(&mut data)?;
  453. let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
  454. let mut tx = Transaction {
  455. calls: vec![xfer_call, exec_call],
  456. proofs: vec![xfer_debris.proofs, exec_proofs],
  457. signatures: vec![],
  458. };
  459. let xfer_sigs = tx.create_sigs(&mut OsRng, &xfer_debris.signature_secrets)?;
  460. let exec_sigs = tx.create_sigs(&mut OsRng, &[exec_signature_secret])?;
  461. tx.signatures = vec![xfer_sigs, exec_sigs];
  462. Ok(tx)
  463. }
  464. }