darkfi_contract_test_harness/
dao_mint.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 redistribute 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    client::make_mint_call,
25    model::{Dao, DaoMintParams},
26    DaoFunction, DAO_CONTRACT_ZKAS_DAO_MINT_NS,
27};
28use darkfi_money_contract::{
29    client::{MoneyNote, OwnCoin},
30    model::MoneyFeeParamsV1,
31};
32use darkfi_sdk::{
33    crypto::{contract_id::DAO_CONTRACT_ID, MerkleNode, SecretKey},
34    ContractCall,
35};
36use darkfi_serial::AsyncEncodable;
37use log::debug;
38
39use super::{Holder, TestHarness};
40
41impl TestHarness {
42    /// Create a `Dao::Mint` transaction with the given [`Dao`] info and keys.
43    /// Takes a [`Holder`] for optionally paying the transaction fee.
44    ///
45    /// Returns the [`Transaction`] and any relevant parameters.
46    #[allow(clippy::too_many_arguments)]
47    pub async fn dao_mint(
48        &mut self,
49        holder: &Holder,
50        dao: &Dao,
51        dao_notes_secret_key: &SecretKey,
52        dao_proposer_secret_key: &SecretKey,
53        dao_proposals_secret_key: &SecretKey,
54        dao_votes_secret_key: &SecretKey,
55        dao_exec_secret_key: &SecretKey,
56        dao_early_exec_secret_key: &SecretKey,
57        block_height: u32,
58    ) -> Result<(Transaction, DaoMintParams, Option<MoneyFeeParamsV1>)> {
59        let (dao_mint_pk, dao_mint_zkbin) =
60            self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_MINT_NS).unwrap();
61
62        // Create the call
63        let (params, proofs) = make_mint_call(
64            dao,
65            dao_notes_secret_key,
66            dao_proposer_secret_key,
67            dao_proposals_secret_key,
68            dao_votes_secret_key,
69            dao_exec_secret_key,
70            dao_early_exec_secret_key,
71            dao_mint_zkbin,
72            dao_mint_pk,
73        )?;
74
75        // Encode the call
76        let mut data = vec![DaoFunction::Mint as u8];
77        params.encode_async(&mut data).await?;
78        let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
79        let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
80
81        // If fees are enabled, make an offering
82        let mut fee_params = None;
83        let mut fee_signature_secrets = None;
84        if self.verify_fees {
85            let mut tx = tx_builder.build()?;
86            let sigs = tx.create_sigs(&[*dao_notes_secret_key])?;
87            tx.signatures = vec![sigs];
88
89            let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
90                self.append_fee_call(holder, tx, block_height, &[]).await?;
91
92            // Append the fee call to the transaction
93            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
94            fee_signature_secrets = Some(fee_secrets);
95            fee_params = Some(fee_call_params);
96        }
97
98        // Now build the actual transaction and sign it with necessary keys.
99        let mut tx = tx_builder.build()?;
100        let sigs = tx.create_sigs(&[*dao_notes_secret_key])?;
101        tx.signatures = vec![sigs];
102        if let Some(fee_signature_secrets) = fee_signature_secrets {
103            let sigs = tx.create_sigs(&fee_signature_secrets)?;
104            tx.signatures.push(sigs);
105        }
106
107        Ok((tx, params, fee_params))
108    }
109
110    /// Execute the transaction created by `dao_mint()` for a given [`Holder`].
111    ///
112    /// Returns any found [`OwnCoin`]s.
113    pub async fn execute_dao_mint_tx(
114        &mut self,
115        holder: &Holder,
116        tx: Transaction,
117        params: &DaoMintParams,
118        fee_params: &Option<MoneyFeeParamsV1>,
119        block_height: u32,
120        append: bool,
121    ) -> Result<Vec<OwnCoin>> {
122        let wallet = self.holders.get_mut(holder).unwrap();
123
124        // Execute the transaction
125        wallet.add_transaction("dao::mint", tx, block_height).await?;
126
127        if !append {
128            return Ok(vec![])
129        }
130
131        wallet.dao_merkle_tree.append(MerkleNode::from(params.dao_bulla.inner()));
132        let leaf_pos = wallet.dao_merkle_tree.mark().unwrap();
133        wallet.dao_leafs.insert(params.dao_bulla, leaf_pos);
134
135        if let Some(ref fee_params) = fee_params {
136            let nullifier = fee_params.input.nullifier.inner();
137            wallet
138                .money_null_smt
139                .insert_batch(vec![(nullifier, nullifier)])
140                .expect("smt.insert_batch()");
141
142            if let Some(spent_coin) = wallet
143                .unspent_money_coins
144                .iter()
145                .find(|x| x.nullifier() == fee_params.input.nullifier)
146                .cloned()
147            {
148                debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder);
149                wallet.unspent_money_coins.retain(|x| x.nullifier() != fee_params.input.nullifier);
150                wallet.spent_money_coins.push(spent_coin.clone());
151            }
152
153            wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner()));
154
155            let Ok(note) = fee_params.output.note.decrypt::<MoneyNote>(&wallet.keypair.secret)
156            else {
157                return Ok(vec![])
158            };
159
160            let owncoin = OwnCoin {
161                coin: fee_params.output.coin,
162                note: note.clone(),
163                secret: wallet.keypair.secret,
164                leaf_position: wallet.money_merkle_tree.mark().unwrap(),
165            };
166
167            debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder);
168            wallet.unspent_money_coins.push(owncoin.clone());
169            return Ok(vec![owncoin])
170        }
171
172        Ok(vec![])
173    }
174}