Просмотр исходного кода

contract/dao/tests/integration: decoupled test cases

skoupidi 1 год назад
Родитель
Сommit
f67b17508c
1 измененных файлов с 395 добавлено и 476 удалено
  1. 395 476
      src/contract/dao/tests/integration.rs

+ 395 - 476
src/contract/dao/tests/integration.rs

@@ -19,7 +19,7 @@
 use darkfi::Result;
 use darkfi_contract_test_harness::{init_logger, Holder, TestHarness};
 use darkfi_dao_contract::{
-    model::{Dao, DaoBlindAggregateVote},
+    model::{Dao, DaoBlindAggregateVote, DaoVoteParams},
     DaoFunction,
 };
 use darkfi_money_contract::{
@@ -31,25 +31,40 @@ use darkfi_sdk::{
         pasta_prelude::*,
         pedersen_commitment_u64, poseidon_hash,
         util::{fp_mod_fv, fp_to_u64},
-        BaseBlind, Blind, FuncId, FuncRef, DAO_CONTRACT_ID, MONEY_CONTRACT_ID,
+        BaseBlind, Blind, FuncId, FuncRef, Keypair, ScalarBlind, DAO_CONTRACT_ID,
+        MONEY_CONTRACT_ID,
     },
     pasta::pallas,
 };
 use log::info;
 use rand::rngs::OsRng;
 
