فهرست منبع

drk: refactored DAO votes functionalities

skoupidi 2 سال پیش
والد
کامیت
cbb53aa8ec
7فایلهای تغییر یافته به همراه307 افزوده شده و 258 حذف شده
  1. 11 6
      bin/drk/dao.sql
  2. 2 9
      bin/drk/src/cli_util.rs
  3. 226 165
      bin/drk/src/dao.rs
  4. 17 34
      bin/drk/src/main.rs
  5. 0 1
      bin/drk/src/rpc.rs
  6. 41 37
      contrib/localnet/darkfid-single-node/README.md
  7. 10 6
      src/contract/dao/src/client/vote.rs

+ 11 - 6
bin/drk/dao.sql

@@ -149,17 +149,22 @@ CREATE TABLE IF NOT EXISTS Fd8kfCuqU8BoFFp6GcXv5pC8XXRkBK7gUPQX5XDz7iXj_dao_prop
 );
 );
 
 
 CREATE TABLE IF NOT EXISTS Fd8kfCuqU8BoFFp6GcXv5pC8XXRkBK7gUPQX5XDz7iXj_dao_votes (
 CREATE TABLE IF NOT EXISTS Fd8kfCuqU8BoFFp6GcXv5pC8XXRkBK7gUPQX5XDz7iXj_dao_votes (
+    -- Numeric identifier of the vote
     vote_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
     vote_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    -- Bulla identifier of the proposal this vote is for
     proposal_bulla BLOB NOT NULL,
     proposal_bulla BLOB NOT NULL,
+    -- The vote
     vote_option INTEGER NOT NULL,
     vote_option INTEGER NOT NULL,
+    -- Blinding factor for the yes vote
     yes_vote_blind BLOB NOT NULL,
     yes_vote_blind BLOB NOT NULL,
+    -- Value of all votes
     all_vote_value BLOB NOT NULL,
     all_vote_value BLOB NOT NULL,
+    -- Blinding facfor of all votes
     all_vote_blind BLOB NOT NULL,
     all_vote_blind BLOB NOT NULL,
-    -- these values are NULL until the vote is minted on chain
-    -- and received by the DAO
-    tx_hash BLOB,
-    call_index INTEGER,
-    -- My code has votes merkle tree and position for votes, but
-    -- that might be a mistake...
+    -- Transaction hash where this vote was casted
+    tx_hash BLOB NOT NULL,
+    -- call index in the transaction where this vote was casted
+    call_index INTEGER NOT NULL,
+
     FOREIGN KEY(proposal_bulla) REFERENCES Fd8kfCuqU8BoFFp6GcXv5pC8XXRkBK7gUPQX5XDz7iXj_dao_proposals(bulla) ON DELETE CASCADE ON UPDATE CASCADE
     FOREIGN KEY(proposal_bulla) REFERENCES Fd8kfCuqU8BoFFp6GcXv5pC8XXRkBK7gUPQX5XDz7iXj_dao_proposals(bulla) ON DELETE CASCADE ON UPDATE CASCADE
 );
 );

+ 2 - 9
bin/drk/src/cli_util.rs

