Przeglądaj źródła

contract/test-harness: Implement functionality for the DAO contract.

parazyd 3 lat temu
rodzic
commit
21d6978739

+ 2 - 1
src/contract/consensus/tests/stake_unstake.rs

@@ -52,7 +52,8 @@ async fn consensus_contract_stake_unstake() -> Result<()> {
     info!(target: "consensus", "[Faucet] =========================");
     info!(target: "consensus", "[Faucet] Building Alice airdrop tx");
     info!(target: "consensus", "[Faucet] =========================");
-    let (airdrop_tx, airdrop_params) = th.airdrop_native(ALICE_AIRDROP, Holder::Alice)?;
+    let (airdrop_tx, airdrop_params) =
+        th.airdrop_native(ALICE_AIRDROP, Holder::Alice, None, None, None, None)?;
 
     info!(target: "consensus", "[Faucet] ==========================");
     info!(target: "consensus", "[Faucet] Executing Alice airdrop tx");

+ 4 - 2
src/contract/money/tests/integration.rs

@@ -52,7 +52,8 @@ async fn money_integration() -> Result<()> {
     let mut th = TestHarness::new(&["money".to_string()]).await?;
 
     info!("[Faucet] Building Alice airdrop tx");
-    let (airdrop_tx, airdrop_params) = th.airdrop_native(ALICE_NATIVE_AIRDROP, Holder::Alice)?;
+    let (airdrop_tx, airdrop_params) =
+        th.airdrop_native(ALICE_NATIVE_AIRDROP, Holder::Alice, None, None, None, None)?;
 
     info!("[Faucet] Executing Alice airdrop tx");
     th.execute_airdrop_native_tx(Holder::Faucet, &airdrop_tx, &airdrop_params, current_slot)
@@ -70,7 +71,8 @@ async fn money_integration() -> Result<()> {
     th.gather_owncoin(Holder::Alice, airdrop_params.outputs[0].clone(), None)?;
 
     info!("[Bob] Building BOB token mint tx");
-    let (token_mint_tx, token_mint_params) = th.token_mint(BOB_SUPPLY, Holder::Bob, Holder::Bob)?;
+    let (token_mint_tx, token_mint_params) =
+        th.token_mint(BOB_SUPPLY, Holder::Bob, Holder::Bob, None, None)?;
 
     info!("[Faucet] Executing BOB token mint tx");
     th.execute_token_mint_tx(Holder::Faucet, &token_mint_tx, &token_mint_params, current_slot)

+ 3 - 2
src/contract/money/tests/mint_pay_swap.rs

@@ -61,7 +61,8 @@ async fn mint_pay_swap() -> Result<()> {
     info!(target: "money", "[Alice] ================================");
     info!(target: "money", "[Alice] Building token mint tx for Alice");
     info!(target: "money", "[Alice] ================================");
-    let (mint_tx, params) = th.token_mint(ALICE_INITIAL, Holder::Alice, Holder::Alice)?;
+    let (mint_tx, params) =
+        th.token_mint(ALICE_INITIAL, Holder::Alice, Holder::Alice, None, None)?;
 
     info!(target: "money", "[Faucet] =============================");
     info!(target: "money", "[Faucet] Executing Alice token mint tx");
@@ -88,7 +89,7 @@ async fn mint_pay_swap() -> Result<()> {
     info!(target: "money", "[Bob] ==============================");
     info!(target: "money", "[Bob] Building token mint tx for Bob");
     info!(target: "money", "[Bob] ==============================");
-    let (mint_tx, params) = th.token_mint(BOB_INITIAL, Holder::Bob, Holder::Bob)?;
+    let (mint_tx, params) = th.token_mint(BOB_INITIAL, Holder::Bob, Holder::Bob, None, None)?;
 
     info!(target: "money", "[Faucet] ===========================");
     info!(target: "money", "[Faucet] Executing Bob token mint tx");

+ 1 - 1
src/contract/money/tests/txs_verification.rs

@@ -55,7 +55,7 @@ async fn txs_verification() -> Result<()> {
     info!(target: "money", "[Alice] Building token mint tx for Alice");
     info!(target: "money", "[Alice] ================================");
     let (token_mint_tx, token_mint_params) =
-        th.token_mint(ALICE_INITIAL, Holder::Alice, Holder::Alice)?;
+        th.token_mint(ALICE_INITIAL, Holder::Alice, Holder::Alice, None, None)?;
 
     info!(target: "money", "[Faucet] =============================");
     info!(target: "money", "[Faucet] Executing Alice token mint tx");

+ 181 - 0
src/contract/test-harness/src/dao_exec.rs

@@ -0,0 +1,181 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::time::Instant;
+
+use darkfi::{tx::Transaction, Result};
+use darkfi_dao_contract::{
+    client::{DaoExecCall, DaoInfo, DaoProposalInfo},
+    model::{DaoBulla, DaoExecParams},
+    DaoFunction, DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
+};
+use darkfi_money_contract::{
+    client::{transfer_v1::TransferCallBuilder, OwnCoin},
+    model::MoneyTransferParamsV1,
+    MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+};
+use darkfi_sdk::{
+    crypto::{pasta_prelude::Field, MerkleNode, SecretKey, DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{serialize, Encodable};
+use rand::rngs::OsRng;
+
+use super::{Holder, TestHarness, TxAction};
+
+impl TestHarness {
+    pub fn dao_exec(
+        &mut self,
+        dao: DaoInfo,
+        dao_bulla: DaoBulla,
+        proposal: DaoProposalInfo,
+        yes_vote_value: u64,
+        all_vote_value: u64,
+        yes_vote_blind: pallas::Scalar,
+        all_vote_blind: pallas::Scalar,
+    ) -> Result<(Transaction, MoneyTransferParamsV1, DaoExecParams)> {
+        let (mint_pk, mint_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
+        let (burn_pk, burn_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
+        let (dao_exec_pk, dao_exec_zkbin) =
+            self.proving_keys.get(&DAO_CONTRACT_ZKAS_DAO_EXEC_NS).unwrap();
+        let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoExec).unwrap();
+        let timer = Instant::now();
+
+        // TODO: FIXME: This is not checked anywhere!
+        let exec_signature_secret = SecretKey::random(&mut OsRng);
+
+        let rcpt_spend_hook = pallas::Base::ZERO;
+        let rcpt_user_data = pallas::Base::ZERO;
+        let rcpt_user_data_blind = pallas::Base::random(&mut OsRng);
+
+        let change_spend_hook = DAO_CONTRACT_ID.inner();
+        let change_user_data = dao_bulla.inner();
+        let change_user_data_blind = pallas::Base::random(&mut OsRng);
+
+        let dao_wallet = self.holders.get(&Holder::Dao).unwrap();
+        let coins: Vec<OwnCoin> = dao_wallet
+            .unspent_money_coins
+            .iter()
+            .filter(|x| x.note.token_id == proposal.token_id)
+            .cloned()
+            .collect();
+        let tree = dao_wallet.money_merkle_tree.clone();
+
+        let xfer_builder = TransferCallBuilder {
+            keypair: dao_wallet.keypair,
+            recipient: proposal.dest,
+            value: proposal.amount,
+            token_id: proposal.token_id,
+            rcpt_spend_hook,
+            rcpt_user_data,
+            rcpt_user_data_blind,
+            change_spend_hook,
+            change_user_data,
+            change_user_data_blind,
+            coins,
+            tree,
+            mint_zkbin: mint_zkbin.clone(),
+            mint_pk: mint_pk.clone(),
+            burn_zkbin: burn_zkbin.clone(),
+            burn_pk: burn_pk.clone(),
+            clear_input: false,
+        };
+
+        let xfer_debris = xfer_builder.build()?;
+        let mut data = vec![MoneyFunction::TransferV1 as u8];
+        xfer_debris.params.encode(&mut data)?;
+        let xfer_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
+
+        // We need to extract stuff from the inputs and outputs that we'll also
+        // use in the DAO::Exec call. This DAO API needs to be better.
+        let mut input_value = 0;
+        let mut input_value_blind = pallas::Scalar::ZERO;
+        for (input, blind) in xfer_debris.spent_coins.iter().zip(xfer_debris.input_value_blinds) {
+            input_value += input.note.value;
+            input_value_blind += blind;
+        }
+
+        // First output is change, second output is recipient.
+        let dao_serial = xfer_debris.minted_coins[0].note.serial;
+        let user_serial = xfer_debris.minted_coins[1].note.serial;
+
+        let exec_builder = DaoExecCall {
+            proposal,
+            dao,
+            yes_vote_value,
+            all_vote_value,
+            yes_vote_blind,
+            all_vote_blind,
+            user_serial,
+            dao_serial,
+            input_value,
+            input_value_blind,
+            hook_dao_exec: DAO_CONTRACT_ID.inner(),
+            signature_secret: exec_signature_secret,
+        };
+
+        let (exec_params, exec_proofs) = exec_builder.make(&dao_exec_zkbin, &dao_exec_pk)?;
+        let mut data = vec![DaoFunction::Exec as u8];
+        exec_params.encode(&mut data)?;
+        let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
+
+        let mut tx = Transaction {
+            calls: vec![xfer_call, exec_call],
+            proofs: vec![xfer_debris.proofs, exec_proofs],
+            signatures: vec![],
+        };
+        let xfer_sigs = tx.create_sigs(&mut OsRng, &xfer_debris.signature_secrets)?;
+        let exec_sigs = tx.create_sigs(&mut OsRng, &[exec_signature_secret])?;
+        tx.signatures = vec![xfer_sigs, exec_sigs];
+        tx_action_benchmark.creation_times.push(timer.elapsed());
+
+        // Calculate transaction sizes
+        let encoded: Vec<u8> = serialize(&tx);
+        let size = std::mem::size_of_val(&*encoded);
+        tx_action_benchmark.sizes.push(size);
+        let base58 = bs58::encode(&encoded).into_string();
+        let size = std::mem::size_of_val(&*base58);
+        tx_action_benchmark.broadcasted_sizes.push(size);
+
+        Ok((tx, xfer_debris.params, exec_params))
+    }
+
+    pub async fn execute_dao_exec_tx(
+        &mut self,
+        holder: Holder,
+        tx: &Transaction,
+        xfer_params: &MoneyTransferParamsV1,
+        _exec_params: &DaoExecParams,
+        slot: u64,
+    ) -> Result<()> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoExec).unwrap();
+        let timer = Instant::now();
+
+        wallet.validator.read().await.add_transactions(&[tx.clone()], slot, true).await?;
+
+        for output in &xfer_params.outputs {
+            wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner()));
+        }
+
+        tx_action_benchmark.verify_times.push(timer.elapsed());
+
+        Ok(())
+    }
+}

+ 88 - 0
src/contract/test-harness/src/dao_mint.rs

@@ -0,0 +1,88 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::time::Instant;
+
+use darkfi::{tx::Transaction, Result};
+use darkfi_dao_contract::{
+    client, client::DaoInfo, model::DaoMintParams, DaoFunction, DAO_CONTRACT_ZKAS_DAO_MINT_NS,
+};
+use darkfi_sdk::{
+    crypto::{Keypair, MerkleNode, DAO_CONTRACT_ID},
+    ContractCall,
+};
+use darkfi_serial::{serialize, Encodable};
+use rand::rngs::OsRng;
+
+use super::{Holder, TestHarness, TxAction};
+
+impl TestHarness {
+    pub fn dao_mint(
+        &mut self,
+        dao_info: &DaoInfo,
+        dao_kp: &Keypair,
+    ) -> Result<(Transaction, DaoMintParams)> {
+        let (dao_mint_pk, dao_mint_zkbin) =
+            self.proving_keys.get(&DAO_CONTRACT_ZKAS_DAO_MINT_NS).unwrap();
+        let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoMint).unwrap();
+        let timer = Instant::now();
+
+        let (params, proofs) =
+            client::make_mint_call(dao_info, &dao_kp.secret, dao_mint_zkbin, dao_mint_pk)?;
+
+        let mut data = vec![DaoFunction::Mint as u8];
+        params.encode(&mut data)?;
+        let calls = vec![ContractCall { contract_id: *DAO_CONTRACT_ID, data }];
+        let proofs = vec![proofs];
+        let mut tx = Transaction { calls, proofs, signatures: vec![] };
+        let sigs = tx.create_sigs(&mut OsRng, &[dao_kp.secret])?;
+        tx.signatures = vec![sigs];
+        tx_action_benchmark.creation_times.push(timer.elapsed());
+
+        // Calculate transaction sizes
+        let encoded: Vec<u8> = serialize(&tx);
+        let size = std::mem::size_of_val(&*encoded);
+        tx_action_benchmark.sizes.push(size);
+        let base58 = bs58::encode(&encoded).into_string();
+        let size = std::mem::size_of_val(&*base58);
+        tx_action_benchmark.broadcasted_sizes.push(size);
+
+        Ok((tx, params))
+    }
+
+    pub async fn execute_dao_mint_tx(
+        &mut self,
+        holder: Holder,
+        tx: &Transaction,
+        params: &DaoMintParams,
+        slot: u64,
+    ) -> Result<()> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoMint).unwrap();
+        let timer = Instant::now();
+
+        wallet.validator.read().await.add_transactions(&[tx.clone()], slot, true).await?;
+        wallet.dao_merkle_tree.append(MerkleNode::from(params.dao_bulla.inner()));
+        let leaf_pos = wallet.dao_merkle_tree.mark().unwrap();
+        wallet.dao_leafs.insert(params.dao_bulla, leaf_pos);
+
+        tx_action_benchmark.verify_times.push(timer.elapsed());
+
+        Ok(())
+    }
+}

+ 144 - 0
src/contract/test-harness/src/dao_propose.rs

@@ -0,0 +1,144 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::time::Instant;
+
+use darkfi::{tx::Transaction, Result};
+use darkfi_dao_contract::{
+    client::{DaoInfo, DaoProposalInfo, DaoProposeCall, DaoProposeStakeInput},
+    model::{DaoBulla, DaoProposeParams},
+    DaoFunction, DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
+};
+use darkfi_money_contract::client::OwnCoin;
+use darkfi_sdk::{
+    crypto::{pasta_prelude::Field, MerkleNode, SecretKey, TokenId, DAO_CONTRACT_ID},
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{serialize, Encodable};
+use rand::rngs::OsRng;
+
+use super::{Holder, TestHarness, TxAction};
+
+impl TestHarness {
+    pub fn dao_propose(
+        &mut self,
+        proposer: Holder,
+        recipient: Holder,
+        amount: u64,
+        tx_token_id: TokenId,
+        dao: DaoInfo,
+        dao_bulla: DaoBulla,
+    ) -> Result<(Transaction, DaoProposeParams, DaoProposalInfo)> {
+        let (dao_propose_burn_pk, dao_propose_burn_zkbin) =
+            self.proving_keys.get(&DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS).unwrap();
+        let (dao_propose_main_pk, dao_propose_main_zkbin) =
+            self.proving_keys.get(&DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS).unwrap();
+        let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoPropose).unwrap();
+        let timer = Instant::now();
+
+        let wallet = self.holders.get(&proposer).unwrap();
+        let propose_owncoin: OwnCoin = wallet
+            .unspent_money_coins
+            .iter()
+            .find(|x| x.note.token_id == dao.gov_token_id)
+            .unwrap()
+            .clone();
+
+        let signature_secret = SecretKey::random(&mut OsRng);
+        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(),
+            signature_secret,
+        };
+
+        let proposal = DaoProposalInfo {
+            dest: self.holders.get(&recipient).unwrap().keypair.public,
+            amount,
+            token_id: tx_token_id,
+            blind: pallas::Base::random(&mut OsRng),
+        };
+
+        let call = DaoProposeCall {
+            inputs: vec![input],
+            proposal: proposal.clone(),
+            dao,
+            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(),
+        };
+
+        let (params, proofs) = call.make(
+            &dao_propose_burn_zkbin,
+            &dao_propose_burn_pk,
+            &dao_propose_main_zkbin,
+            &dao_propose_main_pk,
+        )?;
+
+        let mut data = vec![DaoFunction::Propose as u8];
+        params.encode(&mut data)?;
+        let calls = vec![ContractCall { contract_id: *DAO_CONTRACT_ID, data }];
+        let proofs = vec![proofs];
+        let mut tx = Transaction { calls, proofs, signatures: vec![] };
+        let sigs = tx.create_sigs(&mut OsRng, &[signature_secret])?;
+        tx.signatures = vec![sigs];
+        tx_action_benchmark.creation_times.push(timer.elapsed());
+
+        // Calculate transaction sizes
+        let encoded: Vec<u8> = serialize(&tx);
+        let size = std::mem::size_of_val(&*encoded);
+        tx_action_benchmark.sizes.push(size);
+        let base58 = bs58::encode(&encoded).into_string();
+        let size = std::mem::size_of_val(&*base58);
+        tx_action_benchmark.broadcasted_sizes.push(size);
+
+        Ok((tx, params, proposal))
+    }
+
+    pub async fn execute_dao_propose_tx(
+        &mut self,
+        holder: Holder,
+        tx: &Transaction,
+        params: &DaoProposeParams,
+        slot: u64,
+    ) -> Result<()> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoPropose).unwrap();
+        let timer = Instant::now();
+
+        wallet.validator.read().await.add_transactions(&[tx.clone()], slot, true).await?;
+        wallet.dao_proposals_tree.append(MerkleNode::from(params.proposal_bulla.inner()));
+
+        let prop_leaf_pos = wallet.dao_proposals_tree.mark().unwrap();
+        let prop_money_snapshot = wallet.money_merkle_tree.clone();
+
+        wallet.dao_prop_leafs.insert(params.proposal_bulla, (prop_leaf_pos, prop_money_snapshot));
+
+        tx_action_benchmark.verify_times.push(timer.elapsed());
+
+        Ok(())
+    }
+}

