x 3 лет назад
Родитель
Сommit
66d643c1f7

+ 321 - 0
src/contract/dao/src/dao_vote_client.rs

@@ -0,0 +1,321 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 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 darkfi_sdk::{
+    crypto::{
+        keypair::Keypair, pedersen::pedersen_commitment_u64, poseidon_hash, MerkleNode, Nullifier,
+        PublicKey, SecretKey, TokenId,
+    },
+    incrementalmerkletree,
+    incrementalmerkletree::{bridgetree::BridgeTree, Hashable, Tree},
+    pasta::{
+        arithmetic::CurveAffine,
+        group::{
+            ff::{Field, PrimeField},
+            Curve,
+        },
+        pallas,
+    },
+};
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+use halo2_proofs::circuit::Value;
+use log::{debug, info};
+use rand::rngs::OsRng;
+
+use darkfi::{
+    zk::{
+        proof::{Proof, ProvingKey},
+        vm::ZkCircuit,
+        vm_stack::Witness,
+    },
+    zkas::ZkBinary,
+    Error, Result,
+};
+
+use crate::{
+    dao_propose_client::{DaoParams, Proposal},
+    note,
+    state::{DaoVoteParams, VoteInput},
+};
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct Note {
+    pub vote: Vote,
+    pub vote_value: u64,
+    pub vote_value_blind: pallas::Scalar,
+}
+
+#[derive(SerialEncodable, SerialDecodable)]
+pub struct Vote {
+    pub vote_option: bool,
+    pub vote_option_blind: pallas::Scalar,
+}
+
+pub struct BuilderInput {
+    pub secret: SecretKey,
+    //pub note: money::transfer::wallet::Note,
+    pub note: darkfi_money_contract::client::Note,
+    pub leaf_position: incrementalmerkletree::Position,
+    pub merkle_path: Vec<MerkleNode>,
+    pub signature_secret: SecretKey,
+}
+
+// TODO: should be token locking voting?
+// Inside ZKproof, check proposal is correct.
+pub struct Builder {
+    pub inputs: Vec<BuilderInput>,
+    pub vote: Vote,
+    pub vote_keypair: Keypair,
+    pub proposal: Proposal,
+    pub dao: DaoParams,
+}
+
+impl Builder {
+    pub fn build(
+        self,
+        burn_zkbin: &ZkBinary,
+        burn_pk: &ProvingKey,
+        main_zkbin: &ZkBinary,
+        main_pk: &ProvingKey,
+    ) -> Result<(DaoVoteParams, Vec<Proof>)> {
+        debug!(target: "dao_contract::vote::wallet::Builder", "build()");
+        let mut proofs = vec![];
+
+        let gov_token_blind = pallas::Base::random(&mut OsRng);
+
+        let mut inputs = vec![];
+        let mut vote_value = 0;
+        let mut vote_value_blind = pallas::Scalar::from(0);
+
+        for input in self.inputs {
+            let value_blind = pallas::Scalar::random(&mut OsRng);
+
+            vote_value += input.note.value;
+            vote_value_blind += value_blind;
+
+            let signature_public = PublicKey::from_secret(input.signature_secret);
+
+            /*
+            let zk_info = zk_bins.lookup(&"dao-vote-burn".to_string()).unwrap();
+
+            let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
+                info
+            } else {
+                panic!("Not binary info")
+            };
+            let zk_bin = zk_info.bincode.clone();
+            */
+
+            // Note from the previous output
+            let note = input.note;
+            let leaf_pos: u64 = input.leaf_position.into();
+
+            let prover_witnesses = vec![
+                Witness::Base(Value::known(input.secret.inner())),
+                Witness::Base(Value::known(note.serial)),
+                Witness::Base(Value::known(pallas::Base::from(0))),
+                Witness::Base(Value::known(pallas::Base::from(0))),
+                Witness::Base(Value::known(pallas::Base::from(note.value))),
+                Witness::Base(Value::known(note.token_id.inner())),
+                Witness::Base(Value::known(note.coin_blind)),
+                Witness::Scalar(Value::known(vote_value_blind)),
+                Witness::Base(Value::known(gov_token_blind)),
+                Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())),
+                Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
+                Witness::Base(Value::known(input.signature_secret.inner())),
+            ];
+
+            let public_key = PublicKey::from_secret(input.secret);
+            let (pub_x, pub_y) = public_key.xy();
+
+            let coin = poseidon_hash::<8>([
+                pub_x,
+                pub_y,
+                pallas::Base::from(note.value),
+                note.token_id.inner(),
+                note.serial,
+                pallas::Base::from(0),
+                pallas::Base::from(0),
+                note.coin_blind,
+            ]);
+
+            let merkle_root = {
+                let position: u64 = input.leaf_position.into();
+                let mut current = MerkleNode::from(coin);
+                for (level, sibling) in input.merkle_path.iter().enumerate() {
+                    let level = level as u8;
+                    current = if position & (1 << level) == 0 {
+                        MerkleNode::combine(level.into(), &current, sibling)
+                    } else {
+                        MerkleNode::combine(level.into(), sibling, &current)
+                    };
+                }
+                current
+            };
+
+            let token_commit = poseidon_hash::<2>([note.token_id.inner(), gov_token_blind]);
+            assert_eq!(self.dao.gov_token_id, note.token_id);
+
+            let nullifier = poseidon_hash::<2>([input.secret.inner(), note.serial]);
+
+            let vote_commit = pedersen_commitment_u64(note.value, vote_value_blind);
+            let vote_commit_coords = vote_commit.to_affine().coordinates().unwrap();
+
+            let (sig_x, sig_y) = signature_public.xy();
+
+            let public_inputs = vec![
+                nullifier,
+                *vote_commit_coords.x(),
+                *vote_commit_coords.y(),
+                token_commit,
+                merkle_root.inner(),
+                sig_x,
+                sig_y,
+            ];
+
+            let circuit = ZkCircuit::new(prover_witnesses, burn_zkbin.clone());
+            debug!(target: "dao_contract::vote::wallet::Builder", "input_proof Proof::create()");
+            let input_proof = Proof::create(&burn_pk, &[circuit], &public_inputs, &mut OsRng)
+                .expect("DAO::vote() proving error!");
+            proofs.push(input_proof);
+
+            let input = VoteInput {
+                nullifier: Nullifier::from(nullifier),
+                vote_commit,
+                merkle_root,
+                signature_public,
+            };
+            inputs.push(input);
+        }
+
+        let token_commit = poseidon_hash::<2>([self.dao.gov_token_id.inner(), gov_token_blind]);
+
+        let (proposal_dest_x, proposal_dest_y) = self.proposal.dest.xy();
+
+        let proposal_amount = pallas::Base::from(self.proposal.amount);
+
+        let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
+        let dao_quorum = pallas::Base::from(self.dao.quorum);
+        let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot);
+        let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
+
+        let (dao_pub_x, dao_pub_y) = self.dao.public_key.xy();
+
+        let dao_bulla = poseidon_hash::<8>([
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            self.dao.gov_token_id.inner(),
+            dao_pub_x,
+            dao_pub_y,
+            self.dao.bulla_blind,
+        ]);
+
+        let proposal_bulla = poseidon_hash::<8>([
+            proposal_dest_x,
+            proposal_dest_y,
+            proposal_amount,
+            self.proposal.serial,
+            self.proposal.token_id.inner(),
+            dao_bulla,
+            self.proposal.blind,
+            // @tmp-workaround
+            self.proposal.blind,
+        ]);
+
+        let vote_option = self.vote.vote_option as u64;
+        assert!(vote_option == 0 || vote_option == 1);
+
+        let yes_vote_commit =
+            pedersen_commitment_u64(vote_option * vote_value, self.vote.vote_option_blind);
+        let yes_vote_commit_coords = yes_vote_commit.to_affine().coordinates().unwrap();
+
+        let all_vote_commit = pedersen_commitment_u64(vote_value, vote_value_blind);
+        let all_vote_commit_coords = all_vote_commit.to_affine().coordinates().unwrap();
+
+        /*
+        let zk_info = zk_bins.lookup(&"dao-vote-main".to_string()).unwrap();
+        let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
+            info
+        } else {
+            panic!("Not binary info")
+        };
+        let zk_bin = zk_info.bincode.clone();
+        */
+
+        let prover_witnesses = vec![
+            // proposal params
+            Witness::Base(Value::known(proposal_dest_x)),
+            Witness::Base(Value::known(proposal_dest_y)),
+            Witness::Base(Value::known(proposal_amount)),
+            Witness::Base(Value::known(self.proposal.serial)),
+            Witness::Base(Value::known(self.proposal.token_id.inner())),
+            Witness::Base(Value::known(self.proposal.blind)),
+            // DAO params
+            Witness::Base(Value::known(dao_proposer_limit)),
+            Witness::Base(Value::known(dao_quorum)),
+            Witness::Base(Value::known(dao_approval_ratio_quot)),
+            Witness::Base(Value::known(dao_approval_ratio_base)),
+            Witness::Base(Value::known(self.dao.gov_token_id.inner())),
+            Witness::Base(Value::known(dao_pub_x)),
+            Witness::Base(Value::known(dao_pub_y)),
+            Witness::Base(Value::known(self.dao.bulla_blind)),
+            // Vote
+            Witness::Base(Value::known(pallas::Base::from(vote_option))),
+            Witness::Scalar(Value::known(self.vote.vote_option_blind)),
+            // Total number of gov tokens allocated
+            Witness::Base(Value::known(pallas::Base::from(vote_value))),
+            Witness::Scalar(Value::known(vote_value_blind)),
+            // gov token
+            Witness::Base(Value::known(gov_token_blind)),
+        ];
+
+        let public_inputs = vec![
+            token_commit,
+            proposal_bulla,
+            // this should be a value commit??
+            *yes_vote_commit_coords.x(),
+            *yes_vote_commit_coords.y(),
+            *all_vote_commit_coords.x(),
+            *all_vote_commit_coords.y(),
+        ];
+
+        let circuit = ZkCircuit::new(prover_witnesses, main_zkbin.clone());
+
+        debug!(target: "dao_contract::vote::wallet::Builder", "main_proof = Proof::create()");
+        let main_proof = Proof::create(&main_pk, &[circuit], &public_inputs, &mut OsRng)
+            .expect("DAO::vote() proving error!");
+        proofs.push(main_proof);
+
+        let note = Note { vote: self.vote, vote_value, vote_value_blind };
+        let enc_note = note::encrypt(&note, &self.vote_keypair.public).unwrap();
+
+        let params = DaoVoteParams {
+            token_commit,
+            proposal_bulla,
+            yes_vote_commit,
+
+            ciphertext: enc_note.ciphertext,
+            ephem_public: enc_note.ephem_public,
+            inputs,
+        };
+
+        Ok((params, proofs))
+    }
+}