+// Integration test configuration
+// Holders this test will use:
+// * Alice, Bob, and Charlie are members of the DAO.
+// * Dao is the DAO wallet
+// * Rachel is the transfer proposal recipient.
+const HOLDERS: [Holder; 5] =
+    [Holder::Alice, Holder::Bob, Holder::Charlie, Holder::Dao, Holder::Rachel];
+// DAO gov tokens distribution
+const ALICE_GOV_SUPPLY: u64 = 100_000_000;
+const BOB_GOV_SUPPLY: u64 = 100_000_000;
+const CHARLIE_GOV_SUPPLY: u64 = 100_000_000;
+// DRK token, the treasury token, supply
+const DRK_TOKEN_SUPPLY: u64 = 1_000_000_000;
+// DAO parameters configuration
+const PROPOSER_LIMIT: u64 = 100_000_000;
+const QUORUM: u64 = 200_000_000;
+const APPROVAL_RATIO_BASE: u64 = 2;
+const APPROVAL_RATIO_QUOT: u64 = 1;
+// The tokens we want to send via the transfer proposal
+const TRANSFER_PROPOSAL_AMOUNT: u64 = 250_000_000;
+
 #[test]
 fn integration_test() -> Result<()> {
     smol::block_on(async {
         init_logger();
 
-        // Holders this test will use:
-        // * Alice, Bob, and Charlie are members of the DAO.
-        // * Dao is the DAO wallet
-        // * Rachel is the proposal recipient.
-        const HOLDERS: [Holder; 5] =
-            [Holder::Alice, Holder::Bob, Holder::Charlie, Holder::Dao, Holder::Rachel];
-
         // Initialize harness
         let mut th = TestHarness::new(&HOLDERS, false).await?;
 
@@ -71,25 +86,16 @@ fn integration_test() -> Result<()> {
         };
         let gov_token_id = token_attrs.to_token_id();
 
-        const ALICE_GOV_SUPPLY: u64 = 100_000_000;
-        const BOB_GOV_SUPPLY: u64 = 100_000_000;
-        const CHARLIE_GOV_SUPPLY: u64 = 100_000_000;
-        // And the DRK token as the treasury token
-        let drk_token_id = *DARK_TOKEN_ID;
-        const DRK_TOKEN_SUPPLY: u64 = 1_000_000_000;
-        // The tokens we want to send via the transfer proposal
-        const TRANSFER_PROPOSAL_AMOUNT: u64 = 250_000_000;
-
         // Block height to verify against
         let mut current_block_height = 0;
 
         // DAO parameters
         let dao_keypair = th.holders.get(&Holder::Dao).unwrap().keypair;
         let dao = Dao {
-            proposer_limit: 100_000_000,
-            quorum: 200_000_000,
-            approval_ratio_base: 2,
-            approval_ratio_quot: 1,
+            proposer_limit: PROPOSER_LIMIT,
+            quorum: QUORUM,
+            approval_ratio_base: APPROVAL_RATIO_BASE,
+            approval_ratio_quot: APPROVAL_RATIO_QUOT,
             gov_token_id,
             public_key: dao_keypair.public,
             bulla_blind: Blind::random(&mut OsRng),
@@ -99,7 +105,6 @@ fn integration_test() -> Result<()> {
         // Airdrop some treasury tokens to the DAO
         // =======================================
         info!("[Dao] Building DAO airdrop tx");
-        assert_eq!(current_block_height, 0);
 
         let spend_hook =
             FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }
@@ -138,8 +143,6 @@ fn integration_test() -> Result<()> {
         // Dao::Mint
         // Create the DAO bulla
         // ====================
-        info!("Stage 1. Creating DAO bulla");
-
         info!("[Dao] Building DAO mint tx");
         let (dao_mint_tx, dao_mint_params, fee_params) =
             th.dao_mint(&Holder::Alice, &dao, &dao_keypair, current_block_height).await?;
@@ -164,8 +167,6 @@ fn integration_test() -> Result<()> {
         // ======================================
         // Mint the governance token to 3 holders
         // ======================================
-        info!("Stage 3. Minting governance token");
-
         info!("[Alice] Building governance token mint tx for Alice");
         let (a_token_mint_tx, a_token_mint_params, a_auth_token_mint_params, a_fee_params) = th
             .token_mint(
@@ -270,466 +271,384 @@ fn integration_test() -> Result<()> {
 
         current_block_height += 1;
 
-        // ================
-        // Dao::Propose
-        // Propose the votes
-        // ================
-        info!("Stage 4. Propose the votes");
         // We can add whatever we want in here, even arbitrary text
         // It's up to the auth module to decide what to do with it.
         let user_data = pallas::Base::ZERO;
 
-        info!("[Alice] Building DAO generic proposal tx");
-        let (
-            propose_generic_tx,
-            propose_generic_params,
-            propose_generic_fee_params,
-            propose_generic_info,
-        ) = th.dao_propose_generic(&Holder::Alice, user_data, &dao, current_block_height).await?;
-
-        // TODO: look into proposal expiry once time for voting has finished
-        // TODO: Is it possible for an invalid transfer() to be constructed on exec()?
-        //       Need to look into this.
-        info!("[Alice] Building DAO transfer proposal tx");
-
-        // These coins are passed around to all DAO members who verify its validity
-        // They also check hashing them equals the proposal_commit
-        let transfer_proposal_coinattrs = vec![CoinAttributes {
-            public_key: th.holders.get(&Holder::Rachel).unwrap().keypair.public,
-            value: TRANSFER_PROPOSAL_AMOUNT,
-            token_id: drk_token_id,
-            spend_hook: FuncId::none(),
-            user_data: pallas::Base::ZERO,
-            blind: Blind::random(&mut OsRng),
-        }];
-
-        let (
-            propose_transfer_tx,
-            propose_transfer_params,
-            propose_transfer_fee_params,
-            propose_transfer_info,
-        ) = th
-            .dao_propose_transfer(
-                &Holder::Alice,
-                &transfer_proposal_coinattrs,
-                user_data,
-                &dao,
-                current_block_height,
-            )
-            .await?;
-
-        for holder in &HOLDERS {
-            info!("[{holder:?}] Executing DAO generic proposal tx");
-            th.execute_dao_propose_tx(
-                holder,
-                propose_generic_tx.clone(),
-                &propose_generic_params,
-                &propose_generic_fee_params,
-                current_block_height,
-                true,
-            )
-            .await?;
-
-            info!("[{holder:?}] Executing DAO transfer proposal tx");
-            th.execute_dao_propose_tx(
-                holder,
-                propose_transfer_tx.clone(),
-                &propose_transfer_params,
-                &propose_transfer_fee_params,
-                current_block_height,
-                true,
-            )
-            .await?;
-        }
-
-        th.assert_trees(&HOLDERS);
-
-        current_block_height += 1;
-
-        // =====================================
-        // Dao::Vote
-        // Proposals are accepted. Start the votes.
-        // =====================================
-        info!("Stage 5. Start voting");
-
-        info!("[Alice] Building generic vote tx (yes)");
-        let (alice_generic_vote_tx, alice_generic_vote_params, alice_generic_vote_fee_params) = th
-            .dao_vote(
-                &Holder::Alice,
-                true,
-                &dao,
-                &dao_keypair,
-                &propose_generic_info,
-                current_block_height,
-            )
+        // ============================
+        // Execute proposals test cases
+        // ============================
+        execute_transfer_proposal(
+            &mut th,
+            &dao,
+            &dao_keypair,
+            user_data,
+            &mut current_block_height,
+        )
+        .await?;
+        execute_generic_proposal(&mut th, &dao, &dao_keypair, user_data, &mut current_block_height)
             .await?;
 
-        info!("[Alice] Building transfer vote tx (yes)");
-        let (alice_transfer_vote_tx, alice_transfer_vote_params, alice_transfer_vote_fee_params) =
-            th.dao_vote(
-                &Holder::Alice,
-                true,
-                &dao,
-                &dao_keypair,
-                &propose_transfer_info,
-                current_block_height,
-            )
-            .await?;
-
-        info!("[Bob] Building generic vote tx (no)");
-        let (bob_generic_vote_tx, bob_generic_vote_params, bob_generic_vote_fee_params) = th
-            .dao_vote(
-                &Holder::Bob,
-                false,
-                &dao,
-                &dao_keypair,
-                &propose_generic_info,
-                current_block_height,
-            )
-            .await?;
-
-        info!("[Bob] Building transfer vote tx (no)");
-        let (bob_transfer_vote_tx, bob_transfer_vote_params, bob_transfer_vote_fee_params) = th
-            .dao_vote(
-                &Holder::Bob,
-                false,
-                &dao,
-                &dao_keypair,
-                &propose_transfer_info,
-                current_block_height,
-            )
-            .await?;
-
-        info!("[Charlie] Building generic vote tx (no)");
-        let (charlie_generic_vote_tx, charlie_generic_vote_params, charlie_generic_vote_fee_params) =
-            th.dao_vote(
-                &Holder::Charlie,
-                true,
-                &dao,
-                &dao_keypair,
-                &propose_generic_info,
-                current_block_height,
-            )
-            .await?;
-
-        info!("[Charlie] Building transfer vote tx (yes)");
-        let (
-            charlie_transfer_vote_tx,
-            charlie_transfer_vote_params,
-            charlie_transfer_vote_fee_params,
-        ) = th
-            .dao_vote(
-                &Holder::Charlie,
-                true,
-                &dao,
-                &dao_keypair,
-                &propose_transfer_info,
-                current_block_height,
-            )
-            .await?;
-
-        for holder in &HOLDERS {
-            info!("[{holder:?}] Executing Alice generic vote tx");
-            th.execute_dao_vote_tx(
-                holder,
-                alice_generic_vote_tx.clone(),
-                &alice_generic_vote_fee_params,
-                current_block_height,
-                true,
-            )
-            .await?;
-
-            info!("[{holder:?}] Executing Alice transfer vote tx");
-            th.execute_dao_vote_tx(
-                holder,
-                alice_transfer_vote_tx.clone(),
-                &alice_transfer_vote_fee_params,
-                current_block_height,
-                true,
-            )
-            .await?;
-
-            info!("[{holder:?}] Executing Bob generic vote tx");
-            th.execute_dao_vote_tx(
-                holder,
-                bob_generic_vote_tx.clone(),
-                &bob_generic_vote_fee_params,
-                current_block_height,
-                true,
-            )
-            .await?;
-
-            info!("[{holder:?}] Executing Bob transfer vote tx");
-            th.execute_dao_vote_tx(
-                holder,
-                bob_transfer_vote_tx.clone(),
-                &bob_transfer_vote_fee_params,
-                current_block_height,
-                true,
-            )
-            .await?;
-
-            info!("[{holder:?}] Executing Charlie generic vote tx");
-            th.execute_dao_vote_tx(
-                holder,
-                charlie_generic_vote_tx.clone(),
-                &charlie_generic_vote_fee_params,
-                current_block_height,
-                true,
-            )
-            .await?;
-
-            info!("[{holder:?}] Executing Charlie transfer vote tx");
-            th.execute_dao_vote_tx(
-                holder,
-                charlie_transfer_vote_tx.clone(),
-                &charlie_transfer_vote_fee_params,
-                current_block_height,
-                true,
-            )
-            .await?;
-        }
-
-        // Gather and decrypt all generic vote notes
-        let vote_note_1 =
-            alice_generic_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
-        let vote_note_2 = bob_generic_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
-        let vote_note_3 =
-            charlie_generic_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
-
-        // Count the votes
-        let mut total_yes_generic_vote_value = 0;
-        let mut total_all_generic_vote_value = 0;
-        let mut blind_total_generic_vote = DaoBlindAggregateVote::default();
-        let mut total_yes_generic_vote_blind = Blind::ZERO;
-        let mut total_all_generic_vote_blind = Blind::ZERO;
-
-        for (i, (note, params)) in [
-            (vote_note_1, alice_generic_vote_params),
-            (vote_note_2, bob_generic_vote_params),
-            (vote_note_3, charlie_generic_vote_params),
-        ]
-        .iter()
-        .enumerate()
-        {
-            // Note format: [
-            //   vote_option,
-            //   yes_vote_blind,
-            //   all_vote_value_fp,
-            //   all_vote_blind,
-            // ]
-            let vote_option = fp_to_u64(note[0]).unwrap();
-            let yes_vote_blind = Blind(fp_mod_fv(note[1]));
-            let all_vote_value = fp_to_u64(note[2]).unwrap();
-            let all_vote_blind = Blind(fp_mod_fv(note[3]));
-            assert!(vote_option == 0 || vote_option == 1);
-
-            total_yes_generic_vote_blind += yes_vote_blind;
-            total_all_generic_vote_blind += all_vote_blind;
-
-            // Update private values
-            // vote_option is either 0 or 1
-            let yes_vote_value = vote_option * all_vote_value;
-            total_yes_generic_vote_value += yes_vote_value;
-            total_all_generic_vote_value += all_vote_value;
-
-            // Update public values
-            let yes_vote_commit = params.yes_vote_commit;
-            let all_vote_commit = params.inputs.iter().map(|i| i.vote_commit).sum();
-            let blind_vote = DaoBlindAggregateVote { yes_vote_commit, all_vote_commit };
-            blind_total_generic_vote.aggregate(blind_vote);
-
-            // Just for the debug
-            let vote_result = match vote_option != 0 {
-                true => "yes",
-                false => "no",
-            };
-            info!(
-                "Voter {} voted {} with {} tokens in generic vote",
-                i, vote_result, all_vote_value
-            );
-        }
-
-        info!(
-            "Generic vote outcome = {} / {}",
-            total_yes_generic_vote_value, total_all_generic_vote_value
-        );
-
-        assert!(
-            blind_total_generic_vote.all_vote_commit ==
-                pedersen_commitment_u64(
-                    total_all_generic_vote_value,
-                    total_all_generic_vote_blind
-                )
-        );
-
-        assert!(
-            blind_total_generic_vote.yes_vote_commit ==
-                pedersen_commitment_u64(
-                    total_yes_generic_vote_value,
-                    total_yes_generic_vote_blind
-                )
-        );
-
-        // Gather and decrypt all transfer vote notes
-        let vote_note_1 =
-            alice_transfer_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
-        let vote_note_2 =
-            bob_transfer_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
-        let vote_note_3 =
-            charlie_transfer_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
-
-        // Count the votes
-        let mut total_yes_transfer_vote_value = 0;
-        let mut total_all_transfer_vote_value = 0;
-        let mut blind_total_transfer_vote = DaoBlindAggregateVote::default();
-        let mut total_yes_transfer_vote_blind = Blind::ZERO;
-        let mut total_all_transfer_vote_blind = Blind::ZERO;
-
-        for (i, (note, params)) in [
-            (vote_note_1, alice_transfer_vote_params),
-            (vote_note_2, bob_transfer_vote_params),
-            (vote_note_3, charlie_transfer_vote_params),
-        ]
-        .iter()
-        .enumerate()
-        {
-            // Note format: [
-            //   vote_option,
-            //   yes_vote_blind,
-            //   all_vote_value_fp,
-            //   all_vote_blind,
-            // ]
-            let vote_option = fp_to_u64(note[0]).unwrap();
-            let yes_vote_blind = Blind(fp_mod_fv(note[1]));
-            let all_vote_value = fp_to_u64(note[2]).unwrap();
-            let all_vote_blind = Blind(fp_mod_fv(note[3]));
-            assert!(vote_option == 0 || vote_option == 1);
-
-            total_yes_transfer_vote_blind += yes_vote_blind;
-            total_all_transfer_vote_blind += all_vote_blind;
-
-            // Update private values
-            // vote_option is either 0 or 1
-            let yes_vote_value = vote_option * all_vote_value;
-            total_yes_transfer_vote_value += yes_vote_value;
-            total_all_transfer_vote_value += all_vote_value;
-
-            // Update public values
-            let yes_vote_commit = params.yes_vote_commit;
-            let all_vote_commit = params.inputs.iter().map(|i| i.vote_commit).sum();
-            let blind_vote = DaoBlindAggregateVote { yes_vote_commit, all_vote_commit };
-            blind_total_transfer_vote.aggregate(blind_vote);
-
-            // Just for the debug
-            let vote_result = match vote_option != 0 {
-                true => "yes",
-                false => "no",
-            };
-            info!(
-                "Voter {} voted {} with {} tokens in transfer vote",
-                i, vote_result, all_vote_value
-            );
-        }
-
-        info!(
-            "Transfer vote outcome = {} / {}",
-            total_yes_transfer_vote_value, total_all_transfer_vote_value
-        );
-
-        assert!(
-            blind_total_transfer_vote.all_vote_commit ==
-                pedersen_commitment_u64(
-                    total_all_transfer_vote_value,
-                    total_all_transfer_vote_blind
-                )
-        );
-
-        assert!(
-            blind_total_transfer_vote.yes_vote_commit ==
-                pedersen_commitment_u64(
-                    total_yes_transfer_vote_value,
-                    total_yes_transfer_vote_blind
-                )
-        );
-
-        th.assert_trees(&HOLDERS);
-
-        current_block_height += 1;
-
-        // ================
-        // Dao::Exec
-        // Execute the votes
-        // ================
-        info!("Stage 6. Execute the votes");
-
-        info!("[Dao] Building generic Dao::Exec tx");
-        let (exec_generic_tx, exec_generic_fee_params) = th
-            .dao_exec_generic(
-                &Holder::Alice,
-                &dao,
-                &propose_generic_info,
-                total_yes_generic_vote_value,
-                total_all_generic_vote_value,
-                total_yes_generic_vote_blind,
-                total_all_generic_vote_blind,
-                current_block_height,
-            )
-            .await?;
+        // Thanks for reading
+        Ok(())
+    })
+}
 
-        info!("[Dao] Building transfer Dao::Exec tx");
-        let (exec_transfer_tx, xfer_params, exec_transfer_fee_params) = th
-            .dao_exec_transfer(
-                &Holder::Alice,
-                &dao,
-                &propose_transfer_info,
-                transfer_proposal_coinattrs,
-                total_yes_transfer_vote_value,
-                total_all_transfer_vote_value,
-                total_yes_transfer_vote_blind,
-                total_all_transfer_vote_blind,
-                current_block_height,
-            )
-            .await?;
+/// Test case:
+/// Generate a transfer proposal and execute it after voting passes.
+async fn execute_transfer_proposal(
+    th: &mut TestHarness,
+    dao: &Dao,
+    dao_keypair: &Keypair,
+    user_data: pallas::Base,
+    current_block_height: &mut u32,
+) -> Result<()> {
+    // ================
+    // Dao::Propose
+    // Propose the vote
+    // ================
+    info!("[Alice] Building DAO transfer proposal tx");
+
+    // These coins are passed around to all DAO members who verify its validity
+    // They also check hashing them equals the proposal_commit
+    let proposal_coinattrs = vec![CoinAttributes {
+        public_key: th.holders.get(&Holder::Rachel).unwrap().keypair.public,
+        value: TRANSFER_PROPOSAL_AMOUNT,
+        token_id: *DARK_TOKEN_ID,
+        spend_hook: FuncId::none(),
+        user_data: pallas::Base::ZERO,
+        blind: Blind::random(&mut OsRng),
+    }];
+
+    let (tx, params, fee_params, proposal_info) = th
+        .dao_propose_transfer(
+            &Holder::Alice,
+            &proposal_coinattrs,
+            user_data,
+            dao,
+            *current_block_height,
+        )
+        .await?;
+
+    for holder in &HOLDERS {
+        info!("[{holder:?}] Executing DAO transfer proposal tx");
+        th.execute_dao_propose_tx(
+            holder,
+            tx.clone(),
+            &params,
+            &fee_params,
+            *current_block_height,
+            true,
+        )
+        .await?;
+    }
+    th.assert_trees(&HOLDERS);
+    *current_block_height += 1;
+
+    // =====================================
+    // Dao::Vote
+    // Proposal is accepted. Start the vote.
+    // =====================================
+    info!("[Alice] Building transfer vote tx (yes)");
+    let (alice_vote_tx, alice_vote_params, alice_vote_fee_params) = th
+        .dao_vote(&Holder::Alice, true, dao, dao_keypair, &proposal_info, *current_block_height)
+        .await?;
+
+    info!("[Bob] Building transfer vote tx (no)");
+    let (bob_vote_tx, bob_vote_params, bob_vote_fee_params) = th
+        .dao_vote(&Holder::Bob, false, dao, dao_keypair, &proposal_info, *current_block_height)
+        .await?;
+
+    info!("[Charlie] Building transfer vote tx (yes)");
+    let (charlie_vote_tx, charlie_vote_params, charlie_vote_fee_params) = th
+        .dao_vote(&Holder::Charlie, true, dao, dao_keypair, &proposal_info, *current_block_height)
+        .await?;
+
+    for holder in &HOLDERS {
+        info!("[{holder:?}] Executing Alice transfer vote tx");
+        th.execute_dao_vote_tx(
+            holder,
+            alice_vote_tx.clone(),
+            &alice_vote_fee_params,
+            *current_block_height,
+            true,
+        )
+        .await?;
+
+        info!("[{holder:?}] Executing Bob transfer vote tx");
+        th.execute_dao_vote_tx(
+            holder,
+            bob_vote_tx.clone(),
+            &bob_vote_fee_params,
+            *current_block_height,
+            true,
+        )
+        .await?;
+
+        info!("[{holder:?}] Executing Charlie transfer vote tx");
+        th.execute_dao_vote_tx(
+            holder,
+            charlie_vote_tx.clone(),
+            &charlie_vote_fee_params,
+            *current_block_height,
+            true,
+        )
+        .await?;
+    }
+    th.assert_trees(&HOLDERS);
+    *current_block_height += 1;
+
+    // Gather and decrypt all generic vote notes
+    let vote_note_1 = alice_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
+    let vote_note_2 = bob_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
+    let vote_note_3 = charlie_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
+
+    // Count the votes
+    let (total_yes_vote_value, total_all_vote_value, total_yes_vote_blind, total_all_vote_blind) =
+        count_votes(&[
+            (vote_note_1, alice_vote_params),
+            (vote_note_2, bob_vote_params),
+            (vote_note_3, charlie_vote_params),
+        ]);
+
+    // ================
+    // Dao::Exec
+    // Execute the vote
+    // ================
+    info!("[Dao] Building transfer Dao::Exec tx");
+    let (exec_tx, xfer_params, exec_fee_params) = th
+        .dao_exec_transfer(
+            &Holder::Alice,
+            dao,
+            &proposal_info,
+            proposal_coinattrs,
+            total_yes_vote_value,
+            total_all_vote_value,
+            total_yes_vote_blind,
+            total_all_vote_blind,
+            *current_block_height,
+        )
+        .await?;
+
+    for holder in &HOLDERS {
+        info!("[{holder:?}] Executing transfer Dao::Exec tx");
+        th.execute_dao_exec_tx(
+            holder,
+            exec_tx.clone(),
+            Some(&xfer_params),
+            &exec_fee_params,
+            *current_block_height,
+            true,
+        )
+        .await?;
+    }
+    th.assert_trees(&HOLDERS);
+    *current_block_height += 1;
+
+    let rachel_wallet = th.holders.get(&Holder::Rachel).unwrap();
+    assert!(rachel_wallet.unspent_money_coins[0].note.value == TRANSFER_PROPOSAL_AMOUNT);
+    assert!(rachel_wallet.unspent_money_coins[0].note.token_id == *DARK_TOKEN_ID);
+
+    let dao_wallet = th.holders.get(&Holder::Dao).unwrap();
+    assert!(
+        dao_wallet.unspent_money_coins[0].note.value == DRK_TOKEN_SUPPLY - TRANSFER_PROPOSAL_AMOUNT
+    );
+    assert!(dao_wallet.unspent_money_coins[0].note.token_id == *DARK_TOKEN_ID);
+
+    Ok(())
+}
 
-        for holder in &HOLDERS {
-            info!("[{holder:?}] Executing generic Dao::Exec tx");
-            th.execute_dao_exec_tx(
-                holder,
-                exec_generic_tx.clone(),
-                None,
-                &exec_generic_fee_params,
-                current_block_height,
-                true,
-            )
-            .await?;
+/// Test case:
+/// Generate a generic proposal and execute it after voting passes.
+async fn execute_generic_proposal(
+    th: &mut TestHarness,
+    dao: &Dao,
+    dao_keypair: &Keypair,
+    user_data: pallas::Base,
+    current_block_height: &mut u32,
+) -> Result<()> {
+    // ================
+    // Dao::Propose
+    // Propose the vote
+    // ================
+    info!("[Alice] Building DAO generic proposal tx");
+    let (tx, params, fee_params, proposal_info) =
+        th.dao_propose_generic(&Holder::Alice, user_data, dao, *current_block_height).await?;
+
+    for holder in &HOLDERS {
+        info!("[{holder:?}] Executing DAO generic proposal tx");
+        th.execute_dao_propose_tx(
+            holder,
+            tx.clone(),
+            &params,
+            &fee_params,
+            *current_block_height,
+            true,
+        )
+        .await?;
+    }
+    th.assert_trees(&HOLDERS);
+    *current_block_height += 1;
+
+    // =====================================
+    // Dao::Vote
+    // Proposal is accepted. Start the vote.
+    // =====================================
+    info!("[Alice] Building generic vote tx (yes)");
+    let (alice_vote_tx, alice_vote_params, alice_vote_fee_params) = th
+        .dao_vote(&Holder::Alice, true, dao, dao_keypair, &proposal_info, *current_block_height)
+        .await?;
+
+    info!("[Bob] Building generic vote tx (no)");
+    let (bob_vote_tx, bob_vote_params, bob_vote_fee_params) = th
+        .dao_vote(&Holder::Bob, false, dao, dao_keypair, &proposal_info, *current_block_height)
+        .await?;
+
+    info!("[Charlie] Building generic vote tx (no)");
+    let (charlie_vote_tx, charlie_vote_params, charlie_vote_fee_params) = th
+        .dao_vote(&Holder::Charlie, true, dao, dao_keypair, &proposal_info, *current_block_height)
+        .await?;
+
+    for holder in &HOLDERS {
+        info!("[{holder:?}] Executing Alice generic vote tx");
+        th.execute_dao_vote_tx(
+            holder,
+            alice_vote_tx.clone(),
+            &alice_vote_fee_params,
+            *current_block_height,
+            true,
+        )
+        .await?;
+
+        info!("[{holder:?}] Executing Bob generic vote tx");
+        th.execute_dao_vote_tx(
+            holder,
+            bob_vote_tx.clone(),
+            &bob_vote_fee_params,
+            *current_block_height,
+            true,
+        )
+        .await?;
+
+        info!("[{holder:?}] Executing Charlie generic vote tx");
+        th.execute_dao_vote_tx(
+            holder,
+            charlie_vote_tx.clone(),
+            &charlie_vote_fee_params,
+            *current_block_height,
+            true,
+        )
+        .await?;
+    }
+    th.assert_trees(&HOLDERS);
+    *current_block_height += 1;
+
+    // Gather and decrypt all generic vote notes
+    let vote_note_1 = alice_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
+    let vote_note_2 = bob_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
+    let vote_note_3 = charlie_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
+
+    // Count the votes
+    let (total_yes_vote_value, total_all_vote_value, total_yes_vote_blind, total_all_vote_blind) =
+        count_votes(&[
+            (vote_note_1, alice_vote_params),
+            (vote_note_2, bob_vote_params),
+            (vote_note_3, charlie_vote_params),
+        ]);
+
+    // ================
+    // Dao::Exec
+    // Execute the vote
+    // ================
+    info!("[Dao] Building generic Dao::Exec tx");
+    let (exec_tx, exec_fee_params) = th
+        .dao_exec_generic(
+            &Holder::Alice,
+            dao,
+            &proposal_info,
+            total_yes_vote_value,
+            total_all_vote_value,
+            total_yes_vote_blind,
+            total_all_vote_blind,
+            *current_block_height,
+        )
+        .await?;
+
+    for holder in &HOLDERS {
+        info!("[{holder:?}] Executing generic Dao::Exec tx");
+        th.execute_dao_exec_tx(
+            holder,
+            exec_tx.clone(),
+            None,
+            &exec_fee_params,
+            *current_block_height,
+            true,
+        )
+        .await?;
+    }
+    th.assert_trees(&HOLDERS);
+    *current_block_height += 1;
+
+    Ok(())
+}
 
-            info!("[{holder:?}] Executing transfer Dao::Exec tx");
-            th.execute_dao_exec_tx(
-                holder,
-                exec_transfer_tx.clone(),
-                Some(&xfer_params),
-                &exec_transfer_fee_params,
-                current_block_height,
-                true,
-            )
-            .await?;
-        }
+/// Auxiliary function to count proposal votes.
+fn count_votes(
+    votes: &[([pallas::Base; 4], DaoVoteParams)],
+) -> (u64, u64, ScalarBlind, ScalarBlind) {
+    let mut total_yes_vote_value = 0;
+    let mut total_all_vote_value = 0;
+    let mut blind_total_vote = DaoBlindAggregateVote::default();
+    let mut total_yes_vote_blind = Blind::ZERO;
+    let mut total_all_vote_blind = Blind::ZERO;
+
+    for (i, (note, params)) in votes.iter().enumerate() {
+        // Note format: [
+        //   vote_option,
+        //   yes_vote_blind,
+        //   all_vote_value_fp,
+        //   all_vote_blind,
+        // ]
+        let vote_option = fp_to_u64(note[0]).unwrap();
+        let yes_vote_blind = Blind(fp_mod_fv(note[1]));
+        let all_vote_value = fp_to_u64(note[2]).unwrap();
+        let all_vote_blind = Blind(fp_mod_fv(note[3]));
+        assert!(vote_option == 0 || vote_option == 1);
+
+        total_yes_vote_blind += yes_vote_blind;
+        total_all_vote_blind += all_vote_blind;
+
+        // Update private values
+        // vote_option is either 0 or 1
+        let yes_vote_value = vote_option * all_vote_value;
+        total_yes_vote_value += yes_vote_value;
+        total_all_vote_value += all_vote_value;
+
+        // Update public values
+        let yes_vote_commit = params.yes_vote_commit;
+        let all_vote_commit = params.inputs.iter().map(|i| i.vote_commit).sum();
+        let blind_vote = DaoBlindAggregateVote { yes_vote_commit, all_vote_commit };
+        blind_total_vote.aggregate(blind_vote);
+
+        // Just for the debug
+        let vote_result = match vote_option != 0 {
+            true => "yes",
+            false => "no",
+        };
+        info!("Voter {} voted {} with {} tokens in vote", i, vote_result, all_vote_value);
+    }
 
-        th.assert_trees(&HOLDERS);
+    info!("Vote outcome = {} / {}", total_yes_vote_value, total_all_vote_value);
 
-        let rachel_wallet = th.holders.get(&Holder::Rachel).unwrap();
-        assert!(rachel_wallet.unspent_money_coins[0].note.value == TRANSFER_PROPOSAL_AMOUNT);
-        assert!(rachel_wallet.unspent_money_coins[0].note.token_id == drk_token_id);
+    assert!(
+        blind_total_vote.all_vote_commit ==
+            pedersen_commitment_u64(total_all_vote_value, total_all_vote_blind)
+    );
 
-        let dao_wallet = th.holders.get(&Holder::Dao).unwrap();
-        assert!(
-            dao_wallet.unspent_money_coins[0].note.value ==
-                DRK_TOKEN_SUPPLY - TRANSFER_PROPOSAL_AMOUNT
-        );
-        assert!(dao_wallet.unspent_money_coins[0].note.token_id == drk_token_id);
+    assert!(
+        blind_total_vote.yes_vote_commit ==
+            pedersen_commitment_u64(total_yes_vote_value, total_yes_vote_blind)
+    );
 
-        // Thanks for reading
-        Ok(())
-    })
+    (total_yes_vote_value, total_all_vote_value, total_yes_vote_blind, total_all_vote_blind)
 }