+ 129 - 0
src/contract/test-harness/src/dao_vote.rs

@@ -0,0 +1,129 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::time::Instant;
+
+use darkfi::{tx::Transaction, Result};
+use darkfi_dao_contract::{
+    client::{DaoInfo, DaoProposalInfo, DaoVoteCall, DaoVoteInput},
+    model::{DaoProposalBulla, DaoVoteParams},
+    DaoFunction, DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
+};
+use darkfi_money_contract::client::OwnCoin;
+use darkfi_sdk::{
+    crypto::{pasta_prelude::Field, Keypair, SecretKey, DAO_CONTRACT_ID},
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{serialize, Encodable};
+use rand::rngs::OsRng;
+
+use super::{Holder, TestHarness, TxAction};
+
+impl TestHarness {
+    pub fn dao_vote(
+        &mut self,
+        voter: Holder,
+        dao_kp: &Keypair,
+        vote_option: bool,
+        dao: DaoInfo,
+        proposal: DaoProposalInfo,
+        proposal_bulla: DaoProposalBulla,
+    ) -> Result<(Transaction, DaoVoteParams)> {
+        let (dao_vote_burn_pk, dao_vote_burn_zkbin) =
+            self.proving_keys.get(&DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS).unwrap();
+        let (dao_vote_main_pk, dao_vote_main_zkbin) =
+            self.proving_keys.get(&DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS).unwrap();
+        let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoVote).unwrap();
+        let timer = Instant::now();
+
+        let wallet = self.holders.get(&voter).unwrap();
+
+        let (_proposal_leaf_pos, money_merkle_tree) =
+            wallet.dao_prop_leafs.get(&proposal_bulla).unwrap();
+
+        let vote_owncoin: OwnCoin = wallet
+            .unspent_money_coins
+            .iter()
+            .find(|x| x.note.token_id == dao.gov_token_id)
+            .unwrap()
+            .clone();
+
+        let signature_secret = SecretKey::random(&mut OsRng);
+        let input = DaoVoteInput {
+            secret: wallet.keypair.secret,
+            note: vote_owncoin.note.clone(),
+            leaf_position: vote_owncoin.leaf_position,
+            merkle_path: money_merkle_tree.witness(vote_owncoin.leaf_position, 0).unwrap(),
+            signature_secret,
+        };
+
+        let call = DaoVoteCall {
+            inputs: vec![input],
+            vote_option,
+            yes_vote_blind: pallas::Scalar::random(&mut OsRng),
+            vote_keypair: *dao_kp,
+            proposal,
+            dao,
+        };
+
+        let (params, proofs) = call.make(
+            &dao_vote_burn_zkbin,
+            &dao_vote_burn_pk,
+            &dao_vote_main_zkbin,
+            &dao_vote_main_pk,
+        )?;
+
+        let mut data = vec![DaoFunction::Vote as u8];
+        params.encode(&mut data)?;
+        let calls = vec![ContractCall { contract_id: *DAO_CONTRACT_ID, data }];
+        let proofs = vec![proofs];
+        let mut tx = Transaction { calls, proofs, signatures: vec![] };
+        let sigs = tx.create_sigs(&mut OsRng, &[signature_secret])?;
+        tx.signatures = vec![sigs];
+        tx_action_benchmark.creation_times.push(timer.elapsed());
+
+        // Calculate transaction sizes
+        let encoded: Vec<u8> = serialize(&tx);
+        let size = std::mem::size_of_val(&*encoded);
+        tx_action_benchmark.sizes.push(size);
+        let base58 = bs58::encode(&encoded).into_string();
+        let size = std::mem::size_of_val(&*base58);
+        tx_action_benchmark.broadcasted_sizes.push(size);
+
+        Ok((tx, params))
+    }
+
+    pub async fn execute_dao_vote_tx(
+        &mut self,
+        holder: Holder,
+        tx: &Transaction,
+        _params: &DaoVoteParams,
+        slot: u64,
+    ) -> Result<()> {
+        let wallet = self.holders.get_mut(&holder).unwrap();
+        let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoVote).unwrap();
+        let timer = Instant::now();
+
+        wallet.validator.read().await.add_transactions(&[tx.clone()], slot, true).await?;
+
+        tx_action_benchmark.verify_times.push(timer.elapsed());
+
+        Ok(())
+    }
+}

