parazyd 3 lat temu
rodzic
commit
00e5ae4721
2 zmienionych plików z 168 dodań i 6 usunięć
  1. 29 3
      bin/drk/src/main.rs
  2. 139 3
      bin/drk/src/rpc_dao.rs

+ 29 - 3
bin/drk/src/main.rs

@@ -303,8 +303,11 @@ enum DaoSubcmd {
         /// Numeric identifier for the proposal
         proposal: u64,
 
-        /// Vote
-        vote: String,
+        /// Vote (0 for NO, 1 for YES)
+        vote: u8,
+
+        /// Vote weight (amount of governance tokens)
+        vote_weight: String,
     },
 
     /// Execute a DAO proposal
@@ -881,7 +884,30 @@ async fn main() -> Result<()> {
                 Ok(())
             }
 
-            DaoSubcmd::Vote { dao_id, proposal, vote } => todo!(),
+            DaoSubcmd::Vote { dao_id, proposal, vote, vote_weight } => {
+                let rpc_client = RpcClient::new(args.endpoint.clone())
+                    .await
+                    .with_context(|| "Could not connect to darkfid RPC endpoint")?;
+
+                let drk = Drk { rpc_client };
+
+                let _ = f64::from_str(&vote_weight).with_context(|| "Invalid vote weight")?;
+                let weight = decode_base10(&vote_weight, 8, true)?;
+
+                if vote > 1 {
+                    eprintln!("Vote can be either 0 (NO) or 1 (YES)");
+                    exit(1);
+                }
+                let vote = vote != 0;
+
+                let tx = drk
+                    .dao_vote(dao_id, proposal, vote, weight)
+                    .await
+                    .with_context(|| "Failed to create DAO Vote transaction")?;
+
+                println!("{}", bs58::encode(&serialize(&tx)).into_string());
+                Ok(())
+            }
 
             DaoSubcmd::Exec { dao_id, proposal } => todo!(),
         },

+ 139 - 3
bin/drk/src/rpc_dao.rs

@@ -23,12 +23,15 @@ use darkfi::{
     zkas::ZkBinary,
 };
 use darkfi_dao_contract::{
-    dao_client, dao_client::DaoInfo, DaoFunction, DAO_CONTRACT_ZKAS_DAO_MINT_NS,
-    DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
+    dao_client,
+    dao_client::{DaoInfo, DaoProposalInfo, DaoVoteCall, DaoVoteInput},
+    DaoFunction, 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,
 };
 use darkfi_money_contract::client::OwnCoin;
 use darkfi_sdk::{
-    crypto::{PublicKey, SecretKey, TokenId, DAO_CONTRACT_ID},
+    crypto::{Keypair, PublicKey, SecretKey, TokenId, DAO_CONTRACT_ID},
     incrementalmerkletree::Tree,
     pasta::pallas,
     ContractCall,
@@ -236,4 +239,137 @@ impl Drk {
 
         Ok(tx)
     }
+
+    /// Vote on a DAO proposal
+    pub async fn dao_vote(
+        &self,
+        dao_id: u64,
+        proposal_id: u64,
+        vote_option: bool,
+        weight: u64,
+    ) -> Result<Transaction> {
+        let dao = self.get_dao_by_id(dao_id).await?;
+        let proposals = self.get_dao_proposals(dao_id).await?;
+        let Some(proposal) = proposals.iter().find(|x| x.id == proposal_id) else {
+            return Err(anyhow!("Proposal ID not found"))
+        };
+
+        let money_tree = self.get_money_tree().await?;
+
+        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.gov_token_id);
+
+        if coins.iter().map(|x| x.note.value).sum::<u64>() < weight {
+            return Err(anyhow!("Not enough balance for vote weight"))
+        }
+
+        // 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![];
+
+        // 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
+            }
+
+            spent_value += coin.note.value;
+            spent_coins.push(coin.clone());
+
+            let signature_secret = SecretKey::random(&mut OsRng);
+            input_secrets.push(signature_secret);
+
+            let root = money_tree.root(0).unwrap();
+            let leaf_position = coin.leaf_position;
+            let merkle_path = money_tree.authentication_path(coin.leaf_position, &root).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 vote_keypair = Keypair::new(dao.secret_key);
+
+        let proposal_info = DaoProposalInfo {
+            dest: proposal.recipient,
+            amount: proposal.amount,
+            serial: proposal.serial,
+            token_id: proposal.token_id,
+            blind: proposal.bulla_blind,
+        };
+
+        let dao_info = DaoInfo {
+            proposer_limit: dao.proposer_limit,
+            quorum: dao.quorum,
+            approval_ratio_quot: dao.approval_ratio_quot,
+            approval_ratio_base: dao.approval_ratio_base,
+            gov_token_id: dao.gov_token_id,
+            public_key: PublicKey::from_secret(dao.secret_key),
+            bulla_blind: dao.bulla_blind,
+        };
+
+        let call = DaoVoteCall {
+            inputs,
+            vote_option,
+            yes_vote_blind: pallas::Scalar::random(&mut OsRng),
+            vote_keypair,
+            proposal: proposal_info,
+            dao: dao_info,
+        };
+
+        let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
+        let Some(dao_vote_burn_zkbin) =
+            zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS) else
+        {
+            return Err(anyhow!("DAO Vote Burn circuit not found"))
+        };
+
+        let Some(dao_vote_main_zkbin) =
+            zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS) else
+        {
+            return Err(anyhow!("DAO Vote Main circuit not found"))
+        };
+
+        let dao_vote_burn_zkbin = ZkBinary::decode(&dao_vote_burn_zkbin.1)?;
+        let dao_vote_main_zkbin = ZkBinary::decode(&dao_vote_main_zkbin.1)?;
+
+        let k = 13;
+        let dao_vote_burn_circuit =
+            ZkCircuit::new(empty_witnesses(&dao_vote_burn_zkbin), dao_vote_burn_zkbin.clone());
+        let dao_vote_main_circuit =
+            ZkCircuit::new(empty_witnesses(&dao_vote_main_zkbin), dao_vote_main_zkbin.clone());
+
+        eprintln!("Creating DAO Vote Burn proving key");
+        let dao_vote_burn_pk = ProvingKey::build(k, &dao_vote_burn_circuit);
+        eprintln!("Creating DAO Vote Main proving key");
+        let dao_vote_main_pk = ProvingKey::build(k, &dao_vote_main_circuit);
+
+        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, &input_secrets)?;
+        tx.signatures = vec![sigs];
+
+        Ok(tx)
+    }
 }