darkfi_contract_test_harness/
dao_vote.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 *
5 * This program is free software: you can redistributemoney it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use darkfi::{
20    tx::{ContractCallLeaf, Transaction, TransactionBuilder},
21    Result,
22};
23use darkfi_dao_contract::{
24    blockwindow,
25    client::{DaoVoteCall, DaoVoteInput},
26    model::{Dao, DaoProposal, DaoVoteParams},
27    DaoFunction, DAO_CONTRACT_ZKAS_DAO_VOTE_INPUT_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
28};
29use darkfi_money_contract::{
30    client::{MoneyNote, OwnCoin},
31    model::MoneyFeeParamsV1,
32};
33use darkfi_sdk::{
34    crypto::{contract_id::DAO_CONTRACT_ID, MerkleNode, SecretKey},
35    ContractCall,
36};
37use darkfi_serial::AsyncEncodable;
38use log::debug;
39use rand::rngs::OsRng;
40
41use super::{Holder, TestHarness};
42
43impl TestHarness {
44    /// Create a `Dao::Vote` transaction.
45    pub async fn dao_vote(
46        &mut self,
47        voter: &Holder,
48        vote_option: bool,
49        dao: &Dao,
50        proposal: &DaoProposal,
51        block_height: u32,
52    ) -> Result<(Transaction, DaoVoteParams, Option<MoneyFeeParamsV1>)> {
53        let wallet = self.holders.get(voter).unwrap();
54
55        let (dao_vote_burn_pk, dao_vote_burn_zkbin) =
56            self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_VOTE_INPUT_NS).unwrap();
57
58        let (dao_vote_main_pk, dao_vote_main_zkbin) =
59            self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS).unwrap();
60
61        let (_, snapshot_money_merkle_tree) =
62            wallet.dao_prop_leafs.get(&proposal.to_bulla()).unwrap();
63
64        let vote_owncoin: OwnCoin = wallet
65            .unspent_money_coins
66            .iter()
67            .find(|x| x.note.token_id == dao.gov_token_id)
68            .unwrap()
69            .clone();
70
71        let signature_secret = SecretKey::random(&mut OsRng);
72
73        let input = DaoVoteInput {
74            secret: wallet.keypair.secret,
75            note: vote_owncoin.note.clone(),
76            leaf_position: vote_owncoin.leaf_position,
77            merkle_path: snapshot_money_merkle_tree.witness(vote_owncoin.leaf_position, 0).unwrap(),
78            signature_secret,
79        };
80
81        let block_target = wallet.validator.consensus.module.read().await.target;
82        let current_blockwindow = blockwindow(block_height, block_target);
83        let call = DaoVoteCall {
84            money_null_smt: wallet.money_null_smt_snapshot.as_ref().unwrap(),
85            inputs: vec![input],
86            vote_option,
87            proposal: proposal.clone(),
88            dao: dao.clone(),
89            current_blockwindow,
90        };
91
92        let (params, proofs) = call.make(
93            dao_vote_burn_zkbin,
94            dao_vote_burn_pk,
95            dao_vote_main_zkbin,
96            dao_vote_main_pk,
97        )?;
98
99        // Encode the call
100        let mut data = vec![DaoFunction::Vote as u8];
101        params.encode_async(&mut data).await?;
102        let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
103        let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
104
105        // If fees are enabled, make an offering
106        let mut fee_params = None;
107        let mut fee_signature_secrets = None;
108        if self.verify_fees {
109            let mut tx = tx_builder.build()?;
110            let sigs = tx.create_sigs(&[signature_secret])?;
111            tx.signatures = vec![sigs];
112
113            let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
114                self.append_fee_call(voter, tx, block_height, &[]).await?;
115
116            // Append the fee call to the transaction
117            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
118            fee_signature_secrets = Some(fee_secrets);
119            fee_params = Some(fee_call_params);
120        }
121
122        // Now build the actual transaction and sign it with necessary keys.
123        let mut tx = tx_builder.build()?;
124        let sigs = tx.create_sigs(&[signature_secret])?;
125        tx.signatures = vec![sigs];
126        if let Some(fee_signature_secrets) = fee_signature_secrets {
127            let sigs = tx.create_sigs(&fee_signature_secrets)?;
128            tx.signatures.push(sigs);
129        }
130
131        Ok((tx, params, fee_params))
132    }
133
134    /// Execute the transaction made by `dao_vote()` for a given [`Holder`].
135    ///
136    /// Returns any found [`OwnCoin`]s.
137    pub async fn execute_dao_vote_tx(
138        &mut self,
139        holder: &Holder,
140        tx: Transaction,
141        fee_params: &Option<MoneyFeeParamsV1>,
142        block_height: u32,
143        append: bool,
144    ) -> Result<Vec<OwnCoin>> {
145        let wallet = self.holders.get_mut(holder).unwrap();
146
147        // Execute the transaction
148        wallet.add_transaction("dao::vote", tx, block_height).await?;
149
150        if !append {
151            return Ok(vec![])
152        }
153
154        if let Some(ref fee_params) = fee_params {
155            let nullifier = fee_params.input.nullifier.inner();
156            wallet
157                .money_null_smt
158                .insert_batch(vec![(nullifier, nullifier)])
159                .expect("smt.insert_batch()");
160
161            if let Some(spent_coin) = wallet
162                .unspent_money_coins
163                .iter()
164                .find(|x| x.nullifier() == fee_params.input.nullifier)
165                .cloned()
166            {
167                debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder);
168                wallet.unspent_money_coins.retain(|x| x.nullifier() != fee_params.input.nullifier);
169                wallet.spent_money_coins.push(spent_coin.clone());
170            }
171
172            wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner()));
173
174            let Ok(note) = fee_params.output.note.decrypt::<MoneyNote>(&wallet.keypair.secret)
175            else {
176                return Ok(vec![])
177            };
178
179            let owncoin = OwnCoin {
180                coin: fee_params.output.coin,
181                note: note.clone(),
182                secret: wallet.keypair.secret,
183                leaf_position: wallet.money_merkle_tree.mark().unwrap(),
184            };
185
186            debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder);
187            wallet.unspent_money_coins.push(owncoin.clone());
188            return Ok(vec![owncoin])
189        }
190
191        Ok(vec![])
192    }
193}