diff --git a/src/contract/test-harness/src/contract_deploy.rs b/src/contract/test-harness/src/contract_deploy.rs index fd5ba59ea..a60f79bc4 100644 --- a/src/contract/test-harness/src/contract_deploy.rs +++ b/src/contract/test-harness/src/contract_deploy.rs @@ -21,17 +21,11 @@ use darkfi::{ Result, }; use darkfi_deployooor_contract::{client::deploy_v1::DeployCallBuilder, DeployFunction}; -use darkfi_money_contract::{ - client::{MoneyNote, OwnCoin}, - model::MoneyFeeParamsV1, -}; +use darkfi_money_contract::{client::OwnCoin, model::MoneyFeeParamsV1}; use darkfi_sdk::{ - crypto::{contract_id::DEPLOYOOOR_CONTRACT_ID, MerkleNode}, - deploy::DeployParamsV1, - ContractCall, + crypto::contract_id::DEPLOYOOOR_CONTRACT_ID, deploy::DeployParamsV1, ContractCall, }; -use darkfi_serial::AsyncEncodable; -use tracing::debug; +use darkfi_serial::Encodable; use super::{Holder, TestHarness}; @@ -45,7 +39,7 @@ impl TestHarness { wasm_bincode: Vec, block_height: u32, ) -> Result<(Transaction, DeployParamsV1, Option)> { - let wallet = self.holders.get(holder).unwrap(); + let wallet = self.wallet(holder); let deploy_keypair = wallet.contract_deploy_authority; // Build the contract call @@ -54,7 +48,7 @@ impl TestHarness { // Encode the call let mut data = vec![DeployFunction::DeployV1 as u8]; - debris.params.encode_async(&mut data).await?; + debris.params.encode(&mut data)?; let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?; @@ -102,50 +96,12 @@ impl TestHarness { ) -> Result> { let wallet = self.holders.get_mut(holder).unwrap(); - // Execute the transaction wallet.add_transaction("deploy::deploy", tx, block_height).await?; if !append { - return Ok(vec![]) + return Ok(vec![]); } - if let Some(ref fee_params) = fee_params { - let nullifier = fee_params.input.nullifier.inner(); - wallet - .money_null_smt - .insert_batch(vec![(nullifier, nullifier)]) - .expect("smt.insert_batch()"); - - if let Some(spent_coin) = wallet - .unspent_money_coins - .iter() - .find(|x| x.nullifier() == fee_params.input.nullifier) - .cloned() - { - debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); - wallet.unspent_money_coins.retain(|x| x.nullifier() != fee_params.input.nullifier); - wallet.spent_money_coins.push(spent_coin.clone()); - } - - wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner())); - - let Ok(note) = fee_params.output.note.decrypt::(&wallet.keypair.secret) - else { - return Ok(vec![]) - }; - - let owncoin = OwnCoin { - coin: fee_params.output.coin, - note: note.clone(), - secret: wallet.keypair.secret, - leaf_position: wallet.money_merkle_tree.mark().unwrap(), - }; - - debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); - wallet.unspent_money_coins.push(owncoin.clone()); - return Ok(vec![owncoin]) - } - - Ok(vec![]) + Ok(wallet.process_fee(fee_params, holder)) } } diff --git a/src/contract/test-harness/src/contract_lock.rs b/src/contract/test-harness/src/contract_lock.rs index 7d05a4a83..85f166485 100644 --- a/src/contract/test-harness/src/contract_lock.rs +++ b/src/contract/test-harness/src/contract_lock.rs @@ -23,17 +23,9 @@ use darkfi::{ use darkfi_deployooor_contract::{ client::lock_v1::LockCallBuilder, model::LockParamsV1, DeployFunction, }; -use darkfi_money_contract::{ - client::{MoneyNote, OwnCoin}, - model::MoneyFeeParamsV1, -}; -use darkfi_sdk::{ - crypto::{contract_id::DEPLOYOOOR_CONTRACT_ID, MerkleNode}, - //deploy::LockParamsV1, - ContractCall, -}; -use darkfi_serial::AsyncEncodable; -use tracing::debug; +use darkfi_money_contract::{client::OwnCoin, model::MoneyFeeParamsV1}; +use darkfi_sdk::{crypto::contract_id::DEPLOYOOOR_CONTRACT_ID, ContractCall}; +use darkfi_serial::Encodable; use super::{Holder, TestHarness}; @@ -46,7 +38,7 @@ impl TestHarness { holder: &Holder, block_height: u32, ) -> Result<(Transaction, LockParamsV1, Option)> { - let wallet = self.holders.get(holder).unwrap(); + let wallet = self.wallet(holder); let deploy_keypair = wallet.contract_deploy_authority; // Build the contract call @@ -55,7 +47,7 @@ impl TestHarness { // Encode the call let mut data = vec![DeployFunction::LockV1 as u8]; - debris.params.encode_async(&mut data).await?; + debris.params.encode(&mut data)?; let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?; @@ -101,52 +93,14 @@ impl TestHarness { block_height: u32, append: bool, ) -> Result> { - let wallet = self.holders.get_mut(holder).unwrap(); + let wallet = self.wallet_mut(holder); - // Execute the transaction wallet.add_transaction("deploy::lock", tx, block_height).await?; if !append { - return Ok(vec![]) + return Ok(vec![]); } - if let Some(ref fee_params) = fee_params { - let nullifier = fee_params.input.nullifier.inner(); - wallet - .money_null_smt - .insert_batch(vec![(nullifier, nullifier)]) - .expect("smt.insert_batch()"); - - if let Some(spent_coin) = wallet - .unspent_money_coins - .iter() - .find(|x| x.nullifier() == fee_params.input.nullifier) - .cloned() - { - debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); - wallet.unspent_money_coins.retain(|x| x.nullifier() != fee_params.input.nullifier); - wallet.spent_money_coins.push(spent_coin.clone()); - } - - wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner())); - - let Ok(note) = fee_params.output.note.decrypt::(&wallet.keypair.secret) - else { - return Ok(vec![]) - }; - - let owncoin = OwnCoin { - coin: fee_params.output.coin, - note: note.clone(), - secret: wallet.keypair.secret, - leaf_position: wallet.money_merkle_tree.mark().unwrap(), - }; - - debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); - wallet.unspent_money_coins.push(owncoin.clone()); - return Ok(vec![owncoin]) - } - - Ok(vec![]) + Ok(wallet.process_fee(fee_params, holder)) } } diff --git a/src/contract/test-harness/src/dao_exec.rs b/src/contract/test-harness/src/dao_exec.rs index f0cdbebcd..8543bb580 100644 --- a/src/contract/test-harness/src/dao_exec.rs +++ b/src/contract/test-harness/src/dao_exec.rs @@ -29,21 +29,20 @@ use darkfi_dao_contract::{ DAO_CONTRACT_ZKAS_EXEC_NS, }; use darkfi_money_contract::{ - client::{transfer_v1 as xfer, MoneyNote, OwnCoin}, + client::{transfer_v1 as xfer, OwnCoin}, model::{CoinAttributes, MoneyFeeParamsV1, MoneyTransferParamsV1}, MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1, }; use darkfi_sdk::{ crypto::{ contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID}, - pedersen_commitment_u64, Blind, FuncRef, MerkleNode, ScalarBlind, SecretKey, + pedersen_commitment_u64, Blind, FuncRef, ScalarBlind, SecretKey, }, dark_tree::DarkTree, ContractCall, }; -use darkfi_serial::AsyncEncodable; +use darkfi_serial::Encodable; use rand::rngs::OsRng; -use tracing::debug; use super::{Holder, TestHarness}; @@ -64,7 +63,7 @@ impl TestHarness { all_vote_blind: ScalarBlind, block_height: u32, ) -> Result<(Transaction, MoneyTransferParamsV1, Option)> { - let dao_wallet = self.holders.get(&Holder::Dao).unwrap(); + let dao_wallet = self.wallet(&Holder::Dao); let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap(); let (burn_pk, burn_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap(); @@ -136,7 +135,7 @@ impl TestHarness { let (xfer_params, xfer_secrets) = xfer_builder.build()?; let mut data = vec![MoneyFunction::TransferV1 as u8]; - xfer_params.encode_async(&mut data).await?; + xfer_params.encode(&mut data)?; let xfer_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; // We need to extract stuff from the inputs and outputs that we'll also @@ -172,7 +171,7 @@ impl TestHarness { dao_exec_pk, )?; let mut data = vec![DaoFunction::Exec as u8]; - exec_params.encode_async(&mut data).await?; + exec_params.encode(&mut data)?; let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data }; // Auth module @@ -190,7 +189,7 @@ impl TestHarness { dao_auth_xfer_enc_coin_pk, )?; let mut data = vec![DaoFunction::AuthMoneyTransfer as u8]; - auth_xfer_params.encode_async(&mut data).await?; + auth_xfer_params.encode(&mut data)?; let auth_xfer_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data }; // We need to construct this tree, where exec is the parent: @@ -267,7 +266,7 @@ impl TestHarness { all_vote_blind: ScalarBlind, block_height: u32, ) -> Result<(Transaction, Option)> { - let wallet = self.holders.get_mut(holder).unwrap(); + let wallet = self.wallet(holder); let (dao_exec_pk, dao_exec_zkbin) = match dao_early_exec_secret_key { Some(_) => self.proving_keys.get(DAO_CONTRACT_ZKAS_EARLY_EXEC_NS).unwrap(), @@ -297,7 +296,7 @@ impl TestHarness { // Encode the call let mut data = vec![DaoFunction::Exec as u8]; - exec_params.encode_async(&mut data).await?; + exec_params.encode(&mut data)?; let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data }; // Create the TransactionBuilder containing the `DAO::Exec` call @@ -348,61 +347,27 @@ impl TestHarness { block_height: u32, append: bool, ) -> Result> { - let wallet = self.holders.get_mut(holder).unwrap(); + let wallet = self.wallet_mut(holder); - // Execute the transaction wallet.add_transaction("dao::exec", tx, block_height).await?; if !append { - return Ok(vec![]) + return Ok(vec![]); } - let (mut inputs, mut outputs) = match xfer_params { - Some(params) => (params.inputs.to_vec(), params.outputs.to_vec()), - None => (vec![], vec![]), - }; - - if let Some(ref fee_params) = fee_params { - inputs.push(fee_params.input.clone()); - outputs.push(fee_params.output.clone()); + // Combine transfer and fee inputs/outputs, then process uniformly + let mut inputs = vec![]; + let mut outputs = vec![]; + if let Some(params) = xfer_params { + inputs.extend_from_slice(¶ms.inputs); + outputs.extend_from_slice(¶ms.outputs); + } + if let Some(ref fp) = fee_params { + inputs.push(fp.input.clone()); + outputs.push(fp.output.clone()); } - let nullifiers = inputs.iter().map(|i| i.nullifier.inner()).map(|l| (l, l)).collect(); - wallet.money_null_smt.insert_batch(nullifiers).expect("smt.insert_batch()"); - - for input in inputs { - if let Some(spent_coin) = wallet - .unspent_money_coins - .iter() - .find(|x| x.nullifier() == input.nullifier) - .cloned() - { - debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); - wallet.unspent_money_coins.retain(|x| x.nullifier() != input.nullifier); - wallet.spent_money_coins.push(spent_coin.clone()); - } - } - - let mut found_owncoins = vec![]; - for output in outputs { - wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner())); - - let Ok(note) = output.note.decrypt::(&wallet.keypair.secret) else { - continue - }; - - let owncoin = OwnCoin { - coin: output.coin, - note: note.clone(), - secret: wallet.keypair.secret, - leaf_position: wallet.money_merkle_tree.mark().unwrap(), - }; - - debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); - wallet.unspent_money_coins.push(owncoin.clone()); - found_owncoins.push(owncoin); - } - - Ok(found_owncoins) + wallet.process_inputs(&inputs, holder); + Ok(wallet.process_outputs(&outputs, holder)) } } diff --git a/src/contract/test-harness/src/dao_mint.rs b/src/contract/test-harness/src/dao_mint.rs index 5b3867a39..97d2fc6cb 100644 --- a/src/contract/test-harness/src/dao_mint.rs +++ b/src/contract/test-harness/src/dao_mint.rs @@ -25,16 +25,12 @@ use darkfi_dao_contract::{ model::{Dao, DaoMintParams}, DaoFunction, DAO_CONTRACT_ZKAS_MINT_NS, }; -use darkfi_money_contract::{ - client::{MoneyNote, OwnCoin}, - model::MoneyFeeParamsV1, -}; +use darkfi_money_contract::{client::OwnCoin, model::MoneyFeeParamsV1}; use darkfi_sdk::{ crypto::{contract_id::DAO_CONTRACT_ID, MerkleNode, SecretKey}, ContractCall, }; -use darkfi_serial::AsyncEncodable; -use tracing::debug; +use darkfi_serial::Encodable; use super::{Holder, TestHarness}; @@ -74,7 +70,7 @@ impl TestHarness { // Encode the call let mut data = vec![DaoFunction::Mint as u8]; - params.encode_async(&mut data).await?; + params.encode(&mut data)?; let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?; @@ -119,56 +115,19 @@ impl TestHarness { block_height: u32, append: bool, ) -> Result> { - let wallet = self.holders.get_mut(holder).unwrap(); + let wallet = self.wallet_mut(holder); - // Execute the transaction wallet.add_transaction("dao::mint", tx, block_height).await?; if !append { - return Ok(vec![]) + return Ok(vec![]); } + // Track the DAO bulla in the DAO Merkle tree wallet.dao_merkle_tree.append(MerkleNode::from(params.dao_bulla.inner())); let leaf_pos = wallet.dao_merkle_tree.mark().unwrap(); wallet.dao_leafs.insert(params.dao_bulla, leaf_pos); - if let Some(ref fee_params) = fee_params { - let nullifier = fee_params.input.nullifier.inner(); - wallet - .money_null_smt - .insert_batch(vec![(nullifier, nullifier)]) - .expect("smt.insert_batch()"); - - if let Some(spent_coin) = wallet - .unspent_money_coins - .iter() - .find(|x| x.nullifier() == fee_params.input.nullifier) - .cloned() - { - debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); - wallet.unspent_money_coins.retain(|x| x.nullifier() != fee_params.input.nullifier); - wallet.spent_money_coins.push(spent_coin.clone()); - } - - wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner())); - - let Ok(note) = fee_params.output.note.decrypt::(&wallet.keypair.secret) - else { - return Ok(vec![]) - }; - - let owncoin = OwnCoin { - coin: fee_params.output.coin, - note: note.clone(), - secret: wallet.keypair.secret, - leaf_position: wallet.money_merkle_tree.mark().unwrap(), - }; - - debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); - wallet.unspent_money_coins.push(owncoin.clone()); - return Ok(vec![owncoin]) - } - - Ok(vec![]) + Ok(wallet.process_fee(fee_params, holder)) } } diff --git a/src/contract/test-harness/src/dao_propose.rs b/src/contract/test-harness/src/dao_propose.rs index b9c020cbe..2fb8dea15 100644 --- a/src/contract/test-harness/src/dao_propose.rs +++ b/src/contract/test-harness/src/dao_propose.rs @@ -27,7 +27,7 @@ use darkfi_dao_contract::{ DaoFunction, DAO_CONTRACT_ZKAS_PROPOSE_INPUT_NS, DAO_CONTRACT_ZKAS_PROPOSE_MAIN_NS, }; use darkfi_money_contract::{ - client::{MoneyNote, OwnCoin}, + client::OwnCoin, model::{CoinAttributes, MoneyFeeParamsV1}, MoneyFunction, }; @@ -39,9 +39,8 @@ use darkfi_sdk::{ pasta::pallas, ContractCall, }; -use darkfi_serial::AsyncEncodable; +use darkfi_serial::Encodable; use rand::rngs::OsRng; -use tracing::debug; use super::{Holder, TestHarness}; @@ -58,7 +57,7 @@ impl TestHarness { block_height: u32, duration_blockwindows: u64, ) -> Result<(Transaction, DaoProposeParams, Option, DaoProposal)> { - let wallet = self.holders.get(proposer).unwrap(); + let wallet = self.wallet(proposer); let (dao_propose_burn_pk, dao_propose_burn_zkbin) = self.proving_keys.get(DAO_CONTRACT_ZKAS_PROPOSE_INPUT_NS).unwrap(); @@ -103,7 +102,7 @@ impl TestHarness { proposal_coins.push(coin_params.to_coin()); } let mut proposal_data = vec![]; - proposal_coins.encode_async(&mut proposal_data).await?; + proposal_coins.encode(&mut proposal_data)?; // Create Auth calls let auth_calls = vec![ @@ -155,7 +154,7 @@ impl TestHarness { // Encode the call let mut data = vec![DaoFunction::Propose as u8]; - params.encode_async(&mut data).await?; + params.encode(&mut data)?; let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?; @@ -198,7 +197,7 @@ impl TestHarness { block_height: u32, duration_blockwindows: u64, ) -> Result<(Transaction, DaoProposeParams, Option, DaoProposal)> { - let wallet = self.holders.get(proposer).unwrap(); + let wallet = self.wallet(proposer); let (dao_propose_burn_pk, dao_propose_burn_zkbin) = self.proving_keys.get(DAO_CONTRACT_ZKAS_PROPOSE_INPUT_NS).unwrap(); @@ -213,20 +212,6 @@ impl TestHarness { .unwrap() .clone(); - // Useful code snippet to dump a sled contract DB - /*{ - let blockchain = &wallet.validator.read().await.blockchain; - let contracts = &blockchain.contracts; - let tree = contracts - .lookup(&blockchain.sled_db, &MONEY_CONTRACT_ID, "nullifier_roots") - .unwrap(); - for kv in tree.iter() { - let (key, value) = kv.unwrap(); - debug!("STATE {:?}", key); - debug!(" => {:?}", value); - } - }*/ - let input = DaoProposeStakeInput { secret: wallet.keypair.secret, note: propose_owncoin.note.clone(), @@ -273,7 +258,7 @@ impl TestHarness { // Encode the call let mut data = vec![DaoFunction::Propose as u8]; - params.encode_async(&mut data).await?; + params.encode(&mut data)?; let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?; @@ -318,59 +303,22 @@ impl TestHarness { block_height: u32, append: bool, ) -> Result> { - let wallet = self.holders.get_mut(holder).unwrap(); + let wallet = self.wallet_mut(holder); - // Execute the transaction wallet.add_transaction("dao::propose", tx, block_height).await?; wallet.money_null_smt_snapshot = Some(wallet.money_null_smt.clone()); if !append { - return Ok(vec![]) + return Ok(vec![]); } + // Track proposal in the proposals tree wallet.dao_proposals_tree.append(MerkleNode::from(params.proposal_bulla.inner())); let prop_leaf_pos = wallet.dao_proposals_tree.mark().unwrap(); let prop_money_snapshot = wallet.money_merkle_tree.clone(); wallet.dao_prop_leafs.insert(params.proposal_bulla, (prop_leaf_pos, prop_money_snapshot)); - if let Some(ref fee_params) = fee_params { - let nullifier = fee_params.input.nullifier.inner(); - wallet - .money_null_smt - .insert_batch(vec![(nullifier, nullifier)]) - .expect("smt.insert_batch()"); - - if let Some(spent_coin) = wallet - .unspent_money_coins - .iter() - .find(|x| x.nullifier() == fee_params.input.nullifier) - .cloned() - { - debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); - wallet.unspent_money_coins.retain(|x| x.nullifier() != fee_params.input.nullifier); - wallet.spent_money_coins.push(spent_coin.clone()); - } - - wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner())); - - let Ok(note) = fee_params.output.note.decrypt::(&wallet.keypair.secret) - else { - return Ok(vec![]) - }; - - let owncoin = OwnCoin { - coin: fee_params.output.coin, - note: note.clone(), - secret: wallet.keypair.secret, - leaf_position: wallet.money_merkle_tree.mark().unwrap(), - }; - - debug!("Found new OwnCoin({}) for {:?}:", owncoin.coin, holder); - wallet.unspent_money_coins.push(owncoin.clone()); - return Ok(vec![owncoin]) - } - - Ok(vec![]) + Ok(wallet.process_fee(fee_params, holder)) } } diff --git a/src/contract/test-harness/src/dao_vote.rs b/src/contract/test-harness/src/dao_vote.rs index 41595aee1..02c7f43ac 100644 --- a/src/contract/test-harness/src/dao_vote.rs +++ b/src/contract/test-harness/src/dao_vote.rs @@ -2,7 +2,7 @@ * * Copyright (C) 2020-2026 Dyne.org foundation * - * This program is free software: you can redistributemoney it and/or modify + * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. @@ -26,16 +26,9 @@ use darkfi_dao_contract::{ model::{Dao, DaoProposal, DaoVoteParams}, DaoFunction, DAO_CONTRACT_ZKAS_VOTE_INPUT_NS, DAO_CONTRACT_ZKAS_VOTE_MAIN_NS, }; -use darkfi_money_contract::{ - client::{MoneyNote, OwnCoin}, - model::MoneyFeeParamsV1, -}; -use darkfi_sdk::{ - crypto::{contract_id::DAO_CONTRACT_ID, MerkleNode}, - ContractCall, -}; -use darkfi_serial::AsyncEncodable; -use tracing::debug; +use darkfi_money_contract::{client::OwnCoin, model::MoneyFeeParamsV1}; +use darkfi_sdk::{crypto::contract_id::DAO_CONTRACT_ID, ContractCall}; +use darkfi_serial::Encodable; use super::{Holder, TestHarness}; @@ -49,7 +42,7 @@ impl TestHarness { proposal: &DaoProposal, block_height: u32, ) -> Result<(Transaction, DaoVoteParams, Option)> { - let wallet = self.holders.get(voter).unwrap(); + let wallet = self.wallet(voter); let (dao_vote_burn_pk, dao_vote_burn_zkbin) = self.proving_keys.get(DAO_CONTRACT_ZKAS_VOTE_INPUT_NS).unwrap(); @@ -94,7 +87,7 @@ impl TestHarness { // Encode the call let mut data = vec![DaoFunction::Vote as u8]; - params.encode_async(&mut data).await?; + params.encode(&mut data)?; let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?; @@ -138,52 +131,14 @@ impl TestHarness { block_height: u32, append: bool, ) -> Result> { - let wallet = self.holders.get_mut(holder).unwrap(); + let wallet = self.wallet_mut(holder); - // Execute the transaction wallet.add_transaction("dao::vote", tx, block_height).await?; if !append { - return Ok(vec![]) + return Ok(vec![]); } - if let Some(ref fee_params) = fee_params { - let nullifier = fee_params.input.nullifier.inner(); - wallet - .money_null_smt - .insert_batch(vec![(nullifier, nullifier)]) - .expect("smt.insert_batch()"); - - if let Some(spent_coin) = wallet - .unspent_money_coins - .iter() - .find(|x| x.nullifier() == fee_params.input.nullifier) - .cloned() - { - debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); - wallet.unspent_money_coins.retain(|x| x.nullifier() != fee_params.input.nullifier); - wallet.spent_money_coins.push(spent_coin.clone()); - } - - wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner())); - - let Ok(note) = fee_params.output.note.decrypt::(&wallet.keypair.secret) - else { - return Ok(vec![]) - }; - - let owncoin = OwnCoin { - coin: fee_params.output.coin, - note: note.clone(), - secret: wallet.keypair.secret, - leaf_position: wallet.money_merkle_tree.mark().unwrap(), - }; - - debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); - wallet.unspent_money_coins.push(owncoin.clone()); - return Ok(vec![owncoin]) - } - - Ok(vec![]) + Ok(wallet.process_fee(fee_params, holder)) } } diff --git a/src/contract/test-harness/src/lib.rs b/src/contract/test-harness/src/lib.rs index efe04398d..c66fb0b39 100644 --- a/src/contract/test-harness/src/lib.rs +++ b/src/contract/test-harness/src/lib.rs @@ -18,8 +18,10 @@ use std::{ collections::HashMap, + fs::OpenOptions, io::{Cursor, Write}, slice, + time::Instant, }; use darkfi::{ @@ -36,64 +38,68 @@ use darkfi::{ zkas::ZkBinary, Result, }; -use darkfi_dao_contract::model::{DaoBulla, DaoProposalBulla}; -use darkfi_money_contract::client::OwnCoin; +use darkfi_dao_contract::model::{Dao, DaoBulla, DaoProposal, DaoProposalBulla, DaoVoteParams}; +use darkfi_money_contract::{ + client::{MoneyNote, OwnCoin}, + model::{ + CoinAttributes, Input, MoneyFeeParamsV1, MoneyGenesisMintParamsV1, Nullifier, Output, + TokenAttributes, TokenId, + }, + MoneyFunction, +}; use darkfi_sdk::{ bridgetree, crypto::{ + contract_id::MONEY_CONTRACT_ID, + poseidon_hash, smt::{MemoryStorageFp, PoseidonFp, SmtMemoryFp, EMPTY_NODES_FP}, - Keypair, MerkleNode, MerkleTree, + BaseBlind, FuncRef, Keypair, MerkleNode, MerkleTree, ScalarBlind, SecretKey, }, pasta::pallas, }; use darkfi_serial::{serialize, Encodable}; use num_bigint::BigUint; +use rand::rngs::OsRng; use sled_overlay::sled; -use tracing::warn; +use tracing::{debug, warn}; /// Utility module for caching ZK proof PKs and VKs pub mod vks; -/// `Money::PoWReward` functionality -mod money_pow_reward; - /// `Money::Fee` functionality mod money_fee; - /// `Money::GenesisMint` functionality mod money_genesis_mint; - +/// `Money::OtcSwap` functionality +mod money_otc_swap; +/// `Money::PoWReward` functionality +mod money_pow_reward; +/// `Money::TokenMint` functionality +mod money_token; /// `Money::Transfer` functionality mod money_transfer; -/// `Money::TokenMint` functionality -mod money_token; - -/// `Money::OtcSwap` functionality -mod money_otc_swap; - /// `Deployooor::Deploy` functionality mod contract_deploy; - /// `Deployooor::Lock` functionality mod contract_lock; +/// `Dao::Exec` functionality +mod dao_exec; /// `Dao::Mint` functionality mod dao_mint; - /// `Dao::Propose` functionality mod dao_propose; - /// `Dao::Vote` functionality mod dao_vote; -/// `Dao::Exec` functionality -mod dao_exec; +/// PoW target +const POW_TARGET: u32 = 120; /// Initialize the logging mechanism pub fn init_logger() { - // We check this error so we can execute same file tests in parallel, - // otherwise second one fails to init logger here. + // We check this error so we can execute same-file tests in parallel. + // Otherwise subsequent calls fail to init the logger here. if setup_test_logger( &["sled"], false, @@ -104,7 +110,7 @@ pub fn init_logger() { ) .is_err() { - warn!(target: "test_harness", "Logger already initialized"); + warn!(target: "test-harness", "Logger already initialized"); } } @@ -130,13 +136,16 @@ pub struct Wallet { pub validator: ValidatorPtr, /// Holder's instance of the Merkle tree for the `Money` contract pub money_merkle_tree: MerkleTree, - /// Holder's instance of the SMT tree for the `Money` contract + /// Holder's instance of the nullifiers SMT tree for the `Money` contract pub money_null_smt: SmtMemoryFp, - /// Holder's instance of the SMT tree for the `Money` contract (snapshotted for DAO::propose()) + /// Holder's instance of the nullifiers SMT tree for the `Money` contract + /// snapshotted for `DAO::Propose` pub money_null_smt_snapshot: Option, - /// Holder's instance of the Merkle tree for the `DAO` contract (holding DAO bullas) + /// Holder's instance of the Merkle tree for the `DAO` contract + /// holding DAO bullas pub dao_merkle_tree: MerkleTree, - /// Holder's instance of the Merkle tree for the `DAO` contract (holding DAO proposals) + /// Holder's instance of the Merkle tree for the `DAO` contract + /// holding DAO proposals pub dao_proposals_tree: MerkleTree, /// Holder's set of unspent [`OwnCoin`]s from the `Money` contract pub unspent_money_coins: Vec, @@ -166,8 +175,8 @@ impl Wallet { // Inject the cached VKs into the database let overlay = BlockchainOverlay::new(&Blockchain::new(&sled_db)?)?; vks::inject(&overlay, vks)?; - let pow_target = 120; - deploy_native_contracts(&overlay, pow_target).await?; + + deploy_native_contracts(&overlay, POW_TARGET).await?; let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&[])?; overlay.lock().unwrap().contracts.update_state_monotree(&diff)?; overlay.lock().unwrap().overlay.lock().unwrap().apply()?; @@ -175,15 +184,15 @@ impl Wallet { // Create the `Validator` instance let validator_config = ValidatorConfig { confirmation_threshold: 3, - pow_target, + pow_target: POW_TARGET, pow_fixed_difficulty: Some(BigUint::from(1_u8)), genesis_block, verify_fees, }; let validator = Validator::new(&sled_db, &validator_config).await?; - // The Merkle tree for the `Money` contract is initialized with a "null" - // leaf at position 0. + // The Merkle tree for the Money contract is initialized with a + // "null" leaf at position 0. let mut money_merkle_tree = MerkleTree::new(1); money_merkle_tree.append(MerkleNode::from(pallas::Base::ZERO)); money_merkle_tree.mark().unwrap(); @@ -217,7 +226,7 @@ impl Wallet { block_height: u32, ) -> Result<()> { if self.bench_wasm { - let _ = benchmark_wasm_calls(callname, &self.validator, &tx, block_height).await; + let _ = benchmark_wasm_calls(callname, self.validator.clone(), &tx, block_height).await; } let validator = self.validator.read().await; @@ -232,21 +241,85 @@ impl Wallet { .await?; // Write the data - { - let blockchain = &validator.blockchain; - let txs = &blockchain.transactions; - txs.insert(slice::from_ref(&tx)).expect("insert tx"); - txs.insert_location(&[tx.hash()], block_height).expect("insert loc"); - } + let blockchain = &validator.blockchain; + let txs = &blockchain.transactions; + txs.insert(slice::from_ref(&tx)).expect("insert tx"); + txs.insert_location(&[tx.hash()], block_height).expect("insert loc"); Ok(()) } + + /// Mark a single nullifier as spent in the SMT and move any matching + /// OwnCoin from `unspent_money_coins` to `spent_money_coins` + pub fn mark_spent_nullifier(&mut self, nullifier: Nullifier, holder: &Holder) { + let n = nullifier.inner(); + self.money_null_smt.insert_batch(vec![(n, n)]).expect("smt.insert_batch()"); + + if let Some(spent_coin) = + self.unspent_money_coins.iter().find(|x| x.nullifier() == nullifier).cloned() + { + debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); + self.unspent_money_coins.retain(|x| x.nullifier() != nullifier); + self.spent_money_coins.push(spent_coin); + } + } + + /// Process a set of [`Input`]s. + /// Insert nullifiers into the SMT and move any matching OwnCoins from + /// unspent to spent. + pub fn process_inputs(&mut self, inputs: &[Input], holder: &Holder) { + for input in inputs { + self.mark_spent_nullifier(input.nullifier, holder); + } + } + + /// Process a set of [`Output`]s. + /// Append each coin to the Merkle tree and attempt to decrypt the note. + /// Returns any new OwnCoins found. + pub fn process_outputs(&mut self, outputs: &[Output], holder: &Holder) -> Vec { + let mut found = vec![]; + + for output in outputs { + self.money_merkle_tree.append(MerkleNode::from(output.coin.inner())); + + let Ok(note) = output.note.decrypt::(&self.keypair.secret) else { continue }; + + let owncoin = OwnCoin { + coin: output.coin, + note: note.clone(), + secret: self.keypair.secret, + leaf_position: self.money_merkle_tree.mark().unwrap(), + }; + + debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); + self.unspent_money_coins.push(owncoin.clone()); + found.push(owncoin); + } + + found + } + + /// Process the fee component of a transaction (if present). + /// Handles the fee input nullifier and fee change output. + /// Returns any new OwnCoins found. + pub fn process_fee( + &mut self, + fee_params: &Option, + holder: &Holder, + ) -> Vec { + let Some(ref fp) = fee_params else { return vec![] }; + + self.mark_spent_nullifier(fp.input.nullifier, holder); + self.process_outputs(slice::from_ref(&fp.output), holder) + } } /// Native contract test harness instance pub struct TestHarness { /// Initialized [`Holder`]s for this instance pub holders: HashMap, + /// Ordered list of all holder keys (for broadcast operations) + pub holder_keys: Vec, /// Cached [`ProvingKey`]s for native contract ZK proving pub proving_keys: HashMap, /// The genesis block for this harness @@ -283,13 +356,15 @@ impl TestHarness { let sled_db = sled::Config::new().temporary(true).open()?; let overlay = BlockchainOverlay::new(&Blockchain::new(&sled_db)?)?; vks::inject(&overlay, &vks)?; - deploy_native_contracts(&overlay, 90).await?; + deploy_native_contracts(&overlay, POW_TARGET).await?; let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&[])?; genesis_block.header.state_root = overlay.lock().unwrap().contracts.update_state_monotree(&diff)?; // Create `Wallet` instances let mut holders_map = HashMap::new(); + let mut holder_keys = Vec::with_capacity(holders.len()); + for holder in holders { let keypair = Keypair::random(&mut rng); let token_mint_authority = Keypair::random(&mut rng); @@ -306,35 +381,517 @@ impl TestHarness { .await?; holders_map.insert(*holder, wallet); + holder_keys.push(*holder); } - Ok(Self { holders: holders_map, proving_keys, genesis_block, verify_fees }) + Ok(Self { holders: holders_map, holder_keys, proving_keys, genesis_block, verify_fees }) } - /// Assert that all holders' trees are the same + /// Get a reference to a Holder's Wallet + pub fn wallet(&self, holder: &Holder) -> &Wallet { + self.holders.get(holder).unwrap() + } + + /// Get a mutable reference to a Holder's Wallet + pub fn wallet_mut(&mut self, holder: &Holder) -> &mut Wallet { + self.holders.get_mut(holder).unwrap() + } + + /// Get a Holder's unspent OwnCoins + pub fn coins(&self, holder: &Holder) -> &[OwnCoin] { + &self.wallet(holder).unspent_money_coins + } + + /// Get a Holder's unspent OwnCoins filtered by token ID + pub fn coins_by_token(&self, holder: &Holder, token_id: TokenId) -> Vec { + self.coins(holder).iter().filter(|c| c.note.token_id == token_id).cloned().collect() + } + + /// Get the total balance of a Holder for a given token + pub fn balance(&self, holder: &Holder, token_id: TokenId) -> u64 { + self.coins(holder) + .iter() + .filter(|c| c.note.token_id == token_id) + .map(|c| c.note.value) + .sum() + } + + /// Assert that all holders' Merkle trees are consistent pub fn assert_trees(&self, holders: &[Holder]) { - assert!(holders.len() > 1); - // Gather wallets + assert!(!holders.is_empty()); let mut wallets = vec![]; for holder in holders { wallets.push(self.holders.get(holder).unwrap()); } - // Compare trees - let wallet = wallets[0]; - let money_root = wallet.money_merkle_tree.root(0).unwrap(); + + let money_root = wallets[0].money_merkle_tree.root(0).unwrap(); for wallet in &wallets[1..] { - assert!(money_root == wallet.money_merkle_tree.root(0).unwrap()); + assert_eq!(money_root, wallet.money_merkle_tree.root(0).unwrap()); } } + + /// Assert all registered holders' Merkle trees are consistent + pub fn assert_all_trees(&self) { + if !self.holder_keys.is_empty() { + self.assert_trees(&self.holder_keys); + } + } + + /// Mint a token for `recipient` and execute the tx on all registered + /// holders. + /// Returns the minted `TokenId` + pub async fn token_mint_to_all( + &mut self, + amount: u64, + holder: &Holder, + recipient: &Holder, + block_height: u32, + ) -> Result { + let token_blind = BaseBlind::random(&mut OsRng); + let (tx, mint_params, auth_params, fee_params) = self + .token_mint(amount, holder, recipient, token_blind, None, None, block_height) + .await?; + + // Derive the Token ID + let token_id = self.derive_token_id(recipient, token_blind); + + let holders = self.holder_keys.clone(); + for h in &holders { + self.execute_token_mint_tx( + h, + tx.clone(), + &mint_params, + &auth_params, + &fee_params, + block_height, + true, + ) + .await?; + } + + self.assert_all_trees(); + + Ok(token_id) + } + + /// Mint a token with a specific `token_blind` and execute on all + /// registered holders. Returns the [`TokenId`]. + /// + /// Use this instead of `token_mint_to_all` when you need the same + /// token blind across multiple mints (e.g. DAO governance tokens). + pub async fn token_mint_with_blind_to_all( + &mut self, + amount: u64, + holder: &Holder, + recipient: &Holder, + token_blind: BaseBlind, + block_height: u32, + ) -> Result { + let (tx, mint_params, auth_params, fee_params) = self + .token_mint(amount, holder, recipient, token_blind, None, None, block_height) + .await?; + + let token_id = self.derive_token_id(recipient, token_blind); + + let holders = self.holder_keys.clone(); + for h in &holders { + self.execute_token_mint_tx( + h, + tx.clone(), + &mint_params, + &auth_params, + &fee_params, + block_height, + true, + ) + .await?; + } + self.assert_all_trees(); + + Ok(token_id) + } + + /// Transfer `amount` of `token_id` from `sender` to `recipient` and + /// execute the tx on all registered holders. + pub async fn transfer_to_all( + &mut self, + amount: u64, + sender: &Holder, + recipient: &Holder, + token_id: TokenId, + block_height: u32, + ) -> Result<()> { + let owncoins = self.coins_by_token(sender, token_id); + let (tx, (params, fee_params), _spent) = self + .transfer(amount, sender, recipient, &owncoins, token_id, block_height, false) + .await?; + + let holders = self.holder_keys.clone(); + for h in &holders { + self.execute_transfer_tx(h, tx.clone(), ¶ms, &fee_params, block_height, true) + .await?; + } + + self.assert_all_trees(); + + Ok(()) + } + + /// Build a genesis mint for `holder` and execute on all registered holders. + /// Returns the found [`OwnCoin`]s. + pub async fn genesis_mint_to_all( + &mut self, + holder: &Holder, + amounts: &[u64], + block_height: u32, + ) -> Result> { + let (tx, params) = self.genesis_mint(holder, amounts, None, None).await?; + self.genesis_mint_to_all_with(tx, ¶ms, block_height).await + } + + /// Execute a pre-built genesis mint transaction on all registered holders. + /// Useful when you need to test the transaction before broadcasting + /// (e.g. malicious block height checks). + pub async fn genesis_mint_to_all_with( + &mut self, + tx: Transaction, + params: &MoneyGenesisMintParamsV1, + block_height: u32, + ) -> Result> { + let holders = self.holder_keys.clone(); + let mut found = vec![]; + for h in &holders { + found.extend( + self.execute_genesis_mint_tx(h, tx.clone(), params, block_height, true).await?, + ); + } + self.assert_all_trees(); + Ok(found) + } + + /// Freeze a token authority for `holder` and execute on all registered + /// holders. + pub async fn token_freeze_to_all(&mut self, holder: &Holder, block_height: u32) -> Result<()> { + let (tx, freeze_params, fee_params) = self.token_freeze(holder, block_height).await?; + + let holders = self.holder_keys.clone(); + for h in &holders { + self.execute_token_freeze_tx( + h, + tx.clone(), + &freeze_params, + &fee_params, + block_height, + true, + ) + .await?; + } + self.assert_all_trees(); + Ok(()) + } + + /// Generate a new block mined by `miner` and broadcast to all registered + /// holders. Convenience wrapper around `generate_block` that uses + /// `holder_keys` instead of requiring the caller to pass holders. + pub async fn generate_block_all(&mut self, miner: &Holder) -> Result> { + let holders = self.holder_keys.clone(); + self.generate_block(miner, &holders).await + } + + /// Consolidate all coins of `token_id` owned by `holder` into a single + /// coin by transferring to self, then execute on all registered holders. + pub async fn consolidate_to_all( + &mut self, + holder: &Holder, + token_id: TokenId, + block_height: u32, + ) -> Result<()> { + let owncoins = self.coins_by_token(holder, token_id); + if owncoins.len() <= 1 { + // Nothing to consolidate + return Ok(()) + } + + let total: u64 = owncoins.iter().map(|c| c.note.value).sum(); + let (tx, (params, fee_params), _spent) = + self.transfer(total, holder, holder, &owncoins, token_id, block_height, false).await?; + + let holders = self.holder_keys.clone(); + for h in &holders { + self.execute_transfer_tx(h, tx.clone(), ¶ms, &fee_params, block_height, true) + .await?; + } + + self.assert_all_trees(); + + Ok(()) + } + + /// Perform an OTC swap between two holders and execute on all registered + /// holders. + pub async fn otc_swap_to_all( + &mut self, + holder0: &Holder, + coin0: &OwnCoin, + holder1: &Holder, + coin1: &OwnCoin, + block_height: u32, + ) -> Result<()> { + let (tx, params, fee_params) = + self.otc_swap(holder0, coin0, holder1, coin1, block_height).await?; + + let holders = self.holder_keys.clone(); + for h in &holders { + self.execute_otc_swap_tx(h, tx.clone(), ¶ms, &fee_params, block_height, true) + .await?; + } + + self.assert_all_trees(); + + Ok(()) + } + + /// Build a `Dao::Mint` transaction and execute on all registered holders. + #[allow(clippy::too_many_arguments)] + pub async fn dao_mint_to_all( + &mut self, + holder: &Holder, + dao: &Dao, + dao_notes_secret_key: &SecretKey, + dao_proposer_secret_key: &SecretKey, + dao_proposals_secret_key: &SecretKey, + dao_votes_secret_key: &SecretKey, + dao_exec_secret_key: &SecretKey, + dao_early_exec_secret_key: &SecretKey, + block_height: u32, + ) -> Result<()> { + let (tx, params, fee_params) = self + .dao_mint( + holder, + dao, + dao_notes_secret_key, + dao_proposer_secret_key, + dao_proposals_secret_key, + dao_votes_secret_key, + dao_exec_secret_key, + dao_early_exec_secret_key, + block_height, + ) + .await?; + + let holders = self.holder_keys.clone(); + for h in &holders { + self.execute_dao_mint_tx(h, tx.clone(), ¶ms, &fee_params, block_height, true) + .await?; + } + self.assert_all_trees(); + Ok(()) + } + + /// Build a `Dao::Propose` (transfer) transaction and execute on all + /// registered holders. Returns the [`DaoProposal`] for subsequent + /// voting/execution. + #[allow(clippy::too_many_arguments)] + pub async fn dao_propose_transfer_to_all( + &mut self, + proposer: &Holder, + proposal_coinattrs: &[CoinAttributes], + user_data: pallas::Base, + dao: &Dao, + dao_proposer_secret_key: &SecretKey, + block_height: u32, + duration_blockwindows: u64, + ) -> Result { + let (tx, params, fee_params, proposal_info) = self + .dao_propose_transfer( + proposer, + proposal_coinattrs, + user_data, + dao, + dao_proposer_secret_key, + block_height, + duration_blockwindows, + ) + .await?; + + let holders = self.holder_keys.clone(); + for h in &holders { + self.execute_dao_propose_tx(h, tx.clone(), ¶ms, &fee_params, block_height, true) + .await?; + } + self.assert_all_trees(); + Ok(proposal_info) + } + + /// Build a `Dao::Propose` (generic) transaction and execute on all + /// registered holders. Returns the [`DaoProposal`]. + pub async fn dao_propose_generic_to_all( + &mut self, + proposer: &Holder, + user_data: pallas::Base, + dao: &Dao, + dao_proposer_secret_key: &SecretKey, + block_height: u32, + duration_blockwindows: u64, + ) -> Result { + let (tx, params, fee_params, proposal_info) = self + .dao_propose_generic( + proposer, + user_data, + dao, + dao_proposer_secret_key, + block_height, + duration_blockwindows, + ) + .await?; + + let holders = self.holder_keys.clone(); + for h in &holders { + self.execute_dao_propose_tx(h, tx.clone(), ¶ms, &fee_params, block_height, true) + .await?; + } + self.assert_all_trees(); + Ok(proposal_info) + } + + /// Build and broadcast a single `Dao::Vote` transaction on all registered + /// holders. Returns the [`DaoVoteParams`] (needed for vote counting). + pub async fn dao_vote_to_all( + &mut self, + voter: &Holder, + vote_option: bool, + dao: &Dao, + proposal: &DaoProposal, + block_height: u32, + ) -> Result { + let (tx, vote_params, fee_params) = + self.dao_vote(voter, vote_option, dao, proposal, block_height).await?; + + let holders = self.holder_keys.clone(); + for h in &holders { + self.execute_dao_vote_tx(h, tx.clone(), &fee_params, block_height, true).await?; + } + self.assert_all_trees(); + Ok(vote_params) + } + + /// Build and broadcast a `Dao::Exec` (transfer) transaction on all + /// registered holders. + #[allow(clippy::too_many_arguments)] + pub async fn dao_exec_transfer_to_all( + &mut self, + executor: &Holder, + dao: &Dao, + dao_exec_secret_key: &SecretKey, + dao_early_exec_secret_key: &Option, + proposal_info: &DaoProposal, + proposal_coinattrs: Vec, + total_yes_vote_value: u64, + total_all_vote_value: u64, + total_yes_vote_blind: ScalarBlind, + total_all_vote_blind: ScalarBlind, + block_height: u32, + ) -> Result<()> { + let (tx, xfer_params, fee_params) = self + .dao_exec_transfer( + executor, + dao, + dao_exec_secret_key, + dao_early_exec_secret_key, + proposal_info, + proposal_coinattrs, + total_yes_vote_value, + total_all_vote_value, + total_yes_vote_blind, + total_all_vote_blind, + block_height, + ) + .await?; + + let holders = self.holder_keys.clone(); + for h in &holders { + self.execute_dao_exec_tx( + h, + tx.clone(), + Some(&xfer_params), + &fee_params, + block_height, + true, + ) + .await?; + } + self.assert_all_trees(); + Ok(()) + } + + /// Build and broadcast a `Dao::Exec` (generic) transaction on all + /// registered holders. + #[allow(clippy::too_many_arguments)] + pub async fn dao_exec_generic_to_all( + &mut self, + executor: &Holder, + dao: &Dao, + dao_exec_secret_key: &SecretKey, + dao_early_exec_secret_key: &Option, + proposal_info: &DaoProposal, + total_yes_vote_value: u64, + total_all_vote_value: u64, + total_yes_vote_blind: ScalarBlind, + total_all_vote_blind: ScalarBlind, + block_height: u32, + ) -> Result<()> { + let (tx, fee_params) = self + .dao_exec_generic( + executor, + dao, + dao_exec_secret_key, + dao_early_exec_secret_key, + proposal_info, + total_yes_vote_value, + total_all_vote_value, + total_yes_vote_blind, + total_all_vote_blind, + block_height, + ) + .await?; + + let holders = self.holder_keys.clone(); + for h in &holders { + self.execute_dao_exec_tx(h, tx.clone(), None, &fee_params, block_height, true).await?; + } + self.assert_all_trees(); + Ok(()) + } + + /// Derive the [`TokenId`] that a given holder's `token_mint_authority` + /// would produce with a given blind. + pub fn derive_token_id(&self, holder: &Holder, token_blind: BaseBlind) -> TokenId { + let wallet = self.wallet(holder); + let mint_authority = wallet.token_mint_authority; + + let auth_func_id = FuncRef { + contract_id: *MONEY_CONTRACT_ID, + func_code: MoneyFunction::AuthTokenMintV1 as u8, + } + .to_func_id(); + + let token_attrs = TokenAttributes { + auth_parent: auth_func_id, + user_data: poseidon_hash([mint_authority.public.x(), mint_authority.public.y()]), + blind: token_blind, + }; + + token_attrs.to_token_id() + } } async fn benchmark_wasm_calls( callname: &str, - validator: &ValidatorPtr, + validator: ValidatorPtr, tx: &Transaction, block_height: u32, ) -> Result<()> { - let mut file = std::fs::OpenOptions::new().create(true).append(true).open("bench.csv")?; + let mut file = OpenOptions::new().create(true).append(true).open("bench.csv")?; let validator = validator.read().await; for (idx, call) in tx.calls.iter().enumerate() { @@ -356,29 +913,29 @@ async fn benchmark_wasm_calls( tx.calls.encode(&mut payload)?; let mut times = [0; 3]; - let now = std::time::Instant::now(); + let now = Instant::now(); let _metadata = runtime.metadata(&payload)?; times[0] = now.elapsed().as_micros(); - let now = std::time::Instant::now(); + let now = Instant::now(); let mut update = vec![call.data.data[0]]; update.append(&mut runtime.exec(&payload)?); times[1] = now.elapsed().as_micros(); - let now = std::time::Instant::now(); + let now = Instant::now(); runtime.apply(&update)?; times[2] = now.elapsed().as_micros(); writeln!( file, - "{}, {}, {}, {}, {}, {}, {}", + "{},{},{},{},{},{},{}", callname, tx.hash(), idx, times[0], times[1], times[2], - serialize(tx).len() + serialize(tx).len(), )?; } diff --git a/src/contract/test-harness/src/money_fee.rs b/src/contract/test-harness/src/money_fee.rs index 577cb453b..a5df3300a 100644 --- a/src/contract/test-harness/src/money_fee.rs +++ b/src/contract/test-harness/src/money_fee.rs @@ -36,14 +36,14 @@ use darkfi_money_contract::{ use darkfi_sdk::{ crypto::{ contract_id::MONEY_CONTRACT_ID, note::AeadEncryptedNote, BaseBlind, Blind, FuncId, - MerkleNode, ScalarBlind, SecretKey, + ScalarBlind, SecretKey, }, pasta::pallas, ContractCall, }; -use darkfi_serial::AsyncEncodable; +use darkfi_serial::Encodable; use rand::rngs::OsRng; -use tracing::{debug, info}; +use tracing::info; use super::{Holder, TestHarness}; @@ -55,7 +55,7 @@ impl TestHarness { &mut self, holder: &Holder, ) -> Result<(Transaction, MoneyFeeParamsV1)> { - let wallet = self.holders.get(holder).unwrap(); + let wallet = self.wallet(holder); // Compute fee call required fee let required_fee = compute_fee(&FEE_CALL_GAS); @@ -92,7 +92,7 @@ impl TestHarness { // Generate an ephemeral signing key let signature_secret = SecretKey::random(&mut OsRng); - info!("Creting FeeV1 ZK proof"); + info!("Creating FeeV1 ZK proof"); let (fee_pk, fee_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_FEE_NS_V1).unwrap(); let (proof, public_inputs) = create_fee_proof( @@ -143,8 +143,8 @@ impl TestHarness { }; let mut data = vec![MoneyFunction::FeeV1 as u8]; - required_fee.encode_async(&mut data).await?; - params.encode_async(&mut data).await?; + required_fee.encode(&mut data)?; + params.encode(&mut data)?; let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![proof] }, vec![])?; @@ -155,7 +155,7 @@ impl TestHarness { Ok((tx, params)) } - /// Execute the transaction created by `create_empty_fee_call()` for a given [`Holder`] + /// Execute the transaction created by `create_empty_fee_call()` for a given [`Holder`]. /// /// Returns any found [`OwnCoin`]s. pub async fn execute_empty_fee_call_tx( @@ -165,44 +165,9 @@ impl TestHarness { params: &MoneyFeeParamsV1, block_height: u32, ) -> Result> { - let wallet = self.holders.get_mut(holder).unwrap(); - - let nullifier = params.input.nullifier.inner(); - wallet - .money_null_smt - .insert_batch(vec![(nullifier, nullifier)]) - .expect("smt.insert_batch()"); - + let wallet = self.wallet_mut(holder); wallet.add_transaction("money::fee", tx, block_height).await?; - wallet.money_merkle_tree.append(MerkleNode::from(params.output.coin.inner())); - - // Attempt to decrypt the output note to see if this is a coin for the holder - let Ok(note) = params.output.note.decrypt::(&wallet.keypair.secret) else { - return Ok(vec![]) - }; - - let owncoin = OwnCoin { - coin: params.output.coin, - note: note.clone(), - secret: wallet.keypair.secret, - leaf_position: wallet.money_merkle_tree.mark().unwrap(), - }; - - let spent_coin = wallet - .unspent_money_coins - .iter() - .find(|x| x.nullifier() == params.input.nullifier) - .unwrap() - .clone(); - - debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); - debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); - - wallet.unspent_money_coins.retain(|x| x.nullifier() != params.input.nullifier); - wallet.spent_money_coins.push(spent_coin); - wallet.unspent_money_coins.push(owncoin.clone()); - - Ok(vec![owncoin]) + Ok(wallet.process_fee(&Some(params.clone()), holder)) } /// Create and append a `Money::Fee` call to a given [`Transaction`] for @@ -218,9 +183,9 @@ impl TestHarness { block_height: u32, spent_coins: &[OwnCoin], ) -> Result<(ContractCall, Vec, Vec, Vec, MoneyFeeParamsV1)> { - // First we verify the fee-less transaction to see how much gas it uses for execution - // and verification. - let wallet = self.holders.get(holder).unwrap(); + // First we verify the fee-less transaction to see how much gas it + // uses for execution and verification. + let wallet = self.wallet(holder); let validator = wallet.validator.read().await; let gas_used = validator .add_test_transactions( @@ -236,8 +201,8 @@ impl TestHarness { // Compute the required fee let required_fee = compute_fee(&(gas_used + FEE_CALL_GAS)); - // Knowing the total gas, we can now find an OwnCoin of enough value - // so that we can create a valid Money::Fee call. + // Knowing the total gas, we can now find an OwnCoin of enough + // value so that we can create a valid Money::Fee call. let spent_coins: HashSet<&OwnCoin, RandomState> = HashSet::from_iter(spent_coins); let mut available_coins = wallet.unspent_money_coins.clone(); available_coins @@ -325,8 +290,8 @@ impl TestHarness { // Encode the contract call let mut data = vec![MoneyFunction::FeeV1 as u8]; - required_fee.encode_async(&mut data).await?; - params.encode_async(&mut data).await?; + required_fee.encode(&mut data)?; + params.encode(&mut data)?; let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; Ok((call, vec![proof], vec![signature_secret], vec![coin.clone()], params)) diff --git a/src/contract/test-harness/src/money_genesis_mint.rs b/src/contract/test-harness/src/money_genesis_mint.rs index d422265fd..e84cfa450 100644 --- a/src/contract/test-harness/src/money_genesis_mint.rs +++ b/src/contract/test-harness/src/money_genesis_mint.rs @@ -21,17 +21,16 @@ use darkfi::{ Result, }; use darkfi_money_contract::{ - client::{genesis_mint_v1::GenesisMintCallBuilder, MoneyNote, OwnCoin}, + client::{genesis_mint_v1::GenesisMintCallBuilder, OwnCoin}, model::MoneyGenesisMintParamsV1, MoneyFunction, MONEY_CONTRACT_ZKAS_MINT_NS_V1, }; use darkfi_sdk::{ - crypto::{contract_id::MONEY_CONTRACT_ID, FuncId, MerkleNode}, + crypto::{contract_id::MONEY_CONTRACT_ID, FuncId}, pasta::pallas, ContractCall, }; -use darkfi_serial::AsyncEncodable; -use tracing::debug; +use darkfi_serial::Encodable; use super::{Holder, TestHarness}; @@ -46,7 +45,7 @@ impl TestHarness { spend_hook: Option, user_data: Option, ) -> Result<(Transaction, MoneyGenesisMintParamsV1)> { - let wallet = self.holders.get(holder).unwrap(); + let wallet = self.wallet(holder); let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap(); @@ -65,7 +64,7 @@ impl TestHarness { // Encode and build the transaction let mut data = vec![MoneyFunction::GenesisMintV1 as u8]; - debris.params.encode_async(&mut data).await?; + debris.params.encode(&mut data)?; let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?; @@ -89,35 +88,12 @@ impl TestHarness { ) -> Result> { let wallet = self.holders.get_mut(holder).unwrap(); - // Execute the transaction wallet.add_transaction("money::genesis_mint", tx, block_height).await?; if !append { - return Ok(vec![]) + return Ok(vec![]); } - // Iterate over call outputs to find any new OwnCoins - let mut found_owncoins = vec![]; - for output in ¶ms.outputs { - wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner())); - - // Attempt to decrypt the output note to see if this is a coin for the holder. - let Ok(note) = output.note.decrypt::(&wallet.keypair.secret) else { - continue - }; - - let owncoin = OwnCoin { - coin: output.coin, - note: note.clone(), - secret: wallet.keypair.secret, - leaf_position: wallet.money_merkle_tree.mark().unwrap(), - }; - - debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); - wallet.unspent_money_coins.push(owncoin.clone()); - found_owncoins.push(owncoin); - } - - Ok(found_owncoins) + Ok(wallet.process_outputs(¶ms.outputs, holder)) } } diff --git a/src/contract/test-harness/src/money_otc_swap.rs b/src/contract/test-harness/src/money_otc_swap.rs index 9a3cff977..c1b8eb307 100644 --- a/src/contract/test-harness/src/money_otc_swap.rs +++ b/src/contract/test-harness/src/money_otc_swap.rs @@ -24,18 +24,17 @@ use darkfi::{ Result, }; use darkfi_money_contract::{ - client::{swap_v1::SwapCallBuilder, MoneyNote, OwnCoin}, + client::{swap_v1::SwapCallBuilder, OwnCoin}, model::{MoneyFeeParamsV1, MoneyTransferParamsV1}, MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1, }; use darkfi_sdk::{ - crypto::{contract_id::MONEY_CONTRACT_ID, BaseBlind, Blind, FuncId, MerkleNode}, + crypto::{contract_id::MONEY_CONTRACT_ID, BaseBlind, Blind, FuncId}, pasta::pallas, ContractCall, }; -use darkfi_serial::AsyncEncodable; +use darkfi_serial::Encodable; use rand::rngs::OsRng; -use tracing::debug; use super::{Holder, TestHarness}; @@ -51,8 +50,8 @@ impl TestHarness { owncoin1: &OwnCoin, block_height: u32, ) -> Result<(Transaction, MoneyTransferParamsV1, Option)> { - let wallet0 = self.holders.get(holder0).unwrap(); - let wallet1 = self.holders.get(holder1).unwrap(); + let wallet0 = self.wallet(holder0); + let wallet1 = self.wallet(holder1); let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap(); let (burn_pk, burn_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap(); @@ -131,7 +130,7 @@ impl TestHarness { // Encode the contract call let mut data = vec![MoneyFunction::OtcSwapV1 as u8]; - swap_full_params.encode_async(&mut data).await?; + swap_full_params.encode(&mut data)?; let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs: swap_full_proofs }, vec![])?; @@ -185,60 +184,23 @@ impl TestHarness { block_height: u32, append: bool, ) -> Result> { - let wallet = self.holders.get_mut(holder).unwrap(); + let wallet = self.wallet_mut(holder); - // Execute the transaction wallet.add_transaction("money::otc_swap", tx, block_height).await?; - let mut found_owncoins = vec![]; - if !append { - return Ok(found_owncoins) + return Ok(vec![]); } + // Combine swap inputs/outputs with fee inputs/outputs, then process let mut inputs = swap_params.inputs.to_vec(); let mut outputs = swap_params.outputs.to_vec(); - if let Some(ref fee_params) = fee_params { - inputs.push(fee_params.input.clone()); - outputs.push(fee_params.output.clone()); + if let Some(ref fp) = fee_params { + inputs.push(fp.input.clone()); + outputs.push(fp.output.clone()); } - let nullifiers = inputs.iter().map(|i| i.nullifier.inner()).map(|l| (l, l)).collect(); - wallet.money_null_smt.insert_batch(nullifiers).expect("smt.insert_batch()"); - - for input in inputs { - if let Some(spent_coin) = wallet - .unspent_money_coins - .iter() - .find(|x| x.nullifier() == input.nullifier) - .cloned() - { - debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); - wallet.unspent_money_coins.retain(|x| x.nullifier() != input.nullifier); - wallet.spent_money_coins.push(spent_coin.clone()); - } - } - - for output in outputs { - wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner())); - - // Attempt to decrypt the encrypted note - let Ok(note) = output.note.decrypt::(&wallet.keypair.secret) else { - continue - }; - - let owncoin = OwnCoin { - coin: output.coin, - note: note.clone(), - secret: wallet.keypair.secret, - leaf_position: wallet.money_merkle_tree.mark().unwrap(), - }; - - debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); - wallet.unspent_money_coins.push(owncoin.clone()); - found_owncoins.push(owncoin); - } - - Ok(found_owncoins) + wallet.process_inputs(&inputs, holder); + Ok(wallet.process_outputs(&outputs, holder)) } } diff --git a/src/contract/test-harness/src/money_pow_reward.rs b/src/contract/test-harness/src/money_pow_reward.rs index f55918e5c..9e6e2506a 100644 --- a/src/contract/test-harness/src/money_pow_reward.rs +++ b/src/contract/test-harness/src/money_pow_reward.rs @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +use std::slice; + use darkfi::{ blockchain::{BlockInfo, BlockchainOverlay, Header}, tx::{ContractCallLeaf, Transaction, TransactionBuilder}, @@ -23,15 +25,15 @@ use darkfi::{ Result, }; use darkfi_money_contract::{ - client::{pow_reward_v1::PoWRewardCallBuilder, MoneyNote, OwnCoin}, + client::{pow_reward_v1::PoWRewardCallBuilder, OwnCoin}, model::MoneyPoWRewardParamsV1, MoneyFunction, MONEY_CONTRACT_ZKAS_MINT_NS_V1, }; use darkfi_sdk::{ - crypto::{contract_id::MONEY_CONTRACT_ID, MerkleNode, MerkleTree}, + crypto::{contract_id::MONEY_CONTRACT_ID, MerkleTree}, ContractCall, }; -use darkfi_serial::AsyncEncodable; +use darkfi_serial::Encodable; use tracing::info; use super::{Holder, TestHarness}; @@ -39,7 +41,8 @@ use super::{Holder, TestHarness}; impl TestHarness { /// Create a `Money::PoWReward` transaction for a given [`Holder`]. /// - /// Optionally takes a specific reward recipient and a nonstandard reward value. + /// Optionally takes a specific reward recipient and a nonstandard + /// reward value. /// Returns the created [`Transaction`] and [`MoneyPoWRewardParamsV1`]. async fn pow_reward( &mut self, @@ -48,7 +51,7 @@ impl TestHarness { reward: Option, fees: Option, ) -> Result<(Transaction, MoneyPoWRewardParamsV1)> { - let wallet = self.holders.get(holder).unwrap(); + let wallet = self.wallet(holder); let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap(); @@ -56,11 +59,7 @@ impl TestHarness { let last_block = wallet.validator.read().await.blockchain.last_block()?; // If there's a set reward recipient, use it, otherwise reward the holder - let recipient = if let Some(holder) = recipient { - Some(self.holders.get(holder).unwrap().keypair.public) - } else { - None - }; + let recipient = recipient.map(|holder| self.wallet(holder).keypair.public); // If there's fees paid, use them, otherwise set to zero let fees = fees.unwrap_or_default(); @@ -84,7 +83,7 @@ impl TestHarness { // Encode the transaction let mut data = vec![MoneyFunction::PoWRewardV1 as u8]; - debris.params.encode_async(&mut data).await?; + debris.params.encode(&mut data)?; let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?; @@ -95,7 +94,7 @@ impl TestHarness { Ok((tx, debris.params)) } - /// Generate and add an empty block to the given [`Holder`]s blockchains. + /// Generate and add an empty block to the given [`Holder`]'s blockchains. /// The `miner` holder will produce the block and receive the reward. /// /// Returns any found [`OwnCoin`]s. @@ -109,7 +108,7 @@ impl TestHarness { let (tx, params) = self.pow_reward(miner, None, None, None).await?; // Fetch the last block in the blockchain - let wallet = self.holders.get(miner).unwrap(); + let wallet = self.wallet(miner); let validator = wallet.validator.read().await; let previous = validator.blockchain.last_block()?; @@ -141,33 +140,19 @@ impl TestHarness { ) .await?; drop(validator); + let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&[])?; block.header.state_root = overlay.lock().unwrap().contracts.update_state_monotree(&diff)?; // Attach signature block.sign(&wallet.keypair.secret); - // For all holders, append the block + // For all holders, append the block and process the reward output let mut found_owncoins = vec![]; for holder in holders { - let wallet = self.holders.get_mut(holder).unwrap(); + let wallet = self.wallet_mut(holder); wallet.validator.write().await.add_test_blocks(&[block.clone()]).await?; - wallet.money_merkle_tree.append(MerkleNode::from(params.output.coin.inner())); - - // Attempt to decrypt the note to see if this is a coin for the holder - let Ok(note) = params.output.note.decrypt::(&wallet.keypair.secret) else { - continue - }; - - let owncoin = OwnCoin { - coin: params.output.coin, - note: note.clone(), - secret: wallet.keypair.secret, - leaf_position: wallet.money_merkle_tree.mark().unwrap(), - }; - - wallet.unspent_money_coins.push(owncoin.clone()); - found_owncoins.push(owncoin); + found_owncoins.extend(wallet.process_outputs(slice::from_ref(¶ms.output), holder)); } Ok(found_owncoins) diff --git a/src/contract/test-harness/src/money_token.rs b/src/contract/test-harness/src/money_token.rs index eb19661f5..097158ef5 100644 --- a/src/contract/test-harness/src/money_token.rs +++ b/src/contract/test-harness/src/money_token.rs @@ -39,7 +39,7 @@ use darkfi_sdk::{ pasta::pallas, ContractCall, }; -use darkfi_serial::AsyncEncodable; +use darkfi_serial::Encodable; use rand::rngs::OsRng; use tracing::debug; @@ -63,9 +63,9 @@ impl TestHarness { MoneyAuthTokenMintParamsV1, Option, )> { - let wallet = self.holders.get(holder).unwrap(); + let wallet = self.wallet(holder); let mint_authority = wallet.token_mint_authority; - let rcpt = self.holders.get(recipient).unwrap().keypair.public; + let rcpt = self.wallet(recipient).keypair.public; let (token_mint_pk, token_mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1).unwrap(); @@ -109,7 +109,7 @@ impl TestHarness { }; let auth_debris = builder.build()?; let mut data = vec![MoneyFunction::AuthTokenMintV1 as u8]; - auth_debris.params.encode_async(&mut data).await?; + auth_debris.params.encode(&mut data)?; let auth_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; // Create the minting call @@ -121,7 +121,7 @@ impl TestHarness { }; let mint_debris = builder.build()?; let mut data = vec![MoneyFunction::TokenMintV1 as u8]; - mint_debris.params.encode_async(&mut data).await?; + mint_debris.params.encode(&mut data)?; let mint_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; // Create the TransactionBuilder containing above calls @@ -180,35 +180,17 @@ impl TestHarness { block_height: u32, append: bool, ) -> Result> { - let wallet = self.holders.get_mut(holder).unwrap(); + let wallet = self.wallet_mut(holder); - // Execute the transaction wallet.add_transaction("money::token_mint", tx, block_height).await?; - // Iterate over all inputs to mark any spent coins - if let Some(ref fee_params) = fee_params { - if append { - if let Some(spent_coin) = wallet - .unspent_money_coins - .iter() - .find(|x| x.nullifier() == fee_params.input.nullifier) - .cloned() - { - debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); - wallet - .unspent_money_coins - .retain(|x| x.nullifier() != fee_params.input.nullifier); - wallet.spent_money_coins.push(spent_coin.clone()); - } - } - } - let mut found_owncoins = vec![]; - if append { - wallet.money_merkle_tree.append(MerkleNode::from(mint_params.coin.inner())); + // Process the fee input (mark spent) + found_owncoins.extend(wallet.process_fee(fee_params, holder)); - // Attempt to decrypt the encrypted note of the minted token + // Process the minted coin output + wallet.money_merkle_tree.append(MerkleNode::from(mint_params.coin.inner())); if let Ok(note) = auth_params.enc_note.decrypt::(&wallet.keypair.secret) { let owncoin = OwnCoin { coin: mint_params.coin, @@ -216,30 +198,9 @@ impl TestHarness { secret: wallet.keypair.secret, leaf_position: wallet.money_merkle_tree.mark().unwrap(), }; - debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); wallet.unspent_money_coins.push(owncoin.clone()); found_owncoins.push(owncoin); - }; - - if let Some(ref fee_params) = fee_params { - wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner())); - - // Attempt to decrypt the encrypted note in the fee output - if let Ok(note) = - fee_params.output.note.decrypt::(&wallet.keypair.secret) - { - let owncoin = OwnCoin { - coin: fee_params.output.coin, - note: note.clone(), - secret: wallet.keypair.secret, - leaf_position: wallet.money_merkle_tree.mark().unwrap(), - }; - - debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); - wallet.unspent_money_coins.push(owncoin.clone()); - found_owncoins.push(owncoin); - } } } @@ -252,7 +213,7 @@ impl TestHarness { holder: &Holder, block_height: u32, ) -> Result<(Transaction, MoneyAuthTokenFreezeParamsV1, Option)> { - let wallet = self.holders.get(holder).unwrap(); + let wallet = self.wallet(holder); let mint_authority = wallet.token_mint_authority; let (auth_mint_pk, auth_mint_zkbin) = @@ -282,7 +243,7 @@ impl TestHarness { }; let freeze_debris = builder.build()?; let mut data = vec![MoneyFunction::AuthTokenFreezeV1 as u8]; - freeze_debris.params.encode_async(&mut data).await?; + freeze_debris.params.encode(&mut data)?; let freeze_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; // Create the TransactionBuilder containing the above call @@ -332,53 +293,14 @@ impl TestHarness { block_height: u32, append: bool, ) -> Result> { - let wallet = self.holders.get_mut(holder).unwrap(); + let wallet = self.wallet_mut(holder); - // Execute the transaction wallet.add_transaction("money::token_freeze", tx, block_height).await?; - let mut found_owncoins = vec![]; - if let Some(ref fee_params) = fee_params { - if append { - let nullifier = fee_params.input.nullifier.inner(); - wallet - .money_null_smt - .insert_batch(vec![(nullifier, nullifier)]) - .expect("smt.insert_batch()"); - - if let Some(spent_coin) = wallet - .unspent_money_coins - .iter() - .find(|x| x.nullifier() == fee_params.input.nullifier) - .cloned() - { - debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); - wallet - .unspent_money_coins - .retain(|x| x.nullifier() != fee_params.input.nullifier); - wallet.spent_money_coins.push(spent_coin.clone()); - } - - wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner())); - - // Attempt to decrypt the encrypted note - if let Ok(note) = - fee_params.output.note.decrypt::(&wallet.keypair.secret) - { - let owncoin = OwnCoin { - coin: fee_params.output.coin, - note: note.clone(), - secret: wallet.keypair.secret, - leaf_position: wallet.money_merkle_tree.mark().unwrap(), - }; - - debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); - wallet.unspent_money_coins.push(owncoin.clone()); - found_owncoins.push(owncoin); - } - } + if !append { + return Ok(vec![]); } - Ok(found_owncoins) + Ok(wallet.process_fee(fee_params, holder)) } } diff --git a/src/contract/test-harness/src/money_transfer.rs b/src/contract/test-harness/src/money_transfer.rs index 57c75e84e..091e9c424 100644 --- a/src/contract/test-harness/src/money_transfer.rs +++ b/src/contract/test-harness/src/money_transfer.rs @@ -21,16 +21,12 @@ use darkfi::{ Result, }; use darkfi_money_contract::{ - client::{transfer_v1::make_transfer_call, MoneyNote, OwnCoin}, + client::{transfer_v1::make_transfer_call, OwnCoin}, model::{MoneyFeeParamsV1, MoneyTransferParamsV1, TokenId}, MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1, }; -use darkfi_sdk::{ - crypto::{contract_id::MONEY_CONTRACT_ID, MerkleNode}, - ContractCall, -}; -use darkfi_serial::AsyncEncodable; -use tracing::debug; +use darkfi_sdk::{crypto::contract_id::MONEY_CONTRACT_ID, ContractCall}; +use darkfi_serial::Encodable; use super::{Holder, TestHarness}; @@ -48,8 +44,8 @@ impl TestHarness { half_split: bool, ) -> Result<(Transaction, (MoneyTransferParamsV1, Option), Vec)> { - let wallet = self.holders.get(holder).unwrap(); - let rcpt = self.holders.get(recipient).unwrap().keypair.public; + let wallet = self.wallet(holder); + let rcpt = self.wallet(recipient).keypair.public; let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap(); let (burn_pk, burn_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap(); @@ -73,20 +69,21 @@ impl TestHarness { // Encode the call let mut data = vec![MoneyFunction::TransferV1 as u8]; - params.encode_async(&mut data).await?; + params.encode(&mut data)?; let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; // Create the TransactionBuilder containing the `Transfer` call let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs: secrets.proofs }, vec![])?; - // If we have tx fees enabled, we first have to execute the fee-less tx to gather its - // used gas, and then we feed it into the fee-creating function. - // We also tell it about any spent coins so we don't accidentally reuse them in the - // fee call. - // TODO: We have to build a proper coin selection algorithm so that we can utilize - // the Money::Transfer to merge any coins which would give us a coin with enough - // value for paying the transaction fee. + // If we have tx fees enabled, we first have to execute the fee-less + // transaction to gather its used gas, and then we feed it into the + // fee-creating function. + // We also tell it about any spent coins so we don't accidentally + // reuse them in the fee call. + // TODO: We have to build a proper coin selection algorithm so that we + // can utilize the Money::Transfer to merge any coins which would give + // us a coin with enough value for paying the transaction fee. let mut fee_params = None; let mut fee_signature_secrets = None; if self.verify_fees { @@ -118,7 +115,7 @@ impl TestHarness { /// Execute a `Money::Transfer` transaction for a given [`Holder`]. /// - /// Returns any found [`OwnCoin`]s. + /// Returns any found [`OwnCoin`]s pub async fn execute_transfer_tx( &mut self, holder: &Holder, @@ -128,95 +125,17 @@ impl TestHarness { block_height: u32, append: bool, ) -> Result> { - let wallet = self.holders.get_mut(holder).unwrap(); + let wallet = self.wallet_mut(holder); - // Execute the transaction wallet.add_transaction("money::transfer", tx, block_height).await?; - // Iterate over call inputs to mark any spent coins - let nullifiers = - call_params.inputs.iter().map(|i| i.nullifier.inner()).map(|l| (l, l)).collect(); - wallet.money_null_smt.insert_batch(nullifiers).expect("smt.insert_batch()"); + // Always insert nullifiers into SMT (needed for state consistency) + wallet.process_inputs(&call_params.inputs, holder); let mut found_owncoins = vec![]; if append { - for input in &call_params.inputs { - if let Some(spent_coin) = wallet - .unspent_money_coins - .iter() - .find(|x| x.nullifier() == input.nullifier) - .cloned() - { - debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); - wallet.unspent_money_coins.retain(|x| x.nullifier() != input.nullifier); - wallet.spent_money_coins.push(spent_coin.clone()); - } - } - - // Iterate over call outputs to find any new OwnCoins - for output in &call_params.outputs { - wallet.money_merkle_tree.append(MerkleNode::from(output.coin.inner())); - - // Attempt to decrypt the output note to see if this is a coin for the holder. - let Ok(note) = output.note.decrypt::(&wallet.keypair.secret) else { - continue - }; - - let owncoin = OwnCoin { - coin: output.coin, - note: note.clone(), - secret: wallet.keypair.secret, - leaf_position: wallet.money_merkle_tree.mark().unwrap(), - }; - - debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); - wallet.unspent_money_coins.push(owncoin.clone()); - found_owncoins.push(owncoin); - } - } - - // Handle fee call - if let Some(ref fee_params) = fee_params { - // Process call input to mark any spent coins - let nullifier = fee_params.input.nullifier.inner(); - wallet - .money_null_smt - .insert_batch(vec![(nullifier, nullifier)]) - .expect("smt.insert_batch()"); - - if append { - if let Some(spent_coin) = wallet - .unspent_money_coins - .iter() - .find(|x| x.nullifier() == fee_params.input.nullifier) - .cloned() - { - debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder); - wallet - .unspent_money_coins - .retain(|x| x.nullifier() != fee_params.input.nullifier); - wallet.spent_money_coins.push(spent_coin.clone()); - } - - // Process call output to find any new OwnCoins - wallet.money_merkle_tree.append(MerkleNode::from(fee_params.output.coin.inner())); - - // Attempt to decrypt the output note to see if this is a coin for the holder. - if let Ok(note) = - fee_params.output.note.decrypt::(&wallet.keypair.secret) - { - let owncoin = OwnCoin { - coin: fee_params.output.coin, - note: note.clone(), - secret: wallet.keypair.secret, - leaf_position: wallet.money_merkle_tree.mark().unwrap(), - }; - - debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder); - wallet.unspent_money_coins.push(owncoin.clone()); - found_owncoins.push(owncoin); - }; - } + found_owncoins.extend(wallet.process_outputs(&call_params.outputs, holder)); + found_owncoins.extend(wallet.process_fee(fee_params, holder)); } Ok(found_owncoins)