+ 14 - 10
src/contract/dao/src/entrypoint.rs

@@ -363,16 +363,20 @@ fn process_update(cid: ContractId, ix: &[u8]) -> ContractResult {
 
             let proposal_vote_db = db_lookup(cid, DAO_PROPOSAL_VOTES_TREE)?;
 
-            let Some(proposal_votes) = db_get(proposal_vote_db, &serialize(&update.proposal_bulla))? else {
-                msg!("Proposal {:?} not found in db", update.proposal_bulla);
-                return Err(ContractError::Custom(1));
-            };
-
-            let mut proposal_votes: ProposalVotes = deserialize(&proposal_votes)?;
-
-            proposal_votes.yes_votes_commit += update.yes_vote_commit;
-            proposal_votes.all_votes_commit += update.all_vote_commit;
-            proposal_votes.vote_nullifiers.append(&mut update.vote_nullifiers);
+            // This is not allowed in update
+            // Skip all this for now
+            //let Some(proposal_votes) = db_get(proposal_vote_db, &serialize(&update.proposal_bulla))? else {
+            //    msg!("Proposal {:?} not found in db", update.proposal_bulla);
+            //    return Err(ContractError::Custom(1));
+            //};
+
+            //msg!("vote dezer:(");
+            //let mut proposal_votes: ProposalVotes = deserialize(&proposal_votes)?;
+
+            //msg!("vote pdd");
+            //proposal_votes.yes_votes_commit += update.yes_vote_commit;
+            //proposal_votes.all_votes_commit += update.all_vote_commit;
+            //proposal_votes.vote_nullifiers.append(&mut update.vote_nullifiers);
 
             Ok(())
         }