@@ -326,7 +326,7 @@ pub fn generate_completions(shell: &str) -> Result<()> {
     let vote = Arg::with_name("vote").help("Vote (0 for NO, 1 for YES)");
     let vote = Arg::with_name("vote").help("Vote (0 for NO, 1 for YES)");
 
 
     let vote_weight =
     let vote_weight =
-        Arg::with_name("vote-weight").help("Vote weight (amount of governance tokens)");
+        Arg::with_name("vote-weight").help("Optional vote weight (amount of governance tokens)");
 
 
     let vote = SubCommand::with_name("vote").about("Vote on a given proposal").args(&vec![
     let vote = SubCommand::with_name("vote").about("Vote on a given proposal").args(&vec![
         bulla.clone(),
         bulla.clone(),
@@ -360,16 +360,9 @@ pub fn generate_completions(shell: &str) -> Result<()> {
         .long("reset")
         .long("reset")
         .help("Reset Merkle tree and start scanning from first block");
         .help("Reset Merkle tree and start scanning from first block");
 
 
-    let list = Arg::with_name("list").long("list").help("List all available checkpoints");
-
-    let checkpoint = Arg::with_name("checkpoint")
-        .long("checkpoint")
-        .takes_value(true)
-        .help("Reset Merkle tree to checkpoint index and start scanning");
-
     let scan = SubCommand::with_name("scan")
     let scan = SubCommand::with_name("scan")
         .about("Scan the blockchain and parse relevant transactions")
         .about("Scan the blockchain and parse relevant transactions")
-        .args(&vec![reset, list, checkpoint]);
+        .args(&vec![reset]);
 
 
     // Explorer
     // Explorer
     let tx_hash = Arg::with_name("tx-hash").help("Transaction hash");
     let tx_hash = Arg::with_name("tx-hash").help("Transaction hash");

+ 226 - 165
bin/drk/src/dao.rs

@@ -41,14 +41,13 @@ use darkfi_dao_contract::{
     DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
     DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
 };
 };
 use darkfi_money_contract::{
 use darkfi_money_contract::{
-    client::OwnCoin,
     model::{CoinAttributes, TokenId},
     model::{CoinAttributes, TokenId},
     MoneyFunction, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
     MoneyFunction, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
 };
 };
 use darkfi_sdk::{
 use darkfi_sdk::{
     bridgetree,
     bridgetree,
     crypto::{
     crypto::{
-        smt::{MemoryStorageFp, PoseidonFp, SmtMemoryFp, EMPTY_NODES_FP},
+        smt::{PoseidonFp, EMPTY_NODES_FP},
         util::{fp_mod_fv, fp_to_u64},
         util::{fp_mod_fv, fp_to_u64},
         BaseBlind, Blind, FuncId, FuncRef, Keypair, MerkleNode, MerkleTree, PublicKey, ScalarBlind,
         BaseBlind, Blind, FuncId, FuncRef, Keypair, MerkleNode, MerkleTree, PublicKey, ScalarBlind,
         SecretKey, DAO_CONTRACT_ID, MONEY_CONTRACT_ID,
         SecretKey, DAO_CONTRACT_ID, MONEY_CONTRACT_ID,
@@ -326,8 +325,8 @@ impl fmt::Display for ProposalRecord {
 pub struct DaoVote {
 pub struct DaoVote {
     /// Numeric identifier for the vote
     /// Numeric identifier for the vote
     pub id: u64,
     pub id: u64,
-    /// Numeric identifier for the proposal related to this vote
-    pub proposal_id: u64,
+    /// Bulla identifier of the proposal this vote is for
+    pub proposal: DaoProposalBulla,
     /// The vote
     /// The vote
     pub vote_option: bool,
     pub vote_option: bool,
     /// Blinding factor for the yes vote
     /// Blinding factor for the yes vote
@@ -337,9 +336,9 @@ pub struct DaoVote {
     /// Blinding facfor of all votes
     /// Blinding facfor of all votes
     pub all_vote_blind: ScalarBlind,
     pub all_vote_blind: ScalarBlind,
     /// Transaction hash where this vote was casted
     /// Transaction hash where this vote was casted
-    pub tx_hash: Option<TransactionHash>,
+    pub tx_hash: TransactionHash,
     /// call index in the transaction where this vote was casted
     /// call index in the transaction where this vote was casted
-    pub call_index: Option<u8>,
+    pub call_index: u8,
 }
 }
 
 
 impl Drk {
 impl Drk {
@@ -591,46 +590,38 @@ impl Drk {
     }
     }
 
 
     /// Append data related to DAO contract transactions into the wallet database.
     /// Append data related to DAO contract transactions into the wallet database.
-    /// Optionally, if `confirm` is true, also append the data in the Merkle trees, etc.
     pub async fn apply_tx_dao_data(
     pub async fn apply_tx_dao_data(
         &self,
         &self,
         data: &[u8],
         data: &[u8],
         tx_hash: TransactionHash,
         tx_hash: TransactionHash,
         call_idx: u8,
         call_idx: u8,
-        confirm: bool,
     ) -> Result<()> {
     ) -> Result<()> {
         // DAOs that have been minted
         // DAOs that have been minted
-        let mut new_dao_bullas: Vec<(DaoBulla, Option<TransactionHash>, u8)> = vec![];
+        let mut new_dao_bullas: Vec<(DaoBulla, TransactionHash, u8)> = vec![];
         // DAO proposals that have been minted
         // DAO proposals that have been minted
-        let mut new_dao_proposals: Vec<(
-            DaoProposeParams,
-            Option<MerkleTree>,
-            Option<TransactionHash>,
-            u8,
-        )> = vec![];
+        let mut new_dao_proposals: Vec<(DaoProposeParams, MerkleTree, TransactionHash, u8)> =
+            vec![];
         // DAO votes that have been seen
         // DAO votes that have been seen
-        let mut new_dao_votes: Vec<(DaoVoteParams, Option<TransactionHash>, u8)> = vec![];
+        let mut new_dao_votes: Vec<(DaoVoteParams, TransactionHash, u8)> = vec![];
+
+        // We need to clone the tree here for reproducing the snapshot Merkle root
+        let money_tree = self.get_money_tree().await?;
 
 
         // Run through the transaction and see what we got:
         // Run through the transaction and see what we got:
         match DaoFunction::try_from(data[0])? {
         match DaoFunction::try_from(data[0])? {
             DaoFunction::Mint => {
             DaoFunction::Mint => {
                 println!("[apply_tx_dao_data] Found Dao::Mint call");
                 println!("[apply_tx_dao_data] Found Dao::Mint call");
                 let params: DaoMintParams = deserialize_async(&data[1..]).await?;
                 let params: DaoMintParams = deserialize_async(&data[1..]).await?;
-                let tx_hash = if confirm { Some(tx_hash) } else { None };
                 new_dao_bullas.push((params.dao_bulla, tx_hash, call_idx));
                 new_dao_bullas.push((params.dao_bulla, tx_hash, call_idx));
             }
             }
             DaoFunction::Propose => {
             DaoFunction::Propose => {
                 println!("[apply_tx_dao_data] Found Dao::Propose call");
                 println!("[apply_tx_dao_data] Found Dao::Propose call");
                 let params: DaoProposeParams = deserialize_async(&data[1..]).await?;
                 let params: DaoProposeParams = deserialize_async(&data[1..]).await?;
-                let tx_hash = if confirm { Some(tx_hash) } else { None };
-                // We need to clone the tree here for reproducing the snapshot Merkle root
-                let money_tree = if confirm { Some(self.get_money_tree().await?) } else { None };
-                new_dao_proposals.push((params, money_tree, tx_hash, call_idx));
+                new_dao_proposals.push((params, money_tree.clone(), tx_hash, call_idx));
             }
             }
             DaoFunction::Vote => {
             DaoFunction::Vote => {
                 println!("[apply_tx_dao_data] Found Dao::Vote call");
                 println!("[apply_tx_dao_data] Found Dao::Vote call");
                 let params: DaoVoteParams = deserialize_async(&data[1..]).await?;
                 let params: DaoVoteParams = deserialize_async(&data[1..]).await?;
-                let tx_hash = if confirm { Some(tx_hash) } else { None };
                 new_dao_votes.push((params, tx_hash, call_idx));
                 new_dao_votes.push((params, tx_hash, call_idx));
             }
             }
             DaoFunction::Exec => {
             DaoFunction::Exec => {
@@ -639,17 +630,10 @@ impl Drk {
             }
             }
             DaoFunction::AuthMoneyTransfer => {
             DaoFunction::AuthMoneyTransfer => {
                 println!("[apply_tx_dao_data] Found Dao::AuthMoneyTransfer call");
                 println!("[apply_tx_dao_data] Found Dao::AuthMoneyTransfer call");
-                // TODO: implement
+                // Does nothing, just verifies the other calls are correct
             }
             }
         }
         }
 
 
-        // This code should only be executed when finalized blocks are being scanned.
-        // Here we write the tx metadata, and actually do Merkle tree appends so we
-        // have to make sure it's the same for everyone.
-        if !confirm {
-            return Ok(());
-        }
-
         let daos = self.get_daos().await?;
         let daos = self.get_daos().await?;
         let mut daos_to_confirm = vec![];
         let mut daos_to_confirm = vec![];
         let (mut daos_tree, mut proposals_tree) = self.get_dao_trees().await?;
         let (mut daos_tree, mut proposals_tree) = self.get_dao_trees().await?;
@@ -664,7 +648,7 @@ impl Drk {
                     // We have this DAO imported in our wallet. Add the metadata:
                     // We have this DAO imported in our wallet. Add the metadata:
                     let mut dao_to_confirm = dao.clone();
                     let mut dao_to_confirm = dao.clone();
                     dao_to_confirm.leaf_position = daos_tree.mark();
                     dao_to_confirm.leaf_position = daos_tree.mark();
-                    dao_to_confirm.tx_hash = new_bulla.1;
+                    dao_to_confirm.tx_hash = Some(new_bulla.1);
                     dao_to_confirm.call_index = Some(new_bulla.2);
                     dao_to_confirm.call_index = Some(new_bulla.2);
                     daos_to_confirm.push(dao_to_confirm);
                     daos_to_confirm.push(dao_to_confirm);
                 }
                 }
@@ -688,8 +672,8 @@ impl Drk {
                             Ok(p) => {
                             Ok(p) => {
                                 let mut our_proposal = p;
                                 let mut our_proposal = p;
                                 our_proposal.leaf_position = proposals_tree.mark();
                                 our_proposal.leaf_position = proposals_tree.mark();
-                                our_proposal.money_snapshot_tree = proposal.1;
-                                our_proposal.tx_hash = proposal.2;
+                                our_proposal.money_snapshot_tree = Some(proposal.1);
+                                our_proposal.tx_hash = Some(proposal.2);
                                 our_proposal.call_index = Some(proposal.3);
                                 our_proposal.call_index = Some(proposal.3);
                                 our_proposal
                                 our_proposal
                             }
                             }
@@ -697,8 +681,8 @@ impl Drk {
                                 proposal: note,
                                 proposal: note,
                                 data: None,
                                 data: None,
                                 leaf_position: proposals_tree.mark(),
                                 leaf_position: proposals_tree.mark(),
-                                money_snapshot_tree: proposal.1,
-                                tx_hash: proposal.2,
+                                money_snapshot_tree: Some(proposal.1),
+                                tx_hash: Some(proposal.2),
                                 call_index: Some(proposal.3),
                                 call_index: Some(proposal.3),
                             },
                             },
                         };
                         };
@@ -711,45 +695,72 @@ impl Drk {
 
 
         let mut dao_votes: Vec<DaoVote> = vec![];
         let mut dao_votes: Vec<DaoVote> = vec![];
         for vote in new_dao_votes {
         for vote in new_dao_votes {
-            for dao in &daos {
-                // TODO: we shouldn't decrypt with all DAOs here
-                let note = vote.0.note.decrypt_unsafe(&dao.params.secret_key)?;
-                println!("[apply_tx_dao_data] Managed to decrypt DAO proposal vote note");
-                let daos_proposals = self.get_dao_proposals(&dao.name).await?;
-                let mut proposal_id = None;
-
-                for i in daos_proposals {
-                    if i.bulla() == vote.0.proposal_bulla {
-                        proposal_id = Some(0);
-                        break
+            // Check if we got the corresponding proposal
+            let mut proposal = None;
+            match self.get_dao_proposal_by_bulla(&vote.0.proposal_bulla).await {
+                Ok(p) => proposal = Some(p),
+                Err(_) => {
+                    for p in &our_proposals {
+                        if p.bulla() == vote.0.proposal_bulla {
+                            proposal = Some(p.clone());
+                            break
+                        }
                     }
                     }
                 }
                 }
+            };
+            let Some(proposal) = proposal else { continue };
 
 
-                if proposal_id.is_none() {
-                    println!("[apply_tx_dao_data] Warning: Decrypted DaoVoteNote but did not find proposal");
-                    break
+            // Grab the proposal DAO
+            let dao = match self.get_dao_by_bulla(&proposal.proposal.dao_bulla).await {
+                Ok(d) => d,
+                Err(e) => {
+                    println!(
+                        "[apply_tx_dao_data] Couldn't find proposal {} DAO {}: {e}",
+                        proposal.bulla(),
+                        proposal.proposal.dao_bulla,
+                    );
+                    continue
                 }
                 }
+            };
 
 
-                let vote_option = fp_to_u64(note[0]).unwrap();
-                assert!(vote_option == 0 || vote_option == 1);
-                let vote_option = vote_option != 0;
-                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]));
-
-                let v = DaoVote {
-                    id: 0,
-                    proposal_id: proposal_id.unwrap(),
-                    vote_option,
-                    yes_vote_blind,
-                    all_vote_value,
-                    all_vote_blind,
-                    tx_hash: vote.1,
-                    call_index: Some(vote.2),
-                };
+            // Decrypt the vote note
+            let note = match vote.0.note.decrypt_unsafe(&dao.params.secret_key) {
+                Ok(n) => n,
+                Err(e) => {
+                    println!("[apply_tx_dao_data] Couldn't decrypt proposal {} vote with DAO {} keys: {e}",
+                        proposal.bulla(),
+                        proposal.proposal.dao_bulla,
+                    );
+                    continue
+                }
+            };
 
 
-                dao_votes.push(v);
+            // Create the DAO vote record
+            let vote_option = fp_to_u64(note[0]).unwrap();
+            if vote_option > 1 {
+                println!(
+                    "[apply_tx_dao_data] Malformed vote for proposal {}: {vote_option}",
+                    proposal.bulla(),
+                );
+                continue
             }
             }
+            let vote_option = vote_option != 0;
+            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]));
+
+            let v = DaoVote {
+                id: 0,
+                proposal: vote.0.proposal_bulla,
+                vote_option,
+                yes_vote_blind,
+                all_vote_value,
+                all_vote_blind,
+                tx_hash: vote.1,
+                call_index: vote.2,
+            };
+
+            dao_votes.push(v);
         }
         }
 
 
         if let Err(e) = self.put_dao_trees(&daos_tree, &proposals_tree).await {
         if let Err(e) = self.put_dao_trees(&daos_tree, &proposals_tree).await {
@@ -941,13 +952,13 @@ impl Drk {
             self.wallet.exec_sql(
             self.wallet.exec_sql(
                 &query,
                 &query,
                 rusqlite::params![
                 rusqlite::params![
-                    vote.proposal_id,
+                    serialize_async(&vote.proposal).await,
                     vote.vote_option as u64,
                     vote.vote_option as u64,
                     serialize_async(&vote.yes_vote_blind).await,
                     serialize_async(&vote.yes_vote_blind).await,
                     serialize_async(&vote.all_vote_value).await,
                     serialize_async(&vote.all_vote_value).await,
                     serialize_async(&vote.all_vote_blind).await,
                     serialize_async(&vote.all_vote_blind).await,
-                    serialize_async(&vote.tx_hash.unwrap()).await,
-                    vote.call_index.unwrap(),
+                    serialize_async(&vote.tx_hash).await,
+                    vote.call_index,
                 ],
                 ],
             )?;
             )?;
 
 
@@ -1185,16 +1196,12 @@ impl Drk {
                 return Err(Error::ParseFailed("[get_dao_proposal_votes] ID parsing failed"))
                 return Err(Error::ParseFailed("[get_dao_proposal_votes] ID parsing failed"))
             };
             };
 
 
-            let Value::Integer(proposal_id) = row[1] else {
-                return Err(Error::ParseFailed(
-                    "[get_dao_proposal_votes] Proposal ID parsing failed",
-                ))
-            };
-            let Ok(proposal_id) = u64::try_from(proposal_id) else {
+            let Value::Blob(ref proposal_bytes) = row[1] else {
                 return Err(Error::ParseFailed(
                 return Err(Error::ParseFailed(
-                    "[get_dao_proposal_votes] Proposal ID parsing failed",
+                    "[get_dao_proposal_votes] Proposal bytes bytes parsing failed",
                 ))
                 ))
             };
             };
+            let proposal = deserialize_async(proposal_bytes).await?;
 
 
             let Value::Integer(vote_option) = row[2] else {
             let Value::Integer(vote_option) = row[2] else {
                 return Err(Error::ParseFailed(
                 return Err(Error::ParseFailed(
@@ -1234,11 +1241,7 @@ impl Drk {
                     "[get_dao_proposal_votes] Transaction hash bytes parsing failed",
                     "[get_dao_proposal_votes] Transaction hash bytes parsing failed",
                 ))
                 ))
             };
             };
-            let tx_hash = if tx_hash_bytes.is_empty() {
-                None
-            } else {
-                Some(deserialize_async(tx_hash_bytes).await?)
-            };
+            let tx_hash = deserialize_async(tx_hash_bytes).await?;
 
 
             let Value::Integer(call_index) = row[7] else {
             let Value::Integer(call_index) = row[7] else {
                 return Err(Error::ParseFailed("[get_dao_proposal_votes] Call index parsing failed"))
                 return Err(Error::ParseFailed("[get_dao_proposal_votes] Call index parsing failed"))
@@ -1246,11 +1249,10 @@ impl Drk {
             let Ok(call_index) = u8::try_from(call_index) else {
             let Ok(call_index) = u8::try_from(call_index) else {
                 return Err(Error::ParseFailed("[get_dao_proposal_votes] Call index parsing failed"))
                 return Err(Error::ParseFailed("[get_dao_proposal_votes] Call index parsing failed"))
             };
             };
-            let call_index = Some(call_index);
 
 
             let vote = DaoVote {
             let vote = DaoVote {
                 id,
                 id,
-                proposal_id,
+                proposal,
                 vote_option,
                 vote_option,
                 yes_vote_blind,
                 yes_vote_blind,
                 all_vote_value,
                 all_vote_value,
@@ -1356,7 +1358,7 @@ impl Drk {
     ) -> Result<ProposalRecord> {
     ) -> Result<ProposalRecord> {
         // Fetch DAO and check its deployed
         // Fetch DAO and check its deployed
         let dao = self.get_dao_by_name(name).await?;
         let dao = self.get_dao_by_name(name).await?;
-        if dao.leaf_position.is_none() || dao.tx_hash.is_none() {
+        if dao.leaf_position.is_none() || dao.tx_hash.is_none() || dao.call_index.is_none() {
             return Err(Error::Custom(
             return Err(Error::Custom(
                 "[dao_propose_transfer] DAO seems to not have been deployed yet".to_string(),
                 "[dao_propose_transfer] DAO seems to not have been deployed yet".to_string(),
             ))
             ))
@@ -1452,17 +1454,22 @@ impl Drk {
         // Check we know the plaintext data
         // Check we know the plaintext data
         if proposal.data.is_none() {
         if proposal.data.is_none() {
             return Err(Error::Custom(
             return Err(Error::Custom(
-                "[dao_propose_transfer] Proposal plainext data is empty".to_string(),
+                "[dao_transfer_proposal_tx] Proposal plainext data is empty".to_string(),
             ))
             ))
         }
         }
         let proposal_coinattrs: Vec<CoinAttributes> =
         let proposal_coinattrs: Vec<CoinAttributes> =
             deserialize_async(proposal.data.as_ref().unwrap()).await?;
             deserialize_async(proposal.data.as_ref().unwrap()).await?;
 
 
         // Fetch DAO and check its deployed
         // Fetch DAO and check its deployed
-        let dao = self.get_dao_by_bulla(&proposal.proposal.dao_bulla).await?;
-        if dao.leaf_position.is_none() || dao.tx_hash.is_none() {
+        let Ok(dao) = self.get_dao_by_bulla(&proposal.proposal.dao_bulla).await else {
+            return Err(Error::Custom(format!(
+                "[dao_transfer_proposal_tx] DAO {} was not found",
+                proposal.proposal.dao_bulla
+            )))
+        };
+        if dao.leaf_position.is_none() || dao.tx_hash.is_none() || dao.call_index.is_none() {
             return Err(Error::Custom(
             return Err(Error::Custom(
-                "[dao_propose_transfer] DAO seems to not have been deployed yet".to_string(),
+                "[dao_transfer_proposal_tx] DAO seems to not have been deployed yet".to_string(),
             ))
             ))
         }
         }
 
 
@@ -1480,7 +1487,7 @@ impl Drk {
                 .await?;
                 .await?;
             if dao_owncoins.is_empty() {
             if dao_owncoins.is_empty() {
                 return Err(Error::Custom(format!(
                 return Err(Error::Custom(format!(
-                    "[dao_propose_transfer] Did not find any {} unspent coins owned by this DAO",
+                    "[dao_transfer_proposal_tx] Did not find any {} unspent coins owned by this DAO",
                     coinattr.token_id,
                     coinattr.token_id,
                 )))
                 )))
             }
             }
@@ -1488,7 +1495,7 @@ impl Drk {
             // Check DAO balance is sufficient
             // Check DAO balance is sufficient
             if dao_owncoins.iter().map(|x| x.note.value).sum::<u64>() < coinattr.value {
             if dao_owncoins.iter().map(|x| x.note.value).sum::<u64>() < coinattr.value {
                 return Err(Error::Custom(format!(
                 return Err(Error::Custom(format!(
-                    "[dao_propose_transfer] Not enough DAO balance for token ID: {}",
+                    "[dao_transfer_proposal_tx] Not enough DAO balance for token ID: {}",
                     coinattr.token_id,
                     coinattr.token_id,
                 )))
                 )))
             }
             }
@@ -1498,7 +1505,7 @@ impl Drk {
         let gov_owncoins = self.get_token_coins(&dao.params.dao.gov_token_id).await?;
         let gov_owncoins = self.get_token_coins(&dao.params.dao.gov_token_id).await?;
         if gov_owncoins.is_empty() {
         if gov_owncoins.is_empty() {
             return Err(Error::Custom(format!(
             return Err(Error::Custom(format!(
-                "[dao_propose_transfer] Did not find any governance {} coins in wallet",
+                "[dao_transfer_proposal_tx] Did not find any governance {} coins in wallet",
                 dao.params.dao.gov_token_id
                 dao.params.dao.gov_token_id
             )))
             )))
         }
         }
@@ -1518,7 +1525,7 @@ impl Drk {
         // Check our governance coins balance is sufficient
         // Check our governance coins balance is sufficient
         if total_value < dao.params.dao.proposer_limit {
         if total_value < dao.params.dao.proposer_limit {
             return Err(Error::Custom(format!(
             return Err(Error::Custom(format!(
-                "[dao_propose_transfer] Not enough gov token {} balance to propose",
+                "[dao_transfer_proposal_tx] Not enough gov token {} balance to propose",
                 dao.params.dao.gov_token_id
                 dao.params.dao.gov_token_id
             )))
             )))
         }
         }
@@ -1530,7 +1537,9 @@ impl Drk {
 
 
         let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
         let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
         else {
         else {
-            return Err(Error::Custom("[dao_propose_transfer] Fee circuit not found".to_string()))
+            return Err(Error::Custom(
+                "[dao_transfer_proposal_tx] Fee circuit not found".to_string(),
+            ))
         };
         };
 
 
         let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
         let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
@@ -1547,7 +1556,7 @@ impl Drk {
             zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS)
             zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS)
         else {
         else {
             return Err(Error::Custom(
             return Err(Error::Custom(
-                "[dao_propose_transfer] Propose Burn circuit not found".to_string(),
+                "[dao_transfer_proposal_tx] Propose Burn circuit not found".to_string(),
             ))
             ))
         };
         };
 
 
@@ -1555,7 +1564,7 @@ impl Drk {
             zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS)
             zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS)
         else {
         else {
             return Err(Error::Custom(
             return Err(Error::Custom(
-                "[dao_propose_transfer] Propose Main circuit not found".to_string(),
+                "[dao_transfer_proposal_tx] Propose Main circuit not found".to_string(),
             ))
             ))
         };
         };
 
 
@@ -1664,92 +1673,82 @@ impl Drk {
         &self,
         &self,
         proposal: &DaoProposalBulla,
         proposal: &DaoProposalBulla,
         vote_option: bool,
         vote_option: bool,
-        weight: u64,
+        weight: Option<u64>,
     ) -> Result<Transaction> {
     ) -> Result<Transaction> {
+        // Feth the proposal and check its deployed
         let Ok(proposal) = self.get_dao_proposal_by_bulla(proposal).await else {
         let Ok(proposal) = self.get_dao_proposal_by_bulla(proposal).await else {
-            return Err(Error::Custom(format!("[dao_vote] Proposal {} not found", proposal)))
+            return Err(Error::Custom(format!("[dao_vote] Proposal {} was not found", proposal)))
         };
         };
+        if proposal.leaf_position.is_none() ||
+            proposal.tx_hash.is_none() ||
+            proposal.call_index.is_none() ||
+            proposal.money_snapshot_tree.is_none()
+        {
+            return Err(Error::Custom(
+                "[dao_vote] Proposal seems to not have been deployed yet".to_string(),
+            ))
+        }
+
+        // Check we know the plaintext data
+        if proposal.data.is_none() {
+            return Err(Error::Custom("[dao_vote] Proposal plainext data is empty".to_string()))
+        }
 
 
+        // Fetch DAO and check its deployed
         let Ok(dao) = self.get_dao_by_bulla(&proposal.proposal.dao_bulla).await else {
         let Ok(dao) = self.get_dao_by_bulla(&proposal.proposal.dao_bulla).await else {
             return Err(Error::Custom(format!(
             return Err(Error::Custom(format!(
-                "[dao_vote] DAO {} not found",
+                "[dao_vote] DAO {} was not found",
                 proposal.proposal.dao_bulla
                 proposal.proposal.dao_bulla
             )))
             )))
         };
         };
-
-        let money_tree = proposal.money_snapshot_tree.clone().unwrap();
-
-        let mut coins: Vec<OwnCoin> =
-            self.get_coins(false).await?.iter().map(|x| x.0.clone()).collect();
-
-        coins.retain(|x| x.note.token_id == dao.params.dao.gov_token_id);
-        coins.retain(|x| x.note.spend_hook == FuncId::none());
-
-        if coins.iter().map(|x| x.note.value).sum::<u64>() < weight {
-            return Err(Error::Custom("[dao_vote] Not enough balance for vote weight".to_string()))
+        if dao.leaf_position.is_none() || dao.tx_hash.is_none() || dao.call_index.is_none() {
+            return Err(Error::Custom(
+                "[dao_vote] DAO seems to not have been deployed yet".to_string(),
+            ))
         }
         }
 
 
-        // TODO: The spent coins need to either be marked as spent here, and/or on scan
-        let mut spent_value = 0;
-        let mut spent_coins = vec![];
-        let mut inputs = vec![];
-        let mut input_secrets = vec![];
+        // Fetch our own governance OwnCoins to see what our balance is
+        let gov_owncoins = self.get_token_coins(&dao.params.dao.gov_token_id).await?;
+        if gov_owncoins.is_empty() {
+            return Err(Error::Custom(format!(
+                "[dao_vote] Did not find any governance {} coins in wallet",
+                dao.params.dao.gov_token_id
+            )))
+        }
 
 
-        // FIXME: We don't take back any change so it's possible to vote with > requested weight.
-        for coin in coins {
-            if spent_value >= weight {
-                break
+        // Find which governance coins we can use
+        let gov_owncoins_to_use = match weight {
+            Some(_weight) => {
+                // TODO: Build a proper coin selection algorithm so that we can use a
+                // coins combination that matches the requested weight
+                return Err(Error::Custom(
+                    "[dao_vote] Fractional vote weight not supported yet".to_string(),
+                ))
             }
             }
+            // If no weight was specified, use them all
+            None => gov_owncoins,
+        };
 
 
-            spent_value += coin.note.value;
-            spent_coins.push(coin.clone());
-
-            let signature_secret = SecretKey::random(&mut OsRng);
-            input_secrets.push(signature_secret);
-
-            let leaf_position = coin.leaf_position;
-            let merkle_path = money_tree.witness(coin.leaf_position, 0).unwrap();
-
-            let input = DaoVoteInput {
-                secret: coin.secret,
-                note: coin.note.clone(),
-                leaf_position,
-                merkle_path,
-                signature_secret,
-            };
-
-            inputs.push(input);
-        }
-
-        // We use the DAO secret to encrypt the vote.
-        let dao_keypair = dao.keypair();
+        // Now we need to do a lookup for the zkas proof bincodes, and create
+        // the circuit objects and proving keys so we can build the transaction.
+        // We also do this through the RPC. First we grab the fee call from money.
+        let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
 
 
-        // TODO: Fix this
-        let proposal = DaoProposal {
-            auth_calls: vec![],
-            creation_day: 0,
-            duration_days: 30,
-            user_data: pallas::Base::ZERO,
-            dao_bulla: dao.bulla(),
-            blind: Blind::random(&mut OsRng),
+        let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
+        else {
+            return Err(Error::Custom("[dao_vote] Fee circuit not found".to_string()))
         };
         };
 
 
-        // TODO: get current height to calculate day
-        let hasher = PoseidonFp::new();
-        let store = MemoryStorageFp::new();
-        let money_null_smt = SmtMemoryFp::new(store, hasher.clone(), &EMPTY_NODES_FP);
+        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
 
 
-        let call = DaoVoteCall {
-            inputs,
-            vote_option,
-            current_day: 0,
-            dao_keypair,
-            proposal,
-            money_null_smt: &money_null_smt,
-            dao: dao.params.dao,
-        };
+        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
 
 
+        // Creating Fee circuit proving key
+        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
+
+        // Now we grab the DAO bins
         let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
         let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
+
         let Some(dao_vote_burn_zkbin) =
         let Some(dao_vote_burn_zkbin) =
             zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_VOTE_INPUT_NS)
             zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_VOTE_INPUT_NS)
         else {
         else {
@@ -1770,11 +1769,53 @@ impl Drk {
         let dao_vote_main_circuit =
         let dao_vote_main_circuit =
             ZkCircuit::new(empty_witnesses(&dao_vote_main_zkbin)?, &dao_vote_main_zkbin);
             ZkCircuit::new(empty_witnesses(&dao_vote_main_zkbin)?, &dao_vote_main_zkbin);
 
 
-        println!("Creating DAO Vote Burn proving key");
+        // Creating DAO VoteBurn and VoteMain circuits proving keys
         let dao_vote_burn_pk = ProvingKey::build(dao_vote_burn_zkbin.k, &dao_vote_burn_circuit);
         let dao_vote_burn_pk = ProvingKey::build(dao_vote_burn_zkbin.k, &dao_vote_burn_circuit);
-        println!("Creating DAO Vote Main proving key");
         let dao_vote_main_pk = ProvingKey::build(dao_vote_main_zkbin.k, &dao_vote_main_circuit);
         let dao_vote_main_pk = ProvingKey::build(dao_vote_main_zkbin.k, &dao_vote_main_circuit);
 
 
+        // Now create the parameters for the vote tx
+        let signature_secret = SecretKey::random(&mut OsRng);
+        let mut inputs = Vec::with_capacity(gov_owncoins_to_use.len());
+        for gov_owncoin in gov_owncoins_to_use {
+            let input = DaoVoteInput {
+                secret: gov_owncoin.secret,
+                note: gov_owncoin.note.clone(),
+                leaf_position: gov_owncoin.leaf_position,
+                merkle_path: proposal
+                    .money_snapshot_tree
+                    .as_ref()
+                    .unwrap()
+                    .witness(gov_owncoin.leaf_position, 0)
+                    .unwrap(),
+                signature_secret,
+            };
+            inputs.push(input);
+        }
+
+        // Retrieve current block height and compute current day
+        let current_block_height = self.get_next_block_height().await?;
+        let current_day = blockwindow(current_block_height);
+
+        // Generate the Money nullifiers Sparse Merkle Tree
+        let store = WalletStorage::new(
+            &self.wallet,
+            &MONEY_SMT_TABLE,
+            MONEY_SMT_COL_KEY,
+            MONEY_SMT_COL_VALUE,
+        );
+        let money_null_smt = WalletSmt::new(store, PoseidonFp::new(), &EMPTY_NODES_FP);
+
+        // Create the vote call
+        let call = DaoVoteCall {
+            money_null_smt: &money_null_smt,
+            inputs,
+            vote_option,
+            proposal: proposal.proposal.clone(),
+            dao: dao.params.dao.clone(),
+            dao_keypair: dao.keypair(),
+            current_day,
+        };
+
         let (params, proofs) = call.make(
         let (params, proofs) = call.make(
             &dao_vote_burn_zkbin,
             &dao_vote_burn_zkbin,
             &dao_vote_burn_pk,
             &dao_vote_burn_pk,
@@ -1782,14 +1823,34 @@ impl Drk {
             &dao_vote_main_pk,
             &dao_vote_main_pk,
         )?;
         )?;
 
 
+        // Encode the call
         let mut data = vec![DaoFunction::Vote as u8];
         let mut data = vec![DaoFunction::Vote as u8];
         params.encode_async(&mut data).await?;
         params.encode_async(&mut data).await?;
         let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
         let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
+
+        // Create the TransactionBuilder containing above call
         let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
         let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
+
+        // We first have to execute the fee-less tx to gather its used gas, and then we feed
+        // it into the fee-creating function.
         let mut tx = tx_builder.build()?;
         let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&input_secrets)?;
+        let sigs = tx.create_sigs(&[signature_secret])?;
         tx.signatures = vec![sigs];
         tx.signatures = vec![sigs];
 
 
+        let tree = self.get_money_tree().await?;
+        let (fee_call, fee_proofs, fee_secrets) =
+            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
+
+        // Append the fee call to the transaction
+        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
+
+        // Now build the actual transaction and sign it with all necessary keys.
+        let mut tx = tx_builder.build()?;
+        let sigs = tx.create_sigs(&[signature_secret])?;
+        tx.signatures.push(sigs);
+        let sigs = tx.create_sigs(&fee_secrets)?;
+        tx.signatures.push(sigs);
+
         Ok(tx)
         Ok(tx)
     }
     }
 
 

+ 17 - 34
bin/drk/src/main.rs

@@ -251,14 +251,6 @@ enum Subcmd {
         #[structopt(long)]
         #[structopt(long)]
         /// Reset Merkle tree and start scanning from first block
         /// Reset Merkle tree and start scanning from first block
         reset: bool,
         reset: bool,
-
-        #[structopt(long)]
-        /// List all available checkpoints
-        list: bool,
-
-        #[structopt(long)]
-        /// Reset Merkle tree to checkpoint index and start scanning
-        checkpoint: Option<u64>,
     },
     },
 
 
     /// Explorer related subcommands
     /// Explorer related subcommands
@@ -409,8 +401,8 @@ enum DaoSubcmd {
         /// Vote (0 for NO, 1 for YES)
         /// Vote (0 for NO, 1 for YES)
         vote: u8,
         vote: u8,
 
 
-        /// Vote weight (amount of governance tokens)
-        vote_weight: String,
+        /// Optional vote weight (amount of governance tokens)
+        vote_weight: Option<String>,
     },
     },
 
 
     /// Execute a DAO proposal
     /// Execute a DAO proposal
@@ -1308,7 +1300,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                 let bulla = match DaoProposalBulla::from_str(&bulla) {
                 let bulla = match DaoProposalBulla::from_str(&bulla) {
                     Ok(b) => b,
                     Ok(b) => b,
                     Err(e) => {
                     Err(e) => {
-                        eprintln!("Invalid spend hook: {e:?}");
+                        eprintln!("Invalid proposal bulla: {e:?}");
                         exit(2);
                         exit(2);
                     }
                     }
                 };
                 };
@@ -1457,23 +1449,28 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                 let bulla = match DaoProposalBulla::from_str(&bulla) {
                 let bulla = match DaoProposalBulla::from_str(&bulla) {
                     Ok(b) => b,
                     Ok(b) => b,
                     Err(e) => {
                     Err(e) => {
-                        eprintln!("Invalid spend hook: {e:?}");
+                        eprintln!("Invalid proposal bulla: {e:?}");
                         exit(2);
                         exit(2);
                     }
                     }
                 };
                 };
 
 
-                if let Err(e) = f64::from_str(&vote_weight) {
-                    eprintln!("Invalid vote weight: {e:?}");
-                    exit(2);
-                }
-                let weight = decode_base10(&vote_weight, BALANCE_BASE10_DECIMALS, true)?;
-
                 if vote > 1 {
                 if vote > 1 {
                     eprintln!("Vote can be either 0 (NO) or 1 (YES)");
                     eprintln!("Vote can be either 0 (NO) or 1 (YES)");
                     exit(2);
                     exit(2);
                 }
                 }
                 let vote = vote != 0;
                 let vote = vote != 0;
 
 
+                let weight = match vote_weight {
+                    Some(w) => {
+                        if let Err(e) = f64::from_str(&w) {
+                            eprintln!("Invalid vote weight: {e:?}");
+                            exit(2);
+                        }
+                        Some(decode_base10(&w, BALANCE_BASE10_DECIMALS, true)?)
+                    }
+                    None => None,
+                };
+
                 let drk =
                 let drk =
                     Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
                     Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
                 let tx = match drk.dao_vote(&bulla, vote, weight).await {
                 let tx = match drk.dao_vote(&bulla, vote, weight).await {
@@ -1484,8 +1481,6 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                     }
                     }
                 };
                 };
 
 
-                // TODO: Write our_vote in the proposal sql.
-
                 println!("{}", bs58::encode(&serialize_async(&tx).await).into_string());
                 println!("{}", bs58::encode(&serialize_async(&tx).await).into_string());
                 Ok(())
                 Ok(())
             }
             }
@@ -1494,7 +1489,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                 let bulla = match DaoProposalBulla::from_str(&bulla) {
                 let bulla = match DaoProposalBulla::from_str(&bulla) {
                     Ok(b) => b,
                     Ok(b) => b,
                     Err(e) => {
                     Err(e) => {
-                        eprintln!("Invalid spend hook: {e:?}");
+                        eprintln!("Invalid proposal bulla: {e:?}");
                         exit(2);
                         exit(2);
                     }
                     }
                 };
                 };
@@ -1591,7 +1586,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
             Ok(())
             Ok(())
         }
         }
 
 
-        Subcmd::Scan { reset, list, checkpoint } => {
+        Subcmd::Scan { reset } => {
             let drk = Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
             let drk = Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
 
 
             if reset {
             if reset {
@@ -1605,18 +1600,6 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                 return Ok(())
                 return Ok(())
             }
             }
 
 
-            if list {
-                println!("List requested.");
-                // TODO: implement
-                unimplemented!()
-            }
-
-            if let Some(c) = checkpoint {
-                println!("Checkpoint requested: {c}");
-                // TODO: implement
-                unimplemented!()
-            }
-
             if let Err(e) = drk.scan_blocks(false).await {
             if let Err(e) = drk.scan_blocks(false).await {
                 eprintln!("Failed during scanning: {e:?}");
                 eprintln!("Failed during scanning: {e:?}");
                 exit(2);
                 exit(2);

+ 0 - 1
bin/drk/src/rpc.rs

@@ -196,7 +196,6 @@ impl Drk {
                         &call.data.data,
                         &call.data.data,
                         TransactionHash::new(*blake3::hash(&serialize_async(tx).await).as_bytes()),
                         TransactionHash::new(*blake3::hash(&serialize_async(tx).await).as_bytes()),
                         i as u8,
                         i as u8,
-                        true,
                     )
                     )
                     .await?;
                     .await?;
                     continue
                     continue

+ 41 - 37
contrib/localnet/darkfid-single-node/README.md

@@ -40,41 +40,45 @@ testnet user guide.
 Note: List is not exhaustive. Missing functionalities that are not part
 Note: List is not exhaustive. Missing functionalities that are not part
 of the guide can be added for future regressions.
 of the guide can be added for future regressions.
 
 
-| #  | Description               | Command                                          | Status             |
-|----|---------------------------|--------------------------------------------------|--------------------|
-| 0  | Initialization            | wallet --initialize                              | Pass               |
-| 1  | Key generation            | wallet --keygen                                  | Pass               |
-| 2  | Set default wallet        | wallet --default-address {ADDR_ID}               | Pass               |
-| 3  | Default address retrieval | wallet --address                                 | Pass               |
-| 4  | Block scanning            | scan                                             | Pass               |
-| 5  | Block subscribing         | subscribe                                        | Pass               |
-| 6  | Balance retrieval         | wallet --balance                                 | Pass               |
-| 7  | Aliases retrieval         | alias show                                       | Pass               |
-| 8  | Mint auth generation      | token generate-mint                              | Pass               |
-| 9  | Mint auths retrieval      | token list                                       | Pass               |
-| 10 | Alias add                 | alias add {ALIAS} {TOKEN}                        | Pass               |
-| 11 | Aliases retrieval         | alias show                                       | Pass               |
-| 12 | Mint generation           | token mint {ALIAS} {AMOUNT} {ADDR}               | Pass               |
-| 13 | Token freeze              | token freeze {ALIAS}                             | Pass               |
-| 14 | Transfer                  | transfer {AMOUNT} {ALIAS} {ADDR}                 | Pass               |
-| 15 | Coins retrieval           | wallet --coins                                   | Pass               |
-| 16 | OTC initialization        | otc init -v {AMOUNT}:{AMOUNT} -t {ALIAS}:{ALIAS} | Pass               |
-| 17 | OTC join                  | otc join                                         | Pass               |
-| 18 | OTC sign                  | otc sign                                         | Pass               |
-| 19 | DAO create                | dao create {LIMIT} {QUORUM} {RATIO} {TOKEN}      | Pass               |
-| 20 | DAO view                  | dao view                                         | Pass               |
-| 21 | DAO import                | dao import                                       | Pass               |
-| 22 | DAO list                  | dao list                                         | Pass               |
-| 23 | DAO mint                  | dao mint {DAO}                                   | Pass               |
-| 24 | DAO balance               | dao balance {DAO}                                | Pass               |
-| 25 | DAO propose               | dao propose {DAO} {ADDR} {AMOUNT} {TOKEN}        | Pass               |
-| 26 | DAO proposals retrieval   | dao proposals {DAO}                              | Pass               |
-| 27 | DAO proposal retrieval    | dao proposal {DAO} {PROPOSAL_ID}                 | Pass               |
-| 28 | DAO vote                  | dao vote {DAO} {PROPOSAL_ID} {VOTE} {WEIGHT}     | Failure: needs #25 |
-| 29 | DAO proposal execution    | dao exec {DAO} {PROPOSAL_ID}                     | Failure: needs #28 |
-| 30 | Coins unspend             | unspend {COIN}                                   | Pass               |
-| 31 | Transaction inspect       | inspect                                          | Pass               |
-| 32 | Transaction simulate      | explorer simulate-tx                             | Pass               |
-| 33 | Transaction broadcast     | broadcast                                        | Pass               |
-| 34 | Transaction attach fee    | attach-fee                                       | Pass               |
+| #  | Description               | Command                                                  | Status             |
+|----|---------------------------|----------------------------------------------------------|--------------------|
+| 0  | Initialization            | wallet --initialize                                      | Pass               |
+| 1  | Key generation            | wallet --keygen                                          | Pass               |
+| 2  | Set default wallet        | wallet --default-address {ADDR_ID}                       | Pass               |
+| 3  | Default address retrieval | wallet --address                                         | Pass               |
+| 4  | Block scanning            | scan                                                     | Pass               |
+| 5  | Block subscribing         | subscribe                                                | Pass               |
+| 6  | Balance retrieval         | wallet --balance                                         | Pass               |
+| 7  | Aliases retrieval         | alias show                                               | Pass               |
+| 8  | Mint auth generation      | token generate-mint                                      | Pass               |
+| 9  | Mint auths retrieval      | token list                                               | Pass               |
+| 10 | Alias add                 | alias add {ALIAS} {TOKEN}                                | Pass               |
+| 11 | Aliases retrieval         | alias show                                               | Pass               |
+| 12 | Mint generation           | token mint {ALIAS} {AMOUNT} {ADDR}                       | Pass               |
+| 13 | Token freeze              | token freeze {ALIAS}                                     | Pass               |
+| 14 | Transfer                  | transfer {AMOUNT} {ALIAS} {ADDR}                         | Pass               |
+| 15 | Coins retrieval           | wallet --coins                                           | Pass               |
+| 16 | OTC initialization        | otc init -v {AMOUNT}:{AMOUNT} -t {ALIAS}:{ALIAS}         | Pass               |
+| 17 | OTC join                  | otc join                                                 | Pass               |
+| 18 | OTC sign                  | otc sign                                                 | Pass               |
+| 19 | DAO create                | dao create {LIMIT} {QUORUM} {RATIO} {TOKEN}              | Pass               |
+| 20 | DAO view                  | dao view                                                 | Pass               |
+| 21 | DAO import                | dao import                                               | Pass               |
+| 22 | DAO list                  | dao list                                                 | Pass               |
+| 23 | DAO mint                  | dao mint {DAO}                                           | Pass               |
+| 24 | DAO balance               | dao balance {DAO}                                        | Pass               |
+| 25 | DAO proposals retrieval   | dao proposals {DAO}                                      | Pass               |
+| 26 | DAO propose a transfer    | dao propose-transfer {DAO} {DUR} {AMOUNT} {TOKEN} {ADDR} | Pass               |
+| 27 | DAO proposals retrieval   | dao proposals {DAO}                                      | Pass               |
+| 28 | DAO proposal retrieval    | dao proposal {PROPOSAL_BULLA}                            | Pass               |
+| 29 | DAO proposal export       | dao proposal {PROPOSAL_BULLA} --export                   | Pass               |
+| 30 | DAO proposal import       | dao proposal-import                                      | Pass               |
+| 31 | DAO proposal mint         | dao proposal {PROPOSAL_BULLA} --mint-proposal            | Pass               |
+| 32 | DAO vote                  | dao vote {PROPOSAL_BULLA} {VOTE} {WEIGHT}                | Failure: needs #31 |
+| 33 | DAO proposal execution    | dao exec {PROPOSAL_BULLA}                                | Failure: needs #32 |
+| 34 | Coins unspend             | unspend {COIN}                                           | Pass               |
+| 35 | Transaction inspect       | inspect                                                  | Pass               |
+| 36 | Transaction simulate      | explorer simulate-tx                                     | Pass               |
+| 37 | Transaction broadcast     | broadcast                                                | Pass               |
+| 38 | Transaction attach fee    | attach-fee                                               | Pass               |
 
 

+ 10 - 6
src/contract/dao/src/client/vote.rs

@@ -21,9 +21,12 @@ use darkfi_sdk::{
     bridgetree,
     bridgetree,
     bridgetree::Hashable,
     bridgetree::Hashable,
     crypto::{
     crypto::{
-        note::ElGamalEncryptedNote, pasta_prelude::*, pedersen_commitment_u64, poseidon_hash,
-        smt::SmtMemoryFp, util::fv_mod_fp_unsafe, Blind, FuncId, Keypair, MerkleNode, PublicKey,
-        SecretKey,
+        note::ElGamalEncryptedNote,
+        pasta_prelude::*,
+        pedersen_commitment_u64, poseidon_hash,
+        smt::{PoseidonFp, SparseMerkleTree, StorageAdapter, SMT_FP_DEPTH},
+        util::fv_mod_fp_unsafe,
+        Blind, FuncId, Keypair, MerkleNode, PublicKey, SecretKey,
     },
     },
     pasta::pallas,
     pasta::pallas,
 };
 };
@@ -50,8 +53,9 @@ pub struct DaoVoteInput {
 }
 }
 
 
 // Inside ZK proof, check proposal is correct.
 // Inside ZK proof, check proposal is correct.
-pub struct DaoVoteCall<'a> {
-    pub money_null_smt: &'a SmtMemoryFp,
+pub struct DaoVoteCall<'a, T: StorageAdapter<Value = pallas::Base>> {
+    pub money_null_smt:
+        &'a SparseMerkleTree<'a, SMT_FP_DEPTH, { SMT_FP_DEPTH + 1 }, pallas::Base, PoseidonFp, T>,
     pub inputs: Vec<DaoVoteInput>,
     pub inputs: Vec<DaoVoteInput>,
     pub vote_option: bool,
     pub vote_option: bool,
     pub proposal: DaoProposal,
     pub proposal: DaoProposal,
@@ -60,7 +64,7 @@ pub struct DaoVoteCall<'a> {
     pub current_day: u64,
     pub current_day: u64,
 }
 }
 
 
-impl<'a> DaoVoteCall<'a> {
+impl<'a, T: StorageAdapter<Value = pallas::Base>> DaoVoteCall<'a, T> {
     pub fn make(
     pub fn make(
         self,
         self,
         burn_zkbin: &ZkBinary,
         burn_zkbin: &ZkBinary,