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 */
1819use darkfi::{
20 tx::{ContractCallLeaf, Transaction, TransactionBuilder},
21Result,
22};
23use darkfi_money_contract::{
24 client::{transfer_v1::make_transfer_call, MoneyNote, OwnCoin},
25 model::{MoneyFeeParamsV1, MoneyTransferParamsV1, TokenId},
26 MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
27};
28use darkfi_sdk::{
29 crypto::{contract_id::MONEY_CONTRACT_ID, MerkleNode},
30 ContractCall,
31};
32use darkfi_serial::AsyncEncodable;
33use log::debug;
3435use super::{Holder, TestHarness};
3637impl TestHarness {
38/// Create a `Money::Transfer` transaction.
39#[allow(clippy::too_many_arguments)]
40pub async fn transfer(
41&mut self,
42 amount: u64,
43 holder: &Holder,
44 recipient: &Holder,
45 owncoins: &[OwnCoin],
46 token_id: TokenId,
47 block_height: u32,
48 half_split: bool,
49 ) -> Result<(Transaction, (MoneyTransferParamsV1, Option<MoneyFeeParamsV1>), Vec<OwnCoin>)>
50 {
51let wallet = self.holders.get(holder).unwrap();
52let rcpt = self.holders.get(recipient).unwrap().keypair.public;
5354let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
55let (burn_pk, burn_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
5657// Create the transfer call
58let (params, secrets, mut spent_coins) = make_transfer_call(
59 wallet.keypair,
60 rcpt,
61 amount,
62 token_id,
63 owncoins.to_owned(),
64 wallet.money_merkle_tree.clone(),
65None,
66None,
67 mint_zkbin.clone(),
68 mint_pk.clone(),
69 burn_zkbin.clone(),
70 burn_pk.clone(),
71 half_split,
72 )?;
7374// Encode the call
75let mut data = vec![MoneyFunction::TransferV1 as u8];
76 params.encode_async(&mut data).await?;
77let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
7879// Create the TransactionBuilder containing the `Transfer` call
80let mut tx_builder =
81 TransactionBuilder::new(ContractCallLeaf { call, proofs: secrets.proofs }, vec![])?;
8283// If we have tx fees enabled, we first have to execute the fee-less tx to gather its
84 // used gas, and then we feed it into the fee-creating function.
85 // We also tell it about any spent coins so we don't accidentally reuse them in the
86 // fee call.
87 // TODO: We have to build a proper coin selection algorithm so that we can utilize
88 // the Money::Transfer to merge any coins which would give us a coin with enough
89 // value for paying the transaction fee.
90let mut fee_params = None;
91let mut fee_signature_secrets = None;
92if self.verify_fees {
93let mut tx = tx_builder.build()?;
94let sigs = tx.create_sigs(&secrets.signature_secrets)?;
95 tx.signatures = vec![sigs];
9697let (fee_call, fee_proofs, fee_secrets, spent_fee_coins, fee_call_params) =
98self.append_fee_call(holder, tx, block_height, &spent_coins).await?;
99100// Append the fee call to the transaction
101tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
102 fee_signature_secrets = Some(fee_secrets);
103 spent_coins.extend_from_slice(&spent_fee_coins);
104 fee_params = Some(fee_call_params);
105 }
106107// Now build the actual transaction and sign it with all necessary keys.
108let mut tx = tx_builder.build()?;
109let sigs = tx.create_sigs(&secrets.signature_secrets)?;
110 tx.signatures = vec![sigs];
111if let Some(fee_signature_secrets) = fee_signature_secrets {
112let sigs = tx.create_sigs(&fee_signature_secrets)?;
113 tx.signatures.push(sigs);
114 }
115116Ok((tx, (params, fee_params), spent_coins))
117 }
118119/// Execute a `Money::Transfer` transaction for a given [`Holder`].
120 ///
121 /// Returns any found [`OwnCoin`]s.
122pub async fn execute_transfer_tx(
123&mut self,
124 holder: &Holder,
125 tx: Transaction,
126 call_params: &MoneyTransferParamsV1,
127 fee_params: &Option<MoneyFeeParamsV1>,
128 block_height: u32,
129 append: bool,
130 ) -> Result<Vec<OwnCoin>> {
131let wallet = self.holders.get_mut(holder).unwrap();
132133// Execute the transaction
134wallet.add_transaction("money::transfer", tx, block_height).await?;
135136// Iterate over call inputs to mark any spent coins
137let nullifiers =
138 call_params.inputs.iter().map(|i| i.nullifier.inner()).map(|l| (l, l)).collect();
139 wallet.money_null_smt.insert_batch(nullifiers).expect("smt.insert_batch()");
140141let mut found_owncoins = vec![];
142if append {
143for input in &call_params.inputs {
144if let Some(spent_coin) = wallet
145 .unspent_money_coins
146 .iter()
147 .find(|x| x.nullifier() == input.nullifier)
148 .cloned()
149 {
150debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder);
151 wallet.unspent_money_coins.retain(|x| x.nullifier() != input.nullifier);
152 wallet.spent_money_coins.push(spent_coin.clone());
153 }
154 }
155156// Iterate over call outputs to find any new OwnCoins
157for output in &call_params.outputs {
158 wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner()));
159160// Attempt to decrypt the output note to see if this is a coin for the holder.
161let Ok(note) = output.note.decrypt::<MoneyNote>(&wallet.keypair.secret) else {
162continue
163};
164165let owncoin = OwnCoin {
166 coin: output.coin,
167 note: note.clone(),
168 secret: wallet.keypair.secret,
169 leaf_position: wallet.money_merkle_tree.mark().unwrap(),
170 };
171172debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder);
173 wallet.unspent_money_coins.push(owncoin.clone());
174 found_owncoins.push(owncoin);
175 }
176 }
177178// Handle fee call
179if let Some(ref fee_params) = fee_params {
180// Process call input to mark any spent coins
181let nullifier = fee_params.input.nullifier.inner();
182 wallet
183 .money_null_smt
184 .insert_batch(vec![(nullifier, nullifier)])
185 .expect("smt.insert_batch()");
186187if append {
188if let Some(spent_coin) = wallet
189 .unspent_money_coins
190 .iter()
191 .find(|x| x.nullifier() == fee_params.input.nullifier)
192 .cloned()
193 {
194debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder);
195 wallet
196 .unspent_money_coins
197 .retain(|x| x.nullifier() != fee_params.input.nullifier);
198 wallet.spent_money_coins.push(spent_coin.clone());
199 }
200201// Process call output to find any new OwnCoins
202wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner()));
203204// Attempt to decrypt the output note to see if this is a coin for the holder.
205if let Ok(note) =
206 fee_params.output.note.decrypt::<MoneyNote>(&wallet.keypair.secret)
207 {
208let owncoin = OwnCoin {
209 coin: fee_params.output.coin,
210 note: note.clone(),
211 secret: wallet.keypair.secret,
212 leaf_position: wallet.money_merkle_tree.mark().unwrap(),
213 };
214215debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder);
216 wallet.unspent_money_coins.push(owncoin.clone());
217 found_owncoins.push(owncoin);
218 };
219 }
220 }
221222Ok(found_owncoins)
223 }
224}