+ 37 - 1
src/contract/test-harness/src/lib.rs

@@ -29,6 +29,7 @@ use darkfi::{
     Result,
 };
 use darkfi_dao_contract::{
+    model::{DaoBulla, DaoProposalBulla},
     DAO_CONTRACT_ZKAS_DAO_EXEC_NS, DAO_CONTRACT_ZKAS_DAO_MINT_NS,
     DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
     DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
@@ -43,9 +44,10 @@ use darkfi_money_contract::{
 };
 use darkfi_sdk::{
     blockchain::Slot,
+    bridgetree,
     crypto::{
         pasta_prelude::Field, poseidon_hash, Keypair, MerkleNode, MerkleTree, Nullifier, PublicKey,
-        SecretKey, CONSENSUS_CONTRACT_ID, DAO_CONTRACT_ID, MONEY_CONTRACT_ID,
+        SecretKey, TokenId, CONSENSUS_CONTRACT_ID, DAO_CONTRACT_ID, MONEY_CONTRACT_ID,
     },
     pasta::pallas,
 };
@@ -62,6 +64,10 @@ mod consensus_proposal;
 mod consensus_stake;
 mod consensus_unstake;
 mod consensus_unstake_request;
+mod dao_exec;
+mod dao_mint;
+mod dao_propose;
+mod dao_vote;
 mod money_airdrop;
 mod money_genesis_mint;
 mod money_otc_swap;
@@ -96,6 +102,7 @@ pub enum Holder {
     Bob,
     Charlie,
     Rachel,
+    Dao,
 }
 
 /// Enum representing transaction actions
@@ -112,6 +119,10 @@ pub enum TxAction {
     ConsensusProposal,
     ConsensusUnstakeRequest,
     ConsensusUnstake,
+    DaoMint,
+    DaoPropose,
+    DaoVote,
+    DaoExec,
 }
 
 pub struct Wallet {
@@ -121,9 +132,14 @@ pub struct Wallet {
     pub money_merkle_tree: MerkleTree,
     pub consensus_staked_merkle_tree: MerkleTree,
     pub consensus_unstaked_merkle_tree: MerkleTree,
+    pub dao_merkle_tree: MerkleTree,
+    pub dao_proposals_tree: MerkleTree,
     pub wallet: WalletPtr,
     pub unspent_money_coins: Vec<OwnCoin>,
     pub spent_money_coins: Vec<OwnCoin>,
+    pub dao_leafs: HashMap<DaoBulla, bridgetree::Position>,
+    // Here the MerkleTree is the snapshotted Money tree at the time of proposal creation
+    pub dao_prop_leafs: HashMap<DaoProposalBulla, (bridgetree::Position, MerkleTree)>,
 }
 
 impl Wallet {
@@ -157,6 +173,9 @@ impl Wallet {
         let consensus_staked_merkle_tree = MerkleTree::new(100);
         let consensus_unstaked_merkle_tree = MerkleTree::new(100);
 
+        let dao_merkle_tree = MerkleTree::new(100);
+        let dao_proposals_tree = MerkleTree::new(100);
+
         let unspent_money_coins = vec![];
         let spent_money_coins = vec![];
 
@@ -169,9 +188,13 @@ impl Wallet {
             money_merkle_tree,
             consensus_staked_merkle_tree,
             consensus_unstaked_merkle_tree,
+            dao_merkle_tree,
+            dao_proposals_tree,
             wallet,
             unspent_money_coins,
             spent_money_coins,
+            dao_leafs: HashMap::new(),
+            dao_prop_leafs: HashMap::new(),
         })
     }
 }
@@ -209,6 +232,10 @@ impl TestHarness {
         let rachel = Wallet::new(rachel_kp, &genesis_block, &faucet_pubkeys).await?;
         holders.insert(Holder::Rachel, rachel);
 
+        let dao_kp = Keypair::random(&mut OsRng);
+        let dao = Wallet::new(dao_kp, &genesis_block, &faucet_pubkeys).await?;
+        holders.insert(Holder::Dao, dao);
+
         // Get the zkas circuits and build proving keys
         let mut proving_keys = HashMap::new();
         let alice_sled = alice.validator.read().await.blockchain.sled_db.clone();
@@ -277,6 +304,10 @@ impl TestHarness {
         tx_action_benchmarks
             .insert(TxAction::ConsensusUnstakeRequest, TxActionBenchmarks::default());
         tx_action_benchmarks.insert(TxAction::ConsensusUnstake, TxActionBenchmarks::default());
+        tx_action_benchmarks.insert(TxAction::DaoMint, TxActionBenchmarks::default());
+        tx_action_benchmarks.insert(TxAction::DaoPropose, TxActionBenchmarks::default());
+        tx_action_benchmarks.insert(TxAction::DaoVote, TxActionBenchmarks::default());
+        tx_action_benchmarks.insert(TxAction::DaoExec, TxActionBenchmarks::default());
 
         // Alice jumps down the rabbit hole
         holders.insert(Holder::Alice, alice);
@@ -487,6 +518,11 @@ impl TestHarness {
         }
     }
 
+    pub fn token_id(&self, holder: &Holder) -> TokenId {
+        let holder = self.holders.get(holder).unwrap();
+        TokenId::derive_public(holder.token_mint_authority.public)
+    }
+
     pub fn statistics(&self) {
         info!("==================== Statistics ====================");
         for (action, tx_action_benchmark) in &self.tx_action_benchmarks {

+ 8 - 4
src/contract/test-harness/src/money_airdrop.rs

@@ -38,6 +38,10 @@ impl TestHarness {
         &mut self,
         value: u64,
         holder: Holder,
+        rcpt_spend_hook: Option<pallas::Base>,
+        rcpt_user_data: Option<pallas::Base>,
+        change_spend_hook: Option<pallas::Base>,
+        change_user_data: Option<pallas::Base>,
     ) -> Result<(Transaction, MoneyTransferParamsV1)> {
         let recipient = self.holders.get(&holder).unwrap().keypair.public;
         let faucet = self.holders.get(&Holder::Faucet).unwrap();
@@ -52,11 +56,11 @@ impl TestHarness {
             recipient,
             value,
             token_id: *DARK_TOKEN_ID,
-            rcpt_spend_hook: pallas::Base::ZERO,
-            rcpt_user_data: pallas::Base::ZERO,
+            rcpt_spend_hook: rcpt_spend_hook.unwrap_or(pallas::Base::ZERO),
+            rcpt_user_data: rcpt_user_data.unwrap_or(pallas::Base::ZERO),
             rcpt_user_data_blind: pallas::Base::random(&mut OsRng),
-            change_spend_hook: pallas::Base::ZERO,
-            change_user_data: pallas::Base::ZERO,
+            change_spend_hook: change_spend_hook.unwrap_or(pallas::Base::ZERO),
+            change_user_data: change_user_data.unwrap_or(pallas::Base::ZERO),
             change_user_data_blind: pallas::Base::random(&mut OsRng),
             coins: vec![],
             tree: faucet.money_merkle_tree.clone(),

+ 4 - 2
src/contract/test-harness/src/money_token.rs

@@ -40,6 +40,8 @@ impl TestHarness {
         amount: u64,
         holder: Holder,
         recipient: Holder,
+        spend_hook: Option<pallas::Base>,
+        user_data: Option<pallas::Base>,
     ) -> Result<(Transaction, MoneyTokenMintParamsV1)> {
         let rcpt = self.holders.get(&recipient).unwrap().keypair.public;
         let mint_authority = self.holders.get(&holder).unwrap().token_mint_authority;
@@ -53,8 +55,8 @@ impl TestHarness {
             mint_authority,
             recipient: rcpt,
             amount,
-            spend_hook: pallas::Base::ZERO,
-            user_data: pallas::Base::ZERO,
+            spend_hook: spend_hook.unwrap_or(pallas::Base::ZERO),
+            user_data: user_data.unwrap_or(pallas::Base::ZERO),
             token_mint_zkbin: mint_zkbin.clone(),
             token_mint_pk: mint_pk.clone(),
         };