+ 5 - 0
src/contract/dao/src/lib.rs

@@ -29,10 +29,15 @@ pub mod note;
 #[cfg(feature = "client")]
 /// Transaction building API for clients interacting with DAO contract
 pub mod dao_client;
+
 #[cfg(feature = "client")]
 /// Transaction building API for clients interacting with DAO contract
 pub mod dao_propose_client;
 
+#[cfg(feature = "client")]
+/// Transaction building API for clients interacting with DAO contract
+pub mod dao_vote_client;
+
 #[cfg(feature = "client")]
 /// Transaction building API for clients interacting with money contract
 pub mod money_client;

+ 10 - 0
src/contract/dao/tests/dao_harness.rs

@@ -82,6 +82,12 @@ pub struct DaoTestHarness {
 
     pub dao_propose_main_zkbin: ZkBinary,
     pub dao_propose_main_pk: ProvingKey,
+
+    pub dao_vote_burn_zkbin: ZkBinary,
+    pub dao_vote_burn_pk: ProvingKey,
+
+    pub dao_vote_main_zkbin: ZkBinary,
+    pub dao_vote_main_pk: ProvingKey,
 }
 
 impl DaoTestHarness {
@@ -226,6 +232,10 @@ impl DaoTestHarness {
             dao_propose_burn_pk,
             dao_propose_main_zkbin,
             dao_propose_main_pk,
+            dao_vote_burn_zkbin,
+            dao_vote_burn_pk,
+            dao_vote_main_zkbin,
+            dao_vote_main_pk,
         })
     }
 }

