Przeglądaj źródła

contract/dao/tests/integration: added a generic(without a xfer) proposal execution

skoupidi 1 rok temu
rodzic
commit
9da05dd9b6

+ 317 - 76
src/contract/dao/tests/integration.rs

@@ -77,8 +77,8 @@ fn integration_test() -> Result<()> {
         // 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 proposal
-        const PROPOSAL_AMOUNT: u64 = 250_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;
@@ -87,7 +87,7 @@ fn integration_test() -> Result<()> {
         let dao_keypair = th.holders.get(&Holder::Dao).unwrap().keypair;
         let dao = Dao {
             proposer_limit: 100_000_000,
-            quorum: 199_999_999,
+            quorum: 200_000_000,
             approval_ratio_base: 2,
             approval_ratio_quot: 1,
             gov_token_id,
@@ -272,39 +272,70 @@ fn integration_test() -> Result<()> {
 
         // ================
         // Dao::Propose
-        // Propose the vote
+        // Propose the votes
         // ================
-        info!("Stage 4. Propose the vote");
+        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 proposal tx");
+        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 {
+        let transfer_proposal_coinattrs = vec![CoinAttributes {
             public_key: th.holders.get(&Holder::Rachel).unwrap().keypair.public,
-            value: PROPOSAL_AMOUNT,
+            value: TRANSFER_PROPOSAL_AMOUNT,
             token_id: drk_token_id,
             spend_hook: FuncId::none(),
             user_data: pallas::Base::ZERO,
             blind: Blind::random(&mut OsRng),
         }];
-        // 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;
 
-        let (propose_tx, (propose_params, fee_params), propose_info) = th
-            .dao_propose(&Holder::Alice, &proposal_coinattrs, user_data, &dao, current_block_height)
+        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 proposal tx");
+            info!("[{holder:?}] Executing DAO generic proposal tx");
             th.execute_dao_propose_tx(
                 holder,
-                propose_tx.clone(),
-                &propose_params,
-                &fee_params,
+                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,
             )
@@ -317,80 +348,166 @@ fn integration_test() -> Result<()> {
 
         // =====================================
         // Dao::Vote
-        // Proposal is accepted. Start the vote.
+        // Proposals are accepted. Start the votes.
         // =====================================
         info!("Stage 5. Start voting");
 
-        info!("[Alice] Building vote tx (yes)");
-        let (alice_vote_tx, alice_vote_params, alice_vote_fee_params) = th
-            .dao_vote(&Holder::Alice, true, &dao, &dao_keypair, &propose_info, current_block_height)
+        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,
+            )
             .await?;
 
-        info!("[Bob] Building vote tx (no)");
-        let (bob_vote_tx, bob_vote_params, bob_vote_fee_params) = th
-            .dao_vote(&Holder::Bob, false, &dao, &dao_keypair, &propose_info, current_block_height)
+        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!("[Charlie] Building vote tx (yes)");
-        let (charlie_vote_tx, charlie_vote_params, charlie_vote_fee_params) = th
+        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_info,
+                &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 vote tx");
+            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,
-                alice_vote_tx.clone(),
-                &alice_vote_fee_params,
+                bob_generic_vote_tx.clone(),
+                &bob_generic_vote_fee_params,
                 current_block_height,
                 true,
             )
             .await?;
 
-            info!("[{holder:?}] Executing Bob vote tx");
+            info!("[{holder:?}] Executing Bob transfer vote tx");
             th.execute_dao_vote_tx(
                 holder,
-                bob_vote_tx.clone(),
-                &bob_vote_fee_params,
+                bob_transfer_vote_tx.clone(),
+                &bob_transfer_vote_fee_params,
                 current_block_height,
                 true,
             )
             .await?;
 
-            info!("[{holder:?}] Executing Charlie vote tx");
+            info!("[{holder:?}] Executing Charlie generic vote tx");
             th.execute_dao_vote_tx(
                 holder,
-                charlie_vote_tx.clone(),
-                &charlie_vote_fee_params,
+                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 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();
+        // 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_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;
+        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_vote_params),
-            (vote_note_2, bob_vote_params),
-            (vote_note_3, charlie_vote_params),
+            (vote_note_1, alice_generic_vote_params),
+            (vote_note_2, bob_generic_vote_params),
+            (vote_note_3, charlie_generic_vote_params),
         ]
         .iter()
         .enumerate()
@@ -407,71 +524,192 @@ fn integration_test() -> Result<()> {
             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;
+            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_vote_value += yes_vote_value;
-            total_all_vote_value += 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_vote.aggregate(blind_vote);
+            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", i, vote_result, all_vote_value);
+            info!(
+                "Voter {} voted {} with {} tokens in generic vote",
+                i, vote_result, all_vote_value
+            );
         }
 
-        info!("Outcome = {} / {}", total_yes_vote_value, total_all_vote_value);
+        info!(
+            "Generic vote outcome = {} / {}",
+            total_yes_generic_vote_value, total_all_generic_vote_value
+        );
 
         assert!(
-            blind_total_vote.all_vote_commit ==
-                pedersen_commitment_u64(total_all_vote_value, total_all_vote_blind)
+            blind_total_generic_vote.all_vote_commit ==
+                pedersen_commitment_u64(
+                    total_all_generic_vote_value,
+                    total_all_generic_vote_blind
+                )
         );
 
         assert!(
-            blind_total_vote.yes_vote_commit ==
-                pedersen_commitment_u64(total_yes_vote_value, total_yes_vote_blind)
+            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 vote
+        // Execute the votes
         // ================
-        info!("Stage 6. Execute the vote");
+        info!("Stage 6. Execute the votes");
 
-        info!("[Dao] Building Dao::Exec tx");
-        let (exec_tx, xfer_params, exec_fee_params) = th
-            .dao_exec(
+        info!("[Dao] Building generic Dao::Exec tx");
+        let (exec_generic_tx, exec_generic_fee_params) = th
+            .dao_exec_generic(
                 &Holder::Alice,
                 &dao,
-                &propose_info,
-                proposal_coinattrs,
-                total_yes_vote_value,
-                total_all_vote_value,
-                total_yes_vote_blind,
-                total_all_vote_blind,
+                &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?;
+
+        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?;
 
         for holder in &HOLDERS {
-            info!("[{holder:?}] Executing Dao::Exec tx");
+            info!("[{holder:?}] Executing generic Dao::Exec tx");
             th.execute_dao_exec_tx(
                 holder,
-                exec_tx.clone(),
-                &xfer_params,
-                &exec_fee_params,
+                exec_generic_tx.clone(),
+                None,
+                &exec_generic_fee_params,
+                current_block_height,
+                true,
+            )
+            .await?;
+
+            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,
             )
@@ -481,11 +719,14 @@ fn integration_test() -> Result<()> {
         th.assert_trees(&HOLDERS);
 
         let rachel_wallet = th.holders.get(&Holder::Rachel).unwrap();
-        assert!(rachel_wallet.unspent_money_coins[0].note.value == PROPOSAL_AMOUNT);
+        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);
 
         let dao_wallet = th.holders.get(&Holder::Dao).unwrap();
-        assert!(dao_wallet.unspent_money_coins[0].note.value == DRK_TOKEN_SUPPLY - PROPOSAL_AMOUNT);
+        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);
 
         // Thanks for reading

+ 78 - 6
src/contract/test-harness/src/dao_exec.rs

@@ -46,9 +46,9 @@ use rand::rngs::OsRng;
 use super::{Holder, TestHarness};
 
 impl TestHarness {
-    /// Create a `Dao::Exec` transaction.
+    /// Create a transfer `Dao::Exec` transaction.
     #[allow(clippy::too_many_arguments)]
-    pub async fn dao_exec(
+    pub async fn dao_exec_transfer(
         &mut self,
         holder: &Holder,
         dao: &Dao,
@@ -238,14 +238,84 @@ impl TestHarness {
         Ok((tx, xfer_params, fee_params))
     }
 
-    /// Execute the transaction made by `dao_exec()` for a given [`Holder`].
+    /// Create a generic `Dao::Exec` transaction.
+    #[allow(clippy::too_many_arguments)]
+    pub async fn dao_exec_generic(
+        &mut self,
+        holder: &Holder,
+        dao: &Dao,
+        proposal: &DaoProposal,
+        yes_vote_value: u64,
+        all_vote_value: u64,
+        yes_vote_blind: ScalarBlind,
+        all_vote_blind: ScalarBlind,
+        block_height: u32,
+    ) -> Result<(Transaction, Option<MoneyFeeParamsV1>)> {
+        let (dao_exec_pk, dao_exec_zkbin) =
+            self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_EXEC_NS).unwrap();
+
+        // Create the exec call
+        let exec_signature_secret = SecretKey::random(&mut OsRng);
+        let exec_builder = DaoExecCall {
+            proposal: proposal.clone(),
+            dao: dao.clone(),
+            yes_vote_value,
+            all_vote_value,
+            yes_vote_blind,
+            all_vote_blind,
+            signature_secret: exec_signature_secret,
+        };
+        let (exec_params, exec_proofs) = exec_builder.make(dao_exec_zkbin, dao_exec_pk)?;
+
+        // Encode the call
+        let mut data = vec![DaoFunction::Exec as u8];
+        exec_params.encode_async(&mut data).await?;
+        let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
+
+        // Create the TransactionBuilder containing the `DAO::Exec` call
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf { call: exec_call, proofs: exec_proofs },
+            vec![],
+        )?;
+
+        // If fees are enabled, make an offering
+        let mut fee_params = None;
+        let mut fee_signature_secrets = None;
+        if self.verify_fees {
+            let mut tx = tx_builder.build()?;
+            let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
+            tx.signatures = vec![exec_sigs];
+
+            let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
+                self.append_fee_call(holder, tx, block_height, &[]).await?;
+
+            // Append the fee call to the transaction
+            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
+            fee_signature_secrets = Some(fee_secrets);
+            fee_params = Some(fee_call_params);
+        }
+
+        // Now build the actual transaction and sign it with necessary keys.
+        let mut tx = tx_builder.build()?;
+        let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
+        tx.signatures = vec![exec_sigs];
+
+        if let Some(fee_signature_secrets) = fee_signature_secrets {
+            let sigs = tx.create_sigs(&fee_signature_secrets)?;
+            tx.signatures.push(sigs);
+        }
+
+        Ok((tx, fee_params))
+    }
+
+    /// Execute the transaction made by `dao_exec_*()` for a given [`Holder`].
     ///
     /// Returns any found [`OwnCoin`]s.
     pub async fn execute_dao_exec_tx(
         &mut self,
         holder: &Holder,
         tx: Transaction,
-        xfer_params: &MoneyTransferParamsV1,
+        xfer_params: Option<&MoneyTransferParamsV1>,
         fee_params: &Option<MoneyFeeParamsV1>,
         block_height: u32,
         append: bool,
@@ -259,8 +329,10 @@ impl TestHarness {
             return Ok(vec![])
         }
 
-        let mut inputs = xfer_params.inputs.to_vec();
-        let mut outputs = xfer_params.outputs.to_vec();
+        let (mut inputs, mut outputs) = match xfer_params {
+            Some(params) => (params.inputs.to_vec(), params.outputs.to_vec()),
+            None => (vec![], vec![]),
+        };
 
         if let Some(ref fee_params) = fee_params {
             inputs.push(fee_params.input.clone());

+ 122 - 5
src/contract/test-harness/src/dao_propose.rs

@@ -46,15 +46,15 @@ use rand::rngs::OsRng;
 use super::{Holder, TestHarness};
 
 impl TestHarness {
-    /// Create a `Dao::Propose` transaction.
-    pub async fn dao_propose(
+    /// Create a transfer `Dao::Propose` transaction.
+    pub async fn dao_propose_transfer(
         &mut self,
         proposer: &Holder,
         proposal_coinattrs: &[CoinAttributes],
         user_data: pallas::Base,
         dao: &Dao,
         block_height: u32,
-    ) -> Result<(Transaction, (DaoProposeParams, Option<MoneyFeeParamsV1>), DaoProposal)> {
+    ) -> Result<(Transaction, DaoProposeParams, Option<MoneyFeeParamsV1>, DaoProposal)> {
         let wallet = self.holders.get(proposer).unwrap();
 
         let (dao_propose_burn_pk, dao_propose_burn_zkbin) =
@@ -183,10 +183,127 @@ impl TestHarness {
             tx.signatures.push(sigs);
         }
 
-        Ok((tx, (params, fee_params), proposal))
+        Ok((tx, params, fee_params, proposal))
     }
 
-    /// Execute the transaction created by `dao_propose()` for a given [`Holder`].
+    /// Create a generic `Dao::Propose` transaction.
+    pub async fn dao_propose_generic(
+        &mut self,
+        proposer: &Holder,
+        user_data: pallas::Base,
+        dao: &Dao,
+        block_height: u32,
+    ) -> Result<(Transaction, DaoProposeParams, Option<MoneyFeeParamsV1>, DaoProposal)> {
+        let wallet = self.holders.get(proposer).unwrap();
+
+        let (dao_propose_burn_pk, dao_propose_burn_zkbin) =
+            self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS).unwrap();
+
+        let (dao_propose_main_pk, dao_propose_main_zkbin) =
+            self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS).unwrap();
+
+        let propose_owncoin: OwnCoin = wallet
+            .unspent_money_coins
+            .iter()
+            .find(|x| x.note.token_id == dao.gov_token_id)
+            .unwrap()
+            .clone();
+
+        // Useful code snippet to dump a sled contract DB
+        /*{
+            let blockchain = &wallet.validator.blockchain;
+            let contracts = &blockchain.contracts;
+            let tree = contracts
+                .lookup(&blockchain.sled_db, &MONEY_CONTRACT_ID, "nullifier_roots")
+                .unwrap();
+            for kv in tree.iter() {
+                let (key, value) = kv.unwrap();
+                debug!("STATE {:?}", key);
+                debug!("  => {:?}", value);
+            }
+        }*/
+
+        let input = DaoProposeStakeInput {
+            secret: wallet.keypair.secret,
+            note: propose_owncoin.note.clone(),
+            leaf_position: propose_owncoin.leaf_position,
+            merkle_path: wallet
+                .money_merkle_tree
+                .witness(propose_owncoin.leaf_position, 0)
+                .unwrap(),
+        };
+
+        let block_target = wallet.validator.consensus.module.read().await.target;
+        let creation_blockwindow = blockwindow(block_height, block_target);
+        let proposal = DaoProposal {
+            auth_calls: vec![],
+            creation_blockwindow,
+            duration_blockwindows: 30,
+            user_data,
+            dao_bulla: dao.to_bulla(),
+            blind: Blind::random(&mut OsRng),
+        };
+
+        let signature_secret = SecretKey::random(&mut OsRng);
+        let dao_bulla = dao.to_bulla();
+
+        let call = DaoProposeCall {
+            money_null_smt: &wallet.money_null_smt,
+            inputs: vec![input],
+            proposal: proposal.clone(),
+            dao: dao.clone(),
+            dao_leaf_position: *wallet.dao_leafs.get(&dao_bulla).unwrap(),
+            dao_merkle_path: wallet
+                .dao_merkle_tree
+                .witness(*wallet.dao_leafs.get(&dao_bulla).unwrap(), 0)
+                .unwrap(),
+            dao_merkle_root: wallet.dao_merkle_tree.root(0).unwrap(),
+            signature_secret,
+        };
+
+        let (params, proofs) = call.make(
+            dao_propose_burn_zkbin,
+            dao_propose_burn_pk,
+            dao_propose_main_zkbin,
+            dao_propose_main_pk,
+        )?;
+
+        // Encode the call
+        let mut data = vec![DaoFunction::Propose as u8];
+        params.encode_async(&mut data).await?;
+        let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
+        let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
+
+        // If fees are enabled, make an offering
+        let mut fee_params = None;
+        let mut fee_signature_secrets = None;
+        if self.verify_fees {
+            let mut tx = tx_builder.build()?;
+            let sigs = tx.create_sigs(&[signature_secret])?;
+            tx.signatures = vec![sigs];
+
+            let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
+                self.append_fee_call(proposer, tx, block_height, &[]).await?;
+
+            // Append the fee call to the transaction
+            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
+            fee_signature_secrets = Some(fee_secrets);
+            fee_params = Some(fee_call_params);
+        }
+
+        // Now build the actual transaction and sign it with necessary keys.
+        let mut tx = tx_builder.build()?;
+        let sigs = tx.create_sigs(&[signature_secret])?;
+        tx.signatures = vec![sigs];
+        if let Some(fee_signature_secrets) = fee_signature_secrets {
+            let sigs = tx.create_sigs(&fee_signature_secrets)?;
+            tx.signatures.push(sigs);
+        }
+
+        Ok((tx, params, fee_params, proposal))
+    }
+
+    /// Execute the transaction created by `dao_propose_*()` for a given [`Holder`].
     ///
     /// Returns any found [`OwnCoin`]s.
     pub async fn execute_dao_propose_tx(