integration.rs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2025 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::{util::pcg::Pcg32, Result};
  19. use darkfi_contract_test_harness::{init_logger, Holder, TestHarness};
  20. use darkfi_dao_contract::{
  21. blockwindow,
  22. model::{Dao, DaoBlindAggregateVote, DaoVoteParams},
  23. DaoFunction,
  24. };
  25. use darkfi_money_contract::{
  26. model::{CoinAttributes, TokenAttributes, DARK_TOKEN_ID},
  27. MoneyFunction,
  28. };
  29. use darkfi_sdk::{
  30. crypto::{
  31. pasta_prelude::*,
  32. pedersen_commitment_u64, poseidon_hash,
  33. util::{fp_mod_fv, fp_to_u64},
  34. BaseBlind, Blind, FuncId, FuncRef, Keypair, ScalarBlind, SecretKey, DAO_CONTRACT_ID,
  35. MONEY_CONTRACT_ID,
  36. },
  37. pasta::pallas,
  38. };
  39. use log::info;
  40. use rand::rngs::OsRng;
  41. // Integration test configuration
  42. // Holders this test will use:
  43. // * Alice, Bob, and Charlie are members of the DAO.
  44. // * Dao is the DAO wallet
  45. // * Rachel is the transfer proposal recipient.
  46. const HOLDERS: [Holder; 5] =
  47. [Holder::Alice, Holder::Bob, Holder::Charlie, Holder::Dao, Holder::Rachel];
  48. // DAO gov tokens distribution
  49. const ALICE_GOV_SUPPLY: u64 = 100_000_000;
  50. const BOB_GOV_SUPPLY: u64 = 100_000_000;
  51. const CHARLIE_GOV_SUPPLY: u64 = 100_000_000;
  52. // DRK token, the treasury token, supply
  53. const DRK_TOKEN_SUPPLY: [u64; 1] = [1_000_000_000];
  54. // DAO parameters configuration
  55. const PROPOSER_LIMIT: u64 = 100_000_000;
  56. const QUORUM: u64 = 200_000_000;
  57. const EARLY_EXEC_QUORUM: u64 = 200_000_000;
  58. const APPROVAL_RATIO_BASE: u64 = 2;
  59. const APPROVAL_RATIO_QUOT: u64 = 1;
  60. const PROPOSAL_DURATION_BLOCKWINDOW: u64 = 1;
  61. // The tokens we want to send via the transfer proposal
  62. const TRANSFER_PROPOSAL_AMOUNT: u64 = 250_000_000;
  63. #[test]
  64. fn integration_test() -> Result<()> {
  65. smol::block_on(async {
  66. init_logger();
  67. // Initialize harness
  68. let mut th = TestHarness::new(&HOLDERS, false).await?;
  69. // We'll use the ALICE token as the DAO governance token
  70. let wallet = th.holders.get_mut(&Holder::Alice).unwrap();
  71. //wallet.bench_wasm = true;
  72. let mint_authority = wallet.token_mint_authority;
  73. let gov_token_blind = BaseBlind::random(&mut OsRng);
  74. let auth_func_id = FuncRef {
  75. contract_id: *MONEY_CONTRACT_ID,
  76. func_code: MoneyFunction::AuthTokenMintV1 as u8,
  77. }
  78. .to_func_id();
  79. let token_attrs = TokenAttributes {
  80. auth_parent: auth_func_id,
  81. user_data: poseidon_hash([mint_authority.public.x(), mint_authority.public.y()]),
  82. blind: gov_token_blind,
  83. };
  84. let gov_token_id = token_attrs.to_token_id();
  85. // Block height to verify against
  86. let mut current_block_height = 0;
  87. // DAO parameters
  88. let dao_notes_keypair = th.holders.get(&Holder::Dao).unwrap().keypair;
  89. let mut rng = Pcg32::new(42);
  90. let dao_proposer_keypair = Keypair::random(&mut rng);
  91. let dao_proposals_keypair = Keypair::random(&mut rng);
  92. let dao_votes_keypair = Keypair::random(&mut rng);
  93. let dao_exec_keypair = Keypair::random(&mut rng);
  94. let dao_early_exec_keypair = Keypair::random(&mut rng);
  95. let dao = Dao {
  96. proposer_limit: PROPOSER_LIMIT,
  97. quorum: QUORUM,
  98. early_exec_quorum: EARLY_EXEC_QUORUM,
  99. approval_ratio_base: APPROVAL_RATIO_BASE,
  100. approval_ratio_quot: APPROVAL_RATIO_QUOT,
  101. gov_token_id,
  102. notes_public_key: dao_notes_keypair.public,
  103. proposer_public_key: dao_proposer_keypair.public,
  104. proposals_public_key: dao_proposals_keypair.public,
  105. votes_public_key: dao_votes_keypair.public,
  106. exec_public_key: dao_exec_keypair.public,
  107. early_exec_public_key: dao_early_exec_keypair.public,
  108. bulla_blind: Blind::random(&mut OsRng),
  109. };
  110. // =======================================
  111. // Airdrop some treasury tokens to the DAO
  112. // =======================================
  113. info!("[Dao] Building DAO airdrop tx");
  114. let spend_hook =
  115. FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }
  116. .to_func_id();
  117. let (genesis_mint_tx, genesis_mint_params) = th
  118. .genesis_mint(
  119. &Holder::Dao,
  120. &DRK_TOKEN_SUPPLY,
  121. Some(spend_hook),
  122. Some(dao.to_bulla().inner()),
  123. )
  124. .await?;
  125. for holder in &HOLDERS {
  126. th.execute_genesis_mint_tx(
  127. holder,
  128. genesis_mint_tx.clone(),
  129. &genesis_mint_params,
  130. current_block_height,
  131. true,
  132. )
  133. .await?;
  134. }
  135. th.assert_trees(&HOLDERS);
  136. let _dao_tokens = &th.holders.get(&Holder::Dao).unwrap().unspent_money_coins;
  137. assert!(_dao_tokens.len() == 1);
  138. assert!(_dao_tokens[0].note.token_id == *DARK_TOKEN_ID);
  139. assert!(_dao_tokens[0].note.value == DRK_TOKEN_SUPPLY[0]);
  140. current_block_height += 1;
  141. // ====================
  142. // Dao::Mint
  143. // Create the DAO bulla
  144. // ====================
  145. info!("[Dao] Building DAO mint tx");
  146. let (dao_mint_tx, dao_mint_params, fee_params) = th
  147. .dao_mint(
  148. &Holder::Alice,
  149. &dao,
  150. &dao_notes_keypair.secret,
  151. &dao_proposer_keypair.secret,
  152. &dao_proposals_keypair.secret,
  153. &dao_votes_keypair.secret,
  154. &dao_exec_keypair.secret,
  155. &dao_early_exec_keypair.secret,
  156. current_block_height,
  157. )
  158. .await?;
  159. for holder in &HOLDERS {
  160. info!("[{holder:?}] Executing DAO Mint tx");
  161. th.execute_dao_mint_tx(
  162. holder,
  163. dao_mint_tx.clone(),
  164. &dao_mint_params,
  165. &fee_params,
  166. current_block_height,
  167. true,
  168. )
  169. .await?;
  170. }
  171. th.assert_trees(&HOLDERS);
  172. current_block_height += 1;
  173. // ======================================
  174. // Mint the governance token to 3 holders
  175. // ======================================
  176. info!("[Dao] Building governance token mint tx for Alice");
  177. let (a_token_mint_tx, a_token_mint_params, a_auth_token_mint_params, a_fee_params) = th
  178. .token_mint(
  179. ALICE_GOV_SUPPLY,
  180. &Holder::Alice,
  181. &Holder::Alice,
  182. gov_token_blind,
  183. None,
  184. None,
  185. current_block_height,
  186. )
  187. .await?;
  188. for holder in &HOLDERS {
  189. info!("[{holder:?}] Executing governance token mint tx for Alice");
  190. th.execute_token_mint_tx(
  191. holder,
  192. a_token_mint_tx.clone(),
  193. &a_token_mint_params,
  194. &a_auth_token_mint_params,
  195. &a_fee_params,
  196. current_block_height,
  197. true,
  198. )
  199. .await?;
  200. }
  201. th.assert_trees(&HOLDERS);
  202. let _alice_tokens = &th.holders.get(&Holder::Alice).unwrap().unspent_money_coins;
  203. assert!(_alice_tokens.len() == 1);
  204. assert!(_alice_tokens[0].note.token_id == gov_token_id);
  205. assert!(_alice_tokens[0].note.value == ALICE_GOV_SUPPLY);
  206. info!("[Dao] Building governance token mint tx for Bob");
  207. let (b_token_mint_tx, b_token_mint_params, b_auth_token_mint_params, b_fee_params) = th
  208. .token_mint(
  209. BOB_GOV_SUPPLY,
  210. &Holder::Alice,
  211. &Holder::Bob,
  212. gov_token_blind,
  213. None,
  214. None,
  215. current_block_height,
  216. )
  217. .await?;
  218. for holder in &HOLDERS {
  219. info!("[{holder:?}] Executing governance token mint tx for Bob");
  220. th.execute_token_mint_tx(
  221. holder,
  222. b_token_mint_tx.clone(),
  223. &b_token_mint_params,
  224. &b_auth_token_mint_params,
  225. &b_fee_params,
  226. current_block_height,
  227. true,
  228. )
  229. .await?;
  230. }
  231. th.assert_trees(&HOLDERS);
  232. let _bob_tokens = &th.holders.get(&Holder::Bob).unwrap().unspent_money_coins;
  233. assert!(_bob_tokens.len() == 1);
  234. assert!(_bob_tokens[0].note.token_id == gov_token_id);
  235. assert!(_bob_tokens[0].note.value == BOB_GOV_SUPPLY);
  236. info!("[Dao] Building governance token mint tx for Charlie");
  237. let (c_token_mint_tx, c_token_mint_params, c_auth_token_mint_params, c_fee_params) = th
  238. .token_mint(
  239. CHARLIE_GOV_SUPPLY,
  240. &Holder::Alice,
  241. &Holder::Charlie,
  242. gov_token_blind,
  243. None,
  244. None,
  245. current_block_height,
  246. )
  247. .await?;
  248. for holder in &HOLDERS {
  249. info!("[{holder:?}] Executing governance token mint tx for Charlie");
  250. th.execute_token_mint_tx(
  251. holder,
  252. c_token_mint_tx.clone(),
  253. &c_token_mint_params,
  254. &c_auth_token_mint_params,
  255. &c_fee_params,
  256. current_block_height,
  257. true,
  258. )
  259. .await?;
  260. }
  261. th.assert_trees(&HOLDERS);
  262. let _charlie_tokens = &th.holders.get(&Holder::Charlie).unwrap().unspent_money_coins;
  263. assert!(_charlie_tokens.len() == 1);
  264. assert!(_charlie_tokens[0].note.token_id == gov_token_id);
  265. assert!(_charlie_tokens[0].note.value == CHARLIE_GOV_SUPPLY);
  266. current_block_height += 1;
  267. // We can add whatever we want in here, even arbitrary text
  268. // It's up to the auth module to decide what to do with it.
  269. let user_data = pallas::Base::ZERO;
  270. // ============================
  271. // Execute proposals test cases
  272. // ============================
  273. info!("[Dao] DAO transfer proposal tx test case");
  274. execute_transfer_proposal(
  275. &mut th,
  276. &dao,
  277. &dao_proposer_keypair.secret,
  278. &dao_votes_keypair.secret,
  279. &dao_exec_keypair.secret,
  280. &None,
  281. user_data,
  282. &mut current_block_height,
  283. 0,
  284. TRANSFER_PROPOSAL_AMOUNT,
  285. TRANSFER_PROPOSAL_AMOUNT,
  286. )
  287. .await?;
  288. info!("[Dao] DAO early execution transfer proposal tx test case");
  289. execute_transfer_proposal(
  290. &mut th,
  291. &dao,
  292. &dao_proposer_keypair.secret,
  293. &dao_votes_keypair.secret,
  294. &dao_exec_keypair.secret,
  295. &Some(dao_early_exec_keypair.secret),
  296. user_data,
  297. &mut current_block_height,
  298. 1,
  299. TRANSFER_PROPOSAL_AMOUNT,
  300. TRANSFER_PROPOSAL_AMOUNT * 2,
  301. )
  302. .await?;
  303. info!("[Dao] DAO generic proposal tx test case");
  304. execute_generic_proposal(
  305. &mut th,
  306. &dao,
  307. &dao_proposer_keypair.secret,
  308. &dao_votes_keypair.secret,
  309. &dao_exec_keypair.secret,
  310. &None,
  311. user_data,
  312. &mut current_block_height,
  313. )
  314. .await?;
  315. // Now we will execute a random money transaction,
  316. // to update our merkle tree so our snapshot is fresh.
  317. info!("[Dao] Building governance token mint tx for Alice");
  318. let (a_token_mint_tx, a_token_mint_params, a_auth_token_mint_params, a_fee_params) = th
  319. .token_mint(
  320. ALICE_GOV_SUPPLY,
  321. &Holder::Alice,
  322. &Holder::Alice,
  323. gov_token_blind,
  324. None,
  325. None,
  326. current_block_height,
  327. )
  328. .await?;
  329. for holder in &HOLDERS {
  330. info!("[{holder:?}] Executing governance token mint tx for Alice");
  331. th.execute_token_mint_tx(
  332. holder,
  333. a_token_mint_tx.clone(),
  334. &a_token_mint_params,
  335. &a_auth_token_mint_params,
  336. &a_fee_params,
  337. current_block_height,
  338. true,
  339. )
  340. .await?;
  341. }
  342. th.assert_trees(&HOLDERS);
  343. current_block_height += 1;
  344. // Now we can continue our test cases
  345. info!("[Dao] DAO early execution generic proposal tx test case");
  346. execute_generic_proposal(
  347. &mut th,
  348. &dao,
  349. &dao_proposer_keypair.secret,
  350. &dao_votes_keypair.secret,
  351. &dao_exec_keypair.secret,
  352. &Some(dao_early_exec_keypair.secret),
  353. user_data,
  354. &mut current_block_height,
  355. )
  356. .await?;
  357. // Thanks for reading
  358. Ok(())
  359. })
  360. }
  361. /// Test case:
  362. /// Generate a transfer proposal and execute it after voting passes.
  363. #[allow(clippy::too_many_arguments)]
  364. async fn execute_transfer_proposal(
  365. th: &mut TestHarness,
  366. dao: &Dao,
  367. dao_proposer_secret_key: &SecretKey,
  368. dao_votes_secret_key: &SecretKey,
  369. dao_exec_secret_key: &SecretKey,
  370. dao_early_exec_secret_key: &Option<SecretKey>,
  371. user_data: pallas::Base,
  372. current_block_height: &mut u32,
  373. transfer_token_index: usize,
  374. transfer_amount: u64,
  375. dao_treasury_decrease: u64,
  376. ) -> Result<()> {
  377. // ================
  378. // Dao::Propose
  379. // Propose the vote
  380. // ================
  381. info!("[Dao] Building DAO transfer proposal tx");
  382. // These coins are passed around to all DAO members who verify its validity
  383. // They also check hashing them equals the proposal_commit
  384. let proposal_coinattrs = vec![CoinAttributes {
  385. public_key: th.holders.get(&Holder::Rachel).unwrap().keypair.public,
  386. value: transfer_amount,
  387. token_id: *DARK_TOKEN_ID,
  388. spend_hook: FuncId::none(),
  389. user_data: pallas::Base::ZERO,
  390. blind: Blind::random(&mut OsRng),
  391. }];
  392. // Grab creation blockwindow
  393. let block_target =
  394. th.holders.get_mut(&Holder::Dao).unwrap().validator.consensus.module.read().await.target;
  395. let creation_blockwindow = blockwindow(*current_block_height, block_target);
  396. let (tx, params, fee_params, proposal_info) = th
  397. .dao_propose_transfer(
  398. &Holder::Alice,
  399. &proposal_coinattrs,
  400. user_data,
  401. dao,
  402. dao_proposer_secret_key,
  403. *current_block_height,
  404. PROPOSAL_DURATION_BLOCKWINDOW,
  405. )
  406. .await?;
  407. for holder in &HOLDERS {
  408. info!("[{holder:?}] Executing DAO transfer proposal tx");
  409. th.execute_dao_propose_tx(
  410. holder,
  411. tx.clone(),
  412. &params,
  413. &fee_params,
  414. *current_block_height,
  415. true,
  416. )
  417. .await?;
  418. }
  419. th.assert_trees(&HOLDERS);
  420. *current_block_height += 1;
  421. // =====================================
  422. // Dao::Vote
  423. // Proposal is accepted. Start the vote.
  424. // =====================================
  425. info!("[Alice] Building transfer vote tx (yes)");
  426. let (alice_vote_tx, alice_vote_params, alice_vote_fee_params) =
  427. th.dao_vote(&Holder::Alice, true, dao, &proposal_info, *current_block_height).await?;
  428. info!("[Bob] Building transfer vote tx (no)");
  429. let (bob_vote_tx, bob_vote_params, bob_vote_fee_params) =
  430. th.dao_vote(&Holder::Bob, false, dao, &proposal_info, *current_block_height).await?;
  431. info!("[Charlie] Building transfer vote tx (yes)");
  432. let (charlie_vote_tx, charlie_vote_params, charlie_vote_fee_params) =
  433. th.dao_vote(&Holder::Charlie, true, dao, &proposal_info, *current_block_height).await?;
  434. for holder in &HOLDERS {
  435. info!("[{holder:?}] Executing Alice transfer vote tx");
  436. th.execute_dao_vote_tx(
  437. holder,
  438. alice_vote_tx.clone(),
  439. &alice_vote_fee_params,
  440. *current_block_height,
  441. true,
  442. )
  443. .await?;
  444. info!("[{holder:?}] Executing Bob transfer vote tx");
  445. th.execute_dao_vote_tx(
  446. holder,
  447. bob_vote_tx.clone(),
  448. &bob_vote_fee_params,
  449. *current_block_height,
  450. true,
  451. )
  452. .await?;
  453. info!("[{holder:?}] Executing Charlie transfer vote tx");
  454. th.execute_dao_vote_tx(
  455. holder,
  456. charlie_vote_tx.clone(),
  457. &charlie_vote_fee_params,
  458. *current_block_height,
  459. true,
  460. )
  461. .await?;
  462. }
  463. th.assert_trees(&HOLDERS);
  464. // Gather and decrypt all generic vote notes
  465. let vote_note_1 = alice_vote_params.note.decrypt_unsafe(dao_votes_secret_key).unwrap();
  466. let vote_note_2 = bob_vote_params.note.decrypt_unsafe(dao_votes_secret_key).unwrap();
  467. let vote_note_3 = charlie_vote_params.note.decrypt_unsafe(dao_votes_secret_key).unwrap();
  468. // Count the votes
  469. let (total_yes_vote_value, total_all_vote_value, total_yes_vote_blind, total_all_vote_blind) =
  470. count_votes(&[
  471. (vote_note_1, alice_vote_params),
  472. (vote_note_2, bob_vote_params),
  473. (vote_note_3, charlie_vote_params),
  474. ]);
  475. // Wait until proposal has expired
  476. if dao_early_exec_secret_key.is_none() {
  477. let mut current_blockwindow = creation_blockwindow;
  478. while current_blockwindow <= creation_blockwindow + PROPOSAL_DURATION_BLOCKWINDOW {
  479. *current_block_height += 1;
  480. current_blockwindow = blockwindow(*current_block_height, block_target);
  481. }
  482. }
  483. // ================
  484. // Dao::Exec
  485. // Execute the vote
  486. // ================
  487. info!("[Dao] Building transfer Dao::Exec tx");
  488. let (exec_tx, xfer_params, exec_fee_params) = th
  489. .dao_exec_transfer(
  490. &Holder::Alice,
  491. dao,
  492. dao_exec_secret_key,
  493. dao_early_exec_secret_key,
  494. &proposal_info,
  495. proposal_coinattrs,
  496. total_yes_vote_value,
  497. total_all_vote_value,
  498. total_yes_vote_blind,
  499. total_all_vote_blind,
  500. *current_block_height,
  501. )
  502. .await?;
  503. for holder in &HOLDERS {
  504. info!("[{holder:?}] Executing transfer Dao::Exec tx");
  505. th.execute_dao_exec_tx(
  506. holder,
  507. exec_tx.clone(),
  508. Some(&xfer_params),
  509. &exec_fee_params,
  510. *current_block_height,
  511. true,
  512. )
  513. .await?;
  514. }
  515. th.assert_trees(&HOLDERS);
  516. *current_block_height += 1;
  517. let rachel_wallet = th.holders.get(&Holder::Rachel).unwrap();
  518. assert!(rachel_wallet.unspent_money_coins[transfer_token_index].note.value == transfer_amount);
  519. assert!(
  520. rachel_wallet.unspent_money_coins[transfer_token_index].note.token_id == *DARK_TOKEN_ID
  521. );
  522. let dao_wallet = th.holders.get(&Holder::Dao).unwrap();
  523. assert!(
  524. dao_wallet.unspent_money_coins[0].note.value == DRK_TOKEN_SUPPLY[0] - dao_treasury_decrease
  525. );
  526. assert!(dao_wallet.unspent_money_coins[0].note.token_id == *DARK_TOKEN_ID);
  527. Ok(())
  528. }
  529. /// Test case:
  530. /// Generate a generic proposal and execute it after voting passes.
  531. #[allow(clippy::too_many_arguments)]
  532. async fn execute_generic_proposal(
  533. th: &mut TestHarness,
  534. dao: &Dao,
  535. dao_proposer_secret_key: &SecretKey,
  536. dao_votes_secret_key: &SecretKey,
  537. dao_exec_secret_key: &SecretKey,
  538. dao_early_exec_secret_key: &Option<SecretKey>,
  539. user_data: pallas::Base,
  540. current_block_height: &mut u32,
  541. ) -> Result<()> {
  542. // ================
  543. // Dao::Propose
  544. // Propose the vote
  545. // ================
  546. info!("[Dao] Building DAO generic proposal tx");
  547. // Grab creation blockwindow
  548. let block_target =
  549. th.holders.get_mut(&Holder::Dao).unwrap().validator.consensus.module.read().await.target;
  550. let creation_blockwindow = blockwindow(*current_block_height, block_target);
  551. let (tx, params, fee_params, proposal_info) = th
  552. .dao_propose_generic(
  553. &Holder::Alice,
  554. user_data,
  555. dao,
  556. dao_proposer_secret_key,
  557. *current_block_height,
  558. PROPOSAL_DURATION_BLOCKWINDOW,
  559. )
  560. .await?;
  561. for holder in &HOLDERS {
  562. info!("[{holder:?}] Executing DAO generic proposal tx");
  563. th.execute_dao_propose_tx(
  564. holder,
  565. tx.clone(),
  566. &params,
  567. &fee_params,
  568. *current_block_height,
  569. true,
  570. )
  571. .await?;
  572. }
  573. th.assert_trees(&HOLDERS);
  574. *current_block_height += 1;
  575. // =====================================
  576. // Dao::Vote
  577. // Proposal is accepted. Start the vote.
  578. // =====================================
  579. info!("[Alice] Building generic vote tx (yes)");
  580. let (alice_vote_tx, alice_vote_params, alice_vote_fee_params) =
  581. th.dao_vote(&Holder::Alice, true, dao, &proposal_info, *current_block_height).await?;
  582. info!("[Bob] Building generic vote tx (no)");
  583. let (bob_vote_tx, bob_vote_params, bob_vote_fee_params) =
  584. th.dao_vote(&Holder::Bob, false, dao, &proposal_info, *current_block_height).await?;
  585. info!("[Charlie] Building generic vote tx (no)");
  586. let (charlie_vote_tx, charlie_vote_params, charlie_vote_fee_params) =
  587. th.dao_vote(&Holder::Charlie, true, dao, &proposal_info, *current_block_height).await?;
  588. for holder in &HOLDERS {
  589. info!("[{holder:?}] Executing Alice generic vote tx");
  590. th.execute_dao_vote_tx(
  591. holder,
  592. alice_vote_tx.clone(),
  593. &alice_vote_fee_params,
  594. *current_block_height,
  595. true,
  596. )
  597. .await?;
  598. info!("[{holder:?}] Executing Bob generic vote tx");
  599. th.execute_dao_vote_tx(
  600. holder,
  601. bob_vote_tx.clone(),
  602. &bob_vote_fee_params,
  603. *current_block_height,
  604. true,
  605. )
  606. .await?;
  607. info!("[{holder:?}] Executing Charlie generic vote tx");
  608. th.execute_dao_vote_tx(
  609. holder,
  610. charlie_vote_tx.clone(),
  611. &charlie_vote_fee_params,
  612. *current_block_height,
  613. true,
  614. )
  615. .await?;
  616. }
  617. th.assert_trees(&HOLDERS);
  618. // Gather and decrypt all generic vote notes
  619. let vote_note_1 = alice_vote_params.note.decrypt_unsafe(dao_votes_secret_key).unwrap();
  620. let vote_note_2 = bob_vote_params.note.decrypt_unsafe(dao_votes_secret_key).unwrap();
  621. let vote_note_3 = charlie_vote_params.note.decrypt_unsafe(dao_votes_secret_key).unwrap();
  622. // Count the votes
  623. let (total_yes_vote_value, total_all_vote_value, total_yes_vote_blind, total_all_vote_blind) =
  624. count_votes(&[
  625. (vote_note_1, alice_vote_params),
  626. (vote_note_2, bob_vote_params),
  627. (vote_note_3, charlie_vote_params),
  628. ]);
  629. // Wait until proposal has expired
  630. if dao_early_exec_secret_key.is_none() {
  631. let mut current_blockwindow = creation_blockwindow;
  632. while current_blockwindow <= creation_blockwindow + PROPOSAL_DURATION_BLOCKWINDOW {
  633. *current_block_height += 1;
  634. current_blockwindow = blockwindow(*current_block_height, block_target);
  635. }
  636. }
  637. // ================
  638. // Dao::Exec
  639. // Execute the vote
  640. // ================
  641. info!("[Dao] Building generic Dao::Exec tx");
  642. let (exec_tx, exec_fee_params) = th
  643. .dao_exec_generic(
  644. &Holder::Alice,
  645. dao,
  646. dao_exec_secret_key,
  647. dao_early_exec_secret_key,
  648. &proposal_info,
  649. total_yes_vote_value,
  650. total_all_vote_value,
  651. total_yes_vote_blind,
  652. total_all_vote_blind,
  653. *current_block_height,
  654. )
  655. .await?;
  656. for holder in &HOLDERS {
  657. info!("[{holder:?}] Executing generic Dao::Exec tx");
  658. th.execute_dao_exec_tx(
  659. holder,
  660. exec_tx.clone(),
  661. None,
  662. &exec_fee_params,
  663. *current_block_height,
  664. true,
  665. )
  666. .await?;
  667. }
  668. th.assert_trees(&HOLDERS);
  669. *current_block_height += 1;
  670. Ok(())
  671. }
  672. /// Auxiliary function to count proposal votes.
  673. fn count_votes(
  674. votes: &[([pallas::Base; 4], DaoVoteParams)],
  675. ) -> (u64, u64, ScalarBlind, ScalarBlind) {
  676. let mut total_yes_vote_value = 0;
  677. let mut total_all_vote_value = 0;
  678. let mut blind_total_vote = DaoBlindAggregateVote::default();
  679. let mut total_yes_vote_blind = Blind::ZERO;
  680. let mut total_all_vote_blind = Blind::ZERO;
  681. for (i, (note, params)) in votes.iter().enumerate() {
  682. // Note format: [
  683. // vote_option,
  684. // yes_vote_blind,
  685. // all_vote_value_fp,
  686. // all_vote_blind,
  687. // ]
  688. let vote_option = fp_to_u64(note[0]).unwrap();
  689. let yes_vote_blind = Blind(fp_mod_fv(note[1]));
  690. let all_vote_value = fp_to_u64(note[2]).unwrap();
  691. let all_vote_blind = Blind(fp_mod_fv(note[3]));
  692. assert!(vote_option == 0 || vote_option == 1);
  693. total_yes_vote_blind += yes_vote_blind;
  694. total_all_vote_blind += all_vote_blind;
  695. // Update private values
  696. // vote_option is either 0 or 1
  697. let yes_vote_value = vote_option * all_vote_value;
  698. total_yes_vote_value += yes_vote_value;
  699. total_all_vote_value += all_vote_value;
  700. // Update public values
  701. let yes_vote_commit = params.yes_vote_commit;
  702. let all_vote_commit = params.inputs.iter().map(|i| i.vote_commit).sum();
  703. let blind_vote = DaoBlindAggregateVote { yes_vote_commit, all_vote_commit };
  704. blind_total_vote.aggregate(blind_vote);
  705. // Just for the debug
  706. let vote_result = match vote_option != 0 {
  707. true => "yes",
  708. false => "no",
  709. };
  710. info!("Voter {i} voted {vote_result} with {all_vote_value} tokens in vote");
  711. }
  712. info!("Vote outcome = {total_yes_vote_value} / {total_all_vote_value}");
  713. assert!(
  714. blind_total_vote.all_vote_commit ==
  715. pedersen_commitment_u64(total_all_vote_value, total_all_vote_blind)
  716. );
  717. assert!(
  718. blind_total_vote.yes_vote_commit ==
  719. pedersen_commitment_u64(total_yes_vote_value, total_yes_vote_blind)
  720. );
  721. (total_yes_vote_value, total_all_vote_value, total_yes_vote_blind, total_all_vote_blind)
  722. }