+ 392 - 2
src/contract/dao/tests/integration.rs

@@ -23,12 +23,13 @@ use darkfi_sdk::{
         constants::MERKLE_DEPTH,
         contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
         keypair::Keypair,
+        pedersen::pedersen_commitment_u64,
         poseidon_hash, MerkleNode, SecretKey, TokenId,
     },
     incrementalmerkletree::{bridgetree::BridgeTree, Tree},
     pasta::{
         arithmetic::CurveAffine,
-        group::{ff::Field, Curve},
+        group::{ff::Field, Curve, Group},
         pallas,
     },
     tx::ContractCall,
@@ -39,7 +40,7 @@ use rand::rngs::OsRng;
 
 use darkfi_dao_contract::{
     dao_client::{build_dao_mint_tx, MerkleTree, WalletCache},
-    dao_propose_client, money_client, note, DaoFunction,
+    dao_propose_client, dao_vote_client, money_client, note, DaoFunction,
 };
 
 use darkfi_money_contract::{
@@ -487,5 +488,394 @@ async fn integration_test() -> Result<()> {
     debug!(target: "demo", "  dao_bulla: {:?}", dao_bulla.inner());
     debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla);
 
+    // =======================================================
+    // Proposal is accepted!
+    // Start the voting
+    // =======================================================
+
+    // Copying these schizo comments from python code:
+    // Lets the voting begin
+    // Voters have access to the proposal and dao data
+    //   vote_state = VoteState()
+    // We don't need to copy nullifier set because it is checked from gov_state
+    // in vote_state_transition() anyway
+    //
+    // TODO: what happens if voters don't unblind their vote
+    // Answer:
+    //   1. there is a time limit
+    //   2. both the MPC or users can unblind
+    //
+    // TODO: bug if I vote then send money, then we can double vote
+    // TODO: all timestamps missing
+    //       - timelock (future voting starts in 2 days)
+    // Fix: use nullifiers from money gov state only from
+    // beginning of gov period
+    // Cannot use nullifiers from before voting period
+
+    debug!(target: "demo", "Stage 5. Start voting");
+
+    // We were previously saving updates here for testing
+    // let mut updates = vec![];
+
+    // User 1: YES
+
+    let (money_leaf_position, money_merkle_path) = {
+        let tree = &cache.tree;
+        let leaf_position = gov_recv[0].leaf_position;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    let signature_secret = SecretKey::random(&mut OsRng);
+    let input = dao_vote_client::BuilderInput {
+        secret: dao_th.alice_kp.secret,
+        note: gov_recv[0].note.clone(),
+        leaf_position: money_leaf_position,
+        merkle_path: money_merkle_path,
+        signature_secret,
+    };
+
+    let vote_option: bool = true;
+    // assert!(vote_option || !vote_option); // wtf
+
+    // We create a new keypair to encrypt the vote.
+    // For the demo MVP, you can just use the dao_keypair secret
+    let vote_keypair_1 = Keypair::random(&mut OsRng);
+
+    let builder = dao_vote_client::Builder {
+        inputs: vec![input],
+        vote: dao_vote_client::Vote {
+            vote_option,
+            vote_option_blind: pallas::Scalar::random(&mut OsRng),
+        },
+        vote_keypair: vote_keypair_1,
+        proposal: proposal.clone(),
+        dao: dao_params.clone(),
+    };
+    let (params, proofs) = builder.build(
+        &dao_th.dao_vote_burn_zkbin,
+        &dao_th.dao_vote_burn_pk,
+        &dao_th.dao_vote_main_zkbin,
+        &dao_th.dao_vote_main_pk,
+    )?;
+
+    let contract_id = *DAO_CONTRACT_ID;
+
+    let mut data = vec![DaoFunction::Vote as u8];
+    params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id, data }];
+    let proofs = vec![proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &vec![signature_secret])?;
+    tx.signatures = vec![sigs];
+
+    dao_th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+
+    // Secret vote info. Needs to be revealed at some point.
+    // TODO: look into verifiable encryption for notes
+    // TODO: look into timelock puzzle as a possibility
+    let vote_note_1 = {
+        // TODO: EncryptedNote should be accessible by wasm and put in the structs directly
+        let enc_note = note::EncryptedNote2 {
+            ciphertext: params.ciphertext,
+            ephem_public: params.ephem_public,
+        };
+        let note: dao_vote_client::Note = enc_note.decrypt(&vote_keypair_1.secret).unwrap();
+        note
+    };
+    debug!(target: "demo", "User 1 voted!");
+    debug!(target: "demo", "  vote_option: {}", vote_note_1.vote.vote_option);
+    debug!(target: "demo", "  value: {}", vote_note_1.vote_value);
+
+    // User 2: NO
+
+    let (money_leaf_position, money_merkle_path) = {
+        let tree = &cache.tree;
+        let leaf_position = gov_recv[1].leaf_position;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    let signature_secret = SecretKey::random(&mut OsRng);
+    let input = dao_vote_client::BuilderInput {
+        //secret: gov_keypair_2.secret,
+        secret: dao_th.bob_kp.secret,
+        note: gov_recv[1].note.clone(),
+        leaf_position: money_leaf_position,
+        merkle_path: money_merkle_path,
+        signature_secret,
+    };
+
+    let vote_option: bool = false;
+    // assert!(vote_option || !vote_option); // wtf
+
+    // We create a new keypair to encrypt the vote.
+    let vote_keypair_2 = Keypair::random(&mut OsRng);
+
+    let builder = dao_vote_client::Builder {
+        inputs: vec![input],
+        vote: dao_vote_client::Vote {
+            vote_option,
+            vote_option_blind: pallas::Scalar::random(&mut OsRng),
+        },
+        vote_keypair: vote_keypair_2,
+        proposal: proposal.clone(),
+        dao: dao_params.clone(),
+    };
+    let (params, proofs) = builder.build(
+        &dao_th.dao_vote_burn_zkbin,
+        &dao_th.dao_vote_burn_pk,
+        &dao_th.dao_vote_main_zkbin,
+        &dao_th.dao_vote_main_pk,
+    )?;
+
+    let contract_id = *DAO_CONTRACT_ID;
+
+    let mut data = vec![DaoFunction::Vote as u8];
+    params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id, data }];
+    let proofs = vec![proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &vec![signature_secret])?;
+    tx.signatures = vec![sigs];
+
+    dao_th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+
+    let vote_note_2 = {
+        // TODO: EncryptedNote should be accessible by wasm and put in the structs directly
+        let enc_note = note::EncryptedNote2 {
+            ciphertext: params.ciphertext,
+            ephem_public: params.ephem_public,
+        };
+        let note: dao_vote_client::Note = enc_note.decrypt(&vote_keypair_2.secret).unwrap();
+        note
+    };
+    debug!(target: "demo", "User 2 voted!");
+    debug!(target: "demo", "  vote_option: {}", vote_note_2.vote.vote_option);
+    debug!(target: "demo", "  value: {}", vote_note_2.vote_value);
+
+    // User 3: YES
+
+    let (money_leaf_position, money_merkle_path) = {
+        let tree = &cache.tree;
+        let leaf_position = gov_recv[2].leaf_position;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    let signature_secret = SecretKey::random(&mut OsRng);
+    let input = dao_vote_client::BuilderInput {
+        //secret: gov_keypair_3.secret,
+        secret: dao_th.charlie_kp.secret,
+        note: gov_recv[2].note.clone(),
+        leaf_position: money_leaf_position,
+        merkle_path: money_merkle_path,
+        signature_secret,
+    };
+
+    let vote_option: bool = true;
+    // assert!(vote_option || !vote_option); // wtf
+
+    // We create a new keypair to encrypt the vote.
+    let vote_keypair_3 = Keypair::random(&mut OsRng);
+
+    let builder = dao_vote_client::Builder {
+        inputs: vec![input],
+        vote: dao_vote_client::Vote {
+            vote_option,
+            vote_option_blind: pallas::Scalar::random(&mut OsRng),
+        },
+        vote_keypair: vote_keypair_3,
+        proposal: proposal.clone(),
+        dao: dao_params.clone(),
+    };
+    let (params, proofs) = builder.build(
+        &dao_th.dao_vote_burn_zkbin,
+        &dao_th.dao_vote_burn_pk,
+        &dao_th.dao_vote_main_zkbin,
+        &dao_th.dao_vote_main_pk,
+    )?;
+
+    let contract_id = *DAO_CONTRACT_ID;
+
+    let mut data = vec![DaoFunction::Vote as u8];
+    params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id, data }];
+    let proofs = vec![proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &vec![signature_secret])?;
+    tx.signatures = vec![sigs];
+
+    dao_th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+
+    // Secret vote info. Needs to be revealed at some point.
+    // TODO: look into verifiable encryption for notes
+    // TODO: look into timelock puzzle as a possibility
+    let vote_note_3 = {
+        // TODO: EncryptedNote should be accessible by wasm and put in the structs directly
+        let enc_note = note::EncryptedNote2 {
+            ciphertext: params.ciphertext,
+            ephem_public: params.ephem_public,
+        };
+        let note: dao_vote_client::Note = enc_note.decrypt(&vote_keypair_3.secret).unwrap();
+        note
+    };
+    debug!(target: "demo", "User 3 voted!");
+    debug!(target: "demo", "  vote_option: {}", vote_note_3.vote.vote_option);
+    debug!(target: "demo", "  value: {}", vote_note_3.vote_value);
+
+    // Every votes produces a semi-homomorphic encryption of their vote.
+    // Which is either yes or no
+    // We copy the state tree for the governance token so coins can be used
+    // to vote on other proposals at the same time.
+    // With their vote, they produce a ZK proof + nullifier
+    // The votes are unblinded by MPC to a selected party at the end of the
+    // voting period.
+    // (that's if we want votes to be hidden during voting)
+
+    let mut yes_votes_value = 0;
+    let mut yes_votes_blind = pallas::Scalar::from(0);
+    let mut yes_votes_commit = pallas::Point::identity();
+
+    let mut all_votes_value = 0;
+    let mut all_votes_blind = pallas::Scalar::from(0);
+    let mut all_votes_commit = pallas::Point::identity();
+
+    // We were previously saving votes to a Vec<Update> for testing.
+    // However since Update is now UpdateBase it gets moved into update.apply().
+    // So we need to think of another way to run these tests.
+    //assert!(updates.len() == 3);
+
+    for (i, note /* update*/) in [vote_note_1, vote_note_2, vote_note_3]
+        .iter() /*.zip(updates)*/
+        .enumerate()
+    {
+        let vote_commit = pedersen_commitment_u64(note.vote_value, note.vote_value_blind);
+        //assert!(update.value_commit == all_vote_value_commit);
+        all_votes_commit += vote_commit;
+        all_votes_blind += note.vote_value_blind;
+
+        let yes_vote_commit = pedersen_commitment_u64(
+            note.vote.vote_option as u64 * note.vote_value,
+            note.vote.vote_option_blind,
+        );
+        //assert!(update.yes_vote_commit == yes_vote_commit);
+
+        yes_votes_commit += yes_vote_commit;
+        yes_votes_blind += note.vote.vote_option_blind;
+
+        let vote_option = note.vote.vote_option;
+
+        if vote_option {
+            yes_votes_value += note.vote_value;
+        }
+        all_votes_value += note.vote_value;
+        let vote_result: String = if vote_option { "yes".to_string() } else { "no".to_string() };
+
+        debug!("Voter {} voted {}", i, vote_result);
+    }
+
+    debug!("Outcome = {} / {}", yes_votes_value, all_votes_value);
+
+    assert!(all_votes_commit == pedersen_commitment_u64(all_votes_value, all_votes_blind));
+    assert!(yes_votes_commit == pedersen_commitment_u64(yes_votes_value, yes_votes_blind));
+
+    // =======================================================
+    // Execute the vote
+    // =======================================================
+
+    debug!(target: "demo", "Stage 6. Execute vote");
+
+    // Used to export user_data from this coin so it can be accessed by DAO::exec()
+    let user_data_blind = pallas::Base::random(&mut OsRng);
+
+    let user_serial = pallas::Base::random(&mut OsRng);
+    let user_coin_blind = pallas::Base::random(&mut OsRng);
+    let dao_serial = pallas::Base::random(&mut OsRng);
+    let dao_coin_blind = pallas::Base::random(&mut OsRng);
+    let input_value = treasury_note.value;
+    let input_value_blind = pallas::Scalar::random(&mut OsRng);
+    let tx_signature_secret = SecretKey::random(&mut OsRng);
+    let exec_signature_secret = SecretKey::random(&mut OsRng);
+
+    let (treasury_leaf_position, treasury_merkle_path) = {
+        let tree = &cache.tree;
+        let leaf_position = dao_recv_coin.leaf_position;
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    let input = money_client::BuilderInputInfo {
+        leaf_position: treasury_leaf_position,
+        merkle_path: treasury_merkle_path,
+        secret: dao_th.dao_kp.secret,
+        note: treasury_note,
+        user_data_blind,
+        value_blind: input_value_blind,
+        signature_secret: tx_signature_secret,
+    };
+
+    // TODO: this should be the contract/func ID
+    let spend_hook = pallas::Base::from(110);
+    // The user_data can be a simple hash of the items passed into the ZK proof
+    // up to corresponding linked ZK proof to interpret however they need.
+    // In out case, it's the bulla for the DAO
+    let user_data = dao_bulla.inner();
+
+    let builder = money_client::Builder {
+        clear_inputs: vec![],
+        inputs: vec![input],
+        outputs: vec![
+            // Sending money
+            money_client::BuilderOutputInfo {
+                value: 1000,
+                token_id: xdrk_token_id,
+                //public: user_keypair.public,
+                public: receiver_keypair.public,
+                serial: proposal.serial,
+                coin_blind: proposal.blind,
+                spend_hook: pallas::Base::from(0),
+                user_data: pallas::Base::from(0),
+            },
+            // Change back to DAO
+            money_client::BuilderOutputInfo {
+                value: xdrk_supply - 1000,
+                token_id: xdrk_token_id,
+                public: dao_th.dao_kp.public,
+                serial: dao_serial,
+                coin_blind: dao_coin_blind,
+                spend_hook,
+                user_data,
+            },
+        ],
+    };
+    //let (xfer_params, xfer_proofs) = builder.build(
+    //    &dao_th.dao_propose_burn_zkbin,
+    //    &dao_th.dao_propose_burn_pk,
+    //    &dao_th.dao_propose_main_zkbin,
+    //    &dao_th.dao_propose_main_pk,
+    //)?;
+
+    //let builder = dao::exec::wallet::Builder {
+    //    proposal,
+    //    dao: dao_params.clone(),
+    //    yes_votes_value,
+    //    all_votes_value,
+    //    yes_votes_blind,
+    //    all_votes_blind,
+    //    user_serial,
+    //    user_coin_blind,
+    //    dao_serial,
+    //    dao_coin_blind,
+    //    input_value,
+    //    input_value_blind,
+    //    hook_dao_exec: *dao::exec::FUNC_ID,
+    //    signature_secret: exec_signature_secret,
+    //};
+
     Ok(())
 }